mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
head: fix -c0 and -n0 on directories to match GNU behavior
When zero bytes or zero lines are requested, there is nothing to read, so we should not check whether the path is a directory. GNU head succeeds in this case because it never attempts to open/read the file. Previously, uutils head would stat the file and reject directories even when no reading was needed, causing a spurious 'Is a directory' error. Fixes #12215
This commit is contained in:
committed by
Daniel Hofstetter
parent
0b04f5ec69
commit
28cff684bd
+31
-9
@@ -447,15 +447,12 @@ fn uu_head(options: &HeadOptions) -> UResult<()> {
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
if Path::new(file).is_dir() {
|
||||
show!(USimpleError::new(
|
||||
1,
|
||||
translate!("head-error-reading-file", "name" => file.quote(), "err" => "Is a directory")
|
||||
));
|
||||
continue;
|
||||
}
|
||||
let mut file_handle = match File::open(file) {
|
||||
Ok(f) => f,
|
||||
// Stat the path first so we know whether to print the header.
|
||||
// GNU head prints "==> name <==" for existing files and
|
||||
// directories, but NOT for nonexistent ones — those produce
|
||||
// only an error message.
|
||||
let metadata = match Path::new(file).metadata() {
|
||||
Ok(m) => m,
|
||||
Err(err) => {
|
||||
show!(err.map_err_context(
|
||||
|| translate!("head-error-cannot-open", "name" => file.quote())
|
||||
@@ -470,7 +467,32 @@ fn uu_head(options: &HeadOptions) -> UResult<()> {
|
||||
write!(stdout, "==> ")?;
|
||||
print_verbatim(file).unwrap();
|
||||
writeln!(stdout, " <==")?;
|
||||
first = false;
|
||||
}
|
||||
// When 0 bytes or 0 lines are requested, there is nothing to
|
||||
// read, so we should succeed on directories just like GNU head
|
||||
// does. Skip opening the file entirely in that case (also
|
||||
// avoids platform differences: on Windows, `File::open` on a
|
||||
// directory fails with "Permission denied").
|
||||
let zero_output = matches!(options.mode, Mode::FirstBytes(0) | Mode::FirstLines(0));
|
||||
if metadata.is_dir() {
|
||||
if !zero_output {
|
||||
show!(USimpleError::new(
|
||||
1,
|
||||
translate!("head-error-reading-file", "name" => file.quote(), "err" => "Is a directory")
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let mut file_handle = match File::open(file) {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
show!(err.map_err_context(
|
||||
|| translate!("head-error-cannot-open", "name" => file.quote())
|
||||
));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
head_file(&mut file_handle, options)?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
@@ -933,3 +933,50 @@ fn test_do_not_attempt_to_read_a_directory() {
|
||||
.fails_with_code(1)
|
||||
.stderr_contains("error reading '.'");
|
||||
}
|
||||
|
||||
/// Regression test for https://github.com/uutils/coreutils/issues/12215
|
||||
/// `head -c0 <directory>` should succeed (nothing to read), matching GNU.
|
||||
#[test]
|
||||
fn test_zero_bytes_on_directory_succeeds() {
|
||||
new_ucmd!().args(&["-c", "0", "."]).succeeds().no_output();
|
||||
}
|
||||
|
||||
/// `head -n0 <directory>` should also succeed.
|
||||
#[test]
|
||||
fn test_zero_lines_on_directory_succeeds() {
|
||||
new_ucmd!().args(&["-n", "0", "."]).succeeds().no_output();
|
||||
}
|
||||
|
||||
/// GNU `head` prints the `==> name <==` header for every file argument
|
||||
/// (including directories) when invoked with multiple files. With non-zero
|
||||
/// output, directories also produce an "Is a directory" error after the
|
||||
/// header.
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn test_directory_header_with_multiple_files() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
at.mkdir("d");
|
||||
at.write("f", "hello\n");
|
||||
ts.ucmd()
|
||||
.args(&["-c", "5", "d", "f"])
|
||||
.fails_with_code(1)
|
||||
.stdout_is("==> d <==\n\n==> f <==\nhello")
|
||||
.stderr_contains("Is a directory");
|
||||
}
|
||||
|
||||
/// With `-c 0` (zero output), the directory header is still printed but no
|
||||
/// error is emitted.
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn test_directory_header_with_multiple_files_zero_output() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
at.mkdir("d");
|
||||
at.write("f", "hello\n");
|
||||
ts.ucmd()
|
||||
.args(&["-c", "0", "d", "f"])
|
||||
.succeeds()
|
||||
.stdout_is("==> d <==\n\n==> f <==\n")
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user