2008-10-28 Zoltan Varga <vargaz@gmail.com>
[mono.git] / mono / utils / mono-mmap.c
1 #include "config.h"
2
3 #ifdef PLATFORM_WIN32
4 #include <windows.h>
5 #include <io.h>
6 #else
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <sys/mman.h>
10 #include <fcntl.h>
11 #include <string.h>
12 #include <unistd.h>
13 #include <stdlib.h>
14 #include <signal.h>
15 #include <errno.h>
16 #endif
17
18 #include "mono-mmap.h"
19 #include "mono-proclib.h"
20
21 #ifndef MAP_ANONYMOUS
22 #define MAP_ANONYMOUS MAP_ANON
23 #endif
24
25 #ifndef MAP_32BIT
26 #define MAP_32BIT 0
27 #endif
28
29 typedef struct {
30         int size;
31         int pid;
32         int reserved;
33         short stats_start;
34         short stats_end;
35 } SAreaHeader;
36
37 static void* malloced_shared_area = NULL;
38
39 static void*
40 malloc_shared_area (int pid)
41 {
42         int size = mono_pagesize ();
43         SAreaHeader *sarea = g_malloc0 (size);
44         sarea->size = size;
45         sarea->pid = pid;
46         sarea->stats_start = sizeof (SAreaHeader);
47         sarea->stats_end = sizeof (SAreaHeader);
48
49         return sarea;
50 }
51
52 #ifdef PLATFORM_WIN32
53
54 int
55 mono_pagesize (void)
56 {
57         SYSTEM_INFO info;
58         static int saved_pagesize = 0;
59         if (saved_pagesize)
60                 return saved_pagesize;
61         GetSystemInfo (&info);
62         saved_pagesize = info.dwAllocationGranularity;
63         return saved_pagesize;
64 }
65
66 static int
67 prot_from_flags (int flags)
68 {
69         int prot = flags & (MONO_MMAP_READ|MONO_MMAP_WRITE|MONO_MMAP_EXEC);
70         switch (prot) {
71         case 0: prot = PAGE_NOACCESS; break;
72         case MONO_MMAP_READ: prot = PAGE_READONLY; break;
73         case MONO_MMAP_READ|MONO_MMAP_EXEC: prot = PAGE_EXECUTE_READ; break;
74         case MONO_MMAP_READ|MONO_MMAP_WRITE: prot = PAGE_READWRITE; break;
75         case MONO_MMAP_READ|MONO_MMAP_WRITE|MONO_MMAP_EXEC: prot = PAGE_EXECUTE_READWRITE; break;
76         case MONO_MMAP_WRITE: prot = PAGE_READWRITE; break;
77         case MONO_MMAP_WRITE|MONO_MMAP_EXEC: prot = PAGE_EXECUTE_READWRITE; break;
78         case MONO_MMAP_EXEC: prot = PAGE_EXECUTE; break;
79         default:
80                 g_assert_not_reached ();
81         }
82         return prot;
83 }
84
85 void*
86 mono_valloc (void *addr, size_t length, int flags)
87 {
88         void *ptr;
89         int mflags = MEM_COMMIT;
90         int prot = prot_from_flags (flags);
91         /* translate the flags */
92
93         ptr = VirtualAlloc (addr, length, mflags, prot);
94         return ptr;
95 }
96
97 int
98 mono_vfree (void *addr, size_t length)
99 {
100         int res = VirtualFree (addr, 0, MEM_RELEASE);
101
102         g_assert (res);
103
104         return 0;
105 }
106
107 void*
108 mono_file_map (size_t length, int flags, int fd, guint64 offset, void **ret_handle)
109 {
110         void *ptr;
111         int mflags = 0;
112         HANDLE file, mapping;
113         int prot = prot_from_flags (flags);
114         /* translate the flags */
115         /*if (flags & MONO_MMAP_PRIVATE)
116                 mflags |= MAP_PRIVATE;
117         if (flags & MONO_MMAP_SHARED)
118                 mflags |= MAP_SHARED;
119         if (flags & MONO_MMAP_ANON)
120                 mflags |= MAP_ANONYMOUS;
121         if (flags & MONO_MMAP_FIXED)
122                 mflags |= MAP_FIXED;
123         if (flags & MONO_MMAP_32BIT)
124                 mflags |= MAP_32BIT;*/
125
126         mflags = FILE_MAP_READ;
127         if (flags & MONO_MMAP_WRITE)
128                 mflags = FILE_MAP_COPY;
129
130         file = (HANDLE) _get_osfhandle (fd);
131         mapping = CreateFileMapping (file, NULL, prot, 0, 0, NULL);
132         if (mapping == NULL)
133                 return NULL;
134         ptr = MapViewOfFile (mapping, mflags, 0, offset, length);
135         if (ptr == NULL) {
136                 CloseHandle (mapping);
137                 return NULL;
138         }
139         *ret_handle = (void*)mapping;
140         return ptr;
141 }
142
143 int
144 mono_file_unmap (void *addr, void *handle)
145 {
146         UnmapViewOfFile (addr);
147         CloseHandle ((HANDLE)handle);
148         return 0;
149 }
150
151 int
152 mono_mprotect (void *addr, size_t length, int flags)
153 {
154         DWORD oldprot;
155         int prot = prot_from_flags (flags);
156
157         if (flags & MONO_MMAP_DISCARD) {
158                 VirtualFree (addr, length, MEM_DECOMMIT);
159                 VirtualAlloc (addr, length, MEM_COMMIT, prot);
160                 return 0;
161         }
162         return VirtualProtect (addr, length, prot, &oldprot) == 0;
163 }
164
165 void*
166 mono_shared_area (void)
167 {
168         /* get the pid here */
169         return malloc_shared_area (0);
170 }
171
172 void
173 mono_shared_area_remove (void)
174 {
175         if (malloced_shared_area)
176                 g_free (malloced_shared_area);
177 }
178
179 void*
180 mono_shared_area_for_pid (void *pid)
181 {
182         return NULL;
183 }
184
185 void
186 mono_shared_area_unload (void *area)
187 {
188 }
189
190 int
191 mono_shared_area_instances (void **array, int count)
192 {
193         return 0;
194 }
195
196 #elif defined(HAVE_MMAP)
197
198 /**
199  * mono_pagesize:
200  * Get the page size in use on the system. Addresses and sizes in the
201  * mono_mmap(), mono_munmap() and mono_mprotect() calls must be pagesize
202  * aligned.
203  *
204  * Returns: the page size in bytes.
205  */
206 int
207 mono_pagesize (void)
208 {
209         static int saved_pagesize = 0;
210         if (saved_pagesize)
211                 return saved_pagesize;
212         saved_pagesize = getpagesize ();
213         return saved_pagesize;
214 }
215
216 static int
217 prot_from_flags (int flags)
218 {
219         int prot = PROT_NONE;
220         /* translate the protection bits */
221         if (flags & MONO_MMAP_READ)
222                 prot |= PROT_READ;
223         if (flags & MONO_MMAP_WRITE)
224                 prot |= PROT_WRITE;
225         if (flags & MONO_MMAP_EXEC)
226                 prot |= PROT_EXEC;
227         return prot;
228 }
229
230 /**
231  * mono_valloc:
232  * @addr: memory address
233  * @length: memory area size
234  * @flags: protection flags
235  *
236  * Allocates @length bytes of virtual memory with the @flags
237  * protection. @addr can be a preferred memory address or a
238  * mandatory one if MONO_MMAP_FIXED is set in @flags.
239  * @addr must be pagesize aligned and can be NULL.
240  * @length must be a multiple of pagesize.
241  *
242  * Returns: NULL on failure, the address of the memory area otherwise
243  */
244 void*
245 mono_valloc (void *addr, size_t length, int flags)
246 {
247         void *ptr;
248         int mflags = 0;
249         int prot = prot_from_flags (flags);
250         /* translate the flags */
251         if (flags & MONO_MMAP_FIXED)
252                 mflags |= MAP_FIXED;
253         if (flags & MONO_MMAP_32BIT)
254                 mflags |= MAP_32BIT;
255
256         mflags |= MAP_ANONYMOUS;
257         mflags |= MAP_PRIVATE;
258
259         ptr = mmap (addr, length, prot, mflags, -1, 0);
260         if (ptr == (void*)-1) {
261                 int fd = open ("/dev/zero", O_RDONLY);
262                 if (fd != -1) {
263                         ptr = mmap (addr, length, prot, mflags, fd, 0);
264                         close (fd);
265                 }
266                 if (ptr == (void*)-1)
267                         return NULL;
268         }
269         return ptr;
270 }
271
272 /**
273  * mono_vfree:
274  * @addr: memory address returned by mono_valloc ()
275  * @length: size of memory area
276  *
277  * Remove the memory mapping at the address @addr.
278  *
279  * Returns: 0 on success.
280  */
281 int
282 mono_vfree (void *addr, size_t length)
283 {
284         return munmap (addr, length);
285 }
286
287 /**
288  * mono_file_map:
289  * @length: size of data to map
290  * @flags: protection flags
291  * @fd: file descriptor
292  * @offset: offset in the file
293  * @ret_handle: pointer to storage for returning a handle for the map
294  *
295  * Map the area of the file pointed to by the file descriptor @fd, at offset
296  * @offset and of size @length in memory according to the protection flags
297  * @flags.
298  * @offset and @length must be multiples of the page size.
299  * @ret_handle must point to a void*: this value must be used when unmapping
300  * the memory area using mono_file_unmap ().
301  *
302  */
303 void*
304 mono_file_map (size_t length, int flags, int fd, guint64 offset, void **ret_handle)
305 {
306         void *ptr;
307         int mflags = 0;
308         int prot = prot_from_flags (flags);
309         /* translate the flags */
310         if (flags & MONO_MMAP_PRIVATE)
311                 mflags |= MAP_PRIVATE;
312         if (flags & MONO_MMAP_SHARED)
313                 mflags |= MAP_SHARED;
314         if (flags & MONO_MMAP_FIXED)
315                 mflags |= MAP_FIXED;
316         if (flags & MONO_MMAP_32BIT)
317                 mflags |= MAP_32BIT;
318
319         ptr = mmap (0, length, prot, mflags, fd, offset);
320         if (ptr == (void*)-1)
321                 return NULL;
322         *ret_handle = (void*)length;
323         return ptr;
324 }
325
326 /**
327  * mono_file_unmap:
328  * @addr: memory address returned by mono_file_map ()
329  * @handle: handle of memory map
330  *
331  * Remove the memory mapping at the address @addr.
332  * @handle must be the value returned in ret_handle by mono_file_map ().
333  *
334  * Returns: 0 on success.
335  */
336 int
337 mono_file_unmap (void *addr, void *handle)
338 {
339         return munmap (addr, (size_t)handle);
340 }
341
342 /**
343  * mono_mprotect:
344  * @addr: memory address
345  * @length: size of memory area
346  * @flags: new protection flags
347  *
348  * Change the protection for the memory area at @addr for @length bytes
349  * to matche the supplied @flags.
350  * If @flags includes MON_MMAP_DISCARD the pages are discarded from memory
351  * and the area is cleared to zero.
352  * @addr must be aligned to the page size.
353  * @length must be a multiple of the page size.
354  *
355  * Returns: 0 on success.
356  */
357 int
358 mono_mprotect (void *addr, size_t length, int flags)
359 {
360         int prot = prot_from_flags (flags);
361
362         if (flags & MONO_MMAP_DISCARD) {
363                 /* on non-linux the pages are not guaranteed to be zeroed (*bsd, osx at least) */
364 #ifdef __linux__
365                 if (madvise (addr, length, MADV_DONTNEED))
366                         memset (addr, 0, length);
367 #else
368                 memset (addr, 0, length);
369                 madvise (addr, length, MADV_DONTNEED);
370                 madvise (addr, length, MADV_FREE);
371 #endif
372         }
373         return mprotect (addr, length, prot);
374 }
375
376 static int
377 mono_shared_area_instances_slow (void **array, int count, gboolean cleanup)
378 {
379         int i, j = 0;
380         int num;
381         void *data;
382         gpointer *processes = mono_process_list (&num);
383         for (i = 0; i < num; ++i) {
384                 data = mono_shared_area_for_pid (processes [i]);
385                 if (!data)
386                         continue;
387                 mono_shared_area_unload (data);
388                 if (!cleanup) {
389                         if (j < count)
390                                 array [j++] = processes [i];
391                         else
392                                 break;
393                 }
394         }
395         g_free (processes);
396         return j;
397 }
398
399 static int
400 mono_shared_area_instances_helper (void **array, int count, gboolean cleanup)
401 {
402         const char *name;
403         int i = 0;
404         int curpid = getpid ();
405         GDir *dir = g_dir_open ("/dev/shm/", 0, NULL);
406         if (!dir)
407                 return mono_shared_area_instances_slow (array, count, cleanup);
408         while ((name = g_dir_read_name (dir))) {
409                 int pid;
410                 char *nend;
411                 if (strncmp (name, "mono.", 5))
412                         continue;
413                 pid = strtol (name + 5, &nend, 10);
414                 if (pid <= 0 || nend == name + 5 || *nend)
415                         continue;
416                 if (!cleanup) {
417                         if (i < count)
418                                 array [i++] = GINT_TO_POINTER (pid);
419                         else
420                                 break;
421                 }
422                 if (curpid != pid && kill (pid, SIGCONT) == -1 && errno == ESRCH) {
423                         char buf [128];
424                         g_snprintf (buf, sizeof (buf), "/mono.%d", pid);
425                         shm_unlink (buf);
426                 }
427         }
428         g_dir_close (dir);
429         return i;
430 }
431
432 void*
433 mono_shared_area (void)
434 {
435         int fd;
436         int pid = getpid ();
437         /* we should allow the user to configure the size */
438         int size = mono_pagesize ();
439         char buf [128];
440         void *res;
441         SAreaHeader *header;
442
443         /* perform cleanup of segments left over from dead processes */
444         mono_shared_area_instances_helper (NULL, 0, TRUE);
445
446         g_snprintf (buf, sizeof (buf), "/mono.%d", pid);
447
448         fd = shm_open (buf, O_CREAT|O_EXCL|O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP);
449         if (fd == -1 && errno == EEXIST) {
450                 /* leftover */
451                 shm_unlink (buf);
452                 fd = shm_open (buf, O_CREAT|O_EXCL|O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP);
453         }
454         /* in case of failure we try to return a memory area anyway,
455          * even if it means the data can't be read by other processes
456          */
457         if (fd == -1)
458                 return malloc_shared_area (pid);
459         if (ftruncate (fd, size) != 0) {
460                 shm_unlink (buf);
461                 close (fd);
462         }
463         res = mmap (NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
464         if (res == MAP_FAILED) {
465                 shm_unlink (buf);
466                 close (fd);
467                 return malloc_shared_area (pid);
468         }
469         /* we don't need the file descriptor anymore */
470         close (fd);
471         header = res;
472         header->size = size;
473         header->pid = pid;
474         header->stats_start = sizeof (SAreaHeader);
475         header->stats_end = sizeof (SAreaHeader);
476
477         atexit (mono_shared_area_remove);
478         return res;
479 }
480
481 void
482 mono_shared_area_remove (void)
483 {
484         char buf [128];
485         g_snprintf (buf, sizeof (buf), "/mono.%d", getpid ());
486         shm_unlink (buf);
487         if (malloced_shared_area)
488                 g_free (malloced_shared_area);
489 }
490
491 void*
492 mono_shared_area_for_pid (void *pid)
493 {
494         int fd;
495         /* we should allow the user to configure the size */
496         int size = mono_pagesize ();
497         char buf [128];
498         void *res;
499
500         g_snprintf (buf, sizeof (buf), "/mono.%d", GPOINTER_TO_INT (pid));
501
502         fd = shm_open (buf, O_RDONLY, S_IRUSR|S_IRGRP);
503         if (fd == -1)
504                 return NULL;
505         res = mmap (NULL, size, PROT_READ, MAP_SHARED, fd, 0);
506         if (res == MAP_FAILED) {
507                 close (fd);
508                 return NULL;
509         }
510         /* FIXME: validate the area */
511         /* we don't need the file descriptor anymore */
512         close (fd);
513         return res;
514 }
515
516 void
517 mono_shared_area_unload  (void *area)
518 {
519         /* FIXME: currently we load only a page */
520         munmap (area, mono_pagesize ());
521 }
522
523 int
524 mono_shared_area_instances (void **array, int count)
525 {
526         return mono_shared_area_instances_helper (array, count, FALSE);
527 }
528
529 #else
530
531 /* dummy malloc-based implementation */
532 int
533 mono_pagesize (void)
534 {
535         return 4096;
536 }
537
538 void*
539 mono_valloc (void *addr, size_t length, int flags)
540 {
541         return malloc (length);
542 }
543
544 int
545 mono_vfree (void *addr, size_t length)
546 {
547         free (addr);
548         return 0;
549 }
550
551 void*
552 mono_file_map (size_t length, int flags, int fd, guint64 offset, void **ret_handle)
553 {
554         guint64 cur_offset;
555         size_t bytes_read;
556         void *ptr = malloc (length);
557         if (!ptr)
558                 return NULL;
559         cur_offset = lseek (fd, 0, SEEK_CUR);
560         if (lseek (fd, offset, SEEK_SET) != offset) {
561                 free (ptr);
562                 return NULL;
563         }
564         bytes_read = read (fd, ptr, length);
565         lseek (fd, cur_offset, SEEK_SET);
566         *ret_handle = NULL;
567         return ptr;
568 }
569
570 int
571 mono_file_unmap (void *addr, void *handle)
572 {
573         free (addr);
574         return 0;
575 }
576
577 int
578 mono_mprotect (void *addr, size_t length, int flags)
579 {
580         if (flags & MONO_MMAP_DISCARD) {
581                 memset (addr, 0, length);
582         }
583         return 0;
584 }
585
586 void*
587 mono_shared_area (void)
588 {
589         return malloc_shared_area (getpid ());
590 }
591
592 void
593 mono_shared_area_remove (void)
594 {
595         if (malloced_shared_area)
596                 g_free (malloced_shared_area);
597         malloced_shared_area = NULL;
598 }
599
600 void*
601 mono_shared_area_for_pid (void *pid)
602 {
603         return NULL;
604 }
605
606 void
607 mono_shared_area_unload (void *area)
608 {
609 }
610
611 int
612 mono_shared_area_instances (void **array, int count)
613 {
614         return 0;
615 }
616
617 #endif
618