Skip to content

Commit 3a030e1

Browse files
committed
Add src and documentation
1 parent 0ddc2ce commit 3a030e1

File tree

10 files changed

+275
-0
lines changed

10 files changed

+275
-0
lines changed

Cargo.lock

Lines changed: 60 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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "vas-quod"
3+
version = "0.1.0"
4+
authors = ["flouthoc <https://twitter.com/flouthoc>"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
getopts = "0.2"
11+
nix = "0.19.1"

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# vas-quod
2+
3+
4+
A tiny minimal container runtime written in Rust.
5+
The idea is to support a minimal isolated containers without using existing runtimes, vas-quod uses linux [syscall](https://en.wikipedia.org/wiki/System_call) to achieve isolated containers { namespaces, cgroups, chroot, unshare }.
6+
7+
![](../blob/master/assets/vas-quod.png?raw=true)
8+
9+
10+
## Usage
11+
12+
13+
```bash
14+
Usage: ./vas-quod - minimal container runtime [options]
15+
Options:
16+
-r, --rootfs path Path to root file-system eg. --rootfs /home/alpinefs
17+
-c, --command command
18+
Command to be executed eg. --command `curl
19+
http://google.com`
20+
-h, --help print this help menu
21+
```
22+
23+
* #### rootfs
24+
Path to root filesystem
25+
* #### command
26+
Container entrypoint command
27+
28+
## Roadmap
29+
* Add Support for network bridges.
30+
* Implement `-m` or `--mount` to mount read-only files from host machine.
31+

assets/vas-quod.png

15.3 KB
Loading

src/cgroup.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use std::fs;
2+
use std::path::PathBuf;
3+
4+
use std::process;
5+
use nix::unistd;
6+
7+
use std::os::unix::fs::PermissionsExt;
8+
9+
static CGROUP_PATH: &str = "/sys/fs/cgroup/pids";
10+
11+
pub fn cgroup_init(group_name: &str) {
12+
let mut cgroups_path = PathBuf::from(CGROUP_PATH);
13+
if !cgroups_path.exists() {
14+
println!("Error: Missing Cgroups Support");
15+
process::exit(0);
16+
}
17+
18+
cgroups_path.push(group_name);
19+
if !cgroups_path.exists() {
20+
fs::create_dir_all(&cgroups_path).unwrap();
21+
let mut permission = fs::metadata(&cgroups_path).unwrap().permissions();
22+
permission.set_mode(0o777);
23+
fs::set_permissions(&cgroups_path, permission).ok();
24+
}
25+
26+
let pids_max = cgroups_path.join("pids.max");
27+
let notify_on_release = cgroups_path.join("notify_on_release");
28+
let procs = cgroups_path.join("cgroup.procs");
29+
30+
fs::write(pids_max, b"20").unwrap();
31+
fs::write(notify_on_release, b"1").unwrap();
32+
fs::write(procs,format!("{}", unistd::getpid().as_raw())).unwrap();
33+
34+
}
35+
36+
#[allow(dead_code)]
37+
pub fn cgroup_deinit(group_name: &str){
38+
let mut cgroups_path = PathBuf::from(CGROUP_PATH);
39+
if !cgroups_path.exists() {
40+
println!("Error: Missing Cgroups Support");
41+
process::exit(0);
42+
}
43+
cgroups_path.push(group_name);
44+
fs::remove_dir(cgroups_path).expect("Failed to remove the cgroup");
45+
}
46+

src/filesystem.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use nix::unistd;
2+
3+
static ROOT_PATH: &str = "/";
4+
5+
pub fn set_root_fs(rootfs: &str){
6+
unistd::chroot(rootfs).unwrap();
7+
let _status = unistd::chdir(ROOT_PATH);
8+
}

src/main.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
extern crate getopts;
2+
use getopts::Options;
3+
use std::env;
4+
5+
mod runtime;
6+
mod cgroup;
7+
mod filesystem;
8+
mod mount;
9+
mod namespace;
10+
11+
fn run(rootfs: Option<String>, command_string: Option<String>){
12+
let rootfs = rootfs.unwrap();
13+
let command_string = command_string.unwrap();
14+
15+
let child_command_buffer = command_string.split(" ");
16+
let mut child_command_vector = child_command_buffer.collect::<Vec<&str>>();
17+
let command = child_command_vector[0];
18+
child_command_vector.drain(0..1);
19+
20+
runtime::run_container(&rootfs, command, child_command_vector);
21+
}
22+
23+
fn print_usage(program: &str, opts: Options) {
24+
let brief = format!("Usage: {} vas-quod [options]", program);
25+
print!("{}", opts.usage(&brief));
26+
}
27+
28+
29+
fn main() {
30+
let args: Vec<String> = env::args().collect();
31+
let program = args[0].clone();
32+
33+
let mut opts = Options::new();
34+
opts.optopt("r", "rootfs", "Path to root file-system eg. --rootfs /home/alpinefs", "path");
35+
opts.optopt("c", "command", "Command to be executed eg. --command `curl http://google.com`", "command");
36+
opts.optflag("h", "help", "print this help menu");
37+
let matches = match opts.parse(&args[1..]) {
38+
Ok(m) => { m }
39+
Err(_f) => {
40+
println!("Error: Unrecognzied Options");
41+
print_usage(&program, opts);
42+
return
43+
}
44+
};
45+
if matches.opt_present("h") || !matches.opt_present("r") || !matches.opt_present("c") {
46+
print_usage(&program, opts);
47+
return;
48+
}
49+
50+
let rootfs = matches.opt_str("r");
51+
let command_string = matches.opt_str("c");
52+
run(rootfs, command_string);
53+
}

src/mount.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use nix::mount;
2+
3+
static PROC: &str = "proc";
4+
5+
pub fn mount_proc(){
6+
const NONE: Option<&'static [u8]> = None;
7+
mount::mount(Some(PROC), PROC, Some(PROC), mount::MsFlags::empty(), NONE).expect("Failed to mount the /proc");
8+
}
9+
10+
pub fn unmount_proc(){
11+
mount::umount("proc").unwrap();
12+
}
13+

src/namespace.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
use nix::sched;
2+
3+
pub fn create_isolated_namespace(){
4+
sched::unshare(sched::CloneFlags::CLONE_NEWNS).expect("Failed to unshare");
5+
}

src/runtime.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use nix::sched;
2+
use nix::sys::signal::Signal;
3+
use nix::unistd;
4+
use std::process::Command;
5+
6+
use crate::cgroup;
7+
use crate::filesystem;
8+
use crate::mount;
9+
use crate::namespace;
10+
11+
static CGROUP_NAME: &str = "vasquod-container";
12+
static HOSTNAME: &str = "vasquod";
13+
14+
fn set_hostname(hostname: &str){
15+
// can also use libc here
16+
unistd::sethostname(hostname).unwrap()
17+
}
18+
19+
fn spawn_child(hostname: &str, cgroup_name: &str, rootfs: &str, command: &str, command_args: &[&str]) -> isize {
20+
21+
namespace::create_isolated_namespace();
22+
cgroup::cgroup_init(cgroup_name);
23+
set_hostname(hostname);
24+
25+
filesystem::set_root_fs(rootfs);
26+
mount::mount_proc();
27+
28+
Command::new(command).args(command_args).spawn().expect("Failed to execute container command").wait().unwrap();
29+
30+
mount::unmount_proc();
31+
return 0;
32+
}
33+
34+
pub fn run_container(rootfs: &str, command: &str, command_args: Vec<&str>){
35+
36+
let group_name = CGROUP_NAME;
37+
let hostname = HOSTNAME;
38+
const STACK_SIZE: usize = 1024 * 1024;
39+
let stack: &mut [u8; STACK_SIZE] = &mut [0; STACK_SIZE];
40+
41+
let cb = Box::new(|| spawn_child(hostname, group_name, rootfs, command, command_args.as_slice()));
42+
43+
//SSee `man clone`
44+
let clone_flags = sched::CloneFlags::CLONE_NEWNS | sched::CloneFlags::CLONE_NEWPID | sched::CloneFlags::CLONE_NEWCGROUP | sched::CloneFlags::CLONE_NEWUTS | sched::CloneFlags::CLONE_NEWIPC | sched::CloneFlags::CLONE_NEWNET;
45+
46+
let _child_pid = sched::clone(cb, stack, clone_flags, Some(Signal::SIGCHLD as i32)).expect("Failed to create child process");
47+
48+
}

0 commit comments

Comments
 (0)