1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge pull request #2570 from miDeb/chgrp/no-duplication

chgrp/chown: move common code to `uucore`
This commit is contained in:
Sylvestre Ledru 2021-08-24 21:58:15 +02:00 committed by GitHub
commit 111d3e37bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 331 additions and 534 deletions

4
Cargo.lock generated
View file

@ -2128,7 +2128,6 @@ dependencies = [
"clap",
"uucore",
"uucore_procs",
"walkdir",
]
[[package]]
@ -2147,10 +2146,8 @@ name = "uu_chown"
version = "0.0.7"
dependencies = [
"clap",
"glob",
"uucore",
"uucore_procs",
"walkdir",
]
[[package]]
@ -3165,6 +3162,7 @@ dependencies = [
"termion",
"thiserror",
"time",
"walkdir",
"wild",
"winapi 0.3.9",
"z85",

View file

@ -18,7 +18,6 @@ path = "src/chgrp.rs"
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2"
[[bin]]
name = "chgrp"

View file

@ -5,25 +5,21 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (ToDO) COMFOLLOW Chgrper RFILE RFILE's derefer dgid nonblank nonprint nonprinting
// spell-checker:ignore (ToDO) COMFOLLOW Chowner RFILE RFILE's derefer dgid nonblank nonprint nonprinting
#[macro_use]
extern crate uucore;
pub use uucore::entries;
use uucore::fs::resolve_relative_path;
use uucore::libc::gid_t;
use uucore::perms::{wrap_chgrp, Verbosity};
use uucore::error::{FromIo, UResult, USimpleError};
use uucore::perms::{
ChownExecutor, IfFrom, Verbosity, VerbosityLevel, FTS_COMFOLLOW, FTS_LOGICAL, FTS_PHYSICAL,
};
use clap::{App, Arg};
extern crate walkdir;
use walkdir::WalkDir;
use std::fs;
use std::fs::Metadata;
use std::os::unix::fs::MetadataExt;
use std::path::Path;
use uucore::InvalidEncodingHandling;
static ABOUT: &str = "Change the group of each FILE to GROUP.";
@ -55,23 +51,20 @@ pub mod options {
pub static ARG_FILES: &str = "FILE";
}
const FTS_COMFOLLOW: u8 = 1;
const FTS_PHYSICAL: u8 = 1 << 1;
const FTS_LOGICAL: u8 = 1 << 2;
fn usage() -> String {
fn get_usage() -> String {
format!(
"{0} [OPTION]... GROUP FILE...\n {0} [OPTION]... --reference=RFILE FILE...",
uucore::execution_phrase()
)
}
pub fn uumain(args: impl uucore::Args) -> i32 {
#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let usage = usage();
let usage = get_usage();
let mut app = uu_app().usage(&usage[..]);
@ -140,8 +133,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if recursive {
if bit_flag == FTS_PHYSICAL {
if derefer == 1 {
show_error!("-R --dereference requires -H or -L");
return 1;
return Err(USimpleError::new(1, "-R --dereference requires -H or -L"));
}
derefer = 0;
}
@ -149,26 +141,22 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
bit_flag = FTS_PHYSICAL;
}
let verbosity = if matches.is_present(options::verbosity::CHANGES) {
Verbosity::Changes
let verbosity_level = if matches.is_present(options::verbosity::CHANGES) {
VerbosityLevel::Changes
} else if matches.is_present(options::verbosity::SILENT)
|| matches.is_present(options::verbosity::QUIET)
{
Verbosity::Silent
VerbosityLevel::Silent
} else if matches.is_present(options::verbosity::VERBOSE) {
Verbosity::Verbose
VerbosityLevel::Verbose
} else {
Verbosity::Normal
VerbosityLevel::Normal
};
let dest_gid = if let Some(file) = matches.value_of(options::REFERENCE) {
match fs::metadata(&file) {
Ok(meta) => Some(meta.gid()),
Err(e) => {
show_error!("failed to get attributes of '{}': {}", file, e);
return 1;
}
}
fs::metadata(&file)
.map(|meta| Some(meta.gid()))
.map_err_context(|| format!("failed to get attributes of '{}'", file))?
} else {
let group = matches.value_of(options::ARG_GROUP).unwrap_or_default();
if group.is_empty() {
@ -176,22 +164,24 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} else {
match entries::grp2gid(group) {
Ok(g) => Some(g),
_ => {
show_error!("invalid group: {}", group);
return 1;
}
_ => return Err(USimpleError::new(1, format!("invalid group: '{}'", group))),
}
}
};
let executor = Chgrper {
let executor = ChownExecutor {
bit_flag,
dest_gid,
verbosity,
verbosity: Verbosity {
groups_only: true,
level: verbosity_level,
},
recursive,
dereference: derefer != 0,
preserve_root,
files,
filter: IfFrom::All,
dest_uid: None,
};
executor.exec()
}
@ -275,172 +265,3 @@ pub fn uu_app() -> App<'static, 'static> {
.help("traverse every symbolic link to a directory encountered"),
)
}
struct Chgrper {
dest_gid: Option<gid_t>,
bit_flag: u8,
verbosity: Verbosity,
files: Vec<String>,
recursive: bool,
preserve_root: bool,
dereference: bool,
}
macro_rules! unwrap {
($m:expr, $e:ident, $err:block) => {
match $m {
Ok(meta) => meta,
Err($e) => $err,
}
};
}
impl Chgrper {
fn exec(&self) -> i32 {
let mut ret = 0;
for f in &self.files {
ret |= self.traverse(f);
}
ret
}
#[cfg(windows)]
fn is_bind_root<P: AsRef<Path>>(&self, root: P) -> bool {
// TODO: is there an equivalent on Windows?
false
}
#[cfg(unix)]
fn is_bind_root<P: AsRef<Path>>(&self, path: P) -> bool {
if let (Ok(given), Ok(root)) = (fs::metadata(path), fs::metadata("/")) {
given.dev() == root.dev() && given.ino() == root.ino()
} else {
// FIXME: not totally sure if it's okay to just ignore an error here
false
}
}
fn traverse<P: AsRef<Path>>(&self, root: P) -> i32 {
let follow_arg = self.dereference || self.bit_flag != FTS_PHYSICAL;
let path = root.as_ref();
let meta = match self.obtain_meta(path, follow_arg) {
Some(m) => m,
_ => return 1,
};
// Prohibit only if:
// (--preserve-root and -R present) &&
// (
// (argument is not symlink && resolved to be '/') ||
// (argument is symlink && should follow argument && resolved to be '/')
// )
if self.recursive && self.preserve_root {
let may_exist = if follow_arg {
path.canonicalize().ok()
} else {
let real = resolve_relative_path(path);
if real.is_dir() {
Some(real.canonicalize().expect("failed to get real path"))
} else {
Some(real.into_owned())
}
};
if let Some(p) = may_exist {
if p.parent().is_none() || self.is_bind_root(p) {
show_error!("it is dangerous to operate recursively on '/'");
show_error!("use --no-preserve-root to override this failsafe");
return 1;
}
}
}
let ret = match wrap_chgrp(
path,
&meta,
self.dest_gid,
follow_arg,
self.verbosity.clone(),
) {
Ok(n) => {
if !n.is_empty() {
show_error!("{}", n);
}
0
}
Err(e) => {
if self.verbosity != Verbosity::Silent {
show_error!("{}", e);
}
1
}
};
if !self.recursive {
ret
} else {
ret | self.dive_into(&root)
}
}
fn dive_into<P: AsRef<Path>>(&self, root: P) -> i32 {
let mut ret = 0;
let root = root.as_ref();
let follow = self.dereference || self.bit_flag & FTS_LOGICAL != 0;
for entry in WalkDir::new(root).follow_links(follow).min_depth(1) {
let entry = unwrap!(entry, e, {
ret = 1;
show_error!("{}", e);
continue;
});
let path = entry.path();
let meta = match self.obtain_meta(path, follow) {
Some(m) => m,
_ => {
ret = 1;
continue;
}
};
ret = match wrap_chgrp(path, &meta, self.dest_gid, follow, self.verbosity.clone()) {
Ok(n) => {
if !n.is_empty() {
show_error!("{}", n);
}
0
}
Err(e) => {
if self.verbosity != Verbosity::Silent {
show_error!("{}", e);
}
1
}
}
}
ret
}
fn obtain_meta<P: AsRef<Path>>(&self, path: P, follow: bool) -> Option<Metadata> {
use self::Verbosity::*;
let path = path.as_ref();
let meta = if follow {
unwrap!(path.metadata(), e, {
match self.verbosity {
Silent => (),
_ => show_error!("cannot access '{}': {}", path.display(), e),
}
return None;
})
} else {
unwrap!(path.symlink_metadata(), e, {
match self.verbosity {
Silent => (),
_ => show_error!("cannot dereference '{}': {}", path.display(), e),
}
return None;
})
};
Some(meta)
}
}

View file

@ -16,10 +16,8 @@ path = "src/chown.rs"
[dependencies]
clap = { version = "2.33", features = ["wrap_help"] }
glob = "0.3.0"
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2"
[[bin]]
name = "chown"

View file

@ -5,26 +5,22 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (ToDO) COMFOLLOW Chowner Passwd RFILE RFILE's derefer dgid duid
// spell-checker:ignore (ToDO) COMFOLLOW Passwd RFILE RFILE's derefer dgid duid
#[macro_use]
extern crate uucore;
pub use uucore::entries::{self, Group, Locate, Passwd};
use uucore::fs::resolve_relative_path;
use uucore::libc::{gid_t, uid_t};
use uucore::perms::{wrap_chown, Verbosity};
use uucore::perms::{
ChownExecutor, IfFrom, Verbosity, VerbosityLevel, FTS_COMFOLLOW, FTS_LOGICAL, FTS_PHYSICAL,
};
use uucore::error::{FromIo, UResult, USimpleError};
use clap::{crate_version, App, Arg};
use walkdir::WalkDir;
use std::fs::{self, Metadata};
use std::fs;
use std::os::unix::fs::MetadataExt;
use std::convert::AsRef;
use std::path::Path;
use uucore::InvalidEncodingHandling;
static ABOUT: &str = "change file owner and group";
@ -57,11 +53,7 @@ pub mod options {
static ARG_OWNER: &str = "owner";
static ARG_FILES: &str = "files";
const FTS_COMFOLLOW: u8 = 1;
const FTS_PHYSICAL: u8 = 1 << 1;
const FTS_LOGICAL: u8 = 1 << 2;
fn usage() -> String {
fn get_usage() -> String {
format!(
"{0} [OPTION]... [OWNER][:[GROUP]] FILE...\n{0} [OPTION]... --reference=RFILE FILE...",
uucore::execution_phrase()
@ -74,7 +66,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let usage = usage();
let usage = get_usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
@ -116,15 +108,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}
let verbosity = if matches.is_present(options::verbosity::CHANGES) {
Verbosity::Changes
VerbosityLevel::Changes
} else if matches.is_present(options::verbosity::SILENT)
|| matches.is_present(options::verbosity::QUIET)
{
Verbosity::Silent
VerbosityLevel::Silent
} else if matches.is_present(options::verbosity::VERBOSE) {
Verbosity::Verbose
VerbosityLevel::Verbose
} else {
Verbosity::Normal
VerbosityLevel::Normal
};
let filter = if let Some(spec) = matches.value_of(options::FROM) {
@ -150,11 +142,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
dest_uid = u;
dest_gid = g;
}
let executor = Chowner {
let executor = ChownExecutor {
bit_flag,
dest_uid,
dest_gid,
verbosity,
verbosity: Verbosity {
groups_only: false,
level: verbosity,
},
recursive,
dereference: derefer != 0,
filter,
@ -286,196 +281,6 @@ fn parse_spec(spec: &str) -> UResult<(Option<u32>, Option<u32>)> {
Ok((uid, gid))
}
enum IfFrom {
All,
User(u32),
Group(u32),
UserGroup(u32, u32),
}
struct Chowner {
dest_uid: Option<u32>,
dest_gid: Option<u32>,
bit_flag: u8,
verbosity: Verbosity,
filter: IfFrom,
files: Vec<String>,
recursive: bool,
preserve_root: bool,
dereference: bool,
}
macro_rules! unwrap {
($m:expr, $e:ident, $err:block) => {
match $m {
Ok(meta) => meta,
Err($e) => $err,
}
};
}
impl Chowner {
fn exec(&self) -> UResult<()> {
let mut ret = 0;
for f in &self.files {
ret |= self.traverse(f);
}
if ret != 0 {
return Err(ret.into());
}
Ok(())
}
fn traverse<P: AsRef<Path>>(&self, root: P) -> i32 {
let follow_arg = self.dereference || self.bit_flag != FTS_PHYSICAL;
let path = root.as_ref();
let meta = match self.obtain_meta(path, follow_arg) {
Some(m) => m,
_ => return 1,
};
// Prohibit only if:
// (--preserve-root and -R present) &&
// (
// (argument is not symlink && resolved to be '/') ||
// (argument is symlink && should follow argument && resolved to be '/')
// )
if self.recursive && self.preserve_root {
let may_exist = if follow_arg {
path.canonicalize().ok()
} else {
let real = resolve_relative_path(path);
if real.is_dir() {
Some(real.canonicalize().expect("failed to get real path"))
} else {
Some(real.into_owned())
}
};
if let Some(p) = may_exist {
if p.parent().is_none() {
show_error!("it is dangerous to operate recursively on '/'");
show_error!("use --no-preserve-root to override this failsafe");
return 1;
}
}
}
let ret = if self.matched(meta.uid(), meta.gid()) {
match wrap_chown(
path,
&meta,
self.dest_uid,
self.dest_gid,
follow_arg,
self.verbosity.clone(),
) {
Ok(n) => {
if !n.is_empty() {
show_error!("{}", n);
}
0
}
Err(e) => {
if self.verbosity != Verbosity::Silent {
show_error!("{}", e);
}
1
}
}
} else {
0
};
if !self.recursive {
ret
} else {
ret | self.dive_into(&root)
}
}
fn dive_into<P: AsRef<Path>>(&self, root: P) -> i32 {
let mut ret = 0;
let root = root.as_ref();
let follow = self.dereference || self.bit_flag & FTS_LOGICAL != 0;
for entry in WalkDir::new(root).follow_links(follow).min_depth(1) {
let entry = unwrap!(entry, e, {
ret = 1;
show_error!("{}", e);
continue;
});
let path = entry.path();
let meta = match self.obtain_meta(path, follow) {
Some(m) => m,
_ => {
ret = 1;
continue;
}
};
if !self.matched(meta.uid(), meta.gid()) {
continue;
}
ret = match wrap_chown(
path,
&meta,
self.dest_uid,
self.dest_gid,
follow,
self.verbosity.clone(),
) {
Ok(n) => {
if !n.is_empty() {
show_error!("{}", n);
}
0
}
Err(e) => {
if self.verbosity != Verbosity::Silent {
show_error!("{}", e);
}
1
}
}
}
ret
}
fn obtain_meta<P: AsRef<Path>>(&self, path: P, follow: bool) -> Option<Metadata> {
use self::Verbosity::*;
let path = path.as_ref();
let meta = if follow {
unwrap!(path.metadata(), e, {
match self.verbosity {
Silent => (),
_ => show_error!("cannot access '{}': {}", path.display(), e),
}
return None;
})
} else {
unwrap!(path.symlink_metadata(), e, {
match self.verbosity {
Silent => (),
_ => show_error!("cannot dereference '{}': {}", path.display(), e),
}
return None;
})
};
Some(meta)
}
#[inline]
fn matched(&self, uid: uid_t, gid: gid_t) -> bool {
match self.filter {
IfFrom::All => true,
IfFrom::User(u) => u == uid,
IfFrom::Group(g) => g == gid,
IfFrom::UserGroup(u, g) => u == uid && g == gid,
}
}
}
#[cfg(test)]
mod test {
use super::*;

View file

@ -18,7 +18,7 @@ use filetime::{set_file_times, FileTime};
use uucore::backup_control::{self, BackupMode};
use uucore::entries::{grp2gid, usr2uid};
use uucore::error::{FromIo, UError, UIoError, UResult, USimpleError};
use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity};
use uucore::perms::{wrap_chown, Verbosity, VerbosityLevel};
use libc::{getegid, geteuid};
use std::error::Error;
@ -641,7 +641,10 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
Some(owner_id),
Some(gid),
false,
Verbosity::Normal,
Verbosity {
groups_only: false,
level: VerbosityLevel::Normal,
},
) {
Ok(n) => {
if !n.is_empty() {
@ -662,7 +665,17 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
Ok(g) => g,
_ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()),
};
match wrap_chgrp(to, &meta, Some(group_id), false, Verbosity::Normal) {
match wrap_chown(
to,
&meta,
Some(group_id),
None,
false,
Verbosity {
groups_only: true,
level: VerbosityLevel::Normal,
},
) {
Ok(n) => {
if !n.is_empty() {
show_error!("{}", n);

View file

@ -26,6 +26,7 @@ lazy_static = { version="1.3", optional=true }
nix = { version="<= 0.19", optional=true }
platform-info = { version="<= 0.1", optional=true }
time = { version="<= 0.1.43", optional=true }
walkdir = { version="2.3.2", optional=true }
# * "problem" dependencies (pinned)
data-encoding = { version="2.1", optional=true }
data-encoding-macro = { version="0.1.12", optional=true }
@ -50,7 +51,7 @@ entries = ["libc"]
fs = ["libc"]
fsext = ["libc", "time"]
mode = ["libc"]
perms = ["libc"]
perms = ["libc", "walkdir"]
process = ["libc"]
ringbuffer = []
signals = []

View file

@ -3,8 +3,12 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use crate::error::UResult;
pub use crate::features::entries;
use crate::fs::resolve_relative_path;
use crate::show_error;
use libc::{self, gid_t, lchown, uid_t};
use walkdir::WalkDir;
use std::io::Error as IOError;
use std::io::Result as IOResult;
@ -18,86 +22,16 @@ use std::path::Path;
/// The various level of verbosity
#[derive(PartialEq, Clone, Debug)]
pub enum Verbosity {
pub enum VerbosityLevel {
Silent,
Changes,
Verbose,
Normal,
}
/// Actually perform the change of group on a path
fn chgrp<P: AsRef<Path>>(path: P, gid: gid_t, follow: bool) -> IOResult<()> {
let path = path.as_ref();
let s = CString::new(path.as_os_str().as_bytes()).unwrap();
let ret = unsafe {
if follow {
libc::chown(s.as_ptr(), 0_u32.wrapping_sub(1), gid)
} else {
lchown(s.as_ptr(), 0_u32.wrapping_sub(1), gid)
}
};
if ret == 0 {
Ok(())
} else {
Err(IOError::last_os_error())
}
}
/// Perform the change of group on a path
/// with the various options
/// and error messages management
pub fn wrap_chgrp<P: AsRef<Path>>(
path: P,
meta: &Metadata,
dest_gid: Option<gid_t>,
follow: bool,
verbosity: Verbosity,
) -> Result<String, String> {
use self::Verbosity::*;
let path = path.as_ref();
let mut out: String = String::new();
let dest_gid = dest_gid.unwrap_or_else(|| meta.gid());
if let Err(e) = chgrp(path, dest_gid, follow) {
match verbosity {
Silent => (),
_ => {
out = format!("changing group of '{}': {}", path.display(), e);
if verbosity == Verbose {
out = format!(
"{}\nfailed to change group of '{}' from {} to {}",
out,
path.display(),
entries::gid2grp(meta.gid()).unwrap(),
entries::gid2grp(dest_gid).unwrap()
);
};
}
}
return Err(out);
} else {
let changed = dest_gid != meta.gid();
if changed {
match verbosity {
Changes | Verbose => {
out = format!(
"changed group of '{}' from {} to {}",
path.display(),
entries::gid2grp(meta.gid()).unwrap(),
entries::gid2grp(dest_gid).unwrap()
);
}
_ => (),
};
} else if verbosity == Verbose {
out = format!(
"group of '{}' retained as {}",
path.display(),
entries::gid2grp(dest_gid).unwrap_or_default()
);
}
}
Ok(out)
#[derive(PartialEq, Clone, Debug)]
pub struct Verbosity {
pub groups_only: bool,
pub level: VerbosityLevel,
}
/// Actually perform the change of owner on a path
@ -129,27 +63,45 @@ pub fn wrap_chown<P: AsRef<Path>>(
follow: bool,
verbosity: Verbosity,
) -> Result<String, String> {
use self::Verbosity::*;
let dest_uid = dest_uid.unwrap_or_else(|| meta.uid());
let dest_gid = dest_gid.unwrap_or_else(|| meta.gid());
let path = path.as_ref();
let mut out: String = String::new();
if let Err(e) = chown(path, dest_uid, dest_gid, follow) {
match verbosity {
Silent => (),
_ => {
out = format!("changing ownership of '{}': {}", path.display(), e);
if verbosity == Verbose {
out = format!(
"{}\nfailed to change ownership of '{}' from {}:{} to {}:{}",
out,
path.display(),
entries::uid2usr(meta.uid()).unwrap(),
entries::gid2grp(meta.gid()).unwrap(),
entries::uid2usr(dest_uid).unwrap(),
entries::gid2grp(dest_gid).unwrap()
);
match verbosity.level {
VerbosityLevel::Silent => (),
level => {
out = format!(
"changing {} of '{}': {}",
if verbosity.groups_only {
"group"
} else {
"ownership"
},
path.display(),
e
);
if level == VerbosityLevel::Verbose {
out = if verbosity.groups_only {
format!(
"{}\nfailed to change group of '{}' from {} to {}",
out,
path.display(),
entries::gid2grp(meta.gid()).unwrap(),
entries::gid2grp(dest_gid).unwrap()
)
} else {
format!(
"{}\nfailed to change ownership of '{}' from {}:{} to {}:{}",
out,
path.display(),
entries::uid2usr(meta.uid()).unwrap(),
entries::gid2grp(meta.gid()).unwrap(),
entries::uid2usr(dest_uid).unwrap(),
entries::gid2grp(dest_gid).unwrap()
)
};
};
}
}
@ -157,27 +109,237 @@ pub fn wrap_chown<P: AsRef<Path>>(
} else {
let changed = dest_uid != meta.uid() || dest_gid != meta.gid();
if changed {
match verbosity {
Changes | Verbose => {
out = format!(
"changed ownership of '{}' from {}:{} to {}:{}",
path.display(),
entries::uid2usr(meta.uid()).unwrap(),
entries::gid2grp(meta.gid()).unwrap(),
entries::uid2usr(dest_uid).unwrap(),
entries::gid2grp(dest_gid).unwrap()
);
match verbosity.level {
VerbosityLevel::Changes | VerbosityLevel::Verbose => {
out = if verbosity.groups_only {
format!(
"changed group of '{}' from {} to {}",
path.display(),
entries::gid2grp(meta.gid()).unwrap(),
entries::gid2grp(dest_gid).unwrap()
)
} else {
format!(
"changed ownership of '{}' from {}:{} to {}:{}",
path.display(),
entries::uid2usr(meta.uid()).unwrap(),
entries::gid2grp(meta.gid()).unwrap(),
entries::uid2usr(dest_uid).unwrap(),
entries::gid2grp(dest_gid).unwrap()
)
};
}
_ => (),
};
} else if verbosity == Verbose {
out = format!(
"ownership of '{}' retained as {}:{}",
path.display(),
entries::uid2usr(dest_uid).unwrap(),
entries::gid2grp(dest_gid).unwrap()
);
} else if verbosity.level == VerbosityLevel::Verbose {
out = if verbosity.groups_only {
format!(
"group of '{}' retained as {}",
path.display(),
entries::gid2grp(dest_gid).unwrap_or_default()
)
} else {
format!(
"ownership of '{}' retained as {}:{}",
path.display(),
entries::uid2usr(dest_uid).unwrap(),
entries::gid2grp(dest_gid).unwrap()
)
};
}
}
Ok(out)
}
pub enum IfFrom {
All,
User(u32),
Group(u32),
UserGroup(u32, u32),
}
pub struct ChownExecutor {
pub dest_uid: Option<u32>,
pub dest_gid: Option<u32>,
pub bit_flag: u8,
pub verbosity: Verbosity,
pub filter: IfFrom,
pub files: Vec<String>,
pub recursive: bool,
pub preserve_root: bool,
pub dereference: bool,
}
macro_rules! unwrap {
($m:expr, $e:ident, $err:block) => {
match $m {
Ok(meta) => meta,
Err($e) => $err,
}
};
}
pub const FTS_COMFOLLOW: u8 = 1;
pub const FTS_PHYSICAL: u8 = 1 << 1;
pub const FTS_LOGICAL: u8 = 1 << 2;
impl ChownExecutor {
pub fn exec(&self) -> UResult<()> {
let mut ret = 0;
for f in &self.files {
ret |= self.traverse(f);
}
if ret != 0 {
return Err(ret.into());
}
Ok(())
}
fn traverse<P: AsRef<Path>>(&self, root: P) -> i32 {
let follow_arg = self.dereference || self.bit_flag != FTS_PHYSICAL;
let path = root.as_ref();
let meta = match self.obtain_meta(path, follow_arg) {
Some(m) => m,
_ => return 1,
};
// Prohibit only if:
// (--preserve-root and -R present) &&
// (
// (argument is not symlink && resolved to be '/') ||
// (argument is symlink && should follow argument && resolved to be '/')
// )
if self.recursive && self.preserve_root {
let may_exist = if follow_arg {
path.canonicalize().ok()
} else {
let real = resolve_relative_path(path);
if real.is_dir() {
Some(real.canonicalize().expect("failed to get real path"))
} else {
Some(real.into_owned())
}
};
if let Some(p) = may_exist {
if p.parent().is_none() {
show_error!("it is dangerous to operate recursively on '/'");
show_error!("use --no-preserve-root to override this failsafe");
return 1;
}
}
}
let ret = if self.matched(meta.uid(), meta.gid()) {
match wrap_chown(
path,
&meta,
self.dest_uid,
self.dest_gid,
follow_arg,
self.verbosity.clone(),
) {
Ok(n) => {
if !n.is_empty() {
show_error!("{}", n);
}
0
}
Err(e) => {
if self.verbosity.level != VerbosityLevel::Silent {
show_error!("{}", e);
}
1
}
}
} else {
0
};
if !self.recursive {
ret
} else {
ret | self.dive_into(&root)
}
}
fn dive_into<P: AsRef<Path>>(&self, root: P) -> i32 {
let mut ret = 0;
let root = root.as_ref();
let follow = self.dereference || self.bit_flag & FTS_LOGICAL != 0;
for entry in WalkDir::new(root).follow_links(follow).min_depth(1) {
let entry = unwrap!(entry, e, {
ret = 1;
show_error!("{}", e);
continue;
});
let path = entry.path();
let meta = match self.obtain_meta(path, follow) {
Some(m) => m,
_ => {
ret = 1;
continue;
}
};
if !self.matched(meta.uid(), meta.gid()) {
continue;
}
ret = match wrap_chown(
path,
&meta,
self.dest_uid,
self.dest_gid,
follow,
self.verbosity.clone(),
) {
Ok(n) => {
if !n.is_empty() {
show_error!("{}", n);
}
0
}
Err(e) => {
if self.verbosity.level != VerbosityLevel::Silent {
show_error!("{}", e);
}
1
}
}
}
ret
}
fn obtain_meta<P: AsRef<Path>>(&self, path: P, follow: bool) -> Option<Metadata> {
let path = path.as_ref();
let meta = if follow {
unwrap!(path.metadata(), e, {
match self.verbosity.level {
VerbosityLevel::Silent => (),
_ => show_error!("cannot access '{}': {}", path.display(), e),
}
return None;
})
} else {
unwrap!(path.symlink_metadata(), e, {
match self.verbosity.level {
VerbosityLevel::Silent => (),
_ => show_error!("cannot dereference '{}': {}", path.display(), e),
}
return None;
})
};
Some(meta)
}
#[inline]
fn matched(&self, uid: uid_t, gid: gid_t) -> bool {
match self.filter {
IfFrom::All => true,
IfFrom::User(u) => u == uid,
IfFrom::Group(g) => g == gid,
IfFrom::UserGroup(u, g) => u == uid && g == gid,
}
}
}

View file

@ -43,7 +43,7 @@ fn test_invalid_group() {
.arg("__nosuchgroup__")
.arg("/")
.fails()
.stderr_is("chgrp: invalid group: __nosuchgroup__");
.stderr_is("chgrp: invalid group: '__nosuchgroup__'");
}
#[test]