Author SHA1 Message Date
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
815a1b3036 chore(deps): update rust crate regex to v1.12.4 (#301)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-09 21:28:55 +01:00
Sylvestre LedruandGitHub 25f4bd7c61 Merge pull request #156 from kaladron/dashfdash
tar: support -f - for stdin/stdout archives
2026-06-08 11:05:14 +02:00
Sylvestre LedruandGitHub 26bc94342d Merge pull request #300 from kaladron/merge-pr-294-295
[deps] Update uucore and uufuzz to 0.9.0 (#294, #295)
2026-06-08 08:39:32 +02:00
Jeff Bailey cef2373196 benches: set allow_absolute to true to avoid warning overhead 2026-06-07 12:47:29 +01:00
Jeff Bailey 8d4fef7aba tar: refuse to write archive to terminal, lock stdout, and buffer output 2026-06-07 12:18:30 +01:00
Jeff Bailey 3be98e9e5c tar: support -f - for stdin/stdout archives
When the archive path is "-", read archives from stdin for list and
extract, and write archive data to stdout for create.

Route create status output to stderr when writing the archive to
stdout so verbose output does not corrupt the stream. Add CLI tests
for create-to-stdout, list-from-stdin, extract-from-stdin, and the
stderr routing cases.
2026-06-07 11:19:50 +01:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3eb916a8c2 chore(deps): update codecov/codecov-action action to v7 (#299)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-07 08:54:45 +01:00
Jeff Baileyandrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 9161cb6e2d [deps] Update uucore and uufuzz to 0.9.0 (#294, #295)
Updated uucore and uufuzz to 0.9.0 in fuzz/Cargo.toml. Resolved the
resulting dependency conflicts by updating uucore in the root Cargo.lock
to a compatible revision and updating fuzz/Cargo.lock.

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-07 08:52:20 +01:00
renovate[bot]andDaniel Hofstetter ea51e0cd4c chore(deps): update rust crate libfuzzer-sys to v0.4.13 2026-06-05 07:21:00 +02:00
renovate[bot]andDaniel Hofstetter 31c9e09527 chore(deps): update rust crate chrono to v0.4.45 2026-06-05 07:18:15 +02:00
Sylvestre LedruandGitHub 14185c5626 Merge pull request #297 from uutils/codspeed/wizard-1780601706208
Add CodSpeed performance benchmarks
2026-06-04 22:08:03 +02:00
12 changed files with 389 additions and 267 deletions
+1 -1
View File
@@ -104,7 +104,7 @@ jobs:
grcov . --binary-path="${COVERAGE_REPORT_DIR}" --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" --source-dir .
echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT
- name: Upload coverage results (to Codecov.io)
uses: codecov/codecov-action@v6
uses: codecov/codecov-action@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ${{ steps.coverage.outputs.report }}
Generated
+27 -65
View File
@@ -185,9 +185,9 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.44"
version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [
"iana-time-zone",
"num-traits",
@@ -409,33 +409,16 @@ dependencies = [
"hybrid-array",
]
[[package]]
name = "ctor"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1c888a2a4f677017373fb6c01e13e318dd9e78758445ed5eb985e355d3f8281"
dependencies = [
"ctor-proc-macro",
"dtor",
"link-section 0.0.12",
]
[[package]]
name = "ctor"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01334b89b69ff726750c5ce5073fc8bd860e99aa9a8fc5ca11b04730e3aee97a"
dependencies = [
"link-section 0.18.0",
"link-section",
"linktime-proc-macro",
]
[[package]]
name = "ctor-proc-macro"
version = "0.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7ab264ea985f1bd27887d7b21ea2bb046728e05d11909ca138d700c494730db"
[[package]]
name = "ctutils"
version = "0.4.2"
@@ -523,21 +506,6 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "dtor"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30e4690622ab6700ced40fc370a3f07b7d111f0154bb6fb08f73b4c8834f75b6"
dependencies = [
"dtor-proc-macro",
]
[[package]]
name = "dtor-proc-macro"
version = "0.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c98b077c7463d01d22dde8a24378ddf1ca7263dc687cffbed38819ea6c21131"
[[package]]
name = "dunce"
version = "1.0.5"
@@ -563,7 +531,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -847,7 +815,7 @@ dependencies = [
"portable-atomic",
"portable-atomic-util",
"serde_core",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -916,9 +884,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
[[package]]
name = "libc"
version = "0.2.182"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "libredox"
@@ -932,12 +900,6 @@ dependencies = [
"redox_syscall",
]
[[package]]
name = "link-section"
version = "0.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52437d47b0358721ec869cc7374b2a21f7b2237af9b439c0391341a1fbfbf1b"
[[package]]
name = "link-section"
version = "0.18.0"
@@ -989,9 +951,9 @@ dependencies = [
[[package]]
name = "nix"
version = "0.31.2"
version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3"
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
dependencies = [
"bitflags",
"cfg-if",
@@ -1219,9 +1181,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.12.3"
version = "1.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
dependencies = [
"aho-corasick",
"memchr",
@@ -1248,9 +1210,9 @@ checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973"
[[package]]
name = "regex-syntax"
version = "0.8.10"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "rlimit"
@@ -1283,7 +1245,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -1456,7 +1418,7 @@ dependencies = [
"clap_complete",
"clap_mangen",
"codspeed-divan-compat",
"ctor 1.0.7",
"ctor",
"phf",
"phf_codegen",
"pretty_assertions",
@@ -1481,10 +1443,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.4.2",
"getrandom 0.3.4",
"once_cell",
"rustix",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -1494,7 +1456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
dependencies = [
"rustix",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -1681,20 +1643,20 @@ dependencies = [
"clap",
"regex",
"tar",
"tempfile",
"thiserror",
"uucore",
]
[[package]]
name = "uucore"
version = "0.8.0"
source = "git+https://github.com/uutils/coreutils#b13588cc944be76a123c841f94889b10dfbf7932"
version = "0.9.0"
source = "git+https://github.com/uutils/coreutils#48d030dfb2ca7659ee2735f09cda706c8245a3fc"
dependencies = [
"clap",
"dns-lookup",
"dunce",
"fluent",
"fluent-bundle",
"fluent-syntax",
"jiff",
"libc",
@@ -1713,8 +1675,8 @@ dependencies = [
[[package]]
name = "uucore_procs"
version = "0.8.0"
source = "git+https://github.com/uutils/coreutils#b13588cc944be76a123c841f94889b10dfbf7932"
version = "0.9.0"
source = "git+https://github.com/uutils/coreutils#48d030dfb2ca7659ee2735f09cda706c8245a3fc"
dependencies = [
"proc-macro2",
"quote",
@@ -1722,10 +1684,10 @@ dependencies = [
[[package]]
name = "uutests"
version = "0.8.0"
source = "git+https://github.com/uutils/coreutils#b13588cc944be76a123c841f94889b10dfbf7932"
version = "0.9.0"
source = "git+https://github.com/uutils/coreutils#48d030dfb2ca7659ee2735f09cda706c8245a3fc"
dependencies = [
"ctor 0.9.1",
"ctor",
"libc",
"nix",
"pretty_assertions",
@@ -1861,7 +1823,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
+20 -8
View File
@@ -4,7 +4,7 @@
// at various scales to track performance over time.
use std::fs::{self, File};
use std::io::Write;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use tar::operations;
use tempfile::TempDir;
@@ -39,7 +39,9 @@ fn collect_files(dir: &Path) -> Vec<PathBuf> {
fn build_archive(archive_path: &Path, source_dir: &Path) {
let files = collect_files(source_dir);
let refs: Vec<&Path> = files.iter().map(|p| p.as_path()).collect();
operations::create::create_archive(archive_path, &refs, false, false).unwrap();
let output = File::create(archive_path).unwrap();
let status_output = io::sink();
operations::create::create_archive(output, status_output, &refs, true, false).unwrap();
}
// ---------------------------------------------------------------------------
@@ -57,7 +59,9 @@ fn create_archive_10_files(bencher: divan::Bencher) {
bencher.bench_local(|| {
let refs: Vec<&Path> = files.iter().map(|p| p.as_path()).collect();
operations::create::create_archive(&archive_path, &refs, false, false).unwrap();
let output = File::create(&archive_path).unwrap();
let status_output = io::sink();
operations::create::create_archive(output, status_output, &refs, true, false).unwrap();
});
}
@@ -72,7 +76,9 @@ fn create_archive_100_files(bencher: divan::Bencher) {
bencher.bench_local(|| {
let refs: Vec<&Path> = files.iter().map(|p| p.as_path()).collect();
operations::create::create_archive(&archive_path, &refs, false, false).unwrap();
let output = File::create(&archive_path).unwrap();
let status_output = io::sink();
operations::create::create_archive(output, status_output, &refs, true, false).unwrap();
});
}
@@ -87,7 +93,10 @@ fn create_archive_directory(bencher: divan::Bencher) {
let archive_path = out.path().join("bench.tar");
bencher.bench_local(|| {
operations::create::create_archive(&archive_path, &[sub.as_path()], false, false).unwrap();
let output = File::create(&archive_path).unwrap();
let status_output = io::sink();
operations::create::create_archive(output, status_output, &[sub.as_path()], true, false)
.unwrap();
});
}
@@ -104,7 +113,8 @@ fn list_archive_50_files(bencher: divan::Bencher) {
build_archive(&archive_path, source.path());
bencher.bench_local(|| {
operations::list::list_archive(&archive_path, false).unwrap();
let input = File::open(&archive_path).unwrap();
operations::list::list_archive(input, false).unwrap();
});
}
@@ -117,7 +127,8 @@ fn list_archive_verbose_50_files(bencher: divan::Bencher) {
build_archive(&archive_path, source.path());
bencher.bench_local(|| {
operations::list::list_archive(&archive_path, true).unwrap();
let input = File::open(&archive_path).unwrap();
operations::list::list_archive(input, true).unwrap();
});
}
@@ -138,7 +149,8 @@ fn extract_archive_20_files(bencher: divan::Bencher) {
.with_inputs(|| TempDir::new().unwrap())
.bench_local_values(|extract_dir| {
std::env::set_current_dir(extract_dir.path()).unwrap();
operations::extract::extract_archive(&archive_path, false).unwrap();
let input = File::open(&archive_path).unwrap();
operations::extract::extract_archive(input, &archive_path, false).unwrap();
});
std::env::set_current_dir(original_dir).unwrap();
}
+113 -157
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -14,8 +14,8 @@ libc = "0.2.153"
tempfile = "3.15.0"
rand = { version = "0.10.0" }
similar = "3.0.0"
uucore = { version = "0.8.0", features = ["libc"] }
uufuzz = "0.8.0"
uucore = { version = "0.9.0", features = ["libc"] }
uufuzz = "0.9.0"
uu_tar = { path = "../src/uu/tar/" }
+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 {
+43 -16
View File
@@ -5,8 +5,8 @@
use crate::errors::TarError;
use std::collections::VecDeque;
use std::fs::{self, File};
use std::io::{self, BufWriter, Write};
use std::fs;
use std::io::{BufWriter, Write};
use std::path::Component::{self, ParentDir, Prefix, RootDir};
use std::path::{self, Path, PathBuf};
use tar::Builder;
@@ -28,23 +28,19 @@ use uucore::error::UResult;
/// - Any input file cannot be read
/// - Files cannot be added due to I/O or permission errors
pub fn create_archive(
archive_path: &Path,
output: impl Write,
status_output: impl Write,
files: &[&Path],
allow_absolute: bool,
verbose: bool,
) -> UResult<()> {
// Create the output file
let file = File::create(archive_path).map_err(|e| TarError::CannotCreateArchive {
path: archive_path.to_path_buf(),
source: e,
})?;
let mut output = BufWriter::new(output);
let mut status_output = BufWriter::new(status_output);
// Create Builder instance
let mut builder = Builder::new(file);
let mut builder = Builder::new(&mut output);
builder.preserve_absolute(allow_absolute);
let mut out = BufWriter::new(io::stdout().lock());
// Add each file or directory to the archive
for &path in files {
// Check if path exists
@@ -68,7 +64,7 @@ pub fn create_archive(
})
.collect::<Vec<_>>()
.join("\n");
writeln!(out, "{to_print}").map_err(TarError::Io)?;
writeln!(status_output, "{to_print}").map_err(TarError::Io)?;
}
// Normalize path if needed (so far, handles only absolute paths)
@@ -81,8 +77,8 @@ pub fn create_archive(
.iter()
.collect();
writeln!(
out,
"Removing leading `{}' from member names",
std::io::stderr(),
"tar: Removing leading `{}' from member names",
removed.display()
)
.map_err(TarError::Io)?;
@@ -112,9 +108,11 @@ pub fn create_archive(
}
}
// Finish writing the archive
out.flush().map_err(TarError::Io)?;
builder.finish().map_err(TarError::CannotFinalizeArchive)?;
drop(builder);
status_output.flush().map_err(TarError::Io)?;
output.flush().map_err(TarError::Io)?;
Ok(())
}
@@ -148,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());
}
}
+3 -7
View File
@@ -4,8 +4,7 @@
// file that was distributed with this source code.
use crate::errors::TarError;
use std::fs::File;
use std::io::{self, BufWriter, Write};
use std::io::{self, BufReader, BufWriter, Read, Write};
use std::path::Path;
use tar::Archive;
use uucore::error::UResult;
@@ -23,12 +22,9 @@ use uucore::error::UResult;
/// - The archive file cannot be opened
/// - The archive format is invalid
/// - Files cannot be extracted due to I/O or permission errors
pub fn extract_archive(archive_path: &Path, verbose: bool) -> UResult<()> {
// Open the archive file
let file = File::open(archive_path).map_err(|e| TarError::from_io_error(e, archive_path))?;
pub fn extract_archive(input: impl Read, archive_path: &Path, verbose: bool) -> UResult<()> {
// Create Archive instance
let mut archive = Archive::new(file);
let mut archive = Archive::new(BufReader::new(input));
let mut out = BufWriter::new(io::stdout().lock());
// Extract to current directory
+3 -7
View File
@@ -5,18 +5,14 @@
use crate::errors::TarError;
use chrono::{TimeZone, Utc};
use std::fs::File;
use std::io::{self, BufWriter, Write};
use std::path::Path;
use std::io::{self, BufReader, BufWriter, Read, Write};
use tar::Archive;
use uucore::error::UResult;
use uucore::fs::display_permissions_unix;
/// List the contents of a tar archive, printing one entry per line.
pub fn list_archive(archive_path: &Path, verbose: bool) -> UResult<()> {
let file: File =
File::open(archive_path).map_err(|e| TarError::from_io_error(e, archive_path))?;
let mut archive = Archive::new(file);
pub fn list_archive(input: impl Read, verbose: bool) -> UResult<()> {
let mut archive = Archive::new(BufReader::new(input));
let mut out = BufWriter::new(io::stdout().lock());
for entry_result in archive.entries().map_err(TarError::CannotReadEntries)? {
+46 -3
View File
@@ -6,7 +6,10 @@
pub mod errors;
pub mod operations;
use crate::errors::TarError;
use clap::{arg, crate_version, ArgAction, Command};
use std::fs::File;
use std::io::{self, IsTerminal};
use std::path::{Path, PathBuf};
use uucore::error::UResult;
use uucore::format_usage;
@@ -139,7 +142,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
uucore::error::USimpleError::new(64, "option requires an argument -- 'f'")
})?;
return operations::extract::extract_archive(archive_path, verbose);
return if archive_path == Path::new("-") {
operations::extract::extract_archive(io::stdin(), archive_path, verbose)
} else {
let file =
File::open(archive_path).map_err(|e| TarError::from_io_error(e, archive_path))?;
operations::extract::extract_archive(file, archive_path, verbose)
};
}
// Handle create operation
@@ -160,7 +169,35 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
));
}
return operations::create::create_archive(archive_path, &files, allow_absolute, verbose);
let output_is_stdout = archive_path == Path::new("-");
return if output_is_stdout {
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().lock();
operations::create::create_archive(
output,
status_output,
&files,
allow_absolute,
verbose,
)
};
}
// Handle list operation
@@ -169,7 +206,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
uucore::error::USimpleError::new(64, "option requires an argument -- 'f'")
})?;
return operations::list::list_archive(archive_path, verbose);
return if archive_path == Path::new("-") {
operations::list::list_archive(io::stdin(), verbose)
} else {
let file =
File::open(archive_path).map_err(|e| TarError::from_io_error(e, archive_path))?;
operations::list::list_archive(file, verbose)
};
}
// If no operation specified, show error
+124 -1
View File
@@ -145,6 +145,17 @@ fn test_create_verbose() {
assert!(at.file_exists("archive.tar"));
}
#[test]
fn test_create_verbose_to_stdout_uses_stderr() {
let (at, mut ucmd) = at_and_ucmd!();
at.write("file.txt", "hello");
ucmd.args(&["-cvf", "-", "file.txt"])
.succeeds()
.stderr_contains("file.txt");
}
#[test]
fn test_create_empty_archive_fails() {
new_ucmd!()
@@ -179,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"));
@@ -814,3 +825,115 @@ fn test_list_conflicts_with_extract() {
.code_is(2)
.stderr_contains("cannot be used with");
}
// stdin/stdout (-f -) tests
#[test]
fn test_create_to_stdout() {
let (at, mut ucmd) = at_and_ucmd!();
at.write("file.txt", "hello");
let result = ucmd.args(&["-cf", "-", "file.txt"]).succeeds();
let bytes = result.stdout();
// A tar archive is at least two blocks: one header + two end-of-archive blocks
assert!(
bytes.len() >= TAR_BLOCK_SIZE,
"stdout should contain tar data (got {} bytes)",
bytes.len()
);
// The first 100 bytes of a tar header are the file name
let header_prefix = std::str::from_utf8(&bytes[..100]).unwrap_or("");
assert!(
header_prefix.contains("file.txt"),
"tar header should contain filename"
);
}
#[test]
fn test_create_verbose_to_stdout_keeps_stdout_as_tar_data() {
let (at, mut ucmd) = at_and_ucmd!();
at.write("file.txt", "hello");
let result = ucmd.args(&["-cvf", "-", "file.txt"]).succeeds();
let bytes = result.stdout();
let header_prefix = std::str::from_utf8(&bytes[..100]).unwrap_or("");
assert!(
bytes.len() >= TAR_BLOCK_SIZE,
"stdout should contain tar data (got {} bytes)",
bytes.len()
);
assert!(
header_prefix.contains("file.txt"),
"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]
fn test_create_absolute_path_to_stdout_uses_stderr_for_normalization_notice() {
let (at, mut ucmd) = at_and_ucmd!();
let mut file_abs_path = PathBuf::from(at.root_dir_resolved());
file_abs_path.push("file1.txt");
at.write(&file_abs_path.display().to_string(), "content1");
let result = ucmd
.args(&["-cf", "-", &file_abs_path.display().to_string()])
.succeeds();
result.stderr_contains("Removing leading");
let bytes = result.stdout();
let header_prefix = std::str::from_utf8(&bytes[..100]).unwrap_or("");
assert!(
bytes.len() >= TAR_BLOCK_SIZE,
"stdout should contain tar data (got {} bytes)",
bytes.len()
);
assert!(
!header_prefix.contains("Removing leading"),
"stdout should not contain normalization notices"
);
}
#[test]
fn test_list_from_stdin() {
let (at, mut ucmd) = at_and_ucmd!();
at.write("file.txt", "hello");
// Create archive to a file in the same sandbox
new_ucmd!()
.args(&["-cf", &at.plus_as_string("archive.tar"), "file.txt"])
.current_dir(at.as_string())
.succeeds();
let archive_bytes = at.read_bytes("archive.tar");
ucmd.args(&["-tf", "-"])
.pipe_in(archive_bytes)
.succeeds()
.stdout_contains("file.txt");
}
#[test]
fn test_extract_from_stdin() {
let (at, mut ucmd) = at_and_ucmd!();
at.write("file.txt", "hello world");
// Create archive to a file in the same sandbox
new_ucmd!()
.args(&["-cf", &at.plus_as_string("archive.tar"), "file.txt"])
.current_dir(at.as_string())
.succeeds();
at.remove("file.txt");
let archive_bytes = at.read_bytes("archive.tar");
ucmd.args(&["-xf", "-"]).pipe_in(archive_bytes).succeeds();
assert_eq!(at.read("file.txt"), "hello world");
}