mirror of
https://github.com/uutils/coreutils
synced 2024-07-23 19:04:18 +00:00
refactor: move shared chown logic to uucore
This commit is contained in:
parent
7153a595c6
commit
4e251706be
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<u32>, Option<u32>)> {
|
|||
Ok((uid, gid))
|
||||
}
|
||||
|
||||
pub enum IfFrom {
|
||||
All,
|
||||
User(u32),
|
||||
Group(u32),
|
||||
UserGroup(u32, u32),
|
||||
}
|
||||
|
||||
pub struct Chowner {
|
||||
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,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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<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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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<P: AsRef<Path>>(
|
|||
}
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue