mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
Merge pull request #914 from beneills/implement-install
Implement install
This commit is contained in:
commit
40ae11b29c
10 changed files with 824 additions and 1 deletions
31
Cargo.lock
generated
31
Cargo.lock
generated
|
@ -8,6 +8,7 @@ dependencies = [
|
|||
"basename 0.0.1",
|
||||
"cat 0.0.1",
|
||||
"chmod 0.0.1",
|
||||
"chown 0.0.1",
|
||||
"chroot 0.0.1",
|
||||
"cksum 0.0.1",
|
||||
"comm 0.0.1",
|
||||
|
@ -31,6 +32,7 @@ dependencies = [
|
|||
"hostid 0.0.1",
|
||||
"hostname 0.0.1",
|
||||
"id 0.0.1",
|
||||
"install 0.0.1",
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kill 0.0.1",
|
||||
"libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -177,6 +179,17 @@ dependencies = [
|
|||
"walker 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chown"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uucore 0.0.1",
|
||||
"walkdir 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chroot"
|
||||
version = "0.0.1"
|
||||
|
@ -408,6 +421,15 @@ dependencies = [
|
|||
"uucore 0.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "install"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uucore 0.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.4.15"
|
||||
|
@ -1204,6 +1226,15 @@ name = "vec_map"
|
|||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walker"
|
||||
version = "1.0.0"
|
||||
|
|
|
@ -15,6 +15,7 @@ unix = [
|
|||
"hostid",
|
||||
"hostname",
|
||||
"id",
|
||||
"install",
|
||||
"kill",
|
||||
"logname",
|
||||
"mkfifo",
|
||||
|
@ -126,6 +127,7 @@ head = { optional=true, path="src/head" }
|
|||
hostid = { optional=true, path="src/hostid" }
|
||||
hostname = { optional=true, path="src/hostname" }
|
||||
id = { optional=true, path="src/id" }
|
||||
install = { optional=true, path="src/install" }
|
||||
kill = { optional=true, path="src/kill" }
|
||||
link = { optional=true, path="src/link" }
|
||||
ln = { optional=true, path="src/ln" }
|
||||
|
|
2
Makefile
2
Makefile
|
@ -107,6 +107,7 @@ UNIX_PROGS := \
|
|||
hostid \
|
||||
hostname \
|
||||
id \
|
||||
install \
|
||||
kill \
|
||||
logname \
|
||||
mkfifo \
|
||||
|
@ -152,6 +153,7 @@ TEST_PROGS := \
|
|||
fold \
|
||||
hashsum \
|
||||
head \
|
||||
install \
|
||||
link \
|
||||
ln \
|
||||
ls \
|
||||
|
|
|
@ -158,7 +158,6 @@ To do
|
|||
- df
|
||||
- expr (almost done, no regular expressions)
|
||||
- getlimits
|
||||
- install
|
||||
- join
|
||||
- ls
|
||||
- mv (almost done, one more option)
|
||||
|
|
20
src/install/Cargo.toml
Normal file
20
src/install/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "install"
|
||||
version = "0.0.1"
|
||||
authors = ["Ben Eills <ben@beneills.com>"]
|
||||
|
||||
[lib]
|
||||
name = "uu_install"
|
||||
path = "install.rs"
|
||||
|
||||
[dependencies]
|
||||
getopts = "*"
|
||||
libc = ">= 0.2"
|
||||
uucore = { path="../uucore" }
|
||||
|
||||
[dev-dependencies]
|
||||
time = "*"
|
||||
|
||||
[[bin]]
|
||||
name = "install"
|
||||
path = "main.rs"
|
427
src/install/install.rs
Normal file
427
src/install/install.rs
Normal file
|
@ -0,0 +1,427 @@
|
|||
#![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::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::result::Result;
|
||||
|
||||
static NAME: &'static str = "install";
|
||||
static VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_MODE: libc::mode_t = 755;
|
||||
|
||||
pub struct Behaviour {
|
||||
main_function: MainFunction,
|
||||
specified_mode: Option<libc::mode_t>,
|
||||
suffix: String,
|
||||
verbose: bool
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub enum MainFunction {
|
||||
/// Display version information.
|
||||
Version,
|
||||
/// Display help, including command line arguments.
|
||||
Help,
|
||||
/// Create directories
|
||||
Directory,
|
||||
/// Install files to locations (primary functionality)
|
||||
Standard
|
||||
}
|
||||
|
||||
impl Behaviour {
|
||||
/// Determine the mode for chmod after copy.
|
||||
pub fn mode(&self) -> libc::mode_t {
|
||||
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 opts = opts();
|
||||
|
||||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => m,
|
||||
Err(f) => {
|
||||
show_error!("Invalid options\n{}", f);
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
let usage = opts.usage("Copy SOURCE to DEST or multiple SOURCE(s) to the existing\n \
|
||||
DIRECTORY, while setting permission modes and owner/group");
|
||||
|
||||
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::Version => {
|
||||
println!("{} {}", NAME, VERSION);
|
||||
0
|
||||
},
|
||||
MainFunction::Help => {
|
||||
help(&usage);
|
||||
0
|
||||
},
|
||||
MainFunction::Directory => {
|
||||
directory(&paths[..], behaviour)
|
||||
},
|
||||
MainFunction::Standard => {
|
||||
standard(&paths[..], behaviour)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a specification of the comamnd line.
|
||||
///
|
||||
/// Returns a getopts::Options struct.
|
||||
///
|
||||
fn opts() -> getopts::Options {
|
||||
let mut opts = getopts::Options::new();
|
||||
|
||||
// TODO implement flag
|
||||
opts.optflagopt("", "backup", "(unimplemented) make a backup of each existing destination\n \
|
||||
file", "CONTROL");
|
||||
|
||||
// TODO implement flag
|
||||
opts.optflag("b", "", "(unimplemented) like --backup but does not accept an argument");
|
||||
|
||||
// TODO implement flag
|
||||
opts.optflag("C", "compare", "(unimplemented) compare each pair of source and destination\n \
|
||||
files, and in some cases, do not modify the destination at all");
|
||||
|
||||
opts.optflag("d", "directory", "treat all arguments as directory names;\n \
|
||||
create all components of the specified directories");
|
||||
|
||||
|
||||
// TODO implement flag
|
||||
opts.optflag("D", "", "(unimplemented) create all leading components of DEST except the\n \
|
||||
last, then copy SOURCE to DEST");
|
||||
|
||||
// TODO implement flag
|
||||
opts.optflagopt("g", "group", "(unimplemented) set group ownership, instead of process'\n \
|
||||
current group", "GROUP");
|
||||
|
||||
opts.optflagopt("m", "mode", "set permission mode (as in chmod), instead\n \
|
||||
of rwxr-xr-x", "MODE");
|
||||
|
||||
// TODO implement flag
|
||||
opts.optflagopt("o", "owner", "(unimplemented) set ownership (super-user only)",
|
||||
"OWNER");
|
||||
|
||||
// TODO implement flag
|
||||
opts.optflag("p", "preserve-timestamps", "(unimplemented) apply access/modification times\n \
|
||||
of SOURCE files to corresponding destination files");
|
||||
|
||||
// TODO implement flag
|
||||
opts.optflag("s", "strip", "(unimplemented) strip symbol tables");
|
||||
|
||||
// TODO implement flag
|
||||
opts.optflagopt("", "strip-program", "(unimplemented) program used to strip binaries",
|
||||
"PROGRAM");
|
||||
|
||||
// TODO implement flag
|
||||
opts.optopt("S", "suffix", "(unimplemented) override the usual backup suffix", "SUFFIX");
|
||||
|
||||
// TODO implement flag
|
||||
opts.optopt("t", "target-directory", "(unimplemented) move all SOURCE arguments into\n \
|
||||
DIRECTORY", "DIRECTORY");
|
||||
|
||||
// TODO implement flag
|
||||
opts.optflag("T", "no-target-directory", "(unimplemented) treat DEST as a normal file");
|
||||
|
||||
// TODO implement flag
|
||||
opts.optflag("v", "verbose", "(unimplemented) explain what is being done");
|
||||
|
||||
// TODO implement flag
|
||||
opts.optflag("P", "preserve-context", "(unimplemented) preserve security context");
|
||||
|
||||
// TODO implement flag
|
||||
opts.optflagopt("Z", "context", "(unimplemented) set security context of files and\n \
|
||||
directories", "CONTEXT");
|
||||
|
||||
opts.optflag("h", "help", "display this help and exit");
|
||||
opts.optflag("V", "version", "output version information and exit");
|
||||
|
||||
opts
|
||||
}
|
||||
|
||||
/// 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("verbose") {
|
||||
Err("--verbose, -v")
|
||||
} 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("version") {
|
||||
MainFunction::Version
|
||||
} else if matches.opt_present("help") {
|
||||
MainFunction::Help
|
||||
} else if matches.opt_present("directory") {
|
||||
MainFunction::Directory
|
||||
} else {
|
||||
MainFunction::Standard
|
||||
};
|
||||
|
||||
let considering_dir: bool = MainFunction::Directory == main_function;
|
||||
|
||||
let specified_mode: Option<libc::mode_t> = 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"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Print utility help to stdout.
|
||||
///
|
||||
fn help(usage: &str) {
|
||||
println!("{0} {1}\n\n\
|
||||
Usage: {0} SOURCE DEST\n \
|
||||
or: {0} SOURCE... DIRECTORY\n\n\
|
||||
{2}", NAME, VERSION, usage);
|
||||
}
|
||||
|
||||
/// 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 }
|
||||
}
|
||||
}
|
||||
|
||||
/// 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_directory = &paths[paths.len() - 1];
|
||||
|
||||
copy_files_into_dir(sources, target_directory, &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 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 {
|
||||
print!("‘{}’ -> ‘{}’", from.display(), to.display());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
5
src/install/main.rs
Normal file
5
src/install/main.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
extern crate uu_install;
|
||||
|
||||
fn main() {
|
||||
std::process::exit(uu_install::uumain(std::env::args().collect()));
|
||||
}
|
166
src/install/mode.rs
Normal file
166
src/install/mode.rs
Normal file
|
@ -0,0 +1,166 @@
|
|||
extern crate libc;
|
||||
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Takes a user-supplied string and tries to parse to u16 mode bitmask.
|
||||
pub fn parse(mode_string: &str, considering_dir: bool) -> Result<libc::mode_t, String> {
|
||||
let numbers: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||
|
||||
// Passing 000 as the existing permissions seems to mirror GNU behaviour.
|
||||
if mode_string.contains(numbers) {
|
||||
chmod_rs::parse_numeric(0, mode_string)
|
||||
} else {
|
||||
chmod_rs::parse_symbolic(0, mode_string, considering_dir)
|
||||
}
|
||||
}
|
||||
|
||||
/// chmod a file or directory on UNIX.
|
||||
///
|
||||
/// Adapted from mkdir.rs. Handles own error printing.
|
||||
///
|
||||
#[cfg(unix)]
|
||||
pub fn chmod(path: &Path, mode: libc::mode_t) -> Result<(), ()> {
|
||||
use std::ffi::CString;
|
||||
use std::io::Error;
|
||||
|
||||
let file = CString::new(path.as_os_str().to_str().unwrap()).
|
||||
unwrap_or_else(|e| crash!(1, "{}", e));
|
||||
let mode = mode as libc::mode_t;
|
||||
|
||||
if unsafe { libc::chmod(file.as_ptr(), mode) } != 0 {
|
||||
show_info!("{}: chmod failed with errno {}", path.display(),
|
||||
Error::last_os_error().raw_os_error().unwrap());
|
||||
return Err(());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// chmod a file or directory on Windows.
|
||||
///
|
||||
/// Adapted from mkdir.rs.
|
||||
///
|
||||
#[cfg(windows)]
|
||||
pub fn chmod(path: &Path, mode: libc::mode_t) -> Result<(), ()> {
|
||||
// chmod on Windows only sets the readonly flag, which isn't even honored on directories
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parsing functions taken from chmod.rs.
|
||||
///
|
||||
/// We keep these in a dedicated module to minimize debt of duplicated code.
|
||||
///
|
||||
mod chmod_rs {
|
||||
extern crate libc;
|
||||
|
||||
pub fn parse_numeric(fperm: libc::mode_t, mut mode: &str) -> Result<libc::mode_t, String> {
|
||||
let (op, pos) = try!(parse_op(mode, Some('=')));
|
||||
mode = mode[pos..].trim_left_matches('0');
|
||||
if mode.len() > 4 {
|
||||
Err(format!("mode is too large ({} > 7777)", mode))
|
||||
} else {
|
||||
match libc::mode_t::from_str_radix(mode, 8) {
|
||||
Ok(change) => {
|
||||
Ok(match op {
|
||||
'+' => fperm | change,
|
||||
'-' => fperm & !change,
|
||||
'=' => change,
|
||||
_ => unreachable!()
|
||||
})
|
||||
}
|
||||
Err(err) => Err(String::from("numeric parsing error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_symbolic(mut fperm: libc::mode_t, mut mode: &str, considering_dir: bool) -> Result<libc::mode_t, String> {
|
||||
let (mask, pos) = parse_levels(mode);
|
||||
if pos == mode.len() {
|
||||
return Err(format!("invalid mode ({})", mode));
|
||||
}
|
||||
let respect_umask = pos == 0;
|
||||
let last_umask = unsafe {
|
||||
libc::umask(0)
|
||||
};
|
||||
mode = &mode[pos..];
|
||||
while mode.len() > 0 {
|
||||
let (op, pos) = try!(parse_op(mode, None));
|
||||
mode = &mode[pos..];
|
||||
let (mut srwx, pos) = parse_change(mode, fperm, considering_dir);
|
||||
if respect_umask {
|
||||
srwx &= !last_umask;
|
||||
}
|
||||
mode = &mode[pos..];
|
||||
match op {
|
||||
'+' => fperm |= srwx & mask,
|
||||
'-' => fperm &= !(srwx & mask),
|
||||
'=' => fperm = (fperm & !mask) | (srwx & mask),
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
libc::umask(last_umask);
|
||||
}
|
||||
Ok(fperm)
|
||||
}
|
||||
|
||||
fn parse_levels(mode: &str) -> (libc::mode_t, usize) {
|
||||
let mut mask = 0;
|
||||
let mut pos = 0;
|
||||
for ch in mode.chars() {
|
||||
mask |= match ch {
|
||||
'u' => 0o7700,
|
||||
'g' => 0o7070,
|
||||
'o' => 0o7007,
|
||||
'a' => 0o7777,
|
||||
_ => break
|
||||
};
|
||||
pos += 1;
|
||||
}
|
||||
if pos == 0 {
|
||||
mask = 0o7777; // default to 'a'
|
||||
}
|
||||
(mask, pos)
|
||||
}
|
||||
|
||||
fn parse_op(mode: &str, default: Option<char>) -> Result<(char, usize), String> {
|
||||
match mode.chars().next() {
|
||||
Some(ch) => match ch {
|
||||
'+' | '-' | '=' => Ok((ch, 1)),
|
||||
_ => match default {
|
||||
Some(ch) => Ok((ch, 0)),
|
||||
None => Err(format!("invalid operator (expected +, -, or =, but found {})", ch))
|
||||
}
|
||||
},
|
||||
None => Err("unexpected end of mode".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_change(mode: &str, fperm: libc::mode_t, considering_dir: bool) -> (libc::mode_t, usize) {
|
||||
let mut srwx = fperm & 0o7000;
|
||||
let mut pos = 0;
|
||||
for ch in mode.chars() {
|
||||
match ch {
|
||||
'r' => srwx |= 0o444,
|
||||
'w' => srwx |= 0o222,
|
||||
'x' => srwx |= 0o111,
|
||||
'X' => {
|
||||
if considering_dir || (fperm & 0o0111) != 0 {
|
||||
srwx |= 0o111
|
||||
}
|
||||
}
|
||||
's' => srwx |= 0o4000 | 0o2000,
|
||||
't' => srwx |= 0o1000,
|
||||
'u' => srwx = (fperm & 0o700) | ((fperm >> 3) & 0o070) | ((fperm >> 6) & 0o007),
|
||||
'g' => srwx = ((fperm << 3) & 0o700) | (fperm & 0o070) | ((fperm >> 3) & 0o007),
|
||||
'o' => srwx = ((fperm << 6) & 0o700) | ((fperm << 3) & 0o070) | (fperm & 0o007),
|
||||
_ => break
|
||||
};
|
||||
pos += 1;
|
||||
}
|
||||
if pos == 0 {
|
||||
srwx = 0;
|
||||
}
|
||||
(srwx, pos)
|
||||
}
|
||||
}
|
170
tests/test_install.rs
Normal file
170
tests/test_install.rs
Normal file
|
@ -0,0 +1,170 @@
|
|||
extern crate libc;
|
||||
extern crate time;
|
||||
extern crate kernel32;
|
||||
extern crate winapi;
|
||||
extern crate filetime;
|
||||
|
||||
use self::filetime::*;
|
||||
use common::util::*;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
static UTIL_NAME: &'static str = "install";
|
||||
|
||||
#[test]
|
||||
fn test_install_help() {
|
||||
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||
|
||||
let result = ucmd.arg("--help").run();
|
||||
|
||||
assert!(result.success);
|
||||
assert_empty_stderr!(result);
|
||||
|
||||
assert!(result.stdout.contains("Usage:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_basic() {
|
||||
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||
let dir = "test_install_target_dir_dir_a";
|
||||
let file1 = "test_install_target_dir_file_a1";
|
||||
let file2 = "test_install_target_dir_file_a2";
|
||||
|
||||
at.touch(file1);
|
||||
at.touch(file2);
|
||||
at.mkdir(dir);
|
||||
let result = ucmd.arg(file1).arg(file2).arg(dir).run();
|
||||
|
||||
assert!(result.success);
|
||||
assert_empty_stderr!(result);
|
||||
|
||||
assert!(at.file_exists(file1));
|
||||
assert!(at.file_exists(file2));
|
||||
assert!(at.file_exists(&format!("{}/{}", dir, file1)));
|
||||
assert!(at.file_exists(&format!("{}/{}", dir, file2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_unimplemented_arg() {
|
||||
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||
let dir = "test_install_target_dir_dir_b";
|
||||
let file = "test_install_target_dir_file_b";
|
||||
let context_arg = "--context";
|
||||
|
||||
at.touch(file);
|
||||
at.mkdir(dir);
|
||||
let result = ucmd.arg(context_arg).arg(file).arg(dir).run();
|
||||
|
||||
assert!(!result.success);
|
||||
assert!(result.stderr.contains("Unimplemented"));
|
||||
|
||||
assert!(!at.file_exists(&format!("{}/{}", dir, file)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_component_directories() {
|
||||
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||
let component1 = "test_install_target_dir_component_c1";
|
||||
let component2 = "test_install_target_dir_component_c2";
|
||||
let component3 = "test_install_target_dir_component_c3";
|
||||
let directories_arg = "-d";
|
||||
|
||||
let result = ucmd.arg(directories_arg).arg(component1).arg(component2).arg(component3).run();
|
||||
|
||||
assert!(result.success);
|
||||
assert_empty_stderr!(result);
|
||||
|
||||
assert!(at.dir_exists(component1));
|
||||
assert!(at.dir_exists(component2));
|
||||
assert!(at.dir_exists(component3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_component_directories_failing() {
|
||||
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||
let component = "test_install_target_dir_component_d1";
|
||||
let directories_arg = "-d";
|
||||
|
||||
at.mkdir(component);
|
||||
let result = ucmd.arg(directories_arg).arg(component).run();
|
||||
|
||||
assert!(!result.success);
|
||||
assert!(result.stderr.contains("File exists"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_mode_numeric() {
|
||||
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||
let dir = "test_install_target_dir_dir_e";
|
||||
let file = "test_install_target_dir_file_e";
|
||||
let mode_arg = "--mode=333";
|
||||
|
||||
at.touch(file);
|
||||
at.mkdir(dir);
|
||||
let result = ucmd.arg(file).arg(dir).arg(mode_arg).run();
|
||||
|
||||
assert!(result.success);
|
||||
assert_empty_stderr!(result);
|
||||
|
||||
let dest_file = &format!("{}/{}", dir, file);
|
||||
assert!(at.file_exists(file));
|
||||
assert!(at.file_exists(dest_file));
|
||||
let permissions = at.metadata(dest_file).permissions();
|
||||
assert_eq!(0o333 as u32, PermissionsExt::mode(&permissions));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_mode_symbolic() {
|
||||
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||
let dir = "test_install_target_dir_dir_f";
|
||||
let file = "test_install_target_dir_file_f";
|
||||
let mode_arg = "--mode=o+wx";
|
||||
|
||||
at.touch(file);
|
||||
at.mkdir(dir);
|
||||
let result = ucmd.arg(file).arg(dir).arg(mode_arg).run();
|
||||
|
||||
assert!(result.success);
|
||||
assert_empty_stderr!(result);
|
||||
|
||||
let dest_file = &format!("{}/{}", dir, file);
|
||||
assert!(at.file_exists(file));
|
||||
assert!(at.file_exists(dest_file));
|
||||
let permissions = at.metadata(dest_file).permissions();
|
||||
assert_eq!(0o003 as u32, PermissionsExt::mode(&permissions));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_mode_failing() {
|
||||
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||
let dir = "test_install_target_dir_dir_g";
|
||||
let file = "test_install_target_dir_file_g";
|
||||
let mode_arg = "--mode=999";
|
||||
|
||||
at.touch(file);
|
||||
at.mkdir(dir);
|
||||
let result = ucmd.arg(file).arg(dir).arg(mode_arg).run();
|
||||
|
||||
assert!(!result.success);
|
||||
assert!(result.stderr.contains("Invalid mode string: numeric parsing error"));
|
||||
|
||||
let dest_file = &format!("{}/{}", dir, file);
|
||||
assert!(at.file_exists(file));
|
||||
assert!(!at.file_exists(dest_file));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_mode_directories() {
|
||||
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||
let component = "test_install_target_dir_component_h";
|
||||
let directories_arg = "-d";
|
||||
let mode_arg = "--mode=333";
|
||||
|
||||
let result = ucmd.arg(directories_arg).arg(component).arg(mode_arg).run();
|
||||
|
||||
assert!(result.success);
|
||||
assert_empty_stderr!(result);
|
||||
|
||||
assert!(at.dir_exists(component));
|
||||
let permissions = at.metadata(component).permissions();
|
||||
assert_eq!(0o333 as u32, PermissionsExt::mode(&permissions));
|
||||
}
|
|
@ -27,6 +27,7 @@ macro_rules! unix_only {
|
|||
unix_only! {
|
||||
"chmod", test_chmod;
|
||||
"chown", test_chown;
|
||||
"install", test_install;
|
||||
"mv", test_mv;
|
||||
"pathchk", test_pathchk;
|
||||
"stdbuf", test_stdbuf;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue