From 48e6dfe20be4b3a67f7733ccb1410280e34556e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20Fleur=20de=20Blue?= <135421389+Xylphy@users.noreply.github.com> Date: Tue, 4 Aug 2026 22:48:40 +0800 Subject: [PATCH 1/3] fix: report read error for device files with -files0-from Previously, using -files0-from on character or block device files that return EOF immediately (e.g., /dev/vhost-net) would succeed silently and exit with code 0. GNU find treats this as a read error. This commit adds a check after reading the file: if the file is a character or block device and the read returns zero bytes, we emit a "read error" message and exit with non-zero status, matching GNU find. --- src/find/matchers/mod.rs | 16 ++++++++++++++++ tests/test_find.rs | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/find/matchers/mod.rs b/src/find/matchers/mod.rs index e35ebbbb..5e3b4adf 100644 --- a/src/find/matchers/mod.rs +++ b/src/find/matchers/mod.rs @@ -62,6 +62,7 @@ use ::regex::Regex; use chrono::{DateTime, Datelike, NaiveDateTime, Utc}; use fs::FileSystemMatcher; use ls::Ls; +use std::os::unix::fs::FileTypeExt; use std::{ error::Error, fs::{File, Metadata}, @@ -1031,6 +1032,21 @@ 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)?; + + let meta = file + .metadata() + .map_err(|e| format!("cannot stat '{}': {}", mode, e))?; + + // Read the entire file. + let bytes_read = file.read_to_end(&mut buffer)?; + + if bytes_read == 0 + && (meta.file_type().is_char_device() || meta.file_type().is_block_device()) + { + 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..9478ccd9 100644 --- a/tests/test_find.rs +++ b/tests/test_find.rs @@ -1434,3 +1434,19 @@ 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; + } + ucmd() + .arg("-files0-from") + .arg(path) + .fails() + .stderr_contains("read error") + .no_stdout(); + } +} From 5a986556106518be6b65070feac263712dd8b25e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20Fleur=20de=20Blue?= <135421389+Xylphy@users.noreply.github.com> Date: Tue, 4 Aug 2026 23:01:54 +0800 Subject: [PATCH 2/3] fix(find): gate Unix FileTypeExt/device checks --- src/find/matchers/mod.rs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/find/matchers/mod.rs b/src/find/matchers/mod.rs index 5e3b4adf..06dd7235 100644 --- a/src/find/matchers/mod.rs +++ b/src/find/matchers/mod.rs @@ -62,6 +62,7 @@ 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, @@ -1033,18 +1034,26 @@ fn parse_files0_args(config: &mut Config) -> Result<(), Box> { File::open(mode).map_err(|e| format!("cannot open '{}' for reading: {}", mode, e))?; file.read_to_end(&mut buffer)?; - let meta = file - .metadata() - .map_err(|e| format!("cannot stat '{}': {}", mode, e))?; - // Read the entire file. let bytes_read = file.read_to_end(&mut buffer)?; - if bytes_read == 0 - && (meta.file_type().is_char_device() || meta.file_type().is_block_device()) - { - let err = - std::io::Error::other("File descriptor in bad state"); + 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()); } } From d5dbc2d29a33f208881ef76aa6b839e33e942fef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20Fleur=20de=20Blue?= <135421389+Xylphy@users.noreply.github.com> Date: Tue, 4 Aug 2026 23:33:47 +0800 Subject: [PATCH 3/3] test(find): accept open-permission errors in files0 special-file test --- tests/test_find.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/test_find.rs b/tests/test_find.rs index 9478ccd9..c675b73c 100644 --- a/tests/test_find.rs +++ b/tests/test_find.rs @@ -1442,11 +1442,15 @@ fn files0_from_special_file_read_error() { if !Path::new(path).exists() { continue; } - ucmd() - .arg("-files0-from") - .arg(path) - .fails() - .stderr_contains("read error") - .no_stdout(); + + 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}" + ); } }