1
0
mirror of https://github.com/containers/youki synced 2024-11-23 01:11:58 +01:00

Merge pull request #1930 from yihuaf/yihuaf/container-error

implemented thiserror for containers - Part 5
This commit is contained in:
Toru Komatsu 2023-05-17 21:02:47 +09:00 committed by GitHub
commit 6633786ea4
Signed by: GitHub
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 365 additions and 269 deletions

@ -12,11 +12,6 @@ pub enum AppArmorError {
profile: String,
source: std::io::Error,
},
#[error("failed to read AppArmor profile: {source} {path}")]
ReadProfile {
path: String,
source: std::io::Error,
},
#[error(transparent)]
EnsureProcfs(#[from] utils::EnsureProcfsError),
}
@ -26,12 +21,8 @@ type Result<T> = std::result::Result<T, AppArmorError>;
const ENABLED_PARAMETER_PATH: &str = "/sys/module/apparmor/parameters/enabled";
/// Checks if AppArmor has been enabled on the system.
pub fn is_enabled() -> Result<bool> {
let aa_enabled =
fs::read_to_string(ENABLED_PARAMETER_PATH).map_err(|e| AppArmorError::ReadProfile {
path: ENABLED_PARAMETER_PATH.to_string(),
source: e,
})?;
pub fn is_enabled() -> std::result::Result<bool, std::io::Error> {
let aa_enabled = fs::read_to_string(ENABLED_PARAMETER_PATH)?;
Ok(aa_enabled.starts_with('Y'))
}

@ -1,7 +1,7 @@
use crate::error::{ErrInvalidID, LibcontainerError};
use crate::workload::default::DefaultExecutor;
use crate::workload::{Executor, ExecutorManager};
use crate::{syscall::Syscall, utils::PathBufExt};
use anyhow::{anyhow, bail, Context, Result};
use std::path::PathBuf;
use super::{init_builder::InitContainerBuilder, tenant_builder::TenantContainerBuilder};
@ -93,18 +93,20 @@ impl<'a> ContainerBuilder<'a> {
///
/// In addition, IDs that can't be used to represent a file name
/// (such as . or ..) are rejected.
pub fn validate_id(self) -> Result<Self> {
pub fn validate_id(self) -> Result<Self, LibcontainerError> {
let container_id = self.container_id.clone();
if container_id.is_empty() {
return Err(anyhow!("invalid container ID format: {:?}", container_id));
Err(ErrInvalidID::Empty)?;
}
if container_id == "." || container_id == ".." {
return Err(anyhow!("invalid container ID format: {:?}", container_id));
Err(ErrInvalidID::FileName)?;
}
for c in container_id.chars() {
match c {
'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '+' | '-' | '.' => (),
_ => return Err(anyhow!("invalid container ID format: {:?}", container_id)),
_ => Err(ErrInvalidID::InvalidChars(c))?,
}
}
Ok(self)
@ -166,11 +168,12 @@ impl<'a> ContainerBuilder<'a> {
/// )
/// .with_root_path("/run/containers/youki").expect("invalid root path");
/// ```
pub fn with_root_path<P: Into<PathBuf>>(mut self, path: P) -> Result<Self> {
pub fn with_root_path<P: Into<PathBuf>>(mut self, path: P) -> Result<Self, LibcontainerError> {
let path = path.into();
self.root_path = path
.canonicalize_safely()
.with_context(|| format!("failed to canonicalize root path {path:?}"))?;
self.root_path = path.canonicalize_safely().map_err(|err| {
tracing::error!(?path, ?err, "failed to canonicalize root path");
LibcontainerError::InvalidInput(format!("invalid root path {path:?}: {err:?}"))
})?;
Ok(self)
}
@ -190,15 +193,15 @@ impl<'a> ContainerBuilder<'a> {
/// )
/// .with_pid_file(Some("/var/run/docker.pid")).expect("invalid pid file");
/// ```
pub fn with_pid_file<P: Into<PathBuf>>(mut self, path: Option<P>) -> Result<Self> {
self.pid_file = match path {
Some(path) => {
let p = path.into();
Some(
p.canonicalize_safely()
.with_context(|| format!("failed to canonicalize pid file {p:?}"))?,
)
}
pub fn with_pid_file<P: Into<PathBuf>>(
mut self,
path: Option<P>,
) -> Result<Self, LibcontainerError> {
self.pid_file = match path.map(|p| p.into()) {
Some(path) => Some(path.canonicalize_safely().map_err(|err| {
tracing::error!(?path, ?err, "failed to canonicalize pid file");
LibcontainerError::InvalidInput(format!("invalid pid file path {path:?}: {err:?}"))
})?),
None => None,
};
@ -259,9 +262,12 @@ impl<'a> ContainerBuilder<'a> {
/// )
/// .with_executor(vec![Box::<DefaultExecutor>::default()]);
/// ```
pub fn with_executor(mut self, executors: Vec<Box<dyn Executor>>) -> Result<Self> {
pub fn with_executor(
mut self,
executors: Vec<Box<dyn Executor>>,
) -> Result<Self, LibcontainerError> {
if executors.is_empty() {
bail!("executors must not be empty");
return Err(LibcontainerError::NoExecutors);
};
self.executor_manager = ExecutorManager { executors };
Ok(self)

@ -1,5 +1,6 @@
use super::{Container, ContainerStatus};
use crate::{
error::{LibcontainerError, MissingSpecError},
hooks,
notify_socket::NotifyListener,
process::{
@ -12,7 +13,6 @@ use crate::{
utils,
workload::ExecutorManager,
};
use anyhow::{bail, Context, Result};
use libcgroups::common::CgroupManager;
use nix::unistd::Pid;
use oci_spec::runtime::Spec;
@ -51,16 +51,14 @@ pub(super) struct ContainerBuilderImpl<'a> {
}
impl<'a> ContainerBuilderImpl<'a> {
pub(super) fn create(&mut self) -> Result<Pid> {
match self.run_container().context("failed to create container") {
pub(super) fn create(&mut self) -> Result<Pid, LibcontainerError> {
match self.run_container() {
Ok(pid) => Ok(pid),
Err(outer) => {
// Only the init container should be cleaned up in the case of
// an error.
if matches!(self.container_type, ContainerType::InitContainer) {
if let Err(inner) = self.cleanup_container() {
return Err(outer.context(inner));
}
self.cleanup_container()?;
}
Err(outer)
@ -68,8 +66,8 @@ impl<'a> ContainerBuilderImpl<'a> {
}
}
fn run_container(&mut self) -> Result<Pid> {
let linux = self.spec.linux().as_ref().context("no linux in spec")?;
fn run_container(&mut self) -> Result<Pid, 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,
@ -79,8 +77,13 @@ impl<'a> ContainerBuilderImpl<'a> {
cgroups_path,
self.use_systemd || self.rootless.is_some(),
&self.container_id,
)?;
let process = self.spec.process().as_ref().context("No process in spec")?;
)
.map_err(|err| LibcontainerError::Cgroups(err.to_string()))?;
let process = self
.spec
.process()
.as_ref()
.ok_or(MissingSpecError::Process)?;
if matches!(self.container_type, ContainerType::InitContainer) {
if let Some(hooks) = self.spec.hooks() {
@ -105,8 +108,15 @@ impl<'a> ContainerBuilderImpl<'a> {
// fork(2) so this will always be propagated properly.
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")?;
f.write_all(oom_score_adj.to_string().as_bytes())?;
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)
})?;
}
// Make the process non-dumpable, to avoid various race conditions that
@ -140,11 +150,19 @@ impl<'a> ContainerBuilderImpl<'a> {
};
let (init_pid, need_to_clean_up_intel_rdt_dir) =
process::container_main_process::container_main_process(&container_args)?;
process::container_main_process::container_main_process(&container_args).map_err(
|err| {
tracing::error!(?err, "failed to run container process");
LibcontainerError::MainProcess(err)
},
)?;
// if file to write the pid to is specified, write pid of the child
if let Some(pid_file) = &self.pid_file {
fs::write(pid_file, format!("{init_pid}")).context("failed to write pid file")?;
fs::write(pid_file, format!("{init_pid}")).map_err(|err| {
tracing::error!("failed to write pid to file: {}", err);
LibcontainerError::OtherIO(err)
})?;
}
if let Some(container) = &mut self.container {
@ -154,15 +172,14 @@ impl<'a> ContainerBuilderImpl<'a> {
.set_creator(nix::unistd::geteuid().as_raw())
.set_pid(init_pid.as_raw())
.set_clean_up_intel_rdt_directory(need_to_clean_up_intel_rdt_dir)
.save()
.context("Failed to save container state")?;
.save()?;
}
Ok(init_pid)
}
fn cleanup_container(&self) -> Result<()> {
let linux = self.spec.linux().as_ref().context("no linux in spec")?;
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,
@ -172,37 +189,37 @@ impl<'a> ContainerBuilderImpl<'a> {
cgroups_path,
self.use_systemd || self.rootless.is_some(),
&self.container_id,
)?;
)
.map_err(|err| LibcontainerError::Cgroups(err.to_string()))?;
let mut errors = Vec::new();
if let Err(e) = cmanager.remove().context("failed to remove cgroup") {
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 Some(true) = container.clean_up_intel_rdt_subdirectory() {
if let Err(e) = delete_resctrl_subdirectory(container.id()).with_context(|| {
format!(
"failed to delete resctrl subdirectory: {:?}",
container.id()
)
}) {
if let Err(e) = delete_resctrl_subdirectory(container.id()) {
tracing::error!(id = ?container.id(), error = ?e, "failed to delete resctrl subdirectory");
errors.push(e.to_string());
}
}
if container.root.exists() {
if let Err(e) = fs::remove_dir_all(&container.root)
.with_context(|| format!("could not delete {:?}", container.root))
{
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() {
bail!("failed to cleanup container: {}", errors.join(";"));
return Err(LibcontainerError::Other(format!(
"failed to cleanup container: {}",
errors.join(";")
)));
}
Ok(())

@ -1,20 +1,18 @@
use crate::config::YoukiConfig;
use crate::container::{ContainerStatus, State};
use crate::error::LibcontainerError;
use crate::syscall::syscall::create_syscall;
use chrono::DateTime;
use chrono::Utc;
use nix::unistd::Pid;
use procfs::process::Process;
use std::collections::HashMap;
use std::ffi::OsString;
use std::fs;
use std::path::{Path, PathBuf};
use anyhow::Result;
use chrono::DateTime;
use nix::unistd::Pid;
use chrono::Utc;
use procfs::process::Process;
use crate::config::YoukiConfig;
use crate::syscall::syscall::create_syscall;
use crate::container::{ContainerStatus, State};
/// Structure representing the container data
#[derive(Debug, Clone)]
pub struct Container {
@ -40,10 +38,17 @@ impl Container {
pid: Option<i32>,
bundle: &Path,
container_root: &Path,
) -> Result<Self> {
let container_root = fs::canonicalize(container_root)?;
) -> Result<Self, LibcontainerError> {
let container_root = fs::canonicalize(container_root).map_err(|err| {
LibcontainerError::InvalidInput(format!(
"invalid container root {container_root:?}: {err:?}"
))
})?;
let bundle = fs::canonicalize(bundle).map_err(|err| {
LibcontainerError::InvalidInput(format!("invalid bundle {bundle:?}: {err:?}"))
})?;
let state = State::new(container_id, status, pid, bundle);
let state = State::new(container_id, status, pid, fs::canonicalize(bundle)?);
Ok(Self {
state,
root: container_root,
@ -117,12 +122,12 @@ impl Container {
self
}
pub fn systemd(&self) -> Option<bool> {
pub fn systemd(&self) -> bool {
self.state.use_systemd
}
pub fn set_systemd(&mut self, should_use: bool) -> &mut Self {
self.state.use_systemd = Some(should_use);
self.state.use_systemd = should_use;
self
}
@ -151,7 +156,7 @@ impl Container {
self
}
pub fn refresh_status(&mut self) -> Result<()> {
pub fn refresh_status(&mut self) -> Result<(), LibcontainerError> {
let new_status = match self.pid() {
Some(pid) => {
// Note that Process::new does not spawn a new process
@ -180,14 +185,14 @@ impl Container {
Ok(())
}
pub fn refresh_state(&mut self) -> Result<&mut Self> {
pub fn refresh_state(&mut self) -> Result<&mut Self, LibcontainerError> {
let state = State::load(&self.root)?;
self.state = state;
Ok(self)
}
pub fn load(container_root: PathBuf) -> Result<Self> {
pub fn load(container_root: PathBuf) -> Result<Self, LibcontainerError> {
let state = State::load(&container_root)?;
let mut container = Self {
state,
@ -197,14 +202,14 @@ impl Container {
Ok(container)
}
pub fn save(&self) -> Result<()> {
pub fn save(&self) -> Result<(), LibcontainerError> {
tracing::debug!("Save container status: {:?} in {:?}", self, self.root);
self.state.save(&self.root)?;
Ok(())
}
pub fn spec(&self) -> Result<YoukiConfig> {
pub fn spec(&self) -> Result<YoukiConfig, LibcontainerError> {
let spec = YoukiConfig::load(&self.root)?;
Ok(spec)
}
@ -225,6 +230,7 @@ pub struct CheckpointOptions {
mod tests {
use super::*;
use anyhow::Context;
use anyhow::Result;
use serial_test::serial;
#[test]
@ -280,11 +286,11 @@ mod tests {
#[test]
fn test_get_set_systemd() {
let mut container = Container::default();
assert_eq!(container.systemd(), None);
assert!(!container.systemd());
container.set_systemd(true);
assert_eq!(container.systemd(), Some(true));
assert!(container.systemd());
container.set_systemd(false);
assert_eq!(container.systemd(), Some(false));
assert!(!container.systemd());
}
#[test]

@ -1,8 +1,7 @@
use super::{Container, ContainerStatus};
use crate::config::YoukiConfig;
use crate::hooks;
use crate::process::intel_rdt::delete_resctrl_subdirectory;
use anyhow::{bail, Context, Result};
use crate::{config::YoukiConfig, error::LibcontainerError};
use libcgroups::{self, common::CgroupManager};
use nix::sys::signal;
use std::fs;
@ -29,9 +28,8 @@ impl Container {
/// # Ok(())
/// # }
/// ```
pub fn delete(&mut self, force: bool) -> Result<()> {
self.refresh_status()
.context("failed to refresh container status")?;
pub fn delete(&mut self, force: bool) -> Result<(), LibcontainerError> {
self.refresh_status()?;
tracing::debug!("container status: {:?}", self.status());
@ -55,11 +53,12 @@ impl Container {
self.do_kill(signal::Signal::SIGKILL, true)?;
self.set_status(ContainerStatus::Stopped).save()?;
} else {
bail!(
"{} could not be deleted because it was {:?}",
self.id(),
self.status()
)
tracing::error!(
id = ?self.id(),
status = ?self.status(),
"delete requires the container state to be stopped or created",
);
return Err(LibcontainerError::IncorrectContainerStatus);
}
}
}
@ -83,22 +82,23 @@ impl Container {
// remove the cgroup created for the container
// check https://man7.org/linux/man-pages/man7/cgroups.7.html
// creating and removing cgroups section for more information on cgroups
let use_systemd = self
.systemd()
.context("container state does not contain cgroup manager")?;
let use_systemd = self.systemd();
let cmanager = libcgroups::common::create_cgroup_manager(
&config.cgroup_path,
use_systemd,
self.id(),
)
.context("failed to create cgroup manager")?;
cmanager.remove().with_context(|| {
format!("failed to remove cgroup {}", config.cgroup_path.display())
.map_err(|err| LibcontainerError::Cgroups(err.to_string()))?;
cmanager.remove().map_err(|err| {
tracing::error!(cgroup_path = ?config.cgroup_path, "failed to remove cgroup due to: {err:?}");
LibcontainerError::Cgroups(err.to_string())
})?;
if let Some(hooks) = config.hooks.as_ref() {
hooks::run_hooks(hooks.poststop().as_ref(), Some(self))
.with_context(|| "failed to run post stop hooks")?;
hooks::run_hooks(hooks.poststop().as_ref(), Some(self)).map_err(|err| {
tracing::error!(err = ?err, "failed to run post stop hooks");
err
})?;
}
}
Err(err) => {
@ -114,8 +114,9 @@ impl Container {
// remove the directory storing container state
tracing::debug!("remove dir {:?}", self.root);
fs::remove_dir_all(&self.root).with_context(|| {
format!("failed to remove container dir {}", self.root.display())
fs::remove_dir_all(&self.root).map_err(|err| {
tracing::error!(?err, path = ?self.root, "failed to remove container dir");
LibcontainerError::OtherIO(err)
})?;
}

@ -34,9 +34,7 @@ impl Container {
}
let cgroups_path = self.spec()?.cgroup_path;
let use_systemd = self
.systemd()
.context("could not determine cgroup manager")?;
let use_systemd = self.systemd();
let cgroup_manager =
libcgroups::common::create_cgroup_manager(cgroups_path, use_systemd, self.id())?;

@ -1,6 +1,5 @@
use super::{Container, ContainerStatus};
use crate::signal::Signal;
use anyhow::{bail, Context, Result};
use crate::{error::LibcontainerError, signal::Signal};
use libcgroups::common::{create_cgroup_manager, get_cgroup_setup, CgroupManager};
use nix::sys::signal::{self};
@ -27,28 +26,29 @@ impl Container {
/// # Ok(())
/// # }
/// ```
pub fn kill<S: Into<Signal>>(&mut self, signal: S, all: bool) -> Result<()> {
self.refresh_status()
.context("failed to refresh container status")?;
if self.can_kill() {
self.do_kill(signal, all)?;
} else {
// just like runc, allow kill --all even if the container is stopped
if all && self.status() == ContainerStatus::Stopped {
pub fn kill<S: Into<Signal>>(&mut self, signal: S, all: bool) -> Result<(), LibcontainerError> {
self.refresh_status()?;
match self.can_kill() {
true => {
self.do_kill(signal, all)?;
} else {
bail!(
"{} could not be killed because it was {:?}",
self.id(),
self.status()
)
}
false if all && self.status() == ContainerStatus::Stopped => {
self.do_kill(signal, all)?;
}
false => {
tracing::error!(id = ?self.id(), status = ?self.status(), "cannot kill container due to incorrect state");
return Err(LibcontainerError::IncorrectContainerStatus);
}
}
self.set_status(ContainerStatus::Stopped).save()?;
Ok(())
}
pub(crate) fn do_kill<S: Into<Signal>>(&self, signal: S, all: bool) -> Result<()> {
pub(crate) fn do_kill<S: Into<Signal>>(
&self,
signal: S,
all: bool,
) -> Result<(), LibcontainerError> {
if all {
self.kill_all_processes(signal)
} else {
@ -56,32 +56,36 @@ impl Container {
}
}
fn kill_one_process<S: Into<Signal>>(&self, signal: S) -> Result<()> {
fn kill_one_process<S: Into<Signal>>(&self, signal: S) -> Result<(), LibcontainerError> {
let signal = signal.into().into_raw();
let pid = self.pid().unwrap();
tracing::debug!("kill signal {} to {}", signal, pid);
let res = signal::kill(pid, signal);
match res {
match signal::kill(pid, signal) {
Ok(_) => {}
Err(nix::errno::Errno::ESRCH) => {
/* the process does not exist, which is what we want */
// the process does not exist, which is what we want
}
Err(err) => {
tracing::error!(id = ?self.id(), err = ?err, ?pid, ?signal, "failed to kill process");
return Err(LibcontainerError::OtherSyscall(err));
}
_ => res?,
}
// For cgroup V1, a frozon process cannot respond to signals,
// so we need to thaw it. Only thaw the cgroup for SIGKILL.
if self.status() == ContainerStatus::Paused && signal == signal::Signal::SIGKILL {
match get_cgroup_setup()? {
match get_cgroup_setup().map_err(|err| LibcontainerError::Cgroups(err.to_string()))? {
libcgroups::common::CgroupSetup::Legacy
| libcgroups::common::CgroupSetup::Hybrid => {
let cgroups_path = self.spec()?.cgroup_path;
let use_systemd = self
.systemd()
.context("container state does not contain cgroup manager")?;
let cmanger = create_cgroup_manager(cgroups_path, use_systemd, self.id())?;
cmanger.freeze(libcgroups::common::FreezerState::Thawed)?;
let use_systemd = self.systemd();
let cmanger = create_cgroup_manager(cgroups_path, use_systemd, self.id())
.map_err(|err| LibcontainerError::Cgroups(err.to_string()))?;
cmanger
.freeze(libcgroups::common::FreezerState::Thawed)
.map_err(|err| LibcontainerError::Cgroups(err.to_string()))?;
}
libcgroups::common::CgroupSetup::Unified => {}
}
@ -89,41 +93,45 @@ impl Container {
Ok(())
}
fn kill_all_processes<S: Into<Signal>>(&self, signal: S) -> Result<()> {
fn kill_all_processes<S: Into<Signal>>(&self, signal: S) -> Result<(), LibcontainerError> {
let signal = signal.into().into_raw();
let cgroups_path = self.spec()?.cgroup_path;
let use_systemd = self
.systemd()
.context("container state does not contain cgroup manager")?;
let cmanger = create_cgroup_manager(cgroups_path, use_systemd, self.id())?;
let ret = cmanger.freeze(libcgroups::common::FreezerState::Frozen);
if ret.is_err() {
let use_systemd = self.systemd();
let cmanger = create_cgroup_manager(cgroups_path, use_systemd, self.id())
.map_err(|err| LibcontainerError::Cgroups(err.to_string()))?;
if let Err(e) = cmanger.freeze(libcgroups::common::FreezerState::Frozen) {
tracing::warn!(
"failed to freeze container {}, error: {}",
self.id(),
ret.unwrap_err()
err = ?e,
id = ?self.id(),
"failed to freeze container",
);
}
let pids = cmanger.get_all_pids()?;
pids.iter().try_for_each(|&pid| {
tracing::debug!("kill signal {} to {}", signal, pid);
let res = signal::kill(pid, signal);
match res {
Err(nix::errno::Errno::ESRCH) => {
/* the process does not exist, which is what we want */
Ok(())
let pids = cmanger
.get_all_pids()
.map_err(|err| LibcontainerError::Cgroups(err.to_string()))?;
pids.iter()
.try_for_each(|&pid| {
tracing::debug!("kill signal {} to {}", signal, pid);
let res = signal::kill(pid, signal);
match res {
Err(nix::errno::Errno::ESRCH) => {
// the process does not exist, which is what we want
Ok(())
}
_ => res,
}
_ => res,
}
})?;
let ret = cmanger.freeze(libcgroups::common::FreezerState::Thawed);
if ret.is_err() {
})
.map_err(LibcontainerError::OtherSyscall)?;
if let Err(err) = cmanger.freeze(libcgroups::common::FreezerState::Thawed) {
tracing::warn!(
"failed to thaw container {}, error: {}",
self.id(),
ret.unwrap_err()
err = ?err,
id = ?self.id(),
"failed to thaw container",
);
}
Ok(())
}
}

@ -37,9 +37,7 @@ impl Container {
}
let cgroups_path = self.spec()?.cgroup_path;
let use_systemd = self
.systemd()
.context("container state does not contain cgroup manager")?;
let use_systemd = self.systemd();
let cmanager =
libcgroups::common::create_cgroup_manager(cgroups_path, use_systemd, self.id())?;
cmanager.freeze(FreezerState::Frozen)?;

@ -39,9 +39,7 @@ impl Container {
}
let cgroups_path = self.spec()?.cgroup_path;
let use_systemd = self
.systemd()
.context("container state does not contain cgroup manager")?;
let use_systemd = self.systemd();
let cmanager =
libcgroups::common::create_cgroup_manager(cgroups_path, use_systemd, self.id())?;
// resume the frozen container

@ -1,11 +1,11 @@
use crate::{
config::YoukiConfig,
error::LibcontainerError,
hooks,
notify_socket::{NotifySocket, NOTIFY_FILE},
};
use super::{Container, ContainerStatus};
use anyhow::{bail, Context, Result};
use nix::{sys::signal, unistd};
impl Container {
@ -30,49 +30,57 @@ impl Container {
/// # Ok(())
/// # }
/// ```
pub fn start(&mut self) -> Result<()> {
self.refresh_status()
.context("failed to refresh container status")?;
pub fn start(&mut self) -> Result<(), LibcontainerError> {
self.refresh_status()?;
if !self.can_start() {
let err_msg = format!(
"{} could not be started because it was {:?}",
self.id(),
self.status()
);
tracing::error!("{}", err_msg);
bail!(err_msg);
tracing::error!(status = ?self.status(), id = ?self.id(), "cannot start container due to incorrect state");
return Err(LibcontainerError::IncorrectContainerStatus);
}
let config = YoukiConfig::load(&self.root)
.with_context(|| format!("failed to load runtime spec for container {}", self.id()))?;
let config = YoukiConfig::load(&self.root).map_err(|err| {
tracing::error!(
"failed to load runtime spec for container {}: {}",
self.id(),
err
);
err
})?;
if let Some(hooks) = config.hooks.as_ref() {
// While prestart is marked as deprecated in the OCI spec, the docker and integration test still
// uses it.
#[allow(deprecated)]
let ret = hooks::run_hooks(hooks.prestart().as_ref(), Some(self))
.with_context(|| "failed to run pre start hooks");
if ret.is_err() {
hooks::run_hooks(hooks.prestart().as_ref(), Some(self)).map_err(|err| {
tracing::error!("failed to run pre start hooks: {}", err);
// In the case where prestart hook fails, the runtime must
// stop the container before generating an error and exiting.
self.kill(signal::Signal::SIGKILL, true)?;
return ret;
}
let _ = self.kill(signal::Signal::SIGKILL, true);
err
})?;
}
unistd::chdir(self.root.as_os_str())?;
unistd::chdir(self.root.as_os_str()).map_err(|err| {
tracing::error!("failed to change directory to container root: {}", err);
LibcontainerError::OtherSyscall(err)
})?;
let mut notify_socket = NotifySocket::new(self.root.join(NOTIFY_FILE));
notify_socket.notify_container_start()?;
self.set_status(ContainerStatus::Running)
.save()
.with_context(|| format!("could not save state for container {}", self.id()))?;
.map_err(|err| {
tracing::error!(id = ?self.id(), ?err, "failed to save state for container");
err
})?;
// Run post start hooks. It runs after the container process is started.
// It is called in the runtime namespace.
if let Some(hooks) = config.hooks.as_ref() {
hooks::run_hooks(hooks.poststart().as_ref(), Some(self))
.with_context(|| "failed to run post start hooks")?;
hooks::run_hooks(hooks.poststart().as_ref(), Some(self)).map_err(|err| {
tracing::error!("failed to run post start hooks: {}", err);
err
})?;
}
Ok(())

@ -1,4 +1,3 @@
use anyhow::{bail, Context, Result};
use nix::unistd;
use oci_spec::runtime::Spec;
use rootless::Rootless;
@ -8,8 +7,12 @@ use std::{
};
use crate::{
apparmor, config::YoukiConfig, notify_socket::NOTIFY_FILE, process::args::ContainerType,
rootless, tty, utils,
apparmor,
config::YoukiConfig,
error::{ErrInvalidSpec, LibcontainerError, MissingSpecError},
notify_socket::NOTIFY_FILE,
process::args::ContainerType,
rootless, tty,
};
use super::{
@ -48,23 +51,27 @@ impl<'a> InitContainerBuilder<'a> {
}
/// Creates a new container
pub fn build(self) -> Result<Container> {
let spec = self.load_spec().context("failed to load spec")?;
let container_dir = self
.create_container_dir()
.context("failed to create container dir")?;
pub fn build(self) -> Result<Container, LibcontainerError> {
let spec = self.load_spec()?;
let container_dir = self.create_container_dir()?;
let mut container = self
.create_container_state(&container_dir)
.context("failed to create container state")?;
let mut container = self.create_container_state(&container_dir)?;
container
.set_systemd(self.use_systemd)
.set_annotations(spec.annotations().clone());
unistd::chdir(&container_dir)?;
unistd::chdir(&container_dir).map_err(|err| {
tracing::error!(
?container_dir,
?err,
"failed to chdir into the container directory"
);
LibcontainerError::OtherSyscall(err)
})?;
let notify_path = container_dir.join(NOTIFY_FILE);
// convert path of root file system of the container to absolute path
let rootfs = fs::canonicalize(spec.root().as_ref().context("no root in spec")?.path())?;
let rootfs = fs::canonicalize(spec.root().as_ref().ok_or(MissingSpecError::Root)?.path())
.map_err(LibcontainerError::OtherIO)?;
// if socket file path is given in commandline options,
// get file descriptors of console socket
@ -80,9 +87,10 @@ impl<'a> InitContainerBuilder<'a> {
let rootless = Rootless::new(&spec)?;
let config = YoukiConfig::from_spec(&spec, container.id(), rootless.is_some())?;
config
.save(&container_dir)
.context("failed to save config")?;
config.save(&container_dir).map_err(|err| {
tracing::error!(?container_dir, "failed to save config: {}", err);
err
})?;
let mut builder_impl = ContainerBuilderImpl {
container_type: ContainerType::InitContainer,
@ -108,46 +116,60 @@ impl<'a> InitContainerBuilder<'a> {
Ok(container)
}
fn create_container_dir(&self) -> Result<PathBuf> {
fn create_container_dir(&self) -> Result<PathBuf, LibcontainerError> {
let container_dir = self.base.root_path.join(&self.base.container_id);
tracing::debug!("container directory will be {:?}", container_dir);
if container_dir.exists() {
bail!("container {} already exists", self.base.container_id);
tracing::error!(id = self.base.container_id, dir = ?container_dir, "container already exists");
return Err(LibcontainerError::ContainerAlreadyExists);
}
utils::create_dir_all(&container_dir).context("failed to create container dir")?;
std::fs::create_dir_all(&container_dir).map_err(|err| {
tracing::error!(
?container_dir,
"failed to create container directory: {}",
err
);
LibcontainerError::OtherIO(err)
})?;
Ok(container_dir)
}
fn load_spec(&self) -> Result<Spec> {
fn load_spec(&self) -> Result<Spec, LibcontainerError> {
let source_spec_path = self.bundle.join("config.json");
let mut spec = Spec::load(source_spec_path)?;
Self::validate_spec(&spec).context("failed to validate runtime spec")?;
Self::validate_spec(&spec)?;
spec.canonicalize_rootfs(&self.bundle).map_err(|err| {
tracing::error!(bundle = ?self.bundle, "failed to canonicalize rootfs: {}", err);
err
})?;
spec.canonicalize_rootfs(&self.bundle)
.context("failed to canonicalize rootfs")?;
Ok(spec)
}
fn validate_spec(spec: &Spec) -> Result<()> {
fn validate_spec(spec: &Spec) -> Result<(), LibcontainerError> {
let version = spec.version();
if !version.starts_with("1.") {
bail!(
tracing::error!(
"runtime spec has incompatible version '{}'. Only 1.X.Y is supported",
spec.version()
);
Err(ErrInvalidSpec::UnsupportedVersion)?;
}
if let Some(process) = spec.process() {
if let Some(profile) = process.apparmor_profile() {
if !apparmor::is_enabled()? {
bail!(
"apparmor profile {} is specified in runtime spec, \
but apparmor is not activated on this system",
profile
);
let apparmor_is_enabled = apparmor::is_enabled().map_err(|err| {
tracing::error!(?err, "failed to check if apparmor is enabled");
LibcontainerError::OtherIO(err)
})?;
if !apparmor_is_enabled {
tracing::error!(?profile,
"apparmor profile exists in the spec, but apparmor is not activated on this system");
Err(ErrInvalidSpec::AppArmorNotEnabled)?;
}
}
}
@ -155,7 +177,7 @@ impl<'a> InitContainerBuilder<'a> {
Ok(())
}
fn create_container_state(&self, container_dir: &Path) -> Result<Container> {
fn create_container_state(&self, container_dir: &Path) -> Result<Container, LibcontainerError> {
let container = Container::new(
&self.base.container_id,
ContainerStatus::Creating,

@ -118,7 +118,7 @@ pub struct State {
#[serde(skip_serializing_if = "Option::is_none")]
pub creator: Option<u32>,
// Specifies if systemd should be used to manage cgroups
pub use_systemd: Option<bool>,
pub use_systemd: bool,
// Specifies if the Intel RDT subdirectory needs be cleaned up.
pub clean_up_intel_rdt_subdirectory: Option<bool>,
}
@ -141,7 +141,7 @@ impl State {
annotations: Some(HashMap::default()),
created: None,
creator: None,
use_systemd: None,
use_systemd: false,
clean_up_intel_rdt_subdirectory: None,
}
}

@ -376,11 +376,7 @@ impl<'a> TenantContainerBuilder<'a> {
}
fn should_use_systemd(&self, container: &Container) -> bool {
if let Some(use_systemd) = container.systemd() {
return use_systemd;
}
false
container.systemd()
}
fn setup_notify_listener(container_dir: &Path) -> Result<PathBuf> {

@ -14,9 +14,79 @@ pub enum UnifiedSyscallError {
#[derive(Debug, thiserror::Error)]
pub enum MissingSpecError {
#[error("missing process in spec")]
MissingProcess,
Process,
#[error("missing linux in spec")]
MissingLinux,
Linux,
#[error("missing args in the process spec")]
MissingArgs,
Args,
#[error("missing root in the spec")]
Root,
}
#[derive(Debug, thiserror::Error)]
pub enum LibcontainerError {
#[error("failed to perform operation due to incorrect container status")]
IncorrectContainerStatus,
#[error("container already exists")]
ContainerAlreadyExists,
#[error("invalid input")]
InvalidInput(String),
#[error("requires at least one executors")]
NoExecutors,
// Invalid inputs
#[error(transparent)]
InvalidID(#[from] ErrInvalidID),
#[error(transparent)]
MissingSpec(#[from] MissingSpecError),
#[error("invalid runtime spec")]
InvalidSpec(#[from] ErrInvalidSpec),
// Errors from submodules and other errors
#[error(transparent)]
Tty(#[from] crate::tty::TTYError),
#[error(transparent)]
Rootless(#[from] crate::rootless::RootlessError),
#[error(transparent)]
NotifyListener(#[from] crate::notify_socket::NotifyListenerError),
#[error(transparent)]
Config(#[from] crate::config::ConfigError),
#[error(transparent)]
Hook(#[from] crate::hooks::HookError),
#[error(transparent)]
State(#[from] crate::container::state::StateError),
#[error("oci spec error")]
Spec(#[from] oci_spec::OciSpecError),
#[error("cgroups error: {0}")]
Cgroups(String),
#[error(transparent)]
MainProcess(#[from] crate::process::container_main_process::ProcessError),
#[error(transparent)]
Procfs(#[from] procfs::ProcError),
// Catch all errors that are not covered by the above
#[error("syscall error")]
OtherSyscall(#[source] nix::Error),
#[error("IO error")]
OtherIO(#[source] std::io::Error),
#[error("{0}")]
Other(String),
}
#[derive(Debug, thiserror::Error)]
pub enum ErrInvalidID {
#[error("container id can't be empty")]
Empty,
#[error("container id contains invalid characters: {0}")]
InvalidChars(char),
#[error("container id can't be used to represent a file name (such as . or ..)")]
FileName,
}
#[derive(Debug, thiserror::Error)]
pub enum ErrInvalidSpec {
#[error("runtime spec has incompatible version. Only 1.X.Y is supported")]
UnsupportedVersion,
#[error("apparmor is specified but not enabled on this system")]
AppArmorNotEnabled,
}

@ -339,14 +339,8 @@ pub fn container_init_process(
) -> Result<()> {
let syscall = args.syscall;
let spec = args.spec;
let linux = spec
.linux()
.as_ref()
.ok_or(MissingSpecError::MissingLinux)?;
let proc = spec
.process()
.as_ref()
.ok_or(MissingSpecError::MissingProcess)?;
let linux = spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;
let proc = spec.process().as_ref().ok_or(MissingSpecError::Process)?;
let mut envs: Vec<String> = proc.env().as_ref().unwrap_or(&vec![]).clone();
let rootfs_path = args.rootfs;
let hooks = spec.hooks().as_ref();
@ -693,7 +687,7 @@ pub fn container_init_process(
unreachable!("should not be back here");
} else {
tracing::error!("on non-Windows, at least one process arg entry is required");
Err(MissingSpecError::MissingArgs)
Err(MissingSpecError::Args)
}?
}

@ -42,10 +42,7 @@ pub fn container_intermediate_process(
let (init_sender, init_receiver) = init_chan;
let command = &args.syscall;
let spec = &args.spec;
let linux = spec
.linux()
.as_ref()
.ok_or(MissingSpecError::MissingLinux)?;
let linux = spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;
let namespaces = Namespaces::from(linux.namespaces().as_ref());
// this needs to be done before we create the init process, so that the init
@ -96,10 +93,7 @@ pub fn container_intermediate_process(
}
// set limits and namespaces to the process
let proc = spec
.process()
.as_ref()
.ok_or(MissingSpecError::MissingProcess)?;
let proc = spec.process().as_ref().ok_or(MissingSpecError::Process)?;
if let Some(rlimits) = proc.rlimits() {
for rlimit in rlimits {
command.set_rlimit(rlimit)?;

@ -40,10 +40,7 @@ impl RootFS {
) -> Result<()> {
tracing::debug!("Prepare rootfs: {:?}", rootfs);
let mut flags = MsFlags::MS_REC;
let linux = spec
.linux()
.as_ref()
.ok_or(MissingSpecError::MissingLinux)?;
let linux = spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;
match linux.rootfs_propagation().as_deref() {
Some("shared") => flags |= MsFlags::MS_SHARED,

@ -139,10 +139,7 @@ pub struct Rootless<'a> {
impl<'a> Rootless<'a> {
pub fn new(spec: &'a Spec) -> Result<Option<Rootless<'a>>> {
let linux = spec
.linux()
.as_ref()
.ok_or(MissingSpecError::MissingLinux)?;
let linux = spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;
let namespaces = Namespaces::from(linux.namespaces().as_ref());
let user_namespace = namespaces.get(LinuxNamespaceType::User);
@ -252,10 +249,7 @@ pub fn unprivileged_user_ns_enabled() -> Result<bool> {
/// running in rootless mode
fn validate_spec_for_rootless(spec: &Spec) -> std::result::Result<(), ValidateSpecError> {
tracing::debug!(?spec, "validating spec for rootless container");
let linux = spec
.linux()
.as_ref()
.ok_or(MissingSpecError::MissingLinux)?;
let linux = spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;
let namespaces = Namespaces::from(linux.namespaces().as_ref());
if namespaces.get(LinuxNamespaceType::User).is_none() {
return Err(ValidateSpecError::NoUserNamespace);

@ -1,7 +1,7 @@
//! Contains functionality of kill container command
use std::{convert::TryInto, path::PathBuf};
use anyhow::Result;
use anyhow::{anyhow, Result};
use crate::commands::load_container;
use libcontainer::{container::ContainerStatus, signal::Signal};
@ -15,9 +15,9 @@ pub fn kill(args: Kill, root_path: PathBuf) -> Result<()> {
Err(e) => {
// see https://github.com/containers/youki/issues/1314
if container.status() == ContainerStatus::Stopped {
return Err(e.context("container not running"));
return Err(anyhow!(e).context("container not running"));
}
Err(e)
Err(anyhow!(e).context("failed to kill container"))
}
}
}

@ -59,9 +59,7 @@ fn create_cgroup_manager<P: AsRef<Path>>(
) -> Result<AnyCgroupManager> {
let container = load_container(root_path, container_id)?;
let cgroups_path = container.spec()?.cgroup_path;
let systemd_cgroup = container
.systemd()
.context("could not determine cgroup manager")?;
let systemd_cgroup = container.systemd();
Ok(libcgroups::common::create_cgroup_manager(
cgroups_path,