Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0f75117
feat: Add support for long double floating-point numbers and refine g…
mattsu2020 Nov 19, 2025
a74b99b
feat: Enhance `od` error reporting for file I/O, width, and offset pa…
mattsu2020 Nov 19, 2025
2ec9a6a
feat: Improve long double parsing by converting f128 to f64, enhance …
mattsu2020 Nov 19, 2025
4de5769
style: Apply minor formatting adjustments across the `od` module.
mattsu2020 Nov 19, 2025
1f5ec84
refactor: simplify float formatting logic and update string handling …
mattsu2020 Nov 19, 2025
69dbb11
fix: Correct float formatting logic to use decimal for numbers within…
mattsu2020 Nov 19, 2025
417fd85
refactor(test): use helper function in test_calculate_alignment
mattsu2020 Nov 19, 2025
de9df12
feat(cspell): add ERANGE to jargon wordlist
mattsu2020 Nov 19, 2025
b7d34f3
feat(od): improve width error handling and subnormal float output
mattsu2020 Nov 19, 2025
674b9f0
refactor(od): simplify format_item_bf16 by removing redundant variable
mattsu2020 Nov 19, 2025
55d649e
fix(od): standardize option names in error messages
mattsu2020 Nov 20, 2025
ea5417b
refactor: condense format! macro in format_item_bf16 for readability
mattsu2020 Nov 20, 2025
8b83715
fix(od): add external quoting for filenames in error messages
mattsu2020 Nov 20, 2025
409d0a8
refactor(od): Rename f128_to_f64 to u128_to_f64 for clarity
mattsu2020 Nov 27, 2025
59fe994
refactor(od): simplify error handling in OdOptions using combinators
mattsu2020 Nov 27, 2025
9e79760
Merge branch 'main' into od_compatibility
mattsu2020 Nov 27, 2025
457abea
Update src/uu/od/src/od.rs
mattsu2020 Nov 27, 2025
af5cc17
Update src/uu/od/src/od.rs
mattsu2020 Nov 27, 2025
a49a88e
Update src/uu/od/src/parse_inputs.rs
mattsu2020 Nov 27, 2025
6056548
Update src/uu/od/src/od.rs
mattsu2020 Nov 27, 2025
28e3baa
Update src/uu/od/src/parse_inputs.rs
mattsu2020 Nov 27, 2025
827d1ae
Update src/uu/od/src/parse_inputs.rs
mattsu2020 Nov 27, 2025
68946bd
refactor(od): remove leaking from translated error messages in parse_…
mattsu2020 Nov 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/cspell.dictionaries/jargon.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ duplicative
dsync
endianness
enqueue
ERANGE
errored
executable
executables
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/uu/od/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ clap = { workspace = true }
half = { workspace = true }
uucore = { workspace = true, features = ["parser"] }
fluent = { workspace = true }
libc.workspace = true

[[bin]]
name = "od"
Expand Down
7 changes: 4 additions & 3 deletions src/uu/od/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ od-error-invalid-offset = invalid offset: {$offset}
od-error-invalid-label = invalid label: {$label}
od-error-too-many-inputs = too many inputs after --traditional: {$input}
od-error-parse-failed = parse failed
od-error-invalid-suffix = invalid suffix in --{$option} argument {$value}
od-error-invalid-argument = invalid --{$option} argument {$value}
od-error-argument-too-large = --{$option} argument {$value} too large
od-error-overflow = Numerical result out of range
od-error-invalid-suffix = invalid suffix in {$option} argument {$value}
od-error-invalid-argument = invalid {$option} argument {$value}
od-error-argument-too-large = {$option} argument {$value} too large
od-error-skip-past-end = tried to skip past end of input

# Help messages
Expand Down
6 changes: 3 additions & 3 deletions src/uu/od/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ od-error-invalid-offset = décalage invalide : {$offset}
od-error-invalid-label = étiquette invalide : {$label}
od-error-too-many-inputs = trop d'entrées après --traditional : {$input}
od-error-parse-failed = échec de l'analyse
od-error-invalid-suffix = suffixe invalide dans l'argument --{$option} {$value}
od-error-invalid-argument = argument --{$option} invalide {$value}
od-error-argument-too-large = argument --{$option} {$value} trop grand
od-error-invalid-suffix = suffixe invalide dans l'argument {$option} {$value}
od-error-invalid-argument = argument {$option} invalide {$value}
od-error-argument-too-large = argument {$option} {$value} trop grand
od-error-skip-past-end = tentative d'ignorer au-delà de la fin de l'entrée

# Messages d'aide
Expand Down
3 changes: 2 additions & 1 deletion src/uu/od/src/byteorder_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ gen_byte_order_ops! {
read_i32, write_i32 -> i32,
read_i64, write_i64 -> i64,
read_f32, write_f32 -> f32,
read_f64, write_f64 -> f64
read_f64, write_f64 -> f64,
read_u128, write_u128 -> u128
}
5 changes: 5 additions & 0 deletions src/uu/od/src/formatter_item_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use std::fmt;
pub enum FormatWriter {
IntWriter(fn(u64) -> String),
FloatWriter(fn(f64) -> String),
LongDoubleWriter(fn(f64) -> String), // On most platforms, long double is f64 or emulated
BFloatWriter(fn(f64) -> String),
MultibyteWriter(fn(&[u8]) -> String),
}
Expand All @@ -27,6 +28,10 @@ impl fmt::Debug for FormatWriter {
f.write_str("FloatWriter:")?;
fmt::Pointer::fmt(p, f)
}
Self::LongDoubleWriter(ref p) => {
f.write_str("LongDoubleWriter:")?;
fmt::Pointer::fmt(p, f)
}
Self::BFloatWriter(ref p) => {
f.write_str("BFloatWriter:")?;
fmt::Pointer::fmt(p, f)
Expand Down
57 changes: 56 additions & 1 deletion src/uu/od/src/input_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore bfloat multifile
// spell-checker:ignore bfloat multifile mant

use half::{bf16, f16};
use std::io;
Expand Down Expand Up @@ -165,6 +165,61 @@ impl MemoryDecoder<'_> {
let val = f32::from(bf16::from_bits(bits));
f64::from(val)
}

/// Returns a long double from the internal buffer at position `start`.
/// We read 16 bytes as u128 (respecting endianness) and convert to f64.
/// This ensures that endianness swapping works correctly even if we lose precision.
pub fn read_long_double(&self, start: usize) -> f64 {
let bits = self.byte_order.read_u128(&self.data[start..start + 16]);
f128_to_f64(bits)
}
}

fn f128_to_f64(u: u128) -> f64 {
let sign = (u >> 127) as u64;
let exp = ((u >> 112) & 0x7FFF) as u64;
let mant = u & ((1 << 112) - 1);

if exp == 0x7FFF {
// Infinity or NaN
if mant == 0 {
if sign == 0 {
f64::INFINITY
} else {
f64::NEG_INFINITY
}
} else {
f64::NAN
}
} else if exp == 0 {
// Subnormal or zero
if mant == 0 {
if sign == 0 { 0.0 } else { -0.0 }
} else {
// Subnormal f128 is too small for f64, flush to zero
if sign == 0 { 0.0 } else { -0.0 }
}
} else {
// Normal
let new_exp = exp as i64 - 16383 + 1023;
if new_exp >= 2047 {
// Overflow to infinity
if sign == 0 {
f64::INFINITY
} else {
f64::NEG_INFINITY
}
} else if new_exp <= 0 {
// Underflow to zero
if sign == 0 { 0.0 } else { -0.0 }
} else {
// Normal f64
// Mantissa: take top 52 bits of 112-bit mantissa
let new_mant = (mant >> (112 - 52)) as u64;
let bits = (sign << 63) | ((new_exp as u64) << 52) | new_mant;
f64::from_bits(bits)
}
}
}

#[cfg(test)]
Expand Down
8 changes: 7 additions & 1 deletion src/uu/od/src/multifile_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,13 @@ impl MultifileReader<'_> {
// print an error at the time that the file is needed,
// then move to the next file.
// This matches the behavior of the original `od`
show_error!("{}: {e}", fname.maybe_quote());
// Format error without OS error code to match GNU od
let error_msg = match e.kind() {
io::ErrorKind::NotFound => "No such file or directory",
io::ErrorKind::PermissionDenied => "Permission denied",
_ => "I/O error",
};
show_error!("{}: {}", fname.maybe_quote().external(true), error_msg);
self.any_err = true;
}
}
Expand Down
81 changes: 69 additions & 12 deletions src/uu/od/src/od.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,19 @@ struct OdOptions {
}

/// Helper function to parse bytes with error handling
fn parse_bytes_option(matches: &ArgMatches, option_name: &str) -> UResult<Option<u64>> {
fn parse_bytes_option(
matches: &ArgMatches,
args: &[String],
option_name: &str,
short: Option<char>,
) -> UResult<Option<u64>> {
match matches.get_one::<String>(option_name) {
None => Ok(None),
Some(s) => match parse_number_of_bytes(s) {
Ok(n) => Ok(Some(n)),
Err(e) => Err(USimpleError::new(
1,
format_error_message(&e, s, option_name),
format_error_message(&e, s, &option_display_name(args, option_name, short)),
)),
},
}
Expand All @@ -110,12 +115,12 @@ impl OdOptions {
ByteOrder::Native
};

let mut skip_bytes = parse_bytes_option(matches, options::SKIP_BYTES)?.unwrap_or(0);
let mut skip_bytes =
parse_bytes_option(matches, args, options::SKIP_BYTES, Some('j'))?.unwrap_or(0);

let mut label: Option<u64> = None;

let parsed_input = parse_inputs(matches)
.map_err(|e| USimpleError::new(1, translate!("od-error-invalid-inputs", "msg" => e)))?;
let parsed_input = parse_inputs(matches).map_err(|e| USimpleError::new(1, e))?;
let input_strings = match parsed_input {
CommandLineInputs::FileNames(v) => v,
CommandLineInputs::FileAndOffset((f, s, l)) => {
Expand All @@ -131,16 +136,36 @@ impl OdOptions {
None => 16,
Some(s) => {
if matches.value_source(options::WIDTH) == Some(ValueSource::CommandLine) {
match parse_number_of_bytes(s) {
Ok(n) => usize::try_from(n)
.map_err(|_| USimpleError::new(1, format!("‘{s}‘ is too large")))?,
let width_display = option_display_name(args, options::WIDTH, Some('w'));
let parsed = match parse_number_of_bytes(s) {
Ok(n) => n,
Err(e) => {
return Err(USimpleError::new(
1,
format_error_message(&e, s, options::WIDTH),
format_error_message(&e, s, &width_display),
));
}
};
if parsed == 0 {
return Err(USimpleError::new(
1,
translate!(
"od-error-invalid-argument",
"option" => width_display.clone(),
"value" => s.quote()
),
));
}
usize::try_from(parsed).map_err(|_| {
USimpleError::new(
1,
translate!(
"od-error-argument-too-large",
"option" => width_display.clone(),
"value" => s.quote()
),
)
})?
} else {
16
}
Expand All @@ -160,9 +185,9 @@ impl OdOptions {

let output_duplicates = matches.get_flag(options::OUTPUT_DUPLICATES);

let read_bytes = parse_bytes_option(matches, options::READ_BYTES)?;
let read_bytes = parse_bytes_option(matches, args, options::READ_BYTES, Some('N'))?;

let string_min_length = match parse_bytes_option(matches, options::STRINGS)? {
let string_min_length = match parse_bytes_option(matches, args, options::STRINGS, Some('S'))? {
None => None,
Some(n) => Some(usize::try_from(n).map_err(|_| {
USimpleError::new(
Expand Down Expand Up @@ -491,7 +516,9 @@ where
let length = memory_decoder.length();

if length == 0 {
input_offset.print_final_offset();
if !input_decoder.has_error() {
input_offset.print_final_offset();
}
break;
}

Expand Down Expand Up @@ -669,6 +696,10 @@ fn print_bytes(prefix: &str, input_decoder: &MemoryDecoder, output_info: &Output
let p = input_decoder.read_float(b, f.formatter_item_info.byte_size);
output_text.push_str(&func(p));
}
FormatWriter::LongDoubleWriter(func) => {
let p = input_decoder.read_long_double(b);
output_text.push_str(&func(p));
}
FormatWriter::BFloatWriter(func) => {
let p = input_decoder.read_bfloat(b);
output_text.push_str(&func(p));
Expand Down Expand Up @@ -745,6 +776,32 @@ impl<R: HasError> HasError for BufReader<R> {
}
}

fn option_display_name(args: &[String], option_name: &str, short: Option<char>) -> String {
let long_form = format!("--{option_name}");
let long_form_with_eq = format!("{long_form}=");
if let Some(short_char) = short {
let short_form = format!("-{short_char}");
for arg in args.iter().skip(1) {
if !arg.starts_with("--") && arg.starts_with(&short_form) {
return short_form.clone();
}
}
for arg in args.iter().skip(1) {
if arg == &long_form || arg.starts_with(&long_form_with_eq) {
return long_form.clone();
}
}
short_form
} else {
for arg in args.iter().skip(1) {
if arg == &long_form || arg.starts_with(&long_form_with_eq) {
return long_form.clone();
}
}
long_form
}
}

fn format_error_message(error: &ParseSizeError, s: &str, option: &str) -> String {
// NOTE:
// GNU's od echos affected flag, -N or --read-bytes (-j or --skip-bytes, etc.), depending user's selection
Expand Down
Loading
Loading