xref: /dragonfly/usr.sbin/sysvipcd/shmd.c (revision 1b11ea06a427d663e12e6a7a5083df6209e4cda2)
1 /*
2  * Copyright (c) 1994 Adam Glass and Charles Hannum.  All rights reserved.
3  * Copyright (c) 2013 Larisa Grigore <larisagrigore@gmail.com>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *        This product includes software developed by Adam Glass and Charles
16  *        Hannum.
17  * 4. The names of the authors may not be used to endorse or promote products
18  *    derived from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
21  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23  * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <sys/param.h>
35 #include <sys/vmmeter.h>
36 #include <sys/wait.h>
37 #include <sys/queue.h>
38 #include <sys/mman.h>
39 
40 #include <err.h>
41 #include <errno.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 #include <fcntl.h>
47 #include <time.h>
48 
49 #include "limits.h"
50 #include "perm.h"
51 #include "utilsd.h"
52 
53 #include "shmd.h"
54 #include "sysvipc_hash.h"
55 #include "sysvipc_sockets.h"
56 
57 static struct       shminfo shminfo = {
58 //        0,
59           SHMMIN,
60           SHMMNI,
61           SHMSEG
62 //        0
63 };
64 
65 /* Shared memory.*/
66 static int shm_last_free, shm_committed, shmalloced;
67 int shm_nused;
68 static struct shmid_ds        *shmsegs;
69 
70 /* Message queues.*/
71 extern struct msginfo msginfo;
72 
73 extern struct hashtable *clientshash;
74 
75 static int
create_sysv_file(struct shmget_msg * msg,size_t size,struct shmid_ds * shmseg)76 create_sysv_file(struct shmget_msg *msg, size_t size,
77                     struct shmid_ds *shmseg) {
78           char filename[FILENAME_MAX];
79           int fd;
80           void *addr;
81           int nsems;
82           struct semid_pool *sems;
83           struct msqid_pool *msgq;
84           key_t key = msg->key;
85           int i;
86 
87           errno = 0;
88 
89           switch(msg->type) {
90                     case SHMGET:
91                               sprintf(filename, "%s/%s_%ld", DIRPATH, SHM_NAME, key);
92                               break;
93                     case SEMGET:
94                               sprintf(filename, "%s/%s_%ld", DIRPATH, SEM_NAME, key);
95                               break;
96                     case MSGGET:
97                               sprintf(filename, "%s/%s_%ld", DIRPATH, MSG_NAME, key);
98                               break;
99                     case UNDOGET:
100                               sprintf(filename, "%s/%s_%ld", DIRPATH, UNDO_NAME, key);
101                               break;
102                     default:
103                               return (-EINVAL);
104           }
105 
106           fd = open(filename, O_RDWR | O_CREAT, 0666);
107           if (fd < 0) {
108                     sysvd_print_err("create sysv file: open\n");
109                     goto out;
110           }
111 
112           ftruncate(fd, size);
113 
114           switch(msg->type) {
115                     case SEMGET:
116                               /* Map the semaphore to initialize it. */
117                               addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
118                               //TODO modify 0 for more sems on a page
119                               if (!addr) {
120                                         sysvd_print_err("create sysv file: mmap");
121                                         goto error;
122                               }
123 
124                               /* There is no need for any lock because all clients
125                                * that try to access this segment are blocked until
126                                * it becames ~SHMSEG_REMOVED. */
127                               sems = (struct semid_pool*)addr;
128                               nsems = (msg->size - sizeof(struct semid_pool)) /
129                                         sizeof(struct sem);
130                               sysvd_print("allocate %d sems\n", nsems);
131 
132                               /* Init lock. */
133 #ifdef SYSV_RWLOCK
134                               sysv_rwlock_init(&sems->rwlock);
135 #else
136                               sysv_mutex_init(&sems->mutex);
137 #endif
138                               /* Credentials are kept in shmid_ds structure. */
139                               sems->ds.sem_perm.seq = shmseg->shm_perm.seq;
140                               sems->ds.sem_nsems = nsems;
141                               sems->ds.sem_otime = 0;
142                               //sems->ds.sem_ctime = time(NULL);
143                               //semtot += nsems;
144                               sems->gen = 0;
145 
146                               /* Initialize each sem. */
147                               memset(sems->ds.sem_base, 0, nsems + sizeof(struct sem));
148 
149 #ifdef SYSV_SEMS
150                               int l;
151                               for (l=0; l < nsems; l++)
152                                         sysv_mutex_init(&sems->ds.sem_base[l].sem_mutex);
153 #endif
154 
155                               munmap(addr, size);
156 
157                               break;
158                     case MSGGET:
159                               /* Map the message queue to initialize it. */
160                               addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
161                               if (!addr) {
162                                         sysvd_print_err("create sysv file: mmap");
163                                         goto error;
164                               }
165 
166                               /* There is no need for any lock because all clients
167                                * that try to access this segment are blocked until
168                                * it becames ~SHMSEG_REMOVED. */
169                               msgq = (struct msqid_pool*)addr; //TODO
170                               /*sysvd_print("Attention!!! : %ld %ld %ld %ld\n",
171                                                   sizeof(struct msqid_pool),
172                                                   sizeof(msgq->msghdrs),
173                                                   sizeof(msgq->msgmaps),
174                                                   sizeof(msgq->msgpool));*/
175 
176                               /* Init lock. */
177 #ifdef SYSV_RWLOCK
178                               sysv_rwlock_init(&msgq->rwlock);
179 #else
180                               sysv_mutex_init(&msgq->mutex);
181 #endif
182                               /* In kernel implementation, this was done globally. */
183                               for (i = 0; i < msginfo.msgseg; i++) {
184                                         if (i > 0)
185                                                   msgq->msgmaps[i-1].next = i;
186                                         msgq->msgmaps[i].next = -1;   /* implies entry is available */
187                               }
188                               msgq->free_msgmaps = 0;
189                               msgq->nfree_msgmaps = msginfo.msgseg;
190 
191                               for (i = 0; i < msginfo.msgtql; i++) {
192                                         msgq->msghdrs[i].msg_type = 0;
193                                         if (i > 0)
194                                                   msgq->msghdrs[i-1].msg_next = i;
195                                         msgq->msghdrs[i].msg_next = -1;
196                               }
197                               msgq->free_msghdrs = 0;
198 
199                               /* Credentials are kept in shmid_ds structure. */
200                               msgq->ds.msg_perm.seq = shmseg->shm_perm.seq;
201                               msgq->ds.first.msg_first_index = -1;
202                               msgq->ds.last.msg_last_index = -1;
203                               msgq->ds.msg_cbytes = 0;
204                               msgq->ds.msg_qnum = 0;
205                               msgq->ds.msg_qbytes = msginfo.msgmnb;
206                               msgq->ds.msg_lspid = 0;
207                               msgq->ds.msg_lrpid = 0;
208                               msgq->ds.msg_stime = 0;
209                               msgq->ds.msg_rtime = 0;
210 
211                               munmap(addr, size);
212 
213                               break;
214                     default:
215                               break;
216           }
217 
218           unlink(filename);
219 out:
220           return (fd);
221 error:
222           close(fd);
223           return (-1);
224 }
225 
226 /* Install for the client the file corresponding to fd. */
227 static int
install_fd_client(pid_t pid,int fd)228 install_fd_client(pid_t pid, int fd) {
229           int ret;
230           struct client *cl = _hash_lookup(clientshash, pid);
231           if (!cl) {
232                     sysvd_print_err("no client entry for pid = %d\n", pid);
233                     return (-1);
234           }
235 
236           ret = send_fd(cl->sock, fd);
237           if (ret < 0) {
238                     sysvd_print_err("can not send fd to client %d\n", pid);
239                     return (-1);
240           }
241 
242           return (0);
243 }
244 
245 static int
shm_find_segment_by_key(key_t key)246 shm_find_segment_by_key(key_t key)
247 {
248           int i;
249 
250           for (i = 0; i < shmalloced; i++) {
251                     if ((shmsegs[i].shm_perm.mode & SHMSEG_ALLOCATED) &&
252                                         shmsegs[i].shm_perm.key == key)
253                               return (i);
254           }
255           return (-1);
256 }
257 
258 static struct shmid_ds *
shm_find_segment_by_shmid(int shmid)259 shm_find_segment_by_shmid(int shmid)
260 {
261           int segnum;
262           struct shmid_ds *shmseg;
263 
264           segnum = IPCID_TO_IX(shmid);
265           if (segnum < 0 || segnum >= shmalloced) {
266                     sysvd_print_err("segnum out of range\n");
267                     return (NULL);
268           }
269 
270           shmseg = &shmsegs[segnum];
271           if ((shmseg->shm_perm.mode & (SHMSEG_ALLOCATED | SHMSEG_REMOVED))
272                               != SHMSEG_ALLOCATED ||
273                               shmseg->shm_perm.seq != IPCID_TO_SEQ(shmid)) {
274                     sysvd_print("segment most probably removed\n");
275                     return (NULL);
276           }
277           return (shmseg);
278 }
279 
280 /* Remove a shared memory segment. */
281 static void
shm_deallocate_segment(int segnum)282 shm_deallocate_segment(int segnum)
283 {
284           size_t size;
285           struct shmid_ds *shmseg = &shmsegs[segnum];
286           struct shm_handle *internal =
287                     (struct shm_handle *)shmseg->shm_internal;
288 //        int nsems;
289 
290           sysvd_print("deallocate segment %d\n", segnum);
291 
292           size = round_page(shmseg->shm_segsz);
293 
294 #if 0
295           if (internal->type == SEMGET) {
296                               nsems = (shmseg->shm_segsz - sizeof(struct semid_pool)) /
297                                         sizeof(struct sem);
298                               semtot -= nsems;
299                               sysvd_print("freed %d sems\n", nsems);
300           }
301 #endif
302 
303           /* Close the corresponding file. */
304           close(internal->fd);
305 
306           /* Free other resources. */
307           free(shmseg->shm_internal);
308           shmseg->shm_internal = NULL;
309           shm_committed -= btoc(size);
310           shm_nused--;
311 
312           shmseg->shm_perm.mode = SHMSEG_FREE;
313 }
314 
315 static void *map_seg(int);
316 static int munmap_seg(int, void *);
317 
318 /* In sem and msg case notify the other processes that use it. */
319 static void
mark_segment_removed(int shmid,int type)320 mark_segment_removed(int shmid, int type) {
321           struct semid_pool *semaptr;
322           struct msqid_pool *msgq;
323 
324           switch (type) {
325                     case SEMGET:
326                               semaptr = (struct semid_pool *)map_seg(shmid);
327 #ifdef SYSV_RWLOCK
328                               sysv_rwlock_wrlock(&semaptr->rwlock);
329 #else
330                               sysv_mutex_lock(&semaptr->mutex);
331 #endif
332                               semaptr->gen = -1;
333 
334                               /* It is not necessary to wake waiting threads because
335                                * if the group of semaphores is acquired by a thread,
336                                * the smaptr lock is held, so it is impossible to
337                                * reach this point.
338                                */
339 #ifdef SYSV_RWLOCK
340                               sysv_rwlock_unlock(&semaptr->rwlock);
341 #else
342                               sysv_mutex_unlock(&semaptr->mutex);
343 #endif
344                               munmap_seg(shmid, semaptr);
345                               break;
346                     case MSGGET :
347                               msgq = (struct msqid_pool*)map_seg(shmid);
348 #ifdef SYSV_RWLOCK
349                               sysv_rwlock_wrlock(&msgq->rwlock);
350 #else
351                               sysv_mutex_lock(&msgq->mutex);
352 #endif
353                               msgq->gen = -1;
354 
355 #ifdef SYSV_RWLOCK
356                               sysv_rwlock_unlock(&msgq->rwlock);
357 #else
358                               sysv_mutex_unlock(&msgq->mutex);
359 #endif
360                               munmap_seg(shmid, msgq);
361                               break;
362                     default:
363                               break;
364           }
365 }
366 
367 /* Get the id of an existing shared memory segment. */
368 static int
shmget_existing(struct shmget_msg * shmget_msg,int mode,int segnum,struct cmsgcred * cred)369 shmget_existing(struct shmget_msg *shmget_msg, int mode,
370                     int segnum, struct cmsgcred *cred)
371 {
372           struct shmid_ds *shmseg;
373           int error;
374 
375           shmseg = &shmsegs[segnum];
376           if (shmseg->shm_perm.mode & SHMSEG_REMOVED) {
377                     /*
378                      * This segment is in the process of being allocated.  Wait
379                      * until it's done, and look the key up again (in case the
380                      * allocation failed or it was freed).
381                      */
382                     //TODO Maybe it will be necessary if the daemon is multithreading
383                     /*shmseg->shm_perm.mode |= SHMSEG_WANTED;
384                       error = tsleep((caddr_t)shmseg, PCATCH, "shmget", 0);
385                       if (error)
386                       return error;
387                       return EAGAIN;*/
388           }
389           if ((shmget_msg->shmflg & (IPC_CREAT | IPC_EXCL)) == (IPC_CREAT | IPC_EXCL))
390                     return (-EEXIST);
391           error = ipcperm(cred, &shmseg->shm_perm, mode);
392           if (error)
393                     return (-error);
394           if (shmget_msg->size && (shmget_msg->size > shmseg->shm_segsz))
395                     return (-EINVAL);
396           return (IXSEQ_TO_IPCID(segnum, shmseg->shm_perm));
397 }
398 
399 /* Create a shared memory segment and return the id. */
400 static int
shmget_allocate_segment(pid_t pid,struct shmget_msg * shmget_msg,int mode,struct cmsgcred * cred)401 shmget_allocate_segment(pid_t pid, struct shmget_msg *shmget_msg,
402                     int mode, struct cmsgcred *cred)
403 {
404           int i, segnum, shmid;
405           size_t size;
406           struct shmid_ds *shmseg;
407           struct shm_handle *handle;
408 #if 0
409           /* It is possible after a process calls exec().
410            * We don't create another segment but return the old one
411            * with all information.
412            * This segment is destroyed only when process dies.
413            * */
414           if (shmget_msg->type == UNDOGET) {
415                     struct client *cl= _hash_lookup(clientshash, pid);
416                     if (cl->undoid != -1)
417                               return cl->undoid;
418           }
419 #endif
420           if ((long)shmget_msg->size < shminfo.shmmin)
421                               //|| (long)shmget_msg->size > shminfo.shmmax)
422                               /* There is no need to check the max limit,
423                                * the operating system do this for us.
424                                */
425                     return (-EINVAL);
426           if (shm_nused >= shminfo.shmmni) /* any shmids left? */
427                     return (-ENOSPC);
428 
429           /* Compute the size of the segment. */
430           size = round_page(shmget_msg->size);
431 
432           /* Find a free entry in the shmsegs vector. */
433           if (shm_last_free < 0) {
434                     //        shmrealloc();       /* maybe expand the shmsegs[] array */
435                     for (i = 0; i < shmalloced; i++) {
436                               if (shmsegs[i].shm_perm.mode & SHMSEG_FREE)
437                                         break;
438                     }
439                     if (i == shmalloced) {
440                               sysvd_print("i == shmalloced\n");
441                               return (-ENOSPC);
442                     }
443                     segnum = i;
444           } else  {
445                     segnum = shm_last_free;
446                     shm_last_free = -1;
447           }
448           shmseg = &shmsegs[segnum];
449           /*
450            * In case we sleep in malloc(), mark the segment present but deleted
451            * so that noone else tries to create the same key.
452            */
453           shmseg->shm_perm.mode = SHMSEG_ALLOCATED | SHMSEG_REMOVED;
454           shmseg->shm_perm.key = shmget_msg->key;
455           shmseg->shm_perm.seq = (shmseg->shm_perm.seq + 1) & 0x7fff;
456 
457           /* Create the file for the shared memory segment. */
458           handle = shmseg->shm_internal = malloc(sizeof(struct shm_handle));
459           handle->type = shmget_msg->type;
460           handle->fd = create_sysv_file(shmget_msg, size, shmseg);
461           if (handle->fd == -1) {
462                     free(handle);
463                     handle = NULL;
464                     shmseg->shm_perm.mode = SHMSEG_FREE;
465                     shm_last_free = segnum;
466                     errno = -ENFILE;
467                     return (-1);
468           }
469 
470           LIST_INIT(&handle->attached_list);
471 
472           if (handle->fd < 0) {
473                     free(shmseg->shm_internal);
474                     shmseg->shm_internal = NULL;
475                     shm_last_free = segnum;
476                     shmseg->shm_perm.mode = SHMSEG_FREE;
477                     return (-errno);
478           }
479 
480           /* Get the id. */
481           shmid = IXSEQ_TO_IPCID(segnum, shmseg->shm_perm);
482 
483           shmseg->shm_perm.cuid = shmseg->shm_perm.uid = cred->cmcred_euid;
484           shmseg->shm_perm.cgid = shmseg->shm_perm.gid = cred->cmcred_gid;
485           shmseg->shm_perm.mode = (shmseg->shm_perm.mode & SHMSEG_WANTED) |
486                     (mode & ACCESSPERMS) | SHMSEG_ALLOCATED;
487 
488           shmseg->shm_cpid = pid;
489           shmseg->shm_lpid = shmseg->shm_nattch = 0;
490           shmseg->shm_atime = shmseg->shm_dtime = 0;
491           shmseg->shm_ctime = time(NULL);
492 
493           shmseg->shm_segsz = shmget_msg->size;
494           shm_committed += btoc(size);
495           shm_nused++;
496 
497           if (shmseg->shm_perm.mode & SHMSEG_WANTED) {
498                     /*
499                      * Somebody else wanted this key while we were asleep.  Wake
500                      * them up now.
501                      */
502                     shmseg->shm_perm.mode &= ~SHMSEG_WANTED;
503                     //TODO multithreading
504                     //wakeup((caddr_t)shmseg);
505           }
506           shmseg->shm_perm.mode &= ~SHMSEG_REMOVED;
507 
508           if (shmget_msg->type == UNDOGET) {
509                     /* The file is used by daemon when clients terminates
510                      * and sem_undo resources must be cleaned.
511                      */
512                     struct client *cl= _hash_lookup(clientshash, pid);
513                     cl->undoid = shmid;
514           }
515 
516           return (shmid);
517 }
518 
519 /* Handle a shmget() request. */
520 int
handle_shmget(pid_t pid,struct shmget_msg * shmget_msg,struct cmsgcred * cred)521 handle_shmget(pid_t pid, struct shmget_msg *shmget_msg,
522                     struct cmsgcred *cred ) {
523           int segnum, mode, error;
524           struct shmid_ds *shmseg;
525           struct shm_handle *handle;
526 
527           //if (!jail_sysvipc_allowed && td->td_cmsgcred->cr_prison != NULL)
528           //        return (ENOSYS);
529           mode = shmget_msg->shmflg & ACCESSPERMS;
530 
531           sysvd_print("ask for key = %ld\n", shmget_msg->key);
532           shmget_msg->key = (shmget_msg->key & 0x3FFF) |
533                     (shmget_msg->type << 30);
534           sysvd_print("ask for key = %ld\n", shmget_msg->key);
535 
536           if (shmget_msg->key != IPC_PRIVATE) {
537                     //again:
538                     segnum = shm_find_segment_by_key(shmget_msg->key);
539                     if (segnum >= 0) {
540                               error = shmget_existing(shmget_msg, mode, segnum, cred);
541                               //TODO if daemon is multithreading
542                               //if (error == EAGAIN)
543                               //        goto again;
544                               goto done;
545                     }
546                     if ((shmget_msg->shmflg & IPC_CREAT) == 0) {
547                               error = -ENOENT;
548                               goto done_err;
549                     }
550           }
551           error = shmget_allocate_segment(pid, shmget_msg, mode, cred);
552           sysvd_print("allocate segment = %d\n", error);
553 done:
554           /*
555            * Install to th client the file corresponding to the
556            * shared memory segment.
557            * client_fd is the file descriptor added in the client
558            * files table.
559            */
560           shmseg = shm_find_segment_by_shmid(error);
561           if (shmseg == NULL) {
562                     sysvd_print_err("can not find segment by shmid\n");
563                     return (-1);
564           }
565 
566           handle = (struct shm_handle *)shmseg->shm_internal;
567           if (install_fd_client(pid, handle->fd) != 0)
568                     error = errno;
569 done_err:
570           return (error);
571 
572 }
573 
574 /* Handle a shmat() request. */
575 int
handle_shmat(pid_t pid,struct shmat_msg * shmat_msg,struct cmsgcred * cred)576 handle_shmat(pid_t pid, struct shmat_msg *shmat_msg,
577                     struct cmsgcred *cred ) {
578           int error;
579           int fd;
580           struct shmid_ds *shmseg;
581           struct pid_attached *pidatt;
582           struct shm_handle *handle;
583           size_t new_size = shmat_msg->size;
584           struct client *cl;
585           struct id_attached *idatt;
586 
587           /*if (!jail_sysvipc_allowed && td->td_cmsgcred->cr_prison != NULL)
588             return (ENOSYS);
589 
590 again:*/
591           shmseg = shm_find_segment_by_shmid(shmat_msg->shmid);
592           if (shmseg == NULL) {
593                     sysvd_print_err("shmat error: segment was not found\n");
594                     error = EINVAL;
595                     goto done;
596           }
597           error = ipcperm(cred, &shmseg->shm_perm,
598                               (shmat_msg->shmflg & SHM_RDONLY) ? IPC_R : IPC_R|IPC_W);
599           if (error)
600                     goto done;
601 
602           handle = shmseg->shm_internal;
603 
604           if (shmat_msg->size > shmseg->shm_segsz) {
605                     if (handle->type != UNDOGET) {
606                               error = EINVAL;
607                               goto done;
608                     }
609 
610                     fd = ((struct shm_handle*)shmseg->shm_internal)->fd;
611                     ftruncate(fd, round_page(new_size));
612                     shmseg->shm_segsz = new_size;
613           }
614 
615           shmseg->shm_lpid = pid;
616           shmseg->shm_atime = time(NULL);
617 
618           if (handle->type != UNDOGET)
619                     shmseg->shm_nattch++;
620           else
621                     shmseg->shm_nattch = 1; /* Only a process calls shmat and
622                     only once. If it does it for more than once that is because
623                     it called exec() and reinitialized the undo segment. */
624 
625           /* Insert the pid in the segment list of attaced pids.
626            * The list is checked in handle_shmdt so that only
627            * attached pids can dettached from this segment.
628            */
629           sysvd_print("nattch = %d pid = %d\n",
630                               shmseg->shm_nattch, pid);
631 
632           pidatt = malloc(sizeof(*pidatt));
633           pidatt->pid = pid;
634           LIST_INSERT_HEAD(&handle->attached_list, pidatt, link);
635 
636           /* Add the segment at the list of attached segments of the client.
637            * It is used when the process finishes its execution. The daemon
638            * walks through the list to dettach the segments.
639            */
640           idatt = malloc(sizeof(*idatt));
641           idatt->shmid = shmat_msg->shmid;
642           cl = _hash_lookup(clientshash, pid);
643           LIST_INSERT_HEAD(&cl->ids_attached, idatt, link);
644 
645           return (0);
646 done:
647           return (error);
648 }
649 
650 /* Handle a shmdt() request. */
651 int
handle_shmdt(pid_t pid,int shmid)652 handle_shmdt(pid_t pid, int shmid) {
653           struct shmid_ds *shmseg;
654           int segnum;
655           struct shm_handle *handle;
656           struct pid_attached *pidatt;
657           struct id_attached *idatt;
658           struct client *cl;
659 
660           sysvd_print("shmdt pid %d shmid %d\n", pid, shmid);
661           /*if (!jail_sysvipc_allowed && td->td_cmsgcred->cr_prison != NULL)
662             return (ENOSYS);
663           */
664 
665           segnum = IPCID_TO_IX(shmid);
666           shmseg = &shmsegs[segnum];
667           handle = shmseg->shm_internal;
668 
669           /* Check if pid is attached. */
670           LIST_FOREACH(pidatt, &handle->attached_list, link)
671                     if (pidatt->pid == pid)
672                               break;
673           if (!pidatt) {
674                     sysvd_print_err("process %d is not attached to %d (1)\n",
675                                         pid, shmid);
676                     return (EINVAL);
677           }
678           LIST_REMOVE(pidatt, link);
679 
680           /* Remove the segment from the list of attached segments of the pid.*/
681           cl = _hash_lookup(clientshash, pid);
682           LIST_FOREACH(idatt, &cl->ids_attached, link)
683                     if (idatt->shmid == shmid)
684                               break;
685           if (!idatt) {
686                     sysvd_print_err("process %d is not attached to %d (2)\n",
687                                         pid, shmid);
688                     return (EINVAL);
689           }
690           LIST_REMOVE(idatt, link);
691 
692           shmseg->shm_dtime = time(NULL);
693 
694           /* If no other process attaced remove the segment. */
695           if ((--shmseg->shm_nattch <= 0) &&
696                               (shmseg->shm_perm.mode & SHMSEG_REMOVED)) {
697                     shm_deallocate_segment(segnum);
698                     shm_last_free = segnum;
699           }
700 
701           return (0);
702 }
703 
704 /* Handle a shmctl() request. */
705 int
handle_shmctl(struct shmctl_msg * shmctl_msg,struct cmsgcred * cred)706 handle_shmctl(struct shmctl_msg *shmctl_msg,
707                     struct cmsgcred *cred ) {
708           int error = 0;
709           struct shmid_ds *shmseg, *inbuf;
710 
711           /*        if (!jail_sysvipc_allowed && td->td_cmsgcred->cr_prison != NULL)
712                     return (ENOSYS);
713                     */
714           shmseg = shm_find_segment_by_shmid(shmctl_msg->shmid);
715 
716           if (shmseg == NULL) {
717                     error = EINVAL;
718                     goto done;
719           }
720 
721           switch (shmctl_msg->cmd) {
722                     case IPC_STAT:
723                               sysvd_print("IPC STAT\n");
724                               error = ipcperm(cred, &shmseg->shm_perm, IPC_R);
725                               if (error) {
726                                         sysvd_print("IPC_STAT not allowed\n");
727                                         break;
728                               }
729                               shmctl_msg->buf = *shmseg;
730                               break;
731                     case IPC_SET:
732                               sysvd_print("IPC SET\n");
733                               error = ipcperm(cred, &shmseg->shm_perm, IPC_M);
734                               if (error) {
735                                         sysvd_print("IPC_SET not allowed\n");
736                                         break;
737                               }
738                               inbuf = &shmctl_msg->buf;
739 
740                               shmseg->shm_perm.uid = inbuf->shm_perm.uid;
741                               shmseg->shm_perm.gid = inbuf->shm_perm.gid;
742                               shmseg->shm_perm.mode =
743                                         (shmseg->shm_perm.mode & ~ACCESSPERMS) |
744                                         (inbuf->shm_perm.mode & ACCESSPERMS);
745                               shmseg->shm_ctime = time(NULL);
746                               break;
747                     case IPC_RMID:
748                               sysvd_print("IPC RMID shmid = %d\n",
749                                                   shmctl_msg->shmid);
750                               error = ipcperm(cred, &shmseg->shm_perm, IPC_M);
751                               if (error) {
752                                         sysvd_print("IPC_RMID not allowed\n");
753                                         break;
754                               }
755                               shmseg->shm_perm.key = IPC_PRIVATE;
756                               shmseg->shm_perm.mode |= SHMSEG_REMOVED;
757                               if (shmseg->shm_nattch <= 0) {
758                                         shm_deallocate_segment(IPCID_TO_IX(shmctl_msg->shmid));
759                                         shm_last_free = IPCID_TO_IX(shmctl_msg->shmid);
760                               }
761                               else {
762                                         /* In sem and msg cases, other process must be
763                                          * noticed about the removal. */
764                                         struct shm_handle *internal =
765                                                   (struct shm_handle *)shmseg->shm_internal;
766                                         mark_segment_removed(shmctl_msg->shmid,
767                                                             internal->type);
768                               }
769                               break;
770 #if 0
771                     case SHM_LOCK:
772                     case SHM_UNLOCK:
773 #endif
774                     default:
775                               error = EINVAL;
776                               break;
777           }
778 done:
779           return (error);
780 
781 }
782 
783 /* Function used by daemon to map a sysv resource. */
784 static void *
map_seg(int shmid)785 map_seg(int shmid) {
786           struct shmid_ds *shmseg;
787           struct shm_handle *internal;
788 
789           int fd;
790           size_t size;
791           void *addr;
792 
793           shmseg = shm_find_segment_by_shmid(shmid);
794           if (!shmseg) {
795                     sysvd_print_err("map_seg error:"
796                                         "semid %d not found\n", shmid);
797                     return (NULL);
798           }
799 
800           internal = (struct shm_handle *)shmseg->shm_internal;
801           if (!internal) {
802                     sysvd_print_err("map_seg error: internal for"
803                                         "semid %d not found\n", shmid);
804                     return (NULL);
805           }
806 
807           fd = internal->fd;
808 
809           size = round_page(shmseg->shm_segsz);
810 
811           addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
812           if (!addr) {
813                     sysvd_print_err("map_seg: error mmap semid = %d\n", shmid);
814                     return (NULL);
815           }
816 
817           return (addr);
818 }
819 
820 /* Function used by daemon to munmap a sysv resource. */
821 static int
munmap_seg(int shmid,void * addr)822 munmap_seg(int shmid, void *addr) {
823           struct shmid_ds *shmseg;
824           struct shm_handle *internal;
825 
826           size_t size;
827 
828           shmseg = shm_find_segment_by_shmid(shmid);
829           if (!shmseg) {
830                     sysvd_print_err("munmap_seg error:"
831                                         "semid %d not found\n", shmid);
832                     return (-1);
833           }
834 
835           internal = (struct shm_handle *)shmseg->shm_internal;
836           if (!internal) {
837                     sysvd_print_err("munmap_seg error: internal for"
838                                         "semid %d not found\n", shmid);
839                     return (-1);
840           }
841 
842           size = round_page(shmseg->shm_segsz);
843           munmap(addr, size);
844 
845           return (0);
846 }
847 
848 void
shminit(void)849 shminit(void) {
850           int i;
851 
852           shmalloced = shminfo.shmmni;
853           shmsegs = malloc(shmalloced * sizeof(shmsegs[0]));
854           for (i = 0; i < shmalloced; i++) {
855                     shmsegs[i].shm_perm.mode = SHMSEG_FREE;
856                     shmsegs[i].shm_perm.seq = 0;
857           }
858           shm_last_free = 0;
859           shm_nused = 0;
860           shm_committed = 0;
861 
862           /*
863            * msginfo.msgssz should be a power of two for efficiency reasons.
864            * It is also pretty silly if msginfo.msgssz is less than 8
865            * or greater than about 256 so ...
866            */
867           i = 8;
868           while (i < 1024 && i != msginfo.msgssz)
869                     i <<= 1;
870           if (i != msginfo.msgssz) {
871                     sysvd_print_err("msginfo.msgssz=%d (0x%x)\n", msginfo.msgssz,
872                         msginfo.msgssz);
873                     sysvd_print_err("msginfo.msgssz not a small power of 2");
874                     exit(-1);
875           }
876           msginfo.msgmax = msginfo.msgseg * msginfo.msgssz;
877 }
878 
879 /*static void
880 shmfree(void) {
881           free(shmsegs);
882 }*/
883 
884 int
semexit(int undoid)885 semexit(int undoid) {
886           struct sem_undo *suptr;
887           struct sem *semptr;
888           struct shmid_ds *undoseg;
889 
890           if (undoid < 0) {
891                     return (-1);
892           }
893 
894           undoseg = shm_find_segment_by_shmid(undoid);
895           /* The UNDO segment must be mapped by only one segment. */
896           if (undoseg->shm_nattch != 1) {
897                     sysvd_print_err("undo segment mapped by more"
898                                         "than one process\n");
899                     exit(-1);
900           }
901 
902           suptr = (struct sem_undo *)map_seg(undoid);
903           if (suptr == NULL) {
904                     sysvd_print_err("no %d undo segment found\n", undoid);
905                     return (-1);
906           }
907 
908           /* No locking mechanism is required because only the
909            * client and the daemon can access the UNDO segment.
910            * At this moment the client is disconnected so only
911            * the daemon can modify this segment.
912            */
913           while (suptr->un_cnt) {
914                     struct semid_pool *semaptr;
915                     int semid;
916                     int semnum;
917                     int adjval;
918                     int ix;
919 
920                     ix = suptr->un_cnt - 1;
921                     semid = suptr->un_ent[ix].un_id;
922                     semnum = suptr->un_ent[ix].un_num;
923                     adjval = suptr->un_ent[ix].un_adjval;
924 
925                     semaptr = (struct semid_pool *)map_seg(semid);
926                     if (!semaptr) {
927                               return (-1);
928                     }
929 
930                     /* Was it removed? */
931                     if (semaptr->gen == -1 ||
932                               semaptr->ds.sem_perm.seq != IPCID_TO_SEQ(semid) ||
933                               (semaptr->ds.sem_perm.mode & SHMSEG_ALLOCATED) == 0) {
934                               --suptr->un_cnt;
935                               sysvd_print_err("semexit - semid not allocated\n");
936                               continue;
937                     }
938                     if (semnum >= semaptr->ds.sem_nsems) {
939                               --suptr->un_cnt;
940                               sysvd_print_err("semexit - semnum out of range\n");
941                               continue;
942                     }
943 
944 #ifdef SYSV_RWLOCK
945 #ifdef SYSV_SEMS
946                     sysv_rwlock_rdlock(&semaptr->rwlock);
947 #else
948                     sysv_rwlock_wrlock(&semaptr->rwlock);
949 #endif //SYSV_SEMS
950 #else
951                     sysv_mutex_lock(&semaptr->mutex);
952                     /* Nobody can remove the semaphore beteen the check and the
953                      * lock acquisition because it must first send a IPC_RMID
954                      * to me and I will process that after finishing this function.
955                      */
956 #endif //SYSV_RWLOCK
957                     semptr = &semaptr->ds.sem_base[semnum];
958 #ifdef SYSV_SEMS
959                     sysv_mutex_lock(&semptr->sem_mutex);
960 #endif
961                     if (ix == suptr->un_cnt - 1 &&
962                         semid == suptr->un_ent[ix].un_id &&
963                         semnum == suptr->un_ent[ix].un_num &&
964                         adjval == suptr->un_ent[ix].un_adjval) {
965                               --suptr->un_cnt;
966 
967                               if (adjval < 0) {
968                                         if (semptr->semval < -adjval)
969                                                   semptr->semval = 0;
970                                         else
971                                                   semptr->semval += adjval;
972                               } else {
973                                         semptr->semval += adjval;
974                               }
975                               /* TODO multithreaded daemon:
976                                * Check again if the semaphore was removed and do
977                                * not wake anyone if it was.*/
978                               umtx_wakeup((int *)&semptr->semval, 0);
979                     }
980 #ifdef SYSV_SEMS
981                     sysv_mutex_unlock(&semptr->sem_mutex);
982 #endif
983 
984 #ifdef SYSV_RWLOCK
985                     sysv_rwlock_unlock(&semaptr->rwlock);
986 #else
987                     sysv_mutex_unlock(&semaptr->mutex);
988 #endif
989                     munmap_seg(semid, semaptr);
990           }
991 
992           munmap_seg(undoid, suptr);
993           return (0);
994 }
995 
996 void
shmexit(struct client * cl)997 shmexit(struct client *cl) {
998           struct id_attached *idatt;
999 
1000           while (!LIST_EMPTY(&cl->ids_attached)) {
1001                     idatt = LIST_FIRST(&cl->ids_attached);
1002                     handle_shmdt(cl->pid, idatt->shmid);
1003           }
1004 }
1005