2007-04-26 Dick Porter <dick@ximian.com>
[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-2006 Novell, 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 #ifdef DISABLE_SHARED_HANDLES
33 gboolean _wapi_shm_disabled = TRUE;
34 #else
35 gboolean _wapi_shm_disabled = FALSE;
36 #endif
37
38 static gchar *_wapi_shm_file (_wapi_shm_t type)
39 {
40         static gchar file[_POSIX_PATH_MAX];
41         gchar *name = NULL, *filename, *dir, *wapi_dir;
42         gchar machine_name[256];
43         const gchar *fake_name;
44         struct utsname ubuf;
45         int ret;
46         int len;
47         
48         ret = uname (&ubuf);
49         if (ret == -1) {
50                 ubuf.machine[0] = '\0';
51                 ubuf.sysname[0] = '\0';
52         } else {
53                 g_strdelimit (ubuf.sysname, "/", '_');
54                 g_strdelimit (ubuf.machine, "/", '_');
55         }
56
57         fake_name = g_getenv ("MONO_SHARED_HOSTNAME");
58         if (fake_name == NULL) {
59                 if (gethostname(machine_name, sizeof(machine_name)) != 0)
60                         machine_name[0] = '\0';
61         } else {
62                 len = MIN (strlen (fake_name), sizeof (machine_name) - 1);
63                 strncpy (machine_name, fake_name, len);
64                 machine_name [len] = '\0';
65         }
66         
67         switch (type) {
68         case WAPI_SHM_DATA:
69                 name = g_strdup_printf ("shared_data-%s-%s-%s-%d-%d-%d",
70                                         machine_name, ubuf.sysname,
71                                         ubuf.machine,
72                                         (int) sizeof(struct _WapiHandleShared),
73                                         _WAPI_HANDLE_VERSION, 0);
74                 break;
75                 
76         case WAPI_SHM_FILESHARE:
77                 name = g_strdup_printf ("shared_fileshare-%s-%s-%s-%d-%d-%d",
78                                         machine_name, ubuf.sysname,
79                                         ubuf.machine,
80                                         (int) sizeof(struct _WapiFileShare),
81                                         _WAPI_HANDLE_VERSION, 0);
82                 break;
83         }
84
85         /* I don't know how nfs affects mmap.  If mmap() of files on
86          * nfs mounts breaks, then there should be an option to set
87          * the directory.
88          */
89         wapi_dir = getenv ("MONO_SHARED_DIR");
90         if (wapi_dir == NULL) {
91                 filename = g_build_filename (g_get_home_dir (), ".wapi", name,
92                                              NULL);
93         } else {
94                 filename = g_build_filename (wapi_dir, ".wapi", name, NULL);
95         }
96         g_free (name);
97
98         g_snprintf (file, _POSIX_PATH_MAX, "%s", filename);
99         g_free (filename);
100                 
101         /* No need to check if the dir already exists or check
102          * mkdir() errors, because on any error the open() call will
103          * report the problem.
104          */
105         dir = g_path_get_dirname (file);
106         mkdir (dir, 0755);
107         g_free (dir);
108         
109         return(file);
110 }
111
112 static int _wapi_shm_file_open (const gchar *filename, guint32 wanted_size)
113 {
114         int fd;
115         struct stat statbuf;
116         int ret, tries = 0;
117         gboolean created = FALSE;
118         
119 try_again:
120         if (tries++ > 10) {
121                 /* Just give up */
122                 return (-1);
123         } else if (tries > 5) {
124                 /* Break out of a loop */
125                 unlink (filename);
126         }
127         
128         /* No O_CREAT yet, because we need to initialise the file if
129          * we have to create it.
130          */
131         fd = open (filename, O_RDWR, 0600);
132         if (fd == -1 && errno == ENOENT) {
133                 /* OK, its up to us to create it.  O_EXCL to avoid a
134                  * race condition where two processes can
135                  * simultaneously try and create the file
136                  */
137                 fd = open (filename, O_CREAT|O_EXCL|O_RDWR, 0600);
138                 if (fd == -1 && errno == EEXIST) {
139                         /* It's possible that the file was created in
140                          * between finding it didn't exist, and trying
141                          * to create it.  Just try opening it again
142                          */
143                         goto try_again;
144                 } else if (fd == -1) {
145                         g_critical ("%s: shared file [%s] open error: %s",
146                                     __func__, filename, g_strerror (errno));
147                         return(-1);
148                 } else {
149                         /* We created the file, so we need to expand
150                          * the file.
151                          *
152                          * (wanted_size-1, because we're about to
153                          * write the other byte to actually expand the
154                          * file.)
155                          */
156                         if (lseek (fd, wanted_size-1, SEEK_SET) == -1) {
157                                 g_critical ("%s: shared file [%s] lseek error: %s", __func__, filename, g_strerror (errno));
158                                 close (fd);
159                                 unlink (filename);
160                                 return(-1);
161                         }
162                         
163                         do {
164                                 ret = write (fd, "", 1);
165                         } while (ret == -1 && errno == EINTR);
166                                 
167                         if (ret == -1) {
168                                 g_critical ("%s: shared file [%s] write error: %s", __func__, filename, g_strerror (errno));
169                                 close (fd);
170                                 unlink (filename);
171                                 return(-1);
172                         }
173                         
174                         created = TRUE;
175
176                         /* The contents of the file is set to all
177                          * zero, because it is opened up with lseek,
178                          * so we don't need to do any more
179                          * initialisation here
180                          */
181                 }
182         } else if (fd == -1) {
183                 g_critical ("%s: shared file [%s] open error: %s", __func__,
184                             filename, g_strerror (errno));
185                 return(-1);
186         }
187         
188         /* Use stat to find the file size (instead of hard coding it)
189          * because we can expand the file later if needed (for more
190          * handles or scratch space.)
191          */
192         if (fstat (fd, &statbuf) == -1) {
193                 g_critical ("%s: fstat error: %s", __func__,
194                             g_strerror (errno));
195                 if (created == TRUE) {
196                         unlink (filename);
197                 }
198                 close (fd);
199                 return(-1);
200         }
201
202         if (statbuf.st_size < wanted_size) {
203                 close (fd);
204                 if (created == TRUE) {
205 #ifdef HAVE_LARGE_FILE_SUPPORT
206                         /* Keep gcc quiet... */
207                         g_critical ("%s: shared file [%s] is not big enough! (found %lld, need %d bytes)", __func__, filename, statbuf.st_size, wanted_size);
208 #else
209                         g_critical ("%s: shared file [%s] is not big enough! (found %ld, need %d bytes)", __func__, filename, statbuf.st_size, wanted_size);
210 #endif
211                         unlink (filename);
212                         return(-1);
213                 } else {
214                         /* We didn't create it, so just try opening it again */
215                         _wapi_handle_spin (100);
216                         goto try_again;
217                 }
218         }
219         
220         return(fd);
221 }
222
223 static gboolean check_disabled (void)
224 {
225         if (_wapi_shm_disabled || g_getenv ("MONO_DISABLE_SHM")) {
226                 const char* val = g_getenv ("MONO_DISABLE_SHM");
227                 if (val == NULL || *val == '1' || *val == 'y' || *val == 'Y') {
228                         _wapi_shm_disabled = TRUE;
229                 }
230         }
231
232         return(_wapi_shm_disabled);
233 }
234
235 /*
236  * _wapi_shm_attach:
237  * @success: Was it a success
238  *
239  * Attach to the shared memory file or create it if it did not exist.
240  * Returns the memory area the file was mmapped to.
241  */
242 gpointer _wapi_shm_attach (_wapi_shm_t type)
243 {
244         gpointer shm_seg;
245         int fd;
246         struct stat statbuf;
247         gchar *filename=_wapi_shm_file (type);
248         guint32 size;
249         
250         switch(type) {
251         case WAPI_SHM_DATA:
252                 size = sizeof(struct _WapiHandleSharedLayout);
253                 break;
254                 
255         case WAPI_SHM_FILESHARE:
256                 size = sizeof(struct _WapiFileShareLayout);
257                 break;
258         default:
259                 g_error ("Invalid type in _wapi_shm_attach ()");
260                 return NULL;
261         }
262
263         if (check_disabled ()) {
264                 return g_malloc0 (size);
265         }
266
267         fd = _wapi_shm_file_open (filename, size);
268         if (fd == -1) {
269                 g_critical ("%s: shared file [%s] open error", __func__,
270                             filename);
271                 return(NULL);
272         }
273         
274         if (fstat (fd, &statbuf)==-1) {
275                 g_critical ("%s: fstat error: %s", __func__,
276                             g_strerror (errno));
277                 close (fd);
278                 return(NULL);
279         }
280         
281         shm_seg = mmap (NULL, statbuf.st_size, PROT_READ|PROT_WRITE,
282                         MAP_SHARED, fd, 0);
283         if (shm_seg == MAP_FAILED) {
284                 shm_seg = mmap (NULL, statbuf.st_size, PROT_READ|PROT_WRITE,
285                         MAP_PRIVATE, fd, 0);
286                 if (shm_seg == MAP_FAILED) {
287                         g_critical ("%s: mmap error: %s", __func__, g_strerror (errno));
288                         close (fd);
289                         return(NULL);
290                 }
291         }
292                 
293         close (fd);
294         return(shm_seg);
295 }
296
297 static void shm_semaphores_init (void)
298 {
299         key_t key;
300         key_t oldkey;
301         int thr_ret;
302         struct _WapiHandleSharedLayout *tmp_shared;
303         
304         /* Yet more barmy API - this union is a well-defined parameter
305          * in a syscall, yet I still have to define it here as it
306          * doesn't appear in a header
307          */
308         union semun {
309                 int val;
310                 struct semid_ds *buf;
311                 ushort *array;
312         } defs;
313         ushort def_vals[_WAPI_SHARED_SEM_COUNT];
314         int i;
315         int retries = 0;
316         
317         for (i = 0; i < _WAPI_SHARED_SEM_COUNT; i++) {
318                 def_vals[i] = 1;
319         }
320
321         /* Process count must start at '0' - the 1 for all the others
322          * sets the semaphore to "unlocked"
323          */
324         def_vals[_WAPI_SHARED_SEM_PROCESS_COUNT] = 0;
325         
326         defs.array = def_vals;
327         
328         /* Temporarily attach the shared data so we can read the
329          * semaphore key.  We release this mapping and attach again
330          * after getting the semaphores to avoid a race condition
331          * where a terminating process can delete the shared files
332          * between a new process attaching the file and getting access
333          * to the semaphores (which increments the process count,
334          * preventing destruction of the shared data...)
335          */
336         tmp_shared = _wapi_shm_attach (WAPI_SHM_DATA);
337         g_assert (tmp_shared != NULL);
338         
339         key = ftok (_wapi_shm_file (WAPI_SHM_DATA), 'M');
340
341 again:
342         retries++;
343         oldkey = tmp_shared->sem_key;
344
345         if (oldkey == 0) {
346 #ifdef DEBUG
347                 g_message ("%s: Creating with new key (0x%x)", __func__, key);
348 #endif
349
350                 /* The while loop attempts to make some sense of the
351                  * bonkers 'think of a random number' method of
352                  * picking a key without collision with other
353                  * applications
354                  */
355                 while ((_wapi_sem_id = semget (key, _WAPI_SHARED_SEM_COUNT,
356                                                IPC_CREAT | IPC_EXCL | 0600)) == -1) {
357                         if (errno == ENOMEM) {
358                                 g_error ("%s: semget error: %s", __func__,
359                                             g_strerror (errno));
360                         } else if (errno == ENOSPC) {
361                                 g_error ("%s: semget error: %s.  Try deleting some semaphores with ipcs and ipcrm\nor increase the maximum number of semaphore in the system.", __func__, g_strerror (errno));
362                         } else if (errno != EEXIST) {
363                                 if (retries > 3)
364                                         g_warning ("%s: semget error: %s key 0x%x - trying again", __func__,
365                                                         g_strerror (errno), key);
366                         }
367                         
368                         key++;
369 #ifdef DEBUG
370                         g_message ("%s: Got (%s), trying with new key (0x%x)",
371                                    __func__, g_strerror (errno), key);
372 #endif
373                 }
374                 /* Got a semaphore array, so initialise it and install
375                  * the key into the shared memory
376                  */
377                 
378                 if (semctl (_wapi_sem_id, 0, SETALL, defs) == -1) {
379                         if (retries > 3)
380                                 g_warning ("%s: semctl init error: %s - trying again", __func__, g_strerror (errno));
381
382                         /* Something went horribly wrong, so try
383                          * getting a new set from scratch
384                          */
385                         semctl (_wapi_sem_id, 0, IPC_RMID);
386                         goto again;
387                 }
388
389                 if (InterlockedCompareExchange (&tmp_shared->sem_key,
390                                                 key, 0) != 0) {
391                         /* Someone else created one and installed the
392                          * key while we were working, so delete the
393                          * array we created and fall through to the
394                          * 'key already known' case.
395                          */
396                         semctl (_wapi_sem_id, 0, IPC_RMID);
397                         oldkey = tmp_shared->sem_key;
398                 } else {
399                         /* We've installed this semaphore set's key into
400                          * the shared memory
401                          */
402                         goto done;
403                 }
404         }
405         
406 #ifdef DEBUG
407         g_message ("%s: Trying with old key 0x%x", __func__, oldkey);
408 #endif
409
410         _wapi_sem_id = semget (oldkey, _WAPI_SHARED_SEM_COUNT, 0600);
411         if (_wapi_sem_id == -1) {
412                 if (retries > 3)
413                         g_warning ("%s: semget error opening old key 0x%x (%s) - trying again",
414                                         __func__, oldkey,g_strerror (errno));
415
416                 /* Someone must have deleted the semaphore set, so
417                  * blow away the bad key and try again
418                  */
419                 InterlockedCompareExchange (&tmp_shared->sem_key, 0, oldkey);
420                 
421                 goto again;
422         }
423
424   done:
425         /* Increment the usage count of this semaphore set */
426         thr_ret = _wapi_shm_sem_lock (_WAPI_SHARED_SEM_PROCESS_COUNT_LOCK);
427         g_assert (thr_ret == 0);
428         
429 #ifdef DEBUG
430         g_message ("%s: Incrementing the process count (%d)", __func__, _wapi_getpid ());
431 #endif
432
433         /* We only ever _unlock_ this semaphore, letting the kernel
434          * restore (ie decrement) this unlock when this process exits.
435          * We lock another semaphore around it so we can serialise
436          * access when we're testing the value of this semaphore when
437          * we exit cleanly, so we can delete the whole semaphore set.
438          */
439         _wapi_shm_sem_unlock (_WAPI_SHARED_SEM_PROCESS_COUNT);
440
441 #ifdef DEBUG
442         g_message ("%s: Process count is now %d (%d)", __func__, semctl (_wapi_sem_id, _WAPI_SHARED_SEM_PROCESS_COUNT, GETVAL), _wapi_getpid ());
443 #endif
444         
445         _wapi_shm_sem_unlock (_WAPI_SHARED_SEM_PROCESS_COUNT_LOCK);
446
447         if (_wapi_shm_disabled)
448                 g_free (tmp_shared);
449         else
450                 munmap (tmp_shared, sizeof(struct _WapiHandleSharedLayout));
451 }
452
453 static mono_mutex_t noshm_sems[_WAPI_SHARED_SEM_COUNT] = {MONO_MUTEX_INITIALIZER};
454
455 static void noshm_semaphores_init (void)
456 {
457         /* No need to do anything */
458 }
459
460 static void shm_semaphores_remove (void)
461 {
462         int thr_ret;
463         int proc_count;
464         
465 #ifdef DEBUG
466         g_message ("%s: Checking process count (%d)", __func__,
467                    _wapi_getpid ());
468 #endif
469         
470         thr_ret = _wapi_shm_sem_lock (_WAPI_SHARED_SEM_PROCESS_COUNT_LOCK);
471         g_assert (thr_ret == 0);
472         
473         proc_count = semctl (_wapi_sem_id, _WAPI_SHARED_SEM_PROCESS_COUNT,
474                              GETVAL);
475
476         g_assert (proc_count > 0);
477         if (proc_count == 1) {
478                 /* Just us, so blow away the semaphores and the shared
479                  * files
480                  */
481 #ifdef DEBUG
482                 g_message ("%s: Removing semaphores! (%d)", __func__,
483                            _wapi_getpid ());
484 #endif
485
486                 semctl (_wapi_sem_id, 0, IPC_RMID);
487                 unlink (_wapi_shm_file (WAPI_SHM_DATA));
488                 unlink (_wapi_shm_file (WAPI_SHM_FILESHARE));
489         } else {
490                 /* "else" clause, because there's no point unlocking
491                  * the semaphore if we've just blown it away...
492                  */
493                 _wapi_shm_sem_unlock (_WAPI_SHARED_SEM_PROCESS_COUNT_LOCK);
494         }
495 }
496
497 static void noshm_semaphores_remove (void)
498 {
499         /* No need to do anything */
500 }
501
502 static int shm_sem_lock (int sem)
503 {
504         struct sembuf ops;
505         int ret;
506         
507 #ifdef DEBUG
508         g_message ("%s: locking sem %d", __func__, sem);
509 #endif
510
511         ops.sem_num = sem;
512         ops.sem_op = -1;
513         ops.sem_flg = SEM_UNDO;
514         
515   retry:
516         do {
517                 ret = semop (_wapi_sem_id, &ops, 1);
518         } while (ret == -1 && errno == EINTR);
519
520         if (ret == -1) {
521                 /* EINVAL covers the case when the semaphore was
522                  * deleted before we started the semop
523                  */
524                 if (errno == EIDRM || errno == EINVAL) {
525                         /* Someone blew away this semaphore set, so
526                          * get a new one and try again
527                          */
528 #ifdef DEBUG
529                         g_message ("%s: Reinitialising the semaphores!",
530                                    __func__);
531 #endif
532
533                         _wapi_shm_semaphores_init ();
534                         goto retry;
535                 }
536                 
537                 /* Turn this into a pthreads-style return value */
538                 ret = errno;
539         }
540         
541 #ifdef DEBUG
542         g_message ("%s: returning %d (%s)", __func__, ret, g_strerror (ret));
543 #endif
544         
545         return(ret);
546 }
547
548 static int noshm_sem_lock (int sem)
549 {
550         int ret;
551         
552 #ifdef DEBUG
553         g_message ("%s: locking nosem %d", __func__, sem);
554 #endif
555         
556         ret = mono_mutex_lock (&noshm_sems[sem]);
557         
558         return(ret);
559 }
560
561 static int shm_sem_trylock (int sem)
562 {
563         struct sembuf ops;
564         int ret;
565         
566 #ifdef DEBUG
567         g_message ("%s: trying to lock sem %d", __func__, sem);
568 #endif
569         
570         ops.sem_num = sem;
571         ops.sem_op = -1;
572         ops.sem_flg = IPC_NOWAIT | SEM_UNDO;
573         
574   retry:
575         do {
576                 ret = semop (_wapi_sem_id, &ops, 1);
577         } while (ret == -1 && errno == EINTR);
578
579         if (ret == -1) {
580                 /* EINVAL covers the case when the semaphore was
581                  * deleted before we started the semop
582                  */
583                 if (errno == EIDRM || errno == EINVAL) {
584                         /* Someone blew away this semaphore set, so
585                          * get a new one and try again
586                          */
587 #ifdef DEBUG
588                         g_message ("%s: Reinitialising the semaphores!",
589                                    __func__);
590 #endif
591
592                         _wapi_shm_semaphores_init ();
593                         goto retry;
594                 }
595                 
596                 /* Turn this into a pthreads-style return value */
597                 ret = errno;
598         }
599         
600         if (ret == EAGAIN) {
601                 /* But pthreads uses this code instead */
602                 ret = EBUSY;
603         }
604         
605 #ifdef DEBUG
606         g_message ("%s: returning %d (%s)", __func__, ret, g_strerror (ret));
607 #endif
608         
609         return(ret);
610 }
611
612 static int noshm_sem_trylock (int sem)
613 {
614         int ret;
615         
616 #ifdef DEBUG
617         g_message ("%s: trying to lock nosem %d", __func__, sem);
618 #endif
619         
620         ret = mono_mutex_trylock (&noshm_sems[sem]);
621         
622         return(ret);
623 }
624
625 static int shm_sem_unlock (int sem)
626 {
627         struct sembuf ops;
628         int ret;
629         
630 #ifdef DEBUG
631         g_message ("%s: unlocking sem %d", __func__, sem);
632 #endif
633         
634         ops.sem_num = sem;
635         ops.sem_op = 1;
636         ops.sem_flg = SEM_UNDO;
637         
638   retry:
639         do {
640                 ret = semop (_wapi_sem_id, &ops, 1);
641         } while (ret == -1 && errno == EINTR);
642
643         if (ret == -1) {
644                 /* EINVAL covers the case when the semaphore was
645                  * deleted before we started the semop
646                  */
647                 if (errno == EIDRM || errno == EINVAL) {
648                         /* Someone blew away this semaphore set, so
649                          * get a new one and try again (we can't just
650                          * assume that the semaphore is now unlocked)
651                          */
652 #ifdef DEBUG
653                         g_message ("%s: Reinitialising the semaphores!",
654                                    __func__);
655 #endif
656
657                         _wapi_shm_semaphores_init ();
658                         goto retry;
659                 }
660                 
661                 /* Turn this into a pthreads-style return value */
662                 ret = errno;
663         }
664         
665 #ifdef DEBUG
666         g_message ("%s: returning %d (%s)", __func__, ret, g_strerror (ret));
667 #endif
668
669         return(ret);
670 }
671
672 static int noshm_sem_unlock (int sem)
673 {
674         int ret;
675         
676 #ifdef DEBUG
677         g_message ("%s: unlocking nosem %d", __func__, sem);
678 #endif
679         
680         ret = mono_mutex_unlock (&noshm_sems[sem]);
681         
682         return(ret);
683 }
684
685 void _wapi_shm_semaphores_init (void)
686 {
687         if (check_disabled ()) {
688                 noshm_semaphores_init ();
689         } else {
690                 shm_semaphores_init ();
691         }
692 }
693
694 void _wapi_shm_semaphores_remove (void)
695 {
696         if (_wapi_shm_disabled) {
697                 noshm_semaphores_remove ();
698         } else {
699                 shm_semaphores_remove ();
700         }
701 }
702
703 int _wapi_shm_sem_lock (int sem)
704 {
705         if (_wapi_shm_disabled) {
706                 return(noshm_sem_lock (sem));
707         } else {
708                 return(shm_sem_lock (sem));
709         }
710 }
711
712 int _wapi_shm_sem_trylock (int sem)
713 {
714         if (_wapi_shm_disabled) {
715                 return(noshm_sem_trylock (sem));
716         } else {
717                 return(shm_sem_trylock (sem));
718         }
719 }
720
721 int _wapi_shm_sem_unlock (int sem)
722 {
723         if (_wapi_shm_disabled) {
724                 return(noshm_sem_unlock (sem));
725         } else {
726                 return(shm_sem_unlock (sem));
727         }
728 }