From de9fefaff9e95877d86d45e54616205be01f5ee5 Mon Sep 17 00:00:00 2001 From: grisenti Date: Sun, 13 Apr 2025 00:02:18 +0200 Subject: [PATCH 01/12] sh: remove nix dependency --- Cargo.lock | 134 ++++--------- sh/Cargo.toml | 2 +- sh/builtin/bg.rs | 4 +- sh/builtin/cd.rs | 8 +- sh/builtin/command.rs | 2 +- sh/builtin/dot.rs | 2 +- sh/builtin/fc.rs | 9 +- sh/builtin/fg.rs | 4 +- sh/builtin/kill.rs | 6 +- sh/builtin/mod.rs | 10 +- sh/builtin/read.rs | 9 +- sh/builtin/times.rs | 30 ++- sh/builtin/trap.rs | 2 +- sh/builtin/type_.rs | 2 +- sh/builtin/ulimit.rs | 99 ++++++---- sh/builtin/wait.rs | 4 +- sh/cli/terminal.rs | 61 +++--- sh/jobs.rs | 21 +- sh/main.rs | 16 +- sh/os/errno.rs | 400 +++++++++++++++++++++++++++++++++++++++ sh/os/mod.rs | 301 +++++++++++++++++++++++++++++ sh/{ => os}/signals.rs | 177 ++++++++--------- sh/pattern/regex.rs | 1 - sh/shell/mod.rs | 73 +++---- sh/shell/opened_files.rs | 7 +- sh/utils.rs | 150 +-------------- sh/wordexp/parameter.rs | 3 +- sh/wordexp/tilde.rs | 1 - 28 files changed, 1017 insertions(+), 521 deletions(-) create mode 100644 sh/os/errno.rs create mode 100644 sh/os/mod.rs rename sh/{ => os}/signals.rs (70%) diff --git a/Cargo.lock b/Cargo.lock index 33330b4a4..3d64ea41e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,9 +131,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bigdecimal" -version = "0.4.8" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013" +checksum = "7f31f3af01c5c65a07985c804d3366560e6fa7883d640a122819b14ec327482c" dependencies = [ "autocfg", "libm", @@ -250,9 +250,9 @@ dependencies = [ [[package]] name = "bytemuck_derive" -version = "1.9.3" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" +checksum = "2ff22c2722516255d1823ce3cc4bc0b154dbc9364be5c905d6baa6eccbbc8774" dependencies = [ "proc-macro2", "quote", @@ -295,12 +295,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "chrono" version = "0.4.40" @@ -315,9 +309,9 @@ dependencies = [ [[package]] name = "chrono-tz" -version = "0.10.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efdce149c370f133a071ca8ef6ea340b7b88748ab0810097a9e2976eaa34b4f3" +checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6" dependencies = [ "chrono", "chrono-tz-build", @@ -326,9 +320,9 @@ dependencies = [ [[package]] name = "chrono-tz-build" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f10f8c9340e31fc120ff885fcdb54a0b48e474bbd77cab557f0c30a3e569402" +checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7" dependencies = [ "parse-zoneinfo", "phf_codegen", @@ -347,9 +341,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.35" +version = "4.5.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" +checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" dependencies = [ "clap_builder", "clap_derive", @@ -357,9 +351,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.35" +version = "4.5.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" +checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5" dependencies = [ "anstyle", "clap_lex", @@ -605,9 +599,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" dependencies = [ "anstream", "anstyle", @@ -676,9 +670,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" dependencies = [ "crc32fast", "miniz_oxide", @@ -794,9 +788,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -804,7 +798,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.0", + "windows-core 0.52.0", ] [[package]] @@ -1124,19 +1118,7 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.9.0", "cfg-if", - "cfg_aliases 0.1.1", - "libc", -] - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.9.0", - "cfg-if", - "cfg_aliases 0.2.1", + "cfg_aliases", "libc", ] @@ -1257,9 +1239,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" [[package]] name = "option-ext" @@ -1607,7 +1589,7 @@ version = "0.1.7" dependencies = [ "atty", "gettext-rs", - "nix 0.29.0", + "libc", "plib", ] @@ -1914,9 +1896,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "1.0.5" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" dependencies = [ "bitflags 2.9.0", "errno", @@ -1955,7 +1937,7 @@ dependencies = [ "libc", "log", "memchr", - "nix 0.28.0", + "nix", "unicode-segmentation", "unicode-width", "utf8parse", @@ -2168,9 +2150,9 @@ dependencies = [ [[package]] name = "termion" -version = "4.0.5" +version = "4.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3669a69de26799d6321a5aa713f55f7e2cd37bd47be044b50f2acafc42c122bb" +checksum = "6f359c854fbecc1ea65bc3683f1dcb2dce78b174a1ca7fda37acd1fff81df6ff" dependencies = [ "libc", "libredox", @@ -2575,27 +2557,23 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.57.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", "windows-targets 0.52.6", ] [[package]] name = "windows-core" -version = "0.61.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ - "windows-implement 0.60.0", - "windows-interface 0.59.1", - "windows-link", - "windows-result 0.3.2", - "windows-strings", + "windows-implement", + "windows-interface", + "windows-result", + "windows-targets 0.52.6", ] [[package]] @@ -2609,17 +2587,6 @@ dependencies = [ "syn 2.0.100", ] -[[package]] -name = "windows-implement" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "windows-interface" version = "0.57.0" @@ -2631,17 +2598,6 @@ dependencies = [ "syn 2.0.100", ] -[[package]] -name = "windows-interface" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "windows-link" version = "0.1.1" @@ -2657,24 +2613,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-result" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-sys" version = "0.48.0" diff --git a/sh/Cargo.toml b/sh/Cargo.toml index 41d95c00d..b1236e58f 100644 --- a/sh/Cargo.toml +++ b/sh/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] plib = { path = "../plib" } gettext-rs.workspace = true -nix = { version = "0.29", features = ["process", "fs", "resource", "signal", "user", "term"] } +libc.workspace = true atty = "0.2" [[bin]] diff --git a/sh/builtin/bg.rs b/sh/builtin/bg.rs index c848010b5..00671ea0b 100644 --- a/sh/builtin/bg.rs +++ b/sh/builtin/bg.rs @@ -9,9 +9,9 @@ use crate::builtin::{skip_option_terminator, BuiltinResult, BuiltinUtility}; use crate::jobs::{parse_job_id, Job, JobState}; +use crate::os::signals::{kill, Signal}; use crate::shell::opened_files::OpenedFiles; use crate::shell::Shell; -use nix::sys::signal::kill; fn run_background_job( arg: &str, @@ -23,7 +23,7 @@ fn run_background_job( "bg: job {arg} is already running in the background" )); } - kill(job.pid, nix::sys::signal::SIGCONT) + kill(job.pid, Some(Signal::SigCont)) .map_err(|err| format!("bg: failed to resume job {arg} ({err})"))?; opened_files.write_out(format!("[{}] {}\n", job.number, job.command)); job.state = JobState::Running; diff --git a/sh/builtin/cd.rs b/sh/builtin/cd.rs index 419dafd91..32378399d 100644 --- a/sh/builtin/cd.rs +++ b/sh/builtin/cd.rs @@ -9,6 +9,7 @@ use crate::builtin::{BuiltinResult, BuiltinUtility}; use crate::option_parser::OptionParser; +use crate::os::chdir; use crate::shell::opened_files::OpenedFiles; use crate::shell::Shell; use std::ffi::{OsStr, OsString}; @@ -140,7 +141,7 @@ impl BuiltinUtility for Cd { } let old_working_dir = std::env::current_dir().map_err(io_err_to_string)?; - nix::unistd::chdir(AsRef::::as_ref(&curr_path)).map_err(io_err_to_string)?; + chdir(AsRef::::as_ref(&curr_path)).map_err(io_err_to_string)?; shell.current_directory = curr_path.clone(); shell.assign_global("PWD".to_string(), curr_path.to_string_lossy().into_owned())?; @@ -156,8 +157,9 @@ impl BuiltinUtility for Cd { .map(|s| s.to_string()) { let old_working_dir = std::env::current_dir().unwrap(); - nix::unistd::chdir(oldpwd.as_str()).map_err(io_err_to_string)?; - shell.current_directory = OsString::from_vec(oldpwd.as_bytes().to_vec()); + let oldpwd_ostr = OsString::from(oldpwd.clone()); + chdir(&oldpwd_ostr).map_err(io_err_to_string)?; + shell.current_directory = oldpwd_ostr; shell.assign_global("PWD".to_string(), oldpwd.clone())?; shell.assign_global( "OLDPWD".to_string(), diff --git a/sh/builtin/command.rs b/sh/builtin/command.rs index 3cf7f00d0..0e1b8f3c1 100644 --- a/sh/builtin/command.rs +++ b/sh/builtin/command.rs @@ -11,9 +11,9 @@ use crate::builtin::{ get_builtin_utility, get_special_builtin_utility, BuiltinError, BuiltinResult, BuiltinUtility, }; use crate::option_parser::OptionParser; +use crate::os::DEFAULT_PATH; use crate::shell::opened_files::OpenedFiles; use crate::shell::Shell; -use crate::utils::DEFAULT_PATH; #[derive(PartialEq, Eq)] enum Action { diff --git a/sh/builtin/dot.rs b/sh/builtin/dot.rs index 1096ec53c..5d165543e 100644 --- a/sh/builtin/dot.rs +++ b/sh/builtin/dot.rs @@ -8,9 +8,9 @@ // use crate::builtin::{skip_option_terminator, BuiltinResult, SpecialBuiltinUtility}; +use crate::os::find_command; use crate::shell::opened_files::OpenedFiles; use crate::shell::{execute_file_as_script, ScriptExecutionError, Shell}; -use crate::utils::find_command; use std::path::Path; pub struct Dot; diff --git a/sh/builtin/fc.rs b/sh/builtin/fc.rs index c6720213b..926c7297a 100644 --- a/sh/builtin/fc.rs +++ b/sh/builtin/fc.rs @@ -9,10 +9,10 @@ use crate::builtin::{BuiltinError, BuiltinResult, BuiltinUtility}; use crate::option_parser::OptionParser; +use crate::os::{mkstemp, write}; use crate::shell::history::EndPoint; use crate::shell::opened_files::OpenedFiles; use crate::shell::Shell; -use nix::unistd::mkstemp; use std::fs::File; use std::io::Read; use std::os::fd::{FromRawFd, OwnedFd}; @@ -210,10 +210,9 @@ fn edit( }; // remove call to fc shell.history.remove_last_entry(); - let (raw_fd, path) = mkstemp("/tmp/sh-fc.XXXXXX") + let (fd, path) = mkstemp("/tmp/sh-fc.XXXXXX") .map_err(|err| format!("fc: failed to create temporary file: {err}"))?; - let fd = unsafe { OwnedFd::from_raw_fd(raw_fd) }; - nix::unistd::write(&fd, history.as_bytes()) + write(fd, history.as_bytes()) .map_err(|err| format!("fc: failed to write to temporary file ({err})"))?; let editor = editor .unwrap_or_else(|| shell.environment.get_str_value("FCEDIT").unwrap_or("ed")) @@ -222,7 +221,7 @@ fn edit( if result != 0 { return Ok(()); } - let mut file = File::from(fd); + let mut file = unsafe { File::from_raw_fd(fd) }; let mut edited_contents = String::new(); file.read_to_string(&mut edited_contents) .map_err(|err| format!("fc: failed to read edited commands ({err})"))?; diff --git a/sh/builtin/fg.rs b/sh/builtin/fg.rs index 9640a710c..c3eb2b699 100644 --- a/sh/builtin/fg.rs +++ b/sh/builtin/fg.rs @@ -9,9 +9,9 @@ use crate::builtin::{skip_option_terminator, BuiltinResult, BuiltinUtility}; use crate::jobs::{parse_job_id, Job, JobId, JobState}; +use crate::os::signals::{kill, Signal}; use crate::shell::opened_files::OpenedFiles; use crate::shell::Shell; -use nix::sys::signal::kill; fn run_foreground_job( shell: &mut Shell, @@ -23,7 +23,7 @@ fn run_foreground_job( return Err(format!("fg: job {arg} already terminated")); } if job.state == JobState::Stopped { - kill(job.pid, nix::sys::signal::SIGCONT) + kill(job.pid, Some(Signal::SigCont)) .map_err(|err| format!("fg: failed to resume {arg} ({err})"))?; } opened_files.write_out(format!("{}\n", job.command)); diff --git a/sh/builtin/kill.rs b/sh/builtin/kill.rs index 13c93be79..e4a6d1b39 100644 --- a/sh/builtin/kill.rs +++ b/sh/builtin/kill.rs @@ -8,11 +8,9 @@ // use crate::builtin::{parse_pid, skip_option_terminator, BuiltinResult, BuiltinUtility}; +use crate::os::signals::{kill, Signal, SIGNALS}; use crate::shell::opened_files::OpenedFiles; use crate::shell::Shell; -use crate::signals::{Signal, SIGNALS}; -use nix::sys::signal::kill; -use nix::sys::signal::Signal as NixSignal; use std::str::FromStr; enum KillArgs<'a> { @@ -95,7 +93,7 @@ impl BuiltinUtility for Kill { KillArgs::SendSignal { signal, pids } => { for pid in pids { let pid = parse_pid(pid, shell).map_err(|err| format!("kill: {err}"))?; - kill(pid, signal.map(NixSignal::from)) + kill(pid, signal.map(|s| s.into())) .map_err(|err| format!("kill: failed to send signal ({})", err))?; } } diff --git a/sh/builtin/mod.rs b/sh/builtin/mod.rs index 0f75ff9b4..b7effca25 100644 --- a/sh/builtin/mod.rs +++ b/sh/builtin/mod.rs @@ -36,12 +36,10 @@ use crate::builtin::unalias::Unalias; use crate::builtin::unset::BuiltinUnset; use crate::builtin::wait::Wait; use crate::jobs::parse_job_id; +use crate::os::{OsError, Pid}; use crate::shell::environment::CannotModifyReadonly; use crate::shell::opened_files::OpenedFiles; use crate::shell::Shell; -use crate::utils::OsError; -use nix::libc::pid_t; -use nix::unistd::Pid; use std::fmt::{Display, Formatter}; pub mod alias; @@ -205,9 +203,9 @@ fn parse_pid(pid: &str, shell: &Shell) -> Result { .ok_or(format!("'{pid}' no such job"))?; Ok(job.pid) } else { - let raw_pid = pid - .parse::() + let pid = pid + .parse::() .map_err(|_| format!("'{pid}' is not a valid pid"))?; - Ok(Pid::from_raw(raw_pid)) + Ok(pid) } } diff --git a/sh/builtin/read.rs b/sh/builtin/read.rs index 6935ab748..ee86411ed 100644 --- a/sh/builtin/read.rs +++ b/sh/builtin/read.rs @@ -9,12 +9,13 @@ use crate::builtin::{BuiltinError, BuiltinResult, BuiltinUtility}; use crate::option_parser::OptionParser; +use crate::os::errno::Errno; +use crate::os::read; use crate::shell::opened_files::{OpenedFile, OpenedFiles, STDIN_FILENO}; use crate::shell::Shell; use crate::wordexp::expanded_word::ExpandedWord; use crate::wordexp::split_fields; use atty::Stream; -use nix::errno::Errno; use std::os::fd::{AsRawFd, RawFd}; use std::time::Duration; @@ -24,18 +25,18 @@ fn bytes_to_string(bytes: Vec) -> Result { fn read_byte_non_blocking(fd: RawFd) -> Result, BuiltinError> { let mut buffer = [0u8; 1]; - match nix::unistd::read(fd, &mut buffer) { + match read(fd, &mut buffer) { Ok(0) => Ok(None), Ok(1) => Ok(Some(buffer[0])), Ok(_) => unreachable!(), - Err(Errno::EAGAIN) => Ok(None), + Err(err) if err.errno == Errno::EAGAIN => Ok(None), Err(err) => Err(format!("read: failed to read from stdin ({err})").into()), } } fn read_byte(fd: RawFd) -> Result, BuiltinError> { let mut buffer = [0u8; 1]; - match nix::unistd::read(fd, &mut buffer) { + match read(fd, &mut buffer) { Ok(0) => Ok(None), Ok(1) => Ok(Some(buffer[0])), Ok(_) => unreachable!(), diff --git a/sh/builtin/times.rs b/sh/builtin/times.rs index 07ad93ac6..6817167d6 100644 --- a/sh/builtin/times.rs +++ b/sh/builtin/times.rs @@ -8,16 +8,26 @@ // use crate::builtin::{skip_option_terminator, BuiltinResult, SpecialBuiltinUtility}; +use crate::os::errno::get_current_errno_value; +use crate::os::LibcResult; use crate::shell::opened_files::OpenedFiles; use crate::shell::Shell; -use nix::libc::suseconds_t; -use nix::sys::resource::{getrusage, UsageWho}; + fn seconds_to_minutes(seconds: f32) -> i32 { seconds as i32 / 60 } -fn microseconds_to_seconds(micro: suseconds_t) -> f32 { - micro as f32 / 1000.0 +fn time_in_seconds(time: libc::timeval) -> f32 { + time.tv_sec as f32 + time.tv_usec as f32 / 1000.0 +} + +fn getusage(who: libc::c_int) -> LibcResult { + let mut usage = unsafe { std::mem::zeroed::() }; + let result = unsafe { libc::getrusage(who, &mut usage) }; + if result < 0 { + return Err(get_current_errno_value()); + } + Ok(usage) } pub struct Times; @@ -34,15 +44,15 @@ impl SpecialBuiltinUtility for Times { return Err("times: too many arguments".into()); } - let shell_times = getrusage(UsageWho::RUSAGE_SELF) + let shell_times = getusage(libc::RUSAGE_SELF) .map_err(|err| format!("times: failed to read user times ({err})"))?; - let children_times = getrusage(UsageWho::RUSAGE_CHILDREN) + let children_times = getusage(libc::RUSAGE_CHILDREN) .map_err(|err| format!("times: failed to read children times ({err})"))?; - let shell_user_s = microseconds_to_seconds(shell_times.user_time().tv_usec()); - let shell_system_s = microseconds_to_seconds(shell_times.system_time().tv_usec()); - let children_user_s = microseconds_to_seconds(children_times.user_time().tv_usec()); - let children_system_s = microseconds_to_seconds(children_times.system_time().tv_usec()); + let shell_user_s = time_in_seconds(shell_times.ru_utime); + let shell_system_s = time_in_seconds(shell_times.ru_stime); + let children_user_s = time_in_seconds(children_times.ru_utime); + let children_system_s = time_in_seconds(children_times.ru_stime); opened_files.write_out(format!( "{}m{}s {}m{}s\n{}m{}s {}m{}s\n", diff --git a/sh/builtin/trap.rs b/sh/builtin/trap.rs index 08996477b..cee58da41 100644 --- a/sh/builtin/trap.rs +++ b/sh/builtin/trap.rs @@ -8,9 +8,9 @@ // use crate::builtin::{BuiltinError, BuiltinResult, SpecialBuiltinUtility}; +use crate::os::signals::Signal; use crate::shell::opened_files::OpenedFiles; use crate::shell::Shell; -use crate::signals::Signal; use std::fmt::Display; use std::str::FromStr; diff --git a/sh/builtin/type_.rs b/sh/builtin/type_.rs index 1dc0480e0..1495be2d7 100644 --- a/sh/builtin/type_.rs +++ b/sh/builtin/type_.rs @@ -11,9 +11,9 @@ use crate::builtin::{ get_builtin_utility, get_special_builtin_utility, skip_option_terminator, BuiltinResult, BuiltinUtility, }; +use crate::os::find_command; use crate::shell::opened_files::OpenedFiles; use crate::shell::Shell; -use crate::utils::find_command; pub struct Type_; diff --git a/sh/builtin/ulimit.rs b/sh/builtin/ulimit.rs index dedda9dd0..4f26f98b7 100644 --- a/sh/builtin/ulimit.rs +++ b/sh/builtin/ulimit.rs @@ -9,10 +9,9 @@ use crate::builtin::{BuiltinResult, BuiltinUtility}; use crate::option_parser::OptionParser; +use crate::os::errno::{get_current_errno_value, Errno}; use crate::shell::opened_files::OpenedFiles; use crate::shell::Shell; -use nix::libc::{rlim_t, RLIM_INFINITY}; -use nix::sys::resource::Resource; use std::fmt::Display; #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -37,7 +36,7 @@ enum LimitType { #[derive(PartialEq, Eq, Clone, Copy)] enum LimitQuantity { Unlimited, - Number(rlim_t), + Number(libc::rlim_t), } impl LimitQuantity { @@ -46,23 +45,23 @@ impl LimitQuantity { Ok(LimitQuantity::Unlimited) } else { value - .parse::() + .parse::() .map(LimitQuantity::Number) .map_err(|_| ()) } } - fn new(limit: rlim_t, divisor: rlim_t) -> Self { - if limit == RLIM_INFINITY { + fn new(limit: libc::rlim_t, divisor: libc::rlim_t) -> Self { + if limit == libc::RLIM_INFINITY { LimitQuantity::Unlimited } else { LimitQuantity::Number(limit / divisor) } } - fn into_rlim_t(self, multiplier: rlim_t) -> Result { + fn into_rlim_t(self, multiplier: libc::rlim_t) -> Result { match self { - LimitQuantity::Unlimited => Ok(RLIM_INFINITY), + LimitQuantity::Unlimited => Ok(libc::RLIM_INFINITY), LimitQuantity::Number(value) => value .checked_mul(multiplier) .ok_or("ulimit: value too large".to_string()), @@ -70,10 +69,10 @@ impl LimitQuantity { } } -impl From for rlim_t { +impl From for libc::rlim_t { fn from(limit: LimitQuantity) -> Self { match limit { - LimitQuantity::Unlimited => RLIM_INFINITY, + LimitQuantity::Unlimited => libc::RLIM_INFINITY, LimitQuantity::Number(value) => value, } } @@ -180,39 +179,56 @@ impl UlimitArgs { } } -fn get_limit(resource: Resource, hard_limit: bool, divisor: rlim_t) -> LimitQuantity { - // from what I can tell, this function can never fail, so unwrap is safe - let (soft, hard) = nix::sys::resource::getrlimit(resource).unwrap(); +fn get_limits(resource: libc::c_int) -> libc::rlimit { + let mut limit = libc::rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + let result = unsafe { libc::getrlimit(resource as _, &mut limit) }; + if result < 0 { + panic!("invalid call to getrlimit") + } + limit +} + +fn get_limit(resource: libc::c_int, hard_limit: bool, divisor: libc::rlim_t) -> LimitQuantity { + let limits = get_limits(resource); if hard_limit { - LimitQuantity::new(hard, divisor) + LimitQuantity::new(limits.rlim_max, divisor) } else { - LimitQuantity::new(soft, divisor) + LimitQuantity::new(limits.rlim_cur, divisor) } } fn set_limit( - resource: Resource, + resource: libc::c_int, newlimit: LimitQuantity, limit_type: LimitType, - multiplier: rlim_t, + multiplier: libc::rlim_t, ) -> Result<(), String> { - fn map_err(err: nix::Error) -> String { - format!("ulimit: cannot modify limit ({err})") - } - - let (prev_soft, prev_hard) = nix::sys::resource::getrlimit(resource).unwrap(); + let mut limits = get_limits(resource); let newlimit = newlimit.into_rlim_t(multiplier)?; match limit_type { LimitType::Soft => { - nix::sys::resource::setrlimit(resource, newlimit, prev_hard).map_err(map_err) + limits.rlim_cur = newlimit; + limits.rlim_max = limits.rlim_max.max(newlimit); } LimitType::Hard => { - nix::sys::resource::setrlimit(resource, prev_soft, newlimit).map_err(map_err) + limits.rlim_max = newlimit; } LimitType::Both => { - nix::sys::resource::setrlimit(resource, newlimit, newlimit).map_err(map_err) + limits.rlim_cur = newlimit; + limits.rlim_max = newlimit; } } + let result = unsafe { libc::setrlimit(resource as _, &limits) }; + if result < 0 { + return Err(format!( + "ulimit: cannot modify limit ({})", + get_current_errno_value() + )); + } + Ok(()) } pub struct Ulimit; @@ -229,25 +245,30 @@ impl BuiltinUtility for Ulimit { if let Some(newlimit) = args.newlimit { match args.resource { ResourceLimit::Core => { - set_limit(Resource::RLIMIT_CORE, newlimit, args.limit, 512)?; + set_limit(libc::RLIMIT_CORE as libc::c_int, newlimit, args.limit, 512)?; } ResourceLimit::Data => { - set_limit(Resource::RLIMIT_DATA, newlimit, args.limit, 1024)?; + set_limit(libc::RLIMIT_DATA as libc::c_int, newlimit, args.limit, 1024)?; } ResourceLimit::FSize => { - set_limit(Resource::RLIMIT_FSIZE, newlimit, args.limit, 512)?; + set_limit(libc::RLIMIT_FSIZE as libc::c_int, newlimit, args.limit, 512)?; } ResourceLimit::NoFile => { - set_limit(Resource::RLIMIT_NOFILE, newlimit, args.limit, 1)?; + set_limit(libc::RLIMIT_NOFILE as libc::c_int, newlimit, args.limit, 1)?; } ResourceLimit::Stack => { - set_limit(Resource::RLIMIT_STACK, newlimit, args.limit, 1024)?; + set_limit( + libc::RLIMIT_STACK as libc::c_int, + newlimit, + args.limit, + 1024, + )?; } ResourceLimit::Cpu => { - set_limit(Resource::RLIMIT_CPU, newlimit, args.limit, 1)?; + set_limit(libc::RLIMIT_CPU as libc::c_int, newlimit, args.limit, 1)?; } ResourceLimit::As => { - set_limit(Resource::RLIMIT_AS, newlimit, args.limit, 1024)?; + set_limit(libc::RLIMIT_AS as libc::c_int, newlimit, args.limit, 1024)?; } ResourceLimit::All => unreachable!(), } @@ -258,43 +279,43 @@ impl BuiltinUtility for Ulimit { if args.resource == ResourceLimit::Core || all { opened_files.write_out(format!( "core file size (-c) [blocks] {}\n", - get_limit(Resource::RLIMIT_CORE, hard_limit, 512) + get_limit(libc::RLIMIT_CORE as libc::c_int, hard_limit, 512) )); } if args.resource == ResourceLimit::Data || all { opened_files.write_out(format!( "data seg size (-d) [KiB] {}\n", - get_limit(Resource::RLIMIT_DATA, hard_limit, 1024) + get_limit(libc::RLIMIT_DATA as libc::c_int, hard_limit, 1024) )); } if args.resource == ResourceLimit::FSize || all { opened_files.write_out(format!( "file size (-f) [blocks] {}\n", - get_limit(Resource::RLIMIT_FSIZE, hard_limit, 512) + get_limit(libc::RLIMIT_FSIZE as libc::c_int, hard_limit, 512) )); } if args.resource == ResourceLimit::NoFile || all { opened_files.write_out(format!( "open files (-n) {}\n", - get_limit(Resource::RLIMIT_NOFILE, hard_limit, 1) + get_limit(libc::RLIMIT_NOFILE as libc::c_int, hard_limit, 1) )); } if args.resource == ResourceLimit::Stack || all { opened_files.write_out(format!( "stack size (-s) [KiB] {}\n", - get_limit(Resource::RLIMIT_STACK, hard_limit, 1024) + get_limit(libc::RLIMIT_STACK as libc::c_int, hard_limit, 1024) )); } if args.resource == ResourceLimit::Cpu || all { opened_files.write_out(format!( "cpu time (-t) [seconds] {}\n", - get_limit(Resource::RLIMIT_CPU, hard_limit, 1) + get_limit(libc::RLIMIT_CPU as libc::c_int, hard_limit, 1) )); } if args.resource == ResourceLimit::As || all { opened_files.write_out(format!( "virtual memory (-v) [KiB] {}\n", - get_limit(Resource::RLIMIT_AS, hard_limit, 1024) + get_limit(libc::RLIMIT_AS as libc::c_int, hard_limit, 1024) )); } } diff --git a/sh/builtin/wait.rs b/sh/builtin/wait.rs index 0f1edd701..e9c812da6 100644 --- a/sh/builtin/wait.rs +++ b/sh/builtin/wait.rs @@ -8,10 +8,10 @@ // use crate::builtin::{parse_pid, skip_option_terminator, BuiltinResult, BuiltinUtility}; +use crate::os::errno::Errno; +use crate::os::Pid; use crate::shell::opened_files::OpenedFiles; use crate::shell::Shell; -use nix::errno::Errno; -use nix::unistd::Pid; fn wait_for_pid(pid: Pid, shell: &mut Shell) -> i32 { match shell.wait_child_process(pid) { diff --git a/sh/cli/terminal.rs b/sh/cli/terminal.rs index a2b5e2db2..aa099ff85 100644 --- a/sh/cli/terminal.rs +++ b/sh/cli/terminal.rs @@ -7,16 +7,37 @@ // SPDX-License-Identifier: MIT // +use crate::os::errno::get_current_errno_value; use atty::Stream; -use nix::sys::termios; -use nix::sys::termios::{LocalFlags, Termios}; use std::io; use std::io::Read; -use std::os::fd::AsFd; + +fn get_current_settings() -> libc::termios { + // using zeroed here because terminos has additional members on some systems + let mut settings = unsafe { std::mem::zeroed::() }; + let result = unsafe { libc::tcgetattr(libc::STDIN_FILENO, &mut settings) }; + if result < 0 { + panic!( + "failed to read terminal settings ({})", + get_current_errno_value() + ); + } + settings +} + +fn set_terminal_settings(settings: &libc::termios) { + let result = unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, settings) }; + if result < 0 { + panic!( + "failed to set terminal settings {}", + get_current_errno_value() + ); + } +} #[derive(Clone)] pub struct Terminal { - base_settings: Option, + base_settings: Option, } impl Terminal { @@ -24,45 +45,41 @@ impl Terminal { /// Panics if the current process is not attached to a terminal. pub fn set_nonblocking_no_echo(&self) { let mut termios = self.base_settings.clone().unwrap(); - termios.local_flags &= !(LocalFlags::ECHO | LocalFlags::ICANON); - termios.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 0; - termios.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0; - - termios::tcsetattr(io::stdin().as_fd(), termios::SetArg::TCSANOW, &termios).unwrap(); + termios.c_lflag &= !(libc::ECHO | libc::ICANON); + termios.c_cc[libc::VMIN] = 0; + termios.c_cc[libc::VTIME] = 0; + set_terminal_settings(&termios); } /// # Panic /// Panics if the current process is not attached to a terminal. pub fn set_nonblocking(&self) { let mut termios = self.base_settings.clone().unwrap(); - termios.local_flags &= !LocalFlags::ICANON; - termios.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 0; - termios.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0; - - termios::tcsetattr(io::stdin().as_fd(), termios::SetArg::TCSANOW, &termios).unwrap(); + termios.c_lflag &= !libc::ICANON; + termios.c_cc[libc::VMIN] = 0; + termios.c_cc[libc::VTIME] = 0; + set_terminal_settings(&termios); } /// Doesn't do anything if the current process is not attached to a terminal. - pub fn reset(&self) -> Termios { - let current = termios::tcgetattr(io::stdin().as_fd()).unwrap(); + pub fn reset(&self) -> libc::termios { + let current = get_current_settings(); if let Some(base_settings) = &self.base_settings { - termios::tcsetattr(io::stdin().as_fd(), termios::SetArg::TCSANOW, base_settings) - .unwrap(); + set_terminal_settings(base_settings); } current } - pub fn set(&self, settings: Termios) { - termios::tcsetattr(io::stdin().as_fd(), termios::SetArg::TCSANOW, &settings).unwrap(); + pub fn set(&self, settings: libc::termios) { + set_terminal_settings(&settings) } } impl Default for Terminal { fn default() -> Self { if is_attached_to_terminal() { - let base_settings = termios::tcgetattr(io::stdin().as_fd()).unwrap(); Terminal { - base_settings: Some(base_settings), + base_settings: Some(get_current_settings()), } } else { Terminal { diff --git a/sh/jobs.rs b/sh/jobs.rs index a15017995..d645e8a33 100644 --- a/sh/jobs.rs +++ b/sh/jobs.rs @@ -7,9 +7,8 @@ // SPDX-License-Identifier: MIT // -use crate::utils::{signal_to_exit_status, waitpid, OsResult}; -use nix::sys::wait::{WaitPidFlag, WaitStatus}; -use nix::unistd::Pid; +use crate::os::signals::{signal_to_exit_status, Signal}; +use crate::os::{waitpid, OsResult, Pid, WaitStatus}; use std::fmt::{Display, Formatter, Write}; #[derive(Clone, Copy, PartialEq, Eq)] @@ -31,7 +30,7 @@ impl Display for JobPosition { #[derive(Clone, Copy, PartialEq, Eq)] pub enum JobState { - Done(i32), + Done(libc::c_int), Running, Stopped, } @@ -145,13 +144,13 @@ impl JobManager { if let JobState::Done(_) = job.state { continue; } - match waitpid(job.pid, Some(WaitPidFlag::WNOHANG | WaitPidFlag::WUNTRACED))? { - WaitStatus::Exited(_, status) => { - job.state = JobState::Done(status); + match waitpid(job.pid, true, true)? { + WaitStatus::Exited { exit_status } => { + job.state = JobState::Done(exit_status); job.state_should_be_reported = true; } - WaitStatus::Signaled(_, signal, _) => { - if signal == nix::sys::signal::SIGTSTP { + WaitStatus::Signaled { signal, .. } => { + if signal == Signal::SigStop { job.state = JobState::Stopped; } else { job.state = JobState::Done(signal_to_exit_status(signal)); @@ -159,12 +158,10 @@ impl JobManager { job.state_should_be_reported = true; } WaitStatus::StillAlive => {} - WaitStatus::Stopped(_, _) => { + WaitStatus::Stopped { .. } => { job.state = JobState::Stopped; job.state_should_be_reported = true; } - // no other results possible without specifying flags in waitpid - _ => unreachable!(), } } Ok(()) diff --git a/sh/main.rs b/sh/main.rs index 9240c5d5b..224b62b6d 100644 --- a/sh/main.rs +++ b/sh/main.rs @@ -10,18 +10,18 @@ use crate::cli::args::{parse_args, ExecutionMode}; use crate::cli::terminal::is_attached_to_terminal; use crate::cli::{clear_line, set_cursor_pos}; +use crate::os::{getpgrp, is_process_in_foreground, tcsetpgrp}; use crate::shell::Shell; -use crate::signals::{ - handle_signal_ignore, handle_signal_write_to_signal_buffer, setup_signal_handling, Signal, -}; -use crate::utils::is_process_in_foreground; use cli::terminal::read_nonblocking_char; use cli::vi::{Action, ViEditor}; use gettextrs::{bind_textdomain_codeset, setlocale, textdomain, LocaleCategory}; +use os::signals::{ + handle_signal_ignore, handle_signal_write_to_signal_buffer, setup_signal_handling, Signal, +}; use std::error::Error; use std::io; use std::io::Write; -use std::os::fd::AsFd; +use std::os::fd::{AsFd, AsRawFd}; use std::time::Duration; mod builtin; @@ -29,10 +29,10 @@ mod cli; mod jobs; mod nonempty; mod option_parser; +mod os; mod parse; pub mod pattern; mod shell; -mod signals; mod utils; mod wordexp; @@ -228,8 +228,8 @@ fn vi_repl(shell: &mut Shell) { fn interactive_shell(shell: &mut Shell) { if is_process_in_foreground() { - let pgid = nix::unistd::getpgrp(); - nix::unistd::tcsetpgrp(io::stdin().as_fd(), pgid).unwrap(); + let pgid = getpgrp(); + tcsetpgrp(io::stdin().as_raw_fd(), pgid).unwrap(); } shell.terminal.set_nonblocking_no_echo(); unsafe { handle_signal_ignore(Signal::SigQuit) } diff --git a/sh/os/errno.rs b/sh/os/errno.rs new file mode 100644 index 000000000..b10db5430 --- /dev/null +++ b/sh/os/errno.rs @@ -0,0 +1,400 @@ +use std::fmt::{Debug, Display, Formatter}; + +#[derive(Clone, PartialEq, Eq)] +pub struct Errno { + // value is always a valid libc errno value + value: libc::c_int, +} + +impl Errno { + pub const E2BIG: Self = Self { value: libc::E2BIG }; + pub const EACCES: Self = Self { + value: libc::EACCES, + }; + pub const EADDRINUSE: Self = Self { + value: libc::EADDRINUSE, + }; + pub const EADDRNOTAVAIL: Self = Self { + value: libc::EADDRNOTAVAIL, + }; + pub const EAGAIN: Self = Self { + value: libc::EAGAIN, + }; + pub const EALREADY: Self = Self { + value: libc::EALREADY, + }; + pub const EBADF: Self = Self { value: libc::EBADF }; + pub const EBADMSG: Self = Self { + value: libc::EBADMSG, + }; + pub const EBUSY: Self = Self { value: libc::EBUSY }; + pub const ECANCELED: Self = Self { + value: libc::ECANCELED, + }; + pub const ECHILD: Self = Self { + value: libc::ECHILD, + }; + pub const ECONNABORTED: Self = Self { + value: libc::ECONNABORTED, + }; + pub const ECONNREFUSED: Self = Self { + value: libc::ECONNREFUSED, + }; + pub const ECONNRESET: Self = Self { + value: libc::ECONNRESET, + }; + pub const EDEADLK: Self = Self { + value: libc::EDEADLK, + }; + pub const EDESTADDRREQ: Self = Self { + value: libc::EDESTADDRREQ, + }; + pub const EDOM: Self = Self { value: libc::EDOM }; + pub const EDQUOT: Self = Self { + value: libc::EDQUOT, + }; + pub const EEXIST: Self = Self { + value: libc::EEXIST, + }; + pub const EFAULT: Self = Self { + value: libc::EFAULT, + }; + pub const EFBIG: Self = Self { value: libc::EFBIG }; + pub const EHOSTUNREACH: Self = Self { + value: libc::EHOSTUNREACH, + }; + pub const EIDRM: Self = Self { value: libc::EIDRM }; + pub const EILSEQ: Self = Self { + value: libc::EILSEQ, + }; + pub const EINPROGRESS: Self = Self { + value: libc::EINPROGRESS, + }; + pub const EINTR: Self = Self { value: libc::EINTR }; + pub const EINVAL: Self = Self { + value: libc::EINVAL, + }; + pub const EIO: Self = Self { value: libc::EIO }; + pub const EISCONN: Self = Self { + value: libc::EISCONN, + }; + pub const EISDIR: Self = Self { + value: libc::EISDIR, + }; + pub const ELOOP: Self = Self { value: libc::ELOOP }; + pub const EMFILE: Self = Self { + value: libc::EMFILE, + }; + pub const EMLINK: Self = Self { + value: libc::EMLINK, + }; + pub const EMSGSIZE: Self = Self { + value: libc::EMSGSIZE, + }; + pub const EMULTIHOP: Self = Self { + value: libc::EMULTIHOP, + }; + pub const ENAMETOOLONG: Self = Self { + value: libc::ENAMETOOLONG, + }; + pub const ENETDOWN: Self = Self { + value: libc::ENETDOWN, + }; + pub const ENETRESET: Self = Self { + value: libc::ENETRESET, + }; + pub const ENETUNREACH: Self = Self { + value: libc::ENETUNREACH, + }; + pub const ENFILE: Self = Self { + value: libc::ENFILE, + }; + pub const ENOBUFS: Self = Self { + value: libc::ENOBUFS, + }; + pub const ENODEV: Self = Self { + value: libc::ENODEV, + }; + pub const ENOENT: Self = Self { + value: libc::ENOENT, + }; + pub const ENOEXEC: Self = Self { + value: libc::ENOEXEC, + }; + pub const ENOLCK: Self = Self { + value: libc::ENOLCK, + }; + pub const ENOLINK: Self = Self { + value: libc::ENOLINK, + }; + pub const ENOMEM: Self = Self { + value: libc::ENOMEM, + }; + pub const ENOMSG: Self = Self { + value: libc::ENOMSG, + }; + pub const ENOPROTOOPT: Self = Self { + value: libc::ENOPROTOOPT, + }; + pub const ENOSPC: Self = Self { + value: libc::ENOSPC, + }; + pub const ENOSYS: Self = Self { + value: libc::ENOSYS, + }; + pub const ENOTCONN: Self = Self { + value: libc::ENOTCONN, + }; + pub const ENOTDIR: Self = Self { + value: libc::ENOTDIR, + }; + pub const ENOTEMPTY: Self = Self { + value: libc::ENOTEMPTY, + }; + pub const ENOTRECOVERABLE: Self = Self { + value: libc::ENOTRECOVERABLE, + }; + pub const ENOTSOCK: Self = Self { + value: libc::ENOTSOCK, + }; + pub const ENOTSUP: Self = Self { + value: libc::ENOTSUP, + }; + pub const ENOTTY: Self = Self { + value: libc::ENOTTY, + }; + pub const ENXIO: Self = Self { value: libc::ENXIO }; + pub const EOPNOTSUPP: Self = Self { + value: libc::EOPNOTSUPP, + }; + pub const EOVERFLOW: Self = Self { + value: libc::EOVERFLOW, + }; + pub const EOWNERDEAD: Self = Self { + value: libc::EOWNERDEAD, + }; + pub const EPERM: Self = Self { value: libc::EPERM }; + pub const EPIPE: Self = Self { value: libc::EPIPE }; + pub const EPROTO: Self = Self { + value: libc::EPROTO, + }; + pub const EPROTONOSUPPORT: Self = Self { + value: libc::EPROTONOSUPPORT, + }; + pub const EPROTOTYPE: Self = Self { + value: libc::EPROTOTYPE, + }; + pub const ERANGE: Self = Self { + value: libc::ERANGE, + }; + pub const EROFS: Self = Self { value: libc::EROFS }; + pub const ESOCKTNOSUPPORT: Self = Self { + value: libc::ESOCKTNOSUPPORT, + }; + pub const ESPIPE: Self = Self { + value: libc::ESPIPE, + }; + pub const ESRCH: Self = Self { value: libc::ESRCH }; + pub const ESTALE: Self = Self { + value: libc::ESTALE, + }; + pub const ETIMEDOUT: Self = Self { + value: libc::ETIMEDOUT, + }; + pub const ETXTBSY: Self = Self { + value: libc::ETXTBSY, + }; + pub const EWOULDBLOCK: Self = Self { + value: libc::EWOULDBLOCK, + }; + pub const EXDEV: Self = Self { value: libc::EXDEV }; + + pub fn into_raw(self) -> libc::c_int { + self.value + } +} + +impl Debug for Errno { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self.value { + libc::E2BIG => write!(f, "E2BIG: argument list too long"), + libc::EACCES => write!(f, "EACCESS: permission denied"), + libc::EADDRINUSE => write!(f, "EADDRINUSE: address in use"), + libc::EADDRNOTAVAIL => write!(f, "EADDRNOTAVAILABLE: address not available"), + libc::EAGAIN => write!(f, "EAGAIN: resource unavailable, try again"), + libc::EALREADY => write!(f, "EALREADY: connection already in progress"), + libc::EBADF => write!(f, "EBADF: bad file descriptor"), + libc::EBADMSG => write!(f, "EBADMSG: bad message"), + libc::EBUSY => write!(f, "EBUSY: device or resource busy"), + libc::ECANCELED => write!(f, "ECANCELED: operation canceled"), + libc::ECHILD => write!(f, "ECHILD: no child processes"), + libc::ECONNABORTED => write!(f, "ECONNABORTED: connection aborted"), + libc::ECONNREFUSED => write!(f, "ECONNREFUSED: connection refused"), + libc::ECONNRESET => write!(f, "ECONNRESET: connection reset"), + libc::EDEADLK => write!(f, "EDEADLK: resource deadlock would occur"), + libc::EDESTADDRREQ => write!(f, "EDESTADDRREQ: destination address required"), + libc::EDOM => write!(f, "EDOM: mathematics argument out of domain of function"), + libc::EDQUOT => write!(f, "EDQUOT: disk quota exceeded"), + libc::EEXIST => write!(f, "EEXIST: file exists"), + libc::EFAULT => write!(f, "EFAULT: bad address"), + libc::EFBIG => write!(f, "EFBIG: file too large"), + libc::EHOSTUNREACH => write!(f, "EHOSTUNREACH: host is unreachable"), + libc::EIDRM => write!(f, "EIDRM: identifier removed"), + libc::EILSEQ => write!(f, "EILSEQ: illegal byte sequence"), + libc::EINPROGRESS => write!(f, "EINPROGRESS: operation in progress"), + libc::EINTR => write!(f, "EINTR: interrupted function"), + libc::EINVAL => write!(f, "EINVAL: invalid argument"), + libc::EIO => write!(f, "EIO: I/O error"), + libc::EISCONN => write!(f, "EISCONN: socket is connected"), + libc::EISDIR => write!(f, "EISDIR: is a directory"), + libc::ELOOP => write!(f, "ELOOP: too many levels of symbolic links"), + libc::EMFILE => write!(f, "EMFILE: file descriptor value too large"), + libc::EMLINK => write!(f, "EMLINK: too many hard links"), + libc::EMSGSIZE => write!(f, "EMSGSIZE: message too large"), + libc::EMULTIHOP => write!(f, "EMULTIHOP: multihop attempted"), + libc::ENAMETOOLONG => write!(f, "ENAMETOOLONG: filename too long"), + libc::ENETDOWN => write!(f, "ENETDOWN: network is down"), + libc::ENETRESET => write!(f, "ENETRESET: connection aborted by network"), + libc::ENETUNREACH => write!(f, "ENETUNREACH: network unreachable"), + libc::ENFILE => write!(f, "ENFILE: too many files open in system"), + libc::ENOBUFS => write!(f, "ENOBUFS: no buffer space available"), + libc::ENODEV => write!(f, "ENODEV: no such device"), + libc::ENOENT => write!(f, "ENOENT: no such file or directory"), + libc::ENOEXEC => write!(f, "ENOEXEC: executable file format error"), + libc::ENOLCK => write!(f, "ENOLCK: no locks available"), + libc::ENOLINK => write!(f, "ENOLINK: link has been severed"), + libc::ENOMEM => write!(f, "ENOMEM: not enough space"), + libc::ENOMSG => write!(f, "ENOMSG: no message of the desired type"), + libc::ENOPROTOOPT => write!(f, "ENOPROTOOPT: protocol not available"), + libc::ENOSPC => write!(f, "ENOSPC: no space left on device"), + libc::ENOSYS => write!(f, "ENOSYS: functionality not supported"), + libc::ENOTCONN => write!(f, "ENOTCONN: the socket is not connected"), + libc::ENOTDIR => write!( + f, + "ENOTDIR: not a directory or a symbolic link to a directory" + ), + libc::ENOTEMPTY => write!(f, "ENOTEMPTY: directory not empty"), + libc::ENOTRECOVERABLE => write!(f, "ENOTRECOVERABLE: state not recoverable"), + libc::ENOTSOCK => write!(f, "ENOTSOCK: not a socket"), + libc::ENOTSUP => write!(f, "ENOTSUP: not supported"), + libc::ENOTTY => write!(f, "ENOTTY: inappropriate I/O control operation"), + libc::ENXIO => write!(f, "ENXIO: no such device or address"), + libc::EOPNOTSUPP => write!(f, "EOPNOTSUPP: operation not supported on socket"), + libc::EOVERFLOW => write!(f, "EOVERFLOW: value too large to be stored in data type"), + libc::EOWNERDEAD => write!(f, "EOWNERDEAD: previous owner died"), + libc::EPERM => write!(f, "EPERM: operation not permitted"), + libc::EPIPE => write!(f, "EPIPE: broken pipe"), + libc::EPROTO => write!(f, "EPROTO: protocol error"), + libc::EPROTONOSUPPORT => write!(f, "EPROTONOSUPPORT: protocol not supported"), + libc::EPROTOTYPE => write!(f, "EPROTOTYPE: protocol wrong type for socket"), + libc::ERANGE => write!(f, "ERANGE: result too large"), + libc::EROFS => write!(f, "EROFS: read-only file system"), + libc::ESOCKTNOSUPPORT => write!(f, "ESOCKTNOSUPPORT: socket type not supported"), + libc::ESPIPE => write!(f, "ESPIPE: invalid seek"), + libc::ESRCH => write!(f, "ESRCH: no such process"), + libc::ESTALE => write!(f, "ESTALE: stale file handle"), + libc::ETIMEDOUT => write!(f, "ETIMEDOUT: connection timed out"), + libc::ETXTBSY => write!(f, "ETXTBSY: text file busy"), + libc::EWOULDBLOCK => write!(f, "EWOULDBLOCK: operation would block"), + libc::EXDEV => write!(f, "EXDEV: improper hard link"), + other => unreachable!("invalid errno value {}", other), + } + } +} + +impl Display for Errno { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl TryFrom for Errno { + type Error = (); + fn try_from(value: libc::c_int) -> Result { + match value { + libc::E2BIG => Ok(Self::E2BIG), + libc::EACCES => Ok(Self::EACCES), + libc::EADDRINUSE => Ok(Self::EADDRINUSE), + libc::EADDRNOTAVAIL => Ok(Self::EADDRNOTAVAIL), + libc::EAGAIN => Ok(Self::EAGAIN), + libc::EALREADY => Ok(Self::EALREADY), + libc::EBADF => Ok(Self::EBADF), + libc::EBADMSG => Ok(Self::EBADMSG), + libc::EBUSY => Ok(Self::EBUSY), + libc::ECANCELED => Ok(Self::ECANCELED), + libc::ECHILD => Ok(Self::ECHILD), + libc::ECONNABORTED => Ok(Self::ECONNABORTED), + libc::ECONNREFUSED => Ok(Self::ECONNREFUSED), + libc::ECONNRESET => Ok(Self::ECONNRESET), + libc::EDEADLK => Ok(Self::EDEADLK), + libc::EDESTADDRREQ => Ok(Self::EDESTADDRREQ), + libc::EDOM => Ok(Self::EDOM), + libc::EDQUOT => Ok(Self::EDQUOT), + libc::EEXIST => Ok(Self::EEXIST), + libc::EFAULT => Ok(Self::EFAULT), + libc::EFBIG => Ok(Self::EFBIG), + libc::EHOSTUNREACH => Ok(Self::EHOSTUNREACH), + libc::EIDRM => Ok(Self::EIDRM), + libc::EILSEQ => Ok(Self::EILSEQ), + libc::EINPROGRESS => Ok(Self::EINPROGRESS), + libc::EINTR => Ok(Self::EINTR), + libc::EINVAL => Ok(Self::EINVAL), + libc::EIO => Ok(Self::EIO), + libc::EISCONN => Ok(Self::EISCONN), + libc::EISDIR => Ok(Self::EISDIR), + libc::ELOOP => Ok(Self::ELOOP), + libc::EMFILE => Ok(Self::EMFILE), + libc::EMLINK => Ok(Self::EMLINK), + libc::EMSGSIZE => Ok(Self::EMSGSIZE), + libc::EMULTIHOP => Ok(Self::EMULTIHOP), + libc::ENAMETOOLONG => Ok(Self::ENAMETOOLONG), + libc::ENETDOWN => Ok(Self::ENETDOWN), + libc::ENETRESET => Ok(Self::ENETRESET), + libc::ENETUNREACH => Ok(Self::ENETUNREACH), + libc::ENFILE => Ok(Self::ENFILE), + libc::ENOBUFS => Ok(Self::ENOBUFS), + libc::ENODEV => Ok(Self::ENODEV), + libc::ENOENT => Ok(Self::ENOENT), + libc::ENOEXEC => Ok(Self::ENOEXEC), + libc::ENOLCK => Ok(Self::ENOLCK), + libc::ENOLINK => Ok(Self::ENOLINK), + libc::ENOMEM => Ok(Self::ENOMEM), + libc::ENOMSG => Ok(Self::ENOMSG), + libc::ENOPROTOOPT => Ok(Self::ENOPROTOOPT), + libc::ENOSPC => Ok(Self::ENOSPC), + libc::ENOSYS => Ok(Self::ENOSYS), + libc::ENOTCONN => Ok(Self::ENOTCONN), + libc::ENOTDIR => Ok(Self::ENOTDIR), + libc::ENOTEMPTY => Ok(Self::ENOTEMPTY), + libc::ENOTRECOVERABLE => Ok(Self::ENOTRECOVERABLE), + libc::ENOTSOCK => Ok(Self::ENOTSOCK), + libc::ENOTSUP => Ok(Self::ENOTSUP), + libc::ENOTTY => Ok(Self::ENOTTY), + libc::ENXIO => Ok(Self::ENXIO), + libc::EOPNOTSUPP => Ok(Self::EOPNOTSUPP), + libc::EOVERFLOW => Ok(Self::EOVERFLOW), + libc::EOWNERDEAD => Ok(Self::EOWNERDEAD), + libc::EPERM => Ok(Self::EPERM), + libc::EPIPE => Ok(Self::EPIPE), + libc::EPROTO => Ok(Self::EPROTO), + libc::EPROTONOSUPPORT => Ok(Self::EPROTONOSUPPORT), + libc::EPROTOTYPE => Ok(Self::EPROTOTYPE), + libc::ERANGE => Ok(Self::ERANGE), + libc::EROFS => Ok(Self::EROFS), + libc::ESOCKTNOSUPPORT => Ok(Self::ESOCKTNOSUPPORT), + libc::ESPIPE => Ok(Self::ESPIPE), + libc::ESRCH => Ok(Self::ESRCH), + libc::ESTALE => Ok(Self::ESTALE), + libc::ETIMEDOUT => Ok(Self::ETIMEDOUT), + libc::ETXTBSY => Ok(Self::ETXTBSY), + libc::EWOULDBLOCK => Ok(Self::EWOULDBLOCK), + libc::EXDEV => Ok(Self::EXDEV), + _ => Err(()), + } + } +} + +pub fn get_current_errno_value() -> Errno { + let errno = unsafe { *libc::__errno_location() }; + errno.try_into().expect("invalid errno value") +} diff --git a/sh/os/mod.rs b/sh/os/mod.rs new file mode 100644 index 000000000..6f3e3ad99 --- /dev/null +++ b/sh/os/mod.rs @@ -0,0 +1,301 @@ +use crate::os::errno::{get_current_errno_value, Errno}; +use crate::os::signals::Signal; +use crate::shell::environment::Environment; +use crate::shell::opened_files::{OpenedFile, OpenedFiles}; +use std::convert::Infallible; +use std::ffi::{CString, OsStr, OsString}; +use std::fmt::{Display, Formatter}; +use std::io; +use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd}; +use std::os::unix::ffi::{OsStrExt, OsStringExt}; +use std::path::PathBuf; + +pub mod errno; +pub mod signals; + +pub const DEFAULT_PATH: &str = "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:."; + +pub type Pid = libc::pid_t; + +#[derive(Clone, Debug)] +pub struct OsError { + pub command: &'static str, + pub errno: Errno, +} + +impl OsError { + pub fn from_current_errno(command: &'static str) -> Self { + Self { + command, + errno: get_current_errno_value(), + } + } +} + +impl Display for OsError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "sh: internal call to {} failed ({})", + self.command, self.errno + ) + } +} + +pub type OsResult = Result; +pub type LibcResult = Result; + +pub fn getpgrp() -> Pid { + // always successful + unsafe { libc::getpgrp() } +} + +pub fn write(fd: RawFd, bytes: &[u8]) -> OsResult { + let bytes_written = unsafe { + libc::write( + fd as libc::c_int, + bytes.as_ptr() as *const libc::c_void, + bytes.len(), + ) + }; + if bytes_written < 0 { + return Err(OsError::from_current_errno("write")); + } + Ok(bytes_written as usize) +} + +pub fn read(fd: RawFd, buf: &mut [u8]) -> OsResult { + let bytes_read = unsafe { libc::read(fd, buf.as_ptr() as *mut libc::c_void, buf.len()) }; + if bytes_read < 0 { + return Err(OsError::from_current_errno("read")); + } + Ok(bytes_read as usize) +} + +pub enum ForkResult { + Child, + Parent { child: Pid }, +} + +pub fn fork() -> OsResult { + // fork in general is not safe for multithreaded programs, but all code in this module is single + // threaded, so this is safe + let fork_result = unsafe { libc::fork() }; + if fork_result < 0 { + Err(OsError::from_current_errno("fork")) + } else if fork_result == 0 { + Ok(ForkResult::Child) + } else { + Ok(ForkResult::Parent { child: fork_result }) + } +} + +pub fn pipe() -> OsResult<(OwnedFd, OwnedFd)> { + let mut descriptors = [libc::c_int::default(); 2]; + let pipe_result = unsafe { libc::pipe(descriptors.as_mut_ptr()) }; + if pipe_result < 0 { + return Err(OsError::from_current_errno("pipe")); + } + assert_eq!(pipe_result, 0, "invalid result for libc::pipe"); + let fd0 = unsafe { OwnedFd::from_raw_fd(descriptors[0]) }; + let fd1 = unsafe { OwnedFd::from_raw_fd(descriptors[1]) }; + Ok((fd0, fd1)) +} + +pub fn dup2(old_fd: RawFd, new_fd: RawFd) -> OsResult { + let dup_result = unsafe { libc::dup2(old_fd, new_fd) }; + if dup_result < 0 { + return Err(OsError::from_current_errno("dup2")); + } + Ok(dup_result) +} + +pub enum WaitStatus { + Exited { exit_status: libc::c_int }, + Signaled { signal: Signal, core_dumped: bool }, + Stopped { signal: Signal }, + StillAlive, +} + +pub fn waitpid(pid: Pid, no_hang: bool, untraced: bool) -> OsResult { + let mut status = 0; + let mut options = 0; + if no_hang { + options |= libc::WNOHANG; + } + if untraced { + options |= libc::WUNTRACED; + } + let wait_result = unsafe { libc::waitpid(pid, &mut status, options) }; + if wait_result < 0 { + Err(OsError::from_current_errno("waitpid")) + } else if libc::WIFEXITED(status) { + let exit_status = libc::WEXITSTATUS(status); + Ok(WaitStatus::Exited { exit_status }) + } else if libc::WIFSIGNALED(status) { + let raw_signal = libc::WTERMSIG(status); + let core_dumped = libc::WCOREDUMP(status); + Ok(WaitStatus::Signaled { + signal: Signal::try_from(raw_signal).expect("invalid signal"), + core_dumped, + }) + } else if libc::WIFSTOPPED(status) { + let raw_stop_signal = libc::WSTOPSIG(status); + Ok(WaitStatus::Stopped { + signal: Signal::try_from(raw_stop_signal).expect("invalid signal"), + }) + } else { + Ok(WaitStatus::StillAlive) + } +} + +pub fn close(fd: RawFd) -> OsResult<()> { + let close_result = unsafe { libc::close(fd) }; + if close_result < 0 { + return Err(OsError::from_current_errno("close")); + } + Ok(()) +} + +pub enum ExecError { + OsError(OsError), + CannotExecute(Errno), +} + +impl From for ExecError { + fn from(value: OsError) -> Self { + Self::OsError(value) + } +} + +pub fn exec( + command: OsString, + args: &[String], + opened_files: &OpenedFiles, + env: &Environment, +) -> Result { + for (id, file) in &opened_files.opened_files { + let dest = *id as i32; + let src = match file { + OpenedFile::Stdin => libc::STDIN_FILENO, + OpenedFile::Stdout => libc::STDOUT_FILENO, + OpenedFile::Stderr => libc::STDERR_FILENO, + OpenedFile::ReadFile(file) + | OpenedFile::WriteFile(file) + | OpenedFile::ReadWriteFile(file) => file.as_raw_fd(), + OpenedFile::HereDocument(contents) => { + let (read_pipe, write_pipe) = pipe()?; + write(write_pipe.as_raw_fd(), contents.as_bytes())?; + dup2(read_pipe.as_raw_fd(), dest)?; + continue; + } + }; + dup2(src, dest)?; + } + let command = CString::new(command.into_vec()).unwrap(); + let args = args + .iter() + .map(|s| CString::new(s.as_str()).unwrap()) + .collect::>(); + let args_ptr_vec = args.iter().map(|s| s.as_ptr()).collect::>(); + let env = env + .exported() + .map(|(name, value)| CString::new(format!("{name}={value}")).unwrap()) + .collect::>(); + let env_ptr_vec = env.iter().map(|s| s.as_ptr()).collect::>(); + let exit_status = unsafe { + libc::execve( + command.as_ptr(), + args_ptr_vec.as_ptr(), + env_ptr_vec.as_ptr(), + ) + }; + assert_eq!(exit_status, -1, "invalid return status from execve"); + Err(ExecError::CannotExecute(get_current_errno_value())) +} + +pub fn tcgetpgrp(fd: RawFd) -> OsResult { + let group_id = unsafe { libc::tcgetpgrp(fd) }; + if group_id < 0 { + return Err(OsError::from_current_errno("tcgetpgrp")); + } + Ok(group_id) +} + +pub fn tcsetpgrp(fd: RawFd, pgid: Pid) -> OsResult<()> { + let result = unsafe { libc::tcsetpgrp(fd, pgid) }; + if result < 0 { + return Err(OsError::from_current_errno("tcsetpgrp")); + } + Ok(()) +} + +pub fn is_process_in_foreground() -> bool { + if let Ok(pgid) = tcgetpgrp(io::stdin().as_raw_fd()) { + pgid == getpgrp() + } else { + false + } +} + +pub fn find_in_path(command: &str, env_path: &str) -> Option { + for path in env_path.split(':') { + let mut command_path = PathBuf::from(path); + command_path.push(command); + if command_path.is_file() { + return Some(command_path.into_os_string()); + } + } + None +} + +pub fn find_command(command: &str, env_path: &str) -> Option { + if command.contains('/') { + let path = PathBuf::from(command); + if path.exists() { + Some(path.into_os_string()) + } else { + None + } + } else { + find_in_path(command, env_path) + } +} + +pub fn setpgid(pid: Pid, pgid: Pid) -> OsResult<()> { + let result = unsafe { libc::setpgid(pid, pgid) }; + if result < 0 { + return Err(OsError::from_current_errno("setpgid")); + } + Ok(()) +} + +pub fn getpgid(pid: Pid) -> OsResult { + let pid = unsafe { libc::getpgid(pid) }; + if pid < 0 { + return Err(OsError::from_current_errno("getpgid")); + } + Ok(pid) +} + +pub fn chdir(path: &OsStr) -> LibcResult<()> { + let path = CString::new(path.as_bytes()).expect("path contains null characters"); + let result = unsafe { libc::chdir(path.as_ptr()) }; + if result < 0 { + return Err(get_current_errno_value()); + } + Ok(()) +} + +pub fn mkstemp(template: &str) -> LibcResult<(RawFd, PathBuf)> { + let template_cstr = + CString::new(template).expect("template for mkstemp contained a null character"); + let mut template_cstr = template_cstr.into_bytes_with_nul(); + let fd = unsafe { libc::mkstemp(template_cstr.as_mut_ptr() as *mut libc::c_char) }; + if fd < 0 { + return Err(get_current_errno_value()); + } + // remove null terminator + template_cstr.pop(); + Ok((fd, PathBuf::from(OsString::from_vec(template_cstr)))) +} diff --git a/sh/signals.rs b/sh/os/signals.rs similarity index 70% rename from sh/signals.rs rename to sh/os/signals.rs index 26dc86081..ac4833249 100644 --- a/sh/signals.rs +++ b/sh/os/signals.rs @@ -8,12 +8,10 @@ // use crate::builtin::trap::TrapAction; -use nix::errno::Errno; -use nix::libc; -use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal as NixSignal}; -use nix::unistd::{read, write}; +use crate::os::errno::{get_current_errno_value, Errno}; +use crate::os::{pipe, read, write, OsError, OsResult, Pid}; use std::fmt::{Display, Formatter}; -use std::os::fd::{AsRawFd, BorrowedFd, IntoRawFd, RawFd}; +use std::os::fd::{AsRawFd, IntoRawFd, RawFd}; use std::str::FromStr; #[derive(Clone, Copy, PartialEq, Eq)] @@ -120,36 +118,36 @@ impl FromStr for Signal { } } -impl From for NixSignal { - fn from(value: Signal) -> Self { - match value { - Signal::SigHup => NixSignal::SIGHUP, - Signal::SigInt => NixSignal::SIGINT, - Signal::SigQuit => NixSignal::SIGQUIT, - Signal::SigIll => NixSignal::SIGILL, - Signal::SigTrap => NixSignal::SIGTRAP, - Signal::SigAbrt => NixSignal::SIGABRT, - Signal::SigBus => NixSignal::SIGBUS, - Signal::SigFpe => NixSignal::SIGFPE, - Signal::SigKill => NixSignal::SIGKILL, - Signal::SigUsr1 => NixSignal::SIGUSR1, - Signal::SigSegv => NixSignal::SIGSEGV, - Signal::SigUsr2 => NixSignal::SIGUSR2, - Signal::SigPipe => NixSignal::SIGPIPE, - Signal::SigAlrm => NixSignal::SIGALRM, - Signal::SigTerm => NixSignal::SIGTERM, - Signal::SigChld => NixSignal::SIGCHLD, - Signal::SigCont => NixSignal::SIGCONT, - Signal::SigStop => NixSignal::SIGSTOP, - Signal::SigTstp => NixSignal::SIGTSTP, - Signal::SigTtin => NixSignal::SIGTTIN, - Signal::SigTtou => NixSignal::SIGTTOU, - Signal::SigUrg => NixSignal::SIGURG, - Signal::SigXcpu => NixSignal::SIGXCPU, - Signal::SigXfsz => NixSignal::SIGXFSZ, - Signal::SigVtalrm => NixSignal::SIGVTALRM, - Signal::SigProf => NixSignal::SIGPROF, - Signal::SigSys => NixSignal::SIGSYS, +impl Into for Signal { + fn into(self) -> i32 { + match self { + Signal::SigHup => libc::SIGHUP, + Signal::SigInt => libc::SIGINT, + Signal::SigQuit => libc::SIGQUIT, + Signal::SigIll => libc::SIGILL, + Signal::SigTrap => libc::SIGTRAP, + Signal::SigAbrt => libc::SIGABRT, + Signal::SigBus => libc::SIGBUS, + Signal::SigFpe => libc::SIGFPE, + Signal::SigKill => libc::SIGKILL, + Signal::SigUsr1 => libc::SIGUSR1, + Signal::SigSegv => libc::SIGSEGV, + Signal::SigUsr2 => libc::SIGUSR2, + Signal::SigPipe => libc::SIGPIPE, + Signal::SigAlrm => libc::SIGALRM, + Signal::SigTerm => libc::SIGTERM, + Signal::SigChld => libc::SIGCHLD, + Signal::SigCont => libc::SIGCONT, + Signal::SigStop => libc::SIGSTOP, + Signal::SigTstp => libc::SIGTSTP, + Signal::SigTtin => libc::SIGTTIN, + Signal::SigTtou => libc::SIGTTOU, + Signal::SigUrg => libc::SIGURG, + Signal::SigXcpu => libc::SIGXCPU, + Signal::SigXfsz => libc::SIGXFSZ, + Signal::SigVtalrm => libc::SIGVTALRM, + Signal::SigProf => libc::SIGPROF, + Signal::SigSys => libc::SIGSYS, Signal::Count => unreachable!("invalid trap condition"), } } @@ -192,41 +190,6 @@ impl TryFrom for Signal { } } -impl From for Signal { - fn from(value: NixSignal) -> Self { - match value { - NixSignal::SIGHUP => Signal::SigHup, - NixSignal::SIGINT => Signal::SigInt, - NixSignal::SIGQUIT => Signal::SigQuit, - NixSignal::SIGILL => Signal::SigIll, - NixSignal::SIGTRAP => Signal::SigTrap, - NixSignal::SIGABRT => Signal::SigAbrt, - NixSignal::SIGBUS => Signal::SigBus, - NixSignal::SIGFPE => Signal::SigFpe, - NixSignal::SIGKILL => Signal::SigKill, - NixSignal::SIGUSR1 => Signal::SigUsr1, - NixSignal::SIGSEGV => Signal::SigSegv, - NixSignal::SIGUSR2 => Signal::SigUsr2, - NixSignal::SIGPIPE => Signal::SigPipe, - NixSignal::SIGALRM => Signal::SigAlrm, - NixSignal::SIGTERM => Signal::SigTerm, - NixSignal::SIGCHLD => Signal::SigChld, - NixSignal::SIGCONT => Signal::SigCont, - NixSignal::SIGSTOP => Signal::SigStop, - NixSignal::SIGTSTP => Signal::SigTstp, - NixSignal::SIGTTIN => Signal::SigTtin, - NixSignal::SIGTTOU => Signal::SigTtou, - NixSignal::SIGURG => Signal::SigUrg, - NixSignal::SIGXCPU => Signal::SigXcpu, - NixSignal::SIGXFSZ => Signal::SigXfsz, - NixSignal::SIGVTALRM => Signal::SigVtalrm, - NixSignal::SIGPROF => Signal::SigProf, - NixSignal::SIGSYS => Signal::SigSys, - _ => unreachable!("invalid signal"), - } - } -} - pub const SIGNALS: &[Signal] = &[ Signal::SigHup, Signal::SigInt, @@ -263,19 +226,21 @@ static mut SIGNAL_READ: Option = None; extern "C" fn write_signal_to_buffer(signal: libc::c_int) { // SIGNAL_WRITE is never modified after the initial // setup, and is a valid file descriptor, so this is safe - let fd = unsafe { BorrowedFd::borrow_raw(SIGNAL_WRITE.unwrap()) }; + let fd = unsafe { SIGNAL_WRITE.unwrap() }; write(fd, &signal.to_ne_bytes()).expect("failed to write to signal buffer"); } /// # Safety /// cannot be called by multiple threads pub unsafe fn setup_signal_handling() { - let (read_pipe, write_pipe) = nix::unistd::pipe().expect("could not create signal buffer pipe"); - nix::fcntl::fcntl( - read_pipe.as_raw_fd(), - nix::fcntl::FcntlArg::F_SETFL(nix::fcntl::OFlag::O_NONBLOCK), - ) - .expect("signal buffer pipe could not be set as non-blocking"); + let (read_pipe, write_pipe) = pipe().expect("could not create signal buffer pipe"); + let result = unsafe { libc::fcntl(read_pipe.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK) }; + if result < 0 { + panic!( + "failed initialize async buffer for signal handling ({})", + get_current_errno_value() + ); + } SIGNAL_WRITE = Some(write_pipe.into_raw_fd()); SIGNAL_READ = Some(read_pipe.into_raw_fd()); } @@ -287,7 +252,7 @@ fn get_pending_signal() -> Option { let mut buf = [0u8; size_of::()]; match read(fd, &mut buf) { Err(err) => { - if err == Errno::EAGAIN { + if err.errno == Errno::EAGAIN { None } else { panic!("failed to write to signal pipe ({err})"); @@ -304,32 +269,32 @@ fn get_pending_signal() -> Option { } } +unsafe fn handle_signal(signal: Signal, handler: libc::sighandler_t) { + let mut empty_sigset: libc::sigset_t = std::mem::zeroed::(); + // never fails + libc::sigemptyset(&mut empty_sigset); + let action = libc::sigaction { + sa_sigaction: handler, + sa_mask: empty_sigset, + sa_flags: 0, + sa_restorer: None, + }; + let result = libc::sigaction(signal.into(), &action, std::ptr::null_mut()); + if result < 0 { + panic!("failed to set signal handler") + } +} + pub unsafe fn handle_signal_ignore(signal: Signal) { - sigaction( - signal.into(), - &SigAction::new(SigHandler::SigIgn, SaFlags::empty(), SigSet::empty()), - ) - .unwrap(); + handle_signal(signal, libc::SIG_IGN); } pub unsafe fn handle_signal_default(signal: Signal) { - sigaction( - signal.into(), - &SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty()), - ) - .unwrap(); + handle_signal(signal, libc::SIG_DFL); } pub unsafe fn handle_signal_write_to_signal_buffer(signal: Signal) { - sigaction( - signal.into(), - &SigAction::new( - SigHandler::Handler(write_signal_to_buffer), - SaFlags::empty(), - SigSet::empty(), - ), - ) - .unwrap(); + handle_signal(signal, &write_signal_to_buffer as *const _ as libc::size_t) } #[derive(Clone)] @@ -360,11 +325,7 @@ impl SignalManager { // set at startup for an interactive shell, but its not registered // as a trap action TrapAction::Commands(_) | TrapAction::Default => unsafe { - sigaction( - signal.into(), - &SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty()), - ) - .unwrap(); + unsafe { handle_signal_default(signal) }; *action = TrapAction::Default; }, TrapAction::Ignore => {} @@ -423,3 +384,15 @@ impl SignalManager { self.sigint_count } } + +pub fn signal_to_exit_status(signal: Signal) -> libc::c_int { + 128 + signal as libc::c_int +} + +pub fn kill(pid: Pid, signal: Option) -> OsResult<()> { + let result = unsafe { libc::kill(pid, signal.map(|s| s.into()).unwrap_or(0)) }; + if result < 0 { + return Err(OsError::from_current_errno("kill")); + } + Ok(()) +} diff --git a/sh/pattern/regex.rs b/sh/pattern/regex.rs index 5d80956f3..3ffd7a7b0 100644 --- a/sh/pattern/regex.rs +++ b/sh/pattern/regex.rs @@ -9,7 +9,6 @@ use crate::pattern::parse::{BracketExpression, BracketItem, PatternItem, RangeEndpoint}; use core::fmt; -use nix::libc; use std::ffi::{CStr, CString}; use std::fmt::{Formatter, Write}; use std::ptr; diff --git a/sh/shell/mod.rs b/sh/shell/mod.rs index a484b61c9..36fa12221 100644 --- a/sh/shell/mod.rs +++ b/sh/shell/mod.rs @@ -15,6 +15,12 @@ use crate::builtin::{ use crate::cli::terminal::Terminal; use crate::jobs::{JobManager, JobState}; use crate::nonempty::NonEmpty; +use crate::os::errno::Errno; +use crate::os::signals::{kill, signal_to_exit_status, Signal, SignalManager}; +use crate::os::{ + close, dup2, exec, find_command, fork, getpgid, getpgrp, is_process_in_foreground, pipe, + setpgid, tcsetpgrp, waitpid, ExecError, ForkResult, OsError, OsResult, Pid, WaitStatus, +}; use crate::parse::command::{ Assignment, CaseItem, Command, CommandType, CompleteCommand, CompoundCommand, Conjunction, FunctionDefinition, If, LogicalOp, Name, Pipeline, Redirection, SimpleCommand, @@ -26,28 +32,17 @@ use crate::parse::{AliasTable, ParserError}; use crate::shell::environment::{CannotModifyReadonly, Environment, Value}; use crate::shell::history::{initialize_history_from_system, write_history_to_file, History}; use crate::shell::opened_files::OpenedFiles; -use crate::signals::SignalManager; -use crate::utils::{ - close, dup2, exec, find_command, fork, is_process_in_foreground, pipe, signal_to_exit_status, - waitpid, ExecError, OsError, OsResult, -}; use crate::wordexp::{expand_word, expand_word_to_string, word_to_pattern}; -use nix::errno::Errno; -use nix::libc; -use nix::sys::signal::kill; -use nix::sys::signal::Signal as NixSignal; -use nix::sys::wait::{WaitPidFlag, WaitStatus}; -use nix::unistd::{getcwd, getpgid, getpgrp, getpid, getppid, setpgid, tcsetpgrp, ForkResult, Pid}; use std::collections::HashMap; use std::ffi::{CString, OsString}; use std::fmt::{Display, Formatter}; use std::fs::File; -use std::io; use std::io::{read_to_string, Read}; use std::os::fd::{AsFd, AsRawFd, IntoRawFd}; use std::path::Path; use std::rc::Rc; use std::time::Duration; +use std::{env, io}; pub mod environment; pub mod history; @@ -204,13 +199,10 @@ impl Shell { pub fn wait_child_process(&mut self, child_pid: Pid) -> OsResult { loop { - match waitpid( - child_pid, - Some(WaitPidFlag::WNOHANG | WaitPidFlag::WUNTRACED), - )? { - WaitStatus::Exited(_, status) => return Ok(status), - WaitStatus::Signaled(_, signal, _) => return Ok(signal_to_exit_status(signal)), - WaitStatus::Stopped(_, signal) => { + match waitpid(child_pid, true, true)? { + WaitStatus::Exited { exit_status } => return Ok(exit_status), + WaitStatus::Signaled { signal, .. } => return Ok(signal_to_exit_status(signal)), + WaitStatus::Stopped { signal } => { self.background_jobs.add_job( child_pid, self.last_pipeline_command.clone(), @@ -732,10 +724,10 @@ impl Shell { ForkResult::Child => { self.become_subshell(); // this should never fail as both arguments are valid - setpgid(Pid::from_raw(0), Pid::from_raw(0)).unwrap(); + setpgid(0, 0).expect("failed to create new process group"); let pipeline_pgid = getpgrp(); // wait for the parent process to put the subshell in the foreground - if let Err(err) = kill(Pid::from_raw(0), NixSignal::SIGTSTP) { + if let Err(err) = kill(0, Some(Signal::SigStop)) { self.eprint(&format!("sh: internal call to kill failed ({err})")); self.exit(1); } @@ -746,7 +738,8 @@ impl Shell { match fork()? { ForkResult::Child => { // should never fail as `pipeline_pgid` is a valid process group - setpgid(Pid::from_raw(0), pipeline_pgid).unwrap(); + setpgid(0, pipeline_pgid) + .expect("failed to set process group for pipeline subcommand"); drop(read_pipe); dup2(current_stdin, libc::STDIN_FILENO)?; dup2(write_pipe.as_raw_fd(), libc::STDOUT_FILENO)?; @@ -773,35 +766,31 @@ impl Shell { loop { // some patterns are only available on linux #[allow(unreachable_patterns)] - match waitpid(child, Some(WaitPidFlag::WNOHANG | WaitPidFlag::WUNTRACED))? { - WaitStatus::Exited(_, _) => { + match waitpid(child, true, true)? { + WaitStatus::Exited { .. } => { // the only way this happened is if there was an error before going // the child went to sleep return Ok(1); } - WaitStatus::Signaled(_, _, _) => { + WaitStatus::Signaled { .. } => { self.eprint("sh: unsynchronised pipeline was terminated by another process\n"); return Ok(1); } - WaitStatus::Continued(_) => { - self.eprint("sh: unsynchronised pipeline was restarted by another process\n"); - return Ok(1); - } - WaitStatus::Stopped(_, _) => { + WaitStatus::Stopped { .. } => { if is_process_in_foreground() { // should never fail as child is a valid process id and // in the same session as the current shell - let child_gpid = getpgid(Some(child)).unwrap(); + let child_gpid = getpgid(child).unwrap(); // should never fail as stdin is a valid file descriptor and // child gpid is valid and in the same session - tcsetpgrp(io::stdin().as_fd(), child_gpid).unwrap(); - kill(child, NixSignal::SIGCONT).unwrap(); + tcsetpgrp(io::stdin().as_raw_fd(), child_gpid).unwrap(); + kill(child, Some(Signal::SigCont)).unwrap(); pipeline_exit_status = self.wait_child_process(child)?; // should never fail - tcsetpgrp(io::stdin().as_fd(), getpgrp()).unwrap(); + tcsetpgrp(io::stdin().as_raw_fd(), getpgrp()).unwrap(); break; } else { - kill(child, NixSignal::SIGCONT).unwrap(); + kill(child, Some(Signal::SigCont)).unwrap(); pipeline_exit_status = self.wait_child_process(child)?; break; } @@ -865,8 +854,7 @@ impl Shell { Ok(ForkResult::Child) => { self.become_subshell(); // should never fail - setpgid(Pid::from_raw(0), Pid::from_raw(0)) - .expect("failed to create process group for background job"); + setpgid(0, 0).expect("failed to create process group for background job"); let status = self.interpret_and_or_list(&conjunction.elements, false); self.exit(status); } @@ -913,8 +901,8 @@ impl Shell { } ForkResult::Parent { child } => { drop(write_pipe); - match waitpid(child, None)? { - WaitStatus::Exited(_, _) | WaitStatus::Signaled(_, _, _) => { + match waitpid(child, false, false)? { + WaitStatus::Exited { .. } | WaitStatus::Signaled { .. } => { let read_file = File::from(read_pipe); let mut output = read_to_string(&read_file).unwrap(); let new_len = output.trim_end_matches('\n').len(); @@ -975,14 +963,15 @@ impl Shell { // > export immediately let mut environment = Environment::from(std::env::vars().map(|(k, v)| (k, Value::new_exported(v)))); - environment.set_global_forced("PPID".to_string(), getppid().to_string()); + let ppid = unsafe { libc::getppid() }; + environment.set_global_forced("PPID".to_string(), ppid.to_string()); environment.set_global_if_unset("IFS", " \t\n"); environment.set_global_if_unset("PS1", "\\$ "); environment.set_global_if_unset("PS2", "> "); environment.set_global_if_unset("PS4", "+ "); environment.set_global_if_unset("OPTIND", "1"); let history = initialize_history_from_system(&environment); - let current_directory = match getcwd() { + let current_directory = match env::current_dir() { Ok(path) => path.into_os_string(), Err(err) => { eprintln!( @@ -996,7 +985,7 @@ impl Shell { environment, program_name, positional_parameters: args, - shell_pid: getpid().as_raw(), + shell_pid: unsafe { libc::getpid() }, current_directory, history, set_options, diff --git a/sh/shell/opened_files.rs b/sh/shell/opened_files.rs index 9afbb3301..bc1b6eb9f 100644 --- a/sh/shell/opened_files.rs +++ b/sh/shell/opened_files.rs @@ -7,13 +7,14 @@ // SPDX-License-Identifier: MIT // +use crate::os::write; use crate::parse::command::{IORedirectionKind, Redirection, RedirectionKind}; use crate::shell::{CommandExecutionError, Shell}; use crate::wordexp::expand_word_to_string; -use nix::libc; use std::collections::HashMap; use std::fs::File; use std::io::Write; +use std::os::fd::AsRawFd; use std::os::unix::fs::OpenOptionsExt; use std::path::Path; use std::rc::Rc; @@ -184,8 +185,8 @@ impl OpenedFiles { Some(OpenedFile::Stdout) => std::io::stdout().write_all(contents.as_bytes()), Some(OpenedFile::Stderr) => std::io::stderr().write_all(contents.as_bytes()), Some(OpenedFile::WriteFile(file)) | Some(OpenedFile::ReadWriteFile(file)) => { - nix::unistd::write(file, contents.as_bytes()) - .map_err(std::io::Error::from) + write(file.as_raw_fd(), contents.as_bytes()) + .map_err(|_| std::io::Error::last_os_error()) .map(|_| ()) } _ => unreachable!(), diff --git a/sh/utils.rs b/sh/utils.rs index f21c380a3..6aa4ec1a7 100644 --- a/sh/utils.rs +++ b/sh/utils.rs @@ -7,157 +7,11 @@ // SPDX-License-Identifier: MIT // -use crate::shell::environment::Environment; -use crate::shell::opened_files::{OpenedFile, OpenedFiles}; -use nix::errno::Errno; -use nix::libc; -use nix::sys::signal::Signal as NixSignal; -use nix::sys::wait::{WaitPidFlag, WaitStatus}; -use nix::unistd::{execve, tcgetpgrp, ForkResult, Pid}; -use std::convert::Infallible; -use std::ffi::{CStr, CString, OsString}; -use std::fmt::{Display, Formatter}; -use std::io; -use std::os::fd::{AsFd, AsRawFd, OwnedFd, RawFd}; -use std::os::unix::ffi::OsStringExt; -use std::path::PathBuf; - -pub const DEFAULT_PATH: &str = "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:."; +use std::ffi::CStr; +use std::fmt::Display; pub fn strcoll(lhs: &CStr, rhs: &CStr) -> std::cmp::Ordering { // strings are valid, this is safe let ordering = unsafe { libc::strcoll(lhs.as_ptr(), rhs.as_ptr()) }; ordering.cmp(&0) } - -#[derive(Clone, Debug)] -pub struct OsError { - pub command: &'static str, - pub errno: Errno, -} - -impl OsError { - pub fn new(command: &'static str, errno: Errno) -> Self { - Self { command, errno } - } -} - -impl Display for OsError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "sh: internal call to {} failed ({})", - self.command, self.errno - ) - } -} - -pub type OsResult = Result; - -pub fn fork() -> OsResult { - // fork in general is not safe for multithreaded programs, but all code in this module is single - // threaded, so this is safe - unsafe { nix::unistd::fork().map_err(|err| OsError::new("fork", err)) } -} - -pub fn pipe() -> OsResult<(OwnedFd, OwnedFd)> { - nix::unistd::pipe().map_err(|err| OsError::new("pipe", err)) -} - -pub fn dup2(old_fd: RawFd, new_fd: RawFd) -> OsResult { - nix::unistd::dup2(old_fd, new_fd).map_err(|err| OsError::new("dup2", err)) -} - -pub fn waitpid(pid: Pid, options: Option) -> OsResult { - nix::sys::wait::waitpid(pid, options).map_err(|err| OsError::new("waitpid", err)) -} - -pub fn close(fd: RawFd) -> OsResult<()> { - nix::unistd::close(fd).map_err(|err| OsError::new("close", err)) -} - -pub fn find_in_path(command: &str, env_path: &str) -> Option { - for path in env_path.split(':') { - let mut command_path = PathBuf::from(path); - command_path.push(command); - if command_path.is_file() { - return Some(command_path.into_os_string()); - } - } - None -} - -pub fn find_command(command: &str, env_path: &str) -> Option { - if command.contains('/') { - let path = PathBuf::from(command); - if path.exists() { - Some(path.into_os_string()) - } else { - None - } - } else { - find_in_path(command, env_path) - } -} - -pub enum ExecError { - OsError(OsError), - CannotExecute(Errno), -} - -impl From for ExecError { - fn from(value: OsError) -> Self { - Self::OsError(value) - } -} - -pub fn exec( - command: OsString, - args: &[String], - opened_files: &OpenedFiles, - env: &Environment, -) -> Result { - for (id, file) in &opened_files.opened_files { - let dest = *id as i32; - let src = match file { - OpenedFile::Stdin => libc::STDIN_FILENO, - OpenedFile::Stdout => libc::STDOUT_FILENO, - OpenedFile::Stderr => libc::STDERR_FILENO, - OpenedFile::ReadFile(file) - | OpenedFile::WriteFile(file) - | OpenedFile::ReadWriteFile(file) => file.as_raw_fd(), - OpenedFile::HereDocument(contents) => { - let (read_pipe, write_pipe) = pipe()?; - nix::unistd::write(write_pipe, contents.as_bytes()) - .map_err(|err| OsError::new("write", err))?; - dup2(read_pipe.as_raw_fd(), dest)?; - continue; - } - }; - dup2(src, dest)?; - } - let command = CString::new(command.into_vec()).unwrap(); - let args = args - .iter() - .map(|s| CString::new(s.as_str()).unwrap()) - .collect::>(); - let env = env - .exported() - .map(|(name, value)| CString::new(format!("{name}={value}")).unwrap()) - .collect::>(); - // unwrap is safe here, because execve will only return if it fails - let err = execve(&command, &args, &env).unwrap_err(); - Err(ExecError::CannotExecute(err)) -} - -pub fn is_process_in_foreground() -> bool { - if let Ok(pgid) = tcgetpgrp(io::stdin().as_fd()) { - pgid == nix::unistd::getpgrp() - } else { - false - } -} - -pub fn signal_to_exit_status(signal: NixSignal) -> i32 { - 128 + signal as i32 -} diff --git a/sh/wordexp/parameter.rs b/sh/wordexp/parameter.rs index 1c530613b..b78fbf1b8 100644 --- a/sh/wordexp/parameter.rs +++ b/sh/wordexp/parameter.rs @@ -334,7 +334,6 @@ mod tests { use crate::parse::word::test_utils::unquoted_literal; use crate::parse::word::Word; use crate::wordexp::expanded_word::ExpandedWordPart; - use nix::unistd::Pid; fn shell_with_env(env: &[(&str, &str)]) -> Shell { let mut shell = Shell::default(); @@ -424,7 +423,7 @@ mod tests { ); shell .background_jobs - .add_job(Pid::from_raw(123), "cmd".to_string(), JobState::Running); + .add_job(123, "cmd".to_string(), JobState::Running); assert_eq!( expand_parameter_to_string( ParameterExpansion::Simple(Parameter::Special(SpecialParameter::Bang)), diff --git a/sh/wordexp/tilde.rs b/sh/wordexp/tilde.rs index 42b6fc832..71d447e44 100644 --- a/sh/wordexp/tilde.rs +++ b/sh/wordexp/tilde.rs @@ -9,7 +9,6 @@ use crate::parse::word::{Word, WordPart}; use crate::shell::environment::Environment; -use nix::libc; use std::ffi::{c_char, CStr, CString}; fn is_portable_filename_character(c: char) -> bool { From 7f45d0d9dd63c295d9a7f94925799fed522873c8 Mon Sep 17 00:00:00 2001 From: grisenti Date: Sat, 12 Apr 2025 22:41:38 +0200 Subject: [PATCH 02/12] sh: improve error message --- sh/shell/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sh/shell/mod.rs b/sh/shell/mod.rs index 36fa12221..58a6599ab 100644 --- a/sh/shell/mod.rs +++ b/sh/shell/mod.rs @@ -294,8 +294,9 @@ impl Shell { } } self.eprint(&format!( - "sh: failed to execute {}\n", - command.to_string_lossy() + "sh: failed to execute {} ({})\n", + command.to_string_lossy(), + errno )); self.exit(126); } From 24ce159b245eddc84a0d3ebe8efb07bff23ca9c2 Mon Sep 17 00:00:00 2001 From: grisenti Date: Sat, 12 Apr 2025 22:46:48 +0200 Subject: [PATCH 03/12] sh: fix execve call --- sh/os/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sh/os/mod.rs b/sh/os/mod.rs index 6f3e3ad99..240d05a0e 100644 --- a/sh/os/mod.rs +++ b/sh/os/mod.rs @@ -197,12 +197,14 @@ pub fn exec( .iter() .map(|s| CString::new(s.as_str()).unwrap()) .collect::>(); - let args_ptr_vec = args.iter().map(|s| s.as_ptr()).collect::>(); + let mut args_ptr_vec = args.iter().map(|s| s.as_ptr()).collect::>(); + args_ptr_vec.push(std::ptr::null()); let env = env .exported() .map(|(name, value)| CString::new(format!("{name}={value}")).unwrap()) .collect::>(); - let env_ptr_vec = env.iter().map(|s| s.as_ptr()).collect::>(); + let mut env_ptr_vec = env.iter().map(|s| s.as_ptr()).collect::>(); + env_ptr_vec.push(std::ptr::null()); let exit_status = unsafe { libc::execve( command.as_ptr(), From ef5e609dff3f0ffd9aa5721aaabf592ccf20dd7c Mon Sep 17 00:00:00 2001 From: grisenti Date: Sat, 12 Apr 2025 22:51:04 +0200 Subject: [PATCH 04/12] sh: fix warnings --- sh/builtin/fc.rs | 2 +- sh/builtin/ulimit.rs | 2 +- sh/main.rs | 2 +- sh/os/errno.rs | 10 ++++++---- sh/os/mod.rs | 1 + sh/os/signals.rs | 4 ++-- sh/shell/mod.rs | 6 +----- sh/utils.rs | 1 - 8 files changed, 13 insertions(+), 15 deletions(-) diff --git a/sh/builtin/fc.rs b/sh/builtin/fc.rs index 926c7297a..44394f40e 100644 --- a/sh/builtin/fc.rs +++ b/sh/builtin/fc.rs @@ -15,7 +15,7 @@ use crate::shell::opened_files::OpenedFiles; use crate::shell::Shell; use std::fs::File; use std::io::Read; -use std::os::fd::{FromRawFd, OwnedFd}; +use std::os::fd::FromRawFd; use std::path::PathBuf; #[derive(Debug, Eq, PartialEq)] diff --git a/sh/builtin/ulimit.rs b/sh/builtin/ulimit.rs index 4f26f98b7..b237668b6 100644 --- a/sh/builtin/ulimit.rs +++ b/sh/builtin/ulimit.rs @@ -9,7 +9,7 @@ use crate::builtin::{BuiltinResult, BuiltinUtility}; use crate::option_parser::OptionParser; -use crate::os::errno::{get_current_errno_value, Errno}; +use crate::os::errno::get_current_errno_value; use crate::shell::opened_files::OpenedFiles; use crate::shell::Shell; use std::fmt::Display; diff --git a/sh/main.rs b/sh/main.rs index 224b62b6d..c562754e8 100644 --- a/sh/main.rs +++ b/sh/main.rs @@ -21,7 +21,7 @@ use os::signals::{ use std::error::Error; use std::io; use std::io::Write; -use std::os::fd::{AsFd, AsRawFd}; +use std::os::fd::AsRawFd; use std::time::Duration; mod builtin; diff --git a/sh/os/errno.rs b/sh/os/errno.rs index b10db5430..c59136d1f 100644 --- a/sh/os/errno.rs +++ b/sh/os/errno.rs @@ -208,14 +208,13 @@ impl Errno { value: libc::EWOULDBLOCK, }; pub const EXDEV: Self = Self { value: libc::EXDEV }; - - pub fn into_raw(self) -> libc::c_int { - self.value - } } impl Debug for Errno { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + // some variants have the same value, but it's not guaranteed by the + // standard + #[allow(unreachable_patterns)] match self.value { libc::E2BIG => write!(f, "E2BIG: argument list too long"), libc::EACCES => write!(f, "EACCESS: permission denied"), @@ -311,6 +310,9 @@ impl Display for Errno { impl TryFrom for Errno { type Error = (); fn try_from(value: libc::c_int) -> Result { + // some variants have the same value, but its not guaranteed by the + // standard + #[allow(unreachable_patterns)] match value { libc::E2BIG => Ok(Self::E2BIG), libc::EACCES => Ok(Self::EACCES), diff --git a/sh/os/mod.rs b/sh/os/mod.rs index 240d05a0e..aadaa3e69 100644 --- a/sh/os/mod.rs +++ b/sh/os/mod.rs @@ -110,6 +110,7 @@ pub fn dup2(old_fd: RawFd, new_fd: RawFd) -> OsResult { Ok(dup_result) } +#[allow(dead_code)] pub enum WaitStatus { Exited { exit_status: libc::c_int }, Signaled { signal: Signal, core_dumped: bool }, diff --git a/sh/os/signals.rs b/sh/os/signals.rs index ac4833249..184712710 100644 --- a/sh/os/signals.rs +++ b/sh/os/signals.rs @@ -324,10 +324,10 @@ impl SignalManager { // we also reset default actions because ignore could have been // set at startup for an interactive shell, but its not registered // as a trap action - TrapAction::Commands(_) | TrapAction::Default => unsafe { + TrapAction::Commands(_) | TrapAction::Default => { unsafe { handle_signal_default(signal) }; *action = TrapAction::Default; - }, + } TrapAction::Ignore => {} } } diff --git a/sh/shell/mod.rs b/sh/shell/mod.rs index 58a6599ab..16f336750 100644 --- a/sh/shell/mod.rs +++ b/sh/shell/mod.rs @@ -38,7 +38,7 @@ use std::ffi::{CString, OsString}; use std::fmt::{Display, Formatter}; use std::fs::File; use std::io::{read_to_string, Read}; -use std::os::fd::{AsFd, AsRawFd, IntoRawFd}; +use std::os::fd::{AsRawFd, IntoRawFd}; use std::path::Path; use std::rc::Rc; use std::time::Duration; @@ -214,7 +214,6 @@ impl Shell { self.handle_async_events(); std::thread::sleep(Duration::from_millis(16)); } - _ => unreachable!(), } } } @@ -765,8 +764,6 @@ impl Shell { } ForkResult::Parent { child } => { loop { - // some patterns are only available on linux - #[allow(unreachable_patterns)] match waitpid(child, true, true)? { WaitStatus::Exited { .. } => { // the only way this happened is if there was an error before going @@ -800,7 +797,6 @@ impl Shell { self.handle_async_events(); std::thread::sleep(Duration::from_millis(16)); } - _ => unreachable!(), } } } diff --git a/sh/utils.rs b/sh/utils.rs index 6aa4ec1a7..e7ee0471a 100644 --- a/sh/utils.rs +++ b/sh/utils.rs @@ -8,7 +8,6 @@ // use std::ffi::CStr; -use std::fmt::Display; pub fn strcoll(lhs: &CStr, rhs: &CStr) -> std::cmp::Ordering { // strings are valid, this is safe From b6f73de761f0830d88d8c84c1a4a2aaf26dfb234 Mon Sep 17 00:00:00 2001 From: grisenti Date: Sat, 12 Apr 2025 23:08:04 +0200 Subject: [PATCH 05/12] sh: fix waitpid --- sh/os/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sh/os/mod.rs b/sh/os/mod.rs index aadaa3e69..f47192984 100644 --- a/sh/os/mod.rs +++ b/sh/os/mod.rs @@ -130,6 +130,8 @@ pub fn waitpid(pid: Pid, no_hang: bool, untraced: bool) -> OsResult let wait_result = unsafe { libc::waitpid(pid, &mut status, options) }; if wait_result < 0 { Err(OsError::from_current_errno("waitpid")) + } else if wait_result == 0 && no_hang { + Ok(WaitStatus::StillAlive) } else if libc::WIFEXITED(status) { let exit_status = libc::WEXITSTATUS(status); Ok(WaitStatus::Exited { exit_status }) @@ -146,7 +148,7 @@ pub fn waitpid(pid: Pid, no_hang: bool, untraced: bool) -> OsResult signal: Signal::try_from(raw_stop_signal).expect("invalid signal"), }) } else { - Ok(WaitStatus::StillAlive) + unreachable!() } } From 73b0609aa2dd7c6a9b0c91f697b2860bd181e90e Mon Sep 17 00:00:00 2001 From: grisenti Date: Sat, 12 Apr 2025 23:12:00 +0200 Subject: [PATCH 06/12] sh: cleanup code --- sh/builtin/kill.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sh/builtin/kill.rs b/sh/builtin/kill.rs index e4a6d1b39..701cabbde 100644 --- a/sh/builtin/kill.rs +++ b/sh/builtin/kill.rs @@ -93,7 +93,7 @@ impl BuiltinUtility for Kill { KillArgs::SendSignal { signal, pids } => { for pid in pids { let pid = parse_pid(pid, shell).map_err(|err| format!("kill: {err}"))?; - kill(pid, signal.map(|s| s.into())) + kill(pid, signal) .map_err(|err| format!("kill: failed to send signal ({})", err))?; } } From 703f3a8e5180c743ecf8dbdbb775d08a4761c807 Mon Sep 17 00:00:00 2001 From: grisenti Date: Sat, 12 Apr 2025 23:40:25 +0200 Subject: [PATCH 07/12] sh: fix signal handling --- sh/os/signals.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sh/os/signals.rs b/sh/os/signals.rs index 184712710..37cf7bbad 100644 --- a/sh/os/signals.rs +++ b/sh/os/signals.rs @@ -273,10 +273,11 @@ unsafe fn handle_signal(signal: Signal, handler: libc::sighandler_t) { let mut empty_sigset: libc::sigset_t = std::mem::zeroed::(); // never fails libc::sigemptyset(&mut empty_sigset); + let action = libc::sigaction { sa_sigaction: handler, sa_mask: empty_sigset, - sa_flags: 0, + sa_flags: libc::SA_SIGINFO, sa_restorer: None, }; let result = libc::sigaction(signal.into(), &action, std::ptr::null_mut()); @@ -294,7 +295,10 @@ pub unsafe fn handle_signal_default(signal: Signal) { } pub unsafe fn handle_signal_write_to_signal_buffer(signal: Signal) { - handle_signal(signal, &write_signal_to_buffer as *const _ as libc::size_t) + handle_signal( + signal, + write_signal_to_buffer as *const extern "C" fn(libc::c_int) as libc::sighandler_t, + ) } #[derive(Clone)] From ae752b4ede0d9fd700c3c29f3203582d3772bc4f Mon Sep 17 00:00:00 2001 From: grisenti Date: Sat, 12 Apr 2025 23:43:31 +0200 Subject: [PATCH 08/12] sh: remove atty dependency --- Cargo.lock | 21 --------------------- sh/Cargo.toml | 1 - sh/builtin/read.rs | 4 ++-- sh/cli/terminal.rs | 5 ++--- 4 files changed, 4 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d64ea41e..7bd5e1498 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,17 +106,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.4.0" @@ -766,15 +755,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hostname" version = "0.3.1" @@ -1587,7 +1567,6 @@ dependencies = [ name = "posixutils-sh" version = "0.1.7" dependencies = [ - "atty", "gettext-rs", "libc", "plib", diff --git a/sh/Cargo.toml b/sh/Cargo.toml index b1236e58f..5d4765102 100644 --- a/sh/Cargo.toml +++ b/sh/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" plib = { path = "../plib" } gettext-rs.workspace = true libc.workspace = true -atty = "0.2" [[bin]] name = "sh" diff --git a/sh/builtin/read.rs b/sh/builtin/read.rs index ee86411ed..49d4507c8 100644 --- a/sh/builtin/read.rs +++ b/sh/builtin/read.rs @@ -15,7 +15,7 @@ use crate::shell::opened_files::{OpenedFile, OpenedFiles, STDIN_FILENO}; use crate::shell::Shell; use crate::wordexp::expanded_word::ExpandedWord; use crate::wordexp::split_fields; -use atty::Stream; +use std::io::IsTerminal; use std::os::fd::{AsRawFd, RawFd}; use std::time::Duration; @@ -141,7 +141,7 @@ fn read_from_stdin( delimiter: u8, backslash_escape: bool, ) -> Result { - if atty::is(Stream::Stdin) { + if std::io::stdin().is_terminal() { let original_terminal_settings = shell.terminal.reset(); shell.terminal.set_nonblocking(); diff --git a/sh/cli/terminal.rs b/sh/cli/terminal.rs index aa099ff85..f19a47a09 100644 --- a/sh/cli/terminal.rs +++ b/sh/cli/terminal.rs @@ -8,9 +8,8 @@ // use crate::os::errno::get_current_errno_value; -use atty::Stream; use std::io; -use std::io::Read; +use std::io::{IsTerminal, Read}; fn get_current_settings() -> libc::termios { // using zeroed here because terminos has additional members on some systems @@ -98,5 +97,5 @@ pub fn read_nonblocking_char() -> Option { } pub fn is_attached_to_terminal() -> bool { - atty::is(Stream::Stdin) && atty::is(Stream::Stdout) + std::io::stdin().is_terminal() && std::io::stdout().is_terminal() } From 29614a67bf04529fd009533abe7a688cbe9a46ad Mon Sep 17 00:00:00 2001 From: grisenti Date: Sat, 12 Apr 2025 23:44:38 +0200 Subject: [PATCH 09/12] sh: add license --- sh/os/errno.rs | 9 +++++++++ sh/os/mod.rs | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/sh/os/errno.rs b/sh/os/errno.rs index c59136d1f..499037327 100644 --- a/sh/os/errno.rs +++ b/sh/os/errno.rs @@ -1,3 +1,12 @@ +// +// Copyright (c) 2024 Hemi Labs, Inc. +// +// This file is part of the posixutils-rs project covered under +// the MIT License. For the full license text, please see the LICENSE +// file in the root directory of this project. +// SPDX-License-Identifier: MIT +// + use std::fmt::{Debug, Display, Formatter}; #[derive(Clone, PartialEq, Eq)] diff --git a/sh/os/mod.rs b/sh/os/mod.rs index f47192984..b7788df22 100644 --- a/sh/os/mod.rs +++ b/sh/os/mod.rs @@ -1,3 +1,12 @@ +// +// Copyright (c) 2024 Hemi Labs, Inc. +// +// This file is part of the posixutils-rs project covered under +// the MIT License. For the full license text, please see the LICENSE +// file in the root directory of this project. +// SPDX-License-Identifier: MIT +// + use crate::os::errno::{get_current_errno_value, Errno}; use crate::os::signals::Signal; use crate::shell::environment::Environment; From 0fd87935c6fa5dfeebef7955299222905ed2711f Mon Sep 17 00:00:00 2001 From: grisenti Date: Sat, 12 Apr 2025 23:47:13 +0200 Subject: [PATCH 10/12] sh: make plib a dev dependency --- sh/Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sh/Cargo.toml b/sh/Cargo.toml index 5d4765102..3bec4e81b 100644 --- a/sh/Cargo.toml +++ b/sh/Cargo.toml @@ -4,10 +4,12 @@ version = "0.1.7" edition = "2021" [dependencies] -plib = { path = "../plib" } gettext-rs.workspace = true libc.workspace = true +[dev-dependencies] +plib = { path = "../plib" } + [[bin]] name = "sh" path = "main.rs" From 5d9e753002a3700df72fb782b464455626c56c18 Mon Sep 17 00:00:00 2001 From: grisenti Date: Sat, 12 Apr 2025 23:49:45 +0200 Subject: [PATCH 11/12] sh: replace unwrap with expect --- sh/shell/mod.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sh/shell/mod.rs b/sh/shell/mod.rs index 16f336750..fdb79330c 100644 --- a/sh/shell/mod.rs +++ b/sh/shell/mod.rs @@ -778,17 +778,22 @@ impl Shell { if is_process_in_foreground() { // should never fail as child is a valid process id and // in the same session as the current shell - let child_gpid = getpgid(child).unwrap(); + let child_gpid = getpgid(child) + .expect("failed to get process id of child process"); // should never fail as stdin is a valid file descriptor and // child gpid is valid and in the same session - tcsetpgrp(io::stdin().as_raw_fd(), child_gpid).unwrap(); - kill(child, Some(Signal::SigCont)).unwrap(); + tcsetpgrp(io::stdin().as_raw_fd(), child_gpid) + .expect("failed to set pipeline in foreground"); + kill(child, Some(Signal::SigCont)) + .expect("failed to start pipeline"); pipeline_exit_status = self.wait_child_process(child)?; // should never fail - tcsetpgrp(io::stdin().as_raw_fd(), getpgrp()).unwrap(); + tcsetpgrp(io::stdin().as_raw_fd(), getpgrp()) + .expect("failed to reset foreground process"); break; } else { - kill(child, Some(Signal::SigCont)).unwrap(); + kill(child, Some(Signal::SigCont)) + .expect("failed start pipeline"); pipeline_exit_status = self.wait_child_process(child)?; break; } From c4234dbb9d63ee8e6585e791d0ef1ce29d182535 Mon Sep 17 00:00:00 2001 From: grisenti Date: Sun, 13 Apr 2025 09:01:52 +0200 Subject: [PATCH 12/12] sh: fix build errors on mac --- sh/os/errno.rs | 3 ++- sh/os/signals.rs | 14 ++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/sh/os/errno.rs b/sh/os/errno.rs index 499037327..cdbd42027 100644 --- a/sh/os/errno.rs +++ b/sh/os/errno.rs @@ -406,6 +406,7 @@ impl TryFrom for Errno { } pub fn get_current_errno_value() -> Errno { - let errno = unsafe { *libc::__errno_location() }; + // guaranteed to be some, unwrap is safe + let errno = std::io::Error::last_os_error().raw_os_error().unwrap(); errno.try_into().expect("invalid errno value") } diff --git a/sh/os/signals.rs b/sh/os/signals.rs index 37cf7bbad..daf505e3b 100644 --- a/sh/os/signals.rs +++ b/sh/os/signals.rs @@ -270,16 +270,14 @@ fn get_pending_signal() -> Option { } unsafe fn handle_signal(signal: Signal, handler: libc::sighandler_t) { - let mut empty_sigset: libc::sigset_t = std::mem::zeroed::(); + // sigaction contains different field on different systems, we can't + // initialize it directly + let mut action = std::mem::zeroed::(); + action.sa_sigaction = handler; // never fails - libc::sigemptyset(&mut empty_sigset); + libc::sigemptyset(&mut action.sa_mask); + action.sa_flags = libc::SA_SIGINFO; - let action = libc::sigaction { - sa_sigaction: handler, - sa_mask: empty_sigset, - sa_flags: libc::SA_SIGINFO, - sa_restorer: None, - }; let result = libc::sigaction(signal.into(), &action, std::ptr::null_mut()); if result < 0 { panic!("failed to set signal handler")