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

wc: Report counts and failures correctly

If reading fails midway through then a count should be reported for
what could be read.

If opening a file fails then no count should be reported.

The exit code shouldn't report the number of failures, that's fragile
in case of many failures.
This commit is contained in:
Jan Verbeek 2021-08-25 15:57:33 +02:00 committed by Michael Debertol
parent c16e492cd0
commit 9972cd1327
2 changed files with 66 additions and 32 deletions

View file

@ -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) -> io::Result<usize> { pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> (usize, Option<io::Error>) {
let mut byte_count = 0; let mut byte_count = 0;
#[cfg(unix)] #[cfg(unix)]
@ -98,7 +98,7 @@ pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> io::Result<u
// If the file is regular, then the `st_size` should hold // If the file is regular, then the `st_size` should hold
// the file's size in bytes. // the file's size in bytes.
if (stat.st_mode & S_IFREG) != 0 { if (stat.st_mode & S_IFREG) != 0 {
return Ok(stat.st_size as usize); return (stat.st_size as usize, None);
} }
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
{ {
@ -106,7 +106,7 @@ pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> io::Result<u
// (or stdin), we use splice to count the number of bytes. // (or stdin), we use splice to count the number of bytes.
if (stat.st_mode & S_IFIFO) != 0 { if (stat.st_mode & S_IFIFO) != 0 {
match count_bytes_using_splice(fd) { match count_bytes_using_splice(fd) {
Ok(n) => return Ok(n), Ok(n) => return (n, None),
Err(n) => byte_count = n, Err(n) => byte_count = n,
} }
} }
@ -118,28 +118,30 @@ pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> io::Result<u
let mut buf = [0_u8; BUF_SIZE]; let mut buf = [0_u8; BUF_SIZE];
loop { loop {
match handle.read(&mut buf) { match handle.read(&mut buf) {
Ok(0) => return Ok(byte_count), Ok(0) => return (byte_count, None),
Ok(n) => { Ok(n) => {
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), Err(e) => return (byte_count, Some(e)),
} }
} }
} }
pub(crate) fn count_bytes_and_lines_fast<R: Read>(handle: &mut R) -> io::Result<WordCount> { pub(crate) fn count_bytes_and_lines_fast<R: Read>(
handle: &mut R,
) -> (WordCount, Option<io::Error>) {
let mut total = WordCount::default(); let mut total = WordCount::default();
let mut buf = [0; BUF_SIZE]; let mut buf = [0; BUF_SIZE];
loop { loop {
match handle.read(&mut buf) { match handle.read(&mut buf) {
Ok(0) => return Ok(total), Ok(0) => return (total, None),
Ok(n) => { Ok(n) => {
total.bytes += n; total.bytes += n;
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), Err(e) => return (total, Some(e)),
} }
} }
} }

View file

@ -202,17 +202,21 @@ 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,
) -> io::Result<WordCount> { ) -> (WordCount, Option<io::Error>) {
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
|| settings.show_max_line_length || settings.show_max_line_length
|| settings.show_words)); || settings.show_words));
if only_count_bytes { if only_count_bytes {
return Ok(WordCount { let (bytes, error) = count_bytes_fast(&mut reader);
bytes: count_bytes_fast(&mut reader)?, return (
..WordCount::default() WordCount {
}); bytes,
..WordCount::default()
},
error,
);
} }
// we do not need to decode the byte stream if we're only counting bytes/newlines // we do not need to decode the byte stream if we're only counting bytes/newlines
@ -268,27 +272,47 @@ fn word_count_from_reader<T: WordCountable>(
total.bytes += bytes.len(); total.bytes += bytes.len();
} }
Err(BufReadDecoderError::Io(e)) => { Err(BufReadDecoderError::Io(e)) => {
return Err(e); return (total, Some(e));
} }
} }
} }
total.max_line_length = max(current_len, total.max_line_length); total.max_line_length = max(current_len, total.max_line_length);
Ok(total) (total, None)
} }
fn word_count_from_input(input: &Input, settings: &Settings) -> io::Result<WordCount> { enum CountResult {
/// Nothing went wrong.
Success(WordCount),
/// Managed to open but failed to read.
Interrupted(WordCount, io::Error),
/// Didn't even manage to open.
Failure(io::Error),
}
/// If we fail opening a file we only show the error. If we fail reading it
/// we show a count for what we managed to read.
///
/// Therefore the reading implementations always return a total and sometimes
/// return an error: (WordCount, Option<io::Error>).
fn word_count_from_input(input: &Input, settings: &Settings) -> CountResult {
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) match word_count_from_reader(stdin_lock, settings) {
} (total, Some(error)) => CountResult::Interrupted(total, error),
Input::Path(path) => { (total, None) => CountResult::Success(total),
let file = File::open(path)?; }
word_count_from_reader(file, settings)
} }
Input::Path(path) => match File::open(path) {
Err(error) => CountResult::Failure(error),
Ok(file) => match word_count_from_reader(file, settings) {
(total, Some(error)) => CountResult::Interrupted(total, error),
(total, None) => CountResult::Success(total),
},
},
} }
} }
@ -390,7 +414,7 @@ fn wc(inputs: Vec<Input>, settings: &Settings) -> Result<(), u32> {
// The width is the number of digits needed to print the number of // The width is the number of digits needed to print the number of
// bytes in the largest file. This is true regardless of whether // bytes in the largest file. This is true regardless of whether
// the `settings` indicate that the bytes will be displayed. // the `settings` indicate that the bytes will be displayed.
let mut error_count = 0; let mut failure = false;
let max_width = max_width(&inputs); let max_width = max_width(&inputs);
let mut total_word_count = WordCount::default(); let mut total_word_count = WordCount::default();
@ -398,11 +422,19 @@ fn wc(inputs: Vec<Input>, settings: &Settings) -> Result<(), u32> {
let num_inputs = inputs.len(); let num_inputs = inputs.len();
for input in &inputs { for input in &inputs {
let word_count = word_count_from_input(input, settings).unwrap_or_else(|err| { let word_count = match word_count_from_input(input, settings) {
show_error(input, err); CountResult::Success(word_count) => word_count,
error_count += 1; CountResult::Interrupted(word_count, error) => {
WordCount::default() show_error(input, error);
}); failure = true;
word_count
}
CountResult::Failure(error) => {
show_error(input, error);
failure = true;
continue;
}
};
total_word_count += word_count; total_word_count += word_count;
let result = word_count.with_title(input.to_title()); let result = word_count.with_title(input.to_title());
if let Err(err) = print_stats(settings, &result, max_width) { if let Err(err) = print_stats(settings, &result, max_width) {
@ -411,7 +443,7 @@ fn wc(inputs: Vec<Input>, settings: &Settings) -> Result<(), u32> {
result.title.unwrap_or_else(|| "<stdin>".as_ref()).display(), result.title.unwrap_or_else(|| "<stdin>".as_ref()).display(),
err err
); );
error_count += 1; failure = true;
} }
} }
@ -419,14 +451,14 @@ fn wc(inputs: Vec<Input>, settings: &Settings) -> Result<(), u32> {
let total_result = total_word_count.with_title(Some("total".as_ref())); let total_result = total_word_count.with_title(Some("total".as_ref()));
if let Err(err) = print_stats(settings, &total_result, max_width) { if let Err(err) = print_stats(settings, &total_result, max_width) {
show_warning!("failed to print total: {}", err); show_warning!("failed to print total: {}", err);
error_count += 1; failure = true;
} }
} }
if error_count == 0 { if failure {
Ok(()) Err(1)
} else { } else {
Err(error_count) Ok(())
} }
} }