1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 12:07:46 +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", "clap",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
"walkdir",
] ]
[[package]] [[package]]
@ -2147,10 +2146,8 @@ name = "uu_chown"
version = "0.0.7" version = "0.0.7"
dependencies = [ dependencies = [
"clap", "clap",
"glob",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
"walkdir",
] ]
[[package]] [[package]]
@ -3165,6 +3162,7 @@ dependencies = [
"termion", "termion",
"thiserror", "thiserror",
"time", "time",
"walkdir",
"wild", "wild",
"winapi 0.3.9", "winapi 0.3.9",
"z85", "z85",

View file

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

View file

@ -5,25 +5,21 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // 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] #[macro_use]
extern crate uucore; extern crate uucore;
pub use uucore::entries; pub use uucore::entries;
use uucore::fs::resolve_relative_path; use uucore::error::{FromIo, UResult, USimpleError};
use uucore::libc::gid_t; use uucore::perms::{
use uucore::perms::{wrap_chgrp, Verbosity}; ChownExecutor, IfFrom, Verbosity, VerbosityLevel, FTS_COMFOLLOW, FTS_LOGICAL, FTS_PHYSICAL,
};
use clap::{App, Arg}; use clap::{App, Arg};
extern crate walkdir;
use walkdir::WalkDir;
use std::fs; use std::fs;
use std::fs::Metadata;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::path::Path;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
static ABOUT: &str = "Change the group of each FILE to GROUP."; static ABOUT: &str = "Change the group of each FILE to GROUP.";
@ -55,23 +51,20 @@ pub mod options {
pub static ARG_FILES: &str = "FILE"; pub static ARG_FILES: &str = "FILE";
} }
const FTS_COMFOLLOW: u8 = 1; fn get_usage() -> String {
const FTS_PHYSICAL: u8 = 1 << 1;
const FTS_LOGICAL: u8 = 1 << 2;
fn usage() -> String {
format!( format!(
"{0} [OPTION]... GROUP FILE...\n {0} [OPTION]... --reference=RFILE FILE...", "{0} [OPTION]... GROUP FILE...\n {0} [OPTION]... --reference=RFILE FILE...",
uucore::execution_phrase() 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 let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy) .collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any(); .accept_any();
let usage = usage(); let usage = get_usage();
let mut app = uu_app().usage(&usage[..]); let mut app = uu_app().usage(&usage[..]);
@ -140,8 +133,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if recursive { if recursive {
if bit_flag == FTS_PHYSICAL { if bit_flag == FTS_PHYSICAL {
if derefer == 1 { if derefer == 1 {
show_error!("-R --dereference requires -H or -L"); return Err(USimpleError::new(1, "-R --dereference requires -H or -L"));
return 1;
} }
derefer = 0; derefer = 0;
} }
@ -149,26 +141,22 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
bit_flag = FTS_PHYSICAL; bit_flag = FTS_PHYSICAL;
} }
let verbosity = if matches.is_present(options::verbosity::CHANGES) { let verbosity_level = if matches.is_present(options::verbosity::CHANGES) {
Verbosity::Changes VerbosityLevel::Changes
} else if matches.is_present(options::verbosity::SILENT) } else if matches.is_present(options::verbosity::SILENT)
|| matches.is_present(options::verbosity::QUIET) || matches.is_present(options::verbosity::QUIET)
{ {
Verbosity::Silent VerbosityLevel::Silent
} else if matches.is_present(options::verbosity::VERBOSE) { } else if matches.is_present(options::verbosity::VERBOSE) {
Verbosity::Verbose VerbosityLevel::Verbose
} else { } else {
Verbosity::Normal VerbosityLevel::Normal
}; };
let dest_gid = if let Some(file) = matches.value_of(options::REFERENCE) { let dest_gid = if let Some(file) = matches.value_of(options::REFERENCE) {
match fs::metadata(&file) { fs::metadata(&file)
Ok(meta) => Some(meta.gid()), .map(|meta| Some(meta.gid()))
Err(e) => { .map_err_context(|| format!("failed to get attributes of '{}'", file))?
show_error!("failed to get attributes of '{}': {}", file, e);
return 1;
}
}
} else { } else {
let group = matches.value_of(options::ARG_GROUP).unwrap_or_default(); let group = matches.value_of(options::ARG_GROUP).unwrap_or_default();
if group.is_empty() { if group.is_empty() {
@ -176,22 +164,24 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} else { } else {
match entries::grp2gid(group) { match entries::grp2gid(group) {
Ok(g) => Some(g), Ok(g) => Some(g),
_ => { _ => return Err(USimpleError::new(1, format!("invalid group: '{}'", group))),
show_error!("invalid group: {}", group);
return 1;
}
} }
} }
}; };
let executor = Chgrper { let executor = ChownExecutor {
bit_flag, bit_flag,
dest_gid, dest_gid,
verbosity, verbosity: Verbosity {
groups_only: true,
level: verbosity_level,
},
recursive, recursive,
dereference: derefer != 0, dereference: derefer != 0,
preserve_root, preserve_root,
files, files,
filter: IfFrom::All,
dest_uid: None,
}; };
executor.exec() executor.exec()
} }
@ -275,172 +265,3 @@ pub fn uu_app() -> App<'static, 'static> {
.help("traverse every symbolic link to a directory encountered"), .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] [dependencies]
clap = { version = "2.33", features = ["wrap_help"] } 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 = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2"
[[bin]] [[bin]]
name = "chown" name = "chown"

View file

@ -5,26 +5,22 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // 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] #[macro_use]
extern crate uucore; extern crate uucore;
pub use uucore::entries::{self, Group, Locate, Passwd}; pub use uucore::entries::{self, Group, Locate, Passwd};
use uucore::fs::resolve_relative_path; use uucore::perms::{
use uucore::libc::{gid_t, uid_t}; ChownExecutor, IfFrom, Verbosity, VerbosityLevel, FTS_COMFOLLOW, FTS_LOGICAL, FTS_PHYSICAL,
use uucore::perms::{wrap_chown, Verbosity}; };
use uucore::error::{FromIo, UResult, USimpleError}; use uucore::error::{FromIo, UResult, USimpleError};
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use walkdir::WalkDir; use std::fs;
use std::fs::{self, Metadata};
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::convert::AsRef;
use std::path::Path;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
static ABOUT: &str = "change file owner and group"; static ABOUT: &str = "change file owner and group";
@ -57,11 +53,7 @@ pub mod options {
static ARG_OWNER: &str = "owner"; static ARG_OWNER: &str = "owner";
static ARG_FILES: &str = "files"; static ARG_FILES: &str = "files";
const FTS_COMFOLLOW: u8 = 1; fn get_usage() -> String {
const FTS_PHYSICAL: u8 = 1 << 1;
const FTS_LOGICAL: u8 = 1 << 2;
fn usage() -> String {
format!( format!(
"{0} [OPTION]... [OWNER][:[GROUP]] FILE...\n{0} [OPTION]... --reference=RFILE FILE...", "{0} [OPTION]... [OWNER][:[GROUP]] FILE...\n{0} [OPTION]... --reference=RFILE FILE...",
uucore::execution_phrase() uucore::execution_phrase()
@ -74,7 +66,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.collect_str(InvalidEncodingHandling::Ignore) .collect_str(InvalidEncodingHandling::Ignore)
.accept_any(); .accept_any();
let usage = usage(); let usage = get_usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(args); 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) { let verbosity = if matches.is_present(options::verbosity::CHANGES) {
Verbosity::Changes VerbosityLevel::Changes
} else if matches.is_present(options::verbosity::SILENT) } else if matches.is_present(options::verbosity::SILENT)
|| matches.is_present(options::verbosity::QUIET) || matches.is_present(options::verbosity::QUIET)
{ {
Verbosity::Silent VerbosityLevel::Silent
} else if matches.is_present(options::verbosity::VERBOSE) { } else if matches.is_present(options::verbosity::VERBOSE) {
Verbosity::Verbose VerbosityLevel::Verbose
} else { } else {
Verbosity::Normal VerbosityLevel::Normal
}; };
let filter = if let Some(spec) = matches.value_of(options::FROM) { 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_uid = u;
dest_gid = g; dest_gid = g;
} }
let executor = Chowner { let executor = ChownExecutor {
bit_flag, bit_flag,
dest_uid, dest_uid,
dest_gid, dest_gid,
verbosity, verbosity: Verbosity {
groups_only: false,
level: verbosity,
},
recursive, recursive,
dereference: derefer != 0, dereference: derefer != 0,
filter, filter,
@ -286,196 +281,6 @@ fn parse_spec(spec: &str) -> UResult<(Option<u32>, Option<u32>)> {
Ok((uid, gid)) 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)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

View file

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

View file

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

View file

@ -3,8 +3,12 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
use crate::error::UResult;
pub use crate::features::entries; pub use crate::features::entries;
use crate::fs::resolve_relative_path;
use crate::show_error;
use libc::{self, gid_t, lchown, uid_t}; use libc::{self, gid_t, lchown, uid_t};
use walkdir::WalkDir;
use std::io::Error as IOError; use std::io::Error as IOError;
use std::io::Result as IOResult; use std::io::Result as IOResult;
@ -18,86 +22,16 @@ use std::path::Path;
/// The various level of verbosity /// The various level of verbosity
#[derive(PartialEq, Clone, Debug)] #[derive(PartialEq, Clone, Debug)]
pub enum Verbosity { pub enum VerbosityLevel {
Silent, Silent,
Changes, Changes,
Verbose, Verbose,
Normal, Normal,
} }
#[derive(PartialEq, Clone, Debug)]
/// Actually perform the change of group on a path pub struct Verbosity {
fn chgrp<P: AsRef<Path>>(path: P, gid: gid_t, follow: bool) -> IOResult<()> { pub groups_only: bool,
let path = path.as_ref(); pub level: VerbosityLevel,
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)
} }
/// Actually perform the change of owner on a path /// Actually perform the change of owner on a path
@ -129,19 +63,36 @@ pub fn wrap_chown<P: AsRef<Path>>(
follow: bool, follow: bool,
verbosity: Verbosity, verbosity: Verbosity,
) -> Result<String, String> { ) -> Result<String, String> {
use self::Verbosity::*;
let dest_uid = dest_uid.unwrap_or_else(|| meta.uid()); let dest_uid = dest_uid.unwrap_or_else(|| meta.uid());
let dest_gid = dest_gid.unwrap_or_else(|| meta.gid()); let dest_gid = dest_gid.unwrap_or_else(|| meta.gid());
let path = path.as_ref(); let path = path.as_ref();
let mut out: String = String::new(); let mut out: String = String::new();
if let Err(e) = chown(path, dest_uid, dest_gid, follow) { if let Err(e) = chown(path, dest_uid, dest_gid, follow) {
match verbosity { match verbosity.level {
Silent => (), VerbosityLevel::Silent => (),
_ => { level => {
out = format!("changing ownership of '{}': {}", path.display(), e);
if verbosity == Verbose {
out = format!( 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 {}:{}", "{}\nfailed to change ownership of '{}' from {}:{} to {}:{}",
out, out,
path.display(), path.display(),
@ -149,7 +100,8 @@ pub fn wrap_chown<P: AsRef<Path>>(
entries::gid2grp(meta.gid()).unwrap(), entries::gid2grp(meta.gid()).unwrap(),
entries::uid2usr(dest_uid).unwrap(), entries::uid2usr(dest_uid).unwrap(),
entries::gid2grp(dest_gid).unwrap() entries::gid2grp(dest_gid).unwrap()
); )
};
}; };
} }
} }
@ -157,27 +109,237 @@ pub fn wrap_chown<P: AsRef<Path>>(
} else { } else {
let changed = dest_uid != meta.uid() || dest_gid != meta.gid(); let changed = dest_uid != meta.uid() || dest_gid != meta.gid();
if changed { if changed {
match verbosity { match verbosity.level {
Changes | Verbose => { VerbosityLevel::Changes | VerbosityLevel::Verbose => {
out = format!( 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 {}:{}", "changed ownership of '{}' from {}:{} to {}:{}",
path.display(), path.display(),
entries::uid2usr(meta.uid()).unwrap(), entries::uid2usr(meta.uid()).unwrap(),
entries::gid2grp(meta.gid()).unwrap(), entries::gid2grp(meta.gid()).unwrap(),
entries::uid2usr(dest_uid).unwrap(), entries::uid2usr(dest_uid).unwrap(),
entries::gid2grp(dest_gid).unwrap() entries::gid2grp(dest_gid).unwrap()
); )
};
} }
_ => (), _ => (),
}; };
} else if verbosity == Verbose { } else if verbosity.level == VerbosityLevel::Verbose {
out = format!( 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 {}:{}", "ownership of '{}' retained as {}:{}",
path.display(), path.display(),
entries::uid2usr(dest_uid).unwrap(), entries::uid2usr(dest_uid).unwrap(),
entries::gid2grp(dest_gid).unwrap() entries::gid2grp(dest_gid).unwrap()
); )
};
} }
} }
Ok(out) 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("__nosuchgroup__")
.arg("/") .arg("/")
.fails() .fails()
.stderr_is("chgrp: invalid group: __nosuchgroup__"); .stderr_is("chgrp: invalid group: '__nosuchgroup__'");
} }
#[test] #[test]