mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge pull request #7061 from DaringCuteSeal/cp-stream-2
cp: implement copying from streams
This commit is contained in:
commit
74d80eab0a
7 changed files with 189 additions and 50 deletions
|
@ -28,6 +28,7 @@ quick-error = { workspace = true }
|
|||
selinux = { workspace = true, optional = true }
|
||||
uucore = { workspace = true, features = [
|
||||
"backup-control",
|
||||
"buf-copy",
|
||||
"entries",
|
||||
"fs",
|
||||
"fsxattr",
|
||||
|
|
|
@ -10,7 +10,7 @@ use std::collections::{HashMap, HashSet};
|
|||
#[cfg(not(windows))]
|
||||
use std::ffi::CString;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::{self, File, Metadata, OpenOptions, Permissions};
|
||||
use std::fs::{self, Metadata, OpenOptions, Permissions};
|
||||
use std::io;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
@ -1963,6 +1963,7 @@ fn print_paths(parents: bool, source: &Path, dest: &Path) {
|
|||
///
|
||||
/// * `Ok(())` - The file was copied successfully.
|
||||
/// * `Err(CopyError)` - An error occurred while copying the file.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn handle_copy_mode(
|
||||
source: &Path,
|
||||
dest: &Path,
|
||||
|
@ -1971,15 +1972,10 @@ fn handle_copy_mode(
|
|||
source_metadata: &Metadata,
|
||||
symlinked_files: &mut HashSet<FileInformation>,
|
||||
source_in_command_line: bool,
|
||||
source_is_fifo: bool,
|
||||
#[cfg(unix)] source_is_stream: bool,
|
||||
) -> CopyResult<()> {
|
||||
let source_file_type = source_metadata.file_type();
|
||||
|
||||
let source_is_symlink = source_file_type.is_symlink();
|
||||
|
||||
#[cfg(unix)]
|
||||
let source_is_fifo = source_file_type.is_fifo();
|
||||
#[cfg(not(unix))]
|
||||
let source_is_fifo = false;
|
||||
let source_is_symlink = source_metadata.is_symlink();
|
||||
|
||||
match options.copy_mode {
|
||||
CopyMode::Link => {
|
||||
|
@ -2016,6 +2012,8 @@ fn handle_copy_mode(
|
|||
source_is_symlink,
|
||||
source_is_fifo,
|
||||
symlinked_files,
|
||||
#[cfg(unix)]
|
||||
source_is_stream,
|
||||
)?;
|
||||
}
|
||||
CopyMode::SymLink => {
|
||||
|
@ -2036,6 +2034,8 @@ fn handle_copy_mode(
|
|||
source_is_symlink,
|
||||
source_is_fifo,
|
||||
symlinked_files,
|
||||
#[cfg(unix)]
|
||||
source_is_stream,
|
||||
)?;
|
||||
}
|
||||
update_control::UpdateMode::ReplaceNone => {
|
||||
|
@ -2066,6 +2066,8 @@ fn handle_copy_mode(
|
|||
source_is_symlink,
|
||||
source_is_fifo,
|
||||
symlinked_files,
|
||||
#[cfg(unix)]
|
||||
source_is_stream,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
@ -2079,6 +2081,8 @@ fn handle_copy_mode(
|
|||
source_is_symlink,
|
||||
source_is_fifo,
|
||||
symlinked_files,
|
||||
#[cfg(unix)]
|
||||
source_is_stream,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
@ -2305,6 +2309,18 @@ fn copy_file(
|
|||
|
||||
let dest_permissions = calculate_dest_permissions(dest, &source_metadata, options, context)?;
|
||||
|
||||
#[cfg(unix)]
|
||||
let source_is_fifo = source_metadata.file_type().is_fifo();
|
||||
#[cfg(not(unix))]
|
||||
let source_is_fifo = false;
|
||||
|
||||
#[cfg(unix)]
|
||||
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;
|
||||
|
||||
handle_copy_mode(
|
||||
source,
|
||||
dest,
|
||||
|
@ -2313,6 +2329,9 @@ fn copy_file(
|
|||
&source_metadata,
|
||||
symlinked_files,
|
||||
source_in_command_line,
|
||||
source_is_fifo,
|
||||
#[cfg(unix)]
|
||||
source_is_stream,
|
||||
)?;
|
||||
|
||||
// TODO: implement something similar to gnu's lchown
|
||||
|
@ -2328,8 +2347,16 @@ fn copy_file(
|
|||
|
||||
if options.dereference(source_in_command_line) {
|
||||
if let Ok(src) = canonicalize(source, MissingHandling::Normal, ResolveMode::Physical) {
|
||||
if src.exists() {
|
||||
copy_attributes(&src, dest, &options.attributes)?;
|
||||
}
|
||||
}
|
||||
} else if source_is_stream && source.exists() {
|
||||
// Some stream files may not exist after we have copied it,
|
||||
// like anonymous pipes. Thus, we can't really copy its
|
||||
// attributes. However, this is already handled in the stream
|
||||
// copy function (see `copy_stream` under platform/linux.rs).
|
||||
copy_attributes(source, dest, &options.attributes)?;
|
||||
} else {
|
||||
copy_attributes(source, dest, &options.attributes)?;
|
||||
}
|
||||
|
@ -2393,6 +2420,7 @@ fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 {
|
|||
|
||||
/// Copy the file from `source` to `dest` either using the normal `fs::copy` or a
|
||||
/// copy-on-write scheme if --reflink is specified and the filesystem supports it.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn copy_helper(
|
||||
source: &Path,
|
||||
dest: &Path,
|
||||
|
@ -2401,6 +2429,7 @@ fn copy_helper(
|
|||
source_is_symlink: bool,
|
||||
source_is_fifo: bool,
|
||||
symlinked_files: &mut HashSet<FileInformation>,
|
||||
#[cfg(unix)] source_is_stream: bool,
|
||||
) -> CopyResult<()> {
|
||||
if options.parents {
|
||||
let parent = dest.parent().unwrap_or(dest);
|
||||
|
@ -2411,12 +2440,7 @@ fn copy_helper(
|
|||
return Err(Error::NotADirectory(dest.to_path_buf()));
|
||||
}
|
||||
|
||||
if source.as_os_str() == "/dev/null" {
|
||||
/* workaround a limitation of fs::copy
|
||||
* https://github.com/rust-lang/rust/issues/79390
|
||||
*/
|
||||
File::create(dest).context(dest.display().to_string())?;
|
||||
} else if source_is_fifo && options.recursive && !options.copy_contents {
|
||||
if source_is_fifo && options.recursive && !options.copy_contents {
|
||||
#[cfg(unix)]
|
||||
copy_fifo(dest, options.overwrite, options.debug)?;
|
||||
} else if source_is_symlink {
|
||||
|
@ -2428,8 +2452,10 @@ fn copy_helper(
|
|||
options.reflink_mode,
|
||||
options.sparse_mode,
|
||||
context,
|
||||
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
|
||||
#[cfg(unix)]
|
||||
source_is_fifo,
|
||||
#[cfg(unix)]
|
||||
source_is_stream,
|
||||
)?;
|
||||
|
||||
if !options.attributes_only && options.debug {
|
||||
|
|
|
@ -12,6 +12,7 @@ use std::os::unix::fs::MetadataExt;
|
|||
use std::os::unix::fs::{FileTypeExt, OpenOptionsExt};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::Path;
|
||||
use uucore::buf_copy;
|
||||
|
||||
use quick_error::ResultExt;
|
||||
|
||||
|
@ -220,8 +221,9 @@ fn check_dest_is_fifo(dest: &Path) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// Copy the contents of the given source FIFO to the given file.
|
||||
fn copy_fifo_contents<P>(source: P, dest: P) -> std::io::Result<u64>
|
||||
/// Copy the contents of a stream from `source` to `dest`. The `if_fifo` argument is used to
|
||||
/// determine if we need to modify the file's attributes before and after copying.
|
||||
fn copy_stream<P>(source: P, dest: P, is_fifo: bool) -> std::io::Result<u64>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
@ -250,8 +252,14 @@ where
|
|||
.write(true)
|
||||
.mode(mode)
|
||||
.open(&dest)?;
|
||||
let num_bytes_copied = std::io::copy(&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))?;
|
||||
|
||||
if is_fifo {
|
||||
dst_file.set_permissions(src_file.metadata()?.permissions())?;
|
||||
}
|
||||
|
||||
Ok(num_bytes_copied)
|
||||
}
|
||||
|
||||
|
@ -268,6 +276,7 @@ pub(crate) fn copy_on_write(
|
|||
sparse_mode: SparseMode,
|
||||
context: &str,
|
||||
source_is_fifo: bool,
|
||||
source_is_stream: bool,
|
||||
) -> CopyResult<CopyDebug> {
|
||||
let mut copy_debug = CopyDebug {
|
||||
offload: OffloadReflinkDebug::Unknown,
|
||||
|
@ -279,10 +288,9 @@ pub(crate) fn copy_on_write(
|
|||
copy_debug.sparse_detection = SparseDebug::Zeros;
|
||||
// Default SparseDebug val for SparseMode::Always
|
||||
copy_debug.reflink = OffloadReflinkDebug::No;
|
||||
if source_is_fifo {
|
||||
if source_is_stream {
|
||||
copy_debug.offload = OffloadReflinkDebug::Avoided;
|
||||
|
||||
copy_fifo_contents(source, dest).map(|_| ())
|
||||
copy_stream(source, dest, source_is_fifo).map(|_| ())
|
||||
} else {
|
||||
let mut copy_method = CopyMethod::Default;
|
||||
let result = handle_reflink_never_sparse_always(source, dest);
|
||||
|
@ -300,10 +308,9 @@ pub(crate) fn copy_on_write(
|
|||
(ReflinkMode::Never, SparseMode::Never) => {
|
||||
copy_debug.reflink = OffloadReflinkDebug::No;
|
||||
|
||||
if source_is_fifo {
|
||||
if source_is_stream {
|
||||
copy_debug.offload = OffloadReflinkDebug::Avoided;
|
||||
|
||||
copy_fifo_contents(source, dest).map(|_| ())
|
||||
copy_stream(source, dest, source_is_fifo).map(|_| ())
|
||||
} else {
|
||||
let result = handle_reflink_never_sparse_never(source);
|
||||
if let Ok(debug) = result {
|
||||
|
@ -315,9 +322,9 @@ pub(crate) fn copy_on_write(
|
|||
(ReflinkMode::Never, SparseMode::Auto) => {
|
||||
copy_debug.reflink = OffloadReflinkDebug::No;
|
||||
|
||||
if source_is_fifo {
|
||||
if source_is_stream {
|
||||
copy_debug.offload = OffloadReflinkDebug::Avoided;
|
||||
copy_fifo_contents(source, dest).map(|_| ())
|
||||
copy_stream(source, dest, source_is_fifo).map(|_| ())
|
||||
} else {
|
||||
let mut copy_method = CopyMethod::Default;
|
||||
let result = handle_reflink_never_sparse_auto(source, dest);
|
||||
|
@ -335,10 +342,9 @@ pub(crate) fn copy_on_write(
|
|||
(ReflinkMode::Auto, SparseMode::Always) => {
|
||||
copy_debug.sparse_detection = SparseDebug::Zeros; // Default SparseDebug val for
|
||||
// SparseMode::Always
|
||||
if source_is_fifo {
|
||||
if source_is_stream {
|
||||
copy_debug.offload = OffloadReflinkDebug::Avoided;
|
||||
|
||||
copy_fifo_contents(source, dest).map(|_| ())
|
||||
copy_stream(source, dest, source_is_fifo).map(|_| ())
|
||||
} else {
|
||||
let mut copy_method = CopyMethod::Default;
|
||||
let result = handle_reflink_auto_sparse_always(source, dest);
|
||||
|
@ -356,9 +362,9 @@ pub(crate) fn copy_on_write(
|
|||
|
||||
(ReflinkMode::Auto, SparseMode::Never) => {
|
||||
copy_debug.reflink = OffloadReflinkDebug::No;
|
||||
if source_is_fifo {
|
||||
if source_is_stream {
|
||||
copy_debug.offload = OffloadReflinkDebug::Avoided;
|
||||
copy_fifo_contents(source, dest).map(|_| ())
|
||||
copy_stream(source, dest, source_is_fifo).map(|_| ())
|
||||
} else {
|
||||
let result = handle_reflink_auto_sparse_never(source);
|
||||
if let Ok(debug) = result {
|
||||
|
@ -369,9 +375,9 @@ pub(crate) fn copy_on_write(
|
|||
}
|
||||
}
|
||||
(ReflinkMode::Auto, SparseMode::Auto) => {
|
||||
if source_is_fifo {
|
||||
if source_is_stream {
|
||||
copy_debug.offload = OffloadReflinkDebug::Unsupported;
|
||||
copy_fifo_contents(source, dest).map(|_| ())
|
||||
copy_stream(source, dest, source_is_fifo).map(|_| ())
|
||||
} else {
|
||||
let mut copy_method = CopyMethod::Default;
|
||||
let result = handle_reflink_auto_sparse_auto(source, dest);
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore reflink
|
||||
use std::ffi::CString;
|
||||
use std::fs::{self, File};
|
||||
use std::io;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::path::Path;
|
||||
|
||||
use quick_error::ResultExt;
|
||||
use uucore::buf_copy;
|
||||
use uucore::mode::get_umask;
|
||||
|
||||
use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode};
|
||||
|
||||
|
@ -24,6 +26,7 @@ pub(crate) fn copy_on_write(
|
|||
sparse_mode: SparseMode,
|
||||
context: &str,
|
||||
source_is_fifo: bool,
|
||||
source_is_stream: bool,
|
||||
) -> CopyResult<CopyDebug> {
|
||||
if sparse_mode != SparseMode::Auto {
|
||||
return Err("--sparse is only supported on linux".to_string().into());
|
||||
|
@ -85,10 +88,23 @@ pub(crate) fn copy_on_write(
|
|||
}
|
||||
_ => {
|
||||
copy_debug.reflink = OffloadReflinkDebug::Yes;
|
||||
if source_is_fifo {
|
||||
if source_is_stream {
|
||||
let mut src_file = File::open(source)?;
|
||||
let mut dst_file = File::create(dest)?;
|
||||
io::copy(&mut src_file, &mut dst_file).context(context)?
|
||||
let mode = 0o622 & !get_umask();
|
||||
let mut dst_file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.mode(mode)
|
||||
.open(dest)?;
|
||||
|
||||
let context = buf_copy::copy_stream(&mut src_file, &mut dst_file)
|
||||
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))
|
||||
.context(context)?;
|
||||
|
||||
if source_is_fifo {
|
||||
dst_file.set_permissions(src_file.metadata()?.permissions())?;
|
||||
}
|
||||
context
|
||||
} else {
|
||||
fs::copy(source, dest).context(context)?
|
||||
}
|
||||
|
|
|
@ -2,6 +2,18 @@
|
|||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
#[cfg(all(
|
||||
unix,
|
||||
not(any(target_os = "macos", target_os = "linux", target_os = "android"))
|
||||
))]
|
||||
mod other_unix;
|
||||
#[cfg(all(
|
||||
unix,
|
||||
not(any(target_os = "macos", target_os = "linux", target_os = "android"))
|
||||
))]
|
||||
pub(crate) use self::other_unix::copy_on_write;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
#[cfg(target_os = "macos")]
|
||||
|
@ -12,7 +24,13 @@ mod linux;
|
|||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub(crate) use self::linux::copy_on_write;
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
|
||||
#[cfg(not(any(
|
||||
unix,
|
||||
any(target_os = "macos", target_os = "linux", target_os = "android")
|
||||
)))]
|
||||
mod other;
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
|
||||
#[cfg(not(any(
|
||||
unix,
|
||||
any(target_os = "macos", target_os = "linux", target_os = "android")
|
||||
)))]
|
||||
pub(crate) use self::other::copy_on_write;
|
||||
|
|
62
src/uu/cp/src/platform/other_unix.rs
Normal file
62
src/uu/cp/src/platform/other_unix.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore reflink
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::path::Path;
|
||||
|
||||
use quick_error::ResultExt;
|
||||
use uucore::buf_copy;
|
||||
use uucore::mode::get_umask;
|
||||
|
||||
use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode};
|
||||
|
||||
/// Copies `source` to `dest` for systems without copy-on-write
|
||||
pub(crate) fn copy_on_write(
|
||||
source: &Path,
|
||||
dest: &Path,
|
||||
reflink_mode: ReflinkMode,
|
||||
sparse_mode: SparseMode,
|
||||
context: &str,
|
||||
source_is_fifo: bool,
|
||||
source_is_stream: bool,
|
||||
) -> CopyResult<CopyDebug> {
|
||||
if reflink_mode != ReflinkMode::Never {
|
||||
return Err("--reflink is only supported on linux and macOS"
|
||||
.to_string()
|
||||
.into());
|
||||
}
|
||||
if sparse_mode != SparseMode::Auto {
|
||||
return Err("--sparse is only supported on linux".to_string().into());
|
||||
}
|
||||
let copy_debug = CopyDebug {
|
||||
offload: OffloadReflinkDebug::Unsupported,
|
||||
reflink: OffloadReflinkDebug::Unsupported,
|
||||
sparse_detection: SparseDebug::Unsupported,
|
||||
};
|
||||
|
||||
if source_is_stream {
|
||||
let mut src_file = File::open(source)?;
|
||||
let mode = 0o622 & !get_umask();
|
||||
let mut dst_file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.mode(mode)
|
||||
.open(dest)?;
|
||||
|
||||
buf_copy::copy_stream(&mut src_file, &mut dst_file)
|
||||
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))
|
||||
.context(context)?;
|
||||
|
||||
if source_is_fifo {
|
||||
dst_file.set_permissions(src_file.metadata()?.permissions())?;
|
||||
}
|
||||
return Ok(copy_debug);
|
||||
}
|
||||
|
||||
fs::copy(source, dest).context(context)?;
|
||||
|
||||
Ok(copy_debug)
|
||||
}
|
|
@ -3471,15 +3471,9 @@ fn test_same_file_force_backup() {
|
|||
}
|
||||
|
||||
/// Test for copying the contents of a FIFO as opposed to the FIFO object itself.
|
||||
#[cfg(all(unix, not(target_os = "freebsd"), not(target_os = "openbsd")))]
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_copy_contents_fifo() {
|
||||
// TODO this test should work on FreeBSD, but the command was
|
||||
// causing an error:
|
||||
//
|
||||
// cp: 'fifo' -> 'outfile': the source path is neither a regular file nor a symlink to a regular file
|
||||
//
|
||||
// the underlying `std::fs:copy` doesn't support copying fifo on freeBSD
|
||||
let scenario = TestScenario::new(util_name!());
|
||||
let at = &scenario.fixtures;
|
||||
|
||||
|
@ -6037,3 +6031,19 @@ fn test_cp_preserve_xattr_readonly_source() {
|
|||
"Extended attributes were not preserved"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_cp_from_stdin() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let target = "target";
|
||||
let test_string = "Hello, World!\n";
|
||||
|
||||
ucmd.arg("/dev/fd/0")
|
||||
.arg(target)
|
||||
.pipe_in(test_string)
|
||||
.succeeds();
|
||||
|
||||
assert!(at.file_exists(target));
|
||||
assert_eq!(at.read(target), test_string);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue