tar: refuse to write archive to terminal, lock stdout, and buffer output

This commit is contained in:
Jeff Bailey
2026-06-07 10:55:26 +01:00
parent 3be98e9e5c
commit 8d4fef7aba
6 changed files with 59 additions and 14 deletions
Generated
+1
View File
@@ -1681,6 +1681,7 @@ dependencies = [
"clap",
"regex",
"tar",
"tempfile",
"thiserror",
"uucore",
]
+3
View File
@@ -26,3 +26,6 @@ path = "src/tar.rs"
[[bin]]
name = "tar"
path = "src/main.rs"
[dev-dependencies]
tempfile = { workspace = true }
+4
View File
@@ -54,6 +54,10 @@ pub enum TarError {
/// Cannot finalize the archive
#[error("tar: Cannot finalize archive: {0}")]
CannotFinalizeArchive(io::Error),
/// Refusing to write archive contents to terminal
#[error("tar: Refusing to write archive contents to terminal (missing -f option?)")]
RefuseWriteArchiveToTerminal,
}
impl TarError {
+31 -2
View File
@@ -77,8 +77,8 @@ pub fn create_archive(
.iter()
.collect();
writeln!(
status_output,
"Removing leading `{}' from member names",
std::io::stderr(),
"tar: Removing leading `{}' from member names",
removed.display()
)
.map_err(TarError::Io)?;
@@ -146,3 +146,32 @@ fn normalize_path(path: &Path, allow_absolute: bool) -> Option<PathBuf> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{self, Write};
use tempfile::TempDir;
struct FailFlushWriter;
impl Write for FailFlushWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Err(io::Error::other("flush failed"))
}
}
#[test]
fn test_create_archive_flush_failed() {
let dir = TempDir::new().unwrap();
let file_path = dir.path().join("test.txt");
fs::write(&file_path, "hello").unwrap();
let output = FailFlushWriter;
let status_output = io::sink();
let res = create_archive(output, status_output, &[file_path.as_path()], false, false);
assert!(res.is_err());
}
}
+15 -11
View File
@@ -9,7 +9,7 @@ pub mod operations;
use crate::errors::TarError;
use clap::{arg, crate_version, ArgAction, Command};
use std::fs::File;
use std::io;
use std::io::{self, IsTerminal};
use std::path::{Path, PathBuf};
use uucore::error::UResult;
use uucore::format_usage;
@@ -171,21 +171,25 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let output_is_stdout = archive_path == Path::new("-");
return if output_is_stdout {
let output = io::stdout();
let status_output = io::stderr();
operations::create::create_archive(
output,
status_output,
&files,
allow_absolute,
verbose,
)
if io::stdout().is_terminal() {
Err(TarError::RefuseWriteArchiveToTerminal.into())
} else {
let output = io::stdout().lock();
let status_output = io::stderr();
operations::create::create_archive(
output,
status_output,
&files,
allow_absolute,
verbose,
)
}
} else {
let output = File::create(archive_path).map_err(|e| TarError::CannotCreateArchive {
path: archive_path.clone(),
source: e,
})?;
let status_output = io::stdout();
let status_output = io::stdout().lock();
operations::create::create_archive(
output,
status_output,
+5 -1
View File
@@ -190,7 +190,7 @@ fn test_create_absolute_path() {
&file_abs_path.display().to_string(),
])
.succeeds()
.stdout_contains("Removing leading");
.stderr_contains("tar: Removing leading");
assert!(at.file_exists("archive-trimed.tar"));
@@ -868,6 +868,10 @@ fn test_create_verbose_to_stdout_keeps_stdout_as_tar_data() {
"tar header should contain filename"
);
assert_eq!(&bytes[..8], b"file.txt");
assert_eq!(
bytes[8], 0,
"stdout should not contain verbose output prefix"
);
}
#[test]