From 7da29f206575a6c879f972e38a4a879ac76bd15f Mon Sep 17 00:00:00 2001 From: mattsu Date: Sat, 16 May 2026 20:29:16 +0900 Subject: [PATCH 1/4] 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 | 84 ++++++++++++++++++++++++--------------- 3 files changed, 57 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5fb3f821dd..a5f69a16082 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3768,7 +3768,8 @@ version = "0.13.0" dependencies = [ "clap", "fluent", - "nix", + "libc", + "rustix", "uucore", ] diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index f25823b9ec1..d38c4fbed39 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -20,7 +20,8 @@ doctest = false [dependencies] clap = { workspace = true } fluent = { workspace = true } -nix = { workspace = true, features = ["fs"] } +rustix = { workspace = true, features = ["process", "fs"] } +libc = { workspace = true } uucore = { workspace = true, features = ["fs", "mode"] } [features] diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 2a91f98ba0d..4445f428047 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, dev_t, mknod as nix_mknod, umask as nix_umask}; +use rustix::fs::{FileType as RustixFileType, Mode}; +use rustix::process::umask; use std::ffi::OsString; use std::io::{self, Write as _}; @@ -17,8 +19,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"; @@ -29,7 +30,7 @@ mod options { pub const CONTEXT: &str = "context"; } -#[derive(Clone, PartialEq)] +#[derive(Clone, Copy, PartialEq)] enum FileType { Block, Character, @@ -37,11 +38,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, } } } @@ -56,7 +57,7 @@ struct Config { /// when false, the exact mode bits will be set use_umask: bool, - dev: dev_t, + dev: libc::dev_t, /// Set security context (SELinux/SMACK). #[cfg(any( @@ -73,11 +74,43 @@ 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 { // Label the node at creation, as GNU does; relabelling after leaves a window. #[cfg(all(feature = "selinux", any(target_os = "android", target_os = "linux")))] let _selinux_guard = if config.set_security_context { - let mode = config.file_type.as_sflag().bits() | config.mode.bits(); + let mode = config.file_type.to_rustix().as_raw_mode() | config.mode.as_raw_mode(); match uucore::selinux::FsCreateContext::new( std::path::Path::new(file_name), Some(mode), @@ -93,34 +126,23 @@ fn mknod(file_name: &str, config: Config) -> i32 { None }; - // 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, + config.dev as _, ) .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 { - let _ = writeln!( - io::stderr(), - "{}: {}", - uucore::execution_phrase(), - io::Error::from(err) - ); + let _ = writeln!(io::stderr(), "{}: {err}", uucore::execution_phrase()); } // Apply SMACK context if requested @@ -174,7 +196,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { mode } }; - 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") @@ -215,7 +237,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 8dc3c54297b69e533308cf2878cb294d3d35f5a2 Mon Sep 17 00:00:00 2001 From: mattsu Date: Sat, 16 May 2026 20:30:52 +0900 Subject: [PATCH 2/4] 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 4445f428047..dce421392e8 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 dd05e044fbc65a14d0c6acb821043ce777a63842 Mon Sep 17 00:00:00 2001 From: mattsu Date: Sat, 16 May 2026 20:57:42 +0900 Subject: [PATCH 3/4] 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 | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index d38c4fbed39..89c130dee09 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -20,8 +20,8 @@ doctest = false [dependencies] clap = { workspace = true } fluent = { workspace = true } -rustix = { workspace = true, features = ["process", "fs"] } libc = { workspace = true } +rustix = { workspace = true, features = ["process", "fs"] } uucore = { workspace = true, features = ["fs", "mode"] } [features] diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index dce421392e8..f72d292e02b 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -38,7 +38,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, @@ -110,7 +110,7 @@ fn mknod(file_name: &str, config: Config) -> i32 { // Label the node at creation, as GNU does; relabelling after leaves a window. #[cfg(all(feature = "selinux", any(target_os = "android", target_os = "linux")))] let _selinux_guard = if config.set_security_context { - let mode = config.file_type.to_rustix().as_raw_mode() | config.mode.as_raw_mode(); + let mode = config.file_type.to_file_type().as_raw_mode() | config.mode.as_raw_mode(); match uucore::selinux::FsCreateContext::new( std::path::Path::new(file_name), Some(mode), @@ -134,7 +134,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 as _, ) From 9ddd0cf06fe532d37a440df7a8638a42bd3653be Mon Sep 17 00:00:00 2001 From: mattsu Date: Tue, 29 Sep 2026 09:08:56 +0900 Subject: [PATCH 4/4] mknod: test upstream rustix Apple support with Cargo patch --- Cargo.lock | 6 ++---- Cargo.toml | 3 +++ src/uu/mknod/Cargo.toml | 3 +-- src/uu/mknod/src/mknod.rs | 34 +++++++++++----------------------- tests/by-util/test_mknod.rs | 28 ++++++++++++++++++++++++++++ 5 files changed, 45 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5f69a16082..f7c6d52b05e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2618,14 +2618,13 @@ dependencies = [ [[package]] name = "rustix" version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +source = "git+https://github.com/mattsu2020/rustix.git?branch=mknodat_macos#cdce88186f1501937bc01ce23b74c654f6bf9348" dependencies = [ "bitflags 2.13.2", "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -3768,7 +3767,6 @@ version = "0.13.0" dependencies = [ "clap", "fluent", - "libc", "rustix", "uucore", ] diff --git a/Cargo.toml b/Cargo.toml index d2521e2a426..f6e9cfdf66f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -706,3 +706,6 @@ strip = true inherits = "release" panic = "unwind" debug = 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 89c130dee09..c3d8ed68733 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -20,8 +20,7 @@ doctest = false [dependencies] clap = { workspace = true } fluent = { workspace = true } -libc = { workspace = true } -rustix = { workspace = true, features = ["process", "fs"] } +rustix = { workspace = true, features = ["fs", "process"] } uucore = { workspace = true, features = ["fs", "mode"] } [features] diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index f72d292e02b..401f1606ef7 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, Dev, FileType as RustixFileType, Mode, mknodat}; use rustix::process::umask; use std::ffi::OsString; use std::io::{self, Write as _}; @@ -57,7 +55,7 @@ struct Config { /// when false, the exact mode bits will be set use_umask: bool, - dev: libc::dev_t, + dev: Dev, /// Set security context (SELinux/SMACK). #[cfg(any( @@ -90,22 +88,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 { // Label the node at creation, as GNU does; relabelling after leaves a window. #[cfg(all(feature = "selinux", any(target_os = "android", target_os = "linux")))] @@ -132,17 +114,23 @@ 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 as _, + config.dev, ) .err(); let errno = if mknod_err.is_some() { -1 } else { 0 }; if let Some(err) = mknod_err { - let _ = writeln!(io::stderr(), "{}: {err}", uucore::execution_phrase()); + let _ = writeln!( + io::stderr(), + "{}: {}", + uucore::execution_phrase(), + io::Error::from(err) + ); } // Apply SMACK context if requested diff --git a/tests/by-util/test_mknod.rs b/tests/by-util/test_mknod.rs index 49fdbc243f9..ab059d1b874 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!());