use crate::{namespaces::Namespaces, process::channel, process::fork};
use anyhow::{Context, Error, Result};
use libcgroups::common::CgroupManager;
use nix::unistd::{close, write};
use nix::unistd::{Gid, Pid, Uid};
use oci_spec::runtime::{LinuxNamespaceType, LinuxResources};
use procfs::process::Process;
use std::convert::From;
use super::args::{ContainerArgs, ContainerType};
use super::container_init_process::container_init_process;
pub fn container_intermediate_process(
args: &ContainerArgs,
intermediate_chan: &mut (channel::IntermediateSender, channel::IntermediateReceiver),
init_chan: &mut (channel::InitSender, channel::InitReceiver),
main_sender: &mut channel::MainSender,
) -> Result<Pid> {
let (inter_sender, inter_receiver) = intermediate_chan;
let (init_sender, init_receiver) = init_chan;
let command = &args.syscall;
let spec = &args.spec;
let linux = spec.linux().as_ref().context("no linux in spec")?;
let namespaces = Namespaces::from(linux.namespaces().as_ref());
apply_cgroups(
args.cgroup_manager.as_ref(),
linux.resources().as_ref(),
matches!(args.container_type, ContainerType::InitContainer),
)
.context("failed to apply cgroups")?;
if let Some(user_namespace) = namespaces.get(LinuxNamespaceType::User) {
namespaces
.unshare_or_setns(user_namespace)
.with_context(|| format!("failed to enter user namespace: {:?}", user_namespace))?;
if user_namespace.path().is_none() {
log::debug!("creating new user namespace");
prctl::set_dumpable(true).unwrap();
main_sender.identifier_mapping_request()?;
inter_receiver.wait_for_mapping_ack()?;
prctl::set_dumpable(false).unwrap();
}
command.set_id(Uid::from_raw(0), Gid::from_raw(0)).context(
"failed to configure uid and gid root in the beginning of a new user namespace",
)?;
}
let proc = spec.process().as_ref().context("no process in spec")?;
if let Some(rlimits) = proc.rlimits() {
for rlimit in rlimits {
command.set_rlimit(rlimit).context("failed to set rlimit")?;
}
}
if let Some(pid_namespace) = namespaces.get(LinuxNamespaceType::Pid) {
namespaces
.unshare_or_setns(pid_namespace)
.with_context(|| format!("failed to enter pid namespace: {:?}", pid_namespace))?;
}
let pid = fork::container_fork(|| {
init_sender
.close()
.context("failed to close receiver in init process")?;
inter_sender
.close()
.context("failed to close sender in the intermediate process")?;
match container_init_process(args, main_sender, init_receiver) {
Ok(_) => unreachable!("successful exec should never reach here"),
Err(e) => {
if let ContainerType::TenantContainer { exec_notify_fd } = args.container_type {
let buf = format!("{}", e);
write(exec_notify_fd, buf.as_bytes())?;
close(exec_notify_fd)?;
}
Err(e)
}
}
})?;
if let ContainerType::TenantContainer { exec_notify_fd } = args.container_type {
close(exec_notify_fd)?;
}
main_sender
.intermediate_ready(pid)
.context("failed to send child ready from intermediate process")?;
main_sender
.close()
.context("failed to close unused main sender")?;
inter_sender
.close()
.context("failed to close sender in the intermediate process")?;
init_sender
.close()
.context("failed to close unused init sender")?;
Ok(pid)
}
fn apply_cgroups<C: CgroupManager + ?Sized>(
cmanager: &C,
resources: Option<&LinuxResources>,
init: bool,
) -> Result<(), Error> {
let pid = Pid::from_raw(Process::myself()?.pid());
cmanager
.add_task(pid)
.with_context(|| format!("failed to add task {} to cgroup manager", pid))?;
if let Some(resources) = resources {
if init {
let controller_opt = libcgroups::common::ControllerOpt {
resources,
freezer_state: None,
oom_score_adj: None,
disable_oom_killer: false,
};
cmanager
.apply(&controller_opt)
.context("failed to apply resource limits to cgroup")?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::apply_cgroups;
use anyhow::Result;
use libcgroups::test_manager::TestManager;
use nix::unistd::Pid;
use oci_spec::runtime::LinuxResources;
use procfs::process::Process;
#[test]
fn apply_cgroup_init() -> Result<()> {
let cmanager = TestManager::default();
let resources = LinuxResources::default();
apply_cgroups(&cmanager, Some(&resources), true)?;
assert!(cmanager.get_add_task_args().len() == 1);
assert_eq!(
cmanager.get_add_task_args()[0],
Pid::from_raw(Process::myself()?.pid())
);
assert!(cmanager.apply_called());
Ok(())
}
#[test]
fn apply_cgroup_tenant() -> Result<()> {
let cmanager = TestManager::default();
let resources = LinuxResources::default();
apply_cgroups(&cmanager, Some(&resources), false)?;
assert_eq!(
cmanager.get_add_task_args()[0],
Pid::from_raw(Process::myself()?.pid())
);
assert!(!cmanager.apply_called());
Ok(())
}
#[test]
fn apply_cgroup_no_resources() -> Result<()> {
let cmanager = TestManager::default();
apply_cgroups(&cmanager, None, true)?;
assert_eq!(
cmanager.get_add_task_args()[0],
Pid::from_raw(Process::myself()?.pid())
);
assert!(!cmanager.apply_called());
Ok(())
}
}