From 69beb787d1be696024c4f276cda35cb88a1fb972 Mon Sep 17 00:00:00 2001 From: Knight Date: Wed, 22 Jun 2016 21:36:50 +0800 Subject: [PATCH 1/9] chown: Add entries --- Cargo.toml | 2 ++ Makefile | 2 ++ src/chown/Cargo.toml | 19 +++++++++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 src/chown/Cargo.toml diff --git a/Cargo.toml b/Cargo.toml index e17c40bd2..cf0cd9fd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ build = "build.rs" [features] unix = [ "chmod", + "chown", "chroot", "du", "groups", @@ -100,6 +101,7 @@ base64 = { optional=true, path="src/base64" } basename = { optional=true, path="src/basename" } cat = { optional=true, path="src/cat" } chmod = { optional=true, path="src/chmod" } +chown = { optional=true, path="src/chown" } chroot = { optional=true, path="src/chroot" } cksum = { optional=true, path="src/cksum" } comm = { optional=true, path="src/comm" } diff --git a/Makefile b/Makefile index a9d32b70f..0e49727f7 100644 --- a/Makefile +++ b/Makefile @@ -99,6 +99,7 @@ PROGS := \ UNIX_PROGS := \ chmod \ + chown \ chroot \ du \ groups \ @@ -135,6 +136,7 @@ TEST_PROGS := \ basename \ cat \ chmod \ + chown \ cksum \ comm \ cp \ diff --git a/src/chown/Cargo.toml b/src/chown/Cargo.toml new file mode 100644 index 000000000..1da09f5f0 --- /dev/null +++ b/src/chown/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "chown" +version = "0.0.1" +authors = [] + +[lib] +name = "uu_chown" +path = "chown.rs" + +[dependencies] +getopts = "*" +glob = "*" +libc = "*" +uucore = { path="../uucore" } +clippy = {version = "*", optional = true} + +[[bin]] +name = "chown" +path = "main.rs" From 123175d577cb65ef53b9e34ec37d642de0ef0059 Mon Sep 17 00:00:00 2001 From: Knight Date: Wed, 22 Jun 2016 21:37:40 +0800 Subject: [PATCH 2/9] chown: Add main.rs --- src/chown/main.rs | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/chown/main.rs diff --git a/src/chown/main.rs b/src/chown/main.rs new file mode 100644 index 000000000..50aa0a15f --- /dev/null +++ b/src/chown/main.rs @@ -0,0 +1,5 @@ +extern crate uu_chown; + +fn main() { + std::process::exit(uu_chown::uumain(std::env::args().collect())); +} From 59ed78209a245f7dcab6fc4abc9f4391290cd97c Mon Sep 17 00:00:00 2001 From: Knight Date: Wed, 22 Jun 2016 21:39:01 +0800 Subject: [PATCH 3/9] chown: Add `mod passwd` --- src/chown/passwd.rs | 57 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/chown/passwd.rs diff --git a/src/chown/passwd.rs b/src/chown/passwd.rs new file mode 100644 index 000000000..3b5e7f744 --- /dev/null +++ b/src/chown/passwd.rs @@ -0,0 +1,57 @@ +// (c) Jian Zeng + +extern crate uucore; +use self::uucore::c_types::{getpwuid, getpwnam, getgrgid, getgrnam}; + +use std::ptr; +use std::ffi::{CString, CStr}; +use std::io::Result as IOResult; +use std::io::{ErrorKind, Error}; + +macro_rules! gen_func { + ($fun:ident, $getid:ident, $getnm:ident, $field:ident) => ( + pub fn $fun(name_or_id: &str) -> IOResult { + if let Ok(id) = name_or_id.parse::() { + let data = unsafe { + $getid(id) + }; + if !data.is_null() { + return Ok(id); + } else { + return Err(Error::new(ErrorKind::NotFound, format!("No such id `{}`", id))); + } + } else { + let name = CString::new(name_or_id).unwrap(); + let data = unsafe { + $getnm(name.as_ptr()) + }; + if !data.is_null() { + return Ok(unsafe { + ptr::read(data).$field + }); + } else { + return Err(Error::new(ErrorKind::NotFound, format!("No such name `{}`", name_or_id))); + } + } + } + ); + ($fun:ident, $getid:ident, $field:ident) => ( + pub fn $fun(id: u32) -> IOResult { + let data = unsafe { + $getid(id) + }; + if !data.is_null() { + Ok(unsafe { + CStr::from_ptr(ptr::read(data).$field).to_string_lossy().into_owned() + }) + } else { + Err(Error::new(ErrorKind::NotFound, format!("No such id `{}`", id))) + } + } + ); +} + +gen_func!(getuid, getpwuid, getpwnam, pw_uid); +gen_func!(getgid, getgrgid, getgrnam, gr_gid); +gen_func!(uid2usr, getpwuid, pw_name); +gen_func!(gid2grp, getgrgid, gr_name); From 19676a3ca29fb31b1f1187da3dfc5475f802ed17 Mon Sep 17 00:00:00 2001 From: Knight Date: Wed, 22 Jun 2016 21:39:46 +0800 Subject: [PATCH 4/9] chown: Add tests --- tests/test_chown.rs | 46 +++++++++++++++++++++++++++++++++++++++++++++ tests/tests.rs | 1 + 2 files changed, 47 insertions(+) create mode 100644 tests/test_chown.rs diff --git a/tests/test_chown.rs b/tests/test_chown.rs new file mode 100644 index 000000000..7fe16c92e --- /dev/null +++ b/tests/test_chown.rs @@ -0,0 +1,46 @@ +use common::util::*; + +extern crate uu_chown; +pub use self::uu_chown::*; + +static UTIL_NAME: &'static str = "chown"; + +#[cfg(test)] +mod test_passwd { + use super::passwd::*; + + #[test] + fn test_getuid() { + assert_eq!(0, getuid("root").unwrap()); + assert_eq!(99, getuid("99").unwrap()); + assert!(getuid("88888888").is_err()); + assert!(getuid("agroupthatdoesntexist").is_err()); + } + + #[test] + fn test_getgid() { + assert_eq!(0, getgid("root").unwrap()); + assert_eq!(99, getgid("99").unwrap()); + assert!(getgid("88888888").is_err()); + assert!(getgid("agroupthatdoesntexist").is_err()); + } + + #[test] + fn test_uid2usr() { + assert_eq!("root", uid2usr(0).unwrap()); + assert!(uid2usr(88888888).is_err()); + } + + #[test] + fn test_gid2grp() { + assert_eq!("root", gid2grp(0).unwrap()); + assert!(gid2grp(88888888).is_err()); + } +} + +#[test] +fn test_invalid_option() { + let (_, mut ucmd) = testing(UTIL_NAME); + ucmd.arg("-w").arg("-q").arg("/"); + ucmd.fails(); +} diff --git a/tests/tests.rs b/tests/tests.rs index aa8abf797..d867a934f 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -26,6 +26,7 @@ macro_rules! unix_only { } unix_only! { "chmod", test_chmod; + "chown", test_chown; "mv", test_mv; "pathchk", test_pathchk; "stdbuf", test_stdbuf; From 04d7f81fd3c80cae1584d510d949ce74d00e92d4 Mon Sep 17 00:00:00 2001 From: Knight Date: Wed, 22 Jun 2016 21:40:31 +0800 Subject: [PATCH 5/9] chown: Almost done TODO: * [ ] Describe changes according to verbosity * [ ] More tests * [ ] Clean up the code --- src/chown/chown.rs | 448 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 448 insertions(+) create mode 100644 src/chown/chown.rs diff --git a/src/chown/chown.rs b/src/chown/chown.rs new file mode 100644 index 000000000..811dfda98 --- /dev/null +++ b/src/chown/chown.rs @@ -0,0 +1,448 @@ +#![crate_name = "uu_chown"] + +// This file is part of the uutils coreutils package. +// +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// + +#![cfg_attr(feature="clippy", feature(plugin))] +#![cfg_attr(feature="clippy", plugin(clippy))] + +extern crate libc; +use libc::{uid_t, gid_t, c_char, c_int}; + +#[macro_use] +extern crate uucore; + +extern crate getopts; +use getopts::Options; + +pub mod passwd; + +use std::fs; +use std::os::unix::fs::MetadataExt; + +use std::io::{self, Write}; +use std::io::Result as IOResult; + +use std::path::Path; +use std::convert::AsRef; + +use std::ffi::CString; +use std::os::unix::ffi::OsStrExt; + +use std::sync::Arc; + +static NAME: &'static str = "chown"; +static VERSION: &'static str = env!("CARGO_PKG_VERSION"); + +const FTS_COMFOLLOW: u8 = 1; +const FTS_PHYSICAL: u8 = 1 << 1; +const FTS_LOGICAL: u8 = 1 << 2; + +extern "C" { + pub fn lchown(path: *const c_char, uid: uid_t, gid: gid_t) -> c_int; +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = Options::new(); + + opts.optflag("c", + "changes", + "like verbose but report only when a change is made"); + opts.optflag("f", "silent", ""); + opts.optflag("", "quiet", "suppress most error messages"); + opts.optflag("v", + "verbose", + "output a diagnostic for every file processed"); + opts.optflag("", "dereference", "affect the referent of each symbolic link (this is the default), rather than the symbolic link itself"); + opts.optflag("h", "no-dereference", "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)"); + + opts.optopt("", "from", "change the owner and/or group of each file only if its current owner and/or group match those specified here. Either may be omitted, in which case a match is not required for the omitted attribute", "CURRENT_OWNER:CURRENT_GROUP"); + opts.optopt("", + "reference", + "use RFILE's owner and group rather than specifying OWNER:GROUP values", + "RFILE"); + + opts.optflag("", + "no-preserve-root", + "do not treat '/' specially (the default)"); + opts.optflag("", "preserve-root", "fail to operate recursively on '/'"); + + opts.optflag("R", + "recursive", + "operate on files and directories recursively"); + opts.optflag("H", + "", + "if a command line argument is a symbolic link to a directory, traverse it"); + opts.optflag("L", + "", + "traverse every symbolic link to a directory encountered"); + opts.optflag("P", "", "do not traverse any symbolic links (default)"); + + opts.optflag("", "help", "display this help and exit"); + opts.optflag("", "version", "output version information and exit"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => { + disp_err!("{}", f); + return 1; + } + }; + + let mut bit_flag = FTS_PHYSICAL; + let mut preserve_root = false; + let mut derefer = -1; + for opt in &args { + match opt.as_str() { + // If more than one is specified, only the final one takes effect. + s if s.contains('H') => bit_flag = FTS_COMFOLLOW | FTS_PHYSICAL, + s if s.contains('L') => bit_flag = FTS_LOGICAL, + s if s.contains('P') => bit_flag = FTS_PHYSICAL, + "--no-preserve-root" => preserve_root = false, + "--preserve-root" => preserve_root = true, + "--dereference" => derefer = 1, + "--no-dereference" => derefer = 0, + _ => (), + } + } + + if matches.opt_present("help") { + return help(); + } else if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + let recursive = matches.opt_present("recursive"); + if recursive { + if bit_flag == FTS_PHYSICAL { + if derefer == 1 { + show_info!("-R --dereference requires -H or -L"); + return 1; + } + derefer = 0; + } + } else { + bit_flag = FTS_PHYSICAL; + } + + let verbosity = if matches.opt_present("changes") { + Verbosity::Changes + } else if matches.opt_present("silent") || matches.opt_present("quiet") { + Verbosity::Silent + } else if matches.opt_present("verbose") { + Verbosity::Verbose + } else { + Verbosity::Normal + }; + + let filter = if let Some(spec) = matches.opt_str("from") { + match parse_spec(&spec) { + Ok((Some(uid), None)) => IfFrom::User(uid), + Ok((None, Some(gid))) => IfFrom::Group(gid), + Ok((Some(uid), Some(gid))) => IfFrom::UserGroup(uid, gid), + Ok((None, None)) => IfFrom::All, + Err(e) => { + show_info!("{}", e); + return 1; + } + } + } else { + IfFrom::All + }; + + if matches.free.len() < 1 { + disp_err!("missing operand"); + return 1; + } else if matches.free.len() < 2 && !matches.opt_present("reference") { + disp_err!("missing operand after ‘{}’", matches.free[0]); + return 1; + } + + let dest_uid: Option; + let dest_gid: Option; + if let Some(file) = matches.opt_str("reference") { + // matches.opt_present("reference") + match fs::metadata(&file) { + Ok(meta) => { + dest_gid = Some(meta.gid()); + dest_uid = Some(meta.uid()); + } + Err(e) => { + show_info!("failed to get attributes of '{}': {}", file, e); + return 1; + } + } + } else { + match parse_spec(&matches.free[0]) { + Ok((u, g)) => { + dest_uid = u; + dest_gid = g; + } + Err(e) => { + show_info!("{}", e); + return 1; + } + } + } + let mut files = matches.free; + files.remove(0); + let executor = Chowner { + bit_flag: bit_flag, + dest_uid: dest_uid, + dest_gid: dest_gid, + verbosity: verbosity, + recursive: recursive, + dereference: derefer != 0, + filter: filter, + preserve_root: preserve_root, + files: files, + }; + executor.exec() +} + +fn parse_spec(spec: &str) -> Result<(Option, Option), String> { + let args = spec.split(':').collect::>(); + let usr_only = args.len() == 1; + let grp_only = args.len() == 2 && args[0].is_empty() && !args[1].is_empty(); + let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty(); + + if usr_only { + Ok((Some(match passwd::getuid(args[0]) { + Ok(uid) => uid, + Err(_) => return Err(format!("invalid user: ‘{}’", spec)), + }), + None)) + } else if grp_only { + Ok((None, + Some(match passwd::getgid(args[1]) { + Ok(gid) => gid, + Err(_) => return Err(format!("invalid group: ‘{}’", spec)), + }))) + } else if usr_grp { + Ok((Some(match passwd::getuid(args[0]) { + Ok(uid) => uid, + Err(_) => return Err(format!("invalid user: ‘{}’", spec)), + }), + Some(match passwd::getgid(args[1]) { + Ok(gid) => gid, + Err(_) => return Err(format!("invalid group: ‘{}’", spec)), + }))) + } else { + Ok((None, None)) + } +} + +enum Verbosity { + Silent, + Changes, + Normal, + Verbose, +} + +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, +} + +impl Chowner { + fn exec(&self) -> i32 { + let mut ret = 0; + for f in &self.files { + if f == "/" && self.preserve_root && self.recursive { + show_info!("it is dangerous to operate recursively on '/'"); + show_info!("use --no-preserve-root to override this failsafe"); + ret = 1; + continue; + } + ret = self.traverse(f); + } + ret + } + + fn chown>(&self, path: P, follow: bool) -> IOResult<()> { + let s = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap(); + let ret = unsafe { + if follow { + libc::chown(s.as_ptr(), + self.dest_uid.unwrap_or((0 as uid_t).wrapping_sub(1)), + self.dest_gid.unwrap_or((0 as gid_t).wrapping_sub(1))) + + } else { + lchown(s.as_ptr(), + self.dest_uid.unwrap_or((0 as uid_t).wrapping_sub(1)), + self.dest_gid.unwrap_or((0 as gid_t).wrapping_sub(1))) + } + }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } + } + + fn traverse>(&self, root: P) -> i32 { + let mut ret = 0; + let follow_arg = self.dereference || self.recursive && self.bit_flag != FTS_PHYSICAL; + let root_path = root.as_ref(); + let meta = if follow_arg { + match root_path.metadata() { + Ok(meta) => meta, + Err(e) => { + show_info!("cannot dereference '{}': {}", root_path.display(), e); + return 1; + } + } + } else { + match root_path.symlink_metadata() { + Ok(meta) => meta, + Err(e) => { + show_info!("cannot access '{}': {}", root_path.display(), e); + return 1; + } + } + }; + if self.matched(meta.uid(), meta.gid()) { + if let Err(e) = self.chown(root.as_ref(), follow_arg) { + show_info!("changing ownership of '{}': {}", root_path.display(), e); + ret = 1; + } + } + + if !meta.is_dir() || !self.recursive { + return ret; + } + + let mut dirs = vec![]; + dirs.push(Arc::new(root_path.to_path_buf())); + while !dirs.is_empty() { + let dir = dirs.pop().expect("Poping directory"); + for entry in dir.read_dir().unwrap() { + let entry = entry.unwrap(); + let path = Arc::new(entry.path()); + let smeta = if self.bit_flag & FTS_LOGICAL != 0 { + match path.metadata() { + Ok(meta) => meta, + Err(e) => { + show_info!("cannot access '{}': {}", path.display(), e); + ret = 1; + continue; + } + } + } else { + match path.symlink_metadata() { + Ok(meta) => meta, + Err(e) => { + show_info!("cannot dereference '{}': {}", path.display(), e); + ret = 1; + continue; + } + } + }; + if smeta.is_dir() { + dirs.push(path.clone()); + } + let meta = if self.bit_flag == FTS_PHYSICAL { + smeta + } else { + match path.metadata() { + Ok(meta) => meta, + Err(e) => { + show_info!("cannot dereference '{}': {}", path.display(), e); + ret = 1; + continue; + } + } + }; + if self.matched(meta.uid(), meta.gid()) { + if let Err(e) = self.chown(&*path, true) { + ret = 1; + show_info!("changing ownership of '{}': {}", path.display(), e); + } + } + } + } + ret + } + + #[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, + } + } +} + +fn help() -> i32 { + println!(r#" +Usage: {0} [OPTION]... [OWNER][:[GROUP]] FILE... + or: {0} [OPTION]... --reference=RFILE FILE... +Change the owner and/or group of each FILE to OWNER and/or GROUP. +With --reference, change the owner and group of each FILE to those of RFILE. + + -c, --changes like verbose but report only when a change is made + -f, --silent, --quiet suppress most error messages + -v, --verbose output a diagnostic for every file processed + --dereference affect the referent of each symbolic link (this is + the default), rather than the symbolic link itself + -h, --no-dereference affect symbolic links instead of any referenced file + (useful only on systems that can change the + ownership of a symlink) + --from=CURRENT_OWNER:CURRENT_GROUP + change the owner and/or group of each file only if + its current owner and/or group match those specified + here. Either may be omitted, in which case a match + is not required for the omitted attribute + --no-preserve-root do not treat '/' specially (the default) + --preserve-root fail to operate recursively on '/' + --reference=RFILE use RFILE's owner and group rather than + specifying OWNER:GROUP values + -R, --recursive operate on files and directories recursively + +The following options modify how a hierarchy is traversed when the -R +option is also specified. If more than one is specified, only the final +one takes effect. + + -H if a command line argument is a symbolic link + to a directory, traverse it + -L traverse every symbolic link to a directory + encountered + -P do not traverse any symbolic links (default) + + --help display this help and exit + --version output version information and exit + +Owner is unchanged if missing. Group is unchanged if missing, but changed +to login group if implied by a ':' following a symbolic OWNER. +OWNER and GROUP may be numeric as well as symbolic. + +Examples: + chown root /u Change the owner of /u to "root". + chown root:staff /u Likewise, but also change its group to "staff". + chown -hR root /u Change the owner of /u and subfiles to "root". + "#, + NAME); + 0 +} From d4b2766c4ba8528aacd48a423ed01c190eceeec8 Mon Sep 17 00:00:00 2001 From: Knight Date: Mon, 27 Jun 2016 23:22:13 +0800 Subject: [PATCH 6/9] chown: Add crate `walkdir` --- src/chown/Cargo.toml | 2 +- src/chown/passwd.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/chown/Cargo.toml b/src/chown/Cargo.toml index 1da09f5f0..e34abc72f 100644 --- a/src/chown/Cargo.toml +++ b/src/chown/Cargo.toml @@ -12,7 +12,7 @@ getopts = "*" glob = "*" libc = "*" uucore = { path="../uucore" } -clippy = {version = "*", optional = true} +walkdir = "0.1" [[bin]] name = "chown" diff --git a/src/chown/passwd.rs b/src/chown/passwd.rs index 3b5e7f744..ff0707228 100644 --- a/src/chown/passwd.rs +++ b/src/chown/passwd.rs @@ -18,6 +18,7 @@ macro_rules! gen_func { if !data.is_null() { return Ok(id); } else { + // last_os_error() returns success return Err(Error::new(ErrorKind::NotFound, format!("No such id `{}`", id))); } } else { @@ -30,6 +31,7 @@ macro_rules! gen_func { ptr::read(data).$field }); } else { + // last_os_error() returns success return Err(Error::new(ErrorKind::NotFound, format!("No such name `{}`", name_or_id))); } } From 7e4a708e7cacb1877ae9e4fb83a4db7aa3a1a0ca Mon Sep 17 00:00:00 2001 From: Knight Date: Mon, 27 Jun 2016 23:27:20 +0800 Subject: [PATCH 7/9] chown: refactor --- src/chown/chown.rs | 230 ++++++++++++++++++++++++++------------------ tests/test_chown.rs | 2 - 2 files changed, 138 insertions(+), 94 deletions(-) diff --git a/src/chown/chown.rs b/src/chown/chown.rs index 811dfda98..42b53813f 100644 --- a/src/chown/chown.rs +++ b/src/chown/chown.rs @@ -8,9 +8,6 @@ // file that was distributed with this source code. // -#![cfg_attr(feature="clippy", feature(plugin))] -#![cfg_attr(feature="clippy", plugin(clippy))] - extern crate libc; use libc::{uid_t, gid_t, c_char, c_int}; @@ -20,9 +17,12 @@ extern crate uucore; extern crate getopts; use getopts::Options; +extern crate walkdir; +use walkdir::WalkDir; + pub mod passwd; -use std::fs; +use std::fs::{self, Metadata}; use std::os::unix::fs::MetadataExt; use std::io::{self, Write}; @@ -34,8 +34,6 @@ use std::convert::AsRef; use std::ffi::CString; use std::os::unix::ffi::OsStrExt; -use std::sync::Arc; - static NAME: &'static str = "chown"; static VERSION: &'static str = env!("CARGO_PKG_VERSION"); @@ -44,6 +42,7 @@ const FTS_PHYSICAL: u8 = 1 << 1; const FTS_LOGICAL: u8 = 1 << 2; extern "C" { + #[cfg_attr(all(target_os = "macos", target_arch = "x86"), link_name = "lchown$UNIX2003")] pub fn lchown(path: *const c_char, uid: uid_t, gid: gid_t) -> c_int; } @@ -97,12 +96,20 @@ pub fn uumain(args: Vec) -> i32 { let mut bit_flag = FTS_PHYSICAL; let mut preserve_root = false; let mut derefer = -1; + let flags: &[char] = &['H', 'L', 'P']; for opt in &args { match opt.as_str() { // If more than one is specified, only the final one takes effect. - s if s.contains('H') => bit_flag = FTS_COMFOLLOW | FTS_PHYSICAL, - s if s.contains('L') => bit_flag = FTS_LOGICAL, - s if s.contains('P') => bit_flag = FTS_PHYSICAL, + s if s.contains(flags) => { + if let Some(idx) = s.rfind(flags) { + match s.chars().nth(idx).unwrap() { + 'H' => bit_flag = FTS_COMFOLLOW | FTS_PHYSICAL, + 'L' => bit_flag = FTS_LOGICAL, + 'P' => bit_flag = FTS_PHYSICAL, + _ => (), + } + } + } "--no-preserve-root" => preserve_root = false, "--preserve-root" => preserve_root = true, "--dereference" => derefer = 1, @@ -167,7 +174,6 @@ pub fn uumain(args: Vec) -> i32 { let dest_uid: Option; let dest_gid: Option; if let Some(file) = matches.opt_str("reference") { - // matches.opt_present("reference") match fs::metadata(&file) { Ok(meta) => { dest_gid = Some(meta.gid()); @@ -238,11 +244,12 @@ fn parse_spec(spec: &str) -> Result<(Option, Option), String> { } } +#[derive(PartialEq, Debug)] enum Verbosity { Silent, Changes, - Normal, Verbose, + Normal, } enum IfFrom { @@ -264,6 +271,15 @@ struct Chowner { dereference: bool, } +macro_rules! unwrap { + ($m:expr, $e:ident, $err:block) => ( + match $m { + Ok(meta) => meta, + Err($e) => $err, + } + ) +} + impl Chowner { fn exec(&self) -> i32 { let mut ret = 0; @@ -274,23 +290,19 @@ impl Chowner { ret = 1; continue; } - ret = self.traverse(f); + ret |= self.traverse(f); } ret } - fn chown>(&self, path: P, follow: bool) -> IOResult<()> { - let s = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap(); + fn chown>(&self, path: P, duid: uid_t, dgid: 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(), - self.dest_uid.unwrap_or((0 as uid_t).wrapping_sub(1)), - self.dest_gid.unwrap_or((0 as gid_t).wrapping_sub(1))) - + libc::chown(s.as_ptr(), duid, dgid) } else { - lchown(s.as_ptr(), - self.dest_uid.unwrap_or((0 as uid_t).wrapping_sub(1)), - self.dest_gid.unwrap_or((0 as gid_t).wrapping_sub(1))) + lchown(s.as_ptr(), duid, dgid) } }; if ret == 0 { @@ -301,84 +313,118 @@ impl Chowner { } fn traverse>(&self, root: P) -> i32 { - let mut ret = 0; - let follow_arg = self.dereference || self.recursive && self.bit_flag != FTS_PHYSICAL; - let root_path = root.as_ref(); - let meta = if follow_arg { - match root_path.metadata() { - Ok(meta) => meta, - Err(e) => { - show_info!("cannot dereference '{}': {}", root_path.display(), e); - return 1; - } - } - } else { - match root_path.symlink_metadata() { - Ok(meta) => meta, - Err(e) => { - show_info!("cannot access '{}': {}", root_path.display(), e); - return 1; - } - } + 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, }; - if self.matched(meta.uid(), meta.gid()) { - if let Err(e) = self.chown(root.as_ref(), follow_arg) { - show_info!("changing ownership of '{}': {}", root_path.display(), e); - ret = 1; - } - } - if !meta.is_dir() || !self.recursive { + let ret = if self.matched(meta.uid(), meta.gid()) { + self.wrap_chown(path, &meta, follow_arg) + } else { + 0 + }; + + if !self.recursive { return ret; } - let mut dirs = vec![]; - dirs.push(Arc::new(root_path.to_path_buf())); - while !dirs.is_empty() { - let dir = dirs.pop().expect("Poping directory"); - for entry in dir.read_dir().unwrap() { - let entry = entry.unwrap(); - let path = Arc::new(entry.path()); - let smeta = if self.bit_flag & FTS_LOGICAL != 0 { - match path.metadata() { - Ok(meta) => meta, - Err(e) => { - show_info!("cannot access '{}': {}", path.display(), e); - ret = 1; - continue; - } - } - } else { - match path.symlink_metadata() { - Ok(meta) => meta, - Err(e) => { - show_info!("cannot dereference '{}': {}", path.display(), e); - ret = 1; - continue; - } - } - }; - if smeta.is_dir() { - dirs.push(path.clone()); + self.dive_into(&root) + } + + fn dive_into>(&self, root: P) -> i32 { + let mut ret = 0; + let root = root.as_ref(); + let follow = 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_info!("{}", e); + continue; + }); + let path = entry.path(); + let meta = match self.obtain_meta(path, follow) { + Some(m) => m, + _ => { + ret = 1; + continue; } - let meta = if self.bit_flag == FTS_PHYSICAL { - smeta - } else { - match path.metadata() { - Ok(meta) => meta, - Err(e) => { - show_info!("cannot dereference '{}': {}", path.display(), e); - ret = 1; - continue; - } - } - }; - if self.matched(meta.uid(), meta.gid()) { - if let Err(e) = self.chown(&*path, true) { - ret = 1; - show_info!("changing ownership of '{}': {}", path.display(), e); - } + }; + + if !self.matched(meta.uid(), meta.gid()) { + continue; + } + + ret = self.wrap_chown(path, &meta, true); + } + 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_info!("cannot access '{}': {}", path.display(), e), } + return None; + }) + } else { + unwrap!(path.symlink_metadata(), e, { + match self.verbosity { + Silent => (), + _ => show_info!("cannot dereference '{}': {}", path.display(), e), + } + return None; + }) + }; + Some(meta) + } + + fn wrap_chown>(&self, path: P, meta: &Metadata, follow: bool) -> i32 { + use self::Verbosity::*; + let mut ret = 0; + let dest_uid = self.dest_uid.unwrap_or(meta.uid()); + let dest_gid = self.dest_gid.unwrap_or(meta.gid()); + let path = path.as_ref(); + if let Err(e) = self.chown(path, dest_uid, dest_gid, follow) { + match self.verbosity { + Silent => (), + _ => { + show_info!("changing ownership of '{}': {}", path.display(), e); + if self.verbosity == Verbose { + println!("failed to change ownership of {} from {}:{} to {}:{}", + path.display(), + passwd::uid2usr(meta.uid()).unwrap(), + passwd::gid2grp(meta.gid()).unwrap(), + passwd::uid2usr(dest_uid).unwrap(), + passwd::gid2grp(dest_gid).unwrap()); + }; + } + } + ret = 1; + } else { + let changed = dest_uid != meta.uid() || dest_gid != meta.gid(); + if changed { + match self.verbosity { + Changes | Verbose => { + println!("changed ownership of {} from {}:{} to {}:{}", + path.display(), + passwd::uid2usr(meta.uid()).unwrap(), + passwd::gid2grp(meta.gid()).unwrap(), + passwd::uid2usr(dest_uid).unwrap(), + passwd::gid2grp(dest_gid).unwrap()); + } + _ => (), + }; + } else if self.verbosity == Verbose { + println!("ownership of {} retained as {}:{}", + path.display(), + passwd::uid2usr(dest_uid).unwrap(), + passwd::gid2grp(dest_gid).unwrap()); } } ret diff --git a/tests/test_chown.rs b/tests/test_chown.rs index 7fe16c92e..fc978eee2 100644 --- a/tests/test_chown.rs +++ b/tests/test_chown.rs @@ -12,7 +12,6 @@ mod test_passwd { #[test] fn test_getuid() { assert_eq!(0, getuid("root").unwrap()); - assert_eq!(99, getuid("99").unwrap()); assert!(getuid("88888888").is_err()); assert!(getuid("agroupthatdoesntexist").is_err()); } @@ -20,7 +19,6 @@ mod test_passwd { #[test] fn test_getgid() { assert_eq!(0, getgid("root").unwrap()); - assert_eq!(99, getgid("99").unwrap()); assert!(getgid("88888888").is_err()); assert!(getgid("agroupthatdoesntexist").is_err()); } From f77c4f2b1ab5c037e44455336cc4cde0572b5958 Mon Sep 17 00:00:00 2001 From: Knight Date: Mon, 4 Jul 2016 22:50:54 +0800 Subject: [PATCH 8/9] chown: Fix error on mac --- tests/test_chown.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/test_chown.rs b/tests/test_chown.rs index fc978eee2..0d90f6b5a 100644 --- a/tests/test_chown.rs +++ b/tests/test_chown.rs @@ -13,12 +13,16 @@ mod test_passwd { fn test_getuid() { assert_eq!(0, getuid("root").unwrap()); assert!(getuid("88888888").is_err()); - assert!(getuid("agroupthatdoesntexist").is_err()); + assert!(getuid("auserthatdoesntexist").is_err()); } #[test] fn test_getgid() { - assert_eq!(0, getgid("root").unwrap()); + if cfg!(target_os = "macos") { + assert_eq!(0, getgid("wheel").unwrap()); + } else { + assert_eq!(0, getgid("root").unwrap()); + } assert!(getgid("88888888").is_err()); assert!(getgid("agroupthatdoesntexist").is_err()); } @@ -31,7 +35,11 @@ mod test_passwd { #[test] fn test_gid2grp() { - assert_eq!("root", gid2grp(0).unwrap()); + if cfg!(target_os = "macos") { + assert_eq!("wheel", gid2grp(0).unwrap()); + } else { + assert_eq!("root", gid2grp(0).unwrap()); + } assert!(gid2grp(88888888).is_err()); } } From 197e7787a84ef7eee4892b6614517ec916a186ef Mon Sep 17 00:00:00 2001 From: Knight Date: Sun, 10 Jul 2016 21:32:07 +0800 Subject: [PATCH 9/9] chown: follow symlinks correctly --- src/chown/Cargo.toml | 7 +++++++ src/chown/chown.rs | 7 +++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/chown/Cargo.toml b/src/chown/Cargo.toml index e34abc72f..a17692dc1 100644 --- a/src/chown/Cargo.toml +++ b/src/chown/Cargo.toml @@ -14,6 +14,13 @@ libc = "*" uucore = { path="../uucore" } walkdir = "0.1" +[dependencies.clippy] +version = "*" +optional = true + +[features] +default = [] + [[bin]] name = "chown" path = "main.rs" diff --git a/src/chown/chown.rs b/src/chown/chown.rs index 42b53813f..79ff80b4e 100644 --- a/src/chown/chown.rs +++ b/src/chown/chown.rs @@ -8,6 +8,9 @@ // file that was distributed with this source code. // +#![cfg_attr(feature="clippy", feature(plugin))] +#![cfg_attr(feature="clippy", plugin(clippy))] + extern crate libc; use libc::{uid_t, gid_t, c_char, c_int}; @@ -336,7 +339,7 @@ impl Chowner { fn dive_into>(&self, root: P) -> i32 { let mut ret = 0; let root = root.as_ref(); - let follow = self.bit_flag & FTS_LOGICAL != 0; + 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; @@ -356,7 +359,7 @@ impl Chowner { continue; } - ret = self.wrap_chown(path, &meta, true); + ret = self.wrap_chown(path, &meta, follow); } ret }