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",
"libc",
"nix 0.20.0",
"thiserror",
"unicode-width",
"utf-8",
"uucore",

View file

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

View file

@ -1,10 +1,10 @@
use crate::word_count::WordCount;
use super::{WcResult, WordCountable};
use super::WordCountable;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::fs::{File, OpenOptions};
use std::io::{ErrorKind, Read};
use std::io::{self, ErrorKind, Read};
#[cfg(unix)]
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
/// other things such as lines and words.
#[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;
#[cfg(unix)]
@ -123,12 +123,12 @@ pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> WcResult<usi
byte_count += n;
}
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 buf = [0; BUF_SIZE];
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');
}
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 clap::{crate_version, App, Arg, ArgMatches};
use thiserror::Error;
use std::cmp::max;
use std::fs::{self, File};
use std::io::{self, ErrorKind, Write};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
/// The minimum character width for formatting counts when reading from stdin.
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 {
show_bytes: bool,
show_chars: bool,
@ -132,6 +121,13 @@ impl Input {
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 {
@ -206,8 +202,7 @@ pub fn uu_app() -> App<'static, 'static> {
fn word_count_from_reader<T: WordCountable>(
mut reader: T,
settings: &Settings,
path: &Path,
) -> WcResult<WordCount> {
) -> io::Result<WordCount> {
let only_count_bytes = settings.show_bytes
&& (!(settings.show_chars
|| settings.show_lines
@ -273,7 +268,7 @@ fn word_count_from_reader<T: WordCountable>(
total.bytes += bytes.len();
}
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)
}
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 {
Input::Stdin(_) => {
let stdin = io::stdin();
let stdin_lock = stdin.lock();
word_count_from_reader(stdin_lock, settings, "-".as_ref())
word_count_from_reader(stdin_lock, settings)
}
Input::Path(path) => {
if path.is_dir() {
Err(WcError::IsDirectory(path.to_owned()))
} else {
let file = File::open(path)?;
word_count_from_reader(file, settings, path)
}
let file = File::open(path)?;
word_count_from_reader(file, settings)
}
}
}
@ -310,18 +301,8 @@ fn word_count_from_input(input: &Input, settings: &Settings) -> WcResult<WordCou
/// ```rust,ignore
/// show_error(Input::Path("/tmp"), WcError::IsDirectory("/tmp"))
/// ```
fn show_error(input: &Input, err: WcError) {
match (input, 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);
}
};
fn show_error(input: &Input, err: io::Error) {
show_error!("{}: {}", input.path_display(), err);
}
/// 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);
/// 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 {
Input::Stdin(_) => Ok(Some(MINIMUM_WIDTH)),
Input::Path(filename) => {
@ -453,7 +434,7 @@ fn print_stats(
settings: &Settings,
result: &TitledWordCount,
mut min_width: usize,
) -> WcResult<()> {
) -> io::Result<()> {
let stdout = io::stdout();
let mut stdout_lock = stdout.lock();

View file

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