Skip to content

Commit f7d6395

Browse files
committed
head: preserve read and stdout error context
1 parent 31afe1d commit f7d6395

2 files changed

Lines changed: 72 additions & 15 deletions

File tree

src/uu/head/src/head.rs

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,16 @@ fn print_n_bytes(input: impl Read, n: u64) -> io::Result<u64> {
193193
Ok(bytes_written)
194194
}
195195

196-
fn print_n_lines(input: &mut impl io::BufRead, n: u64, separator: u8) -> io::Result<u64> {
196+
enum HeadFileError {
197+
Read(io::Error),
198+
WriteStdout(io::Error),
199+
}
200+
201+
fn print_n_lines(
202+
input: &mut impl io::BufRead,
203+
n: u64,
204+
separator: u8,
205+
) -> Result<u64, HeadFileError> {
197206
// Read the first `n` lines from the `input` reader.
198207
let mut reader = take_lines(input, n, separator);
199208

@@ -205,21 +214,27 @@ fn print_n_lines(input: &mut impl io::BufRead, n: u64, separator: u8) -> io::Res
205214
let mut bytes_written = 0;
206215
let mut buf = [0; BUF_SIZE];
207216
loop {
208-
let n = match reader.read(&mut buf) {
209-
Ok(0) => break,
210-
Ok(n) => n,
211-
Err(e) => return Err(e),
212-
};
217+
let bytes_read = reader.read(&mut buf).map_err(HeadFileError::Read)?;
218+
219+
if bytes_read == 0 {
220+
break;
221+
}
213222

214-
writer.write_all(&buf[..n]).map_err(wrap_in_stdout_error)?;
223+
writer
224+
.write_all(&buf[..bytes_read])
225+
.map_err(wrap_in_stdout_error)
226+
.map_err(HeadFileError::WriteStdout)?;
215227

216-
bytes_written += n as u64;
228+
bytes_written += bytes_read as u64;
217229
}
218230

219231
// Make sure we finish writing everything to the target before
220232
// exiting. Otherwise, when Rust is implicitly flushing, any
221233
// error will be silently ignored.
222-
writer.flush().map_err(wrap_in_stdout_error)?;
234+
writer
235+
.flush()
236+
.map_err(wrap_in_stdout_error)
237+
.map_err(HeadFileError::WriteStdout)?;
223238

224239
Ok(bytes_written)
225240
}
@@ -400,15 +415,17 @@ fn head_backwards_on_seekable_file(input: &mut File, options: &HeadOptions) -> i
400415
}
401416
}
402417

403-
fn head_file(input: &mut File, options: &HeadOptions) -> io::Result<u64> {
418+
fn head_file(input: &mut File, options: &HeadOptions) -> Result<u64, HeadFileError> {
404419
match options.mode {
405-
Mode::FirstBytes(n) => print_n_bytes(input, n),
420+
Mode::FirstBytes(n) => print_n_bytes(input, n).map_err(HeadFileError::WriteStdout),
406421
Mode::FirstLines(n) => print_n_lines(
407422
&mut io::BufReader::with_capacity(BUF_SIZE, input),
408423
n,
409424
options.line_ending.into(),
410425
),
411-
Mode::AllButLastBytes(_) | Mode::AllButLastLines(_) => head_backwards_file(input, options),
426+
Mode::AllButLastBytes(_) | Mode::AllButLastLines(_) => {
427+
head_backwards_file(input, options).map_err(HeadFileError::WriteStdout)
428+
}
412429
}
413430
}
414431

@@ -436,10 +453,30 @@ fn uu_head(options: &HeadOptions) -> UResult<()> {
436453
// last byte read so that any tools that parse the remainder of
437454
// the stdin stream read from the correct place.
438455

439-
let bytes_read = head_file(&mut stdin_file, options)?;
456+
let bytes_read = match head_file(&mut stdin_file, options) {
457+
Ok(bytes_read) => bytes_read,
458+
Err(HeadFileError::Read(err)) => {
459+
return Err(HeadError::Io {
460+
name: "standard input".into(),
461+
err,
462+
}
463+
.into());
464+
}
465+
Err(HeadFileError::WriteStdout(err)) => return Err(err.into()),
466+
};
440467
stdin_file.seek(SeekFrom::Start(current_pos + bytes_read))?;
441468
} else {
442-
let _bytes_read = head_file(&mut stdin_file, options)?;
469+
match head_file(&mut stdin_file, options) {
470+
Ok(_) => {}
471+
Err(HeadFileError::Read(err)) => {
472+
return Err(HeadError::Io {
473+
name: "standard input".into(),
474+
err,
475+
}
476+
.into());
477+
}
478+
Err(HeadFileError::WriteStdout(err)) => return Err(err.into()),
479+
}
443480
}
444481
}
445482

@@ -507,13 +544,14 @@ fn uu_head(options: &HeadOptions) -> UResult<()> {
507544
};
508545
match head_file(&mut file_handle, options) {
509546
Ok(_) => {}
510-
Err(err) => {
547+
Err(HeadFileError::Read(err)) => {
511548
show!(HeadError::Io {
512549
name: file.into(),
513550
err
514551
});
515552
continue;
516553
}
554+
Err(HeadFileError::WriteStdout(err)) => return Err(err.into()),
517555
}
518556
Ok(())
519557
};

tests/by-util/test_head.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,25 @@ fn test_write_to_dev_full() {
917917
}
918918
}
919919

920+
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
921+
#[test]
922+
fn test_write_to_dev_full_with_named_file() {
923+
use std::fs::OpenOptions;
924+
925+
let ts = TestScenario::new(util_name!());
926+
let at = &ts.fixtures;
927+
928+
at.write("input", "hello\nworld\n");
929+
930+
let dev_full = OpenOptions::new().write(true).open("/dev/full").unwrap();
931+
932+
ts.ucmd()
933+
.arg("input")
934+
.set_stdout(dev_full)
935+
.fails()
936+
.stderr_is("head: error writing 'standard output': No space left on device\n");
937+
}
938+
920939
#[test]
921940
#[cfg(target_os = "linux")]
922941
#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")]

0 commit comments

Comments
 (0)