Add chmod
applet (untested)
This commit is contained in:
parent
e648a8a83a
commit
0453acd0de
@ -21,6 +21,8 @@ code between applets, making for an overall smaller binary.
|
|||||||
- base64
|
- base64
|
||||||
- basename
|
- basename
|
||||||
- bootstrap
|
- bootstrap
|
||||||
|
- chmod
|
||||||
|
- chown
|
||||||
- clear
|
- clear
|
||||||
- cut
|
- cut
|
||||||
- dirname
|
- dirname
|
||||||
|
@ -1 +1,168 @@
|
|||||||
|
use super::{Cmd, Feedback};
|
||||||
|
use crate::mode::{Mode, Parser};
|
||||||
|
use clap::{Arg, ArgAction, Command};
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
fs::{self, File, Permissions},
|
||||||
|
io,
|
||||||
|
os::unix::prelude::{MetadataExt, PermissionsExt},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
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([
|
||||||
|
Arg::new("verbose")
|
||||||
|
.short('v')
|
||||||
|
.long("verbose")
|
||||||
|
.help("output a diagnostic for every file processed")
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("changes")
|
||||||
|
.short('c')
|
||||||
|
.long("changes")
|
||||||
|
.help("like verbose but report only when a change is made")
|
||||||
|
.conflicts_with("verbose")
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("quiet")
|
||||||
|
.short('f')
|
||||||
|
.long("silent")
|
||||||
|
.visible_alias("quiet")
|
||||||
|
.help("suppress most error messages")
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("recursive")
|
||||||
|
.short('R')
|
||||||
|
.long("recursive")
|
||||||
|
.visible_short_alias('r')
|
||||||
|
.help("change files and directories recursively")
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("mode")
|
||||||
|
.value_name("MODE")
|
||||||
|
.value_delimiter(',')
|
||||||
|
.num_args(1)
|
||||||
|
.required(true),
|
||||||
|
Arg::new("file")
|
||||||
|
.value_name("FILE")
|
||||||
|
.num_args(1..)
|
||||||
|
.required(true),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let Some(matches) = matches else {
|
||||||
|
return Err(io::Error::new(io::ErrorKind::Other, "no input").into());
|
||||||
|
};
|
||||||
|
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,
|
||||||
|
quiet: matches.get_flag("quiet"),
|
||||||
|
mode,
|
||||||
|
};
|
||||||
|
action.apply()?;
|
||||||
|
if matches.get_flag("recursive") {
|
||||||
|
if action.path.is_dir() {
|
||||||
|
action.recurse()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> Option<crate::Path> {
|
||||||
|
Some(crate::Path::Bin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Action<'a> {
|
||||||
|
path: PathBuf,
|
||||||
|
feedback: Option<Feedback>,
|
||||||
|
quiet: bool,
|
||||||
|
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 into_child(&self, entry: DirEntry) -> Self {
|
||||||
|
Self {
|
||||||
|
path: entry.path().to_path_buf(),
|
||||||
|
feedback: self.feedback,
|
||||||
|
quiet: self.quiet,
|
||||||
|
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.into_child(entry);
|
||||||
|
action.apply()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::Cmd;
|
use super::{Cmd, Feedback};
|
||||||
use crate::pw;
|
use crate::pw;
|
||||||
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command, ValueHint};
|
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command, ValueHint};
|
||||||
use std::{
|
use std::{
|
||||||
@ -165,24 +165,6 @@ impl Recurse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
enum Feedback {
|
|
||||||
Full,
|
|
||||||
Changes,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Feedback {
|
|
||||||
fn from_matches(matches: &ArgMatches) -> Option<Self> {
|
|
||||||
if matches.get_flag("verbose") {
|
|
||||||
Some(Feedback::Full)
|
|
||||||
} else if matches.get_flag("changes") {
|
|
||||||
Some(Feedback::Changes)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct User<'a> {
|
struct User<'a> {
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
|
@ -75,6 +75,7 @@ pub fn get(name: &str) -> Option<Box<dyn Cmd>> {
|
|||||||
"base32" => Some(Box::new(Base32::default())),
|
"base32" => Some(Box::new(Base32::default())),
|
||||||
"basename" => Some(Box::new(Basename::default())),
|
"basename" => Some(Box::new(Basename::default())),
|
||||||
"bootstrap" => Some(Box::new(Bootstrap::default())),
|
"bootstrap" => Some(Box::new(Bootstrap::default())),
|
||||||
|
"chmod" => Some(Box::new(chmod::Chmod::default())),
|
||||||
"chown" => Some(Box::new(chown::Chown::default())),
|
"chown" => Some(Box::new(chown::Chown::default())),
|
||||||
"clear" => Some(Box::new(Clear::default())),
|
"clear" => Some(Box::new(Clear::default())),
|
||||||
"cut" => Some(Box::new(Cut::default())),
|
"cut" => Some(Box::new(Cut::default())),
|
||||||
@ -104,11 +105,12 @@ pub fn get(name: &str) -> Option<Box<dyn Cmd>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static COMMANDS: [&str; 30] = [
|
pub static COMMANDS: [&str; 31] = [
|
||||||
"base32",
|
"base32",
|
||||||
"base64",
|
"base64",
|
||||||
"basename",
|
"basename",
|
||||||
"bootstrap",
|
"bootstrap",
|
||||||
|
"chmod",
|
||||||
"chown",
|
"chown",
|
||||||
"clear",
|
"clear",
|
||||||
"cut",
|
"cut",
|
||||||
@ -136,3 +138,21 @@ pub static COMMANDS: [&str; 30] = [
|
|||||||
"whoami",
|
"whoami",
|
||||||
"yes",
|
"yes",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
enum Feedback {
|
||||||
|
Full,
|
||||||
|
Changes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Feedback {
|
||||||
|
fn from_matches(matches: &ArgMatches) -> Option<Self> {
|
||||||
|
if matches.get_flag("verbose") {
|
||||||
|
Some(Feedback::Full)
|
||||||
|
} else if matches.get_flag("changes") {
|
||||||
|
Some(Feedback::Changes)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -54,11 +54,16 @@ impl Bit {
|
|||||||
/// Functions for extracting information about Unix modes
|
/// Functions for extracting information about Unix modes
|
||||||
pub trait Mode {
|
pub trait Mode {
|
||||||
/// Returns a string representing permissions in symbolic format
|
/// Returns a string representing permissions in symbolic format
|
||||||
|
/// including file type
|
||||||
|
fn mode_string_full(&self) -> Result<String, fmt::Error>;
|
||||||
|
|
||||||
|
/// Returns a string representing permissions in symbolic format
|
||||||
|
/// minus file type
|
||||||
fn mode_string(&self) -> Result<String, fmt::Error>;
|
fn mode_string(&self) -> Result<String, fmt::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mode for u32 {
|
impl Mode for u32 {
|
||||||
fn mode_string(&self) -> Result<String, fmt::Error> {
|
fn mode_string_full(&self) -> Result<String, fmt::Error> {
|
||||||
let b = if self & 0o40000 != 0 && self & 0o20000 != 0 {
|
let b = if self & 0o40000 != 0 && self & 0o20000 != 0 {
|
||||||
'b'
|
'b'
|
||||||
} else if self & 0o40000 != 0 {
|
} else if self & 0o40000 != 0 {
|
||||||
@ -85,6 +90,24 @@ impl Mode for u32 {
|
|||||||
.try_for_each(|b| write!(s, "{}", b.as_char(*self)))?;
|
.try_for_each(|b| write!(s, "{}", b.as_char(*self)))?;
|
||||||
Ok(s)
|
Ok(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mode_string(&self) -> Result<String, fmt::Error> {
|
||||||
|
let mut s = String::new();
|
||||||
|
[
|
||||||
|
Bit::URead,
|
||||||
|
Bit::UWrite,
|
||||||
|
Bit::UExec,
|
||||||
|
Bit::GRead,
|
||||||
|
Bit::GWrite,
|
||||||
|
Bit::GExec,
|
||||||
|
Bit::ORead,
|
||||||
|
Bit::OWrite,
|
||||||
|
Bit::OExec,
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.try_for_each(|b| write!(s, "{}", b.as_char(*self)))?;
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BitAnd<u32> for Bit {
|
impl BitAnd<u32> for Bit {
|
||||||
@ -147,49 +170,49 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn display_bits_dir() {
|
fn display_bits_dir() {
|
||||||
let m: u32 = 0o40755;
|
let m: u32 = 0o40755;
|
||||||
let s = m.mode_string().unwrap();
|
let s = m.mode_string_full().unwrap();
|
||||||
assert_eq!(s.as_str(), "drwxr-xr-x")
|
assert_eq!(s.as_str(), "drwxr-xr-x")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn display_bits_char() {
|
fn display_bits_char() {
|
||||||
let m: u32 = 0o20666;
|
let m: u32 = 0o20666;
|
||||||
let s = m.mode_string().unwrap();
|
let s = m.mode_string_full().unwrap();
|
||||||
assert_eq!(s.as_str(), "crw-rw-rw-")
|
assert_eq!(s.as_str(), "crw-rw-rw-")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn display_bits_block() {
|
fn display_bits_block() {
|
||||||
let m: u32 = 0o60660;
|
let m: u32 = 0o60660;
|
||||||
let s = m.mode_string().unwrap();
|
let s = m.mode_string_full().unwrap();
|
||||||
assert_eq!(s.as_str(), "brw-rw----")
|
assert_eq!(s.as_str(), "brw-rw----")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn display_bits_file() {
|
fn display_bits_file() {
|
||||||
let m: u32 = 0o100644;
|
let m: u32 = 0o100644;
|
||||||
let s = m.mode_string().unwrap();
|
let s = m.mode_string_full().unwrap();
|
||||||
assert_eq!(s.as_str(), "-rw-r--r--")
|
assert_eq!(s.as_str(), "-rw-r--r--")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn display_bits_suid() {
|
fn display_bits_suid() {
|
||||||
let m: u32 = 0o104755;
|
let m: u32 = 0o104755;
|
||||||
let s = m.mode_string().unwrap();
|
let s = m.mode_string_full().unwrap();
|
||||||
assert_eq!(s.as_str(), "-rwsr-xr-x")
|
assert_eq!(s.as_str(), "-rwsr-xr-x")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn display_bits_sgid() {
|
fn display_bits_sgid() {
|
||||||
let m: u32 = 0o102755;
|
let m: u32 = 0o102755;
|
||||||
let s = m.mode_string().unwrap();
|
let s = m.mode_string_full().unwrap();
|
||||||
assert_eq!(s.as_str(), "-rwxr-sr-x")
|
assert_eq!(s.as_str(), "-rwxr-sr-x")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn display_bits_sticky() {
|
fn display_bits_sticky() {
|
||||||
let m: u32 = 0o41777;
|
let m: u32 = 0o41777;
|
||||||
let s = m.mode_string().unwrap();
|
let s = m.mode_string_full().unwrap();
|
||||||
assert_eq!(s.as_str(), "drwxrwxrwt")
|
assert_eq!(s.as_str(), "drwxrwxrwt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user