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

Add symbolic mode support to mkdir

This commit is contained in:
James Robson 2021-10-11 21:23:52 +01:00
parent 20becf8166
commit 0b2483452a
5 changed files with 136 additions and 42 deletions

View file

@ -57,7 +57,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
// Before we can parse 'args' with clap (and previously getopts), // Before we can parse 'args' with clap (and previously getopts),
// a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE"). // a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE").
let mode_had_minus_prefix = strip_minus_from_mode(&mut args); let mode_had_minus_prefix = mode::strip_minus_from_mode(&mut args);
let usage = usage(); let usage = usage();
let after_help = get_long_usage(); let after_help = get_long_usage();
@ -180,30 +180,6 @@ pub fn uu_app() -> App<'static, 'static> {
) )
} }
// Iterate 'args' and delete the first occurrence
// of a prefix '-' if it's associated with MODE
// e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE"
pub fn strip_minus_from_mode(args: &mut Vec<String>) -> bool {
for arg in args {
if arg == "--" {
break;
}
if arg.starts_with('-') {
if let Some(second) = arg.chars().nth(1) {
match second {
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => {
// TODO: use strip_prefix() once minimum rust version reaches 1.45.0
*arg = arg[1..arg.len()].to_string();
return true;
}
_ => {}
}
}
}
}
false
}
struct Chmoder { struct Chmoder {
changes: bool, changes: bool,
quiet: bool, quiet: bool,

View file

@ -5,15 +5,22 @@
// * For the full copyright and license information, please view the LICENSE // * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // * file that was distributed with this source code.
// spell-checker:ignore (ToDO) ugoa cmode
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use clap::OsValues; use clap::OsValues;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg, ArgMatches};
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError}; use uucore::error::{FromIo, UResult, USimpleError};
#[cfg(not(windows))]
use uucore::mode;
use uucore::InvalidEncodingHandling;
static DEFAULT_PERM: u32 = 0o755;
static ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist"; static ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist";
mod options { mod options {
@ -26,29 +33,81 @@ mod options {
fn usage() -> String { fn usage() -> String {
format!("{0} [OPTION]... [USER]", uucore::execution_phrase()) format!("{0} [OPTION]... [USER]", uucore::execution_phrase())
} }
fn get_long_usage() -> String {
String::from("Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.")
}
#[cfg(windows)]
fn get_mode(_matches: &ArgMatches, _mode_had_minus_prefix: bool) -> Result<u32, String> {
Ok(DEFAULT_PERM)
}
#[cfg(not(windows))]
fn get_mode(matches: &ArgMatches, mode_had_minus_prefix: bool) -> Result<u32, String> {
let digits: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
// Translate a ~str in octal form to u16, default to 755
// Not tested on Windows
let mut new_mode = DEFAULT_PERM;
match matches.value_of(options::MODE) {
Some(m) => {
for mode in m.split(',') {
if mode.contains(digits) {
new_mode = mode::parse_numeric(new_mode, m, true)?;
} else {
let cmode = if mode_had_minus_prefix {
// clap parsing is finished, now put prefix back
format!("-{}", mode)
} else {
mode.to_string()
};
new_mode = mode::parse_symbolic(new_mode, &cmode, mode::get_umask(), true)?;
}
}
Ok(new_mode)
}
None => Ok(DEFAULT_PERM),
}
}
#[cfg(windows)]
fn strip_minus_from_mode(_args: &mut Vec<String>) -> bool {
false
}
#[cfg(not(windows))]
fn strip_minus_from_mode(args: &mut Vec<String>) -> bool {
mode::strip_minus_from_mode(args)
}
#[uucore_procs::gen_uumain] #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let mut args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
// Before we can parse 'args' with clap (and previously getopts),
// a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE").
let mode_had_minus_prefix = strip_minus_from_mode(&mut args);
let usage = usage(); let usage = usage();
let after_help = get_long_usage();
// Linux-specific options, not implemented // Linux-specific options, not implemented
// opts.optflag("Z", "context", "set SELinux security context" + // opts.optflag("Z", "context", "set SELinux security context" +
// " of each created directory to CTX"), // " of each created directory to CTX"),
let matches = uu_app().usage(&usage[..]).get_matches_from(args); let matches = uu_app()
.usage(&usage[..])
.after_help(&after_help[..])
.get_matches_from(args);
let dirs = matches.values_of_os(options::DIRS).unwrap_or_default(); let dirs = matches.values_of_os(options::DIRS).unwrap_or_default();
let verbose = matches.is_present(options::VERBOSE); let verbose = matches.is_present(options::VERBOSE);
let recursive = matches.is_present(options::PARENTS); let recursive = matches.is_present(options::PARENTS);
// Translate a ~str in octal form to u16, default to 755 match get_mode(&matches, mode_had_minus_prefix) {
// Not tested on Windows Ok(mode) => exec(dirs, recursive, mode, verbose),
let mode: u16 = match matches.value_of(options::MODE) { Err(f) => Err(USimpleError::new(1, f)),
Some(m) => u16::from_str_radix(m, 8) }
.map_err(|_| USimpleError::new(1, format!("invalid mode {}", m.quote())))?,
None => 0o755_u16,
};
exec(dirs, recursive, mode, verbose)
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
@ -86,7 +145,7 @@ pub fn uu_app() -> App<'static, 'static> {
/** /**
* Create the list of new directories * Create the list of new directories
*/ */
fn exec(dirs: OsValues, recursive: bool, mode: u16, verbose: bool) -> UResult<()> { fn exec(dirs: OsValues, recursive: bool, mode: u32, verbose: bool) -> UResult<()> {
for dir in dirs { for dir in dirs {
let path = Path::new(dir); let path = Path::new(dir);
show_if_err!(mkdir(path, recursive, mode, verbose)); show_if_err!(mkdir(path, recursive, mode, verbose));
@ -94,7 +153,7 @@ fn exec(dirs: OsValues, recursive: bool, mode: u16, verbose: bool) -> UResult<()
Ok(()) Ok(())
} }
fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> UResult<()> { fn mkdir(path: &Path, recursive: bool, mode: u32, verbose: bool) -> UResult<()> {
let create_dir = if recursive { let create_dir = if recursive {
fs::create_dir_all fs::create_dir_all
} else { } else {
@ -115,18 +174,18 @@ fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> UResult<()>
} }
#[cfg(any(unix, target_os = "redox"))] #[cfg(any(unix, target_os = "redox"))]
fn chmod(path: &Path, mode: u16) -> UResult<()> { fn chmod(path: &Path, mode: u32) -> UResult<()> {
use std::fs::{set_permissions, Permissions}; use std::fs::{set_permissions, Permissions};
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
let mode = Permissions::from_mode(u32::from(mode)); let mode = Permissions::from_mode(mode);
set_permissions(path, mode) set_permissions(path, mode)
.map_err_context(|| format!("cannot set permissions {}", path.quote())) .map_err_context(|| format!("cannot set permissions {}", path.quote()))
} }
#[cfg(windows)] #[cfg(windows)]
fn chmod(_path: &Path, _mode: u16) -> UResult<()> { fn chmod(_path: &Path, _mode: u32) -> UResult<()> {
// chmod on Windows only sets the readonly flag, which isn't even honored on directories // chmod on Windows only sets the readonly flag, which isn't even honored on directories
Ok(()) Ok(())
} }

View file

@ -155,6 +155,30 @@ pub fn get_umask() -> u32 {
mask as u32 mask as u32
} }
// Iterate 'args' and delete the first occurrence
// of a prefix '-' if it's associated with MODE
// e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE"
pub fn strip_minus_from_mode(args: &mut Vec<String>) -> bool {
for arg in args {
if arg == "--" {
break;
}
if arg.starts_with('-') {
if let Some(second) = arg.chars().nth(1) {
match second {
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => {
// TODO: use strip_prefix() once minimum rust version reaches 1.45.0
*arg = arg[1..arg.len()].to_string();
return true;
}
_ => {}
}
}
}
}
false
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {

View file

@ -4,7 +4,7 @@ use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
use std::sync::Mutex; use std::sync::Mutex;
extern crate libc; extern crate libc;
use self::chmod::strip_minus_from_mode; use uucore::mode::strip_minus_from_mode;
extern crate chmod; extern crate chmod;
use self::libc::umask; use self::libc::umask;

View file

@ -1,4 +1,6 @@
use crate::common::util::*; use crate::common::util::*;
#[cfg(not(windows))]
use std::os::unix::fs::PermissionsExt;
static TEST_DIR1: &str = "mkdir_test1"; static TEST_DIR1: &str = "mkdir_test1";
static TEST_DIR2: &str = "mkdir_test2"; static TEST_DIR2: &str = "mkdir_test2";
@ -65,3 +67,36 @@ fn test_mkdir_dup_file() {
// mkdir should fail for a file even if -p is specified. // mkdir should fail for a file even if -p is specified.
scene.ucmd().arg("-p").arg(TEST_FILE7).fails(); scene.ucmd().arg("-p").arg(TEST_FILE7).fails();
} }
#[test]
#[cfg(not(windows))]
fn test_symbolic_mode() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("-m").arg("a=rwx").arg(TEST_DIR1).succeeds();
let perms = at.metadata(TEST_DIR1).permissions().mode();
assert_eq!(perms, 0o40777)
}
#[test]
#[cfg(not(windows))]
fn test_symbolic_alteration() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("-m").arg("-w").arg(TEST_DIR1).succeeds();
let perms = at.metadata(TEST_DIR1).permissions().mode();
assert_eq!(perms, 0o40555)
}
#[test]
#[cfg(not(windows))]
fn test_multi_symbolic() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("-m")
.arg("u=rwx,g=rx,o=")
.arg(TEST_DIR1)
.succeeds();
let perms = at.metadata(TEST_DIR1).permissions().mode();
assert_eq!(perms, 0o40750)
}