mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge pull request #6056 from sylvestre/refactor-copy-function
cp: Split the copy_file function a bit
This commit is contained in:
commit
0da5a7885b
1 changed files with 216 additions and 158 deletions
|
@ -10,7 +10,7 @@ use std::collections::{HashMap, HashSet};
|
||||||
use std::env;
|
use std::env;
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::fs::{self, File, OpenOptions};
|
use std::fs::{self, File, Metadata, OpenOptions, Permissions};
|
||||||
use std::io;
|
use std::io;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
@ -1639,6 +1639,210 @@ fn aligned_ancestors<'a>(source: &'a Path, dest: &'a Path) -> Vec<(&'a Path, &'a
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_verbose_output(
|
||||||
|
parents: bool,
|
||||||
|
progress_bar: &Option<ProgressBar>,
|
||||||
|
source: &Path,
|
||||||
|
dest: &Path,
|
||||||
|
) {
|
||||||
|
if let Some(pb) = progress_bar {
|
||||||
|
// Suspend (hide) the progress bar so the println won't overlap with the progress bar.
|
||||||
|
pb.suspend(|| {
|
||||||
|
print_paths(parents, source, dest);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
print_paths(parents, source, dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_paths(parents: bool, source: &Path, dest: &Path) {
|
||||||
|
if parents {
|
||||||
|
// For example, if copying file `a/b/c` and its parents
|
||||||
|
// to directory `d/`, then print
|
||||||
|
//
|
||||||
|
// a -> d/a
|
||||||
|
// a/b -> d/a/b
|
||||||
|
//
|
||||||
|
for (x, y) in aligned_ancestors(source, dest) {
|
||||||
|
println!("{} -> {}", x.display(), y.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}", context_for(source, dest));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles the copy mode for a file copy operation.
|
||||||
|
///
|
||||||
|
/// This function determines how to copy a file based on the provided options.
|
||||||
|
/// It supports different copy modes, including hard linking, copying, symbolic linking, updating, and attribute-only copying.
|
||||||
|
/// It also handles file backups, overwriting, and dereferencing based on the provided options.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Ok(())` - The file was copied successfully.
|
||||||
|
/// * `Err(CopyError)` - An error occurred while copying the file.
|
||||||
|
fn handle_copy_mode(
|
||||||
|
source: &Path,
|
||||||
|
dest: &Path,
|
||||||
|
options: &Options,
|
||||||
|
context: &str,
|
||||||
|
source_metadata: Metadata,
|
||||||
|
symlinked_files: &mut HashSet<FileInformation>,
|
||||||
|
source_in_command_line: 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;
|
||||||
|
|
||||||
|
match options.copy_mode {
|
||||||
|
CopyMode::Link => {
|
||||||
|
if dest.exists() {
|
||||||
|
let backup_path =
|
||||||
|
backup_control::get_backup_path(options.backup, dest, &options.backup_suffix);
|
||||||
|
if let Some(backup_path) = backup_path {
|
||||||
|
backup_dest(dest, &backup_path)?;
|
||||||
|
fs::remove_file(dest)?;
|
||||||
|
}
|
||||||
|
if options.overwrite == OverwriteMode::Clobber(ClobberMode::Force) {
|
||||||
|
fs::remove_file(dest)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.dereference(source_in_command_line) && source.is_symlink() {
|
||||||
|
let resolved =
|
||||||
|
canonicalize(source, MissingHandling::Missing, ResolveMode::Physical).unwrap();
|
||||||
|
fs::hard_link(resolved, dest)
|
||||||
|
} else {
|
||||||
|
fs::hard_link(source, dest)
|
||||||
|
}
|
||||||
|
.context(context)?;
|
||||||
|
}
|
||||||
|
CopyMode::Copy => {
|
||||||
|
copy_helper(
|
||||||
|
source,
|
||||||
|
dest,
|
||||||
|
options,
|
||||||
|
context,
|
||||||
|
source_is_symlink,
|
||||||
|
source_is_fifo,
|
||||||
|
symlinked_files,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
CopyMode::SymLink => {
|
||||||
|
if dest.exists() && options.overwrite == OverwriteMode::Clobber(ClobberMode::Force) {
|
||||||
|
fs::remove_file(dest)?;
|
||||||
|
}
|
||||||
|
symlink_file(source, dest, context, symlinked_files)?;
|
||||||
|
}
|
||||||
|
CopyMode::Update => {
|
||||||
|
if dest.exists() {
|
||||||
|
match options.update {
|
||||||
|
update_control::UpdateMode::ReplaceAll => {
|
||||||
|
copy_helper(
|
||||||
|
source,
|
||||||
|
dest,
|
||||||
|
options,
|
||||||
|
context,
|
||||||
|
source_is_symlink,
|
||||||
|
source_is_fifo,
|
||||||
|
symlinked_files,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
update_control::UpdateMode::ReplaceNone => {
|
||||||
|
if options.debug {
|
||||||
|
println!("skipped {}", dest.quote());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
update_control::UpdateMode::ReplaceIfOlder => {
|
||||||
|
let dest_metadata = fs::symlink_metadata(dest)?;
|
||||||
|
|
||||||
|
let src_time = source_metadata.modified()?;
|
||||||
|
let dest_time = dest_metadata.modified()?;
|
||||||
|
if src_time <= dest_time {
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
copy_helper(
|
||||||
|
source,
|
||||||
|
dest,
|
||||||
|
options,
|
||||||
|
context,
|
||||||
|
source_is_symlink,
|
||||||
|
source_is_fifo,
|
||||||
|
symlinked_files,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
copy_helper(
|
||||||
|
source,
|
||||||
|
dest,
|
||||||
|
options,
|
||||||
|
context,
|
||||||
|
source_is_symlink,
|
||||||
|
source_is_fifo,
|
||||||
|
symlinked_files,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CopyMode::AttrOnly => {
|
||||||
|
OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.truncate(false)
|
||||||
|
.create(true)
|
||||||
|
.open(dest)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates the permissions for the destination file in a copy operation.
|
||||||
|
///
|
||||||
|
/// If the destination file already exists, its current permissions are returned.
|
||||||
|
/// If the destination file does not exist, the source file's permissions are used,
|
||||||
|
/// with the `no-preserve` option and the umask taken into account on Unix platforms.
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Ok(Permissions)` - The calculated permissions for the destination file.
|
||||||
|
/// * `Err(CopyError)` - An error occurred while getting the metadata of the destination file.
|
||||||
|
/// Allow unused variables for Windows (on options)
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn calculate_dest_permissions(
|
||||||
|
dest: &Path,
|
||||||
|
source_metadata: &Metadata,
|
||||||
|
options: &Options,
|
||||||
|
context: &str,
|
||||||
|
) -> CopyResult<Permissions> {
|
||||||
|
if dest.exists() {
|
||||||
|
Ok(dest.symlink_metadata().context(context)?.permissions())
|
||||||
|
} else {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
let mut permissions = source_metadata.permissions();
|
||||||
|
let mode = handle_no_preserve_mode(options, permissions.mode());
|
||||||
|
|
||||||
|
// Apply umask
|
||||||
|
use uucore::mode::get_umask;
|
||||||
|
let mode = mode & !get_umask();
|
||||||
|
permissions.set_mode(mode);
|
||||||
|
Ok(permissions)
|
||||||
|
}
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
{
|
||||||
|
let permissions = source_metadata.permissions();
|
||||||
|
Ok(permissions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Copy the a file from `source` to `dest`. `source` will be dereferenced if
|
/// Copy the a file from `source` to `dest`. `source` will be dereferenced if
|
||||||
/// `options.dereference` is set to true. `dest` will be dereferenced only if
|
/// `options.dereference` is set to true. `dest` will be dereferenced only if
|
||||||
/// the source was not a symlink.
|
/// the source was not a symlink.
|
||||||
|
@ -1759,38 +1963,7 @@ fn copy_file(
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.verbose {
|
if options.verbose {
|
||||||
if let Some(pb) = progress_bar {
|
print_verbose_output(options.parents, progress_bar, source, dest);
|
||||||
// Suspend (hide) the progress bar so the println won't overlap with the progress bar.
|
|
||||||
pb.suspend(|| {
|
|
||||||
if options.parents {
|
|
||||||
// For example, if copying file `a/b/c` and its parents
|
|
||||||
// to directory `d/`, then print
|
|
||||||
//
|
|
||||||
// a -> d/a
|
|
||||||
// a/b -> d/a/b
|
|
||||||
//
|
|
||||||
for (x, y) in aligned_ancestors(source, dest) {
|
|
||||||
println!("{} -> {}", x.display(), y.display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{}", context_for(source, dest));
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if options.parents {
|
|
||||||
// For example, if copying file `a/b/c` and its parents
|
|
||||||
// to directory `d/`, then print
|
|
||||||
//
|
|
||||||
// a -> d/a
|
|
||||||
// a/b -> d/a/b
|
|
||||||
//
|
|
||||||
for (x, y) in aligned_ancestors(source, dest) {
|
|
||||||
println!("{} -> {}", x.display(), y.display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{}", context_for(source, dest));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the context upfront before canonicalizing the path
|
// Calculate the context upfront before canonicalizing the path
|
||||||
|
@ -1805,133 +1978,18 @@ fn copy_file(
|
||||||
};
|
};
|
||||||
result.context(context)?
|
result.context(context)?
|
||||||
};
|
};
|
||||||
let source_file_type = source_metadata.file_type();
|
|
||||||
let source_is_symlink = source_file_type.is_symlink();
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
let dest_permissions = calculate_dest_permissions(dest, &source_metadata, options, context)?;
|
||||||
let source_is_fifo = source_file_type.is_fifo();
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
let source_is_fifo = false;
|
|
||||||
|
|
||||||
let dest_permissions = if dest.exists() {
|
handle_copy_mode(
|
||||||
dest.symlink_metadata().context(context)?.permissions()
|
source,
|
||||||
} else {
|
dest,
|
||||||
#[allow(unused_mut)]
|
options,
|
||||||
let mut permissions = source_metadata.permissions();
|
context,
|
||||||
#[cfg(unix)]
|
source_metadata,
|
||||||
{
|
symlinked_files,
|
||||||
let mut mode = handle_no_preserve_mode(options, permissions.mode());
|
source_in_command_line,
|
||||||
|
)?;
|
||||||
// apply umask
|
|
||||||
use uucore::mode::get_umask;
|
|
||||||
mode &= !get_umask();
|
|
||||||
|
|
||||||
permissions.set_mode(mode);
|
|
||||||
}
|
|
||||||
permissions
|
|
||||||
};
|
|
||||||
|
|
||||||
match options.copy_mode {
|
|
||||||
CopyMode::Link => {
|
|
||||||
if dest.exists() {
|
|
||||||
let backup_path =
|
|
||||||
backup_control::get_backup_path(options.backup, dest, &options.backup_suffix);
|
|
||||||
if let Some(backup_path) = backup_path {
|
|
||||||
backup_dest(dest, &backup_path)?;
|
|
||||||
fs::remove_file(dest)?;
|
|
||||||
}
|
|
||||||
if options.overwrite == OverwriteMode::Clobber(ClobberMode::Force) {
|
|
||||||
fs::remove_file(dest)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if options.dereference(source_in_command_line) && source.is_symlink() {
|
|
||||||
let resolved =
|
|
||||||
canonicalize(source, MissingHandling::Missing, ResolveMode::Physical).unwrap();
|
|
||||||
fs::hard_link(resolved, dest)
|
|
||||||
} else {
|
|
||||||
fs::hard_link(source, dest)
|
|
||||||
}
|
|
||||||
.context(context)?;
|
|
||||||
}
|
|
||||||
CopyMode::Copy => {
|
|
||||||
copy_helper(
|
|
||||||
source,
|
|
||||||
dest,
|
|
||||||
options,
|
|
||||||
context,
|
|
||||||
source_is_symlink,
|
|
||||||
source_is_fifo,
|
|
||||||
symlinked_files,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
CopyMode::SymLink => {
|
|
||||||
if dest.exists() && options.overwrite == OverwriteMode::Clobber(ClobberMode::Force) {
|
|
||||||
fs::remove_file(dest)?;
|
|
||||||
}
|
|
||||||
symlink_file(source, dest, context, symlinked_files)?;
|
|
||||||
}
|
|
||||||
CopyMode::Update => {
|
|
||||||
if dest.exists() {
|
|
||||||
match options.update {
|
|
||||||
update_control::UpdateMode::ReplaceAll => {
|
|
||||||
copy_helper(
|
|
||||||
source,
|
|
||||||
dest,
|
|
||||||
options,
|
|
||||||
context,
|
|
||||||
source_is_symlink,
|
|
||||||
source_is_fifo,
|
|
||||||
symlinked_files,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
update_control::UpdateMode::ReplaceNone => {
|
|
||||||
if options.debug {
|
|
||||||
println!("skipped {}", dest.quote());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
update_control::UpdateMode::ReplaceIfOlder => {
|
|
||||||
let dest_metadata = fs::symlink_metadata(dest)?;
|
|
||||||
|
|
||||||
let src_time = source_metadata.modified()?;
|
|
||||||
let dest_time = dest_metadata.modified()?;
|
|
||||||
if src_time <= dest_time {
|
|
||||||
return Ok(());
|
|
||||||
} else {
|
|
||||||
copy_helper(
|
|
||||||
source,
|
|
||||||
dest,
|
|
||||||
options,
|
|
||||||
context,
|
|
||||||
source_is_symlink,
|
|
||||||
source_is_fifo,
|
|
||||||
symlinked_files,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
copy_helper(
|
|
||||||
source,
|
|
||||||
dest,
|
|
||||||
options,
|
|
||||||
context,
|
|
||||||
source_is_symlink,
|
|
||||||
source_is_fifo,
|
|
||||||
symlinked_files,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CopyMode::AttrOnly => {
|
|
||||||
OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.truncate(false)
|
|
||||||
.create(true)
|
|
||||||
.open(dest)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: implement something similar to gnu's lchown
|
// TODO: implement something similar to gnu's lchown
|
||||||
if !dest.is_symlink() {
|
if !dest.is_symlink() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue