2021-06-06 13:33:51 +02:00
|
|
|
//! Utility functionality
|
|
|
|
|
2021-08-05 09:42:23 +02:00
|
|
|
use std::collections::HashMap;
|
2021-08-08 01:08:14 +02:00
|
|
|
use std::fs::{self, DirBuilder, File};
|
|
|
|
use std::os::linux::fs::MetadataExt;
|
|
|
|
use std::os::unix::fs::DirBuilderExt;
|
2023-05-20 08:09:43 +02:00
|
|
|
use std::os::unix::prelude::AsRawFd;
|
2022-04-16 20:27:07 +02:00
|
|
|
use std::path::{Component, Path, PathBuf};
|
2021-03-27 12:08:13 +01:00
|
|
|
|
2023-05-13 19:30:17 +02:00
|
|
|
use nix::sys::stat::Mode;
|
|
|
|
use nix::sys::statfs;
|
|
|
|
use nix::unistd::{Uid, User};
|
2023-09-05 08:31:33 +02:00
|
|
|
use oci_spec::runtime::Spec;
|
|
|
|
|
|
|
|
use crate::error::LibcontainerError;
|
|
|
|
use crate::user_ns::UserNamespaceConfig;
|
2023-05-13 19:30:17 +02:00
|
|
|
|
2023-05-06 06:26:08 +02:00
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
|
|
pub enum PathBufExtError {
|
|
|
|
#[error("relative path cannot be converted to the path in the container")]
|
|
|
|
RelativePath,
|
|
|
|
#[error("failed to strip prefix from {path:?}")]
|
|
|
|
StripPrefix {
|
|
|
|
path: PathBuf,
|
|
|
|
source: std::path::StripPrefixError,
|
|
|
|
},
|
|
|
|
#[error("failed to canonicalize path {path:?}")]
|
|
|
|
Canonicalize {
|
|
|
|
path: PathBuf,
|
|
|
|
source: std::io::Error,
|
|
|
|
},
|
|
|
|
#[error("failed to get current directory")]
|
|
|
|
CurrentDir { source: std::io::Error },
|
|
|
|
}
|
|
|
|
|
2021-04-07 14:07:01 +02:00
|
|
|
pub trait PathBufExt {
|
2023-05-06 06:26:08 +02:00
|
|
|
fn as_relative(&self) -> Result<&Path, PathBufExtError>;
|
|
|
|
fn join_safely<P: AsRef<Path>>(&self, p: P) -> Result<PathBuf, PathBufExtError>;
|
|
|
|
fn canonicalize_safely(&self) -> Result<PathBuf, PathBufExtError>;
|
2022-04-16 20:27:07 +02:00
|
|
|
fn normalize(&self) -> PathBuf;
|
2021-04-07 14:07:01 +02:00
|
|
|
}
|
|
|
|
|
2021-09-29 20:59:16 +02:00
|
|
|
impl PathBufExt for Path {
|
2023-05-06 06:26:08 +02:00
|
|
|
fn as_relative(&self) -> Result<&Path, PathBufExtError> {
|
|
|
|
match self.is_relative() {
|
|
|
|
true => Err(PathBufExtError::RelativePath),
|
|
|
|
false => Ok(self
|
|
|
|
.strip_prefix("/")
|
|
|
|
.map_err(|e| PathBufExtError::StripPrefix {
|
|
|
|
path: self.to_path_buf(),
|
|
|
|
source: e,
|
|
|
|
})?),
|
2021-04-07 14:07:01 +02:00
|
|
|
}
|
|
|
|
}
|
2021-04-10 16:36:41 +02:00
|
|
|
|
2023-05-06 06:26:08 +02:00
|
|
|
fn join_safely<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf, PathBufExtError> {
|
2021-10-06 23:09:02 +02:00
|
|
|
let path = path.as_ref();
|
|
|
|
if path.is_relative() {
|
|
|
|
return Ok(self.join(path));
|
2021-09-29 20:59:16 +02:00
|
|
|
}
|
|
|
|
|
2021-10-06 23:09:02 +02:00
|
|
|
let stripped = path
|
2021-09-29 20:59:16 +02:00
|
|
|
.strip_prefix("/")
|
2023-05-06 06:26:08 +02:00
|
|
|
.map_err(|e| PathBufExtError::StripPrefix {
|
|
|
|
path: self.to_path_buf(),
|
|
|
|
source: e,
|
|
|
|
})?;
|
2021-09-29 20:59:16 +02:00
|
|
|
Ok(self.join(stripped))
|
|
|
|
}
|
2022-04-16 20:27:07 +02:00
|
|
|
|
|
|
|
/// Canonicalizes existing and not existing paths
|
2023-05-06 06:26:08 +02:00
|
|
|
fn canonicalize_safely(&self) -> Result<PathBuf, PathBufExtError> {
|
2022-04-16 20:27:07 +02:00
|
|
|
if self.exists() {
|
|
|
|
self.canonicalize()
|
2023-05-06 06:26:08 +02:00
|
|
|
.map_err(|e| PathBufExtError::Canonicalize {
|
|
|
|
path: self.to_path_buf(),
|
|
|
|
source: e,
|
|
|
|
})
|
2022-04-16 20:27:07 +02:00
|
|
|
} else {
|
|
|
|
if self.is_relative() {
|
|
|
|
let p = std::env::current_dir()
|
2023-05-06 06:26:08 +02:00
|
|
|
.map_err(|e| PathBufExtError::CurrentDir { source: e })?
|
2022-04-16 20:27:07 +02:00
|
|
|
.join(self);
|
|
|
|
return Ok(p.normalize());
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(self.normalize())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Normalizes a path. In contrast to canonicalize the path does not need to exist.
|
|
|
|
// adapted from https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61
|
|
|
|
fn normalize(&self) -> PathBuf {
|
|
|
|
let mut components = self.components().peekable();
|
|
|
|
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
|
|
|
|
components.next();
|
|
|
|
PathBuf::from(c.as_os_str())
|
|
|
|
} else {
|
|
|
|
PathBuf::new()
|
|
|
|
};
|
|
|
|
|
|
|
|
for component in components {
|
|
|
|
match component {
|
|
|
|
Component::Prefix(..) => unreachable!(),
|
|
|
|
Component::RootDir => {
|
|
|
|
ret.push(component.as_os_str());
|
|
|
|
}
|
|
|
|
Component::CurDir => {}
|
|
|
|
Component::ParentDir => {
|
|
|
|
ret.pop();
|
|
|
|
}
|
|
|
|
Component::Normal(c) => {
|
|
|
|
ret.push(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ret
|
|
|
|
}
|
2021-04-07 14:07:01 +02:00
|
|
|
}
|
|
|
|
|
2021-08-05 19:48:05 +02:00
|
|
|
pub fn parse_env(envs: &[String]) -> HashMap<String, String> {
|
2021-08-05 09:42:23 +02:00
|
|
|
envs.iter()
|
|
|
|
.filter_map(|e| {
|
|
|
|
let mut split = e.split('=');
|
|
|
|
|
2021-09-28 00:46:57 +02:00
|
|
|
split.next().map(|key| {
|
|
|
|
let value = split.collect::<Vec<&str>>().join("=");
|
|
|
|
(key.into(), value)
|
|
|
|
})
|
2021-08-05 09:42:23 +02:00
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2022-02-08 23:19:06 +01:00
|
|
|
/// Get a nix::unistd::User via UID. Potential errors will be ignored.
|
|
|
|
pub fn get_unix_user(uid: Uid) -> Option<User> {
|
|
|
|
match User::from_uid(uid) {
|
|
|
|
Ok(x) => x,
|
|
|
|
Err(_) => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get home path of a User via UID.
|
|
|
|
pub fn get_user_home(uid: u32) -> Option<PathBuf> {
|
|
|
|
match get_unix_user(Uid::from_raw(uid)) {
|
|
|
|
Some(user) => Some(user.dir),
|
|
|
|
None => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-29 10:52:36 +02:00
|
|
|
/// If None, it will generate a default path for cgroups.
|
2022-01-06 22:48:50 +01:00
|
|
|
pub fn get_cgroup_path(
|
|
|
|
cgroups_path: &Option<PathBuf>,
|
|
|
|
container_id: &str,
|
2023-08-12 15:58:25 +02:00
|
|
|
new_user_ns: bool,
|
2022-01-06 22:48:50 +01:00
|
|
|
) -> PathBuf {
|
2021-05-29 10:52:36 +02:00
|
|
|
match cgroups_path {
|
|
|
|
Some(cpath) => cpath.clone(),
|
2023-08-12 15:58:25 +02:00
|
|
|
None => match new_user_ns {
|
2022-01-06 22:48:50 +01:00
|
|
|
false => PathBuf::from(container_id),
|
2023-02-12 12:08:03 +01:00
|
|
|
true => PathBuf::from(format!(":youki:{container_id}")),
|
2022-01-06 22:48:50 +01:00
|
|
|
},
|
2021-05-29 10:52:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-06 06:26:08 +02:00
|
|
|
pub fn write_file<P: AsRef<Path>, C: AsRef<[u8]>>(
|
|
|
|
path: P,
|
|
|
|
contents: C,
|
2023-05-20 08:52:36 +02:00
|
|
|
) -> Result<(), std::io::Error> {
|
|
|
|
fs::write(path.as_ref(), contents).map_err(|err| {
|
|
|
|
tracing::error!(path = ?path.as_ref(), ?err, "failed to write file");
|
|
|
|
err
|
|
|
|
})?;
|
|
|
|
|
|
|
|
Ok(())
|
2021-07-01 22:25:23 +02:00
|
|
|
}
|
|
|
|
|
2023-05-20 08:52:36 +02:00
|
|
|
pub fn create_dir_all<P: AsRef<Path>>(path: P) -> Result<(), std::io::Error> {
|
|
|
|
fs::create_dir_all(path.as_ref()).map_err(|err| {
|
|
|
|
tracing::error!(path = ?path.as_ref(), ?err, "failed to create directory");
|
|
|
|
err
|
|
|
|
})?;
|
|
|
|
Ok(())
|
2023-05-06 06:26:08 +02:00
|
|
|
}
|
|
|
|
|
2023-05-19 12:03:37 +02:00
|
|
|
pub fn open<P: AsRef<Path>>(path: P) -> Result<File, std::io::Error> {
|
|
|
|
File::open(path.as_ref()).map_err(|err| {
|
|
|
|
tracing::error!(path = ?path.as_ref(), ?err, "failed to open file");
|
|
|
|
err
|
2023-05-06 06:26:08 +02:00
|
|
|
})
|
2021-07-15 22:12:21 +02:00
|
|
|
}
|
|
|
|
|
2023-05-20 08:52:36 +02:00
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
|
|
pub enum MkdirWithModeError {
|
|
|
|
#[error("IO error")]
|
|
|
|
Io(#[from] std::io::Error),
|
2023-06-08 06:49:17 +02:00
|
|
|
#[error("metadata doesn't match the expected attributes")]
|
2023-05-20 08:52:36 +02:00
|
|
|
MetadataMismatch,
|
|
|
|
}
|
|
|
|
|
2021-08-08 01:08:14 +02:00
|
|
|
/// Creates the specified directory and all parent directories with the specified mode. Ensures
|
|
|
|
/// that the directory has been created with the correct mode and that the owner of the directory
|
|
|
|
/// is the owner that has been specified
|
|
|
|
/// # Example
|
|
|
|
/// ``` no_run
|
2021-10-21 15:33:18 +02:00
|
|
|
/// use libcontainer::utils::create_dir_all_with_mode;
|
2021-08-08 01:08:14 +02:00
|
|
|
/// use nix::sys::stat::Mode;
|
|
|
|
/// use std::path::Path;
|
|
|
|
///
|
|
|
|
/// let path = Path::new("/tmp/youki");
|
|
|
|
/// create_dir_all_with_mode(&path, 1000, Mode::S_IRWXU).unwrap();
|
|
|
|
/// assert!(path.exists())
|
|
|
|
/// ```
|
2023-05-06 06:26:08 +02:00
|
|
|
pub fn create_dir_all_with_mode<P: AsRef<Path>>(
|
|
|
|
path: P,
|
|
|
|
owner: u32,
|
|
|
|
mode: Mode,
|
2023-05-20 08:52:36 +02:00
|
|
|
) -> Result<(), MkdirWithModeError> {
|
2021-08-08 01:08:14 +02:00
|
|
|
let path = path.as_ref();
|
|
|
|
if !path.exists() {
|
|
|
|
DirBuilder::new()
|
|
|
|
.recursive(true)
|
|
|
|
.mode(mode.bits())
|
2023-05-20 08:52:36 +02:00
|
|
|
.create(path)?;
|
2021-08-08 01:08:14 +02:00
|
|
|
}
|
|
|
|
|
2023-05-20 08:52:36 +02:00
|
|
|
let metadata = path.metadata()?;
|
2021-08-08 01:08:14 +02:00
|
|
|
if metadata.is_dir()
|
|
|
|
&& metadata.st_uid() == owner
|
|
|
|
&& metadata.st_mode() & mode.bits() == mode.bits()
|
|
|
|
{
|
|
|
|
Ok(())
|
|
|
|
} else {
|
2023-05-20 08:52:36 +02:00
|
|
|
Err(MkdirWithModeError::MetadataMismatch)
|
2021-08-08 01:08:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-06 06:26:08 +02:00
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
|
|
pub enum EnsureProcfsError {
|
2023-05-19 06:24:17 +02:00
|
|
|
#[error(transparent)]
|
|
|
|
Nix(#[from] nix::Error),
|
|
|
|
#[error(transparent)]
|
|
|
|
IO(#[from] std::io::Error),
|
2023-05-06 06:26:08 +02:00
|
|
|
}
|
|
|
|
|
2021-09-21 01:13:13 +02:00
|
|
|
// Make sure a given path is on procfs. This is to avoid the security risk that
|
|
|
|
// /proc path is mounted over. Ref: CVE-2019-16884
|
2023-05-06 06:26:08 +02:00
|
|
|
pub fn ensure_procfs(path: &Path) -> Result<(), EnsureProcfsError> {
|
2023-05-19 06:24:17 +02:00
|
|
|
let procfs_fd = fs::File::open(path).map_err(|err| {
|
|
|
|
tracing::error!(?err, ?path, "failed to open procfs file");
|
|
|
|
err
|
|
|
|
})?;
|
|
|
|
let fstat_info = statfs::fstatfs(&procfs_fd.as_raw_fd()).map_err(|err| {
|
|
|
|
tracing::error!(?err, ?path, "failed to fstatfs the procfs");
|
|
|
|
err
|
2023-05-06 06:26:08 +02:00
|
|
|
})?;
|
2021-09-21 01:13:13 +02:00
|
|
|
|
|
|
|
if fstat_info.filesystem_type() != statfs::PROC_SUPER_MAGIC {
|
2023-05-19 06:24:17 +02:00
|
|
|
tracing::error!(?path, "given path is not on the procfs");
|
|
|
|
Err(nix::Error::EINVAL)?;
|
2021-09-21 01:13:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-08-18 08:38:43 +02:00
|
|
|
pub fn is_in_new_userns() -> bool {
|
|
|
|
let uid_map_path = "/proc/self/uid_map";
|
|
|
|
let content = std::fs::read_to_string(uid_map_path)
|
|
|
|
.unwrap_or_else(|_| panic!("failed to read {}", uid_map_path));
|
|
|
|
!content.contains("4294967295")
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Checks if rootless mode needs to be used
|
|
|
|
pub fn rootless_required() -> bool {
|
|
|
|
if !nix::unistd::geteuid().is_root() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
is_in_new_userns()
|
|
|
|
}
|
|
|
|
|
2023-09-05 08:31:33 +02:00
|
|
|
/// checks if given spec is valid for current user namespace setup
|
|
|
|
pub fn validate_spec_for_new_user_ns(spec: &Spec) -> Result<(), LibcontainerError> {
|
|
|
|
let config = UserNamespaceConfig::new(spec)?;
|
|
|
|
|
|
|
|
// In case of rootless, there are 2 possible cases :
|
|
|
|
// we have a new user ns specified in the spec
|
|
|
|
// or the youki is launched in a new user ns (this is how podman does it)
|
|
|
|
// So here, we check if rootless is required,
|
|
|
|
// but we are neither in a new user ns nor a new user ns is specified in spec
|
|
|
|
// then it is an error
|
|
|
|
if rootless_required() && !is_in_new_userns() && config.is_none() {
|
|
|
|
return Err(LibcontainerError::NoUserNamespace);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-04-29 10:34:23 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2023-09-05 08:31:33 +02:00
|
|
|
use crate::test_utils;
|
2023-05-20 08:52:36 +02:00
|
|
|
use anyhow::{bail, Result};
|
2023-09-05 08:31:33 +02:00
|
|
|
use serial_test::serial;
|
2021-04-29 10:34:23 +02:00
|
|
|
|
2022-02-08 23:19:06 +01:00
|
|
|
#[test]
|
|
|
|
pub fn test_get_unix_user() {
|
|
|
|
let user = get_unix_user(Uid::from_raw(0));
|
|
|
|
assert_eq!(user.unwrap().name, "root");
|
|
|
|
|
|
|
|
// for a non-exist UID
|
|
|
|
let user = get_unix_user(Uid::from_raw(1000000000));
|
|
|
|
assert!(user.is_none());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn test_get_user_home() {
|
|
|
|
let dir = get_user_home(0);
|
|
|
|
assert_eq!(dir.unwrap().to_str().unwrap(), "/root");
|
|
|
|
|
|
|
|
// for a non-exist UID
|
|
|
|
let dir = get_user_home(1000000000);
|
|
|
|
assert!(dir.is_none());
|
|
|
|
}
|
|
|
|
|
2021-05-29 10:52:36 +02:00
|
|
|
#[test]
|
|
|
|
fn test_get_cgroup_path() {
|
|
|
|
let cid = "sample_container_id";
|
|
|
|
assert_eq!(
|
2022-01-06 22:48:50 +01:00
|
|
|
get_cgroup_path(&None, cid, false),
|
2021-09-25 22:52:07 +02:00
|
|
|
PathBuf::from("sample_container_id")
|
2021-05-29 10:52:36 +02:00
|
|
|
);
|
|
|
|
assert_eq!(
|
2022-01-06 22:48:50 +01:00
|
|
|
get_cgroup_path(&Some(PathBuf::from("/youki")), cid, false),
|
2021-05-29 10:52:36 +02:00
|
|
|
PathBuf::from("/youki")
|
|
|
|
);
|
|
|
|
}
|
2023-05-13 19:30:17 +02:00
|
|
|
|
2021-08-05 09:42:23 +02:00
|
|
|
#[test]
|
|
|
|
fn test_parse_env() -> Result<()> {
|
|
|
|
let key = "key".to_string();
|
|
|
|
let value = "value".to_string();
|
2023-02-12 12:08:03 +01:00
|
|
|
let env_input = vec![format!("{key}={value}")];
|
2021-08-05 10:17:17 +02:00
|
|
|
let env_output = parse_env(&env_input);
|
2021-08-05 09:42:23 +02:00
|
|
|
assert_eq!(
|
|
|
|
env_output.len(),
|
|
|
|
1,
|
|
|
|
"There should be exactly one entry inside"
|
|
|
|
);
|
|
|
|
assert_eq!(env_output.get_key_value(&key), Some((&key, &value)));
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-05-20 08:52:36 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_create_dir_all_with_mode() -> Result<()> {
|
|
|
|
{
|
|
|
|
let temdir = tempfile::tempdir()?;
|
|
|
|
let path = temdir.path().join("test");
|
|
|
|
let uid = nix::unistd::getuid().as_raw();
|
|
|
|
let mode = Mode::S_IRWXU;
|
|
|
|
create_dir_all_with_mode(&path, uid, mode)?;
|
|
|
|
let metadata = path.metadata()?;
|
|
|
|
assert!(path.is_dir());
|
|
|
|
assert_eq!(metadata.st_uid(), uid);
|
|
|
|
assert_eq!(metadata.st_mode() & mode.bits(), mode.bits());
|
|
|
|
}
|
|
|
|
{
|
|
|
|
let temdir = tempfile::tempdir()?;
|
|
|
|
let path = temdir.path().join("test");
|
|
|
|
let mode = Mode::S_IRWXU;
|
|
|
|
std::fs::create_dir(&path)?;
|
|
|
|
assert!(path.is_dir());
|
|
|
|
match create_dir_all_with_mode(&path, 8899, mode) {
|
|
|
|
Err(MkdirWithModeError::MetadataMismatch) => {}
|
|
|
|
_ => bail!("should return MetadataMismatch"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_io() -> Result<()> {
|
|
|
|
{
|
|
|
|
let tempdir = tempfile::tempdir()?;
|
|
|
|
let path = tempdir.path().join("test");
|
|
|
|
write_file(&path, "test".as_bytes())?;
|
|
|
|
open(&path)?;
|
|
|
|
assert!(create_dir_all(path).is_err());
|
|
|
|
}
|
|
|
|
{
|
|
|
|
let tempdir = tempfile::tempdir()?;
|
|
|
|
let path = tempdir.path().join("test");
|
|
|
|
create_dir_all(&path)?;
|
|
|
|
assert!(write_file(&path, "test".as_bytes()).is_err());
|
|
|
|
}
|
|
|
|
{
|
|
|
|
let tempdir = tempfile::tempdir()?;
|
|
|
|
let path = tempdir.path().join("test");
|
|
|
|
assert!(open(&path).is_err());
|
|
|
|
create_dir_all(&path)?;
|
|
|
|
assert!(path.is_dir())
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-09-05 08:31:33 +02:00
|
|
|
|
|
|
|
// the following test is marked as serial because
|
|
|
|
// we are doing unshare of user ns and fork, so better to run in serial,
|
|
|
|
#[test]
|
|
|
|
#[serial]
|
|
|
|
fn test_userns_spec_validation() -> Result<(), test_utils::TestError> {
|
|
|
|
use nix::sched::{unshare, CloneFlags};
|
|
|
|
// default rootful spec
|
|
|
|
let rootful_spec = Spec::default();
|
|
|
|
// as we are not in a user ns, and spec does not have user ns
|
|
|
|
// we should get error here
|
|
|
|
assert!(validate_spec_for_new_user_ns(&rootful_spec).is_err());
|
|
|
|
|
|
|
|
let rootless_spec = Spec::rootless(1000, 1000);
|
|
|
|
// because the spec contains user ns info, we should not get error
|
|
|
|
assert!(validate_spec_for_new_user_ns(&rootless_spec).is_ok());
|
|
|
|
|
|
|
|
test_utils::test_in_child_process(|| {
|
|
|
|
unshare(CloneFlags::CLONE_NEWUSER).unwrap();
|
|
|
|
// here we are in a new user namespace
|
|
|
|
let rootful_spec = Spec::default();
|
|
|
|
// because we are already in a new user ns, it is fine if spec
|
|
|
|
// does not have user ns, and because the test is running as
|
|
|
|
// non root
|
|
|
|
assert!(validate_spec_for_new_user_ns(&rootful_spec).is_ok());
|
|
|
|
|
|
|
|
let rootless_spec = Spec::rootless(1000, 1000);
|
|
|
|
// following should succeed irrespective if we're in user ns or not
|
|
|
|
assert!(validate_spec_for_new_user_ns(&rootless_spec).is_ok());
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
}
|
2021-04-29 10:34:23 +02:00
|
|
|
}
|