mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
Merge pull request #2500 from Funky185540/feature/install-use-uresult
install: Use UResult
This commit is contained in:
commit
ca0bae0dfd
2 changed files with 245 additions and 142 deletions
|
@ -5,7 +5,7 @@
|
||||||
// * For the full copyright and license information, please view the LICENSE file
|
// * For the full copyright and license information, please view the LICENSE file
|
||||||
// * that was distributed with this source code.
|
// * that was distributed with this source code.
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) rwxr sourcepath targetpath
|
// spell-checker:ignore (ToDO) rwxr sourcepath targetpath Isnt uioerror
|
||||||
|
|
||||||
mod mode;
|
mod mode;
|
||||||
|
|
||||||
|
@ -17,15 +17,17 @@ use file_diff::diff;
|
||||||
use filetime::{set_file_times, FileTime};
|
use filetime::{set_file_times, FileTime};
|
||||||
use uucore::backup_control::{self, BackupMode};
|
use uucore::backup_control::{self, BackupMode};
|
||||||
use uucore::entries::{grp2gid, usr2uid};
|
use uucore::entries::{grp2gid, usr2uid};
|
||||||
|
use uucore::error::{FromIo, UCustomError, UIoError, UResult, USimpleError};
|
||||||
use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity};
|
use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity};
|
||||||
|
|
||||||
use libc::{getegid, geteuid};
|
use libc::{getegid, geteuid};
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::{Debug, Display};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::result::Result;
|
|
||||||
|
|
||||||
const DEFAULT_MODE: u32 = 0o755;
|
const DEFAULT_MODE: u32 = 0o755;
|
||||||
const DEFAULT_STRIP_PROGRAM: &str = "strip";
|
const DEFAULT_STRIP_PROGRAM: &str = "strip";
|
||||||
|
@ -47,6 +49,87 @@ pub struct Behavior {
|
||||||
target_dir: Option<String>,
|
target_dir: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum InstallError {
|
||||||
|
Unimplemented(String),
|
||||||
|
DirNeedsArg(),
|
||||||
|
CreateDirFailed(PathBuf, std::io::Error),
|
||||||
|
ChmodFailed(PathBuf),
|
||||||
|
InvalidTarget(PathBuf),
|
||||||
|
TargetDirIsntDir(PathBuf),
|
||||||
|
BackupFailed(PathBuf, PathBuf, std::io::Error),
|
||||||
|
InstallFailed(PathBuf, PathBuf, std::io::Error),
|
||||||
|
StripProgramFailed(String),
|
||||||
|
MetadataFailed(std::io::Error),
|
||||||
|
NoSuchUser(String),
|
||||||
|
NoSuchGroup(String),
|
||||||
|
OmittingDirectory(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UCustomError for InstallError {
|
||||||
|
fn code(&self) -> i32 {
|
||||||
|
match self {
|
||||||
|
InstallError::Unimplemented(_) => 2,
|
||||||
|
_ => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for InstallError {}
|
||||||
|
|
||||||
|
impl Display for InstallError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
use InstallError as IE;
|
||||||
|
match self {
|
||||||
|
IE::Unimplemented(opt) => write!(f, "Unimplemented feature: {}", opt),
|
||||||
|
IE::DirNeedsArg() => write!(
|
||||||
|
f,
|
||||||
|
"{} with -d requires at least one argument.",
|
||||||
|
executable!()
|
||||||
|
),
|
||||||
|
IE::CreateDirFailed(dir, e) => {
|
||||||
|
Display::fmt(&uio_error!(e, "failed to create {}", dir.display()), f)
|
||||||
|
}
|
||||||
|
IE::ChmodFailed(file) => write!(f, "failed to chmod {}", file.display()),
|
||||||
|
IE::InvalidTarget(target) => write!(
|
||||||
|
f,
|
||||||
|
"invalid target {}: No such file or directory",
|
||||||
|
target.display()
|
||||||
|
),
|
||||||
|
IE::TargetDirIsntDir(target) => {
|
||||||
|
write!(f, "target '{}' is not a directory", target.display())
|
||||||
|
}
|
||||||
|
IE::BackupFailed(from, to, e) => Display::fmt(
|
||||||
|
&uio_error!(
|
||||||
|
e,
|
||||||
|
"cannot backup '{}' to '{}'",
|
||||||
|
from.display(),
|
||||||
|
to.display()
|
||||||
|
),
|
||||||
|
f,
|
||||||
|
),
|
||||||
|
IE::InstallFailed(from, to, e) => Display::fmt(
|
||||||
|
&uio_error!(
|
||||||
|
e,
|
||||||
|
"cannot install '{}' to '{}'",
|
||||||
|
from.display(),
|
||||||
|
to.display()
|
||||||
|
),
|
||||||
|
f,
|
||||||
|
),
|
||||||
|
IE::StripProgramFailed(msg) => write!(f, "strip program failed: {}", msg),
|
||||||
|
IE::MetadataFailed(e) => Display::fmt(&uio_error!(e, ""), f),
|
||||||
|
IE::NoSuchUser(user) => write!(f, "no such user: {}", user),
|
||||||
|
IE::NoSuchGroup(group) => write!(f, "no such group: {}", group),
|
||||||
|
IE::OmittingDirectory(dir) => write!(f, "omitting directory '{}'", dir.display()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub enum MainFunction {
|
pub enum MainFunction {
|
||||||
/// Create directories
|
/// Create directories
|
||||||
|
@ -97,7 +180,8 @@ fn get_usage() -> String {
|
||||||
///
|
///
|
||||||
/// Returns a program return code.
|
/// Returns a program return code.
|
||||||
///
|
///
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
#[uucore_procs::gen_uumain]
|
||||||
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let usage = get_usage();
|
let usage = get_usage();
|
||||||
|
|
||||||
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
|
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
|
||||||
|
@ -107,17 +191,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
.map(|v| v.map(ToString::to_string).collect())
|
.map(|v| v.map(ToString::to_string).collect())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if let Err(s) = check_unimplemented(&matches) {
|
check_unimplemented(&matches)?;
|
||||||
show_error!("Unimplemented feature: {}", s);
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
let behavior = match behavior(&matches) {
|
let behavior = behavior(&matches)?;
|
||||||
Ok(x) => x,
|
|
||||||
Err(ret) => {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match behavior.main_function {
|
match behavior.main_function {
|
||||||
MainFunction::Directory => directory(paths, behavior),
|
MainFunction::Directory => directory(paths, behavior),
|
||||||
|
@ -269,13 +345,13 @@ pub fn uu_app() -> App<'static, 'static> {
|
||||||
/// Error datum is a string of the unimplemented argument.
|
/// Error datum is a string of the unimplemented argument.
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
|
fn check_unimplemented(matches: &ArgMatches) -> UResult<()> {
|
||||||
if matches.is_present(OPT_NO_TARGET_DIRECTORY) {
|
if matches.is_present(OPT_NO_TARGET_DIRECTORY) {
|
||||||
Err("--no-target-directory, -T")
|
Err(InstallError::Unimplemented(String::from("--no-target-directory, -T")).into())
|
||||||
} else if matches.is_present(OPT_PRESERVE_CONTEXT) {
|
} else if matches.is_present(OPT_PRESERVE_CONTEXT) {
|
||||||
Err("--preserve-context, -P")
|
Err(InstallError::Unimplemented(String::from("--preserve-context, -P")).into())
|
||||||
} else if matches.is_present(OPT_CONTEXT) {
|
} else if matches.is_present(OPT_CONTEXT) {
|
||||||
Err("--context, -Z")
|
Err(InstallError::Unimplemented(String::from("--context, -Z")).into())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -289,7 +365,7 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
|
||||||
///
|
///
|
||||||
/// In event of failure, returns an integer intended as a program return code.
|
/// In event of failure, returns an integer intended as a program return code.
|
||||||
///
|
///
|
||||||
fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
|
fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
|
||||||
let main_function = if matches.is_present(OPT_DIRECTORY) {
|
let main_function = if matches.is_present(OPT_DIRECTORY) {
|
||||||
MainFunction::Directory
|
MainFunction::Directory
|
||||||
} else {
|
} else {
|
||||||
|
@ -314,10 +390,7 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
|
||||||
matches.value_of(OPT_BACKUP),
|
matches.value_of(OPT_BACKUP),
|
||||||
);
|
);
|
||||||
let backup_mode = match backup_mode {
|
let backup_mode = match backup_mode {
|
||||||
Err(err) => {
|
Err(err) => return Err(USimpleError::new(1, err)),
|
||||||
show_usage_error!("{}", err);
|
|
||||||
return Err(1);
|
|
||||||
}
|
|
||||||
Ok(mode) => mode,
|
Ok(mode) => mode,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -349,45 +422,46 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
|
||||||
/// GNU man pages describe this functionality as creating 'all components of
|
/// GNU man pages describe this functionality as creating 'all components of
|
||||||
/// the specified directories'.
|
/// the specified directories'.
|
||||||
///
|
///
|
||||||
/// Returns an integer intended as a program return code.
|
/// Returns a Result type with the Err variant containing the error message.
|
||||||
///
|
///
|
||||||
fn directory(paths: Vec<String>, b: Behavior) -> i32 {
|
fn directory(paths: Vec<String>, b: Behavior) -> UResult<()> {
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
println!("{} with -d requires at least one argument.", executable!());
|
Err(InstallError::DirNeedsArg().into())
|
||||||
1
|
|
||||||
} else {
|
} else {
|
||||||
let mut all_successful = true;
|
|
||||||
|
|
||||||
for path in paths.iter().map(Path::new) {
|
for path in paths.iter().map(Path::new) {
|
||||||
// if the path already exist, don't try to create it again
|
// if the path already exist, don't try to create it again
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
// Differently than the primary functionality (MainFunction::Standard), the directory
|
// Differently than the primary functionality
|
||||||
// functionality should create all ancestors (or components) of a directory regardless
|
// (MainFunction::Standard), the directory functionality should
|
||||||
// of the presence of the "-D" flag.
|
// create all ancestors (or components) of a directory
|
||||||
// NOTE: the GNU "install" sets the expected mode only for the target directory. All
|
// regardless of the presence of the "-D" flag.
|
||||||
// created ancestor directories will have the default mode. Hence it is safe to use
|
//
|
||||||
// fs::create_dir_all and then only modify the target's dir mode.
|
// NOTE: the GNU "install" sets the expected mode only for the
|
||||||
if let Err(e) = fs::create_dir_all(path) {
|
// target directory. All created ancestor directories will have
|
||||||
show_error!("{}: {}", path.display(), e);
|
// the default mode. Hence it is safe to use fs::create_dir_all
|
||||||
all_successful = false;
|
// and then only modify the target's dir mode.
|
||||||
|
if let Err(e) =
|
||||||
|
fs::create_dir_all(path).map_err_context(|| format!("{}", path.display()))
|
||||||
|
{
|
||||||
|
show!(e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.verbose {
|
if b.verbose {
|
||||||
show_error!("creating directory '{}'", path.display());
|
println!("creating directory '{}'", path.display());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode::chmod(path, b.mode()).is_err() {
|
if mode::chmod(path, b.mode()).is_err() {
|
||||||
all_successful = false;
|
// Error messages are printed by the mode::chmod function!
|
||||||
|
uucore::error::set_exit_code(1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if all_successful {
|
// If the exit code was set, or show! has been called at least once
|
||||||
0
|
// (which sets the exit code as well), function execution will end after
|
||||||
} else {
|
// this return.
|
||||||
1
|
Ok(())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,9 +475,9 @@ fn is_new_file_path(path: &Path) -> bool {
|
||||||
|
|
||||||
/// Perform an install, given a list of paths and behavior.
|
/// Perform an install, given a list of paths and behavior.
|
||||||
///
|
///
|
||||||
/// Returns an integer intended as a program return code.
|
/// Returns a Result type with the Err variant containing the error message.
|
||||||
///
|
///
|
||||||
fn standard(mut paths: Vec<String>, b: Behavior) -> i32 {
|
fn standard(mut paths: Vec<String>, b: Behavior) -> UResult<()> {
|
||||||
let target: PathBuf = b
|
let target: PathBuf = b
|
||||||
.target_dir
|
.target_dir
|
||||||
.clone()
|
.clone()
|
||||||
|
@ -418,25 +492,19 @@ fn standard(mut paths: Vec<String>, b: Behavior) -> i32 {
|
||||||
if let Some(parent) = target.parent() {
|
if let Some(parent) = target.parent() {
|
||||||
if !parent.exists() && b.create_leading {
|
if !parent.exists() && b.create_leading {
|
||||||
if let Err(e) = fs::create_dir_all(parent) {
|
if let Err(e) = fs::create_dir_all(parent) {
|
||||||
show_error!("failed to create {}: {}", parent.display(), e);
|
return Err(InstallError::CreateDirFailed(parent.to_path_buf(), e).into());
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode::chmod(parent, b.mode()).is_err() {
|
if mode::chmod(parent, b.mode()).is_err() {
|
||||||
show_error!("failed to chmod {}", parent.display());
|
return Err(InstallError::ChmodFailed(parent.to_path_buf()).into());
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if target.is_file() || is_new_file_path(&target) {
|
if target.is_file() || is_new_file_path(&target) {
|
||||||
copy_file_to_file(&sources[0], &target, &b)
|
copy(&sources[0], &target, &b)
|
||||||
} else {
|
} else {
|
||||||
show_error!(
|
Err(InstallError::InvalidTarget(target).into())
|
||||||
"invalid target {}: No such file or directory",
|
|
||||||
target.display()
|
|
||||||
);
|
|
||||||
1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -444,34 +512,30 @@ fn standard(mut paths: Vec<String>, b: Behavior) -> i32 {
|
||||||
/// Copy some files into a directory.
|
/// Copy some files into a directory.
|
||||||
///
|
///
|
||||||
/// Prints verbose information and error messages.
|
/// Prints verbose information and error messages.
|
||||||
/// Returns an integer intended as a program return code.
|
/// Returns a Result type with the Err variant containing the error message.
|
||||||
///
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
///
|
///
|
||||||
/// _files_ must all exist as non-directories.
|
/// _files_ must all exist as non-directories.
|
||||||
/// _target_dir_ must be a directory.
|
/// _target_dir_ must be a directory.
|
||||||
///
|
///
|
||||||
fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i32 {
|
fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UResult<()> {
|
||||||
if !target_dir.is_dir() {
|
if !target_dir.is_dir() {
|
||||||
show_error!("target '{}' is not a directory", target_dir.display());
|
return Err(InstallError::TargetDirIsntDir(target_dir.to_path_buf()).into());
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut all_successful = true;
|
|
||||||
for sourcepath in files.iter() {
|
for sourcepath in files.iter() {
|
||||||
if !sourcepath.exists() {
|
if !sourcepath.exists() {
|
||||||
show_error!(
|
let err = UIoError::new(
|
||||||
"cannot stat '{}': No such file or directory",
|
std::io::ErrorKind::NotFound,
|
||||||
sourcepath.display()
|
format!("cannot stat '{}'", sourcepath.display()),
|
||||||
);
|
);
|
||||||
|
show!(err);
|
||||||
all_successful = false;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if sourcepath.is_dir() {
|
if sourcepath.is_dir() {
|
||||||
show_error!("omitting directory '{}'", sourcepath.display());
|
let err = InstallError::OmittingDirectory(sourcepath.to_path_buf());
|
||||||
all_successful = false;
|
show!(err);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -479,37 +543,18 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i3
|
||||||
let filename = sourcepath.components().last().unwrap();
|
let filename = sourcepath.components().last().unwrap();
|
||||||
targetpath.push(filename);
|
targetpath.push(filename);
|
||||||
|
|
||||||
if copy(sourcepath, &targetpath, b).is_err() {
|
show_if_err!(copy(sourcepath, &targetpath, b));
|
||||||
all_successful = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if all_successful {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copy a file to another file.
|
|
||||||
///
|
|
||||||
/// Prints verbose information and error messages.
|
|
||||||
/// Returns an integer intended as a program return code.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
///
|
|
||||||
/// _file_ must exist as a non-directory.
|
|
||||||
/// _target_ must be a non-directory
|
|
||||||
///
|
|
||||||
fn copy_file_to_file(file: &Path, target: &Path, b: &Behavior) -> i32 {
|
|
||||||
if copy(file, target, b).is_err() {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
}
|
||||||
|
// If the exit code was set, or show! has been called at least once
|
||||||
|
// (which sets the exit code as well), function execution will end after
|
||||||
|
// this return.
|
||||||
|
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.
|
||||||
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
///
|
///
|
||||||
/// _from_ must exist as a non-directory.
|
/// _from_ must exist as a non-directory.
|
||||||
|
@ -520,8 +565,8 @@ fn copy_file_to_file(file: &Path, target: &Path, b: &Behavior) -> i32 {
|
||||||
/// 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)]
|
#[allow(clippy::cognitive_complexity)]
|
||||||
fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
|
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.
|
||||||
|
@ -536,13 +581,12 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
|
||||||
if let Some(ref backup_path) = backup_path {
|
if let Some(ref backup_path) = backup_path {
|
||||||
// TODO!!
|
// TODO!!
|
||||||
if let Err(err) = fs::rename(to, backup_path) {
|
if let Err(err) = fs::rename(to, backup_path) {
|
||||||
show_error!(
|
return Err(InstallError::BackupFailed(
|
||||||
"install: cannot backup file '{}' to '{}': {}",
|
to.to_path_buf(),
|
||||||
to.display(),
|
backup_path.to_path_buf(),
|
||||||
backup_path.display(),
|
err,
|
||||||
err
|
)
|
||||||
);
|
.into());
|
||||||
return Err(());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -552,52 +596,41 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
|
||||||
* https://github.com/rust-lang/rust/issues/79390
|
* https://github.com/rust-lang/rust/issues/79390
|
||||||
*/
|
*/
|
||||||
if let Err(err) = File::create(to) {
|
if let Err(err) = File::create(to) {
|
||||||
show_error!(
|
return Err(
|
||||||
"install: cannot install '{}' to '{}': {}",
|
InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into(),
|
||||||
from.display(),
|
|
||||||
to.display(),
|
|
||||||
err
|
|
||||||
);
|
);
|
||||||
return Err(());
|
|
||||||
}
|
}
|
||||||
} else if let Err(err) = fs::copy(from, to) {
|
} else if let Err(err) = fs::copy(from, to) {
|
||||||
show_error!(
|
return Err(InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into());
|
||||||
"cannot install '{}' to '{}': {}",
|
|
||||||
from.display(),
|
|
||||||
to.display(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
return Err(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.strip && cfg!(not(windows)) {
|
if b.strip && cfg!(not(windows)) {
|
||||||
match Command::new(&b.strip_program).arg(to).output() {
|
match Command::new(&b.strip_program).arg(to).output() {
|
||||||
Ok(o) => {
|
Ok(o) => {
|
||||||
if !o.status.success() {
|
if !o.status.success() {
|
||||||
crash!(
|
return Err(InstallError::StripProgramFailed(
|
||||||
1,
|
String::from_utf8(o.stderr).unwrap_or_default(),
|
||||||
"strip program failed: {}",
|
)
|
||||||
String::from_utf8(o.stderr).unwrap_or_default()
|
.into());
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => crash!(1, "strip program execution failed: {}", e),
|
Err(e) => return Err(InstallError::StripProgramFailed(e.to_string()).into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode::chmod(to, b.mode()).is_err() {
|
if mode::chmod(to, b.mode()).is_err() {
|
||||||
return Err(());
|
return Err(InstallError::ChmodFailed(to.to_path_buf()).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
if !b.owner.is_empty() {
|
if !b.owner.is_empty() {
|
||||||
let meta = match fs::metadata(to) {
|
let meta = match fs::metadata(to) {
|
||||||
Ok(meta) => meta,
|
Ok(meta) => meta,
|
||||||
Err(f) => crash!(1, "{}", f.to_string()),
|
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let owner_id = match usr2uid(&b.owner) {
|
let owner_id = match usr2uid(&b.owner) {
|
||||||
Ok(g) => g,
|
Ok(g) => g,
|
||||||
_ => crash!(1, "no such user: {}", b.owner),
|
_ => return Err(InstallError::NoSuchUser(b.owner.clone()).into()),
|
||||||
};
|
};
|
||||||
let gid = meta.gid();
|
let gid = meta.gid();
|
||||||
match wrap_chown(
|
match wrap_chown(
|
||||||
|
@ -620,12 +653,12 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
|
||||||
if !b.group.is_empty() {
|
if !b.group.is_empty() {
|
||||||
let meta = match fs::metadata(to) {
|
let meta = match fs::metadata(to) {
|
||||||
Ok(meta) => meta,
|
Ok(meta) => meta,
|
||||||
Err(f) => crash!(1, "{}", f.to_string()),
|
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let group_id = match grp2gid(&b.group) {
|
let group_id = match grp2gid(&b.group) {
|
||||||
Ok(g) => g,
|
Ok(g) => g,
|
||||||
_ => crash!(1, "no such group: {}", b.group),
|
_ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()),
|
||||||
};
|
};
|
||||||
match wrap_chgrp(to, &meta, group_id, false, Verbosity::Normal) {
|
match wrap_chgrp(to, &meta, group_id, false, Verbosity::Normal) {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
|
@ -640,7 +673,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
|
||||||
if b.preserve_timestamps {
|
if b.preserve_timestamps {
|
||||||
let meta = match fs::metadata(from) {
|
let meta = match fs::metadata(from) {
|
||||||
Ok(meta) => meta,
|
Ok(meta) => meta,
|
||||||
Err(f) => crash!(1, "{}", f.to_string()),
|
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let modified_time = FileTime::from_last_modification_time(&meta);
|
let modified_time = FileTime::from_last_modification_time(&meta);
|
||||||
|
@ -664,6 +697,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if a file is necessary to copy. This is the case when:
|
/// Return true if a file is necessary to copy. This is the case when:
|
||||||
|
///
|
||||||
/// - _from_ or _to_ is nonexistent;
|
/// - _from_ or _to_ is nonexistent;
|
||||||
/// - either file has a sticky bit or set[ug]id bit, or the user specified one;
|
/// - either file has a sticky bit or set[ug]id bit, or the user specified one;
|
||||||
/// - either file isn't a regular file;
|
/// - either file isn't a regular file;
|
||||||
|
@ -679,14 +713,14 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
|
||||||
///
|
///
|
||||||
/// Crashes the program if a nonexistent owner or group is specified in _b_.
|
/// Crashes the program if a nonexistent owner or group is specified in _b_.
|
||||||
///
|
///
|
||||||
fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool {
|
fn need_copy(from: &Path, to: &Path, b: &Behavior) -> UResult<bool> {
|
||||||
let from_meta = match fs::metadata(from) {
|
let from_meta = match fs::metadata(from) {
|
||||||
Ok(meta) => meta,
|
Ok(meta) => meta,
|
||||||
Err(_) => return true,
|
Err(_) => return Ok(true),
|
||||||
};
|
};
|
||||||
let to_meta = match fs::metadata(to) {
|
let to_meta = match fs::metadata(to) {
|
||||||
Ok(meta) => meta,
|
Ok(meta) => meta,
|
||||||
Err(_) => return true,
|
Err(_) => return Ok(true),
|
||||||
};
|
};
|
||||||
|
|
||||||
// setuid || setgid || sticky
|
// setuid || setgid || sticky
|
||||||
|
@ -696,15 +730,15 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool {
|
||||||
|| from_meta.mode() & extra_mode != 0
|
|| from_meta.mode() & extra_mode != 0
|
||||||
|| to_meta.mode() & extra_mode != 0
|
|| to_meta.mode() & extra_mode != 0
|
||||||
{
|
{
|
||||||
return true;
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !from_meta.is_file() || !to_meta.is_file() {
|
if !from_meta.is_file() || !to_meta.is_file() {
|
||||||
return true;
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if from_meta.len() != to_meta.len() {
|
if from_meta.len() != to_meta.len() {
|
||||||
return true;
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: if -P (#1809) and from/to contexts mismatch, return true.
|
// TODO: if -P (#1809) and from/to contexts mismatch, return true.
|
||||||
|
@ -712,31 +746,31 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool {
|
||||||
if !b.owner.is_empty() {
|
if !b.owner.is_empty() {
|
||||||
let owner_id = match usr2uid(&b.owner) {
|
let owner_id = match usr2uid(&b.owner) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
_ => crash!(1, "no such user: {}", b.owner),
|
_ => return Err(InstallError::NoSuchUser(b.owner.clone()).into()),
|
||||||
};
|
};
|
||||||
if owner_id != to_meta.uid() {
|
if owner_id != to_meta.uid() {
|
||||||
return true;
|
return Ok(true);
|
||||||
}
|
}
|
||||||
} else if !b.group.is_empty() {
|
} else if !b.group.is_empty() {
|
||||||
let group_id = match grp2gid(&b.group) {
|
let group_id = match grp2gid(&b.group) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
_ => crash!(1, "no such group: {}", b.group),
|
_ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()),
|
||||||
};
|
};
|
||||||
if group_id != to_meta.gid() {
|
if group_id != to_meta.gid() {
|
||||||
return true;
|
return Ok(true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
unsafe {
|
unsafe {
|
||||||
if to_meta.uid() != geteuid() || to_meta.gid() != getegid() {
|
if to_meta.uid() != geteuid() || to_meta.gid() != getegid() {
|
||||||
return true;
|
return Ok(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !diff(from.to_str().unwrap(), to.to_str().unwrap()) {
|
if !diff(from.to_str().unwrap(), to.to_str().unwrap()) {
|
||||||
return true;
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,8 @@
|
||||||
//! * Using [`ExitCode`] is not recommended but can be useful for converting utils to use
|
//! * Using [`ExitCode`] is not recommended but can be useful for converting utils to use
|
||||||
//! [`UResult`].
|
//! [`UResult`].
|
||||||
|
|
||||||
|
// spell-checker:ignore uioerror
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
fmt::{Display, Formatter},
|
fmt::{Display, Formatter},
|
||||||
|
@ -578,6 +580,73 @@ impl From<UIoError> for UError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand to construct [`UIoError`]-instances.
|
||||||
|
///
|
||||||
|
/// This macro serves as a convenience call to quickly construct instances of
|
||||||
|
/// [`UIoError`]. It takes:
|
||||||
|
///
|
||||||
|
/// - An instance of [`std::io::Error`]
|
||||||
|
/// - A `format!`-compatible string and
|
||||||
|
/// - An arbitrary number of arguments to the format string
|
||||||
|
///
|
||||||
|
/// In exactly this order. It is equivalent to the more verbose code seen in the
|
||||||
|
/// example.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use uucore::error::UIoError;
|
||||||
|
/// use uucore::uio_error;
|
||||||
|
///
|
||||||
|
/// let io_err = std::io::Error::new(
|
||||||
|
/// std::io::ErrorKind::PermissionDenied, "fix me please!"
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// let uio_err = UIoError::new(
|
||||||
|
/// io_err.kind(),
|
||||||
|
/// format!("Error code: {}", 2)
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// let other_uio_err = uio_error!(io_err, "Error code: {}", 2);
|
||||||
|
///
|
||||||
|
/// // prints "fix me please!: Permission denied"
|
||||||
|
/// println!("{}", uio_err);
|
||||||
|
/// // prints "Error code: 2: Permission denied"
|
||||||
|
/// println!("{}", other_uio_err);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The [`std::fmt::Display`] impl of [`UIoError`] will then ensure that an
|
||||||
|
/// appropriate error message relating to the actual error kind of the
|
||||||
|
/// [`std::io::Error`] is appended to whatever error message is defined in
|
||||||
|
/// addition (as secondary argument).
|
||||||
|
///
|
||||||
|
/// If you want to show only the error message for the [`std::io::ErrorKind`]
|
||||||
|
/// that's contained in [`UIoError`], pass the second argument as empty string:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use uucore::error::UIoError;
|
||||||
|
/// use uucore::uio_error;
|
||||||
|
///
|
||||||
|
/// let io_err = std::io::Error::new(
|
||||||
|
/// std::io::ErrorKind::PermissionDenied, "fix me please!"
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// let other_uio_err = uio_error!(io_err, "");
|
||||||
|
///
|
||||||
|
/// // prints: ": Permission denied"
|
||||||
|
/// println!("{}", other_uio_err);
|
||||||
|
/// ```
|
||||||
|
//#[macro_use]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! uio_error(
|
||||||
|
($err:expr, $($args:tt)+) => ({
|
||||||
|
UIoError::new(
|
||||||
|
$err.kind(),
|
||||||
|
format!($($args)+)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
/// Common errors for utilities.
|
/// Common errors for utilities.
|
||||||
///
|
///
|
||||||
/// If identical errors appear across multiple utilities, they should be added here.
|
/// If identical errors appear across multiple utilities, they should be added here.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue