use std::fs;
use std::io::Write;
use std::os::fd::{AsRawFd, OwnedFd};
use std::path::PathBuf;
use std::rc::Rc;
use libcgroups::common::CgroupManager;
use nix::unistd::Pid;
use oci_spec::runtime::Spec;
use super::{Container, ContainerStatus};
use crate::error::{CreateContainerError, LibcontainerError, MissingSpecError};
use crate::notify_socket::NotifyListener;
use crate::process::args::{ContainerArgs, ContainerType};
use crate::process::intel_rdt::{cleanup_intel_rdt, setup_intel_rdt};
use crate::process::{self};
use crate::syscall::syscall::SyscallType;
use crate::user_ns::UserNamespaceConfig;
use crate::utils;
use crate::utils::PathBufExt;
use crate::workload::Executor;
pub(super) struct ContainerBuilderImpl {
pub container_type: ContainerType,
pub syscall: SyscallType,
pub use_systemd: bool,
pub container_id: String,
pub spec: Rc<Spec>,
pub rootfs: PathBuf,
pub pid_file: Option<PathBuf>,
pub console_socket: Option<OwnedFd>,
pub user_ns_config: Option<UserNamespaceConfig>,
pub notify_path: PathBuf,
pub container: Option<Container>,
pub preserve_fds: i32,
pub detached: bool,
pub executor: Box<dyn Executor>,
pub no_pivot: bool,
pub stdin: Option<OwnedFd>,
pub stdout: Option<OwnedFd>,
pub stderr: Option<OwnedFd>,
pub as_sibling: bool,
pub sub_cgroup_path: Option<String>,
#[allow(dead_code)]
pub process_label: Option<String>,
}
impl ContainerBuilderImpl {
pub(super) fn create(&mut self) -> Result<Pid, LibcontainerError> {
match self.run_container() {
Ok(pid) => Ok(pid),
Err(outer) => {
let cleanup_err = if self.is_init_container() {
self.cleanup_container().err()
} else {
None
};
Err(CreateContainerError::new(outer, cleanup_err).into())
}
}
}
fn is_init_container(&self) -> bool {
matches!(self.container_type, ContainerType::InitContainer)
}
fn run_container(&mut self) -> Result<Pid, LibcontainerError> {
let linux = self.spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;
let base_cgroups_path = utils::get_cgroup_path(linux.cgroups_path(), &self.container_id);
let mut final_cgroups_path = base_cgroups_path;
if let Some(sub_cgroup_path) = &self.sub_cgroup_path
&& sub_cgroup_path != "/"
{
let potential_path = final_cgroups_path.join(sub_cgroup_path);
let normalized = potential_path.normalize();
if !normalized.starts_with(&final_cgroups_path) {
return Err(LibcontainerError::OtherCgroup(format!(
"{} is not a sub cgroup path",
sub_cgroup_path
)));
}
final_cgroups_path = normalized;
}
let cgroup_config = libcgroups::common::CgroupConfig {
cgroup_path: final_cgroups_path,
systemd_cgroup: self.use_systemd || self.user_ns_config.is_some(),
container_name: self.container_id.to_owned(),
};
let process = self
.spec
.process()
.as_ref()
.ok_or(MissingSpecError::Process)?;
let notify_listener = NotifyListener::new(&self.notify_path)?;
if let Some(oom_score_adj) = process.oom_score_adj() {
tracing::debug!("Set OOM score to {}", oom_score_adj);
let mut f = fs::File::create("/proc/self/oom_score_adj").map_err(|err| {
tracing::error!("failed to open /proc/self/oom_score_adj: {}", err);
LibcontainerError::OtherIO(err)
})?;
f.write_all(oom_score_adj.to_string().as_bytes())
.map_err(|err| {
tracing::error!("failed to write to /proc/self/oom_score_adj: {}", err);
LibcontainerError::OtherIO(err)
})?;
}
if linux.namespaces().is_some() {
prctl::set_dumpable(false).map_err(|e| {
LibcontainerError::Other(format!(
"error in setting dumpable to false : {}",
nix::errno::Errno::from_raw(e)
))
})?;
}
let container_args = ContainerArgs {
container_type: self.container_type,
syscall: self.syscall,
spec: Rc::clone(&self.spec),
rootfs: self.rootfs.to_owned(),
console_socket: self.console_socket.as_ref().map(|c| c.as_raw_fd()),
notify_listener,
preserve_fds: self.preserve_fds,
container: self.container.to_owned(),
user_ns_config: self.user_ns_config.to_owned(),
cgroup_config,
detached: self.detached,
executor: self.executor.clone(),
no_pivot: self.no_pivot,
stdin: self.stdin.as_ref().map(|x| x.as_raw_fd()),
stdout: self.stdout.as_ref().map(|x| x.as_raw_fd()),
stderr: self.stderr.as_ref().map(|x| x.as_raw_fd()),
as_sibling: self.as_sibling,
pid_file: self.pid_file.to_owned(),
};
let init_pid = process::container_main_process::container_main_process(&container_args)
.map_err(|err| {
tracing::error!("failed to run container process {}", err);
LibcontainerError::MainProcess(err)
})?;
let mut intel_rdt_dir = None;
let mut intel_rdt_monitoring_dir = None;
if let Some(linux) = self.spec.linux() {
if let Some(intel_rdt) = linux.intel_rdt() {
let container_id = self.container.as_ref().map(|c| c.id());
let (dir, mon_dir) = setup_intel_rdt(container_id, &init_pid, intel_rdt)?;
intel_rdt_dir = dir;
intel_rdt_monitoring_dir = mon_dir;
}
}
if let Some(container) = &mut self.container {
container
.set_status(ContainerStatus::Created)
.set_creator(nix::unistd::geteuid().as_raw())
.set_pid(init_pid.as_raw())
.set_intel_rdt_dir(intel_rdt_dir)
.set_intel_rdt_monitoring_dir(intel_rdt_monitoring_dir)
.save()?;
}
Ok(init_pid)
}
fn cleanup_container(&self) -> Result<(), LibcontainerError> {
let linux = self.spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;
let cgroups_path = utils::get_cgroup_path(linux.cgroups_path(), &self.container_id);
let cmanager =
libcgroups::common::create_cgroup_manager(libcgroups::common::CgroupConfig {
cgroup_path: cgroups_path,
systemd_cgroup: self.use_systemd || self.user_ns_config.is_some(),
container_name: self.container_id.to_string(),
})?;
let mut errors = Vec::new();
if let Err(e) = cmanager.remove() {
tracing::error!(error = ?e, "failed to remove cgroup manager");
errors.push(e.to_string());
}
if let Some(container) = &self.container {
if let Err(e) = cleanup_intel_rdt(
container.intel_rdt_dir().map(|p| p.as_path()),
container.intel_rdt_monitoring_dir().map(|p| p.as_path()),
container.clean_up_intel_rdt_subdirectory(),
container.id(),
) {
tracing::error!(id = ?container.id(), error = ?e, "failed to cleanup intel rdt");
errors.push(e.to_string());
}
if container.root.exists() {
if let Err(e) = fs::remove_dir_all(&container.root) {
tracing::error!(container_root = ?container.root, error = ?e, "failed to delete container root");
errors.push(e.to_string());
}
}
}
if !errors.is_empty() {
return Err(LibcontainerError::Other(format!(
"failed to cleanup container: {}",
errors.join(";")
)));
}
Ok(())
}
}