🏡 index : ~doyle/pisshoff.git

author Jordan Doyle <jordan@doyle.la> 2023-06-28 21:28:58.0 +00:00:00
committer Jordan Doyle <jordan@doyle.la> 2023-06-28 21:28:58.0 +00:00:00
commit
8147cd7eaed4d557c78302d8590e1cc3812076d8 [patch]
tree
7002e207340d0eaf4d2f849269285772de2fc0ce
parent
abdb93d8b0c1402f4f45a7bd75ea2f84566deb63
download
8147cd7eaed4d557c78302d8590e1cc3812076d8.tar.gz

Be more faithful to uname argument parsing



Diff

 Cargo.lock                                                                                   | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
 pisshoff-server/Cargo.toml                                                                   |   5 ++-
 pisshoff-server/src/command.rs                                                               |  90 +++++++++++++------------------
 pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-a.snap         |   6 ++-
 pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-asnrvmpio.snap |   6 ++-
 pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-sn --fake.snap |   7 ++-
 pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-sn -z.snap     |   7 ++-
 pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-sn oper.snap   |   7 ++-
 pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-sn.snap        |   6 ++-
 pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-snrvmpio.snap  |   6 ++-
 pisshoff-server/src/command/uname.rs                                                         | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 11 files changed, 452 insertions(+), 74 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 56b66b5..b7b9ab3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -66,7 +66,7 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
 "windows-sys",
 "windows-sys 0.48.0",
]

[[package]]
@@ -76,7 +76,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
dependencies = [
 "anstyle",
 "windows-sys",
 "windows-sys 0.48.0",
]

[[package]]
@@ -138,6 +138,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"

[[package]]
name = "bitflags"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"

[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -243,7 +249,7 @@ checksum = "9a78fbdd3cc2914ddf37ba444114bc7765bbdcb55ec9cbe6fa054f0137400717"
dependencies = [
 "anstream",
 "anstyle",
 "bitflags",
 "bitflags 1.3.2",
 "clap_lex",
 "once_cell",
 "strsim",
@@ -274,6 +280,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"

[[package]]
name = "console"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
dependencies = [
 "encode_unicode",
 "lazy_static",
 "libc",
 "windows-sys 0.45.0",
]

[[package]]
name = "cpufeatures"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -419,6 +437,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"

[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"

[[package]]
name = "equivalent"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -432,7 +456,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
 "errno-dragonfly",
 "libc",
 "windows-sys",
 "windows-sys 0.48.0",
]

[[package]]
@@ -665,6 +689,19 @@ dependencies = [
]

[[package]]
name = "insta"
version = "1.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28491f7753051e5704d4d0ae7860d45fae3238d7d235bc4289dcd45c48d3cec3"
dependencies = [
 "console",
 "lazy_static",
 "linked-hash-map",
 "similar",
 "yaml-rust",
]

[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -681,7 +718,7 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
 "hermit-abi 0.3.1",
 "libc",
 "windows-sys",
 "windows-sys 0.48.0",
]

[[package]]
@@ -693,7 +730,7 @@ dependencies = [
 "hermit-abi 0.3.1",
 "io-lifetimes",
 "rustix",
 "windows-sys",
 "windows-sys 0.48.0",
]

[[package]]
@@ -736,6 +773,12 @@ dependencies = [
]

[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"

[[package]]
name = "linux-raw-sys"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -813,7 +856,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
 "libc",
 "wasi",
 "windows-sys",
 "windows-sys 0.48.0",
]

[[package]]
@@ -822,7 +865,7 @@ version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
dependencies = [
 "bitflags",
 "bitflags 1.3.2",
 "cfg-if",
 "libc",
 "memoffset",
@@ -918,7 +961,7 @@ dependencies = [
 "libc",
 "redox_syscall 0.3.5",
 "smallvec",
 "windows-targets",
 "windows-targets 0.48.0",
]

[[package]]
@@ -995,9 +1038,11 @@ name = "pisshoff-server"
version = "0.1.0"
dependencies = [
 "anyhow",
 "bitflags 2.3.3",
 "clap",
 "fastrand",
 "futures",
 "insta",
 "itertools",
 "nix",
 "parking_lot",
@@ -1005,6 +1050,7 @@ dependencies = [
 "serde",
 "serde_json",
 "shlex",
 "test-case",
 "thrussh",
 "thrussh-keys",
 "tokio",
@@ -1090,6 +1136,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"

[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
 "proc-macro-error-attr",
 "proc-macro2",
 "quote",
 "syn 1.0.109",
 "version_check",
]

[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
 "proc-macro2",
 "quote",
 "version_check",
]

[[package]]
name = "proc-macro2"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1143,7 +1213,7 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
 "bitflags",
 "bitflags 1.3.2",
]

[[package]]
@@ -1152,7 +1222,7 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
 "bitflags",
 "bitflags 1.3.2",
]

[[package]]
@@ -1255,12 +1325,12 @@ version = "0.37.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0"
dependencies = [
 "bitflags",
 "bitflags 1.3.2",
 "errno",
 "io-lifetimes",
 "libc",
 "linux-raw-sys",
 "windows-sys",
 "windows-sys 0.48.0",
]

[[package]]
@@ -1379,6 +1449,12 @@ dependencies = [
]

[[package]]
name = "similar"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf"

[[package]]
name = "siphasher"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1416,7 +1492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877"
dependencies = [
 "libc",
 "windows-sys",
 "windows-sys 0.48.0",
]

[[package]]
@@ -1492,6 +1568,41 @@ dependencies = [
]

[[package]]
name = "test-case"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a1d6e7bde536b0412f20765b76e921028059adfd1b90d8974d33fd3c91b25df"
dependencies = [
 "test-case-macros",
]

[[package]]
name = "test-case-core"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d10394d5d1e27794f772b6fc854c7e91a2dc26e2cbf807ad523370c2a59c0cee"
dependencies = [
 "cfg-if",
 "proc-macro-error",
 "proc-macro2",
 "quote",
 "syn 1.0.109",
]

[[package]]
name = "test-case-macros"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeb9a44b1c6a54c1ba58b152797739dba2a83ca74e18168a68c980eb142f9404"
dependencies = [
 "proc-macro-error",
 "proc-macro2",
 "quote",
 "syn 1.0.109",
 "test-case-core",
]

[[package]]
name = "thiserror"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1526,7 +1637,7 @@ name = "thrussh"
version = "0.34.0"
source = "git+https://github.com/JordanForks/thrussh#5b7232db70817c3519d191d1c4440da933f28563"
dependencies = [
 "bitflags",
 "bitflags 1.3.2",
 "byteorder",
 "cryptovec",
 "digest 0.9.0",
@@ -1644,7 +1755,7 @@ dependencies = [
 "signal-hook-registry",
 "socket2 0.4.9",
 "tokio-macros",
 "windows-sys",
 "windows-sys 0.48.0",
]

[[package]]
@@ -1924,11 +2035,35 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
 "windows-targets 0.42.2",
]

[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
 "windows-targets",
 "windows-targets 0.48.0",
]

[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
 "windows_aarch64_gnullvm 0.42.2",
 "windows_aarch64_msvc 0.42.2",
 "windows_i686_gnu 0.42.2",
 "windows_i686_msvc 0.42.2",
 "windows_x86_64_gnu 0.42.2",
 "windows_x86_64_gnullvm 0.42.2",
 "windows_x86_64_msvc 0.42.2",
]

[[package]]
@@ -1937,53 +2072,95 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
 "windows_aarch64_gnullvm",
 "windows_aarch64_msvc",
 "windows_i686_gnu",
 "windows_i686_msvc",
 "windows_x86_64_gnu",
 "windows_x86_64_gnullvm",
 "windows_x86_64_msvc",
 "windows_aarch64_gnullvm 0.48.0",
 "windows_aarch64_msvc 0.48.0",
 "windows_i686_gnu 0.48.0",
 "windows_i686_msvc 0.48.0",
 "windows_x86_64_gnu 0.48.0",
 "windows_x86_64_gnullvm 0.48.0",
 "windows_x86_64_msvc 0.48.0",
]

[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"

[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"

[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"

[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"

[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"

[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"

[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"

[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"

[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"

[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"

[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"

[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"

[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"

[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
@@ -1998,6 +2175,15 @@ dependencies = [
]

[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
 "linked-hash-map",
]

[[package]]
name = "yasna"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/pisshoff-server/Cargo.toml b/pisshoff-server/Cargo.toml
index 11445b6..700c0f4 100644
--- a/pisshoff-server/Cargo.toml
+++ b/pisshoff-server/Cargo.toml
@@ -9,6 +9,7 @@ edition = "2021"
pisshoff-types = { path = "../pisshoff-types" }

anyhow = "1.0"
bitflags = "2.3"
clap = { version = "4.3", features = ["derive", "env", "cargo"] }
futures = "0.3"
parking_lot = "0.12"
@@ -25,3 +26,7 @@ toml = "0.7"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
uuid = { version = "1.3", features = ["v4", "serde"] }

[dev-dependencies]
insta = "1.29"
test-case = "3.1"
diff --git a/pisshoff-server/src/command.rs b/pisshoff-server/src/command.rs
index 1f789d8..0df5a50 100644
--- a/pisshoff-server/src/command.rs
+++ b/pisshoff-server/src/command.rs
@@ -1,5 +1,7 @@
use itertools::Itertools;
use std::{borrow::Cow, f32, str::FromStr, time::Duration};
pub mod uname;

use itertools::{Either, Itertools};
use std::{f32, str::FromStr, time::Duration};
use thrussh::{server::Session, ChannelId};

pub async fn run_command(args: &[String], channel: ChannelId, session: &mut Session) {
@@ -41,54 +43,8 @@ pub async fn run_command(args: &[String], channel: ChannelId, session: &mut Sess
            }
        }
        "uname" => {
            // todo: move this out to its own module
            let out: Cow<'static, str> = match args.get(1).map(String::as_str) {
                None | Some("-s" | "--kernel-name") => "Linux\n".into(),
                Some("-a" | "--all") => "Linux cd5079c0d642 5.15.49 #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux\n".into(),
                Some("-n" | "--nodename") => "cd5079c0d642\n".into(),
                Some("-r" | "--kernel-release") => "5.15.49\n".into(),
                Some("-v" | "--kernel-version") => "#1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022\n".into(),
                Some("-m" | "--machine" | "-p" | "--processor" | "-i" | "--hardware-platform") => "x86_64\n".into(),
                Some("-o" | "--operating-system") => "GNU/Linux\n".into(),
                Some("--version") => "uname (GNU coreutils) 8.32
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by David MacKenzie.\n".into(),
                Some("--help") => "Usage: uname [OPTION]...
Print certain system information.  With no OPTION, same as -s.

  -a, --all                print all information, in the following order,
                             except omit -p and -i if unknown:
  -s, --kernel-name        print the kernel name
  -n, --nodename           print the network node hostname
  -r, --kernel-release     print the kernel release
  -v, --kernel-version     print the kernel version
  -m, --machine            print the machine hardware name
  -p, --processor          print the processor type (non-portable)
  -i, --hardware-platform  print the hardware platform (non-portable)
  -o, --operating-system   print the operating system
      --help     display this help and exit
      --version  output version information and exit

GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
Report any translation bugs to <https://translationproject.org/team/>
Full documentation <https://www.gnu.org/software/coreutils/uname>
or available locally via: info '(coreutils) uname invocation'\n".into(),
                Some("-") => "uname: extra operand '-'\nTry 'uname --help' for more information.\n".into(),
                Some(v) => format!(
                    "uname: invalid option -- '{}'\nTry 'uname --help' for more information.\n",
                    if v.starts_with('-') && !v.starts_with("--") {
                        &v[1..]
                    } else {
                        v
                    }
                ).into(),
            };

            session.data(channel, out.into_owned().into());
            let out = uname::execute(&args[1..]);
            session.data(channel, out.into());
        }
        other => {
            // TODO: fix stderr displaying out of order
@@ -99,3 +55,37 @@ or available locally via: info '(coreutils) uname invocation'\n".into(),
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Arg<'a> {
    Operand(&'a str),
    Long(&'a str),
    Short(char),
}

fn argparse(args: &[String]) -> impl Iterator<Item = Arg<'_>> {
    args.iter().flat_map(|rest| {
        if let Some(rest) = rest.strip_prefix("--") {
            Either::Left(std::iter::once(Arg::Long(rest)))
        } else if let Some(rest) = rest.strip_prefix('-').filter(|v| !v.is_empty()) {
            Either::Right(rest.chars().map(Arg::Short))
        } else {
            Either::Left(std::iter::once(Arg::Operand(rest)))
        }
    })
}

#[cfg(test)]
mod test {
    use super::Arg;
    use test_case::test_case;

    #[test_case("-a", &[Arg::Short('a')]; "single short parameter")]
    #[test_case("-abc", &[Arg::Short('a'), Arg::Short('b'), Arg::Short('c')]; "multiple short parameter")]
    #[test_case("-a --long operand -b -", &[Arg::Short('a'), Arg::Long("long"), Arg::Operand("operand"), Arg::Short('b'), Arg::Operand("-")]; "full hit")]
    fn argparse(input: &str, expected: &[Arg<'static>]) {
        let input = shlex::split(input).unwrap();
        let output = super::argparse(&input).collect::<Vec<_>>();
        assert_eq!(output, expected);
    }
}
diff --git a/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-a.snap b/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-a.snap
new file mode 100644
index 0000000..2522ec3
--- /dev/null
+++ b/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-a.snap
@@ -0,0 +1,6 @@
---
source: pisshoff-server/src/command/uname.rs
expression: output
---
Linux cd5079c0d642 5.15.49 #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022 x86_64 GNU/Linux

diff --git a/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-asnrvmpio.snap b/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-asnrvmpio.snap
new file mode 100644
index 0000000..2522ec3
--- /dev/null
+++ b/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-asnrvmpio.snap
@@ -0,0 +1,6 @@
---
source: pisshoff-server/src/command/uname.rs
expression: output
---
Linux cd5079c0d642 5.15.49 #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022 x86_64 GNU/Linux

diff --git a/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-sn --fake.snap b/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-sn --fake.snap
new file mode 100644
index 0000000..2e0640c
--- /dev/null
+++ b/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-sn --fake.snap
@@ -0,0 +1,7 @@
---
source: pisshoff-server/src/command/uname.rs
expression: output
---
uname: unrecognized option '--fake'
Try 'uname --help' for more information.

diff --git a/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-sn -z.snap b/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-sn -z.snap
new file mode 100644
index 0000000..09e859b
--- /dev/null
+++ b/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-sn -z.snap
@@ -0,0 +1,7 @@
---
source: pisshoff-server/src/command/uname.rs
expression: output
---
uname: invalid option -- 'z'
Try 'uname --help' for more information.

diff --git a/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-sn oper.snap b/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-sn oper.snap
new file mode 100644
index 0000000..f18ea6d
--- /dev/null
+++ b/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-sn oper.snap
@@ -0,0 +1,7 @@
---
source: pisshoff-server/src/command/uname.rs
expression: output
---
uname: extra operand 'oper'
Try 'uname --help' for more information.

diff --git a/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-sn.snap b/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-sn.snap
new file mode 100644
index 0000000..28f09d8
--- /dev/null
+++ b/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-sn.snap
@@ -0,0 +1,6 @@
---
source: pisshoff-server/src/command/uname.rs
expression: output
---
Linux cd5079c0d642

diff --git a/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-snrvmpio.snap b/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-snrvmpio.snap
new file mode 100644
index 0000000..f9a4deb
--- /dev/null
+++ b/pisshoff-server/src/command/snapshots/pisshoff_server__command__uname__test__-snrvmpio.snap
@@ -0,0 +1,6 @@
---
source: pisshoff-server/src/command/uname.rs
expression: output
---
Linux cd5079c0d642 5.15.49 #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022 x86_64 unknown unknown GNU/Linux

diff --git a/pisshoff-server/src/command/uname.rs b/pisshoff-server/src/command/uname.rs
new file mode 100644
index 0000000..3563de8
--- /dev/null
+++ b/pisshoff-server/src/command/uname.rs
@@ -0,0 +1,152 @@
use crate::command::Arg;
use bitflags::bitflags;

bitflags! {
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
    struct ToPrint: u8 {
        const KERNEL_NAME      = 0b0000_0001;
        const NODE_NAME        = 0b0000_0010;
        const KERNEL_RELEASE   = 0b0000_0100;
        const KERNEL_VERSION   = 0b0000_1000;
        const MACHINE          = 0b0001_0000;
        const PROCESSOR        = 0b0010_0000;
        const PLATFORM         = 0b0100_0000;
        const OPERATING_SYSTEM = 0b1000_0000;
    }
}

const VERSION_STRING: &str = "uname (GNU coreutils) 8.32
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by David MacKenzie.
";

pub const HELP_STRING: &str = "Usage: uname [OPTION]...
Print certain system information.  With no OPTION, same as -s.

  -a, --all                print all information, in the following order,
                             except omit -p and -i if unknown:
  -s, --kernel-name        print the kernel name
  -n, --nodename           print the network node hostname
  -r, --kernel-release     print the kernel release
  -v, --kernel-version     print the kernel version
  -m, --machine            print the machine hardware name
  -p, --processor          print the processor type (non-portable)
  -i, --hardware-platform  print the hardware platform (non-portable)
  -o, --operating-system   print the operating system
      --help     display this help and exit
      --version  output version information and exit

GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
Report any translation bugs to <https://translationproject.org/team/>
Full documentation <https://www.gnu.org/software/coreutils/uname>
or available locally via: info '(coreutils) uname invocation'
";

pub fn execute(params: &[String]) -> String {
    let mut to_print = ToPrint::empty();
    let mut filter_unknown = false;

    for param in super::argparse(params) {
        to_print |= match param {
            Arg::Short('a') | Arg::Long("all") => {
                filter_unknown = true;
                ToPrint::all()
            }
            Arg::Short('s') | Arg::Long("kernel-name") => ToPrint::KERNEL_NAME,
            Arg::Short('n') | Arg::Long("nodename") => ToPrint::NODE_NAME,
            Arg::Short('r') | Arg::Long("kernel-release") => ToPrint::KERNEL_RELEASE,
            Arg::Short('v') | Arg::Long("kernel-version") => ToPrint::KERNEL_VERSION,
            Arg::Short('m') | Arg::Long("machine") => ToPrint::MACHINE,
            Arg::Short('p') | Arg::Long("processor") => ToPrint::PROCESSOR,
            Arg::Short('i') | Arg::Long("hardware-platform") => ToPrint::PLATFORM,
            Arg::Short('o') | Arg::Long("operating-system") => ToPrint::OPERATING_SYSTEM,
            Arg::Long("help") => return HELP_STRING.to_string(),
            Arg::Long("version") => return VERSION_STRING.to_string(),
            Arg::Operand(operand) => {
                return format!(
                    "uname: extra operand '{operand}'\nTry 'uname --help' for more information.\n"
                );
            }
            Arg::Short(s) => {
                return format!(
                    "uname: invalid option -- '{s}'\nTry 'uname --help' for more information.\n"
                );
            }
            Arg::Long(s) => return format!(
                "uname: unrecognized option '--{s}'\nTry 'uname --help' for more information.\n"
            ),
        };
    }

    let mut out = String::with_capacity(105);

    macro_rules! write {
        ($v:expr) => {
            if !out.is_empty() {
                out.push(' ');
            }

            out.push_str($v);
        };
    }

    if to_print.contains(ToPrint::KERNEL_NAME) {
        write!("Linux");
    }

    if to_print.contains(ToPrint::NODE_NAME) {
        write!("cd5079c0d642");
    }

    if to_print.contains(ToPrint::KERNEL_RELEASE) {
        write!("5.15.49");
    }

    if to_print.contains(ToPrint::KERNEL_VERSION) {
        write!("#1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022");
    }

    if to_print.contains(ToPrint::MACHINE) {
        write!("x86_64");
    }

    if to_print.contains(ToPrint::PROCESSOR) && !filter_unknown {
        write!("unknown");
    }

    if to_print.contains(ToPrint::PLATFORM) && !filter_unknown {
        write!("unknown");
    }

    if to_print.contains(ToPrint::OPERATING_SYSTEM) {
        write!("GNU/Linux");
    }

    out.push('\n');

    out
}

#[cfg(test)]
mod test {
    use crate::command::uname::execute;
    use test_case::test_case;

    #[test_case("-a"; "all")]
    #[test_case("-snrvmpio"; "all separate")]
    #[test_case("-asnrvmpio"; "all separate with all")]
    #[test_case("-sn"; "subset")]
    #[test_case("-sn --fake"; "unknown long arg param")]
    #[test_case("-sn -z"; "unknown short arg param")]
    #[test_case("-sn oper"; "unknown operand")]
    fn snapshot(input: &str) {
        let input_parsed = shlex::split(input).unwrap();
        let output = execute(&input_parsed);

        insta::assert_display_snapshot!(input, output);
    }
}