mirror of
https://github.com/containers/youki
synced 2024-05-08 08:36:15 +02:00
Move and split youki into container crate and cli
This commit is contained in:
parent
c77a71afd4
commit
0dbae24651
|
@ -171,6 +171,37 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "container"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"caps",
|
||||||
|
"cgroups",
|
||||||
|
"chrono",
|
||||||
|
"crossbeam-channel",
|
||||||
|
"dbus",
|
||||||
|
"fastrand",
|
||||||
|
"futures",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"mio",
|
||||||
|
"nix",
|
||||||
|
"oci-spec 0.5.2 (git+https://github.com/containers/oci-spec-rs?rev=d6fb1e91742313cd0d0085937e2d6df5d4669720)",
|
||||||
|
"once_cell",
|
||||||
|
"path-clean",
|
||||||
|
"pentacle",
|
||||||
|
"prctl",
|
||||||
|
"procfs",
|
||||||
|
"quickcheck",
|
||||||
|
"seccomp",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serial_test",
|
||||||
|
"systemd",
|
||||||
|
"tabwriter",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
@ -1388,29 +1419,18 @@ name = "youki"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"caps",
|
|
||||||
"cgroups",
|
"cgroups",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"crossbeam-channel",
|
"container",
|
||||||
"dbus",
|
|
||||||
"fastrand",
|
|
||||||
"futures",
|
|
||||||
"libc",
|
|
||||||
"log",
|
"log",
|
||||||
"mio",
|
|
||||||
"nix",
|
"nix",
|
||||||
"oci-spec 0.5.2 (git+https://github.com/containers/oci-spec-rs?rev=d6fb1e91742313cd0d0085937e2d6df5d4669720)",
|
"oci-spec 0.5.2 (git+https://github.com/containers/oci-spec-rs?rev=d6fb1e91742313cd0d0085937e2d6df5d4669720)",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"path-clean",
|
|
||||||
"pentacle",
|
"pentacle",
|
||||||
"prctl",
|
|
||||||
"procfs",
|
"procfs",
|
||||||
"quickcheck",
|
|
||||||
"seccomp",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serial_test",
|
"serial_test",
|
||||||
"systemd",
|
|
||||||
"tabwriter",
|
"tabwriter",
|
||||||
]
|
]
|
||||||
|
|
46
Cargo.toml
46
Cargo.toml
|
@ -1,54 +1,8 @@
|
||||||
[package]
|
|
||||||
name = "youki"
|
|
||||||
version = "0.0.1"
|
|
||||||
authors = ["utam0k <k0ma@utam0k.jp>"]
|
|
||||||
edition = "2018"
|
|
||||||
description = "A container runtime written in Rust"
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"crates/*"
|
"crates/*"
|
||||||
]
|
]
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["systemd_cgroups"]
|
|
||||||
systemd_cgroups = ["systemd"]
|
|
||||||
|
|
||||||
[dependencies.clap]
|
|
||||||
version = "3.0.0-beta.2"
|
|
||||||
default-features = false
|
|
||||||
features = ["std", "suggestions", "derive", "cargo"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
nix = "0.22.0"
|
|
||||||
procfs = "0.11.0"
|
|
||||||
# Waiting for new caps release, replace git with version on release
|
|
||||||
caps = { git = "https://github.com/lucab/caps-rs", rev = "cb54844", features = ["serde_support"] }
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
serde_json = "1.0"
|
|
||||||
prctl = "1.0.0"
|
|
||||||
libc = "0.2.84"
|
|
||||||
log = "0.4"
|
|
||||||
anyhow = "1.0"
|
|
||||||
mio = { version = "0.7.13", features = ["os-ext", "os-poll"] }
|
|
||||||
chrono = { version="0.4", features = ["serde"] }
|
|
||||||
once_cell = "1.6.0"
|
|
||||||
futures = { version = "0.3", features = ["thread-pool"] }
|
|
||||||
oci-spec = { git = "https://github.com/containers/oci-spec-rs", rev = "d6fb1e91742313cd0d0085937e2d6df5d4669720" }
|
|
||||||
cgroups = { version = "0.1.0", path = "./crates/cgroups" }
|
|
||||||
systemd = { version = "0.8", default-features = false, optional = true }
|
|
||||||
dbus = "0.9.2"
|
|
||||||
tabwriter = "1"
|
|
||||||
fastrand = "1.4.1"
|
|
||||||
crossbeam-channel = "0.5"
|
|
||||||
seccomp = { version = "0.1.0", path = "./crates/seccomp" }
|
|
||||||
pentacle = "1.0.0"
|
|
||||||
path-clean = "0.1.0"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
oci-spec = { git = "https://github.com/containers/oci-spec-rs", rev = "d6fb1e91742313cd0d0085937e2d6df5d4669720", features = ["proptests"] }
|
|
||||||
quickcheck = "1"
|
|
||||||
serial_test = "0.5.1"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
[package]
|
||||||
|
name = "container"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = ["youki team"]
|
||||||
|
edition = "2018"
|
||||||
|
description = "A container runtime written in Rust"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["systemd_cgroups"]
|
||||||
|
systemd_cgroups = ["systemd"]
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nix = "0.22.0"
|
||||||
|
procfs = "0.11.0"
|
||||||
|
# Waiting for new caps release, replace git with version on release
|
||||||
|
caps = { git = "https://github.com/lucab/caps-rs", rev = "cb54844", features = ["serde_support"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
prctl = "1.0.0"
|
||||||
|
libc = "0.2.84"
|
||||||
|
log = "0.4"
|
||||||
|
anyhow = "1.0"
|
||||||
|
mio = { version = "0.7.13", features = ["os-ext", "os-poll"] }
|
||||||
|
chrono = { version="0.4", features = ["serde"] }
|
||||||
|
once_cell = "1.6.0"
|
||||||
|
futures = { version = "0.3", features = ["thread-pool"] }
|
||||||
|
oci-spec = { git = "https://github.com/containers/oci-spec-rs", rev = "d6fb1e91742313cd0d0085937e2d6df5d4669720" }
|
||||||
|
cgroups = { version = "0.1.0", path = "../cgroups" }
|
||||||
|
systemd = { version = "0.8", default-features = false, optional = true }
|
||||||
|
dbus = "0.9.2"
|
||||||
|
tabwriter = "1"
|
||||||
|
fastrand = "1.4.1"
|
||||||
|
crossbeam-channel = "0.5"
|
||||||
|
seccomp = { version = "0.1.0", path = "../seccomp" }
|
||||||
|
pentacle = "1.0.0"
|
||||||
|
path-clean = "0.1.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
oci-spec = { git = "https://github.com/containers/oci-spec-rs", rev = "d6fb1e91742313cd0d0085937e2d6df5d4669720", features = ["proptests"] }
|
||||||
|
quickcheck = "1"
|
||||||
|
serial_test = "0.5.1"
|
|
@ -1,10 +1,8 @@
|
||||||
pub mod apparmor;
|
pub mod apparmor;
|
||||||
pub mod capabilities;
|
pub mod capabilities;
|
||||||
pub mod commands;
|
|
||||||
pub mod container;
|
pub mod container;
|
||||||
pub mod dbus;
|
pub mod dbus;
|
||||||
pub mod hooks;
|
pub mod hooks;
|
||||||
pub mod logger;
|
|
||||||
pub mod namespaces;
|
pub mod namespaces;
|
||||||
pub mod notify_socket;
|
pub mod notify_socket;
|
||||||
pub mod process;
|
pub mod process;
|
|
@ -1,48 +0,0 @@
|
||||||
//! Handles the creation of a new container
|
|
||||||
use anyhow::Result;
|
|
||||||
use clap::Clap;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use crate::{container::builder::ContainerBuilder, syscall::syscall::create_syscall};
|
|
||||||
|
|
||||||
/// Create a container
|
|
||||||
#[derive(Clap, Debug)]
|
|
||||||
pub struct Create {
|
|
||||||
/// File to write pid of the container created
|
|
||||||
// note that in the end, container is just another process
|
|
||||||
#[clap(short, long)]
|
|
||||||
pid_file: Option<PathBuf>,
|
|
||||||
/// path to the bundle directory, containing config.json and root filesystem
|
|
||||||
#[clap(short, long, default_value = ".")]
|
|
||||||
bundle: PathBuf,
|
|
||||||
/// Unix socket (file) path , which will receive file descriptor of the writing end of the pseudoterminal
|
|
||||||
#[clap(short, long)]
|
|
||||||
console_socket: Option<PathBuf>,
|
|
||||||
/// Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)
|
|
||||||
#[clap(long, default_value = "0")]
|
|
||||||
preserve_fds: i32,
|
|
||||||
/// name of the container instance to be started
|
|
||||||
#[clap(required = true)]
|
|
||||||
pub container_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
// One thing to note is that in the end, container is just another process in Linux
|
|
||||||
// it has specific/different control group, namespace, using which program executing in it
|
|
||||||
// can be given impression that is is running on a complete system, but on the system which
|
|
||||||
// it is running, it is just another process, and has attributes such as pid, file descriptors, etc.
|
|
||||||
// associated with it like any other process.
|
|
||||||
impl Create {
|
|
||||||
pub fn exec(&self, root_path: PathBuf, systemd_cgroup: bool) -> Result<()> {
|
|
||||||
let syscall = create_syscall();
|
|
||||||
ContainerBuilder::new(self.container_id.clone(), syscall.as_ref())
|
|
||||||
.with_pid_file(self.pid_file.as_ref())
|
|
||||||
.with_console_socket(self.console_socket.as_ref())
|
|
||||||
.with_root_path(root_path)
|
|
||||||
.with_preserved_fds(self.preserve_fds)
|
|
||||||
.as_init(&self.bundle)
|
|
||||||
.with_systemd(systemd_cgroup)
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
use crate::commands::load_container;
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use clap::Clap;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
/// Release any resources held by the container
|
|
||||||
#[derive(Clap, Debug)]
|
|
||||||
pub struct Delete {
|
|
||||||
#[clap(required = true)]
|
|
||||||
container_id: String,
|
|
||||||
/// forces deletion of the container if it is still running (using SIGKILL)
|
|
||||||
#[clap(short, long)]
|
|
||||||
force: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Delete {
|
|
||||||
pub fn exec(&self, root_path: PathBuf) -> Result<()> {
|
|
||||||
log::debug!("start deleting {}", self.container_id);
|
|
||||||
let mut container = load_container(root_path, &self.container_id)?;
|
|
||||||
container
|
|
||||||
.delete(self.force)
|
|
||||||
.with_context(|| format!("failed to delete container {}", self.container_id))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
use clap::Clap;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
|
|
||||||
use crate::commands::load_container;
|
|
||||||
|
|
||||||
/// Show resource statistics for the container
|
|
||||||
#[derive(Clap, Debug)]
|
|
||||||
pub struct Events {
|
|
||||||
/// Sets the stats collection interval in seconds (default: 5s)
|
|
||||||
#[clap(long, default_value = "5")]
|
|
||||||
pub interval: u32,
|
|
||||||
/// Display the container stats only once
|
|
||||||
#[clap(long)]
|
|
||||||
pub stats: bool,
|
|
||||||
/// Name of the container instance
|
|
||||||
#[clap(required = true)]
|
|
||||||
pub container_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Events {
|
|
||||||
pub fn exec(&self, root_path: PathBuf) -> Result<()> {
|
|
||||||
let mut container = load_container(root_path, &self.container_id)?;
|
|
||||||
container
|
|
||||||
.events(self.interval, self.stats)
|
|
||||||
.with_context(|| format!("failed to get events from container {}", self.container_id))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
use clap::Clap;
|
|
||||||
use std::{error::Error, path::PathBuf};
|
|
||||||
|
|
||||||
use crate::{container::builder::ContainerBuilder, syscall::syscall::create_syscall};
|
|
||||||
|
|
||||||
/// Execute a process within an existing container
|
|
||||||
#[derive(Clap, Debug)]
|
|
||||||
pub struct Exec {
|
|
||||||
/// Unix socket (file) path , which will receive file descriptor of the writing end of the pseudoterminal
|
|
||||||
#[clap(long)]
|
|
||||||
pub console_socket: Option<PathBuf>,
|
|
||||||
#[clap(short, long)]
|
|
||||||
pub tty: bool,
|
|
||||||
#[clap(long)]
|
|
||||||
/// Current working directory of the container
|
|
||||||
pub cwd: Option<PathBuf>,
|
|
||||||
#[clap(long)]
|
|
||||||
/// The file to which the pid of the container process should be written to
|
|
||||||
pub pid_file: Option<PathBuf>,
|
|
||||||
/// Environment variables that should be set in the container
|
|
||||||
#[clap(short, long, parse(try_from_str = parse_key_val), number_of_values = 1)]
|
|
||||||
pub env: Vec<(String, String)>,
|
|
||||||
/// Prevent the process from gaining additional privileges
|
|
||||||
#[clap(long)]
|
|
||||||
pub no_new_privs: bool,
|
|
||||||
/// Path to process.json
|
|
||||||
#[clap(short, long)]
|
|
||||||
pub process: Option<PathBuf>,
|
|
||||||
/// Detach from the container process
|
|
||||||
#[clap(short, long)]
|
|
||||||
pub detach: bool,
|
|
||||||
/// Identifier of the container
|
|
||||||
#[clap(required = true)]
|
|
||||||
pub container_id: String,
|
|
||||||
/// Command that should be executed in the container
|
|
||||||
#[clap(required = false)]
|
|
||||||
pub command: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Exec {
|
|
||||||
pub fn exec(&self, root_path: PathBuf) -> Result<()> {
|
|
||||||
let syscall = create_syscall();
|
|
||||||
ContainerBuilder::new(self.container_id.clone(), syscall.as_ref())
|
|
||||||
.with_root_path(root_path)
|
|
||||||
.with_console_socket(self.console_socket.as_ref())
|
|
||||||
.with_pid_file(self.pid_file.as_ref())
|
|
||||||
.as_tenant()
|
|
||||||
.with_cwd(self.cwd.as_ref())
|
|
||||||
.with_env(self.env.clone().into_iter().collect())
|
|
||||||
.with_process(self.process.as_ref())
|
|
||||||
.with_no_new_privs(self.no_new_privs)
|
|
||||||
.with_process(self.process.as_ref())
|
|
||||||
.with_container_args(self.command.clone())
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_key_val<T, U>(s: &str) -> Result<(T, U), Box<dyn Error + Send + Sync + 'static>>
|
|
||||||
where
|
|
||||||
T: std::str::FromStr,
|
|
||||||
T::Err: Error + Send + Sync + 'static,
|
|
||||||
U: std::str::FromStr,
|
|
||||||
U::Err: Error + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
let pos = s
|
|
||||||
.find('=')
|
|
||||||
.ok_or_else(|| format!("invalid KEY=value: no `=` found in `{}`", s))?;
|
|
||||||
Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
|
|
||||||
}
|
|
|
@ -1,224 +0,0 @@
|
||||||
//! Contains functions related to printing information about system running Youki
|
|
||||||
use std::{collections::HashSet, fs, path::Path};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use clap::Clap;
|
|
||||||
use procfs::{CpuInfo, Meminfo};
|
|
||||||
|
|
||||||
use cgroups::{self, common::CgroupSetup, v2::controller_type::ControllerType};
|
|
||||||
|
|
||||||
/// Show information about the system
|
|
||||||
#[derive(Clap, Debug)]
|
|
||||||
pub struct Info {}
|
|
||||||
|
|
||||||
impl Info {
|
|
||||||
pub fn exec(&self) -> Result<()> {
|
|
||||||
print_youki();
|
|
||||||
print_kernel();
|
|
||||||
print_os();
|
|
||||||
print_hardware();
|
|
||||||
print_cgroups();
|
|
||||||
print_namespaces();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// print Version of Youki
|
|
||||||
pub fn print_youki() {
|
|
||||||
println!("{:<18}{}", "Version", env!("CARGO_PKG_VERSION"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Print Kernel Release, Version and Architecture
|
|
||||||
pub fn print_kernel() {
|
|
||||||
let uname = nix::sys::utsname::uname();
|
|
||||||
println!("{:<18}{}", "Kernel-Release", uname.release());
|
|
||||||
println!("{:<18}{}", "Kernel-Version", uname.version());
|
|
||||||
println!("{:<18}{}", "Architecture", uname.machine());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prints OS Distribution information
|
|
||||||
// see https://www.freedesktop.org/software/systemd/man/os-release.html
|
|
||||||
pub fn print_os() {
|
|
||||||
if let Some(os) = try_read_os_from("/etc/os-release") {
|
|
||||||
println!("{:<18}{}", "Operating System", os);
|
|
||||||
} else if let Some(os) = try_read_os_from("/usr/lib/os-release") {
|
|
||||||
println!("{:<18}{}", "Operating System", os);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper function to read the OS Distribution info
|
|
||||||
fn try_read_os_from<P: AsRef<Path>>(path: P) -> Option<String> {
|
|
||||||
let os_release = path.as_ref();
|
|
||||||
if !os_release.exists() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(release_content) = fs::read_to_string(path) {
|
|
||||||
let pretty = find_parameter(&release_content, "PRETTY_NAME");
|
|
||||||
|
|
||||||
if let Some(pretty) = pretty {
|
|
||||||
return Some(pretty.trim_matches('"').to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = find_parameter(&release_content, "NAME");
|
|
||||||
let version = find_parameter(&release_content, "VERSION");
|
|
||||||
|
|
||||||
if let Some((name, version)) = name.zip(version) {
|
|
||||||
return Some(format!(
|
|
||||||
"{} {}",
|
|
||||||
name.trim_matches('"'),
|
|
||||||
version.trim_matches('"')
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper function to find keyword values in OS info string
|
|
||||||
fn find_parameter<'a>(content: &'a str, param_name: &str) -> Option<&'a str> {
|
|
||||||
content
|
|
||||||
.lines()
|
|
||||||
.find(|l| l.starts_with(param_name))
|
|
||||||
.and_then(|l| l.split_terminator('=').last())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Print Hardware information of system
|
|
||||||
pub fn print_hardware() {
|
|
||||||
if let Ok(cpu_info) = CpuInfo::new() {
|
|
||||||
println!("{:<18}{}", "Cores", cpu_info.num_cores());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(mem_info) = Meminfo::new() {
|
|
||||||
println!(
|
|
||||||
"{:<18}{}",
|
|
||||||
"Total Memory",
|
|
||||||
mem_info.mem_total / u64::pow(1024, 2)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Print cgroups info of system
|
|
||||||
pub fn print_cgroups() {
|
|
||||||
let cgroup_setup = cgroups::common::get_cgroup_setup();
|
|
||||||
if let Ok(cgroup_setup) = &cgroup_setup {
|
|
||||||
println!("{:<18}{}", "Cgroup setup", cgroup_setup);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("Cgroup mounts");
|
|
||||||
if let Ok(v1_mounts) = cgroups::v1::util::list_supported_mount_points() {
|
|
||||||
let mut v1_mounts: Vec<String> = v1_mounts
|
|
||||||
.iter()
|
|
||||||
.map(|kv| format!(" {:<16}{}", kv.0.to_string(), kv.1.display()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
v1_mounts.sort();
|
|
||||||
for cgroup_mount in v1_mounts {
|
|
||||||
println!("{}", cgroup_mount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let unified = cgroups::v2::util::get_unified_mount_point();
|
|
||||||
if let Ok(mount_point) = &unified {
|
|
||||||
println!(" {:<16}{}", "unified", mount_point.display());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(cgroup_setup) = cgroup_setup {
|
|
||||||
if let Ok(unified) = &unified {
|
|
||||||
if matches!(cgroup_setup, CgroupSetup::Hybrid | CgroupSetup::Unified) {
|
|
||||||
if let Ok(controllers) = cgroups::v2::util::get_available_controllers(unified) {
|
|
||||||
println!("CGroup v2 controllers");
|
|
||||||
let active_controllers: HashSet<ControllerType> =
|
|
||||||
controllers.into_iter().collect();
|
|
||||||
for controller in cgroups::v2::controller_type::CONTROLLER_TYPES {
|
|
||||||
let status = if active_controllers.contains(controller) {
|
|
||||||
"attached"
|
|
||||||
} else {
|
|
||||||
"detached"
|
|
||||||
};
|
|
||||||
|
|
||||||
println!(" {:<16}{}", controller.to_string(), status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(config) = read_kernel_config() {
|
|
||||||
let display = FeatureDisplay::with_status("device", "attached", "detached");
|
|
||||||
print_feature_status(&config, "CONFIG_CGROUP_BPF", display);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_kernel_config() -> Option<String> {
|
|
||||||
let uname = nix::sys::utsname::uname();
|
|
||||||
let kernel_config = Path::new("/boot").join(format!("config-{}", uname.release()));
|
|
||||||
if !kernel_config.exists() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs::read_to_string(kernel_config).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_namespaces() {
|
|
||||||
if let Some(content) = read_kernel_config() {
|
|
||||||
if let Some(ns_enabled) = find_parameter(&content, "CONFIG_NAMESPACES") {
|
|
||||||
if ns_enabled == "y" {
|
|
||||||
println!("{:<18}enabled", "Namespaces");
|
|
||||||
} else {
|
|
||||||
println!("{:<18}disabled", "Namespaces");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mount namespace is always enabled if namespaces are enabled
|
|
||||||
println!(" {:<16}enabled", "mount");
|
|
||||||
print_feature_status(&content, "CONFIG_UTS_NS", FeatureDisplay::new("uts"));
|
|
||||||
print_feature_status(&content, "CONFIG_IPC_NS", FeatureDisplay::new("ipc"));
|
|
||||||
print_feature_status(&content, "CONFIG_USER_NS", FeatureDisplay::new("user"));
|
|
||||||
print_feature_status(&content, "CONFIG_PID_NS", FeatureDisplay::new("pid"));
|
|
||||||
print_feature_status(&content, "CONFIG_NET_NS", FeatureDisplay::new("network"));
|
|
||||||
// While the CONFIG_CGROUP_NS kernel feature exists, it is obsolete and should not be used. CGroup namespaces
|
|
||||||
// are instead enabled with CONFIG_CGROUPS.
|
|
||||||
print_feature_status(&content, "CONFIG_CGROUPS", FeatureDisplay::new("cgroup"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_feature_status(config: &str, feature: &str, display: FeatureDisplay) {
|
|
||||||
if let Some(status_flag) = find_parameter(config, feature) {
|
|
||||||
let status = if status_flag == "y" {
|
|
||||||
display.enabled
|
|
||||||
} else {
|
|
||||||
display.disabled
|
|
||||||
};
|
|
||||||
|
|
||||||
println!(" {:<16}{}", display.name, status);
|
|
||||||
} else {
|
|
||||||
println!(" {:<16}{}", display.name, display.disabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FeatureDisplay<'a> {
|
|
||||||
name: &'a str,
|
|
||||||
enabled: &'a str,
|
|
||||||
disabled: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> FeatureDisplay<'a> {
|
|
||||||
fn new(name: &'a str) -> Self {
|
|
||||||
Self {
|
|
||||||
name,
|
|
||||||
enabled: "enabled",
|
|
||||||
disabled: "disabled",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_status(name: &'a str, enabled: &'a str, disabled: &'a str) -> Self {
|
|
||||||
Self {
|
|
||||||
name,
|
|
||||||
enabled,
|
|
||||||
disabled,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
//! Contains functionality of kill container command
|
|
||||||
use std::{convert::TryInto, path::PathBuf};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use clap::Clap;
|
|
||||||
|
|
||||||
use crate::{commands::load_container, signal::Signal};
|
|
||||||
|
|
||||||
/// Send the specified signal to the container
|
|
||||||
#[derive(Clap, Debug)]
|
|
||||||
pub struct Kill {
|
|
||||||
#[clap(required = true)]
|
|
||||||
container_id: String,
|
|
||||||
signal: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Kill {
|
|
||||||
pub fn exec(&self, root_path: PathBuf) -> Result<()> {
|
|
||||||
let mut container = load_container(root_path, &self.container_id)?;
|
|
||||||
let signal: Signal = self.signal.as_str().try_into()?;
|
|
||||||
container.kill(signal)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
//! Contains Functionality of list container command
|
|
||||||
use std::fs;
|
|
||||||
use std::io;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use chrono::{DateTime, Local};
|
|
||||||
use clap::Clap;
|
|
||||||
use tabwriter::TabWriter;
|
|
||||||
|
|
||||||
use crate::container::{state::State, Container};
|
|
||||||
|
|
||||||
/// List created containers
|
|
||||||
#[derive(Clap, Debug)]
|
|
||||||
pub struct List {}
|
|
||||||
|
|
||||||
impl List {
|
|
||||||
/// lists all existing containers
|
|
||||||
pub fn exec(&self, root_path: PathBuf) -> Result<()> {
|
|
||||||
let root_path = fs::canonicalize(root_path)?;
|
|
||||||
let mut content = String::new();
|
|
||||||
// all containers' data is stored in their respective dir in root directory
|
|
||||||
// so we iterate through each and print the various info
|
|
||||||
for container_dir in fs::read_dir(root_path)? {
|
|
||||||
let container_dir = container_dir?.path();
|
|
||||||
let state_file = State::file_path(&container_dir);
|
|
||||||
if !state_file.exists() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let container = Container::load(container_dir)?;
|
|
||||||
let pid = if let Some(pid) = container.pid() {
|
|
||||||
pid.to_string()
|
|
||||||
} else {
|
|
||||||
"".to_owned()
|
|
||||||
};
|
|
||||||
|
|
||||||
let user_name = container.creator().unwrap_or_default();
|
|
||||||
|
|
||||||
let created = if let Some(utc) = container.created() {
|
|
||||||
let local: DateTime<Local> = DateTime::from(utc);
|
|
||||||
local.to_rfc3339_opts(chrono::SecondsFormat::Secs, false)
|
|
||||||
} else {
|
|
||||||
"".to_owned()
|
|
||||||
};
|
|
||||||
|
|
||||||
content.push_str(&format!(
|
|
||||||
"{}\t{}\t{}\t{}\t{}\t{}\n",
|
|
||||||
container.id(),
|
|
||||||
pid,
|
|
||||||
container.status(),
|
|
||||||
container.bundle().display(),
|
|
||||||
created,
|
|
||||||
user_name.to_string_lossy()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut tab_writer = TabWriter::new(io::stdout());
|
|
||||||
writeln!(&mut tab_writer, "ID\tPID\tSTATUS\tBUNDLE\tCREATED\tCREATOR")?;
|
|
||||||
write!(&mut tab_writer, "{}", content)?;
|
|
||||||
tab_writer.flush()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
use anyhow::{bail, Context, Result};
|
|
||||||
use std::{fs, path::Path};
|
|
||||||
|
|
||||||
use crate::container::Container;
|
|
||||||
|
|
||||||
pub mod create;
|
|
||||||
pub mod delete;
|
|
||||||
pub mod events;
|
|
||||||
pub mod exec;
|
|
||||||
pub mod info;
|
|
||||||
pub mod kill;
|
|
||||||
pub mod list;
|
|
||||||
pub mod pause;
|
|
||||||
pub mod ps;
|
|
||||||
pub mod resume;
|
|
||||||
pub mod run;
|
|
||||||
pub mod spec_json;
|
|
||||||
pub mod start;
|
|
||||||
pub mod state;
|
|
||||||
|
|
||||||
fn load_container<P: AsRef<Path>>(root_path: P, container_id: &str) -> Result<Container> {
|
|
||||||
// resolves relative paths, symbolic links etc. and get complete path
|
|
||||||
let root_path = fs::canonicalize(&root_path)
|
|
||||||
.with_context(|| format!("failed to canonicalize {}", root_path.as_ref().display()))?;
|
|
||||||
// the state of the container is stored in a directory named after the container id
|
|
||||||
let container_root = root_path.join(container_id);
|
|
||||||
if !container_root.exists() {
|
|
||||||
bail!("{} does not exist.", container_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
Container::load(container_root)
|
|
||||||
.with_context(|| format!("could not load state for container {}", container_id))
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
//! Contains functionality of pause container command
|
|
||||||
use crate::commands::load_container;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use clap::Clap;
|
|
||||||
|
|
||||||
/// Suspend the processes within the container
|
|
||||||
#[derive(Clap, Debug)]
|
|
||||||
pub struct Pause {
|
|
||||||
#[clap(required = true)]
|
|
||||||
pub container_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pausing a container indicates suspending all processes in given container
|
|
||||||
// This uses Freezer cgroup to suspend and resume processes
|
|
||||||
// For more information see :
|
|
||||||
// https://man7.org/linux/man-pages/man7/cgroups.7.html
|
|
||||||
// https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt
|
|
||||||
impl Pause {
|
|
||||||
pub fn exec(&self, root_path: PathBuf) -> Result<()> {
|
|
||||||
log::debug!("start pausing container {}", self.container_id);
|
|
||||||
let mut container = load_container(root_path, &self.container_id)?;
|
|
||||||
container
|
|
||||||
.pause()
|
|
||||||
.with_context(|| format!("failed to pause container {}", self.container_id))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
use crate::{container::Container, utils};
|
|
||||||
use anyhow::{bail, Context, Result};
|
|
||||||
use cgroups;
|
|
||||||
use clap::{self, Clap};
|
|
||||||
use std::{path::PathBuf, process::Command};
|
|
||||||
|
|
||||||
/// Display the processes inside the container
|
|
||||||
#[derive(Clap, Debug)]
|
|
||||||
pub struct Ps {
|
|
||||||
/// format to display processes: table or json (default: "table")
|
|
||||||
#[clap(short, long, default_value = "table")]
|
|
||||||
format: String,
|
|
||||||
#[clap(required = true)]
|
|
||||||
pub container_id: String,
|
|
||||||
/// options will be passed to the ps utility
|
|
||||||
#[clap(setting = clap::ArgSettings::Last)]
|
|
||||||
ps_options: Vec<String>,
|
|
||||||
}
|
|
||||||
impl Ps {
|
|
||||||
pub fn exec(&self, root_path: PathBuf) -> Result<()> {
|
|
||||||
let container_root = root_path.join(&self.container_id);
|
|
||||||
if !container_root.exists() {
|
|
||||||
bail!("{} doesn't exist.", self.container_id)
|
|
||||||
}
|
|
||||||
let container = Container::load(container_root)?;
|
|
||||||
if container.root.exists() {
|
|
||||||
let config_absolute_path = container.root.join("config.json");
|
|
||||||
log::debug!("load spec from {:?}", config_absolute_path);
|
|
||||||
let spec = oci_spec::runtime::Spec::load(config_absolute_path)?;
|
|
||||||
log::debug!("spec: {:?}", spec);
|
|
||||||
let cgroups_path = utils::get_cgroup_path(
|
|
||||||
spec.linux()
|
|
||||||
.as_ref()
|
|
||||||
.context("no linux in spec")?
|
|
||||||
.cgroups_path(),
|
|
||||||
container.id(),
|
|
||||||
);
|
|
||||||
let systemd_cgroup = container
|
|
||||||
.systemd()
|
|
||||||
.context("could not determine cgroup manager")?;
|
|
||||||
let cmanager = cgroups::common::create_cgroup_manager(cgroups_path, systemd_cgroup)?;
|
|
||||||
let pids: Vec<i32> = cmanager
|
|
||||||
.get_all_pids()?
|
|
||||||
.iter()
|
|
||||||
.map(|pid| pid.as_raw())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if self.format == "json" {
|
|
||||||
println!("{}", serde_json::to_string(&pids)?);
|
|
||||||
} else if self.format == "table" {
|
|
||||||
let default_ps_options = vec![String::from("-ef")];
|
|
||||||
let ps_options = if self.ps_options.is_empty() {
|
|
||||||
&default_ps_options
|
|
||||||
} else {
|
|
||||||
&self.ps_options
|
|
||||||
};
|
|
||||||
let output = Command::new("ps").args(ps_options).output()?;
|
|
||||||
if !output.status.success() {
|
|
||||||
println!("{}", std::str::from_utf8(&output.stderr)?);
|
|
||||||
} else {
|
|
||||||
let lines = std::str::from_utf8(&output.stdout)?;
|
|
||||||
let lines: Vec<&str> = lines.split('\n').collect();
|
|
||||||
let pid_index = get_pid_index(lines[0])?;
|
|
||||||
println!("{}", &lines[0]);
|
|
||||||
for line in &lines[1..] {
|
|
||||||
if line.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let fields: Vec<&str> = line.split_whitespace().collect();
|
|
||||||
let pid: i32 = fields[pid_index].parse()?;
|
|
||||||
if pids.contains(&pid) {
|
|
||||||
println!("{}", line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_pid_index(title: &str) -> Result<usize> {
|
|
||||||
let titles = title.split_whitespace();
|
|
||||||
|
|
||||||
for (index, name) in titles.enumerate() {
|
|
||||||
if name == "PID" {
|
|
||||||
return Ok(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bail!("could't find PID field in ps output");
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
//! Contains functionality of resume container command
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use clap::Clap;
|
|
||||||
|
|
||||||
use crate::commands::load_container;
|
|
||||||
|
|
||||||
/// Resume the processes within the container
|
|
||||||
#[derive(Clap, Debug)]
|
|
||||||
pub struct Resume {
|
|
||||||
#[clap(required = true)]
|
|
||||||
pub container_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resuming a container indicates resuming all processes in given container from paused state
|
|
||||||
// This uses Freezer cgroup to suspend and resume processes
|
|
||||||
// For more information see :
|
|
||||||
// https://man7.org/linux/man-pages/man7/cgroups.7.html
|
|
||||||
// https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt
|
|
||||||
impl Resume {
|
|
||||||
pub fn exec(&self, root_path: PathBuf) -> Result<()> {
|
|
||||||
log::debug!("start resuming container {}", self.container_id);
|
|
||||||
let mut container = load_container(root_path, &self.container_id)?;
|
|
||||||
container
|
|
||||||
.resume()
|
|
||||||
.with_context(|| format!("failed to resume container {}", self.container_id))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use crate::container::builder::ContainerBuilder;
|
|
||||||
use crate::syscall::syscall::create_syscall;
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use clap::Clap;
|
|
||||||
|
|
||||||
/// Create a container and immediately start it
|
|
||||||
#[derive(Clap, Debug)]
|
|
||||||
pub struct Run {
|
|
||||||
/// File to write pid of the container created
|
|
||||||
// note that in the end, container is just another process
|
|
||||||
#[clap(short, long)]
|
|
||||||
pid_file: Option<PathBuf>,
|
|
||||||
/// path to the bundle directory, containing config.json and root filesystem
|
|
||||||
#[clap(short, long, default_value = ".")]
|
|
||||||
bundle: PathBuf,
|
|
||||||
/// Unix socket (file) path , which will receive file descriptor of the writing end of the pseudoterminal
|
|
||||||
#[clap(short, long)]
|
|
||||||
console_socket: Option<PathBuf>,
|
|
||||||
/// Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)
|
|
||||||
#[clap(long, default_value = "0")]
|
|
||||||
preserve_fds: i32,
|
|
||||||
/// name of the container instance to be started
|
|
||||||
#[clap(required = true)]
|
|
||||||
pub container_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Run {
|
|
||||||
pub fn exec(&self, root_path: PathBuf, systemd_cgroup: bool) -> Result<()> {
|
|
||||||
let syscall = create_syscall();
|
|
||||||
let mut container = ContainerBuilder::new(self.container_id.clone(), syscall.as_ref())
|
|
||||||
.with_pid_file(self.pid_file.as_ref())
|
|
||||||
.with_console_socket(self.console_socket.as_ref())
|
|
||||||
.with_root_path(root_path)
|
|
||||||
.with_preserved_fds(self.preserve_fds)
|
|
||||||
.as_init(&self.bundle)
|
|
||||||
.with_systemd(systemd_cgroup)
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
container
|
|
||||||
.start()
|
|
||||||
.with_context(|| format!("failed to start container {}", self.container_id))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
use clap::Clap;
|
|
||||||
use nix;
|
|
||||||
use oci_spec::runtime::Mount;
|
|
||||||
use oci_spec::runtime::{
|
|
||||||
LinuxBuilder, LinuxIdMappingBuilder, LinuxNamespace, LinuxNamespaceBuilder, LinuxNamespaceType,
|
|
||||||
Spec,
|
|
||||||
};
|
|
||||||
use serde_json::to_writer_pretty;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
/// Command generates a config.json
|
|
||||||
#[derive(Clap, Debug)]
|
|
||||||
pub struct SpecJson {
|
|
||||||
/// Generate a configuration for a rootless container
|
|
||||||
#[clap(long)]
|
|
||||||
pub rootless: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_default() -> Result<Spec> {
|
|
||||||
Ok(Spec::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_rootless() -> Result<Spec> {
|
|
||||||
// Remove network and user namespace from the default spec
|
|
||||||
let mut namespaces: Vec<LinuxNamespace> = oci_spec::runtime::get_default_namespaces()
|
|
||||||
.into_iter()
|
|
||||||
.filter(|ns| {
|
|
||||||
ns.typ() != LinuxNamespaceType::Network && ns.typ() != LinuxNamespaceType::User
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Add user namespace
|
|
||||||
namespaces.push(
|
|
||||||
LinuxNamespaceBuilder::default()
|
|
||||||
.typ(LinuxNamespaceType::User)
|
|
||||||
.build()?,
|
|
||||||
);
|
|
||||||
|
|
||||||
let uid = nix::unistd::geteuid().as_raw();
|
|
||||||
let gid = nix::unistd::getegid().as_raw();
|
|
||||||
|
|
||||||
let linux = LinuxBuilder::default()
|
|
||||||
.namespaces(namespaces)
|
|
||||||
.uid_mappings(vec![LinuxIdMappingBuilder::default()
|
|
||||||
.host_id(uid)
|
|
||||||
.container_id(0_u32)
|
|
||||||
.size(1_u32)
|
|
||||||
.build()?])
|
|
||||||
.gid_mappings(vec![LinuxIdMappingBuilder::default()
|
|
||||||
.host_id(gid)
|
|
||||||
.container_id(0_u32)
|
|
||||||
.size(1_u32)
|
|
||||||
.build()?])
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
// Prepare the mounts
|
|
||||||
|
|
||||||
let mut mounts: Vec<Mount> = oci_spec::runtime::get_default_mounts();
|
|
||||||
for mount in &mut mounts {
|
|
||||||
if mount.destination().eq(Path::new("/sys")) {
|
|
||||||
mount
|
|
||||||
.set_source(Some(PathBuf::from("/sys")))
|
|
||||||
.set_typ(Some(String::from("none")))
|
|
||||||
.set_options(Some(vec![
|
|
||||||
"rbind".to_string(),
|
|
||||||
"nosuid".to_string(),
|
|
||||||
"noexec".to_string(),
|
|
||||||
"nodev".to_string(),
|
|
||||||
"ro".to_string(),
|
|
||||||
]));
|
|
||||||
} else {
|
|
||||||
let options: Vec<String> = mount
|
|
||||||
.options()
|
|
||||||
.as_ref()
|
|
||||||
.unwrap_or(&vec![])
|
|
||||||
.iter()
|
|
||||||
.filter(|&o| !o.starts_with("gid=") && !o.starts_with("uid="))
|
|
||||||
.map(|o| o.to_string())
|
|
||||||
.collect();
|
|
||||||
mount.set_options(Some(options));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut spec = get_default()?;
|
|
||||||
spec.set_linux(Some(linux)).set_mounts(Some(mounts));
|
|
||||||
Ok(spec)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// spec Cli command
|
|
||||||
impl SpecJson {
|
|
||||||
pub fn exec(&self) -> Result<()> {
|
|
||||||
let spec = if self.rootless {
|
|
||||||
get_rootless()?
|
|
||||||
} else {
|
|
||||||
get_default()?
|
|
||||||
};
|
|
||||||
|
|
||||||
// write data to config.json
|
|
||||||
to_writer_pretty(&File::create("config.json")?, &spec)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
// Tests become unstable if not serial. The cause is not known.
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::utils::create_temp_dir;
|
|
||||||
use serial_test::serial;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[serial]
|
|
||||||
fn test_spec_json() -> Result<()> {
|
|
||||||
let spec = get_rootless()?;
|
|
||||||
let tmpdir = create_temp_dir("test_spec_json").expect("failed to create temp dir");
|
|
||||||
let path = tmpdir.path().join("config.json");
|
|
||||||
to_writer_pretty(&File::create(path)?, &spec)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
//! Starts execution of the container
|
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use clap::Clap;
|
|
||||||
|
|
||||||
use crate::commands::load_container;
|
|
||||||
|
|
||||||
/// Start a previously created container
|
|
||||||
#[derive(Clap, Debug)]
|
|
||||||
pub struct Start {
|
|
||||||
#[clap(required = true)]
|
|
||||||
pub container_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Start {
|
|
||||||
pub fn exec(&self, root_path: PathBuf) -> Result<()> {
|
|
||||||
let mut container = load_container(root_path, &self.container_id)?;
|
|
||||||
container
|
|
||||||
.start()
|
|
||||||
.with_context(|| format!("failed to start container {}", self.container_id))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
use std::fs;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use clap::Clap;
|
|
||||||
|
|
||||||
use crate::container::Container;
|
|
||||||
|
|
||||||
/// Show the container state
|
|
||||||
#[derive(Clap, Debug)]
|
|
||||||
pub struct State {
|
|
||||||
#[clap(required = true)]
|
|
||||||
pub container_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
pub fn exec(&self, root_path: PathBuf) -> Result<()> {
|
|
||||||
let root_path = fs::canonicalize(root_path)?;
|
|
||||||
let container_root = root_path.join(&self.container_id);
|
|
||||||
let container = Container::load(container_root)?;
|
|
||||||
println!("{}", serde_json::to_string_pretty(&container.state)?);
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
121
src/logger.rs
121
src/logger.rs
|
@ -1,121 +0,0 @@
|
||||||
//! Default Youki Logger
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use std::io::{stderr, Write};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::{
|
|
||||||
fs::{File, OpenOptions},
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use log::{LevelFilter, Log, Metadata, Record};
|
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
|
|
||||||
/// Public global variables to access logger and logfile
|
|
||||||
pub static YOUKI_LOGGER: OnceCell<YoukiLogger> = OnceCell::new();
|
|
||||||
pub static LOG_FILE: OnceCell<Option<File>> = OnceCell::new();
|
|
||||||
|
|
||||||
/// If in debug mode, default level is debug to get maximum logging
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
const DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::Debug;
|
|
||||||
|
|
||||||
/// If not in debug mode, default level is warn to get important logs
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
const DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::Warn;
|
|
||||||
|
|
||||||
/// Initialize the logger, must be called before accessing the logger
|
|
||||||
/// Multiple parts might call this at once, but the actual initialization
|
|
||||||
/// is done only once due to use of OnceCell
|
|
||||||
pub fn init(log_file: Option<PathBuf>) -> Result<()> {
|
|
||||||
// If file exists, ignore, else create and open the file
|
|
||||||
let _log_file = LOG_FILE.get_or_init(|| -> Option<File> {
|
|
||||||
// set the log level if specified in env variable or set to default
|
|
||||||
let level_filter = if let Ok(log_level_str) = env::var("YOUKI_LOG_LEVEL") {
|
|
||||||
LevelFilter::from_str(&log_level_str).unwrap_or(DEFAULT_LOG_LEVEL)
|
|
||||||
} else {
|
|
||||||
DEFAULT_LOG_LEVEL
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a new logger, or get existing if already created
|
|
||||||
let logger = YOUKI_LOGGER.get_or_init(|| YoukiLogger::new(level_filter.to_level()));
|
|
||||||
|
|
||||||
log::set_logger(logger)
|
|
||||||
.map(|()| log::set_max_level(level_filter))
|
|
||||||
.expect("set logger failed");
|
|
||||||
|
|
||||||
// Create and open log file
|
|
||||||
log_file.as_ref().map(|log_file_path| {
|
|
||||||
OpenOptions::new()
|
|
||||||
.create(true)
|
|
||||||
.write(true)
|
|
||||||
.truncate(false)
|
|
||||||
.open(log_file_path)
|
|
||||||
.expect("failed opening log file ")
|
|
||||||
})
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Youki's custom Logger
|
|
||||||
pub struct YoukiLogger {
|
|
||||||
/// Indicates level up to which logs are to be printed
|
|
||||||
level: Option<log::Level>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl YoukiLogger {
|
|
||||||
/// Create new logger
|
|
||||||
pub fn new(level: Option<log::Level>) -> Self {
|
|
||||||
Self { level }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implements Log interface given by log crate, so we can use its functionality
|
|
||||||
impl Log for YoukiLogger {
|
|
||||||
/// Check if level of given log is enabled or not
|
|
||||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
|
||||||
if let Some(level) = self.level {
|
|
||||||
metadata.level() <= level
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Function to carry out logging
|
|
||||||
fn log(&self, record: &Record) {
|
|
||||||
if self.enabled(record.metadata()) {
|
|
||||||
let log_msg = match (record.file(), record.line()) {
|
|
||||||
(Some(file), Some(line)) => format!(
|
|
||||||
"[{} {}:{}] {} {}\r",
|
|
||||||
record.level(),
|
|
||||||
file,
|
|
||||||
line,
|
|
||||||
chrono::Local::now().to_rfc3339(),
|
|
||||||
record.args()
|
|
||||||
),
|
|
||||||
(_, _) => format!(
|
|
||||||
"[{}] {} {}\r",
|
|
||||||
record.level(),
|
|
||||||
chrono::Local::now().to_rfc3339(),
|
|
||||||
record.args()
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
// if log file is set, write to it, else write to stderr
|
|
||||||
if let Some(mut log_file) = LOG_FILE.get().unwrap().as_ref() {
|
|
||||||
let _ = writeln!(log_file, "{}", log_msg);
|
|
||||||
} else {
|
|
||||||
let _ = writeln!(stderr(), "{}", log_msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Flush logs to file
|
|
||||||
fn flush(&self) {
|
|
||||||
if let Some(mut log_file) = LOG_FILE.get().unwrap().as_ref() {
|
|
||||||
log_file.flush().expect("Failed to flush");
|
|
||||||
} else {
|
|
||||||
stderr().flush().expect("Failed to flush");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
167
src/main.rs
167
src/main.rs
|
@ -1,167 +0,0 @@
|
||||||
//! # Youki
|
|
||||||
//! Container Runtime written in Rust, inspired by [railcar](https://github.com/oracle/railcar)
|
|
||||||
//! This crate provides a container runtime which can be used by a high-level container runtime to run containers.
|
|
||||||
|
|
||||||
use std::fs;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use anyhow::bail;
|
|
||||||
use anyhow::Context;
|
|
||||||
use anyhow::Result;
|
|
||||||
use clap::{crate_version, Clap};
|
|
||||||
|
|
||||||
use nix::sys::stat::Mode;
|
|
||||||
use nix::unistd::getuid;
|
|
||||||
use youki::commands::create;
|
|
||||||
use youki::commands::delete;
|
|
||||||
use youki::commands::events;
|
|
||||||
use youki::commands::exec;
|
|
||||||
use youki::commands::info;
|
|
||||||
use youki::commands::kill;
|
|
||||||
use youki::commands::list;
|
|
||||||
use youki::commands::pause;
|
|
||||||
use youki::commands::ps;
|
|
||||||
use youki::commands::resume;
|
|
||||||
use youki::commands::run;
|
|
||||||
use youki::commands::spec_json;
|
|
||||||
use youki::commands::start;
|
|
||||||
use youki::commands::state;
|
|
||||||
use youki::rootless::rootless_required;
|
|
||||||
use youki::utils::{self, create_dir_all_with_mode};
|
|
||||||
|
|
||||||
// High-level commandline option definition
|
|
||||||
// This takes global options as well as individual commands as specified in [OCI runtime-spec](https://github.com/opencontainers/runtime-spec/blob/master/runtime.md)
|
|
||||||
// Also check [runc commandline documentation](https://github.com/opencontainers/runc/blob/master/man/runc.8.md) for more explanation
|
|
||||||
#[derive(Clap, Debug)]
|
|
||||||
#[clap(version = crate_version!(), author = "youki team")]
|
|
||||||
struct Opts {
|
|
||||||
/// root directory to store container state
|
|
||||||
#[clap(short, long)]
|
|
||||||
root: Option<PathBuf>,
|
|
||||||
#[clap(short, long)]
|
|
||||||
log: Option<PathBuf>,
|
|
||||||
#[clap(long)]
|
|
||||||
log_format: Option<String>,
|
|
||||||
/// Enable systemd cgroup manager, rather then use the cgroupfs directly.
|
|
||||||
#[clap(short, long)]
|
|
||||||
systemd_cgroup: bool,
|
|
||||||
/// command to actually manage container
|
|
||||||
#[clap(subcommand)]
|
|
||||||
subcmd: SubCommand,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subcommands accepted by Youki, confirming with [OCI runtime-spec](https://github.com/opencontainers/runtime-spec/blob/master/runtime.md)
|
|
||||||
// Also for a short information, check [runc commandline documentation](https://github.com/opencontainers/runc/blob/master/man/runc.8.md)
|
|
||||||
#[derive(Clap, Debug)]
|
|
||||||
enum SubCommand {
|
|
||||||
#[clap(version = crate_version!(), author = "youki team")]
|
|
||||||
Create(create::Create),
|
|
||||||
#[clap(version = crate_version!(), author = "youki team")]
|
|
||||||
Start(start::Start),
|
|
||||||
#[clap(version = crate_version!(), author = "youki team")]
|
|
||||||
Run(run::Run),
|
|
||||||
#[clap(version = crate_version!(), author = "youki team")]
|
|
||||||
Exec(exec::Exec),
|
|
||||||
#[clap(version = crate_version!(), author = "youki team")]
|
|
||||||
Kill(kill::Kill),
|
|
||||||
#[clap(version = crate_version!(), author = "youki team")]
|
|
||||||
Delete(delete::Delete),
|
|
||||||
#[clap(version = crate_version!(), author = "youki team")]
|
|
||||||
State(state::State),
|
|
||||||
#[clap(version = crate_version!(), author = "youki team")]
|
|
||||||
Info(info::Info),
|
|
||||||
#[clap(version = crate_version!(), author = "youki team")]
|
|
||||||
Spec(spec_json::SpecJson),
|
|
||||||
#[clap(version = crate_version!(), author = "youki team")]
|
|
||||||
List(list::List),
|
|
||||||
#[clap(version = crate_version!(), author = "youki team")]
|
|
||||||
Pause(pause::Pause),
|
|
||||||
#[clap(version = crate_version!(), author = "youki team")]
|
|
||||||
Resume(resume::Resume),
|
|
||||||
#[clap(version = crate_version!(), author = "youki team")]
|
|
||||||
Events(events::Events),
|
|
||||||
#[clap(version = crate_version!(), author = "youki team", setting=clap::AppSettings::AllowLeadingHyphen)]
|
|
||||||
Ps(ps::Ps),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is the entry point in the container runtime. The binary is run by a high-level container runtime,
|
|
||||||
/// with various flags passed. This parses the flags, creates and manages appropriate resources.
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
// A malicious container can gain access to the host machine by modifying youki's host
|
|
||||||
// binary and infect it with malicious code. This vulnerability was first discovered
|
|
||||||
// in runc and was assigned as CVE-2019-5736, but it also affects youki.
|
|
||||||
//
|
|
||||||
// The fix is to copy /proc/self/exe in an anonymous file descriptor (created via memfd_create),
|
|
||||||
// seal it and re-execute it. Because the final step is re-execution, this needs to be done at
|
|
||||||
// the beginning of this process.
|
|
||||||
//
|
|
||||||
// Ref: https://github.com/opencontainers/runc/commit/0a8e4117e7f715d5fbeef398405813ce8e88558b
|
|
||||||
// Ref: https://github.com/lxc/lxc/commit/6400238d08cdf1ca20d49bafb85f4e224348bf9d
|
|
||||||
pentacle::ensure_sealed().context("Failed to seal /proc/self/exe")?;
|
|
||||||
|
|
||||||
let opts = Opts::parse();
|
|
||||||
|
|
||||||
if let Err(e) = youki::logger::init(opts.log) {
|
|
||||||
eprintln!("log init failed: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
let root_path = determine_root_path(opts.root)?;
|
|
||||||
let systemd_cgroup = opts.systemd_cgroup;
|
|
||||||
|
|
||||||
match opts.subcmd {
|
|
||||||
SubCommand::Create(create) => create.exec(root_path, systemd_cgroup),
|
|
||||||
SubCommand::Start(start) => start.exec(root_path),
|
|
||||||
SubCommand::Run(run) => run.exec(root_path, systemd_cgroup),
|
|
||||||
SubCommand::Exec(exec) => exec.exec(root_path),
|
|
||||||
SubCommand::Kill(kill) => kill.exec(root_path),
|
|
||||||
SubCommand::Delete(delete) => delete.exec(root_path),
|
|
||||||
SubCommand::State(state) => state.exec(root_path),
|
|
||||||
SubCommand::Info(info) => info.exec(),
|
|
||||||
SubCommand::List(list) => list.exec(root_path),
|
|
||||||
SubCommand::Spec(spec) => spec.exec(),
|
|
||||||
SubCommand::Pause(pause) => pause.exec(root_path),
|
|
||||||
SubCommand::Resume(resume) => resume.exec(root_path),
|
|
||||||
SubCommand::Events(events) => events.exec(root_path),
|
|
||||||
SubCommand::Ps(ps) => ps.exec(root_path),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn determine_root_path(root_path: Option<PathBuf>) -> Result<PathBuf> {
|
|
||||||
if let Some(path) = root_path {
|
|
||||||
return Ok(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !rootless_required() {
|
|
||||||
let default = PathBuf::from("/run/youki");
|
|
||||||
utils::create_dir_all(&default)?;
|
|
||||||
return Ok(default);
|
|
||||||
}
|
|
||||||
|
|
||||||
// see https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
|
||||||
if let Ok(path) = std::env::var("XDG_RUNTIME_DIR") {
|
|
||||||
return Ok(PathBuf::from(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
// XDG_RUNTIME_DIR is not set, try the usual location
|
|
||||||
let uid = getuid().as_raw();
|
|
||||||
let runtime_dir = PathBuf::from(format!("/run/user/{}", uid));
|
|
||||||
if create_dir_all_with_mode(&runtime_dir, uid, Mode::S_IRWXU).is_ok() {
|
|
||||||
return Ok(runtime_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(path) = std::env::var("HOME") {
|
|
||||||
if let Ok(resolved) = fs::canonicalize(path) {
|
|
||||||
let run_dir = resolved.join(".youki/run");
|
|
||||||
if create_dir_all_with_mode(&run_dir, uid, Mode::S_IRWXU).is_ok() {
|
|
||||||
return Ok(run_dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let tmp_dir = PathBuf::from(format!("/tmp/youki/{}", uid));
|
|
||||||
if create_dir_all_with_mode(&tmp_dir, uid, Mode::S_IRWXU).is_ok() {
|
|
||||||
return Ok(tmp_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("could not find a storage location with suitable permissions for the current user");
|
|
||||||
}
|
|
Loading…
Reference in New Issue