2003-01-12 Alp Toker <alp@atoker.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 try_again:
117         /* No O_CREAT yet, because we need to initialise the file if
118          * we have to create it.
119          */
120         fd=open (shared_file (), O_RDWR, 0600);
121         if(fd==-1 && errno==ENOENT) {
122                 /* OK, its up to us to create it.  O_EXCL to avoid a
123                  * race condition where two processes can
124                  * simultaneously try and create the file
125                  */
126                 fd=open (shared_file (), O_CREAT|O_EXCL|O_RDWR, 0600);
127                 if(fd==-1 && errno==EEXIST) {
128                         /* It's possible that the file was created in
129                          * between finding it didn't exist, and trying
130                          * to create it.  Just try opening it again
131                          */
132                         goto try_again;
133                 } else if (fd==-1) {
134                         g_critical (G_GNUC_PRETTY_FUNCTION
135                                     ": shared file [%s] open error: %s",
136                                     shared_file (), g_strerror (errno));
137                         exit (-1);
138                 } else {
139                         /* We created the file, so we need to expand
140                          * the file and fork the handle daemon too
141                          */
142                         if(lseek (fd, wanted_size, SEEK_SET)==-1) {
143                                 g_critical (G_GNUC_PRETTY_FUNCTION ": shared file [%s] lseek error: %s", shared_file (), g_strerror (errno));
144                                 _wapi_shm_destroy ();
145                                 exit (-1);
146                         }
147                         
148                         if(write (fd, "", 1)==-1) {
149                                 g_critical (G_GNUC_PRETTY_FUNCTION ": shared file [%s] write error: %s", shared_file (), g_strerror (errno));
150                                 _wapi_shm_destroy ();
151                                 exit (-1);
152                         }
153                         
154                         fork_daemon=TRUE;
155
156                         /* The contents of the file is set to all
157                          * zero, because it is opened up with lseek,
158                          * so we don't need to do any more
159                          * initialisation here
160                          */
161                 }
162         } else if(fd==-1) {
163                 g_critical (G_GNUC_PRETTY_FUNCTION
164                             ": shared file [%s] open error: %s",
165                             shared_file (), g_strerror (errno));
166                 exit (-1);
167         } else {
168                 /* We dont need to fork the handle daemon */
169         }
170         
171         /* From now on, we need to delete the file before exiting on
172          * error if we created it (ie, if fork_daemon==TRUE)
173          */
174
175         /* Use stat to find the file size (instead of hard coding it)
176          * so that we can expand the file later if needed (for more
177          * handles or scratch space, though that will require a tweak
178          * to the file format to store the count).
179          */
180         if(fstat (fd, &statbuf)==-1) {
181                 g_critical (G_GNUC_PRETTY_FUNCTION ": fstat error: %s",
182                             g_strerror (errno));
183                 if(fork_daemon==TRUE) {
184                         _wapi_shm_destroy ();
185                 }
186                 exit (-1);
187         }
188
189         if(statbuf.st_size < wanted_size) {
190 #ifdef HAVE_LARGE_FILE_SUPPORT
191                 /* Keep gcc quiet... */
192                 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);
193 #else
194                 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);
195 #endif
196                 if(fork_daemon==TRUE) {
197                         _wapi_shm_destroy ();
198                 }
199                 exit (-1);
200         }
201         
202         shm_seg=mmap (NULL, statbuf.st_size, PROT_READ|PROT_WRITE, MAP_SHARED,
203                       fd, 0);
204         if(shm_seg==MAP_FAILED) {
205                 g_critical (G_GNUC_PRETTY_FUNCTION ": mmap error: %s",
206                             g_strerror (errno));
207                 if(fork_daemon==TRUE) {
208                         _wapi_shm_destroy ();
209                 }
210                 exit (-1);
211         }
212         close (fd);
213                 
214         data=shm_seg;
215
216         if(fork_daemon==TRUE) {
217                 pid_t pid;
218                         
219                 pid=fork ();
220                 if(pid==-1) {
221                         g_critical (G_GNUC_PRETTY_FUNCTION ": fork error: %s",
222                                     strerror (errno));
223                         _wapi_shm_destroy ();
224                         exit (-1);
225                 } else if (pid==0) {
226                         int i;
227                         
228                         /* child */
229                         setsid ();
230                         
231                         /* FIXME: Clean up memory.  We can delete all
232                          * the managed data
233                          */
234                         /* FIXME2: Set process title to something
235                          * informative
236                          */
237
238                         /* Start the daemon with a clean sheet of file
239                          * descriptors
240                          */
241                         for(i=3; i<getdtablesize (); i++) {
242                                 close (i);
243                         }
244                         
245                         /* _wapi_daemon_main() does not return */
246                         _wapi_daemon_main (data);
247                         
248                         /* But just in case... */
249                         data->daemon_running=DAEMON_DIED_AT_STARTUP;
250                         exit (-1);
251                 }
252                 /* parent carries on */
253 #ifdef DEBUG
254                 g_message (G_GNUC_PRETTY_FUNCTION ": Daemon pid %d", pid);
255 #endif
256         } else {
257                 /* Do some sanity checking on the shared memory we
258                  * attached
259                  */
260                 if(!(data->daemon_running==DAEMON_STARTING || 
261                      data->daemon_running==DAEMON_RUNNING ||
262                      data->daemon_running==DAEMON_DIED_AT_STARTUP) ||
263 #ifdef NEED_LINK_UNLINK
264                    (strncmp (data->daemon, "/tmp/mono-handle-daemon-",
265                              24)!=0)) {
266 #else
267                    (strncmp (data->daemon+1, "mono-handle-daemon-", 19)!=0)) {
268 #endif
269                         g_warning ("Shared memory sanity check failed.");
270                         *success=FALSE;
271                         return(NULL);
272                 }
273         }
274                 
275         for(tries=0; data->daemon_running==DAEMON_STARTING && tries < 100;
276             tries++) {
277                 /* wait for the daemon to sort itself out.  To be
278                  * completely safe, we should have a timeout before
279                  * giving up.
280                  */
281                 struct timespec sleepytime;
282                         
283                 sleepytime.tv_sec=0;
284                 sleepytime.tv_nsec=10000000;    /* 10ms */
285                         
286                 nanosleep (&sleepytime, NULL);
287         }
288         if(tries==100 && data->daemon_running==DAEMON_STARTING) {
289                 /* Daemon didnt get going */
290                 if(fork_daemon==TRUE) {
291                         _wapi_shm_destroy ();
292                 }
293                 g_warning ("The handle daemon didnt start up properly");
294                 *success=FALSE;
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                 *success=FALSE;
305                 return(NULL);
306         }
307                 
308         /* From now on, it's up to the daemon to delete the shared
309          * memory segment
310          */
311         
312         *success=TRUE;
313         return(shm_seg);
314 }
315
316 void _wapi_shm_destroy (void)
317 {
318 #ifndef DISABLE_SHARED_HANDLES
319 #ifdef DEBUG
320         g_message (G_GNUC_PRETTY_FUNCTION ": unlinking %s", shared_file ());
321 #endif
322         unlink (shared_file ());
323 #endif /* DISABLE_SHARED_HANDLES */
324 }
325