Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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/jargon.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,4 @@ TUNABLES
tunables
VMULL
vmull
tmpfs
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

89 changes: 36 additions & 53 deletions fuzz/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/uu/tac/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ clap = { workspace = true }
uucore = { workspace = true }
thiserror = { workspace = true }
fluent = { workspace = true }
tempfile = { workspace = true }

[[bin]]
name = "tac"
Expand Down
47 changes: 40 additions & 7 deletions src/uu/tac/src/tac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use std::ffi::OsString;
use std::io::{BufWriter, Read, Write, stdin, stdout};
use std::{
fs::{File, read},
io::copy,
path::Path,
};
use uucore::error::UError;
Expand Down Expand Up @@ -241,14 +242,22 @@ fn tac(filenames: &[OsString], before: bool, regex: bool, separator: &str) -> UR
mmap = mmap1;
&mmap
} else {
let mut buf1 = Vec::new();
if let Err(e) = stdin().read_to_end(&mut buf1) {
let e: Box<dyn UError> = TacError::ReadError(OsString::from("stdin"), e).into();
show!(e);
continue;
// Copy stdin to a temp file (respects TMPDIR), then mmap it.
// Falls back to Vec buffer if temp file creation fails (e.g., bad TMPDIR).
match buffer_stdin() {
Ok(StdinData::Mmap(mmap1)) => {
mmap = mmap1;
&mmap
}
Ok(StdinData::Vec(buf1)) => {
buf = buf1;
&buf
}
Err(e) => {
show!(TacError::ReadError(OsString::from("stdin"), e));
continue;
}
}
buf = buf1;
&buf
}
} else {
let path = Path::new(filename);
Expand Down Expand Up @@ -304,6 +313,30 @@ fn try_mmap_stdin() -> Option<Mmap> {
unsafe { Mmap::map(&stdin()).ok() }
}

enum StdinData {
Mmap(Mmap),
Vec(Vec<u8>),
}

/// Copy stdin to a temp file, then memory-map it.
/// Falls back to reading directly into memory if temp file creation fails.
fn buffer_stdin() -> std::io::Result<StdinData> {
// Try to create a temp file (respects TMPDIR)
if let Ok(mut tmp) = tempfile::tempfile() {
// Temp file created - copy stdin to it, then read back
copy(&mut stdin(), &mut tmp)?;
// SAFETY: If the file is truncated while we map it, SIGBUS will be raised
// and our process will be terminated, thus preventing access of invalid memory.
let mmap = unsafe { Mmap::map(&tmp)? };
Ok(StdinData::Mmap(mmap))
} else {
// Fall back to reading directly into memory (e.g., bad TMPDIR)
let mut buf = Vec::new();
stdin().read_to_end(&mut buf)?;
Ok(StdinData::Vec(buf))
}
}

fn try_mmap_path(path: &Path) -> Option<Mmap> {
let file = File::open(path).ok()?;

Expand Down
12 changes: 12 additions & 0 deletions tests/by-util/test_tac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,15 @@ fn test_failed_write_is_reported() {
.fails()
.stderr_is("tac: failed to write to stdout: No space left on device (os error 28)\n");
}

#[cfg(target_os = "linux")]
#[test]
fn test_stdin_bad_tmpdir_fallback() {
// When TMPDIR is invalid, tac falls back to reading stdin directly into memory
new_ucmd!()
.env("TMPDIR", "/nonexistent/dir")
.arg("-")
.pipe_in("a\nb\nc\n")
.succeeds()
.stdout_is("c\nb\na\n");
}
3 changes: 3 additions & 0 deletions util/fetch-gnu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ curl -L ${repo}/raw/refs/heads/master/tests/csplit/csplit-io-err.sh > tests/cspl
curl -L ${repo}/raw/refs/heads/master/tests/stty/bad-speed.sh > tests/stty/bad-speed.sh
# Avoid incorrect PASS
curl -L ${repo}/raw/refs/heads/master/tests/runcon/runcon-compute.sh > tests/runcon/runcon-compute.sh
curl -L ${repo}/raw/refs/heads/master/tests/tac/tac-continue.sh > tests/tac/tac-continue.sh
# Add tac-continue.sh to root tests (it requires root to mount tmpfs)
sed -i 's|tests/split/l-chunk-root.sh.*|tests/split/l-chunk-root.sh\t\t\t\\\n tests/tac/tac-continue.sh\t\t\t\\|' tests/local.mk
Loading