Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/uu/head/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ path = "src/head.rs"
[dependencies]
clap = { workspace = true }
memchr = { workspace = true }
thiserror = { workspace = true }
uucore = { workspace = true, features = ["ringbuffer", "lines", "fs"] }

[[bin]]
Expand Down
98 changes: 65 additions & 33 deletions src/uu/head/src/head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,21 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore (vars) BUFWRITER seekable
// spell-checker:ignore (vars) seekable

use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
use std::ffi::OsString;
use std::io::{self, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write};
use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
use std::num::TryFromIntError;
use thiserror::Error;
use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError};
use uucore::error::{FromIo, UError, UResult};
use uucore::line_ending::LineEnding;
use uucore::lines::lines;
use uucore::{format_usage, help_about, help_usage, show};

const BUF_SIZE: usize = 65536;

/// The capacity in bytes for buffered writers.
const BUFWRITER_CAPACITY: usize = 16_384; // 16 kilobytes

const ABOUT: &str = help_about!("head.md");
const USAGE: &str = help_usage!("head.md");

Expand All @@ -37,6 +36,36 @@ mod take;
use take::take_all_but;
use take::take_lines;

#[derive(Error, Debug)]
enum HeadError {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

much better, thanks :)
I think we should move all the quick_error to thiserror in the code base if you are interested :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that'd be a lot more consistent. I'd definitely do that

/// Wrapper around `io::Error`
#[error("error reading {name}: {err}")]
Io { name: String, err: io::Error },

#[error("parse error: {0}")]
ParseError(String),

#[error("bad argument encoding")]
BadEncoding,

#[error("{0}: number of -bytes or -lines is too large")]
NumTooLarge(#[from] TryFromIntError),

#[error("clap error: {0}")]
Clap(#[from] clap::Error),

#[error("{0}")]
MatchOption(String),
}

impl UError for HeadError {
fn code(&self) -> i32 {
1
}
}

type HeadResult<T> = Result<T, HeadError>;

pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(crate_version!())
Expand Down Expand Up @@ -152,30 +181,27 @@ impl Mode {

fn arg_iterate<'a>(
mut args: impl uucore::Args + 'a,
) -> UResult<Box<dyn Iterator<Item = OsString> + 'a>> {
) -> HeadResult<Box<dyn Iterator<Item = OsString> + 'a>> {
// argv[0] is always present
let first = args.next().unwrap();
if let Some(second) = args.next() {
if let Some(s) = second.to_str() {
match parse::parse_obsolete(s) {
Some(Ok(iter)) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))),
Some(Err(e)) => match e {
parse::ParseError::Syntax => Err(USimpleError::new(
1,
format!("bad argument format: {}", s.quote()),
)),
parse::ParseError::Overflow => Err(USimpleError::new(
1,
format!(
"invalid argument: {} Value too large for defined datatype",
s.quote()
),
)),
parse::ParseError::Syntax => Err(HeadError::ParseError(format!(
"bad argument format: {}",
s.quote()
))),
parse::ParseError::Overflow => Err(HeadError::ParseError(format!(
"invalid argument: {} Value too large for defined datatype",
s.quote()
))),
},
None => Ok(Box::new(vec![first, second].into_iter().chain(args))),
}
} else {
Err(USimpleError::new(1, "bad argument encoding".to_owned()))
Err(HeadError::BadEncoding)
}
} else {
Ok(Box::new(vec![first].into_iter()))
Expand Down Expand Up @@ -226,6 +252,11 @@ where

io::copy(&mut reader, &mut stdout)?;

// Make sure we finish writing everything to the target before
// exiting. Otherwise, when Rust is implicitly flushing, any
// error will be silently ignored.
stdout.flush()?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a comment explaining why the flush is important

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added


Ok(())
}

Expand All @@ -234,11 +265,14 @@ fn read_n_lines(input: &mut impl std::io::BufRead, n: u64, separator: u8) -> std
let mut reader = take_lines(input, n, separator);

// Write those bytes to `stdout`.
let stdout = std::io::stdout();
let stdout = stdout.lock();
let mut writer = BufWriter::with_capacity(BUFWRITER_CAPACITY, stdout);
let mut stdout = std::io::stdout();

io::copy(&mut reader, &mut stdout)?;

io::copy(&mut reader, &mut writer)?;
// Make sure we finish writing everything to the target before
// exiting. Otherwise, when Rust is implicitly flushing, any
// error will be silently ignored.
stdout.flush()?;

Ok(())
}
Expand All @@ -247,10 +281,7 @@ fn catch_too_large_numbers_in_backwards_bytes_or_lines(n: u64) -> Option<usize>
match usize::try_from(n) {
Ok(value) => Some(value),
Err(e) => {
show!(USimpleError::new(
1,
format!("{e}: number of -bytes or -lines is too large")
));
show!(HeadError::NumTooLarge(e));
None
}
}
Expand Down Expand Up @@ -511,16 +542,17 @@ fn uu_head(options: &HeadOptions) -> UResult<()> {
head_file(&mut file, options)
}
};
if res.is_err() {
if let Err(e) = res {
let name = if file.as_str() == "-" {
"standard input"
} else {
file
};
show!(USimpleError::new(
1,
format!("error reading {name}: Input/output error")
));
return Err(HeadError::Io {
name: name.to_string(),
err: e,
}
.into());
}
first = false;
}
Expand All @@ -537,7 +569,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = match HeadOptions::get_from(&matches) {
Ok(o) => o,
Err(s) => {
return Err(USimpleError::new(1, s));
return Err(HeadError::MatchOption(s).into());
}
};
uu_head(&args)
Expand Down
22 changes: 22 additions & 0 deletions tests/by-util/test_head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,3 +475,25 @@ fn test_all_but_last_lines() {
.succeeds()
.stdout_is_fixture("lorem_ipsum_backwards_15_lines.expected");
}

#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
#[test]
fn test_write_to_dev_full() {
use std::fs::OpenOptions;

for append in [true, false] {
{
let dev_full = OpenOptions::new()
.write(true)
.append(append)
.open("/dev/full")
.unwrap();

new_ucmd!()
.pipe_in_fixture(INPUT)
.set_stdout(dev_full)
.run()
.stderr_contains("No space left on device");
}
}
}
Loading