diff --git a/src/find/matchers/mod.rs b/src/find/matchers/mod.rs index e35ebbbb..06dd7235 100644 --- a/src/find/matchers/mod.rs +++ b/src/find/matchers/mod.rs @@ -62,6 +62,8 @@ use ::regex::Regex; use chrono::{DateTime, Datelike, NaiveDateTime, Utc}; use fs::FileSystemMatcher; use ls::Ls; +#[cfg(unix)] +use std::os::unix::fs::FileTypeExt; use std::{ error::Error, fs::{File, Metadata}, @@ -1031,6 +1033,29 @@ fn parse_files0_args(config: &mut Config) -> Result<(), Box> { let mut file = File::open(mode).map_err(|e| format!("cannot open '{}' for reading: {}", mode, e))?; file.read_to_end(&mut buffer)?; + + // Read the entire file. + let bytes_read = file.read_to_end(&mut buffer)?; + + let is_special = { + #[cfg(unix)] + { + let meta = file + .metadata() + .map_err(|e| format!("cannot stat '{}': {}", mode, e))?; + let file_type = meta.file_type(); + file_type.is_char_device() || file_type.is_block_device() + } + #[cfg(not(unix))] + { + false + } + }; + + if bytes_read == 0 && is_special { + let err = std::io::Error::other("File descriptor in bad state"); + return Err(format!("read error: {}: {}", mode, err).into()); + } } let mut buffer_split: Vec<&[u8]> = buffer.split(|&b| b == 0).collect(); diff --git a/tests/test_find.rs b/tests/test_find.rs index ce223d56..c675b73c 100644 --- a/tests/test_find.rs +++ b/tests/test_find.rs @@ -1434,3 +1434,23 @@ fn find_exits_cleanly_on_broken_pipe() { "find panicked instead of exiting cleanly on a broken pipe:\n{stderr}" ); } + +#[test] +#[cfg(target_os = "linux")] +fn files0_from_special_file_read_error() { + for path in &["/dev/vhost-net", "/dev/vhost-vsock"] { + if !Path::new(path).exists() { + continue; + } + + let file_list_failure = ucmd().arg("-files0-from").arg(path).fails(); + + let error_output = file_list_failure.no_stdout().stderr_str(); + assert!( + error_output.contains("read error") + || (error_output.contains("cannot open") + && error_output.contains("Permission denied")), + "unexpected stderr for {path}: {error_output}" + ); + } +}