Skip to content

Commit

Permalink
Merge pull request #3971 from niyaznigmatullin/cp_H_option
Browse files Browse the repository at this point in the history
cp: add `-H` option
  • Loading branch information
tertsdiepraam authored Sep 25, 2022
2 parents 9ce600f + 9166269 commit a3ba02a
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 84 deletions.
9 changes: 4 additions & 5 deletions src/uu/chmod/src/chmod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use std::path::Path;
use uucore::display::Quotable;
use uucore::error::{ExitCode, UResult, USimpleError, UUsageError};
use uucore::fs::display_permissions_unix;
use uucore::fs::is_symlink;
use uucore::libc::mode_t;
#[cfg(not(windows))]
use uucore::mode;
Expand Down Expand Up @@ -195,7 +194,7 @@ impl Chmoder {
let filename = &filename[..];
let file = Path::new(filename);
if !file.exists() {
if is_symlink(file) {
if file.is_symlink() {
println!(
"failed to change mode of {} from 0000 (---------) to 0000 (---------)",
filename.quote()
Expand Down Expand Up @@ -237,10 +236,10 @@ impl Chmoder {

fn walk_dir(&self, file_path: &Path) -> UResult<()> {
let mut r = self.chmod_file(file_path);
if !is_symlink(file_path) && file_path.is_dir() {
if !file_path.is_symlink() && file_path.is_dir() {
for dir_entry in file_path.read_dir()? {
let path = dir_entry?.path();
if !is_symlink(&path) {
if !path.is_symlink() {
r = self.walk_dir(path.as_path());
}
}
Expand All @@ -262,7 +261,7 @@ impl Chmoder {
let fperm = match fs::metadata(file) {
Ok(meta) => meta.mode() & 0o7777,
Err(err) => {
if is_symlink(file) {
if file.is_symlink() {
if self.verbose {
println!(
"neither symbolic link {} nor referent has been changed",
Expand Down
130 changes: 74 additions & 56 deletions src/uu/cp/src/cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use std::str::FromStr;
use std::string::ToString;
use uucore::backup_control::{self, BackupMode};
use uucore::error::{set_exit_code, UClapError, UError, UResult, UUsageError};
use uucore::fs::{canonicalize, is_symlink, MissingHandling, ResolveMode};
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
use walkdir::WalkDir;

quick_error! {
Expand Down Expand Up @@ -207,6 +207,7 @@ pub struct Options {
attributes_only: bool,
backup: BackupMode,
copy_contents: bool,
cli_dereference: bool,
copy_mode: CopyMode,
dereference: bool,
no_target_dir: bool,
Expand Down Expand Up @@ -496,6 +497,11 @@ pub fn uu_app<'a>() -> Command<'a> {
.overrides_with(options::NO_DEREFERENCE)
.help("always follow symbolic links in SOURCE"),
)
.arg(
Arg::new(options::CLI_SYMBOLIC_LINKS)
.short('H')
.help("follow command-line symbolic links in SOURCE"),
)
.arg(
Arg::new(options::ARCHIVE)
.short('a')
Expand Down Expand Up @@ -543,11 +549,6 @@ pub fn uu_app<'a>() -> Command<'a> {
default type",
),
)
.arg(
Arg::new(options::CLI_SYMBOLIC_LINKS)
.short('H')
.help("NotImplemented: follow command-line symbolic links in SOURCE"),
)
// END TODO
.arg(
Arg::new(options::PATHS)
Expand Down Expand Up @@ -760,6 +761,7 @@ impl Options {
let options = Self {
attributes_only: matches.contains_id(options::ATTRIBUTES_ONLY),
copy_contents: matches.contains_id(options::COPY_CONTENTS),
cli_dereference: matches.contains_id(options::CLI_SYMBOLIC_LINKS),
copy_mode: CopyMode::from_matches(matches),
// No dereference is set with -p, -d and --archive
dereference: !(matches.contains_id(options::NO_DEREFERENCE)
Expand Down Expand Up @@ -823,6 +825,10 @@ impl Options {

Ok(options)
}

fn dereference(&self, in_command_line: bool) -> bool {
self.dereference || (in_command_line && self.cli_dereference)
}
}

impl TargetType {
Expand Down Expand Up @@ -1017,11 +1023,11 @@ fn copy_source(
let source_path = Path::new(&source);
if source_path.is_dir() {
// Copy as directory
copy_directory(source, target, options, symlinked_files)
copy_directory(source, target, options, symlinked_files, true)
} else {
// Copy as file
let dest = construct_dest_path(source_path, target, target_type, options)?;
copy_file(source_path, dest.as_path(), options, symlinked_files)
copy_file(source_path, dest.as_path(), options, symlinked_files, true)
}
}

Expand Down Expand Up @@ -1056,14 +1062,21 @@ fn copy_directory(
target: &TargetSlice,
options: &Options,
symlinked_files: &mut HashSet<FileInformation>,
source_in_command_line: bool,
) -> CopyResult<()> {
if !options.recursive {
return Err(format!("omitting directory {}", root.quote()).into());
}

// if no-dereference is enabled and this is a symlink, copy it as a file
if !options.dereference && is_symlink(root) {
return copy_file(root, target, options, symlinked_files);
if !options.dereference(source_in_command_line) && root.is_symlink() {
return copy_file(
root,
target,
options,
symlinked_files,
source_in_command_line,
);
}

// check if root is a prefix of target
Expand Down Expand Up @@ -1128,7 +1141,7 @@ fn copy_directory(
};

let local_to_target = target.join(&local_to_root_parent);
if is_symlink(p.path()) && !options.dereference {
if p.path().is_symlink() && !options.dereference {
copy_link(&path, &local_to_target, symlinked_files)?;
} else if path.is_dir() && !local_to_target.exists() {
if target.is_file() {
Expand All @@ -1147,10 +1160,11 @@ fn copy_directory(
local_to_target.as_path(),
options,
symlinked_files,
false,
) {
Ok(_) => Ok(()),
Err(err) => {
if is_symlink(source) {
if source.is_symlink() {
// silent the error with a symlink
// In case we do --archive, we might copy the symlink
// before the file itself
Expand All @@ -1167,6 +1181,7 @@ fn copy_directory(
local_to_target.as_path(),
options,
symlinked_files,
false,
)?;
}
}
Expand Down Expand Up @@ -1204,7 +1219,7 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu
// permissions of a symbolic link. In that case, we just
// do nothing, since every symbolic link has the same
// permissions.
if !is_symlink(dest) {
if !dest.is_symlink() {
fs::set_permissions(dest, source_metadata.permissions()).context(context)?;
// FIXME: Implement this for windows as well
#[cfg(feature = "feat_acl")]
Expand Down Expand Up @@ -1239,7 +1254,7 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu
Attribute::Timestamps => {
let atime = FileTime::from_last_access_time(&source_metadata);
let mtime = FileTime::from_last_modification_time(&source_metadata);
if is_symlink(dest) {
if dest.is_symlink() {
filetime::set_symlink_file_times(dest, atime, mtime)?;
} else {
filetime::set_file_times(dest, atime, mtime)?;
Expand Down Expand Up @@ -1316,8 +1331,14 @@ fn backup_dest(dest: &Path, backup_path: &Path) -> CopyResult<PathBuf> {
Ok(backup_path.into())
}

fn handle_existing_dest(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
let dereference_to_compare = options.dereference || !is_symlink(source);
fn handle_existing_dest(
source: &Path,
dest: &Path,
options: &Options,
source_in_command_line: bool,
) -> CopyResult<()> {
let dereference_to_compare =
options.dereference(source_in_command_line) || !source.is_symlink();
if paths_refer_to_same_file(source, dest, dereference_to_compare) {
return Err(format!("{}: same file", context_for(source, dest)).into());
}
Expand Down Expand Up @@ -1369,13 +1390,14 @@ fn copy_file(
dest: &Path,
options: &Options,
symlinked_files: &mut HashSet<FileInformation>,
source_in_command_line: bool,
) -> CopyResult<()> {
if dest.exists() {
handle_existing_dest(source, dest, options)?;
handle_existing_dest(source, dest, options, source_in_command_line)?;
}

// Fail if dest is a dangling symlink or a symlink this program created previously
if is_symlink(dest) {
if dest.is_symlink() {
if FileInformation::from_path(dest, false)
.map(|info| symlinked_files.contains(&info))
.unwrap_or(false)
Expand All @@ -1386,7 +1408,7 @@ fn copy_file(
dest.display()
)));
}
let copy_contents = options.dereference || !is_symlink(source);
let copy_contents = options.dereference(source_in_command_line) || !source.is_symlink();
if copy_contents && !dest.exists() {
return Err(Error::Error(format!(
"not writing through dangling symlink '{}'",
Expand All @@ -1403,36 +1425,27 @@ fn copy_file(
let context = context_for(source, dest);
let context = context.as_str();

// canonicalize dest and source so that later steps can work with the paths directly
let source = if options.dereference {
canonicalize(source, MissingHandling::Missing, ResolveMode::Physical).unwrap()
} else {
source.to_owned()
let source_metadata = {
let result = if options.dereference(source_in_command_line) {
fs::metadata(source)
} else {
fs::symlink_metadata(source)
};
result.context(context)?
};

let source_file_type = fs::symlink_metadata(&source).context(context)?.file_type();
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 dest_already_exists_as_symlink = is_symlink(dest);

let dest = if !(source_is_symlink && dest_already_exists_as_symlink) {
canonicalize(dest, MissingHandling::Missing, ResolveMode::Physical).unwrap()
} else {
// Don't canonicalize a symlink copied over another symlink, because
// then we'll end up overwriting the destination's target.
dest.to_path_buf()
};

let dest_permissions = if dest.exists() {
dest.symlink_metadata().context(context)?.permissions()
} else {
#[allow(unused_mut)]
let mut permissions = source.symlink_metadata().context(context)?.permissions();
let mut permissions = source_metadata.permissions();
#[cfg(unix)]
{
use uucore::mode::get_umask;
Expand All @@ -1455,19 +1468,25 @@ fn copy_file(
CopyMode::Link => {
if dest.exists() {
let backup_path =
backup_control::get_backup_path(options.backup, &dest, &options.backup_suffix);
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)?;
backup_dest(dest, &backup_path)?;
fs::remove_file(dest)?;
}
}

fs::hard_link(&source, &dest).context(context)?;
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,
source,
dest,
options,
context,
source_is_symlink,
Expand All @@ -1476,21 +1495,20 @@ fn copy_file(
)?;
}
CopyMode::SymLink => {
symlink_file(&source, &dest, context, symlinked_files)?;
symlink_file(source, dest, context, symlinked_files)?;
}
CopyMode::Update => {
if dest.exists() {
let src_metadata = fs::symlink_metadata(&source)?;
let dest_metadata = fs::symlink_metadata(&dest)?;
let dest_metadata = fs::symlink_metadata(dest)?;

let src_time = src_metadata.modified()?;
let src_time = source_metadata.modified()?;
let dest_time = dest_metadata.modified()?;
if src_time <= dest_time {
return Ok(());
} else {
copy_helper(
&source,
&dest,
source,
dest,
options,
context,
source_is_symlink,
Expand All @@ -1500,8 +1518,8 @@ fn copy_file(
}
} else {
copy_helper(
&source,
&dest,
source,
dest,
options,
context,
source_is_symlink,
Expand All @@ -1521,17 +1539,17 @@ fn copy_file(
};

// TODO: implement something similar to gnu's lchown
if !is_symlink(&dest) {
if !dest.is_symlink() {
// Here, to match GNU semantics, we quietly ignore an error
// if a user does not have the correct ownership to modify
// the permissions of a file.
//
// FWIW, the OS will throw an error later, on the write op, if
// the user does not have permission to write to the file.
fs::set_permissions(&dest, dest_permissions).ok();
fs::set_permissions(dest, dest_permissions).ok();
}
for attribute in &options.preserve_attributes {
copy_attribute(&source, &dest, attribute)?;
copy_attribute(source, dest, attribute)?;
}
Ok(())
}
Expand Down Expand Up @@ -1630,7 +1648,7 @@ fn copy_link(
} else {
// we always need to remove the file to be able to create a symlink,
// even if it is writeable.
if is_symlink(dest) || dest.is_file() {
if dest.is_symlink() || dest.is_file() {
fs::remove_file(dest)?;
}
dest.into()
Expand Down
Loading

0 comments on commit a3ba02a

Please sign in to comment.