1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2026-01-16 02:01:05 +00:00
uutils-coreutils/src/install/install.rs
2018-03-12 01:20:58 -07:00

416 lines
12 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#![crate_name = "uu_install"]
/*
* This file is part of the uutils coreutils package.
*
* (c) Ben Eills <ben@beneills.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
extern crate getopts;
extern crate libc;
mod mode;
#[macro_use]
extern crate uucore;
use std::fs;
use std::path::{Path, PathBuf};
use std::result::Result;
static NAME: &'static str = "install";
static SUMMARY: &'static str = "Copy SOURCE to DEST or multiple SOURCE(s) to the existing
DIRECTORY, while setting permission modes and owner/group";
static LONG_HELP: &'static str = "";
const DEFAULT_MODE: u32 = 755;
#[allow(dead_code)]
pub struct Behaviour {
main_function: MainFunction,
specified_mode: Option<u32>,
suffix: String,
verbose: bool,
}
#[derive(Clone, Eq, PartialEq)]
pub enum MainFunction {
/// Create directories
Directory,
/// Install files to locations (primary functionality)
Standard,
}
impl Behaviour {
/// Determine the mode for chmod after copy.
pub fn mode(&self) -> u32 {
match self.specified_mode {
Some(x) => x,
None => DEFAULT_MODE,
}
}
}
/// Main install utility function, called from main.rs.
///
/// Returns a program return code.
///
pub fn uumain(args: Vec<String>) -> i32 {
let matches = parse_opts(args);
if let Err(s) = check_unimplemented(&matches) {
show_error!("Unimplemented feature: {}", s);
return 2;
}
let behaviour = match behaviour(&matches) {
Ok(x) => x,
Err(ret) => {
return ret;
}
};
let paths: Vec<PathBuf> = {
fn string_to_path<'a>(s: &'a String) -> &'a Path {
Path::new(s)
};
let to_owned = |p: &Path| p.to_owned();
let arguments = matches.free.iter().map(string_to_path);
arguments.map(to_owned).collect()
};
match behaviour.main_function {
MainFunction::Directory => directory(&paths[..], behaviour),
MainFunction::Standard => standard(&paths[..], behaviour),
}
}
/// Build a specification of the command line.
///
/// Returns a getopts::Options struct.
///
fn parse_opts(args: Vec<String>) -> getopts::Matches {
let syntax = format!(
"SOURCE DEST
{} SOURCE... DIRECTORY",
NAME
);
new_coreopts!(&syntax, SUMMARY, LONG_HELP)
// TODO implement flag
.optflagopt("", "backup", "(unimplemented) make a backup of each existing destination\n \
file", "CONTROL")
// TODO implement flag
.optflag("b", "", "(unimplemented) like --backup but does not accept an argument")
.optflag("c", "", "ignored")
// TODO implement flag
.optflag("C", "compare", "(unimplemented) compare each pair of source and destination\n \
files, and in some cases, do not modify the destination at all")
.optflag("d", "directory", "treat all arguments as directory names.\n \
create all components of the specified directories")
// TODO implement flag
.optflag("D", "", "(unimplemented) create all leading components of DEST except the\n \
last, then copy SOURCE to DEST")
// TODO implement flag
.optflagopt("g", "group", "(unimplemented) set group ownership, instead of process'\n \
current group", "GROUP")
.optflagopt("m", "mode", "set permission mode (as in chmod), instead\n \
of rwxr-xr-x", "MODE")
// TODO implement flag
.optflagopt("o", "owner", "(unimplemented) set ownership (super-user only)",
"OWNER")
// TODO implement flag
.optflag("p", "preserve-timestamps", "(unimplemented) apply access/modification times\n \
of SOURCE files to corresponding destination files")
// TODO implement flag
.optflag("s", "strip", "(unimplemented) strip symbol tables")
// TODO implement flag
.optflagopt("", "strip-program", "(unimplemented) program used to strip binaries",
"PROGRAM")
// TODO implement flag
.optopt("S", "suffix", "(unimplemented) override the usual backup suffix", "SUFFIX")
// TODO implement flag
.optopt("t", "target-directory", "(unimplemented) move all SOURCE arguments into\n \
DIRECTORY", "DIRECTORY")
// TODO implement flag
.optflag("T", "no-target-directory", "(unimplemented) treat DEST as a normal file")
.optflag("v", "verbose", "explain what is being done")
// TODO implement flag
.optflag("P", "preserve-context", "(unimplemented) preserve security context")
// TODO implement flag
.optflagopt("Z", "context", "(unimplemented) set security context of files and\n \
directories", "CONTEXT")
.parse(args)
}
/// Check for unimplemented command line arguments.
///
/// Either return the degenerate Ok value, or an Err with string.
///
/// # Errors
///
/// Error datum is a string of the unimplemented argument.
///
fn check_unimplemented(matches: &getopts::Matches) -> Result<(), &str> {
if matches.opt_present("backup") {
Err("--backup")
} else if matches.opt_present("b") {
Err("-b")
} else if matches.opt_present("compare") {
Err("--compare, -C")
} else if matches.opt_present("D") {
Err("-D")
} else if matches.opt_present("group") {
Err("--group, -g")
} else if matches.opt_present("owner") {
Err("--owner, -o")
} else if matches.opt_present("preserve-timestamps") {
Err("--preserve-timestamps, -p")
} else if matches.opt_present("strip") {
Err("--strip, -s")
} else if matches.opt_present("strip-program") {
Err("--strip-program")
} else if matches.opt_present("suffix") {
Err("--suffix, -S")
} else if matches.opt_present("target-directory") {
Err("--target-directory, -t")
} else if matches.opt_present("no-target-directory") {
Err("--no-target-directory, -T")
} else if matches.opt_present("preserve-context") {
Err("--preserve-context, -P")
} else if matches.opt_present("context") {
Err("--context, -Z")
} else {
Ok(())
}
}
/// Determine behaviour, given command line arguments.
///
/// If successful, returns a filled-out Behaviour struct.
///
/// # Errors
///
/// In event of failure, returns an integer intended as a program return code.
///
fn behaviour(matches: &getopts::Matches) -> Result<Behaviour, i32> {
let main_function = if matches.opt_present("directory") {
MainFunction::Directory
} else {
MainFunction::Standard
};
let considering_dir: bool = MainFunction::Directory == main_function;
let specified_mode: Option<u32> = if matches.opt_present("mode") {
match matches.opt_str("mode") {
Some(x) => match mode::parse(&x[..], considering_dir) {
Ok(y) => Some(y),
Err(err) => {
show_error!("Invalid mode string: {}", err);
return Err(1);
}
},
None => {
show_error!(
"option '--mode' requires an argument\n \
Try '{} --help' for more information.",
NAME
);
return Err(1);
}
}
} else {
None
};
let backup_suffix = if matches.opt_present("suffix") {
match matches.opt_str("suffix") {
Some(x) => x,
None => {
show_error!(
"option '--suffix' requires an argument\n\
Try '{} --help' for more information.",
NAME
);
return Err(1);
}
}
} else {
"~".to_owned()
};
Ok(Behaviour {
main_function: main_function,
specified_mode: specified_mode,
suffix: backup_suffix,
verbose: matches.opt_present("v"),
})
}
/// Creates directories.
///
/// GNU man pages describe this functionality as creating 'all components of
/// the specified directories'.
///
/// Returns an integer intended as a program return code.
///
fn directory(paths: &[PathBuf], b: Behaviour) -> i32 {
if paths.len() < 1 {
println!("{} with -d requires at least one argument.", NAME);
1
} else {
let mut all_successful = true;
for directory in paths.iter() {
let path = directory.as_path();
if path.exists() {
show_info!("cannot create directory '{}': File exists", path.display());
all_successful = false;
}
if let Err(e) = fs::create_dir(directory) {
show_info!("{}: {}", path.display(), e.to_string());
all_successful = false;
}
if mode::chmod(&path, b.mode()).is_err() {
all_successful = false;
}
if b.verbose {
show_info!("created directory '{}'", path.display());
}
}
if all_successful {
0
} else {
1
}
}
}
/// Test if the path is a a new file path that can be
/// created immediately
fn is_new_file_path(path: &Path) -> bool {
path.is_file() || !path.exists() && path.parent().map(|p| p.is_dir()).unwrap_or(true)
}
/// Perform an install, given a list of paths and behaviour.
///
/// Returns an integer intended as a program return code.
///
fn standard(paths: &[PathBuf], b: Behaviour) -> i32 {
if paths.len() < 2 {
println!("{} requires at least 2 arguments.", NAME);
1
} else {
let sources = &paths[0..paths.len() - 1];
let target = &paths[paths.len() - 1];
if (target.is_file() || is_new_file_path(target)) && sources.len() == 1 {
copy_file_to_file(&sources[0], target, &b)
} else {
copy_files_into_dir(sources, target, &b)
}
}
}
/// Copy some files into a directory.
///
/// Prints verbose information and error messages.
/// Returns an integer intended as a program return code.
///
/// # Parameters
///
/// _files_ must all exist as non-directories.
/// _target_dir_ must be a directory.
///
fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behaviour) -> i32 {
if !target_dir.is_dir() {
show_error!("target {} is not a directory", target_dir.display());
return 1;
}
let mut all_successful = true;
for sourcepath in files.iter() {
let targetpath = match sourcepath.as_os_str().to_str() {
Some(name) => target_dir.join(name),
None => {
show_error!(
"cannot stat {}: No such file or directory",
sourcepath.display()
);
all_successful = false;
continue;
}
};
if copy(sourcepath, &targetpath, b).is_err() {
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: &PathBuf, target: &PathBuf, b: &Behaviour) -> i32 {
if copy(file, &target, b).is_err() {
1
} else {
0
}
}
/// Copy one file to a new location, changing metadata.
///
/// # Parameters
///
/// _from_ must exist as a non-directory.
/// _to_ must be a non-existent file, whose parent directory exists.
///
/// # Errors
///
/// If the copy system call fails, we print a verbose error and return an empty error value.
///
fn copy(from: &PathBuf, to: &PathBuf, b: &Behaviour) -> Result<(), ()> {
let io_result = fs::copy(from, to);
if let Err(err) = io_result {
show_error!(
"install: cannot install {} to {}: {}",
from.display(),
to.display(),
err
);
return Err(());
}
if mode::chmod(&to, b.mode()).is_err() {
return Err(());
}
if b.verbose {
show_info!("'{}' -> '{}'", from.display(), to.display());
}
Ok(())
}