Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions src/uu/cp/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ cp-error-selinux-error = SELinux error: { $error }
cp-error-selinux-context-conflict = cannot combine --context (-Z) with --preserve=context
cp-error-cannot-create-fifo = cannot create fifo { $path }: File exists
cp-error-cannot-create-special-file = cannot create special file { $path }: { $error }
cp-error-cannot-create-regular-file = cannot create regular file { $path }
cp-error-invalid-attribute = invalid attribute { $value }
cp-error-failed-to-create-whole-tree = failed to create whole tree
cp-error-failed-to-create-directory = Failed to create directory: { $error }
Expand Down
1 change: 1 addition & 0 deletions src/uu/cp/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ cp-error-selinux-error = Erreur SELinux : { $error }
cp-error-selinux-context-conflict = impossible de combiner --context (-Z) avec --preserve=context
cp-error-cannot-create-fifo = impossible de créer le fifo { $path } : Le fichier existe
cp-error-cannot-create-special-file = impossible de créer le fichier spécial { $path } : { $error }
cp-error-cannot-create-regular-file = impossible de créer le fichier standard { $path }
cp-error-invalid-attribute = attribut invalide { $value }
cp-error-failed-to-create-whole-tree = échec de la création de l'arborescence complète
cp-error-failed-to-create-directory = Échec de la création du répertoire : { $error }
Expand Down
10 changes: 9 additions & 1 deletion src/uu/cp/src/cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2431,7 +2431,15 @@ fn handle_copy_mode(
.truncate(false)
.create(true)
.open(dest)
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
.map_err(|e| {
CpError::IoErrContext(
e,
translate!(
"cp-error-cannot-create-regular-file",
"path" => dest.quote()
),
)
})?;
}
}

Expand Down
178 changes: 123 additions & 55 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::path::Path;

use uucore::buf_copy;
use uucore::display::Quotable;
use uucore::safe_copy::{create_dest_restrictive, open_source};
use uucore::translate;

Expand All @@ -24,14 +25,23 @@ use crate::{
// only applies `O_NOFOLLOW` to the *source* open. The destination is
// followed if it is a pre-existing symlink, matching GNU cp -d/-P which
// only forbid dereferencing on the source side.
fn fs_copy<P, Q>(source: P, dest: Q, source_nofollow: bool) -> std::io::Result<()>
fn fs_copy<P, Q>(source: P, dest: Q, source_nofollow: bool, context: &str) -> CopyResult<()>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let mut src = open_source(source, source_nofollow)?;
let mut dst = create_dest_restrictive(dest, false)?;
buf_copy::copy_fast(&mut src, &mut dst)?;
let mut src = open_source(source, source_nofollow)
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
let mut dst = create_dest_restrictive(&dest, false).map_err(|e| {
CpError::IoErrContext(
e,
translate!("cp-error-cannot-create-regular-file", "path" => dest.as_ref().quote()),
)
})?;
if ioctl_ficlone(&dst, &src).is_err() {
Comment thread
BAMF0 marked this conversation as resolved.
Outdated
buf_copy::copy_fast(&mut src, &mut dst)
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
}
Ok(())
}

Expand Down Expand Up @@ -67,19 +77,34 @@ enum CopyMethod {
/// Use the Linux `ioctl_ficlone` API to do a copy-on-write clone.
///
/// `fallback` controls what to do if the system call fails.
fn clone<P>(source: P, dest: P, fallback: CloneFallback, nofollow: bool) -> std::io::Result<()>
fn clone<P>(
source: P,
dest: P,
fallback: CloneFallback,
nofollow: bool,
context: &str,
) -> CopyResult<()>
where
P: AsRef<Path>,
{
let src_file = open_source(&source, nofollow)?;
let dst_file = create_dest_restrictive(&dest, false)?;
let src_file =
open_source(&source, nofollow).map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
let dst_file = create_dest_restrictive(&dest, false).map_err(|e| {
CpError::IoErrContext(
e,
translate!("cp-error-cannot-create-regular-file", "path" => dest.as_ref().quote()),
)
})?;
if ioctl_ficlone(dst_file, src_file).is_err() {
return match fallback {
CloneFallback::Error => Err(std::io::Error::last_os_error()),
CloneFallback::FSCopy => fs_copy(source, dest, nofollow),
CloneFallback::SparseCopy => sparse_copy(source, dest, nofollow),
CloneFallback::Error => Err(CpError::IoErrContext(
std::io::Error::last_os_error(),
context.to_owned(),
)),
CloneFallback::FSCopy => fs_copy(source, dest, nofollow, context),
CloneFallback::SparseCopy => sparse_copy(source, dest, nofollow, context),
CloneFallback::SparseCopyWithoutHole => {
sparse_copy_without_hole(source, dest, nofollow)
sparse_copy_without_hole(source, dest, nofollow, context)
}
};
}
Expand Down Expand Up @@ -119,15 +144,23 @@ fn check_sparse_detection(source: &Path, nofollow: bool) -> Result<bool, std::io

/// Optimized [`sparse_copy`] doesn't create holes for large sequences of zeros in non `sparse_files`
/// Used when `--sparse=auto`
fn sparse_copy_without_hole<P>(source: P, dest: P, nofollow: bool) -> std::io::Result<()>
fn sparse_copy_without_hole<P>(source: P, dest: P, nofollow: bool, context: &str) -> CopyResult<()>
where
P: AsRef<Path>,
{
let src_file = open_source(source, nofollow)?;
let dst_file = create_dest_restrictive(dest, false)?;

let size = src_file.metadata()?.size();
ftruncate(&dst_file, size)?;
let src_file =
open_source(&source, nofollow).map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
let dst_file = create_dest_restrictive(&dest, false).map_err(|e| {
CpError::IoErrContext(
e,
translate!("cp-error-cannot-create-regular-file", "path" => dest.as_ref().quote()),
)
})?;

let ctx_err = |e: std::io::Error| CpError::IoErrContext(e, context.to_owned());

let size = src_file.metadata().map_err(&ctx_err)?.size();
ftruncate(&dst_file, size).map_err(|e| CpError::IoErrContext(e.into(), context.to_owned()))?;
let mut current_offset = 0;
// Maximize the data read at once to 16 MiB to avoid memory hogging with large files
// 16 MiB chunks should saturate an SSD
Expand All @@ -144,37 +177,57 @@ where
// Ensure we don't read past the end of the file or the start of the next hole
let read_len = std::cmp::min((len - i) as usize, step);
let buf = &mut buf[..read_len];
src_file.read_exact_at(buf, current_offset + i)?;
dst_file.write_all_at(buf, current_offset + i)?;
src_file
.read_exact_at(buf, current_offset + i)
.map_err(&ctx_err)?;
dst_file
.write_all_at(buf, current_offset + i)
.map_err(&ctx_err)?;
}
current_offset = hole;
}
Ok(())
}
/// Perform a sparse copy from one file to another.
/// Creates a holes for large sequences of zeros in `non_sparse_files`, used for `--sparse=always`
fn sparse_copy<P>(source: P, dest: P, nofollow: bool) -> std::io::Result<()>
fn sparse_copy<P>(source: P, dest: P, nofollow: bool, context: &str) -> CopyResult<()>
where
P: AsRef<Path>,
{
let mut src_file = open_source(source, nofollow)?;
let dst_file = create_dest_restrictive(dest, false)?;

let size: usize = src_file.metadata()?.size().try_into().unwrap();
ftruncate(&dst_file, size.try_into().unwrap())?;

let blksize = dst_file.metadata()?.blksize();
let mut src_file =
open_source(&source, nofollow).map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
let dst_file = create_dest_restrictive(&dest, false).map_err(|e| {
CpError::IoErrContext(
e,
translate!("cp-error-cannot-create-regular-file", "path" => dest.as_ref().quote()),
)
})?;

let ctx_err = |e: std::io::Error| CpError::IoErrContext(e, context.to_owned());

let size: usize = src_file
.metadata()
.map_err(&ctx_err)?
.size()
.try_into()
.unwrap();
ftruncate(&dst_file, size.try_into().unwrap())
.map_err(|e| CpError::IoErrContext(e.into(), context.to_owned()))?;

let blksize = dst_file.metadata().map_err(&ctx_err)?.blksize();
let mut buf: Vec<u8> = vec![0; blksize.try_into().unwrap()];
let mut current_offset: usize = 0;

// TODO Perhaps we can employ the "fiemap ioctl" API to get the
// file extent mappings:
// https://www.kernel.org/doc/html/latest/filesystems/fiemap.html
while current_offset < size {
let this_read = src_file.read(&mut buf)?;
let this_read = src_file.read(&mut buf).map_err(&ctx_err)?;
let buf = &buf[..this_read];
if buf.iter().any(|&x| x != 0) {
dst_file.write_all_at(buf, current_offset.try_into().unwrap())?;
dst_file
.write_all_at(buf, current_offset.try_into().unwrap())
.map_err(&ctx_err)?;
}
current_offset += this_read;
}
Expand All @@ -188,7 +241,7 @@ fn check_dest_is_fifo(dest: &Path) -> bool {
}

/// Copy the contents of a stream from `source` to `dest`.
fn copy_stream<P>(source: P, dest: P, nofollow: bool) -> std::io::Result<()>
fn copy_stream<P>(source: P, dest: P, nofollow: bool, context: &str) -> CopyResult<()>
where
P: AsRef<Path>,
{
Expand All @@ -210,21 +263,30 @@ where
//
// TODO Update the code below to respect the case where
// `--preserve=ownership` is not true.
let mut src_file = open_source(&source, nofollow)?;
let mut src_file =
open_source(&source, nofollow).map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
// Use the same restrictive initial mode as the regular file path so that
// the dest does not momentarily sit with broader perms. The `0o622 &
// !umask` form previously used here could still allow group/other write
// under a permissive umask. See #10011.
let mut dst_file = create_dest_restrictive(&dest, false)?;
let mut dst_file = create_dest_restrictive(&dest, false).map_err(|e| {
CpError::IoErrContext(
e,
translate!("cp-error-cannot-create-regular-file", "path" => dest.as_ref().quote()),
)
})?;

let ctx_err = |e: std::io::Error| CpError::IoErrContext(e, context.to_owned());

let dest_is_stream = is_stream(&dst_file.metadata()?);
let dest_is_stream = is_stream(&dst_file.metadata().map_err(&ctx_err)?);
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)?;
dst_file.set_len(0).map_err(&ctx_err)?;
}

buf_copy::copy_fast(&mut src_file, &mut dst_file)
.map_err(|e| std::io::Error::other(format!("{e}")))?;
.map_err(|e| std::io::Error::other(format!("{e}")))
.map_err(&ctx_err)?;

Ok(())
}
Expand All @@ -251,7 +313,7 @@ pub(crate) fn copy_on_write(
copy_debug.reflink = OffloadReflinkDebug::No;
if source_is_stream {
copy_debug.offload = OffloadReflinkDebug::Avoided;
copy_stream(source, dest, nofollow)
copy_stream(source, dest, nofollow, context)
} else {
let mut copy_method = CopyMethod::Default;
let result = handle_reflink_never_sparse_always(source, dest, nofollow);
Expand All @@ -261,8 +323,8 @@ pub(crate) fn copy_on_write(
}

match copy_method {
CopyMethod::FSCopy => fs_copy(source, dest, nofollow),
_ => sparse_copy(source, dest, nofollow),
CopyMethod::FSCopy => fs_copy(source, dest, nofollow, context),
_ => sparse_copy(source, dest, nofollow, context),
}
}
}
Expand All @@ -271,21 +333,21 @@ pub(crate) fn copy_on_write(

if source_is_stream {
copy_debug.offload = OffloadReflinkDebug::Avoided;
copy_stream(source, dest, nofollow)
copy_stream(source, dest, nofollow, context)
} else {
let result = handle_reflink_never_sparse_never(source, nofollow);
if let Ok(debug) = result {
copy_debug = debug;
}
fs_copy(source, dest, nofollow)
fs_copy(source, dest, nofollow, context)
}
}
(ReflinkMode::Never, SparseMode::Auto) => {
copy_debug.reflink = OffloadReflinkDebug::No;

if source_is_stream {
copy_debug.offload = OffloadReflinkDebug::Avoided;
copy_stream(source, dest, nofollow)
copy_stream(source, dest, nofollow, context)
} else {
let mut copy_method = CopyMethod::Default;
let result = handle_reflink_never_sparse_auto(source, dest, nofollow);
Expand All @@ -296,9 +358,9 @@ pub(crate) fn copy_on_write(

match copy_method {
CopyMethod::SparseCopyWithoutHole => {
sparse_copy_without_hole(source, dest, nofollow)
sparse_copy_without_hole(source, dest, nofollow, context)
}
_ => fs_copy(source, dest, nofollow),
_ => fs_copy(source, dest, nofollow, context),
}
}
}
Expand All @@ -307,7 +369,7 @@ pub(crate) fn copy_on_write(
// SparseMode::Always
if source_is_stream {
copy_debug.offload = OffloadReflinkDebug::Avoided;
copy_stream(source, dest, nofollow)
copy_stream(source, dest, nofollow, context)
} else {
let mut copy_method = CopyMethod::Default;
let result = handle_reflink_auto_sparse_always(source, dest, nofollow);
Expand All @@ -317,8 +379,10 @@ pub(crate) fn copy_on_write(
}

match copy_method {
CopyMethod::FSCopy => clone(source, dest, CloneFallback::FSCopy, nofollow),
_ => clone(source, dest, CloneFallback::SparseCopy, nofollow),
CopyMethod::FSCopy => {
clone(source, dest, CloneFallback::FSCopy, nofollow, context)
}
_ => clone(source, dest, CloneFallback::SparseCopy, nofollow, context),
}
}
}
Expand All @@ -327,20 +391,20 @@ pub(crate) fn copy_on_write(
copy_debug.reflink = OffloadReflinkDebug::No;
if source_is_stream {
copy_debug.offload = OffloadReflinkDebug::Avoided;
copy_stream(source, dest, nofollow)
copy_stream(source, dest, nofollow, context)
} else {
let result = handle_reflink_auto_sparse_never(source, nofollow);
if let Ok(debug) = result {
copy_debug = debug;
}

clone(source, dest, CloneFallback::FSCopy, nofollow)
clone(source, dest, CloneFallback::FSCopy, nofollow, context)
}
}
(ReflinkMode::Auto, SparseMode::Auto) => {
if source_is_stream {
copy_debug.offload = OffloadReflinkDebug::Unsupported;
copy_stream(source, dest, nofollow)
copy_stream(source, dest, nofollow, context)
} else {
let mut copy_method = CopyMethod::Default;
let result = handle_reflink_auto_sparse_auto(source, dest, nofollow);
Expand All @@ -350,10 +414,14 @@ pub(crate) fn copy_on_write(
}

match copy_method {
CopyMethod::SparseCopyWithoutHole => {
clone(source, dest, CloneFallback::SparseCopyWithoutHole, nofollow)
}
_ => clone(source, dest, CloneFallback::FSCopy, nofollow),
CopyMethod::SparseCopyWithoutHole => clone(
source,
dest,
CloneFallback::SparseCopyWithoutHole,
nofollow,
context,
),
_ => clone(source, dest, CloneFallback::FSCopy, nofollow, context),
}
}
}
Expand All @@ -362,13 +430,13 @@ pub(crate) fn copy_on_write(
copy_debug.sparse_detection = SparseDebug::No;
copy_debug.reflink = OffloadReflinkDebug::Yes;

clone(source, dest, CloneFallback::Error, nofollow)
clone(source, dest, CloneFallback::Error, nofollow, context)
}
(ReflinkMode::Always, _) => {
return Err(translate!("cp-error-reflink-always-sparse-auto").into());
}
};
result.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
result?;
Ok(copy_debug)
}

Expand Down
Loading
Loading