xref: /freebsd/sys/kern/sysv_shm.c (revision a316b26e50bbed7cf655fbba726ab87d8ab7599d)
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