xref: /dragonfly/lib/libc/sysvipc/shm.c (revision 3f7b72606e8fadb0b2e9e32302c89298c107534b)
1 /*
2  * Copyright (c) 2013 Larisa Grigore <larisagrigore@gmail.com>.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  * 3. All advertising materials mentioning features or use of this software
13  *    must display the following acknowledgement:
14  *        This product includes software developed by Adam Glass and Charles
15  *        Hannum.
16  * 4. The names of the authors may not be used to endorse or promote products
17  *    derived from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include "namespace.h"
32 #include <sys/param.h>
33 #include <sys/queue.h>
34 #include <sys/mman.h>
35 #include <sys/shm.h>
36 #include <stdint.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <err.h>
42 #include <pthread.h>
43 #include <unistd.h>
44 #include "un-namespace.h"
45 
46 #include "sysvipc_lock.h"
47 #include "sysvipc_ipc.h"
48 #include "sysvipc_sockets.h"
49 #include "sysvipc_shm.h"
50 #include "sysvipc_hash.h"
51 
52 #define SYSV_MUTEX_LOCK(x)    if (__isthreaded) _pthread_mutex_lock(x)
53 #define SYSV_MUTEX_UNLOCK(x)  if (__isthreaded) _pthread_mutex_unlock(x)
54 #define SYSV_MUTEX_DESTROY(x) if (__isthreaded) _pthread_mutex_destroy(x)
55 
56 struct hashtable *shmres = NULL;
57 struct hashtable *shmaddrs = NULL;
58 pthread_mutex_t lock_resources = PTHREAD_MUTEX_INITIALIZER;
59 
60 /* File descriptor used to communicate with the daemon. */
61 extern int daemon_fd;
62 /* Structure used to save semaphore operation with SEMUNDO flag. */
63 extern struct sem_undo *undos;
64 
65 static int
shminit(void)66 shminit(void)
67 {
68           if (shmres) {
69                     errno = EPERM;
70                     return (-1);
71           }
72 
73           shmres = _hash_init(MAXSIZE);
74           if (!shmres)
75                     goto out_resources;
76 
77           shmaddrs = _hash_init(MAXSIZE);
78           if (!shmaddrs)
79                     goto out_addrs;
80 
81           return 0;
82 
83 out_addrs:
84           _hash_destroy(shmres);
85 out_resources:
86           return -1;
87 }
88 
89 /*static int
90 shmexit(void)
91 {
92           if (!shmres)
93                     return -EPERM;
94 
95           _hash_destroy(shmres);
96           _hash_destroy(shmaddrs);
97           SYSV_MUTEX_DESTROY(lock_resources);
98 
99           return 0;
100 }*/
101 
102 /* Init sysv ipc resources and those used for shared memory. */
103 static int
shmcheck(void)104 shmcheck(void)
105 {
106           int ret;
107 
108           /* Init sysv resources. */
109           if ((ret = sysvinit()) != 0)
110                     return (ret);
111           /* Init resorces used for shared memory. */
112           if ((ret = shminit()) < 0)
113                     return (ret);
114           return (0);
115 }
116 
117 /* Check if sysv ipc resources are initialized. */
118 static int
is_shm_started(void)119 is_shm_started(void)
120 {
121           if (!is_sysvinit())
122                     return (0);
123           if (!shmres)
124                     return (0);
125           return (1);
126 }
127 
128 /* OBS: It is used only a rwlock for both hashtables and
129  * socket. I've made that choice because is I considered to
130  * be much expensive to acquire/release more than one especially
131  * as the daemon is not multithreading.
132  */
133 
134 /* This function has another parameter apart from shmget.
135  * The parameter has information about the type of sysv
136  * ipc resource (shm, sem, msg, undo).
137  * The undo segment is used for sem ops with UNDO flag set.
138  */
139 int
_shmget(key_t key,size_t size,int shmflg,int type)140 _shmget(key_t key, size_t size, int shmflg, int type)
141 {
142           struct shmget_msg msg;
143           struct shm_data *data;
144           int shmid, fd;
145           int flags;
146 
147           SYSV_MUTEX_LOCK(&lock_resources);
148           if (shmcheck() < 0) {
149                     sysv_print_err("init sysv ipc\n");
150                     goto done;
151           }
152 
153           msg.key = key;
154           msg.size = size;
155           msg.shmflg = shmflg;
156           msg.type = type;
157 
158           send_message(daemon_fd, type, (char *)&msg, sizeof(msg));
159 
160           /* Accept a file installed by the daemon.
161            * The file is used as shared memory. */
162           fd = receive_fd(daemon_fd);
163           if (fd < 0) {
164                     shmid = -1;
165                     goto done;
166           }
167 
168           flags = _fcntl(fd, F_GETFD, 0);
169           if (_fcntl(fd, F_SETFD, flags & FD_CLOEXEC) == -1) {
170                     sysv_print_err("fcntl error\n");
171                     shmid = -1;
172                     goto done;
173           }
174 
175           /* Receive the resource id or error. */
176           receive_message(daemon_fd, (char *)&shmid, sizeof(shmid));
177 
178           if (shmid < 0) {
179                     errno = -shmid;
180                     shmid = -1;
181                     goto done;
182           }
183 
184           /* Do we already have an entry for this resource? */
185           data = _hash_lookup(shmres, shmid);
186           if (data)
187                     goto done;
188 
189           /* If not, add necessary data about it. */
190           data = malloc(sizeof(struct shm_data));
191           data->fd = fd;
192           data->size = size;
193           data->shmid = shmid;
194           data->type = type;
195           data->used = 0;
196           data->removed = 0;
197           data->access = 0; /* Used for sems. */
198 
199           /* Insert data in hashtable using the shmid. */
200           _hash_insert(shmres, shmid, data);
201 done:
202           SYSV_MUTEX_UNLOCK(&lock_resources);
203           return (shmid);
204 }
205 
206 int
sysvipc_shmget(key_t key,size_t size,int shmflg)207 sysvipc_shmget(key_t key, size_t size, int shmflg)
208 {
209           return (_shmget(key, size, shmflg, SHMGET));
210 }
211 
212 void *
sysvipc_shmat(int shmid,const void * shmaddr,int shmflg)213 sysvipc_shmat(int shmid, const void *shmaddr, int shmflg)
214 {
215           struct shmat_msg msg;
216           void *addr = NULL;
217           int error;
218           int flags, prot;
219           size_t size;
220           struct shm_data *data;
221 
222           SYSV_MUTEX_LOCK(&lock_resources);
223           if (!is_shm_started()) {
224                     errno = EINVAL;
225                     goto done;
226           }
227 
228           /* Get data using shmid. */
229           data = _hash_lookup(shmres, shmid);
230           if (data == NULL) {
231                     errno = EINVAL;
232                     goto done;
233           }
234 
235           size = round_page(data->size);
236 
237 #ifdef VM_PROT_READ_IS_EXEC
238           prot = PROT_READ | PROT_EXECUTE;
239 #else
240           prot = PROT_READ;
241 #endif
242           if ((shmflg & SHM_RDONLY) == 0)
243                     prot |= PROT_WRITE;
244 
245           flags = MAP_SHARED;
246           if (shmaddr) {
247                     if (shmflg & SHM_RND) {
248                               addr = (void *)(rounddown2((uintptr_t)shmaddr, SHMLBA));
249                     } else if (((uintptr_t)shmaddr & (SHMLBA-1)) == 0) {
250                               addr = __DECONST(void *, shmaddr);
251                     } else {
252                               errno = EINVAL;
253                               goto done;
254                     }
255           }
256 
257           msg.shmid = shmid;
258           msg.shmaddr = shmaddr;
259           msg.shmflg = shmflg;
260           msg.size = data->size; /* For undo segment. */
261 
262           send_message(daemon_fd, SHMAT, (char *)&msg, sizeof(msg));
263           receive_message(daemon_fd, (char *)&error, sizeof(error));
264           if (error) {
265                     errno = error;
266                     goto done;
267           }
268 
269           addr = mmap(addr, size, prot, flags, data->fd, 0);
270           if (!addr) {
271                     sysv_print_err("mmap\n");
272                     /* Detach ourselves from the segment. */
273                     send_message(daemon_fd, SHMDT, (char *)&shmid, sizeof(shmid));
274                     goto done;
275           }
276 
277           /* Necessary for SEMGET, MSGGET, UNDOGET. */
278           data->internal = addr;
279 
280           /* Save the mapped address for munmap call. */
281           _hash_insert(shmaddrs, (u_long)addr, data);
282 done:
283           SYSV_MUTEX_UNLOCK(&lock_resources);
284           return (addr);
285 }
286 
287 /* Remove a sysv ipc resource. */
288 static
shmremove(int shmid)289 void shmremove(int shmid)
290 {
291           struct shm_data *data;
292           data = _hash_remove(shmres, shmid);
293 
294           //TODO nu trebuie demapat?
295           _close(data->fd);
296           free(data);
297           data = NULL;
298 }
299 
300 int
sysvipc_shmctl(int shmid,int cmd,struct shmid_ds * buf)301 sysvipc_shmctl(int shmid, int cmd, struct shmid_ds *buf)
302 {
303           int size, ret;
304           struct shmctl_msg *msg;
305 
306 /*        if (cmd == IPC_SET)
307                     size = sizeof(struct shmctl_msg) + sizeof(struct shmid_ds);
308           else
309                     size = sizeof(struct shmctl_msg);
310 */
311           SYSV_MUTEX_LOCK(&lock_resources);
312 
313           ret = -1;
314 
315           if (!is_shm_started()) {
316                     errno = EINVAL;
317                     goto done;
318           }
319 
320           size = sizeof(struct shmctl_msg);
321           msg = malloc(size);
322           msg->shmid = shmid;
323           msg->cmd = cmd;
324 
325           if (cmd == IPC_SET)
326                     msg->buf = *buf;
327 
328           send_message(daemon_fd, SHMCTL, (char *)msg, sizeof(*msg));
329 
330           receive_message(daemon_fd, (char *)&ret, sizeof(ret));
331 
332           /* Get data in IPC_STAT case. */
333           if (ret == 0 && cmd == IPC_STAT)
334                     receive_message(daemon_fd, (char *)buf, sizeof(*buf));
335 
336           /* Free all resources specific to a shmid in IPC_RMID case. */
337           if (ret == 0 && cmd == IPC_RMID)
338                     shmremove(shmid);
339 
340           errno = ret;
341 done:
342           SYSV_MUTEX_UNLOCK(&lock_resources);
343           return (ret == 0 ? 0 : -1);
344 }
345 
346 /* Functionality of shmdt with the possibility to inform or not
347  * the daemon.
348  * Inform the daemon when shmdt is called and not when an error
349  * occurs and the daemon doesn't know that the process is attaced.
350  */
351 static int
_shmdt(const void * shmaddr,int send_to_daemon)352 _shmdt(const void *shmaddr, int send_to_daemon)
353 {
354           int ret;
355           size_t size;
356           struct shm_data *data;
357 
358           ret = -1;
359 
360           SYSV_MUTEX_LOCK(&lock_resources);
361           if (!is_shm_started()) {
362                     errno = EINVAL;
363                     goto done;
364           }
365 
366           /* Verify if shmaddr was returned from a shmat call. */
367           data = _hash_remove(shmaddrs, (u_long)shmaddr);
368           if (data == NULL) {
369                     errno = EINVAL;
370                     goto done;
371           }
372 
373           size = round_page(data->size);
374 
375           ret = munmap(__DECONST(void *, shmaddr), size);
376           if (ret)
377                     goto done;
378 
379           if (send_to_daemon)
380                     send_message(daemon_fd, SHMDT, (char *)&data->shmid, sizeof(int));
381 
382           shmaddr = NULL;
383           free(data);
384           data = NULL;
385 done:
386           SYSV_MUTEX_UNLOCK(&lock_resources);
387           return (ret);
388 }
389 
390 int
sysvipc_shmdt(const void * shmaddr)391 sysvipc_shmdt(const void *shmaddr)
392 {
393           return (_shmdt(shmaddr, 1));
394 }
395 
396 void
shmchild(void)397 shmchild(void)
398 {
399           int i;
400           struct entries_list *list;
401           struct hashentry *tmp, *ttmp;
402           struct shmat_msg msg;
403           struct shm_data *data;
404           int error;
405 
406 /* OBS: no locking is necessary because this function is called
407  * after the child is created and at that moment only one thread
408  * exists in the process.
409  */
410           for (i=0; i<get_hash_size(MAXSIZE); i++) {
411                     list = &shmaddrs->entries[i];
412                     if (LIST_EMPTY(list))
413                               continue;
414                     LIST_FOREACH_MUTABLE(tmp, list, entry_link, ttmp) {
415                               data = (struct shm_data*)tmp->value;
416                               /* Inform daemon that we are attached. */
417 
418                               if (data->type == UNDOGET) {
419                                         continue;
420                               }
421 
422                               msg.shmid = data->shmid;
423                               msg.shmaddr = data->internal;
424                               msg.shmflg = 0; /* This is enough at this moment. */
425                               msg.size = data->size;
426                               /* Last field is not necessary because it is used only
427                                * for undo segments.
428                                */
429 
430                               send_message(daemon_fd, SHMAT, (char *)&msg, sizeof(msg));
431                               receive_message(daemon_fd, (char *)&error, sizeof(error));
432 
433                               /* If the daemon returned error munmap the region. */
434                               if (error) {
435                                         errno = error;
436                                         _shmdt(data->internal, 0);
437                                         shmremove(data->shmid);
438                                         sysv_print_err(" %d shmchild\n", error);
439                                         sleep(20);
440                               }
441 
442                     }
443           }
444 
445           /* Remove semundo structures. Those are specific only for the parent.
446            * The child must create for itself a new one.
447            */
448           data = _hash_remove(shmaddrs, (u_long)undos);
449           if (undos) {
450                     munmap(undos, round_page(data->size));
451                     undos = NULL;
452           }
453 }
454 
455 /* Called each time a thread tries to access the sem/msg.
456  * It is used in order to protect data against its removal
457  * by another thread.
458  */
459 struct shm_data *
get_shmdata(int id,int to_remove,int shm_access)460 get_shmdata(int id, int to_remove, int shm_access)
461 {
462           struct shm_data *data = NULL;
463 
464           SYSV_MUTEX_LOCK(&lock_resources);
465           if (!is_shm_started()) {
466                     errno = EINVAL;
467                     goto done;
468           }
469 
470           data = _hash_lookup(shmres, id);
471           if (!data) {
472                     errno = EINVAL;
473                     goto done;
474           }
475 
476           /* If segment was removed by another thread we can't use it. */
477           if (data->removed) {
478                     sysv_print("segment already removed\n");
479                     errno = EINVAL;
480                     data = NULL;
481                     goto done;
482           }
483 
484           /* Mark for removal. Inform the other threads from the
485            * same address space. */
486           if (to_remove) {
487                     sysv_print("segment is removed\n");
488                     data->removed = to_remove; /* 1 if it is removed by
489                     the current process and 2 if it was removed by
490                     another one. */
491 
492                     /* No need for any rights check because this is
493                      * done by daemon if this is the process that removes
494                      * the sem/msg.
495                      * If not, there is no need for any right to clean
496                      * internal resources.
497                      */
498                     goto done2;
499           }
500 
501           /* Avoid segmentation fault if the memory zone
502            * is accessed without necessary permissions
503            * (it was mapped according to them).
504            */
505           if (!(data->access & shm_access)) {
506 #if 0
507                     sysv_print("no access rights has %o and wants %o\n",
508                                         data->access, shm_access);
509                     errno = EACCES;
510                     data = NULL;
511                     goto done;
512 #endif
513           }
514 
515 done2:
516           data->used++;
517 done:
518           SYSV_MUTEX_UNLOCK(&lock_resources);
519           return (data);
520 }
521 
522 /* Set the shm_access type (IPC_R, IPC_W) for sem/msg. */
523 int
set_shmdata_access(int id,int shm_access)524 set_shmdata_access(int id, int shm_access)
525 {
526           struct shm_data *data;
527           int ret = -1;
528 
529           SYSV_MUTEX_LOCK(&lock_resources);
530           if (!is_shm_started()) {
531                     errno = EINVAL;
532                     goto done;
533           }
534 
535           data = _hash_lookup(shmres, id);
536           if (!data) {
537                     errno = EINVAL;
538                     goto done;
539           }
540 
541           /* If segment was removed by another thread we can't use it. */
542           if (data->removed) {
543                     errno = EINVAL;
544                     goto done;
545           }
546 
547           data->access = shm_access;
548           ret = 0;
549 done:
550           SYSV_MUTEX_UNLOCK(&lock_resources);
551 
552           return (ret);
553 }
554