Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions src/uu/od/src/multifile_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ pub struct MultifileReader<'a> {
ni: Vec<InputSource<'a>>,
curr_file: Option<CurrentReader>,
any_err: bool,
any_opened: bool,
file_name: Option<&'a OsStr>,
}

Expand All @@ -69,12 +70,19 @@ impl MultifileReader<'_> {
ni: fnames,
curr_file: None, // normally this means done; call next_file()
any_err: false,
any_opened: false,
file_name: None,
};
mf.next_file();
mf
}

/// Whether at least one input was opened successfully, even if reading it
/// failed afterwards.
pub fn has_input(&self) -> bool {
self.any_opened
}

fn next_file(&mut self) {
// loop retries with subsequent files if err - normally 'loops' once
loop {
Expand Down Expand Up @@ -104,6 +112,7 @@ impl MultifileReader<'_> {
{
self.curr_file = Some(CurrentReader::Stdin(io::stdin()));
}
self.any_opened = true;
break;
}
InputSource::FileName(fname) => {
Expand All @@ -113,6 +122,7 @@ impl MultifileReader<'_> {
// No need to wrap `f` in a BufReader - buffered reading is taken care
// of elsewhere.
self.curr_file = Some(CurrentReader::File(f));
self.any_opened = true;
break;
}
Err(e) => {
Expand Down
18 changes: 14 additions & 4 deletions src/uu/od/src/od.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let mut input_offset =
InputOffset::new(od_options.radix, od_options.skip_bytes, od_options.label);

let mut input = open_input_peek_reader(
let (mut input, input_was_opened) = open_input_peek_reader(
&od_options.input_strings,
od_options.skip_bytes,
od_options.read_bytes,
Expand All @@ -305,6 +305,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
&mut input_offset,
&mut input_decoder,
&output_info,
input_was_opened,
&mut out,
)
}
Expand Down Expand Up @@ -528,6 +529,7 @@ fn odfunc<I, W>(
input_offset: &mut InputOffset,
input_decoder: &mut InputDecoder<I>,
output_info: &OutputInfo,
input_was_opened: bool,
writer: &mut W,
) -> UResult<()>
where
Expand All @@ -546,7 +548,11 @@ where
let length = memory_decoder.length();

if length == 0 {
if !input_decoder.has_error() {
// GNU prints the final offset for an input it managed to open,
// even if reading it failed (e.g. `od directory`), but stays
// silent when no input could be opened at all (e.g.
// `od missing-file`).
if input_was_opened || !input_decoder.has_error() {
input_offset.write_final_offset(writer)?;
}
break;
Expand Down Expand Up @@ -811,19 +817,23 @@ fn open_input_peek_reader(
input_strings: &[OsString],
skip_bytes: u64,
read_bytes: Option<u64>,
) -> UResult<PeekReader<BufReader<PartialReader<MultifileReader<'_>>>>> {
) -> UResult<(
PeekReader<BufReader<PartialReader<MultifileReader<'_>>>>,
bool,
)> {
// should return "impl PeekRead + Read + HasError" when supported in (stable) rust
let inputs = map_input_strings(input_strings);
let mut mf = MultifileReader::new(inputs);
mf.skip(skip_bytes)
.map_err(|e| USimpleError::new(1, e.to_string()))?;
let input_was_opened = mf.has_input();
let pr = PartialReader::new(mf, read_bytes);
// Add a BufReader over the top of the PartialReader. This will have the
// effect of generating buffered reads to files/stdin, but since these reads
// go through MultifileReader (which limits the maximum number of bytes read)
// we won't ever read more bytes than were specified with the `-N` flag.
let buf_pr = BufReader::new(pr);
Ok(PeekReader::new(buf_pr))
Ok((PeekReader::new(buf_pr), input_was_opened))
}

impl<R: HasError> HasError for BufReader<R> {
Expand Down
39 changes: 39 additions & 0 deletions tests/by-util/test_od.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1726,3 +1726,42 @@ fn test_od_zero_byte_type_size() {
.succeeds()
.stdout_is("0000000 64636261\n0000004\n");
}

#[test]
fn test_final_offset_printed_when_an_input_fails() {
Comment thread
Logic769 marked this conversation as resolved.
Resolved
let (at, mut ucmd) = at_and_ucmd!();
at.write("a.txt", "a\n");
at.mkdir("d");

// The message for an unreadable input differs between platforms (WASI says
// "Bad file descriptor"), so only the prefix is checked here.
ucmd.args(&["a.txt", "d"])
.fails_with_code(1)
.stdout_is("0000000 005141\n0000002\n")
.stderr_contains("od: d:");
}

#[test]
#[cfg(not(windows))]
#[cfg_attr(wasi_runner, ignore)]
fn test_final_offset_printed_for_directory() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("d");

// A directory can be opened but not read, so GNU still prints the final
// offset even though no data was decoded.
ucmd.arg("d")
.fails_with_code(1)
.stdout_is("0000000\n")
.stderr_contains("od: d:");
}

#[test]
fn test_no_final_offset_when_no_input_can_be_opened() {
let (_at, mut ucmd) = at_and_ucmd!();

ucmd.arg("missing")
.fails_with_code(1)
.no_stdout()
.stderr_contains("od: missing:");
}
Loading