151 lines
4.5 KiB
Rust
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(())
|
|
}
|
|
}
|