From a503cb7c9388401f5e9bb27ce3c7ac99f7e91d2c Mon Sep 17 00:00:00 2001 From: jayhemnani9910 <193022578+jayhemnani9910@users.noreply.github.com> Date: Wed, 21 Oct 2026 03:22:03 +0530 Subject: [PATCH 1/4] tar: show link targets and local times in -tv listings --- src/uu/tar/src/operations/list.rs | 20 +++++++-- tests/by-util/test_tar.rs | 67 +++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/uu/tar/src/operations/list.rs b/src/uu/tar/src/operations/list.rs index 8f88b7b..da82564 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,25 @@ 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 = match entry_type { + tar::EntryType::Symlink | tar::EntryType::Link => entry.link_name().ok().flatten(), + _ => None, + }; + let link_str = match (entry_type, link) { + (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..c321142 100644 --- a/tests/by-util/test_tar.rs +++ b/tests/by-util/test_tar.rs @@ -1020,6 +1020,73 @@ 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] +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", "Asia/Kolkata") + .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!() From e747c03cedbda269f30a6d520a6a47fdd8224b9f Mon Sep 17 00:00:00 2001 From: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com> Date: Wed, 21 Oct 2026 11:45:56 +0530 Subject: [PATCH 2/4] tar: run the TZ listing test on Unix only --- tests/by-util/test_tar.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/by-util/test_tar.rs b/tests/by-util/test_tar.rs index c321142..f05ace3 100644 --- a/tests/by-util/test_tar.rs +++ b/tests/by-util/test_tar.rs @@ -1061,6 +1061,8 @@ fn test_list_verbose_links() { } #[test] +// chrono reads TZ on Unix only +#[cfg(unix)] fn test_list_verbose_uses_local_time() { let at = &at_and_ucmd!().0; From c30dd7053e875ddad5f4443244150931b90fc440 Mon Sep 17 00:00:00 2001 From: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com> Date: Wed, 21 Oct 2026 13:00:12 +0530 Subject: [PATCH 3/4] tar: use a POSIX TZ string in the local time test --- tests/by-util/test_tar.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_tar.rs b/tests/by-util/test_tar.rs index f05ace3..6f3a59e 100644 --- a/tests/by-util/test_tar.rs +++ b/tests/by-util/test_tar.rs @@ -1082,7 +1082,7 @@ fn test_list_verbose_uses_local_time() { at.write_bytes("epoch.tar", &tar_bytes); new_ucmd!() - .env("TZ", "Asia/Kolkata") + .env("TZ", "IST-5:30") .args(&["-tvf", "epoch.tar"]) .current_dir(at.as_string()) .succeeds() From c886ba4e992393d5ca805e04bfa21d47045a95b3 Mon Sep 17 00:00:00 2001 From: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com> Date: Wed, 21 Oct 2026 13:02:19 +0530 Subject: [PATCH 4/4] tar: one match for the link target --- src/uu/tar/src/operations/list.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/uu/tar/src/operations/list.rs b/src/uu/tar/src/operations/list.rs index da82564..24f496a 100644 --- a/src/uu/tar/src/operations/list.rs +++ b/src/uu/tar/src/operations/list.rs @@ -85,11 +85,7 @@ fn format_verbose_entry(entry: &tar::Entry<'_, R>) -> Result entry.link_name().ok().flatten(), - _ => None, - }; - let link_str = match (entry_type, link) { + 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(),