mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge pull request #1641 from sylvestre/install-owner-group
feature(install) - add group & owner support + a refactor
This commit is contained in:
commit
94571ecf8f
13 changed files with 428 additions and 215 deletions
|
@ -15,7 +15,7 @@ edition = "2018"
|
|||
path = "src/chgrp.rs"
|
||||
|
||||
[dependencies]
|
||||
uucore = { version=">=0.0.4", package="uucore", path="../../uucore", features=["entries", "fs"] }
|
||||
uucore = { version=">=0.0.4", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
|
||||
uucore_procs = { version=">=0.0.4", package="uucore_procs", path="../../uucore_procs" }
|
||||
walkdir = "2.2"
|
||||
|
||||
|
|
|
@ -11,23 +11,18 @@
|
|||
extern crate uucore;
|
||||
pub use uucore::entries;
|
||||
use uucore::fs::resolve_relative_path;
|
||||
use uucore::libc::{self, gid_t, lchown};
|
||||
use uucore::libc::gid_t;
|
||||
use uucore::perms::{wrap_chgrp, Verbosity};
|
||||
|
||||
extern crate walkdir;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use std::io::Error as IOError;
|
||||
use std::io::Result as IOResult;
|
||||
|
||||
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: &str =
|
||||
"chgrp [OPTION]... GROUP FILE...\n or : chgrp [OPTION]... --reference=RFILE FILE...";
|
||||
static SUMMARY: &str = "Change the group of each FILE to GROUP.";
|
||||
|
@ -165,14 +160,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
executor.exec()
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum Verbosity {
|
||||
Silent,
|
||||
Changes,
|
||||
Verbose,
|
||||
Normal,
|
||||
}
|
||||
|
||||
struct Chgrper {
|
||||
dest_gid: gid_t,
|
||||
bit_flag: u8,
|
||||
|
@ -201,23 +188,6 @@ impl Chgrper {
|
|||
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())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_bind_root<P: AsRef<Path>>(&self, root: P) -> bool {
|
||||
// TODO: is there an equivalent on Windows?
|
||||
|
@ -269,7 +239,24 @@ impl Chgrper {
|
|||
}
|
||||
}
|
||||
|
||||
let ret = self.wrap_chgrp(path, &meta, follow_arg);
|
||||
let ret = match wrap_chgrp(
|
||||
path,
|
||||
&meta,
|
||||
self.dest_gid,
|
||||
follow_arg,
|
||||
self.verbosity.clone(),
|
||||
) {
|
||||
Ok(n) => {
|
||||
show_info!("{}", n);
|
||||
0
|
||||
}
|
||||
Err(e) => {
|
||||
if self.verbosity != Verbosity::Silent {
|
||||
show_info!("{}", e);
|
||||
}
|
||||
1
|
||||
}
|
||||
};
|
||||
|
||||
if !self.recursive {
|
||||
ret
|
||||
|
@ -297,8 +284,22 @@ impl Chgrper {
|
|||
}
|
||||
};
|
||||
|
||||
ret = self.wrap_chgrp(path, &meta, follow);
|
||||
ret = match wrap_chgrp(path, &meta, self.dest_gid, follow, self.verbosity.clone()) {
|
||||
Ok(n) => {
|
||||
if n != "" {
|
||||
show_info!("{}", n);
|
||||
}
|
||||
0
|
||||
}
|
||||
Err(e) => {
|
||||
if self.verbosity != Verbosity::Silent {
|
||||
show_info!("{}", e);
|
||||
}
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
|
@ -324,50 +325,4 @@ impl Chgrper {
|
|||
};
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ path = "src/chown.rs"
|
|||
[dependencies]
|
||||
clap = "2.33"
|
||||
glob = "0.3.0"
|
||||
uucore = { version=">=0.0.4", package="uucore", path="../../uucore", features=["entries", "fs"] }
|
||||
uucore = { version=">=0.0.4", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
|
||||
uucore_procs = { version=">=0.0.4", package="uucore_procs", path="../../uucore_procs" }
|
||||
walkdir = "2.2"
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
extern crate uucore;
|
||||
pub use uucore::entries::{self, Group, Locate, Passwd};
|
||||
use uucore::fs::resolve_relative_path;
|
||||
use uucore::libc::{self, gid_t, lchown, uid_t};
|
||||
use uucore::libc::{gid_t, uid_t};
|
||||
use uucore::perms::{wrap_chown, Verbosity};
|
||||
|
||||
extern crate clap;
|
||||
use clap::{App, Arg};
|
||||
|
@ -22,15 +23,9 @@ use walkdir::WalkDir;
|
|||
use std::fs::{self, Metadata};
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use std::io;
|
||||
use std::io::Result as IOResult;
|
||||
|
||||
use std::convert::AsRef;
|
||||
use std::path::Path;
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
static ABOUT: &str = "change file owner and group";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
|
@ -304,14 +299,6 @@ fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum Verbosity {
|
||||
Silent,
|
||||
Changes,
|
||||
Verbose,
|
||||
Normal,
|
||||
}
|
||||
|
||||
enum IfFrom {
|
||||
All,
|
||||
User(u32),
|
||||
|
@ -349,29 +336,6 @@ impl Chowner {
|
|||
ret
|
||||
}
|
||||
|
||||
fn chown<P: AsRef<Path>>(
|
||||
&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(), duid, dgid)
|
||||
} else {
|
||||
lchown(s.as_ptr(), duid, dgid)
|
||||
}
|
||||
};
|
||||
if ret == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::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();
|
||||
|
@ -408,7 +372,27 @@ impl Chowner {
|
|||
}
|
||||
|
||||
let ret = if self.matched(meta.uid(), meta.gid()) {
|
||||
self.wrap_chown(path, &meta, follow_arg)
|
||||
match wrap_chown(
|
||||
path,
|
||||
&meta,
|
||||
self.dest_uid,
|
||||
self.dest_gid,
|
||||
follow_arg,
|
||||
self.verbosity.clone(),
|
||||
) {
|
||||
Ok(n) => {
|
||||
if n != "" {
|
||||
show_info!("{}", n);
|
||||
}
|
||||
0
|
||||
}
|
||||
Err(e) => {
|
||||
if self.verbosity != Verbosity::Silent {
|
||||
show_info!("{}", e);
|
||||
}
|
||||
1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
@ -443,7 +427,27 @@ impl Chowner {
|
|||
continue;
|
||||
}
|
||||
|
||||
ret = self.wrap_chown(path, &meta, follow);
|
||||
ret = match wrap_chown(
|
||||
path,
|
||||
&meta,
|
||||
self.dest_uid,
|
||||
self.dest_gid,
|
||||
follow,
|
||||
self.verbosity.clone(),
|
||||
) {
|
||||
Ok(n) => {
|
||||
if n != "" {
|
||||
show_info!("{}", n);
|
||||
}
|
||||
0
|
||||
}
|
||||
Err(e) => {
|
||||
if self.verbosity != Verbosity::Silent {
|
||||
show_info!("{}", e);
|
||||
}
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
@ -471,58 +475,6 @@ impl Chowner {
|
|||
Some(meta)
|
||||
}
|
||||
|
||||
fn wrap_chown<P: AsRef<Path>>(&self, path: P, meta: &Metadata, follow: bool) -> i32 {
|
||||
use self::Verbosity::*;
|
||||
let mut ret = 0;
|
||||
let dest_uid = self.dest_uid.unwrap_or_else(|| meta.uid());
|
||||
let dest_gid = self.dest_gid.unwrap_or_else(|| 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(),
|
||||
entries::uid2usr(meta.uid()).unwrap(),
|
||||
entries::gid2grp(meta.gid()).unwrap(),
|
||||
entries::uid2usr(dest_uid).unwrap(),
|
||||
entries::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(),
|
||||
entries::uid2usr(meta.uid()).unwrap(),
|
||||
entries::gid2grp(meta.gid()).unwrap(),
|
||||
entries::uid2usr(dest_uid).unwrap(),
|
||||
entries::gid2grp(dest_gid).unwrap()
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
} else if self.verbosity == Verbose {
|
||||
println!(
|
||||
"ownership of {} retained as {}:{}",
|
||||
path.display(),
|
||||
entries::uid2usr(dest_uid).unwrap(),
|
||||
entries::gid2grp(dest_gid).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn matched(&self, uid: uid_t, gid: gid_t) -> bool {
|
||||
match self.filter {
|
||||
|
|
|
@ -20,7 +20,7 @@ path = "src/install.rs"
|
|||
[dependencies]
|
||||
clap = "2.33"
|
||||
libc = ">= 0.2"
|
||||
uucore = { version=">=0.0.4", package="uucore", path="../../uucore", features=["mode"] }
|
||||
uucore = { version=">=0.0.4", package="uucore", path="../../uucore", features=["mode", "perms"] }
|
||||
uucore_procs = { version=">=0.0.4", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -16,7 +16,11 @@ mod mode;
|
|||
extern crate uucore;
|
||||
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use uucore::entries::{grp2gid, usr2uid};
|
||||
use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity};
|
||||
|
||||
use std::fs;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::result::Result;
|
||||
|
||||
|
@ -27,6 +31,8 @@ pub struct Behavior {
|
|||
main_function: MainFunction,
|
||||
specified_mode: Option<u32>,
|
||||
suffix: String,
|
||||
owner: String,
|
||||
group: String,
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
|
@ -126,12 +132,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.help("(unimplemented) create all leading components of DEST except the last, then copy SOURCE to DEST")
|
||||
)
|
||||
.arg(
|
||||
// TODO implement flag
|
||||
Arg::with_name(OPT_GROUP)
|
||||
.short("g")
|
||||
.long(OPT_GROUP)
|
||||
.help("(unimplemented) set group ownership, instead of process's current group")
|
||||
.help("set group ownership, instead of process's current group")
|
||||
.value_name("GROUP")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_MODE)
|
||||
|
@ -139,14 +145,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.long(OPT_MODE)
|
||||
.help("set permission mode (as in chmod), instead of rwxr-xr-x")
|
||||
.value_name("MODE")
|
||||
.takes_value(true)
|
||||
.min_values(1)
|
||||
)
|
||||
.arg(
|
||||
// TODO implement flag
|
||||
Arg::with_name(OPT_OWNER)
|
||||
.short("o")
|
||||
.long(OPT_OWNER)
|
||||
.help("(unimplemented) set ownership (super-user only)")
|
||||
.help("set ownership (super-user only)")
|
||||
.value_name("OWNER")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(
|
||||
// TODO implement flag
|
||||
|
@ -176,6 +184,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.long(OPT_SUFFIX)
|
||||
.help("(unimplemented) override the usual backup suffix")
|
||||
.value_name("SUFFIX")
|
||||
.takes_value(true)
|
||||
.min_values(1)
|
||||
)
|
||||
.arg(
|
||||
// TODO implement flag
|
||||
|
@ -214,7 +224,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.help("(unimplemented) set security context of files and directories")
|
||||
.value_name("CONTEXT")
|
||||
)
|
||||
.arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true))
|
||||
.arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true).min_values(1))
|
||||
.get_matches_from(args);
|
||||
|
||||
let paths: Vec<String> = matches
|
||||
|
@ -258,10 +268,6 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
|
|||
Err("--compare, -C")
|
||||
} else if matches.is_present(OPT_CREATED) {
|
||||
Err("-D")
|
||||
} else if matches.is_present(OPT_GROUP) {
|
||||
Err("--group, -g")
|
||||
} else if matches.is_present(OPT_OWNER) {
|
||||
Err("--owner, -o")
|
||||
} else if matches.is_present(OPT_PRESERVE_TIMESTAMPS) {
|
||||
Err("--preserve-timestamps, -p")
|
||||
} else if matches.is_present(OPT_STRIP) {
|
||||
|
@ -292,7 +298,7 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
|
|||
/// In event of failure, returns an integer intended as a program return code.
|
||||
///
|
||||
fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
|
||||
let main_function = if matches.is_present("directory") {
|
||||
let main_function = if matches.is_present(OPT_DIRECTORY) {
|
||||
MainFunction::Directory
|
||||
} else {
|
||||
MainFunction::Standard
|
||||
|
@ -310,11 +316,6 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
|
|||
}
|
||||
},
|
||||
None => {
|
||||
show_error!(
|
||||
"option '--mode' requires an argument\n \
|
||||
Try '{} --help' for more information.",
|
||||
executable!()
|
||||
);
|
||||
return Err(1);
|
||||
}
|
||||
}
|
||||
|
@ -326,11 +327,6 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
|
|||
match matches.value_of(OPT_SUFFIX) {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
show_error!(
|
||||
"option '--suffix' requires an argument\n\
|
||||
Try '{} --help' for more information.",
|
||||
executable!()
|
||||
);
|
||||
return Err(1);
|
||||
}
|
||||
}
|
||||
|
@ -342,6 +338,8 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
|
|||
main_function,
|
||||
specified_mode,
|
||||
suffix: backup_suffix.to_string(),
|
||||
owner: matches.value_of(OPT_OWNER).unwrap_or("").to_string(),
|
||||
group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(),
|
||||
verbose: matches.is_present(OPT_VERBOSE),
|
||||
})
|
||||
}
|
||||
|
@ -400,10 +398,6 @@ fn is_new_file_path(path: &Path) -> bool {
|
|||
/// Returns an integer intended as a program return code.
|
||||
///
|
||||
fn standard(paths: Vec<String>, b: Behavior) -> i32 {
|
||||
if paths.len() < 2 {
|
||||
println!("{} requires at least 2 arguments.", executable!());
|
||||
1
|
||||
} else {
|
||||
let sources = &paths[0..paths.len() - 1]
|
||||
.iter()
|
||||
.map(PathBuf::from)
|
||||
|
@ -411,13 +405,11 @@ fn standard(paths: Vec<String>, b: Behavior) -> i32 {
|
|||
let target = Path::new(paths.last().unwrap());
|
||||
|
||||
if (target.is_file() || is_new_file_path(target)) && sources.len() == 1 {
|
||||
/* If the target already exist or directly creatable */
|
||||
copy_file_to_file(&sources[0], &target.to_path_buf(), &b)
|
||||
} else {
|
||||
copy_files_into_dir(sources, &target.to_path_buf(), &b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy some files into a directory.
|
||||
///
|
||||
|
@ -495,7 +487,7 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
|
|||
|
||||
if let Err(err) = io_result {
|
||||
show_error!(
|
||||
"install: cannot install ‘{}’ to ‘{}’: {}",
|
||||
"cannot install ‘{}’ to ‘{}’: {}",
|
||||
from.display(),
|
||||
to.display(),
|
||||
err
|
||||
|
@ -507,6 +499,54 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
|
|||
return Err(());
|
||||
}
|
||||
|
||||
if b.owner != "" {
|
||||
let meta = match fs::metadata(to) {
|
||||
Ok(meta) => meta,
|
||||
Err(f) => crash!(1, "{}", f.to_string()),
|
||||
};
|
||||
|
||||
let owner_id = match usr2uid(&b.owner) {
|
||||
Ok(g) => g,
|
||||
_ => crash!(1, "no such user: {}", b.owner),
|
||||
};
|
||||
let gid = meta.gid();
|
||||
match wrap_chown(
|
||||
to.as_path(),
|
||||
&meta,
|
||||
Some(owner_id),
|
||||
Some(gid),
|
||||
false,
|
||||
Verbosity::Normal,
|
||||
) {
|
||||
Ok(n) => {
|
||||
if n != "" {
|
||||
show_info!("{}", n);
|
||||
}
|
||||
}
|
||||
Err(e) => show_info!("{}", e),
|
||||
}
|
||||
}
|
||||
|
||||
if b.group != "" {
|
||||
let meta = match fs::metadata(to) {
|
||||
Ok(meta) => meta,
|
||||
Err(f) => crash!(1, "{}", f.to_string()),
|
||||
};
|
||||
|
||||
let group_id = match grp2gid(&b.group) {
|
||||
Ok(g) => g,
|
||||
_ => crash!(1, "no such group: {}", b.group),
|
||||
};
|
||||
match wrap_chgrp(to.as_path(), &meta, group_id, false, Verbosity::Normal) {
|
||||
Ok(n) => {
|
||||
if n != "" {
|
||||
show_info!("{}", n);
|
||||
}
|
||||
}
|
||||
Err(e) => show_info!("{}", e),
|
||||
}
|
||||
}
|
||||
|
||||
if b.verbose {
|
||||
show_info!("'{}' -> '{}'", from.display(), to.display());
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ entries = ["libc"]
|
|||
fs = ["libc"]
|
||||
mode = ["libc"]
|
||||
parse_time = []
|
||||
perms = ["libc"]
|
||||
process = ["libc"]
|
||||
signals = []
|
||||
utf8 = []
|
||||
|
|
|
@ -13,11 +13,15 @@ pub mod zero_copy;
|
|||
// ** non-windows
|
||||
#[cfg(all(not(windows), feature = "mode"))]
|
||||
pub mod mode;
|
||||
|
||||
// ** unix-only
|
||||
#[cfg(all(unix, feature = "entries"))]
|
||||
pub mod entries;
|
||||
#[cfg(all(unix, feature = "perms"))]
|
||||
pub mod perms;
|
||||
#[cfg(all(unix, feature = "process"))]
|
||||
pub mod process;
|
||||
|
||||
#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
|
||||
pub mod signals;
|
||||
#[cfg(all(
|
||||
|
|
182
src/uucore/src/lib/features/perms.rs
Normal file
182
src/uucore/src/lib/features/perms.rs
Normal file
|
@ -0,0 +1,182 @@
|
|||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
pub use crate::features::entries;
|
||||
use libc::{self, gid_t, lchown, uid_t};
|
||||
|
||||
use std::io::Error as IOError;
|
||||
use std::io::Result as IOResult;
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::fs::Metadata;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
/// The various level of verbosity
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum Verbosity {
|
||||
Silent,
|
||||
Changes,
|
||||
Verbose,
|
||||
Normal,
|
||||
}
|
||||
|
||||
/// Actually perform the change of group on a path
|
||||
fn chgrp<P: AsRef<Path>>(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())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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: gid_t,
|
||||
follow: bool,
|
||||
verbosity: Verbosity,
|
||||
) -> Result<String, String> {
|
||||
use self::Verbosity::*;
|
||||
let path = path.as_ref();
|
||||
let mut out: String = String::new();
|
||||
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Actually perform the change of owner on a path
|
||||
fn chown<P: AsRef<Path>>(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(), duid, dgid)
|
||||
} else {
|
||||
lchown(s.as_ptr(), duid, dgid)
|
||||
}
|
||||
};
|
||||
if ret == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(IOError::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform the change of owner on a path
|
||||
/// with the various options
|
||||
/// and error messages management
|
||||
pub fn wrap_chown<P: AsRef<Path>>(
|
||||
path: P,
|
||||
meta: &Metadata,
|
||||
dest_uid: Option<u32>,
|
||||
dest_gid: Option<u32>,
|
||||
follow: bool,
|
||||
verbosity: Verbosity,
|
||||
) -> Result<String, String> {
|
||||
use self::Verbosity::*;
|
||||
let dest_uid = dest_uid.unwrap_or_else(|| meta.uid());
|
||||
let dest_gid = dest_gid.unwrap_or_else(|| meta.gid());
|
||||
let path = path.as_ref();
|
||||
let mut out: String = String::new();
|
||||
|
||||
if let Err(e) = chown(path, dest_uid, dest_gid, follow) {
|
||||
match verbosity {
|
||||
Silent => (),
|
||||
_ => {
|
||||
out = format!("changing ownership of '{}': {}", path.display(), e);
|
||||
if verbosity == Verbose {
|
||||
out = format!(
|
||||
"{}\nfailed to change ownership of '{}' from {}:{} to {}:{}",
|
||||
out,
|
||||
path.display(),
|
||||
entries::uid2usr(meta.uid()).unwrap(),
|
||||
entries::gid2grp(meta.gid()).unwrap(),
|
||||
entries::uid2usr(dest_uid).unwrap(),
|
||||
entries::gid2grp(dest_gid).unwrap()
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
return Err(out);
|
||||
} else {
|
||||
let changed = dest_uid != meta.uid() || dest_gid != meta.gid();
|
||||
if changed {
|
||||
match verbosity {
|
||||
Changes | Verbose => {
|
||||
out = format!(
|
||||
"changed ownership of '{}' from {}:{} to {}:{}",
|
||||
path.display(),
|
||||
entries::uid2usr(meta.uid()).unwrap(),
|
||||
entries::gid2grp(meta.gid()).unwrap(),
|
||||
entries::uid2usr(dest_uid).unwrap(),
|
||||
entries::gid2grp(dest_gid).unwrap()
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
} else if verbosity == Verbose {
|
||||
out = format!(
|
||||
"ownership of '{}' retained as {}:{}",
|
||||
path.display(),
|
||||
entries::uid2usr(dest_uid).unwrap(),
|
||||
entries::gid2grp(dest_gid).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
|
@ -53,6 +53,8 @@ pub use crate::features::mode;
|
|||
// ** unix-only
|
||||
#[cfg(all(unix, feature = "entries"))]
|
||||
pub use crate::features::entries;
|
||||
#[cfg(all(unix, feature = "perms"))]
|
||||
pub use crate::features::perms;
|
||||
#[cfg(all(unix, feature = "process"))]
|
||||
pub use crate::features::process;
|
||||
#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
|
||||
|
|
|
@ -110,8 +110,7 @@ fn test_reference() {
|
|||
.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");
|
||||
.stderr_is("chgrp: changing group of '/etc': Operation not permitted (os error 1)\nfailed to change group of '/etc' from root to root");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use crate::common::util::*;
|
||||
#[cfg(target_os = "linux")]
|
||||
use rust_users::get_effective_uid;
|
||||
|
||||
extern crate chown;
|
||||
// pub use self::uu_chown::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_passgrp {
|
||||
|
@ -345,7 +346,9 @@ fn test_chown_recursive() {
|
|||
// As seems to be a configuration issue, ignoring it
|
||||
return;
|
||||
}
|
||||
assert!(result.stdout.contains("ownership of a/a retained as"));
|
||||
|
||||
assert!(result.stderr.contains("ownership of 'a/a' retained as"));
|
||||
assert!(result.stderr.contains("ownership of 'z/y' retained as"));
|
||||
assert!(result.success);
|
||||
}
|
||||
|
||||
|
@ -379,3 +382,17 @@ fn test_root_preserve() {
|
|||
.stderr
|
||||
.contains("chown: it is dangerous to operate recursively"));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_big_p() {
|
||||
if get_effective_uid() != 0 {
|
||||
new_ucmd!()
|
||||
.arg("-RP")
|
||||
.arg("bin")
|
||||
.arg("/proc/self/cwd")
|
||||
.fails()
|
||||
.stderr_is(
|
||||
"chown: changing ownership of '/proc/self/cwd': Operation not permitted (os error 1)\n",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::common::util::*;
|
||||
use rust_users::*;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
#[test]
|
||||
|
@ -206,6 +207,66 @@ fn test_install_target_new_file() {
|
|||
assert!(at.file_exists(&format!("{}/{}", dir, file)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_target_new_file_with_group() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "test_install_target_new_filer_file_j";
|
||||
let dir = "test_install_target_new_file_dir_j";
|
||||
let gid = get_effective_gid();
|
||||
|
||||
at.touch(file);
|
||||
at.mkdir(dir);
|
||||
let result = ucmd
|
||||
.arg(file)
|
||||
.arg("--group")
|
||||
.arg(gid.to_string())
|
||||
.arg(format!("{}/{}", dir, file))
|
||||
.run();
|
||||
|
||||
println!("stderr = {:?}", result.stderr);
|
||||
println!("stdout = {:?}", result.stdout);
|
||||
|
||||
if is_ci() && result.stderr.contains("error: no such group:") {
|
||||
// In the CI, some server are failing to return the group.
|
||||
// As seems to be a configuration issue, ignoring it
|
||||
return;
|
||||
}
|
||||
|
||||
assert!(result.success);
|
||||
assert!(at.file_exists(file));
|
||||
assert!(at.file_exists(&format!("{}/{}", dir, file)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_target_new_file_with_owner() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "test_install_target_new_filer_file_j";
|
||||
let dir = "test_install_target_new_file_dir_j";
|
||||
let uid = get_effective_uid();
|
||||
|
||||
at.touch(file);
|
||||
at.mkdir(dir);
|
||||
let result = ucmd
|
||||
.arg(file)
|
||||
.arg("--owner")
|
||||
.arg(uid.to_string())
|
||||
.arg(format!("{}/{}", dir, file))
|
||||
.run();
|
||||
|
||||
println!("stderr = {:?}", result.stderr);
|
||||
println!("stdout = {:?}", result.stdout);
|
||||
|
||||
if is_ci() && result.stderr.contains("error: no such user:") {
|
||||
// In the CI, some server are failing to return the user id.
|
||||
// As seems to be a configuration issue, ignoring it
|
||||
return;
|
||||
}
|
||||
|
||||
assert!(result.success);
|
||||
assert!(at.file_exists(file));
|
||||
assert!(at.file_exists(&format!("{}/{}", dir, file)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_target_new_file_failing_nonexistent_parent() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue