From 5bf9be2ad05d0316502a54a033fa8e7f8a85e20e Mon Sep 17 00:00:00 2001 From: Yunus Emre Ozen <94426948+ozenyunus35@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:57:30 +0300 Subject: [PATCH 1/2] stat: print the -f block and free node counts signed, as GNU does GNU prints %a, %b, %d and %f of `stat -f` with out_int, so the `+` and ` ` flags add a sign: `stat -f -c %+b /` gives `+123`. We printed them unsigned and dropped the flags. --- src/uu/stat/src/stat.rs | 10 ++++++---- tests/by-util/test_stat.rs | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 41fe5bf895c..0c50d67bfdf 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -544,16 +544,18 @@ fn process_token_filesystem( format, } => { let output = match format { + // GNU prints the block and free node counts signed (`out_int`), so + // `+` and ` ` add a sign; the other counts are unsigned. // free blocks available to non-superuser - 'a' => OutputType::Unsigned(meta.avail_blocks()), + 'a' => OutputType::Integer(meta.avail_blocks() as i64), // total data blocks in file system - 'b' => OutputType::Unsigned(meta.total_blocks()), + 'b' => OutputType::Integer(meta.total_blocks() as i64), // total file nodes in file system 'c' => OutputType::Unsigned(meta.total_file_nodes()), // free file nodes in file system - 'd' => OutputType::Unsigned(meta.free_file_nodes()), + 'd' => OutputType::Integer(meta.free_file_nodes() as i64), // free blocks in file system - 'f' => OutputType::Unsigned(meta.free_blocks()), + 'f' => OutputType::Integer(meta.free_blocks() as i64), // file system ID in hex 'i' => OutputType::UnsignedHex(meta.fsid()), // maximum length of filenames diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 2f7c3157723..a4807df5b40 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -774,6 +774,25 @@ fn test_printf_invalid_directive() { .stderr_contains("'%9%': invalid directive"); } +#[test] +#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))] +fn test_fs_signed_counts_take_sign_flags() { + // GNU prints %a, %b, %d and %f signed, so `+` and ` ` add a sign there, + // while the unsigned counts (%c, %s) ignore them. + let out = new_ucmd!() + .args(&["-f", "--printf=%+a|% b|%+d|%+f|%+c|%+s", "."]) + .succeeds() + .stdout_move_str(); + let fields: Vec<&str> = out.split('|').collect(); + assert!(fields[0].starts_with('+'), "{out}"); + assert!(fields[1].starts_with(' '), "{out}"); + assert!(fields[2].starts_with('+'), "{out}"); + assert!(fields[3].starts_with('+'), "{out}"); + for unsigned in &fields[4..] { + assert!(unsigned.chars().all(|c| c.is_ascii_digit()), "{out}"); + } +} + #[test] fn test_invalid_directive_after_multibyte_char() { let ts = TestScenario::new(util_name!()); From 71aea2dc527e7a9de88f4e6a955a13f5bd1bd046 Mon Sep 17 00:00:00 2001 From: Yunus Emre Ozen <94426948+ozenyunus35@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:41:39 +0300 Subject: [PATCH 2/2] stat: drop the GNU internals reference and the repeated test comment --- src/uu/stat/src/stat.rs | 3 +-- tests/by-util/test_stat.rs | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 0c50d67bfdf..88f41b9d54f 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -544,8 +544,7 @@ fn process_token_filesystem( format, } => { let output = match format { - // GNU prints the block and free node counts signed (`out_int`), so - // `+` and ` ` add a sign; the other counts are unsigned. + // The block and free node counts are signed, so `+` and ` ` apply. // free blocks available to non-superuser 'a' => OutputType::Integer(meta.avail_blocks() as i64), // total data blocks in file system diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index a4807df5b40..b2daa36dadb 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -777,8 +777,6 @@ fn test_printf_invalid_directive() { #[test] #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))] fn test_fs_signed_counts_take_sign_flags() { - // GNU prints %a, %b, %d and %f signed, so `+` and ` ` add a sign there, - // while the unsigned counts (%c, %s) ignore them. let out = new_ucmd!() .args(&["-f", "--printf=%+a|% b|%+d|%+f|%+c|%+s", "."]) .succeeds()