1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

chroot: move to clap from getopts (#1792)

+ add tests
This commit is contained in:
Yagiz Degirmenci 2021-03-21 18:18:47 +03:00 committed by GitHub
parent 25d4a08387
commit f60790dd41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 172 additions and 51 deletions

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/chroot.rs" path = "src/chroot.rs"
[dependencies] [dependencies]
getopts = "0.2.18" clap= "2.33"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] } uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -10,60 +10,81 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use uucore::entries; use clap::{App, Arg};
use uucore::libc::{self, chroot, setgid, setgroups, setuid};
use std::ffi::CString; use std::ffi::CString;
use std::io::Error; use std::io::Error;
use std::path::Path; use std::path::Path;
use std::process::Command; use std::process::Command;
use uucore::entries;
use uucore::libc::{self, chroot, setgid, setgroups, setuid};
static VERSION: &str = env!("CARGO_PKG_VERSION");
static NAME: &str = "chroot"; static NAME: &str = "chroot";
static ABOUT: &str = "Run COMMAND with root directory set to NEWROOT.";
static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]"; static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]";
static SUMMARY: &str = "Run COMMAND with root directory set to NEWROOT.";
static LONG_HELP: &str = " mod options {
If COMMAND is not specified, it defaults to '$(SHELL) -i'. pub const NEWROOT: &str = "newroot";
If $(SHELL) is not set, /bin/sh is used. pub const USER: &str = "user";
"; pub const GROUP: &str = "group";
pub const GROUPS: &str = "groups";
pub const USERSPEC: &str = "userspec";
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args.collect_str();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP) let matches = App::new(executable!())
.optopt( .version(VERSION)
"u", .about(ABOUT)
"user", .usage(SYNTAX)
"User (ID or name) to switch before running the program", .arg(Arg::with_name(options::NEWROOT).hidden(true).required(true))
"USER", .arg(
Arg::with_name(options::USER)
.short("u")
.long(options::USER)
.help("User (ID or name) to switch before running the program")
.value_name("USER"),
) )
.optopt("g", "group", "Group (ID or name) to switch to", "GROUP") .arg(
.optopt( Arg::with_name(options::GROUP)
"G", .short("g")
"groups", .long(options::GROUP)
"Comma-separated list of groups to switch to", .help("Group (ID or name) to switch to")
"GROUP1,GROUP2...", .value_name("GROUP"),
) )
.optopt( .arg(
"", Arg::with_name(options::GROUPS)
"userspec", .short("G")
.long(options::GROUPS)
.help("Comma-separated list of groups to switch to")
.value_name("GROUP1,GROUP2..."),
)
.arg(
Arg::with_name(options::USERSPEC)
.long(options::USERSPEC)
.help(
"Colon-separated user and group to switch to. \ "Colon-separated user and group to switch to. \
Same as -u USER -g GROUP. \ Same as -u USER -g GROUP. \
Userspec has higher preference than -u and/or -g", Userspec has higher preference than -u and/or -g",
"USER:GROUP",
) )
.parse(args); .value_name("USER:GROUP"),
)
if matches.free.is_empty() { .get_matches_from(args);
println!("Missing operand: NEWROOT");
println!("Try `{} --help` for more information.", NAME);
return 1;
}
let default_shell: &'static str = "/bin/sh"; let default_shell: &'static str = "/bin/sh";
let default_option: &'static str = "-i"; let default_option: &'static str = "-i";
let user_shell = std::env::var("SHELL"); let user_shell = std::env::var("SHELL");
let newroot = Path::new(&matches.free[0][..]); let newroot: &Path = match matches.value_of(options::NEWROOT) {
Some(v) => Path::new(v),
None => crash!(
1,
"Missing operand: NEWROOT\nTry '{} --help' for more information.",
NAME
),
};
if !newroot.is_dir() { if !newroot.is_dir() {
crash!( crash!(
1, 1,
@ -72,7 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
); );
} }
let command: Vec<&str> = match matches.free.len() { let command: Vec<&str> = match matches.args.len() {
1 => { 1 => {
let shell: &str = match user_shell { let shell: &str = match user_shell {
Err(_) => default_shell, Err(_) => default_shell,
@ -80,7 +101,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}; };
vec![shell, default_option] vec![shell, default_option]
} }
_ => matches.free[1..].iter().map(|x| &x[..]).collect(), _ => {
let mut vector: Vec<&str> = Vec::new();
for (&k, v) in matches.args.iter() {
vector.push(k.clone());
vector.push(&v.vals[0].to_str().unwrap());
}
vector
}
}; };
set_context(&newroot, &matches); set_context(&newroot, &matches);
@ -97,30 +125,26 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
} }
fn set_context(root: &Path, options: &getopts::Matches) { fn set_context(root: &Path, options: &clap::ArgMatches) {
let userspec_str = options.opt_str("userspec"); let userspec_str = options.value_of(options::USERSPEC);
let user_str = options.opt_str("user").unwrap_or_default(); let user_str = options.value_of(options::USER).unwrap_or_default();
let group_str = options.opt_str("group").unwrap_or_default(); let group_str = options.value_of(options::GROUP).unwrap_or_default();
let groups_str = options.opt_str("groups").unwrap_or_default(); let groups_str = options.value_of(options::GROUPS).unwrap_or_default();
let userspec = match userspec_str { let userspec = match userspec_str {
Some(ref u) => { Some(ref u) => {
let s: Vec<&str> = u.split(':').collect(); let s: Vec<&str> = u.split(':').collect();
if s.len() != 2 { if s.len() != 2 || s.iter().any(|&spec| spec == "") {
crash!(1, "invalid userspec: `{}`", u) crash!(1, "invalid userspec: `{}`", u)
}; };
s s
} }
None => Vec::new(), None => Vec::new(),
}; };
let user = if userspec.is_empty() {
&user_str[..] let (user, group) = if userspec.is_empty() {
(&user_str[..], &group_str[..])
} else { } else {
&userspec[0][..] (&userspec[0][..], &userspec[1][..])
};
let group = if userspec.is_empty() {
&group_str[..]
} else {
&userspec[1][..]
}; };
enter_chroot(root); enter_chroot(root);

View file

@ -1 +1,98 @@
// ToDO: add tests use crate::common::util::*;
#[test]
fn test_missing_operand() {
let result = new_ucmd!().run();
assert_eq!(
true,
result
.stderr
.starts_with("error: The following required arguments were not provided")
);
assert_eq!(true, result.stderr.contains("<newroot>"));
}
#[test]
fn test_enter_chroot_fails() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("jail");
let result = ucmd.arg("jail").run();
assert_eq!(
true,
result.stderr.starts_with(
"chroot: error: cannot chroot to jail: Operation not permitted (os error 1)"
)
)
}
#[test]
fn test_no_such_directory() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch(&at.plus_as_string("a"));
ucmd.arg("a")
.fails()
.stderr_is("chroot: error: cannot change root directory to `a`: no such directory");
}
#[test]
fn test_invalid_user_spec() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("a");
let result = ucmd.arg("a").arg("--userspec=ARABA:").run();
assert_eq!(
true,
result.stderr.starts_with("chroot: error: invalid userspec")
);
}
#[test]
fn test_preference_of_userspec() {
let scene = TestScenario::new(util_name!());
let result = scene.cmd("whoami").run();
if is_ci() && result.stderr.contains("No such user/group") {
// In the CI, some server are failing to return whoami.
// As seems to be a configuration issue, ignoring it
return;
}
println!("result.stdout {}", result.stdout);
println!("result.stderr = {}", result.stderr);
let username = result.stdout.trim_end();
let ts = TestScenario::new("id");
let result = ts.cmd("id").arg("-g").arg("-n").run();
println!("result.stdout {}", result.stdout);
println!("result.stderr = {}", result.stderr);
if is_ci() && result.stderr.contains("cannot find name for user ID") {
// In the CI, some server are failing to return id.
// As seems to be a configuration issue, ignoring it
return;
}
let group_name = result.stdout.trim_end();
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("a");
let result = ucmd
.arg("a")
.arg("--user")
.arg("fake")
.arg("-G")
.arg("ABC,DEF")
.arg(format!("--userspec={}:{}", username, group_name))
.run();
println!("result.stdout {}", result.stdout);
println!("result.stderr = {}", result.stderr);
}