From de8c78439f7b521709fae5cc6cff2d09dd578cbe Mon Sep 17 00:00:00 2001 From: Logic769 Date: Fri, 30 Oct 2026 03:59:30 +0800 Subject: [PATCH 1/3] od: reject type options when --strings is given --- src/uu/od/locales/en-US.ftl | 1 + src/uu/od/src/od.rs | 9 ++++++++- src/uu/od/src/parse_formats.rs | 26 ++++++++++++++++++++++++++ tests/by-util/test_od.rs | 10 ++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/uu/od/locales/en-US.ftl b/src/uu/od/locales/en-US.ftl index e9edda4395b..86ccc869991 100644 --- a/src/uu/od/locales/en-US.ftl +++ b/src/uu/od/locales/en-US.ftl @@ -45,6 +45,7 @@ od-error-invalid-endian = Invalid argument --endian={$endian} od-error-invalid-inputs = Invalid inputs: {$msg} od-error-too-large = value is too large od-error-radix-invalid = invalid output address radix '{$radix}'; it must be one character from [doxn] +od-error-no-type-with-strings = no type may be specified when dumping strings od-error-radix-empty = Radix cannot be empty, and must be one of [o, d, x, n] od-error-invalid-width = invalid width {$width}; using {$min} instead od-error-missing-format-spec = missing format specification after '--format' / '-t' diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index bdd69fbe18e..2253e744fd6 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -31,7 +31,7 @@ use crate::input_decoder::{InputDecoder, MemoryDecoder}; use crate::input_offset::{InputOffset, Radix}; use crate::multifile_reader::{HasError, InputSource, MultifileReader}; use crate::output_info::OutputInfo; -use crate::parse_formats::{ParsedFormatterItemInfo, parse_format_flags}; +use crate::parse_formats::{ParsedFormatterItemInfo, has_type_option, parse_format_flags}; use crate::parse_inputs::{CommandLineInputs, parse_inputs}; use crate::parse_nrofbytes::{parse_number_of_bytes, parse_width}; use crate::partial_reader::PartialReader; @@ -195,6 +195,13 @@ impl OdOptions { let read_bytes = parse_bytes_option(matches, args, diag_args, options::READ_BYTES, 'N')?; let strings = parse_bytes_option(matches, args, diag_args, options::STRINGS, 'S')?; + if strings.is_some() && has_type_option(args) { + return Err(USimpleError::new( + 1, + translate!("od-error-no-type-with-strings"), + )); + } + let string_min_length = match strings { None => None, Some(n) => Some(usize::try_from(n).map_err(|_| { diff --git a/src/uu/od/src/parse_formats.rs b/src/uu/od/src/parse_formats.rs index fb88e4df73c..d159a4d9e94 100644 --- a/src/uu/od/src/parse_formats.rs +++ b/src/uu/od/src/parse_formats.rs @@ -164,6 +164,32 @@ pub fn parse_format_flags(args: &[String]) -> Result bool { + for arg in args.iter().skip(1) { + if arg.starts_with("--") { + if arg.len() == 2 { + break; + } + if arg.starts_with("--format") { + return true; + } + } else if arg.starts_with('-') { + for c in arg.chars().skip(1) { + if od_argument_with_option(c) { + break; + } + if c == 't' || od_argument_traditional_format(c).is_some() { + return true; + } + } + } + } + + false +} + #[derive(PartialEq, Eq, Debug, Copy, Clone)] enum FormatType { Ascii, diff --git a/tests/by-util/test_od.rs b/tests/by-util/test_od.rs index a27c855378c..cdc540e3a34 100644 --- a/tests/by-util/test_od.rs +++ b/tests/by-util/test_od.rs @@ -1792,3 +1792,13 @@ fn test_od_zero_byte_type_size() { .succeeds() .stdout_is("0000000 64636261\n0000004\n"); } + +#[test] +fn test_strings_with_type_option_is_error() { + let (at, mut ucmd) = at_and_ucmd!(); + at.write("a.txt", "a\n"); + ucmd.args(&["--strings=1", "-c", "a.txt"]) + .fails() + .code_is(1) + .stderr_contains("no type may be specified when dumping strings"); +} From c46c9aa8baaf06faee1e05cb782d05a5431a0105 Mon Sep 17 00:00:00 2001 From: Logic769 Date: Mon, 2 Nov 2026 10:27:30 +0800 Subject: [PATCH 2/3] fix(od): add French translation for strings type conflict --- src/uu/od/locales/fr-FR.ftl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/od/locales/fr-FR.ftl b/src/uu/od/locales/fr-FR.ftl index d4a775c95ea..e84813578a7 100644 --- a/src/uu/od/locales/fr-FR.ftl +++ b/src/uu/od/locales/fr-FR.ftl @@ -46,6 +46,7 @@ od-error-invalid-endian = Argument invalide --endian={$endian} od-error-invalid-inputs = Entrées invalides : {$msg} od-error-too-large = la valeur est trop grande od-error-radix-invalid = base d'adresse de sortie invalide '{$radix}' ; elle doit être un caractère parmi [doxn] +od-error-no-type-with-strings = aucun type ne peut être spécifié lors de l'affichage des chaînes od-error-radix-empty = Radix ne peut pas être vide, et doit être l'un de [o, d, x, n] od-error-invalid-width = largeur invalide {$width} ; utilisation de {$min} à la place od-error-missing-format-spec = spécification de format manquante après '--format' / '-t' From 31e10e2ff976010988b128946c605d4666e95161 Mon Sep 17 00:00:00 2001 From: Logic769 Date: Tue, 3 Nov 2026 08:15:09 +0800 Subject: [PATCH 3/3] od: decide the default format after parsing the flags --- src/uu/od/src/od.rs | 11 ++++++--- src/uu/od/src/parse_formats.rs | 45 +++++++--------------------------- tests/by-util/test_od.rs | 18 +++++++++----- 3 files changed, 29 insertions(+), 45 deletions(-) diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 2253e744fd6..769ec0c345b 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -31,12 +31,13 @@ use crate::input_decoder::{InputDecoder, MemoryDecoder}; use crate::input_offset::{InputOffset, Radix}; use crate::multifile_reader::{HasError, InputSource, MultifileReader}; use crate::output_info::OutputInfo; -use crate::parse_formats::{ParsedFormatterItemInfo, has_type_option, parse_format_flags}; +use crate::parse_formats::{ParsedFormatterItemInfo, parse_format_flags}; use crate::parse_inputs::{CommandLineInputs, parse_inputs}; use crate::parse_nrofbytes::{parse_number_of_bytes, parse_width}; use crate::partial_reader::PartialReader; use crate::peek_reader::{PeekRead, PeekReader}; use crate::prn_char::format_ascii_dump; +use crate::prn_int::FORMAT_ITEM_OCT16; use clap::ArgAction; use clap::{Arg, ArgMatches, Command, parser::ValueSource}; use std::ffi::OsString; @@ -138,7 +139,11 @@ impl OdOptions { } }; - let formats = parse_format_flags(args).map_err(|e| USimpleError::new(1, e))?; + let mut formats = parse_format_flags(args).map_err(|e| USimpleError::new(1, e))?; + let has_type_option = !formats.is_empty(); + if formats.is_empty() { + formats.push(ParsedFormatterItemInfo::new(FORMAT_ITEM_OCT16, false)); // 2 byte octal is the default + } let mut line_bytes = if let (Some(s), Some(ValueSource::CommandLine)) = ( matches.get_one::(options::WIDTH), @@ -195,7 +200,7 @@ impl OdOptions { let read_bytes = parse_bytes_option(matches, args, diag_args, options::READ_BYTES, 'N')?; let strings = parse_bytes_option(matches, args, diag_args, options::STRINGS, 'S')?; - if strings.is_some() && has_type_option(args) { + if strings.is_some() && has_type_option { return Err(USimpleError::new( 1, translate!("od-error-no-type-with-strings"), diff --git a/src/uu/od/src/parse_formats.rs b/src/uu/od/src/parse_formats.rs index d159a4d9e94..ef85e96ab33 100644 --- a/src/uu/od/src/parse_formats.rs +++ b/src/uu/od/src/parse_formats.rs @@ -157,39 +157,9 @@ pub fn parse_format_flags(args: &[String]) -> Result bool { - for arg in args.iter().skip(1) { - if arg.starts_with("--") { - if arg.len() == 2 { - break; - } - if arg.starts_with("--format") { - return true; - } - } else if arg.starts_with('-') { - for c in arg.chars().skip(1) { - if od_argument_with_option(c) { - break; - } - if c == 't' || od_argument_traditional_format(c).is_some() { - return true; - } - } - } - } - - false -} - #[derive(PartialEq, Eq, Debug, Copy, Clone)] enum FormatType { Ascii, @@ -391,13 +361,16 @@ fn parse_type_string(params: &str) -> Result, Strin #[cfg(test)] pub fn parse_format_flags_str(args_str: &[&'static str]) -> Result, String> { let args: Vec = args_str.iter().map(|s| (*s).to_string()).collect(); - parse_format_flags(&args).map(|v| { + let mut formats = parse_format_flags(&args)?; + if formats.is_empty() { + formats.push(ParsedFormatterItemInfo::new(FORMAT_ITEM_OCT16, false)); // 2 byte octal is the default + } + Ok(formats + .into_iter() // tests using this function assume add_ascii_dump is not set - v.into_iter() - .inspect(|f| assert!(!f.add_ascii_dump)) - .map(|f| f.formatter_item_info) - .collect() - }) + .inspect(|f| assert!(!f.add_ascii_dump)) + .map(|f| f.formatter_item_info) + .collect()) } #[test] diff --git a/tests/by-util/test_od.rs b/tests/by-util/test_od.rs index cdc540e3a34..7d52940f10d 100644 --- a/tests/by-util/test_od.rs +++ b/tests/by-util/test_od.rs @@ -1795,10 +1795,16 @@ fn test_od_zero_byte_type_size() { #[test] fn test_strings_with_type_option_is_error() { - let (at, mut ucmd) = at_and_ucmd!(); - at.write("a.txt", "a\n"); - ucmd.args(&["--strings=1", "-c", "a.txt"]) - .fails() - .code_is(1) - .stderr_contains("no type may be specified when dumping strings"); + for args in [ + &["--strings=1", "-t", "x1", "a.txt"][..], + &["--strings=1", "--format=x1", "a.txt"][..], + &["-S1", "-x", "a.txt"][..], + ] { + let (at, mut ucmd) = at_and_ucmd!(); + at.write("a.txt", "a\n"); + ucmd.args(args) + .fails() + .code_is(1) + .stderr_contains("no type may be specified when dumping strings"); + } }