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 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,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 --- /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. */ +static const char *names_queue[] = {"-q", "--queue", "-t", "--topic", NULL}; +static const struct Option option_queue = { + .pattern = names_queue, + .parse = parse_queue, + .validate = validate_queue}; +static const struct Option option_single_queue = { + .pattern = names_queue, + .parse = parse_single_queue, + .validate = validate_single_queue}; +static const char *names_depth[] = {"-d", "--depth", "--maxmsg", NULL}; +static const struct Option option_depth = { + .pattern = names_depth, + .parse = parse_depth, + .validate = validate_always_true}; +static const char *names_size[] = {"-s", "--size", "--msgsize", NULL}; +static const struct Option option_size = { + .pattern = names_size, + .parse = parse_size, + .validate = validate_always_true}; +static const char *names_block[] = {"-b", "--block", NULL}; +static const struct Option option_block = { + .pattern = names_block, + .parse = parse_block, + .validate = validate_always_true}; +static const char *names_content[] = { + "-c", "--content", "--data", "--message", NULL}; +static const struct Option option_content = { + .pattern = names_content, + .parse = parse_content, + .validate = validate_content}; +static const char *names_priority[] = {"-p", "--priority", NULL}; +static const struct Option option_priority = { + .pattern = names_priority, + .parse = parse_priority, + .validate = validate_always_true}; +static const char *names_mode[] = {"-m", "--mode", NULL}; +static const struct Option option_mode = { + .pattern = names_mode, + .parse = parse_mode, + .validate = validate_always_true}; +static const char *names_group[] = {"-g", "--gid", NULL}; +static const struct Option option_group = { + .pattern = names_group, + .parse = parse_group, + .validate = validate_always_true}; +static const char *names_user[] = {"-u", "--uid", NULL}; +static const struct Option option_user = { + .pattern = names_user, + .parse = parse_user, + .validate = validate_always_true}; + +/* careful: these arrays must be terminated by a null pointer. */ +#ifdef __FreeBSD__ +static const struct Option *create_options[] = { + &option_queue, &option_depth, &option_size, &option_block, + &option_mode, &option_group, &option_user, NULL}; +#else /* !__FreeBSD__ */ +static const struct Option *create_options[] = { + &option_queue, &option_depth, &option_size, &option_block, + &option_mode, NULL}; +#endif /* __FreeBSD__ */ +static const struct Option *info_options[] = {&option_queue, NULL}; +static const struct Option *unlink_options[] = {&option_queue, NULL}; +static const struct Option *recv_options[] = {&option_single_queue, NULL}; +static const struct Option *send_options[] = { + &option_queue, &option_content, &option_priority, NULL}; + +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 *itq; + + STAILQ_FOREACH(itq, &queues, links) { + const char *queue = itq->text; + + int result = create(queue, creation); + if (result != 0) + worst = result; + } + + return (grace(worst)); + } + + return (EX_USAGE); + } 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 *itq; + + STAILQ_FOREACH(itq, &queues, links) { + const char *queue = itq->text; + int result = info(queue); + + if (result != 0) + worst = result; + } + + return (grace(worst)); + } + + return (EX_USAGE); + } else 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 (grace(worst)); + } + return (EX_USAGE); + } 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; + int worst = recv(queue); + + return (grace(worst)); + } + + return (EX_USAGE); + } 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 *itq; + + STAILQ_FOREACH(itq, &queues, links) { + const char *queue = itq->text; + int result = rm(queue); + + if (result != 0) + worst = result; + } + + return (grace(worst)); + } + + return (EX_USAGE); + } else if (strcmp("help", verb) == 0) { + usage(stdout); + return (EX_OK); + } else { + warnx("Unknown verb [%s]", verb); + return (EX_USAGE); + } + } + + usage(stdout); + return (EX_OK); +} 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='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='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,28 @@ +#!/bin/sh +# test for 'insane' queue names. + +subject='posixmqcontrol' + +# does sanity check enforce leading slash? +${subject} info -q missing.leading.slash 2>/dev/null +code=$? +if [ $code != 64 ]; 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 != 64 ]; 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 != 64 ]; then + exit 1 +fi + +echo "Pass!" +exit 0