use super::Cmd; use crate::{ args, mode::{get_umask, Parser}, }; use clap::{value_parser, Arg, 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), args::verbose(), 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!(), }) } }