mirror of
https://github.com/uutils/coreutils.git
synced 2025-07-07 21:45:01 +00:00
cp: clean existing file when copy from stream (#8149)
Some checks are pending
CICD / Build (push) Blocked by required conditions
CICD / Tests/BusyBox test suite (push) Blocked by required conditions
CICD / Build/SELinux (push) Blocked by required conditions
CICD / Style/cargo-deny (push) Waiting to run
CICD / Style/deps (push) Waiting to run
CICD / Test all features separately (push) Blocked by required conditions
CICD / Documentation/warnings (push) Waiting to run
CICD / MinRustV (push) Waiting to run
CICD / Dependencies (push) Waiting to run
CICD / Build/Makefile (push) Blocked by required conditions
CICD / Build/stable (push) Blocked by required conditions
CICD / Build/nightly (push) Blocked by required conditions
CICD / Binary sizes (push) Blocked by required conditions
CICD / Tests/Toybox test suite (push) Blocked by required conditions
CICD / Code Coverage (push) Waiting to run
CICD / Separate Builds (push) Waiting to run
Android / Test builds (push) Waiting to run
Code Quality / Style/toml (push) Waiting to run
Code Quality / Style/Python (push) Waiting to run
Code Quality / Style/format (push) Waiting to run
Code Quality / Style/lint (push) Waiting to run
Code Quality / Style/spelling (push) Waiting to run
Code Quality / Pre-commit hooks (push) Waiting to run
FreeBSD / Style and Lint (push) Waiting to run
FreeBSD / Tests (push) Waiting to run
GnuTests / Run GNU tests (push) Waiting to run
Some checks are pending
CICD / Build (push) Blocked by required conditions
CICD / Tests/BusyBox test suite (push) Blocked by required conditions
CICD / Build/SELinux (push) Blocked by required conditions
CICD / Style/cargo-deny (push) Waiting to run
CICD / Style/deps (push) Waiting to run
CICD / Test all features separately (push) Blocked by required conditions
CICD / Documentation/warnings (push) Waiting to run
CICD / MinRustV (push) Waiting to run
CICD / Dependencies (push) Waiting to run
CICD / Build/Makefile (push) Blocked by required conditions
CICD / Build/stable (push) Blocked by required conditions
CICD / Build/nightly (push) Blocked by required conditions
CICD / Binary sizes (push) Blocked by required conditions
CICD / Tests/Toybox test suite (push) Blocked by required conditions
CICD / Code Coverage (push) Waiting to run
CICD / Separate Builds (push) Waiting to run
Android / Test builds (push) Waiting to run
Code Quality / Style/toml (push) Waiting to run
Code Quality / Style/Python (push) Waiting to run
Code Quality / Style/format (push) Waiting to run
Code Quality / Style/lint (push) Waiting to run
Code Quality / Style/spelling (push) Waiting to run
Code Quality / Pre-commit hooks (push) Waiting to run
FreeBSD / Style and Lint (push) Waiting to run
FreeBSD / Tests (push) Waiting to run
GnuTests / Run GNU tests (push) Waiting to run
* cp: clean existing file when copy from stream * cp: remove outdated comments --------- Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>
This commit is contained in:
parent
f74cceabc7
commit
69bc6a791e
5 changed files with 94 additions and 42 deletions
|
@ -2545,12 +2545,7 @@ fn copy_file(
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
let source_is_fifo = false;
|
let source_is_fifo = false;
|
||||||
|
|
||||||
#[cfg(unix)]
|
let source_is_stream = is_stream(&source_metadata);
|
||||||
let source_is_stream = source_is_fifo
|
|
||||||
|| source_metadata.file_type().is_char_device()
|
|
||||||
|| source_metadata.file_type().is_block_device();
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
let source_is_stream = false;
|
|
||||||
|
|
||||||
let performed_action = handle_copy_mode(
|
let performed_action = handle_copy_mode(
|
||||||
source,
|
source,
|
||||||
|
@ -2620,6 +2615,19 @@ fn copy_file(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_stream(metadata: &Metadata) -> bool {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
let file_type = metadata.file_type();
|
||||||
|
file_type.is_fifo() || file_type.is_char_device() || file_type.is_block_device()
|
||||||
|
}
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
{
|
||||||
|
let _ = metadata;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 {
|
fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 {
|
||||||
let (is_preserve_mode, is_explicit_no_preserve_mode) = options.preserve_mode();
|
let (is_preserve_mode, is_explicit_no_preserve_mode) = options.preserve_mode();
|
||||||
|
@ -2700,8 +2708,6 @@ fn copy_helper(
|
||||||
options.sparse_mode,
|
options.sparse_mode,
|
||||||
context,
|
context,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
source_is_fifo,
|
|
||||||
#[cfg(unix)]
|
|
||||||
source_is_stream,
|
source_is_stream,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ use uucore::mode::get_umask;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
CopyDebug, CopyResult, CpError, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode,
|
CopyDebug, CopyResult, CpError, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode,
|
||||||
|
is_stream,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The fallback behavior for [`clone`] on failed system call.
|
/// The fallback behavior for [`clone`] on failed system call.
|
||||||
|
@ -220,9 +221,8 @@ fn check_dest_is_fifo(dest: &Path) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copy the contents of a stream from `source` to `dest`. The `if_fifo` argument is used to
|
/// Copy the contents of a stream from `source` to `dest`.
|
||||||
/// determine if we need to modify the file's attributes before and after copying.
|
fn copy_stream<P>(source: P, dest: P) -> std::io::Result<u64>
|
||||||
fn copy_stream<P>(source: P, dest: P, is_fifo: bool) -> std::io::Result<u64>
|
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
|
@ -252,29 +252,25 @@ where
|
||||||
.mode(mode)
|
.mode(mode)
|
||||||
.open(&dest)?;
|
.open(&dest)?;
|
||||||
|
|
||||||
|
let dest_is_stream = is_stream(&dst_file.metadata()?);
|
||||||
|
if !dest_is_stream {
|
||||||
|
// `copy_stream` doesn't clear the dest file, if dest is not a stream, we should clear it manually.
|
||||||
|
dst_file.set_len(0)?;
|
||||||
|
}
|
||||||
|
|
||||||
let num_bytes_copied = buf_copy::copy_stream(&mut src_file, &mut dst_file)
|
let num_bytes_copied = buf_copy::copy_stream(&mut src_file, &mut dst_file)
|
||||||
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))?;
|
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))?;
|
||||||
|
|
||||||
if is_fifo {
|
|
||||||
dst_file.set_permissions(src_file.metadata()?.permissions())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(num_bytes_copied)
|
Ok(num_bytes_copied)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copies `source` to `dest` using copy-on-write if possible.
|
/// Copies `source` to `dest` using copy-on-write if possible.
|
||||||
///
|
|
||||||
/// The `source_is_fifo` flag must be set to `true` if and only if
|
|
||||||
/// `source` is a FIFO (also known as a named pipe). In this case,
|
|
||||||
/// copy-on-write is not possible, so we copy the contents using
|
|
||||||
/// [`std::io::copy`].
|
|
||||||
pub(crate) fn copy_on_write(
|
pub(crate) fn copy_on_write(
|
||||||
source: &Path,
|
source: &Path,
|
||||||
dest: &Path,
|
dest: &Path,
|
||||||
reflink_mode: ReflinkMode,
|
reflink_mode: ReflinkMode,
|
||||||
sparse_mode: SparseMode,
|
sparse_mode: SparseMode,
|
||||||
context: &str,
|
context: &str,
|
||||||
source_is_fifo: bool,
|
|
||||||
source_is_stream: bool,
|
source_is_stream: bool,
|
||||||
) -> CopyResult<CopyDebug> {
|
) -> CopyResult<CopyDebug> {
|
||||||
let mut copy_debug = CopyDebug {
|
let mut copy_debug = CopyDebug {
|
||||||
|
@ -289,7 +285,7 @@ pub(crate) fn copy_on_write(
|
||||||
copy_debug.reflink = OffloadReflinkDebug::No;
|
copy_debug.reflink = OffloadReflinkDebug::No;
|
||||||
if source_is_stream {
|
if source_is_stream {
|
||||||
copy_debug.offload = OffloadReflinkDebug::Avoided;
|
copy_debug.offload = OffloadReflinkDebug::Avoided;
|
||||||
copy_stream(source, dest, source_is_fifo).map(|_| ())
|
copy_stream(source, dest).map(|_| ())
|
||||||
} else {
|
} else {
|
||||||
let mut copy_method = CopyMethod::Default;
|
let mut copy_method = CopyMethod::Default;
|
||||||
let result = handle_reflink_never_sparse_always(source, dest);
|
let result = handle_reflink_never_sparse_always(source, dest);
|
||||||
|
@ -309,7 +305,7 @@ pub(crate) fn copy_on_write(
|
||||||
|
|
||||||
if source_is_stream {
|
if source_is_stream {
|
||||||
copy_debug.offload = OffloadReflinkDebug::Avoided;
|
copy_debug.offload = OffloadReflinkDebug::Avoided;
|
||||||
copy_stream(source, dest, source_is_fifo).map(|_| ())
|
copy_stream(source, dest).map(|_| ())
|
||||||
} else {
|
} else {
|
||||||
let result = handle_reflink_never_sparse_never(source);
|
let result = handle_reflink_never_sparse_never(source);
|
||||||
if let Ok(debug) = result {
|
if let Ok(debug) = result {
|
||||||
|
@ -323,7 +319,7 @@ pub(crate) fn copy_on_write(
|
||||||
|
|
||||||
if source_is_stream {
|
if source_is_stream {
|
||||||
copy_debug.offload = OffloadReflinkDebug::Avoided;
|
copy_debug.offload = OffloadReflinkDebug::Avoided;
|
||||||
copy_stream(source, dest, source_is_fifo).map(|_| ())
|
copy_stream(source, dest).map(|_| ())
|
||||||
} else {
|
} else {
|
||||||
let mut copy_method = CopyMethod::Default;
|
let mut copy_method = CopyMethod::Default;
|
||||||
let result = handle_reflink_never_sparse_auto(source, dest);
|
let result = handle_reflink_never_sparse_auto(source, dest);
|
||||||
|
@ -343,7 +339,7 @@ pub(crate) fn copy_on_write(
|
||||||
// SparseMode::Always
|
// SparseMode::Always
|
||||||
if source_is_stream {
|
if source_is_stream {
|
||||||
copy_debug.offload = OffloadReflinkDebug::Avoided;
|
copy_debug.offload = OffloadReflinkDebug::Avoided;
|
||||||
copy_stream(source, dest, source_is_fifo).map(|_| ())
|
copy_stream(source, dest).map(|_| ())
|
||||||
} else {
|
} else {
|
||||||
let mut copy_method = CopyMethod::Default;
|
let mut copy_method = CopyMethod::Default;
|
||||||
let result = handle_reflink_auto_sparse_always(source, dest);
|
let result = handle_reflink_auto_sparse_always(source, dest);
|
||||||
|
@ -363,7 +359,7 @@ pub(crate) fn copy_on_write(
|
||||||
copy_debug.reflink = OffloadReflinkDebug::No;
|
copy_debug.reflink = OffloadReflinkDebug::No;
|
||||||
if source_is_stream {
|
if source_is_stream {
|
||||||
copy_debug.offload = OffloadReflinkDebug::Avoided;
|
copy_debug.offload = OffloadReflinkDebug::Avoided;
|
||||||
copy_stream(source, dest, source_is_fifo).map(|_| ())
|
copy_stream(source, dest).map(|_| ())
|
||||||
} else {
|
} else {
|
||||||
let result = handle_reflink_auto_sparse_never(source);
|
let result = handle_reflink_auto_sparse_never(source);
|
||||||
if let Ok(debug) = result {
|
if let Ok(debug) = result {
|
||||||
|
@ -376,7 +372,7 @@ pub(crate) fn copy_on_write(
|
||||||
(ReflinkMode::Auto, SparseMode::Auto) => {
|
(ReflinkMode::Auto, SparseMode::Auto) => {
|
||||||
if source_is_stream {
|
if source_is_stream {
|
||||||
copy_debug.offload = OffloadReflinkDebug::Unsupported;
|
copy_debug.offload = OffloadReflinkDebug::Unsupported;
|
||||||
copy_stream(source, dest, source_is_fifo).map(|_| ())
|
copy_stream(source, dest).map(|_| ())
|
||||||
} else {
|
} else {
|
||||||
let mut copy_method = CopyMethod::Default;
|
let mut copy_method = CopyMethod::Default;
|
||||||
let result = handle_reflink_auto_sparse_auto(source, dest);
|
let result = handle_reflink_auto_sparse_auto(source, dest);
|
||||||
|
|
|
@ -16,19 +16,16 @@ use uucore::mode::get_umask;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
CopyDebug, CopyResult, CpError, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode,
|
CopyDebug, CopyResult, CpError, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode,
|
||||||
|
is_stream,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Copies `source` to `dest` using copy-on-write if possible.
|
/// Copies `source` to `dest` using copy-on-write if possible.
|
||||||
///
|
|
||||||
/// The `source_is_fifo` flag must be set to `true` if and only if
|
|
||||||
/// `source` is a FIFO (also known as a named pipe).
|
|
||||||
pub(crate) fn copy_on_write(
|
pub(crate) fn copy_on_write(
|
||||||
source: &Path,
|
source: &Path,
|
||||||
dest: &Path,
|
dest: &Path,
|
||||||
reflink_mode: ReflinkMode,
|
reflink_mode: ReflinkMode,
|
||||||
sparse_mode: SparseMode,
|
sparse_mode: SparseMode,
|
||||||
context: &str,
|
context: &str,
|
||||||
source_is_fifo: bool,
|
|
||||||
source_is_stream: bool,
|
source_is_stream: bool,
|
||||||
) -> CopyResult<CopyDebug> {
|
) -> CopyResult<CopyDebug> {
|
||||||
if sparse_mode != SparseMode::Auto {
|
if sparse_mode != SparseMode::Auto {
|
||||||
|
@ -110,14 +107,15 @@ pub(crate) fn copy_on_write(
|
||||||
.mode(mode)
|
.mode(mode)
|
||||||
.open(dest)?;
|
.open(dest)?;
|
||||||
|
|
||||||
let context = buf_copy::copy_stream(&mut src_file, &mut dst_file)
|
let dest_is_stream = is_stream(&dst_file.metadata()?);
|
||||||
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))
|
if !dest_is_stream {
|
||||||
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
|
// `copy_stream` doesn't clear the dest file, if dest is not a stream, we should clear it manually.
|
||||||
|
dst_file.set_len(0)?;
|
||||||
if source_is_fifo {
|
|
||||||
dst_file.set_permissions(src_file.metadata()?.permissions())?;
|
|
||||||
}
|
}
|
||||||
context
|
|
||||||
|
buf_copy::copy_stream(&mut src_file, &mut dst_file)
|
||||||
|
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))
|
||||||
|
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?
|
||||||
} else {
|
} else {
|
||||||
fs::copy(source, dest)
|
fs::copy(source, dest)
|
||||||
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?
|
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?
|
||||||
|
|
|
@ -13,6 +13,7 @@ use uucore::mode::get_umask;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
CopyDebug, CopyResult, CpError, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode,
|
CopyDebug, CopyResult, CpError, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode,
|
||||||
|
is_stream,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Copies `source` to `dest` for systems without copy-on-write
|
/// Copies `source` to `dest` for systems without copy-on-write
|
||||||
|
@ -22,7 +23,6 @@ pub(crate) fn copy_on_write(
|
||||||
reflink_mode: ReflinkMode,
|
reflink_mode: ReflinkMode,
|
||||||
sparse_mode: SparseMode,
|
sparse_mode: SparseMode,
|
||||||
context: &str,
|
context: &str,
|
||||||
source_is_fifo: bool,
|
|
||||||
source_is_stream: bool,
|
source_is_stream: bool,
|
||||||
) -> CopyResult<CopyDebug> {
|
) -> CopyResult<CopyDebug> {
|
||||||
if reflink_mode != ReflinkMode::Never {
|
if reflink_mode != ReflinkMode::Never {
|
||||||
|
@ -50,13 +50,16 @@ pub(crate) fn copy_on_write(
|
||||||
.mode(mode)
|
.mode(mode)
|
||||||
.open(dest)?;
|
.open(dest)?;
|
||||||
|
|
||||||
|
let dest_is_stream = is_stream(&dst_file.metadata()?);
|
||||||
|
if !dest_is_stream {
|
||||||
|
// `copy_stream` doesn't clear the dest file, if dest is not a stream, we should clear it manually.
|
||||||
|
dst_file.set_len(0)?;
|
||||||
|
}
|
||||||
|
|
||||||
buf_copy::copy_stream(&mut src_file, &mut dst_file)
|
buf_copy::copy_stream(&mut src_file, &mut dst_file)
|
||||||
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))
|
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))
|
||||||
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
|
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
|
||||||
|
|
||||||
if source_is_fifo {
|
|
||||||
dst_file.set_permissions(src_file.metadata()?.permissions())?;
|
|
||||||
}
|
|
||||||
return Ok(copy_debug);
|
return Ok(copy_debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6247,6 +6247,55 @@ fn test_cp_update_none_interactive_prompt_no() {
|
||||||
assert_eq!(at.read(new_file), "new content");
|
assert_eq!(at.read(new_file), "new content");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// only unix has `/dev/fd/0`
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn test_cp_from_stream() {
|
||||||
|
let target = "target";
|
||||||
|
let test_string1 = "longer: Hello, World!\n";
|
||||||
|
let test_string2 = "shorter";
|
||||||
|
let scenario = TestScenario::new(util_name!());
|
||||||
|
let at = &scenario.fixtures;
|
||||||
|
at.touch(target);
|
||||||
|
|
||||||
|
let mut ucmd = scenario.ucmd();
|
||||||
|
ucmd.arg("/dev/fd/0")
|
||||||
|
.arg(target)
|
||||||
|
.pipe_in(test_string1)
|
||||||
|
.succeeds();
|
||||||
|
assert_eq!(at.read(target), test_string1);
|
||||||
|
|
||||||
|
let mut ucmd = scenario.ucmd();
|
||||||
|
ucmd.arg("/dev/fd/0")
|
||||||
|
.arg(target)
|
||||||
|
.pipe_in(test_string2)
|
||||||
|
.succeeds();
|
||||||
|
assert_eq!(at.read(target), test_string2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// only unix has `/dev/fd/0`
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn test_cp_from_stream_permission() {
|
||||||
|
let target = "target";
|
||||||
|
let link = "link";
|
||||||
|
let test_string = "Hello, World!\n";
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
at.touch(target);
|
||||||
|
at.symlink_file(target, link);
|
||||||
|
let mode = 0o777;
|
||||||
|
at.set_mode("target", mode);
|
||||||
|
|
||||||
|
ucmd.arg("/dev/fd/0")
|
||||||
|
.arg(link)
|
||||||
|
.pipe_in(test_string)
|
||||||
|
.succeeds();
|
||||||
|
|
||||||
|
assert_eq!(at.read(target), test_string);
|
||||||
|
assert_eq!(at.metadata(target).permissions().mode(), 0o100_777);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "feat_selinux")]
|
#[cfg(feature = "feat_selinux")]
|
||||||
fn get_getfattr_output(f: &str) -> String {
|
fn get_getfattr_output(f: &str) -> String {
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue