1
Fork 0
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:
Sylvestre Ledru 2020-12-12 16:42:50 +01:00 committed by GitHub
commit 94571ecf8f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 428 additions and 215 deletions

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/chgrp.rs" path = "src/chgrp.rs"
[dependencies] [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" } uucore_procs = { version=">=0.0.4", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2" walkdir = "2.2"

View file

@ -11,23 +11,18 @@
extern crate uucore; extern crate uucore;
pub use uucore::entries; pub use uucore::entries;
use uucore::fs::resolve_relative_path; 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; extern crate walkdir;
use walkdir::WalkDir; use walkdir::WalkDir;
use std::io::Error as IOError;
use std::io::Result as IOResult;
use std::fs; use std::fs;
use std::fs::Metadata; use std::fs::Metadata;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::path::Path; use std::path::Path;
use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
static SYNTAX: &str = static SYNTAX: &str =
"chgrp [OPTION]... GROUP FILE...\n or : chgrp [OPTION]... --reference=RFILE FILE..."; "chgrp [OPTION]... GROUP FILE...\n or : chgrp [OPTION]... --reference=RFILE FILE...";
static SUMMARY: &str = "Change the group of each FILE to GROUP."; 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() executor.exec()
} }
#[derive(PartialEq, Debug)]
enum Verbosity {
Silent,
Changes,
Verbose,
Normal,
}
struct Chgrper { struct Chgrper {
dest_gid: gid_t, dest_gid: gid_t,
bit_flag: u8, bit_flag: u8,
@ -201,23 +188,6 @@ impl Chgrper {
ret 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)] #[cfg(windows)]
fn is_bind_root<P: AsRef<Path>>(&self, root: P) -> bool { fn is_bind_root<P: AsRef<Path>>(&self, root: P) -> bool {
// TODO: is there an equivalent on Windows? // 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 { if !self.recursive {
ret 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 ret
} }
@ -324,50 +325,4 @@ impl Chgrper {
}; };
Some(meta) 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
}
} }

View file

@ -17,7 +17,7 @@ path = "src/chown.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
glob = "0.3.0" 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" } uucore_procs = { version=">=0.0.4", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2" walkdir = "2.2"

View file

@ -11,7 +11,8 @@
extern crate uucore; extern crate uucore;
pub use uucore::entries::{self, Group, Locate, Passwd}; pub use uucore::entries::{self, Group, Locate, Passwd};
use uucore::fs::resolve_relative_path; 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; extern crate clap;
use clap::{App, Arg}; use clap::{App, Arg};
@ -22,15 +23,9 @@ use walkdir::WalkDir;
use std::fs::{self, Metadata}; use std::fs::{self, Metadata};
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::io;
use std::io::Result as IOResult;
use std::convert::AsRef; use std::convert::AsRef;
use std::path::Path; use std::path::Path;
use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
static ABOUT: &str = "change file owner and group"; static ABOUT: &str = "change file owner and group";
static VERSION: &str = env!("CARGO_PKG_VERSION"); 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 { enum IfFrom {
All, All,
User(u32), User(u32),
@ -349,29 +336,6 @@ impl Chowner {
ret 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 { fn traverse<P: AsRef<Path>>(&self, root: P) -> i32 {
let follow_arg = self.dereference || self.bit_flag != FTS_PHYSICAL; let follow_arg = self.dereference || self.bit_flag != FTS_PHYSICAL;
let path = root.as_ref(); let path = root.as_ref();
@ -408,7 +372,27 @@ impl Chowner {
} }
let ret = if self.matched(meta.uid(), meta.gid()) { 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 { } else {
0 0
}; };
@ -443,7 +427,27 @@ impl Chowner {
continue; 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 ret
} }
@ -471,58 +475,6 @@ impl Chowner {
Some(meta) 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] #[inline]
fn matched(&self, uid: uid_t, gid: gid_t) -> bool { fn matched(&self, uid: uid_t, gid: gid_t) -> bool {
match self.filter { match self.filter {

View file

@ -20,7 +20,7 @@ path = "src/install.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
libc = ">= 0.2" 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" } uucore_procs = { version=">=0.0.4", package="uucore_procs", path="../../uucore_procs" }
[dev-dependencies] [dev-dependencies]

View file

@ -16,7 +16,11 @@ mod mode;
extern crate uucore; extern crate uucore;
use clap::{App, Arg, ArgMatches}; use clap::{App, Arg, ArgMatches};
use uucore::entries::{grp2gid, usr2uid};
use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity};
use std::fs; use std::fs;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::result::Result; use std::result::Result;
@ -27,6 +31,8 @@ pub struct Behavior {
main_function: MainFunction, main_function: MainFunction,
specified_mode: Option<u32>, specified_mode: Option<u32>,
suffix: String, suffix: String,
owner: String,
group: String,
verbose: bool, 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") .help("(unimplemented) create all leading components of DEST except the last, then copy SOURCE to DEST")
) )
.arg( .arg(
// TODO implement flag
Arg::with_name(OPT_GROUP) Arg::with_name(OPT_GROUP)
.short("g") .short("g")
.long(OPT_GROUP) .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") .value_name("GROUP")
.takes_value(true)
) )
.arg( .arg(
Arg::with_name(OPT_MODE) Arg::with_name(OPT_MODE)
@ -139,14 +145,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.long(OPT_MODE) .long(OPT_MODE)
.help("set permission mode (as in chmod), instead of rwxr-xr-x") .help("set permission mode (as in chmod), instead of rwxr-xr-x")
.value_name("MODE") .value_name("MODE")
.takes_value(true)
.min_values(1)
) )
.arg( .arg(
// TODO implement flag
Arg::with_name(OPT_OWNER) Arg::with_name(OPT_OWNER)
.short("o") .short("o")
.long(OPT_OWNER) .long(OPT_OWNER)
.help("(unimplemented) set ownership (super-user only)") .help("set ownership (super-user only)")
.value_name("OWNER") .value_name("OWNER")
.takes_value(true)
) )
.arg( .arg(
// TODO implement flag // TODO implement flag
@ -176,6 +184,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.long(OPT_SUFFIX) .long(OPT_SUFFIX)
.help("(unimplemented) override the usual backup suffix") .help("(unimplemented) override the usual backup suffix")
.value_name("SUFFIX") .value_name("SUFFIX")
.takes_value(true)
.min_values(1)
) )
.arg( .arg(
// TODO implement flag // TODO implement flag
@ -214,7 +224,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.help("(unimplemented) set security context of files and directories") .help("(unimplemented) set security context of files and directories")
.value_name("CONTEXT") .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); .get_matches_from(args);
let paths: Vec<String> = matches let paths: Vec<String> = matches
@ -258,10 +268,6 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
Err("--compare, -C") Err("--compare, -C")
} else if matches.is_present(OPT_CREATED) { } else if matches.is_present(OPT_CREATED) {
Err("-D") 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) { } else if matches.is_present(OPT_PRESERVE_TIMESTAMPS) {
Err("--preserve-timestamps, -p") Err("--preserve-timestamps, -p")
} else if matches.is_present(OPT_STRIP) { } 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. /// In event of failure, returns an integer intended as a program return code.
/// ///
fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> { 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 MainFunction::Directory
} else { } else {
MainFunction::Standard MainFunction::Standard
@ -310,11 +316,6 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
} }
}, },
None => { None => {
show_error!(
"option '--mode' requires an argument\n \
Try '{} --help' for more information.",
executable!()
);
return Err(1); return Err(1);
} }
} }
@ -326,11 +327,6 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
match matches.value_of(OPT_SUFFIX) { match matches.value_of(OPT_SUFFIX) {
Some(x) => x, Some(x) => x,
None => { None => {
show_error!(
"option '--suffix' requires an argument\n\
Try '{} --help' for more information.",
executable!()
);
return Err(1); return Err(1);
} }
} }
@ -342,6 +338,8 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
main_function, main_function,
specified_mode, specified_mode,
suffix: backup_suffix.to_string(), 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), verbose: matches.is_present(OPT_VERBOSE),
}) })
} }
@ -400,22 +398,16 @@ fn is_new_file_path(path: &Path) -> bool {
/// Returns an integer intended as a program return code. /// Returns an integer intended as a program return code.
/// ///
fn standard(paths: Vec<String>, b: Behavior) -> i32 { fn standard(paths: Vec<String>, b: Behavior) -> i32 {
if paths.len() < 2 { let sources = &paths[0..paths.len() - 1]
println!("{} requires at least 2 arguments.", executable!()); .iter()
1 .map(PathBuf::from)
} else { .collect::<Vec<_>>();
let sources = &paths[0..paths.len() - 1] let target = Path::new(paths.last().unwrap());
.iter()
.map(PathBuf::from)
.collect::<Vec<_>>();
let target = Path::new(paths.last().unwrap());
if (target.is_file() || is_new_file_path(target)) && sources.len() == 1 { 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)
copy_file_to_file(&sources[0], &target.to_path_buf(), &b) } else {
} else { copy_files_into_dir(sources, &target.to_path_buf(), &b)
copy_files_into_dir(sources, &target.to_path_buf(), &b)
}
} }
} }
@ -495,7 +487,7 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
if let Err(err) = io_result { if let Err(err) = io_result {
show_error!( show_error!(
"install: cannot install {} to {}: {}", "cannot install {} to {}: {}",
from.display(), from.display(),
to.display(), to.display(),
err err
@ -507,6 +499,54 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
return Err(()); 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 { if b.verbose {
show_info!("'{}' -> '{}'", from.display(), to.display()); show_info!("'{}' -> '{}'", from.display(), to.display());
} }

View file

@ -43,6 +43,7 @@ entries = ["libc"]
fs = ["libc"] fs = ["libc"]
mode = ["libc"] mode = ["libc"]
parse_time = [] parse_time = []
perms = ["libc"]
process = ["libc"] process = ["libc"]
signals = [] signals = []
utf8 = [] utf8 = []

View file

@ -13,11 +13,15 @@ pub mod zero_copy;
// ** non-windows // ** non-windows
#[cfg(all(not(windows), feature = "mode"))] #[cfg(all(not(windows), feature = "mode"))]
pub mod mode; pub mod mode;
// ** unix-only // ** unix-only
#[cfg(all(unix, feature = "entries"))] #[cfg(all(unix, feature = "entries"))]
pub mod entries; pub mod entries;
#[cfg(all(unix, feature = "perms"))]
pub mod perms;
#[cfg(all(unix, feature = "process"))] #[cfg(all(unix, feature = "process"))]
pub mod process; pub mod process;
#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))] #[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
pub mod signals; pub mod signals;
#[cfg(all( #[cfg(all(

View 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)
}

View file

@ -53,6 +53,8 @@ pub use crate::features::mode;
// ** unix-only // ** unix-only
#[cfg(all(unix, feature = "entries"))] #[cfg(all(unix, feature = "entries"))]
pub use crate::features::entries; pub use crate::features::entries;
#[cfg(all(unix, feature = "perms"))]
pub use crate::features::perms;
#[cfg(all(unix, feature = "process"))] #[cfg(all(unix, feature = "process"))]
pub use crate::features::process; pub use crate::features::process;
#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))] #[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]

View file

@ -110,8 +110,7 @@ fn test_reference() {
.arg("--reference=/etc/passwd") .arg("--reference=/etc/passwd")
.arg("/etc") .arg("/etc")
.fails() .fails()
.stderr_is("chgrp: changing group of '/etc': Operation not permitted (os error 1)\n") .stderr_is("chgrp: changing group of '/etc': Operation not permitted (os error 1)\nfailed to change group of '/etc' from root to root");
.stdout_is("failed to change group of /etc from root to root\n");
} }
} }

View file

@ -1,7 +1,8 @@
use crate::common::util::*; use crate::common::util::*;
#[cfg(target_os = "linux")]
use rust_users::get_effective_uid;
extern crate chown; extern crate chown;
// pub use self::uu_chown::*;
#[cfg(test)] #[cfg(test)]
mod test_passgrp { mod test_passgrp {
@ -345,7 +346,9 @@ fn test_chown_recursive() {
// As seems to be a configuration issue, ignoring it // As seems to be a configuration issue, ignoring it
return; 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); assert!(result.success);
} }
@ -379,3 +382,17 @@ fn test_root_preserve() {
.stderr .stderr
.contains("chown: it is dangerous to operate recursively")); .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",
);
}
}

View file

@ -1,4 +1,5 @@
use crate::common::util::*; use crate::common::util::*;
use rust_users::*;
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
#[test] #[test]
@ -206,6 +207,66 @@ fn test_install_target_new_file() {
assert!(at.file_exists(&format!("{}/{}", dir, 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] #[test]
fn test_install_target_new_file_failing_nonexistent_parent() { fn test_install_target_new_file_failing_nonexistent_parent() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();