mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
fix tests/od/od-N
This commit is contained in:
+154
-27
@@ -26,7 +26,7 @@ mod prn_int;
|
||||
|
||||
use std::cmp;
|
||||
use std::fmt::Write;
|
||||
use std::io::BufReader;
|
||||
use std::io::{BufReader, Read};
|
||||
|
||||
use crate::byteorder_io::ByteOrder;
|
||||
use crate::formatter_item_info::FormatWriter;
|
||||
@@ -76,6 +76,7 @@ struct OdOptions {
|
||||
line_bytes: usize,
|
||||
output_duplicates: bool,
|
||||
radix: Radix,
|
||||
string_min_length: Option<usize>,
|
||||
}
|
||||
|
||||
impl OdOptions {
|
||||
@@ -169,6 +170,16 @@ impl OdOptions {
|
||||
},
|
||||
};
|
||||
|
||||
let string_min_length = match parse_bytes_option(matches, options::STRINGS)? {
|
||||
None => None,
|
||||
Some(n) => Some(usize::try_from(n).map_err(|_| {
|
||||
USimpleError::new(
|
||||
1,
|
||||
translate!("od-error-argument-too-large", "option" => "-S", "value" => n.to_string()),
|
||||
)
|
||||
})?),
|
||||
};
|
||||
|
||||
let radix = match matches.get_one::<String>(options::ADDRESS_RADIX) {
|
||||
None => Radix::Octal,
|
||||
Some(s) => {
|
||||
@@ -208,6 +219,7 @@ impl OdOptions {
|
||||
line_bytes,
|
||||
output_duplicates,
|
||||
radix,
|
||||
string_min_length,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -224,28 +236,39 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
|
||||
let od_options = OdOptions::new(&clap_matches, &args)?;
|
||||
|
||||
let mut input_offset =
|
||||
InputOffset::new(od_options.radix, od_options.skip_bytes, od_options.label);
|
||||
// Check if we're in strings mode
|
||||
if let Some(min_length) = od_options.string_min_length {
|
||||
extract_strings_from_input(
|
||||
&od_options.input_strings,
|
||||
od_options.skip_bytes,
|
||||
od_options.read_bytes,
|
||||
min_length,
|
||||
od_options.radix,
|
||||
)
|
||||
} else {
|
||||
let mut input_offset =
|
||||
InputOffset::new(od_options.radix, od_options.skip_bytes, od_options.label);
|
||||
|
||||
let mut input = open_input_peek_reader(
|
||||
&od_options.input_strings,
|
||||
od_options.skip_bytes,
|
||||
od_options.read_bytes,
|
||||
);
|
||||
let mut input_decoder = InputDecoder::new(
|
||||
&mut input,
|
||||
od_options.line_bytes,
|
||||
PEEK_BUFFER_SIZE,
|
||||
od_options.byte_order,
|
||||
);
|
||||
let mut input = open_input_peek_reader(
|
||||
&od_options.input_strings,
|
||||
od_options.skip_bytes,
|
||||
od_options.read_bytes,
|
||||
);
|
||||
let mut input_decoder = InputDecoder::new(
|
||||
&mut input,
|
||||
od_options.line_bytes,
|
||||
PEEK_BUFFER_SIZE,
|
||||
od_options.byte_order,
|
||||
);
|
||||
|
||||
let output_info = OutputInfo::new(
|
||||
od_options.line_bytes,
|
||||
&od_options.formats[..],
|
||||
od_options.output_duplicates,
|
||||
);
|
||||
let output_info = OutputInfo::new(
|
||||
od_options.line_bytes,
|
||||
&od_options.formats[..],
|
||||
od_options.output_duplicates,
|
||||
);
|
||||
|
||||
odfunc(&mut input_offset, &mut input_decoder, &output_info)
|
||||
odfunc(&mut input_offset, &mut input_decoder, &output_info)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uu_app() -> Command {
|
||||
@@ -263,7 +286,7 @@ pub fn uu_app() -> Command {
|
||||
Arg::new(options::HELP)
|
||||
.long(options::HELP)
|
||||
.help(translate!("od-help-help"))
|
||||
.action(ArgAction::Help)
|
||||
.action(ArgAction::Help),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::ADDRESS_RADIX)
|
||||
@@ -297,10 +320,8 @@ pub fn uu_app() -> Command {
|
||||
Arg::new(options::STRINGS)
|
||||
.short('S')
|
||||
.long(options::STRINGS)
|
||||
.help(
|
||||
"NotImplemented: output strings of at least BYTES graphic chars. 3 is assumed when \
|
||||
BYTES is not specified.",
|
||||
)
|
||||
.help(translate!("od-help-strings"))
|
||||
.num_args(0..=1)
|
||||
.default_missing_value("3")
|
||||
.value_name("BYTES"),
|
||||
)
|
||||
@@ -379,8 +400,8 @@ pub fn uu_app() -> Command {
|
||||
.arg(
|
||||
Arg::new("O")
|
||||
.short('O')
|
||||
.help("octal 4-byte units")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help(translate!("od-help-capital-o"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("s")
|
||||
@@ -532,6 +553,112 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract and display printable strings from input (od -S option)
|
||||
fn extract_strings_from_input(
|
||||
input_strings: &[String],
|
||||
skip_bytes: u64,
|
||||
read_bytes: Option<u64>,
|
||||
min_length: usize,
|
||||
radix: Radix,
|
||||
) -> UResult<()> {
|
||||
let inputs = input_strings
|
||||
.iter()
|
||||
.map(|w| match w as &str {
|
||||
"-" => InputSource::Stdin,
|
||||
x => InputSource::FileName(x),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut mf = MultifileReader::new(inputs);
|
||||
|
||||
// Apply skip_bytes by reading and discarding
|
||||
let mut skipped = 0u64;
|
||||
while skipped < skip_bytes {
|
||||
let to_skip = std::cmp::min(8192, skip_bytes - skipped);
|
||||
let mut skip_buf = vec![0u8; to_skip as usize];
|
||||
match mf.read(&mut skip_buf) {
|
||||
Ok(0) => break, // EOF reached
|
||||
Ok(n) => skipped += n as u64,
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to format and print a string
|
||||
let print_string = |offset: u64, string: &[u8]| {
|
||||
let string_content = String::from_utf8_lossy(string);
|
||||
match radix {
|
||||
Radix::NoPrefix => println!("{string_content}"),
|
||||
Radix::Decimal => println!("{offset:07} {string_content}"),
|
||||
Radix::Hexadecimal => println!("{offset:07x} {string_content}"),
|
||||
Radix::Octal => println!("{offset:07o} {string_content}"),
|
||||
}
|
||||
};
|
||||
|
||||
let mut current_string = Vec::new();
|
||||
let mut string_start_offset = 0u64;
|
||||
let mut current_offset = skip_bytes;
|
||||
let mut bytes_read = 0u64;
|
||||
let mut buf = [0u8; 1];
|
||||
|
||||
loop {
|
||||
// Check if we've reached the read_bytes limit
|
||||
if let Some(limit) = read_bytes {
|
||||
if bytes_read >= limit {
|
||||
// Special case: when -N limit is reached with a pending string
|
||||
// that meets min_length, output it even without null terminator
|
||||
if current_string.len() >= min_length {
|
||||
print_string(string_start_offset, ¤t_string);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Read one byte at a time
|
||||
match mf.read(&mut buf) {
|
||||
Ok(0) => break, // EOF
|
||||
Ok(_) => {
|
||||
bytes_read += 1;
|
||||
let byte = buf[0];
|
||||
|
||||
// Check if it's a printable character (including space)
|
||||
if (0x20..=0x7E).contains(&byte) {
|
||||
if current_string.is_empty() {
|
||||
string_start_offset = current_offset;
|
||||
}
|
||||
current_string.push(byte);
|
||||
} else {
|
||||
// Either null terminator or non-printable character
|
||||
if byte == 0 && current_string.len() >= min_length {
|
||||
// Null terminator found with valid string
|
||||
print_string(string_start_offset, ¤t_string);
|
||||
}
|
||||
current_string.clear();
|
||||
}
|
||||
|
||||
current_offset += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
// Note: GNU od does not output unterminated strings at EOF
|
||||
// Strings must be null-terminated to be output
|
||||
if mf.has_error() {
|
||||
show_error!("{}", e);
|
||||
return Err(1.into());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GNU od doesn't output an offset when strings mode finds no valid strings
|
||||
// This includes cases with only unterminated or too-short strings
|
||||
|
||||
if mf.has_error() {
|
||||
Err(1.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Outputs a single line of input, into one or more lines human readable output.
|
||||
fn print_bytes(prefix: &str, input_decoder: &MemoryDecoder, output_info: &OutputInfo) {
|
||||
let mut first = true; // First line of a multi-format raster.
|
||||
|
||||
@@ -992,3 +992,68 @@ fn test_od_options_after_filename() {
|
||||
.no_stderr()
|
||||
.stdout_is(" 1c68 fdbb\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_od_strings_option() {
|
||||
// Test -S option: output strings of at least N graphic chars
|
||||
|
||||
// Test -S0: output all null-terminated strings regardless of length
|
||||
new_ucmd!()
|
||||
.arg("-S0")
|
||||
.pipe_in(b"hello\x00world\x00")
|
||||
.succeeds()
|
||||
.stdout_is("0000000 hello\n0000006 world\n");
|
||||
|
||||
// Test -S0 with single character strings
|
||||
new_ucmd!()
|
||||
.arg("-S0")
|
||||
.pipe_in(b"a\x00b\x00cd\x00")
|
||||
.succeeds()
|
||||
.stdout_is("0000000 a\n0000002 b\n0000004 cd\n");
|
||||
|
||||
// Test with null-terminated strings
|
||||
new_ucmd!()
|
||||
.arg("-S3")
|
||||
.pipe_in(b"\x01hello\x00world\x00ab\x00")
|
||||
.succeeds()
|
||||
.stdout_is("0000001 hello\n0000007 world\n");
|
||||
|
||||
// Test with -S10 to show only strings with minimum length
|
||||
new_ucmd!()
|
||||
.arg("-S10")
|
||||
.pipe_in(b"\x01 \x00 \x00")
|
||||
.succeeds()
|
||||
.stdout_is("0000001 \n0000014 \n");
|
||||
|
||||
// Test with unterminated string at EOF (should not output)
|
||||
new_ucmd!()
|
||||
.arg("-S10")
|
||||
.pipe_in(b" ")
|
||||
.succeeds()
|
||||
.stdout_is("");
|
||||
|
||||
// Test with -N limit and pending string (should output even without null)
|
||||
let expected = "0000000 \n";
|
||||
new_ucmd!()
|
||||
.arg("-N2")
|
||||
.arg("-S1")
|
||||
.pipe_in(" ")
|
||||
.succeeds()
|
||||
.stdout_is(expected);
|
||||
|
||||
// Test with -N limit and string too short
|
||||
new_ucmd!()
|
||||
.arg("-N11")
|
||||
.arg("-S11")
|
||||
.pipe_in(b" \x00")
|
||||
.succeeds()
|
||||
.stdout_is("");
|
||||
|
||||
// Test with no address prefix (-An)
|
||||
new_ucmd!()
|
||||
.arg("-S3")
|
||||
.arg("-An")
|
||||
.pipe_in(b"hello\x00world\x00")
|
||||
.succeeds()
|
||||
.stdout_is("hello\nworld\n");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user