shitbox/corebox/commands/chown/chgrp.rs

151 lines
4.5 KiB
Rust

use super::{Feedback, Group, Recurse, Traversal};
use clap::{Arg, ArgGroup, Command, ValueHint};
use shitbox::Cmd;
use std::{
error::Error,
fs::File,
io,
os::{fd::AsRawFd, unix::prelude::MetadataExt},
path::PathBuf,
};
use walkdir::{DirEntry, WalkDir};
#[derive(Debug, Default)]
pub struct Chgrp;
impl Cmd for Chgrp {
fn cli(&self) -> clap::Command {
Command::new("chgrp")
.about("change group ownership")
.author("Nathan Fisher")
.version(env!("CARGO_PKG_VERSION"))
.arg(
Arg::new("group")
.value_name("GROUP")
.value_hint(ValueHint::Username)
.num_args(1)
.required(true),
)
.args(super::args())
.group(
ArgGroup::new("links")
.args(["cli-traverse", "full-traverse", "no-traverse"])
.requires("recursive")
.multiple(false),
)
}
fn run(&self, matches: &clap::ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
let recurse = Recurse::from_matches(matches);
let feedback = Feedback::from_matches(matches);
let group = if let Some(grp) = matches.get_one::<String>("group") {
let gid = pw::get_gid_for_groupname(grp)
.ok_or(io::Error::new(io::ErrorKind::Other, "cannot get gid"))?;
Group { name: grp, gid }
} else {
return Err(Box::new(io::Error::new(
io::ErrorKind::Other,
"no user specified",
)));
};
if let Some(files) = matches.get_many::<String>("file") {
for f in files {
let action = Action {
path: PathBuf::from(f),
group: group.clone(),
feedback,
};
if let Some(r) = recurse {
if action.path.is_dir()
&& action.path.is_symlink()
&& r.traversal != Traversal::NoLinks
{
action.recurse(recurse)?;
}
}
action.apply()?;
}
}
Ok(())
}
fn path(&self) -> Option<shitbox::Path> {
Some(shitbox::Path::Bin)
}
}
#[derive(Debug)]
struct Action<'a> {
path: PathBuf,
group: Group<'a>,
feedback: Option<Feedback>,
}
impl Action<'_> {
fn apply(&self) -> Result<(), Box<dyn Error>> {
let fd = File::open(&self.path)?;
let meta = fd.metadata()?;
let uid = meta.uid();
let gid = meta.gid();
unsafe {
if libc::fchown(fd.as_raw_fd(), uid, self.group.gid) != 0 {
return Err(io::Error::last_os_error().into());
}
}
drop(fd);
if let Some(feedback) = self.feedback {
match feedback {
Feedback::Full => {
if self.group.gid != gid {
self.display_changes(gid)?;
} else {
self.display_retained();
}
}
Feedback::Changes => {
if self.group.gid != gid {
self.display_changes(gid)?;
}
}
}
}
Ok(())
}
fn display_changes(&self, gid: u32) -> Result<(), std::str::Utf8Error> {
let groupname = pw::get_groupname_for_gid(gid)?;
println!(
"{} changed from {groupname} to {}",
&self.path.display(),
&self.group.name
);
Ok(())
}
fn display_retained(&self) {
println!("{} retained as {}", &self.path.display(), &self.group.name);
}
fn as_child(&self, entry: DirEntry) -> Result<Self, Box<dyn Error>> {
let path = entry.path().to_path_buf();
Ok(Self {
path,
group: self.group.clone(),
feedback: self.feedback,
})
}
fn recurse(&self, recurse: Option<Recurse>) -> Result<(), Box<dyn Error>> {
let walker = WalkDir::new(&self.path)
.max_open(1)
.same_file_system(recurse.map_or(false, |x| x.same_filesystem))
.follow_links(recurse.map_or(false, |x| matches!(x.traversal, Traversal::FullLinks)));
for entry in walker {
let entry = entry?;
let action = self.as_child(entry)?;
action.apply()?;
}
Ok(())
}
}