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