diff --git a/.gitea/issue_template/bug.md b/.gitea/issue_template/bug.md new file mode 100644 index 0000000..7257e02 --- /dev/null +++ b/.gitea/issue_template/bug.md @@ -0,0 +1,21 @@ +--- + +name: "Bug Report" +about: "Something is not working as expected, file a bug report." +title: "" +labels: +- bug +- "help needed" + +--- + +# What do you want to achieve? + +# What is the result + +# Further information + +# System information +- Rust version: +- Operating system: +- Contents of all files in `/proc/sys/fs/mqueue`: diff --git a/.gitea/issue_template/enhancement.md b/.gitea/issue_template/enhancement.md new file mode 100644 index 0000000..551a59d --- /dev/null +++ b/.gitea/issue_template/enhancement.md @@ -0,0 +1,16 @@ +--- + +name: "Enhancement" +about: "Something could be improved or missing a feature?" +title: "" +labels: +- enhancement +- "help needed" + +--- + +# What do you want to achieve? + +# Example of the enhancement + +# Further information diff --git a/Cargo.lock b/Cargo.lock index a76a2d3..dc36e4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,10 +1,21 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" -version = "1.0.41" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" +checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" [[package]] name = "atty" @@ -30,10 +41,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] -name = "clap" -version = "3.0.0-beta.2" +name = "cc" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "clap" +version = "3.0.0-beta.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63" dependencies = [ "atty", "bitflags", @@ -44,15 +80,14 @@ dependencies = [ "strsim", "termcolor", "textwrap", - "unicode-width", - "vec_map", + "unicase", ] [[package]] name = "clap_derive" -version = "3.0.0-beta.2" +version = "3.0.0-beta.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1" +checksum = "8b15c6b4f786ffb6192ffe65a36855bc1fc2444bcd0945ae16748dcd6ed7d0d3" dependencies = [ "heck", "proc-macro-error", @@ -62,10 +97,23 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.9.1" +name = "env_logger" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" [[package]] name = "heck" @@ -78,18 +126,24 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] -name = "indexmap" -version = "1.6.2" +name = "humantime" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" dependencies = [ "autocfg", "hashbrown", @@ -103,24 +157,88 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.97" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" +checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] [[package]] name = "mqrs" -version = "0.1.0-dev" +version = "0.1.1" dependencies = [ "anyhow", + "chrono", "clap", + "env_logger", + "humantime", + "log", "posixmq", + "sysvmq", +] + +[[package]] +name = "nix" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77d9f3521ea8e0641a153b3cddaf008dcbf26acd4ed739a2517295e0760d12c7" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", ] [[package]] name = "os_str_bytes" -version = "2.4.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" +checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799" +dependencies = [ + "memchr", +] [[package]] name = "posixmq" @@ -157,22 +275,39 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.27" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + [[package]] name = "strsim" version = "0.10.0" @@ -181,15 +316,24 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.73" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] +[[package]] +name = "sysvmq" +version = "0.1.0" +dependencies = [ + "libc", + "nix", + "thiserror", +] + [[package]] name = "termcolor" version = "1.1.2" @@ -201,24 +345,64 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.12.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" dependencies = [ "unicode-width", ] [[package]] -name = "unicode-segmentation" -version = "1.7.1" +name = "thiserror" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" [[package]] name = "unicode-width" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" @@ -226,18 +410,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 4222fbc..f8005b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,32 @@ [package] name = "mqrs" -version = "0.1.0-dev" +version = "0.1.1" authors = ["finga "] -edition = "2018" +edition = "2021" +repository = "https://git.onders.org/finga/mqrs" license = "GPL-3.0-or-later" readme = "README.md" -description = "A CLI program for interacting Posix Message Queues" +description = "A CLI program for interacting with different kinds of message queues." +keywords = ["message_queue", "mq", "mqueue", "queue"] +categories = ["command-line-utilities"] [dependencies] anyhow = "1.0" clap = "3.0.0-beta.2" posixmq = "1.0" +chrono = "0.4" +humantime = "2.1" +log = "0.4" +env_logger = "0.8" +sysvmq = { path = "sysvmq" } + +[workspace] +members = ["sysvmq"] + +[package.metadata.deb] +extended-description = "`mqrs` is a small cli application to handle different kinds of message queues." +assets = [ + ["target/release/mqrs", "usr/bin/", "755"], + ["README.md", "usr/share/doc/cargo-deb/README.md", "644"], + ["mqrs.1", "/usr/share/man/man1/mqrs.1", "644"], +] diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..7b638ed --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,49 @@ +# Installation +To build `mqrs` having the rust toolchain installed is mandatory. + +## Install Rust +Install the Rust toolchain from [rustup.rs](https://rustup.rs). + +## Build `mqrs` +`mqrs` can be built for development: +```sh +cargo b +``` + +or for releasing: +```sh +cargo b --release +``` + +## Build the `mqrs` Debian package +For that [`cargo-deb`](https://github.com/mmstick/cargo-deb) is +required, which can be installed with: +```sh +cargo install cargo-deb +``` + +A Debian package can be built with: +```sh +cargo deb +``` + +## Install `mqrs` +When a Rust toolchain installed you can also install `mqrs` +directly without cloning it manually: +``` sh +cargo install --git https://git.onders.org/finga/mqrs.git mqrs +``` + +or from within the project: +```sh +cargo install mqrs +``` + +## Run `mqrs` +`mqrs` can either be run from the project directory with: +```sh +cargo b +``` + +or you can copy the produced binary somewhere else or link to them +from `target/{debug,release}/mqrs` depending on which one you built. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9e73663 --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# mqrs +`mqrs` is a small cli application to handle different kinds of message +queues. + +## Install `mqrs` +For information about how to build, install and run `mqrs` please see +[`INSTALL.md`](INSTALL.md). + +## Using `mqrs` +Depending on which backend you want to use there are different subsets +of subcommands. Following backends are supported: +- `posix`: Use POSIX message queues +- `sysv`: Use SysV IPC message queues + +If a command is clearly distinguishable from all the others, +it does not have to be completed further. + +### POSIX message queues +The POSIX backend supports six commands: `create`, `info`, `list`, +`unlink`, `send` and `recv`. + +#### Create a message queue +Use the `create` command to create a new POSIX message queue. Following +optional arguments are supported: +- `-c`, `--capacity`: Maximum number of messages in the queue +- `-m`, `--mode`: Permissions (octal) to create the queue with +- `-s`, `--msgsize`: Message size in bytes + +#### Print information about a message queue +Use the `info` command to print further information about a message +queue. + +#### List all message queues +Use the `list` command to print a list of all message +queues. Following option argument is supported: +- `-a`, `--all`: Print all available information + +#### Delete a message queue +Use the `unlink` command to delete a message queue. + +#### Send a message to a queue +Use the `send` command to send a message to a message queue. Following +optional arguments are supported: +- `-n`, `--non-blocking`: Do not block +- `-d`, `--deadline `: Deadline until messages are sent + (format: `%Y-%m-%d %H:%M:%S`) +- `-p`, `--priority `: Set a different priority than + default, priority >= 0 [default: 0] +- `-o`, `--timeout `: As for example in "5h 23min 42ms" + +#### Receive a message from a queue +Use the `recv` command to receive one or more messages from a message +queue. Following optional arguments are supported: +- `-f`, `--follow`: Print messages as they are received +- `-n`, `--non-blocking`: Do not block +- `-t`, `--timestamp`: Print a timestamp before each message +- `-d`, `--deadline `: Deadline until messages are received + (format: `%Y-%m-%d %H:%M:%S`) +- `-o,` `--timeout `: As for example in "5h 23min 42ms" + +### SysV IPC message queues +The SysV IPC backend supports two commands: `create` and `unlink`. + +#### Create a message queue +Use the `create` command to create a new SysV IPC message +queue. Following optional arguments are supported: +- `-m`, `--mode`: Permissions (octal) to create the queue + with. Default: 0644. + +#### Print information about a message queue +Use the `info` command to print further information about a message +queue. Exactly of the following arguments is mandatory: +- `-i`, `--id id`: Id of the queue +- `-k`, `--key key`: Key of the queue + +#### List all message queues +Use the `list` command to print a list of all message queues. No +further arguments are supported. + +#### Delete a message queue +Use the `unlink` command to delete a message queue. This can either be +done by providing a `key` or an `id` of the queue: +- `-i`, `--id `: Id of the queue +- `-k`, `--key `: Key of the queue diff --git a/mqrs.1 b/mqrs.1 new file mode 100644 index 0000000..ce48a87 --- /dev/null +++ b/mqrs.1 @@ -0,0 +1,326 @@ +.\" Manpage for mqrs +.TH man 1 "7 July 2021" "0.1.1" "mqrs man page" +.SH NAME +mqrs \- Handle POSIX message queues +.SH SYNOPSIS +.B mqrs [\-h] [\-\-help] [\-v] [\-\-verbose] [\-V] [\-\-version] [SUBCOMMAND] +.SH DESCRIPTION +.B mqrs +is a small cli program to handle message queues. Depending on which +backend you want to use there are different subsets of +subcommands. Following backends are supported: +.B posix\ +and +.B sysv\ +. + +If a command is clearly distinguishable from all the others, it does +not have to be completed further. +.SH OPTIONS +.TP 8 +.B \-h, \-\-help +Prints help information +.TP 8 +.B \-v, \-\-verbose +Produce verbose output, multiple -v options increase the verbosity +(max. 3) +.TP 8 +.B \-V, \-\-version +Prints version information +.SH POSIX MESSAGE QUEUE SUBCOMMANDS +The POSIX backend supports six commands: +.B create\ +, +.B info\ +, +.B list\ +, +.B unlink\ +, +.B send +and +.B recv +. +.SS posix create [FLAGS] [OPTIONS] \fI\fP +Create a new POSIX message queue. +.TP 8 +.SS ARGS +.RS +.TP 8 +.B \fI\fP +Name of the new queue +.RE +.TP 8 +.SS FLAGS +.RS +.TP 8 +.B \-h, \-\-help +Prints help information +.TP 8 +.B \-v, \-\-verbose +Produce verbose output +.TP 8 +.SS OPTIONS +.RS +.TP 8 +.B \-c, \-\-capacity \fI\fP +Maximum number of messages in the queue +.TP 8 +.B \-s, \-\-msgsize \fI\fP +Message size in bytes +.TP 8 +.B \-m, \-\-mode \fI\fP +Permissions (octal) to create the queue with +.RE +.SS posix help [SUBCOMMAND] +Prints this message or the help of the given subcommand. +.TP 8 +.SS ARGS +.RS +.TP 8 +.B \fI\fP +Show help for \fISUBCOMMAND\fP +.RE +.SS posix info [FLAGS] \fI\fP +Print further information about an existing message queue. +.TP 8 +.SS ARGS +.RS +.TP 8 +.B \fI\fP +Name of the queue +.RE +.TP 8 +.SS FLAGS +.RS +.TP 8 +.B \-h, \-\-help +Prints help information +.TP 8 +.B \-v, \-\-verbose +Produce verbose output +.RE +.SS posix list [FLAGS] +Print a list of all existing POSIX message queues. +.TP 8 +.SS FLAGS +.RS +.TP 8 +.B \-h, \-\-help +Prints help information +.TP 8 +.B \-v, \-\-verbose +Produce verbose output +.TP 8 +.B \-a, \-\-all +Print all available information +.RE +.SS posix recv [FLAGS] [OPTIONS] \fI\fP +Receive and print one or more messages message from a message queue. +.TP 8 +.SS ARGS +.RS +.TP 8 +.B \fI\fP +Name of the queue +.RE +.TP 8 +.SS FLAGS +.RS +.TP 8 +.B \-f, \-\-follow +Print messages as they are received +.TP 8 +.B \-h, \-\-help +Prints help information +.TP 8 +.B \-n, \-\-non\-blocking +Do not block +.TP 8 +.B \-t, \-\-timestamp +Print a timestamp before each message +.TP 8 +.B \-v, \-\-verbose +Produce verbose output +.RE +.TP 8 +.SS OPTIONS +.RS +.TP 8 +.B \-d, \-\-deadline \fI\fP +Deadline until messages are received (format: "%Y-%m-%d %H:%M:%S") +.TP 8 +.B \-o, \-\-timeout \fI\fP +Timeout as for example in "5h 23min 42ms" +.RE +.SS posix send [FLAGS] [OPTIONS] \fI\fP \fI\fP +Send a message to a message queue. +.TP 8 +.SS ARGS +.RS +.TP 8 +.B \fI\fP +Name of the queue +.TP 8 +.B \fI\fP +Message to be sent to the queue +.RE +.TP 8 +.SS FLAGS +.RS +.TP 8 +.B \-h, \-\-help +Prints help information +.TP 8 +.B \-n, \-\-non\-blocking +Do not block +.TP 8 +.B \-v, \-\-verbose +Produce verbose output +.RE +.TP 8 +.SS OPTIONS +.RS +.TP 8 +.B \-d, \-\-deadline \fI\fP +Deadline until messages are received (format: "%Y-%m-%d %H:%M:%S") +.TP 8 +.B \-p, \-\-priority \fI\fP +Set a different priority than default, priority >= 0 [default: 0] +.TP 8 +.B \-o, \-\-timeout \fI\fP +Timeout as for example in "5h 23min 42ms" +.RE +.SS posix unlink [FLAGS] \fI\fP +Delete an existing POSIX message queue. +.TP 8 +.SS ARGS +.RS +.TP 8 +.B \fI\fP +Name of the queue +.RE +.TP 8 +.SS FLAGS +.RS +.TP 8 +.B \-h, \-\-help +Prints help information +.TP 8 +.B \-v, \-\-verbose +Produce verbose output +.RE +.SH SYSV IPC MESSAGE QUEUE SUBCOMMANDS +The SysV IPC backend supports four commands: +.B create\ +, +.B info\ +, +.B list +and +.B unlink\ +. +.SS sysv create [FLAGS] [OPTIONS] \fI\fP +Create a new SysV IPC message queue. +.TP 8 +.SS ARGS +.RS +.TP 8 +.B \fI\fP +Key of the new queue +.RE +.TP 8 +.SS FLAGS +.RS +.TP 8 +.B \-h, \-\-help +Prints help information +.TP 8 +.B \-v, \-\-verbose +Produce verbose output +.TP 8 +.SS OPTIONS +.RS +.TP 8 +.B \-m, \-\-mode \fI\fP +Permissions (octal) to create the queue with (default: 0644) +.RE +.SS sysv help [SUBCOMMAND] +Prints this message or the help of the given subcommand. +.TP 8 +.SS ARGS +.RS +.TP 8 +.B \fI\fP +Show help for \fISUBCOMMAND\fP +.RE +.SS sysv info [FLAGS] [OPTIONS] +Print further information about an existing message queue. Exactly of +the OPTION arguments is mandatory. +.TP 8 +.SS FLAGS +.RS +.TP 8 +.B \-h, \-\-help +Prints help information +.TP 8 +.B \-v, \-\-verbose +Produce verbose output +.RE +.TP 8 +.SS OPTIONS +.RS +.TP 8 +.B \-i, \-\-id \fI\fP +Id of the queue +.TP 8 +.B \-k, \-\-key \fI\fP +Key of the queue +.RE +.SS sysv list [FLAGS] +Print a list of all existing SysV IPC message queues. +.TP 8 +.SS FLAGS +.RS +.TP 8 +.B \-h, \-\-help +Prints help information +.TP 8 +.B \-v, \-\-verbose +Produce verbose output +.RE +.SS sysv unlink [FLAGS] [OPTIONS] +Delete an existing SysV IPC message queue. It is mandatory to pass +exactly one OPTION. +.TP 8 +.SS ARGS +.RS +.TP 8 +.B \fI\fP +Key of the new queue +.RE +.TP 8 +.SS FLAGS +.RS +.TP 8 +.B \-h, \-\-help +Prints help information +.TP 8 +.B \-v, \-\-verbose +Produce verbose output +.TP 8 +.SS OPTIONS +.RS +.TP 8 +.B \-i, \-\-id \fI\fP +Id of the queue +.TP 8 +.B \-k, \-\-key \fI\fP +Key of the queue +.RE +.SH SEE ALSO +mq_overview(7), sysvipc(7) +.SH BUGS +No known bugs. +.SH AUTHOR +finga (mqrs@onders.org) diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index 8568937..0000000 --- a/src/cli.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::{create::Create, info::Info, send::Send, unlink::Unlink}; -use clap::{crate_authors, crate_version, AppSettings, Clap}; - -#[derive(Clap, Debug)] -#[clap( - version = crate_version!(), - author = crate_authors!(", "), - setting = AppSettings::SubcommandRequiredElseHelp, - global_setting = AppSettings::VersionlessSubcommands, - global_setting = AppSettings::InferSubcommands, -)] -pub struct Opts { - /// Produce verbose output - #[clap(short, long, global = true)] - pub verbose: bool, - #[clap(subcommand)] - pub command: Command, -} - -#[derive(Clap, Debug)] -pub enum Command { - Create(Create), - Info(Info), - Unlink(Unlink), - Send(Send), -} diff --git a/src/main.rs b/src/main.rs index e361ffe..c4b0787 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,82 @@ use anyhow::Result; -use clap::Clap; +use clap::{crate_authors, crate_version, AppSettings, Parser}; -mod cli; -mod create; -mod info; -mod send; -mod unlink; +mod posix; +mod sysv; -use cli::{Command, Opts}; +#[derive(Debug, Parser)] +enum Backend { + /// Handle POSIX message queues + #[clap(subcommand)] + Posix(PosixCommand), + /// Handle SysV message queues + #[clap(subcommand)] + Sysv(SysvCommand), +} + +#[derive(Debug, Parser)] +enum PosixCommand { + Create(posix::Create), + Info(posix::Info), + List(posix::List), + Unlink(posix::Unlink), + Send(posix::Send), + Recv(posix::Recv), +} + +#[derive(Debug, Parser)] +enum SysvCommand { + Create(sysv::Create), + Info(sysv::Info), + List(sysv::List), + Unlink(sysv::Unlink), +} + +#[derive(Debug, Parser)] +#[clap( + version = crate_version!(), + author = crate_authors!(", "), + setting = AppSettings::SubcommandRequiredElseHelp, + global_setting = AppSettings::PropagateVersion, + global_setting = AppSettings::InferSubcommands, +)] +struct Opts { + /// Produce verbose output, multiple -v options increase the verbosity (max. 3) + #[clap(short, long, global = true, parse(from_occurrences))] + verbose: u32, + /// Backend to be used + #[clap(subcommand)] + backend: Backend, +} fn main() -> Result<()> { let opts: Opts = Opts::parse(); - match opts.command { - Command::Create(c) => c.run(opts.verbose)?, - Command::Info(i) => i.run()?, - Command::Unlink(u) => u.run(opts.verbose)?, - Command::Send(s) => s.run(opts.verbose)?, + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or( + match opts.verbose { + 0 => "warn", + 1 => "info", + 2 => "debug", + _ => "trace", + }, + )) + .init(); + + match opts.backend { + Backend::Posix(p) => match p { + PosixCommand::Create(c) => c.run()?, + PosixCommand::Info(i) => i.run()?, + PosixCommand::List(l) => l.run()?, + PosixCommand::Unlink(u) => u.run()?, + PosixCommand::Send(s) => s.run()?, + PosixCommand::Recv(r) => r.run()?, + }, + Backend::Sysv(s) => match s { + SysvCommand::Create(c) => c.run()?, + SysvCommand::Info(i) => i.run()?, + SysvCommand::List(l) => l.run()?, + SysvCommand::Unlink(u) => u.run()?, + }, } Ok(()) diff --git a/src/create.rs b/src/posix/create.rs similarity index 60% rename from src/create.rs rename to src/posix/create.rs index 26652c3..cceabde 100644 --- a/src/create.rs +++ b/src/posix/create.rs @@ -1,13 +1,14 @@ use anyhow::Result; -use clap::Clap; +use clap::Parser; +use log::{info, log_enabled, Level::Info}; use posixmq::PosixMq; use std::fs; /// Create a POSIX message queue -#[derive(Clap, Debug)] +#[derive(Debug, Parser)] pub struct Create { /// Permissions (octal) to create the queue with - #[clap(short = 'p', long)] + #[clap(short, long)] mode: Option, /// Maximum number of messages in the queue #[clap(short, long)] @@ -15,31 +16,31 @@ pub struct Create { /// Message size in bytes #[clap(short = 's', long)] msgsize: Option, - /// Name of the queue - #[clap(value_name = "QNAME")] + /// Name of the new queue + #[clap(value_name = "QUEUE")] queue: String, } fn msgsize_default() -> usize { match fs::read_to_string("/proc/sys/fs/mqueue/msgsize_default") { - Ok(m) => m.trim().parse::().unwrap(), // should never fail + Ok(m) => m.trim().parse::().expect("can never fail"), _ => 8192, } } fn msg_default() -> usize { match fs::read_to_string("/proc/sys/fs/mqueue/msg_default") { - Ok(m) => m.trim().parse::().unwrap(), // should never fail + Ok(m) => m.trim().parse::().expect("can never fail"), _ => 10, } } impl Create { - pub fn run(&self, verbose: bool) -> Result<()> { + pub fn run(&self) -> Result<()> { let mq = &mut posixmq::OpenOptions::readonly(); if let Some(m) = &self.mode { - mq.mode(u32::from_str_radix(&m, 8)?); + mq.mode(u32::from_str_radix(m, 8)?); } mq.max_msg_len(self.msgsize.unwrap_or_else(msgsize_default)) @@ -47,15 +48,15 @@ impl Create { .create_new() .open(&self.queue)?; - if verbose { + if log_enabled!(Info) { let mq = PosixMq::open(&self.queue)?; let attributes = mq.attributes()?; - println!("Created message queue: {} with attributes msgsize: {}, capacity: {}, current_messages: {}", - &self.queue, - &attributes.max_msg_len, - &attributes.capacity, - &attributes.current_messages); + info!("Created message queue: {} with attributes msgsize: {}, capacity: {}, current_messages: {}", + self.queue, + attributes.max_msg_len, + attributes.capacity, + attributes.current_messages); } Ok(()) diff --git a/src/info.rs b/src/posix/info.rs similarity index 68% rename from src/info.rs rename to src/posix/info.rs index e2be5ba..2a95f6b 100644 --- a/src/info.rs +++ b/src/posix/info.rs @@ -1,13 +1,13 @@ use anyhow::Result; -use clap::Clap; +use clap::Parser; use posixmq::PosixMq; /// Print information about an existing message queue -#[derive(Clap, Debug)] +#[derive(Debug, Parser)] pub struct Info { /// Name of the queue - #[clap(value_name = "QNAME")] - pub queue: String, + #[clap(value_name = "QUEUE")] + queue: String, } impl Info { @@ -16,7 +16,7 @@ impl Info { println!( "Message queue: {}, msg_max: {}, msgsize_max: {}, current_messages: {}", - &self.queue, &attrs.capacity, &attrs.max_msg_len, &attrs.current_messages + self.queue, attrs.capacity, attrs.max_msg_len, attrs.current_messages ); Ok(()) diff --git a/src/posix/list.rs b/src/posix/list.rs new file mode 100644 index 0000000..57fa45d --- /dev/null +++ b/src/posix/list.rs @@ -0,0 +1,58 @@ +use anyhow::{anyhow, Result}; +use chrono::{DateTime, Local}; +use clap::Parser; +use log::warn; +use std::{fs, os::unix::fs::PermissionsExt}; + +/// Print a list of existing message queues +#[derive(Debug, Parser)] +pub struct List { + /// Show all parameters + #[clap(short, long)] + all: bool, +} + +impl List { + pub fn run(&self) -> Result<()> { + match self.all { + false => println!("Name"), + true => println!( + "{0: <10} {1: <10} {2: <12} {3: <26} {4: <26}", + "Name", "Size", "Permissions", "Modified", "Accessed", + ), + } + + for mq in fs::read_dir("/dev/mqueue")? { + match mq { + Ok(mq) => { + print!( + "/{0: <10}", + mq.file_name().into_string().map_err(|e| anyhow!( + "Could not convert queue name to string: {:?}", + e + ))? + ); + + if self.all { + let metadata = mq.metadata()?; + let modified: DateTime = metadata.modified()?.into(); + let accessed: DateTime = metadata.accessed()?.into(); + + print!( + "{0: <10} {1: <12o} {2: <26} {3: <26}", + metadata.len(), + metadata.permissions().mode(), + modified, + accessed, + ); + } + + println!(); + } + Err(e) => warn!("Could not read file: {:?}", e), + } + } + + Ok(()) + } +} diff --git a/src/posix/mod.rs b/src/posix/mod.rs new file mode 100644 index 0000000..c5855c8 --- /dev/null +++ b/src/posix/mod.rs @@ -0,0 +1,13 @@ +mod create; +mod info; +mod list; +mod recv; +mod send; +mod unlink; + +pub use create::Create; +pub use info::Info; +pub use list::List; +pub use recv::Recv; +pub use send::Send; +pub use unlink::Unlink; diff --git a/src/posix/recv.rs b/src/posix/recv.rs new file mode 100644 index 0000000..c3abb66 --- /dev/null +++ b/src/posix/recv.rs @@ -0,0 +1,85 @@ +use anyhow::Result; +use chrono::{DateTime, Local}; +use clap::Parser; +use humantime::Duration; +use log::info; +use posixmq::PosixMq; +use std::str; + +/// Receive and print a message from a message queue +#[derive(Debug, Parser)] +pub struct Recv { + /// Do not block + #[clap(short, long)] + non_blocking: bool, + /// Print messages as they are received + #[clap(short, long)] + follow: bool, + /// Print a timestamp before each message + #[clap(short, long)] + timestamp: bool, + /// Timeout, example "5h 23min 42ms" + #[clap(short = 'o', long, conflicts_with = "deadline")] + timeout: Option, + /// Deadline until messages are received (format: "%Y-%m-%d %H:%M:%S") + #[clap(short, long, conflicts_with = "timeout")] + deadline: Option, + /// Name of the queue + #[clap(value_name = "QUEUE")] + queue: String, +} + +fn print_message(priority: u32, length: usize, timestamp: bool, msg: &str) { + info!("Priority: {}, length: {}", priority, length); + + if timestamp { + println!("{}", Local::now()); + } + + println!("{}", msg); +} + +impl Recv { + fn receive(&self, mq: &PosixMq) -> Result<()> { + let mut buf = vec![0; mq.attributes()?.max_msg_len]; + + if let Some(timeout) = &self.timeout { + let (prio, len) = mq.recv_timeout(&mut buf, *timeout.parse::()?)?; + + print_message(prio, len, self.timestamp, str::from_utf8(&buf)?); + } else if let Some(deadline) = &self.deadline { + let (prio, len) = mq.recv_deadline( + &mut buf, + DateTime::parse_from_str(deadline, "%Y-%m-%d %H:%M:%S")?.into(), + )?; + + print_message(prio, len, self.timestamp, str::from_utf8(&buf)?); + } else { + let (prio, len) = mq.recv(&mut buf)?; + + print_message(prio, len, self.timestamp, str::from_utf8(&buf)?); + } + + Ok(()) + } + + pub fn run(&self) -> Result<()> { + let mq = &mut posixmq::OpenOptions::readonly(); + + if self.non_blocking { + mq.nonblocking(); + } + + let mq = mq.open(&self.queue)?; + + if self.follow { + loop { + self.receive(&mq)?; + } + } else { + self.receive(&mq)?; + } + + Ok(()) + } +} diff --git a/src/posix/send.rs b/src/posix/send.rs new file mode 100644 index 0000000..6dd70de --- /dev/null +++ b/src/posix/send.rs @@ -0,0 +1,63 @@ +use anyhow::Result; +use chrono::DateTime; +use clap::Parser; +use humantime::Duration; +use log::info; + +/// Send a message to a message queue +#[derive(Debug, Parser)] +pub struct Send { + /// Set a different priority, priority >= 0 + #[clap(short, long, default_value = "0")] + priority: u32, + /// Do not block + #[clap(short, long)] + non_blocking: bool, + /// Timeout, example "5h 23min 42ms" + #[clap(short = 'o', long, conflicts_with = "deadline")] + timeout: Option, + /// Deadline until messages are sent (format: "%Y-%m-%d %H:%M:%S") + #[clap(short, long, conflicts_with = "timeout")] + deadline: Option, + /// Name of the queue + #[clap(value_name = "QUEUE")] + queue: String, + /// Message to be sent to the queue + #[clap(value_name = "MESSAGE")] + msg: String, +} + +impl Send { + pub fn run(&self) -> Result<()> { + let mq = &mut posixmq::OpenOptions::writeonly(); + + if self.non_blocking { + mq.nonblocking(); + } + + if let Some(timeout) = &self.timeout { + mq.open(&self.queue)?.send_timeout( + self.priority, + self.msg.as_bytes(), + *timeout.parse::()?, + )?; + + info!("Sent message: \"{}\" to queue: {}", self.msg, self.queue); + } else if let Some(deadline) = &self.deadline { + mq.open(&self.queue)?.send_deadline( + self.priority, + self.msg.as_bytes(), + DateTime::parse_from_str(deadline, "%Y-%m-%d %H:%M:%S")?.into(), + )?; + + info!("Sent message: \"{}\" to queue: {}", self.msg, self.queue); + } else { + mq.open(&self.queue)? + .send(self.priority, self.msg.as_bytes())?; + + info!("Sent message: \"{}\" to queue: {}", self.msg, self.queue); + } + + Ok(()) + } +} diff --git a/src/posix/unlink.rs b/src/posix/unlink.rs new file mode 100644 index 0000000..75a8bd2 --- /dev/null +++ b/src/posix/unlink.rs @@ -0,0 +1,21 @@ +use anyhow::Result; +use clap::Parser; +use log::info; + +/// Delete a message queue +#[derive(Debug, Parser)] +pub struct Unlink { + /// Name of the queue + #[clap(value_name = "QUEUE")] + pub queue: String, +} + +impl Unlink { + pub fn run(&self) -> Result<()> { + posixmq::remove_queue(&self.queue)?; + + info!("Removed message queue: {}", self.queue); + + Ok(()) + } +} diff --git a/src/send.rs b/src/send.rs deleted file mode 100644 index 7b8a6e8..0000000 --- a/src/send.rs +++ /dev/null @@ -1,38 +0,0 @@ -use anyhow::Result; -use clap::Clap; - -/// Send a message to a message queue -#[derive(Clap, Debug)] -pub struct Send { - /// Use priority PRIO, PRIO >= 0 - #[clap(short, long, default_value = "0")] - pub priority: u32, - /// Do not block - #[clap(short, long)] - pub non_blocking: bool, - /// Name of the queue - #[clap(value_name = "QNAME")] - pub queue: String, - /// Message to be sent to the queue - #[clap(value_name = "MESSAGE")] - pub msg: String, -} - -impl Send { - pub fn run(&self, verbose: bool) -> Result<()> { - let mq = &mut posixmq::OpenOptions::writeonly(); - - if self.non_blocking { - mq.nonblocking(); - } - - mq.open(&self.queue)? - .send(self.priority, &self.msg.as_bytes())?; - - if verbose { - println!("Sent message: \"{}\" to queue: {}", &self.msg, &self.queue); - } - - Ok(()) - } -} diff --git a/src/sysv/create.rs b/src/sysv/create.rs new file mode 100644 index 0000000..4adad77 --- /dev/null +++ b/src/sysv/create.rs @@ -0,0 +1,31 @@ +use anyhow::Result; +use clap::Parser; +use log::info; +use sysvmq::SysvMq; + +/// Create a SysV message queue +#[derive(Debug, Parser)] +pub struct Create { + /// Permissions (octal) to create the queue with (default: 0644) + #[clap(short, long)] + mode: Option, + /// Key of the new queue + #[clap(value_name = "KEY")] + key: i32, +} + +impl Create { + pub fn run(&self) -> Result<()> { + let mut mq = SysvMq::::new(); + + if let Some(m) = &self.mode { + mq.mode(i32::from_str_radix(m, 8)?); + } + + mq.create(self.key)?; + + info!("SysV message queue created, key: {}, id: {}", mq.key, mq.id); + + Ok(()) + } +} diff --git a/src/sysv/info.rs b/src/sysv/info.rs new file mode 100644 index 0000000..77d4b3e --- /dev/null +++ b/src/sysv/info.rs @@ -0,0 +1,53 @@ +use anyhow::Result; +use clap::Parser; +use std::{ + fs::File, + io::{BufRead, BufReader}, +}; + +/// Print information about an existing message queue +#[derive(Debug, Parser)] +pub struct Info { + /// Id of the queue + #[clap(short, long, required_unless_present_any = &["key"], conflicts_with = "key")] + id: Option, + /// Key of the queue + #[clap(short, long, required_unless_present_any = &["id"], conflicts_with = "id")] + key: Option, +} + +fn print_line(line: &str) { + for field in line.split_whitespace().collect::>() { + print!("{0: <10}", field); + } + + println!(); +} + +impl Info { + pub fn run(&self) -> Result<()> { + let mut lines = BufReader::new(File::open("/proc/sysvipc/msg")?).lines(); + + print_line(&lines.next().unwrap_or_else(|| Ok(String::new()))?); + + for line in lines { + let line = line?; + + if let Some(id) = self.id { + if id == line.split_whitespace().collect::>()[1].parse::()? { + print_line(&line); + + break; + } + } else if let Some(key) = self.key { + if key == line.split_whitespace().collect::>()[0].parse::()? { + print_line(&line); + + break; + } + } + } + + Ok(()) + } +} diff --git a/src/sysv/list.rs b/src/sysv/list.rs new file mode 100644 index 0000000..7c12f16 --- /dev/null +++ b/src/sysv/list.rs @@ -0,0 +1,24 @@ +use anyhow::Result; +use clap::Parser; +use std::{ + fs::File, + io::{BufRead, BufReader}, +}; + +/// Print a list of existing message queues +#[derive(Debug, Parser)] +pub struct List {} + +impl List { + pub fn run(&self) -> Result<()> { + for line in BufReader::new(File::open("/proc/sysvipc/msg")?).lines() { + for field in line?.split_whitespace().collect::>() { + print!("{0: <10}", field); + } + + println!(); + } + + Ok(()) + } +} diff --git a/src/sysv/mod.rs b/src/sysv/mod.rs new file mode 100644 index 0000000..7270e78 --- /dev/null +++ b/src/sysv/mod.rs @@ -0,0 +1,9 @@ +mod create; +mod info; +mod list; +mod unlink; + +pub use create::Create; +pub use info::Info; +pub use list::List; +pub use unlink::Unlink; diff --git a/src/sysv/unlink.rs b/src/sysv/unlink.rs new file mode 100644 index 0000000..9b4b998 --- /dev/null +++ b/src/sysv/unlink.rs @@ -0,0 +1,37 @@ +use anyhow::Result; +use clap::Parser; +use log::info; + +/// Delete a message queue +#[derive(Debug, Parser)] +pub struct Unlink { + /// Id of the queue + #[clap( + short, + long, + required_unless_present_any = &["key"], + conflicts_with = "key" + )] + id: Option, + /// Key of the queue + #[clap(short, long, required_unless_present_any = &["id"], conflicts_with = "id")] + key: Option, +} + +impl Unlink { + pub fn run(&self) -> Result<()> { + if let Some(id) = self.id { + sysvmq::unlink_id(id)?; + + info!("Removed message queue with id: {}", id); + } else if let Some(key) = self.key { + let id = sysvmq::id_from_key(key)?; + + sysvmq::unlink_id(id)?; + + info!("Removed message queue key: {} (id: {})", key, id); + } + + Ok(()) + } +} diff --git a/src/unlink.rs b/src/unlink.rs deleted file mode 100644 index 71eaac4..0000000 --- a/src/unlink.rs +++ /dev/null @@ -1,22 +0,0 @@ -use anyhow::Result; -use clap::Clap; - -/// Delete a message queue -#[derive(Clap, Debug)] -pub struct Unlink { - /// Name of the queue - #[clap(value_name = "QNAME")] - pub queue: String, -} - -impl Unlink { - pub fn run(&self, verbose: bool) -> Result<()> { - posixmq::remove_queue(&self.queue)?; - - if verbose { - println!("Removed message queue: {}", &self.queue); - } - - Ok(()) - } -} diff --git a/sysvmq/.gitignore b/sysvmq/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/sysvmq/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/sysvmq/Cargo.toml b/sysvmq/Cargo.toml new file mode 100644 index 0000000..a2cee0f --- /dev/null +++ b/sysvmq/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "sysvmq" +version = "0.1.0" +edition = "2018" +authors = ["finga "] +repository = "https://git.onders.org/finga/mqrs" +license = "GPL-3.0-or-later" + +[dependencies] +libc = "0.2.98" +thiserror = "1.0.26" +nix = "0.21.0" diff --git a/sysvmq/src/lib.rs b/sysvmq/src/lib.rs new file mode 100644 index 0000000..298a48e --- /dev/null +++ b/sysvmq/src/lib.rs @@ -0,0 +1,179 @@ +use libc::{ + msgctl, msgget, msginfo, msqid_ds, IPC_CREAT, IPC_EXCL, IPC_INFO, IPC_NOWAIT, IPC_PRIVATE, + IPC_RMID, IPC_SET, IPC_STAT, MSG_COPY, MSG_EXCEPT, MSG_INFO, MSG_NOERROR, MSG_STAT, +}; +use nix::errno::{errno, Errno}; +use std::{marker::PhantomData, mem::MaybeUninit, ptr}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum SysvMqError { + #[error("SysV message queue: {0}")] + ErrnoError(&'static str), +} + +/// IPC bit flags +#[repr(i32)] +pub enum Flags { + /// Create key if key does not exist. + CreateKey = IPC_CREAT, + /// Fail if key exists. + Exclusive = IPC_EXCL, + /// Return error on wait. + NoWait = IPC_NOWAIT, + /// No error if message is too big. + NoError = MSG_NOERROR, + /// Receive any message except of specified type. + Except = MSG_EXCEPT, + /// Copy (not remove) all queue messages. + Copy = MSG_COPY, + /// Private key (Special key value). + Private = IPC_PRIVATE, +} + +/// Commands for `msgctl()` +#[repr(i32)] +pub enum ControlCommands { + /// Remove identifier (Control command for `msgctl`, `semctl`, and `shmctl`). + Remove = IPC_RMID, + /// Set `ipc_perm` options (Control command for `msgctl`, `semctl`, and `shmctl`). + SetPerm = IPC_SET, + /// Get `ipc_perm` options (Control command for `msgctl`, `semctl`, and `shmctl`). + GetPerm = IPC_STAT, + /// See ipcs (Control command for `msgctl`, `semctl`, and `shmctl`). + IpcInfo = IPC_INFO, + /// IPCS control command. + Stat = MSG_STAT, + /// IPCS control command. + MsgInfo = MSG_INFO, +} + +pub fn unlink_id(id: i32) -> Result<(), SysvMqError> { + let res = unsafe { + msgctl( + id, + ControlCommands::Remove as i32, + ptr::null::() as *mut msqid_ds, + ) + }; + + match res { + -1 => Err(SysvMqError::ErrnoError(Errno::from_i32(errno()).desc())), + _ => Ok(()), + } +} + +pub fn id_from_key(key: i32) -> Result { + let id = unsafe { msgget(key, 0) }; + + match id { + -1 => Err(SysvMqError::ErrnoError(Errno::from_i32(errno()).desc())), + id => Ok(id), + } +} + +pub fn ipc_info(id: i32) -> Result<(), SysvMqError> { + let mut msginfo = MaybeUninit::::uninit(); + + unsafe { + msgctl( + id, + ControlCommands::IpcInfo as i32, + msginfo.as_mut_ptr() as *mut msqid_ds, + ); + } + + let msginfo = unsafe { msginfo.assume_init() }; + + println!("info: {:?}", msginfo); + + Ok(()) +} + +pub fn stat_info(id: i32) -> Result<(), SysvMqError> { + let mut msginfo = MaybeUninit::::uninit(); + + unsafe { + msgctl(id, ControlCommands::Stat as i32, msginfo.as_mut_ptr()); + } + + let msginfo = unsafe { msginfo.assume_init() }; + + println!("info: {:?}", msginfo); + + Ok(()) +} + +pub fn msg_info(id: i32) -> Result<(), SysvMqError> { + let mut msginfo = MaybeUninit::::uninit(); + + unsafe { + msgctl( + id, + ControlCommands::MsgInfo as i32, + msginfo.as_mut_ptr() as *mut msqid_ds, + ); + } + + let msginfo = unsafe { msginfo.assume_init() }; + + println!("info: {:?}", msginfo); + + Ok(()) +} + +pub struct SysvMq { + pub id: i32, + pub key: i32, + message_mask: i32, + mode: i32, + types: PhantomData, +} + +impl SysvMq { + pub fn create(&mut self, key: i32) -> Result<&Self, SysvMqError> { + self.key = key; + self.id = unsafe { msgget(self.key, Flags::CreateKey as i32 | self.mode) }; + + match self.id { + -1 => Err(SysvMqError::ErrnoError(Errno::from_i32(errno()).desc())), + _ => Ok(self), + } + } + + pub fn open(mut self, key: i32) -> Result { + self.key = key; + self.id = unsafe { msgget(self.key, self.mode) }; + + match self.id { + -1 => Err(SysvMqError::ErrnoError(Errno::from_i32(errno()).desc())), + _ => Ok(self), + } + } + + pub fn mode(&mut self, mode: i32) -> &Self { + self.mode = mode; + self + } + + pub fn nonblocking(&mut self) -> &Self { + self.message_mask |= Flags::NoWait as i32; + self + } + + pub fn new() -> Self { + SysvMq { + id: -1, + key: 0, + message_mask: 0, + mode: 0o644, + types: PhantomData, + } + } +} + +impl Default for SysvMq { + fn default() -> Self { + Self::new() + } +}