diff --git a/Cargo.lock b/Cargo.lock index a5fb3f821dd..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,7 @@ version = "0.13.0" dependencies = [ "clap", "fluent", - "nix", + "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 f25823b9ec1..c3d8ed68733 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -20,7 +20,7 @@ doctest = false [dependencies] clap = { workspace = true } fluent = { workspace = true } -nix = { workspace = true, features = ["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 2a91f98ba0d..401f1606ef7 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -3,11 +3,11 @@ // 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 RAII mknodat 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::{CWD, Dev, FileType as RustixFileType, Mode, mknodat}; +use rustix::process::umask; use std::ffi::OsString; use std::io::{self, Write as _}; @@ -17,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"; @@ -29,7 +28,7 @@ mod options { pub const CONTEXT: &str = "context"; } -#[derive(Clone, PartialEq)] +#[derive(Clone, Copy, PartialEq)] enum FileType { Block, Character, @@ -37,11 +36,11 @@ enum FileType { } impl FileType { - fn as_sflag(&self) -> SFlag { + fn to_file_type(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 +55,7 @@ struct Config { /// when false, the exact mode bits will be set use_umask: bool, - dev: dev_t, + dev: Dev, /// Set security context (SELinux/SMACK). #[cfg(any( @@ -73,11 +72,27 @@ 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); + } +} + 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_file_type().as_raw_mode() | config.mode.as_raw_mode(); match uucore::selinux::FsCreateContext::new( std::path::Path::new(file_name), Some(mode), @@ -93,27 +108,22 @@ 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 = mknodat( + CWD, file_name, - config.file_type.as_sflag(), + config.file_type.to_file_type(), config.mode, 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 { let _ = writeln!( io::stderr(), @@ -174,7 +184,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 +225,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( 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!());