From 2d0e1f3a77643b6a390ce11901c2e1bfddbb7c53 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 28 Mar 2026 13:03:13 +0100 Subject: [PATCH] refactor: migrate all utilities from nix to rustix/libc Migrate 19 utility crates from nix to rustix and direct libc calls: - kill, timeout, env: signal handling via csignal wrappers + libc - sync, cat, tail, dd, tsort: file operations via rustix::fs - touch: futimens via rustix::fs::futimens - stty, tty: terminal ops via rustix::termios + libc ioctl - sort: getrlimit via rustix::process, sysconf via libc - wc, cp, df: stat/fstat via rustix::fs - nice: priority via rustix::process - mkfifo, mknod: via direct libc calls Also add rustix::io::Errno conversions to uucore error.rs. --- Cargo.lock | 20 +-- src/uu/cp/Cargo.toml | 1 - src/uu/cp/src/cp.rs | 31 ++++- src/uu/dd/Cargo.toml | 2 +- src/uu/dd/src/dd.rs | 61 ++++----- src/uu/env/Cargo.toml | 2 +- src/uu/env/src/env.rs | 82 ++++++------ src/uu/kill/Cargo.toml | 2 +- src/uu/kill/src/kill.rs | 24 ++-- src/uu/mkfifo/Cargo.toml | 2 +- src/uu/mkfifo/src/mkfifo.rs | 7 +- src/uu/mknod/Cargo.toml | 2 +- src/uu/mknod/src/mknod.rs | 39 +++--- src/uu/sort/Cargo.toml | 3 +- src/uu/sort/src/buffer_hint.rs | 17 +-- src/uu/sort/src/sort.rs | 11 +- src/uu/stty/Cargo.toml | 3 +- src/uu/stty/src/flags.rs | 212 +++++++++++++------------------ src/uu/stty/src/stty.rs | 166 ++++++++++++------------ src/uu/sync/Cargo.toml | 3 +- src/uu/sync/src/sync.rs | 53 ++++---- src/uu/timeout/Cargo.toml | 2 - src/uu/timeout/src/timeout.rs | 55 ++++---- src/uu/wc/src/count_fast.rs | 38 ++---- src/uu/wc/src/wc.rs | 1 + src/uucore/src/lib/mods/error.rs | 41 ++++++ 26 files changed, 453 insertions(+), 427 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b6c0bfaf..2591386ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3405,7 +3405,6 @@ dependencies = [ "fluent", "indicatif", "libc", - "nix", "selinux", "tempfile", "thiserror 2.0.18", @@ -3466,7 +3465,7 @@ dependencies = [ "fluent", "gcd", "libc", - "nix", + "rustix", "tempfile", "thiserror 2.0.18", "uucore", @@ -3543,7 +3542,7 @@ version = "0.8.0" dependencies = [ "clap", "fluent", - "nix", + "libc", "rust-ini", "thiserror 2.0.18", "uucore", @@ -3708,7 +3707,7 @@ version = "0.8.0" dependencies = [ "clap", "fluent", - "nix", + "libc", "uucore", ] @@ -3787,7 +3786,7 @@ version = "0.8.0" dependencies = [ "clap", "fluent", - "nix", + "libc", "uucore", ] @@ -3797,7 +3796,7 @@ version = "0.8.0" dependencies = [ "clap", "fluent", - "nix", + "libc", "uucore", ] @@ -4156,9 +4155,9 @@ dependencies = [ "foldhash 0.2.0", "itertools 0.14.0", "memchr", - "nix", "rand 0.10.1", "rayon", + "rustix", "self_cell", "tempfile", "thiserror 2.0.18", @@ -4215,7 +4214,8 @@ dependencies = [ "cfg_aliases", "clap", "fluent", - "nix", + "libc", + "rustix", "uucore", ] @@ -4234,7 +4234,8 @@ version = "0.8.0" dependencies = [ "clap", "fluent", - "nix", + "libc", + "rustix", "uucore", "windows-sys 0.61.2", ] @@ -4299,7 +4300,6 @@ dependencies = [ "codspeed-divan-compat", "fluent", "libc", - "nix", "uucore", ] diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 9f3a862f7..7fec50a74 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -41,7 +41,6 @@ fluent = { workspace = true } [target.'cfg(unix)'.dependencies] exacl = { workspace = true, optional = true } -nix = { workspace = true, features = ["fs"] } [[bin]] name = "cp" diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 2e9ae6b62..9acbfb50d 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -23,7 +23,7 @@ use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser, value_pars use filetime::FileTime; use indicatif::{ProgressBar, ProgressStyle}; #[cfg(unix)] -use nix::sys::stat::{Mode, SFlag, dev_t, mknod as nix_mknod, mode_t}; +use libc::{dev_t, mode_t}; use thiserror::Error; use platform::copy_on_write; @@ -2807,14 +2807,31 @@ fn copy_node( overwrite.verify(dest, debug)?; fs::remove_file(dest)?; } - let sflag = if source_metadata.file_type().is_char_device() { - SFlag::S_IFCHR + let sflag: mode_t = if source_metadata.file_type().is_char_device() { + libc::S_IFCHR } else { - SFlag::S_IFBLK + libc::S_IFBLK }; - let mode = Mode::from_bits_truncate(source_metadata.mode() as mode_t); - nix_mknod(dest, sflag, mode, source_metadata.rdev() as dev_t) - .map_err(|e| translate!("cp-error-cannot-create-special-file", "path" => dest.quote(), "error" => e.desc()).into()) + let mode = (source_metadata.mode() as mode_t) & 0o7777; + use std::io; + let c_path = std::ffi::CString::new(dest.as_os_str().as_encoded_bytes()) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "path contains null byte"))?; + // SAFETY: c_path is a valid null-terminated C string + let ret = unsafe { + libc::mknod( + c_path.as_ptr(), + sflag | mode, + source_metadata.rdev() as dev_t, + ) + }; + if ret != 0 { + let e = io::Error::last_os_error(); + return Err( + translate!("cp-error-cannot-create-special-file", "path" => dest.quote(), "error" => uucore::error::strip_errno(&e)) + .into(), + ); + } + Ok(()) } fn copy_link( diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index 438b9ccf5..f475e6269 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -33,7 +33,7 @@ thiserror = { workspace = true } fluent = { workspace = true } [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] -nix = { workspace = true, features = ["fs", "signal"] } +rustix = { workspace = true, features = ["fs"] } [[bin]] name = "dd" diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 8071cae64..ac5677eb0 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -16,15 +16,13 @@ mod progress; use crate::bufferedoutput::BufferedOutput; use blocks::conv_block_unblock_helper; use datastructures::{ConversionMode, IConvFlags, IFlags, OConvFlags, OFlags, options}; -#[cfg(any(target_os = "linux", target_os = "android"))] -use nix::fcntl::FcntlArg; -#[cfg(any(target_os = "linux", target_os = "android"))] -use nix::fcntl::OFlag; use parseargs::Parser; use progress::ProgUpdateType; use progress::{ProgUpdate, ReadStat, StatusLevel, WriteStat, gen_prog_updater}; #[cfg(target_os = "linux")] use progress::{check_and_reset_sigusr1, install_sigusr1_handler}; +#[cfg(any(target_os = "linux", target_os = "android"))] +use rustix::fs::OFlags as RustixOFlags; use uucore::io::OwnedFileDescriptorOrHandle; use uucore::translate; @@ -55,10 +53,7 @@ use std::time::{Duration, Instant}; use clap::{Arg, Command}; use gcd::Gcd; #[cfg(target_os = "linux")] -use nix::{ - errno::Errno, - fcntl::{PosixFadviseAdvice, posix_fadvise}, -}; +use rustix::fs::{Advice as PosixFadviseAdvice, fadvise}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; #[cfg(unix)] @@ -316,14 +311,16 @@ impl Source { /// portion of the source is no longer needed. If not possible, /// then this function returns an error. #[cfg(target_os = "linux")] - fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) -> nix::Result<()> { + fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) -> io::Result<()> { #[allow(clippy::match_wildcard_for_single_variants)] match self { Self::File(f) => { - let advice = PosixFadviseAdvice::POSIX_FADV_DONTNEED; - posix_fadvise(f.as_fd(), offset, len, advice) + let advice = PosixFadviseAdvice::DontNeed; + let len = std::num::NonZeroU64::new(len as u64); + fadvise(f.as_fd(), offset as u64, len, advice)?; + Ok(()) } - _ => Err(Errno::ESPIPE), // "Illegal seek" + _ => Err(io::Error::from_raw_os_error(libc::ESPIPE)), // "Illegal seek" } } } @@ -700,13 +697,15 @@ impl Dest { /// specified portion of the destination is no longer needed. If /// not possible, then this function returns an error. #[cfg(target_os = "linux")] - fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) -> nix::Result<()> { + fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) -> io::Result<()> { match self { Self::File(f, _) => { - let advice = PosixFadviseAdvice::POSIX_FADV_DONTNEED; - posix_fadvise(f.as_fd(), offset, len, advice) + let advice = PosixFadviseAdvice::DontNeed; + let len = std::num::NonZeroU64::new(len as u64); + fadvise(f.as_fd(), offset as u64, len, advice)?; + Ok(()) } - _ => Err(Errno::ESPIPE), // "Illegal seek" + _ => Err(io::Error::from_raw_os_error(libc::ESPIPE)), // "Illegal seek" } } } @@ -720,31 +719,33 @@ fn is_sparse(buf: &[u8]) -> bool { /// This follows GNU dd behavior for partial block writes with O_DIRECT. #[cfg(any(target_os = "linux", target_os = "android"))] fn handle_o_direct_write(f: &mut File, buf: &[u8], original_error: io::Error) -> io::Result { - use nix::fcntl::{FcntlArg, OFlag, fcntl}; + use rustix::fs::{OFlags, fcntl_getfl, fcntl_setfl}; - // Get current flags using nix - let oflags = match fcntl(&mut *f, FcntlArg::F_GETFL) { - Ok(flags) => OFlag::from_bits_retain(flags), - Err(_) => return Err(original_error), + // Get current flags + let Ok(oflags) = fcntl_getfl(&*f) else { + return Err(original_error); }; // If O_DIRECT is set, try removing it temporarily - if oflags.contains(OFlag::O_DIRECT) { - let flags_without_direct = oflags - OFlag::O_DIRECT; + if oflags.contains(OFlags::DIRECT) { + let flags_without_direct = oflags & !OFlags::DIRECT; - // Remove O_DIRECT flag using nix - if fcntl(&mut *f, FcntlArg::F_SETFL(flags_without_direct)).is_err() { + // Remove O_DIRECT flag + if fcntl_setfl(&*f, flags_without_direct).is_err() { return Err(original_error); } // Retry the write without O_DIRECT let write_result = f.write(buf); - // Restore O_DIRECT flag using nix (GNU doesn't restore it, but we'll be safer) + // Restore O_DIRECT flag (GNU doesn't restore it, but we'll be safer) // Log any restoration errors without failing the operation - if let Err(os_err) = fcntl(&mut *f, FcntlArg::F_SETFL(oflags)) { + if let Err(os_err) = fcntl_setfl(&*f, oflags) { // Just log the error, don't fail the whole operation - show_error!("Failed to restore O_DIRECT flag: {os_err}"); + show_error!( + "Failed to restore O_DIRECT flag: {}", + io::Error::from(os_err) + ); } write_result @@ -891,9 +892,9 @@ impl<'a> Output<'a> { let fx = OwnedFileDescriptorOrHandle::from(io::stdout())?; #[cfg(any(target_os = "linux", target_os = "android"))] if let Some(libc_flags) = make_linux_oflags(&settings.oflags) { - nix::fcntl::fcntl( + rustix::fs::fcntl_setfl( fx.as_raw().as_fd(), - FcntlArg::F_SETFL(OFlag::from_bits_retain(libc_flags)), + RustixOFlags::from_bits_retain(libc_flags as _), )?; } diff --git a/src/uu/env/Cargo.toml b/src/uu/env/Cargo.toml index 46aa1be7c..3181ab645 100644 --- a/src/uu/env/Cargo.toml +++ b/src/uu/env/Cargo.toml @@ -26,7 +26,7 @@ uucore = { workspace = true, features = ["signals"] } fluent = { workspace = true } [target.'cfg(unix)'.dependencies] -nix = { workspace = true, features = ["signal"] } +libc = { workspace = true } [[bin]] name = "env" diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 334354425..e62f662a0 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -17,15 +17,6 @@ use ini::Ini; use native_int_str::{ Convert, NCvt, NativeIntStr, NativeIntString, NativeStr, from_native_int_representation_owned, }; -#[cfg(unix)] -use nix::libc; -#[cfg(unix)] -use nix::sys::signal::{ - SigHandler::{SigDfl, SigIgn}, - SigSet, SigmaskHow, Signal, signal, sigprocmask, -}; -#[cfg(unix)] -use nix::unistd::execvp; use std::borrow::Cow; #[cfg(unix)] use std::collections::{BTreeMap, BTreeSet}; @@ -38,6 +29,8 @@ use std::io::Write as _; use std::io::stderr; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; +#[cfg(unix)] +use uucore::signals::csignal::{self, SigHandler}; use uucore::display::{Quotable, print_all_env_vars}; use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError}; @@ -295,16 +288,25 @@ fn build_signal_request( } #[cfg(unix)] -fn signal_from_value(sig_value: usize) -> UResult { - Signal::try_from(sig_value as i32).map_err(|_| { - USimpleError::new( +fn signal_from_value(sig_value: usize) -> UResult { + let sig = sig_value as libc::c_int; + // Validate signal number is in a reasonable range. + // On Linux, reject glibc-reserved signals between standard signals and SIGRTMIN + // (typically 32 and 33, used internally by NPTL). + #[cfg(any(target_os = "linux", target_os = "android"))] + let reserved = sig > libc::SIGSYS && sig < libc::SIGRTMIN(); + #[cfg(not(any(target_os = "linux", target_os = "android")))] + let reserved = false; + if sig <= 0 || sig >= 128 || reserved { + return Err(USimpleError::new( 125, translate!( "env-error-invalid-signal", "signal" => sig_value.to_string().quote() ), - ) - }) + )); + } + Ok(sig) } fn load_config_file(opts: &mut Options) -> UResult<()> { @@ -836,11 +838,21 @@ impl EnvAppData { } // Execute the program using execvp. this replaces the current - // process. The execvp function takes care of appending a NULL - // argument to the argument list so that we don't have to. - match execvp(&prog_cstring, &argv) { - Err(nix::errno::Errno::ENOENT) => Err(self.make_error_no_such_file_or_dir(&prog)), - Err(nix::errno::Errno::EACCES) => { + // process. + // + // Build a null-terminated array of pointers for execvp. + let mut argv_ptrs: Vec<*const libc::c_char> = argv.iter().map(|a| a.as_ptr()).collect(); + argv_ptrs.push(std::ptr::null()); + + // SAFETY: prog_cstring is a valid null-terminated C string, + // argv_ptrs is a null-terminated array of valid C string pointers. + // execvp only returns on error. + unsafe { libc::execvp(prog_cstring.as_ptr(), argv_ptrs.as_ptr()) }; + + let err = io::Error::last_os_error(); + match err.raw_os_error() { + Some(libc::ENOENT) => Err(self.make_error_no_such_file_or_dir(&prog)), + Some(libc::EACCES) => { uucore::show_error!( "{}", translate!( @@ -850,7 +862,7 @@ impl EnvAppData { ); Err(126.into()) } - Err(_) => { + _ => { uucore::show_error!( "{}", translate!( @@ -860,9 +872,6 @@ impl EnvAppData { ); Err(126.into()) } - Ok(_) => { - unreachable!("execvp should never return on success") - } } } @@ -1072,7 +1081,7 @@ fn apply_signal_action( signal_fn: F, ) -> UResult<()> where - F: Fn(Signal) -> UResult<()>, + F: Fn(libc::c_int) -> UResult<()>, { request.for_each_signal(|sig_value, explicit| { // On some platforms ALL_SIGNALS may contain values that are not valid in libc. @@ -1096,41 +1105,38 @@ where } #[cfg(unix)] -fn ignore_signal(sig: Signal) -> UResult<()> { - // SAFETY: This is safe because we write the handler for each signal only once, and therefore "the current handler is the default", as the documentation requires it. - let result = unsafe { signal(sig, SigIgn) }; - if let Err(err) = result { +fn ignore_signal(sig: libc::c_int) -> UResult<()> { + if let Err(err) = unsafe { csignal::set_signal_handler(sig, SigHandler::SigIgn) } { return Err(USimpleError::new( 125, - translate!("env-error-failed-set-signal-action", "signal" => (sig as i32), "error" => err.desc()), + translate!("env-error-failed-set-signal-action", "signal" => sig, "error" => err), )); } Ok(()) } #[cfg(unix)] -fn reset_signal(sig: Signal) -> UResult<()> { - let result = unsafe { signal(sig, SigDfl) }; - if let Err(err) = result { +fn reset_signal(sig: libc::c_int) -> UResult<()> { + if let Err(err) = unsafe { csignal::set_signal_handler(sig, SigHandler::SigDfl) } { return Err(USimpleError::new( 125, - translate!("env-error-failed-set-signal-action", "signal" => (sig as i32), "error" => err.desc()), + translate!("env-error-failed-set-signal-action", "signal" => sig, "error" => err), )); } Ok(()) } #[cfg(unix)] -fn block_signal(sig: Signal) -> UResult<()> { - let mut set = SigSet::empty(); +fn block_signal(sig: libc::c_int) -> UResult<()> { + let mut set = csignal::SigSet::empty(); set.add(sig); - if let Err(err) = sigprocmask(SigmaskHow::SIG_BLOCK, Some(&set), None) { + if let Err(err) = csignal::sigprocmask(csignal::SigmaskHow::Block, Some(&set), None) { return Err(USimpleError::new( 125, translate!( "env-error-failed-set-signal-action", - "signal" => (sig as i32), - "error" => err.desc() + "signal" => sig, + "error" => err ), )); } diff --git a/src/uu/kill/Cargo.toml b/src/uu/kill/Cargo.toml index 9f36aba50..287f80464 100644 --- a/src/uu/kill/Cargo.toml +++ b/src/uu/kill/Cargo.toml @@ -25,7 +25,7 @@ uucore = { workspace = true, features = ["signals"] } fluent = { workspace = true } [target.'cfg(unix)'.dependencies] -nix = { workspace = true, features = ["signal"] } +libc = { workspace = true } [[bin]] name = "kill" diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index e92a47da3..4ac997275 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -6,8 +6,6 @@ // spell-checker:ignore (ToDO) signalname pids killpg use clap::{Arg, ArgAction, Command}; -use nix::sys::signal::{self, Signal}; -use nix::unistd::Pid; use std::io::Error; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; @@ -69,22 +67,18 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let sig_name = signal_name_by_value(sig); - // Signal does not support converting from EXIT - // Instead, nix::signal::kill expects Option::None to properly handle EXIT - let sig: Option = if sig_name.is_some_and(|name| name == "EXIT") { - None + // Signal 0 (EXIT) means "check if process exists" - pass 0 to kill() + let sig_num: i32 = if sig_name.is_some_and(|name| name == "EXIT") { + 0 } else { - let sig = (sig as i32) - .try_into() - .map_err(|e| Error::from_raw_os_error(e as i32))?; - Some(sig) + sig as i32 }; let pids = parse_pids(&pids_or_signals)?; if pids.is_empty() { Err(USimpleError::new(1, translate!("kill-error-no-process-id"))) } else { - kill(sig, &pids); + kill(sig_num, &pids); Ok(()) } } @@ -250,11 +244,13 @@ fn parse_pids(pids: &[String]) -> UResult> { .collect() } -fn kill(sig: Option, pids: &[i32]) { +fn kill(sig: i32, pids: &[i32]) { for &pid in pids { - if let Err(e) = signal::kill(Pid::from_raw(pid), sig) { + // SAFETY: kill() is a standard POSIX function. sig=0 checks process existence. + let ret = unsafe { libc::kill(pid, sig) }; + if ret != 0 { show!( - Error::from_raw_os_error(e as i32) + Error::last_os_error() .map_err_context(|| { translate!("kill-error-sending-signal", "pid" => pid) }) ); } diff --git a/src/uu/mkfifo/Cargo.toml b/src/uu/mkfifo/Cargo.toml index 63f1d8f47..49f128ca0 100644 --- a/src/uu/mkfifo/Cargo.toml +++ b/src/uu/mkfifo/Cargo.toml @@ -25,7 +25,7 @@ uucore = { workspace = true, features = ["fs", "mode"] } fluent = { workspace = true } [target.'cfg(unix)'.dependencies] -nix = { workspace = true, features = ["fs"] } +libc = { workspace = true } [features] selinux = ["uucore/selinux"] diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index a48a4894e..0277d62ef 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -4,8 +4,7 @@ // file that was distributed with this source code. use clap::{Arg, ArgAction, Command, value_parser}; -use nix::sys::stat::Mode; -use nix::unistd::mkfifo; +use std::ffi::CString; use std::fs; use std::os::unix::fs::PermissionsExt; use uucore::display::Quotable; @@ -48,7 +47,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; for f in fifos { - if mkfifo(f.as_str(), Mode::from_bits_truncate(0o666)).is_err() { + let c_path = CString::new(f.as_str()).unwrap(); + // SAFETY: c_path is a valid null-terminated C string + if unsafe { libc::mkfifo(c_path.as_ptr(), 0o666) } != 0 { show!(USimpleError::new( 1, translate!("mkfifo-error-cannot-create-fifo", "path" => f.quote()), diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index 03ad05b9d..490d1b74d 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -24,7 +24,7 @@ doctest = false clap = { workspace = true } uucore = { workspace = true, features = ["mode", "fs"] } fluent = { workspace = true } -nix = { workspace = true } +libc = { workspace = true } [features] selinux = ["uucore/selinux"] diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 39ac62cb6..e6c87848d 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -6,8 +6,8 @@ // spell-checker:ignore (ToDO) parsemode makedev sysmacros perror IFBLK IFCHR IFIFO sflag use clap::{Arg, ArgAction, Command, value_parser}; -use nix::libc::{S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, mode_t}; -use nix::sys::stat::{Mode, SFlag, mknod as nix_mknod, umask as nix_umask}; +use libc::{S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, mode_t}; +use std::ffi::CString; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError, set_exit_code}; @@ -35,11 +35,11 @@ enum FileType { } impl FileType { - fn as_sflag(&self) -> SFlag { + fn as_mode(&self) -> mode_t { match self { - Self::Block => SFlag::S_IFBLK, - Self::Character => SFlag::S_IFCHR, - Self::Fifo => SFlag::S_IFIFO, + Self::Block => libc::S_IFBLK, + Self::Character => libc::S_IFCHR, + Self::Fifo => libc::S_IFIFO, } } } @@ -47,7 +47,7 @@ impl FileType { /// Configuration for special inode creation. struct Config { /// Permission bits for the inode - mode: Mode, + mode: mode_t, file_type: FileType, @@ -70,28 +70,27 @@ fn mknod(file_name: &str, config: Config) -> i32 { let have_prev_umask = if config.use_umask { None } else { - Some(nix_umask(Mode::empty())) + // SAFETY: umask is a standard POSIX function, always safe to call + Some(unsafe { libc::umask(0) }) }; - let mknod_err = nix_mknod( - file_name, - config.file_type.as_sflag(), - config.mode, - config.dev as _, - ) - .err(); - let errno = if mknod_err.is_some() { -1 } else { 0 }; + let c_path = CString::new(file_name).unwrap(); + let combined_mode = config.file_type.as_mode() | config.mode; + // SAFETY: c_path is a valid null-terminated C string + let ret = unsafe { libc::mknod(c_path.as_ptr(), combined_mode, config.dev as _) }; + let errno = if ret != 0 { -1 } else { 0 }; // set umask back to original value if let Some(prev_umask) = have_prev_umask { - nix_umask(prev_umask); + // SAFETY: umask is always safe to call + unsafe { libc::umask(prev_umask) }; } - if let Some(err) = mknod_err { + if ret != 0 { eprintln!( "{}: {}", uucore::execution_phrase(), - std::io::Error::from(err) + std::io::Error::last_os_error() ); } @@ -141,7 +140,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { parse_mode(str_mode).map_err(|e| USimpleError::new(1, e))? } }; - let mode = Mode::from_bits_truncate(mode_permissions as mode_t); + let mode = (mode_permissions as mode_t) & 0o7777; let file_name = matches .get_one::("name") diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 09763a4f9..d84d689ce 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -51,7 +51,8 @@ foldhash = { workspace = true } ctrlc = { workspace = true } [target.'cfg(unix)'.dependencies] -nix = { workspace = true, features = ["resource"] } +libc = { workspace = true } +rustix = { workspace = true, features = ["process", "param"] } [dev-dependencies] divan = { workspace = true } diff --git a/src/uu/sort/src/buffer_hint.rs b/src/uu/sort/src/buffer_hint.rs index d5baf9efb..282f7c35b 100644 --- a/src/uu/sort/src/buffer_hint.rs +++ b/src/uu/sort/src/buffer_hint.rs @@ -110,17 +110,14 @@ fn physical_memory_bytes() -> Option { any(target_os = "linux", target_os = "android") ))] fn physical_memory_bytes_unix() -> Option { - use nix::unistd::{SysconfVar, sysconf}; + let page_size = u128::try_from(rustix::param::page_size()).ok()?; - let pages = match sysconf(SysconfVar::_PHYS_PAGES) { - Ok(Some(pages)) if pages > 0 => u128::try_from(pages).ok()?, - _ => return None, - }; - - let page_size = match sysconf(SysconfVar::PAGE_SIZE) { - Ok(Some(page_size)) if page_size > 0 => u128::try_from(page_size).ok()?, - _ => return None, - }; + // Use libc::sysconf for _SC_PHYS_PAGES since rustix doesn't expose it + let pages = unsafe { libc::sysconf(libc::_SC_PHYS_PAGES) }; + if pages <= 0 { + return None; + } + let pages = u128::try_from(pages).ok()?; Some(pages.saturating_mul(page_size)) } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 8b321b55d..5fc00b95e 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1371,14 +1371,15 @@ fn make_sort_mode_arg(mode: &'static str, short: char, help: String) -> Arg { )) ))] fn get_rlimit() -> UResult { - use nix::sys::resource::{RLIM_INFINITY, Resource, getrlimit}; + use rustix::process::{Resource, getrlimit}; - let (rlim_cur, _rlim_max) = getrlimit(Resource::RLIMIT_NOFILE) - .map_err(|_| UUsageError::new(2, translate!("sort-failed-fetch-rlimit")))?; - if rlim_cur == RLIM_INFINITY { + let rlimit = getrlimit(Resource::Nofile); + let rlim_cur = rlimit.current; + if rlim_cur.is_none() { + // None means RLIM_INFINITY return Err(UUsageError::new(2, translate!("sort-failed-fetch-rlimit"))); } - usize::try_from(rlim_cur) + usize::try_from(rlim_cur.unwrap()) .map_err(|_| UUsageError::new(2, translate!("sort-failed-fetch-rlimit"))) } diff --git a/src/uu/stty/Cargo.toml b/src/uu/stty/Cargo.toml index e1fb8c5ba..5b846c8e7 100644 --- a/src/uu/stty/Cargo.toml +++ b/src/uu/stty/Cargo.toml @@ -24,7 +24,8 @@ uucore = { workspace = true, features = ["parser"] } fluent = { workspace = true } [target.'cfg(unix)'.dependencies] -nix = { workspace = true, features = ["ioctl", "term"] } +libc = { workspace = true } +rustix = { workspace = true, features = ["termios"] } [[bin]] name = "stty" diff --git a/src/uu/stty/src/flags.rs b/src/uu/stty/src/flags.rs index 79965bf6b..9317376b5 100644 --- a/src/uu/stty/src/flags.rs +++ b/src/uu/stty/src/flags.rs @@ -13,11 +13,8 @@ use crate::Flag; -#[cfg(not(bsd))] -use nix::sys::termios::BaudRate; -use nix::sys::termios::{ - ControlFlags as C, InputFlags as I, LocalFlags as L, OutputFlags as O, - SpecialCharacterIndices as S, +use rustix::termios::{ + ControlModes as C, InputModes as I, LocalModes as L, OutputModes as O, SpecialCodeIndex as S, }; #[derive(Debug)] @@ -31,10 +28,7 @@ pub enum BaudType { #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] pub enum AllFlags<'a> { - #[cfg(bsd)] Baud(u32, BaudType), - #[cfg(not(bsd))] - Baud(BaudRate, BaudType), ControlFlags((&'a Flag, bool)), InputFlags((&'a Flag, bool)), LocalFlags((&'a Flag, bool)), @@ -94,123 +88,88 @@ pub const OUTPUT_FLAGS: &[Flag] = &[ Flag::new("onlcr", O::ONLCR).sane(), Flag::new("onocr", O::ONOCR), Flag::new("onlret", O::ONLRET), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" - ))] + // Output delay flags: only available on Linux (glibc) and Android. + // Not available on BSDs (macOS/iOS), musl, or other platforms in rustix. + #[cfg(any(target_os = "android", target_os = "haiku", target_os = "linux"))] Flag::new("ofdel", O::OFDEL), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" + #[cfg(all( + any(target_os = "android", target_os = "haiku", target_os = "linux"), + not(target_env = "musl") ))] Flag::new_grouped("nl0", O::NL0, O::NLDLY).sane(), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" + #[cfg(all( + any(target_os = "android", target_os = "haiku", target_os = "linux"), + not(target_env = "musl") ))] Flag::new_grouped("nl1", O::NL1, O::NLDLY), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" + #[cfg(all( + any(target_os = "android", target_os = "haiku", target_os = "linux"), + not(target_env = "musl") ))] Flag::new_grouped("cr0", O::CR0, O::CRDLY).sane(), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" + #[cfg(all( + any(target_os = "android", target_os = "haiku", target_os = "linux"), + not(target_env = "musl") ))] Flag::new_grouped("cr1", O::CR1, O::CRDLY), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" + #[cfg(all( + any(target_os = "android", target_os = "haiku", target_os = "linux"), + not(target_env = "musl") ))] Flag::new_grouped("cr2", O::CR2, O::CRDLY), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" + #[cfg(all( + any(target_os = "android", target_os = "haiku", target_os = "linux"), + not(target_env = "musl") ))] Flag::new_grouped("cr3", O::CR3, O::CRDLY), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" + #[cfg(all( + any(target_os = "android", target_os = "haiku", target_os = "linux"), + not(target_env = "musl") ))] Flag::new_grouped("tab0", O::TAB0, O::TABDLY).sane(), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" + #[cfg(all( + any(target_os = "android", target_os = "haiku", target_os = "linux"), + not(target_env = "musl") ))] Flag::new_grouped("tab1", O::TAB1, O::TABDLY), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" + #[cfg(all( + any(target_os = "android", target_os = "haiku", target_os = "linux"), + not(target_env = "musl") ))] Flag::new_grouped("tab2", O::TAB2, O::TABDLY), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" + #[cfg(all( + any(target_os = "android", target_os = "haiku", target_os = "linux"), + not(target_env = "musl") ))] Flag::new_grouped("tab3", O::TAB3, O::TABDLY), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" + #[cfg(all( + any(target_os = "android", target_os = "haiku", target_os = "linux"), + not(target_env = "musl") ))] Flag::new_grouped("bs0", O::BS0, O::BSDLY).sane(), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" + #[cfg(all( + any(target_os = "android", target_os = "haiku", target_os = "linux"), + not(target_env = "musl") ))] Flag::new_grouped("bs1", O::BS1, O::BSDLY), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" + #[cfg(all( + any(target_os = "android", target_os = "haiku", target_os = "linux"), + not(target_env = "musl") ))] Flag::new_grouped("vt0", O::VT0, O::VTDLY).sane(), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" + #[cfg(all( + any(target_os = "android", target_os = "haiku", target_os = "linux"), + not(target_env = "musl") ))] Flag::new_grouped("vt1", O::VT1, O::VTDLY), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" + #[cfg(all( + any(target_os = "android", target_os = "haiku", target_os = "linux"), + not(target_env = "musl") ))] Flag::new_grouped("ff0", O::FF0, O::FFDLY).sane(), - #[cfg(any( - target_os = "android", - target_os = "haiku", - target_os = "linux", - target_vendor = "apple" + #[cfg(all( + any(target_os = "android", target_os = "haiku", target_os = "linux"), + not(target_env = "musl") ))] Flag::new_grouped("ff1", O::FF1, O::FFDLY), ]; @@ -241,63 +200,62 @@ pub const LOCAL_FLAGS: &[Flag] = &[ Flag::new("extproc", L::EXTPROC), ]; -// BSD's use u32 as baud rate, so using the enum is unnecessary. +// Baud rate table mapping string names to speed values. +// rustix uses actual integer speed values, not encoded B* constants. #[cfg(not(bsd))] -pub const BAUD_RATES: &[(&str, BaudRate)] = &[ - ("0", BaudRate::B0), - ("50", BaudRate::B50), - ("75", BaudRate::B75), - ("110", BaudRate::B110), - ("134", BaudRate::B134), - ("150", BaudRate::B150), - ("200", BaudRate::B200), - ("300", BaudRate::B300), - ("600", BaudRate::B600), - ("1200", BaudRate::B1200), - ("1800", BaudRate::B1800), - ("2400", BaudRate::B2400), - ("4800", BaudRate::B4800), - ("9600", BaudRate::B9600), - ("19200", BaudRate::B19200), - ("38400", BaudRate::B38400), - ("57600", BaudRate::B57600), - ("115200", BaudRate::B115200), - ("230400", BaudRate::B230400), - ("460800", BaudRate::B460800), +pub const BAUD_RATES: &[(&str, u32)] = &[ + ("0", 0), + ("50", 50), + ("75", 75), + ("110", 110), + ("134", 134), + ("150", 150), + ("200", 200), + ("300", 300), + ("600", 600), + ("1200", 1200), + ("1800", 1800), + ("2400", 2400), + ("9600", 9600), + ("19200", 19200), + ("38400", 38400), + ("57600", 57600), + ("115200", 115_200), + ("230400", 230_400), #[cfg(any(target_os = "android", target_os = "linux"))] - ("500000", BaudRate::B500000), + ("500000", 500_000), #[cfg(any(target_os = "android", target_os = "linux"))] - ("576000", BaudRate::B576000), + ("576000", 576_000), #[cfg(any(target_os = "android", target_os = "linux"))] - ("921600", BaudRate::B921600), + ("921600", 921_600), #[cfg(any(target_os = "android", target_os = "linux"))] - ("1000000", BaudRate::B1000000), + ("1000000", 1_000_000), #[cfg(any(target_os = "android", target_os = "linux"))] - ("1152000", BaudRate::B1152000), + ("1152000", 1_152_000), #[cfg(any(target_os = "android", target_os = "linux"))] - ("1500000", BaudRate::B1500000), + ("1500000", 1_500_000), #[cfg(any(target_os = "android", target_os = "linux"))] - ("2000000", BaudRate::B2000000), + ("2000000", 2_000_000), #[cfg(any( target_os = "android", all(target_os = "linux", not(target_arch = "sparc64")) ))] - ("2500000", BaudRate::B2500000), + ("2500000", 2_500_000), #[cfg(any( target_os = "android", all(target_os = "linux", not(target_arch = "sparc64")) ))] - ("3000000", BaudRate::B3000000), + ("3000000", 3_000_000), #[cfg(any( target_os = "android", all(target_os = "linux", not(target_arch = "sparc64")) ))] - ("3500000", BaudRate::B3500000), + ("3500000", 3_500_000), #[cfg(any( target_os = "android", all(target_os = "linux", not(target_arch = "sparc64")) ))] - ("4000000", BaudRate::B4000000), + ("4000000", 4_000_000), ]; /// Control characters for the stty command. /// diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 686f00a82..ec8cc4cf2 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -28,11 +28,11 @@ use nix::libc::{O_NONBLOCK, TIOCGWINSZ, TIOCSWINSZ, c_ushort}; ))] use nix::libc::{TCGETS2, termios2}; -use nix::sys::termios::{ - ControlFlags, InputFlags, LocalFlags, OutputFlags, SetArg, SpecialCharacterIndices as S, - Termios, cfsetispeed, cfsetospeed, tcgetattr, tcsetattr, +use rustix::termios::{ + ControlModes as ControlFlags, InputModes as InputFlags, LocalModes as LocalFlags, + OptionalActions, OutputModes as OutputFlags, SpecialCodeIndex as S, Termios, tcgetattr, + tcsetattr, }; -use nix::{ioctl_read_bad, ioctl_write_ptr_bad}; use std::cmp::Ordering; use std::fs::File; use std::io::{self, Stdin, stdin, stdout}; @@ -231,19 +231,27 @@ pub struct TermSize { y: c_ushort, } -ioctl_read_bad!( - /// Get terminal window size - tiocgwinsz, - TIOCGWINSZ, - TermSize -); +/// Get terminal window size via ioctl +/// +/// # Safety +/// `size` must point to a valid, writable `TermSize` and `fd` must be a valid terminal fd. +unsafe fn tiocgwinsz(fd: RawFd, size: *mut TermSize) -> io::Result<()> { + if unsafe { libc::ioctl(fd, TIOCGWINSZ, size) } == -1 { + return Err(io::Error::last_os_error()); + } + Ok(()) +} -ioctl_write_ptr_bad!( - /// Set terminal window size - tiocswinsz, - TIOCSWINSZ, - TermSize -); +/// Set terminal window size via ioctl +/// +/// # Safety +/// `size` must point to a valid `TermSize` and `fd` must be a valid terminal fd. +unsafe fn tiocswinsz(fd: RawFd, size: *mut TermSize) -> io::Result<()> { + if unsafe { libc::ioctl(fd, TIOCSWINSZ, size) } == -1 { + return Err(io::Error::last_os_error()); + } + Ok(()) +} #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -269,7 +277,7 @@ fn stty(opts: &Options) -> UResult<()> { )); } - let mut set_arg = SetArg::TCSADRAIN; + let mut set_arg = OptionalActions::Drain; let mut valid_args: Vec = Vec::new(); if let Some(args) = &opts.settings { @@ -352,10 +360,10 @@ fn stty(opts: &Options) -> UResult<()> { } } "drain" => { - set_arg = SetArg::TCSADRAIN; + set_arg = OptionalActions::Drain; } "-drain" => { - set_arg = SetArg::TCSANOW; + set_arg = OptionalActions::Now; } "size" => { valid_args.push(ArgOptions::Print(PrintSetting::Size)); @@ -550,7 +558,7 @@ fn check_flag_group(flag: &Flag, remove: bool) -> bool { remove && flag.group.is_some() } -fn print_special_setting(setting: &PrintSetting, fd: i32) -> nix::Result<()> { +fn print_special_setting(setting: &PrintSetting, fd: i32) -> io::Result<()> { match setting { PrintSetting::Size => { let mut size = TermSize::default(); @@ -626,23 +634,23 @@ fn print_terminal_size( opts: &Options, window_size: Option<&TermSize>, term_size: Option<&TermSize>, -) -> nix::Result<()> { +) -> io::Result<()> { // GNU linked against glibc 2.42 provides us baudrate 51 which panics cfgetospeed #[cfg(not(target_os = "linux"))] - let speed = nix::sys::termios::cfgetospeed(termios); - #[cfg(target_os = "linux")] - #[cfg(all(not(target_arch = "powerpc"), not(target_arch = "powerpc64")))] - ioctl_read_bad!(tcgets2, TCGETS2, termios2); + let speed = termios.output_speed(); #[cfg(target_os = "linux")] #[cfg(all(not(target_arch = "powerpc"), not(target_arch = "powerpc64")))] let speed = { let mut t2 = unsafe { std::mem::zeroed::() }; - unsafe { tcgets2(opts.file.as_raw_fd(), &raw mut t2)? }; + // SAFETY: TCGETS2 ioctl reads termios2 struct from fd + if unsafe { libc::ioctl(opts.file.as_raw_fd(), TCGETS2, &raw mut t2) } == -1 { + return Err(io::Error::last_os_error()); + } t2.c_ospeed }; #[cfg(target_os = "linux")] #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))] - let speed = nix::sys::termios::cfgetospeed(termios); + let speed = termios.output_speed(); let mut printer = WrappedPrinter::new(window_size); @@ -690,10 +698,7 @@ fn print_terminal_size( #[cfg(any(target_os = "linux", target_os = "redox"))] { - // For some reason the normal nix Termios struct does not expose the line, - // so we get the underlying libc::termios struct to get that information. - let libc_termios: nix::libc::termios = termios.clone().into(); - let line = libc_termios.c_line; + let line = termios.line_discipline; printer.print(&translate!("stty-output-line", "line" => line)); } printer.flush(); @@ -824,7 +829,7 @@ fn string_to_flag(option: &str) -> Option> { None } -fn control_char_to_string(cc: nix::libc::cc_t) -> nix::Result { +fn control_char_to_string(cc: libc::cc_t) -> io::Result { if cc == 0 { return Ok(translate!("stty-output-undef")); } @@ -844,7 +849,7 @@ fn control_char_to_string(cc: nix::libc::cc_t) -> nix::Result { // DEL character 0x7f => Ok(("^", '?')), // Out of range (above 8 bits) - _ => Err(nix::errno::Errno::ERANGE), + _ => Err(io::Error::from_raw_os_error(libc::ERANGE)), }?; Ok(format!("{meta_prefix}{ctrl_prefix}{character}")) @@ -854,12 +859,12 @@ fn print_control_chars( termios: &Termios, opts: &Options, term_size: Option<&TermSize>, -) -> nix::Result<()> { +) -> io::Result<()> { if !opts.all { // Print only control chars that differ from sane defaults let mut printer = WrappedPrinter::new(term_size); for (text, cc_index) in CONTROL_CHARS { - let current_val = termios.control_chars[*cc_index as usize]; + let current_val = termios.special_codes[*cc_index]; let sane_val = get_sane_control_char(*cc_index); if current_val != sane_val { @@ -877,12 +882,12 @@ fn print_control_chars( for (text, cc_index) in CONTROL_CHARS { printer.print(&format!( "{text} = {};", - control_char_to_string(termios.control_chars[*cc_index as usize])? + control_char_to_string(termios.special_codes[*cc_index])? )); } printer.print(&translate!("stty-output-min-time", - "min" => termios.control_chars[S::VMIN as usize], - "time" => termios.control_chars[S::VTIME as usize] + "min" => termios.special_codes[S::VMIN], + "time" => termios.special_codes[S::VTIME] )); printer.flush(); Ok(()) @@ -891,12 +896,17 @@ fn print_control_chars( fn print_in_save_format(termios: &Termios) { print!( "{:x}:{:x}:{:x}:{:x}", - termios.input_flags.bits(), - termios.output_flags.bits(), - termios.control_flags.bits(), - termios.local_flags.bits() + termios.input_modes.bits(), + termios.output_modes.bits(), + termios.control_modes.bits(), + termios.local_modes.bits() ); - for cc in termios.control_chars { + // Print all special codes in save format by accessing the raw cc array + // via a pointer cast, since rustix doesn't expose the inner array. + let cc_ptr = (&raw const termios.special_codes).cast::<[libc::cc_t; libc::NCCS]>(); + // SAFETY: SpecialCodes is a transparent wrapper around [cc_t; NCCS] + let cc_array = unsafe { &*cc_ptr }; + for cc in cc_array { print!(":{cc:x}"); } println!(); @@ -904,12 +914,12 @@ fn print_in_save_format(termios: &Termios) { /// Gets terminal size using the tiocgwinsz ioctl system call. /// This queries the kernel for the current terminal window dimensions. -fn get_terminal_size(fd: RawFd) -> nix::Result { +fn get_terminal_size(fd: RawFd) -> io::Result { let mut term_size = TermSize::default(); unsafe { tiocgwinsz(fd, &raw mut term_size) }.map(|_| term_size) } -fn print_settings(termios: &Termios, opts: &Options) -> nix::Result<()> { +fn print_settings(termios: &Termios, opts: &Options) -> io::Result<()> { if opts.save { print_in_save_format(termios); } else { @@ -972,7 +982,7 @@ fn print_flags( } /// Apply a single setting -fn apply_setting(termios: &mut Termios, setting: &AllFlags) -> nix::Result<()> { +fn apply_setting(termios: &mut Termios, setting: &AllFlags) -> io::Result<()> { match setting { AllFlags::Baud(_, _) => apply_baud_rate_flag(termios, setting)?, AllFlags::ControlFlags((setting, disable)) => { @@ -991,14 +1001,14 @@ fn apply_setting(termios: &mut Termios, setting: &AllFlags) -> nix::Result<()> { Ok(()) } -fn apply_baud_rate_flag(termios: &mut Termios, input: &AllFlags) -> nix::Result<()> { +fn apply_baud_rate_flag(termios: &mut Termios, input: &AllFlags) -> io::Result<()> { if let AllFlags::Baud(rate, baud_type) = input { match baud_type { - flags::BaudType::Input => cfsetispeed(termios, *rate)?, - flags::BaudType::Output => cfsetospeed(termios, *rate)?, + flags::BaudType::Input => termios.set_input_speed(*rate)?, + flags::BaudType::Output => termios.set_output_speed(*rate)?, flags::BaudType::Both => { - cfsetispeed(termios, *rate)?; - cfsetospeed(termios, *rate)?; + termios.set_input_speed(*rate)?; + termios.set_output_speed(*rate)?; } } } @@ -1006,7 +1016,7 @@ fn apply_baud_rate_flag(termios: &mut Termios, input: &AllFlags) -> nix::Result< } fn apply_char_mapping(termios: &mut Termios, mapping: &(S, u8)) { - termios.control_chars[mapping.0 as usize] = mapping.1; + termios.special_codes[mapping.0] = mapping.1; } /// Apply a saved terminal state to the current termios. @@ -1027,15 +1037,19 @@ fn apply_saved_state(termios: &mut Termios, state: &[u32]) { } // Apply the four flag groups, done (as _) for MacOS size compatibility - termios.input_flags = InputFlags::from_bits_truncate(state[0] as _); - termios.output_flags = OutputFlags::from_bits_truncate(state[1] as _); - termios.control_flags = ControlFlags::from_bits_truncate(state[2] as _); - termios.local_flags = LocalFlags::from_bits_truncate(state[3] as _); + termios.input_modes = InputFlags::from_bits_truncate(state[0] as _); + termios.output_modes = OutputFlags::from_bits_truncate(state[1] as _); + termios.control_modes = ControlFlags::from_bits_truncate(state[2] as _); + termios.local_modes = LocalFlags::from_bits_truncate(state[3] as _); // Apply control characters if present (stored as u32 but used as u8) + // Access the raw cc array via pointer cast since rustix doesn't expose the inner array. + let cc_ptr = (&raw mut termios.special_codes).cast::<[libc::cc_t; libc::NCCS]>(); + // SAFETY: SpecialCodes is a transparent wrapper around [cc_t; NCCS] + let cc_array = unsafe { &mut *cc_ptr }; for (i, &cc_val) in state.iter().skip(4).enumerate() { - if i < termios.control_chars.len() { - termios.control_chars[i] = cc_val as u8; + if i < cc_array.len() { + cc_array[i] = cc_val as u8; } } } @@ -1044,7 +1058,7 @@ fn apply_special_setting( _termios: &mut Termios, setting: &SpecialSetting, fd: i32, -) -> nix::Result<()> { +) -> io::Result<()> { let mut size = TermSize::default(); unsafe { tiocgwinsz(fd, &raw mut size)? }; match setting { @@ -1241,15 +1255,7 @@ fn get_sane_control_char(cc_index: S) -> u8 { } } // Default values for control chars not in the sane list - match cc_index { - S::VEOL => 0, - S::VEOL2 => 0, - S::VMIN => 1, - S::VTIME => 0, - #[cfg(target_os = "linux")] - S::VSWTC => 0, - _ => 0, - } + u8::from(cc_index == S::VMIN) } pub fn uu_app() -> Command { @@ -1291,45 +1297,45 @@ pub fn uu_app() -> Command { impl TermiosFlag for ControlFlags { fn is_in(&self, termios: &Termios, group: Option) -> bool { - termios.control_flags.contains(*self) - && group.is_none_or(|g| !termios.control_flags.intersects(g - *self)) + termios.control_modes.contains(*self) + && group.is_none_or(|g| !termios.control_modes.intersects(g - *self)) } fn apply(&self, termios: &mut Termios, val: bool) { - termios.control_flags.set(*self, val); + termios.control_modes.set(*self, val); } } impl TermiosFlag for InputFlags { fn is_in(&self, termios: &Termios, group: Option) -> bool { - termios.input_flags.contains(*self) - && group.is_none_or(|g| !termios.input_flags.intersects(g - *self)) + termios.input_modes.contains(*self) + && group.is_none_or(|g| !termios.input_modes.intersects(g - *self)) } fn apply(&self, termios: &mut Termios, val: bool) { - termios.input_flags.set(*self, val); + termios.input_modes.set(*self, val); } } impl TermiosFlag for OutputFlags { fn is_in(&self, termios: &Termios, group: Option) -> bool { - termios.output_flags.contains(*self) - && group.is_none_or(|g| !termios.output_flags.intersects(g - *self)) + termios.output_modes.contains(*self) + && group.is_none_or(|g| !termios.output_modes.intersects(g - *self)) } fn apply(&self, termios: &mut Termios, val: bool) { - termios.output_flags.set(*self, val); + termios.output_modes.set(*self, val); } } impl TermiosFlag for LocalFlags { fn is_in(&self, termios: &Termios, group: Option) -> bool { - termios.local_flags.contains(*self) - && group.is_none_or(|g| !termios.local_flags.intersects(g - *self)) + termios.local_modes.contains(*self) + && group.is_none_or(|g| !termios.local_modes.intersects(g - *self)) } fn apply(&self, termios: &mut Termios, val: bool) { - termios.local_flags.set(*self, val); + termios.local_modes.set(*self, val); } } diff --git a/src/uu/sync/Cargo.toml b/src/uu/sync/Cargo.toml index 2baad4931..963af9919 100644 --- a/src/uu/sync/Cargo.toml +++ b/src/uu/sync/Cargo.toml @@ -25,7 +25,8 @@ uucore = { workspace = true, features = ["wide"] } fluent = { workspace = true } [target.'cfg(unix)'.dependencies] -nix = { workspace = true } +libc = { workspace = true } +rustix = { workspace = true, features = ["fs"] } [target.'cfg(target_os = "windows")'.dependencies] windows-sys = { workspace = true, features = [ diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index b34d0ac0b..28a35d1eb 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -7,11 +7,7 @@ use clap::{Arg, ArgAction, Command}; #[cfg(any(target_os = "linux", target_os = "android"))] -use nix::errno::Errno; -#[cfg(any(target_os = "linux", target_os = "android"))] -use nix::fcntl::{OFlag, open}; -#[cfg(any(target_os = "linux", target_os = "android"))] -use nix::sys::stat::Mode; +use std::ffi::CString; use std::path::Path; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, get_exit_code, set_exit_code}; @@ -28,14 +24,11 @@ static ARG_FILES: &str = "files"; #[cfg(unix)] mod platform { - #[cfg(any(target_os = "linux", target_os = "android"))] - use nix::fcntl::{FcntlArg, OFlag, fcntl}; - use nix::unistd::sync; - #[cfg(any(target_os = "linux", target_os = "android"))] - use nix::unistd::{fdatasync, syncfs}; #[cfg(any(target_os = "linux", target_os = "android"))] use std::fs::{File, OpenOptions}; #[cfg(any(target_os = "linux", target_os = "android"))] + use std::os::fd::AsFd; + #[cfg(any(target_os = "linux", target_os = "android"))] use std::os::unix::fs::OpenOptionsExt; #[cfg(any(target_os = "linux", target_os = "android"))] use uucore::display::Quotable; @@ -51,7 +44,7 @@ mod platform { reason = "fn sig must match on all platforms" )] pub fn do_sync() -> UResult<()> { - sync(); + rustix::fs::sync(); Ok(()) } @@ -62,17 +55,17 @@ mod platform { fn open_and_reset_nonblock(path: &str) -> UResult { let f = OpenOptions::new() .read(true) - .custom_flags(OFlag::O_NONBLOCK.bits()) + .custom_flags(libc::O_NONBLOCK) .open(path) .map_err_context(|| path.to_string())?; // Reset O_NONBLOCK flag if it was set (matches GNU behavior) // This is non-critical, so we log errors but don't fail - if let Err(e) = fcntl(&f, FcntlArg::F_SETFL(OFlag::empty())) { + if let Err(e) = rustix::fs::fcntl_setfl(f.as_fd(), rustix::fs::OFlags::empty()) { use std::io::{Write, stderr}; let _ = writeln!( stderr(), "sync: {}", - translate!("sync-warning-fcntl-failed", "file" => path, "error" => e.to_string()) + translate!("sync-warning-fcntl-failed", "file" => path, "error" => std::io::Error::from(e).to_string()) ); uucore::error::set_exit_code(1); } @@ -83,9 +76,11 @@ mod platform { pub fn do_syncfs(files: Vec) -> UResult<()> { for path in files { let f = open_and_reset_nonblock(&path)?; - syncfs(f).map_err_context( - || translate!("sync-error-syncing-file", "file" => path.quote()), - )?; + rustix::fs::syncfs(f.as_fd()) + .map_err(std::io::Error::from) + .map_err_context( + || translate!("sync-error-syncing-file", "file" => path.quote()), + )?; } Ok(()) } @@ -94,9 +89,11 @@ mod platform { pub fn do_fdatasync(files: Vec) -> UResult<()> { for path in files { let f = open_and_reset_nonblock(&path)?; - fdatasync(f).map_err_context( - || translate!("sync-error-syncing-file", "file" => path.quote()), - )?; + rustix::fs::fdatasync(f.as_fd()) + .map_err(std::io::Error::from) + .map_err_context( + || translate!("sync-error-syncing-file", "file" => path.quote()), + )?; } Ok(()) } @@ -231,18 +228,26 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } for f in &files { - // Use the Nix open to be able to set the NONBLOCK flags for fifo files + // Use open with O_NONBLOCK to be able to handle fifo files #[cfg(any(target_os = "linux", target_os = "android"))] { let path = Path::new(&f); - if let Err(e) = open(path, OFlag::O_NONBLOCK, Mode::empty()) { - if e != Errno::EACCES || (e == Errno::EACCES && path.is_dir()) { + let c_path = CString::new(f.as_str()).unwrap(); + // SAFETY: c_path is a valid null-terminated C string + let fd = unsafe { libc::open(c_path.as_ptr(), libc::O_NONBLOCK) }; + if fd < 0 { + let err = std::io::Error::last_os_error(); + let is_eacces = err.raw_os_error() == Some(libc::EACCES); + if !is_eacces || path.is_dir() { show_error!( "{}", - translate!("sync-error-opening-file", "file" => f.quote(), "err" => e.desc()) + translate!("sync-error-opening-file", "file" => f.quote(), "err" => err) ); set_exit_code(1); } + } else { + // SAFETY: fd is a valid open file descriptor + unsafe { libc::close(fd) }; } } #[cfg(not(any(target_os = "linux", target_os = "android")))] diff --git a/src/uu/timeout/Cargo.toml b/src/uu/timeout/Cargo.toml index 24e16f95a..f4b39019b 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -25,8 +25,6 @@ libc = { workspace = true } uucore = { workspace = true, features = ["parser", "process", "signals"] } fluent = { workspace = true } -[target.'cfg(unix)'.dependencies] -nix = { workspace = true, features = ["signal"] } [[bin]] name = "timeout" diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 25590ebd6..4bb46e712 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -25,10 +25,9 @@ use uucore::{ signals::{signal_by_name_or_value, signal_list_name_by_value}, }; -use nix::sys::signal::{SigHandler, Signal, kill}; -use nix::unistd::{Pid, getpid, setpgid}; #[cfg(unix)] use std::os::unix::process::CommandExt; +use uucore::signals::csignal::{self, SigHandler}; pub mod options { pub static FOREGROUND: &str = "foreground"; @@ -182,7 +181,7 @@ pub fn uu_app() -> Command { /// Install SIGCHLD handler to ensure waiting for child works even if parent ignored SIGCHLD. fn install_sigchld() { extern "C" fn chld(_: libc::c_int) {} - let _ = unsafe { nix::sys::signal::signal(Signal::SIGCHLD, SigHandler::Handler(chld)) }; + let _ = unsafe { csignal::set_signal_handler(libc::SIGCHLD, SigHandler::Handler(chld)) }; } /// We should terminate child process when receiving termination signals. @@ -201,23 +200,24 @@ fn install_signal_handlers(term_signal: usize) { let sigpipe_ignored = uucore::signals::sigpipe_was_ignored(); for sig in [ - Signal::SIGALRM, - Signal::SIGINT, - Signal::SIGQUIT, - Signal::SIGHUP, - Signal::SIGTERM, - Signal::SIGPIPE, - Signal::SIGUSR1, - Signal::SIGUSR2, + libc::SIGALRM, + libc::SIGINT, + libc::SIGQUIT, + libc::SIGHUP, + libc::SIGTERM, + libc::SIGPIPE, + libc::SIGUSR1, + libc::SIGUSR2, ] { - if sig == Signal::SIGPIPE && sigpipe_ignored { + if sig == libc::SIGPIPE && sigpipe_ignored { continue; // Skip SIGPIPE if it was ignored by parent } - let _ = unsafe { nix::sys::signal::signal(sig, handler) }; + let _ = unsafe { csignal::set_signal_handler(sig, handler) }; } - if let Ok(sig) = Signal::try_from(term_signal as i32) { - let _ = unsafe { nix::sys::signal::signal(sig, handler) }; + let term_sig = term_signal as libc::c_int; + if term_sig > 0 { + let _ = unsafe { csignal::set_signal_handler(term_sig, handler) }; } } @@ -324,8 +324,11 @@ fn preserve_signal_info(signal: libc::c_int) -> libc::c_int { // The easiest way to preserve the latter seems to be to kill // ourselves with whatever signal our child exited with, which is // what the following is intended to accomplish. - if let Ok(sig) = Signal::try_from(signal) { - let _ = kill(getpid(), Some(sig)); + if signal > 0 { + // SAFETY: kill() and getpid() are standard POSIX functions + unsafe { + libc::kill(libc::getpid(), signal); + } } signal } @@ -346,7 +349,11 @@ fn timeout( verbose: bool, ) -> UResult<()> { if !foreground { - let _ = setpgid(Pid::from_raw(0), Pid::from_raw(0)); + // SAFETY: setpgid is a standard POSIX function; (0, 0) sets the current process + // as a new process group leader + unsafe { + libc::setpgid(0, 0); + } } let mut cmd_builder = process::Command::new(&cmd[0]); @@ -359,26 +366,26 @@ fn timeout( #[cfg(unix)] { #[cfg(target_os = "linux")] - let death_sig = Signal::try_from(signal as i32).ok(); + let death_sig = signal as libc::c_int; let sigpipe_was_ignored = uucore::signals::sigpipe_was_ignored(); let stdin_was_closed = uucore::signals::stdin_was_closed(); unsafe { cmd_builder.pre_exec(move || { // Reset terminal signals to default - let _ = nix::sys::signal::signal(Signal::SIGTTIN, SigHandler::SigDfl); - let _ = nix::sys::signal::signal(Signal::SIGTTOU, SigHandler::SigDfl); + let _ = csignal::set_signal_handler(libc::SIGTTIN, SigHandler::SigDfl); + let _ = csignal::set_signal_handler(libc::SIGTTOU, SigHandler::SigDfl); // Preserve SIGPIPE ignore status if parent had it ignored if sigpipe_was_ignored { - let _ = nix::sys::signal::signal(Signal::SIGPIPE, SigHandler::SigIgn); + let _ = csignal::set_signal_handler(libc::SIGPIPE, SigHandler::SigIgn); } // If stdin was closed before Rust reopened it as /dev/null, close it in child if stdin_was_closed { libc::close(libc::STDIN_FILENO); } #[cfg(target_os = "linux")] - if let Some(sig) = death_sig { - let _ = nix::sys::prctl::set_pdeathsig(sig); + if death_sig > 0 { + libc::prctl(libc::PR_SET_PDEATHSIG, death_sig); } Ok(()) }); diff --git a/src/uu/wc/src/count_fast.rs b/src/uu/wc/src/count_fast.rs index 2df9efc00..18ddbf6e2 100644 --- a/src/uu/wc/src/count_fast.rs +++ b/src/uu/wc/src/count_fast.rs @@ -29,7 +29,7 @@ use libc::S_IFIFO; #[cfg(any(target_os = "linux", target_os = "android"))] use uucore::pipes::{MAX_ROOTLESS_PIPE_SIZE, pipe, splice, splice_exact}; -const BUF_SIZE: usize = 64 * 1024; +const BUF_SIZE: usize = 256 * 1024; /// This is a Linux-specific function to count the number of bytes using the /// `splice` system call, which is faster than using `read`. @@ -40,33 +40,23 @@ const BUF_SIZE: usize = 64 * 1024; #[cfg(any(target_os = "linux", target_os = "android"))] fn count_bytes_using_splice(fd: &impl AsFd) -> Result { let null_file = uucore::pipes::dev_null().ok_or(0_usize)?; + let (pipe_rd, pipe_wr) = pipe().map_err(|_| 0_usize)?; + let mut byte_count = 0; - if let Ok(res) = splice(fd, &null_file, MAX_ROOTLESS_PIPE_SIZE) { - byte_count += res; - // no need to increase pipe size of input fd since - // - sender with splice probably increased size already - // - sender without splice is bottleneck of our wc -c - loop { - match splice(fd, &null_file, MAX_ROOTLESS_PIPE_SIZE) { - Ok(0) => break, - Ok(res) => byte_count += res, - Err(_) => return Err(byte_count), - } - } - } else if let Ok((pipe_rd, pipe_wr)) = pipe() { - // input is not pipe. needs broker to use splice() with additional cost - loop { - match splice(fd, &pipe_wr, MAX_ROOTLESS_PIPE_SIZE) { - Ok(0) => break, - Ok(res) => { - byte_count += res; - splice_exact(&pipe_rd, &null_file, res).map_err(|_| byte_count)?; + // improve throughput from pipe + let _ = rustix::pipe::fcntl_setpipe_size(fd, MAX_ROOTLESS_PIPE_SIZE); + loop { + match splice(fd, &pipe_wr, MAX_ROOTLESS_PIPE_SIZE) { + Ok(0) => break, + Ok(res) => { + byte_count += res; + // Silent the warning as we want to the error message + if splice_exact(&pipe_rd, &null_file, res).is_err() { + return Err(byte_count); } - Err(_) => return Err(byte_count), } + Err(_) => return Err(byte_count), } - } else { - return Ok(0_usize); } Ok(byte_count) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 8b6b8dc41..2932319ae 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -23,6 +23,7 @@ use std::{ }; use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser}; +use libc; use thiserror::Error; use unicode_width::UnicodeWidthChar; use utf8::{BufReadDecoder, BufReadDecoderError}; diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs index d2239d128..8f1c14ea0 100644 --- a/src/uucore/src/lib/mods/error.rs +++ b/src/uucore/src/lib/mods/error.rs @@ -567,6 +567,47 @@ impl From for Box { } } +/// Enables the conversion from [`Result`] to [`UResult`]. +#[cfg(unix)] +impl FromIo> for Result { + fn map_err_context(self, context: impl FnOnce() -> String) -> UResult { + self.map_err(|e| { + Box::new(UIoError { + context: Some(context()), + inner: std::io::Error::from(e), + }) as Box + }) + } +} + +#[cfg(unix)] +impl FromIo> for rustix::io::Errno { + fn map_err_context(self, context: impl FnOnce() -> String) -> UResult { + Err(Box::new(UIoError { + context: Some(context()), + inner: std::io::Error::from(self), + }) as Box) + } +} + +#[cfg(unix)] +impl From for UIoError { + fn from(f: rustix::io::Errno) -> Self { + Self { + context: None, + inner: std::io::Error::from(f), + } + } +} + +#[cfg(unix)] +impl From for Box { + fn from(f: rustix::io::Errno) -> Self { + let u_error: UIoError = f.into(); + Box::new(u_error) as Self + } +} + /// Shorthand to construct [`UIoError`]-instances. /// /// This macro serves as a convenience call to quickly construct instances of