Cleanup chown applet

This commit is contained in:
Nathan Fisher 2023-01-19 19:08:09 -05:00
parent 7fc064a441
commit e648a8a83a
3 changed files with 94 additions and 228 deletions

99
Cargo.lock generated
View File

@ -13,12 +13,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -31,12 +25,6 @@ version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.1.1" version = "4.1.1"
@ -88,61 +76,12 @@ dependencies = [
"roff", "roff",
] ]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "data-encoding" name = "data-encoding"
version = "2.3.3" version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
[[package]]
name = "either"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.2.8" version = "0.2.8"
@ -233,15 +172,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
[[package]]
name = "memoffset"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.15.0" version = "1.15.0"
@ -258,28 +188,6 @@ version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "rayon"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]] [[package]]
name = "roff" name = "roff"
version = "0.2.1" version = "0.2.1"
@ -309,12 +217,6 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "shitbox" name = "shitbox"
version = "0.1.0" version = "0.1.0"
@ -328,7 +230,6 @@ dependencies = [
"hostname", "hostname",
"libc", "libc",
"num_cpus", "num_cpus",
"rayon",
"termcolor", "termcolor",
"textwrap", "textwrap",
"walkdir", "walkdir",

View File

@ -15,7 +15,6 @@ data-encoding = "2.3"
hostname = { version = "0.3", features = ["set"] } hostname = { version = "0.3", features = ["set"] }
libc = "0.2" libc = "0.2"
num_cpus = "1.15" num_cpus = "1.15"
rayon = "1.6.1"
termcolor = "1.1" termcolor = "1.1"
textwrap = { version = "0.16", default-features = false, features = ["smawk"] } textwrap = { version = "0.16", default-features = false, features = ["smawk"] }
walkdir = "2.3.2" walkdir = "2.3.2"

View File

@ -1,9 +1,9 @@
use super::Cmd; use super::Cmd;
use crate::{fs::FileType, pw}; use crate::pw;
use clap::{Arg, ArgAction, ArgGroup, Command, ValueHint}; use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command, ValueHint};
use std::{ use std::{
error::Error, error::Error,
fs::{self, File}, fs::File,
io, io,
os::{fd::AsRawFd, unix::prelude::MetadataExt}, os::{fd::AsRawFd, unix::prelude::MetadataExt},
path::PathBuf, path::PathBuf,
@ -40,7 +40,6 @@ impl Cmd for Chown {
.help("operate on files and directories recursively") .help("operate on files and directories recursively")
.short('R') .short('R')
.long("recursive") .long("recursive")
.requires("links")
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
Arg::new("cli-traverse") Arg::new("cli-traverse")
.help( .help(
@ -80,59 +79,19 @@ impl Cmd for Chown {
let Some(matches) = matches else { let Some(matches) = matches else {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "no input"))); return Err(Box::new(io::Error::new(io::ErrorKind::Other, "no input")));
}; };
let recurse = if matches.get_flag("recursive") { let recurse = Recurse::from_matches(matches);
if matches.get_flag("full-traverse") { let feedback = Feedback::from_matches(matches);
Some(Recurse {
traversal: Traversal::FullLinks,
same_filesystem: matches.get_flag("same-filesystem"),
})
} else if matches.get_flag("cli-traverse") {
Some(Recurse {
traversal: Traversal::CliLinks,
same_filesystem: matches.get_flag("same-filesystem"),
})
} else {
Some(Recurse {
traversal: Traversal::NoLinks,
same_filesystem: matches.get_flag("same-filesystem"),
})
}
} else {
None
};
let feedback = if matches.get_flag("verbose") {
Some(Feedback::Full)
} else if matches.get_flag("changes") {
Some(Feedback::Changes)
} else {
None
};
let (user, group) = if let Some(who) = matches.get_one::<String>("user") { let (user, group) = if let Some(who) = matches.get_one::<String>("user") {
if let Some((u, g)) = who.split_once(':') { if let Some((u, g)) = who.split_once(':') {
let uid = pw::get_uid_for_name(u) let uid = pw::get_uid_for_name(u)
.ok_or(io::Error::new(io::ErrorKind::Other, "cannot get uid"))?; .ok_or(io::Error::new(io::ErrorKind::Other, "cannot get uid"))?;
let gid = pw::get_gid_for_groupname(g) let gid = pw::get_gid_for_groupname(g)
.ok_or(io::Error::new(io::ErrorKind::Other, "cannot get gid"))?; .ok_or(io::Error::new(io::ErrorKind::Other, "cannot get gid"))?;
( (User { name: u, uid }, Some(Group { name: g, gid }))
User {
name: u.to_string(),
uid,
},
Some(Group {
name: g.to_string(),
gid,
}),
)
} else { } else {
let uid = pw::get_uid_for_name(who) let uid = pw::get_uid_for_name(who)
.ok_or(io::Error::new(io::ErrorKind::Other, "cannot get uid"))?; .ok_or(io::Error::new(io::ErrorKind::Other, "cannot get uid"))?;
( (User { name: who, uid }, None)
User {
name: who.to_string(),
uid,
},
None,
)
} }
} else { } else {
return Err(Box::new(io::Error::new( return Err(Box::new(io::Error::new(
@ -146,9 +105,17 @@ impl Cmd for Chown {
path: PathBuf::from(f), path: PathBuf::from(f),
user: user.clone(), user: user.clone(),
group: group.clone(), group: group.clone(),
recurse,
feedback, feedback,
}; };
if let Some(r) = recurse {
if action.path.is_dir() {
action.recurse(recurse)?;
} else if action.path.is_symlink() {
if r.traversal != Traversal::NoLinks {
action.recurse(recurse)?;
}
}
}
action.apply()?; action.apply()?;
} }
} }
@ -168,10 +135,13 @@ enum Traversal {
} }
impl Traversal { impl Traversal {
fn increment(&self) -> Self { fn from_matches(matches: &ArgMatches) -> Self {
match self { if matches.get_flag("full-traverse") {
Self::CliLinks => Self::NoLinks, Traversal::FullLinks
_ => *self, } else if matches.get_flag("cli-traverse") {
Self::CliLinks
} else {
Self::NoLinks
} }
} }
} }
@ -183,10 +153,14 @@ struct Recurse {
} }
impl Recurse { impl Recurse {
fn increment(&self) -> Self { fn from_matches(matches: &ArgMatches) -> Option<Self> {
Self { if matches.get_flag("recursive") {
traversal: self.traversal.increment(), Some(Self {
same_filesystem: self.same_filesystem, traversal: Traversal::from_matches(matches),
same_filesystem: matches.get_flag("same-filesystem"),
})
} else {
None
} }
} }
} }
@ -197,28 +171,39 @@ enum Feedback {
Changes, 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 { struct User<'a> {
name: String, name: &'a str,
uid: u32, uid: u32,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct Group { struct Group<'a> {
name: String, name: &'a str,
gid: u32, gid: u32,
} }
#[derive(Debug)] #[derive(Debug)]
struct Action { struct Action<'a> {
path: PathBuf, path: PathBuf,
user: User, user: User<'a>,
group: Option<Group>, group: Option<Group<'a>>,
recurse: Option<Recurse>,
feedback: Option<Feedback>, feedback: Option<Feedback>,
} }
impl Action { impl Action<'_> {
fn apply(&self) -> Result<(), Box<dyn Error>> { fn apply(&self) -> Result<(), Box<dyn Error>> {
let fd = File::open(&self.path)?; let fd = File::open(&self.path)?;
let meta = fd.metadata()?; let meta = fd.metadata()?;
@ -234,66 +219,19 @@ impl Action {
return Err(io::Error::last_os_error().into()); return Err(io::Error::last_os_error().into());
} }
} }
let ft = FileType::from(meta); drop(fd);
match ft {
FileType::File => {}
FileType::Symlink => {
let tgt = fs::read_link(&self.path)?;
if tgt.is_dir() {
if let Some(r) = self.recurse {
if r.traversal != Traversal::NoLinks {
self.recurse()?;
}
}
}
}
FileType::Dir => {
if let Some(r) = self.recurse {
if r.traversal != Traversal::NoLinks {
self.recurse()?;
}
}
}
}
if let Some(feedback) = self.feedback { if let Some(feedback) = self.feedback {
match feedback { match feedback {
Feedback::Full => { Feedback::Full => {
if self.user.uid != uid || self.group.as_ref().map(|x| x.gid) != Some(gid) { if self.user.uid != uid || self.group.as_ref().map(|x| x.gid) != Some(gid) {
if let Some(g) = &self.group { self.display_changes(uid, gid)?;
println!(
"{} changed to {}:{}",
&self.path.display(),
&self.user.name,
&g.name
);
} else {
println!("{} changed to {}", &self.path.display(), &self.user.name);
}
} else { } else {
if let Some(g) = &self.group { self.display_retained();
println!(
"{} retained as {}:{}",
&self.path.display(),
&self.user.name,
&g.name
);
} else {
println!("{} retained as {}", &self.path.display(), &self.user.name);
}
} }
} }
Feedback::Changes => { Feedback::Changes => {
if self.user.uid != uid || self.group.as_ref().map(|x| x.gid) != Some(gid) { if self.user.uid != uid || self.group.as_ref().map(|x| x.gid) != Some(gid) {
if let Some(g) = &self.group { self.display_changes(uid, gid)?;
println!(
"{} changed to {}:{}",
self.path.display(),
&self.user.name,
&g.name
);
} else {
println!("{}, changed to {}", self.path.display(), &self.user.name);
}
} }
} }
} }
@ -301,28 +239,56 @@ impl Action {
Ok(()) Ok(())
} }
fn display_changes(&self, uid: u32, gid: u32) -> Result<(), std::str::Utf8Error> {
let username = pw::get_username_for_uid(uid)?;
let groupname = pw::get_groupname_for_gid(gid)?;
if let Some(g) = &self.group {
println!(
"{} changed from {username}:{groupname} to {}:{}",
&self.path.display(),
&self.user.name,
&g.name
);
} else {
println!(
"{} changed from {username} to {}",
&self.path.display(),
&self.user.name
);
}
Ok(())
}
fn display_retained(&self) {
if let Some(g) = &self.group {
println!(
"{} retained as {}:{}",
&self.path.display(),
&self.user.name,
&g.name
);
} else {
println!("{} retained as {}", &self.path.display(), &self.user.name);
}
}
fn into_child(&self, entry: DirEntry) -> Result<Self, Box<dyn Error>> { fn into_child(&self, entry: DirEntry) -> Result<Self, Box<dyn Error>> {
let path = entry.path().to_path_buf(); let path = entry.path().to_path_buf();
let recurse = if let Some(r) = self.recurse {
Some(r.increment())
} else {
None
};
Ok(Self { Ok(Self {
path, path,
user: self.user.clone(), user: self.user.clone(),
group: self.group.clone(), group: self.group.clone(),
recurse,
feedback: self.feedback, feedback: self.feedback,
}) })
} }
fn recurse(&self) -> Result<(), Box<dyn Error>> { fn recurse(&self, recurse: Option<Recurse>) -> Result<(), Box<dyn Error>> {
let walker = WalkDir::new(&self.path) let walker = WalkDir::new(&self.path)
.same_file_system(self.recurse.map_or(false, |x| !x.same_filesystem)) .max_open(1)
.follow_links(self.recurse.map_or(false, |x| match x.traversal { .same_file_system(recurse.map_or(false, |x| x.same_filesystem))
Traversal::NoLinks | Traversal::CliLinks => false, .follow_links(recurse.map_or(false, |x| match x.traversal {
_ => true, Traversal::FullLinks => true,
_ => false,
})); }));
for entry in walker { for entry in walker {
let entry = entry?; let entry = entry?;