Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c9d6f81
feat: Expand safe directory traversal to all Unix platforms and fix r…
abendrothj Dec 23, 2025
9cd62b3
fix: Fix type conversion errors and add TOCTOU to dictionary
abendrothj Dec 27, 2025
f1c7082
Merge branch 'main' of https://github.com/uutils/coreutils into main
abendrothj Dec 27, 2025
1c06851
Merge branch 'main' into main
abendrothj Dec 28, 2025
e1d5465
fix: Fix macOS/BSD build failures and test compatibility
abendrothj Dec 28, 2025
cfbf666
Merge branch 'main' into main
abendrothj Dec 28, 2025
9445537
fix: Add cross-platform type handling in safe_traversal
abendrothj Dec 28, 2025
959d39d
Merge branch 'main' into main
abendrothj Dec 28, 2025
d8348c5
fix: That should be all the casts ignored by clippy (were showing as …
abendrothj Dec 28, 2025
4f65a77
fix: Suppress unnecessary cast warnings in cp and du modules
abendrothj Dec 28, 2025
3cb2071
fix: Ensure proper type handling for mode_t in rm and safe_traversal …
abendrothj Dec 28, 2025
29a04c0
Merge branch 'main' into main
abendrothj Dec 28, 2025
e326036
fix: Improve code formatting for readability in safe_remove_dir_recur…
abendrothj Dec 28, 2025
ee18c58
fix(unix,redox): platform compatibility
abendrothj Dec 28, 2025
c8cc10c
fix: Use type inference for mode checks in prompt_file_with_stat and …
abendrothj Dec 28, 2025
05d675b
fix(android): handle symlink and directory checks with appropriate ty…
abendrothj Dec 28, 2025
0f48443
fix(redox): improve error handling for unsupported safe traversal ope…
abendrothj Dec 28, 2025
e6a9637
fix(unix): simplify symlink and directory checks for improved readabi…
abendrothj Dec 28, 2025
5704355
test(date): Relax locale format assertions to support variable CI env…
abendrothj Dec 28, 2025
e4f13ef
fix(uucore): gate unix-specific safe_traversal methods for redox
abendrothj Dec 28, 2025
827556a
fix(redox): gate Metadata and nix-dependent methods for Redox compati…
abendrothj Dec 29, 2025
d946a9d
Merge branch 'main' into main
abendrothj Dec 29, 2025
9f41965
fix(redox): move libc import under the correct conditional compilation
abendrothj Dec 29, 2025
b0803a5
Merge branch 'main' into main
sylvestre Dec 29, 2025
262b53b
Merge branch 'main' into main
abendrothj Dec 30, 2025
78cbd9a
Refactor conditional compilation for Unix-specific features to exclud…
abendrothj Dec 30, 2025
ea5ad61
Remove Redox-specific exclusion code from safe_traversal module
abendrothj Dec 30, 2025
df5f26a
Exclude Redox from safe_traversal feature for Unix compatibility
abendrothj Dec 30, 2025
3e0c411
Merge branch 'main' into main
abendrothj Dec 31, 2025
2102a85
rm: add Redox-specific stub implementations for safe traversal
abendrothj Dec 31, 2025
370107e
rm: add spell-checker directive for fstatat in Redox stub implementat…
abendrothj Dec 31, 2025
b6c20a8
chmod, du: exclude Redox from safe-traversal feature
abendrothj Dec 31, 2025
5cc0c65
cargo: fix formatting of uucore dependencies in Cargo.toml
abendrothj Dec 31, 2025
1fc7d18
Merge branch 'main' into main
abendrothj Dec 31, 2025
44950a2
Merge branch 'main' into main
abendrothj Jan 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/cspell.dictionaries/acronyms+names.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 4 additions & 7 deletions src/uu/chmod/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
43 changes: 35 additions & 8 deletions src/uu/chmod/src/chmod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -419,7 +419,7 @@ impl Chmoder {
r
}

#[cfg(not(target_os = "linux"))]
#[cfg(not(unix))]
fn walk_dir_with_context(&self, file_path: &Path, is_command_line_arg: bool) -> UResult<()> {
let mut r = self.chmod_file(file_path);

Expand Down Expand Up @@ -450,7 +450,34 @@ impl Chmoder {
r
}

#[cfg(target_os = "linux")]
#[cfg(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);

// Determine whether to traverse symlinks based on context and traversal mode
let should_follow_symlink = match self.traverse_symlinks {
TraverseSymlinks::All => true,
TraverseSymlinks::First => is_command_line_arg, // Only follow symlinks that are command line args
TraverseSymlinks::None => false,
};

// 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() {
for dir_entry in file_path.read_dir()? {
let path = match dir_entry {
Ok(entry) => entry.path(),
Err(err) => {
r = r.and(Err(err.into()));
continue;
}
};
r = self.walk_dir_with_context(path.as_path(), false).and(r);
}
}
r
}

#[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);

Expand Down Expand Up @@ -480,7 +507,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(());

Expand Down Expand Up @@ -536,7 +563,7 @@ impl Chmoder {
r
}

#[cfg(target_os = "linux")]
#[cfg(all(unix, not(target_os = "redox")))]
fn handle_symlink_during_safe_recursion(
&self,
path: &Path,
Expand Down Expand Up @@ -568,7 +595,7 @@ impl Chmoder {
}
}

#[cfg(target_os = "linux")]
#[cfg(all(unix, not(target_os = "redox")))]
fn safe_chmod_file(
&self,
file_path: &Path,
Expand Down Expand Up @@ -598,7 +625,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)
Expand Down
7 changes: 5 additions & 2 deletions src/uu/cp/src/cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) =
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion src/uu/du/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
34 changes: 20 additions & 14 deletions src/uu/du/src/du.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<Self> {
// Get metadata for the directory itself using fstat
let safe_metadata = dir_fd.metadata()?;
Expand Down Expand Up @@ -293,9 +293,9 @@ fn read_block_size(s: Option<&str>) -> UResult<u64> {
}
}

#[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,
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -1096,14 +1102,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let mut seen_inodes: HashSet<FileInfo> = 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) {
Expand Down Expand Up @@ -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());

Expand Down
8 changes: 4 additions & 4 deletions src/uu/id/src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

Expand Down Expand Up @@ -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
}
Expand Down
Loading
Loading