Skip to content

Commit

Permalink
Merge pull request #977 from knight42/chgrp
Browse files Browse the repository at this point in the history
Implement chgrp
  • Loading branch information
nathanross committed Aug 21, 2016
2 parents 55d1378 + d2d9fcd commit ae0e1c4
Show file tree
Hide file tree
Showing 8 changed files with 461 additions and 1 deletion.
2 changes: 2 additions & 0 deletions Cargo.toml
Expand Up @@ -7,6 +7,7 @@ build = "build.rs"
[features]
unix = [
"arch",
"chgrp",
"chmod",
"chown",
"chroot",
Expand Down Expand Up @@ -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" }
Expand Down
2 changes: 2 additions & 0 deletions Makefile
Expand Up @@ -103,6 +103,7 @@ PROGS := \

UNIX_PROGS := \
arch \
chgrp \
chmod \
chown \
chroot \
Expand Down Expand Up @@ -144,6 +145,7 @@ TEST_PROGS := \
base64 \
basename \
cat \
chgrp \
chmod \
chown \
cksum \
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -153,7 +153,7 @@ To do
* [x] basename
* [x] cat
* [ ] chcon
* [ ] chgrp
* [x] chgrp
* [x] chmod
* [x] chown
* [x] chroot
Expand Down
20 changes: 20 additions & 0 deletions 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"
328 changes: 328 additions & 0 deletions src/chgrp/chgrp.rs
@@ -0,0 +1,328 @@
#![crate_name = "uu_chgrp"]

// This file is part of the uutils coreutils package.
//
// (c) Jian Zeng <anonymousknight96@gmail.com>
//
// 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<String>) -> 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<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 {
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<P: AsRef<Path>>(&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<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,
};

let ret = self.wrap_chgrp(path, &meta, follow_arg);

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_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<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_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<P: AsRef<Path>>(&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
}
}
5 changes: 5 additions & 0 deletions src/chgrp/main.rs
@@ -0,0 +1,5 @@
extern crate uu_chgrp;

fn main() {
std::process::exit(uu_chgrp::uumain(std::env::args().collect()));
}

0 comments on commit ae0e1c4

Please sign in to comment.