Skip to content

Commit 99ee1a1

Browse files
committed
Allow some configuration
1 parent 3fbb9a1 commit 99ee1a1

File tree

18 files changed

+539
-400
lines changed

18 files changed

+539
-400
lines changed

minion-cli/src/main.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,13 @@ fn main() {
108108
eprintln!("Error: {}", err);
109109
std::process::exit(1);
110110
}
111-
let backend = minion::erased::setup();
111+
let backend = match minion::erased::setup() {
112+
Ok(b) => b,
113+
Err(err) => {
114+
eprintln!("Backend initialization failed: {}", err);
115+
std::process::exit(1);
116+
}
117+
};
112118

113119
let sandbox = backend
114120
.new_sandbox(minion::SandboxOptions {

minion-ffi/src/lib.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ pub enum ErrorCode {
1717
/// - something was expected to be unique, but wasn't, and so on
1818
/// these errors usually imply bug exists in caller code
1919
InvalidInput,
20-
/// unknown error
21-
Unknown,
20+
/// Minion error
21+
Minion,
2222
}
2323

2424
/// Get string description of given `error_code`, returned by minion-ffi previously.
@@ -29,7 +29,7 @@ pub extern "C" fn minion_describe_status(error_code: ErrorCode) -> *const u8 {
2929
match error_code {
3030
ErrorCode::Ok => b"ok\0".as_ptr(),
3131
ErrorCode::InvalidInput => b"invalid input\0".as_ptr(),
32-
ErrorCode::Unknown => b"unknown error\0".as_ptr(),
32+
ErrorCode::Minion => b"minion error\0".as_ptr(),
3333
}
3434
}
3535

@@ -69,7 +69,11 @@ pub unsafe extern "C" fn minion_lib_init() -> ErrorCode {
6969
#[no_mangle]
7070
#[must_use]
7171
pub extern "C" fn minion_backend_create(out: &mut *mut Backend) -> ErrorCode {
72-
let backend = Backend(minion::erased::setup());
72+
let backend = match minion::erased::setup() {
73+
Ok(b) => b,
74+
Err(_) => return ErrorCode::Minion,
75+
};
76+
let backend = Backend(backend);
7377
let backend = Box::new(backend);
7478
*out = Box::into_raw(backend);
7579
ErrorCode::Ok
@@ -119,7 +123,7 @@ pub unsafe extern "C" fn minion_sandbox_check_cpu_tle(
119123
}
120124
ErrorCode::Ok
121125
}
122-
Err(_) => ErrorCode::Unknown,
126+
Err(_) => ErrorCode::Minion,
123127
}
124128
}
125129

@@ -137,15 +141,15 @@ pub unsafe extern "C" fn minion_sandbox_check_real_tle(
137141
}
138142
ErrorCode::Ok
139143
}
140-
Err(_) => ErrorCode::Unknown,
144+
Err(_) => ErrorCode::Minion,
141145
}
142146
}
143147

144148
#[no_mangle]
145149
pub extern "C" fn minion_sandbox_kill(sandbox: &Sandbox) -> ErrorCode {
146150
match sandbox.0.kill() {
147151
Ok(_) => ErrorCode::Ok,
148-
Err(_) => ErrorCode::Unknown,
152+
Err(_) => ErrorCode::Minion,
149153
}
150154
}
151155

@@ -352,7 +356,7 @@ pub unsafe extern "C" fn minion_cp_wait(
352356
}
353357
ErrorCode::Ok
354358
}
355-
Result::Err(_) => ErrorCode::Unknown,
359+
Result::Err(_) => ErrorCode::Minion,
356360
}
357361
}
358362

@@ -389,7 +393,7 @@ pub unsafe extern "C" fn minion_cp_exitcode(
389393
}
390394
ErrorCode::Ok
391395
}
392-
Result::Err(_) => ErrorCode::Unknown,
396+
Result::Err(_) => ErrorCode::Minion,
393397
}
394398
}
395399

minion-tests/src/worker.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub fn main(test_cases: &[&'static dyn TestCase]) {
1414
.unwrap();
1515

1616
let tempdir = tempfile::TempDir::new().expect("cannot create temporary dir");
17-
let backend = minion::erased::setup();
17+
let backend = minion::erased::setup().expect("backend creation failed");
1818
let opts = minion::SandboxOptions {
1919
cpu_time_limit: test_case.time_limit(),
2020
real_time_limit: test_case.real_time_limit(),

src/erased.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ impl<B: crate::Backend> Backend for B {
132132
pub type ChildProcessOptions = crate::ChildProcessOptions<Box<dyn Sandbox>>;
133133

134134
/// Returns backend instance
135-
pub fn setup() -> Box<dyn Backend> {
136-
Box::new(crate::linux::LinuxBackend::new())
135+
pub fn setup() -> crate::Result<Box<dyn Backend>> {
136+
Ok(Box::new(crate::linux::LinuxBackend::new(
137+
crate::linux::Settings::new(),
138+
)?))
137139
}

src/linux.rs

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod cgroup;
12
pub mod check;
23
pub mod ext;
34
mod jail_common;
@@ -10,7 +11,7 @@ pub use crate::linux::sandbox::LinuxSandbox;
1011
use crate::{
1112
linux::{
1213
pipe::{LinuxReadPipe, LinuxWritePipe},
13-
util::{get_last_error, Handle, Pid},
14+
util::{get_last_error, Fd, Pid},
1415
},
1516
Backend, ChildProcess, ChildProcessOptions, InputSpecification, InputSpecificationData,
1617
OutputSpecification, OutputSpecificationData, SandboxOptions, WaitOutcome,
@@ -20,7 +21,9 @@ use std::{
2021
ffi::CString,
2122
fs,
2223
os::unix::io::IntoRawFd,
24+
path::PathBuf,
2325
sync::atomic::{AtomicI64, Ordering},
26+
sync::Arc,
2427
time::Duration,
2528
};
2629

@@ -98,7 +101,7 @@ impl ChildProcess for LinuxChildProcess {
98101
}
99102
}
100103

101-
fn handle_input_io(spec: InputSpecification) -> crate::Result<(Option<Handle>, Handle)> {
104+
fn handle_input_io(spec: InputSpecification) -> crate::Result<(Option<Fd>, Fd)> {
102105
match spec.0 {
103106
InputSpecificationData::Pipe => {
104107
let mut h_read = 0;
@@ -109,22 +112,22 @@ fn handle_input_io(spec: InputSpecification) -> crate::Result<(Option<Handle>, H
109112
Ok((Some(h_write), f))
110113
}
111114
InputSpecificationData::Handle(rh) => {
112-
let h = rh as Handle;
115+
let h = rh as Fd;
113116
Ok((None, h))
114117
}
115118
InputSpecificationData::Empty => {
116119
let file = fs::File::create("/dev/null")?;
117120
let file = file.into_raw_fd();
118121
Ok((None, file))
119122
}
120-
InputSpecificationData::Null => Ok((None, -1 as Handle)),
123+
InputSpecificationData::Null => Ok((None, -1 as Fd)),
121124
}
122125
}
123126

124-
fn handle_output_io(spec: OutputSpecification) -> crate::Result<(Option<Handle>, Handle)> {
127+
fn handle_output_io(spec: OutputSpecification) -> crate::Result<(Option<Fd>, Fd)> {
125128
match spec.0 {
126-
OutputSpecificationData::Null => Ok((None, -1 as Handle)),
127-
OutputSpecificationData::Handle(rh) => Ok((None, rh as Handle)),
129+
OutputSpecificationData::Null => Ok((None, -1 as Fd)),
130+
OutputSpecificationData::Handle(rh) => Ok((None, rh as Fd)),
128131
OutputSpecificationData::Pipe => {
129132
let mut h_read = 0;
130133
let mut h_write = 0;
@@ -223,17 +226,46 @@ fn spawn(mut options: ChildProcessOptions<LinuxSandbox>) -> crate::Result<LinuxC
223226
}
224227
}
225228

229+
/// Allows some customization
230+
#[non_exhaustive]
231+
#[derive(Debug)]
232+
pub struct Settings {
233+
/// All created cgroups will be children of specified group
234+
/// Default value is "/minion"
235+
pub cgroup_prefix: PathBuf,
236+
237+
/// If enabled, minion will ignore clone(MOUNT_NEWNS) error.
238+
/// This flag has to be enabled for gVisor support.
239+
pub allow_unsupported_mount_namespace: bool,
240+
}
241+
242+
impl Default for Settings {
243+
fn default() -> Self {
244+
Settings {
245+
cgroup_prefix: "/minion".into(),
246+
allow_unsupported_mount_namespace: false,
247+
}
248+
}
249+
}
250+
251+
impl Settings {
252+
pub fn new() -> Settings {
253+
Default::default()
254+
}
255+
}
226256
#[derive(Debug)]
227257
pub struct LinuxBackend {
228-
_priv: (),
258+
settings: Settings,
259+
cgroup_driver: Arc<cgroup::Driver>,
229260
}
230261

231262
impl Backend for LinuxBackend {
232263
type Sandbox = LinuxSandbox;
233264
type ChildProcess = LinuxChildProcess;
234265
fn new_sandbox(&self, mut options: SandboxOptions) -> crate::Result<LinuxSandbox> {
235266
options.postprocess();
236-
let sb = unsafe { LinuxSandbox::create(options)? };
267+
let sb =
268+
unsafe { LinuxSandbox::create(options, &self.settings, self.cgroup_driver.clone())? };
237269
Ok(sb)
238270
}
239271

@@ -246,13 +278,11 @@ impl Backend for LinuxBackend {
246278
}
247279

248280
impl LinuxBackend {
249-
pub fn new() -> LinuxBackend {
250-
LinuxBackend { _priv: () }
251-
}
252-
}
253-
254-
impl Default for LinuxBackend {
255-
fn default() -> Self {
256-
LinuxBackend::new()
281+
pub fn new(settings: Settings) -> crate::Result<LinuxBackend> {
282+
let cgroup_driver = Arc::new(cgroup::Driver::new(&settings)?);
283+
Ok(LinuxBackend {
284+
settings,
285+
cgroup_driver,
286+
})
257287
}
258288
}

src/linux/cgroup.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/// Implements Cgroup Driver - high-level cgroup manager
2+
mod detect;
3+
mod v1;
4+
mod v2;
5+
6+
use crate::linux::util::Fd;
7+
use std::{ffi::OsString, path::PathBuf};
8+
9+
// used by crate::linux::check
10+
pub(in crate::linux) use detect::CgroupVersion;
11+
12+
/// Information, sufficient for joining a cgroup.
13+
pub(in crate::linux) enum JoinHandle {
14+
/// Fds of `tasks` file in each hierarchy.
15+
V1(Vec<Fd>),
16+
/// Fd of `cgroup.procs` file in cgroup dir.
17+
V2(Fd),
18+
}
19+
20+
impl JoinHandle {
21+
fn with(&self, f: impl FnOnce(&mut dyn Iterator<Item = Fd>)) {
22+
let mut slice_iter;
23+
let mut once_iter;
24+
let it: &mut dyn std::iter::Iterator<Item = Fd> = match &self {
25+
Self::V1(handles) => {
26+
slice_iter = handles.iter().copied();
27+
&mut slice_iter
28+
}
29+
Self::V2(handle) => {
30+
once_iter = std::iter::once(*handle);
31+
&mut once_iter
32+
}
33+
};
34+
f(it);
35+
}
36+
pub(super) fn join_self(&self) {
37+
let my_pid = std::process::id();
38+
let my_pid = format!("{}", my_pid);
39+
self.with(|it| {
40+
for fd in it {
41+
nix::unistd::write(fd, my_pid.as_bytes()).expect("Couldn't join cgroup");
42+
}
43+
});
44+
}
45+
}
46+
47+
impl Drop for JoinHandle {
48+
fn drop(&mut self) {
49+
self.with(|it| {
50+
for fd in it {
51+
nix::unistd::close(fd).ok();
52+
}
53+
})
54+
}
55+
}
56+
57+
/// Abstracts all cgroups manipulations.
58+
/// Each Backend has exactly one Driver.
59+
#[derive(Debug)]
60+
pub(in crate::linux) struct Driver {
61+
cgroupfs_path: PathBuf,
62+
cgroup_prefix: Vec<OsString>,
63+
version: detect::CgroupVersion,
64+
}
65+
66+
/// Represents resource limits imposed on sandbox
67+
pub(in crate::linux) struct ResourceLimits {
68+
pub(in crate::linux) pids_max: u32,
69+
pub(in crate::linux) memory_max: u64,
70+
}
71+
72+
impl Driver {
73+
pub(in crate::linux) fn new(settings: &crate::linux::Settings) -> crate::Result<Driver> {
74+
let cgroup_version = detect::CgroupVersion::detect();
75+
let mut cgroup_prefix = Vec::new();
76+
for comp in settings.cgroup_prefix.components() {
77+
if let std::path::Component::Normal(n) = comp {
78+
cgroup_prefix.push(n.to_os_string());
79+
}
80+
}
81+
let cgroupfs_path = "/sys/fs/cgroup".into();
82+
Ok(Driver {
83+
version: cgroup_version,
84+
cgroup_prefix,
85+
cgroupfs_path,
86+
})
87+
}
88+
pub(in crate::linux) fn create_group(
89+
&self,
90+
cgroup_id: &str,
91+
limits: &ResourceLimits,
92+
) -> JoinHandle {
93+
match CgroupVersion::detect() {
94+
CgroupVersion::V1 => JoinHandle::V1(self.setup_cgroups_v1(limits, cgroup_id)),
95+
CgroupVersion::V2 => JoinHandle::V2(self.setup_cgroups_v2(limits, cgroup_id)),
96+
}
97+
}
98+
99+
pub(in crate::linux) fn get_cpu_usage(&self, cgroup_id: &str) -> u64 {
100+
match self.version {
101+
CgroupVersion::V1 => self.get_cpu_usage_v1(cgroup_id),
102+
CgroupVersion::V2 => self.get_cpu_usage_v2(cgroup_id),
103+
}
104+
}
105+
106+
pub(in crate::linux) fn get_memory_usage(&self, cgroup_id: &str) -> Option<u64> {
107+
match self.version {
108+
// memory cgroup v2 does not provide way to get peak memory usage.
109+
// `memory.current` contains only current usage.
110+
CgroupVersion::V2 => None,
111+
CgroupVersion::V1 => Some(self.get_memory_usage_v1(cgroup_id)),
112+
}
113+
}
114+
pub(in crate::linux) fn get_cgroup_tasks_file_path(&self, cgroup_id: &str) -> PathBuf {
115+
match self.version {
116+
CgroupVersion::V1 => self.get_cgroup_tasks_file_path_v1(cgroup_id),
117+
CgroupVersion::V2 => self.get_cgroup_tasks_file_path_v2(cgroup_id),
118+
}
119+
}
120+
121+
pub(in crate::linux) fn drop_cgroup(&self, cgroup_id: &str, legacy_subsystems: &[&str]) {
122+
match self.version {
123+
CgroupVersion::V1 => self.drop_cgroup_v1(cgroup_id, legacy_subsystems),
124+
CgroupVersion::V2 => self.drop_cgroup_v2(cgroup_id),
125+
}
126+
}
127+
}

src/linux/cgroup/detect.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//! This module is responsible for CGroup version detection
2+
#[derive(Eq, PartialEq, Debug)]
3+
pub(in crate::linux) enum CgroupVersion {
4+
/// Legacy
5+
V1,
6+
/// Unified
7+
V2,
8+
}
9+
10+
impl CgroupVersion {
11+
pub(in crate::linux) fn detect() -> CgroupVersion {
12+
let stat = nix::sys::statfs::statfs("/sys/fs/cgroup")
13+
.expect("/sys/fs/cgroup is not root of cgroupfs");
14+
let ty = stat.filesystem_type();
15+
// man 2 statfs
16+
match ty.0 {
17+
0x0027_e0eb => return CgroupVersion::V1,
18+
0x6367_7270 => return CgroupVersion::V2,
19+
_ => (),
20+
};
21+
let p = std::path::Path::new("/sys/fs/cgroup");
22+
if p.join("cgroup.subtree_control").exists() {
23+
CgroupVersion::V2
24+
} else {
25+
CgroupVersion::V1
26+
}
27+
}
28+
}

0 commit comments

Comments
 (0)