mirror of
https://github.com/uutils/coreutils
synced 2024-10-14 20:07:13 +00:00
commit
ae0e1c4768
|
@ -7,6 +7,7 @@ build = "build.rs"
|
||||||
[features]
|
[features]
|
||||||
unix = [
|
unix = [
|
||||||
"arch",
|
"arch",
|
||||||
|
"chgrp",
|
||||||
"chmod",
|
"chmod",
|
||||||
"chown",
|
"chown",
|
||||||
"chroot",
|
"chroot",
|
||||||
|
@ -108,6 +109,7 @@ base32 = { optional=true, path="src/base32" }
|
||||||
base64 = { optional=true, path="src/base64" }
|
base64 = { optional=true, path="src/base64" }
|
||||||
basename = { optional=true, path="src/basename" }
|
basename = { optional=true, path="src/basename" }
|
||||||
cat = { optional=true, path="src/cat" }
|
cat = { optional=true, path="src/cat" }
|
||||||
|
chgrp = { optional=true, path="src/chgrp" }
|
||||||
chmod = { optional=true, path="src/chmod" }
|
chmod = { optional=true, path="src/chmod" }
|
||||||
chown = { optional=true, path="src/chown" }
|
chown = { optional=true, path="src/chown" }
|
||||||
chroot = { optional=true, path="src/chroot" }
|
chroot = { optional=true, path="src/chroot" }
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -103,6 +103,7 @@ PROGS := \
|
||||||
|
|
||||||
UNIX_PROGS := \
|
UNIX_PROGS := \
|
||||||
arch \
|
arch \
|
||||||
|
chgrp \
|
||||||
chmod \
|
chmod \
|
||||||
chown \
|
chown \
|
||||||
chroot \
|
chroot \
|
||||||
|
@ -144,6 +145,7 @@ TEST_PROGS := \
|
||||||
base64 \
|
base64 \
|
||||||
basename \
|
basename \
|
||||||
cat \
|
cat \
|
||||||
|
chgrp \
|
||||||
chmod \
|
chmod \
|
||||||
chown \
|
chown \
|
||||||
cksum \
|
cksum \
|
||||||
|
|
|
@ -153,7 +153,7 @@ To do
|
||||||
* [x] basename
|
* [x] basename
|
||||||
* [x] cat
|
* [x] cat
|
||||||
* [ ] chcon
|
* [ ] chcon
|
||||||
* [ ] chgrp
|
* [x] chgrp
|
||||||
* [x] chmod
|
* [x] chmod
|
||||||
* [x] chown
|
* [x] chown
|
||||||
* [x] chroot
|
* [x] chroot
|
||||||
|
|
20
src/chgrp/Cargo.toml
Normal file
20
src/chgrp/Cargo.toml
Normal file
|
@ -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
src/chgrp/chgrp.rs
Normal file
328
src/chgrp/chgrp.rs
Normal file
|
@ -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
src/chgrp/main.rs
Normal file
5
src/chgrp/main.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
extern crate uu_chgrp;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
std::process::exit(uu_chgrp::uumain(std::env::args().collect()));
|
||||||
|
}
|
102
tests/test_chgrp.rs
Normal file
102
tests/test_chgrp.rs
Normal file
|
@ -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);
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ macro_rules! unix_only {
|
||||||
unix_only! {
|
unix_only! {
|
||||||
"chmod", test_chmod;
|
"chmod", test_chmod;
|
||||||
"chown", test_chown;
|
"chown", test_chown;
|
||||||
|
"chgrp", test_chgrp;
|
||||||
"install", test_install;
|
"install", test_install;
|
||||||
"mv", test_mv;
|
"mv", test_mv;
|
||||||
"pathchk", test_pathchk;
|
"pathchk", test_pathchk;
|
||||||
|
|
Loading…
Reference in a new issue