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