From 3e4221a4615e800d6010d564984b93d2d9e6abfb Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 2 Dec 2024 22:11:45 +0100 Subject: [PATCH] tr: generate an error for real if the input is a directory --- src/uu/tr/Cargo.toml | 2 +- src/uu/tr/src/tr.rs | 10 +++++++--- src/uucore/src/lib/features/fs.rs | 29 +++++++++++++++++++++++++++++ tests/by-util/test_tr.rs | 8 ++++++++ 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/uu/tr/Cargo.toml b/src/uu/tr/Cargo.toml index 0787e4279..6378b7766 100644 --- a/src/uu/tr/Cargo.toml +++ b/src/uu/tr/Cargo.toml @@ -19,7 +19,7 @@ path = "src/tr.rs" [dependencies] nom = { workspace = true } clap = { workspace = true } -uucore = { workspace = true } +uucore = { workspace = true, features = ["fs"] } [[bin]] name = "tr" diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index ff85002e7..c226d2189 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -8,17 +8,17 @@ mod operation; mod unicode_table; +use crate::operation::DeleteOperation; use clap::{crate_version, value_parser, Arg, ArgAction, Command}; use operation::{ translate_input, Sequence, SqueezeOperation, SymbolTranslator, TranslateOperation, }; use std::ffi::OsString; use std::io::{stdin, stdout, BufWriter}; -use uucore::{format_usage, help_about, help_section, help_usage, os_str_as_bytes, show}; - -use crate::operation::DeleteOperation; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError}; +use uucore::fs::is_stdin_directory; +use uucore::{format_usage, help_about, help_section, help_usage, os_str_as_bytes, show}; const ABOUT: &str = help_about!("tr.md"); const AFTER_HELP: &str = help_section!("after help", "tr.md"); @@ -126,6 +126,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { translating, )?; + if is_stdin_directory(&stdin) { + return Err(USimpleError::new(1, "read error: Is a directory")); + } + // '*_op' are the operations that need to be applied, in order. if delete_flag { if squeeze_flag { diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index e0c8ea79d..beb4d77a9 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -20,6 +20,7 @@ use std::ffi::{OsStr, OsString}; use std::fs; use std::fs::read_dir; use std::hash::Hash; +use std::io::Stdin; use std::io::{Error, ErrorKind, Result as IOResult}; #[cfg(unix)] use std::os::unix::{fs::MetadataExt, io::AsRawFd}; @@ -721,6 +722,34 @@ pub fn path_ends_with_terminator(path: &Path) -> bool { .map_or(false, |wide| wide == b'/'.into() || wide == b'\\'.into()) } +/// Checks if the standard input (stdin) is a directory. +/// +/// # Arguments +/// +/// * `stdin` - A reference to the standard input handle. +/// +/// # Returns +/// +/// * `bool` - Returns `true` if stdin is a directory, `false` otherwise. +pub fn is_stdin_directory(stdin: &Stdin) -> bool { + #[cfg(unix)] + { + use nix::sys::stat::fstat; + let mode = fstat(stdin.as_raw_fd()).unwrap().st_mode as mode_t; + has!(mode, S_IFDIR) + } + + #[cfg(windows)] + { + use std::os::windows::io::AsRawHandle; + let handle = stdin.as_raw_handle(); + if let Ok(metadata) = fs::metadata(format!("{}", handle as usize)) { + return metadata.is_dir(); + } + false + } +} + pub mod sane_blksize { #[cfg(not(target_os = "windows"))] diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index f8fcafce3..cd99f1c3a 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -20,6 +20,14 @@ fn test_invalid_input() { .fails() .code_is(1) .stderr_contains("tr: extra operand '<'"); + #[cfg(unix)] + new_ucmd!() + .args(&["1", "1"]) + // will test "tr 1 1 < ." + .set_stdin(std::process::Stdio::from(std::fs::File::open(".").unwrap())) + .fails() + .code_is(1) + .stderr_contains("tr: read error: Is a directory"); } #[test]