mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-30 20:47:46 +00:00
mv: factor rename_with_fallback func into helpers
Factor out helper functions from the `rename_with_fallback()` function so that there is one fallback helper per file type (symlink, directory, or file). This doesn't change the functionality of `mv`, it is just a re-organization of the code.
This commit is contained in:
parent
d99b7b31a6
commit
3b2db58a78
1 changed files with 117 additions and 109 deletions
|
@ -672,7 +672,7 @@ fn rename_with_fallback(
|
||||||
to: &Path,
|
to: &Path,
|
||||||
multi_progress: Option<&MultiProgress>,
|
multi_progress: Option<&MultiProgress>,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
if let Err(err) = fs::rename(from, to) {
|
fs::rename(from, to).or_else(|err| {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
const EXDEV: i32 = windows_sys::Win32::Foundation::ERROR_NOT_SAME_DEVICE as _;
|
const EXDEV: i32 = windows_sys::Win32::Foundation::ERROR_NOT_SAME_DEVICE as _;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -687,131 +687,139 @@ fn rename_with_fallback(
|
||||||
if !should_fallback {
|
if !should_fallback {
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get metadata without following symlinks
|
// Get metadata without following symlinks
|
||||||
let metadata = from.symlink_metadata()?;
|
let metadata = from.symlink_metadata()?;
|
||||||
let file_type = metadata.file_type();
|
let file_type = metadata.file_type();
|
||||||
|
|
||||||
if file_type.is_symlink() {
|
if file_type.is_symlink() {
|
||||||
rename_symlink_fallback(from, to)?;
|
rename_symlink_fallback(from, to)
|
||||||
} else if file_type.is_dir() {
|
} else if file_type.is_dir() {
|
||||||
// We remove the destination directory if it exists to match the
|
rename_dir_fallback(from, to, multi_progress)
|
||||||
// behavior of `fs::rename`. As far as I can tell, `fs_extra`'s
|
|
||||||
// `move_dir` would otherwise behave differently.
|
|
||||||
if to.exists() {
|
|
||||||
fs::remove_dir_all(to)?;
|
|
||||||
}
|
|
||||||
let options = DirCopyOptions {
|
|
||||||
// From the `fs_extra` documentation:
|
|
||||||
// "Recursively copy a directory with a new name or place it
|
|
||||||
// inside the destination. (same behaviors like cp -r in Unix)"
|
|
||||||
copy_inside: true,
|
|
||||||
..DirCopyOptions::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate total size of directory
|
|
||||||
// Silently degrades:
|
|
||||||
// If finding the total size fails for whatever reason,
|
|
||||||
// the progress bar wont be shown for this file / dir.
|
|
||||||
// (Move will probably fail due to permission error later?)
|
|
||||||
let total_size = dir_get_size(from).ok();
|
|
||||||
|
|
||||||
let progress_bar =
|
|
||||||
if let (Some(multi_progress), Some(total_size)) = (multi_progress, total_size) {
|
|
||||||
let bar = ProgressBar::new(total_size).with_style(
|
|
||||||
ProgressStyle::with_template(
|
|
||||||
"{msg}: [{elapsed_precise}] {wide_bar} {bytes:>7}/{total_bytes:7}",
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Some(multi_progress.add(bar))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
|
|
||||||
let xattrs =
|
|
||||||
fsxattr::retrieve_xattrs(from).unwrap_or_else(|_| std::collections::HashMap::new());
|
|
||||||
|
|
||||||
let result = if let Some(ref pb) = progress_bar {
|
|
||||||
move_dir_with_progress(from, to, &options, |process_info: TransitProcess| {
|
|
||||||
pb.set_position(process_info.copied_bytes);
|
|
||||||
pb.set_message(process_info.file_name);
|
|
||||||
TransitProcessResult::ContinueOrAbort
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
move_dir(from, to, &options)
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
|
|
||||||
fsxattr::apply_xattrs(to, xattrs)?;
|
|
||||||
|
|
||||||
if let Err(err) = result {
|
|
||||||
return match err.kind {
|
|
||||||
fs_extra::error::ErrorKind::PermissionDenied => Err(io::Error::new(
|
|
||||||
io::ErrorKind::PermissionDenied,
|
|
||||||
"Permission denied",
|
|
||||||
)),
|
|
||||||
_ => Err(io::Error::other(format!("{err:?}"))),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if to.is_symlink() {
|
rename_file_fallback(from, to)
|
||||||
fs::remove_file(to).map_err(|err| {
|
|
||||||
let to = to.to_string_lossy();
|
|
||||||
let from = from.to_string_lossy();
|
|
||||||
io::Error::new(
|
|
||||||
err.kind(),
|
|
||||||
format!(
|
|
||||||
"inter-device move failed: '{from}' to '{to}'\
|
|
||||||
; unable to remove target: {err}"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
|
|
||||||
fs::copy(from, to)
|
|
||||||
.and_then(|_| fsxattr::copy_xattrs(&from, &to))
|
|
||||||
.and_then(|_| fs::remove_file(from))?;
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "redox", not(unix)))]
|
|
||||||
fs::copy(from, to).and_then(|_| fs::remove_file(from))?;
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move the given symlink to the given destination. On Windows, dangling
|
/// Move the given symlink to the given destination. On Windows, dangling
|
||||||
/// symlinks return an error.
|
/// symlinks return an error.
|
||||||
#[inline]
|
#[cfg(unix)]
|
||||||
fn rename_symlink_fallback(from: &Path, to: &Path) -> io::Result<()> {
|
fn rename_symlink_fallback(from: &Path, to: &Path) -> io::Result<()> {
|
||||||
let path_symlink_points_to = fs::read_link(from)?;
|
let path_symlink_points_to = fs::read_link(from)?;
|
||||||
#[cfg(unix)]
|
unix::fs::symlink(path_symlink_points_to, to).and_then(|_| fs::remove_file(from))
|
||||||
{
|
}
|
||||||
unix::fs::symlink(path_symlink_points_to, to).and_then(|_| fs::remove_file(from))?;
|
|
||||||
}
|
#[cfg(windows)]
|
||||||
#[cfg(windows)]
|
fn rename_symlink_fallback(from: &Path, to: &Path) -> io::Result<()> {
|
||||||
{
|
let path_symlink_points_to = fs::read_link(from)?;
|
||||||
if path_symlink_points_to.exists() {
|
if path_symlink_points_to.exists() {
|
||||||
if path_symlink_points_to.is_dir() {
|
if path_symlink_points_to.is_dir() {
|
||||||
windows::fs::symlink_dir(&path_symlink_points_to, to)?;
|
windows::fs::symlink_dir(&path_symlink_points_to, to)?;
|
||||||
} else {
|
|
||||||
windows::fs::symlink_file(&path_symlink_points_to, to)?;
|
|
||||||
}
|
|
||||||
fs::remove_file(from)?;
|
|
||||||
} else {
|
} else {
|
||||||
return Err(io::Error::new(
|
windows::fs::symlink_file(&path_symlink_points_to, to)?;
|
||||||
io::ErrorKind::NotFound,
|
|
||||||
"can't determine symlink type, since it is dangling",
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
fs::remove_file(from)
|
||||||
|
} else {
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::NotFound,
|
||||||
|
"can't determine symlink type, since it is dangling",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
#[cfg(not(any(windows, unix)))]
|
}
|
||||||
{
|
|
||||||
return Err(io::Error::other(
|
#[cfg(not(any(windows, unix)))]
|
||||||
"your operating system does not support symlinks",
|
fn rename_symlink_fallback(from: &Path, to: &Path) -> io::Result<()> {
|
||||||
));
|
let path_symlink_points_to = fs::read_link(from)?;
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"your operating system does not support symlinks",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rename_dir_fallback(
|
||||||
|
from: &Path,
|
||||||
|
to: &Path,
|
||||||
|
multi_progress: Option<&MultiProgress>,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
// We remove the destination directory if it exists to match the
|
||||||
|
// behavior of `fs::rename`. As far as I can tell, `fs_extra`'s
|
||||||
|
// `move_dir` would otherwise behave differently.
|
||||||
|
if to.exists() {
|
||||||
|
fs::remove_dir_all(to)?;
|
||||||
}
|
}
|
||||||
|
let options = DirCopyOptions {
|
||||||
|
// From the `fs_extra` documentation:
|
||||||
|
// "Recursively copy a directory with a new name or place it
|
||||||
|
// inside the destination. (same behaviors like cp -r in Unix)"
|
||||||
|
copy_inside: true,
|
||||||
|
..DirCopyOptions::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate total size of directory
|
||||||
|
// Silently degrades:
|
||||||
|
// If finding the total size fails for whatever reason,
|
||||||
|
// the progress bar wont be shown for this file / dir.
|
||||||
|
// (Move will probably fail due to permission error later?)
|
||||||
|
let total_size = dir_get_size(from).ok();
|
||||||
|
|
||||||
|
let progress_bar = match (multi_progress, total_size) {
|
||||||
|
(Some(multi_progress), Some(total_size)) => {
|
||||||
|
let template = "{msg}: [{elapsed_precise}] {wide_bar} {bytes:>7}/{total_bytes:7}";
|
||||||
|
let style = ProgressStyle::with_template(template).unwrap();
|
||||||
|
let bar = ProgressBar::new(total_size).with_style(style);
|
||||||
|
Some(multi_progress.add(bar))
|
||||||
|
}
|
||||||
|
(_, _) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
|
||||||
|
let xattrs =
|
||||||
|
fsxattr::retrieve_xattrs(from).unwrap_or_else(|_| std::collections::HashMap::new());
|
||||||
|
|
||||||
|
let result = if let Some(ref pb) = progress_bar {
|
||||||
|
move_dir_with_progress(from, to, &options, |process_info: TransitProcess| {
|
||||||
|
pb.set_position(process_info.copied_bytes);
|
||||||
|
pb.set_message(process_info.file_name);
|
||||||
|
TransitProcessResult::ContinueOrAbort
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
move_dir(from, to, &options)
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
|
||||||
|
fsxattr::apply_xattrs(to, xattrs)?;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Err(err) => match err.kind {
|
||||||
|
fs_extra::error::ErrorKind::PermissionDenied => Err(io::Error::new(
|
||||||
|
io::ErrorKind::PermissionDenied,
|
||||||
|
"Permission denied",
|
||||||
|
)),
|
||||||
|
_ => Err(io::Error::new(io::ErrorKind::Other, format!("{err:?}"))),
|
||||||
|
},
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rename_file_fallback(from: &Path, to: &Path) -> io::Result<()> {
|
||||||
|
if to.is_symlink() {
|
||||||
|
fs::remove_file(to).map_err(|err| {
|
||||||
|
let to = to.to_string_lossy();
|
||||||
|
let from = from.to_string_lossy();
|
||||||
|
io::Error::new(
|
||||||
|
err.kind(),
|
||||||
|
format!(
|
||||||
|
"inter-device move failed: '{from}' to '{to}'\
|
||||||
|
; unable to remove target: {err}"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
|
||||||
|
fs::copy(from, to)
|
||||||
|
.and_then(|_| fsxattr::copy_xattrs(&from, &to))
|
||||||
|
.and_then(|_| fs::remove_file(from))?;
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "redox", not(unix)))]
|
||||||
|
fs::copy(from, to).and_then(|_| fs::remove_file(from))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue