Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/uu/cat/src/cat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

mod platform;

use crate::platform::is_unsafe_overwrite;
use crate::platform::is_safe_overwrite;
use clap::{Arg, ArgAction, Command};
use memchr::memchr2;
use std::ffi::OsString;
Expand Down Expand Up @@ -370,7 +370,7 @@ fn cat_path(path: &OsString, options: &OutputOptions, state: &mut OutputState) -
InputType::StdIn => {
let stdin = io::stdin();
let is_interactive = stdin.is_terminal();
if is_unsafe_overwrite(&stdin, &io::stdout()) {
if !is_safe_overwrite(&stdin, &io::stdout()) {
return Err(CatError::OutputIsInput);
}
let mut handle = InputHandle {
Expand All @@ -384,7 +384,7 @@ fn cat_path(path: &OsString, options: &OutputOptions, state: &mut OutputState) -
InputType::Socket => Err(CatError::NoSuchDeviceOrAddress),
_ => {
let file = File::open(path)?;
if is_unsafe_overwrite(&file, &io::stdout()) {
if !is_safe_overwrite(&file, &io::stdout()) {
return Err(CatError::OutputIsInput);
}
let mut handle = InputHandle {
Expand Down
8 changes: 4 additions & 4 deletions src/uu/cat/src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
// file that was distributed with this source code.

#[cfg(unix)]
pub use self::unix::is_unsafe_overwrite;
pub use self::unix::is_safe_overwrite;

#[cfg(windows)]
pub use self::windows::is_unsafe_overwrite;
pub use self::windows::is_safe_overwrite;

// WASI: no fstat-based device/inode checks available; assume safe.
#[cfg(target_os = "wasi")]
pub fn is_unsafe_overwrite<I, O>(_input: &I, _output: &O) -> bool {
false
pub fn is_safe_overwrite<I, O>(_input: &I, _output: &O) -> bool {
true
}

#[cfg(unix)]
Expand Down
38 changes: 19 additions & 19 deletions src/uu/cat/src/platform/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,36 @@ use uucore::fs::FileInformation;
/// In this scenario, bytes read from stdin are written to a later part of the file
/// via stdout, which can then be read again by stdin and written again by stdout,
/// causing an infinite loop and potential file corruption.
pub fn is_unsafe_overwrite<I: AsFd, O: AsFd>(input: &I, output: &O) -> bool {
pub fn is_safe_overwrite<I: AsFd, O: AsFd>(input: &I, output: &O) -> bool {
// `FileInformation::from_file` returns an error if the file descriptor is closed, invalid,
// or refers to a non-regular file (e.g., socket, pipe, or special device).
let Ok(input_info) = FileInformation::from_file(input) else {
return false;
return true;
};
let Ok(output_info) = FileInformation::from_file(output) else {
return false;
return true;
};
if input_info != output_info {
return false;
return true;
}
let file_size = output_info.file_size();
if file_size == 0 {
return false;
return true;
}
// `seek` returns an error if the file descriptor is closed or it refers to
// a non-seekable resource (e.g., pipe, socket, or some devices).
let input_pos = rustix::fs::seek(input, SeekFrom::Current(0)).map(|v| v as i64);
let output_pos = rustix::fs::seek(output, SeekFrom::Current(0)).map(|v| v as i64);
if is_appending(output) {
return !input_pos.is_ok_and(|pos| pos >= 0 && (pos as u64) >= file_size);
return input_pos.is_ok_and(|pos| pos >= 0 && (pos as u64) >= file_size);
}
let Ok(input_pos) = input_pos else {
return false;
return true;
};
let Ok(output_pos) = output_pos else {
return false;
return true;
};
input_pos < output_pos
input_pos >= output_pos
}

/// Whether the file is opened with the `O_APPEND` flag
Expand All @@ -54,7 +54,7 @@ fn is_appending<F: AsFd>(file: &F) -> bool {

#[cfg(test)]
mod tests {
use crate::platform::unix::{is_appending, is_unsafe_overwrite};
use crate::platform::unix::{is_appending, is_safe_overwrite};
use std::fs::OpenOptions;
use std::io::{Seek, SeekFrom, Write};
use tempfile::NamedTempFile;
Expand All @@ -75,38 +75,38 @@ mod tests {
}

#[test]
fn test_is_unsafe_overwrite() {
fn test_is_safe_overwrite() {
// Create two temp files one of which is empty
let empty = NamedTempFile::new().unwrap();
let mut nonempty = NamedTempFile::new().unwrap();
nonempty.write_all(b"anything").unwrap();
nonempty.seek(SeekFrom::Start(0)).unwrap();

// Using a different file as input and output does not result in an overwrite
assert!(!is_unsafe_overwrite(&empty, &nonempty));
assert!(is_safe_overwrite(&empty, &nonempty));

// Overwriting an empty file is always safe
assert!(!is_unsafe_overwrite(&empty, &empty));
assert!(is_safe_overwrite(&empty, &empty));

// Overwriting a nonempty file with itself is safe
assert!(!is_unsafe_overwrite(&nonempty, &nonempty));
assert!(is_safe_overwrite(&nonempty, &nonempty));

// Overwriting an empty file opened in append mode is safe
let empty_append = OpenOptions::new().append(true).open(&empty).unwrap();
assert!(!is_unsafe_overwrite(&empty, &empty_append));
assert!(is_safe_overwrite(&empty, &empty_append));

// Overwriting a nonempty file opened in append mode is unsafe
let nonempty_append = OpenOptions::new().append(true).open(&nonempty).unwrap();
assert!(is_unsafe_overwrite(&nonempty, &nonempty_append));
assert!(!is_safe_overwrite(&nonempty, &nonempty_append));

// Overwriting a file opened in write mode is safe
let mut nonempty_write = OpenOptions::new().write(true).open(&nonempty).unwrap();
assert!(!is_unsafe_overwrite(&nonempty, &nonempty_write));
assert!(is_safe_overwrite(&nonempty, &nonempty_write));

// Overwriting a file when the input and output file descriptors are pointing to
// different offsets is safe if the input offset is further than the output offset
nonempty_write.seek(SeekFrom::Start(1)).unwrap();
assert!(!is_unsafe_overwrite(&nonempty_write, &nonempty));
assert!(is_unsafe_overwrite(&nonempty, &nonempty_write));
assert!(is_safe_overwrite(&nonempty_write, &nonempty));
assert!(!is_safe_overwrite(&nonempty, &nonempty_write));
}
}
6 changes: 3 additions & 3 deletions src/uu/cat/src/platform/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ use windows_sys::Win32::Storage::FileSystem::{

/// An unsafe overwrite occurs when the same file is used as both stdin and stdout
/// and the stdout file is not empty.
pub fn is_unsafe_overwrite<I: AsHandleRef, O: AsHandleRef>(input: &I, output: &O) -> bool {
is_same_file_by_path(input, output) &&
pub fn is_safe_overwrite<I: AsHandleRef, O: AsHandleRef>(input: &I, output: &O) -> bool {
!is_same_file_by_path(input, output) ||
// Check if the output file is empty
FileInformation::from_file(output).is_ok_and(|info| info.file_size() > 0)
!FileInformation::from_file(output).is_ok_and(|info| info.file_size() > 0)
}

/// Get the file path for a file handle
Expand Down
Loading