diff --git a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt index 180111d3d54..4de6f38f07a 100644 --- a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt +++ b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt @@ -34,6 +34,7 @@ RISCV RNG # random number generator RNGs Solaris +TOCTOU # time-of-check time-of-use UID # user ID UIDs UUID # universally unique identifier diff --git a/src/uu/chmod/Cargo.toml b/src/uu/chmod/Cargo.toml index bae2961ae89..e1be6896fb8 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -20,15 +20,12 @@ path = "src/chmod.rs" [dependencies] clap = { workspace = true } thiserror = { workspace = true } -uucore = { workspace = true, features = [ - "entries", - "fs", - "mode", - "perms", - "safe-traversal", -] } +uucore = { workspace = true, features = ["entries", "fs", "mode", "perms"] } fluent = { workspace = true } +[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies] +uucore = { workspace = true, features = ["safe-traversal"] } + [[bin]] name = "chmod" path = "src/main.rs" diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 43760b45017..6ff03e5032c 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -18,7 +18,7 @@ use uucore::libc::mode_t; use uucore::mode; use uucore::perms::{TraverseSymlinks, configure_symlink_and_recursion}; -#[cfg(target_os = "linux")] +#[cfg(all(unix, not(target_os = "redox")))] use uucore::safe_traversal::DirFd; use uucore::{format_usage, show, show_error}; @@ -338,7 +338,7 @@ impl Chmoder { } /// Handle symlinks during directory traversal based on traversal mode - #[cfg(not(target_os = "linux"))] + #[cfg(not(unix))] fn handle_symlink_during_traversal( &self, path: &Path, @@ -423,7 +423,8 @@ impl Chmoder { matches!(fs::canonicalize(&file), Ok(p) if p == Path::new("/")) } - #[cfg(not(target_os = "linux"))] + // Non-safe traversal implementation for platforms without safe_traversal support + #[cfg(any(not(unix), target_os = "redox"))] fn walk_dir_with_context(&self, file_path: &Path, is_command_line_arg: bool) -> UResult<()> { let mut r = self.chmod_file(file_path); @@ -436,8 +437,7 @@ impl Chmoder { // If the path is a directory (or we should follow symlinks), recurse into it if (!file_path.is_symlink() || should_follow_symlink) && file_path.is_dir() { - // We buffer all paths in this dir to not keep to be able to close the fd so not - // too many fd's are open during the recursion + // We buffer all paths in this dir to not keep too many fd's open during recursion let mut paths_in_this_dir = Vec::new(); for dir_entry in file_path.read_dir()? { @@ -450,9 +450,16 @@ impl Chmoder { } } for path in paths_in_this_dir { - if path.is_symlink() { - r = self.handle_symlink_during_recursion(&path).and(r); - } else { + #[cfg(not(unix))] + { + if path.is_symlink() { + r = self.handle_symlink_during_recursion(&path).and(r); + } else { + r = self.walk_dir_with_context(path.as_path(), false).and(r); + } + } + #[cfg(target_os = "redox")] + { r = self.walk_dir_with_context(path.as_path(), false).and(r); } } @@ -460,7 +467,7 @@ impl Chmoder { r } - #[cfg(target_os = "linux")] + #[cfg(all(unix, not(target_os = "redox")))] fn walk_dir_with_context(&self, file_path: &Path, is_command_line_arg: bool) -> UResult<()> { let mut r = self.chmod_file(file_path); @@ -490,7 +497,7 @@ impl Chmoder { r } - #[cfg(target_os = "linux")] + #[cfg(all(unix, not(target_os = "redox")))] fn safe_traverse_dir(&self, dir_fd: &DirFd, dir_path: &Path) -> UResult<()> { let mut r = Ok(()); @@ -546,7 +553,7 @@ impl Chmoder { r } - #[cfg(target_os = "linux")] + #[cfg(all(unix, not(target_os = "redox")))] fn handle_symlink_during_safe_recursion( &self, path: &Path, @@ -578,7 +585,7 @@ impl Chmoder { } } - #[cfg(target_os = "linux")] + #[cfg(all(unix, not(target_os = "redox")))] fn safe_chmod_file( &self, file_path: &Path, @@ -608,7 +615,7 @@ impl Chmoder { Ok(()) } - #[cfg(not(target_os = "linux"))] + #[cfg(not(unix))] fn handle_symlink_during_recursion(&self, path: &Path) -> UResult<()> { // Use the common symlink handling logic self.handle_symlink_during_traversal(path, false) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index cd84caa36af..c3d75b2254b 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1566,6 +1566,7 @@ fn file_mode_for_interactive_overwrite( match path.metadata() { Ok(me) => { // Cast is necessary on some platforms + #[allow(clippy::unnecessary_cast)] let mode: mode_t = me.mode() as mode_t; // It looks like this extra information is added to the prompt iff the file's user write bit is 0 @@ -1758,7 +1759,7 @@ pub(crate) fn copy_attributes( Ok(()) })?; - #[cfg(feature = "selinux")] + #[cfg(all(feature = "selinux", target_os = "linux"))] handle_preserve(&attributes.context, || -> CopyResult<()> { // Get the source context and apply it to the destination if let Ok(context) = selinux::SecurityContext::of_path(source, false, false) { @@ -2552,7 +2553,7 @@ fn copy_file( copy_attributes(source, dest, &options.attributes)?; } - #[cfg(feature = "selinux")] + #[cfg(all(feature = "selinux", target_os = "linux"))] if options.set_selinux_context && uucore::selinux::is_selinux_enabled() { // Set the given selinux permissions on the copied file. if let Err(e) = @@ -2620,8 +2621,10 @@ fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { target_os = "redox", ))] { + #[allow(clippy::unnecessary_cast)] const MODE_RW_UGO: u32 = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; + #[allow(clippy::unnecessary_cast)] const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32; return if is_explicit_no_preserve_mode { MODE_RW_UGO diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index 1241746e735..192ec9dea15 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -27,11 +27,13 @@ uucore = { workspace = true, features = [ "parser-size", "parser-glob", "time", - "safe-traversal", ] } thiserror = { workspace = true } fluent = { workspace = true } +[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies] +uucore = { workspace = true, features = ["safe-traversal"] } + [target.'cfg(target_os = "windows")'.dependencies] windows-sys = { workspace = true, features = [ "Win32_Storage_FileSystem", diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 1b8084e2ebb..3ad6e07f916 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -25,7 +25,7 @@ use uucore::display::{Quotable, print_verbatim}; use uucore::error::{FromIo, UError, UResult, USimpleError, set_exit_code}; use uucore::fsext::{MetadataTimeField, metadata_get_time}; use uucore::line_ending::LineEnding; -#[cfg(target_os = "linux")] +#[cfg(all(unix, not(target_os = "redox")))] use uucore::safe_traversal::DirFd; use uucore::translate; @@ -164,7 +164,7 @@ impl Stat { } /// Create a Stat using safe traversal methods with `DirFd` for the root directory - #[cfg(target_os = "linux")] + #[cfg(all(unix, not(target_os = "redox")))] fn new_from_dirfd(dir_fd: &DirFd, full_path: &Path) -> std::io::Result { // Get metadata for the directory itself using fstat let safe_metadata = dir_fd.metadata()?; @@ -293,9 +293,9 @@ fn read_block_size(s: Option<&str>) -> UResult { } } -#[cfg(target_os = "linux")] -// For now, implement safe_du only on Linux -// This is done for Ubuntu but should be extended to other platforms that support openat +#[cfg(all(unix, not(target_os = "redox")))] +// Implement safe_du on Unix (except Redox which lacks full stat support) +// This is done for TOCTOU safety fn safe_du( path: &Path, options: &TraversalOptions, @@ -439,7 +439,8 @@ fn safe_du( const S_IFMT: u32 = 0o170_000; const S_IFDIR: u32 = 0o040_000; const S_IFLNK: u32 = 0o120_000; - let is_symlink = (lstat.st_mode & S_IFMT) == S_IFLNK; + #[allow(clippy::unnecessary_cast)] + let is_symlink = (lstat.st_mode as u32 & S_IFMT) == S_IFLNK; // Handle symlinks with -L option // For safe traversal with -L, we skip symlinks to directories entirely @@ -450,12 +451,14 @@ fn safe_du( continue; } - let is_dir = (lstat.st_mode & S_IFMT) == S_IFDIR; + #[allow(clippy::unnecessary_cast)] + let is_dir = (lstat.st_mode as u32 & S_IFMT) == S_IFDIR; let entry_stat = lstat; + #[allow(clippy::unnecessary_cast)] let file_info = (entry_stat.st_ino != 0).then_some(FileInfo { file_id: entry_stat.st_ino as u128, - dev_id: entry_stat.st_dev, + dev_id: entry_stat.st_dev as u64, }); // For safe traversal, we need to handle stats differently @@ -465,6 +468,7 @@ fn safe_du( Stat { path: entry_path.clone(), size: 0, + #[allow(clippy::unnecessary_cast)] blocks: entry_stat.st_blocks as u64, inodes: 1, inode: file_info, @@ -476,7 +480,9 @@ fn safe_du( // For files Stat { path: entry_path.clone(), + #[allow(clippy::unnecessary_cast)] size: entry_stat.st_size as u64, + #[allow(clippy::unnecessary_cast)] blocks: entry_stat.st_blocks as u64, inodes: 1, inode: file_info, @@ -1096,14 +1102,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut seen_inodes: HashSet = HashSet::new(); // Determine which traversal method to use - #[cfg(target_os = "linux")] + #[cfg(all(unix, not(target_os = "redox")))] let use_safe_traversal = traversal_options.dereference != Deref::All; - #[cfg(not(target_os = "linux"))] + #[cfg(not(all(unix, not(target_os = "redox"))))] let use_safe_traversal = false; if use_safe_traversal { - // Use safe traversal (Linux only, when not using -L) - #[cfg(target_os = "linux")] + // Use safe traversal (Unix except Redox, when not using -L) + #[cfg(all(unix, not(target_os = "redox")))] { // Pre-populate seen_inodes with the starting directory to detect cycles if let Ok(stat) = Stat::new(&path, None, &traversal_options) { @@ -1158,9 +1164,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .send(Ok(StatPrintInfo { stat, depth: 0 })) .map_err(|e| USimpleError::new(1, e.to_string()))?; } else { - #[cfg(target_os = "linux")] + #[cfg(unix)] let error_msg = translate!("du-error-cannot-access", "path" => path.quote()); - #[cfg(not(target_os = "linux"))] + #[cfg(not(unix))] let error_msg = translate!("du-error-cannot-access-no-such-file", "path" => path.quote()); diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 9ff314f626e..698672233b3 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -62,9 +62,9 @@ macro_rules! cstr2cow { } fn get_context_help_text() -> String { - #[cfg(not(feature = "selinux"))] + #[cfg(not(all(feature = "selinux", target_os = "linux")))] return translate!("id-context-help-disabled"); - #[cfg(feature = "selinux")] + #[cfg(all(feature = "selinux", target_os = "linux"))] return translate!("id-context-help-enabled"); } @@ -137,11 +137,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { cflag: matches.get_flag(options::OPT_CONTEXT), selinux_supported: { - #[cfg(feature = "selinux")] + #[cfg(all(feature = "selinux", target_os = "linux"))] { uucore::selinux::is_selinux_enabled() } - #[cfg(not(feature = "selinux"))] + #[cfg(not(all(feature = "selinux", target_os = "linux")))] { false } diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 8e43d1fd2ea..d3e0b3e89a4 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -10,7 +10,7 @@ mod mode; use clap::{Arg, ArgAction, ArgMatches, Command}; use file_diff::diff; use filetime::{FileTime, set_file_times}; -#[cfg(feature = "selinux")] +#[cfg(all(feature = "selinux", target_os = "linux"))] use selinux::SecurityContext; use std::ffi::OsString; use std::fmt::Debug; @@ -27,7 +27,7 @@ use uucore::error::{FromIo, UError, UResult, UUsageError}; use uucore::fs::dir_strip_dot_for_creation; use uucore::perms::{Verbosity, VerbosityLevel, wrap_chown}; use uucore::process::{getegid, geteuid}; -#[cfg(feature = "selinux")] +#[cfg(all(feature = "selinux", target_os = "linux"))] use uucore::selinux::{ SeLinuxError, contexts_differ, get_selinux_security_context, is_selinux_enabled, selinux_error_description, set_selinux_security_context, @@ -118,7 +118,7 @@ enum InstallError { #[error("{}", translate!("install-error-extra-operand", "operand" => .0.quote(), "usage" => .1.clone()))] ExtraOperand(OsString, String), - #[cfg(feature = "selinux")] + #[cfg(all(feature = "selinux", target_os = "linux"))] #[error("{}", .0)] SelinuxContextFailed(String), } @@ -1030,7 +1030,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> { Ok(()) } -#[cfg(feature = "selinux")] +#[cfg(all(feature = "selinux", target_os = "linux"))] fn get_context_for_selinux(b: &Behavior) -> Option<&String> { if b.default_context { None @@ -1165,7 +1165,7 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool { false } -#[cfg(feature = "selinux")] +#[cfg(all(feature = "selinux", target_os = "linux"))] /// Sets the `SELinux` security context for install's -Z flag behavior. /// /// This function implements the specific behavior needed for install's -Z flag, @@ -1199,7 +1199,7 @@ pub fn set_selinux_default_context(path: &Path) -> Result<(), SeLinuxError> { } } -#[cfg(feature = "selinux")] +#[cfg(all(feature = "selinux", target_os = "linux"))] /// Gets the default `SELinux` context for a path based on the system's security policy. /// /// This function attempts to determine what the "correct" `SELinux` context should be @@ -1255,7 +1255,7 @@ fn get_default_context_for_path(path: &Path) -> Result, SeLinuxEr Ok(None) } -#[cfg(feature = "selinux")] +#[cfg(all(feature = "selinux", target_os = "linux"))] /// Derives an appropriate `SELinux` context based on a parent directory context. /// /// This is a heuristic function that attempts to generate an appropriate @@ -1293,7 +1293,7 @@ fn derive_context_from_parent(parent_context: &str) -> String { } } -#[cfg(feature = "selinux")] +#[cfg(all(feature = "selinux", target_os = "linux"))] /// Helper function to collect paths that need `SELinux` context setting. /// /// Traverses from the given starting path up to existing parent directories. @@ -1307,7 +1307,7 @@ fn collect_paths_for_context_setting(starting_path: &Path) -> Vec<&Path> { paths } -#[cfg(feature = "selinux")] +#[cfg(all(feature = "selinux", target_os = "linux"))] /// Sets the `SELinux` security context for a directory hierarchy. /// /// This function traverses from the given starting path up to existing parent directories @@ -1347,7 +1347,7 @@ fn set_selinux_context_for_directories(target_path: &Path, context: Option<&Stri } } -#[cfg(feature = "selinux")] +#[cfg(all(feature = "selinux", target_os = "linux"))] /// Sets `SELinux` context for created directories using install's -Z default behavior. /// /// Similar to `set_selinux_context_for_directories` but uses install's @@ -1371,10 +1371,10 @@ pub fn set_selinux_context_for_directories_install(target_path: &Path, context: #[cfg(test)] mod tests { - #[cfg(feature = "selinux")] + #[cfg(all(feature = "selinux", target_os = "linux"))] use super::derive_context_from_parent; - #[cfg(feature = "selinux")] + #[cfg(all(feature = "selinux", target_os = "linux"))] #[test] fn test_derive_context_from_parent() { // Test cases: (input_context, file_type, expected_output, description) diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 76262f4bdbc..ef70c36b3ed 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -291,7 +291,7 @@ fn create_single_dir(path: &Path, is_parent: bool, config: &Config) -> UResult<( chmod(path, new_mode)?; // Apply SELinux context if requested - #[cfg(feature = "selinux")] + #[cfg(all(feature = "selinux", target_os = "linux"))] if config.set_selinux_context && uucore::selinux::is_selinux_enabled() { if let Err(e) = uucore::selinux::set_selinux_security_context(path, config.context) { diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index c55593dcbca..938f57528a4 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -59,7 +59,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } // Apply SELinux context if requested - #[cfg(feature = "selinux")] + #[cfg(all(feature = "selinux", target_os = "linux"))] { // Extract the SELinux related flags and options let set_selinux_context = matches.get_flag(options::SELINUX); diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 8a4cf82d01e..49b70de731b 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -87,7 +87,7 @@ fn mknod(file_name: &str, config: Config) -> i32 { } // Apply SELinux context if requested - #[cfg(feature = "selinux")] + #[cfg(all(feature = "selinux", target_os = "linux"))] if config.set_selinux_context { if let Err(e) = uucore::selinux::set_selinux_security_context( std::path::Path::new(file_name), diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index ccf1bf93e28..cf53e032388 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -20,10 +20,13 @@ path = "src/rm.rs" [dependencies] thiserror = { workspace = true } clap = { workspace = true } -uucore = { workspace = true, features = ["fs", "parser", "safe-traversal"] } +uucore = { workspace = true, features = ["fs", "parser"] } fluent = { workspace = true } indicatif = { workspace = true } +[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies] +uucore = { workspace = true, features = ["safe-traversal"] } + [target.'cfg(unix)'.dependencies] libc = { workspace = true } diff --git a/src/uu/rm/src/platform/mod.rs b/src/uu/rm/src/platform/mod.rs index 1f2911acbc9..db37b7845c0 100644 --- a/src/uu/rm/src/platform/mod.rs +++ b/src/uu/rm/src/platform/mod.rs @@ -5,8 +5,8 @@ // Platform-specific implementations for the rm utility -#[cfg(target_os = "linux")] -pub mod linux; +#[cfg(all(unix, not(target_os = "redox")))] +pub mod unix; -#[cfg(target_os = "linux")] -pub use linux::*; +#[cfg(all(unix, not(target_os = "redox")))] +pub use unix::*; diff --git a/src/uu/rm/src/platform/linux.rs b/src/uu/rm/src/platform/unix.rs similarity index 94% rename from src/uu/rm/src/platform/linux.rs rename to src/uu/rm/src/platform/unix.rs index 3e29bf85e7f..5c8e0981be9 100644 --- a/src/uu/rm/src/platform/linux.rs +++ b/src/uu/rm/src/platform/unix.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// Linux-specific implementations for the rm utility +// Unix-specific implementations for the rm utility // spell-checker:ignore fstatat unlinkat statx behaviour @@ -42,8 +42,8 @@ fn prompt_file_with_stat(path: &Path, stat: &libc::stat, options: &Options) -> b return true; } - let is_symlink = (stat.st_mode & libc::S_IFMT) == libc::S_IFLNK; - let writable = mode_writable(stat.st_mode); + let is_symlink = ((stat.st_mode as libc::mode_t) & libc::S_IFMT) == libc::S_IFLNK; + let writable = mode_writable(stat.st_mode as libc::mode_t); let len = stat.st_size as u64; let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal(); @@ -82,8 +82,8 @@ fn prompt_dir_with_mode(path: &Path, mode: libc::mode_t, options: &Options) -> b return true; } - let readable = mode_readable(mode); - let writable = mode_writable(mode); + let readable = mode_readable(mode as libc::mode_t); + let writable = mode_writable(mode as libc::mode_t); let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal(); match (stdin_ok, readable, writable, options.interactive) { @@ -317,7 +317,7 @@ pub fn safe_remove_dir_recursive( } else { // Ask user permission if needed if options.interactive == InteractiveMode::Always - && !prompt_dir_with_mode(path, initial_mode, options) + && !prompt_dir_with_mode(path, initial_mode as libc::mode_t, options) { return false; } @@ -345,6 +345,7 @@ pub fn safe_remove_dir_recursive( } } +#[cfg(not(target_os = "redox"))] pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Options) -> bool { // Read directory entries using safe traversal let entries = match dir_fd.read_dir() { @@ -376,7 +377,7 @@ pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Opt }; // Check if it's a directory - let is_dir = (entry_stat.st_mode & libc::S_IFMT) == libc::S_IFDIR; + let is_dir = ((entry_stat.st_mode as libc::mode_t) & libc::S_IFMT) == libc::S_IFDIR; if is_dir { // Ask user if they want to descend into this directory @@ -413,7 +414,7 @@ pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Opt // Ask user permission if needed for this subdirectory if !child_error && options.interactive == InteractiveMode::Always - && !prompt_dir_with_mode(&entry_path, entry_stat.st_mode, options) + && !prompt_dir_with_mode(&entry_path, entry_stat.st_mode as libc::mode_t, options) { continue; } @@ -432,3 +433,10 @@ pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Opt error } + +#[cfg(target_os = "redox")] +pub fn safe_remove_dir_recursive_impl(_path: &Path, _dir_fd: &DirFd, _options: &Options) -> bool { + // safe_traversal stat_at is not supported on Redox + // This shouldn't be called on Redox, but provide a stub for compilation + true // Return error +} diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index ce1ce47a1eb..55c5b932f96 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -26,7 +26,7 @@ use uucore::translate; use uucore::{format_usage, os_str_as_bytes, prompt_yes, show_error}; mod platform; -#[cfg(target_os = "linux")] +#[cfg(all(unix, not(target_os = "redox")))] use platform::{safe_remove_dir_recursive, safe_remove_empty_dir, safe_remove_file}; #[derive(Debug, Error)] @@ -538,17 +538,7 @@ fn is_readable_metadata(metadata: &Metadata) -> bool { } /// Whether the given file or directory is readable. -#[cfg(unix)] -#[cfg(not(target_os = "linux"))] -fn is_readable(path: &Path) -> bool { - match fs::metadata(path) { - Err(_) => false, - Ok(metadata) => is_readable_metadata(&metadata), - } -} - -/// Whether the given file or directory is readable. -#[cfg(not(unix))] +#[cfg(any(not(unix), target_os = "redox"))] fn is_readable(_path: &Path) -> bool { true } @@ -605,14 +595,14 @@ fn remove_dir_recursive( return false; } - // Use secure traversal on Linux for all recursive directory removals - #[cfg(target_os = "linux")] + // Use secure traversal on Unix (except Redox) for all recursive directory removals + #[cfg(all(unix, not(target_os = "redox")))] { safe_remove_dir_recursive(path, options, progress_bar) } - // Fallback for non-Linux or use fs::remove_dir_all for very long paths - #[cfg(not(target_os = "linux"))] + // Fallback for non-Unix, Redox, or use fs::remove_dir_all for very long paths + #[cfg(any(not(unix), target_os = "redox"))] { if let Some(s) = path.to_str() { if s.len() > 1000 { @@ -734,8 +724,8 @@ fn remove_dir(path: &Path, options: &Options, progress_bar: Option<&ProgressBar> return true; } - // Use safe traversal on Linux for empty directory removal - #[cfg(target_os = "linux")] + // Use safe traversal on Unix (except Redox) for empty directory removal + #[cfg(all(unix, not(target_os = "redox")))] { if let Some(result) = safe_remove_empty_dir(path, options, progress_bar) { return result; @@ -758,15 +748,15 @@ fn remove_file(path: &Path, options: &Options, progress_bar: Option<&ProgressBar pb.inc(1); } - // Use safe traversal on Linux for individual file removal - #[cfg(target_os = "linux")] + // Use safe traversal on Unix (except Redox) for individual file removal + #[cfg(all(unix, not(target_os = "redox")))] { if let Some(result) = safe_remove_file(path, options, progress_bar) { return result; } } - // Fallback method for non-Linux or when safe traversal is unavailable + // Fallback method for non-Unix, Redox, or when safe traversal is unavailable match fs::remove_file(path) { Ok(_) => { verbose_removed_file(path, options); diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 48430a26157..a7a876b0826 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -1044,7 +1044,7 @@ impl Stater { 'B' => OutputType::Unsigned(512), // SELinux security context string 'C' => { - #[cfg(feature = "selinux")] + #[cfg(all(feature = "selinux", target_os = "linux"))] { if uucore::selinux::is_selinux_enabled() { match uucore::selinux::get_selinux_security_context( @@ -1060,7 +1060,7 @@ impl Stater { OutputType::Str(translate!("stat-selinux-unsupported-system")) } } - #[cfg(not(feature = "selinux"))] + #[cfg(not(all(feature = "selinux", target_os = "linux")))] { OutputType::Str(translate!("stat-selinux-unsupported-os")) } diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index e56968c50fa..cd2ce405ffe 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -72,7 +72,7 @@ pub mod pipes; pub mod proc_info; #[cfg(all(unix, feature = "process"))] pub mod process; -#[cfg(target_os = "linux")] +#[cfg(all(unix, not(target_os = "redox")))] pub mod safe_traversal; #[cfg(all(target_os = "linux", feature = "tty"))] pub mod tty; diff --git a/src/uucore/src/lib/features/safe_traversal.rs b/src/uucore/src/lib/features/safe_traversal.rs index 6574910a95d..3b1ac70678f 100644 --- a/src/uucore/src/lib/features/safe_traversal.rs +++ b/src/uucore/src/lib/features/safe_traversal.rs @@ -6,7 +6,7 @@ // Safe directory traversal using openat() and related syscalls // This module provides TOCTOU-safe filesystem operations for recursive traversal // -// Only available on Linux +// Available on Unix // // spell-checker:ignore CLOEXEC RDONLY TOCTOU closedir dirp fdopendir fstatat openat REMOVEDIR unlinkat smallfile // spell-checker:ignore RAII dirfd fchownat fchown FchmodatFlags fchmodat fchmod @@ -85,15 +85,11 @@ fn read_dir_entries(fd: &OwnedFd) -> io::Result> { // Duplicate the fd for Dir (it takes ownership) let dup_fd = nix::unistd::dup(fd).map_err(|e| io::Error::from_raw_os_error(e as i32))?; - let mut dir = Dir::from_fd(dup_fd).map_err(|e| io::Error::from_raw_os_error(e as i32))?; - for entry_result in dir.iter() { let entry = entry_result.map_err(|e| io::Error::from_raw_os_error(e as i32))?; - let name = entry.file_name(); let name_os = OsStr::from_bytes(name.to_bytes()); - if name_os != "." && name_os != ".." { entries.push(name_os.to_os_string()); } @@ -117,7 +113,6 @@ impl DirFd { source: io::Error::from_raw_os_error(e as i32), } })?; - Ok(Self { fd }) } @@ -125,7 +120,6 @@ impl DirFd { pub fn open_subdir(&self, name: &OsStr) -> io::Result { let name_cstr = CString::new(name.as_bytes()).map_err(|_| SafeTraversalError::PathContainsNull)?; - let flags = OFlag::O_RDONLY | OFlag::O_DIRECTORY | OFlag::O_CLOEXEC; let fd = openat(&self.fd, name_cstr.as_c_str(), flags, Mode::empty()).map_err(|e| { SafeTraversalError::OpenFailed { @@ -133,7 +127,6 @@ impl DirFd { source: io::Error::from_raw_os_error(e as i32), } })?; - Ok(Self { fd }) } @@ -174,7 +167,6 @@ impl DirFd { path: translate!("safe-traversal-current-directory").into(), source: io::Error::from_raw_os_error(e as i32), })?; - Ok(stat) } @@ -254,7 +246,7 @@ impl DirFd { FchmodatFlags::NoFollowSymlink }; - let mode = Mode::from_bits_truncate(mode); + let mode = Mode::from_bits_truncate(mode as libc::mode_t); let name_cstr = CString::new(name.as_bytes()).map_err(|_| SafeTraversalError::PathContainsNull)?; @@ -267,7 +259,7 @@ impl DirFd { /// Change mode of this directory pub fn fchmod(&self, mode: u32) -> io::Result<()> { - let mode = Mode::from_bits_truncate(mode); + let mode = Mode::from_bits_truncate(mode as libc::mode_t); nix::sys::stat::fchmod(&self.fd, mode) .map_err(|e| io::Error::from_raw_os_error(e as i32))?; @@ -378,30 +370,30 @@ impl Metadata { } pub fn file_type(&self) -> FileType { - FileType::from_mode(self.stat.st_mode) + FileType::from_mode(self.stat.st_mode as libc::mode_t) } pub fn file_info(&self) -> FileInfo { FileInfo::from_stat(&self.stat) } + // st_size type varies by platform (i64 vs u64) + #[allow(clippy::unnecessary_cast)] pub fn size(&self) -> u64 { self.stat.st_size as u64 } + // st_mode type varies by platform (u16 on macOS, u32 on Linux) + #[allow(clippy::unnecessary_cast)] pub fn mode(&self) -> u32 { - self.stat.st_mode + self.stat.st_mode as u32 } pub fn nlink(&self) -> u64 { - // st_nlink is u32 on most platforms except x86_64 - #[cfg(target_arch = "x86_64")] - { - self.stat.st_nlink - } - #[cfg(not(target_arch = "x86_64"))] + // st_nlink type varies by platform (u16 on FreeBSD, u32/u64 on others) + #[allow(clippy::unnecessary_cast)] { - self.stat.st_nlink.into() + self.stat.st_nlink as u64 } } @@ -421,34 +413,31 @@ impl Metadata { // Add MetadataExt trait implementation for compatibility impl std::os::unix::fs::MetadataExt for Metadata { + // st_dev type varies by platform (i32 on macOS, u64 on Linux) + #[allow(clippy::unnecessary_cast)] fn dev(&self) -> u64 { - self.stat.st_dev + self.stat.st_dev as u64 } fn ino(&self) -> u64 { - #[cfg(target_pointer_width = "32")] - { - self.stat.st_ino.into() - } - #[cfg(not(target_pointer_width = "32"))] + // st_ino type varies by platform (u32 on FreeBSD, u64 on Linux) + #[allow(clippy::unnecessary_cast)] { - self.stat.st_ino + self.stat.st_ino as u64 } } + // st_mode type varies by platform (u16 on macOS, u32 on Linux) + #[allow(clippy::unnecessary_cast)] fn mode(&self) -> u32 { - self.stat.st_mode + self.stat.st_mode as u32 } fn nlink(&self) -> u64 { - // st_nlink is u32 on most platforms except x86_64 - #[cfg(target_arch = "x86_64")] - { - self.stat.st_nlink - } - #[cfg(not(target_arch = "x86_64"))] + // st_nlink type varies by platform (u16 on FreeBSD, u32/u64 on others) + #[allow(clippy::unnecessary_cast)] { - self.stat.st_nlink.into() + self.stat.st_nlink as u64 } } @@ -460,10 +449,14 @@ impl std::os::unix::fs::MetadataExt for Metadata { self.stat.st_gid } + // st_rdev type varies by platform (i32 on macOS, u64 on Linux) + #[allow(clippy::unnecessary_cast)] fn rdev(&self) -> u64 { - self.stat.st_rdev + self.stat.st_rdev as u64 } + // st_size type varies by platform (i64 on some platforms, u64 on others) + #[allow(clippy::unnecessary_cast)] fn size(&self) -> u64 { self.stat.st_size as u64 } @@ -534,10 +527,14 @@ impl std::os::unix::fs::MetadataExt for Metadata { } } + // st_blksize type varies by platform (i32/i64/u32/u64 depending on platform) + #[allow(clippy::unnecessary_cast)] fn blksize(&self) -> u64 { self.stat.st_blksize as u64 } + // st_blocks type varies by platform (i64 on some platforms, u64 on others) + #[allow(clippy::unnecessary_cast)] fn blocks(&self) -> u64 { self.stat.st_blocks as u64 } diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 7931a69205e..c1ece8bff35 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -99,7 +99,7 @@ pub use crate::features::perms; pub use crate::features::pipes; #[cfg(all(unix, feature = "process"))] pub use crate::features::process; -#[cfg(target_os = "linux")] +#[cfg(all(unix, not(target_os = "redox")))] pub use crate::features::safe_traversal; #[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))] pub use crate::features::signals; diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index a17fc4a2ca3..18c180bd05a 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -390,10 +390,12 @@ fn test_chmod_recursive_correct_exit_code() { perms.set_mode(0o000); set_permissions(at.plus_as_string("a"), perms).unwrap(); - #[cfg(not(target_os = "linux"))] - let err_msg = "chmod: Permission denied\n"; - #[cfg(target_os = "linux")] + // With safe_traversal enabled on all Unix platforms (except Redox), + // we get detailed error messages that include the file path + #[cfg(all(unix, not(target_os = "redox")))] let err_msg = "chmod: cannot access 'a': Permission denied\n"; + #[cfg(not(all(unix, not(target_os = "redox"))))] + let err_msg = "chmod: Permission denied\n"; // order of command is a, a/b then c // command is expected to fail and not just take the last exit code @@ -434,9 +436,8 @@ fn test_chmod_recursive() { make_file(&at.plus_as_string("a/b/b"), 0o100444); make_file(&at.plus_as_string("a/b/c/c"), 0o100444); make_file(&at.plus_as_string("z/y"), 0o100444); - #[cfg(not(target_os = "linux"))] - let err_msg = "chmod: Permission denied\n"; - #[cfg(target_os = "linux")] + // With safe_traversal enabled on all Unix platforms, the error message + // now includes the file path consistently across platforms let err_msg = "chmod: cannot access 'z': Permission denied\n"; // only the permissions of folder `a` and `z` are changed