diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 544af3138fd..0725971b4be 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -4,30 +4,31 @@ // file that was distributed with this source code. // spell-checker:ignore (ToDO) nonprint nonblank nonprinting ELOOP -use clap::{crate_version, Arg, ArgAction, Command}; use std::fs::{metadata, File}; use std::io::{self, IsTerminal, Read, Write}; -use thiserror::Error; -use uucore::display::Quotable; -use uucore::error::UResult; -use uucore::fs::FileInformation; - -#[cfg(unix)] -use std::os::fd::{AsFd, AsRawFd}; - -/// Linux splice support -#[cfg(any(target_os = "linux", target_os = "android"))] -mod splice; - /// Unix domain socket support #[cfg(unix)] use std::net::Shutdown; #[cfg(unix)] +use std::os::fd::{AsFd, AsRawFd}; +#[cfg(unix)] use std::os::unix::fs::FileTypeExt; #[cfg(unix)] use std::os::unix::net::UnixStream; + +use clap::{crate_version, Arg, ArgAction, Command}; +#[cfg(unix)] +use nix::fcntl::{fcntl, FcntlArg}; +use thiserror::Error; +use uucore::display::Quotable; +use uucore::error::UResult; +use uucore::fs::FileInformation; use uucore::{format_usage, help_about, help_usage}; +/// Linux splice support +#[cfg(any(target_os = "linux", target_os = "android"))] +mod splice; + const USAGE: &str = help_usage!("cat.md"); const ABOUT: &str = help_about!("cat.md"); @@ -322,6 +323,24 @@ fn cat_handle( } } +/// Whether this process is appending to stdout. +#[cfg(unix)] +fn is_appending() -> bool { + let stdout = std::io::stdout(); + let flags = match fcntl(stdout.as_raw_fd(), FcntlArg::F_GETFL) { + Ok(flags) => flags, + Err(_) => return false, + }; + // TODO Replace `1 << 10` with `nix::fcntl::Oflag::O_APPEND`. + let o_append = 1 << 10; + (flags & o_append) > 0 +} + +#[cfg(not(unix))] +fn is_appending() -> bool { + false +} + fn cat_path( path: &str, options: &OutputOptions, @@ -331,10 +350,16 @@ fn cat_path( match get_input_type(path)? { InputType::StdIn => { let stdin = io::stdin(); + let in_info = FileInformation::from_file(&stdin)?; let mut handle = InputHandle { reader: stdin, is_interactive: std::io::stdin().is_terminal(), }; + if let Some(out_info) = out_info { + if in_info == *out_info && is_appending() { + return Err(CatError::OutputIsInput); + } + } cat_handle(&mut handle, options, state) } InputType::Directory => Err(CatError::IsDirectory), diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index d9be694365a..3e23191e3ba 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -9,6 +9,8 @@ use crate::common::util::vec_of_size; use crate::common::util::TestScenario; #[cfg(any(target_os = "linux", target_os = "android"))] use rlimit::Resource; +#[cfg(target_os = "linux")] +use std::fs::File; use std::fs::OpenOptions; #[cfg(not(windows))] use std::process::Stdio; @@ -646,3 +648,23 @@ fn test_u_ignored() { .stdout_only("hello"); } } + +#[test] +#[cfg(target_os = "linux")] +fn test_appending_same_input_output() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.write("foo", "content"); + let foo_file = at.plus_as_string("foo"); + + let file_read = File::open(&foo_file).unwrap(); + let file_write = OpenOptions::new().append(true).open(&foo_file).unwrap(); + + ucmd.set_stdin(file_read); + ucmd.set_stdout(file_write); + + ucmd.run() + .failure() + .no_stdout() + .stderr_contains("input file is output file"); +}