use async_trait::async_trait; use bitflags::bitflags; use thrussh::ChannelId; use crate::{ command::{Arg, Command, CommandResult}, server::{ConnectionState, ThrusshSession}, }; 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 . 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: Report any translation bugs to Full documentation or available locally via: info '(coreutils) uname invocation' "; #[derive(Debug, Clone)] pub struct Uname {} #[async_trait] impl Command for Uname { async fn new( _connection: &mut ConnectionState, params: &[String], channel: ChannelId, session: &mut S, ) -> CommandResult { let (out, exit_code) = execute(params); session.data(channel, out.into()); CommandResult::Exit(exit_code) } async fn stdin( self, _connection: &mut ConnectionState, _channel: ChannelId, _data: &[u8], _session: &mut S, ) -> CommandResult { CommandResult::Exit(0) } } pub fn execute(params: &[String]) -> (String, u32) { 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(), 0), Arg::Long("version") => return (VERSION_STRING.to_string(), 0), Arg::Operand(operand) => { return ( format!( "uname: extra operand '{operand}'\nTry 'uname --help' for more information.\n" ), 1, ); } Arg::Short(s) => { return ( format!( "uname: invalid option -- '{s}'\nTry 'uname --help' for more information.\n" ), 1, ); } Arg::Long(s) => { return ( format!( "uname: unrecognized option '--{s}'\nTry 'uname --help' for more information.\n" ), 1, ); } }; } if to_print.is_empty() { to_print |= ToPrint::KERNEL_NAME; } 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, 0) } #[cfg(test)] mod test { use test_case::test_case; use crate::command::uname::execute; #[test_case("", 0; "none")] #[test_case("-a", 0; "all")] #[test_case("-snrvmpio", 0; "all separate")] #[test_case("-asnrvmpio", 0; "all separate with all")] #[test_case("-sn", 0; "subset")] #[test_case("-sn --fake", 1; "unknown long arg param")] #[test_case("-sn -z", 1; "unknown short arg param")] #[test_case("-sn oper", 1; "unknown operand")] fn snapshot(input: &str, expected_exit_code: u32) { let input_parsed = shlex::split(input).unwrap(); let (output, actual_exit_code) = execute(&input_parsed); insta::assert_display_snapshot!(input, output); assert_eq!(actual_exit_code, expected_exit_code); } }