diff --git a/src/uu/od/src/input_offset.rs b/src/uu/od/src/input_offset.rs index 9cef72e97..32c4098cf 100644 --- a/src/uu/od/src/input_offset.rs +++ b/src/uu/od/src/input_offset.rs @@ -2,6 +2,8 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use std::io::{self, Write}; + #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Radix { Decimal, @@ -60,10 +62,11 @@ impl InputOffset { /// Prints the byte offset followed by a newline, or nothing at all if /// both `Radix::NoPrefix` was set and no label (--traditional) is used. - pub fn print_final_offset(&self) { + pub fn print_final_offset(&self, out: &mut W) -> io::Result<()> { if self.radix != Radix::NoPrefix || self.label.is_some() { - println!("{}", self.format_byte_offset()); + writeln!(out, "{}", self.format_byte_offset())?; } + Ok(()) } } diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index e8f9841b1..e6cac1233 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -26,7 +26,7 @@ mod prn_int; use std::cmp; use std::fmt::Write; -use std::io::{BufReader, Read}; +use std::io::{BufReader, Read, Write as IoWrite}; use crate::byteorder_io::ByteOrder; use crate::formatter_item_info::FormatWriter; @@ -246,6 +246,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let clap_matches = uucore::clap_localization::handle_clap_result(clap_opts, &args)?; let od_options = OdOptions::new(&clap_matches, &args)?; + let mut out = std::io::stdout().lock(); // Check if we're in strings mode if let Some(min_length) = od_options.string_min_length { @@ -255,6 +256,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { od_options.read_bytes, min_length, od_options.radix, + &mut out, ) } else { let mut input_offset = @@ -278,7 +280,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { od_options.output_duplicates, ); - odfunc(&mut input_offset, &mut input_decoder, &output_info) + odfunc(&mut input_offset, &mut input_decoder, &output_info, &mut out) } } @@ -490,13 +492,15 @@ pub fn uu_app() -> Command { } /// Loops through the input line by line, calling `print_bytes` to take care of the output. -fn odfunc( +fn odfunc( input_offset: &mut InputOffset, input_decoder: &mut InputDecoder, output_info: &OutputInfo, + out: &mut W, ) -> UResult<()> where I: PeekRead + HasError, + W: IoWrite, { let mut duplicate_line = false; let mut previous_bytes: Vec = Vec::new(); @@ -511,7 +515,7 @@ where if length == 0 { if !input_decoder.has_error() { - input_offset.print_final_offset(); + input_offset.print_final_offset(out)?; } break; } @@ -533,7 +537,7 @@ where { if !duplicate_line { duplicate_line = true; - println!("*"); + writeln!(out, "*")?; } } else { duplicate_line = false; @@ -546,14 +550,15 @@ where &input_offset.format_byte_offset(), &memory_decoder, output_info, - ); + out, + )?; } input_offset.increase_position(length as u64); } Err(e) => { show_error!("{e}"); - input_offset.print_final_offset(); + input_offset.print_final_offset(out)?; return Err(1.into()); } } @@ -573,6 +578,7 @@ fn extract_strings_from_input( read_bytes: Option, min_length: usize, radix: Radix, + out: &mut impl IoWrite, ) -> UResult<()> { let inputs = map_input_strings(input_strings); let mut mf = MultifileReader::new(inputs); @@ -590,13 +596,13 @@ fn extract_strings_from_input( } // Helper function to format and print a string - let print_string = |offset: u64, string: &[u8]| { + let mut print_string = |offset: u64, string: &[u8]| -> std::io::Result<()> { 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}"), + Radix::NoPrefix => writeln!(out, "{string_content}"), + Radix::Decimal => writeln!(out, "{offset:07} {string_content}"), + Radix::Hexadecimal => writeln!(out, "{offset:07x} {string_content}"), + Radix::Octal => writeln!(out, "{offset:07o} {string_content}"), } }; @@ -613,7 +619,7 @@ fn extract_strings_from_input( // 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); + print_string(string_start_offset, ¤t_string)?; } break; } @@ -636,7 +642,7 @@ fn extract_strings_from_input( // 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); + print_string(string_start_offset, ¤t_string)?; } current_string.clear(); } @@ -666,7 +672,12 @@ fn extract_strings_from_input( } /// 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) { +fn print_bytes( + prefix: &str, + input_decoder: &MemoryDecoder, + output_info: &OutputInfo, + out: &mut W, +) -> std::io::Result<()> { let mut first = true; // First line of a multi-format raster. for f in output_info.spaced_formatters_iter() { let mut output_text = String::new(); @@ -720,16 +731,17 @@ fn print_bytes(prefix: &str, input_decoder: &MemoryDecoder, output_info: &Output } if first { - print!("{prefix}"); // print offset + write!(out, "{prefix}")?; // print offset // if printing in multiple formats offset is printed only once first = false; } else { // this takes the space of the file offset on subsequent // lines of multi-format rasters. - print!("{:>width$}", "", width = prefix.chars().count()); + write!(out, "{:>width$}", "", width = prefix.chars().count())?; } - println!("{output_text}"); + writeln!(out, "{output_text}")?; } + Ok(()) } /// Helper function to convert input strings to InputSource diff --git a/tests/by-util/test_od.rs b/tests/by-util/test_od.rs index fea019e3a..21cef8404 100644 --- a/tests/by-util/test_od.rs +++ b/tests/by-util/test_od.rs @@ -1259,3 +1259,20 @@ fn test_od_eintr_handling() { .no_stderr() .stdout_contains("e"); // Should contain 'e' from "ello" } + +// Regression test: od should handle write errors to /dev/full without aborting. +#[test] +#[cfg(target_os = "linux")] +fn test_write_error_dev_full() { + use std::fs::File; + + let dev_full = File::create("/dev/full").expect("Failed to open /dev/full"); + + new_ucmd!() + .arg("-An") + .pipe_in("abcd") + .set_stdout(dev_full) + .fails() + .code_is(1) + .stderr_contains("No space left on device"); +}