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",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -31,12 +25,6 @@ version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.1.1"
@ -88,61 +76,12 @@ dependencies = [
"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]]
name = "data-encoding"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
[[package]]
name = "either"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "errno"
version = "0.2.8"
@ -233,15 +172,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
[[package]]
name = "memoffset"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
@ -258,28 +188,6 @@ version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "roff"
version = "0.2.1"
@ -309,12 +217,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "shitbox"
version = "0.1.0"
@ -328,7 +230,6 @@ dependencies = [
"hostname",
"libc",
"num_cpus",
"rayon",
"termcolor",
"textwrap",
"walkdir",

View File

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

View File

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