From eeef8290df9e5ea24eb8ca4ae5fb3e0fc40576b3 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 16 May 2021 21:21:20 -0400 Subject: [PATCH] head: display errors for each input file Change the behavior of `head` to display an error for each problematic file, instead of displaying an error message for the first problematic file and terminating immediately at that point. This change now matches the behavior of GNU `head`. Before this commit, the first error caused the program to terminate immediately: $ head a b c head: error: head: cannot open 'a' for reading: No such file or directory After this commit: $ head a b c head: cannot open 'a' for reading: No such file or directory head: cannot open 'b' for reading: No such file or directory head: cannot open 'c' for reading: No such file or directory --- src/uu/head/src/head.rs | 73 ++++++++++++++++++-------------------- tests/by-util/test_head.rs | 12 +++++++ tests/common/util.rs | 7 +++- 3 files changed, 52 insertions(+), 40 deletions(-) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index faaeedd3f..0c8b3bc88 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -2,7 +2,7 @@ use clap::{App, Arg}; use std::convert::TryFrom; use std::ffi::OsString; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; -use uucore::{crash, executable, show_error}; +use uucore::{crash, executable, show_error, show_error_custom_description}; const EXIT_FAILURE: i32 = 1; const EXIT_SUCCESS: i32 = 0; @@ -400,7 +400,8 @@ fn head_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Resul } } -fn uu_head(options: &HeadOptions) { +fn uu_head(options: &HeadOptions) -> Result<(), u32> { + let mut error_count = 0; let mut first = true; for fname in &options.files { let res = match fname.as_str() { @@ -433,30 +434,22 @@ fn uu_head(options: &HeadOptions) { name => { let mut file = match std::fs::File::open(name) { Ok(f) => f, - Err(err) => match err.kind() { - ErrorKind::NotFound => { - crash!( - EXIT_FAILURE, - "head: cannot open '{}' for reading: No such file or directory", - name - ); + Err(err) => { + let prefix = format!("cannot open '{}' for reading", name); + match err.kind() { + ErrorKind::NotFound => { + show_error_custom_description!(prefix, "No such file or directory"); + } + ErrorKind::PermissionDenied => { + show_error_custom_description!(prefix, "Permission denied"); + } + _ => { + show_error_custom_description!(prefix, "{}", err); + } } - ErrorKind::PermissionDenied => { - crash!( - EXIT_FAILURE, - "head: cannot open '{}' for reading: Permission denied", - name - ); - } - _ => { - crash!( - EXIT_FAILURE, - "head: cannot open '{}' for reading: {}", - name, - err - ); - } - }, + error_count += 1; + continue; + } }; if (options.files.len() > 1 && !options.quiet) || options.verbose { if !first { @@ -468,21 +461,22 @@ fn uu_head(options: &HeadOptions) { } }; if res.is_err() { - if fname.as_str() == "-" { - crash!( - EXIT_FAILURE, - "head: error reading standard input: Input/output error" - ); + let name = if fname.as_str() == "-" { + "standard input" } else { - crash!( - EXIT_FAILURE, - "head: error reading {}: Input/output error", - fname - ); - } + fname + }; + let prefix = format!("error reading {}", name); + show_error_custom_description!(prefix, "Input/output error"); + error_count += 1; } first = false; } + if error_count > 0 { + Err(error_count) + } else { + Ok(()) + } } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -492,9 +486,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!(EXIT_FAILURE, "head: {}", s); } }; - uu_head(&args); - - EXIT_SUCCESS + match uu_head(&args) { + Ok(_) => EXIT_SUCCESS, + Err(_) => EXIT_FAILURE, + } } #[cfg(test)] diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 2aedbdcbe..88df1f068 100755 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -162,6 +162,18 @@ fn test_no_such_file_or_directory() { .stderr_contains("cannot open 'no_such_file.toml' for reading: No such file or directory"); } +/// Test that each non-existent files gets its own error message printed. +#[test] +fn test_multiple_nonexistent_files() { + new_ucmd!() + .args(&["bogusfile1", "bogusfile2"]) + .fails() + .stdout_does_not_contain("==> bogusfile1 <==") + .stderr_contains("cannot open 'bogusfile1' for reading: No such file or directory") + .stdout_does_not_contain("==> bogusfile2 <==") + .stderr_contains("cannot open 'bogusfile2' for reading: No such file or directory"); +} + // there was a bug not caught by previous tests // where for negative n > 3, the total amount of lines // was correct, but it would eat from the second line diff --git a/tests/common/util.rs b/tests/common/util.rs index 719849afc..611baadd4 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -315,7 +315,12 @@ impl CmdResult { } pub fn stdout_does_not_contain>(&self, cmp: T) -> &CmdResult { - assert!(!self.stdout_str().contains(cmp.as_ref())); + assert!( + !self.stdout_str().contains(cmp.as_ref()), + "'{}' contains '{}' but should not", + self.stdout_str(), + cmp.as_ref(), + ); self }