Skip to content

Commit 6cccd34

Browse files
authored
Merge pull request #7774 from jfinkels/mv-refactor-into-helpers
mv: factor rename_with_fallback function into helpers
2 parents 6ea58ea + 3b2db58 commit 6cccd34

File tree

1 file changed

+116
-108
lines changed

1 file changed

+116
-108
lines changed

src/uu/mv/src/mv.rs

Lines changed: 116 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ fn rename_with_fallback(
672672
to: &Path,
673673
multi_progress: Option<&MultiProgress>,
674674
) -> io::Result<()> {
675-
if let Err(err) = fs::rename(from, to) {
675+
fs::rename(from, to).or_else(|err| {
676676
#[cfg(windows)]
677677
const EXDEV: i32 = windows_sys::Win32::Foundation::ERROR_NOT_SAME_DEVICE as _;
678678
#[cfg(unix)]
@@ -687,131 +687,139 @@ fn rename_with_fallback(
687687
if !should_fallback {
688688
return Err(err);
689689
}
690-
691690
// Get metadata without following symlinks
692691
let metadata = from.symlink_metadata()?;
693692
let file_type = metadata.file_type();
694-
695693
if file_type.is_symlink() {
696-
rename_symlink_fallback(from, to)?;
694+
rename_symlink_fallback(from, to)
697695
} else if file_type.is_dir() {
698-
// We remove the destination directory if it exists to match the
699-
// behavior of `fs::rename`. As far as I can tell, `fs_extra`'s
700-
// `move_dir` would otherwise behave differently.
701-
if to.exists() {
702-
fs::remove_dir_all(to)?;
703-
}
704-
let options = DirCopyOptions {
705-
// From the `fs_extra` documentation:
706-
// "Recursively copy a directory with a new name or place it
707-
// inside the destination. (same behaviors like cp -r in Unix)"
708-
copy_inside: true,
709-
..DirCopyOptions::new()
710-
};
711-
712-
// Calculate total size of directory
713-
// Silently degrades:
714-
// If finding the total size fails for whatever reason,
715-
// the progress bar wont be shown for this file / dir.
716-
// (Move will probably fail due to permission error later?)
717-
let total_size = dir_get_size(from).ok();
718-
719-
let progress_bar =
720-
if let (Some(multi_progress), Some(total_size)) = (multi_progress, total_size) {
721-
let bar = ProgressBar::new(total_size).with_style(
722-
ProgressStyle::with_template(
723-
"{msg}: [{elapsed_precise}] {wide_bar} {bytes:>7}/{total_bytes:7}",
724-
)
725-
.unwrap(),
726-
);
727-
728-
Some(multi_progress.add(bar))
729-
} else {
730-
None
731-
};
696+
rename_dir_fallback(from, to, multi_progress)
697+
} else {
698+
rename_file_fallback(from, to)
699+
}
700+
})
701+
}
732702

733-
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
734-
let xattrs =
735-
fsxattr::retrieve_xattrs(from).unwrap_or_else(|_| std::collections::HashMap::new());
703+
/// Move the given symlink to the given destination. On Windows, dangling
704+
/// symlinks return an error.
705+
#[cfg(unix)]
706+
fn rename_symlink_fallback(from: &Path, to: &Path) -> io::Result<()> {
707+
let path_symlink_points_to = fs::read_link(from)?;
708+
unix::fs::symlink(path_symlink_points_to, to).and_then(|_| fs::remove_file(from))
709+
}
736710

737-
let result = if let Some(ref pb) = progress_bar {
738-
move_dir_with_progress(from, to, &options, |process_info: TransitProcess| {
739-
pb.set_position(process_info.copied_bytes);
740-
pb.set_message(process_info.file_name);
741-
TransitProcessResult::ContinueOrAbort
742-
})
743-
} else {
744-
move_dir(from, to, &options)
745-
};
746-
747-
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
748-
fsxattr::apply_xattrs(to, xattrs)?;
749-
750-
if let Err(err) = result {
751-
return match err.kind {
752-
fs_extra::error::ErrorKind::PermissionDenied => Err(io::Error::new(
753-
io::ErrorKind::PermissionDenied,
754-
"Permission denied",
755-
)),
756-
_ => Err(io::Error::other(format!("{err:?}"))),
757-
};
758-
}
711+
#[cfg(windows)]
712+
fn rename_symlink_fallback(from: &Path, to: &Path) -> io::Result<()> {
713+
let path_symlink_points_to = fs::read_link(from)?;
714+
if path_symlink_points_to.exists() {
715+
if path_symlink_points_to.is_dir() {
716+
windows::fs::symlink_dir(&path_symlink_points_to, to)?;
759717
} else {
760-
if to.is_symlink() {
761-
fs::remove_file(to).map_err(|err| {
762-
let to = to.to_string_lossy();
763-
let from = from.to_string_lossy();
764-
io::Error::new(
765-
err.kind(),
766-
format!(
767-
"inter-device move failed: '{from}' to '{to}'\
768-
; unable to remove target: {err}"
769-
),
770-
)
771-
})?;
772-
}
773-
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
774-
fs::copy(from, to)
775-
.and_then(|_| fsxattr::copy_xattrs(&from, &to))
776-
.and_then(|_| fs::remove_file(from))?;
777-
#[cfg(any(target_os = "macos", target_os = "redox", not(unix)))]
778-
fs::copy(from, to).and_then(|_| fs::remove_file(from))?;
718+
windows::fs::symlink_file(&path_symlink_points_to, to)?;
779719
}
720+
fs::remove_file(from)
721+
} else {
722+
Err(io::Error::new(
723+
io::ErrorKind::NotFound,
724+
"can't determine symlink type, since it is dangling",
725+
))
780726
}
781-
Ok(())
782727
}
783728

784-
/// Move the given symlink to the given destination. On Windows, dangling
785-
/// symlinks return an error.
786-
#[inline]
729+
#[cfg(not(any(windows, unix)))]
787730
fn rename_symlink_fallback(from: &Path, to: &Path) -> io::Result<()> {
788731
let path_symlink_points_to = fs::read_link(from)?;
789-
#[cfg(unix)]
790-
{
791-
unix::fs::symlink(path_symlink_points_to, to).and_then(|_| fs::remove_file(from))?;
732+
Err(io::Error::new(
733+
io::ErrorKind::Other,
734+
"your operating system does not support symlinks",
735+
))
736+
}
737+
738+
fn rename_dir_fallback(
739+
from: &Path,
740+
to: &Path,
741+
multi_progress: Option<&MultiProgress>,
742+
) -> io::Result<()> {
743+
// We remove the destination directory if it exists to match the
744+
// behavior of `fs::rename`. As far as I can tell, `fs_extra`'s
745+
// `move_dir` would otherwise behave differently.
746+
if to.exists() {
747+
fs::remove_dir_all(to)?;
792748
}
793-
#[cfg(windows)]
794-
{
795-
if path_symlink_points_to.exists() {
796-
if path_symlink_points_to.is_dir() {
797-
windows::fs::symlink_dir(&path_symlink_points_to, to)?;
798-
} else {
799-
windows::fs::symlink_file(&path_symlink_points_to, to)?;
800-
}
801-
fs::remove_file(from)?;
802-
} else {
803-
return Err(io::Error::new(
804-
io::ErrorKind::NotFound,
805-
"can't determine symlink type, since it is dangling",
806-
));
749+
let options = DirCopyOptions {
750+
// From the `fs_extra` documentation:
751+
// "Recursively copy a directory with a new name or place it
752+
// inside the destination. (same behaviors like cp -r in Unix)"
753+
copy_inside: true,
754+
..DirCopyOptions::new()
755+
};
756+
757+
// Calculate total size of directory
758+
// Silently degrades:
759+
// If finding the total size fails for whatever reason,
760+
// the progress bar wont be shown for this file / dir.
761+
// (Move will probably fail due to permission error later?)
762+
let total_size = dir_get_size(from).ok();
763+
764+
let progress_bar = match (multi_progress, total_size) {
765+
(Some(multi_progress), Some(total_size)) => {
766+
let template = "{msg}: [{elapsed_precise}] {wide_bar} {bytes:>7}/{total_bytes:7}";
767+
let style = ProgressStyle::with_template(template).unwrap();
768+
let bar = ProgressBar::new(total_size).with_style(style);
769+
Some(multi_progress.add(bar))
807770
}
771+
(_, _) => None,
772+
};
773+
774+
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
775+
let xattrs =
776+
fsxattr::retrieve_xattrs(from).unwrap_or_else(|_| std::collections::HashMap::new());
777+
778+
let result = if let Some(ref pb) = progress_bar {
779+
move_dir_with_progress(from, to, &options, |process_info: TransitProcess| {
780+
pb.set_position(process_info.copied_bytes);
781+
pb.set_message(process_info.file_name);
782+
TransitProcessResult::ContinueOrAbort
783+
})
784+
} else {
785+
move_dir(from, to, &options)
786+
};
787+
788+
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
789+
fsxattr::apply_xattrs(to, xattrs)?;
790+
791+
match result {
792+
Err(err) => match err.kind {
793+
fs_extra::error::ErrorKind::PermissionDenied => Err(io::Error::new(
794+
io::ErrorKind::PermissionDenied,
795+
"Permission denied",
796+
)),
797+
_ => Err(io::Error::new(io::ErrorKind::Other, format!("{err:?}"))),
798+
},
799+
_ => Ok(()),
808800
}
809-
#[cfg(not(any(windows, unix)))]
810-
{
811-
return Err(io::Error::other(
812-
"your operating system does not support symlinks",
813-
));
801+
}
802+
803+
fn rename_file_fallback(from: &Path, to: &Path) -> io::Result<()> {
804+
if to.is_symlink() {
805+
fs::remove_file(to).map_err(|err| {
806+
let to = to.to_string_lossy();
807+
let from = from.to_string_lossy();
808+
io::Error::new(
809+
err.kind(),
810+
format!(
811+
"inter-device move failed: '{from}' to '{to}'\
812+
; unable to remove target: {err}"
813+
),
814+
)
815+
})?;
814816
}
817+
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
818+
fs::copy(from, to)
819+
.and_then(|_| fsxattr::copy_xattrs(&from, &to))
820+
.and_then(|_| fs::remove_file(from))?;
821+
#[cfg(any(target_os = "macos", target_os = "redox", not(unix)))]
822+
fs::copy(from, to).and_then(|_| fs::remove_file(from))?;
815823
Ok(())
816824
}
817825

0 commit comments

Comments
 (0)