2 * file-mmap-posix.c: File mmap internal calls
7 * Copyright 2014 Xamarin Inc (http://www.xamarin.com)
20 #ifdef HAVE_SYS_STAT_H
23 #ifdef HAVE_SYS_TYPES_H
24 #include <sys/types.h>
33 #include <mono/metadata/object.h>
34 #include <mono/metadata/file-io.h>
35 #include <mono/metadata/file-mmap.h>
36 #include <mono/utils/atomic.h>
37 #include <mono/utils/mono-memory-model.h>
38 #include <mono/utils/mono-mmap.h>
55 BAD_CAPACITY_FOR_FILE_BACKED
= 1,
56 CAPACITY_SMALLER_THAN_FILE_SIZE
,
61 CAPACITY_MUST_BE_POSITIVE
,
67 FILE_MODE_CREATE_NEW
= 1,
70 FILE_MODE_OPEN_OR_CREATE
= 4,
71 FILE_MODE_TRUNCATE
= 5,
76 MMAP_FILE_ACCESS_READ_WRITE
= 0,
77 MMAP_FILE_ACCESS_READ
= 1,
78 MMAP_FILE_ACCESS_WRITE
= 2,
79 MMAP_FILE_ACCESS_COPY_ON_WRITE
= 3,
80 MMAP_FILE_ACCESS_READ_EXECUTE
= 4,
81 MMAP_FILE_ACCESS_READ_WRITE_EXECUTE
= 5,
85 #define DEFAULT_FILEMODE DEFFILEMODE
87 #define DEFAULT_FILEMODE 0666
90 static int mmap_init_state
;
91 static mono_mutex_t named_regions_mutex
;
92 static GHashTable
*named_regions
;
96 align_up_to_page_size (gint64 size
)
98 gint64 page_size
= mono_pagesize ();
99 return (size
+ page_size
- 1) & ~(page_size
- 1);
103 align_down_to_page_size (gint64 size
)
105 gint64 page_size
= mono_pagesize ();
106 return size
& ~(page_size
- 1);
110 file_mmap_init (void)
113 switch (mmap_init_state
) {
115 if (InterlockedCompareExchange (&mmap_init_state
, 1, 0) != 0)
117 named_regions
= g_hash_table_new_full (g_str_hash
, g_str_equal
, NULL
, NULL
);
118 mono_mutex_init (&named_regions_mutex
);
120 mono_atomic_store_release (&mmap_init_state
, 2);
125 g_usleep (1000); /* Been init'd by other threads, this is very rare. */
126 } while (mmap_init_state
!= 2);
131 g_error ("Invalid init state %d", mmap_init_state
);
136 named_regions_lock (void)
139 mono_mutex_lock (&named_regions_mutex
);
143 named_regions_unlock (void)
145 mono_mutex_unlock (&named_regions_mutex
);
150 file_mode_to_unix (int mode
)
153 case FILE_MODE_CREATE_NEW
:
154 return O_CREAT
| O_EXCL
;
155 case FILE_MODE_CREATE
:
156 return O_CREAT
| O_TRUNC
;
159 case FILE_MODE_OPEN_OR_CREATE
:
161 case FILE_MODE_TRUNCATE
:
163 case FILE_MODE_APPEND
:
166 g_error ("unknown FileMode %d", mode
);
171 access_mode_to_unix (int access
)
174 case MMAP_FILE_ACCESS_READ_WRITE
:
175 case MMAP_FILE_ACCESS_COPY_ON_WRITE
:
176 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE
:
178 case MMAP_FILE_ACCESS_READ
:
179 case MMAP_FILE_ACCESS_READ_EXECUTE
:
181 case MMAP_FILE_ACCESS_WRITE
:
184 g_error ("unknown MemoryMappedFileAccess %d", access
);
189 acess_to_mmap_flags (int access
)
192 case MMAP_FILE_ACCESS_READ_WRITE
:
193 return MONO_MMAP_WRITE
| MONO_MMAP_READ
| MONO_MMAP_SHARED
;
195 case MMAP_FILE_ACCESS_WRITE
:
196 return MONO_MMAP_WRITE
| MONO_MMAP_SHARED
;
198 case MMAP_FILE_ACCESS_COPY_ON_WRITE
:
199 return MONO_MMAP_WRITE
| MONO_MMAP_READ
| MONO_MMAP_PRIVATE
;
201 case MMAP_FILE_ACCESS_READ_EXECUTE
:
202 return MONO_MMAP_EXEC
| MONO_MMAP_PRIVATE
| MONO_MMAP_SHARED
;
204 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE
:
205 return MONO_MMAP_WRITE
| MONO_MMAP_READ
| MONO_MMAP_EXEC
| MONO_MMAP_SHARED
;
207 case MMAP_FILE_ACCESS_READ
:
208 return MONO_MMAP_READ
| MONO_MMAP_SHARED
;
210 g_error ("unknown MemoryMappedFileAccess %d", access
);
215 This allow us to special case zero size files that can be arbitrarily mapped.
218 is_special_zero_size_file (struct stat
*buf
)
220 return buf
->st_size
== 0 && (buf
->st_mode
& (S_IFCHR
| S_IFBLK
| S_IFIFO
| S_IFSOCK
)) != 0;
224 XXX implement options
227 open_file_map (MonoString
*path
, int input_fd
, int mode
, gint64
*capacity
, int access
, int options
, int *error
)
230 char *c_path
= path
? mono_string_to_utf8 (path
) : NULL
;
231 MmapHandle
*handle
= NULL
;
235 result
= stat (c_path
, &buf
);
237 result
= fstat (input_fd
, &buf
);
239 if (mode
== FILE_MODE_TRUNCATE
|| mode
== FILE_MODE_APPEND
|| mode
== FILE_MODE_OPEN
) {
240 if (result
== -1) { //XXX translate errno?
241 *error
= FILE_NOT_FOUND
;
246 if (mode
== FILE_MODE_CREATE_NEW
&& result
== 0) {
247 *error
= FILE_ALREADY_EXISTS
;
252 if (*capacity
== 0) {
254 * Special files such as FIFOs, sockets, and devices can have a size of 0. Specifying a capacity for these
255 * also makes little sense, so don't do the check if th file is one of these.
257 if (buf
.st_size
== 0 && !is_special_zero_size_file (&buf
)) {
258 *error
= CAPACITY_SMALLER_THAN_FILE_SIZE
;
261 *capacity
= buf
.st_size
;
262 } else if (*capacity
< buf
.st_size
) {
263 *error
= CAPACITY_SMALLER_THAN_FILE_SIZE
;
267 if (mode
== FILE_MODE_CREATE_NEW
&& *capacity
== 0) {
268 *error
= CAPACITY_SMALLER_THAN_FILE_SIZE
;
273 if (path
) //FIXME use io portability?
274 fd
= open (c_path
, file_mode_to_unix (mode
) | access_mode_to_unix (access
), DEFAULT_FILEMODE
);
278 if (fd
== -1) { //XXX translate errno?
279 *error
= COULD_NOT_OPEN
;
283 if (result
!= 0 || *capacity
> buf
.st_size
) {
284 int unused G_GNUC_UNUSED
= ftruncate (fd
, (off_t
)*capacity
);
287 handle
= g_new0 (MmapHandle
, 1);
288 handle
->ref_count
= 1;
289 handle
->capacity
= *capacity
;
294 return (void*)handle
;
297 #define MONO_ANON_FILE_TEMPLATE "/mono.anonmap.XXXXXXXXX"
299 open_memory_map (MonoString
*mapName
, int mode
, gint64
*capacity
, int access
, int options
, int *error
)
303 if (*capacity
<= 1) {
304 *error
= CAPACITY_MUST_BE_POSITIVE
;
308 if (!(mode
== FILE_MODE_CREATE_NEW
|| mode
== FILE_MODE_OPEN_OR_CREATE
|| mode
== FILE_MODE_OPEN
)) {
309 *error
= INVALID_FILE_MODE
;
313 c_mapName
= mono_string_to_utf8 (mapName
);
315 named_regions_lock ();
316 handle
= (MmapHandle
*)g_hash_table_lookup (named_regions
, c_mapName
);
318 if (mode
== FILE_MODE_CREATE_NEW
) {
319 *error
= FILE_ALREADY_EXISTS
;
324 //XXX should we ftruncate if the file is smaller than capacity?
329 int unused G_GNUC_UNUSED
, alloc_size
;
331 if (mode
== FILE_MODE_OPEN
) {
332 *error
= FILE_NOT_FOUND
;
335 *capacity
= align_up_to_page_size (*capacity
);
337 tmp_dir
= g_get_tmp_dir ();
338 alloc_size
= strlen (tmp_dir
) + strlen (MONO_ANON_FILE_TEMPLATE
) + 1;
339 if (alloc_size
> 1024) {//rather fail that stack overflow
340 *error
= COULD_NOT_MAP_MEMORY
;
343 file_name
= alloca (alloc_size
);
344 strcpy (file_name
, tmp_dir
);
345 strcat (file_name
, MONO_ANON_FILE_TEMPLATE
);
347 fd
= mkstemp (file_name
);
349 *error
= COULD_NOT_MAP_MEMORY
;
354 unused
= ftruncate (fd
, (off_t
)*capacity
);
356 handle
= g_new0 (MmapHandle
, 1);
357 handle
->ref_count
= 1;
358 handle
->capacity
= *capacity
;
360 handle
->name
= g_strdup (c_mapName
);
362 g_hash_table_insert (named_regions
, handle
->name
, handle
);
367 named_regions_unlock ();
375 mono_mmap_open_file (MonoString
*path
, int mode
, MonoString
*mapName
, gint64
*capacity
, int access
, int options
, int *error
)
377 g_assert (path
|| mapName
);
380 return open_file_map (path
, -1, mode
, capacity
, access
, options
, error
);
384 char *c_mapName
= mono_string_to_utf8 (mapName
);
386 named_regions_lock ();
387 handle
= (MmapHandle
*)g_hash_table_lookup (named_regions
, c_mapName
);
389 *error
= FILE_ALREADY_EXISTS
;
392 handle
= open_file_map (path
, -1, mode
, capacity
, access
, options
, error
);
394 handle
->name
= g_strdup (c_mapName
);
395 g_hash_table_insert (named_regions
, handle
->name
, handle
);
398 named_regions_unlock ();
404 return open_memory_map (mapName
, mode
, capacity
, access
, options
, error
);
408 mono_mmap_open_handle (void *input_fd
, MonoString
*mapName
, gint64
*capacity
, int access
, int options
, int *error
)
412 handle
= open_file_map (NULL
, GPOINTER_TO_INT (input_fd
), FILE_MODE_OPEN
, capacity
, access
, options
, error
);
414 char *c_mapName
= mono_string_to_utf8 (mapName
);
416 named_regions_lock ();
417 handle
= (MmapHandle
*)g_hash_table_lookup (named_regions
, c_mapName
);
419 *error
= FILE_ALREADY_EXISTS
;
422 //XXX we're exploiting wapi HANDLE == FD equivalence. THIS IS FRAGILE, create a _wapi_handle_to_fd call
423 handle
= open_file_map (NULL
, GPOINTER_TO_INT (input_fd
), FILE_MODE_OPEN
, capacity
, access
, options
, error
);
424 handle
->name
= g_strdup (c_mapName
);
425 g_hash_table_insert (named_regions
, handle
->name
, handle
);
427 named_regions_unlock ();
435 mono_mmap_close (void *mmap_handle
)
437 MmapHandle
*handle
= mmap_handle
;
439 named_regions_lock ();
441 if (handle
->ref_count
== 0) {
443 g_hash_table_remove (named_regions
, handle
->name
);
445 g_free (handle
->name
);
449 named_regions_unlock ();
453 mono_mmap_configure_inheritability (void *mmap_handle
, gboolean inheritability
)
455 MmapHandle
*h
= mmap_handle
;
459 flags
= fcntl (fd
, F_GETFD
, 0);
461 flags
&= ~FD_CLOEXEC
;
464 fcntl (fd
, F_SETFD
, flags
);
468 mono_mmap_flush (void *mmap_handle
)
470 MmapInstance
*h
= mmap_handle
;
473 msync (h
->address
, h
->length
, MS_SYNC
);
477 mono_mmap_map (void *handle
, gint64 offset
, gint64
*size
, int access
, void **mmap_handle
, void **base_address
)
479 gint64 mmap_offset
= 0;
480 MmapHandle
*fh
= handle
;
481 MmapInstance res
= { 0 };
482 size_t eff_size
= *size
;
483 struct stat buf
= { 0 };
484 fstat (fh
->fd
, &buf
); //FIXME error handling
486 if (offset
> buf
.st_size
|| ((eff_size
+ offset
) > buf
.st_size
&& !is_special_zero_size_file (&buf
)))
489 * We use the file size if one of the following conditions is true:
490 * -input size is zero
491 * -input size is bigger than the file and the file is not a magical zero size file such as /dev/mem.
494 eff_size
= align_up_to_page_size (buf
.st_size
) - offset
;
497 mmap_offset
= align_down_to_page_size (offset
);
498 eff_size
+= (offset
- mmap_offset
);
499 //FIXME translate some interesting errno values
500 res
.address
= mono_file_map ((size_t)eff_size
, acess_to_mmap_flags (access
), fh
->fd
, mmap_offset
, &res
.free_handle
);
501 res
.length
= eff_size
;
504 *mmap_handle
= g_memdup (&res
, sizeof (MmapInstance
));
505 *base_address
= (char*)res
.address
+ (offset
- mmap_offset
);
511 *base_address
= NULL
;
512 return COULD_NOT_MAP_MEMORY
;
516 mono_mmap_unmap (void *mmap_handle
)
519 MmapInstance
*h
= mmap_handle
;
521 res
= mono_file_unmap (h
->address
, h
->free_handle
);