| 1 | /* |
| 2 | * Copyright (c) 1988 University of Utah. |
| 3 | * Copyright (c) 1990 The Regents of the University of California. |
| 4 | * All rights reserved. |
| 5 | * |
| 6 | * This code is derived from software contributed to Berkeley by |
| 7 | * the Systems Programming Group of the University of Utah Computer |
| 8 | * Science Department. Originally from University of Wisconsin. |
| 9 | * |
| 10 | * %sccs.include.redist.c% |
| 11 | * |
| 12 | * from: Utah $Hdr: uipc_shm.c 1.9 89/08/14$ |
| 13 | * |
| 14 | * @(#)sysv_shm.c 7.8 (Berkeley) %G% |
| 15 | */ |
| 16 | |
| 17 | /* |
| 18 | * System V shared memory routines. |
| 19 | * TEMPORARY, until mmap is in place; |
| 20 | * needed now for HP-UX compatibility and X server (yech!). |
| 21 | */ |
| 22 | |
| 23 | #ifdef SYSVSHM |
| 24 | |
| 25 | #include "machine/pte.h" |
| 26 | |
| 27 | #include "param.h" |
| 28 | #include "systm.h" |
| 29 | #include "syscontext.h" |
| 30 | #include "kernel.h" |
| 31 | #include "proc.h" |
| 32 | #include "vm.h" |
| 33 | #include "shm.h" |
| 34 | #include "mapmem.h" |
| 35 | #include "malloc.h" |
| 36 | |
| 37 | #ifdef HPUXCOMPAT |
| 38 | #include "../hpux/hpux.h" |
| 39 | #endif |
| 40 | |
| 41 | int shmat(), shmctl(), shmdt(), shmget(); |
| 42 | int (*shmcalls[])() = { shmat, shmctl, shmdt, shmget }; |
| 43 | int shmtot = 0; |
| 44 | |
| 45 | int shmfork(), shmexit(); |
| 46 | struct mapmemops shmops = { shmfork, (int (*)())0, shmexit, shmexit }; |
| 47 | |
| 48 | shminit() |
| 49 | { |
| 50 | register int i; |
| 51 | |
| 52 | if (shminfo.shmmni > SHMMMNI) |
| 53 | shminfo.shmmni = SHMMMNI; |
| 54 | for (i = 0; i < shminfo.shmmni; i++) { |
| 55 | shmsegs[i].shm_perm.mode = 0; |
| 56 | shmsegs[i].shm_perm.seq = 0; |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | /* |
| 61 | * Entry point for all SHM calls |
| 62 | */ |
| 63 | shmsys(p, uap, retval) |
| 64 | struct proc *p; |
| 65 | struct args { |
| 66 | u_int which; |
| 67 | } *uap; |
| 68 | int *retval; |
| 69 | { |
| 70 | |
| 71 | if (uap->which >= sizeof(shmcalls)/sizeof(shmcalls[0])) |
| 72 | RETURN (EINVAL); |
| 73 | RETURN ((*shmcalls[uap->which])(p, &uap[1], retval)); |
| 74 | } |
| 75 | |
| 76 | /* |
| 77 | * Get a shared memory segment |
| 78 | */ |
| 79 | shmget(p, uap, retval) |
| 80 | struct proc *p; |
| 81 | register struct args { |
| 82 | key_t key; |
| 83 | int size; |
| 84 | int shmflg; |
| 85 | } *uap; |
| 86 | int *retval; |
| 87 | { |
| 88 | register struct shmid_ds *shp; |
| 89 | register struct ucred *cred = u.u_cred; |
| 90 | register int i; |
| 91 | int error, size, rval = 0; |
| 92 | caddr_t kva; |
| 93 | |
| 94 | /* look up the specified shm_id */ |
| 95 | if (uap->key != IPC_PRIVATE) { |
| 96 | for (i = 0; i < shminfo.shmmni; i++) |
| 97 | if ((shmsegs[i].shm_perm.mode & SHM_ALLOC) && |
| 98 | shmsegs[i].shm_perm.key == uap->key) { |
| 99 | rval = i; |
| 100 | break; |
| 101 | } |
| 102 | } else |
| 103 | i = shminfo.shmmni; |
| 104 | |
| 105 | /* create a new shared segment if necessary */ |
| 106 | if (i == shminfo.shmmni) { |
| 107 | if ((uap->shmflg & IPC_CREAT) == 0) |
| 108 | return (ENOENT); |
| 109 | if (uap->size < shminfo.shmmin || uap->size > shminfo.shmmax) |
| 110 | return (EINVAL); |
| 111 | for (i = 0; i < shminfo.shmmni; i++) |
| 112 | if ((shmsegs[i].shm_perm.mode & SHM_ALLOC) == 0) { |
| 113 | rval = i; |
| 114 | break; |
| 115 | } |
| 116 | if (i == shminfo.shmmni) |
| 117 | return (ENOSPC); |
| 118 | size = clrnd(btoc(uap->size)); |
| 119 | if (shmtot + size > shminfo.shmall) |
| 120 | return (ENOMEM); |
| 121 | shp = &shmsegs[rval]; |
| 122 | /* |
| 123 | * We need to do a couple of things to ensure consistency |
| 124 | * in case we sleep in malloc(). We mark segment as |
| 125 | * allocated so that other shmgets() will not allocate it. |
| 126 | * We mark it as "destroyed" to insure that shmvalid() is |
| 127 | * false making most operations fail (XXX). We set the key, |
| 128 | * so that other shmget()s will fail. |
| 129 | */ |
| 130 | shp->shm_perm.mode = SHM_ALLOC | SHM_DEST; |
| 131 | shp->shm_perm.key = uap->key; |
| 132 | kva = (caddr_t) malloc((u_long)ctob(size), M_SHM, M_WAITOK); |
| 133 | if (kva == NULL) { |
| 134 | shp->shm_perm.mode = 0; |
| 135 | return (ENOMEM); |
| 136 | } |
| 137 | if (!claligned(kva)) |
| 138 | panic("shmget: non-aligned memory"); |
| 139 | bzero(kva, (u_int)ctob(size)); |
| 140 | shmtot += size; |
| 141 | shp->shm_perm.cuid = shp->shm_perm.uid = cred->cr_uid; |
| 142 | shp->shm_perm.cgid = shp->shm_perm.gid = cred->cr_gid; |
| 143 | shp->shm_perm.mode = SHM_ALLOC | (uap->shmflg&0777); |
| 144 | shp->shm_handle = (void *) kvtopte(kva); |
| 145 | shp->shm_segsz = uap->size; |
| 146 | shp->shm_cpid = p->p_pid; |
| 147 | shp->shm_lpid = shp->shm_nattch = 0; |
| 148 | shp->shm_atime = shp->shm_dtime = 0; |
| 149 | shp->shm_ctime = time.tv_sec; |
| 150 | } else { |
| 151 | shp = &shmsegs[rval]; |
| 152 | /* XXX: probably not the right thing to do */ |
| 153 | if (shp->shm_perm.mode & SHM_DEST) |
| 154 | return (EBUSY); |
| 155 | if (error = ipcaccess(&shp->shm_perm, uap->shmflg&0777, cred)) |
| 156 | return (error); |
| 157 | if (uap->size && uap->size > shp->shm_segsz) |
| 158 | return (EINVAL); |
| 159 | if ((uap->shmflg&IPC_CREAT) && (uap->shmflg&IPC_EXCL)) |
| 160 | return (EEXIST); |
| 161 | } |
| 162 | *retval = shp->shm_perm.seq * SHMMMNI + rval; |
| 163 | return (0); |
| 164 | } |
| 165 | |
| 166 | /* |
| 167 | * Shared memory control |
| 168 | */ |
| 169 | /* ARGSUSED */ |
| 170 | shmctl(p, uap, retval) |
| 171 | struct proc *p; |
| 172 | register struct args { |
| 173 | int shmid; |
| 174 | int cmd; |
| 175 | caddr_t buf; |
| 176 | } *uap; |
| 177 | int *retval; |
| 178 | { |
| 179 | register struct shmid_ds *shp; |
| 180 | register struct ucred *cred = u.u_cred; |
| 181 | struct shmid_ds sbuf; |
| 182 | int error; |
| 183 | |
| 184 | if (error = shmvalid(uap->shmid)) |
| 185 | return (error); |
| 186 | shp = &shmsegs[uap->shmid % SHMMMNI]; |
| 187 | switch (uap->cmd) { |
| 188 | case IPC_STAT: |
| 189 | if (error = ipcaccess(&shp->shm_perm, IPC_R, cred)) |
| 190 | return (error); |
| 191 | return (copyout((caddr_t)shp, uap->buf, sizeof(*shp))); |
| 192 | |
| 193 | case IPC_SET: |
| 194 | if (cred->cr_uid && cred->cr_uid != shp->shm_perm.uid && |
| 195 | cred->cr_uid != shp->shm_perm.cuid) |
| 196 | return (EPERM); |
| 197 | if (error = copyin(uap->buf, (caddr_t)&sbuf, sizeof sbuf)) |
| 198 | return (error); |
| 199 | shp->shm_perm.uid = sbuf.shm_perm.uid; |
| 200 | shp->shm_perm.gid = sbuf.shm_perm.gid; |
| 201 | shp->shm_perm.mode = (shp->shm_perm.mode & ~0777) |
| 202 | | (sbuf.shm_perm.mode & 0777); |
| 203 | shp->shm_ctime = time.tv_sec; |
| 204 | break; |
| 205 | |
| 206 | case IPC_RMID: |
| 207 | if (cred->cr_uid && cred->cr_uid != shp->shm_perm.uid && |
| 208 | cred->cr_uid != shp->shm_perm.cuid) |
| 209 | return (EPERM); |
| 210 | /* set ctime? */ |
| 211 | shp->shm_perm.key = IPC_PRIVATE; |
| 212 | shp->shm_perm.mode |= SHM_DEST; |
| 213 | if (shp->shm_nattch <= 0) |
| 214 | shmfree(shp); |
| 215 | break; |
| 216 | |
| 217 | #ifdef HPUXCOMPAT |
| 218 | case SHM_LOCK: |
| 219 | case SHM_UNLOCK: |
| 220 | /* don't really do anything, but make them think we did */ |
| 221 | if ((p->p_flag & SHPUX) == 0) |
| 222 | return (EINVAL); |
| 223 | if (cred->cr_uid && cred->cr_uid != shp->shm_perm.uid && |
| 224 | cred->cr_uid != shp->shm_perm.cuid) |
| 225 | return (EPERM); |
| 226 | break; |
| 227 | #endif |
| 228 | |
| 229 | default: |
| 230 | return (EINVAL); |
| 231 | } |
| 232 | return (0); |
| 233 | } |
| 234 | |
| 235 | /* |
| 236 | * Attach to shared memory segment. |
| 237 | */ |
| 238 | shmat(p, uap, retval) |
| 239 | struct proc *p; |
| 240 | register struct args { |
| 241 | int shmid; |
| 242 | caddr_t shmaddr; |
| 243 | int shmflg; |
| 244 | } *uap; |
| 245 | int *retval; |
| 246 | { |
| 247 | register struct shmid_ds *shp; |
| 248 | register int size; |
| 249 | struct mapmem *mp; |
| 250 | caddr_t uva; |
| 251 | int error, prot, shmmapin(); |
| 252 | |
| 253 | if (error = shmvalid(uap->shmid)) |
| 254 | return (error); |
| 255 | shp = &shmsegs[uap->shmid % SHMMMNI]; |
| 256 | if (shp->shm_handle == NULL) |
| 257 | panic("shmat NULL handle"); |
| 258 | if (error = ipcaccess(&shp->shm_perm, |
| 259 | (uap->shmflg&SHM_RDONLY) ? IPC_R : IPC_R|IPC_W, u.u_cred)) |
| 260 | return (error); |
| 261 | uva = uap->shmaddr; |
| 262 | if (uva && ((int)uva & (SHMLBA-1))) { |
| 263 | if (uap->shmflg & SHM_RND) |
| 264 | uva = (caddr_t) ((int)uva & ~(SHMLBA-1)); |
| 265 | else |
| 266 | return (EINVAL); |
| 267 | } |
| 268 | /* |
| 269 | * Make sure user doesn't use more than their fair share |
| 270 | */ |
| 271 | size = 0; |
| 272 | for (mp = u.u_mmap; mp; mp = mp->mm_next) |
| 273 | if (mp->mm_ops == &shmops) |
| 274 | size++; |
| 275 | if (size >= shminfo.shmseg) |
| 276 | return (EMFILE); |
| 277 | /* |
| 278 | * Allocate a mapped memory region descriptor and |
| 279 | * attempt to expand the user page table to allow for region |
| 280 | */ |
| 281 | prot = (uap->shmflg & SHM_RDONLY) ? MM_RO : MM_RW; |
| 282 | #if defined(hp300) |
| 283 | prot |= MM_CI; |
| 284 | #endif |
| 285 | size = ctob(clrnd(btoc(shp->shm_segsz))); |
| 286 | error = mmalloc(p, uap->shmid, &uva, (segsz_t)size, prot, &shmops, &mp); |
| 287 | if (error) |
| 288 | return (error); |
| 289 | if (error = mmmapin(p, mp, shmmapin)) { |
| 290 | (void) mmfree(p, mp); |
| 291 | return (error); |
| 292 | } |
| 293 | /* |
| 294 | * Fill in the remaining fields |
| 295 | */ |
| 296 | shp->shm_lpid = p->p_pid; |
| 297 | shp->shm_atime = time.tv_sec; |
| 298 | shp->shm_nattch++; |
| 299 | *retval = (int) uva; |
| 300 | return (0); |
| 301 | } |
| 302 | |
| 303 | /* |
| 304 | * Detach from shared memory segment. |
| 305 | */ |
| 306 | /* ARGSUSED */ |
| 307 | shmdt(p, uap, retval) |
| 308 | struct proc *p; |
| 309 | struct args { |
| 310 | caddr_t shmaddr; |
| 311 | } *uap; |
| 312 | int *retval; |
| 313 | { |
| 314 | register struct mapmem *mp; |
| 315 | |
| 316 | for (mp = u.u_mmap; mp; mp = mp->mm_next) |
| 317 | if (mp->mm_ops == &shmops && mp->mm_uva == uap->shmaddr) |
| 318 | break; |
| 319 | if (mp == MMNIL) |
| 320 | return (EINVAL); |
| 321 | shmsegs[mp->mm_id % SHMMMNI].shm_lpid = p->p_pid; |
| 322 | return (shmufree(p, mp)); |
| 323 | } |
| 324 | |
| 325 | shmmapin(mp, off) |
| 326 | struct mapmem *mp; |
| 327 | { |
| 328 | register struct shmid_ds *shp; |
| 329 | |
| 330 | shp = &shmsegs[mp->mm_id % SHMMMNI]; |
| 331 | if (off >= ctob(clrnd(btoc(shp->shm_segsz)))) |
| 332 | return(-1); |
| 333 | return(((struct pte *)shp->shm_handle)[btop(off)].pg_pfnum); |
| 334 | } |
| 335 | |
| 336 | /* |
| 337 | * Increment attach count on fork |
| 338 | */ |
| 339 | /* ARGSUSED */ |
| 340 | shmfork(mp, ischild) |
| 341 | register struct mapmem *mp; |
| 342 | { |
| 343 | if (!ischild) |
| 344 | shmsegs[mp->mm_id % SHMMMNI].shm_nattch++; |
| 345 | } |
| 346 | |
| 347 | /* |
| 348 | * Detach from shared memory segment on exit (or exec) |
| 349 | */ |
| 350 | shmexit(mp) |
| 351 | struct mapmem *mp; |
| 352 | { |
| 353 | struct proc *p = u.u_procp; /* XXX */ |
| 354 | |
| 355 | return (shmufree(p, mp)); |
| 356 | } |
| 357 | |
| 358 | shmvalid(id) |
| 359 | register int id; |
| 360 | { |
| 361 | register struct shmid_ds *shp; |
| 362 | |
| 363 | if (id < 0 || (id % SHMMMNI) >= shminfo.shmmni) |
| 364 | return(EINVAL); |
| 365 | shp = &shmsegs[id % SHMMMNI]; |
| 366 | if (shp->shm_perm.seq == (id / SHMMMNI) && |
| 367 | (shp->shm_perm.mode & (SHM_ALLOC|SHM_DEST)) == SHM_ALLOC) |
| 368 | return(0); |
| 369 | return(EINVAL); |
| 370 | } |
| 371 | |
| 372 | /* |
| 373 | * Free user resources associated with a shared memory segment |
| 374 | */ |
| 375 | shmufree(p, mp) |
| 376 | struct proc *p; |
| 377 | struct mapmem *mp; |
| 378 | { |
| 379 | register struct shmid_ds *shp; |
| 380 | int error; |
| 381 | |
| 382 | shp = &shmsegs[mp->mm_id % SHMMMNI]; |
| 383 | mmmapout(p, mp); |
| 384 | error = mmfree(p, mp); |
| 385 | shp->shm_dtime = time.tv_sec; |
| 386 | if (--shp->shm_nattch <= 0 && (shp->shm_perm.mode & SHM_DEST)) |
| 387 | shmfree(shp); |
| 388 | return (error); |
| 389 | } |
| 390 | |
| 391 | /* |
| 392 | * Deallocate resources associated with a shared memory segment |
| 393 | */ |
| 394 | shmfree(shp) |
| 395 | register struct shmid_ds *shp; |
| 396 | { |
| 397 | caddr_t kva; |
| 398 | |
| 399 | if (shp->shm_handle == NULL) |
| 400 | panic("shmfree"); |
| 401 | kva = (caddr_t) ptetokv(shp->shm_handle); |
| 402 | free(kva, M_SHM); |
| 403 | shp->shm_handle = NULL; |
| 404 | shmtot -= clrnd(btoc(shp->shm_segsz)); |
| 405 | shp->shm_perm.mode = 0; |
| 406 | /* |
| 407 | * Increment the sequence number to ensure that outstanding |
| 408 | * shmids for this segment will be invalid in the event that |
| 409 | * the segment is reallocated. Note that shmids must be |
| 410 | * positive as decreed by SVID. |
| 411 | */ |
| 412 | shp->shm_perm.seq++; |
| 413 | if ((int)(shp->shm_perm.seq * SHMMMNI) < 0) |
| 414 | shp->shm_perm.seq = 0; |
| 415 | } |
| 416 | |
| 417 | /* |
| 418 | * XXX This routine would be common to all sysV style IPC |
| 419 | * (if the others were implemented). |
| 420 | */ |
| 421 | ipcaccess(ipc, mode, cred) |
| 422 | register struct ipc_perm *ipc; |
| 423 | int mode; |
| 424 | register struct ucred *cred; |
| 425 | { |
| 426 | register int m; |
| 427 | |
| 428 | if (cred->cr_uid == 0) |
| 429 | return(0); |
| 430 | /* |
| 431 | * Access check is based on only one of owner, group, public. |
| 432 | * If not owner, then check group. |
| 433 | * If not a member of the group, then check public access. |
| 434 | */ |
| 435 | mode &= 0700; |
| 436 | m = ipc->mode; |
| 437 | if (cred->cr_uid != ipc->uid && cred->cr_uid != ipc->cuid) { |
| 438 | m <<= 3; |
| 439 | if (!groupmember(ipc->gid, cred) && |
| 440 | !groupmember(ipc->cgid, cred)) |
| 441 | m <<= 3; |
| 442 | } |
| 443 | if ((mode&m) == mode) |
| 444 | return (0); |
| 445 | return (EACCES); |
| 446 | } |
| 447 | |
| 448 | #endif /* SYSVSHM */ |