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

Merge pull request #7068 from DaringCuteSeal/head-dev-full

head: make head fail when writing to /dev/full
This commit is contained in:
Sylvestre Ledru 2025-01-06 09:07:28 +01:00 committed by GitHub
commit 0459369e92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 89 additions and 33 deletions

1
Cargo.lock generated
View file

@ -2771,6 +2771,7 @@ version = "0.0.28"
dependencies = [
"clap",
"memchr",
"thiserror 2.0.9",
"uucore",
]

View file

@ -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]]

View file

@ -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");
@ -37,6 +36,36 @@ mod take;
use take::take_all_but;
use take::take_lines;
#[derive(Error, Debug)]
enum HeadError {
/// 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!())
@ -152,7 +181,7 @@ 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() {
@ -160,22 +189,19 @@ fn arg_iterate<'a>(
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()))
@ -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()?;
Ok(())
}
@ -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 writer)?;
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()?;
Ok(())
}
@ -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
}
}
@ -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;
}
@ -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)

View file

@ -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");
}
}
}