From 707e346b84a0b54a6154014100d965dfbe9934db Mon Sep 17 00:00:00 2001 From: Dorian Peron Date: Fri, 31 Jan 2025 16:14:24 +0100 Subject: [PATCH] printf: negative asterisk param changes alignement --- src/uucore/src/lib/features/format/spec.rs | 33 +++++++++++++++++++--- tests/by-util/test_printf.rs | 26 +++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index 81dbc1ebc..295aa1f98 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -314,15 +314,17 @@ impl Spec { ) -> Result<(), FormatError> { match self { Self::Char { width, align_left } => { - let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0); - write_padded(writer, &[args.get_char()], width, *align_left) + let (width, neg_width) = + resolve_asterisk_maybe_negative(*width, &mut args)?.unwrap_or_default(); + write_padded(writer, &[args.get_char()], width, *align_left || neg_width) } Self::String { width, align_left, precision, } => { - let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0); + let (width, neg_width) = + resolve_asterisk_maybe_negative(*width, &mut args)?.unwrap_or_default(); // GNU does do this truncation on a byte level, see for instance: // printf "%.1s" 🙃 @@ -336,7 +338,12 @@ impl Spec { Some(p) if p < s.len() => &s[..p], _ => s, }; - write_padded(writer, truncated.as_bytes(), width, *align_left) + write_padded( + writer, + truncated.as_bytes(), + width, + *align_left || neg_width, + ) } Self::EscapedString => { let s = args.get_str(); @@ -458,6 +465,24 @@ fn resolve_asterisk<'a>( }) } +fn resolve_asterisk_maybe_negative<'a>( + option: Option>, + mut args: impl ArgumentIter<'a>, +) -> Result, FormatError> { + Ok(match option { + None => None, + Some(CanAsterisk::Asterisk) => { + let nb = args.get_i64(); + if nb < 0 { + Some((usize::try_from(-(nb as isize)).ok().unwrap_or(0), true)) + } else { + Some((usize::try_from(nb).ok().unwrap_or(0), false)) + } + } + Some(CanAsterisk::Fixed(w)) => Some((w, false)), + }) +} + fn write_padded( mut writer: impl Write, text: &[u8], diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index 044817214..9eb41b44b 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -495,6 +495,32 @@ fn sub_any_asterisk_hex_arg() { .stdout_only("0123456789"); } +#[test] +fn sub_any_asterisk_negative_first_param() { + new_ucmd!() + .args(&["a(%*s)b", "-5", "xyz"]) + .succeeds() + .stdout_only("a(xyz )b"); // Would be 'a( xyz)b' if -5 was 5 + + // Negative octal + new_ucmd!() + .args(&["a(%*s)b", "-010", "xyz"]) + .succeeds() + .stdout_only("a(xyz )b"); + + // Negative hexadecimal + new_ucmd!() + .args(&["a(%*s)b", "-0x10", "xyz"]) + .succeeds() + .stdout_only("a(xyz )b"); + + // Should also work on %c + new_ucmd!() + .args(&["a(%*c)b", "-5", "x"]) + .succeeds() + .stdout_only("a(x )b"); // Would be 'a( x)b' if -5 was 5 +} + #[test] fn sub_any_specifiers_no_params() { new_ucmd!()