Skip to content

Commit

Permalink
Merge pull request #7061 from DaringCuteSeal/cp-stream-2
Browse files Browse the repository at this point in the history
cp: implement copying from streams
  • Loading branch information
sylvestre authored Jan 13, 2025
2 parents 40511f6 + c6d1923 commit 74d80ea
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 50 deletions.
1 change: 1 addition & 0 deletions src/uu/cp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ quick-error = { workspace = true }
selinux = { workspace = true, optional = true }
uucore = { workspace = true, features = [
"backup-control",
"buf-copy",
"entries",
"fs",
"fsxattr",
Expand Down
60 changes: 43 additions & 17 deletions src/uu/cp/src/cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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 => {
Expand Down Expand Up @@ -2016,6 +2012,8 @@ fn handle_copy_mode(
source_is_symlink,
source_is_fifo,
symlinked_files,
#[cfg(unix)]
source_is_stream,
)?;
}
CopyMode::SymLink => {
Expand All @@ -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 => {
Expand Down Expand Up @@ -2066,6 +2066,8 @@ fn handle_copy_mode(
source_is_symlink,
source_is_fifo,
symlinked_files,
#[cfg(unix)]
source_is_stream,
)?;
}
}
Expand All @@ -2079,6 +2081,8 @@ fn handle_copy_mode(
source_is_symlink,
source_is_fifo,
symlinked_files,
#[cfg(unix)]
source_is_stream,
)?;
}
}
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -2328,8 +2347,16 @@ fn copy_file(

if options.dereference(source_in_command_line) {
if let Ok(src) = canonicalize(source, MissingHandling::Normal, ResolveMode::Physical) {
copy_attributes(&src, dest, &options.attributes)?;
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)?;
}
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
44 changes: 25 additions & 19 deletions src/uu/cp/src/platform/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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>,
{
Expand Down Expand Up @@ -250,8 +252,14 @@ where
.write(true)
.mode(mode)
.open(&dest)?;
let num_bytes_copied = std::io::copy(&mut src_file, &mut dst_file)?;
dst_file.set_permissions(src_file.metadata()?.permissions())?;

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)
}

Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -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 {
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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 {
Expand All @@ -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);
Expand Down
26 changes: 21 additions & 5 deletions src/uu/cp/src/platform/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -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());
Expand Down Expand Up @@ -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)?
}
Expand Down
22 changes: 20 additions & 2 deletions src/uu/cp/src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -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;
Loading

0 comments on commit 74d80ea

Please sign in to comment.