Skip to content

Commit bce4b97

Browse files
committed
Add minion-cli
1 parent 3069aa3 commit bce4b97

File tree

4 files changed

+240
-1
lines changed

4 files changed

+240
-1
lines changed

Cargo.lock

Lines changed: 70 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ backtrace = "0.3.46"
1818
thiserror = "1.0.15"
1919

2020
[workspace]
21-
members = ["minion-ffi", ".", "minion-tests"]
21+
members = ["minion-ffi", ".", "minion-tests", "minion-cli"]

minion-cli/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "minion-cli"
3+
version = "0.1.0"
4+
authors = ["Mikail Bagishov <bagishov.mikail@yandex.ru>"]
5+
edition = "2018"
6+
7+
[dependencies]
8+
minion = {path = ".."}
9+
structopt = "0.3.13"
10+
libc = "0.2.68"

minion-cli/src/main.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
use minion::{self, Dominion};
2+
use std::time::Duration;
3+
use structopt::StructOpt;
4+
5+
#[derive(Debug)]
6+
struct EnvItem {
7+
name: String,
8+
value: String,
9+
}
10+
11+
fn parse_env_item(src: &str) -> Result<EnvItem, &'static str> {
12+
let p = src.find('=').ok_or("Env item doesn't look like KEY=VAL")?;
13+
Ok(EnvItem {
14+
name: String::from(&src[0..p]),
15+
value: String::from(&src[p + 1..]),
16+
})
17+
}
18+
19+
fn parse_path_exposition_item(src: &str) -> Result<minion::PathExpositionOptions, String> {
20+
let parts = src.splitn(3, ':').collect::<Vec<_>>();
21+
if parts.len() != 3 {
22+
return Err(format!(
23+
"--expose item must contain two colons (`:`), but no {} was provided",
24+
parts.len()
25+
));
26+
}
27+
let amask = parts[1];
28+
if amask.len() != 3 {
29+
return Err(format!(
30+
"access mask must contain 3 chars (R, W, X flags), but {} provided",
31+
amask.len()
32+
));
33+
}
34+
let access = match amask {
35+
"rwx" => minion::DesiredAccess::Full,
36+
"r-x" => minion::DesiredAccess::Readonly,
37+
_ => {
38+
return Err(format!(
39+
"unknown access mask {}. rwx or r-x expected",
40+
amask
41+
));
42+
}
43+
};
44+
Ok(minion::PathExpositionOptions {
45+
src: parts[0].to_string().into(),
46+
dest: parts[2].to_string().into(),
47+
access,
48+
})
49+
}
50+
51+
#[derive(StructOpt, Debug)]
52+
struct ExecOpt {
53+
/// Full name of executable file (e.g. /bin/ls)
54+
#[structopt(name = "bin")]
55+
executable: String,
56+
57+
/// Arguments for isolated process
58+
#[structopt(short = "a", long = "arg")]
59+
argv: Vec<String>,
60+
61+
/// Environment variables (KEY=VAL) which will be passed to isolated process
62+
#[structopt(short = "e", long, parse(try_from_str = parse_env_item))]
63+
env: Vec<EnvItem>,
64+
65+
/// Max peak process count (including main)
66+
#[structopt(short = "n", long = "max-process-count", default_value = "16")]
67+
num_processes: usize,
68+
69+
/// Max memory available to isolated process
70+
#[structopt(short = "m", long, default_value = "256000000")]
71+
memory_limit: usize,
72+
73+
/// Total CPU time in milliseconds
74+
#[structopt(short = "t", long, default_value = "1000")]
75+
time_limit: u32,
76+
77+
/// Print parsed argv
78+
#[structopt(long)]
79+
dump_argv: bool,
80+
81+
/// Print libminion parameters
82+
#[structopt(long = "dump-generated-security-settings")]
83+
dump_minion_params: bool,
84+
85+
/// Isolation root
86+
#[structopt(short = "r", long = "root")]
87+
isolation_root: String,
88+
89+
/// Exposed paths (/source/path:MASK:/dest/path), MASK is r-x for readonly access and rwx for full access
90+
#[structopt(
91+
short = "x",
92+
long = "expose",
93+
parse(try_from_str = parse_path_exposition_item)
94+
)]
95+
exposed_paths: Vec<minion::PathExpositionOptions>,
96+
97+
/// Process working dir, relative to `isolation_root`
98+
#[structopt(short = "p", long = "pwd", default_value = "/")]
99+
pwd: String,
100+
}
101+
102+
fn main() {
103+
let options: ExecOpt = ExecOpt::from_args();
104+
if options.dump_argv {
105+
println!("{:#?}", options);
106+
}
107+
if let Some(err) = minion::check() {
108+
eprintln!("Error: {}", err);
109+
std::process::exit(1);
110+
}
111+
let backend = minion::setup();
112+
113+
let dominion = backend.new_dominion(minion::DominionOptions {
114+
max_alive_process_count: options.num_processes.min(u32::max_value() as usize) as u32,
115+
memory_limit: options.memory_limit as u64,
116+
isolation_root: options.isolation_root.into(),
117+
exposed_paths: options.exposed_paths,
118+
cpu_time_limit: Duration::from_millis(u64::from(options.time_limit)),
119+
real_time_limit: Duration::from_millis(u64::from(options.time_limit * 3)),
120+
});
121+
122+
let dominion = dominion.unwrap();
123+
124+
let (stdin_fd, stdout_fd, stderr_fd);
125+
unsafe {
126+
stdin_fd = libc::dup(0) as u64;
127+
stdout_fd = libc::dup(1) as u64;
128+
stderr_fd = libc::dup(2) as u64;
129+
}
130+
let args = minion::ChildProcessOptions {
131+
path: options.executable.into(),
132+
arguments: options.argv.iter().map(|x| x.into()).collect(),
133+
environment: options
134+
.env
135+
.iter()
136+
.map(|v| format!("{}={}", &v.name, &v.value).into())
137+
.collect(),
138+
dominion: dominion.clone(),
139+
stdio: minion::StdioSpecification {
140+
stdin: unsafe { minion::InputSpecification::handle(stdin_fd) },
141+
stdout: unsafe { minion::OutputSpecification::handle(stdout_fd) },
142+
stderr: unsafe { minion::OutputSpecification::handle(stderr_fd) },
143+
},
144+
pwd: options.pwd.into(),
145+
};
146+
if options.dump_minion_params {
147+
println!("{:#?}", args);
148+
}
149+
let cp = backend.spawn(args).unwrap();
150+
cp.wait_for_exit(None).unwrap();
151+
let exit_code = cp.get_exit_code().unwrap();
152+
println!("---> Child process exited with code {:?} <---", exit_code);
153+
if dominion.check_cpu_tle().unwrap() {
154+
println!("Note: CPU time limit was exceeded");
155+
}
156+
if dominion.check_real_tle().unwrap() {
157+
println!("Note: wall-clock time limit was exceeded");
158+
}
159+
}

0 commit comments

Comments
 (0)