git: 0112f8c4a88e - main - posixmqcontrol(1): manage posix message queues

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Fri, 23 Feb 2024 23:54:44 UTC
The branch main has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=0112f8c4a88e75342bdb6b9815fa220c5f645aa0

commit 0112f8c4a88e75342bdb6b9815fa220c5f645aa0
Author:     Rick Parrish <unitrunker@unitrunker.net>
AuthorDate: 2024-02-22 12:33:12 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2024-02-23 23:08:48 +0000

    posixmqcontrol(1): manage posix message queues
    
    Reviewed by:    kib, paumma
    MFC after:      1 week
    Differential revision:  https://reviews.freebsd.org/D43845
---
 usr.bin/Makefile                                 |   1 +
 usr.bin/posixmqcontrol/Makefile                  |   4 +
 usr.bin/posixmqcontrol/posixmqcontrol.1          | 180 +++++
 usr.bin/posixmqcontrol/posixmqcontrol.c          | 924 +++++++++++++++++++++++
 usr.bin/posixmqcontrol/posixmqcontroltest8qs.sh  |  50 ++
 usr.bin/posixmqcontrol/posixmqcontroltest8x64.sh |  99 +++
 usr.bin/posixmqcontrol/posixmqcontroltestsane.sh |  28 +
 7 files changed, 1286 insertions(+)

diff --git a/usr.bin/Makefile b/usr.bin/Makefile
index 5cccf1903471..84b7c4dc4dec 100644
--- a/usr.bin/Makefile
+++ b/usr.bin/Makefile
@@ -110,6 +110,7 @@ SUBDIR=	alias \
 	patch \
 	pathchk \
 	perror \
+	posixmqcontrol \
 	posixshmcontrol \
 	pr \
 	printenv \
diff --git a/usr.bin/posixmqcontrol/Makefile b/usr.bin/posixmqcontrol/Makefile
new file mode 100644
index 000000000000..3cbfa8557625
--- /dev/null
+++ b/usr.bin/posixmqcontrol/Makefile
@@ -0,0 +1,4 @@
+PROG=   posixmqcontrol
+LIBADD=	rt
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/posixmqcontrol/posixmqcontrol.1 b/usr.bin/posixmqcontrol/posixmqcontrol.1
new file mode 100644
index 000000000000..ec60230aac6e
--- /dev/null
+++ b/usr.bin/posixmqcontrol/posixmqcontrol.1
@@ -0,0 +1,180 @@
+.\"-
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2024 Rick Parrish <unitrunker@unitrunker.net>.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd February 19, 2024
+.Dt POSIXMQCONTROL 1
+.Os
+.Sh NAME
+.Nm posixmqcontrol
+.Nd Control POSIX mqueuefs message queues
+.Sh SYNOPSIS
+.Nm
+.Ar create
+.Fl q Ar queue
+.Fl s Ar size
+.Fl d Ar depth
+.Op Fl m Ar mode
+.Op Fl g Ar group
+.Op Fl u Ar user
+.Nm
+.Ar info
+.Fl q Ar queue
+.Nm
+.Ar recv
+.Fl q Ar queue
+.Nm
+.Ar rm
+.Fl q Ar queue
+.Nm
+.Ar send
+.Fl q Ar queue
+.Fl c Ar content
+.Op Fl p Ar priority
+.Sh DESCRIPTION
+The
+.Nm
+command allows separating POSIX message queue administration from application
+stack.
+Defining and adjusting queue attributes can be done without touching
+application code.
+It allows creating queues, inspecting queue metadata, altering group and user
+access to queues, dumping queue contents, and unlinking queues.
+.Pp
+Unlinking removes the name from the system and frees underlying memory.
+.Pp
+The maximum message size, maximum queue size, and current queue size are
+displayed by the
+.Ic info
+subcommand. This output is similar to running
+.Ic cat
+on a mqueuefs queue mounted under a mount point.
+This utility requires the
+.Ic mqueuefs
+kernel module to be loaded but does not require
+.Ic mqueuefs
+to be mounted as a file system.
+.Pp
+The following subcommands are provided:
+.Bl -tag -width truncate
+.It Ic create
+Create the named queues, if they do not already exist.
+More than one queue name may be created. The same maximum queue depth and
+maximum message size are used to create all queues.
+If a queue exists, then depth and size are optional.
+.Pp
+The required
+.Ar size
+and
+.Ar depth
+arguments specify the maximum message size (bytes per message) and maximum queue
+size (depth or number of messages in the queue).
+The optional numerical
+.Ar mode
+argument specifies the initial access mode.
+If the queue exists but does not match the requested size and depth, this
+utility will attempt to recreate the queue by first unlinking and then creating
+it.
+This will fail if the queue is not empty or is opened by other processes.
+.It Ic rm
+Unlink the queues specified - one attempt per queue.
+Failure to unlink one queue does not stop this sub-command from attempting to
+unlink the others.
+.It Ic info
+For each named queue, dispay the maximum message size, maximum queue size,
+current queue depth, user owner id, group owner id, and mode permission bits.
+.It Ic recv
+Wait for a message from a single named queue and display the message to
+standard output.
+.It Ic send
+Send messages to one or more named queues.
+If multiple messages and multiple queues are specified, the utility attempts to
+send all messages to all queues.
+The optional -p priority, if omitted, defaults to MQ_PRIO_MAX / 2 or medium
+priority.
+.El
+.Sh NOTES
+A change of queue geometry (maximum message size and/or maximum number of
+messages) requires destroying and re-creating the queue.
+As a safety feature,
+the create subcommand refuses to destroy a non-empty queue.
+If you use the rm subcommand to destroy a queue, any queued messages are lost.
+To avoid down-time when altering queue attributes, consider creating a new
+queue and configure reading applications to drain both new and old queues.
+Retire the old queue once all writers have been updated to write to the new
+queue.
+.Sh EXIT STATUS
+.Ex -std
+.Bl -bullet
+.It
+EX_NOTAVAILABLE usually means the mqueuefs kernel module is not loaded.
+.It
+EX_USAGE reports one or more incorrect parameters.
+.El
+.Sh EXAMPLES
+.Bl -bullet
+.It
+To retrieve the current message from a named queue,
+.Pa /1 ,
+use the command
+.Dl "posixmqcontrol recv -q /1"
+.It
+To create a queue with the name
+.Pa /2
+with maximum message size 100 and maximum queue depth 10,
+use the command
+.Dl "posixmqcontrol create -q /2 -s 100 -d 10"
+.It
+To send a message to a queue with the name
+.Pa /3
+use the command
+.Dl "posixmqcontrol send -q /3 -c 'some choice words.'"
+.It
+To examine attributes of a queue named
+.Pa /4
+use the command
+.Dl "posixmqcontrol info -q /4"
+.El
+.Sh SEE ALSO
+.Xr mq_open 2 ,
+.Xr mq_getattr 2 ,
+.Xr mq_receive 2 ,
+.Xr mq_send 2 ,
+.Xr mq_setattr 2 ,
+.Xr mq_unlink 2 ,
+.Xr mqueuefs 5
+.Sh BUGS
+mq_timedsend and mq_timedrecv are not implemented.
+info reports a worst-case estimate for QSIZE.
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Fx 15.0 .
+.Sh AUTHORS
+The
+.Nm
+command and this manual page were written by
+.An Rick Parrish Aq Mt unitrunker@unitrunker.net.
diff --git a/usr.bin/posixmqcontrol/posixmqcontrol.c b/usr.bin/posixmqcontrol/posixmqcontrol.c
new file mode 100644
index 000000000000..c965b41a1dfb
--- /dev/null
+++ b/usr.bin/posixmqcontrol/posixmqcontrol.c
@@ -0,0 +1,924 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 Rick Parrish <unitrunker@unitrunker.net>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *	notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *	notice, this list of conditions and the following disclaimer in the
+ *	documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <limits.h>
+#include <mqueue.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+struct Creation {
+	/* true if the queue exists. */
+	bool exists;
+	/* true if a mode value was specified. */
+	bool set_mode;
+	/* access mode with rwx permission bits. */
+	mode_t mode;
+	/* maximum queue depth. default to an invalid depth. */
+	long depth;
+	/* maximum message size. default to an invalid size. */
+	long size;
+	/* true for blocking I/O and false for non-blocking I/O. */
+	bool block;
+	/* true if a group ID was specified. */
+	bool set_group;
+	/* group ID. */
+	gid_t group;
+	/* true if a user ID was specified. */
+	bool set_user;
+	/* user ID. */
+	uid_t user;
+};
+
+struct element {
+	STAILQ_ENTRY(element) links;
+	const char *text;
+};
+
+static struct element *
+malloc_element(const char *context)
+{
+	struct element *item = malloc(sizeof(struct element));
+
+	if (item == NULL)
+		/* the only non-EX_* prefixed exit code. */
+		err(1, "malloc(%s)", context);
+	return (item);
+}
+
+static STAILQ_HEAD(tqh, element)
+	queues = STAILQ_HEAD_INITIALIZER(queues),
+	contents = STAILQ_HEAD_INITIALIZER(contents);
+/* send defaults to medium priority. */
+static long priority = MQ_PRIO_MAX / 2;
+static struct Creation creation = {
+	.exists = false,
+	.set_mode = false,
+	.mode = 0755,
+	.depth = -1,
+	.size = -1,
+	.block = true,
+	.set_group = false,
+	.group = 0,
+	.set_user = false,
+	.user = 0
+};
+static const mqd_t fail = (mqd_t)-1;
+static const mode_t accepted_mode_bits =
+    S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISTXT;
+
+/* OPTIONS parsing utilitarian */
+
+static void
+parse_long(const char *text, long *capture, const char *knob, const char *name)
+{
+	char *cursor = NULL;
+	long value = strtol(text, &cursor, 10);
+
+	if (cursor > text && *cursor == 0) {
+		*capture = value;
+	} else {
+		warnx("%s %s invalid format [%s].", knob, name, text);
+	}
+}
+
+static void
+parse_unsigned(const char *text, bool *set,
+   unsigned *capture, const char *knob, const char *name)
+{
+	char *cursor = NULL;
+	unsigned value = strtoul(text, &cursor, 8);
+
+	if (cursor > text && *cursor == 0) {
+		*set = true;
+		*capture = value;
+	} else {
+		warnx("%s %s format [%s] ignored.", knob, name, text);
+	}
+}
+
+static bool
+sane_queue(const char *queue)
+{
+	int size = 0;
+
+	if (queue[size] != '/') {
+		warnx("queue name [%-.*s] must start with '/'.", NAME_MAX, queue);
+		return (false);
+	}
+
+	for (size++; queue[size] != 0 && size < NAME_MAX; size++) {
+		if (queue[size] == '/') {
+			warnx("queue name [%-.*s] - only one '/' permitted.",
+			    NAME_MAX, queue);
+			return (false);
+		}
+	}
+
+	if (size == NAME_MAX && queue[size] != 0) {
+		warnx("queue name [%-.*s...] may not be longer than %d.",
+		    NAME_MAX, queue, NAME_MAX);
+		return (false);
+	}
+	return (true);
+}
+
+/* OPTIONS parsers */
+
+static void
+parse_block(const char *text)
+{
+	if (strcmp(text, "true") == 0 || strcmp(text, "yes") == 0) {
+		creation.block = true;
+	} else if (strcmp(text, "false") == 0 || strcmp(text, "no") == 0) {
+		creation.block = false;
+	} else {
+		char *cursor = NULL;
+		long value = strtol(text, &cursor, 10);
+		if (cursor > text) {
+			creation.block = value != 0;
+		} else {
+			warnx("bad -b block format [%s] ignored.", text);
+		}
+	}
+}
+
+static void
+parse_content(const char *content)
+{
+	struct element *n1 = malloc_element("content");
+
+	n1->text = content;
+	STAILQ_INSERT_TAIL(&contents, n1, links);
+}
+
+static void
+parse_depth(const char *text)
+{
+	parse_long(text, &creation.depth, "-d", "depth");
+}
+
+static void
+parse_group(const char *text)
+{
+	struct group *entry = getgrnam(text);
+
+	if (entry == NULL) {
+		parse_unsigned(text, &creation.set_group,
+		    &creation.group, "-g", "group");
+	} else {
+		creation.set_group = true;
+		creation.group = entry->gr_gid;
+	}
+}
+
+static void
+parse_mode(const char *text)
+{
+	char *cursor = NULL;
+	long value = strtol(text, &cursor, 8);
+
+	// verify only accepted mode bits are set.
+	if (cursor > text && *cursor == 0 && (value & accepted_mode_bits) == value) {
+		creation.set_mode = true;
+		creation.mode = (mode_t)value;
+	} else {
+		warnx("impossible -m mode value [%s] ignored.", text);
+	}
+}
+
+static void
+parse_priority(const char *text)
+{
+	char *cursor = NULL;
+	long value = strtol(text, &cursor, 10);
+
+	if (cursor > text && *cursor == 0) {
+		if (value >= 0 && value < MQ_PRIO_MAX) {
+			priority = value;
+		} else {
+			warnx("bad -p priority range [%s] ignored.", text);
+		}
+	} else {
+		warnx("bad -p priority format [%s] ignored.", text);
+	}
+}
+
+static void
+parse_queue(const char *queue)
+{
+	if (sane_queue(queue)) {
+		struct element *n1 = malloc_element("queue name");
+
+		n1->text = queue;
+		STAILQ_INSERT_TAIL(&queues, n1, links);
+	}
+}
+
+static void
+parse_single_queue(const char *queue)
+{
+	if (sane_queue(queue)) {
+		if (STAILQ_EMPTY(&queues)) {
+			struct element *n1 = malloc_element("queue name");
+
+			n1->text = queue;
+			STAILQ_INSERT_TAIL(&queues, n1, links);
+		} else
+			warnx("ignoring extra -q queue [%s].", queue);
+	}
+}
+
+static void
+parse_size(const char *text)
+{
+	parse_long(text, &creation.size, "-s", "size");
+}
+
+static void
+parse_user(const char *text)
+{
+	struct passwd *entry = getpwnam(text);
+	if (entry == NULL) {
+		parse_unsigned(text, &creation.set_user,
+		    &creation.user, "-u", "user");
+	} else {
+		creation.set_user = true;
+		creation.user = entry->pw_uid;
+	}
+}
+
+/* OPTIONS validators */
+
+static bool
+validate_always_true(void)
+{
+	return (true);
+}
+
+static bool
+validate_content(void)
+{
+	bool valid = !STAILQ_EMPTY(&contents);
+
+	if (!valid)
+		warnx("no content to send.");
+	return (valid);
+}
+
+static bool
+validate_depth(void)
+{
+	bool valid = creation.exists || creation.depth > 0;
+
+	if (!valid)
+		warnx("-d maximum queue depth not provided.");
+	return (valid);
+}
+
+static bool
+validate_queue(void)
+{
+	bool valid = !STAILQ_EMPTY(&queues);
+
+	if (!valid)
+		warnx("missing -q, or no sane queue name given.");
+	return (valid);
+}
+
+static bool
+validate_single_queue(void)
+{
+	bool valid = !STAILQ_EMPTY(&queues) &&
+	    STAILQ_NEXT(STAILQ_FIRST(&queues), links) == NULL;
+
+	if (!valid)
+		warnx("expected one queue.");
+	return (valid);
+}
+
+static bool
+validate_size(void)
+{
+	bool valid = creation.exists || creation.size > 0;
+
+	if (!valid)
+		warnx("-s maximum message size not provided.");
+	return (valid);
+}
+
+/* OPTIONS table handling. */
+
+struct Option {
+	/* points to array of string pointers terminated by a null pointer. */
+	const char **pattern;
+	/* parse argument. */
+	void (*parse)(const char *);
+	/*
+	 * displays an error and returns false if this parameter is not valid.
+	 * returns true otherwise.
+	 */
+	bool (*validate)(void);
+};
+
+/*
+ * parse options by table.
+ * index - current index into argv list.
+ * argc, argv - command line parameters.
+ * options - null terminated list of pointers to options.
+ */
+static void
+parse_options(int index, int argc,
+    const char *argv[], const struct Option **options)
+{
+	while ((index + 1) < argc) {
+		const struct Option **cursor = options;
+		bool match = false;
+		while (*cursor != NULL && !match) {
+			const struct Option *option = cursor[0];
+			const char **pattern = option->pattern;
+
+			while (*pattern != NULL && !match) {
+				const char *knob = *pattern;
+
+				match = strcmp(knob, argv[index]) == 0;
+				if (!match)
+					pattern++;
+			}
+
+			if (match) {
+				option->parse(argv[index + 1]);
+				index += 2;
+				break;
+			}
+			cursor++;
+		}
+
+		if (!match && index < argc) {
+			warnx("skipping [%s].", argv[index]);
+			index++;
+		}
+	}
+
+	if (index < argc) {
+		warnx("skipping [%s].", argv[index]);
+	}
+}
+
+/* options - null terminated list of pointers to options. */
+static bool
+validate_options(const struct Option **options)
+{
+	bool valid = true;
+
+	while (*options != NULL) {
+		const struct Option *option = options[0];
+
+		if (!option->validate())
+			valid = false;
+		options++;
+	}
+	return (valid);
+}
+
+/* SUBCOMMANDS */
+
+/*
+ * queue: name of queue to be created.
+ * q_creation: creation parameters (copied by value).
+ */
+static int
+create(const char *queue, struct Creation q_creation)
+{
+	int flags = O_RDWR;
+	struct mq_attr stuff = {
+		.mq_curmsgs = 0,
+		.mq_maxmsg = q_creation.depth,
+		.mq_msgsize = q_creation.size,
+		.mq_flags = 0
+	};
+
+	if (!q_creation.block) {
+		flags |= O_NONBLOCK;
+		stuff.mq_flags |= O_NONBLOCK;
+	}
+
+	mqd_t handle = mq_open(queue, flags);
+	q_creation.exists = handle != fail;
+	if (!q_creation.exists) {
+		/*
+		 * apply size and depth checks here.
+		 * if queue exists, we can default to existing depth and size.
+		 * but for a new queue, we require that input.
+		 */
+		if (validate_size() && validate_depth()) {
+			/* no need to re-apply mode. */
+			q_creation.set_mode = false;
+			flags |= O_CREAT;
+			handle = mq_open(queue, flags, q_creation.mode, &stuff);
+		}
+	}
+
+	if (handle == fail) {
+		errno_t what = errno;
+
+		warnc(what, "mq_open(create)");
+		return (what);
+	}
+
+#ifdef __FreeBSD__
+	/*
+	 * undocumented.
+	 * See https://bugs.freebsd.org/bugzilla//show_bug.cgi?id=273230
+	 */
+	int fd = mq_getfd_np(handle);
+
+	if (fd < 0) {
+		errno_t what = errno;
+
+		warnc(what, "mq_getfd_np(create)");
+		mq_close(handle);
+		return (what);
+	}
+	struct stat status = {0};
+	int result = fstat(fd, &status);
+	if (result != 0) {
+		errno_t what = errno;
+
+		warnc(what, "fstat(create)");
+		mq_close(handle);
+		return (what);
+	}
+
+	/* do this only if group and / or user given. */
+	if (q_creation.set_group || q_creation.set_user) {
+		q_creation.user =
+		    q_creation.set_user ? q_creation.user : status.st_uid;
+		q_creation.group =
+		    q_creation.set_group ? q_creation.group : status.st_gid;
+		result = fchown(fd, q_creation.user, q_creation.group);
+		if (result != 0) {
+			errno_t what = errno;
+
+			warnc(what, "fchown(create)");
+			mq_close(handle);
+			return (what);
+		}
+	}
+
+	/* do this only if altering mode of an existing queue. */
+	if (q_creation.exists && q_creation.set_mode &&
+	    q_creation.mode != (status.st_mode & accepted_mode_bits)) {
+		result = fchmod(fd, q_creation.mode);
+		if (result != 0) {
+			errno_t what = errno;
+
+			warnc(what, "fchmod(create)");
+			mq_close(handle);
+			return (what);
+		}
+	}
+#endif /* __FreeBSD__ */
+
+	return (mq_close(handle));
+}
+
+/* queue: name of queue to be removed. */
+static int
+rm(const char *queue)
+{
+	int result = mq_unlink(queue);
+
+	if (result != 0) {
+		errno_t what = errno;
+
+		warnc(what, "mq_unlink");
+		return (what);
+	}
+
+	return (result);
+}
+
+/* Return the display character for non-zero mode. */
+static char
+dual(mode_t mode, char display)
+{
+	return (mode != 0 ? display : '-');
+}
+
+/* Select one of four display characters based on mode and modifier. */
+static char
+quad(mode_t mode, mode_t modifier)
+{
+	static const char display[] = "-xSs";
+	unsigned index = 0;
+	if (mode != 0)
+		index += 1;
+	if (modifier)
+		index += 2;
+	return (display[index]);
+}
+
+/* queue: name of queue to be inspected. */
+static int
+info(const char *queue)
+{
+	mqd_t handle = mq_open(queue, O_RDONLY);
+
+	if (handle == fail) {
+		errno_t what = errno;
+
+		warnc(what, "mq_open(info)");
+		return (what);
+	}
+
+	struct mq_attr actual;
+
+	int result = mq_getattr(handle, &actual);
+	if (result != 0) {
+		errno_t what = errno;
+
+		warnc(what, "mq_getattr(info)");
+		return (what);
+	}
+
+	fprintf(stdout,
+	    "queue: '%s'\nQSIZE: %lu\nMSGSIZE: %ld\nMAXMSG: %ld\n"
+	    "CURMSG: %ld\nflags: %03ld\n",
+	    queue, actual.mq_msgsize * actual.mq_curmsgs, actual.mq_msgsize,
+	    actual.mq_maxmsg, actual.mq_curmsgs, actual.mq_flags);
+#ifdef __FreeBSD__
+
+	int fd = mq_getfd_np(handle);
+	struct stat status;
+
+	result = fstat(fd, &status);
+	if (result != 0) {
+		warn("fstat(info)");
+	} else {
+		mode_t mode = status.st_mode;
+
+		fprintf(stdout, "UID: %u\nGID: %u\n", status.st_uid, status.st_gid);
+		fprintf(stdout, "MODE: %c%c%c%c%c%c%c%c%c%c\n",
+		    dual(mode & S_ISVTX, 's'),
+		    dual(mode & S_IRUSR, 'r'),
+		    dual(mode & S_IWUSR, 'w'),
+		    quad(mode & S_IXUSR, mode & S_ISUID),
+		    dual(mode & S_IRGRP, 'r'),
+		    dual(mode & S_IWGRP, 'w'),
+		    quad(mode & S_IXGRP, mode & S_ISGID),
+		    dual(mode & S_IROTH, 'r'),
+		    dual(mode & S_IWOTH, 'w'),
+		    dual(mode & S_IXOTH, 'x'));
+	}
+#endif /* __FreeBSD__ */
+
+	return (mq_close(handle));
+}
+
+/* queue: name of queue to drain one message. */
+static int
+recv(const char *queue)
+{
+	mqd_t handle = mq_open(queue, O_RDONLY);
+
+	if (handle == fail) {
+		errno_t what = errno;
+
+		warnc(what, "mq_open(recv)");
+		return (what);
+	}
+
+	struct mq_attr actual;
+
+	int result = mq_getattr(handle, &actual);
+
+	if (result != 0) {
+		errno_t what = errno;
+
+		warnc(what, "mq_attr(recv)");
+		mq_close(handle);
+		return (what);
+	}
+
+	char *text = malloc(actual.mq_msgsize + 1);
+	unsigned q_priority = 0;
+
+	memset(text, 0, actual.mq_msgsize + 1);
+	result = mq_receive(handle, text, actual.mq_msgsize, &q_priority);
+	if (result < 0) {
+		errno_t what = errno;
+
+		warnc(what, "mq_receive");
+		mq_close(handle);
+		return (what);
+	}
+
+	fprintf(stdout, "[%u]: %-*.*s\n", q_priority, result, result, text);
+	return (mq_close(handle));
+}
+
+/*
+ * queue: name of queue to send one message.
+ * text: message text.
+ * q_priority: message priority in range of 0 to 63.
+ */
+static int
+send(const char *queue, const char *text, unsigned q_priority)
+{
+	mqd_t handle = mq_open(queue, O_WRONLY);
+
+	if (handle == fail) {
+		errno_t what = errno;
+
+		warnc(what, "mq_open(send)");
+		return (what);
+	}
+
+	struct mq_attr actual;
+
+	int result = mq_getattr(handle, &actual);
+
+	if (result != 0) {
+		errno_t what = errno;
+
+		warnc(what, "mq_attr(send)");
+		mq_close(handle);
+		return (what);
+	}
+
+	int size = strlen(text);
+
+	if (size > actual.mq_msgsize) {
+		warnx("truncating message to %ld characters.\n", actual.mq_msgsize);
+		size = actual.mq_msgsize;
+	}
+
+	result = mq_send(handle, text, size, q_priority);
+
+	if (result != 0) {
+		errno_t what = errno;
+
+		warnc(what, "mq_send");
+		mq_close(handle);
+		return (what);
+	}
+
+	return (mq_close(handle));
+}
+
+static void
+usage(FILE *file)
+{
+	fprintf(file,
+	    "usage:\n\tposixmqcontrol [rm|info|recv] -q <queue>\n"
+	    "\tposixmqcontrol create -q <queue> -s <maxsize> -d <maxdepth> "
+	    "[ -m <mode> ] [ -b <block> ] [-u <uid> ] [ -g <gid> ]\n"
+	    "\tposixmqcontrol send -q <queue> -c <content> "
+	    "[-p <priority> ]\n");
+}
+
+/* end of SUBCOMMANDS */
+
+#define _countof(arg) ((sizeof(arg)) / (sizeof((arg)[0])))
+
+/* convert an errno style error code to a sysexits code. */
+static int
+grace(int err_number)
+{
+	static const int xlat[][2] = {
+		/* generally means the mqueuefs driver is not loaded. */
+		{ENOSYS, EX_UNAVAILABLE},
+		/* no such queue name. */
+		{ENOENT, EX_OSFILE},
+		{EIO, EX_IOERR},
+		{ENODEV, EX_IOERR},
+		{ENOTSUP, EX_TEMPFAIL},
+		{EAGAIN, EX_IOERR},
+		{EPERM, EX_NOPERM},
+		{EACCES, EX_NOPERM},
+		{0, EX_OK}
+	};
+
+	for (unsigned i = 0; i < _countof(xlat); i++) {
+		if (xlat[i][0] == err_number)
+			return (xlat[i][1]);
+	}
+
+	return (EX_OSERR);
+}
+
+/* OPTIONS tables */
+
+/* careful: these 'names' arrays must be terminated by a null pointer. */
*** 372 LINES SKIPPED ***