From 76e7036da95035734c81c88bc64b15e50a1880eb Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Thu, 12 Jan 2023 19:00:16 -0500 Subject: [PATCH] Add `link` applet --- src/cmd/clear/mod.rs | 2 +- src/cmd/groups/mod.rs | 8 ++---- src/cmd/link/mod.rs | 60 +++++++++++++++++++++++++++++++++++++++++++ src/cmd/mod.rs | 12 ++++++--- src/pw/mod.rs | 18 ++++++++++--- 5 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 src/cmd/link/mod.rs diff --git a/src/cmd/clear/mod.rs b/src/cmd/clear/mod.rs index 5969845..b552f39 100644 --- a/src/cmd/clear/mod.rs +++ b/src/cmd/clear/mod.rs @@ -1,5 +1,5 @@ -use clap::Command; use super::Cmd; +use clap::Command; #[derive(Debug, Default)] pub struct Clear; diff --git a/src/cmd/groups/mod.rs b/src/cmd/groups/mod.rs index cc9bf8c..a66c02b 100644 --- a/src/cmd/groups/mod.rs +++ b/src/cmd/groups/mod.rs @@ -30,12 +30,8 @@ impl Cmd for Groups { return Err(Box::new(io::Error::new(io::ErrorKind::Other, "no input"))) }; let groups = match matches.get_one::("user") { - Some(u) => { - pw::get_group_names_for_name(&u)? - }, - None => { - pw::get_group_names()? - }, + Some(u) => pw::get_group_names_for_name(&u)?, + None => pw::get_group_names()?, }; let len = groups.len(); for (idx, group) in groups.into_iter().enumerate() { diff --git a/src/cmd/link/mod.rs b/src/cmd/link/mod.rs new file mode 100644 index 0000000..4d43c99 --- /dev/null +++ b/src/cmd/link/mod.rs @@ -0,0 +1,60 @@ +use super::Cmd; +use clap::{Arg, ArgAction, Command}; +use std::{fs, io}; + +#[derive(Debug, Default)] +pub struct Link; + +impl Cmd for Link { + fn name(&self) -> &str { + "link" + } + + fn cli(&self) -> clap::Command { + Command::new("link") + .about("call the link function to create a link to a file") + .author("Nathan Fisher") + .version(env!("CARGO_PKG_VERSION")) + .args([ + Arg::new("file1").required(true).index(1), + Arg::new("file2").required(true).index(2), + Arg::new("verbose") + .short('v') + .long("verbose") + .action(ArgAction::SetTrue), + ]) + } + + fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box> { + let Some(matches) = matches else { + return Err(Box::new(io::Error::new(io::ErrorKind::Other, "no input"))); + }; + let f1 = match matches.get_one::("file1") { + Some(s) => s, + None => { + return Err(Box::new(io::Error::new( + io::ErrorKind::Other, + "missing file 1", + ))) + } + }; + let f2 = match matches.get_one::("file2") { + Some(s) => s, + None => { + return Err(Box::new(io::Error::new( + io::ErrorKind::Other, + "missing file 2", + ))) + } + }; + fs::hard_link(f1, f2)?; + if matches.get_flag("verbose") { + println!("{f2} -> {f1}"); + } + Ok(()) + } + + fn path(&self) -> Option { + Some(crate::Path::UsrBin) + } +} diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index eba7f46..f7eda74 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -20,6 +20,7 @@ mod getty; pub mod groups; pub mod head; pub mod hostname; +pub mod link; mod ln; mod ls; pub mod mountpoint; @@ -40,9 +41,10 @@ pub mod yes; pub use { self::hostname::Hostname, base32::Base32, base64::Base64, basename::Basename, - bootstrap::Bootstrap, clear::Clear, dirname::Dirname, echo::Echo, factor::Factor, fold::Fold, groups::Groups, - head::Head, mountpoint::Mountpoint, nologin::Nologin, nproc::Nproc, r#false::False, - r#true::True, rev::Rev, shitbox::Shitbox, sleep::Sleep, which::Which, whoami::Whoami, yes::Yes, + bootstrap::Bootstrap, clear::Clear, dirname::Dirname, echo::Echo, factor::Factor, fold::Fold, + groups::Groups, head::Head, link::Link, mountpoint::Mountpoint, nologin::Nologin, nproc::Nproc, + r#false::False, r#true::True, rev::Rev, shitbox::Shitbox, sleep::Sleep, which::Which, + whoami::Whoami, yes::Yes, }; pub trait Cmd: fmt::Debug + Sync { @@ -66,6 +68,7 @@ pub fn get(name: &str) -> Option> { "fold" => Some(Box::new(Fold::default())), "groups" => Some(Box::new(Groups::default())), "head" => Some(Box::new(Head::default())), + "link" => Some(Box::new(Link::default())), "mountpoint" => Some(Box::new(Mountpoint::default())), "nologin" => Some(Box::new(Nologin::default())), "nproc" => Some(Box::new(Nproc::default())), @@ -80,7 +83,7 @@ pub fn get(name: &str) -> Option> { } } -pub static COMMANDS: [&'static str; 23] = [ +pub static COMMANDS: [&'static str; 24] = [ "base32", "base64", "basename", @@ -94,6 +97,7 @@ pub static COMMANDS: [&'static str; 23] = [ "groups", "head", "hostname", + "link", "mountpoint", "nologin", "nproc", diff --git a/src/pw/mod.rs b/src/pw/mod.rs index c8c7f96..c3e53f3 100644 --- a/src/pw/mod.rs +++ b/src/pw/mod.rs @@ -1,4 +1,8 @@ -use std::{error::Error, ffi::{CStr, CString, c_int}, num, io}; +use std::{ + error::Error, + ffi::{c_int, CStr, CString}, + io, num, +}; pub fn get_username<'a>() -> Result<&'a str, std::str::Utf8Error> { let user = unsafe { @@ -88,7 +92,12 @@ pub fn get_gids() -> Result, num::TryFromIntError> { pub fn get_gids_for_name(name: &str) -> Result, Box> { let gid = match get_pgid_for_name(name) { Some(g) => g as libc::gid_t, - None => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "cannot get primary group"))), + None => { + return Err(Box::new(io::Error::new( + io::ErrorKind::Other, + "cannot get primary group", + ))) + } }; let mut buf: Vec = vec![0; 1024]; let name = CString::new(name.as_bytes())?; @@ -96,7 +105,10 @@ pub fn get_gids_for_name(name: &str) -> Result, Box> { unsafe { let res = libc::getgrouplist(name.as_ptr(), gid, buf.as_mut_ptr(), &mut ct); if res < 0 { - return Err(Box::new(io::Error::new(io::ErrorKind::Other, "too many groups"))) + return Err(Box::new(io::Error::new( + io::ErrorKind::Other, + "too many groups", + ))); } } Ok(buf[0..ct as usize].iter().map(|x| *x as u32).collect())