From 9be4058da1eb1aaa16208a7cbc8088fc44f47630 Mon Sep 17 00:00:00 2001 From: mattsu Date: Sat, 16 May 2026 20:29:16 +0900 Subject: [PATCH 1/5] mknod: replace nix::sys::stat with rustix + libc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace `nix::sys::stat` usage with `rustix::fs` types and `libc::mknod`: - `nix::sys::stat::Mode` → `rustix::fs::Mode` - `nix::sys::stat::SFlag` → `rustix::fs::FileType` - `nix::sys::stat::umask` → `rustix::process::umask` with RAII `UmaskGuard` - `nix::sys::stat::mknod` → `libc::mknod` (rustix::fs::mknodat is unavailable on apple targets) - `nix::libc::S_I*` constants → `0o666` literal The `UmaskGuard` pattern (borrowed from mkdir) ensures umask is restored on drop, even on panic — an improvement over the previous manual save/restore. --- Cargo.lock | 3 +- src/uu/mknod/Cargo.toml | 3 +- src/uu/mknod/src/mknod.rs | 79 +++++++++++++++++++++++++-------------- 3 files changed, 55 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 275c7b23117..bbdb7b6f98c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3778,7 +3778,8 @@ version = "0.8.0" dependencies = [ "clap", "fluent", - "nix", + "libc", + "rustix", "uucore", ] diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index 03ad05b9d50..8ba32c343e6 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -24,7 +24,8 @@ doctest = false clap = { workspace = true } uucore = { workspace = true, features = ["mode", "fs"] } fluent = { workspace = true } -nix = { workspace = true } +rustix = { workspace = true, features = ["process", "fs"] } +libc = { workspace = true } [features] selinux = ["uucore/selinux"] diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 9640806d818..bbf0a427cf6 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -3,11 +3,13 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) parsemode makedev sysmacros perror IFBLK IFCHR IFIFO sflag +// spell-checker:ignore (ToDO) parsemode makedev sysmacros perror + +use std::ffi::CString; 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 rustix::fs::{FileType as RustixFileType, Mode}; +use rustix::process::umask; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError, set_exit_code}; @@ -15,8 +17,7 @@ use uucore::format_usage; use uucore::fs::makedev; use uucore::translate; -#[allow(clippy::unnecessary_cast)] -const MODE_RW_UGO: u32 = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; +const MODE_RW_UGO: u32 = 0o666; mod options { pub const MODE: &str = "mode"; @@ -27,7 +28,7 @@ mod options { pub const CONTEXT: &str = "context"; } -#[derive(Clone, PartialEq)] +#[derive(Clone, Copy, PartialEq)] enum FileType { Block, Character, @@ -35,11 +36,11 @@ enum FileType { } impl FileType { - fn as_sflag(&self) -> SFlag { + fn to_rustix(self) -> RustixFileType { match self { - Self::Block => SFlag::S_IFBLK, - Self::Character => SFlag::S_IFCHR, - Self::Fifo => SFlag::S_IFIFO, + Self::Block => RustixFileType::BlockDevice, + Self::Character => RustixFileType::CharacterDevice, + Self::Fifo => RustixFileType::Fifo, } } } @@ -71,34 +72,56 @@ struct Config { context: Option, } +/// RAII guard to restore umask on drop, ensuring cleanup even on panic. +struct UmaskGuard(Mode); + +impl UmaskGuard { + fn set(new_mask: Mode) -> Self { + let old_mask = umask(new_mask); + Self(old_mask) + } +} + +impl Drop for UmaskGuard { + fn drop(&mut self) { + umask(self.0); + } +} + +/// Create a special file using `mknod(2)`. +/// +/// Uses `libc::mknod` directly since `rustix::fs::mknodat` is unavailable on +/// Apple targets. Combines `file_type` (S_IF* bits) with `mode` (permission +/// bits) into the raw `mode_t` argument expected by the syscall. +fn do_mknod(path: &str, file_type: RustixFileType, mode: Mode, dev: u64) -> std::io::Result<()> { + let raw_mode = file_type.as_raw_mode() | mode.as_raw_mode(); + let c_path = CString::new(path)?; + let result = unsafe { libc::mknod(c_path.as_ptr(), raw_mode as _, dev as _) }; + if result == 0 { + Ok(()) + } else { + Err(std::io::Error::last_os_error()) + } +} + fn mknod(file_name: &str, config: Config) -> i32 { - // set umask to 0 and store previous umask - let have_prev_umask = if config.use_umask { + let _guard = if config.use_umask { None } else { - Some(nix_umask(Mode::empty())) + Some(UmaskGuard::set(Mode::empty())) }; - let mknod_err = nix_mknod( + let mknod_err = do_mknod( file_name, - config.file_type.as_sflag(), + config.file_type.to_rustix(), config.mode, - config.dev as _, + config.dev, ) .err(); let errno = if mknod_err.is_some() { -1 } else { 0 }; - // set umask back to original value - if let Some(prev_umask) = have_prev_umask { - nix_umask(prev_umask); - } - if let Some(err) = mknod_err { - eprintln!( - "{}: {}", - uucore::execution_phrase(), - std::io::Error::from(err) - ); + eprintln!("{}: {err}", uucore::execution_phrase()); } // Apply SELinux context if requested @@ -149,7 +172,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::from_bits_truncate(mode_permissions as _); let file_name = matches .get_one::("name") @@ -190,7 +213,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let config = Config { mode, - file_type: file_type.clone(), + file_type: *file_type, use_umask, dev, #[cfg(any( From 5f297d2ffd04e36b6e3d7fb85143ba04407919aa Mon Sep 17 00:00:00 2001 From: mattsu Date: Sat, 16 May 2026 20:30:52 +0900 Subject: [PATCH 2/5] fix: add cspell ignore entries for RAII and mknodat --- src/uu/mknod/src/mknod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index bbf0a427cf6..8c75939391a 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) parsemode makedev sysmacros perror +// spell-checker:ignore (ToDO) parsemode makedev sysmacros perror RAII mknodat use std::ffi::CString; From 25073f19fcaacc88895e94bf72a709db06a1b4bb Mon Sep 17 00:00:00 2001 From: mattsu Date: Sat, 16 May 2026 20:57:42 +0900 Subject: [PATCH 3/5] fix: address PR review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename `to_rustix` → `to_file_type` to avoid embedding crate name - Use `let _ = writeln!(stderr, ...)` to prevent SIGABRT on /dev/full - Sort Cargo.toml dependencies alphabetically (libc before rustix) --- src/uu/mknod/Cargo.toml | 2 +- src/uu/mknod/src/mknod.rs | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index 8ba32c343e6..72c67ce35de 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -24,8 +24,8 @@ doctest = false clap = { workspace = true } uucore = { workspace = true, features = ["mode", "fs"] } fluent = { workspace = true } -rustix = { workspace = true, features = ["process", "fs"] } libc = { workspace = true } +rustix = { workspace = true, features = ["process", "fs"] } [features] selinux = ["uucore/selinux"] diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 8c75939391a..b531a86fd73 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -36,7 +36,7 @@ enum FileType { } impl FileType { - fn to_rustix(self) -> RustixFileType { + fn to_file_type(self) -> RustixFileType { match self { Self::Block => RustixFileType::BlockDevice, Self::Character => RustixFileType::CharacterDevice, @@ -113,7 +113,7 @@ fn mknod(file_name: &str, config: Config) -> i32 { let mknod_err = do_mknod( file_name, - config.file_type.to_rustix(), + config.file_type.to_file_type(), config.mode, config.dev, ) @@ -121,7 +121,12 @@ fn mknod(file_name: &str, config: Config) -> i32 { let errno = if mknod_err.is_some() { -1 } else { 0 }; if let Some(err) = mknod_err { - eprintln!("{}: {err}", uucore::execution_phrase()); + use std::io::Write as _; + let _ = writeln!( + std::io::stderr(), + "{}: {err}", + uucore::execution_phrase() + ); } // Apply SELinux context if requested From 000ee9de630069b04385896b4b0a8c24f06e6c61 Mon Sep 17 00:00:00 2001 From: mattsu Date: Sat, 16 May 2026 20:59:01 +0900 Subject: [PATCH 4/5] refactor(mknod): simplify error message formatting Replaced the multi-line writeln! macro call with a single-line version for better readability. --- src/uu/mknod/src/mknod.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index b531a86fd73..21c6cbc40fc 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -122,11 +122,7 @@ fn mknod(file_name: &str, config: Config) -> i32 { if let Some(err) = mknod_err { use std::io::Write as _; - let _ = writeln!( - std::io::stderr(), - "{}: {err}", - uucore::execution_phrase() - ); + let _ = writeln!(std::io::stderr(), "{}: {err}", uucore::execution_phrase()); } // Apply SELinux context if requested From f37d62d200e04856395392a7f79cfd58fc6c7e50 Mon Sep 17 00:00:00 2001 From: mattsu Date: Tue, 29 Sep 2026 09:08:56 +0900 Subject: [PATCH 5/5] mknod: test upstream rustix Apple support with Cargo patch --- Cargo.lock | 20 +++++++++----------- Cargo.toml | 3 +++ src/uu/mknod/Cargo.toml | 1 - src/uu/mknod/src/mknod.rs | 26 +++++--------------------- tests/by-util/test_mknod.rs | 28 ++++++++++++++++++++++++++++ 5 files changed, 45 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bbdb7b6f98c..6d5064fd2e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1001,7 +1001,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1730,7 +1730,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2046,7 +2046,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2637,15 +2637,14 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +version = "1.1.5" +source = "git+https://github.com/mattsu2020/rustix.git?branch=mknodat_macos#cdce88186f1501937bc01ce23b74c654f6bf9348" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2945,7 +2944,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2955,7 +2954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -3778,7 +3777,6 @@ version = "0.8.0" dependencies = [ "clap", "fluent", - "libc", "rustix", "uucore", ] @@ -4750,7 +4748,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a775c1e7ef7..25bc9277943 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -767,3 +767,6 @@ debug = true [lints] workspace = true + +[patch.crates-io] +rustix = { git = "https://github.com/mattsu2020/rustix.git", branch = "mknodat_macos" } diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index 72c67ce35de..2a30f91a177 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -24,7 +24,6 @@ doctest = false clap = { workspace = true } uucore = { workspace = true, features = ["mode", "fs"] } fluent = { workspace = true } -libc = { workspace = true } rustix = { workspace = true, features = ["process", "fs"] } [features] diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 21c6cbc40fc..adc1d8687e1 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -5,10 +5,8 @@ // spell-checker:ignore (ToDO) parsemode makedev sysmacros perror RAII mknodat -use std::ffi::CString; - use clap::{Arg, ArgAction, Command, value_parser}; -use rustix::fs::{FileType as RustixFileType, Mode}; +use rustix::fs::{CWD, FileType as RustixFileType, Mode, mknodat}; use rustix::process::umask; use uucore::display::Quotable; @@ -88,22 +86,6 @@ impl Drop for UmaskGuard { } } -/// Create a special file using `mknod(2)`. -/// -/// Uses `libc::mknod` directly since `rustix::fs::mknodat` is unavailable on -/// Apple targets. Combines `file_type` (S_IF* bits) with `mode` (permission -/// bits) into the raw `mode_t` argument expected by the syscall. -fn do_mknod(path: &str, file_type: RustixFileType, mode: Mode, dev: u64) -> std::io::Result<()> { - let raw_mode = file_type.as_raw_mode() | mode.as_raw_mode(); - let c_path = CString::new(path)?; - let result = unsafe { libc::mknod(c_path.as_ptr(), raw_mode as _, dev as _) }; - if result == 0 { - Ok(()) - } else { - Err(std::io::Error::last_os_error()) - } -} - fn mknod(file_name: &str, config: Config) -> i32 { let _guard = if config.use_umask { None @@ -111,17 +93,19 @@ fn mknod(file_name: &str, config: Config) -> i32 { Some(UmaskGuard::set(Mode::empty())) }; - let mknod_err = do_mknod( + let mknod_err = mknodat( + CWD, file_name, config.file_type.to_file_type(), config.mode, - config.dev, + config.dev as _, ) .err(); let errno = if mknod_err.is_some() { -1 } else { 0 }; if let Some(err) = mknod_err { use std::io::Write as _; + let err = std::io::Error::from(err); let _ = writeln!(std::io::stderr(), "{}: {err}", uucore::execution_phrase()); } diff --git a/tests/by-util/test_mknod.rs b/tests/by-util/test_mknod.rs index 1bcae7ea31a..ad655905b5e 100644 --- a/tests/by-util/test_mknod.rs +++ b/tests/by-util/test_mknod.rs @@ -72,6 +72,34 @@ fn test_mknod_fifo_mnemonic_usage() { assert!(ts.fixtures.is_fifo("test_file")); } +#[test] +fn test_mknod_fifo_umask() { + for (mode, expected) in [(None, 0o600), (Some("0644"), 0o644)] { + let ts = TestScenario::new(util_name!()); + let mut cmd = ts.ucmd(); + if let Some(mode) = mode { + cmd.args(&["-m", mode]); + } + cmd.args(&["test_file", "p"]).umask(0o077).succeeds(); + assert!(ts.fixtures.is_fifo("test_file")); + assert_eq!( + ts.fixtures.metadata("test_file").permissions().mode() & 0o777, + expected + ); + } +} + +#[test] +fn test_mknod_fifo_existing_file() { + let ts = TestScenario::new(util_name!()); + ts.fixtures.write("test_file", "keep this content"); + ts.ucmd() + .args(&["test_file", "p"]) + .fails() + .stderr_contains("File exists"); + assert_eq!(ts.fixtures.read("test_file"), "keep this content"); +} + #[test] fn test_mknod_fifo_read_only() { let ts = TestScenario::new(util_name!());