1
Fork 0
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:
mpkh 2016-07-14 09:21:30 +04:00 committed by GitHub
commit 40ae11b29c
10 changed files with 824 additions and 1 deletions

31
Cargo.lock generated
View file

@ -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"

View file

@ -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" }

View file

@ -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 \

View file

@ -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
View 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
View 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
View 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
View 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
View 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));
}

View file

@ -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;