1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

install: Use UResult

Related to uutils#2464
This commit is contained in:
Andreas Hartmann 2021-07-13 15:57:07 +02:00
parent 6b73ddcd12
commit 2b18e45ece

View file

@ -5,7 +5,7 @@
// * For the full copyright and license information, please view the LICENSE file
// * that was distributed with this source code.
// spell-checker:ignore (ToDO) rwxr sourcepath targetpath
// spell-checker:ignore (ToDO) rwxr sourcepath targetpath Isnt
mod mode;
@ -17,15 +17,17 @@ use file_diff::diff;
use filetime::{set_file_times, FileTime};
use uucore::backup_control::{self, BackupMode};
use uucore::entries::{grp2gid, usr2uid};
use uucore::error::{FromIo, UCustomError, UIoError, UResult, USimpleError};
use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity};
use libc::{getegid, geteuid};
use std::error::Error;
use std::fmt::{Debug, Display};
use std::fs;
use std::fs::File;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::result::Result;
const DEFAULT_MODE: u32 = 0o755;
const DEFAULT_STRIP_PROGRAM: &str = "strip";
@ -47,6 +49,81 @@ pub struct Behavior {
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, String),
InstallFailed(PathBuf, PathBuf, std::io::Error),
StripProgramFailed(String),
MetadataFailed(std::io::Error),
NoSuchUser(String),
NoSuchGroup(String),
SilentError(),
}
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) => write!(f, "failed to create {}: {}", dir.display(), e),
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) => write!(
f,
"cannot backup file '{}' to '{}': {}",
from.display(),
to.display(),
e
),
IE::InstallFailed(from, to, e) => write!(
f,
"cannot install '{}' to '{}': {}",
from.display(),
to.display(),
e
),
IE::StripProgramFailed(msg) => write!(f, "strip program failed: {}", msg),
IE::MetadataFailed(e) => write!(f, "{}", e.to_string()),
IE::NoSuchUser(user) => write!(f, "no such user: {}", user),
IE::NoSuchGroup(group) => write!(f, "no such group: {}", group),
IE::SilentError() => write!(f, ""),
}
}
}
#[derive(Clone, Eq, PartialEq)]
pub enum MainFunction {
/// Create directories
@ -97,7 +174,8 @@ fn get_usage() -> String {
///
/// 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 matches = uu_app().usage(&usage[..]).get_matches_from(args);
@ -107,17 +185,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
if let Err(s) = check_unimplemented(&matches) {
show_error!("Unimplemented feature: {}", s);
return 2;
}
check_unimplemented(&matches)?;
let behavior = match behavior(&matches) {
Ok(x) => x,
Err(ret) => {
return ret;
}
};
let behavior = behavior(&matches)?;
match behavior.main_function {
MainFunction::Directory => directory(paths, behavior),
@ -269,13 +339,13 @@ pub fn uu_app() -> App<'static, 'static> {
/// 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) {
Err("--no-target-directory, -T")
Err(InstallError::Unimplemented(String::from("--no-target-directory, -T")).into())
} 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) {
Err("--context, -Z")
Err(InstallError::Unimplemented(String::from("--context, -Z")).into())
} else {
Ok(())
}
@ -289,7 +359,7 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
///
/// 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) {
MainFunction::Directory
} else {
@ -314,10 +384,7 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
matches.value_of(OPT_BACKUP),
);
let backup_mode = match backup_mode {
Err(err) => {
show_usage_error!("{}", err);
return Err(1);
}
Err(err) => return Err(USimpleError::new(1, err)),
Ok(mode) => mode,
};
@ -351,10 +418,9 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
///
/// Returns an integer intended as a program return code.
///
fn directory(paths: Vec<String>, b: Behavior) -> i32 {
fn directory(paths: Vec<String>, b: Behavior) -> UResult<()> {
if paths.is_empty() {
println!("{} with -d requires at least one argument.", executable!());
1
Err(InstallError::DirNeedsArg().into())
} else {
let mut all_successful = true;
@ -384,9 +450,9 @@ fn directory(paths: Vec<String>, b: Behavior) -> i32 {
}
}
if all_successful {
0
Ok(())
} else {
1
Err(InstallError::SilentError().into())
}
}
}
@ -403,7 +469,7 @@ fn is_new_file_path(path: &Path) -> bool {
///
/// Returns an integer intended as a program return code.
///
fn standard(mut paths: Vec<String>, b: Behavior) -> i32 {
fn standard(mut paths: Vec<String>, b: Behavior) -> UResult<()> {
let target: PathBuf = b
.target_dir
.clone()
@ -418,13 +484,11 @@ fn standard(mut paths: Vec<String>, b: Behavior) -> i32 {
if let Some(parent) = target.parent() {
if !parent.exists() && b.create_leading {
if let Err(e) = fs::create_dir_all(parent) {
show_error!("failed to create {}: {}", parent.display(), e);
return 1;
return Err(InstallError::CreateDirFailed(parent.to_path_buf(), e).into());
}
if mode::chmod(parent, b.mode()).is_err() {
show_error!("failed to chmod {}", parent.display());
return 1;
return Err(InstallError::ChmodFailed(parent.to_path_buf()).into());
}
}
}
@ -432,11 +496,7 @@ fn standard(mut paths: Vec<String>, b: Behavior) -> i32 {
if target.is_file() || is_new_file_path(&target) {
copy_file_to_file(&sources[0], &target, &b)
} else {
show_error!(
"invalid target {}: No such file or directory",
target.display()
);
1
Err(InstallError::InvalidTarget(target).into())
}
}
}
@ -451,10 +511,9 @@ fn standard(mut paths: Vec<String>, b: Behavior) -> i32 {
/// _files_ must all exist as non-directories.
/// _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() {
show_error!("target '{}' is not a directory", target_dir.display());
return 1;
return Err(InstallError::TargetDirIsntDir(target_dir.to_path_buf()).into());
}
let mut all_successful = true;
@ -484,9 +543,9 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i3
}
}
if all_successful {
0
Ok(())
} else {
1
Err(InstallError::SilentError().into())
}
}
@ -500,12 +559,8 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i3
/// _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
}
fn copy_file_to_file(file: &Path, target: &Path, b: &Behavior) -> UResult<()> {
copy(file, target, b)
}
/// Copy one file to a new location, changing metadata.
@ -520,8 +575,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.
///
#[allow(clippy::cognitive_complexity)]
fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
if b.compare && !need_copy(from, to, b) {
fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
if b.compare && !need_copy(from, to, b)? {
return Ok(());
}
// Declare the path here as we may need it for the verbose output below.
@ -536,13 +591,12 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
if let Some(ref backup_path) = backup_path {
// TODO!!
if let Err(err) = fs::rename(to, backup_path) {
show_error!(
"install: cannot backup file '{}' to '{}': {}",
to.display(),
backup_path.display(),
err
);
return Err(());
return Err(InstallError::BackupFailed(
to.to_path_buf(),
backup_path.to_path_buf(),
err.to_string(),
)
.into());
}
}
}
@ -552,52 +606,41 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
* https://github.com/rust-lang/rust/issues/79390
*/
if let Err(err) = File::create(to) {
show_error!(
"install: cannot install '{}' to '{}': {}",
from.display(),
to.display(),
err
return Err(
InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into(),
);
return Err(());
}
} else if let Err(err) = fs::copy(from, to) {
show_error!(
"cannot install '{}' to '{}': {}",
from.display(),
to.display(),
err
);
return Err(());
return Err(InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into());
}
if b.strip && cfg!(not(windows)) {
match Command::new(&b.strip_program).arg(to).output() {
Ok(o) => {
if !o.status.success() {
crash!(
1,
"strip program failed: {}",
String::from_utf8(o.stderr).unwrap_or_default()
);
return Err(InstallError::StripProgramFailed(
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() {
return Err(());
return Err(InstallError::ChmodFailed(to.to_path_buf()).into());
}
if !b.owner.is_empty() {
let meta = match fs::metadata(to) {
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) {
Ok(g) => g,
_ => crash!(1, "no such user: {}", b.owner),
_ => return Err(InstallError::NoSuchUser(b.owner.clone()).into()),
};
let gid = meta.gid();
match wrap_chown(
@ -620,12 +663,12 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
if !b.group.is_empty() {
let meta = match fs::metadata(to) {
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) {
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) {
Ok(n) => {
@ -640,7 +683,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
if b.preserve_timestamps {
let meta = match fs::metadata(from) {
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);
@ -679,14 +722,14 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
///
/// 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) {
Ok(meta) => meta,
Err(_) => return true,
Err(_) => return Ok(true),
};
let to_meta = match fs::metadata(to) {
Ok(meta) => meta,
Err(_) => return true,
Err(_) => return Ok(true),
};
// setuid || setgid || sticky
@ -696,15 +739,15 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool {
|| from_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() {
return true;
return Ok(true);
}
if from_meta.len() != to_meta.len() {
return true;
return Ok(true);
}
// TODO: if -P (#1809) and from/to contexts mismatch, return true.
@ -712,31 +755,31 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool {
if !b.owner.is_empty() {
let owner_id = match usr2uid(&b.owner) {
Ok(id) => id,
_ => crash!(1, "no such user: {}", b.owner),
_ => return Err(InstallError::NoSuchUser(b.owner.clone()).into()),
};
if owner_id != to_meta.uid() {
return true;
return Ok(true);
}
} else if !b.group.is_empty() {
let group_id = match grp2gid(&b.group) {
Ok(id) => id,
_ => crash!(1, "no such group: {}", b.group),
_ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()),
};
if group_id != to_meta.gid() {
return true;
return Ok(true);
}
} else {
#[cfg(not(target_os = "windows"))]
unsafe {
if to_meta.uid() != geteuid() || to_meta.gid() != getegid() {
return true;
return Ok(true);
}
}
}
if !diff(from.to_str().unwrap(), to.to_str().unwrap()) {
return true;
return Ok(true);
}
false
Ok(false)
}