mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
install: split/doc copy into smaller functions
This commit is contained in:
parent
fb72738db4
commit
2c1aa229a0
1 changed files with 153 additions and 74 deletions
|
@ -682,6 +682,151 @@ fn chown_optional_user_group(path: &Path, b: &Behavior) -> UResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Perform backup before overwriting.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// * `to` - The destination file path.
|
||||||
|
/// * `b` - The behavior configuration.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns an Option containing the backup path, or None if backup is not needed.
|
||||||
|
///
|
||||||
|
fn perform_backup(to: &Path, b: &Behavior) -> UResult<Option<PathBuf>> {
|
||||||
|
if to.exists() {
|
||||||
|
if b.verbose {
|
||||||
|
println!("removed {}", to.quote());
|
||||||
|
}
|
||||||
|
let backup_path = backup_control::get_backup_path(b.backup_mode, to, &b.suffix);
|
||||||
|
if let Some(ref backup_path) = backup_path {
|
||||||
|
// TODO!!
|
||||||
|
if let Err(err) = fs::rename(to, backup_path) {
|
||||||
|
return Err(InstallError::BackupFailed(
|
||||||
|
to.to_path_buf(),
|
||||||
|
backup_path.to_path_buf(),
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(backup_path)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy a file from one path to another.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// * `from` - The source file path.
|
||||||
|
/// * `to` - The destination file path.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns an empty Result or an error in case of failure.
|
||||||
|
///
|
||||||
|
fn copy_file(from: &Path, to: &Path) -> UResult<()> {
|
||||||
|
if from.as_os_str() == "/dev/null" {
|
||||||
|
/* workaround a limitation of fs::copy
|
||||||
|
* https://github.com/rust-lang/rust/issues/79390
|
||||||
|
*/
|
||||||
|
if let Err(err) = File::create(to) {
|
||||||
|
return Err(
|
||||||
|
InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if let Err(err) = fs::copy(from, to) {
|
||||||
|
return Err(InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Strip a file using an external program.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// * `to` - The destination file path.
|
||||||
|
/// * `b` - The behavior configuration.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns an empty Result or an error in case of failure.
|
||||||
|
///
|
||||||
|
fn strip_file(to: &Path, b: &Behavior) -> UResult<()> {
|
||||||
|
match process::Command::new(&b.strip_program).arg(to).output() {
|
||||||
|
Ok(o) => {
|
||||||
|
if !o.status.success() {
|
||||||
|
// Follow GNU's behavior: if strip fails, removes the target
|
||||||
|
let _ = fs::remove_file(to);
|
||||||
|
return Err(InstallError::StripProgramFailed(
|
||||||
|
String::from_utf8(o.stderr).unwrap_or_default(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// Follow GNU's behavior: if strip fails, removes the target
|
||||||
|
let _ = fs::remove_file(to);
|
||||||
|
return Err(InstallError::StripProgramFailed(e.to_string()).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set ownership and permissions on the destination file.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// * `to` - The destination file path.
|
||||||
|
/// * `b` - The behavior configuration.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns an empty Result or an error in case of failure.
|
||||||
|
///
|
||||||
|
fn set_ownership_and_permissions(to: &Path, b: &Behavior) -> UResult<()> {
|
||||||
|
// Silent the warning as we want to the error message
|
||||||
|
#[allow(clippy::question_mark)]
|
||||||
|
if mode::chmod(to, b.mode()).is_err() {
|
||||||
|
return Err(InstallError::ChmodFailed(to.to_path_buf()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
chown_optional_user_group(to, b)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Preserve timestamps on the destination file.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// * `from` - The source file path.
|
||||||
|
/// * `to` - The destination file path.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns an empty Result or an error in case of failure.
|
||||||
|
///
|
||||||
|
fn preserve_timestamps(from: &Path, to: &Path) -> UResult<()> {
|
||||||
|
let meta = match fs::metadata(from) {
|
||||||
|
Ok(meta) => meta,
|
||||||
|
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let modified_time = FileTime::from_last_modification_time(&meta);
|
||||||
|
let accessed_time = FileTime::from_last_access_time(&meta);
|
||||||
|
|
||||||
|
match set_file_times(to, accessed_time, modified_time) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => {
|
||||||
|
show_error!("{}", e);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Copy one file to a new location, changing metadata.
|
/// Copy one file to a new location, changing metadata.
|
||||||
///
|
///
|
||||||
/// Returns a Result type with the Err variant containing the error message.
|
/// Returns a Result type with the Err variant containing the error message.
|
||||||
|
@ -695,90 +840,24 @@ fn chown_optional_user_group(path: &Path, b: &Behavior) -> UResult<()> {
|
||||||
///
|
///
|
||||||
/// If the copy system call fails, we print a verbose error and return an empty error value.
|
/// If the copy system call fails, we print a verbose error and return an empty error value.
|
||||||
///
|
///
|
||||||
#[allow(clippy::cognitive_complexity)]
|
|
||||||
fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
|
fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
|
||||||
if b.compare && !need_copy(from, to, b)? {
|
if b.compare && !need_copy(from, to, b)? {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
// Declare the path here as we may need it for the verbose output below.
|
// Declare the path here as we may need it for the verbose output below.
|
||||||
let mut backup_path = None;
|
let backup_path = perform_backup(to, b)?;
|
||||||
|
|
||||||
// Perform backup, if any, before overwriting 'to'
|
copy_file(from, to)?;
|
||||||
//
|
|
||||||
// The codes actually making use of the backup process don't seem to agree
|
#[cfg(not(windows))]
|
||||||
// on how best to approach the issue. (mv and ln, for example)
|
if b.strip {
|
||||||
if to.exists() {
|
strip_file(to, b)?;
|
||||||
if b.verbose {
|
|
||||||
println!("removed {}", to.quote());
|
|
||||||
}
|
|
||||||
backup_path = backup_control::get_backup_path(b.backup_mode, to, &b.suffix);
|
|
||||||
if let Some(ref backup_path) = backup_path {
|
|
||||||
// TODO!!
|
|
||||||
if let Err(err) = fs::rename(to, backup_path) {
|
|
||||||
return Err(InstallError::BackupFailed(
|
|
||||||
to.to_path_buf(),
|
|
||||||
backup_path.to_path_buf(),
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if from.as_os_str() == "/dev/null" {
|
set_ownership_and_permissions(to, b)?;
|
||||||
/* workaround a limitation of fs::copy
|
|
||||||
* https://github.com/rust-lang/rust/issues/79390
|
|
||||||
*/
|
|
||||||
if let Err(err) = File::create(to) {
|
|
||||||
return Err(
|
|
||||||
InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if let Err(err) = fs::copy(from, to) {
|
|
||||||
return Err(InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.strip && cfg!(not(windows)) {
|
|
||||||
match process::Command::new(&b.strip_program).arg(to).output() {
|
|
||||||
Ok(o) => {
|
|
||||||
if !o.status.success() {
|
|
||||||
// Follow GNU's behavior: if strip fails, removes the target
|
|
||||||
let _ = fs::remove_file(to);
|
|
||||||
return Err(InstallError::StripProgramFailed(
|
|
||||||
String::from_utf8(o.stderr).unwrap_or_default(),
|
|
||||||
)
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
// Follow GNU's behavior: if strip fails, removes the target
|
|
||||||
let _ = fs::remove_file(to);
|
|
||||||
return Err(InstallError::StripProgramFailed(e.to_string()).into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Silent the warning as we want to the error message
|
|
||||||
#[allow(clippy::question_mark)]
|
|
||||||
if mode::chmod(to, b.mode()).is_err() {
|
|
||||||
return Err(InstallError::ChmodFailed(to.to_path_buf()).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
chown_optional_user_group(to, b)?;
|
|
||||||
|
|
||||||
if b.preserve_timestamps {
|
if b.preserve_timestamps {
|
||||||
let meta = match fs::metadata(from) {
|
preserve_timestamps(from, to)?;
|
||||||
Ok(meta) => meta,
|
|
||||||
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let modified_time = FileTime::from_last_modification_time(&meta);
|
|
||||||
let accessed_time = FileTime::from_last_access_time(&meta);
|
|
||||||
|
|
||||||
match set_file_times(to, accessed_time, modified_time) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => show_error!("{}", e),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.verbose {
|
if b.verbose {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue