xref: /openbsd-src/sys/kern/sysv_msg.c (revision 0b7734b3d77bb9b21afec6f4621cae6c805dbd45)
1 /*	$OpenBSD: sysv_msg.c,v 1.31 2015/10/07 14:49:04 deraadt Exp $	*/
2 /*	$NetBSD: sysv_msg.c,v 1.19 1996/02/09 19:00:18 christos Exp $	*/
3 /*
4  * Copyright (c) 2009 Bret S. Lambert <blambert@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 /*
19  * Implementation of SVID messages
20  *
21  * Author:  Daniel Boulet
22  *
23  * Copyright 1993 Daniel Boulet and RTMX Inc.
24  *
25  * This system call was implemented by Daniel Boulet under contract from RTMX.
26  *
27  * Redistribution and use in source forms, with and without modification,
28  * are permitted provided that this entire comment appears intact.
29  *
30  * Redistribution in binary form may occur without any restrictions.
31  * Obviously, it would be nice if you gave credit where credit is due
32  * but requiring it would be too onerous.
33  *
34  * This software is provided ``AS IS'' without any warranties of any kind.
35  */
36 
37 #include <sys/param.h>
38 #include <sys/malloc.h>
39 #include <sys/mbuf.h>
40 #include <sys/mount.h>
41 #include <sys/msg.h>
42 #include <sys/pool.h>
43 #include <sys/proc.h>
44 #include <sys/queue.h>
45 #include <sys/syscallargs.h>
46 #include <sys/sysctl.h>
47 #include <sys/systm.h>
48 #include <sys/uio.h>
49 
50 struct que *que_create(key_t, struct ucred *, int);
51 struct que *que_lookup(int);
52 struct que *que_key_lookup(key_t);
53 void que_wakewriters(void);
54 void que_free(struct que *);
55 struct msg *msg_create(struct que *);
56 void msg_free(struct msg *);
57 void msg_enqueue(struct que *, struct msg *, struct proc *);
58 void msg_dequeue(struct que *, struct msg *, struct proc *);
59 struct msg *msg_lookup(struct que *, int);
60 int msg_copyin(struct msg *, const char *, size_t, struct proc *);
61 int msg_copyout(struct msg *, char *, size_t *, struct proc *);
62 
63 struct	pool sysvmsgpl;
64 struct	msginfo msginfo;
65 
66 TAILQ_HEAD(, que) msg_queues;
67 
68 int num_ques;
69 int num_msgs;
70 int sequence;
71 int maxmsgs;
72 
73 void
74 msginit(void)
75 {
76 	msginfo.msgmax = MSGMAX;
77 	msginfo.msgmni = MSGMNI;
78 	msginfo.msgmnb = MSGMNB;
79 	msginfo.msgtql = MSGTQL;
80 	msginfo.msgssz = MSGSSZ;
81 	msginfo.msgseg = MSGSEG;
82 
83 	pool_init(&sysvmsgpl, sizeof(struct msg), 0, 0, PR_WAITOK, "sysvmsgpl",
84 	    NULL);
85 
86 	TAILQ_INIT(&msg_queues);
87 
88 	num_ques = 0;
89 	num_msgs = 0;
90 	sequence = 1;
91 	maxmsgs = 0;
92 }
93 
94 int
95 sys_msgctl(struct proc *p, void *v, register_t *retval)
96 {
97 	struct sys_msgctl_args /* {
98 		syscallarg(int) msqid;
99 		syscallarg(int) cmd;
100 		syscallarg(struct msqid_ds *) buf;
101 	} */ *uap = v;
102 
103 	return (msgctl1(p, SCARG(uap, msqid), SCARG(uap, cmd),
104 	    (caddr_t)SCARG(uap, buf), copyin, copyout));
105 }
106 
107 int
108 msgctl1(struct proc *p, int msqid, int cmd, caddr_t buf,
109     int (*ds_copyin)(const void *, void *, size_t),
110     int (*ds_copyout)(const void *, void *, size_t))
111 {
112 	struct msqid_ds tmp;
113 	struct ucred *cred = p->p_ucred;
114 	struct que *que;
115 	int error = 0;
116 
117 	if ((que = que_lookup(msqid)) == NULL)
118 		return (EINVAL);
119 
120 	QREF(que);
121 
122 	switch (cmd) {
123 
124 	case IPC_RMID:
125 		if ((error = ipcperm(cred, &que->msqid_ds.msg_perm, IPC_M)))
126 			goto out;
127 
128 		TAILQ_REMOVE(&msg_queues, que, que_next);
129 		que->que_flags |= MSGQ_DYING;
130 
131 		/* lose interest in the queue and wait for others to too */
132 		if (--que->que_references > 0) {
133 			wakeup(que);
134 			tsleep(&que->que_references, PZERO, "msgqrm", 0);
135 		}
136 
137 		que_free(que);
138 
139 		return (0);
140 
141 	case IPC_SET:
142 		if ((error = ipcperm(cred, &que->msqid_ds.msg_perm, IPC_M)))
143 			goto out;
144 		if ((error = ds_copyin(buf, &tmp, sizeof(struct msqid_ds))))
145 			goto out;
146 
147 		/* only superuser can bump max bytes in queue */
148 		if (tmp.msg_qbytes > que->msqid_ds.msg_qbytes &&
149 		    cred->cr_uid != 0) {
150 			error = EPERM;
151 			goto out;
152 		}
153 
154 		/* restrict max bytes in queue to system limit */
155 		if (tmp.msg_qbytes > msginfo.msgmnb)
156 			tmp.msg_qbytes = msginfo.msgmnb;
157 
158 		/* can't reduce msg_bytes to 0 */
159 		if (tmp.msg_qbytes == 0) {
160 			error = EINVAL;		/* non-standard errno! */
161 			goto out;
162 		}
163 
164 		que->msqid_ds.msg_perm.uid = tmp.msg_perm.uid;
165 		que->msqid_ds.msg_perm.gid = tmp.msg_perm.gid;
166 		que->msqid_ds.msg_perm.mode =
167 		    (que->msqid_ds.msg_perm.mode & ~0777) |
168 		    (tmp.msg_perm.mode & 0777);
169 		que->msqid_ds.msg_qbytes = tmp.msg_qbytes;
170 		que->msqid_ds.msg_ctime = time_second;
171 		break;
172 
173 	case IPC_STAT:
174 		if ((error = ipcperm(cred, &que->msqid_ds.msg_perm, IPC_R)))
175 			goto out;
176 		error = ds_copyout(&que->msqid_ds, buf,
177 		    sizeof(struct msqid_ds));
178 		break;
179 
180 	default:
181 		error = EINVAL;
182 		break;
183 	}
184 out:
185 	QRELE(que);
186 
187 	return (error);
188 }
189 
190 int
191 sys_msgget(struct proc *p, void *v, register_t *retval)
192 {
193 	struct sys_msgget_args /* {
194 		syscallarg(key_t) key;
195 		syscallarg(int) msgflg;
196 	} */ *uap = v;
197 	struct ucred *cred = p->p_ucred;
198 	struct que *que;
199 	key_t key = SCARG(uap, key);
200 	int msgflg = SCARG(uap, msgflg);
201 	int error = 0;
202 
203 again:
204 	if (key != IPC_PRIVATE) {
205 		que = que_key_lookup(key);
206 		if (que) {
207 			if ((msgflg & IPC_CREAT) && (msgflg & IPC_EXCL))
208 				return (EEXIST);
209 			if ((error = ipcperm(cred, &que->msqid_ds.msg_perm,
210 			    msgflg & 0700)))
211 				return (error);
212 			goto found;
213 		}
214 	}
215 
216 	/* don't create a new message queue if the caller doesn't want to */
217 	if (key != IPC_PRIVATE && !(msgflg & IPC_CREAT))
218 		return (ENOENT);
219 
220 	/* enforce limits on the maximum number of message queues */
221 	if (num_ques >= msginfo.msgmni)
222 		return (ENOSPC);
223 
224 	/*
225 	 * if que_create returns NULL, it means that a que with an identical
226 	 * key was created while this process was sleeping, so start over
227 	 */
228 	if ((que = que_create(key, cred, msgflg & 0777)) == NULL)
229 		goto again;
230 
231 found:
232 	*retval = IXSEQ_TO_IPCID(que->que_ix, que->msqid_ds.msg_perm);
233 	return (error);
234 }
235 
236 #define	MSGQ_SPACE(q)	((q)->msqid_ds.msg_qbytes - (q)->msqid_ds.msg_cbytes)
237 
238 int
239 sys_msgsnd(struct proc *p, void *v, register_t *retval)
240 {
241 	struct sys_msgsnd_args /* {
242 		syscallarg(int) msqid;
243 		syscallarg(const void *) msgp;
244 		syscallarg(size_t) msgsz;
245 		syscallarg(int) msgflg;
246 	} */ *uap = v;
247 	struct ucred *cred = p->p_ucred;
248 	struct que *que;
249 	struct msg *msg;
250 	size_t msgsz = SCARG(uap, msgsz);
251 	int error;
252 
253 	if ((que = que_lookup(SCARG(uap, msqid))) == NULL)
254 		return (EINVAL);
255 
256 	if (msgsz > que->msqid_ds.msg_qbytes || msgsz > msginfo.msgmax)
257 		return (EINVAL);
258 
259 	if ((error = ipcperm(cred, &que->msqid_ds.msg_perm, IPC_W)))
260 		return (error);
261 
262 	QREF(que);
263 
264 	while (MSGQ_SPACE(que) < msgsz || num_msgs >= msginfo.msgtql) {
265 
266 		if (SCARG(uap, msgflg) & IPC_NOWAIT) {
267 			error = EAGAIN;
268 			goto out;
269 		}
270 
271 		/* notify world that process may wedge here */
272 		if (num_msgs >= msginfo.msgtql)
273 			maxmsgs = 1;
274 
275 		que->que_flags |= MSGQ_WRITERS;
276 		if ((error = tsleep(que, PZERO|PCATCH, "msgwait", 0)))
277 			goto out;
278 
279 		if (que->que_flags & MSGQ_DYING) {
280 			error = EIDRM;
281 			goto out;
282 		}
283 	}
284 
285 	/* if msg_create returns NULL, the queue is being removed */
286 	if ((msg = msg_create(que)) == NULL) {
287 		error = EIDRM;
288 		goto out;
289 	}
290 
291 	/* msg_copyin frees msg on error */
292 	if ((error = msg_copyin(msg, (const char *)SCARG(uap, msgp), msgsz, p)))
293 		goto out;
294 
295 	msg_enqueue(que, msg, p);
296 
297 	if (que->que_flags & MSGQ_READERS) {
298 		que->que_flags &= ~MSGQ_READERS;
299 		wakeup(que);
300 	}
301 
302 	if (que->que_flags & MSGQ_DYING) {
303 		error = EIDRM;
304 		wakeup(que);
305 	}
306 out:
307 	QRELE(que);
308 
309 	return (error);
310 }
311 
312 int
313 sys_msgrcv(struct proc *p, void *v, register_t *retval)
314 {
315 	struct sys_msgrcv_args /* {
316 		syscallarg(int) msqid;
317 		syscallarg(void *) msgp;
318 		syscallarg(size_t) msgsz;
319 		syscallarg(long) msgtyp;
320 		syscallarg(int) msgflg;
321 	} */ *uap = v;
322 	struct ucred *cred = p->p_ucred;
323 	char *msgp = SCARG(uap, msgp);
324 	struct que *que;
325 	struct msg *msg;
326 	size_t msgsz = SCARG(uap, msgsz);
327 	long msgtyp = SCARG(uap, msgtyp);
328 	int error;
329 
330 	if ((que = que_lookup(SCARG(uap, msqid))) == NULL)
331 		return (EINVAL);
332 
333 	if ((error = ipcperm(cred, &que->msqid_ds.msg_perm, IPC_R)))
334 		return (error);
335 
336 	QREF(que);
337 
338 	/* msg_lookup handles matching; sleeping gets handled here */
339 	while ((msg = msg_lookup(que, msgtyp)) == NULL) {
340 
341 		if (SCARG(uap, msgflg) & IPC_NOWAIT) {
342 			error = ENOMSG;
343 			goto out;
344 		}
345 
346 		que->que_flags |= MSGQ_READERS;
347 		if ((error = tsleep(que, PZERO|PCATCH, "msgwait", 0)))
348 			goto out;
349 
350 		/* make sure the queue still alive */
351 		if (que->que_flags & MSGQ_DYING) {
352 			error = EIDRM;
353 			goto out;
354 		}
355 	}
356 
357 	/* if msg_copyout fails, keep the message around so it isn't lost */
358 	if ((error = msg_copyout(msg, msgp, &msgsz, p)))
359 		goto out;
360 
361 	msg_dequeue(que, msg, p);
362 	msg_free(msg);
363 
364 	if (que->que_flags & MSGQ_WRITERS) {
365 		que->que_flags &= ~MSGQ_WRITERS;
366 		wakeup(que);
367 	}
368 
369 	/* ensure processes waiting on the global limit don't wedge */
370 	if (maxmsgs) {
371 		maxmsgs = 0;
372 		que_wakewriters();
373 	}
374 
375 	*retval = msgsz;
376 out:
377 	QRELE(que);
378 
379 	return (error);
380 }
381 
382 /*
383  * que management functions
384  */
385 
386 struct que *
387 que_create(key_t key, struct ucred *cred, int mode)
388 {
389 	struct que *que, *que2;
390 	int nextix = 1;
391 
392 	que = malloc(sizeof(*que), M_TEMP, M_WAIT|M_ZERO);
393 
394 	/* if malloc slept, a queue with the same key may have been created */
395 	if (que_key_lookup(key)) {
396 		free(que, M_TEMP, sizeof *que);
397 		return (NULL);
398 	}
399 
400 	/* find next available "index" */
401 	TAILQ_FOREACH(que2, &msg_queues, que_next) {
402 		if (nextix < que2->que_ix)
403 			break;
404 		nextix = que2->que_ix + 1;
405 	}
406 	que->que_ix = nextix;
407 
408 	que->msqid_ds.msg_perm.key = key;
409 	que->msqid_ds.msg_perm.cuid = cred->cr_uid;
410 	que->msqid_ds.msg_perm.uid = cred->cr_uid;
411 	que->msqid_ds.msg_perm.cgid = cred->cr_gid;
412 	que->msqid_ds.msg_perm.gid = cred->cr_gid;
413 	que->msqid_ds.msg_perm.mode = mode & 0777;
414 	que->msqid_ds.msg_perm.seq = ++sequence & 0x7fff;
415 	que->msqid_ds.msg_qbytes = msginfo.msgmnb;
416 	que->msqid_ds.msg_ctime = time_second;
417 
418 	TAILQ_INIT(&que->que_msgs);
419 
420 	/* keep queues in "index" order */
421 	if (que2)
422 		TAILQ_INSERT_BEFORE(que2, que, que_next);
423 	else
424 		TAILQ_INSERT_TAIL(&msg_queues, que, que_next);
425 	num_ques++;
426 
427 	return (que);
428 }
429 
430 struct que *
431 que_lookup(int id)
432 {
433 	struct que *que;
434 
435 	TAILQ_FOREACH(que, &msg_queues, que_next)
436 		if (que->que_ix == IPCID_TO_IX(id))
437 			break;
438 
439 	/* don't return queues marked for removal */
440 	if (que && que->que_flags & MSGQ_DYING)
441 		return (NULL);
442 
443 	return (que);
444 }
445 
446 struct que *
447 que_key_lookup(key_t key)
448 {
449 	struct que *que;
450 
451 	if (key == IPC_PRIVATE)
452 		return (NULL);
453 
454 	TAILQ_FOREACH(que, &msg_queues, que_next)
455 		if (que->msqid_ds.msg_perm.key == key)
456 			break;
457 
458 	/* don't return queues marked for removal */
459 	if (que && que->que_flags & MSGQ_DYING)
460 		return (NULL);
461 
462 	return (que);
463 }
464 
465 void
466 que_wakewriters(void)
467 {
468 	struct que *que;
469 
470 	TAILQ_FOREACH(que, &msg_queues, que_next) {
471 		if (que->que_flags & MSGQ_WRITERS) {
472 			que->que_flags &= ~MSGQ_WRITERS;
473 			wakeup(que);
474 		}
475 	}
476 }
477 
478 void
479 que_free(struct que *que)
480 {
481 	struct msg *msg;
482 #ifdef DIAGNOSTIC
483 	if (que->que_references > 0)
484 		panic("freeing message queue with active references");
485 #endif
486 
487 	while ((msg = TAILQ_FIRST(&que->que_msgs))) {
488 		TAILQ_REMOVE(&que->que_msgs, msg, msg_next);
489 		msg_free(msg);
490 	}
491 	free(que, M_TEMP, sizeof *que);
492 	num_ques--;
493 }
494 
495 /*
496  * msg management functions
497  */
498 
499 struct msg *
500 msg_create(struct que *que)
501 {
502 	struct msg *msg;
503 
504 	msg = pool_get(&sysvmsgpl, PR_WAITOK|PR_ZERO);
505 
506 	/* if the queue has died during allocation, return NULL */
507 	if (que->que_flags & MSGQ_DYING) {
508 		pool_put(&sysvmsgpl, msg);
509 		wakeup(que);
510 		return(NULL);
511 	}
512 
513 	num_msgs++;
514 
515 	return (msg);
516 }
517 
518 struct msg *
519 msg_lookup(struct que *que, int msgtyp)
520 {
521 	struct msg *msg;
522 
523 	/*
524 	 * Three different matches are performed based on the value of msgtyp:
525 	 * 1) msgtyp > 0 => match exactly
526 	 * 2) msgtyp = 0 => match any
527 	 * 3) msgtyp < 0 => match any up to absolute value of msgtyp
528 	 */
529 	TAILQ_FOREACH(msg, &que->que_msgs, msg_next)
530 		if (msgtyp == 0 || msgtyp == msg->msg_type ||
531 		    (msgtyp < 0 && -msgtyp <= msg->msg_type))
532 			break;
533 
534 	return (msg);
535 }
536 
537 void
538 msg_free(struct msg *msg)
539 {
540 	m_freem(msg->msg_data);
541 	pool_put(&sysvmsgpl, msg);
542 	num_msgs--;
543 }
544 
545 void
546 msg_enqueue(struct que *que, struct msg *msg, struct proc *p)
547 {
548 	que->msqid_ds.msg_cbytes += msg->msg_len;
549 	que->msqid_ds.msg_qnum++;
550 	que->msqid_ds.msg_lspid = p->p_p->ps_pid;
551 	que->msqid_ds.msg_stime = time_second;
552 
553 	TAILQ_INSERT_TAIL(&que->que_msgs, msg, msg_next);
554 }
555 
556 void
557 msg_dequeue(struct que *que, struct msg *msg, struct proc *p)
558 {
559 	que->msqid_ds.msg_cbytes -= msg->msg_len;
560 	que->msqid_ds.msg_qnum--;
561 	que->msqid_ds.msg_lrpid = p->p_p->ps_pid;
562 	que->msqid_ds.msg_rtime = time_second;
563 
564 	TAILQ_REMOVE(&que->que_msgs, msg, msg_next);
565 }
566 
567 /*
568  * The actual I/O routines. A note concerning the layout of SysV msg buffers:
569  *
570  * The data to be copied is laid out as a single userspace buffer, with a
571  * long preceding an opaque buffer of len bytes. The long value ends
572  * up being the message type, which needs to be copied separately from
573  * the buffer data, which is stored in in mbufs.
574  */
575 
576 int
577 msg_copyin(struct msg *msg, const char *ubuf, size_t len, struct proc *p)
578 {
579 	struct mbuf **mm, *m;
580 	size_t xfer;
581 	int error;
582 
583 	if (msg == NULL)
584 		panic ("msg NULL");
585 
586 	if ((error = copyin(ubuf, &msg->msg_type, sizeof(long)))) {
587 		msg_free(msg);
588 		return (error);
589 	}
590 
591 	if (msg->msg_type < 0) {
592 		msg_free(msg);
593 		return (EINVAL);
594 	}
595 
596 	ubuf += sizeof(long);
597 
598 	msg->msg_len = 0;
599 	mm = &msg->msg_data;
600 
601 	while (msg->msg_len < len) {
602 		m = m_get(M_WAIT, MT_DATA);
603 		if (len >= MINCLSIZE) {
604 			MCLGET(m, M_WAIT);
605 			xfer = min(len, MCLBYTES);
606 		} else {
607 			xfer = min(len, MLEN);
608 		}
609 		m->m_len = xfer;
610 		msg->msg_len += xfer;
611 		*mm = m;
612 		mm = &m->m_next;
613 	}
614 
615 	for (m = msg->msg_data; m; m = m->m_next) {
616 		if ((error = copyin(ubuf, mtod(m, void *), m->m_len))) {
617 			msg_free(msg);
618 			return (error);
619 		}
620 		ubuf += m->m_len;
621 	}
622 
623 	return (0);
624 }
625 
626 int
627 msg_copyout(struct msg *msg, char *ubuf, size_t *len, struct proc *p)
628 {
629 	struct mbuf *m;
630 	size_t xfer;
631 	int error;
632 
633 #ifdef DIAGNOSTIC
634 	if (msg->msg_len > MSGMAX)
635 		panic("SysV message longer than MSGMAX");
636 #endif
637 
638 	/* silently truncate messages too large for user buffer */
639 	xfer = min(*len, msg->msg_len);
640 
641 	if ((error = copyout(&msg->msg_type, ubuf, sizeof(long))))
642 		return (error);
643 
644 	ubuf += sizeof(long);
645 	*len = xfer;
646 
647 	for (m = msg->msg_data; m; m = m->m_next) {
648 		if ((error = copyout(mtod(m, void *), ubuf, m->m_len)))
649 			return (error);
650 		ubuf += m->m_len;
651 	}
652 
653 	return (0);
654 }
655 
656 int
657 sysctl_sysvmsg(int *name, u_int namelen, void *where, size_t *sizep)
658 {
659 	struct msg_sysctl_info *info;
660 	struct que *que;
661 	size_t infolen;
662 	int error;
663 
664 	switch (*name) {
665 	case KERN_SYSVIPC_MSG_INFO:
666 
667 		if (namelen != 1)
668 			return (ENOTDIR);
669 
670 		/*
671 		 * The userland ipcs(1) utility expects to be able
672 		 * to iterate over at least msginfo.msgmni queues,
673 		 * even if those queues don't exist. This is an
674 		 * artifact of the previous implementation of
675 		 * message queues; for now, emulate this behavior
676 		 * until a more thorough fix can be made.
677 		 */
678 		infolen = sizeof(msginfo) +
679 		    msginfo.msgmni * sizeof(struct msqid_ds);
680 		if (where == NULL) {
681 			*sizep = infolen;
682 			return (0);
683 		}
684 
685 		/*
686 		 * More special-casing due to previous implementation:
687 		 * if the caller just wants the msginfo struct, then
688 		 * sizep will point to the value sizeof(struct msginfo).
689 		 * In that case, only copy out the msginfo struct to
690 		 * the caller.
691 		 */
692 		if (*sizep == sizeof(struct msginfo))
693 			return (copyout(&msginfo, where, sizeof(msginfo)));
694 
695 		info = malloc(infolen, M_TEMP, M_WAIT|M_ZERO);
696 
697 		/* if the malloc slept, this may have changed */
698 		infolen = sizeof(msginfo) +
699 		    msginfo.msgmni * sizeof(struct msqid_ds);
700 
701 		if (*sizep < infolen) {
702 			free(info, M_TEMP, 0);
703 			return (ENOMEM);
704 		}
705 
706 		memcpy(&info->msginfo, &msginfo, sizeof(struct msginfo));
707 
708 		/*
709 		 * Special case #3: the previous array-based implementation
710 		 * exported the array indices and userland has come to rely
711 		 * upon these indices, so keep behavior consisitent.
712 		 */
713 		TAILQ_FOREACH(que, &msg_queues, que_next)
714 			memcpy(&info->msgids[que->que_ix], &que->msqid_ds,
715 			    sizeof(struct msqid_ds));
716 
717 		error = copyout(info, where, infolen);
718 
719 		free(info, M_TEMP, 0);
720 
721 		return (error);
722 
723 	default:
724 		return (EINVAL);
725 	}
726 }
727