use clap::builder::ValueParser;
use clap::parser::ValuesRef;
use clap::{Arg, ArgAction, ArgMatches, Command};
use std::ffi::OsString;
use std::io::{Write, stdout};
use std::path::{Path, PathBuf};
#[cfg(all(unix, target_os = "linux"))]
use uucore::error::FromIo;
use uucore::error::{UResult, USimpleError};
use uucore::translate;
#[cfg(not(windows))]
use uucore::mode;
use uucore::{display::Quotable, fs::dir_strip_dot_for_creation};
use uucore::{format_usage, show_if_err};
static DEFAULT_PERM: u32 = 0o777;
mod options {
pub const MODE: &str = "mode";
pub const PARENTS: &str = "parents";
pub const VERBOSE: &str = "verbose";
pub const DIRS: &str = "dirs";
pub const SECURITY_CONTEXT: &str = "z";
pub const CONTEXT: &str = "context";
}
pub struct Config<'a> {
pub recursive: bool,
pub mode: u32,
pub verbose: bool,
pub set_security_context: bool,
pub context: Option<&'a String>,
}
#[cfg(windows)]
fn get_mode(_matches: &ArgMatches) -> Result<u32, String> {
Ok(DEFAULT_PERM)
}
#[cfg(not(windows))]
fn get_mode(matches: &ArgMatches) -> Result<u32, String> {
if let Some(m) = matches.get_one::<String>(options::MODE) {
mode::parse_chmod(DEFAULT_PERM, m, true, mode::get_umask())
} else {
Ok(!mode::get_umask() & DEFAULT_PERM)
}
}
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
let dirs = matches
.get_many::<OsString>(options::DIRS)
.unwrap_or_default();
let verbose = matches.get_flag(options::VERBOSE);
let recursive = matches.get_flag(options::PARENTS);
let set_security_context = matches.get_flag(options::SECURITY_CONTEXT);
let context = matches.get_one::<String>(options::CONTEXT);
match get_mode(&matches) {
Ok(mode) => {
let config = Config {
recursive,
mode,
verbose,
set_security_context: set_security_context || context.is_some(),
context,
};
exec(dirs, &config)
}
Err(f) => Err(USimpleError::new(1, f)),
}
}
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(uucore::crate_version!())
.help_template(uucore::localized_help_template(uucore::util_name()))
.about(translate!("mkdir-about"))
.override_usage(format_usage(&translate!("mkdir-usage")))
.infer_long_args(true)
.after_help(translate!("mkdir-after-help"))
.arg(
Arg::new(options::MODE)
.short('m')
.long(options::MODE)
.help(translate!("mkdir-help-mode"))
.allow_hyphen_values(true)
.num_args(1),
)
.arg(
Arg::new(options::PARENTS)
.short('p')
.long(options::PARENTS)
.help(translate!("mkdir-help-parents"))
.overrides_with(options::PARENTS)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::VERBOSE)
.short('v')
.long(options::VERBOSE)
.help(translate!("mkdir-help-verbose"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::SECURITY_CONTEXT)
.short('Z')
.help(translate!("mkdir-help-selinux"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::CONTEXT)
.long(options::CONTEXT)
.value_name("CTX")
.help(translate!("mkdir-help-context")),
)
.arg(
Arg::new(options::DIRS)
.action(ArgAction::Append)
.num_args(1..)
.required(true)
.value_parser(ValueParser::os_string())
.value_hint(clap::ValueHint::DirPath),
)
}
fn exec(dirs: ValuesRef<OsString>, config: &Config) -> UResult<()> {
for dir in dirs {
let path_buf = PathBuf::from(dir);
let path = path_buf.as_path();
show_if_err!(mkdir(path, config));
}
Ok(())
}
pub fn mkdir(path: &Path, config: &Config) -> UResult<()> {
if path.as_os_str().is_empty() {
return Err(USimpleError::new(
1,
translate!("mkdir-error-empty-directory-name"),
));
}
let path_buf = dir_strip_dot_for_creation(path);
let path = path_buf.as_path();
create_dir(path, false, config)
}
#[cfg(all(unix, target_os = "linux"))]
fn chmod(path: &Path, mode: u32) -> UResult<()> {
use std::fs::{Permissions, set_permissions};
use std::os::unix::fs::PermissionsExt;
let mode = Permissions::from_mode(mode);
set_permissions(path, mode).map_err_context(
|| translate!("mkdir-error-cannot-set-permissions", "path" => path.quote()),
)
}
fn create_dir(path: &Path, is_parent: bool, config: &Config) -> UResult<()> {
let path_exists = path.exists();
if path_exists && !config.recursive {
return Err(USimpleError::new(
1,
translate!("mkdir-error-file-exists", "path" => path.maybe_quote()),
));
}
if path == Path::new("") {
return Ok(());
}
if config.recursive {
let mut dirs_to_create = Vec::with_capacity(16);
let mut current = path;
while let Some(parent) = current.parent() {
if parent == Path::new("") {
break;
}
dirs_to_create.push(parent);
current = parent;
}
for dir in dirs_to_create.iter().rev() {
if !dir.exists() {
create_single_dir(dir, true, config)?;
}
}
}
create_single_dir(path, is_parent, config)
}
#[cfg(unix)]
struct UmaskGuard(uucore::libc::mode_t);
#[cfg(unix)]
impl UmaskGuard {
fn set(new_mask: uucore::libc::mode_t) -> Self {
let old_mask = unsafe { uucore::libc::umask(new_mask) };
Self(old_mask)
}
}
#[cfg(unix)]
impl Drop for UmaskGuard {
fn drop(&mut self) {
unsafe {
uucore::libc::umask(self.0);
}
}
}
#[cfg(unix)]
fn create_dir_with_mode(path: &Path, mode: u32) -> std::io::Result<()> {
use std::os::unix::fs::DirBuilderExt;
let _guard = UmaskGuard::set(0);
std::fs::DirBuilder::new().mode(mode).create(path)
}
#[cfg(not(unix))]
fn create_dir_with_mode(path: &Path, _mode: u32) -> std::io::Result<()> {
std::fs::create_dir(path)
}
#[allow(unused_variables)]
fn create_single_dir(path: &Path, is_parent: bool, config: &Config) -> UResult<()> {
let path_exists = path.exists();
#[cfg(unix)]
let create_mode = if is_parent {
(!mode::get_umask() & 0o777) | 0o300
} else {
config.mode
};
#[cfg(not(unix))]
let create_mode = config.mode;
match create_dir_with_mode(path, create_mode) {
Ok(()) => {
if config.verbose {
writeln!(
stdout(),
"{}",
translate!("mkdir-verbose-created-directory", "util_name" => uucore::util_name(), "path" => path.quote())
)?;
}
#[cfg(all(unix, target_os = "linux"))]
if !path_exists {
let acl_perm_bits = uucore::fsxattr::get_acl_perm_bits_from_xattr(path);
if acl_perm_bits != 0 {
chmod(path, create_mode | acl_perm_bits)?;
}
}
#[cfg(feature = "selinux")]
if config.set_security_context && uucore::selinux::is_selinux_enabled() {
if let Err(e) = uucore::selinux::set_selinux_security_context(path, config.context)
{
let _ = std::fs::remove_dir(path);
return Err(USimpleError::new(1, e.to_string()));
}
}
#[cfg(feature = "smack")]
if config.set_security_context {
uucore::smack::set_smack_label_and_cleanup(path, config.context, |p| {
std::fs::remove_dir(p)
})?;
}
Ok(())
}
Err(_) if path.is_dir() => {
let ends_with_parent_dir = matches!(
path.components().next_back(),
Some(std::path::Component::ParentDir)
);
if config.verbose && is_parent && config.recursive && !ends_with_parent_dir {
writeln!(
stdout(),
"{}",
translate!("mkdir-verbose-created-directory", "util_name" => uucore::util_name(), "path" => path.quote())
)?;
}
Ok(())
}
Err(e) => Err(e.into()),
}
}