2021-06-06 13:33:51 +02:00
|
|
|
//! Utility functionality
|
|
|
|
|
2021-09-29 07:58:00 +02:00
|
|
|
use anyhow::Context;
|
|
|
|
use anyhow::{bail, Result};
|
|
|
|
use nix::sys::stat::Mode;
|
|
|
|
use nix::sys::statfs;
|
|
|
|
use nix::unistd;
|
2021-08-05 09:42:23 +02:00
|
|
|
use std::collections::HashMap;
|
2021-03-27 12:08:13 +01:00
|
|
|
use std::ffi::CString;
|
2021-08-08 01:08:14 +02:00
|
|
|
use std::fs::{self, DirBuilder, File};
|
2021-06-20 02:32:02 +02:00
|
|
|
use std::ops::Deref;
|
2021-08-08 01:08:14 +02:00
|
|
|
use std::os::linux::fs::MetadataExt;
|
|
|
|
use std::os::unix::fs::DirBuilderExt;
|
2021-09-21 01:13:13 +02:00
|
|
|
use std::os::unix::prelude::AsRawFd;
|
2021-04-10 16:36:41 +02:00
|
|
|
use std::path::{Path, PathBuf};
|
2021-06-05 00:20:02 +02:00
|
|
|
use std::time::Duration;
|
2021-03-27 12:08:13 +01:00
|
|
|
|
2021-04-07 14:07:01 +02:00
|
|
|
pub trait PathBufExt {
|
|
|
|
fn as_in_container(&self) -> Result<PathBuf>;
|
2021-04-10 16:36:41 +02:00
|
|
|
fn join_absolute_path(&self, p: &Path) -> Result<PathBuf>;
|
2021-04-07 14:07:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl PathBufExt for PathBuf {
|
|
|
|
fn as_in_container(&self) -> Result<PathBuf> {
|
|
|
|
if self.is_relative() {
|
2021-06-06 12:13:22 +02:00
|
|
|
bail!("Relative path cannot be converted to the path in the container.")
|
2021-04-07 14:07:01 +02:00
|
|
|
} else {
|
|
|
|
let path_string = self.to_string_lossy().into_owned();
|
|
|
|
Ok(PathBuf::from(path_string[1..].to_string()))
|
|
|
|
}
|
|
|
|
}
|
2021-04-10 16:36:41 +02:00
|
|
|
|
|
|
|
fn join_absolute_path(&self, p: &Path) -> Result<PathBuf> {
|
|
|
|
if !p.is_absolute() && !p.as_os_str().is_empty() {
|
|
|
|
bail!(
|
2021-06-08 08:34:50 +02:00
|
|
|
"cannot join {:?} because it is not the absolute path.",
|
2021-04-10 16:36:41 +02:00
|
|
|
p.display()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
Ok(PathBuf::from(format!("{}{}", self.display(), p.display())))
|
|
|
|
}
|
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()
|
|
|
|
}
|
|
|
|
|
2021-08-05 09:52:41 +02:00
|
|
|
pub fn do_exec(path: impl AsRef<Path>, args: &[String]) -> Result<()> {
|
2021-05-05 13:54:11 +02:00
|
|
|
let p = CString::new(path.as_ref().to_string_lossy().to_string())?;
|
2021-03-27 12:08:13 +01:00
|
|
|
let a: Vec<CString> = args
|
|
|
|
.iter()
|
|
|
|
.map(|s| CString::new(s.to_string()).unwrap_or_default())
|
|
|
|
.collect();
|
|
|
|
unistd::execvp(&p, &a)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO implement
|
|
|
|
pub fn set_name(_name: &str) -> Result<()> {
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-04-29 10:34:23 +02:00
|
|
|
|
2021-05-29 10:52:36 +02:00
|
|
|
/// If None, it will generate a default path for cgroups.
|
|
|
|
pub fn get_cgroup_path(cgroups_path: &Option<PathBuf>, container_id: &str) -> PathBuf {
|
|
|
|
match cgroups_path {
|
|
|
|
Some(cpath) => cpath.clone(),
|
2021-09-25 22:52:07 +02:00
|
|
|
None => PathBuf::from(container_id),
|
2021-05-29 10:52:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-05 00:20:02 +02:00
|
|
|
pub fn delete_with_retry<P: AsRef<Path>>(path: P) -> Result<()> {
|
|
|
|
let mut attempts = 0;
|
|
|
|
let mut delay = Duration::from_millis(10);
|
|
|
|
let path = path.as_ref();
|
|
|
|
|
|
|
|
while attempts < 5 {
|
|
|
|
if fs::remove_dir(path).is_ok() {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
std::thread::sleep(delay);
|
|
|
|
attempts += attempts;
|
|
|
|
delay *= attempts;
|
|
|
|
}
|
|
|
|
|
|
|
|
bail!("could not delete {:?}", path)
|
|
|
|
}
|
|
|
|
|
2021-06-15 22:38:51 +02:00
|
|
|
pub fn write_file<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
|
|
|
|
let path = path.as_ref();
|
|
|
|
fs::write(path, contents).with_context(|| format!("failed to write to {:?}", path))?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-07-01 22:25:23 +02:00
|
|
|
pub fn create_dir_all<P: AsRef<Path>>(path: P) -> Result<()> {
|
|
|
|
let path = path.as_ref();
|
|
|
|
fs::create_dir_all(path).with_context(|| format!("failed to create directory {:?}", path))
|
|
|
|
}
|
|
|
|
|
2021-07-15 22:12:21 +02:00
|
|
|
pub fn open<P: AsRef<Path>>(path: P) -> Result<File> {
|
|
|
|
let path = path.as_ref();
|
|
|
|
File::open(path).with_context(|| format!("failed to open {:?}", path))
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
/// use youki::utils::create_dir_all_with_mode;
|
|
|
|
/// 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())
|
|
|
|
/// ```
|
|
|
|
pub fn create_dir_all_with_mode<P: AsRef<Path>>(path: P, owner: u32, mode: Mode) -> Result<()> {
|
|
|
|
let path = path.as_ref();
|
|
|
|
if !path.exists() {
|
|
|
|
DirBuilder::new()
|
|
|
|
.recursive(true)
|
|
|
|
.mode(mode.bits())
|
|
|
|
.create(path)
|
|
|
|
.with_context(|| format!("failed to create directory {}", path.display()))?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let metadata = path
|
|
|
|
.metadata()
|
|
|
|
.with_context(|| format!("failed to get metadata for {}", path.display()))?;
|
|
|
|
|
|
|
|
if metadata.is_dir()
|
|
|
|
&& metadata.st_uid() == owner
|
|
|
|
&& metadata.st_mode() & mode.bits() == mode.bits()
|
|
|
|
{
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
bail!(
|
|
|
|
"metadata for {} does not possess the expected attributes",
|
|
|
|
path.display()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
pub fn ensure_procfs(path: &Path) -> Result<()> {
|
|
|
|
let procfs_fd = fs::File::open(path)?;
|
|
|
|
let fstat_info = statfs::fstatfs(&procfs_fd.as_raw_fd())?;
|
|
|
|
|
|
|
|
if fstat_info.filesystem_type() != statfs::PROC_SUPER_MAGIC {
|
|
|
|
bail!(format!("{:?} is not on the procfs", path));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-06-20 02:32:02 +02:00
|
|
|
pub struct TempDir {
|
|
|
|
path: Option<PathBuf>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TempDir {
|
|
|
|
pub fn new<P: Into<PathBuf>>(path: P) -> Result<Self> {
|
|
|
|
let p = path.into();
|
2021-08-06 23:22:12 +02:00
|
|
|
std::fs::create_dir_all(&p)
|
|
|
|
.with_context(|| format!("failed to create directory {}", p.display()))?;
|
2021-06-20 02:32:02 +02:00
|
|
|
Ok(Self { path: Some(p) })
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn path(&self) -> &Path {
|
|
|
|
self.path
|
|
|
|
.as_ref()
|
|
|
|
.expect("temp dir has already been removed")
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove(&mut self) {
|
|
|
|
if let Some(p) = &self.path {
|
|
|
|
let _ = fs::remove_dir_all(p);
|
|
|
|
self.path = None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for TempDir {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
self.remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AsRef<Path> for TempDir {
|
|
|
|
fn as_ref(&self) -> &Path {
|
|
|
|
self.path()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Deref for TempDir {
|
|
|
|
type Target = Path;
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
self.path()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn create_temp_dir(test_name: &str) -> Result<TempDir> {
|
|
|
|
let dir = TempDir::new(std::env::temp_dir().join(test_name))?;
|
|
|
|
Ok(dir)
|
|
|
|
}
|
|
|
|
|
2021-09-30 01:06:11 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
pub(crate) mod test_utils {
|
|
|
|
use anyhow::Context;
|
|
|
|
use anyhow::{bail, Result};
|
|
|
|
use ipc_channel::ipc;
|
|
|
|
use nix::sys::wait;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
struct TestResult {
|
|
|
|
success: bool,
|
|
|
|
message: String,
|
|
|
|
}
|
2021-09-29 07:58:00 +02:00
|
|
|
|
2021-09-30 01:06:11 +02:00
|
|
|
pub fn test_in_child_process<F: FnOnce() -> Result<()>>(cb: F) -> Result<()> {
|
|
|
|
let (sender, receiver) = ipc::channel::<TestResult>()?;
|
|
|
|
match unsafe { nix::unistd::fork()? } {
|
|
|
|
nix::unistd::ForkResult::Parent { child } => {
|
|
|
|
let res = receiver.recv().unwrap();
|
|
|
|
wait::waitpid(child, None)?;
|
2021-09-29 07:58:00 +02:00
|
|
|
|
2021-09-30 01:06:11 +02:00
|
|
|
if !res.success {
|
|
|
|
bail!("child process failed: {}", res.message);
|
|
|
|
}
|
2021-09-29 07:58:00 +02:00
|
|
|
}
|
2021-09-30 01:06:11 +02:00
|
|
|
nix::unistd::ForkResult::Child => {
|
|
|
|
let test_result = match cb() {
|
|
|
|
Ok(_) => TestResult {
|
|
|
|
success: true,
|
|
|
|
message: String::new(),
|
|
|
|
},
|
|
|
|
Err(err) => TestResult {
|
|
|
|
success: false,
|
|
|
|
message: err.to_string(),
|
|
|
|
},
|
|
|
|
};
|
|
|
|
sender
|
|
|
|
.send(test_result)
|
|
|
|
.context("failed to send from the child process")?;
|
|
|
|
std::process::exit(0);
|
|
|
|
}
|
|
|
|
};
|
2021-09-29 07:58:00 +02:00
|
|
|
|
2021-09-30 01:06:11 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
2021-09-29 07:58:00 +02:00
|
|
|
}
|
|
|
|
|
2021-04-29 10:34:23 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_join_absolute_path() {
|
|
|
|
assert_eq!(
|
|
|
|
PathBuf::from("sample/a/")
|
|
|
|
.join_absolute_path(&PathBuf::from("/b"))
|
|
|
|
.unwrap(),
|
|
|
|
PathBuf::from("sample/a/b")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_join_absolute_path_error() {
|
2021-06-21 16:04:46 +02:00
|
|
|
assert!(PathBuf::from("sample/a/")
|
|
|
|
.join_absolute_path(&PathBuf::from("b/c"))
|
|
|
|
.is_err(),);
|
2021-04-29 10:34:23 +02:00
|
|
|
}
|
2021-05-29 10:52:36 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_cgroup_path() {
|
|
|
|
let cid = "sample_container_id";
|
|
|
|
assert_eq!(
|
|
|
|
get_cgroup_path(&None, cid),
|
2021-09-25 22:52:07 +02:00
|
|
|
PathBuf::from("sample_container_id")
|
2021-05-29 10:52:36 +02:00
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
get_cgroup_path(&Some(PathBuf::from("/youki")), cid),
|
|
|
|
PathBuf::from("/youki")
|
|
|
|
);
|
|
|
|
}
|
2021-08-05 09:42:23 +02:00
|
|
|
#[test]
|
|
|
|
fn test_parse_env() -> Result<()> {
|
|
|
|
let key = "key".to_string();
|
|
|
|
let value = "value".to_string();
|
|
|
|
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(())
|
|
|
|
}
|
2021-04-29 10:34:23 +02:00
|
|
|
}
|