mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
parent
25d4a08387
commit
f60790dd41
3 changed files with 172 additions and 51 deletions
|
@ -15,7 +15,7 @@ edition = "2018"
|
|||
path = "src/chroot.rs"
|
||||
|
||||
[dependencies]
|
||||
getopts = "0.2.18"
|
||||
clap= "2.33"
|
||||
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
|
|
|
@ -10,60 +10,81 @@
|
|||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
use uucore::entries;
|
||||
use uucore::libc::{self, chroot, setgid, setgroups, setuid};
|
||||
|
||||
use clap::{App, Arg};
|
||||
use std::ffi::CString;
|
||||
use std::io::Error;
|
||||
use std::path::Path;
|
||||
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 ABOUT: &str = "Run COMMAND with root directory set to NEWROOT.";
|
||||
static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]";
|
||||
static SUMMARY: &str = "Run COMMAND with root directory set to NEWROOT.";
|
||||
static LONG_HELP: &str = "
|
||||
If COMMAND is not specified, it defaults to '$(SHELL) -i'.
|
||||
If $(SHELL) is not set, /bin/sh is used.
|
||||
";
|
||||
|
||||
mod options {
|
||||
pub const NEWROOT: &str = "newroot";
|
||||
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 {
|
||||
let args = args.collect_str();
|
||||
|
||||
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
|
||||
.optopt(
|
||||
"u",
|
||||
"user",
|
||||
"User (ID or name) to switch before running the program",
|
||||
"USER",
|
||||
let matches = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.usage(SYNTAX)
|
||||
.arg(Arg::with_name(options::NEWROOT).hidden(true).required(true))
|
||||
.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")
|
||||
.optopt(
|
||||
"G",
|
||||
"groups",
|
||||
"Comma-separated list of groups to switch to",
|
||||
"GROUP1,GROUP2...",
|
||||
.arg(
|
||||
Arg::with_name(options::GROUP)
|
||||
.short("g")
|
||||
.long(options::GROUP)
|
||||
.help("Group (ID or name) to switch to")
|
||||
.value_name("GROUP"),
|
||||
)
|
||||
.optopt(
|
||||
"",
|
||||
"userspec",
|
||||
"Colon-separated user and group to switch to. \
|
||||
.arg(
|
||||
Arg::with_name(options::GROUPS)
|
||||
.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. \
|
||||
Same as -u USER -g GROUP. \
|
||||
Userspec has higher preference than -u and/or -g",
|
||||
"USER:GROUP",
|
||||
)
|
||||
.value_name("USER:GROUP"),
|
||||
)
|
||||
.parse(args);
|
||||
|
||||
if matches.free.is_empty() {
|
||||
println!("Missing operand: NEWROOT");
|
||||
println!("Try `{} --help` for more information.", NAME);
|
||||
return 1;
|
||||
}
|
||||
.get_matches_from(args);
|
||||
|
||||
let default_shell: &'static str = "/bin/sh";
|
||||
let default_option: &'static str = "-i";
|
||||
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() {
|
||||
crash!(
|
||||
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 => {
|
||||
let shell: &str = match user_shell {
|
||||
Err(_) => default_shell,
|
||||
|
@ -80,7 +101,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
};
|
||||
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);
|
||||
|
@ -97,30 +125,26 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_context(root: &Path, options: &getopts::Matches) {
|
||||
let userspec_str = options.opt_str("userspec");
|
||||
let user_str = options.opt_str("user").unwrap_or_default();
|
||||
let group_str = options.opt_str("group").unwrap_or_default();
|
||||
let groups_str = options.opt_str("groups").unwrap_or_default();
|
||||
fn set_context(root: &Path, options: &clap::ArgMatches) {
|
||||
let userspec_str = options.value_of(options::USERSPEC);
|
||||
let user_str = options.value_of(options::USER).unwrap_or_default();
|
||||
let group_str = options.value_of(options::GROUP).unwrap_or_default();
|
||||
let groups_str = options.value_of(options::GROUPS).unwrap_or_default();
|
||||
let userspec = match userspec_str {
|
||||
Some(ref u) => {
|
||||
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)
|
||||
};
|
||||
s
|
||||
}
|
||||
None => Vec::new(),
|
||||
};
|
||||
let user = if userspec.is_empty() {
|
||||
&user_str[..]
|
||||
|
||||
let (user, group) = if userspec.is_empty() {
|
||||
(&user_str[..], &group_str[..])
|
||||
} else {
|
||||
&userspec[0][..]
|
||||
};
|
||||
let group = if userspec.is_empty() {
|
||||
&group_str[..]
|
||||
} else {
|
||||
&userspec[1][..]
|
||||
(&userspec[0][..], &userspec[1][..])
|
||||
};
|
||||
|
||||
enter_chroot(root);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue