use super::ContainerStatistics;
use super::FilesystemOperation;
use super::Mount;
use super::StatCategory;
use super::UnixSocketConfiguration;
use crate::containerization::GIB;
use crate::containerization::network::Dns;
use crate::containerization::network::Hosts;
use crate::containerization::network::Interface;
use crate::containerization::process::ExitStatus;
use crate::containerization::process::LinuxProcess;
use crate::containerization::process::LinuxProcessConfiguration;
use crate::containerization::process::Signal;
use crate::containerization::strings;
use crate::containerization::vm::BootLog;
use crate::containerization::vm::VmResources;
use crate::containerization::vm::VzVirtualMachineInstance;
use crate::containerization::vm::VzVirtualMachineManager;
use crate::containerization_oci;
use crate::containerization_os::terminal;
use crate::error::Error;
use crate::platform;
use crate::platform::ffi;
use std::collections::BTreeMap;
use std::os::fd::FromRawFd;
use std::os::fd::OwnedFd;
use std::path::Path;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub enum SeccompProfile {
#[default]
Unconfined,
Default,
Profile(containerization_oci::runtime::LinuxSeccomp),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Configuration {
pub process: LinuxProcessConfiguration,
pub cpus: u32,
pub memory_in_bytes: u64,
pub hostname: Option<String>,
pub sysctl: BTreeMap<String, String>,
pub interfaces: Vec<Interface>,
pub sockets: Vec<UnixSocketConfiguration>,
pub mounts: Vec<Mount>,
pub masked_paths: Vec<String>,
pub readonly_paths: Vec<String>,
pub dns: Option<Dns>,
pub hosts: Option<Hosts>,
pub virtualization: bool,
pub boot_log: Option<BootLog>,
pub oci_runtime_path: Option<String>,
pub seccomp_profile: SeccompProfile,
pub use_init: bool,
}
impl Default for Configuration {
fn default() -> Self {
Self {
process: LinuxProcessConfiguration::default(),
cpus: 4,
memory_in_bytes: GIB,
hostname: None,
sysctl: BTreeMap::new(),
interfaces: Vec::new(),
sockets: Vec::new(),
mounts: LinuxContainer::default_mounts(),
masked_paths: LinuxContainer::default_masked_paths(),
readonly_paths: LinuxContainer::default_readonly_paths(),
dns: None,
hosts: None,
virtualization: false,
boot_log: None,
oci_runtime_path: None,
seccomp_profile: SeccompProfile::Unconfined,
use_init: false,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CopyInOptions {
pub mode: u32,
pub create_parents: bool,
pub chunk_size: usize,
}
impl Default for CopyInOptions {
fn default() -> Self {
Self {
mode: 0o644,
create_parents: true,
chunk_size: LinuxContainer::DEFAULT_COPY_CHUNK_SIZE,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CopyOutOptions {
pub create_parents: bool,
pub chunk_size: usize,
}
impl Default for CopyOutOptions {
fn default() -> Self {
Self {
create_parents: true,
chunk_size: LinuxContainer::DEFAULT_COPY_CHUNK_SIZE,
}
}
}
pub struct LinuxContainer {
pub(crate) handle: ffi::CzLinuxContainer,
}
unsafe impl Send for LinuxContainer {}
unsafe impl Sync for LinuxContainer {}
impl LinuxContainer {
pub const MAX_ID_LENGTH: usize = 64;
pub const DEFAULT_COPY_CHUNK_SIZE: usize = 1024 * 1024;
pub fn new(
id: &str,
rootfs: Mount,
writable_layer: Option<Mount>,
vmm: &VzVirtualMachineManager,
vm: VmResources,
configuration: Configuration,
) -> Result<Self, Error> {
let (has_writable_layer, writable_layer) = writable_layer_crossing(writable_layer);
platform::outcome(
ffi::cz_linux_container_new(
id,
rootfs,
has_writable_layer,
writable_layer,
vmm.handle.duplicate(),
vm.cpus,
vm.memory_in_bytes,
configuration,
),
format!("make {id}"),
)
.map(|outcome| Self {
handle: outcome.linux_container(),
})
}
pub fn new_with(
id: &str,
rootfs: Mount,
writable_layer: Option<Mount>,
vmm: &VzVirtualMachineManager,
vm: VmResources,
configuration: impl FnOnce(&mut Configuration),
) -> Result<Self, Error> {
let mut config = Configuration::default();
configuration(&mut config);
Self::new(id, rootfs, writable_layer, vmm, vm, config)
}
pub fn id(&self) -> String {
self.handle.id()
}
pub fn rootfs(&self) -> Mount {
self.handle.rootfs().mount()
}
pub fn writable_layer(&self) -> Option<Mount> {
self.handle.writable_layer().optional_mount()
}
pub fn config(&self) -> Configuration {
let ((), config) = platform::filled(|receive| self.handle.config(Configuration::default(), receive));
config.expect("Swift hands back the configuration it fills")
}
pub fn vm(&self) -> VmResources {
VmResources {
cpus: self.handle.vm_cpus(),
memory_in_bytes: self.handle.vm_memory_in_bytes(),
}
}
pub fn cpus(&self) -> u32 {
self.config().cpus
}
pub fn memory_in_bytes(&self) -> u64 {
self.config().memory_in_bytes
}
pub fn interfaces(&self) -> Vec<Interface> {
self.config().interfaces
}
pub fn create(&self) -> Result<(), Error> {
platform::outcome(self.handle.create(), format!("create {}", self.id())).map(|_| ())
}
pub fn start(&self) -> Result<(), Error> {
platform::outcome(self.handle.start(), format!("start {}", self.id())).map(|_| ())
}
pub fn stop(&self) -> Result<(), Error> {
platform::outcome(self.handle.stop(), format!("stop {}", self.id())).map(|_| ())
}
pub fn kill(&self, signal: Signal) -> Result<(), Error> {
platform::outcome(self.handle.kill(signal.raw_value), format!("signal {}", self.id())).map(|_| ())
}
pub fn wait(&self, timeout_in_seconds: Option<i64>) -> Result<ExitStatus, Error> {
platform::outcome(self.handle.wait(timeout_in_seconds), format!("wait for {}", self.id()))
.map(|outcome| platform::exit_status(&outcome))
}
pub fn resize(&self, to: terminal::Size) -> Result<(), Error> {
platform::outcome(self.handle.resize(to.width, to.height), format!("resize {}", self.id())).map(|_| ())
}
pub fn exec(&self, id: &str, configuration: LinuxProcessConfiguration) -> Result<LinuxProcess, Error> {
platform::outcome(
self.handle.exec(id, configuration),
format!("exec {id} in {}", self.id()),
)
.map(|outcome| LinuxProcess {
handle: outcome.linux_process(),
})
}
pub fn exec_with(
&self,
id: &str,
configuration: impl FnOnce(&mut LinuxProcessConfiguration) + Send + 'static,
) -> Result<LinuxProcess, Error> {
platform::outcome(
self.handle.exec_with(
id,
LinuxProcessConfiguration::default(),
platform::ConfigureProcess::new(configuration),
),
format!("exec {id} in {}", self.id()),
)
.map(|outcome| LinuxProcess {
handle: outcome.linux_process(),
})
}
pub fn dial_vsock(&self, port: u32) -> Result<OwnedFd, Error> {
platform::outcome(
self.handle.dial_vsock(port),
format!("dial vsock port {port} in {}", self.id()),
)
.map(|outcome| unsafe { OwnedFd::from_raw_fd(outcome.int32()) })
}
pub fn with_virtual_machine_instance<T>(
&self,
body: impl FnOnce(&VzVirtualMachineInstance) -> Result<T, Error>,
) -> Result<T, Error> {
let instance = platform::outcome(
self.handle.virtual_machine_instance(),
format!("reach {}'s virtual machine", self.id()),
)?;
body(&VzVirtualMachineInstance {
handle: instance.virtual_machine_instance(),
})
}
pub fn close_stdin(&self) -> Result<(), Error> {
platform::outcome(self.handle.close_stdin(), format!("close {}'s stdin", self.id())).map(|_| ())
}
pub fn statistics(&self, categories: StatCategory) -> Result<ContainerStatistics, Error> {
platform::outcome(
self.handle.statistics(categories.raw_value),
format!("read {}'s statistics", self.id()),
)
.map(|outcome| outcome.container_statistics())
}
pub fn filesystem_operation(&self, operation: FilesystemOperation, path: &str) -> Result<(), Error> {
platform::outcome(
self.handle.filesystem_operation(operation.into(), path),
format!("{operation:?} {path} in {}", self.id()),
)
.map(|_| ())
}
pub fn copy_in(&self, source: &Path, destination: &Path, options: CopyInOptions) -> Result<(), Error> {
platform::outcome(
self.handle.copy_in(
&source.display().to_string(),
&destination.display().to_string(),
options.mode,
options.create_parents,
options.chunk_size,
),
format!("copy {} into {}", source.display(), self.id()),
)
.map(|_| ())
}
pub fn copy_out(&self, source: &Path, destination: &Path, options: CopyOutOptions) -> Result<(), Error> {
platform::outcome(
self.handle.copy_out(
&source.display().to_string(),
&destination.display().to_string(),
options.create_parents,
options.chunk_size,
),
format!("copy {} out of {}", source.display(), self.id()),
)
.map(|_| ())
}
pub fn default_mounts() -> Vec<Mount> {
let defaults = ["nosuid", "noexec", "nodev"];
vec![
Mount::any("proc", "proc", "/proc", &[], &[]),
Mount::any("sysfs", "sysfs", "/sys", &defaults, &[]),
Mount::any("devtmpfs", "none", "/dev", &["nosuid", "mode=755"], &[]),
Mount::any("mqueue", "mqueue", "/dev/mqueue", &defaults, &[]),
Mount::any(
"tmpfs",
"tmpfs",
"/dev/shm",
&["nosuid", "noexec", "nodev", "mode=1777", "size=65536k"],
&[],
),
Mount::any("cgroup2", "none", "/sys/fs/cgroup", &defaults, &[]),
Mount::any(
"devpts",
"devpts",
"/dev/pts",
&["nosuid", "noexec", "newinstance", "gid=5", "mode=0620", "ptmxmode=0666"],
&[],
),
]
}
pub fn default_masked_paths() -> Vec<String> {
strings(&[
"/proc/asound",
"/proc/acpi",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware",
"/sys/devices/virtual/powercap",
])
}
pub fn default_readonly_paths() -> Vec<String> {
strings(&["/proc/bus", "/proc/fs", "/proc/irq", "/proc/sys", "/proc/sysrq-trigger"])
}
pub fn default_oci_mounts() -> Vec<Mount> {
let defaults = ["nosuid", "noexec", "nodev"];
vec![
Mount::any("proc", "proc", "/proc", &[], &[]),
Mount::any("tmpfs", "tmpfs", "/dev", &["nosuid", "mode=755", "size=65536k"], &[]),
Mount::any(
"devpts",
"devpts",
"/dev/pts",
&["nosuid", "noexec", "newinstance", "gid=5", "mode=0620", "ptmxmode=0666"],
&[],
),
Mount::any("sysfs", "sysfs", "/sys", &defaults, &[]),
Mount::any("mqueue", "mqueue", "/dev/mqueue", &defaults, &[]),
Mount::any(
"tmpfs",
"tmpfs",
"/dev/shm",
&["nosuid", "noexec", "nodev", "mode=1777", "size=65536k"],
&[],
),
Mount::any("cgroup2", "none", "/sys/fs/cgroup", &defaults, &[]),
]
}
}
fn writable_layer_crossing(writable_layer: Option<Mount>) -> (bool, Mount) {
match writable_layer {
Some(writable_layer) => (true, writable_layer),
None => (false, Mount::any("", "", "", &[], &[])),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_a_configuration_as_containerization_does() {
let configuration = Configuration::default();
assert_eq!(configuration.cpus, 4);
assert_eq!(configuration.memory_in_bytes, GIB);
assert_eq!(configuration.mounts.len(), 7);
assert!(
configuration
.masked_paths
.contains(&"/proc/kcore".to_string())
);
assert!(
configuration
.readonly_paths
.contains(&"/proc/sys".to_string())
);
assert_eq!(configuration.dns, None);
assert_eq!(configuration.seccomp_profile, SeccompProfile::Unconfined);
assert_eq!(configuration.process.working_directory, "/");
assert_eq!(
configuration.process.environment_variables,
[format!("PATH={}", LinuxProcessConfiguration::DEFAULT_PATH)]
);
}
}