1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 03:27:44 +00:00

wc: Stricter simpler error handling

Errors are now always shown with the corresponding filename.

Errors are no longer converted into warnings. Previously `wc < .`
would cause a loop.

Checking whether something is a directory is no longer done in
advance. This removes race conditions and the edge case where stdin is
a directory.

The custom error type is removed because io::Error is now enough.
This commit is contained in:
Jan Verbeek 2021-08-25 14:26:03 +02:00 committed by Michael Debertol
parent 35793fc260
commit 657a04f706
5 changed files with 26 additions and 47 deletions

1
Cargo.lock generated
View file

@ -3126,7 +3126,6 @@ dependencies = [
"clap", "clap",
"libc", "libc",
"nix 0.20.0", "nix 0.20.0",
"thiserror",
"unicode-width", "unicode-width",
"utf-8", "utf-8",
"uucore", "uucore",

View file

@ -18,7 +18,6 @@ path = "src/wc.rs"
clap = { version = "2.33", features = ["wrap_help"] } clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } uucore = { version=">=0.0.9", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
thiserror = "1.0"
bytecount = "0.6.2" bytecount = "0.6.2"
utf-8 = "0.7.6" utf-8 = "0.7.6"
unicode-width = "0.1.8" unicode-width = "0.1.8"

View file

@ -1,10 +1,10 @@
use crate::word_count::WordCount; use crate::word_count::WordCount;
use super::{WcResult, WordCountable}; use super::WordCountable;
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::io::{ErrorKind, Read}; use std::io::{self, ErrorKind, Read};
#[cfg(unix)] #[cfg(unix)]
use libc::S_IFREG; use libc::S_IFREG;
@ -88,7 +88,7 @@ fn count_bytes_using_splice(fd: RawFd) -> Result<usize, usize> {
/// 3. Otherwise, we just read normally, but without the overhead of counting /// 3. Otherwise, we just read normally, but without the overhead of counting
/// other things such as lines and words. /// other things such as lines and words.
#[inline] #[inline]
pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> WcResult<usize> { pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> io::Result<usize> {
let mut byte_count = 0; let mut byte_count = 0;
#[cfg(unix)] #[cfg(unix)]
@ -123,12 +123,12 @@ pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> WcResult<usi
byte_count += n; byte_count += n;
} }
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => return Err(e.into()), Err(e) => return Err(e),
} }
} }
} }
pub(crate) fn count_bytes_and_lines_fast<R: Read>(handle: &mut R) -> WcResult<WordCount> { pub(crate) fn count_bytes_and_lines_fast<R: Read>(handle: &mut R) -> io::Result<WordCount> {
let mut total = WordCount::default(); let mut total = WordCount::default();
let mut buf = [0; BUF_SIZE]; let mut buf = [0; BUF_SIZE];
loop { loop {
@ -139,7 +139,7 @@ pub(crate) fn count_bytes_and_lines_fast<R: Read>(handle: &mut R) -> WcResult<Wo
total.lines += bytecount::count(&buf[..n], b'\n'); total.lines += bytecount::count(&buf[..n], b'\n');
} }
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => return Err(e.into()), Err(e) => return Err(e),
} }
} }
} }

View file

@ -18,26 +18,15 @@ use utf8::{BufReadDecoder, BufReadDecoderError};
use word_count::{TitledWordCount, WordCount}; use word_count::{TitledWordCount, WordCount};
use clap::{crate_version, App, Arg, ArgMatches}; use clap::{crate_version, App, Arg, ArgMatches};
use thiserror::Error;
use std::cmp::max; use std::cmp::max;
use std::fs::{self, File}; use std::fs::{self, File};
use std::io::{self, ErrorKind, Write}; use std::io::{self, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
/// The minimum character width for formatting counts when reading from stdin. /// The minimum character width for formatting counts when reading from stdin.
const MINIMUM_WIDTH: usize = 7; const MINIMUM_WIDTH: usize = 7;
#[derive(Error, Debug)]
pub enum WcError {
#[error("{0}")]
Io(#[from] io::Error),
#[error("Expected a file, found directory {0}")]
IsDirectory(PathBuf),
}
type WcResult<T> = Result<T, WcError>;
struct Settings { struct Settings {
show_bytes: bool, show_bytes: bool,
show_chars: bool, show_chars: bool,
@ -132,6 +121,13 @@ impl Input {
Input::Stdin(StdinKind::Implicit) => None, Input::Stdin(StdinKind::Implicit) => None,
} }
} }
fn path_display(&self) -> std::path::Display<'_> {
match self {
Input::Path(path) => path.display(),
Input::Stdin(_) => Path::display("'standard input'".as_ref()),
}
}
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
@ -206,8 +202,7 @@ pub fn uu_app() -> App<'static, 'static> {
fn word_count_from_reader<T: WordCountable>( fn word_count_from_reader<T: WordCountable>(
mut reader: T, mut reader: T,
settings: &Settings, settings: &Settings,
path: &Path, ) -> io::Result<WordCount> {
) -> WcResult<WordCount> {
let only_count_bytes = settings.show_bytes let only_count_bytes = settings.show_bytes
&& (!(settings.show_chars && (!(settings.show_chars
|| settings.show_lines || settings.show_lines
@ -273,7 +268,7 @@ fn word_count_from_reader<T: WordCountable>(
total.bytes += bytes.len(); total.bytes += bytes.len();
} }
Err(BufReadDecoderError::Io(e)) => { Err(BufReadDecoderError::Io(e)) => {
show_warning!("Error while reading {}: {}", path.display(), e); return Err(e);
} }
} }
} }
@ -283,20 +278,16 @@ fn word_count_from_reader<T: WordCountable>(
Ok(total) Ok(total)
} }
fn word_count_from_input(input: &Input, settings: &Settings) -> WcResult<WordCount> { fn word_count_from_input(input: &Input, settings: &Settings) -> io::Result<WordCount> {
match input { match input {
Input::Stdin(_) => { Input::Stdin(_) => {
let stdin = io::stdin(); let stdin = io::stdin();
let stdin_lock = stdin.lock(); let stdin_lock = stdin.lock();
word_count_from_reader(stdin_lock, settings, "-".as_ref()) word_count_from_reader(stdin_lock, settings)
} }
Input::Path(path) => { Input::Path(path) => {
if path.is_dir() { let file = File::open(path)?;
Err(WcError::IsDirectory(path.to_owned())) word_count_from_reader(file, settings)
} else {
let file = File::open(path)?;
word_count_from_reader(file, settings, path)
}
} }
} }
} }
@ -310,18 +301,8 @@ fn word_count_from_input(input: &Input, settings: &Settings) -> WcResult<WordCou
/// ```rust,ignore /// ```rust,ignore
/// show_error(Input::Path("/tmp"), WcError::IsDirectory("/tmp")) /// show_error(Input::Path("/tmp"), WcError::IsDirectory("/tmp"))
/// ``` /// ```
fn show_error(input: &Input, err: WcError) { fn show_error(input: &Input, err: io::Error) {
match (input, err) { show_error!("{}: {}", input.path_display(), err);
(_, WcError::IsDirectory(path)) => {
show_error_custom_description!(path.display(), "Is a directory");
}
(Input::Path(path), WcError::Io(e)) if e.kind() == ErrorKind::NotFound => {
show_error_custom_description!(path.display(), "No such file or directory");
}
(_, e) => {
show_error!("{}", e);
}
};
} }
/// Compute the number of digits needed to represent any count for this input. /// Compute the number of digits needed to represent any count for this input.
@ -343,7 +324,7 @@ fn show_error(input: &Input, err: WcError) {
/// let input = Input::Stdin(StdinKind::Explicit); /// let input = Input::Stdin(StdinKind::Explicit);
/// assert_eq!(7, digit_width(input)); /// assert_eq!(7, digit_width(input));
/// ``` /// ```
fn digit_width(input: &Input) -> WcResult<Option<usize>> { fn digit_width(input: &Input) -> io::Result<Option<usize>> {
match input { match input {
Input::Stdin(_) => Ok(Some(MINIMUM_WIDTH)), Input::Stdin(_) => Ok(Some(MINIMUM_WIDTH)),
Input::Path(filename) => { Input::Path(filename) => {
@ -453,7 +434,7 @@ fn print_stats(
settings: &Settings, settings: &Settings,
result: &TitledWordCount, result: &TitledWordCount,
mut min_width: usize, mut min_width: usize,
) -> WcResult<()> { ) -> io::Result<()> {
let stdout = io::stdout(); let stdout = io::stdout();
let mut stdout_lock = stdout.lock(); let mut stdout_lock = stdout.lock();

View file

@ -212,7 +212,7 @@ fn test_read_from_directory_error() {
new_ucmd!() new_ucmd!()
.args(&["."]) .args(&["."])
.fails() .fails()
.stderr_contains(".: Is a directory\n") .stderr_contains(".: Is a directory")
.stdout_is("0 0 0 .\n"); .stdout_is("0 0 0 .\n");
} }
@ -222,5 +222,5 @@ fn test_read_from_nonexistent_file() {
new_ucmd!() new_ucmd!()
.args(&["bogusfile"]) .args(&["bogusfile"])
.fails() .fails()
.stderr_contains("bogusfile: No such file or directory\n"); .stderr_contains("bogusfile: No such file or directory");
} }