2003-02-25 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 static guchar *shared_file (void)
65 {
66         static guchar *file=NULL;
67         guchar *name, *dir;
68         
69         if(file!=NULL) {
70                 return(file);
71         }
72         
73         /* Change the filename whenever the format of the contents
74          * changes
75          */
76         name=g_strdup_printf ("shared_data-%d", _WAPI_HANDLE_VERSION);
77
78         /* I don't know how nfs affects mmap.  If mmap() of files on
79          * nfs mounts breaks, then there should be an option to set
80          * the directory.
81          */
82         file=g_build_filename (g_get_home_dir (), ".wapi", name, NULL);
83         g_free (name);
84
85         /* No need to check if the dir already exists or check
86          * mkdir() errors, because on any error the open() call will
87          * report the problem.
88          */
89         dir=g_path_get_dirname (file);
90         mkdir (dir, 0755);
91         g_free (dir);
92                 
93         return(file);
94 }
95
96 /*
97  * _wapi_shm_attach:
98  * @success: Was it a success
99  *
100  * Attach to the shared memory file or create it if it did not
101  * exist. If it was created and daemon was FALSE a new daemon is
102  * forked into existence. Returns the memory area the file was mmapped
103  * to.
104  */
105 gpointer _wapi_shm_attach (gboolean *success)
106 {
107         gpointer shm_seg;
108         int fd;
109         gboolean fork_daemon=FALSE;
110         struct stat statbuf;
111         struct _WapiHandleShared_list *data;
112         int tries;
113         int wanted_size=sizeof(struct _WapiHandleShared_list) +
114                 _WAPI_SHM_SCRATCH_SIZE;
115         
116         *success=FALSE;
117
118 try_again:
119         /* No O_CREAT yet, because we need to initialise the file if
120          * we have to create it.
121          */
122         fd=open (shared_file (), O_RDWR, 0600);
123         if(fd==-1 && errno==ENOENT) {
124                 /* OK, its up to us to create it.  O_EXCL to avoid a
125                  * race condition where two processes can
126                  * simultaneously try and create the file
127                  */
128                 fd=open (shared_file (), O_CREAT|O_EXCL|O_RDWR, 0600);
129                 if(fd==-1 && errno==EEXIST) {
130                         /* It's possible that the file was created in
131                          * between finding it didn't exist, and trying
132                          * to create it.  Just try opening it again
133                          */
134                         goto try_again;
135                 } else if (fd==-1) {
136                         g_critical (G_GNUC_PRETTY_FUNCTION
137                                     ": shared file [%s] open error: %s",
138                                     shared_file (), g_strerror (errno));
139                         return(NULL);
140                 } else {
141                         /* We created the file, so we need to expand
142                          * the file and fork the handle daemon too
143                          */
144                         if(lseek (fd, wanted_size, SEEK_SET)==-1) {
145                                 g_critical (G_GNUC_PRETTY_FUNCTION ": shared file [%s] lseek error: %s", shared_file (), g_strerror (errno));
146                                 _wapi_shm_destroy ();
147                                 return(NULL);
148                         }
149                         
150                         if(write (fd, "", 1)==-1) {
151                                 g_critical (G_GNUC_PRETTY_FUNCTION ": shared file [%s] write error: %s", shared_file (), g_strerror (errno));
152                                 _wapi_shm_destroy ();
153                                 return(NULL);
154                         }
155                         
156                         fork_daemon=TRUE;
157
158                         /* The contents of the file is set to all
159                          * zero, because it is opened up with lseek,
160                          * so we don't need to do any more
161                          * initialisation here
162                          */
163                 }
164         } else if(fd==-1) {
165                 g_critical (G_GNUC_PRETTY_FUNCTION
166                             ": shared file [%s] open error: %s",
167                             shared_file (), g_strerror (errno));
168                 return(NULL);
169         } else {
170                 /* We dont need to fork the handle daemon */
171         }
172         
173         /* From now on, we need to delete the file before exiting on
174          * error if we created it (ie, if fork_daemon==TRUE)
175          */
176
177         /* Use stat to find the file size (instead of hard coding it)
178          * so that we can expand the file later if needed (for more
179          * handles or scratch space, though that will require a tweak
180          * to the file format to store the count).
181          */
182         if(fstat (fd, &statbuf)==-1) {
183                 g_critical (G_GNUC_PRETTY_FUNCTION ": fstat error: %s",
184                             g_strerror (errno));
185                 if(fork_daemon==TRUE) {
186                         _wapi_shm_destroy ();
187                 }
188                 return(NULL);
189         }
190
191         if(statbuf.st_size < wanted_size) {
192 #ifdef HAVE_LARGE_FILE_SUPPORT
193                 /* Keep gcc quiet... */
194                 g_critical (G_GNUC_PRETTY_FUNCTION ": shared file [%s] is not big enough! (found %lld, need %d bytes)", shared_file (), statbuf.st_size, wanted_size);
195 #else
196                 g_critical (G_GNUC_PRETTY_FUNCTION ": shared file [%s] is not big enough! (found %ld, need %d bytes)", shared_file (), statbuf.st_size, wanted_size);
197 #endif
198                 if(fork_daemon==TRUE) {
199                         _wapi_shm_destroy ();
200                 }
201                 return(NULL);
202         }
203         
204         shm_seg=mmap (NULL, statbuf.st_size, PROT_READ|PROT_WRITE, MAP_SHARED,
205                       fd, 0);
206         if(shm_seg==MAP_FAILED) {
207                 g_critical (G_GNUC_PRETTY_FUNCTION ": mmap error: %s",
208                             g_strerror (errno));
209                 if(fork_daemon==TRUE) {
210                         _wapi_shm_destroy ();
211                 }
212                 return(NULL);
213         }
214         close (fd);
215                 
216         data=shm_seg;
217
218         if(fork_daemon==TRUE) {
219                 pid_t pid;
220                         
221                 pid=fork ();
222                 if(pid==-1) {
223                         g_critical (G_GNUC_PRETTY_FUNCTION ": fork error: %s",
224                                     strerror (errno));
225                         _wapi_shm_destroy ();
226                         return(NULL);
227                 } else if (pid==0) {
228                         int i;
229                         
230                         /* child */
231                         setsid ();
232                         
233                         /* FIXME: Clean up memory.  We can delete all
234                          * the managed data
235                          */
236                         /* FIXME2: Set process title to something
237                          * informative
238                          */
239
240                         /* Start the daemon with a clean sheet of file
241                          * descriptors
242                          */
243                         for(i=3; i<getdtablesize (); i++) {
244                                 close (i);
245                         }
246                         
247                         /* _wapi_daemon_main() does not return */
248                         _wapi_daemon_main (data);
249                         
250                         /* But just in case... */
251                         data->daemon_running=DAEMON_DIED_AT_STARTUP;
252                         exit (-1);
253                 }
254                 /* parent carries on */
255 #ifdef DEBUG
256                 g_message (G_GNUC_PRETTY_FUNCTION ": Daemon pid %d", pid);
257 #endif
258         } else {
259                 /* Do some sanity checking on the shared memory we
260                  * attached
261                  */
262                 if(!(data->daemon_running==DAEMON_STARTING || 
263                      data->daemon_running==DAEMON_RUNNING ||
264                      data->daemon_running==DAEMON_DIED_AT_STARTUP) ||
265 #ifdef NEED_LINK_UNLINK
266                    (strncmp (data->daemon, "/tmp/mono-handle-daemon-",
267                              24)!=0)) {
268 #else
269                    (strncmp (data->daemon+1, "mono-handle-daemon-", 19)!=0)) {
270 #endif
271                         g_warning ("Shared memory sanity check failed.");
272                         return(NULL);
273                 }
274         }
275                 
276         for(tries=0; data->daemon_running==DAEMON_STARTING && tries < 100;
277             tries++) {
278                 /* wait for the daemon to sort itself out.  To be
279                  * completely safe, we should have a timeout before
280                  * giving up.
281                  */
282                 struct timespec sleepytime;
283                         
284                 sleepytime.tv_sec=0;
285                 sleepytime.tv_nsec=10000000;    /* 10ms */
286                         
287                 nanosleep (&sleepytime, NULL);
288         }
289         if(tries==100 && data->daemon_running==DAEMON_STARTING) {
290                 /* Daemon didnt get going */
291                 if(fork_daemon==TRUE) {
292                         _wapi_shm_destroy ();
293                 }
294                 g_warning ("The handle daemon didnt start up properly");
295                 return(NULL);
296         }
297         
298         if(data->daemon_running==DAEMON_DIED_AT_STARTUP) {
299                 /* Oh dear, the daemon had an error starting up */
300                 if(fork_daemon==TRUE) {
301                         _wapi_shm_destroy ();
302                 }
303                 g_warning ("Handle daemon failed to start");
304                 return(NULL);
305         }
306                 
307         /* From now on, it's up to the daemon to delete the shared
308          * memory segment
309          */
310         
311         *success=TRUE;
312         return(shm_seg);
313 }
314
315 void _wapi_shm_destroy (void)
316 {
317 #ifndef DISABLE_SHARED_HANDLES
318 #ifdef DEBUG
319         g_message (G_GNUC_PRETTY_FUNCTION ": unlinking %s", shared_file ());
320 #endif
321         unlink (shared_file ());
322 #endif /* DISABLE_SHARED_HANDLES */
323 }
324