Add chown
applet (untested not optimized)
This commit is contained in:
parent
fcf416ea3b
commit
3016f4c234
120
Cargo.lock
generated
120
Cargo.lock
generated
@ -13,6 +13,12 @@ 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"
|
||||||
@ -25,6 +31,12 @@ 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"
|
||||||
@ -76,12 +88,61 @@ 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"
|
||||||
@ -172,6 +233,15 @@ 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"
|
||||||
@ -188,6 +258,28 @@ 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"
|
||||||
@ -208,6 +300,21 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"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"
|
||||||
@ -221,8 +328,10 @@ dependencies = [
|
|||||||
"hostname",
|
"hostname",
|
||||||
"libc",
|
"libc",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
|
"rayon",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -255,6 +364,17 @@ dependencies = [
|
|||||||
"smawk",
|
"smawk",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
@ -15,8 +15,10 @@ 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"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
288
src/cmd/chown/mod.rs
Normal file
288
src/cmd/chown/mod.rs
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
use super::Cmd;
|
||||||
|
use crate::{fs::FileType, pw};
|
||||||
|
use clap::{Arg, ArgAction, ArgGroup, Command, ValueHint};
|
||||||
|
use std::{error::Error, fs::{File, self}, io, path::PathBuf, os::{unix::prelude::MetadataExt, fd::AsRawFd}};
|
||||||
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Chown;
|
||||||
|
|
||||||
|
impl Cmd for Chown {
|
||||||
|
fn cli(&self) -> clap::Command {
|
||||||
|
Command::new("chown")
|
||||||
|
.about("change file owner and group")
|
||||||
|
.author("Nathan Fisher")
|
||||||
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
|
.args([
|
||||||
|
Arg::new("user")
|
||||||
|
.value_name("OWNER[:GROUP]")
|
||||||
|
.value_hint(ValueHint::Username)
|
||||||
|
.num_args(1)
|
||||||
|
.required(true),
|
||||||
|
Arg::new("changes")
|
||||||
|
.help("report only when a change is made")
|
||||||
|
.short('c')
|
||||||
|
.long("changes")
|
||||||
|
.conflicts_with("verbose")
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("verbose")
|
||||||
|
.help("output a diagnostic for every file processed")
|
||||||
|
.short('v')
|
||||||
|
.long("verbose")
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("recursive")
|
||||||
|
.help("operate on files and directories recursively")
|
||||||
|
.short('R')
|
||||||
|
.long("recursive")
|
||||||
|
.requires("links")
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("cli-traverse")
|
||||||
|
.help(
|
||||||
|
"if a command line argument is a symbolic link to a directory, traverse it",
|
||||||
|
)
|
||||||
|
.short('H')
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("full-traverse")
|
||||||
|
.help("traverse every symbolic link encountered in a directory")
|
||||||
|
.short('L')
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("no-traverse")
|
||||||
|
.help("do not traverse any symbolic links (default)")
|
||||||
|
.short('P')
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("same-filesystem")
|
||||||
|
.help("do not cross filesystem boundaries (requires recursive)")
|
||||||
|
.short('s')
|
||||||
|
.requires("recursive")
|
||||||
|
.long("same-filesystem")
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("file")
|
||||||
|
.value_name("FILE")
|
||||||
|
.value_hint(ValueHint::AnyPath)
|
||||||
|
.num_args(1..)
|
||||||
|
.required(true),
|
||||||
|
])
|
||||||
|
.group(
|
||||||
|
ArgGroup::new("links")
|
||||||
|
.args(["cli-traverse", "full-traverse", "no-traverse"])
|
||||||
|
.requires("recursive")
|
||||||
|
.multiple(false),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
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 (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 }))
|
||||||
|
} 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)
|
||||||
|
}
|
||||||
|
} 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),
|
||||||
|
user: user.clone(),
|
||||||
|
group: group.clone(),
|
||||||
|
recurse,
|
||||||
|
feedback,
|
||||||
|
};
|
||||||
|
action.apply()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> Option<crate::Path> {
|
||||||
|
Some(crate::Path::Bin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
enum Traversal {
|
||||||
|
CliLinks,
|
||||||
|
FullLinks,
|
||||||
|
NoLinks,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Traversal {
|
||||||
|
fn increment(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::CliLinks => Self::NoLinks,
|
||||||
|
_ => *self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
struct Recurse {
|
||||||
|
traversal: Traversal,
|
||||||
|
same_filesystem: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Recurse {
|
||||||
|
fn increment(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
traversal: self.traversal.increment(),
|
||||||
|
same_filesystem: self.same_filesystem,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
enum Feedback {
|
||||||
|
Full,
|
||||||
|
Changes,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct User {
|
||||||
|
name: String,
|
||||||
|
uid: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Group {
|
||||||
|
name: String,
|
||||||
|
gid: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Action {
|
||||||
|
path: PathBuf,
|
||||||
|
user: User,
|
||||||
|
group: Option<Group>,
|
||||||
|
recurse: Option<Recurse>,
|
||||||
|
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(), self.user.uid, self.group.clone().map(|x| x.gid).unwrap_or(gid)) != 0 {
|
||||||
|
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()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if let Some(feedback) = self.feedback {
|
||||||
|
match feedback {
|
||||||
|
Feedback::Full => {
|
||||||
|
if self.user.uid != uid || self.group.clone().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);
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Feedback::Changes => {
|
||||||
|
if self.user.uid != uid || self.group.clone().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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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>> {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
for entry in walker {
|
||||||
|
let entry = entry?;
|
||||||
|
let action = self.into_child(entry)?;
|
||||||
|
action.apply()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ mod basename;
|
|||||||
mod bootstrap;
|
mod bootstrap;
|
||||||
mod cat;
|
mod cat;
|
||||||
mod chmod;
|
mod chmod;
|
||||||
|
mod chown;
|
||||||
mod clear;
|
mod clear;
|
||||||
mod cp;
|
mod cp;
|
||||||
mod cut;
|
mod cut;
|
||||||
@ -44,10 +45,11 @@ mod yes;
|
|||||||
#[allow(clippy::module_name_repetitions)]
|
#[allow(clippy::module_name_repetitions)]
|
||||||
pub use {
|
pub use {
|
||||||
self::hostname::Hostname, self::shitbox::Shitbox, base32::Base32, base64::Base64,
|
self::hostname::Hostname, self::shitbox::Shitbox, base32::Base32, base64::Base64,
|
||||||
basename::Basename, bootstrap::Bootstrap, clear::Clear, cut::Cut, dirname::Dirname, echo::Echo,
|
basename::Basename, bootstrap::Bootstrap, chown::Chown, clear::Clear, cut::Cut,
|
||||||
factor::Factor, fold::Fold, groups::Groups, head::Head, link::Link, mountpoint::Mountpoint,
|
dirname::Dirname, echo::Echo, factor::Factor, fold::Fold, groups::Groups, head::Head,
|
||||||
nologin::Nologin, nproc::Nproc, r#false::False, r#true::True, rev::Rev, rm::Rm, rmdir::Rmdir,
|
link::Link, mountpoint::Mountpoint, nologin::Nologin, nproc::Nproc, r#false::False,
|
||||||
sleep::Sleep, sync::Sync as SyncCmd, unlink::Unlink, which::Which, whoami::Whoami, yes::Yes,
|
r#true::True, rev::Rev, rm::Rm, rmdir::Rmdir, sleep::Sleep, sync::Sync as SyncCmd,
|
||||||
|
unlink::Unlink, which::Which, whoami::Whoami, yes::Yes,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Defines a command or applet, it's cli interface, and it's installation directory
|
/// Defines a command or applet, it's cli interface, and it's installation directory
|
||||||
@ -73,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())),
|
||||||
|
"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())),
|
||||||
"dirname" => Some(Box::new(Dirname::default())),
|
"dirname" => Some(Box::new(Dirname::default())),
|
||||||
@ -101,11 +104,12 @@ pub fn get(name: &str) -> Option<Box<dyn Cmd>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static COMMANDS: [&str; 29] = [
|
pub static COMMANDS: [&str; 30] = [
|
||||||
"base32",
|
"base32",
|
||||||
"base64",
|
"base64",
|
||||||
"basename",
|
"basename",
|
||||||
"bootstrap",
|
"bootstrap",
|
||||||
|
"chown",
|
||||||
"clear",
|
"clear",
|
||||||
"cut",
|
"cut",
|
||||||
"dirname",
|
"dirname",
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use super::Cmd;
|
use super::Cmd;
|
||||||
|
use crate::fs::FileType;
|
||||||
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command, ValueHint};
|
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command, ValueHint};
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
fmt,
|
fmt,
|
||||||
fs::{self, File, Metadata},
|
fs::{self, File},
|
||||||
io,
|
io,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
@ -142,39 +143,6 @@ impl FromStr for When {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Filetype {
|
|
||||||
File,
|
|
||||||
Dir,
|
|
||||||
Symlink,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Metadata> for Filetype {
|
|
||||||
fn from(meta: Metadata) -> Self {
|
|
||||||
let ft = meta.file_type();
|
|
||||||
if ft.is_dir() {
|
|
||||||
Self::Dir
|
|
||||||
} else if ft.is_symlink() {
|
|
||||||
Self::Symlink
|
|
||||||
} else {
|
|
||||||
Self::File
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Filetype {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
match self {
|
|
||||||
Self::File => "file",
|
|
||||||
Self::Symlink => "symlink",
|
|
||||||
Self::Dir => "directory",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Action {
|
struct Action {
|
||||||
path: String,
|
path: String,
|
||||||
prompt: When,
|
prompt: When,
|
||||||
@ -200,7 +168,7 @@ impl Action {
|
|||||||
} else {
|
} else {
|
||||||
match File::open(&self.path)
|
match File::open(&self.path)
|
||||||
.and_then(|fd| fd.metadata())
|
.and_then(|fd| fd.metadata())
|
||||||
.and_then(|meta| Ok(Filetype::from(meta)))
|
.and_then(|meta| Ok(FileType::from(meta)))
|
||||||
{
|
{
|
||||||
Ok(ft) => ft,
|
Ok(ft) => ft,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -213,13 +181,13 @@ impl Action {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
match ft {
|
match ft {
|
||||||
Filetype::File | Filetype::Symlink => {
|
FileType::File | FileType::Symlink => {
|
||||||
fs::remove_file(&self.path)?;
|
fs::remove_file(&self.path)?;
|
||||||
if self.verbose {
|
if self.verbose {
|
||||||
println!("removed '{}'", &self.path);
|
println!("removed '{}'", &self.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Filetype::Dir => {
|
FileType::Dir => {
|
||||||
if self.recursive {
|
if self.recursive {
|
||||||
match fs::read_dir(&self.path) {
|
match fs::read_dir(&self.path) {
|
||||||
Ok(items) => {
|
Ok(items) => {
|
||||||
@ -253,18 +221,18 @@ impl Action {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prompt(&self) -> Result<Option<Filetype>, Box<dyn Error>> {
|
fn prompt(&self) -> Result<Option<FileType>, Box<dyn Error>> {
|
||||||
let path = PathBuf::from(&self.path);
|
let path = PathBuf::from(&self.path);
|
||||||
let ft: Filetype = if path.is_dir() {
|
let ft: FileType = if path.is_dir() {
|
||||||
if self.recursive {
|
if self.recursive {
|
||||||
return Ok(Some(Filetype::Dir));
|
return Ok(Some(FileType::Dir));
|
||||||
} else {
|
} else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
File::open(path)
|
File::open(path)
|
||||||
.and_then(|fd| fd.metadata())
|
.and_then(|fd| fd.metadata())
|
||||||
.and_then(|meta| Ok(Filetype::from(meta)))?
|
.and_then(|meta| Ok(FileType::from(meta)))?
|
||||||
};
|
};
|
||||||
eprint!("rm: remove {ft} '{}'? ", &self.path);
|
eprint!("rm: remove {ft} '{}'? ", &self.path);
|
||||||
let mut reply = String::new();
|
let mut reply = String::new();
|
||||||
|
60
src/fs/mod.rs
Normal file
60
src/fs/mod.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
fs::{self, DirEntry, Metadata},
|
||||||
|
io,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum FileType {
|
||||||
|
File,
|
||||||
|
Dir,
|
||||||
|
Symlink,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Metadata> for FileType {
|
||||||
|
fn from(meta: Metadata) -> Self {
|
||||||
|
let ft = meta.file_type();
|
||||||
|
if ft.is_dir() {
|
||||||
|
Self::Dir
|
||||||
|
} else if ft.is_symlink() {
|
||||||
|
Self::Symlink
|
||||||
|
} else {
|
||||||
|
Self::File
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<fs::FileType> for FileType {
|
||||||
|
fn from(value: fs::FileType) -> Self {
|
||||||
|
if value.is_dir() {
|
||||||
|
Self::Dir
|
||||||
|
} else if value.is_file() {
|
||||||
|
Self::File
|
||||||
|
} else {
|
||||||
|
Self::Symlink
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<DirEntry> for FileType {
|
||||||
|
type Error = io::Error;
|
||||||
|
|
||||||
|
fn try_from(value: DirEntry) -> Result<Self, Self::Error> {
|
||||||
|
let ft = value.file_type()?;
|
||||||
|
Ok(ft.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FileType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
Self::File => "file",
|
||||||
|
Self::Symlink => "symlink",
|
||||||
|
Self::Dir => "directory",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ use std::{env, path::PathBuf, process, string::ToString};
|
|||||||
|
|
||||||
mod cmd;
|
mod cmd;
|
||||||
pub use cmd::Cmd;
|
pub use cmd::Cmd;
|
||||||
|
pub mod fs;
|
||||||
pub mod math;
|
pub mod math;
|
||||||
pub mod mode;
|
pub mod mode;
|
||||||
pub mod pw;
|
pub mod pw;
|
||||||
|
@ -31,6 +31,15 @@ pub fn get_eusername<'a>() -> Result<&'a str, std::str::Utf8Error> {
|
|||||||
user.to_str()
|
user.to_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_username_for_uid<'a>(uid: u32) -> Result<&'a str, std::str::Utf8Error> {
|
||||||
|
let user = unsafe {
|
||||||
|
let pw = libc::getpwuid(uid);
|
||||||
|
let name = (*pw).pw_name;
|
||||||
|
CStr::from_ptr(name)
|
||||||
|
};
|
||||||
|
user.to_str()
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the uid associated with the given name
|
/// Gets the uid associated with the given name
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_uid_for_name(name: &str) -> Option<u32> {
|
pub fn get_uid_for_name(name: &str) -> Option<u32> {
|
||||||
@ -72,6 +81,20 @@ pub fn get_grpname<'a>() -> Result<&'a str, std::str::Utf8Error> {
|
|||||||
group.to_str()
|
group.to_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_gid_for_groupname(groupname: &str) -> Option<u32> {
|
||||||
|
let Ok(grp) = CString::new(groupname.as_bytes()) else { return None };
|
||||||
|
unsafe { Some((*libc::getgrnam(grp.as_ptr())).gr_gid as u32) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_groupname_for_gid<'a>(gid: u32) -> Result<&'a str, std::str::Utf8Error> {
|
||||||
|
let group = unsafe {
|
||||||
|
let gr = libc::getgrgid(gid);
|
||||||
|
let name = (*gr).gr_name;
|
||||||
|
CStr::from_ptr(name)
|
||||||
|
};
|
||||||
|
group.to_str()
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the effective group for the current process
|
/// Gets the effective group for the current process
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// The name must be valid utf8
|
/// The name must be valid utf8
|
||||||
|
Loading…
Reference in New Issue
Block a user