From 0cb64bbd56e7228409f1b527881bfc164327d4cb Mon Sep 17 00:00:00 2001 From: weili <541602953@qq.com> Date: Wed, 5 Aug 2026 06:29:45 +0000 Subject: [PATCH] stat: truncate string precision on a byte boundary to avoid a panic print_str applied a `%.P` precision by slicing the &str value (`&s[..p]`), which panics when p lands inside a multibyte UTF-8 character (e.g. a file name printed with %n). Route it through write_padded_bytes, as print_os_str already does, so truncation and padding operate on bytes and match GNU stat's byte truncation instead of crashing. --- src/uu/stat/src/stat.rs | 13 ++++++++----- tests/by-util/test_stat.rs | 11 +++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 8e1f76779a1..2cc6c655701 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -405,11 +405,14 @@ fn determine_padding_char(flags: Flags) -> Padding { /// * `width` - The width of the field for the printed string. /// * `precision` - How many digits of precision, if any. fn print_str(s: &str, flags: Flags, width: usize, precision: Precision) { - let s = match precision { - Precision::Number(p) if p < s.len() => &s[..p], - _ => s, - }; - pad_and_print(s, flags.left, width, Padding::Space); + // Truncate and pad on the byte representation, so a precision that lands inside a multibyte character does not cause a panic. + let _ = write_padded_bytes( + std::io::stdout(), + s.as_bytes(), + flags.left, + width, + precision, + ); } /// Prints a `OsString` value based on the provided flags, width, and precision. diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 153ef39c88d..13103ecc772 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -593,6 +593,17 @@ fn test_invalid_directive_after_multibyte_char() { } } +#[test] +fn test_precision_splits_multibyte_char_in_value() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("é"); + ts.ucmd() + .args(&["-c", "%.1n", "é"]) + .succeeds() + .stdout_only_bytes([0xc3, b'\n']); +} + #[test] #[cfg(all( feature = "feat_selinux",