From 8cba71adb49985b21e6cad9f2934010c7f0312d3 Mon Sep 17 00:00:00 2001 From: Knight Date: Sun, 21 Aug 2016 17:04:02 +0800 Subject: [PATCH 1/3] chgrp: add entries --- Cargo.toml | 2 ++ Makefile | 2 ++ README.md | 2 +- src/chgrp/main.rs | 5 +++++ 4 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 src/chgrp/main.rs diff --git a/Cargo.toml b/Cargo.toml index 92859ca85..426f08b45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ build = "build.rs" [features] unix = [ "arch", + "chgrp", "chmod", "chown", "chroot", @@ -108,6 +109,7 @@ base32 = { optional=true, path="src/base32" } base64 = { optional=true, path="src/base64" } basename = { optional=true, path="src/basename" } cat = { optional=true, path="src/cat" } +chgrp = { optional=true, path="src/chgrp" } chmod = { optional=true, path="src/chmod" } chown = { optional=true, path="src/chown" } chroot = { optional=true, path="src/chroot" } diff --git a/Makefile b/Makefile index e67a262ad..98c34f4b7 100644 --- a/Makefile +++ b/Makefile @@ -103,6 +103,7 @@ PROGS := \ UNIX_PROGS := \ arch \ + chgrp \ chmod \ chown \ chroot \ @@ -144,6 +145,7 @@ TEST_PROGS := \ base64 \ basename \ cat \ + chgrp \ chmod \ chown \ cksum \ diff --git a/README.md b/README.md index 5c5d42d29..69354af78 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ To do * [x] basename * [x] cat * [ ] chcon -* [ ] chgrp +* [x] chgrp * [x] chmod * [x] chown * [x] chroot diff --git a/src/chgrp/main.rs b/src/chgrp/main.rs new file mode 100644 index 000000000..55be10cdb --- /dev/null +++ b/src/chgrp/main.rs @@ -0,0 +1,5 @@ +extern crate uu_chgrp; + +fn main() { + std::process::exit(uu_chgrp::uumain(std::env::args().collect())); +} From 9dda0bcd25122dda8ce9aa448781b6add21f50c3 Mon Sep 17 00:00:00 2001 From: Knight Date: Sun, 21 Aug 2016 17:04:38 +0800 Subject: [PATCH 2/3] chgrp: implemented --- src/chgrp/Cargo.toml | 20 +++ src/chgrp/chgrp.rs | 328 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 348 insertions(+) create mode 100644 src/chgrp/Cargo.toml create mode 100644 src/chgrp/chgrp.rs diff --git a/src/chgrp/Cargo.toml b/src/chgrp/Cargo.toml new file mode 100644 index 000000000..58e26e3cb --- /dev/null +++ b/src/chgrp/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "chgrp" +version = "0.0.1" +authors = [] + +[lib] +name = "uu_chgrp" +path = "chgrp.rs" + +[dependencies] +walkdir = "*" + +[dependencies.uucore] +path = "../uucore" +default-features = false +features = ["entries"] + +[[bin]] +name = "chgrp" +path = "main.rs" diff --git a/src/chgrp/chgrp.rs b/src/chgrp/chgrp.rs new file mode 100644 index 000000000..1a8be92e5 --- /dev/null +++ b/src/chgrp/chgrp.rs @@ -0,0 +1,328 @@ +#![crate_name = "uu_chgrp"] + +// 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. +// + +#[macro_use] +extern crate uucore; +use uucore::libc::{self, gid_t, lchown}; +pub use uucore::entries; + +extern crate walkdir; +use walkdir::WalkDir; + +use std::io::prelude::*; +use std::io::Result as IOResult; +use std::io::Error as IOError; + +use std::fs; +use std::fs::Metadata; +use std::os::unix::fs::MetadataExt; + +use std::path::Path; + +use std::ffi::CString; +use std::os::unix::ffi::OsStrExt; + +static SYNTAX: &'static str = "chgrp [OPTION]... GROUP FILE...\n or : chgrp [OPTION]... --reference=RFILE FILE..."; +static SUMMARY: &'static str = "Change the group of each FILE to GROUP."; + +const FTS_COMFOLLOW: u8 = 1; +const FTS_PHYSICAL: u8 = 1 << 1; +const FTS_LOGICAL: u8 = 1 << 2; + +pub fn uumain(args: Vec) -> i32 { + let mut opts = new_coreopts!(SYNTAX, SUMMARY, ""); + opts.optflag("c", + "changes", + "like verbose but report only when a change is made") + .optflag("f", "silent", "") + .optflag("", "quiet", "suppress most error messages") + .optflag("v", + "verbose", + "output a diagnostic for every file processed") + .optflag("", "dereference", "affect the referent of each symbolic link (this is the default), rather than the symbolic link itself") + .optflag("h", "no-dereference", "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)") + .optflag("", + "no-preserve-root", + "do not treat '/' specially (the default)") + .optflag("", "preserve-root", "fail to operate recursively on '/'") + .optopt("", + "reference", + "use RFILE's owner and group rather than specifying OWNER:GROUP values", + "RFILE") + .optflag("R", + "recursive", + "operate on files and directories recursively") + .optflag("H", + "", + "if a command line argument is a symbolic link to a directory, traverse it") + .optflag("L", + "", + "traverse every symbolic link to a directory encountered") + .optflag("P", "", "do not traverse any symbolic links (default)"); + + 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(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, + "--no-dereference" => derefer = 0, + _ => (), + } + } + + let matches = opts.parse(args); + 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 + }; + + 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_gid: gid_t; + let mut files; + if let Some(file) = matches.opt_str("reference") { + match fs::metadata(&file) { + Ok(meta) => { + dest_gid = meta.gid(); + } + Err(e) => { + show_info!("failed to get attributes of '{}': {}", file, e); + return 1; + } + } + files = matches.free; + } else { + match entries::grp2gid(&matches.free[0]) { + Ok(g) => { + dest_gid = g; + } + _ => { + show_info!("invalid group: {}", matches.free[0].as_str()); + return 1; + } + } + files = matches.free; + files.remove(0); + } + + let executor = Chgrper { + bit_flag: bit_flag, + dest_gid: dest_gid, + verbosity: verbosity, + recursive: recursive, + dereference: derefer != 0, + preserve_root: preserve_root, + files: files, + }; + executor.exec() +} + +#[derive(PartialEq, Debug)] +enum Verbosity { + Silent, + Changes, + Verbose, + Normal, +} + +struct Chgrper { + dest_gid: gid_t, + 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 { + 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 chgrp>(&self, path: P, 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(), (0 as gid_t).wrapping_sub(1), dgid) + } else { + lchown(s.as_ptr(), (0 as gid_t).wrapping_sub(1), dgid) + } + }; + if ret == 0 { + Ok(()) + } else { + Err(IOError::last_os_error()) + } + } + + 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, + }; + + let ret = self.wrap_chgrp(path, &meta, follow_arg); + + 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_info!("{}", e); + continue; + }); + let path = entry.path(); + let meta = match self.obtain_meta(path, follow) { + Some(m) => m, + _ => { + ret = 1; + continue; + } + }; + + ret = self.wrap_chgrp(path, &meta, follow); + } + 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_chgrp>(&self, path: P, meta: &Metadata, follow: bool) -> i32 { + use self::Verbosity::*; + let mut ret = 0; + let dest_gid = self.dest_gid; + let path = path.as_ref(); + if let Err(e) = self.chgrp(path, dest_gid, follow) { + match self.verbosity { + Silent => (), + _ => { + show_info!("changing group of '{}': {}", path.display(), e); + if self.verbosity == Verbose { + println!("failed to change group of {} from {} to {}", + path.display(), + entries::gid2grp(meta.gid()).unwrap(), + entries::gid2grp(dest_gid).unwrap()); + }; + } + } + ret = 1; + } else { + let changed = dest_gid != meta.gid(); + if changed { + match self.verbosity { + Changes | Verbose => { + println!("changed group of {} from {} to {}", + path.display(), + entries::gid2grp(meta.gid()).unwrap(), + entries::gid2grp(dest_gid).unwrap()); + } + _ => (), + }; + } else if self.verbosity == Verbose { + println!("group of {} retained as {}", + path.display(), + entries::gid2grp(dest_gid).unwrap()); + } + } + ret + } +} From d2d9fcd6284a6959bee6ea24682ffbd23ca36cc3 Mon Sep 17 00:00:00 2001 From: Knight Date: Sun, 21 Aug 2016 17:05:05 +0800 Subject: [PATCH 3/3] chgrp: add tests --- tests/test_chgrp.rs | 102 ++++++++++++++++++++++++++++++++++++++++++++ tests/tests.rs | 1 + 2 files changed, 103 insertions(+) create mode 100644 tests/test_chgrp.rs diff --git a/tests/test_chgrp.rs b/tests/test_chgrp.rs new file mode 100644 index 000000000..29af80912 --- /dev/null +++ b/tests/test_chgrp.rs @@ -0,0 +1,102 @@ +use common::util::*; + +static UTIL_NAME: &'static str = "chgrp"; +fn new_ucmd() -> UCommand { + TestScenario::new(UTIL_NAME).ucmd() +} + +#[test] +fn test_invalid_option() { + new_ucmd() + .arg("-w") + .arg("/") + .fails(); +} + +static DIR: &'static str = "/tmp"; + +#[test] +fn test_invalid_group() { + new_ucmd() + .arg("nosuchgroup") + .arg("/") + .fails() + .stderr_is("chgrp: invalid group: nosuchgroup"); +} + +#[test] +fn test_1() { + new_ucmd() + .arg("bin") + .arg(DIR) + .fails() + .stderr_is("chgrp: changing group of '/tmp': Operation not permitted (os error 1)"); +} + +#[test] +fn test_fail_silently() { + for opt in &["-f", "--silent", "--quiet"] { + new_ucmd() + .arg(opt) + .arg("bin") + .arg(DIR) + .run() + .fails_silently(); + } +} + +#[test] +fn test_preserve_root() { + new_ucmd() + .arg("--preserve-root") + .arg("-R") + .arg("bin").arg("/") + .fails() + .stderr_is("chgrp: it is dangerous to operate recursively on '/'\nchgrp: use --no-preserve-root to override this failsafe"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_reference() { + new_ucmd() + .arg("-v") + .arg("--reference=/etc/passwd") + .arg("/etc") + .fails() + .stderr_is("chgrp: changing group of '/etc': Operation not permitted (os error 1)\n") + .stdout_is("failed to change group of /etc from root to root\n"); +} + +#[test] +#[cfg(target_os = "macos")] +fn test_reference() { + new_ucmd() + .arg("-v") + .arg("--reference=/etc/passwd") + .arg("/etc") + .succeeds(); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_big_p() { + new_ucmd() + .arg("-RP") + .arg("bin") + .arg("/proc/self/cwd") + .fails() + .stderr_is("chgrp: changing group of '/proc/self/cwd': Operation not permitted (os error 1)\n"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_big_h() { + assert!(new_ucmd() + .arg("-RH") + .arg("bin") + .arg("/proc/self/fd") + .fails() + .stderr + .lines() + .fold(0, |acc, _| acc + 1) > 1); +} diff --git a/tests/tests.rs b/tests/tests.rs index 81336f5a6..3897935e2 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -14,6 +14,7 @@ macro_rules! unix_only { unix_only! { "chmod", test_chmod; "chown", test_chown; + "chgrp", test_chgrp; "install", test_install; "mv", test_mv; "pathchk", test_pathchk;