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