2004-09-28 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 Ximian, Inc.
8  */
9
10 /*
11  * Code to support inter-process sharing of handles.
12  *
13  * I thought of using an mmap()ed file for this.  If linuxthreads
14  * supported PTHREAD_PROCESS_SHARED I would have done; however without
15  * that pthread support the only other inter-process IPC
16  * synchronisation option is a sysV semaphore, and if I'm going to use
17  * that I may as well take advantage of sysV shared memory too.
18  * Actually, semaphores seem to be buggy, or I was using them
19  * incorrectly :-).  I've replaced the sysV semaphore with a shared
20  * integer controlled with Interlocked functions.  And I've since
21  * replaced that with a separate process to serialise access to the
22  * shared memory, to avoid the possibility of DOS by leaving the
23  * shared memory locked, and also to allow the shared memory to be
24  * cleaned up.
25  *
26  * mmap() files have the advantage of avoiding namespace collisions,
27  * but have the disadvantage of needing cleaning up, and also msync().
28  * sysV shared memory has a really stupid way of getting random key
29  * IDs, which can lead to collisions.
30  *
31  * Having tried sysv shm, I tested mmap() and found that MAP_SHARED
32  * makes msync() irrelevent, and both types need cleaning up.  Seeing
33  * as mmap() doesn't suffer from the bonkers method of allocating
34  * segments, it seems to be the best method.
35  *
36  * This shared memory is needed because w32 processes do not have the
37  * POSIX parent-child relationship, so a process handle is available
38  * to any other process to find out exit status.  Handles are
39  * destroyed when the last reference to them is closed.  New handles
40  * can be created for long lasting items such as processes or threads,
41  * and also for named synchronisation objects so long as these haven't
42  * been deleted by having the last referencing handle closed.
43  */
44
45
46 #include <config.h>
47 #include <glib.h>
48 #include <stdio.h>
49 #include <fcntl.h>
50 #include <unistd.h>
51 #include <sys/mman.h>
52 #include <sys/types.h>
53 #include <sys/stat.h>
54 #include <errno.h>
55 #include <string.h>
56
57 #include <mono/io-layer/wapi.h>
58 #include <mono/io-layer/wapi-private.h>
59 #include <mono/io-layer/shared.h>
60 #include <mono/io-layer/daemon-private.h>
61
62 #undef DEBUG
63
64 /* Define this to make it easier to run valgrind on the daemon.  Then
65  * the first process to start will turn into a daemon without forking
66  * (the debug utility mono/handles/hps is ideal for this.)
67  */
68 #undef VALGRINDING
69
70 guchar *_wapi_shm_file (_wapi_shm_t type, guint32 segment)
71 {
72         static guchar file[_POSIX_PATH_MAX];
73         guchar *name = NULL, *filename, *dir, *wapi_dir;
74         gchar machine_name[256];
75
76         if (gethostname(machine_name, sizeof(machine_name)) != 0)
77                 machine_name[0] = '\0';
78         
79         /* Change the filename whenever the format of the contents
80          * changes
81          */
82         if(type==WAPI_SHM_DATA) {
83                 name=g_strdup_printf ("shared_data-%s-%d-%d",
84                                       machine_name, _WAPI_HANDLE_VERSION, segment);
85         } else if (type==WAPI_SHM_SCRATCH) {
86                 name=g_strdup_printf ("shared_scratch-%s-%d-%d",
87                                       machine_name, _WAPI_HANDLE_VERSION, segment);
88         } else {
89                 g_assert_not_reached ();
90         }
91
92         /* I don't know how nfs affects mmap.  If mmap() of files on
93          * nfs mounts breaks, then there should be an option to set
94          * the directory.
95          */
96         wapi_dir=getenv ("MONO_SHARED_DIR");
97         if(wapi_dir==NULL) {
98                 filename=g_build_filename (g_get_home_dir (), ".wapi", name,
99                                            NULL);
100         } else {
101                 filename=g_build_filename (wapi_dir, ".wapi", name, NULL);
102         }
103         g_free (name);
104
105         g_snprintf (file, _POSIX_PATH_MAX, "%s", filename);
106         g_free (filename);
107                 
108         /* No need to check if the dir already exists or check
109          * mkdir() errors, because on any error the open() call will
110          * report the problem.
111          */
112         dir=g_path_get_dirname (file);
113         mkdir (dir, 0755);
114         g_free (dir);
115         
116         return(file);
117 }
118
119 gpointer _wapi_shm_file_expand (gpointer mem, _wapi_shm_t type,
120                                 guint32 segment, guint32 old_len,
121                                 guint32 new_len)
122 {
123         int fd;
124         gpointer new_mem;
125         guchar *filename=_wapi_shm_file (type, segment);
126         int ret;
127
128         if(old_len>=new_len) {
129                 return(mem);
130         }
131         
132         munmap (mem, old_len);
133         
134         fd=open (filename, O_RDWR, 0600);
135         if(fd==-1) {
136                 g_critical (G_GNUC_PRETTY_FUNCTION
137                             ": shared file [%s] open error: %s", filename,
138                             g_strerror (errno));
139                 return(NULL);
140         }
141
142         if(lseek (fd, new_len-1, SEEK_SET)==-1) {
143                 g_critical (G_GNUC_PRETTY_FUNCTION
144                             ": shared file [%s] lseek error: %s", filename,
145                             g_strerror (errno));
146                 return(NULL);
147         }
148         
149         do {
150                 ret=write (fd, "", 1);
151         }
152         while (ret==-1 && errno==EINTR);
153
154         if(ret==-1) {
155                 g_critical (G_GNUC_PRETTY_FUNCTION
156                             ": shared file [%s] write error: %s", filename,
157                             g_strerror (errno));
158                 return(NULL);
159         }
160
161         close (fd);
162         
163         new_mem=_wapi_shm_file_map (type, segment, NULL, NULL);
164         
165         return(new_mem);
166 }
167
168 static int _wapi_shm_file_open (const guchar *filename, _wapi_shm_t type,
169                                 gboolean *created)
170 {
171         int fd;
172         struct stat statbuf;
173         guint32 wanted_size = 0;
174         int ret;
175         
176         if(created) {
177                 *created=FALSE;
178         }
179         
180         if(type==WAPI_SHM_DATA) {
181                 wanted_size=sizeof(struct _WapiHandleShared_list);
182         } else if (type==WAPI_SHM_SCRATCH) {
183                 wanted_size=sizeof(struct _WapiHandleScratch) + 
184                                 (_WAPI_SHM_SCRATCH_SIZE - MONO_ZERO_ARRAY_LENGTH);
185         } else {
186                 g_assert_not_reached ();
187         }
188         
189 try_again:
190         /* No O_CREAT yet, because we need to initialise the file if
191          * we have to create it.
192          */
193         fd=open (filename, O_RDWR, 0600);
194         if(fd==-1 && errno==ENOENT) {
195                 /* OK, its up to us to create it.  O_EXCL to avoid a
196                  * race condition where two processes can
197                  * simultaneously try and create the file
198                  */
199                 fd=open (filename, O_CREAT|O_EXCL|O_RDWR, 0600);
200                 if(fd==-1 && errno==EEXIST) {
201                         /* It's possible that the file was created in
202                          * between finding it didn't exist, and trying
203                          * to create it.  Just try opening it again
204                          */
205                         goto try_again;
206                 } else if (fd==-1) {
207                         g_critical (G_GNUC_PRETTY_FUNCTION
208                                     ": shared file [%s] open error: %s",
209                                     filename, g_strerror (errno));
210                         return(-1);
211                 } else {
212                         /* We created the file, so we need to expand
213                          * the file and inform the caller so it can
214                          * fork the handle daemon too.
215                          *
216                          * (wanted_size-1, because we're about to
217                          * write the other byte to actually expand the
218                          * file.)
219                          */
220                         if(lseek (fd, wanted_size-1, SEEK_SET)==-1) {
221                                 g_critical (G_GNUC_PRETTY_FUNCTION ": shared file [%s] lseek error: %s", filename, g_strerror (errno));
222                                 close (fd);
223                                 unlink (filename);
224                                 return(-1);
225                         }
226                         
227                         do {
228                                 ret=write (fd, "", 1);
229                         }
230                         while (ret==-1 && errno==EINTR);
231                                 
232                         if(ret==-1) {
233                                 g_critical (G_GNUC_PRETTY_FUNCTION ": shared file [%s] write error: %s", filename, g_strerror (errno));
234                                 close (fd);
235                                 unlink (filename);
236                                 return(-1);
237                         }
238                         
239                         if(created) {
240                                 *created=TRUE;
241                         }
242
243                         /* The contents of the file is set to all
244                          * zero, because it is opened up with lseek,
245                          * so we don't need to do any more
246                          * initialisation here
247                          */
248                 }
249         } else if(fd==-1) {
250                 g_critical (G_GNUC_PRETTY_FUNCTION
251                             ": shared file [%s] open error: %s", filename,
252                             g_strerror (errno));
253                 return(-1);
254         }
255         
256         /* From now on, we need to delete the file before exiting on
257          * error if we created it (ie, if *created==TRUE)
258          */
259
260         /* Use stat to find the file size (instead of hard coding it)
261          * because we can expand the file later if needed (for more
262          * handles or scratch space.)
263          */
264         if(fstat (fd, &statbuf)==-1) {
265                 g_critical (G_GNUC_PRETTY_FUNCTION ": fstat error: %s",
266                             g_strerror (errno));
267                 if(created && *created==TRUE) {
268                         unlink (filename);
269                 }
270                 close (fd);
271                 return(-1);
272         }
273
274         if(statbuf.st_size < wanted_size) {
275                 close (fd);
276                 if(created && *created==TRUE) {
277 #ifdef HAVE_LARGE_FILE_SUPPORT
278                         /* Keep gcc quiet... */
279                         g_critical (G_GNUC_PRETTY_FUNCTION ": shared file [%s] is not big enough! (found %lld, need %d bytes)", filename, statbuf.st_size, wanted_size);
280 #else
281                         g_critical (G_GNUC_PRETTY_FUNCTION ": shared file [%s] is not big enough! (found %ld, need %d bytes)", filename, statbuf.st_size, wanted_size);
282 #endif
283                         unlink (filename);
284                         return(-1);
285                 } else {
286                         /* We didn't create it, so just try opening it again */
287                         goto try_again;
288                 }
289         }
290         
291         return(fd);
292 }
293
294 gpointer _wapi_shm_file_map (_wapi_shm_t type, guint32 segment,
295                              gboolean *created, off_t *size)
296 {
297         gpointer shm_seg;
298         int fd;
299         struct stat statbuf;
300         guchar *filename=_wapi_shm_file (type, segment);
301         
302         fd=_wapi_shm_file_open (filename, type, created);
303         if(fd==-1) {
304                 g_critical (G_GNUC_PRETTY_FUNCTION
305                             ": shared file [%s] open error", filename);
306                 return(NULL);
307         }
308         
309         if(fstat (fd, &statbuf)==-1) {
310                 g_critical (G_GNUC_PRETTY_FUNCTION ": fstat error: %s",
311                             g_strerror (errno));
312                 close (fd);
313                 return(NULL);
314         }
315         if(size) {
316                 *size=statbuf.st_size;
317         }
318         
319         shm_seg=mmap (NULL, statbuf.st_size, PROT_READ|PROT_WRITE, MAP_SHARED,
320                       fd, 0);
321         if(shm_seg==MAP_FAILED) {
322                 g_critical (G_GNUC_PRETTY_FUNCTION ": mmap error: %s",
323                             g_strerror (errno));
324                 close (fd);
325                 return(NULL);
326         }
327                 
328         close (fd);
329         return(shm_seg);
330 }
331
332 /*
333  * _wapi_shm_attach:
334  * @success: Was it a success
335  *
336  * Attach to the shared memory file or create it if it did not
337  * exist. If it was created and daemon was FALSE a new daemon is
338  * forked into existence. Returns the memory area the file was mmapped
339  * to.
340  */
341 gboolean _wapi_shm_attach (struct _WapiHandleShared_list **data,
342                            struct _WapiHandleScratch **scratch)
343 {
344         gboolean data_created=FALSE, scratch_created=FALSE;
345         off_t data_size, scratch_size;
346         int tries, closing_tries=0;
347
348 map_again:
349         *data=_wapi_shm_file_map (WAPI_SHM_DATA, 0, &data_created, &data_size);
350         if(*data==NULL) {
351                 return(FALSE);
352         }
353         
354         *scratch=_wapi_shm_file_map (WAPI_SHM_SCRATCH, 0, &scratch_created,
355                                      &scratch_size);
356         if(*scratch==NULL) {
357                 if(data_created) {
358                         _wapi_shm_destroy ();
359                 }
360                 return(FALSE);
361         }
362
363         if(scratch_created)
364                 (*scratch)->data_len = scratch_size - 
365                                 (sizeof(struct _WapiHandleScratch) - MONO_ZERO_ARRAY_LENGTH);
366
367         if(data_created==FALSE && (*data)->daemon_running==DAEMON_CLOSING) {
368                 /* Daemon is closing down, give it a few ms and try
369                  * again.
370                  */
371                 
372                 struct timespec sleepytime;
373                         
374                 /* Something must have gone wrong, so delete the
375                  * shared segments and try again.
376                  */
377                 _wapi_shm_destroy ();
378                 
379                 munmap (*data, data_size);
380                 munmap (*scratch, scratch_size);
381                 
382                 if(closing_tries++ == 5) {
383                         /* Still can't get going, so bail out */
384                         g_warning ("The handle daemon is stuck closing");
385                         return(FALSE);
386                 }
387                 
388                 sleepytime.tv_sec=0;
389                 sleepytime.tv_nsec=10000000;    /* 10ms */
390                         
391                 nanosleep (&sleepytime, NULL);
392                 goto map_again;
393         }
394         
395         if(data_created==TRUE) {
396 #ifdef VALGRINDING
397                 /* _wapi_daemon_main() does not return */
398                 _wapi_daemon_main (*data, *scratch);
399                         
400                 /* But just in case... */
401                 (*data)->daemon_running=DAEMON_DIED_AT_STARTUP;
402                 exit (-1);
403 #else
404                 pid_t pid;
405                         
406                 pid=fork ();
407                 if(pid==-1) {
408                         g_critical (G_GNUC_PRETTY_FUNCTION ": fork error: %s",
409                                     strerror (errno));
410                         _wapi_shm_destroy ();
411                         return(FALSE);
412                 } else if (pid==0) {
413                         int i;
414                         
415                         /* child */
416                         setsid ();
417                         
418                         /* FIXME: Set process title to something
419                          * informative
420                          */
421
422                         /* Start the daemon with a clean sheet of file
423                          * descriptors
424                          */
425                         for(i=3; i<getdtablesize (); i++) {
426                                 close (i);
427                         }
428                         
429                         /* _wapi_daemon_main() does not return */
430                         _wapi_daemon_main (*data, *scratch);
431                         
432                         /* But just in case... */
433                         (*data)->daemon_running=DAEMON_DIED_AT_STARTUP;
434                         exit (-1);
435                 }
436                 /* parent carries on */
437 #ifdef DEBUG
438                 g_message (G_GNUC_PRETTY_FUNCTION ": Daemon pid %d", pid);
439 #endif
440 #endif /* !VALGRINDING */
441         }
442                 
443         for(tries=0; (*data)->daemon_running==DAEMON_STARTING && tries < 100;
444             tries++) {
445                 /* wait for the daemon to sort itself out.  To be
446                  * completely safe, we should have a timeout before
447                  * giving up.
448                  */
449                 struct timespec sleepytime;
450                         
451                 sleepytime.tv_sec=0;
452                 sleepytime.tv_nsec=10000000;    /* 10ms */
453                         
454                 nanosleep (&sleepytime, NULL);
455         }
456         if(tries==100 && (*data)->daemon_running==DAEMON_STARTING) {
457                 /* Daemon didnt get going */
458                 struct timespec sleepytime;
459                         
460                 /* Something must have gone wrong, so delete the
461                  * shared segments and try again.
462                  */
463                 _wapi_shm_destroy ();
464
465                 /* Daemon didn't get going, give it a few ms and try
466                  * again.
467                  */
468                 
469                 munmap (*data, data_size);
470                 munmap (*scratch, scratch_size);
471                 
472                 if(closing_tries++ == 5) {
473                         /* Still can't get going, so bail out */
474                         g_warning ("The handle daemon didnt start up properly");
475                         return(FALSE);
476                 }
477                 
478                 sleepytime.tv_sec=0;
479                 sleepytime.tv_nsec=10000000;    /* 10ms */
480                         
481                 nanosleep (&sleepytime, NULL);
482                 goto map_again;
483         }
484         
485         if((*data)->daemon_running==DAEMON_DIED_AT_STARTUP) {
486                 /* Oh dear, the daemon had an error starting up */
487                 if(data_created==TRUE) {
488                         _wapi_shm_destroy ();
489                 }
490                 g_warning ("Handle daemon failed to start");
491                 return(FALSE);
492         }
493
494         /* Do some sanity checking on the shared memory we
495          * attached
496          */
497         if(((*data)->daemon_running!=DAEMON_RUNNING) ||
498 #ifdef NEED_LINK_UNLINK
499            (strncmp ((*data)->daemon, "/tmp/mono-handle-daemon-",
500                      24)!=0)) {
501 #else
502            (strncmp ((*data)->daemon+1, "mono-handle-daemon-", 19)!=0)) {
503 #endif
504                 g_warning ("Shared memory sanity check failed.");
505                 g_warning("status: %d", (*data)->daemon_running);
506 #ifdef NEED_LINK_UNLINK
507                 g_warning("daemon: [%s]", (*data)->daemon);
508 #else
509                 g_warning("daemon: [%s]", (*data)->daemon+1);
510 #endif
511                 return(FALSE);
512         }
513                 
514         /* From now on, it's up to the daemon to delete the shared
515          * memory segment
516          */
517         
518         return(TRUE);
519 }
520
521 void _wapi_shm_destroy (void)
522 {
523 #ifndef DISABLE_SHARED_HANDLES
524 #ifdef DEBUG
525         g_message (G_GNUC_PRETTY_FUNCTION ": unlinking shared data");
526 #endif
527         /* Only delete the first segments.  The daemon will destroy
528          * any others when it exits
529          */
530         unlink (_wapi_shm_file (WAPI_SHM_DATA, 0));
531         unlink (_wapi_shm_file (WAPI_SHM_SCRATCH, 0));
532 #endif /* DISABLE_SHARED_HANDLES */
533 }
534