From 4e251706be95b6a1664655b97afca4b68e1ee617 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 17 Aug 2021 22:35:04 +0200 Subject: [PATCH] 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, + } + } +}