From 6963ba4a4b4cc9ef13cc91dad5a84b7f354f1196 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Thu, 2 Feb 2023 23:34:37 -0500 Subject: [PATCH] Convert several commands to use raw system calls instead of libc functions --- Cargo.lock | 36 +++---------- Cargo.toml | 3 +- src/cmd/expand/mod.rs | 53 ++++++++++++++++++ src/cmd/hostname/mod.rs | 9 ++-- src/cmd/mkfifo/mod.rs | 10 ++-- src/cmd/mknod/mod.rs | 10 ++-- src/cmd/nproc/mod.rs | 9 +++- src/cmd/sync/mod.rs | 36 ++++--------- src/cmd/unlink/mod.rs | 13 ++--- src/lib.rs | 2 + src/stat/mod.rs | 22 ++++++++ src/unistd/mod.rs | 115 ++++++++++++++++++++++++++++++++++++++++ 12 files changed, 232 insertions(+), 86 deletions(-) create mode 100644 src/cmd/expand/mod.rs create mode 100644 src/stat/mod.rs create mode 100644 src/unistd/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 4beadb6..4aaad80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,17 +121,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - [[package]] name = "io-lifetimes" version = "1.0.4" @@ -166,22 +155,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - -[[package]] -name = "num_cpus" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" -dependencies = [ - "hermit-abi 0.2.6", - "libc", -] - [[package]] name = "os_str_bytes" version = "6.4.1" @@ -217,6 +190,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "sc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010e18bd3bfd1d45a7e666b236c78720df0d9a7698ebaa9c1c559961eb60a38b" + [[package]] name = "shitbox" version = "0.1.0" @@ -227,9 +206,8 @@ dependencies = [ "clap_complete_nushell", "clap_mangen", "data-encoding", - "hostname", "libc", - "num_cpus", + "sc", "termcolor", "textwrap", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index cf91692..4feb26b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,8 @@ clap_complete = "4.0" clap_complete_nushell = "0.1" clap_mangen = "0.2" data-encoding = "2.3" -hostname = { version = "0.3", features = ["set"] } libc = "0.2" -num_cpus = "1.15" +sc = "0.2" termcolor = "1.1" textwrap = { version = "0.16", default-features = false, features = ["smawk"] } walkdir = "2.3.2" diff --git a/src/cmd/expand/mod.rs b/src/cmd/expand/mod.rs new file mode 100644 index 0000000..c40e6b8 --- /dev/null +++ b/src/cmd/expand/mod.rs @@ -0,0 +1,53 @@ +use std::io; + +use super::Cmd; +use clap::{Arg, ArgAction, Command, ValueHint}; + +#[derive(Debug, Default)] +pub struct Expand; + +impl Cmd for Expand { + fn cli(&self) -> clap::Command { + Command::new("expand") + .about("convert tabs to spaces") + .author("Nathan Fisher") + .version(env!("CARGO_PKG_VERSION")) + .args([ + Arg::new("file") + .num_args(1..) + .default_value("-") + .value_name("FILE") + .value_hint(ValueHint::FilePath), + Arg::new("initial") + .short('i') + .long("initial") + .help("do not convert tabs after non blanks") + .action(ArgAction::SetTrue), + Arg::new("tabs") + .short('t') + .long("tabs") + .default_value("8") + .help( + "Specify the tab stops. The application shall ensure that \ + the argument tablist consists of either a single positive \ + decimal integer or a list of tabstops. If a single number \ + is given, tabs shall be set that number of column positions \ + apart instead of the default 8.", + ) + .value_name("tablist") + .value_delimiter(',') + .num_args(1..), + ]) + } + + fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box> { + let Some(matches) = matches else { + return Err(io::Error::new(io::ErrorKind::Other, "no input").into()); + }; + Ok(()) + } + + fn path(&self) -> Option { + Some(crate::Path::UsrBin) + } +} diff --git a/src/cmd/hostname/mod.rs b/src/cmd/hostname/mod.rs index 93c400c..32cdbda 100644 --- a/src/cmd/hostname/mod.rs +++ b/src/cmd/hostname/mod.rs @@ -1,4 +1,5 @@ use super::Cmd; +use crate::unistd; use clap::{Arg, ArgAction, ArgMatches, Command}; use std::{error::Error, io}; @@ -24,14 +25,14 @@ impl Cmd for Hostname { fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box> { let matches = matches.unwrap(); if let Some(name) = matches.get_one::("NAME") { - hostname::set(name)?; + unistd::sethostname(name)?; Ok(()) } else { - let hostname = hostname::get()?; + let hostname = unistd::gethostname()?; if matches.get_flag("STRIP") { println!( "{}", - if let Some(s) = hostname.to_string_lossy().split('.').next() { + if let Some(s) = hostname.split('.').next() { s } else { return Err(io::Error::new( @@ -42,7 +43,7 @@ impl Cmd for Hostname { } ); } else { - println!("{}", hostname.to_string_lossy()); + println!("{hostname}"); } Ok(()) } diff --git a/src/cmd/mkfifo/mod.rs b/src/cmd/mkfifo/mod.rs index 0a62d23..968f3f6 100644 --- a/src/cmd/mkfifo/mod.rs +++ b/src/cmd/mkfifo/mod.rs @@ -1,7 +1,7 @@ use super::Cmd; -use crate::{args, mode::Parser}; +use crate::{args, mode::Parser, stat}; use clap::{Arg, ArgMatches, Command}; -use std::{error::Error, ffi::CString, io}; +use std::{error::Error, io}; #[derive(Debug, Default)] pub struct MkFifo; @@ -46,11 +46,7 @@ impl Cmd for MkFifo { }; if let Some(files) = matches.get_many::("file") { for f in files { - let fname = CString::new(f.as_str())?; - let res = unsafe { libc::mkfifo(fname.as_ptr(), mode) }; - if res != 0 { - return Err(io::Error::last_os_error().into()); - } + stat::mkfifo(f.as_str(), mode)?; if matches.get_flag("verbose") { println!( "{}: created pipe '{f}' with mode {mode:o}", diff --git a/src/cmd/mknod/mod.rs b/src/cmd/mknod/mod.rs index f6d5c5e..e06efa5 100644 --- a/src/cmd/mknod/mod.rs +++ b/src/cmd/mknod/mod.rs @@ -1,10 +1,10 @@ use super::Cmd; use crate::{ args, - mode::{get_umask, Parser}, + mode::{get_umask, Parser}, stat, }; use clap::{value_parser, Arg, ArgMatches, Command}; -use std::{convert::Infallible, error::Error, ffi::CString, io, str::FromStr}; +use std::{convert::Infallible, error::Error, io, str::FromStr}; #[derive(Debug, Default)] pub struct MkNod; @@ -69,11 +69,7 @@ impl Cmd for MkNod { } 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()); - } + stat::mknod(file.as_str(), mode, dev_t)?; if matches.get_flag("verbose") { match special { Special::Block => { diff --git a/src/cmd/nproc/mod.rs b/src/cmd/nproc/mod.rs index 52d3369..51eae8f 100644 --- a/src/cmd/nproc/mod.rs +++ b/src/cmd/nproc/mod.rs @@ -24,9 +24,9 @@ impl Cmd for Nproc { return Err(Box::new(io::Error::new(io::ErrorKind::Other, "no input"))); }; if matches.get_flag("ALL") { - println!("{}", num_cpus::get()); + println!("{}", unsafe { get_nprocs_conf() }); } else { - println!("{}", num_cpus::get_physical()); + println!("{}", unsafe { get_nprocs() }); } Ok(()) } @@ -35,3 +35,8 @@ impl Cmd for Nproc { Some(crate::Path::UsrBin) } } + +extern "C" { + fn get_nprocs() -> libc::c_int; + fn get_nprocs_conf() -> libc::c_int; +} diff --git a/src/cmd/sync/mod.rs b/src/cmd/sync/mod.rs index 95442e5..27d3d59 100644 --- a/src/cmd/sync/mod.rs +++ b/src/cmd/sync/mod.rs @@ -1,6 +1,8 @@ +use crate::unistd; + use super::Cmd; use clap::{Arg, ArgAction, Command}; -use std::{error::Error, ffi::CString, io}; +use std::{error::Error, io, fs::OpenOptions}; #[derive(Debug, Default)] pub struct Sync; @@ -38,37 +40,19 @@ impl Cmd for Sync { }; if let Some(files) = matches.get_many::("FILE") { for f in files { - let file = CString::new(f.as_bytes())?; + let mut opts = OpenOptions::new(); + let opts = opts.read(true).write(true); + let fd = opts.open(f)?; if matches.get_flag("data") { - let rc = unsafe { - let fd = libc::open(file.as_ptr(), libc::O_RDWR | libc::O_NOATIME); - libc::fdatasync(fd) - }; - if rc != 0 { - return Err(Box::new(io::Error::last_os_error())); - } + unistd::fdatasync(&fd)?; } else if matches.get_flag("fs") { - let rc = unsafe { - let fd = libc::open(file.as_ptr(), libc::O_RDWR | libc::O_NOATIME); - libc::syncfs(fd) - }; - if rc != 0 { - return Err(Box::new(io::Error::last_os_error())); - } + unistd::syncfs(&fd)?; } else { - let rc = unsafe { - let fd = libc::open(file.as_ptr(), libc::O_RDWR | libc::O_NOATIME); - libc::fsync(fd) - }; - if rc != 0 { - return Err(Box::new(io::Error::last_os_error())); - } + unistd::fsync(&fd)?; } } } else { - unsafe { - libc::sync(); - } + unistd::sync(); } Ok(()) } diff --git a/src/cmd/unlink/mod.rs b/src/cmd/unlink/mod.rs index 02e2b8c..d3adb5d 100644 --- a/src/cmd/unlink/mod.rs +++ b/src/cmd/unlink/mod.rs @@ -1,7 +1,7 @@ use super::Cmd; -use crate::args; +use crate::{args, unistd}; use clap::{Arg, Command}; -use std::{ffi::CString, io, process}; +use std::io; #[derive(Debug, Default)] pub struct Unlink; @@ -27,14 +27,9 @@ impl Cmd for Unlink { }; if let Some(files) = matches.get_many::("file") { for f in files { - let fname = CString::new(f.as_str())?; - let ret = unsafe { libc::unlink(fname.as_ptr()) }; - if matches.get_flag("verbose") && ret == 0 { + unistd::unlink(f)?; + if matches.get_flag("verbose") { println!("unlink: {f}"); - } else if ret != 0 { - let err = io::Error::last_os_error(); - eprintln!("unlink: cannot unlink {f}: {err}"); - process::exit(ret); } } } diff --git a/src/lib.rs b/src/lib.rs index 6823b71..86d94e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,8 @@ pub mod fs; pub mod math; pub mod mode; pub mod pw; +pub mod stat; +pub mod unistd; pub use cmd::Cmd; diff --git a/src/stat/mod.rs b/src/stat/mod.rs new file mode 100644 index 0000000..1b0030c --- /dev/null +++ b/src/stat/mod.rs @@ -0,0 +1,22 @@ +use sc::*; +use std::{ffi::CString, io}; + +#[inline(always)] +pub fn mknod(path: &str, mode: u32, dev: u64) -> io::Result<()> { + let ret = unsafe { syscall!(MKNOD, CString::new(path)?.as_ptr(), mode, dev) }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} + +#[inline(always)] +pub fn mkfifo(path: &str, mode: u32) -> io::Result<()> { + let ret = unsafe { syscall!(MKNOD, CString::new(path)?.as_ptr(), mode | libc::S_IFIFO, 0) }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} diff --git a/src/unistd/mod.rs b/src/unistd/mod.rs new file mode 100644 index 0000000..ddecb19 --- /dev/null +++ b/src/unistd/mod.rs @@ -0,0 +1,115 @@ +use libc::utsname; +use sc::syscall; +use std::{ffi::CString, io, u8, fs::File, os::fd::AsRawFd}; + +#[inline(always)] +fn new_utsname() -> utsname { + utsname { + sysname: [0; 65], + nodename: [0; 65], + release: [0; 65], + version: [0; 65], + machine: [0; 65], + domainname: [0; 65], + } +} + +#[inline(always)] +pub fn gethostname() -> io::Result { + let mut uts = new_utsname(); + unsafe { + if syscall!(UNAME, &mut uts as *mut utsname) != 0 { + return Err(io::Error::last_os_error()); + } + } + let name = uts + .nodename + .iter() + .map(|x| *x as u8) + .take_while(|x| *x != 0) + .collect(); + String::from_utf8(name).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) +} + +#[inline(always)] +pub fn sethostname(name: &str) -> io::Result<()> { + let ret = unsafe { syscall!(SETHOSTNAME, name.as_ptr(), name.len()) }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error().into()) + } +} + +#[inline(always)] +pub fn link(source: &str, dest: &str) -> io::Result<()> { + let ret = unsafe { + syscall!( + LINK, + CString::new(source)?.as_ptr(), + CString::new(dest)?.as_ptr() + ) + }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} + +#[inline(always)] +pub fn unlink(name: &str) -> io::Result<()> { + let ret = unsafe { syscall!(UNLINK, CString::new(name)?.as_ptr()) }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error().into()) + } +} + +#[inline(always)] +pub fn readlink(name: &str) -> io::Result { + let mut buf = Vec::::with_capacity(1024); + let ret = unsafe { syscall!(READLINK, CString::new(name)?.as_ptr(), buf.as_mut_ptr(), 1024) }; + if ret == 0 { + let path = buf.iter().map(|x| *x as u8).take_while(|x| *x != 0).collect(); + String::from_utf8(path).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + } else { + Err(io::Error::last_os_error()) + } +} + +#[inline(always)] +pub fn fdatasync(fd: &File) -> io::Result<()> { + let ret = unsafe { syscall!(FDATASYNC, fd.as_raw_fd()) }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} + +#[inline(always)] +pub fn syncfs(fd: &File) -> io::Result<()> { + let ret = unsafe { syscall!(SYNCFS, fd.as_raw_fd()) }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} + +#[inline(always)] +pub fn fsync(fd: &File) -> io::Result<()> { + let ret = unsafe { syscall!(FSYNC, fd.as_raw_fd()) }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} + +#[inline(always)] +pub fn sync() { + unsafe { syscall!(SYNC) }; +}