From 7153a595c6f4dbfbd68dd08fcbfd5231d2b18c6d Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 14 Aug 2021 20:55:04 +0200 Subject: [PATCH 1/2] chgrp: forward to chown chgrp does mostly the same as chown. By making chown a bit more configurable we can reuse its code for chgrp. --- Cargo.lock | 2 +- src/uu/chgrp/Cargo.toml | 2 +- src/uu/chgrp/src/chgrp.rs | 222 +++------------------------ src/uu/chown/src/chown.rs | 52 ++++--- src/uu/install/src/install.rs | 19 ++- src/uucore/src/lib/features/perms.rs | 179 +++++++++------------ tests/by-util/test_chgrp.rs | 2 +- 7 files changed, 141 insertions(+), 337 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 29be47dfb..477895e15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2126,9 +2126,9 @@ name = "uu_chgrp" version = "0.0.7" dependencies = [ "clap", + "uu_chown", "uucore", "uucore_procs", - "walkdir", ] [[package]] diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index 5a2591f56..cc160626c 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -18,7 +18,7 @@ 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" +uu_chown = { version=">=0.0.6", package="uu_chown", path="../chown"} [[bin]] name = "chgrp" diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index 7840ba829..5ae97955d 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -5,25 +5,20 @@ // 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; +use uu_chown::{Chowner, IfFrom}; 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::{Verbosity, VerbosityLevel}; 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."; @@ -66,7 +61,8 @@ fn usage() -> String { ) } -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(); @@ -140,8 +136,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 +144,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 +167,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 = Chowner { 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 +268,3 @@ pub fn uu_app() -> App<'static, 'static> { .help("traverse every symbolic link to a directory encountered"), ) } - -struct Chgrper { - dest_gid: Option, - bit_flag: u8, - verbosity: Verbosity, - files: Vec, - 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>(&self, root: P) -> bool { - // TODO: is there an equivalent on Windows? - false - } - - #[cfg(unix)] - fn is_bind_root>(&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>(&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>(&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>(&self, path: P, follow: bool) -> Option { - 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) - } -} diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 8813c07e2..13d16dc2a 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -12,7 +12,7 @@ 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::{wrap_chown, Verbosity, VerbosityLevel}; use uucore::error::{FromIo, UResult, USimpleError}; @@ -116,15 +116,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) { @@ -154,7 +154,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { bit_flag, dest_uid, dest_gid, - verbosity, + verbosity: Verbosity { + groups_only: false, + level: verbosity, + }, recursive, dereference: derefer != 0, filter, @@ -286,23 +289,23 @@ fn parse_spec(spec: &str) -> UResult<(Option, Option)> { Ok((uid, gid)) } -enum IfFrom { +pub enum IfFrom { All, User(u32), Group(u32), UserGroup(u32, u32), } -struct Chowner { - dest_uid: Option, - dest_gid: Option, - bit_flag: u8, - verbosity: Verbosity, - filter: IfFrom, - files: Vec, - recursive: bool, - preserve_root: bool, - dereference: bool, +pub struct Chowner { + pub dest_uid: Option, + pub dest_gid: Option, + pub bit_flag: u8, + pub verbosity: Verbosity, + pub filter: IfFrom, + pub files: Vec, + pub recursive: bool, + pub preserve_root: bool, + pub dereference: bool, } macro_rules! unwrap { @@ -315,7 +318,7 @@ macro_rules! unwrap { } impl Chowner { - fn exec(&self) -> UResult<()> { + pub fn exec(&self) -> UResult<()> { let mut ret = 0; for f in &self.files { ret |= self.traverse(f); @@ -377,7 +380,7 @@ impl Chowner { 0 } Err(e) => { - if self.verbosity != Verbosity::Silent { + if self.verbosity.level != VerbosityLevel::Silent { show_error!("{}", e); } 1 @@ -432,7 +435,7 @@ impl Chowner { 0 } Err(e) => { - if self.verbosity != Verbosity::Silent { + if self.verbosity.level != VerbosityLevel::Silent { show_error!("{}", e); } 1 @@ -443,20 +446,19 @@ impl Chowner { } fn obtain_meta>(&self, path: P, follow: bool) -> Option { - use self::Verbosity::*; let path = path.as_ref(); let meta = if follow { unwrap!(path.metadata(), e, { - match self.verbosity { - Silent => (), + match self.verbosity.level { + VerbosityLevel::Silent => (), _ => show_error!("cannot access '{}': {}", path.display(), e), } return None; }) } else { unwrap!(path.symlink_metadata(), e, { - match self.verbosity { - Silent => (), + match self.verbosity.level { + VerbosityLevel::Silent => (), _ => show_error!("cannot dereference '{}': {}", path.display(), e), } return None; diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 5c951ad5b..d5f853de7 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -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); diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index 69491c297..2542c4d35 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -18,86 +18,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>(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>( - path: P, - meta: &Metadata, - dest_gid: Option, - follow: bool, - verbosity: Verbosity, -) -> Result { - 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 +59,45 @@ pub fn wrap_chown>( follow: bool, verbosity: Verbosity, ) -> Result { - 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,26 +105,43 @@ pub fn wrap_chown>( } 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) diff --git a/tests/by-util/test_chgrp.rs b/tests/by-util/test_chgrp.rs index 1b8057e47..0741838a4 100644 --- a/tests/by-util/test_chgrp.rs +++ b/tests/by-util/test_chgrp.rs @@ -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] From 4e251706be95b6a1664655b97afca4b68e1ee617 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 17 Aug 2021 22:35:04 +0200 Subject: [PATCH 2/2] refactor: move shared chown logic to uucore --- Cargo.lock | 4 +- src/uu/chgrp/Cargo.toml | 1 - src/uu/chgrp/src/chgrp.rs | 15 +- src/uu/chown/Cargo.toml | 2 - src/uu/chown/src/chown.rs | 213 +-------------------------- src/uucore/Cargo.toml | 3 +- src/uucore/src/lib/features/perms.rs | 197 +++++++++++++++++++++++++ 7 files changed, 214 insertions(+), 221 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 477895e15..1066c02ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2126,7 +2126,6 @@ name = "uu_chgrp" version = "0.0.7" dependencies = [ "clap", - "uu_chown", "uucore", "uucore_procs", ] @@ -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", diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index cc160626c..0d1b7e5aa 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -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" } -uu_chown = { version=">=0.0.6", package="uu_chown", path="../chown"} [[bin]] name = "chgrp" diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index 5ae97955d..d37da578e 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -9,10 +9,11 @@ #[macro_use] extern crate uucore; -use uu_chown::{Chowner, IfFrom}; pub use uucore::entries; use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::perms::{Verbosity, VerbosityLevel}; +use uucore::perms::{ + ChownExecutor, IfFrom, Verbosity, VerbosityLevel, FTS_COMFOLLOW, FTS_LOGICAL, FTS_PHYSICAL, +}; use clap::{App, Arg}; @@ -50,11 +51,7 @@ 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() @@ -67,7 +64,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let usage = usage(); + let usage = get_usage(); let mut app = uu_app().usage(&usage[..]); @@ -172,7 +169,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } }; - let executor = Chowner { + let executor = ChownExecutor { bit_flag, dest_gid, verbosity: Verbosity { diff --git a/src/uu/chown/Cargo.toml b/src/uu/chown/Cargo.toml index 828c214be..e6dc7d4fe 100644 --- a/src/uu/chown/Cargo.toml +++ b/src/uu/chown/Cargo.toml @@ -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" diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 13d16dc2a..06f0c6a32 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -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, VerbosityLevel}; +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); @@ -150,7 +142,7 @@ 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, @@ -289,195 +281,6 @@ fn parse_spec(spec: &str) -> UResult<(Option, Option)> { Ok((uid, gid)) } -pub enum IfFrom { - All, - User(u32), - Group(u32), - UserGroup(u32, u32), -} - -pub struct Chowner { - pub dest_uid: Option, - pub dest_gid: Option, - pub bit_flag: u8, - pub verbosity: Verbosity, - pub filter: IfFrom, - pub files: Vec, - 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, - } - }; -} - -impl Chowner { - 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>(&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>(&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>(&self, path: P, follow: bool) -> Option { - 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, - } - } -} - #[cfg(test)] mod test { use super::*; diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 28cba3a61..c49e0a0f3 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -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 = [] diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index 2542c4d35..4d2a2afad 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -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; @@ -146,3 +150,196 @@ pub fn wrap_chown>( } Ok(out) } + +pub enum IfFrom { + All, + User(u32), + Group(u32), + UserGroup(u32, u32), +} + +pub struct ChownExecutor { + pub dest_uid: Option, + pub dest_gid: Option, + pub bit_flag: u8, + pub verbosity: Verbosity, + pub filter: IfFrom, + pub files: Vec, + 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>(&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>(&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>(&self, path: P, follow: bool) -> Option { + 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, + } + } +}