From nobody Fri Feb 23 23:54:44 2024 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4ThRh43RXPz5Bmfb; Fri, 23 Feb 2024 23:54:44 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4ThRh43F5Dz4ND6; Fri, 23 Feb 2024 23:54:44 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1708732484; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=LM3Dr18qsg0lS4HWbN3mR9y3Mj4unlzTD3E8qroOIPc=; b=JGQwCtPGdHPGxA84gSZNG+neah11ekJPjSChra6RjjdGqOzOOQTIu3ggWxosgftBa6ydJY wWVhn5btkuqmKZCbYu+rK4IpqEE6BhQ4bnTQrUy3j+Ui76XlXxAs2+MkAJzx1SeDQep5+s 4Pr+K/34MjxdP6MfHpBJp9l/z36i6nQXtRf7xXjIYATTg3JoppxwBFAIvfb3WcgLp3dLbk mSjK8dB74oZ18n6EPrA7lBnSaYEumT3uGYMyDPIYtQWkFJMStuMOzWwuhDAhmDcx7LLOUI 0IQqHnQJ47roDyIN7lqeigwyY6HU8UAg6CzAVfa+ZwVEXoIQ5CVU03aFGTXvng== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1708732484; a=rsa-sha256; cv=none; b=IgGuA0RcAQ9qx2XOM/bz8G6HnW42A/kguAeuI5yftHxM5rjIbmQa1msCG7anVuUI6EwqMc xcCqtH3VGmb7vqnl1fJ8GUjMTiK/3S1orRQ3G7ADfjaIpPbbV9HjkL1rEgwcQ49XUByKAl CCFmnuZv4noIAKd2UopDqNbSrpo+3cPLAEWnVwaYrWZ/gN/KtRLG+8F1g47cM/ONjppLoz 10j9ktPdFcOLZNaQkB7ARLT7LtOJ6WkFKz4maxDBYGCI5UqXnePWMMAi1pDJsJxYD5ZbYh NfnkhzfUVBndBdX55Qc8lRUuXSep2M9uSuHOqUD5MbcM/SqEjM6oxVwkTm37jA== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1708732484; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=LM3Dr18qsg0lS4HWbN3mR9y3Mj4unlzTD3E8qroOIPc=; b=P3keKJGMQXAaM0ZptLIXhycKG61Uj4T+jIzs5NvFpSks+dTAwEJ02auaat5H1R3TVUwixC Ex+Wxsa/7V5TLLf/aaO37jcWvYRDe9JjO25rp9cFBv16Sy8OHNxwJx/inXRoSbQc0xgTlH bii4BWKbrs3jn9oIuTrSnZyzCnf2tuyk9vzRjAmrAY7t0SnAdNZ5iRUUYnvmVf92tPystT VBWT5WQCdNEMeOrF3oxPL+Nd1IULNeACKmfx0jS+PXPwSunX+jjeCbymRe7chF47+oxKJM oJlIxSTtsPzMtLdCg6pOHhYvtBjFXOHs8PJ9q6IPOIqAhMDOO/5OofvnrZXwSw== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4ThRh42KMhzd3h; Fri, 23 Feb 2024 23:54:44 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.17.1/8.17.1) with ESMTP id 41NNsiUI029399; Fri, 23 Feb 2024 23:54:44 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.17.1/8.17.1/Submit) id 41NNsiDD029396; Fri, 23 Feb 2024 23:54:44 GMT (envelope-from git) Date: Fri, 23 Feb 2024 23:54:44 GMT Message-Id: <202402232354.41NNsiDD029396@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Konstantin Belousov Subject: git: 0112f8c4a88e - main - posixmqcontrol(1): manage posix message queues List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-dev-commits-src-all@freebsd.org X-BeenThere: dev-commits-src-all@freebsd.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: kib X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 0112f8c4a88e75342bdb6b9815fa220c5f645aa0 Auto-Submitted: auto-generated The branch main has been updated by kib: URL: https://cgit.FreeBSD.org/src/commit/?id=0112f8c4a88e75342bdb6b9815fa220c5f645aa0 commit 0112f8c4a88e75342bdb6b9815fa220c5f645aa0 Author: Rick Parrish AuthorDate: 2024-02-22 12:33:12 +0000 Commit: Konstantin Belousov 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 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 . +.\" +.\" 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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 \n" + "\tposixmqcontrol create -q -s -d " + "[ -m ] [ -b ] [-u ] [ -g ]\n" + "\tposixmqcontrol send -q -c " + "[-p ]\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 ***