mirror of
https://github.com/uutils/coreutils
synced 2024-12-14 07:12:44 +00:00
cat: check if the input file is also the output file
This commit is contained in:
parent
d967a7a553
commit
03ceb6750e
4 changed files with 87 additions and 6 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2023,6 +2023,7 @@ dependencies = [
|
|||
"unix_socket",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -23,9 +23,10 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
|
|||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
unix_socket = "0.5.0"
|
||||
nix = "0.20.0"
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
|
||||
nix = "0.20"
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi-util = "0.1.5"
|
||||
|
||||
[[bin]]
|
||||
name = "cat"
|
||||
|
|
|
@ -22,11 +22,14 @@ use std::io::{self, Read, Write};
|
|||
use thiserror::Error;
|
||||
use uucore::error::UResult;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
/// Linux splice support
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
mod splice;
|
||||
#[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
|
||||
#[cfg(unix)]
|
||||
|
@ -59,6 +62,8 @@ enum CatError {
|
|||
},
|
||||
#[error("Is a directory")]
|
||||
IsDirectory,
|
||||
#[error("input file is output file")]
|
||||
OutputIsInput,
|
||||
}
|
||||
|
||||
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 == "-" {
|
||||
let stdin = io::stdin();
|
||||
let mut handle = InputHandle {
|
||||
|
@ -324,6 +335,10 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
|
|||
}
|
||||
_ => {
|
||||
let file = File::open(path)?;
|
||||
#[cfg(any(windows, unix))]
|
||||
if same_file(out_info, &file) {
|
||||
return Err(CatError::OutputIsInput);
|
||||
}
|
||||
let mut handle = InputHandle {
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
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<()> {
|
||||
#[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 {
|
||||
line_number: 1,
|
||||
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();
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use crate::common::util::*;
|
||||
#[cfg(unix)]
|
||||
use std::fs::OpenOptions;
|
||||
#[cfg(unix)]
|
||||
use std::io::Read;
|
||||
|
@ -443,3 +442,49 @@ fn test_domain_socket() {
|
|||
|
||||
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."
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue