1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 12:07:46 +00:00

chroot: improve parsing of --groups parameter

Improve the parsing of the `--groups` parameter to `chroot` so that it
handles more error cases and better matches the behavior of GNU
`chroot`. For example, multiple adjacent commas are allowed:

    --groups=a,,,b

but spaces between commas are not allowed:

    --groups="a, , ,b"
This commit is contained in:
Jeffrey Finkelstein 2025-01-02 19:06:19 -05:00
parent 6a8b766d6d
commit 52c05e2fd6
2 changed files with 71 additions and 2 deletions

View file

@ -16,7 +16,7 @@ use std::process;
use uucore::error::{set_exit_code, UClapError, UResult, UUsageError};
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
use uucore::libc::{self, chroot, setgid, setgroups, setuid};
use uucore::{entries, format_usage, help_about, help_usage};
use uucore::{entries, format_usage, help_about, help_usage, show};
static ABOUT: &str = help_about!("chroot.md");
static USAGE: &str = help_usage!("chroot.md");
@ -71,6 +71,60 @@ fn parse_userspec(spec: &str) -> UResult<UserSpec> {
}
}
// Pre-condition: `list_str` is non-empty.
fn parse_group_list(list_str: &str) -> Result<Vec<String>, ChrootError> {
let split: Vec<&str> = list_str.split(",").collect();
if split.len() == 1 {
let name = split[0].trim();
if name.is_empty() {
// --groups=" "
// chroot: invalid group
Err(ChrootError::InvalidGroup(name.to_string()))
} else {
// --groups="blah"
Ok(vec![name.to_string()])
}
} else if split.iter().all(|s| s.is_empty()) {
// --groups=","
// chroot: invalid group list ,
Err(ChrootError::InvalidGroupList(list_str.to_string()))
} else {
let mut result = vec![];
let mut err = false;
for name in split {
let trimmed_name = name.trim();
if trimmed_name.is_empty() {
if name.is_empty() {
// --groups=","
continue;
} else {
// --groups=", "
// chroot: invalid group
show!(ChrootError::InvalidGroup(name.to_string()));
err = true;
}
} else {
// TODO Figure out a better condition here.
if trimmed_name.starts_with(char::is_numeric)
&& trimmed_name.ends_with(|c: char| !c.is_numeric())
{
// --groups="0trail"
// chroot: invalid group 0trail
show!(ChrootError::InvalidGroup(name.to_string()));
err = true;
} else {
result.push(trimmed_name.to_string());
}
}
}
if err {
Err(ChrootError::GroupsParsingFailed)
} else {
Ok(result)
}
}
}
impl Options {
/// Parse parameters from the command-line arguments.
fn from(matches: &clap::ArgMatches) -> UResult<Self> {
@ -80,7 +134,13 @@ impl Options {
};
let groups = match matches.get_one::<String>(options::GROUPS) {
None => vec![],
Some(s) => s.split(",").map(str::to_string).collect(),
Some(s) => {
if s.is_empty() {
vec![]
} else {
parse_group_list(s)?
}
}
};
let skip_chdir = matches.get_flag(options::SKIP_CHDIR);
let userspec = match matches.get_one::<String>(options::USERSPEC) {

View file

@ -21,6 +21,12 @@ pub enum ChrootError {
/// Failed to find the specified command.
CommandNotFound(String, Error),
GroupsParsingFailed,
InvalidGroup(String),
InvalidGroupList(String),
/// The given user and group specification was invalid.
InvalidUserspec(String),
@ -68,6 +74,9 @@ impl Display for ChrootError {
Self::CommandFailed(s, e) | Self::CommandNotFound(s, e) => {
write!(f, "failed to run command {}: {}", s.to_string().quote(), e,)
}
Self::GroupsParsingFailed => write!(f, "--groups parsing failed"),
Self::InvalidGroup(s) => write!(f, "invalid group: {}", s.quote()),
Self::InvalidGroupList(s) => write!(f, "invalid group list: {}", s.quote()),
Self::InvalidUserspec(s) => write!(f, "invalid userspec: {}", s.quote(),),
Self::MissingNewRoot => write!(
f,