|
| 1 | +// |
| 2 | +// Copyright (c) 2024 Jeff Garzik |
| 3 | +// |
| 4 | +// This file is part of the posixutils-rs project covered under |
| 5 | +// the MIT License. For the full license text, please see the LICENSE |
| 6 | +// file in the root directory of this project. |
| 7 | +// SPDX-License-Identifier: MIT |
| 8 | +// |
| 9 | + |
| 10 | +extern crate clap; |
| 11 | +extern crate libc; |
| 12 | +extern crate plib; |
| 13 | + |
| 14 | +use clap::Parser; |
| 15 | +use gettextrs::{bind_textdomain_codeset, textdomain}; |
| 16 | +use modestr::ChmodMode; |
| 17 | +use plib::{modestr, PROJECT_NAME}; |
| 18 | +use std::ffi::CString; |
| 19 | +use std::io; |
| 20 | +use std::path::PathBuf; |
| 21 | + |
| 22 | +/// mkdir - make directories |
| 23 | +#[derive(Parser, Debug)] |
| 24 | +#[command(author, version, about, long_about)] |
| 25 | +struct Args { |
| 26 | + /// Create any missing intermediate pathname components. |
| 27 | + #[arg(short, long)] |
| 28 | + parents: bool, |
| 29 | + |
| 30 | + /// Set the file permission bits of the newly-created directory to the specified mode value. |
| 31 | + #[arg(short, long)] |
| 32 | + mode: Option<String>, |
| 33 | + |
| 34 | + /// A pathname of a directory to be created. |
| 35 | + dirs: Vec<String>, |
| 36 | +} |
| 37 | + |
| 38 | +fn create_dir_with_mode(path: &str, mode: u32) -> io::Result<()> { |
| 39 | + let c_path = CString::new(path).expect("CString::new failed"); |
| 40 | + let c_mode = mode as libc::mode_t; |
| 41 | + |
| 42 | + let result = unsafe { libc::mkdir(c_path.as_ptr(), c_mode) }; |
| 43 | + if result == 0 { |
| 44 | + Ok(()) |
| 45 | + } else { |
| 46 | + Err(io::Error::last_os_error()) |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +fn do_mkdir(dirname: &str, mode: &ChmodMode, parents: bool) -> io::Result<()> { |
| 51 | + let mode_val = match mode { |
| 52 | + ChmodMode::Absolute(mode) => *mode, |
| 53 | + ChmodMode::Symbolic(sym) => modestr::mutate(0o777, sym), |
| 54 | + }; |
| 55 | + |
| 56 | + if parents { |
| 57 | + let mut path = PathBuf::new(); |
| 58 | + for part in dirname.split('/') { |
| 59 | + path.push(part); |
| 60 | + if path.is_dir() { |
| 61 | + continue; |
| 62 | + } |
| 63 | + create_dir_with_mode(&path.to_string_lossy(), mode_val)?; |
| 64 | + } |
| 65 | + } else { |
| 66 | + create_dir_with_mode(dirname, mode_val)?; |
| 67 | + } |
| 68 | + |
| 69 | + Ok(()) |
| 70 | +} |
| 71 | + |
| 72 | +fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 73 | + // parse command line arguments |
| 74 | + let args = Args::parse(); |
| 75 | + |
| 76 | + textdomain(PROJECT_NAME)?; |
| 77 | + bind_textdomain_codeset(PROJECT_NAME, "UTF-8")?; |
| 78 | + |
| 79 | + let mut exit_code = 0; |
| 80 | + |
| 81 | + // parse the mode string |
| 82 | + let mode = match args.mode { |
| 83 | + Some(mode) => modestr::parse(&mode)?, |
| 84 | + None => ChmodMode::Absolute(0o777), |
| 85 | + }; |
| 86 | + |
| 87 | + // apply the mode to each file |
| 88 | + for dirname in &args.dirs { |
| 89 | + if let Err(e) = do_mkdir(dirname, &mode, args.parents) { |
| 90 | + exit_code = 1; |
| 91 | + eprintln!("{}: {}", dirname, e); |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + std::process::exit(exit_code) |
| 96 | +} |
0 commit comments