diff --git a/src/uu/tar/src/operations/list.rs b/src/uu/tar/src/operations/list.rs index 8f88b7b..24f496a 100644 --- a/src/uu/tar/src/operations/list.rs +++ b/src/uu/tar/src/operations/list.rs @@ -6,7 +6,7 @@ use crate::compression::open_archive_reader; use crate::errors::TarError; use crate::CompressionMode; -use chrono::{TimeZone, Utc}; +use chrono::{Local, TimeZone}; use std::io::Read; use std::io::{self, BufWriter, Write}; use std::path::Path; @@ -69,6 +69,7 @@ fn format_verbose_entry(entry: &tar::Entry<'_, R>) -> Result 'd', tar::EntryType::Symlink => 'l', + tar::EntryType::Link => 'h', tar::EntryType::Char => 'c', tar::EntryType::Block => 'b', tar::EntryType::Fifo => 'p', @@ -77,14 +78,21 @@ fn format_verbose_entry(entry: &tar::Entry<'_, R>) -> Result = Utc + // GNU tar shows times in the local time zone + let dt: chrono::DateTime = Local .timestamp_opt(mtime as i64, 0) .single() - .unwrap_or_else(Utc::now); + .unwrap_or_else(Local::now); let date_str = dt.format("%Y-%m-%d %H:%M"); + let link_str = match (entry_type, entry.link_name().ok().flatten()) { + (tar::EntryType::Symlink, Some(target)) => format!(" -> {}", target.display()), + (tar::EntryType::Link, Some(target)) => format!(" link to {}", target.display()), + _ => String::new(), + }; + Ok(format!( - "{permissions} {owner}/{group} {size:>8} {date_str} {}", + "{permissions} {owner}/{group} {size:>8} {date_str} {}{link_str}", path.display() )) } diff --git a/tests/by-util/test_tar.rs b/tests/by-util/test_tar.rs index b766295..6f3a59e 100644 --- a/tests/by-util/test_tar.rs +++ b/tests/by-util/test_tar.rs @@ -1020,6 +1020,75 @@ fn test_list_verbose() { .stdout_contains("7 "); // verbose output includes file size; absent from plain -t listing } +#[test] +fn test_list_verbose_links() { + let at = &at_and_ucmd!().0; + + let mut tar_bytes = Vec::new(); + { + let mut builder = TarRsBuilder::new(&mut tar_bytes); + let mut header = TarRsHeader::new_gnu(); + header.set_mode(0o644); + header.set_size(5); + header.set_cksum(); + builder + .append_data(&mut header, "a.txt", Cursor::new("hello")) + .unwrap(); + + let mut header = TarRsHeader::new_gnu(); + header.set_entry_type(tar_rs_crate::EntryType::Symlink); + header.set_mode(0o777); + header.set_size(0); + builder.append_link(&mut header, "sym", "a.txt").unwrap(); + + let mut header = TarRsHeader::new_gnu(); + header.set_entry_type(tar_rs_crate::EntryType::Link); + header.set_mode(0o644); + header.set_size(0); + builder.append_link(&mut header, "hard", "a.txt").unwrap(); + builder.finish().unwrap(); + } + at.write_bytes("links.tar", &tar_bytes); + + new_ucmd!() + .args(&["-tvf", "links.tar"]) + .current_dir(at.as_string()) + .succeeds() + .stdout_contains("\nlrwxrwxrwx ") + .stdout_contains(" sym -> a.txt\n") + .stdout_contains("\nhrw-r--r-- ") + .stdout_contains(" hard link to a.txt\n"); +} + +#[test] +// chrono reads TZ on Unix only +#[cfg(unix)] +fn test_list_verbose_uses_local_time() { + let at = &at_and_ucmd!().0; + + let mut tar_bytes = Vec::new(); + { + let mut builder = TarRsBuilder::new(&mut tar_bytes); + let mut header = TarRsHeader::new_gnu(); + header.set_mode(0o644); + header.set_size(0); + header.set_mtime(0); + header.set_cksum(); + builder + .append_data(&mut header, "epoch.txt", Cursor::new("")) + .unwrap(); + builder.finish().unwrap(); + } + at.write_bytes("epoch.tar", &tar_bytes); + + new_ucmd!() + .env("TZ", "IST-5:30") + .args(&["-tvf", "epoch.tar"]) + .current_dir(at.as_string()) + .succeeds() + .stdout_contains("1970-01-01 05:30 epoch.txt"); +} + #[test] fn test_list_nonexistent_archive() { new_ucmd!()