sysv_shm.c revision 12819
1/*	$Id: sysv_shm.c,v 1.13 1995/12/07 12:46:55 davidg 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/param.h>
35#include <sys/systm.h>
36#include <sys/sysproto.h>
37#include <sys/kernel.h>
38#include <sys/shm.h>
39#include <sys/proc.h>
40#include <sys/malloc.h>
41#include <sys/mman.h>
42#include <sys/stat.h>
43#include <sys/sysent.h>
44
45#include <vm/vm.h>
46#include <vm/vm_param.h>
47#include <vm/vm_prot.h>
48#include <vm/lock.h>
49#include <vm/pmap.h>
50#include <vm/vm_map.h>
51#include <vm/vm_kern.h>
52#include <vm/vm_extern.h>
53
54struct shmat_args;
55extern int shmat __P((struct proc *p, struct shmat_args *uap, int *retval));
56struct shmctl_args;
57extern int shmctl __P((struct proc *p, struct shmctl_args *uap, int *retval));
58struct shmdt_args;
59extern int shmdt __P((struct proc *p, struct shmdt_args *uap, int *retval));
60struct shmget_args;
61extern int shmget __P((struct proc *p, struct shmget_args *uap, int *retval));
62
63static void shminit __P((void *));
64SYSINIT(sysv_shm, SI_SUB_SYSV_SHM, SI_ORDER_FIRST, shminit, NULL)
65
66struct oshmctl_args;
67static int oshmctl __P((struct proc *p, struct oshmctl_args *uap, int *retval));
68static int shmget_allocate_segment __P((struct proc *p, struct shmget_args *uap, int mode, int *retval));
69static int shmget_existing __P((struct proc *p, struct shmget_args *uap, int mode, int segnum, int *retval));
70
71/* XXX casting to (sy_call_t *) is bogus, as usual. */
72sy_call_t *shmcalls[] = {
73	(sy_call_t *)shmat, (sy_call_t *)oshmctl,
74	(sy_call_t *)shmdt, (sy_call_t *)shmget,
75	(sy_call_t *)shmctl
76};
77
78#define	SHMSEG_FREE     	0x0200
79#define	SHMSEG_REMOVED  	0x0400
80#define	SHMSEG_ALLOCATED	0x0800
81#define	SHMSEG_WANTED		0x1000
82
83static vm_map_t sysvshm_map;
84static int shm_last_free, shm_nused, shm_committed;
85struct shmid_ds	*shmsegs;
86
87struct shm_handle {
88	vm_offset_t kva;
89};
90
91struct shmmap_state {
92	vm_offset_t va;
93	int shmid;
94};
95
96static void shm_deallocate_segment __P((struct shmid_ds *));
97static int shm_find_segment_by_key __P((key_t));
98static struct shmid_ds *shm_find_segment_by_shmid __P((int));
99static int shm_delete_mapping __P((struct proc *, struct shmmap_state *));
100
101static int
102shm_find_segment_by_key(key)
103	key_t key;
104{
105	int i;
106
107	for (i = 0; i < shminfo.shmmni; i++)
108		if ((shmsegs[i].shm_perm.mode & SHMSEG_ALLOCATED) &&
109		    shmsegs[i].shm_perm.key == key)
110			return i;
111	return -1;
112}
113
114static struct shmid_ds *
115shm_find_segment_by_shmid(shmid)
116	int shmid;
117{
118	int segnum;
119	struct shmid_ds *shmseg;
120
121	segnum = IPCID_TO_IX(shmid);
122	if (segnum < 0 || segnum >= shminfo.shmmni)
123		return NULL;
124	shmseg = &shmsegs[segnum];
125	if ((shmseg->shm_perm.mode & (SHMSEG_ALLOCATED | SHMSEG_REMOVED))
126	    != SHMSEG_ALLOCATED ||
127	    shmseg->shm_perm.seq != IPCID_TO_SEQ(shmid))
128		return NULL;
129	return shmseg;
130}
131
132static void
133shm_deallocate_segment(shmseg)
134	struct shmid_ds *shmseg;
135{
136	struct shm_handle *shm_handle;
137	size_t size;
138
139	shm_handle = shmseg->shm_internal;
140	size = (shmseg->shm_segsz + CLOFSET) & ~CLOFSET;
141	(void) vm_map_remove(sysvshm_map, shm_handle->kva, shm_handle->kva + size);
142	free((caddr_t)shm_handle, M_SHM);
143	shmseg->shm_internal = NULL;
144	shm_committed -= btoc(size);
145	shm_nused--;
146	shmseg->shm_perm.mode = SHMSEG_FREE;
147}
148
149static int
150shm_delete_mapping(p, shmmap_s)
151	struct proc *p;
152	struct shmmap_state *shmmap_s;
153{
154	struct shmid_ds *shmseg;
155	int segnum, result;
156	size_t size;
157
158	segnum = IPCID_TO_IX(shmmap_s->shmid);
159	shmseg = &shmsegs[segnum];
160	size = (shmseg->shm_segsz + CLOFSET) & ~CLOFSET;
161	result = vm_map_remove(&p->p_vmspace->vm_map, shmmap_s->va, shmmap_s->va + size);
162	if (result != KERN_SUCCESS)
163		return EINVAL;
164	shmmap_s->shmid = -1;
165	shmseg->shm_dtime = time.tv_sec;
166	if ((--shmseg->shm_nattch <= 0) &&
167	    (shmseg->shm_perm.mode & SHMSEG_REMOVED)) {
168		shm_deallocate_segment(shmseg);
169		shm_last_free = segnum;
170	}
171	return 0;
172}
173
174struct shmdt_args {
175	void *shmaddr;
176};
177int
178shmdt(p, uap, retval)
179	struct proc *p;
180	struct shmdt_args *uap;
181	int *retval;
182{
183	struct shmmap_state *shmmap_s;
184	int i;
185
186	shmmap_s = (struct shmmap_state *)p->p_vmspace->vm_shm;
187 	if (shmmap_s == NULL)
188 	    return EINVAL;
189	for (i = 0; i < shminfo.shmseg; i++, shmmap_s++)
190		if (shmmap_s->shmid != -1 &&
191		    shmmap_s->va == (vm_offset_t)uap->shmaddr)
192			break;
193	if (i == shminfo.shmseg)
194		return EINVAL;
195	return shm_delete_mapping(p, shmmap_s);
196}
197
198struct shmat_args {
199	int shmid;
200	void *shmaddr;
201	int shmflg;
202};
203int
204shmat(p, uap, retval)
205	struct proc *p;
206	struct shmat_args *uap;
207	int *retval;
208{
209	int error, i, flags;
210	struct ucred *cred = p->p_ucred;
211	struct shmid_ds *shmseg;
212	struct shmmap_state *shmmap_s = NULL;
213	vm_offset_t attach_va;
214	vm_prot_t prot;
215	vm_size_t size;
216
217	shmmap_s = (struct shmmap_state *)p->p_vmspace->vm_shm;
218	if (shmmap_s == NULL) {
219		size = shminfo.shmseg * sizeof(struct shmmap_state);
220		shmmap_s = malloc(size, M_SHM, M_WAITOK);
221		for (i = 0; i < shminfo.shmseg; i++)
222			shmmap_s[i].shmid = -1;
223		p->p_vmspace->vm_shm = (caddr_t)shmmap_s;
224	}
225	shmseg = shm_find_segment_by_shmid(uap->shmid);
226	if (shmseg == NULL)
227		return EINVAL;
228	error = ipcperm(cred, &shmseg->shm_perm,
229	    (uap->shmflg & SHM_RDONLY) ? IPC_R : IPC_R|IPC_W);
230	if (error)
231		return error;
232	for (i = 0; i < shminfo.shmseg; i++) {
233		if (shmmap_s->shmid == -1)
234			break;
235		shmmap_s++;
236	}
237	if (i >= shminfo.shmseg)
238		return EMFILE;
239	size = (shmseg->shm_segsz + CLOFSET) & ~CLOFSET;
240	prot = VM_PROT_READ;
241	if ((uap->shmflg & SHM_RDONLY) == 0)
242		prot |= VM_PROT_WRITE;
243	flags = MAP_ANON | MAP_SHARED;
244	if (uap->shmaddr) {
245		flags |= MAP_FIXED;
246		if (uap->shmflg & SHM_RND)
247			attach_va = (vm_offset_t)uap->shmaddr & ~(SHMLBA-1);
248		else if (((vm_offset_t)uap->shmaddr & (SHMLBA-1)) == 0)
249			attach_va = (vm_offset_t)uap->shmaddr;
250		else
251			return EINVAL;
252	} else {
253		/* This is just a hint to vm_mmap() about where to put it. */
254		attach_va = round_page(p->p_vmspace->vm_taddr + MAXTSIZ + MAXDSIZ);
255	}
256	error = vm_mmap(&p->p_vmspace->vm_map, &attach_va, size, prot,
257	    VM_PROT_DEFAULT, flags, (caddr_t) uap->shmid, 0);
258	if (error)
259		return error;
260	shmmap_s->va = attach_va;
261	shmmap_s->shmid = uap->shmid;
262	shmseg->shm_lpid = p->p_pid;
263	shmseg->shm_atime = time.tv_sec;
264	shmseg->shm_nattch++;
265	*retval = attach_va;
266	return 0;
267}
268
269struct oshmid_ds {
270	struct	ipc_perm shm_perm;	/* operation perms */
271	int	shm_segsz;		/* size of segment (bytes) */
272	ushort	shm_cpid;		/* pid, creator */
273	ushort	shm_lpid;		/* pid, last operation */
274	short	shm_nattch;		/* no. of current attaches */
275	time_t	shm_atime;		/* last attach time */
276	time_t	shm_dtime;		/* last detach time */
277	time_t	shm_ctime;		/* last change time */
278	void	*shm_handle;		/* internal handle for shm segment */
279};
280
281struct oshmctl_args {
282	int shmid;
283	int cmd;
284	struct oshmid_ds *ubuf;
285};
286
287static int
288oshmctl(p, uap, retval)
289	struct proc *p;
290	struct oshmctl_args *uap;
291	int *retval;
292{
293#ifdef COMPAT_43
294	int error;
295	struct ucred *cred = p->p_ucred;
296	struct shmid_ds *shmseg;
297	struct oshmid_ds outbuf;
298
299	shmseg = shm_find_segment_by_shmid(uap->shmid);
300	if (shmseg == NULL)
301		return EINVAL;
302	switch (uap->cmd) {
303	case IPC_STAT:
304		error = ipcperm(cred, &shmseg->shm_perm, IPC_R);
305		if (error)
306			return error;
307		outbuf.shm_perm = shmseg->shm_perm;
308		outbuf.shm_segsz = shmseg->shm_segsz;
309		outbuf.shm_cpid = shmseg->shm_cpid;
310		outbuf.shm_lpid = shmseg->shm_lpid;
311		outbuf.shm_nattch = shmseg->shm_nattch;
312		outbuf.shm_atime = shmseg->shm_atime;
313		outbuf.shm_dtime = shmseg->shm_dtime;
314		outbuf.shm_ctime = shmseg->shm_ctime;
315		outbuf.shm_handle = shmseg->shm_internal;
316		error = copyout((caddr_t)&outbuf, uap->ubuf, sizeof(outbuf));
317		if (error)
318			return error;
319		break;
320	default:
321		/* XXX casting to (sy_call_t *) is bogus, as usual. */
322		return ((sy_call_t *)shmctl)(p, uap, retval);
323	}
324	return 0;
325#else
326	return EINVAL;
327#endif
328}
329
330struct shmctl_args {
331	int shmid;
332	int cmd;
333	struct shmid_ds *ubuf;
334};
335int
336shmctl(p, uap, retval)
337	struct proc *p;
338	struct shmctl_args *uap;
339	int *retval;
340{
341	int error;
342	struct ucred *cred = p->p_ucred;
343	struct shmid_ds inbuf;
344	struct shmid_ds *shmseg;
345
346	shmseg = shm_find_segment_by_shmid(uap->shmid);
347	if (shmseg == NULL)
348		return EINVAL;
349	switch (uap->cmd) {
350	case IPC_STAT:
351		error = ipcperm(cred, &shmseg->shm_perm, IPC_R);
352		if (error)
353			return error;
354		error = copyout((caddr_t)shmseg, uap->ubuf, sizeof(inbuf));
355		if (error)
356			return error;
357		break;
358	case IPC_SET:
359		error = ipcperm(cred, &shmseg->shm_perm, IPC_M);
360		if (error)
361			return error;
362		error = copyin(uap->ubuf, (caddr_t)&inbuf, sizeof(inbuf));
363		if (error)
364			return error;
365		shmseg->shm_perm.uid = inbuf.shm_perm.uid;
366		shmseg->shm_perm.gid = inbuf.shm_perm.gid;
367		shmseg->shm_perm.mode =
368		    (shmseg->shm_perm.mode & ~ACCESSPERMS) |
369		    (inbuf.shm_perm.mode & ACCESSPERMS);
370		shmseg->shm_ctime = time.tv_sec;
371		break;
372	case IPC_RMID:
373		error = ipcperm(cred, &shmseg->shm_perm, IPC_M);
374		if (error)
375			return error;
376		shmseg->shm_perm.key = IPC_PRIVATE;
377		shmseg->shm_perm.mode |= SHMSEG_REMOVED;
378		if (shmseg->shm_nattch <= 0) {
379			shm_deallocate_segment(shmseg);
380			shm_last_free = IPCID_TO_IX(uap->shmid);
381		}
382		break;
383#if 0
384	case SHM_LOCK:
385	case SHM_UNLOCK:
386#endif
387	default:
388		return EINVAL;
389	}
390	return 0;
391}
392
393struct shmget_args {
394	key_t key;
395	size_t size;
396	int shmflg;
397};
398static int
399shmget_existing(p, uap, mode, segnum, retval)
400	struct proc *p;
401	struct shmget_args *uap;
402	int mode;
403	int segnum;
404	int *retval;
405{
406	struct shmid_ds *shmseg;
407	struct ucred *cred = p->p_ucred;
408	int error;
409
410	shmseg = &shmsegs[segnum];
411	if (shmseg->shm_perm.mode & SHMSEG_REMOVED) {
412		/*
413		 * This segment is in the process of being allocated.  Wait
414		 * until it's done, and look the key up again (in case the
415		 * allocation failed or it was freed).
416		 */
417		shmseg->shm_perm.mode |= SHMSEG_WANTED;
418		error = tsleep((caddr_t)shmseg, PLOCK | PCATCH, "shmget", 0);
419		if (error)
420			return error;
421		return EAGAIN;
422	}
423	error = ipcperm(cred, &shmseg->shm_perm, mode);
424	if (error)
425		return error;
426	if (uap->size && uap->size > shmseg->shm_segsz)
427		return EINVAL;
428	if (uap->shmflg & (IPC_CREAT | IPC_EXCL) == (IPC_CREAT | IPC_EXCL))
429		return EEXIST;
430	*retval = IXSEQ_TO_IPCID(segnum, shmseg->shm_perm);
431	return 0;
432}
433
434static int
435shmget_allocate_segment(p, uap, mode, retval)
436	struct proc *p;
437	struct shmget_args *uap;
438	int mode;
439	int *retval;
440{
441	int i, segnum, result, shmid, size;
442	struct ucred *cred = p->p_ucred;
443	struct shmid_ds *shmseg;
444	struct shm_handle *shm_handle;
445
446	if (uap->size < shminfo.shmmin || uap->size > shminfo.shmmax)
447		return EINVAL;
448	if (shm_nused >= shminfo.shmmni) /* any shmids left? */
449		return ENOSPC;
450	size = (uap->size + CLOFSET) & ~CLOFSET;
451	if (shm_committed + btoc(size) > shminfo.shmall)
452		return ENOMEM;
453	if (shm_last_free < 0) {
454		for (i = 0; i < shminfo.shmmni; i++)
455			if (shmsegs[i].shm_perm.mode & SHMSEG_FREE)
456				break;
457		if (i == shminfo.shmmni)
458			panic("shmseg free count inconsistent");
459		segnum = i;
460	} else  {
461		segnum = shm_last_free;
462		shm_last_free = -1;
463	}
464	shmseg = &shmsegs[segnum];
465	/*
466	 * In case we sleep in malloc(), mark the segment present but deleted
467	 * so that noone else tries to create the same key.
468	 */
469	shmseg->shm_perm.mode = SHMSEG_ALLOCATED | SHMSEG_REMOVED;
470	shmseg->shm_perm.key = uap->key;
471	shmseg->shm_perm.seq = (shmseg->shm_perm.seq + 1) & 0x7fff;
472	shm_handle = (struct shm_handle *)
473	    malloc(sizeof(struct shm_handle), M_SHM, M_WAITOK);
474	shmid = IXSEQ_TO_IPCID(segnum, shmseg->shm_perm);
475	result = vm_mmap(sysvshm_map, &shm_handle->kva, size, VM_PROT_ALL,
476	    VM_PROT_DEFAULT, MAP_ANON, (caddr_t) shmid, 0);
477	if (result != KERN_SUCCESS) {
478		shmseg->shm_perm.mode = SHMSEG_FREE;
479		shm_last_free = segnum;
480		free((caddr_t)shm_handle, M_SHM);
481		/* Just in case. */
482		wakeup((caddr_t)shmseg);
483		return ENOMEM;
484	}
485	shmseg->shm_internal = shm_handle;
486	shmseg->shm_perm.cuid = shmseg->shm_perm.uid = cred->cr_uid;
487	shmseg->shm_perm.cgid = shmseg->shm_perm.gid = cred->cr_gid;
488	shmseg->shm_perm.mode = (shmseg->shm_perm.mode & SHMSEG_WANTED) |
489	    (mode & ACCESSPERMS) | SHMSEG_ALLOCATED;
490	shmseg->shm_segsz = uap->size;
491	shmseg->shm_cpid = p->p_pid;
492	shmseg->shm_lpid = shmseg->shm_nattch = 0;
493	shmseg->shm_atime = shmseg->shm_dtime = 0;
494	shmseg->shm_ctime = time.tv_sec;
495	shm_committed += btoc(size);
496	shm_nused++;
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		wakeup((caddr_t)shmseg);
504	}
505	*retval = shmid;
506	return 0;
507}
508
509int
510shmget(p, uap, retval)
511	struct proc *p;
512	struct shmget_args *uap;
513	int *retval;
514{
515	int segnum, mode, error;
516
517	mode = uap->shmflg & ACCESSPERMS;
518	if (uap->key != IPC_PRIVATE) {
519	again:
520		segnum = shm_find_segment_by_key(uap->key);
521		if (segnum >= 0) {
522			error = shmget_existing(p, uap, mode, segnum, retval);
523			if (error == EAGAIN)
524				goto again;
525			return error;
526		}
527		if ((uap->shmflg & IPC_CREAT) == 0)
528			return ENOENT;
529	}
530	return shmget_allocate_segment(p, uap, mode, retval);
531}
532
533int
534shmsys(p, uap, retval)
535	struct proc *p;
536	/* XXX actually varargs. */
537	struct shmsys_args /* {
538		u_int	which;
539		int	a2;
540		int	a3;
541		int	a4;
542	} */ *uap;
543	int *retval;
544{
545
546	if (uap->which >= sizeof(shmcalls)/sizeof(shmcalls[0]))
547		return EINVAL;
548	return ((*shmcalls[uap->which])(p, &uap->a2, retval));
549}
550
551void
552shmfork(p1, p2, isvfork)
553	struct proc *p1, *p2;
554	int isvfork;
555{
556	struct shmmap_state *shmmap_s;
557	size_t size;
558	int i;
559
560	size = shminfo.shmseg * sizeof(struct shmmap_state);
561	shmmap_s = malloc(size, M_SHM, M_WAITOK);
562	bcopy((caddr_t)p1->p_vmspace->vm_shm, (caddr_t)shmmap_s, size);
563	p2->p_vmspace->vm_shm = (caddr_t)shmmap_s;
564	for (i = 0; i < shminfo.shmseg; i++, shmmap_s++)
565		if (shmmap_s->shmid != -1)
566			shmsegs[IPCID_TO_IX(shmmap_s->shmid)].shm_nattch++;
567}
568
569void
570shmexit(p)
571	struct proc *p;
572{
573	struct shmmap_state *shmmap_s;
574	int i;
575
576	shmmap_s = (struct shmmap_state *)p->p_vmspace->vm_shm;
577	for (i = 0; i < shminfo.shmseg; i++, shmmap_s++)
578		if (shmmap_s->shmid != -1)
579			shm_delete_mapping(p, shmmap_s);
580	free((caddr_t)p->p_vmspace->vm_shm, M_SHM);
581	p->p_vmspace->vm_shm = NULL;
582}
583
584void
585shminit(dummy)
586	void *dummy;
587{
588	int i;
589	vm_offset_t garbage1, garbage2;
590
591	/* actually this *should* be pageable.  SHM_{LOCK,UNLOCK} */
592	sysvshm_map = kmem_suballoc(kernel_map, &garbage1, &garbage2,
593				    shminfo.shmall * NBPG, TRUE);
594	for (i = 0; i < shminfo.shmmni; i++) {
595		shmsegs[i].shm_perm.mode = SHMSEG_FREE;
596		shmsegs[i].shm_perm.seq = 0;
597	}
598	shm_last_free = 0;
599	shm_nused = 0;
600	shm_committed = 0;
601}
602