Page MenuHomeFreeBSD

D43813.id134081.diff
No OneTemporary

D43813.id134081.diff

diff --git a/usr.bin/Makefile b/usr.bin/Makefile
--- a/usr.bin/Makefile
+++ b/usr.bin/Makefile
@@ -110,6 +110,7 @@
patch \
pathchk \
perror \
+ posixmqcontrol \
posixshmcontrol \
pr \
printenv \
diff --git a/usr.bin/posixmqcontrol/Makefile b/usr.bin/posixmqcontrol/Makefile
new file mode 100644
--- /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
--- /dev/null
+++ b/usr.bin/posixmqcontrol/posixmqcontrol.1
@@ -0,0 +1,166 @@
+.\" Copyright (c) 2024 The FreeBSD Foundation, Inc.
+.\"
+.\" This documentation was written by
+.\" 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 7, 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 manipulates the named POSIX message queue. 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 depth 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
+.Ic mqueuefs
+kernal 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 depth and size are used to create
+all queues. If a queue exists, then the depth are size are optional.
+.Pp
+The required
+.Ar size
+and
+.Ar depth
+arguments specify the maxmimum message size and maxmimum message depth.
+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 SUMMARY
+.Nm
+allows you to move POSIX message queue administration out of your
+applications. Defining and adjusting queue attributes can be done
+without touching application code. 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.
+.El
+.Sh EXIT STATUS
+.Ex -std
+An exit value of 78 (ENOSYS) usually means the mqueuefs kernel module
+is not loaded.
+.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 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
--- /dev/null
+++ b/usr.bin/posixmqcontrol/posixmqcontrol.c
@@ -0,0 +1,667 @@
+/*-
+ * Copyright (c) 2024 The FreeBSD Foundation
+ *
+ * This software was written by 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 <mqueue.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <grp.h>
+#include <pwd.h>
+#include <sys/queue.h>
+#include <limits.h>
+
+typedef enum {false, true} bool;
+#define nullptr NULL
+
+struct Creation {
+ // true if the queue exists.
+ bool exists;
+ // set_mode: true if a mode value was specified.
+ bool set_mode;
+ // mode: access mode with rwx permission bits.
+ mode_t mode;
+ // depth: maximum queue depth. default to an invalid depth.
+ long depth;
+ // size: maximum message size. default to an invalid size.
+ long size;
+ // block: true for blocking I/O and false for non-blocking I/O.
+ bool block;
+ // set_group: true if a group ID was specified.
+ bool set_group;
+ // group: group ID.
+ unsigned group;
+ // set_user: true if a user ID was specified.
+ bool set_user;
+ // user: user ID.
+ unsigned user;
+};
+
+// linked list element - used by queues and contents below.
+struct element {
+ STAILQ_ENTRY(element) links;
+ const char *text;
+};
+// linked list head(s) for queues and contents.
+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;
+
+// OPTIONS parsing utilitarian
+
+static void parse_long(const char *text, long *capture, const char *knob, const char *name) {
+ char *cursor = nullptr;
+ long value = strtol(text, &cursor, 10);
+ if (cursor > text) {
+ *capture = value;
+ }
+ else {
+ fprintf(stderr, "error: %s %s invalid format [%s].\n", knob, name, text);
+ }
+}
+
+static void parse_unsigned(const char *text, bool *set, unsigned *capture, const char *knob, const char *name) {
+ char *cursor = nullptr;
+ unsigned value = strtoul(text, &cursor, 8);
+ if (cursor > text) {
+ *set = true;
+ *capture = value;
+ }
+ else {
+ fprintf(stderr, "warning: %s %s format [%s] ignored.\n", knob, name, text);
+ }
+}
+
+static bool sane_queue(const char *text) {
+ int size = 0;
+ const char * queue = text;
+ if (*queue != '/') {
+ fprintf(stderr, "error: queue name [%-*.0s] must start with '/'.\n", PATH_MAX, text);
+ return false;
+ }
+ queue++;
+ size++;
+ while (*queue && size < PATH_MAX) {
+ if (*queue == '/') {
+ fprintf(stderr, "error: queue name [%-*.0s] - only one '/' permitted.\n", PATH_MAX, text);
+ return false;
+ }
+ queue++;
+ size++;
+ }
+ if (size == PATH_MAX && *queue) {
+ fprintf(stderr, "error: queue name [%-*.0s...] may not be longer than %d.\n", PATH_MAX, text, PATH_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 = nullptr;
+ long value = strtol(text, &cursor, 10);
+ if (cursor > text) {
+ creation.block = value != 0;
+ }
+ else {
+ fprintf(stderr, "warning: bad -b block format [%s] ignored.\n", text);
+ }
+ }
+}
+
+static void parse_content(const char *content) {
+ struct element *n1 = (struct element *)malloc(sizeof(struct element));
+ 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 == nullptr) {
+ 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 = nullptr;
+ long value = strtol(text, &cursor, 8);
+ if (cursor > text && value > 0 && value < 010000) {
+ creation.set_mode = true;
+ creation.mode = (mode_t)value;
+ }
+ else {
+ fprintf(stderr, "warning: impossible -m mode value [%s] ignored.\n", text);
+ }
+}
+
+static void parse_priority(const char *text) {
+ char *cursor = nullptr;
+ long value = strtol(text, &cursor, 10);
+ if (cursor > text) {
+ if (value >= 0 && value < MQ_PRIO_MAX) {
+ priority = value;
+ }
+ else {
+ fprintf(stderr, "warning: bad -p priority range [%s] ignored.\n", text);
+ }
+ }
+ else {
+ fprintf(stderr, "warning: bad -p priority format [%s] ignored.\n", text);
+ }
+}
+
+static void parse_queue(const char *queue) {
+ if (sane_queue(queue)) {
+ struct element *n1 = (struct element *)malloc(sizeof(struct element));
+ n1->text = queue;
+ STAILQ_INSERT_TAIL(&queues, n1, links);
+ }
+}
+
+static void parse_single_queue(const char *queue) {
+ if (sane_queue(queue)) {
+ if (STAILQ_FIRST(&queues) == nullptr) {
+ struct element *n1 = (struct element *)malloc(sizeof(struct element));
+ n1->text = queue;
+ STAILQ_INSERT_TAIL(&queues, n1, links);
+ }
+ else fprintf(stderr, "warning: ignoring extra -q queue [%s].\n", 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 == nullptr) {
+ 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_FIRST(&contents) != nullptr;
+ if (!valid) fprintf(stderr, "error: no content to send.\n");
+ return valid;
+}
+
+static bool validate_depth(void) {
+ bool valid = creation.exists || creation.depth > 0;
+ if (!valid) fprintf(stderr, "error: -d maximum queue depth not provided.\n");
+ return valid;
+}
+
+static bool validate_mode(void) { return creation.mode > 0; }
+
+static bool validate_queue(void) {
+ bool valid = STAILQ_FIRST(&queues) != nullptr;
+ if (!valid) fprintf(stderr, "error: missing -q, or no sane queue name given.\n");
+ return valid;
+}
+
+static bool validate_single_queue(void) {
+ bool valid = STAILQ_FIRST(&queues) != nullptr && STAILQ_NEXT(STAILQ_FIRST(&queues), links) == nullptr;
+ if (!valid) fprintf(stderr, "error: expected one queue.\n");
+ return valid;
+}
+
+static bool validate_size(void) {
+ bool valid = creation.exists || creation.size > 0;
+ if (!valid) fprintf(stderr, "error: -s maximum message size not provided.\n");
+ 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 != nullptr && !match) {
+ const struct Option * option = cursor[0];
+ const char **pattern = option->pattern;
+ while (*pattern != nullptr && !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) {
+ fprintf(stderr, "warning: skipping [%s].\n", argv[index]);
+ index++;
+ }
+ }
+ if (index < argc) {
+ fprintf(stderr, "warning: skipping [%s].\n", argv[index]);
+ }
+}
+
+// options - null terminated list of pointers to options.
+static bool validate_options(const struct Option **options) {
+ bool valid = true;
+ while (*options != nullptr) {
+ 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 = {0, q_creation.depth, q_creation.size, 0, {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;
+ perror("mq_open(create)");
+ return what;
+ }
+
+#if __BSD_VISIBLE
+ // 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;
+ perror("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;
+ perror("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;
+ perror("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 & 0x06777)) {
+ result = fchmod(fd, q_creation.mode);
+ if (result != 0) {
+ errno_t what = errno;
+ perror("fchmod(create)");
+ mq_close(handle);
+ return what;
+ }
+ }
+#endif
+
+ 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;
+ perror("mq_unlink");
+ return what;
+ }
+ return result;
+}
+
+// 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;
+ perror("mq_open(info)");
+ return what;
+ }
+ struct mq_attr actual = {0, 0, 0, 0, {0}};
+ int result = mq_getattr(handle, &actual);
+ if (result != 0) {
+ errno_t what = errno;
+ perror("mq_getattr(info)");
+ return what;
+ }
+ fprintf(stdout, "queue: '%s'\nQSIZE: %lu\nMSGSIZE: %ld\nMAXMSG: %ld\nCURMSG: %ld\nflags: %03ld\n",
+ queue, actual.mq_msgsize * actual.mq_curmsgs, actual.mq_msgsize, actual.mq_maxmsg, actual.mq_curmsgs, actual.mq_flags);
+#if __BSD_VISIBLE
+ int fd = mq_getfd_np(handle);
+ struct stat status = {0};
+ result = fstat(fd, &status);
+ if (result != 0) {
+ perror("fstat(info)");
+ }
+ else {
+ fprintf(stdout, "UID: %u\nGID: %u\nMODE: %03o\n", status.st_uid, status.st_gid, status.st_mode);
+ }
+#endif
+ 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;
+ perror("mq_open(recv)");
+ fprintf(stdout, "error %d\n", what);
+ return what;
+ }
+ struct mq_attr actual = {0, 0, 0, 0, {0}};
+ int result = mq_getattr(handle, &actual);
+ if (result != 0) {
+ errno_t what = errno;
+ perror("mq_attr(recv)");
+ mq_close(handle);
+ return what;
+ }
+ char *text = (char *)malloc(actual.mq_msgsize + 1);
+ memset(text, 0, actual.mq_msgsize + 1);
+ unsigned q_priority = 0;
+ result = mq_receive(handle, text, actual.mq_msgsize, &q_priority);
+ if (result < 0) {
+ errno_t what = errno;
+ perror("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;
+ perror("mq_open(send)");
+ return what;
+ }
+ struct mq_attr actual = {0, 0, 0, 0, {0}};
+ int result = mq_getattr(handle, &actual);
+ if (result != 0) {
+ errno_t what = errno;
+ perror("mq_attr(send)");
+ mq_close(handle);
+ return what;
+ }
+ int size = strlen(text);
+ if (size > actual.mq_msgsize) {
+ fprintf(stderr, "warning: 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;
+ perror("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
+
+// OPTIONS tables
+
+// careful: these 'names' arrays must be terminated by a null pointer.
+static const char *names_queue[] = {"-q", "--queue", "-t", "--topic", nullptr};
+static const struct Option option_queue = {names_queue, parse_queue, validate_queue};
+static const struct Option option_single_queue = {names_queue, parse_single_queue, validate_single_queue};
+static const char *names_depth[] = {"-d", "--depth", "--maxmsg", nullptr};
+static const struct Option option_depth = {names_depth, parse_depth, validate_always_true};
+static const char *names_size[] = {"-s", "--size", "--msgsize", nullptr};
+static const struct Option option_size = {names_size, parse_size, validate_always_true};
+static const char *names_block[] = {"-b", "--block", nullptr};
+static const struct Option option_block = {names_block, parse_block, validate_always_true};
+static const char *names_content[] = {"-c", "--content", "--data", "--message", nullptr};
+static const struct Option option_content = {names_content, parse_content, validate_content};
+static const char *names_priority[] = {"-p", "--priority", nullptr};
+static const struct Option option_priority = {names_priority, parse_priority, validate_always_true};
+static const char *names_mode[] = {"-m", "--mode", nullptr};
+static const struct Option option_mode = {names_mode, parse_mode, validate_mode};
+static const char *names_group[] = {"-g", "--gid", nullptr};
+static const struct Option option_group = {names_group, parse_group, validate_always_true};
+static const char *names_user[] = {"-u", "--uid", nullptr};
+static const struct Option option_user = {names_user, parse_user, validate_always_true};
+
+// careful: these arrays must be terminated by a null pointer.
+#if __BSD_VISIBLE
+static const struct Option *create_options[] = {&option_queue, &option_depth, &option_size, &option_block, &option_mode, &option_group, &option_user, nullptr};
+#else
+static const struct Option *create_options[] = {&option_queue, &option_depth, &option_size, &option_block, &option_mode, nullptr};
+#endif
+static const struct Option *info_options[] = {&option_queue, nullptr};
+static const struct Option *unlink_options[] = {&option_queue, nullptr};
+static const struct Option *recv_options[] = {&option_single_queue, nullptr};
+static const struct Option *send_options[] = {&option_queue, &option_content, &option_priority, nullptr};
+
+// 12 mode bits ugsrwxrwxrwx
+// g = GID
+// u = UID
+// s = sticky bit (ignored)
+// three rwx fields for owner, group, and world.
+// r = read
+// w = write
+// x = execute
+
+int main(int argc, const char *argv[]) {
+ STAILQ_INIT(&queues);
+ STAILQ_INIT(&contents);
+
+ if (argc > 1) {
+ const char *verb = argv[1];
+ int index = 2;
+
+ if ( strcmp("create", verb) == 0 || strcmp("attr", verb) == 0 ) {
+ parse_options(index, argc, argv, create_options);
+ if ( validate_options(create_options) ) {
+ int worst = 0;
+ struct element *it;
+ STAILQ_FOREACH(it, &queues, links) {
+ const char *queue = it->text;
+ int result = create(queue, creation);
+ if (result != 0)
+ worst = result;
+ }
+ return worst;
+ }
+ return EINVAL;
+ }
+ else if ( strcmp("info", verb) == 0 || strcmp("cat", verb) == 0 ) {
+ parse_options(index, argc, argv, info_options);
+ if ( validate_options(info_options) ) {
+ int worst = 0;
+ struct element *it;
+ STAILQ_FOREACH(it, &queues, links) {
+ const char *queue = it->text;
+ int result = info(queue);
+ if (result != 0)
+ worst = result;
+ }
+ return worst;
+ }
+ return EINVAL;
+ }
+ if ( strcmp("send", verb) == 0 ) {
+ parse_options(index, argc, argv, send_options);
+ if ( validate_options(send_options) ) {
+ int worst = 0;
+ struct element *itq;
+ STAILQ_FOREACH(itq, &queues, links) {
+ const char *queue = itq->text;
+ struct element *itc;
+ STAILQ_FOREACH(itc, &contents, links) {
+ const char *content = itc->text;
+ int result = send(queue, content, priority);
+ if (result != 0)
+ worst = result;
+ }
+ }
+ return worst;
+ }
+ return EINVAL;
+ }
+ else if ( strcmp("recv", verb) == 0 || strcmp("receive", verb) == 0 ) {
+ parse_options(index, argc, argv, recv_options);
+ if ( validate_options(recv_options) ) {
+ const char *queue = STAILQ_FIRST(&queues)->text;
+ return recv(queue);
+ }
+ return EINVAL;
+ }
+ else if ( strcmp("unlink", verb) == 0 || strcmp("rm", verb) == 0 ) {
+ parse_options(index, argc, argv, unlink_options);
+ if ( validate_options(unlink_options) ) {
+ int worst = 0;
+ struct element *it;
+ STAILQ_FOREACH(it, &queues, links)
+ {
+ const char *queue = it->text;
+ int result = rm(queue);
+ if (result != 0)
+ worst = result;
+ }
+ return worst;
+ }
+ return EINVAL;
+ }
+ else if (strcmp("help", verb) == 0 ) {
+ usage(stdout);
+ return 0;
+ }
+ else {
+ fprintf(stderr, "error: Unknown verb [%s]\n", verb);
+ return EINVAL;
+ }
+ }
+
+ usage(stdout);
+ return 0;
+}
diff --git a/usr.bin/posixmqcontrol/posixmqcontroltest8qs.sh b/usr.bin/posixmqcontrol/posixmqcontroltest8qs.sh
new file mode 100644
--- /dev/null
+++ b/usr.bin/posixmqcontrol/posixmqcontroltest8qs.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+# testing create, info, and send operations applied to multiple queue names at once.
+# recv accepts a single queue name so draining is done one queue at a time.
+subject='./build/posixmqcontrol'
+prefix='/posixmqcontroltest'
+
+list=
+for i in 1 2 3 4 5 6 7 8
+do
+ topic="${prefix}${i}"
+ ${subject} info -q "${topic}" 2>/dev/null
+ if [ $? == 0 ]; then
+ echo "sorry, $topic exists."
+ exit 1
+ fi
+ list="${list} -q ${topic}"
+done
+
+${subject} create -d 2 -s 64 ${list}
+if [ $? != 0 ]; then
+ exit 1
+fi
+
+ignore=$( ${subject} info ${list} )
+if [ $? != 0 ]; then
+ exit 1
+fi
+
+${subject} send -c 'this message sent to all listed queues.' ${list}
+if [ $? != 0 ]; then
+ exit 1
+fi
+
+# we can only drain one message at a time.
+for i in 1 2 3 4 5 6 7 8
+do
+ topic="${prefix}${i}"
+ ignore=$( ${subject} recv -q "${topic}" )
+ if [ $? != 0 ]; then
+ exit 1
+ fi
+done
+
+${subject} rm ${list}
+if [ $? == 0 ]; then
+ echo "Pass!"
+ exit 0
+fi
+
+exit 1
diff --git a/usr.bin/posixmqcontrol/posixmqcontroltest8x64.sh b/usr.bin/posixmqcontrol/posixmqcontroltest8x64.sh
new file mode 100644
--- /dev/null
+++ b/usr.bin/posixmqcontrol/posixmqcontroltest8x64.sh
@@ -0,0 +1,99 @@
+#!/bin/sh
+# exercises create, info, send and recv subcommands.
+
+subject='./build/posixmqcontrol'
+topic='/test123'
+
+${subject} info -q "$topic" 2>/dev/null
+if [ $? == 0 ]; then
+ echo "sorry, $topic exists."
+ exit 1
+fi
+
+# create trivial queue that can hold 8 messages of 64 bytes each.
+${subject} create -q "$topic" -s 64 -d 8
+if [ $? != 0 ]; then
+ exit 1
+fi
+
+info=$(${subject} info -q "$topic")
+if [ $? != 0 ]; then
+ exit 1
+fi
+expected='MSGSIZE: 64'
+actual=$(echo "${info}" | grep 'MSGSIZE: ')
+if [ "$expected" != "$actual" ]; then
+ echo "EXPECTED: $expected"
+ echo " ACTUAL: $actual"
+ exit 1
+fi
+expected='MAXMSG: 8'
+actual=$(echo "${info}" | grep 'MAXMSG: ')
+if [ "$expected" != "$actual" ]; then
+ echo "EXPECTED: $expected"
+ echo " ACTUAL: $actual"
+ exit 1
+fi
+expected='CURMSG: 0'
+actual=$(echo "${info}" | grep 'CURMSG: ')
+if [ "$expected" != "$actual" ]; then
+ echo "EXPECTED: $expected"
+ echo " ACTUAL: $actual"
+ exit 1
+fi
+
+# write eight messages of increasing priority.
+for i in 1 2 3 4 5 6 7 8
+do
+ ${subject} send -q "$topic" -c "message $i" -p "$i"
+ if [ $? != 0 ]; then
+ exit 1
+ fi
+done
+
+info=$(${subject} info -q "$topic")
+if [ $? != 0 ]; then
+ exit
+fi
+expected='CURMSG: 8'
+actual=$(echo "${info}" | grep 'CURMSG: ')
+if [ "$expected" != "$actual" ]; then
+ echo "EXPECTED: $expected"
+ echo " ACTUAL: $actual"
+ exit 1
+fi
+
+# expect the eight messages to appear in priority order.
+for i in 8 7 6 5 4 3 2 1
+do
+ expected='['"$i"']: message '"$i"
+ actual=$(${subject} recv -q "$topic")
+ if [ $? != 0 ]; then
+ exit
+ fi
+ if [ "$expected" != "$actual" ]; then
+ echo "EXPECTED: $expected"
+ echo " ACTUAL: $actual"
+ exit 1
+ fi
+done
+
+info=$(${subject} info -q "$topic")
+if [ $? != 0 ]; then
+ exit 1
+fi
+expected='CURMSG: 0'
+actual=$(echo "${info}" | grep 'CURMSG: ')
+if [ "$expected" != "$actual" ]; then
+ echo "EXPECTED: $expected"
+ echo " ACTUAL: $actual"
+ exit 1
+fi
+
+${subject} rm -q "$topic"
+if [ $? == 0 ]; then
+ echo "Pass!"
+ exit 0
+fi
+
+exit 1
diff --git a/usr.bin/posixmqcontrol/posixmqcontroltestsane.sh b/usr.bin/posixmqcontrol/posixmqcontroltestsane.sh
new file mode 100644
--- /dev/null
+++ b/usr.bin/posixmqcontrol/posixmqcontroltestsane.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+# test for 'insane' queue names.
+
+subject='./build/posixmqcontrol'
+
+# does sanity check enforce leading slash?
+${subject} info -q missing.leading.slash 2>/dev/null
+code=$?
+if [ $code != 22 ]; then
+ exit 1
+fi
+
+# does sanity check enforce one and only one slash?
+${subject} info -q /to/many/slashes 2>/dev/null
+code=$?
+if [ $code != 22 ]; then
+ exit 1
+fi
+
+# does sanity check enforce length limit?
+${subject} info -q /this.queue.name.is.way.too.long.at.more.than.one.thousand.and.twenty.four.characters.long.because.nobody.needs.to.type.out.something.this.ridiculously.long.than.just.goes.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on.and.on 2>/dev/null
+code=$?
+if [ $code != 22 ]; then
+ echo $code
+ exit 1
+fi
+
+echo "Pass!"
+exit 0

File Metadata

Mime Type
text/plain
Expires
Mon, Apr 20, 6:29 AM (10 h, 12 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
31823373
Default Alt Text
D43813.id134081.diff (31 KB)

Event Timeline