Skip to content
Closed
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
6 changes: 4 additions & 2 deletions benches/bench_tar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ fn list_archive_50_files(bencher: divan::Bencher) {

bencher.bench_local(|| {
let input = File::open(&archive_path).unwrap();
operations::list::list_archive(input, &archive_path, false, CompressionMode::None).unwrap();
operations::list::list_archive(input, &archive_path, false, false, CompressionMode::None)
.unwrap();
});
}

Expand All @@ -164,7 +165,8 @@ fn list_archive_verbose_50_files(bencher: divan::Bencher) {

bencher.bench_local(|| {
let input = File::open(&archive_path).unwrap();
operations::list::list_archive(input, &archive_path, true, CompressionMode::None).unwrap();
operations::list::list_archive(input, &archive_path, true, false, CompressionMode::None)
.unwrap();
});
}

Expand Down
40 changes: 26 additions & 14 deletions src/uu/tar/src/operations/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub fn list_archive(
input: impl Read,
_archive_path: &Path,
verbose: bool,
numeric_owner: bool,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

two bools in a row are easy to swap at the call site
not blocking, but an options struct could come later

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I'd keep it as is here and move the list options into a struct in a follow-up once more of them show up.

compression: CompressionMode,
) -> UResult<()> {
let reader = open_archive_reader(input, compression)?;
Expand All @@ -29,7 +30,7 @@ pub fn list_archive(
let entry = entry_result.map_err(TarError::CannotReadEntry)?;

if verbose {
let formatted = format_verbose_entry(&entry)?;
let formatted = format_verbose_entry(&entry, numeric_owner)?;
writeln!(out, "{formatted}").map_err(TarError::Io)?;
} else {
let path = entry.path().map_err(TarError::CannotReadEntryPath)?;
Expand All @@ -41,24 +42,35 @@ pub fn list_archive(
Ok(())
}

fn format_verbose_entry<R: Read>(entry: &tar::Entry<'_, R>) -> Result<String, TarError> {
/// The owner or group as GNU tar lists it: the name the header records, or the
/// numeric id when there is no name or `--numeric-owner` asks for numbers. An
/// id field that can't be read is left empty rather than shown as 0 (root).
fn owner_label(name: Option<&str>, id: Option<u64>, numeric: bool) -> String {
match name {
Some(name) if !numeric && !name.is_empty() => name.to_owned(),
_ => id.map(|id| id.to_string()).unwrap_or_default(),
}
}

fn format_verbose_entry<R: Read>(
entry: &tar::Entry<'_, R>,
numeric_owner: bool,
) -> Result<String, TarError> {
let (mode, entry_type, owner, group, size, mtime) = {
let header = entry.header();
(
header.mode().unwrap_or(0),
header.entry_type(),
header
.username()
.ok()
.flatten()
.unwrap_or_default()
.to_owned(),
header
.groupname()
.ok()
.flatten()
.unwrap_or_default()
.to_owned(),
owner_label(
header.username().ok().flatten(),
header.uid().ok(),
numeric_owner,
),
owner_label(
header.groupname().ok().flatten(),
header.gid().ok(),
numeric_owner,
),
header.size().unwrap_or(0),
header.mtime().unwrap_or(0),
)
Expand Down
15 changes: 13 additions & 2 deletions src/uu/tar/src/operations/list_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fn test_list_archive_with_zstd_non_verbose() {
write_zstd_tar(&archive_path);

let input = fs::File::open(&archive_path).unwrap();
list_archive(input, &archive_path, false, CompressionMode::Zstd).unwrap();
list_archive(input, &archive_path, false, false, CompressionMode::Zstd).unwrap();
}

#[test]
Expand All @@ -43,5 +43,16 @@ fn test_list_archive_with_zstd_verbose() {
write_zstd_tar(&archive_path);

let input = fs::File::open(&archive_path).unwrap();
list_archive(input, &archive_path, true, CompressionMode::Zstd).unwrap();
list_archive(input, &archive_path, true, false, CompressionMode::Zstd).unwrap();
}

#[test]
fn test_owner_label() {
assert_eq!(owner_label(Some("alice"), Some(1000), false), "alice");
assert_eq!(owner_label(Some("alice"), Some(1000), true), "1000");
assert_eq!(owner_label(Some(""), Some(1000), false), "1000");
assert_eq!(owner_label(None, Some(1000), false), "1000");
// An unreadable id field is left empty, not shown as root.
assert_eq!(owner_label(None, None, false), "");
assert_eq!(owner_label(Some("alice"), None, true), "");
}
12 changes: 10 additions & 2 deletions src/uu/tar/src/tar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {

let verbose = matches.get_flag("verbose");
let allow_absolute = matches.get_flag("absolute-names");
let numeric_owner = matches.get_flag("numeric-owner");
let recursive = !matches.get_flag("no-recursion");
let explicit_compression = if matches.get_flag("gzip") {
Some(CompressionMode::Gzip)
Expand Down Expand Up @@ -232,11 +233,17 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {

let compression = explicit_compression.unwrap_or(CompressionMode::Auto);
return if archive_path == Path::new("-") {
operations::list::list_archive(io::stdin(), archive_path, verbose, compression)
operations::list::list_archive(
io::stdin(),
archive_path,
verbose,
numeric_owner,
compression,
)
} else {
let file =
File::open(archive_path).map_err(|e| TarError::from_io_error(e, archive_path))?;
operations::list::list_archive(file, archive_path, verbose, compression)
operations::list::list_archive(file, archive_path, verbose, numeric_owner, compression)
};
}

Expand Down Expand Up @@ -279,6 +286,7 @@ pub fn uu_app() -> Command {
arg!(--zstd "Filter through zstd").conflicts_with("gzip"),
// Common options
arg!(-v --verbose "Verbosely list files processed"),
arg!(--"numeric-owner" "Always use numbers for user/group names"),
arg!(--"no-recursion" "Avoid descending automatically in directories"),
// arg!(-h --dereference "Follow symlinks"),
// arg!(-p --"preserve-permissions" "Extract information about file permissions"),
Expand Down
49 changes: 49 additions & 0 deletions tests/by-util/test_tar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,55 @@ fn test_list_verbose() {
.stdout_contains("7 "); // verbose output includes file size; absent from plain -t listing
}

#[test]
fn test_list_verbose_owner_names_and_numeric_owner() {
let at = &at_and_ucmd!().0;

let mut tar_bytes = Vec::new();
{
let mut builder = TarRsBuilder::new(&mut tar_bytes);
for (path, names) in [("named.txt", true), ("unnamed.txt", false)] {
let mut header = TarRsHeader::new_gnu();
header.set_mode(0o644);
header.set_size("content".len() as u64);
header.set_uid(1234);
header.set_gid(5678);
if names {
header.set_username("alice").unwrap();
header.set_groupname("staff").unwrap();
}
header.set_cksum();
builder
.append_data(&mut header, path, Cursor::new("content"))
.unwrap();
}
builder.finish().unwrap();
}
at.write_bytes("owners.tar", &tar_bytes);

// Like GNU tar: the recorded names, or the ids when the header has none.
let listing = new_ucmd!()
.args(&["-tvf", "owners.tar"])
.current_dir(at.as_string())
.succeeds()
.stdout_move_str();
let lines: Vec<&str> = listing.lines().collect();
assert!(lines[0].contains(" alice/staff ") && lines[0].ends_with("named.txt"));
assert!(lines[1].contains(" 1234/5678 ") && lines[1].ends_with("unnamed.txt"));

// --numeric-owner always lists the ids.
let listing = new_ucmd!()
.args(&["--numeric-owner", "-tvf", "owners.tar"])
.current_dir(at.as_string())
.succeeds()
.stdout_move_str();
let lines: Vec<&str> = listing.lines().collect();
assert_eq!(lines.len(), 2, "{listing}");
for line in lines {
assert!(line.contains(" 1234/5678 "), "{line}");
}
}

#[test]
fn test_list_nonexistent_archive() {
new_ucmd!()
Expand Down
Loading