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