diff --git a/Cargo.toml b/Cargo.toml index 4f592a7..cf91692 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] atty = "0.2" -clap = "4.0" +clap = "4.1" clap_complete = "4.0" clap_complete_nushell = "0.1" clap_mangen = "0.2" diff --git a/src/cmd/mknod/mod.rs b/src/cmd/mknod/mod.rs new file mode 100644 index 0000000..6836ef8 --- /dev/null +++ b/src/cmd/mknod/mod.rs @@ -0,0 +1,124 @@ +use super::Cmd; +use crate::mode::{get_umask, Parser}; +use clap::{value_parser, Arg, ArgAction, ArgMatches, Command}; +use std::{convert::Infallible, error::Error, ffi::CString, io, str::FromStr}; + +#[derive(Debug, Default)] +pub struct MkNod; + +impl Cmd for MkNod { + fn cli(&self) -> clap::Command { + Command::new("mknod") + .about("make block or character special files") + .author("Nathan Fisher") + .version(env!("CARGO_PKG_VERSION")) + .args([ + Arg::new("mode") + .short('m') + .long("mode") + .help("set file permission bits to MODE, not a=rw - umask") + .value_name("MODE") + .num_args(1), + Arg::new("verbose") + .short('v') + .long("verbose") + .action(ArgAction::SetTrue), + Arg::new("file").value_name("NAME").required(true), + Arg::new("type") + .value_name("TYPE") + .required(true) + .value_parser(["b", "c", "p"]), + Arg::new("major") + .value_name("MAJOR") + .value_parser(value_parser!(u32)) + .required_if_eq_any([("type", "b"), ("type", "c")]), + Arg::new("minor") + .value_name("MINOR") + .value_parser(value_parser!(u32)) + .required_if_eq_any([("type", "b"), ("type", "c")]), + ]) + } + + fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box> { + let Some(matches) = matches else { + return Err(io::Error::new(io::ErrorKind::Other, "no input").into()); + }; + let Some(file) = matches.get_one::("file") else { + return Err(io::Error::new(io::ErrorKind::Other, "no file given").into()); + }; + let Some(t) = matches.get_one::("type") else { + return Err(io::Error::new(io::ErrorKind::Other, "no type given").into()); + }; + let mut mode = if let Some(m) = matches.get_one::("mode") { + Parser::default().parse(m.as_str())? + } else { + let mask = get_umask(); + 0o666 & !mask + }; + let special: Special = t.parse()?; + mode |= special as u32; + let dev_t = match special { + Special::Block | Special::Char => { + let Some(major) = matches.get_one::("major") else { + return Err(io::Error::new(io::ErrorKind::Other, "missing MAJOR").into()); + }; + let Some(minor) = matches.get_one::("minor") else { + return Err(io::Error::new(io::ErrorKind::Other, "missing MINOR").into()); + }; + libc::makedev(*major, *minor) + } + Special::Pipe => 0, + }; + let fname = CString::new(file.as_str())?; + let ret = unsafe { libc::mknod(fname.as_ptr(), mode, dev_t) }; + if ret != 0 { + return Err(io::Error::last_os_error().into()); + } + if matches.get_flag("verbose") { + match special { + Special::Block => { + println!( + "mknod: created block device with mode {mode:o} major {} and minor {}", + unsafe { libc::major(dev_t) }, + unsafe { libc::minor(dev_t) }, + ); + } + Special::Char => { + println!( + "mknod: created character device with mode {mode:o} major {} and minor {}", + unsafe { libc::major(dev_t) }, + unsafe { libc::minor(dev_t) }, + ); + } + Special::Pipe => { + println!("mknod: created fifo with mode {mode:o}"); + } + } + } + Ok(()) + } + + fn path(&self) -> Option { + Some(crate::Path::Sbin) + } +} + +#[derive(Clone, Copy, Debug)] +enum Special { + Block = 0o60000, + Char = 0o20000, + Pipe = 0o10000, +} + +impl FromStr for Special { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(match s { + "b" => Self::Block, + "c" => Self::Char, + "p" => Self::Pipe, + _ => unreachable!(), + }) + } +} diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 90b20bd..f25746a 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -26,6 +26,7 @@ mod link; mod ln; mod ls; mod mkfifo; +mod mknod; mod mountpoint; mod mv; mod nologin; @@ -79,6 +80,7 @@ pub fn get(name: &str) -> Option> { "head" => Some(Box::new(head::Head::default())), "link" => Some(Box::new(link::Link::default())), "mkfifo" => Some(Box::new(mkfifo::MkFifo::default())), + "mknod" => Some(Box::new(mknod::MkNod::default())), "mountpoint" => Some(Box::new(mountpoint::Mountpoint::default())), "nologin" => Some(Box::new(nologin::Nologin::default())), "nproc" => Some(Box::new(nproc::Nproc::default())), @@ -98,7 +100,7 @@ pub fn get(name: &str) -> Option> { } } -pub static COMMANDS: [&str; 33] = [ +pub static COMMANDS: [&str; 34] = [ "base32", "base64", "basename", @@ -117,6 +119,7 @@ pub static COMMANDS: [&str; 33] = [ "hostname", "link", "mkfifo", + "mknod", "mountpoint", "nologin", "nproc", diff --git a/src/mode/parser.rs b/src/mode/parser.rs index 4008678..6177563 100644 --- a/src/mode/parser.rs +++ b/src/mode/parser.rs @@ -101,7 +101,7 @@ pub struct Parser { impl Default for Parser { fn default() -> Self { let umask = get_umask(); - let mut mode = 0o0777; + let mut mode = 0o0666; mode &= umask; Self { mode,