1 /* $Id: sysv_shm.c,v 1.2 1994/09/16 17:43:22 dfr Exp $ */ 2 /* $NetBSD: sysv_shm.c,v 1.23 1994/07/04 23:25:12 glass Exp $ */ 3 4 /* 5 * Copyright (c) 1994 Adam Glass and Charles Hannum. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by Adam Glass and Charles 18 * Hannum. 19 * 4. The names of the authors may not be used to endorse or promote products 20 * derived from this software without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 25 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 27 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 31 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34 #include <sys/types.h> 35 #include <sys/param.h> 36 #include <sys/kernel.h> 37 #include <sys/shm.h> 38 #include <sys/proc.h> 39 #include <sys/uio.h> 40 #include <sys/time.h> 41 #include <sys/malloc.h> 42 #include <sys/mman.h> 43 #include <sys/systm.h> 44 #include <sys/stat.h> 45 46 #include <vm/vm.h> 47 #include <vm/vm_map.h> 48 #include <vm/vm_map.h> 49 #include <vm/vm_kern.h> 50 51 /* 52 * Provides the following externally accessible functions: 53 * 54 * shminit(void); initialization 55 * shmexit(struct proc *) cleanup 56 * shmfork(struct proc *, struct proc *, int) fork handling 57 * shmsys(arg1, arg2, arg3, arg4); shm{at,ctl,dt,get}(arg2, arg3, arg4) 58 * 59 * Structures: 60 * shmsegs (an array of 'struct shmid_ds') 61 * per proc array of 'struct shmmap_state' 62 */ 63 64 int oshmctl(); 65 int shmat(), shmctl(), shmdt(), shmget(); 66 int (*shmcalls[])() = { shmat, oshmctl, shmdt, shmget, shmctl }; 67 68 #define SHMSEG_FREE 0x0200 69 #define SHMSEG_REMOVED 0x0400 70 #define SHMSEG_ALLOCATED 0x0800 71 #define SHMSEG_WANTED 0x1000 72 73 vm_map_t sysvshm_map; 74 int shm_last_free, shm_nused, shm_committed; 75 76 struct shm_handle { 77 vm_offset_t kva; 78 }; 79 80 struct shmmap_state { 81 vm_offset_t va; 82 int shmid; 83 }; 84 85 static void shm_deallocate_segment __P((struct shmid_ds *)); 86 static int shm_find_segment_by_key __P((key_t)); 87 static struct shmid_ds *shm_find_segment_by_shmid __P((int)); 88 static int shm_delete_mapping __P((struct proc *, struct shmmap_state *)); 89 90 static int 91 shm_find_segment_by_key(key) 92 key_t key; 93 { 94 int i; 95 96 for (i = 0; i < shminfo.shmmni; i++) 97 if ((shmsegs[i].shm_perm.mode & SHMSEG_ALLOCATED) && 98 shmsegs[i].shm_perm.key == key) 99 return i; 100 return -1; 101 } 102 103 static struct shmid_ds * 104 shm_find_segment_by_shmid(shmid) 105 int shmid; 106 { 107 int segnum; 108 struct shmid_ds *shmseg; 109 110 segnum = IPCID_TO_IX(shmid); 111 if (segnum < 0 || segnum >= shminfo.shmmni) 112 return NULL; 113 shmseg = &shmsegs[segnum]; 114 if ((shmseg->shm_perm.mode & (SHMSEG_ALLOCATED | SHMSEG_REMOVED)) 115 != SHMSEG_ALLOCATED || 116 shmseg->shm_perm.seq != IPCID_TO_SEQ(shmid)) 117 return NULL; 118 return shmseg; 119 } 120 121 static void 122 shm_deallocate_segment(shmseg) 123 struct shmid_ds *shmseg; 124 { 125 struct shm_handle *shm_handle; 126 size_t size; 127 128 shm_handle = shmseg->shm_internal; 129 size = (shmseg->shm_segsz + CLOFSET) & ~CLOFSET; 130 vm_deallocate(sysvshm_map, shm_handle->kva, size); 131 free((caddr_t)shm_handle, M_SHM); 132 shmseg->shm_internal = NULL; 133 shm_committed -= btoc(size); 134 shm_nused--; 135 shmseg->shm_perm.mode = SHMSEG_FREE; 136 } 137 138 static int 139 shm_delete_mapping(p, shmmap_s) 140 struct proc *p; 141 struct shmmap_state *shmmap_s; 142 { 143 struct shmid_ds *shmseg; 144 int segnum, result; 145 size_t size; 146 147 segnum = IPCID_TO_IX(shmmap_s->shmid); 148 shmseg = &shmsegs[segnum]; 149 size = (shmseg->shm_segsz + CLOFSET) & ~CLOFSET; 150 result = vm_deallocate(&p->p_vmspace->vm_map, shmmap_s->va, size); 151 if (result != KERN_SUCCESS) 152 return EINVAL; 153 shmmap_s->shmid = -1; 154 shmseg->shm_dtime = time.tv_sec; 155 if ((--shmseg->shm_nattch <= 0) && 156 (shmseg->shm_perm.mode & SHMSEG_REMOVED)) { 157 shm_deallocate_segment(shmseg); 158 shm_last_free = segnum; 159 } 160 return 0; 161 } 162 163 struct shmdt_args { 164 void *shmaddr; 165 }; 166 int 167 shmdt(p, uap, retval) 168 struct proc *p; 169 struct shmdt_args *uap; 170 int *retval; 171 { 172 struct shmmap_state *shmmap_s; 173 int i; 174 175 shmmap_s = (struct shmmap_state *)p->p_vmspace->vm_shm; 176 for (i = 0; i < shminfo.shmseg; i++, shmmap_s++) 177 if (shmmap_s->shmid != -1 && 178 shmmap_s->va == (vm_offset_t)uap->shmaddr) 179 break; 180 if (i == shminfo.shmseg) 181 return EINVAL; 182 return shm_delete_mapping(p, shmmap_s); 183 } 184 185 struct shmat_args { 186 int shmid; 187 void *shmaddr; 188 int shmflg; 189 }; 190 int 191 shmat(p, uap, retval) 192 struct proc *p; 193 struct shmat_args *uap; 194 int *retval; 195 { 196 int error, i, flags; 197 struct ucred *cred = p->p_ucred; 198 struct shmid_ds *shmseg; 199 struct shmmap_state *shmmap_s = NULL; 200 vm_offset_t attach_va; 201 vm_prot_t prot; 202 vm_size_t size; 203 204 shmmap_s = (struct shmmap_state *)p->p_vmspace->vm_shm; 205 if (shmmap_s == NULL) { 206 size = shminfo.shmseg * sizeof(struct shmmap_state); 207 shmmap_s = malloc(size, M_SHM, M_WAITOK); 208 for (i = 0; i < shminfo.shmseg; i++) 209 shmmap_s[i].shmid = -1; 210 p->p_vmspace->vm_shm = (caddr_t)shmmap_s; 211 } 212 shmseg = shm_find_segment_by_shmid(uap->shmid); 213 if (shmseg == NULL) 214 return EINVAL; 215 error = ipcperm(cred, &shmseg->shm_perm, 216 (uap->shmflg & SHM_RDONLY) ? IPC_R : IPC_R|IPC_W); 217 if (error) 218 return error; 219 for (i = 0; i < shminfo.shmseg; i++) { 220 if (shmmap_s->shmid == -1) 221 break; 222 shmmap_s++; 223 } 224 if (i >= shminfo.shmseg) 225 return EMFILE; 226 size = (shmseg->shm_segsz + CLOFSET) & ~CLOFSET; 227 prot = VM_PROT_READ; 228 if ((uap->shmflg & SHM_RDONLY) == 0) 229 prot |= VM_PROT_WRITE; 230 flags = MAP_ANON | MAP_SHARED; 231 if (uap->shmaddr) { 232 flags |= MAP_FIXED; 233 if (uap->shmflg & SHM_RND) 234 attach_va = (vm_offset_t)uap->shmaddr & ~(SHMLBA-1); 235 else if (((vm_offset_t)uap->shmaddr & (SHMLBA-1)) == 0) 236 attach_va = (vm_offset_t)uap->shmaddr; 237 else 238 return EINVAL; 239 } else { 240 /* This is just a hint to vm_mmap() about where to put it. */ 241 attach_va = round_page(p->p_vmspace->vm_daddr + MAXDSIZ); 242 } 243 error = vm_mmap(&p->p_vmspace->vm_map, &attach_va, size, prot, 244 VM_PROT_DEFAULT, flags, (caddr_t) uap->shmid, 0); 245 if (error) 246 return error; 247 shmmap_s->va = attach_va; 248 shmmap_s->shmid = uap->shmid; 249 shmseg->shm_lpid = p->p_pid; 250 shmseg->shm_atime = time.tv_sec; 251 shmseg->shm_nattch++; 252 *retval = attach_va; 253 return 0; 254 } 255 256 struct oshmid_ds { 257 struct ipc_perm shm_perm; /* operation perms */ 258 int shm_segsz; /* size of segment (bytes) */ 259 ushort shm_cpid; /* pid, creator */ 260 ushort shm_lpid; /* pid, last operation */ 261 short shm_nattch; /* no. of current attaches */ 262 time_t shm_atime; /* last attach time */ 263 time_t shm_dtime; /* last detach time */ 264 time_t shm_ctime; /* last change time */ 265 void *shm_handle; /* internal handle for shm segment */ 266 }; 267 268 struct oshmctl_args { 269 int shmid; 270 int cmd; 271 struct oshmid_ds *ubuf; 272 }; 273 274 int 275 oshmctl(p, uap, retval) 276 struct proc *p; 277 struct oshmctl_args *uap; 278 int *retval; 279 { 280 #ifdef COMPAT_43 281 int error; 282 struct ucred *cred = p->p_ucred; 283 struct shmid_ds *shmseg; 284 struct oshmid_ds outbuf; 285 286 shmseg = shm_find_segment_by_shmid(uap->shmid); 287 if (shmseg == NULL) 288 return EINVAL; 289 switch (uap->cmd) { 290 case IPC_STAT: 291 error = ipcperm(cred, &shmseg->shm_perm, IPC_R); 292 if (error) 293 return error; 294 outbuf.shm_perm = shmseg->shm_perm; 295 outbuf.shm_segsz = shmseg->shm_segsz; 296 outbuf.shm_cpid = shmseg->shm_cpid; 297 outbuf.shm_lpid = shmseg->shm_lpid; 298 outbuf.shm_nattch = shmseg->shm_nattch; 299 outbuf.shm_atime = shmseg->shm_atime; 300 outbuf.shm_dtime = shmseg->shm_dtime; 301 outbuf.shm_ctime = shmseg->shm_ctime; 302 outbuf.shm_handle = shmseg->shm_internal; 303 error = copyout((caddr_t)&outbuf, uap->ubuf, sizeof(outbuf)); 304 if (error) 305 return error; 306 break; 307 default: 308 return shmctl(p, uap, retval); 309 } 310 return 0; 311 #else 312 return EINVAL; 313 #endif 314 } 315 316 struct shmctl_args { 317 int shmid; 318 int cmd; 319 struct shmid_ds *ubuf; 320 }; 321 int 322 shmctl(p, uap, retval) 323 struct proc *p; 324 struct shmctl_args *uap; 325 int *retval; 326 { 327 int error; 328 struct ucred *cred = p->p_ucred; 329 struct shmid_ds inbuf; 330 struct shmid_ds *shmseg; 331 332 shmseg = shm_find_segment_by_shmid(uap->shmid); 333 if (shmseg == NULL) 334 return EINVAL; 335 switch (uap->cmd) { 336 case IPC_STAT: 337 error = ipcperm(cred, &shmseg->shm_perm, IPC_R); 338 if (error) 339 return error; 340 error = copyout((caddr_t)shmseg, uap->ubuf, sizeof(inbuf)); 341 if (error) 342 return error; 343 break; 344 case IPC_SET: 345 error = ipcperm(cred, &shmseg->shm_perm, IPC_M); 346 if (error) 347 return error; 348 error = copyin(uap->ubuf, (caddr_t)&inbuf, sizeof(inbuf)); 349 if (error) 350 return error; 351 shmseg->shm_perm.uid = inbuf.shm_perm.uid; 352 shmseg->shm_perm.gid = inbuf.shm_perm.gid; 353 shmseg->shm_perm.mode = 354 (shmseg->shm_perm.mode & ~ACCESSPERMS) | 355 (inbuf.shm_perm.mode & ACCESSPERMS); 356 shmseg->shm_ctime = time.tv_sec; 357 break; 358 case IPC_RMID: 359 error = ipcperm(cred, &shmseg->shm_perm, IPC_M); 360 if (error) 361 return error; 362 shmseg->shm_perm.key = IPC_PRIVATE; 363 shmseg->shm_perm.mode |= SHMSEG_REMOVED; 364 if (shmseg->shm_nattch <= 0) { 365 shm_deallocate_segment(shmseg); 366 shm_last_free = IPCID_TO_IX(uap->shmid); 367 } 368 break; 369 #if 0 370 case SHM_LOCK: 371 case SHM_UNLOCK: 372 #endif 373 default: 374 return EINVAL; 375 } 376 return 0; 377 } 378 379 struct shmget_args { 380 key_t key; 381 size_t size; 382 int shmflg; 383 }; 384 static int 385 shmget_existing(p, uap, mode, segnum, retval) 386 struct proc *p; 387 struct shmget_args *uap; 388 int mode; 389 int segnum; 390 int *retval; 391 { 392 struct shmid_ds *shmseg; 393 struct ucred *cred = p->p_ucred; 394 int error; 395 396 shmseg = &shmsegs[segnum]; 397 if (shmseg->shm_perm.mode & SHMSEG_REMOVED) { 398 /* 399 * This segment is in the process of being allocated. Wait 400 * until it's done, and look the key up again (in case the 401 * allocation failed or it was freed). 402 */ 403 shmseg->shm_perm.mode |= SHMSEG_WANTED; 404 error = tsleep((caddr_t)shmseg, PLOCK | PCATCH, "shmget", 0); 405 if (error) 406 return error; 407 return EAGAIN; 408 } 409 error = ipcperm(cred, &shmseg->shm_perm, mode); 410 if (error) 411 return error; 412 if (uap->size && uap->size > shmseg->shm_segsz) 413 return EINVAL; 414 if (uap->shmflg & (IPC_CREAT | IPC_EXCL) == (IPC_CREAT | IPC_EXCL)) 415 return EEXIST; 416 *retval = IXSEQ_TO_IPCID(segnum, shmseg->shm_perm); 417 return 0; 418 } 419 420 static int 421 shmget_allocate_segment(p, uap, mode, retval) 422 struct proc *p; 423 struct shmget_args *uap; 424 int mode; 425 int *retval; 426 { 427 int i, segnum, result, shmid, size; 428 struct ucred *cred = p->p_ucred; 429 struct shmid_ds *shmseg; 430 struct shm_handle *shm_handle; 431 432 if (uap->size < shminfo.shmmin || uap->size > shminfo.shmmax) 433 return EINVAL; 434 if (shm_nused >= shminfo.shmmni) /* any shmids left? */ 435 return ENOSPC; 436 size = (uap->size + CLOFSET) & ~CLOFSET; 437 if (shm_committed + btoc(size) > shminfo.shmall) 438 return ENOMEM; 439 if (shm_last_free < 0) { 440 for (i = 0; i < shminfo.shmmni; i++) 441 if (shmsegs[i].shm_perm.mode & SHMSEG_FREE) 442 break; 443 if (i == shminfo.shmmni) 444 panic("shmseg free count inconsistent"); 445 segnum = i; 446 } else { 447 segnum = shm_last_free; 448 shm_last_free = -1; 449 } 450 shmseg = &shmsegs[segnum]; 451 /* 452 * In case we sleep in malloc(), mark the segment present but deleted 453 * so that noone else tries to create the same key. 454 */ 455 shmseg->shm_perm.mode = SHMSEG_ALLOCATED | SHMSEG_REMOVED; 456 shmseg->shm_perm.key = uap->key; 457 shmseg->shm_perm.seq = (shmseg->shm_perm.seq + 1) & 0x7fff; 458 shm_handle = (struct shm_handle *) 459 malloc(sizeof(struct shm_handle), M_SHM, M_WAITOK); 460 shmid = IXSEQ_TO_IPCID(segnum, shmseg->shm_perm); 461 result = vm_mmap(sysvshm_map, &shm_handle->kva, size, VM_PROT_ALL, 462 VM_PROT_DEFAULT, MAP_ANON, (caddr_t) shmid, 0); 463 if (result != KERN_SUCCESS) { 464 shmseg->shm_perm.mode = SHMSEG_FREE; 465 shm_last_free = segnum; 466 free((caddr_t)shm_handle, M_SHM); 467 /* Just in case. */ 468 wakeup((caddr_t)shmseg); 469 return ENOMEM; 470 } 471 shmseg->shm_internal = shm_handle; 472 shmseg->shm_perm.cuid = shmseg->shm_perm.uid = cred->cr_uid; 473 shmseg->shm_perm.cgid = shmseg->shm_perm.gid = cred->cr_gid; 474 shmseg->shm_perm.mode = (shmseg->shm_perm.mode & SHMSEG_WANTED) | 475 (mode & ACCESSPERMS) | SHMSEG_ALLOCATED; 476 shmseg->shm_segsz = uap->size; 477 shmseg->shm_cpid = p->p_pid; 478 shmseg->shm_lpid = shmseg->shm_nattch = 0; 479 shmseg->shm_atime = shmseg->shm_dtime = 0; 480 shmseg->shm_ctime = time.tv_sec; 481 shm_committed += btoc(size); 482 shm_nused++; 483 if (shmseg->shm_perm.mode & SHMSEG_WANTED) { 484 /* 485 * Somebody else wanted this key while we were asleep. Wake 486 * them up now. 487 */ 488 shmseg->shm_perm.mode &= ~SHMSEG_WANTED; 489 wakeup((caddr_t)shmseg); 490 } 491 *retval = shmid; 492 return 0; 493 } 494 495 int 496 shmget(p, uap, retval) 497 struct proc *p; 498 struct shmget_args *uap; 499 int *retval; 500 { 501 int segnum, mode, error; 502 503 mode = uap->shmflg & ACCESSPERMS; 504 if (uap->key != IPC_PRIVATE) { 505 again: 506 segnum = shm_find_segment_by_key(uap->key); 507 if (segnum >= 0) { 508 error = shmget_existing(p, uap, mode, segnum, retval); 509 if (error == EAGAIN) 510 goto again; 511 return error; 512 } 513 if ((uap->shmflg & IPC_CREAT) == 0) 514 return ENOENT; 515 } 516 return shmget_allocate_segment(p, uap, mode, retval); 517 } 518 519 struct shmsys_args { 520 u_int which; 521 }; 522 int 523 shmsys(p, uap, retval) 524 struct proc *p; 525 struct shmsys_args *uap; 526 int *retval; 527 { 528 529 if (uap->which >= sizeof(shmcalls)/sizeof(shmcalls[0])) 530 return EINVAL; 531 return ((*shmcalls[uap->which])(p, &uap[1], retval)); 532 } 533 534 void 535 shmfork(p1, p2, isvfork) 536 struct proc *p1, *p2; 537 int isvfork; 538 { 539 struct shmmap_state *shmmap_s; 540 size_t size; 541 int i; 542 543 size = shminfo.shmseg * sizeof(struct shmmap_state); 544 shmmap_s = malloc(size, M_SHM, M_WAITOK); 545 bcopy((caddr_t)p1->p_vmspace->vm_shm, (caddr_t)shmmap_s, size); 546 p2->p_vmspace->vm_shm = (caddr_t)shmmap_s; 547 for (i = 0; i < shminfo.shmseg; i++, shmmap_s++) 548 if (shmmap_s->shmid != -1) 549 shmsegs[IPCID_TO_IX(shmmap_s->shmid)].shm_nattch++; 550 } 551 552 void 553 shmexit(p) 554 struct proc *p; 555 { 556 struct shmmap_state *shmmap_s; 557 int i; 558 559 shmmap_s = (struct shmmap_state *)p->p_vmspace->vm_shm; 560 for (i = 0; i < shminfo.shmseg; i++, shmmap_s++) 561 if (shmmap_s->shmid != -1) 562 shm_delete_mapping(p, shmmap_s); 563 free((caddr_t)p->p_vmspace->vm_shm, M_SHM); 564 p->p_vmspace->vm_shm = NULL; 565 } 566 567 void 568 shminit() 569 { 570 int i; 571 vm_offset_t garbage1, garbage2; 572 573 /* actually this *should* be pageable. SHM_{LOCK,UNLOCK} */ 574 sysvshm_map = kmem_suballoc(kernel_map, &garbage1, &garbage2, 575 shminfo.shmall * NBPG, TRUE); 576 for (i = 0; i < shminfo.shmmni; i++) { 577 shmsegs[i].shm_perm.mode = SHMSEG_FREE; 578 shmsegs[i].shm_perm.seq = 0; 579 } 580 shm_last_free = 0; 581 shm_nused = 0; 582 shm_committed = 0; 583 } 584