1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2024 Rick Parrish <unitrunker@unitrunker.net>.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *	notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *	notice, this list of conditions and the following disclaimer in the
13  *	documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/queue.h>
29 #include <sys/stat.h>
30 #include <err.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <grp.h>
34 #include <limits.h>
35 #include <mqueue.h>
36 #include <pwd.h>
37 #include <stdbool.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sysexits.h>
42 #include <unistd.h>
43 
44 struct Creation {
45 	/* true if the queue exists. */
46 	bool exists;
47 	/* true if a mode value was specified. */
48 	bool set_mode;
49 	/* access mode with rwx permission bits. */
50 	mode_t mode;
51 	/* maximum queue depth. default to an invalid depth. */
52 	long depth;
53 	/* maximum message size. default to an invalid size. */
54 	long size;
55 	/* true for blocking I/O and false for non-blocking I/O. */
56 	bool block;
57 	/* true if a group ID was specified. */
58 	bool set_group;
59 	/* group ID. */
60 	gid_t group;
61 	/* true if a user ID was specified. */
62 	bool set_user;
63 	/* user ID. */
64 	uid_t user;
65 };
66 
67 struct element {
68 	STAILQ_ENTRY(element) links;
69 	const char *text;
70 };
71 
72 static struct element *
73 malloc_element(const char *context)
74 {
75 	struct element *item = malloc(sizeof(struct element));
76 
77 	if (item == NULL)
78 		/* the only non-EX_* prefixed exit code. */
79 		err(1, "malloc(%s)", context);
80 	return (item);
81 }
82 
83 static STAILQ_HEAD(tqh, element)
84 	queues = STAILQ_HEAD_INITIALIZER(queues),
85 	contents = STAILQ_HEAD_INITIALIZER(contents);
86 /* send defaults to medium priority. */
87 static long priority = MQ_PRIO_MAX / 2;
88 static struct Creation creation = {
89 	.exists = false,
90 	.set_mode = false,
91 	.mode = 0755,
92 	.depth = -1,
93 	.size = -1,
94 	.block = true,
95 	.set_group = false,
96 	.group = 0,
97 	.set_user = false,
98 	.user = 0
99 };
100 static const mqd_t fail = (mqd_t)-1;
101 static const mode_t accepted_mode_bits =
102     S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISTXT;
103 
104 /* OPTIONS parsing utilitarian */
105 
106 static void
107 parse_long(const char *text, long *capture, const char *knob, const char *name)
108 {
109 	char *cursor = NULL;
110 	long value = strtol(text, &cursor, 10);
111 
112 	if (cursor > text && *cursor == 0) {
113 		*capture = value;
114 	} else {
115 		warnx("%s %s invalid format [%s].", knob, name, text);
116 	}
117 }
118 
119 static void
120 parse_unsigned(const char *text, bool *set,
121    unsigned *capture, const char *knob, const char *name)
122 {
123 	char *cursor = NULL;
124 	unsigned value = strtoul(text, &cursor, 8);
125 
126 	if (cursor > text && *cursor == 0) {
127 		*set = true;
128 		*capture = value;
129 	} else {
130 		warnx("%s %s format [%s] ignored.", knob, name, text);
131 	}
132 }
133 
134 static bool
135 sane_queue(const char *queue)
136 {
137 	int size = 0;
138 
139 	if (queue[size] != '/') {
140 		warnx("queue name [%-.*s] must start with '/'.", NAME_MAX, queue);
141 		return (false);
142 	}
143 
144 	for (size++; queue[size] != 0 && size < NAME_MAX; size++) {
145 		if (queue[size] == '/') {
146 			warnx("queue name [%-.*s] - only one '/' permitted.",
147 			    NAME_MAX, queue);
148 			return (false);
149 		}
150 	}
151 
152 	if (size == NAME_MAX && queue[size] != 0) {
153 		warnx("queue name [%-.*s...] may not be longer than %d.",
154 		    NAME_MAX, queue, NAME_MAX);
155 		return (false);
156 	}
157 	return (true);
158 }
159 
160 /* OPTIONS parsers */
161 
162 static void
163 parse_block(const char *text)
164 {
165 	if (strcmp(text, "true") == 0 || strcmp(text, "yes") == 0) {
166 		creation.block = true;
167 	} else if (strcmp(text, "false") == 0 || strcmp(text, "no") == 0) {
168 		creation.block = false;
169 	} else {
170 		char *cursor = NULL;
171 		long value = strtol(text, &cursor, 10);
172 		if (cursor > text) {
173 			creation.block = value != 0;
174 		} else {
175 			warnx("bad -b block format [%s] ignored.", text);
176 		}
177 	}
178 }
179 
180 static void
181 parse_content(const char *content)
182 {
183 	struct element *n1 = malloc_element("content");
184 
185 	n1->text = content;
186 	STAILQ_INSERT_TAIL(&contents, n1, links);
187 }
188 
189 static void
190 parse_depth(const char *text)
191 {
192 	parse_long(text, &creation.depth, "-d", "depth");
193 }
194 
195 static void
196 parse_group(const char *text)
197 {
198 	struct group *entry = getgrnam(text);
199 
200 	if (entry == NULL) {
201 		parse_unsigned(text, &creation.set_group,
202 		    &creation.group, "-g", "group");
203 	} else {
204 		creation.set_group = true;
205 		creation.group = entry->gr_gid;
206 	}
207 }
208 
209 static void
210 parse_mode(const char *text)
211 {
212 	char *cursor = NULL;
213 	long value = strtol(text, &cursor, 8);
214 
215 	// verify only accepted mode bits are set.
216 	if (cursor > text && *cursor == 0 && (value & accepted_mode_bits) == value) {
217 		creation.set_mode = true;
218 		creation.mode = (mode_t)value;
219 	} else {
220 		warnx("impossible -m mode value [%s] ignored.", text);
221 	}
222 }
223 
224 static void
225 parse_priority(const char *text)
226 {
227 	char *cursor = NULL;
228 	long value = strtol(text, &cursor, 10);
229 
230 	if (cursor > text && *cursor == 0) {
231 		if (value >= 0 && value < MQ_PRIO_MAX) {
232 			priority = value;
233 		} else {
234 			warnx("bad -p priority range [%s] ignored.", text);
235 		}
236 	} else {
237 		warnx("bad -p priority format [%s] ignored.", text);
238 	}
239 }
240 
241 static void
242 parse_queue(const char *queue)
243 {
244 	if (sane_queue(queue)) {
245 		struct element *n1 = malloc_element("queue name");
246 
247 		n1->text = queue;
248 		STAILQ_INSERT_TAIL(&queues, n1, links);
249 	}
250 }
251 
252 static void
253 parse_single_queue(const char *queue)
254 {
255 	if (sane_queue(queue)) {
256 		if (STAILQ_EMPTY(&queues)) {
257 			struct element *n1 = malloc_element("queue name");
258 
259 			n1->text = queue;
260 			STAILQ_INSERT_TAIL(&queues, n1, links);
261 		} else
262 			warnx("ignoring extra -q queue [%s].", queue);
263 	}
264 }
265 
266 static void
267 parse_size(const char *text)
268 {
269 	parse_long(text, &creation.size, "-s", "size");
270 }
271 
272 static void
273 parse_user(const char *text)
274 {
275 	struct passwd *entry = getpwnam(text);
276 	if (entry == NULL) {
277 		parse_unsigned(text, &creation.set_user,
278 		    &creation.user, "-u", "user");
279 	} else {
280 		creation.set_user = true;
281 		creation.user = entry->pw_uid;
282 	}
283 }
284 
285 /* OPTIONS validators */
286 
287 static bool
288 validate_always_true(void)
289 {
290 	return (true);
291 }
292 
293 static bool
294 validate_content(void)
295 {
296 	bool valid = !STAILQ_EMPTY(&contents);
297 
298 	if (!valid)
299 		warnx("no content to send.");
300 	return (valid);
301 }
302 
303 static bool
304 validate_depth(void)
305 {
306 	bool valid = creation.exists || creation.depth > 0;
307 
308 	if (!valid)
309 		warnx("-d maximum queue depth not provided.");
310 	return (valid);
311 }
312 
313 static bool
314 validate_queue(void)
315 {
316 	bool valid = !STAILQ_EMPTY(&queues);
317 
318 	if (!valid)
319 		warnx("missing -q, or no sane queue name given.");
320 	return (valid);
321 }
322 
323 static bool
324 validate_single_queue(void)
325 {
326 	bool valid = !STAILQ_EMPTY(&queues) &&
327 	    STAILQ_NEXT(STAILQ_FIRST(&queues), links) == NULL;
328 
329 	if (!valid)
330 		warnx("expected one queue.");
331 	return (valid);
332 }
333 
334 static bool
335 validate_size(void)
336 {
337 	bool valid = creation.exists || creation.size > 0;
338 
339 	if (!valid)
340 		warnx("-s maximum message size not provided.");
341 	return (valid);
342 }
343 
344 /* OPTIONS table handling. */
345 
346 struct Option {
347 	/* points to array of string pointers terminated by a null pointer. */
348 	const char **pattern;
349 	/* parse argument. */
350 	void (*parse)(const char *);
351 	/*
352 	 * displays an error and returns false if this parameter is not valid.
353 	 * returns true otherwise.
354 	 */
355 	bool (*validate)(void);
356 };
357 
358 /*
359  * parse options by table.
360  * index - current index into argv list.
361  * argc, argv - command line parameters.
362  * options - null terminated list of pointers to options.
363  */
364 static void
365 parse_options(int index, int argc,
366     const char *argv[], const struct Option **options)
367 {
368 	while ((index + 1) < argc) {
369 		const struct Option **cursor = options;
370 		bool match = false;
371 		while (*cursor != NULL && !match) {
372 			const struct Option *option = cursor[0];
373 			const char **pattern = option->pattern;
374 
375 			while (*pattern != NULL && !match) {
376 				const char *knob = *pattern;
377 
378 				match = strcmp(knob, argv[index]) == 0;
379 				if (!match)
380 					pattern++;
381 			}
382 
383 			if (match) {
384 				option->parse(argv[index + 1]);
385 				index += 2;
386 				break;
387 			}
388 			cursor++;
389 		}
390 
391 		if (!match && index < argc) {
392 			warnx("skipping [%s].", argv[index]);
393 			index++;
394 		}
395 	}
396 
397 	if (index < argc) {
398 		warnx("skipping [%s].", argv[index]);
399 	}
400 }
401 
402 /* options - null terminated list of pointers to options. */
403 static bool
404 validate_options(const struct Option **options)
405 {
406 	bool valid = true;
407 
408 	while (*options != NULL) {
409 		const struct Option *option = options[0];
410 
411 		if (!option->validate())
412 			valid = false;
413 		options++;
414 	}
415 	return (valid);
416 }
417 
418 /* SUBCOMMANDS */
419 
420 /*
421  * queue: name of queue to be created.
422  * q_creation: creation parameters (copied by value).
423  */
424 static int
425 create(const char *queue, struct Creation q_creation)
426 {
427 	int flags = O_RDWR;
428 	struct mq_attr stuff = {
429 		.mq_curmsgs = 0,
430 		.mq_maxmsg = q_creation.depth,
431 		.mq_msgsize = q_creation.size,
432 		.mq_flags = 0
433 	};
434 
435 	if (!q_creation.block) {
436 		flags |= O_NONBLOCK;
437 		stuff.mq_flags |= O_NONBLOCK;
438 	}
439 
440 	mqd_t handle = mq_open(queue, flags);
441 	q_creation.exists = handle != fail;
442 	if (!q_creation.exists) {
443 		/*
444 		 * apply size and depth checks here.
445 		 * if queue exists, we can default to existing depth and size.
446 		 * but for a new queue, we require that input.
447 		 */
448 		if (validate_size() && validate_depth()) {
449 			/* no need to re-apply mode. */
450 			q_creation.set_mode = false;
451 			flags |= O_CREAT;
452 			handle = mq_open(queue, flags, q_creation.mode, &stuff);
453 		}
454 	}
455 
456 	if (handle == fail) {
457 		errno_t what = errno;
458 
459 		warnc(what, "mq_open(create)");
460 		return (what);
461 	}
462 
463 #ifdef __FreeBSD__
464 	/*
465 	 * undocumented.
466 	 * See https://bugs.freebsd.org/bugzilla//show_bug.cgi?id=273230
467 	 */
468 	int fd = mq_getfd_np(handle);
469 
470 	if (fd < 0) {
471 		errno_t what = errno;
472 
473 		warnc(what, "mq_getfd_np(create)");
474 		mq_close(handle);
475 		return (what);
476 	}
477 	struct stat status = {0};
478 	int result = fstat(fd, &status);
479 	if (result != 0) {
480 		errno_t what = errno;
481 
482 		warnc(what, "fstat(create)");
483 		mq_close(handle);
484 		return (what);
485 	}
486 
487 	/* do this only if group and / or user given. */
488 	if (q_creation.set_group || q_creation.set_user) {
489 		q_creation.user =
490 		    q_creation.set_user ? q_creation.user : status.st_uid;
491 		q_creation.group =
492 		    q_creation.set_group ? q_creation.group : status.st_gid;
493 		result = fchown(fd, q_creation.user, q_creation.group);
494 		if (result != 0) {
495 			errno_t what = errno;
496 
497 			warnc(what, "fchown(create)");
498 			mq_close(handle);
499 			return (what);
500 		}
501 	}
502 
503 	/* do this only if altering mode of an existing queue. */
504 	if (q_creation.exists && q_creation.set_mode &&
505 	    q_creation.mode != (status.st_mode & accepted_mode_bits)) {
506 		result = fchmod(fd, q_creation.mode);
507 		if (result != 0) {
508 			errno_t what = errno;
509 
510 			warnc(what, "fchmod(create)");
511 			mq_close(handle);
512 			return (what);
513 		}
514 	}
515 #endif /* __FreeBSD__ */
516 
517 	return (mq_close(handle));
518 }
519 
520 /* queue: name of queue to be removed. */
521 static int
522 rm(const char *queue)
523 {
524 	int result = mq_unlink(queue);
525 
526 	if (result != 0) {
527 		errno_t what = errno;
528 
529 		warnc(what, "mq_unlink");
530 		return (what);
531 	}
532 
533 	return (result);
534 }
535 
536 /* Return the display character for non-zero mode. */
537 static char
538 dual(mode_t mode, char display)
539 {
540 	return (mode != 0 ? display : '-');
541 }
542 
543 /* Select one of four display characters based on mode and modifier. */
544 static char
545 quad(mode_t mode, mode_t modifier)
546 {
547 	static const char display[] = "-xSs";
548 	unsigned index = 0;
549 	if (mode != 0)
550 		index += 1;
551 	if (modifier)
552 		index += 2;
553 	return (display[index]);
554 }
555 
556 /* queue: name of queue to be inspected. */
557 static int
558 info(const char *queue)
559 {
560 	mqd_t handle = mq_open(queue, O_RDONLY);
561 
562 	if (handle == fail) {
563 		errno_t what = errno;
564 
565 		warnc(what, "mq_open(info)");
566 		return (what);
567 	}
568 
569 	struct mq_attr actual;
570 
571 	int result = mq_getattr(handle, &actual);
572 	if (result != 0) {
573 		errno_t what = errno;
574 
575 		warnc(what, "mq_getattr(info)");
576 		return (what);
577 	}
578 
579 	fprintf(stdout,
580 	    "queue: '%s'\nQSIZE: %lu\nMSGSIZE: %ld\nMAXMSG: %ld\n"
581 	    "CURMSG: %ld\nflags: %03ld\n",
582 	    queue, actual.mq_msgsize * actual.mq_curmsgs, actual.mq_msgsize,
583 	    actual.mq_maxmsg, actual.mq_curmsgs, actual.mq_flags);
584 #ifdef __FreeBSD__
585 
586 	int fd = mq_getfd_np(handle);
587 	struct stat status;
588 
589 	result = fstat(fd, &status);
590 	if (result != 0) {
591 		warn("fstat(info)");
592 	} else {
593 		mode_t mode = status.st_mode;
594 
595 		fprintf(stdout, "UID: %u\nGID: %u\n", status.st_uid, status.st_gid);
596 		fprintf(stdout, "MODE: %c%c%c%c%c%c%c%c%c%c\n",
597 		    dual(mode & S_ISVTX, 's'),
598 		    dual(mode & S_IRUSR, 'r'),
599 		    dual(mode & S_IWUSR, 'w'),
600 		    quad(mode & S_IXUSR, mode & S_ISUID),
601 		    dual(mode & S_IRGRP, 'r'),
602 		    dual(mode & S_IWGRP, 'w'),
603 		    quad(mode & S_IXGRP, mode & S_ISGID),
604 		    dual(mode & S_IROTH, 'r'),
605 		    dual(mode & S_IWOTH, 'w'),
606 		    dual(mode & S_IXOTH, 'x'));
607 	}
608 #endif /* __FreeBSD__ */
609 
610 	return (mq_close(handle));
611 }
612 
613 /* queue: name of queue to drain one message. */
614 static int
615 recv(const char *queue)
616 {
617 	mqd_t handle = mq_open(queue, O_RDONLY);
618 
619 	if (handle == fail) {
620 		errno_t what = errno;
621 
622 		warnc(what, "mq_open(recv)");
623 		return (what);
624 	}
625 
626 	struct mq_attr actual;
627 
628 	int result = mq_getattr(handle, &actual);
629 
630 	if (result != 0) {
631 		errno_t what = errno;
632 
633 		warnc(what, "mq_attr(recv)");
634 		mq_close(handle);
635 		return (what);
636 	}
637 
638 	char *text = malloc(actual.mq_msgsize + 1);
639 	unsigned q_priority = 0;
640 
641 	memset(text, 0, actual.mq_msgsize + 1);
642 	result = mq_receive(handle, text, actual.mq_msgsize, &q_priority);
643 	if (result < 0) {
644 		errno_t what = errno;
645 
646 		warnc(what, "mq_receive");
647 		mq_close(handle);
648 		return (what);
649 	}
650 
651 	fprintf(stdout, "[%u]: %-*.*s\n", q_priority, result, result, text);
652 	return (mq_close(handle));
653 }
654 
655 /*
656  * queue: name of queue to send one message.
657  * text: message text.
658  * q_priority: message priority in range of 0 to 63.
659  */
660 static int
661 send(const char *queue, const char *text, unsigned q_priority)
662 {
663 	mqd_t handle = mq_open(queue, O_WRONLY);
664 
665 	if (handle == fail) {
666 		errno_t what = errno;
667 
668 		warnc(what, "mq_open(send)");
669 		return (what);
670 	}
671 
672 	struct mq_attr actual;
673 
674 	int result = mq_getattr(handle, &actual);
675 
676 	if (result != 0) {
677 		errno_t what = errno;
678 
679 		warnc(what, "mq_attr(send)");
680 		mq_close(handle);
681 		return (what);
682 	}
683 
684 	int size = strlen(text);
685 
686 	if (size > actual.mq_msgsize) {
687 		warnx("truncating message to %ld characters.\n", actual.mq_msgsize);
688 		size = actual.mq_msgsize;
689 	}
690 
691 	result = mq_send(handle, text, size, q_priority);
692 
693 	if (result != 0) {
694 		errno_t what = errno;
695 
696 		warnc(what, "mq_send");
697 		mq_close(handle);
698 		return (what);
699 	}
700 
701 	return (mq_close(handle));
702 }
703 
704 static void
705 usage(FILE *file)
706 {
707 	fprintf(file,
708 	    "usage:\n\tposixmqcontrol [rm|info|recv] -q <queue>\n"
709 	    "\tposixmqcontrol create -q <queue> -s <maxsize> -d <maxdepth> "
710 	    "[ -m <mode> ] [ -b <block> ] [-u <uid> ] [ -g <gid> ]\n"
711 	    "\tposixmqcontrol send -q <queue> -c <content> "
712 	    "[-p <priority> ]\n");
713 }
714 
715 /* end of SUBCOMMANDS */
716 
717 #define _countof(arg) ((sizeof(arg)) / (sizeof((arg)[0])))
718 
719 /* convert an errno style error code to a sysexits code. */
720 static int
721 grace(int err_number)
722 {
723 	static const int xlat[][2] = {
724 		/* generally means the mqueuefs driver is not loaded. */
725 		{ENOSYS, EX_UNAVAILABLE},
726 		/* no such queue name. */
727 		{ENOENT, EX_OSFILE},
728 		{EIO, EX_IOERR},
729 		{ENODEV, EX_IOERR},
730 		{ENOTSUP, EX_TEMPFAIL},
731 		{EAGAIN, EX_IOERR},
732 		{EPERM, EX_NOPERM},
733 		{EACCES, EX_NOPERM},
734 		{0, EX_OK}
735 	};
736 
737 	for (unsigned i = 0; i < _countof(xlat); i++) {
738 		if (xlat[i][0] == err_number)
739 			return (xlat[i][1]);
740 	}
741 
742 	return (EX_OSERR);
743 }
744 
745 /* OPTIONS tables */
746 
747 /* careful: these 'names' arrays must be terminated by a null pointer. */
748 static const char *names_queue[] = {"-q", "--queue", "-t", "--topic", NULL};
749 static const struct Option option_queue = {
750 	.pattern = names_queue,
751 	.parse = parse_queue,
752 	.validate = validate_queue};
753 static const struct Option option_single_queue = {
754 	.pattern = names_queue,
755 	.parse = parse_single_queue,
756 	.validate = validate_single_queue};
757 static const char *names_depth[] = {"-d", "--depth", "--maxmsg", NULL};
758 static const struct Option option_depth = {
759 	.pattern = names_depth,
760 	.parse = parse_depth,
761 	.validate = validate_always_true};
762 static const char *names_size[] = {"-s", "--size", "--msgsize", NULL};
763 static const struct Option option_size = {
764 	.pattern = names_size,
765 	.parse = parse_size,
766 	.validate = validate_always_true};
767 static const char *names_block[] = {"-b", "--block", NULL};
768 static const struct Option option_block = {
769 	.pattern = names_block,
770 	.parse = parse_block,
771 	.validate = validate_always_true};
772 static const char *names_content[] = {
773 	"-c", "--content", "--data", "--message", NULL};
774 static const struct Option option_content = {
775 	.pattern = names_content,
776 	.parse = parse_content,
777 	.validate = validate_content};
778 static const char *names_priority[] = {"-p", "--priority", NULL};
779 static const struct Option option_priority = {
780 	.pattern = names_priority,
781 	.parse = parse_priority,
782 	.validate = validate_always_true};
783 static const char *names_mode[] = {"-m", "--mode", NULL};
784 static const struct Option option_mode = {
785 	.pattern = names_mode,
786 	.parse = parse_mode,
787 	.validate = validate_always_true};
788 static const char *names_group[] = {"-g", "--gid", NULL};
789 static const struct Option option_group = {
790 	.pattern = names_group,
791 	.parse = parse_group,
792 	.validate = validate_always_true};
793 static const char *names_user[] = {"-u", "--uid", NULL};
794 static const struct Option option_user = {
795 	.pattern = names_user,
796 	.parse = parse_user,
797 	.validate = validate_always_true};
798 
799 /* careful: these arrays must be terminated by a null pointer. */
800 #ifdef __FreeBSD__
801 static const struct Option *create_options[] = {
802 	&option_queue, &option_depth, &option_size, &option_block,
803 	&option_mode, &option_group, &option_user, NULL};
804 #else  /* !__FreeBSD__ */
805 static const struct Option *create_options[] = {
806 	&option_queue, &option_depth, &option_size, &option_block,
807 	&option_mode, NULL};
808 #endif /* __FreeBSD__ */
809 static const struct Option *info_options[] = {&option_queue, NULL};
810 static const struct Option *unlink_options[] = {&option_queue, NULL};
811 static const struct Option *recv_options[] = {&option_single_queue, NULL};
812 static const struct Option *send_options[] = {
813 	&option_queue, &option_content, &option_priority, NULL};
814 
815 int
816 main(int argc, const char *argv[])
817 {
818 	STAILQ_INIT(&queues);
819 	STAILQ_INIT(&contents);
820 
821 	if (argc > 1) {
822 		const char *verb = argv[1];
823 		int index = 2;
824 
825 		if (strcmp("create", verb) == 0 || strcmp("attr", verb) == 0) {
826 			parse_options(index, argc, argv, create_options);
827 			if (validate_options(create_options)) {
828 				int worst = 0;
829 				struct element *itq;
830 
831 				STAILQ_FOREACH(itq, &queues, links) {
832 					const char *queue = itq->text;
833 
834 					int result = create(queue, creation);
835 					if (result != 0)
836 						worst = result;
837 				}
838 
839 				return (grace(worst));
840 			}
841 
842 			return (EX_USAGE);
843 		} else if (strcmp("info", verb) == 0 || strcmp("cat", verb) == 0) {
844 			parse_options(index, argc, argv, info_options);
845 			if (validate_options(info_options)) {
846 				int worst = 0;
847 				struct element *itq;
848 
849 				STAILQ_FOREACH(itq, &queues, links) {
850 					const char *queue = itq->text;
851 					int result = info(queue);
852 
853 					if (result != 0)
854 						worst = result;
855 				}
856 
857 				return (grace(worst));
858 			}
859 
860 			return (EX_USAGE);
861 		} else if (strcmp("send", verb) == 0) {
862 			parse_options(index, argc, argv, send_options);
863 			if (validate_options(send_options)) {
864 				int worst = 0;
865 				struct element *itq;
866 
867 				STAILQ_FOREACH(itq, &queues, links) {
868 					const char *queue = itq->text;
869 					struct element *itc;
870 
871 					STAILQ_FOREACH(itc, &contents, links) {
872 						const char *content = itc->text;
873 						int result = send(queue, content, priority);
874 
875 						if (result != 0)
876 							worst = result;
877 					}
878 				}
879 
880 				return (grace(worst));
881 			}
882 			return (EX_USAGE);
883 		} else if (strcmp("recv", verb) == 0 ||
884 		    strcmp("receive", verb) == 0) {
885 			parse_options(index, argc, argv, recv_options);
886 			if (validate_options(recv_options)) {
887 				const char *queue = STAILQ_FIRST(&queues)->text;
888 				int worst = recv(queue);
889 
890 				return (grace(worst));
891 			}
892 
893 			return (EX_USAGE);
894 		} else if (strcmp("unlink", verb) == 0 ||
895 		    strcmp("rm", verb) == 0) {
896 			parse_options(index, argc, argv, unlink_options);
897 			if (validate_options(unlink_options)) {
898 				int worst = 0;
899 				struct element *itq;
900 
901 				STAILQ_FOREACH(itq, &queues, links) {
902 					const char *queue = itq->text;
903 					int result = rm(queue);
904 
905 					if (result != 0)
906 						worst = result;
907 				}
908 
909 				return (grace(worst));
910 			}
911 
912 			return (EX_USAGE);
913 		} else if (strcmp("help", verb) == 0) {
914 			usage(stdout);
915 			return (EX_OK);
916 		} else {
917 			warnx("Unknown verb [%s]", verb);
918 			return (EX_USAGE);
919 		}
920 	}
921 
922 	usage(stdout);
923 	return (EX_OK);
924 }
925