shitbox/corebox/commands/chmod/mod.rs

155 lines
4.6 KiB
Rust

use super::{Cmd, Feedback};
use clap::{Arg, ArgAction, Command};
use mode::{Mode, Parser};
use shitbox::args;
use std::{
error::Error,
fs::{self, File, Permissions},
io,
os::unix::prelude::{MetadataExt, PermissionsExt},
path::PathBuf,
};
use walkdir::{DirEntry, WalkDir};
#[derive(Debug)]
pub struct Chmod;
impl Cmd for Chmod {
fn cli(&self) -> clap::Command {
Command::new("chmod")
.about("change file mode bits")
.author("Nathan Fisher")
.version(env!("CARGO_PKG_VERSION"))
.args([
args::verbose(),
args::changes(),
Arg::new("quiet")
.short('f')
.long("silent")
.visible_alias("quiet")
.help("suppress most error messages")
.action(ArgAction::SetTrue),
args::recursive(),
Arg::new("mode")
.value_name("MODE")
.num_args(1)
.required(true),
Arg::new("file")
.value_name("FILE")
.num_args(1..)
.required(true),
])
}
fn run(&self, matches: &clap::ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
let feedback = Feedback::from_matches(matches);
let mode = matches
.get_one::<String>("mode")
.ok_or(io::Error::new(io::ErrorKind::Other, "no mode given"))?;
if let Some(files) = matches.get_many::<String>("file") {
for f in files {
let mut path = PathBuf::from(f);
if path.is_symlink() {
path = fs::read_link(&path)?;
}
let action = Action {
path,
feedback,
mode,
};
if let Err(e) = action.apply() {
if !matches.get_flag("quiet") {
return Err(e);
}
}
if matches.get_flag("recursive") && action.path.is_dir() {
if let Err(e) = action.recurse() {
if !matches.get_flag("quiet") {
return Err(e);
}
}
}
}
}
Ok(())
}
fn path(&self) -> Option<shitbox::Path> {
Some(shitbox::Path::Bin)
}
}
struct Action<'a> {
path: PathBuf,
feedback: Option<Feedback>,
mode: &'a str,
}
impl Action<'_> {
fn apply(&self) -> Result<(), Box<dyn Error>> {
let oldmode = {
let fd = File::open(&self.path)?;
let meta = fd.metadata()?;
meta.mode()
};
let mut parser = Parser::new(oldmode);
let mode = parser.parse(self.mode)?;
let permissions = Permissions::from_mode(mode);
fs::set_permissions(&self.path, permissions)?;
if let Some(f) = self.feedback {
match f {
Feedback::Full => {
if oldmode == mode {
self.display_retained(oldmode)?;
} else {
self.display_changes(oldmode, mode)?;
}
}
Feedback::Changes => {
if oldmode != mode {
self.display_changes(oldmode, mode)?;
}
}
}
}
Ok(())
}
fn display_changes(&self, oldmode: u32, mode: u32) -> Result<(), Box<dyn Error>> {
let oldstring = oldmode.mode_string()?;
let newstring = mode.mode_string()?;
println!(
"mode of '{}' changed from {oldmode:o} ({oldstring}) to {mode:o} ({newstring})",
self.path.display()
);
Ok(())
}
fn display_retained(&self, mode: u32) -> Result<(), Box<dyn Error>> {
let modestring = mode.mode_string()?;
println!(
"mode of '{}' retained as {mode:o} ({modestring})",
self.path.display()
);
Ok(())
}
fn get_child(&self, entry: DirEntry) -> Self {
Self {
path: entry.path().to_path_buf(),
feedback: self.feedback,
mode: self.mode,
}
}
fn recurse(&self) -> Result<(), Box<dyn Error>> {
let walker = WalkDir::new(&self.path).max_open(1);
for entry in walker {
let entry = entry?;
let action = self.get_child(entry);
action.apply()?;
}
Ok(())
}
}