posixmqcontrol(1): manage posix message queues

Reviewed by:	kib, paumma
MFC after:	1 week
Differential revision:	https://reviews.freebsd.org/D43845
This commit is contained in:
Rick Parrish 2024-02-22 06:33:12 -06:00 committed by Konstantin Belousov
parent c5698afcd5
commit 0112f8c4a8
7 changed files with 1286 additions and 0 deletions

View File

@ -110,6 +110,7 @@ SUBDIR= alias \
patch \
pathchk \
perror \
posixmqcontrol \
posixshmcontrol \
pr \
printenv \

View File

@ -0,0 +1,4 @@
PROG= posixmqcontrol
LIBADD= rt
.include <bsd.prog.mk>

View File

@ -0,0 +1,180 @@
.\"-
.\" SPDX-License-Identifier: BSD-2-Clause
.\"
.\" Copyright (c) 2024 Rick Parrish <unitrunker@unitrunker.net>.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd February 19, 2024
.Dt POSIXMQCONTROL 1
.Os
.Sh NAME
.Nm posixmqcontrol
.Nd Control POSIX mqueuefs message queues
.Sh SYNOPSIS
.Nm
.Ar create
.Fl q Ar queue
.Fl s Ar size
.Fl d Ar depth
.Op Fl m Ar mode
.Op Fl g Ar group
.Op Fl u Ar user
.Nm
.Ar info
.Fl q Ar queue
.Nm
.Ar recv
.Fl q Ar queue
.Nm
.Ar rm
.Fl q Ar queue
.Nm
.Ar send
.Fl q Ar queue
.Fl c Ar content
.Op Fl p Ar priority
.Sh DESCRIPTION
The
.Nm
command allows separating POSIX message queue administration from application
stack.
Defining and adjusting queue attributes can be done without touching
application code.
It allows creating queues, inspecting queue metadata, altering group and user
access to queues, dumping queue contents, and unlinking queues.
.Pp
Unlinking removes the name from the system and frees underlying memory.
.Pp
The maximum message size, maximum queue size, and current queue size are
displayed by the
.Ic info
subcommand. This output is similar to running
.Ic cat
on a mqueuefs queue mounted under a mount point.
This utility requires the
.Ic mqueuefs
kernel module to be loaded but does not require
.Ic mqueuefs
to be mounted as a file system.
.Pp
The following subcommands are provided:
.Bl -tag -width truncate
.It Ic create
Create the named queues, if they do not already exist.
More than one queue name may be created. The same maximum queue depth and
maximum message size are used to create all queues.
If a queue exists, then depth and size are optional.
.Pp
The required
.Ar size
and
.Ar depth
arguments specify the maximum message size (bytes per message) and maximum queue
size (depth or number of messages in the queue).
The optional numerical
.Ar mode
argument specifies the initial access mode.
If the queue exists but does not match the requested size and depth, this
utility will attempt to recreate the queue by first unlinking and then creating
it.
This will fail if the queue is not empty or is opened by other processes.
.It Ic rm
Unlink the queues specified - one attempt per queue.
Failure to unlink one queue does not stop this sub-command from attempting to
unlink the others.
.It Ic info
For each named queue, dispay the maximum message size, maximum queue size,
current queue depth, user owner id, group owner id, and mode permission bits.
.It Ic recv
Wait for a message from a single named queue and display the message to
standard output.
.It Ic send
Send messages to one or more named queues.
If multiple messages and multiple queues are specified, the utility attempts to
send all messages to all queues.
The optional -p priority, if omitted, defaults to MQ_PRIO_MAX / 2 or medium
priority.
.El
.Sh NOTES
A change of queue geometry (maximum message size and/or maximum number of
messages) requires destroying and re-creating the queue.
As a safety feature,
the create subcommand refuses to destroy a non-empty queue.
If you use the rm subcommand to destroy a queue, any queued messages are lost.
To avoid down-time when altering queue attributes, consider creating a new
queue and configure reading applications to drain both new and old queues.
Retire the old queue once all writers have been updated to write to the new
queue.
.Sh EXIT STATUS
.Ex -std
.Bl -bullet
.It
EX_NOTAVAILABLE usually means the mqueuefs kernel module is not loaded.
.It
EX_USAGE reports one or more incorrect parameters.
.El
.Sh EXAMPLES
.Bl -bullet
.It
To retrieve the current message from a named queue,
.Pa /1 ,
use the command
.Dl "posixmqcontrol recv -q /1"
.It
To create a queue with the name
.Pa /2
with maximum message size 100 and maximum queue depth 10,
use the command
.Dl "posixmqcontrol create -q /2 -s 100 -d 10"
.It
To send a message to a queue with the name
.Pa /3
use the command
.Dl "posixmqcontrol send -q /3 -c 'some choice words.'"
.It
To examine attributes of a queue named
.Pa /4
use the command
.Dl "posixmqcontrol info -q /4"
.El
.Sh SEE ALSO
.Xr mq_open 2 ,
.Xr mq_getattr 2 ,
.Xr mq_receive 2 ,
.Xr mq_send 2 ,
.Xr mq_setattr 2 ,
.Xr mq_unlink 2 ,
.Xr mqueuefs 5
.Sh BUGS
mq_timedsend and mq_timedrecv are not implemented.
info reports a worst-case estimate for QSIZE.
.Sh HISTORY
The
.Nm
command appeared in
.Fx 15.0 .
.Sh AUTHORS
The
.Nm
command and this manual page were written by
.An Rick Parrish Aq Mt unitrunker@unitrunker.net.

View File

@ -0,0 +1,924 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2024 Rick Parrish <unitrunker@unitrunker.net>.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/queue.h>
#include <sys/stat.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <limits.h>
#include <mqueue.h>
#include <pwd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
struct Creation {
/* true if the queue exists. */
bool exists;
/* true if a mode value was specified. */
bool set_mode;
/* access mode with rwx permission bits. */
mode_t mode;
/* maximum queue depth. default to an invalid depth. */
long depth;
/* maximum message size. default to an invalid size. */
long size;
/* true for blocking I/O and false for non-blocking I/O. */
bool block;
/* true if a group ID was specified. */
bool set_group;
/* group ID. */
gid_t group;
/* true if a user ID was specified. */
bool set_user;
/* user ID. */
uid_t user;
};
struct element {
STAILQ_ENTRY(element) links;
const char *text;
};
static struct element *
malloc_element(const char *context)
{
struct element *item = malloc(sizeof(struct element));
if (item == NULL)
/* the only non-EX_* prefixed exit code. */
err(1, "malloc(%s)", context);
return (item);
}
static STAILQ_HEAD(tqh, element)
queues = STAILQ_HEAD_INITIALIZER(queues),
contents = STAILQ_HEAD_INITIALIZER(contents);
/* send defaults to medium priority. */
static long priority = MQ_PRIO_MAX / 2;
static struct Creation creation = {
.exists = false,
.set_mode = false,
.mode = 0755,
.depth = -1,
.size = -1,
.block = true,
.set_group = false,
.group = 0,
.set_user = false,
.user = 0
};
static const mqd_t fail = (mqd_t)-1;
static const mode_t accepted_mode_bits =
S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISTXT;
/* OPTIONS parsing utilitarian */
static void
parse_long(const char *text, long *capture, const char *knob, const char *name)
{
char *cursor = NULL;
long value = strtol(text, &cursor, 10);
if (cursor > text && *cursor == 0) {
*capture = value;
} else {
warnx("%s %s invalid format [%s].", knob, name, text);
}
}
static void
parse_unsigned(const char *text, bool *set,
unsigned *capture, const char *knob, const char *name)
{
char *cursor = NULL;
unsigned value = strtoul(text, &cursor, 8);
if (cursor > text && *cursor == 0) {
*set = true;
*capture = value;
} else {
warnx("%s %s format [%s] ignored.", knob, name, text);
}
}
static bool
sane_queue(const char *queue)
{
int size = 0;
if (queue[size] != '/') {
warnx("queue name [%-.*s] must start with '/'.", NAME_MAX, queue);
return (false);
}
for (size++; queue[size] != 0 && size < NAME_MAX; size++) {
if (queue[size] == '/') {
warnx("queue name [%-.*s] - only one '/' permitted.",
NAME_MAX, queue);
return (false);
}
}
if (size == NAME_MAX && queue[size] != 0) {
warnx("queue name [%-.*s...] may not be longer than %d.",
NAME_MAX, queue, NAME_MAX);
return (false);
}
return (true);
}
/* OPTIONS parsers */
static void
parse_block(const char *text)
{
if (strcmp(text, "true") == 0 || strcmp(text, "yes") == 0) {
creation.block = true;
} else if (strcmp(text, "false") == 0 || strcmp(text, "no") == 0) {
creation.block = false;
} else {
char *cursor = NULL;
long value = strtol(text, &cursor, 10);
if (cursor > text) {
creation.block = value != 0;
} else {
warnx("bad -b block format [%s] ignored.", text);
}
}
}
static void
parse_content(const char *content)
{
struct element *n1 = malloc_element("content");
n1->text = content;
STAILQ_INSERT_TAIL(&contents, n1, links);
}
static void
parse_depth(const char *text)
{
parse_long(text, &creation.depth, "-d", "depth");
}
static void
parse_group(const char *text)
{
struct group *entry = getgrnam(text);
if (entry == NULL) {
parse_unsigned(text, &creation.set_group,
&creation.group, "-g", "group");
} else {
creation.set_group = true;
creation.group = entry->gr_gid;
}
}
static void
parse_mode(const char *text)
{
char *cursor = NULL;
long value = strtol(text, &cursor, 8);
// verify only accepted mode bits are set.
if (cursor > text && *cursor == 0 && (value & accepted_mode_bits) == value) {
creation.set_mode = true;
creation.mode = (mode_t)value;
} else {
warnx("impossible -m mode value [%s] ignored.", text);
}
}
static void
parse_priority(const char *text)
{
char *cursor = NULL;
long value = strtol(text, &cursor, 10);
if (cursor > text && *cursor == 0) {
if (value >= 0 && value < MQ_PRIO_MAX) {
priority = value;
} else {
warnx("bad -p priority range [%s] ignored.", text);
}
} else {
warnx("bad -p priority format [%s] ignored.", text);
}
}
static void
parse_queue(const char *queue)
{
if (sane_queue(queue)) {
struct element *n1 = malloc_element("queue name");
n1->text = queue;
STAILQ_INSERT_TAIL(&queues, n1, links);
}
}
static void
parse_single_queue(const char *queue)
{
if (sane_queue(queue)) {
if (STAILQ_EMPTY(&queues)) {
struct element *n1 = malloc_element("queue name");
n1->text = queue;
STAILQ_INSERT_TAIL(&queues, n1, links);
} else
warnx("ignoring extra -q queue [%s].", queue);
}
}
static void
parse_size(const char *text)
{
parse_long(text, &creation.size, "-s", "size");
}
static void
parse_user(const char *text)
{
struct passwd *entry = getpwnam(text);
if (entry == NULL) {
parse_unsigned(text, &creation.set_user,
&creation.user, "-u", "user");
} else {
creation.set_user = true;
creation.user = entry->pw_uid;
}
}
/* OPTIONS validators */
static bool
validate_always_true(void)
{
return (true);
}
static bool
validate_content(void)
{
bool valid = !STAILQ_EMPTY(&contents);
if (!valid)
warnx("no content to send.");
return (valid);
}
static bool
validate_depth(void)
{
bool valid = creation.exists || creation.depth > 0;
if (!valid)
warnx("-d maximum queue depth not provided.");
return (valid);
}
static bool
validate_queue(void)
{
bool valid = !STAILQ_EMPTY(&queues);
if (!valid)
warnx("missing -q, or no sane queue name given.");
return (valid);
}
static bool
validate_single_queue(void)
{
bool valid = !STAILQ_EMPTY(&queues) &&
STAILQ_NEXT(STAILQ_FIRST(&queues), links) == NULL;
if (!valid)
warnx("expected one queue.");
return (valid);
}
static bool
validate_size(void)
{
bool valid = creation.exists || creation.size > 0;
if (!valid)
warnx("-s maximum message size not provided.");
return (valid);
}
/* OPTIONS table handling. */
struct Option {
/* points to array of string pointers terminated by a null pointer. */
const char **pattern;
/* parse argument. */
void (*parse)(const char *);
/*
* displays an error and returns false if this parameter is not valid.
* returns true otherwise.
*/
bool (*validate)(void);
};
/*
* parse options by table.
* index - current index into argv list.
* argc, argv - command line parameters.
* options - null terminated list of pointers to options.
*/
static void
parse_options(int index, int argc,
const char *argv[], const struct Option **options)
{
while ((index + 1) < argc) {
const struct Option **cursor = options;
bool match = false;
while (*cursor != NULL && !match) {
const struct Option *option = cursor[0];
const char **pattern = option->pattern;
while (*pattern != NULL && !match) {
const char *knob = *pattern;
match = strcmp(knob, argv[index]) == 0;
if (!match)
pattern++;
}
if (match) {
option->parse(argv[index + 1]);
index += 2;
break;
}
cursor++;
}
if (!match && index < argc) {
warnx("skipping [%s].", argv[index]);
index++;
}
}
if (index < argc) {
warnx("skipping [%s].", argv[index]);
}
}
/* options - null terminated list of pointers to options. */
static bool
validate_options(const struct Option **options)
{
bool valid = true;
while (*options != NULL) {
const struct Option *option = options[0];
if (!option->validate())
valid = false;
options++;
}
return (valid);
}
/* SUBCOMMANDS */
/*
* queue: name of queue to be created.
* q_creation: creation parameters (copied by value).
*/
static int
create(const char *queue, struct Creation q_creation)
{
int flags = O_RDWR;
struct mq_attr stuff = {
.mq_curmsgs = 0,
.mq_maxmsg = q_creation.depth,
.mq_msgsize = q_creation.size,
.mq_flags = 0
};
if (!q_creation.block) {
flags |= O_NONBLOCK;
stuff.mq_flags |= O_NONBLOCK;
}
mqd_t handle = mq_open(queue, flags);
q_creation.exists = handle != fail;
if (!q_creation.exists) {
/*
* apply size and depth checks here.
* if queue exists, we can default to existing depth and size.
* but for a new queue, we require that input.
*/
if (validate_size() && validate_depth()) {
/* no need to re-apply mode. */
q_creation.set_mode = false;
flags |= O_CREAT;
handle = mq_open(queue, flags, q_creation.mode, &stuff);
}
}
if (handle == fail) {
errno_t what = errno;
warnc(what, "mq_open(create)");
return (what);
}
#ifdef __FreeBSD__
/*
* undocumented.
* See https://bugs.freebsd.org/bugzilla//show_bug.cgi?id=273230
*/
int fd = mq_getfd_np(handle);
if (fd < 0) {
errno_t what = errno;
warnc(what, "mq_getfd_np(create)");
mq_close(handle);
return (what);
}
struct stat status = {0};
int result = fstat(fd, &status);
if (result != 0) {
errno_t what = errno;
warnc(what, "fstat(create)");
mq_close(handle);
return (what);
}
/* do this only if group and / or user given. */
if (q_creation.set_group || q_creation.set_user) {
q_creation.user =
q_creation.set_user ? q_creation.user : status.st_uid;
q_creation.group =
q_creation.set_group ? q_creation.group : status.st_gid;
result = fchown(fd, q_creation.user, q_creation.group);
if (result != 0) {
errno_t what = errno;
warnc(what, "fchown(create)");
mq_close(handle);
return (what);
}
}
/* do this only if altering mode of an existing queue. */
if (q_creation.exists && q_creation.set_mode &&
q_creation.mode != (status.st_mode & accepted_mode_bits)) {
result = fchmod(fd, q_creation.mode);
if (result != 0) {
errno_t what = errno;
warnc(what, "fchmod(create)");
mq_close(handle);
return (what);
}
}
#endif /* __FreeBSD__ */
return (mq_close(handle));
}
/* queue: name of queue to be removed. */
static int
rm(const char *queue)
{
int result = mq_unlink(queue);
if (result != 0) {
errno_t what = errno;
warnc(what, "mq_unlink");
return (what);
}
return (result);
}
/* Return the display character for non-zero mode. */
static char
dual(mode_t mode, char display)
{
return (mode != 0 ? display : '-');
}
/* Select one of four display characters based on mode and modifier. */
static char
quad(mode_t mode, mode_t modifier)
{
static const char display[] = "-xSs";
unsigned index = 0;
if (mode != 0)
index += 1;
if (modifier)
index += 2;
return (display[index]);
}
/* queue: name of queue to be inspected. */
static int
info(const char *queue)
{
mqd_t handle = mq_open(queue, O_RDONLY);
if (handle == fail) {
errno_t what = errno;
warnc(what, "mq_open(info)");
return (what);
}
struct mq_attr actual;
int result = mq_getattr(handle, &actual);
if (result != 0) {
errno_t what = errno;
warnc(what, "mq_getattr(info)");
return (what);
}
fprintf(stdout,
"queue: '%s'\nQSIZE: %lu\nMSGSIZE: %ld\nMAXMSG: %ld\n"
"CURMSG: %ld\nflags: %03ld\n",
queue, actual.mq_msgsize * actual.mq_curmsgs, actual.mq_msgsize,
actual.mq_maxmsg, actual.mq_curmsgs, actual.mq_flags);
#ifdef __FreeBSD__
int fd = mq_getfd_np(handle);
struct stat status;
result = fstat(fd, &status);
if (result != 0) {
warn("fstat(info)");
} else {
mode_t mode = status.st_mode;
fprintf(stdout, "UID: %u\nGID: %u\n", status.st_uid, status.st_gid);
fprintf(stdout, "MODE: %c%c%c%c%c%c%c%c%c%c\n",
dual(mode & S_ISVTX, 's'),
dual(mode & S_IRUSR, 'r'),
dual(mode & S_IWUSR, 'w'),
quad(mode & S_IXUSR, mode & S_ISUID),
dual(mode & S_IRGRP, 'r'),
dual(mode & S_IWGRP, 'w'),
quad(mode & S_IXGRP, mode & S_ISGID),
dual(mode & S_IROTH, 'r'),
dual(mode & S_IWOTH, 'w'),
dual(mode & S_IXOTH, 'x'));
}
#endif /* __FreeBSD__ */
return (mq_close(handle));
}
/* queue: name of queue to drain one message. */
static int
recv(const char *queue)
{
mqd_t handle = mq_open(queue, O_RDONLY);
if (handle == fail) {
errno_t what = errno;
warnc(what, "mq_open(recv)");
return (what);
}
struct mq_attr actual;
int result = mq_getattr(handle, &actual);
if (result != 0) {
errno_t what = errno;
warnc(what, "mq_attr(recv)");
mq_close(handle);
return (what);
}
char *text = malloc(actual.mq_msgsize + 1);
unsigned q_priority = 0;
memset(text, 0, actual.mq_msgsize + 1);
result = mq_receive(handle, text, actual.mq_msgsize, &q_priority);
if (result < 0) {
errno_t what = errno;
warnc(what, "mq_receive");
mq_close(handle);
return (what);
}
fprintf(stdout, "[%u]: %-*.*s\n", q_priority, result, result, text);
return (mq_close(handle));
}
/*
* queue: name of queue to send one message.
* text: message text.
* q_priority: message priority in range of 0 to 63.
*/
static int
send(const char *queue, const char *text, unsigned q_priority)
{
mqd_t handle = mq_open(queue, O_WRONLY);
if (handle == fail) {
errno_t what = errno;
warnc(what, "mq_open(send)");
return (what);
}
struct mq_attr actual;
int result = mq_getattr(handle, &actual);
if (result != 0) {
errno_t what = errno;
warnc(what, "mq_attr(send)");
mq_close(handle);
return (what);
}
int size = strlen(text);
if (size > actual.mq_msgsize) {
warnx("truncating message to %ld characters.\n", actual.mq_msgsize);
size = actual.mq_msgsize;
}
result = mq_send(handle, text, size, q_priority);
if (result != 0) {
errno_t what = errno;
warnc(what, "mq_send");
mq_close(handle);
return (what);
}
return (mq_close(handle));
}
static void
usage(FILE *file)
{
fprintf(file,
"usage:\n\tposixmqcontrol [rm|info|recv] -q <queue>\n"
"\tposixmqcontrol create -q <queue> -s <maxsize> -d <maxdepth> "
"[ -m <mode> ] [ -b <block> ] [-u <uid> ] [ -g <gid> ]\n"
"\tposixmqcontrol send -q <queue> -c <content> "
"[-p <priority> ]\n");
}
/* end of SUBCOMMANDS */
#define _countof(arg) ((sizeof(arg)) / (sizeof((arg)[0])))
/* convert an errno style error code to a sysexits code. */
static int
grace(int err_number)
{
static const int xlat[][2] = {
/* generally means the mqueuefs driver is not loaded. */
{ENOSYS, EX_UNAVAILABLE},
/* no such queue name. */
{ENOENT, EX_OSFILE},
{EIO, EX_IOERR},
{ENODEV, EX_IOERR},
{ENOTSUP, EX_TEMPFAIL},
{EAGAIN, EX_IOERR},
{EPERM, EX_NOPERM},
{EACCES, EX_NOPERM},
{0, EX_OK}
};
for (unsigned i = 0; i < _countof(xlat); i++) {
if (xlat[i][0] == err_number)
return (xlat[i][1]);
}
return (EX_OSERR);
}
/* OPTIONS tables */
/* careful: these 'names' arrays must be terminated by a null pointer. */
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);
}

View File

@ -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

View File

@ -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

View File

@ -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