Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 38 additions & 110 deletions tree/chgrp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,17 @@

mod common;

use self::common::error_string;
use self::common::{chown_traverse, error_string, ChangeOwnershipArgs};
use clap::Parser;
use gettextrs::{bind_textdomain_codeset, gettext, setlocale, textdomain, LocaleCategory};
use std::{cell::RefCell, ffi::CString, io, os::unix::fs::MetadataExt};
use std::{ffi::CString, io};

/// chgrp - change file group ownership
#[derive(Parser)]
#[command(version, about, disable_help_flag = true)]
struct Args {
#[arg(long, action = clap::ArgAction::HelpLong)] // Bec. help clashes with -h
help: Option<bool>,

/// Change symbolic links, rather than the files they point to
#[arg(short = 'h', long, default_value_t = false)]
no_derereference: bool,

/// Follow command line symlinks during -R recursion
#[arg(short = 'H', overrides_with_all = ["follow_cli", "follow_symlinks", "follow_none"])]
follow_cli: bool,

/// Follow symlinks during -R recursion
#[arg(short = 'L', overrides_with_all = ["follow_cli", "follow_symlinks", "follow_none"])]
follow_symlinks: bool,

/// Never follow symlinks during -R recursion
#[arg(short = 'P', overrides_with_all = ["follow_cli", "follow_symlinks", "follow_none"], default_value_t = true)]
follow_none: bool,

/// Recursively change groups of directories and their contents
#[arg(short, short_alias = 'R', long)]
recurse: bool,
#[command(flatten)]
delegate: ChangeOwnershipArgs,

/// A group name from the group database or a numeric group ID
group: String,
Expand All @@ -48,89 +28,6 @@ struct Args {
files: Vec<String>,
}

fn chgrp_file(filename: &str, gid: Option<u32>, args: &Args) -> bool {
let recurse = args.recurse;
let no_derereference = args.no_derereference;

let terminate = RefCell::new(false);

ftw::traverse_directory(
filename,
|entry| {
if *terminate.borrow() {
return Ok(false);
}

let md = entry.metadata().unwrap();

// According to the spec:
// "The user ID of the file shall be used as the owner argument."
let uid = md.uid();

// Don't change the group ID if the group argument is empty
let gid = gid.unwrap_or(libc::gid_t::MAX);

let ret = unsafe {
libc::fchownat(
entry.dir_fd(),
entry.file_name().as_ptr(),
uid,
gid,
// Default is to change the file that the symbolic link points to unless the
// -h flag is specified.
if no_derereference {
libc::AT_SYMLINK_NOFOLLOW
} else {
0
},
)
};
if ret != 0 {
let e = io::Error::last_os_error();
let err_str = match e.kind() {
io::ErrorKind::PermissionDenied => {
gettext!("cannot access '{}': {}", entry.path(), error_string(&e))
}
_ => {
gettext!("changing group of '{}': {}", entry.path(), error_string(&e))
}
};
eprintln!("chgrp: {}", err_str);
*terminate.borrow_mut() = true;
return Err(());
}

Ok(recurse)
},
|_| Ok(()), // Do nothing on `postprocess_dir`
|entry, error| {
let e = error.inner();
let err_str = match e.kind() {
io::ErrorKind::PermissionDenied => {
gettext!(
"cannot read directory '{}': {}",
entry.path(),
error_string(&e)
)
}
_ => {
gettext!("changing group of '{}': {}", entry.path(), error_string(&e))
}
};
eprintln!("chgrp: {}", err_str);
*terminate.borrow_mut() = true;
},
ftw::TraverseDirectoryOpts {
follow_symlinks_on_args: args.follow_cli,
follow_symlinks: args.follow_symlinks,
..Default::default()
},
);

let failed = *terminate.borrow();
!failed
}

// lookup string group by name, or parse numeric group ID
fn parse_group(group: &str) -> Result<Option<u32>, String> {
// empty strings are accepted without errors
Expand All @@ -155,13 +52,37 @@ fn parse_group(group: &str) -> Result<Option<u32>, String> {
}
}

fn err_handler(e: io::Error, path: ftw::DisplayablePath) {
let err_str = match e.kind() {
io::ErrorKind::PermissionDenied => {
gettext!("cannot read directory '{}': {}", path, error_string(&e))
}
_ => {
gettext!("changing group of '{}': {}", path, error_string(&e))
}
};
eprintln!("chgrp: {}", err_str);
}

fn chown_err_handler(e: io::Error, path: ftw::DisplayablePath) {
let err_str = match e.kind() {
io::ErrorKind::PermissionDenied => {
gettext!("cannot access '{}': {}", path, error_string(&e))
}
_ => {
gettext!("changing group of '{}': {}", path, error_string(&e))
}
};
eprintln!("chgrp: {}", err_str);
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
// parse command line arguments
let mut args = Args::parse();

// Enable `no_derereference` if `-R` is enabled without either `-H` or `-L`
if args.recurse && !(args.follow_cli || args.follow_symlinks) {
args.no_derereference = true;
if args.delegate.recurse && !(args.delegate.follow_cli || args.delegate.follow_symlinks) {
args.delegate.no_dereference = true;
}

// initialize translations
Expand All @@ -182,7 +103,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

// apply the group to each file
for filename in &args.files {
let success = chgrp_file(filename, gid, &args);
let success = chown_traverse(
filename,
None,
gid,
&args.delegate,
err_handler,
chown_err_handler,
);
if !success {
exit_code = 1;
}
Expand Down
Loading