1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 03:27:44 +00:00

cat: check if the input file is also the output file

This commit is contained in:
Michael Debertol 2021-08-08 00:48:37 +02:00
parent d967a7a553
commit 03ceb6750e
4 changed files with 87 additions and 6 deletions

1
Cargo.lock generated
View file

@ -2023,6 +2023,7 @@ dependencies = [
"unix_socket", "unix_socket",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
"winapi-util",
] ]
[[package]] [[package]]

View file

@ -23,9 +23,10 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
unix_socket = "0.5.0" unix_socket = "0.5.0"
nix = "0.20.0"
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] [target.'cfg(windows)'.dependencies]
nix = "0.20" winapi-util = "0.1.5"
[[bin]] [[bin]]
name = "cat" name = "cat"

View file

@ -22,11 +22,14 @@ use std::io::{self, Read, Write};
use thiserror::Error; use thiserror::Error;
use uucore::error::UResult; use uucore::error::UResult;
#[cfg(unix)]
use std::os::unix::io::AsRawFd;
/// Linux splice support /// Linux splice support
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
mod splice; mod splice;
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::io::RawFd;
/// Unix domain socket support /// Unix domain socket support
#[cfg(unix)] #[cfg(unix)]
@ -59,6 +62,8 @@ enum CatError {
}, },
#[error("Is a directory")] #[error("Is a directory")]
IsDirectory, IsDirectory,
#[error("input file is output file")]
OutputIsInput,
} }
type CatResult<T> = Result<T, CatError>; type CatResult<T> = Result<T, CatError>;
@ -297,7 +302,13 @@ fn cat_handle<R: Read>(
} }
} }
fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> CatResult<()> { fn cat_path(
path: &str,
options: &OutputOptions,
state: &mut OutputState,
#[cfg(unix)] out_info: &nix::sys::stat::FileStat,
#[cfg(windows)] out_info: &winapi_util::file::Information,
) -> CatResult<()> {
if path == "-" { if path == "-" {
let stdin = io::stdin(); let stdin = io::stdin();
let mut handle = InputHandle { let mut handle = InputHandle {
@ -324,6 +335,10 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
} }
_ => { _ => {
let file = File::open(path)?; let file = File::open(path)?;
#[cfg(any(windows, unix))]
if same_file(out_info, &file) {
return Err(CatError::OutputIsInput);
}
let mut handle = InputHandle { let mut handle = InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: file.as_raw_fd(), file_descriptor: file.as_raw_fd(),
@ -335,7 +350,26 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
} }
} }
#[cfg(unix)]
fn same_file(a_info: &nix::sys::stat::FileStat, b: &File) -> bool {
let b_info = nix::sys::stat::fstat(b.as_raw_fd()).unwrap();
b_info.st_size != 0 && b_info.st_dev == a_info.st_dev && b_info.st_ino == a_info.st_ino
}
#[cfg(windows)]
fn same_file(a_info: &winapi_util::file::Information, b: &File) -> bool {
let b_info = winapi_util::file::information(b).unwrap();
b_info.file_size() != 0
&& b_info.volume_serial_number() == a_info.volume_serial_number()
&& b_info.file_index() == a_info.file_index()
}
fn cat_files(files: Vec<String>, options: &OutputOptions) -> UResult<()> { fn cat_files(files: Vec<String>, options: &OutputOptions) -> UResult<()> {
#[cfg(windows)]
let out_info = winapi_util::file::information(&std::io::stdout()).unwrap();
#[cfg(unix)]
let out_info = nix::sys::stat::fstat(std::io::stdout().as_raw_fd()).unwrap();
let mut state = OutputState { let mut state = OutputState {
line_number: 1, line_number: 1,
at_line_start: true, at_line_start: true,
@ -343,7 +377,7 @@ fn cat_files(files: Vec<String>, options: &OutputOptions) -> UResult<()> {
let mut error_messages: Vec<String> = Vec::new(); let mut error_messages: Vec<String> = Vec::new();
for path in &files { for path in &files {
if let Err(err) = cat_path(path, options, &mut state) { if let Err(err) = cat_path(path, options, &mut state, &out_info) {
error_messages.push(format!("{}: {}", path, err)); error_messages.push(format!("{}: {}", path, err));
} }
} }

View file

@ -1,5 +1,4 @@
use crate::common::util::*; use crate::common::util::*;
#[cfg(unix)]
use std::fs::OpenOptions; use std::fs::OpenOptions;
#[cfg(unix)] #[cfg(unix)]
use std::io::Read; use std::io::Read;
@ -443,3 +442,49 @@ fn test_domain_socket() {
thread.join().unwrap(); thread.join().unwrap();
} }
#[test]
fn test_write_to_self_empty() {
// it's ok if the input file is also the output file if it's empty
let s = TestScenario::new(util_name!());
let file_path = s.fixtures.plus("file.txt");
let file = OpenOptions::new()
.create_new(true)
.write(true)
.append(true)
.open(&file_path)
.unwrap();
s.ucmd().set_stdout(file).arg(&file_path).succeeds();
}
#[test]
fn test_write_to_self() {
let s = TestScenario::new(util_name!());
let file_path = s.fixtures.plus("first_file");
s.fixtures.write("second_file", "second_file_content.");
let file = OpenOptions::new()
.create_new(true)
.write(true)
.append(true)
.open(&file_path)
.unwrap();
s.fixtures.append("first_file", "first_file_content.");
s.ucmd()
.set_stdout(file)
.arg("first_file")
.arg("first_file")
.arg("second_file")
.fails()
.code_is(2)
.stderr_only("cat: first_file: input file is output file\ncat: first_file: input file is output file");
assert_eq!(
s.fixtures.read("first_file"),
"first_file_content.second_file_content."
);
}