Skip to content

Commit ee67c4a

Browse files
authored
Merge branch 'main' into date-positional-set-format
2 parents 83710e4 + 2e04477 commit ee67c4a

11 files changed

Lines changed: 261 additions & 66 deletions

File tree

.github/workflows/benchmarks.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ jobs:
4848
uu_shuf,
4949
uu_sort,
5050
uu_split,
51+
uu_tee,
5152
uu_timeout,
5253
uu_tsort,
5354
uu_unexpand,

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ memchr = "2.7.2"
430430
memmap2 = "0.9.4"
431431
nix = { version = "0.31.2", default-features = false }
432432
nom = "8.0.0"
433-
notify = { version = "=8.2.0", features = ["macos_kqueue"] }
433+
notify = { version = "8.2.0", features = ["macos_kqueue"] }
434434
num-bigint = "0.4.4"
435435
num-prime = "0.5.0"
436436
num-traits = "0.2.19"

renovate.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"extends": [
33
"config:recommended"
4-
],
5-
"updatePinnedDependencies": false
4+
]
65
}

src/uu/chmod/src/chmod.rs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -519,9 +519,18 @@ impl Chmoder {
519519
.handle_symlink_during_safe_recursion(&entry_path, dir_fd, &entry_name)
520520
.and(r);
521521
} else {
522-
// For regular files and directories, chmod them
522+
// For regular files and directories, chmod them.
523+
// Always use NoFollow: we already confirmed via stat that the entry
524+
// is not a symlink, so this prevents TOCTOU races where an attacker
525+
// replaces the entry with a symlink between stat and chmod.
523526
r = self
524-
.safe_chmod_file(&entry_path, dir_fd, &entry_name, meta.mode() & 0o7777)
527+
.safe_chmod_file(
528+
&entry_path,
529+
dir_fd,
530+
&entry_name,
531+
meta.mode() & 0o7777,
532+
SymlinkBehavior::NoFollow,
533+
)
525534
.and(r);
526535

527536
// Recurse into subdirectories using the existing directory fd
@@ -555,13 +564,23 @@ impl Chmoder {
555564
// During recursion, determine behavior based on traversal mode
556565
match self.traverse_symlinks {
557566
TraverseSymlinks::All => {
558-
// Follow all symlinks during recursion
567+
// Follow all symlinks during recursion.
568+
// NOTE: This path-based stat + Follow is only reachable when the user
569+
// explicitly specifies -L (--dereference). In that case the user has
570+
// consciously opted in to following symlinks, so a path-based stat
571+
// followed by Follow is the intended behavior and not a TOCTOU concern.
559572
// Check if the symlink target is a directory, but handle dangling symlinks gracefully
560573
match fs::metadata(path) {
561574
Ok(meta) if meta.is_dir() => self.walk_dir_with_context(path, false),
562575
Ok(meta) => {
563576
// It's a file symlink, chmod it using safe traversal
564-
self.safe_chmod_file(path, dir_fd, entry_name, meta.mode() & 0o7777)
577+
self.safe_chmod_file(
578+
path,
579+
dir_fd,
580+
entry_name,
581+
meta.mode() & 0o7777,
582+
self.dereference.into(),
583+
)
565584
}
566585
Err(_) => {
567586
// Dangling symlink, chmod it without dereferencing
@@ -584,13 +603,13 @@ impl Chmoder {
584603
dir_fd: &DirFd,
585604
entry_name: &std::ffi::OsStr,
586605
current_mode: u32,
606+
symlink_behavior: SymlinkBehavior,
587607
) -> UResult<()> {
588608
// Calculate the new mode using the helper method
589609
let (new_mode, _) = self.calculate_new_mode(current_mode, file_path.is_dir())?;
590610

591611
// Use safe traversal to change the mode
592-
let follow_symlinks = self.dereference;
593-
if let Err(_e) = dir_fd.chmod_at(entry_name, new_mode, follow_symlinks.into()) {
612+
if let Err(_e) = dir_fd.chmod_at(entry_name, new_mode, symlink_behavior) {
594613
if self.verbose {
595614
println!(
596615
"failed to change mode of {} to {new_mode:o}",

src/uu/nohup/src/nohup.rs

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -137,42 +137,29 @@ fn replace_fds() -> UResult<()> {
137137
}
138138

139139
fn find_stdout() -> UResult<File> {
140-
match OpenOptions::new()
141-
.create(true)
142-
.append(true)
143-
.open(Path::new(NOHUP_OUT))
144-
{
145-
Ok(t) => {
146-
show_error!(
147-
"{}",
148-
translate!("nohup-ignoring-input-appending-output", "path" => NOHUP_OUT.quote())
149-
);
150-
Ok(t)
151-
}
152-
Err(e1) => {
153-
let Ok(home) = env::var("HOME") else {
154-
return Err(NohupError::OpenFailed(*FAILURE_CODE, e1).into());
155-
};
156-
let mut homeout = PathBuf::from(home);
157-
homeout.push(NOHUP_OUT);
158-
let homeout_str = homeout.to_str().unwrap();
159-
match OpenOptions::new().create(true).append(true).open(&homeout) {
160-
Ok(t) => {
161-
show_error!(
162-
"{}",
163-
translate!("nohup-ignoring-input-appending-output", "path" => homeout_str.quote())
164-
);
165-
Ok(t)
166-
}
167-
Err(e2) => {
168-
Err(
169-
NohupError::OpenFailed2(*FAILURE_CODE, e1, homeout_str.to_string(), e2)
170-
.into(),
171-
)
172-
}
173-
}
174-
}
175-
}
140+
try_open_nohup_file(NOHUP_OUT).or_else(|e1| {
141+
let Ok(home) = env::var("HOME") else {
142+
return Err(NohupError::OpenFailed(*FAILURE_CODE, e1).into());
143+
};
144+
145+
let home_out = PathBuf::from(home).join(NOHUP_OUT);
146+
let home_out = home_out.to_str().unwrap();
147+
148+
try_open_nohup_file(home_out).map_err(|e2| {
149+
NohupError::OpenFailed2(*FAILURE_CODE, e1, home_out.to_string(), e2).into()
150+
})
151+
})
152+
}
153+
154+
fn try_open_nohup_file(path: &str) -> std::io::Result<File> {
155+
let file = OpenOptions::new().create(true).append(true).open(path)?;
156+
157+
show_error!(
158+
"{}",
159+
translate!("nohup-ignoring-input-appending-output", "path" => path.quote())
160+
);
161+
162+
Ok(file)
176163
}
177164

178165
#[cfg(target_vendor = "apple")]

src/uu/tee/Cargo.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,17 @@ clap = { workspace = true }
2424
uucore = { workspace = true, features = ["libc", "parser", "signals"] }
2525
fluent = { workspace = true }
2626

27-
[target.'cfg(unix)'.dev-dependencies]
28-
rustix = { workspace = true }
27+
[dev-dependencies]
28+
divan = { workspace = true }
29+
rustix = { workspace = true, features = ["stdio", "fs"] }
30+
tempfile = { workspace = true }
31+
uucore = { workspace = true, features = ["benchmark"] }
2932

3033
[[bin]]
3134
name = "tee"
3235
path = "src/main.rs"
36+
37+
38+
[[bench]]
39+
name = "tee_bench"
40+
harness = false

src/uu/tee/benches/tee_bench.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#[cfg(unix)]
2+
use divan::{Bencher, black_box};
3+
#[cfg(unix)]
4+
use uu_tee::uumain;
5+
#[cfg(unix)]
6+
use uucore::benchmark::{run_util_function, setup_test_file};
7+
8+
#[cfg(unix)]
9+
#[divan::bench(args = [10_000_000])]
10+
fn tee_stdin_file(bencher: Bencher, size_bytes: usize) {
11+
let data = vec![b'a'; size_bytes];
12+
let file_path = setup_test_file(&data);
13+
let file = std::fs::File::open(file_path).unwrap();
14+
let stdin_bak = rustix::io::dup(rustix::stdio::stdin()).unwrap();
15+
16+
bencher.bench_local(|| {
17+
use rustix::stdio::dup2_stdin;
18+
rustix::fs::seek(&file, rustix::fs::SeekFrom::Start(0)).unwrap();
19+
dup2_stdin(&file).unwrap(); // should be 1 thread
20+
black_box(run_util_function(uumain, &[]));
21+
dup2_stdin(&stdin_bak).unwrap(); // should be 1 thread
22+
});
23+
}
24+
25+
fn main() {
26+
divan::main();
27+
}

src/uu/yes/src/yes.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@ use clap::{Arg, ArgAction, Command, builder::ValueParser};
99
use std::ffi::OsString;
1010
use std::io::{self, Write};
1111
use uucore::error::{UResult, USimpleError, strip_errno};
12-
use uucore::format_usage;
13-
use uucore::translate;
12+
#[cfg(any(target_os = "linux", target_os = "android"))]
13+
use uucore::pipes::MAX_ROOTLESS_PIPE_SIZE;
14+
use uucore::{format_usage, translate};
1415

1516
#[cfg(any(target_os = "linux", target_os = "android"))]
1617
const PAGE_SIZE: usize = 4096;
1718
#[cfg(any(target_os = "linux", target_os = "android"))]
18-
use uucore::pipes::MAX_ROOTLESS_PIPE_SIZE;
19-
#[cfg(any(target_os = "linux", target_os = "android"))]
2019
const BUF_SIZE: usize = MAX_ROOTLESS_PIPE_SIZE;
2120
// it's possible that using a smaller or larger buffer might provide better performance
2221
#[cfg(not(any(target_os = "linux", target_os = "android")))]
@@ -116,6 +115,7 @@ pub fn exec(mut bytes: Vec<u8>) -> io::Result<()> {
116115
#[cfg(any(target_os = "linux", target_os = "android"))]
117116
pub fn exec(mut bytes: Vec<u8>) -> io::Result<()> {
118117
use uucore::pipes::{pipe, splice, tee};
118+
119119
let aligned = PAGE_SIZE.is_multiple_of(bytes.len());
120120
repeat_content_to_capacity(&mut bytes);
121121
let bytes = bytes.as_slice();

0 commit comments

Comments
 (0)