merge from trunk revisions 58933, 58935, 58936
[mono.git] / mono / io-layer / shared.c
1 /*
2  * shared.c:  Shared memory handling, and daemon launching
3  *
4  * Author:
5  *      Dick Porter (dick@ximian.com)
6  *
7  * (C) 2002 Ximian, Inc.
8  */
9
10
11 #include <config.h>
12 #include <glib.h>
13 #include <stdio.h>
14 #include <fcntl.h>
15 #include <unistd.h>
16 #include <sys/mman.h>
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <errno.h>
20 #include <string.h>
21 #include <sys/ipc.h>
22 #include <sys/sem.h>
23 #include <sys/utsname.h>
24
25 #include <mono/io-layer/wapi.h>
26 #include <mono/io-layer/wapi-private.h>
27 #include <mono/io-layer/shared.h>
28 #include <mono/io-layer/handles-private.h>
29
30 #undef DEBUG
31
32 static guchar *_wapi_shm_file (_wapi_shm_t type)
33 {
34         static guchar file[_POSIX_PATH_MAX];
35         guchar *name = NULL, *filename, *dir, *wapi_dir;
36         gchar machine_name[256];
37         struct utsname ubuf;
38         int ret;
39         
40         ret = uname (&ubuf);
41         if (ret == -1) {
42                 ubuf.machine[0] = '\0';
43                 ubuf.sysname[0] = '\0';
44         }
45         
46         if (gethostname(machine_name, sizeof(machine_name)) != 0)
47                 machine_name[0] = '\0';
48         
49         switch (type) {
50         case WAPI_SHM_DATA:
51                 name = g_strdup_printf ("shared_data-%s-%s-%s-%d-%d-%d",
52                                         machine_name, ubuf.sysname,
53                                         ubuf.machine,
54                                         sizeof(struct _WapiHandleShared),
55                                         _WAPI_HANDLE_VERSION, 0);
56                 break;
57                 
58         case WAPI_SHM_FILESHARE:
59                 name = g_strdup_printf ("shared_fileshare-%s-%s-%s-%d-%d-%d",
60                                         machine_name, ubuf.sysname,
61                                         ubuf.machine,
62                                         sizeof(struct _WapiFileShare),
63                                         _WAPI_HANDLE_VERSION, 0);
64                 break;
65         }
66
67         /* I don't know how nfs affects mmap.  If mmap() of files on
68          * nfs mounts breaks, then there should be an option to set
69          * the directory.
70          */
71         wapi_dir = getenv ("MONO_SHARED_DIR");
72         if (wapi_dir == NULL) {
73                 filename = g_build_filename (g_get_home_dir (), ".wapi", name,
74                                              NULL);
75         } else {
76                 filename = g_build_filename (wapi_dir, ".wapi", name, NULL);
77         }
78         g_free (name);
79
80         g_snprintf (file, _POSIX_PATH_MAX, "%s", filename);
81         g_free (filename);
82                 
83         /* No need to check if the dir already exists or check
84          * mkdir() errors, because on any error the open() call will
85          * report the problem.
86          */
87         dir = g_path_get_dirname (file);
88         mkdir (dir, 0755);
89         g_free (dir);
90         
91         return(file);
92 }
93
94 static int _wapi_shm_file_open (const guchar *filename, guint32 wanted_size)
95 {
96         int fd;
97         struct stat statbuf;
98         int ret;
99         gboolean created = FALSE;
100         
101 try_again:
102         /* No O_CREAT yet, because we need to initialise the file if
103          * we have to create it.
104          */
105         fd = open (filename, O_RDWR, 0600);
106         if (fd == -1 && errno == ENOENT) {
107                 /* OK, its up to us to create it.  O_EXCL to avoid a
108                  * race condition where two processes can
109                  * simultaneously try and create the file
110                  */
111                 fd = open (filename, O_CREAT|O_EXCL|O_RDWR, 0600);
112                 if (fd == -1 && errno == EEXIST) {
113                         /* It's possible that the file was created in
114                          * between finding it didn't exist, and trying
115                          * to create it.  Just try opening it again
116                          */
117                         goto try_again;
118                 } else if (fd == -1) {
119                         g_critical ("%s: shared file [%s] open error: %s",
120                                     __func__, filename, g_strerror (errno));
121                         return(-1);
122                 } else {
123                         /* We created the file, so we need to expand
124                          * the file.
125                          *
126                          * (wanted_size-1, because we're about to
127                          * write the other byte to actually expand the
128                          * file.)
129                          */
130                         if (lseek (fd, wanted_size-1, SEEK_SET) == -1) {
131                                 g_critical ("%s: shared file [%s] lseek error: %s", __func__, filename, g_strerror (errno));
132                                 close (fd);
133                                 unlink (filename);
134                                 return(-1);
135                         }
136                         
137                         do {
138                                 ret = write (fd, "", 1);
139                         } while (ret == -1 && errno == EINTR);
140                                 
141                         if (ret == -1) {
142                                 g_critical ("%s: shared file [%s] write error: %s", __func__, filename, g_strerror (errno));
143                                 close (fd);
144                                 unlink (filename);
145                                 return(-1);
146                         }
147                         
148                         created = TRUE;
149
150                         /* The contents of the file is set to all
151                          * zero, because it is opened up with lseek,
152                          * so we don't need to do any more
153                          * initialisation here
154                          */
155                 }
156         } else if (fd == -1) {
157                 g_critical ("%s: shared file [%s] open error: %s", __func__,
158                             filename, g_strerror (errno));
159                 return(-1);
160         }
161         
162         /* Use stat to find the file size (instead of hard coding it)
163          * because we can expand the file later if needed (for more
164          * handles or scratch space.)
165          */
166         if (fstat (fd, &statbuf) == -1) {
167                 g_critical ("%s: fstat error: %s", __func__,
168                             g_strerror (errno));
169                 if (created == TRUE) {
170                         unlink (filename);
171                 }
172                 close (fd);
173                 return(-1);
174         }
175
176         if (statbuf.st_size < wanted_size) {
177                 close (fd);
178                 if (created == TRUE) {
179 #ifdef HAVE_LARGE_FILE_SUPPORT
180                         /* Keep gcc quiet... */
181                         g_critical ("%s: shared file [%s] is not big enough! (found %lld, need %d bytes)", __func__, filename, statbuf.st_size, wanted_size);
182 #else
183                         g_critical ("%s: shared file [%s] is not big enough! (found %ld, need %d bytes)", __func__, filename, statbuf.st_size, wanted_size);
184 #endif
185                         unlink (filename);
186                         return(-1);
187                 } else {
188                         /* We didn't create it, so just try opening it again */
189                         goto try_again;
190                 }
191         }
192         
193         return(fd);
194 }
195
196 /*
197  * _wapi_shm_attach:
198  * @success: Was it a success
199  *
200  * Attach to the shared memory file or create it if it did not exist.
201  * Returns the memory area the file was mmapped to.
202  */
203 gpointer _wapi_shm_attach (_wapi_shm_t type)
204 {
205         gpointer shm_seg;
206         int fd;
207         struct stat statbuf;
208         guchar *filename=_wapi_shm_file (type);
209         guint32 size;
210         
211         switch(type) {
212         case WAPI_SHM_DATA:
213                 size = sizeof(struct _WapiHandleSharedLayout);
214                 break;
215                 
216         case WAPI_SHM_FILESHARE:
217                 size = sizeof(struct _WapiFileShareLayout);
218                 break;
219         }
220         
221         fd = _wapi_shm_file_open (filename, size);
222         if (fd == -1) {
223                 g_critical ("%s: shared file [%s] open error", __func__,
224                             filename);
225                 return(NULL);
226         }
227         
228         if (fstat (fd, &statbuf)==-1) {
229                 g_critical ("%s: fstat error: %s", __func__,
230                             g_strerror (errno));
231                 close (fd);
232                 return(NULL);
233         }
234         
235         shm_seg = mmap (NULL, statbuf.st_size, PROT_READ|PROT_WRITE,
236                         MAP_SHARED, fd, 0);
237         if (shm_seg == MAP_FAILED) {
238                 g_critical ("%s: mmap error: %s", __func__,
239                             g_strerror (errno));
240                 close (fd);
241                 return(NULL);
242         }
243                 
244         close (fd);
245         return(shm_seg);
246 }
247
248 void _wapi_shm_semaphores_init ()
249 {
250         key_t key = ftok (_wapi_shm_file (WAPI_SHM_DATA), 'M');
251         key_t oldkey;
252
253         /* Yet more barmy API - this union is a well-defined parameter
254          * in a syscall, yet I still have to define it here as it
255          * doesn't appear in a header
256          */
257         union semun {
258                 int val;
259                 struct semid_ds *buf;
260                 ushort *array;
261         } defs;
262         ushort def_vals[_WAPI_SHARED_SEM_COUNT];
263         int i;
264         int retries = 0;
265         
266         for (i = 0; i < _WAPI_SHARED_SEM_COUNT; i++) {
267                 def_vals[i] = 1;
268         }
269         defs.array = def_vals;
270         
271 again:
272         retries++;
273         oldkey = _wapi_shared_layout->sem_key;
274
275         if (oldkey == 0) {
276 #ifdef DEBUG
277                 g_message ("%s: Creating with new key (0x%x)", __func__, key);
278 #endif
279
280                 /* The while loop attempts to make some sense of the
281                  * bonkers 'think of a random number' method of
282                  * picking a key without collision with other
283                  * applications
284                  */
285                 while ((_wapi_sem_id = semget (key, _WAPI_SHARED_SEM_COUNT,
286                                                IPC_CREAT | IPC_EXCL | 0600)) == -1) {
287                         if (errno == ENOMEM) {
288                                 g_critical ("%s: semget error: %s", __func__,
289                                             g_strerror (errno));
290                         } else if (errno == ENOSPC) {
291                                 g_critical ("%s: semget error: %s.  Try deleting some semaphores with ipcs and ipcrm", __func__, g_strerror (errno));
292                         } else if (errno != EEXIST) {
293                                 if (retries > 3)
294                                         g_warning ("%s: semget error: %s key 0x%x - trying again", __func__,
295                                                         g_strerror (errno), key);
296                         }
297                         
298                         key++;
299 #ifdef DEBUG
300                         g_message ("%s: Got (%s), trying with new key (0x%x)",
301                                    __func__, g_strerror (errno), key);
302 #endif
303                 }
304                 /* Got a semaphore array, so initialise it and install
305                  * the key into the shared memory
306                  */
307                 
308                 if (semctl (_wapi_sem_id, 0, SETALL, defs) == -1) {
309                         if (retries > 3)
310                                 g_warning ("%s: semctl init error: %s - trying again", __func__, g_strerror (errno));
311
312                         /* Something went horribly wrong, so try
313                          * getting a new set from scratch
314                          */
315                         semctl (_wapi_sem_id, 0, IPC_RMID);
316                         goto again;
317                 }
318
319                 if (InterlockedCompareExchange (&_wapi_shared_layout->sem_key,
320                                                 key, 0) != 0) {
321                         /* Someone else created one and installed the
322                          * key while we were working, so delete the
323                          * array we created and fall through to the
324                          * 'key already known' case.
325                          */
326                         semctl (_wapi_sem_id, 0, IPC_RMID);
327                         oldkey = _wapi_shared_layout->sem_key;
328                 } else {
329                         /* We've installed this semaphore set's key into
330                          * the shared memory
331                          */
332                         return;
333                 }
334         }
335         
336 #ifdef DEBUG
337         g_message ("%s: Trying with old key 0x%x", __func__, oldkey);
338 #endif
339
340         _wapi_sem_id = semget (oldkey, _WAPI_SHARED_SEM_COUNT, 0600);
341         if (_wapi_sem_id == -1) {
342                 if (retries > 3)
343                         g_warning ("%s: semget error opening old key 0x%x (%s) - trying again",
344                                         __func__, oldkey,g_strerror (errno));
345
346                 /* Someone must have deleted the semaphore set, so
347                  * blow away the bad key and try again
348                  */
349                 InterlockedCompareExchange (&_wapi_shared_layout->sem_key, 0,
350                                             oldkey);
351                 
352                 goto again;
353         }
354 }
355
356 int _wapi_shm_sem_lock (int sem)
357 {
358         struct sembuf ops;
359         int ret;
360         
361 #ifdef DEBUG
362         g_message ("%s: locking sem %d", __func__, sem);
363 #endif
364
365         ops.sem_num = sem;
366         ops.sem_op = -1;
367         ops.sem_flg = SEM_UNDO;
368         
369         do {
370                 ret = semop (_wapi_sem_id, &ops, 1);
371         } while (ret == -1 && errno == EINTR);
372
373         if (ret == -1) {
374                 /* Turn this into a pthreads-style return value */
375                 ret = errno;
376         }
377         
378 #ifdef DEBUG
379         g_message ("%s: returning %d (%s)", __func__, ret, g_strerror (ret));
380 #endif
381         
382         return(ret);
383 }
384
385 int _wapi_shm_sem_trylock (int sem)
386 {
387         struct sembuf ops;
388         int ret;
389         
390 #ifdef DEBUG
391         g_message ("%s: trying to lock sem %d", __func__, sem);
392 #endif
393         
394         ops.sem_num = sem;
395         ops.sem_op = -1;
396         ops.sem_flg = IPC_NOWAIT | SEM_UNDO;
397         
398         do {
399                 ret = semop (_wapi_sem_id, &ops, 1);
400         } while (ret == -1 && errno == EINTR);
401
402         if (ret == -1) {
403                 /* Turn this into a pthreads-style return value */
404                 ret = errno;
405         }
406         
407         if (ret == EAGAIN) {
408                 /* But pthreads uses this code instead */
409                 ret = EBUSY;
410         }
411         
412 #ifdef DEBUG
413         g_message ("%s: returning %d (%s)", __func__, ret, g_strerror (ret));
414 #endif
415         
416         return(ret);
417 }
418
419 int _wapi_shm_sem_unlock (int sem)
420 {
421         struct sembuf ops;
422         int ret;
423         
424 #ifdef DEBUG
425         g_message ("%s: unlocking sem %d", __func__, sem);
426 #endif
427         
428         ops.sem_num = sem;
429         ops.sem_op = 1;
430         ops.sem_flg = SEM_UNDO;
431         
432         do {
433                 ret = semop (_wapi_sem_id, &ops, 1);
434         } while (ret == -1 && errno == EINTR);
435
436         if (ret == -1) {
437                 /* Turn this into a pthreads-style return value */
438                 ret = errno;
439         }
440         
441 #ifdef DEBUG
442         g_message ("%s: returning %d (%s)", __func__, ret, g_strerror (ret));
443 #endif
444
445         return(ret);
446 }
447