mirror of
https://github.com/containers/youki
synced 2024-05-07 08:06:18 +02:00
256 lines
7.8 KiB
Rust
256 lines
7.8 KiB
Rust
use crate::utils::test_inside_container;
|
|
use anyhow::{anyhow, bail};
|
|
use nix::sys::stat::SFlag;
|
|
use oci_spec::runtime::LinuxBuilder;
|
|
use oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder};
|
|
use std::path::PathBuf;
|
|
use test_framework::{Test, TestGroup, TestResult};
|
|
|
|
fn get_spec(readonly_paths: Vec<String>) -> Spec {
|
|
SpecBuilder::default()
|
|
.linux(
|
|
LinuxBuilder::default()
|
|
.readonly_paths(readonly_paths)
|
|
.build()
|
|
.expect("could not build"),
|
|
)
|
|
.process(
|
|
ProcessBuilder::default()
|
|
.args(vec!["runtimetest".to_string()])
|
|
.build()
|
|
.unwrap(),
|
|
)
|
|
.build()
|
|
.unwrap()
|
|
}
|
|
|
|
fn check_readonly_paths() -> TestResult {
|
|
// here we abbreviate 'readonly' as ro for variable names,
|
|
// purely for ease of writing
|
|
|
|
let ro_dir = "readonly_dir";
|
|
let ro_subdir = "readonly_subdir";
|
|
let ro_file = "readonly_file";
|
|
|
|
// in the runtime-tools tests, they start these with a '/',
|
|
// but in that case, when joined with any path later,
|
|
// the '/' takes preference, and path is not actually joined
|
|
// eg : (test).join(t1) = test/t1
|
|
// (test).join(.t1) = /t1
|
|
// which is not what we want, so we leave them without '/'
|
|
let ro_dir_top = PathBuf::from(ro_dir);
|
|
let ro_file_top = PathBuf::from(ro_file);
|
|
|
|
let ro_dir_sub = ro_dir_top.join(ro_subdir);
|
|
let ro_file_sub = ro_dir_top.join(ro_file);
|
|
let ro_file_sub_sub = ro_dir_sub.join(ro_file);
|
|
|
|
let root = PathBuf::from("/");
|
|
|
|
let ro_paths = vec![
|
|
root.join(&ro_dir_top).to_string_lossy().to_string(),
|
|
root.join(&ro_file_top).to_string_lossy().to_string(),
|
|
root.join(&ro_dir_sub).to_string_lossy().to_string(),
|
|
root.join(&ro_file_sub).to_string_lossy().to_string(),
|
|
root.join(&ro_file_sub_sub).to_string_lossy().to_string(),
|
|
];
|
|
|
|
let spec = get_spec(ro_paths);
|
|
test_inside_container(spec, &|bundle_path| {
|
|
use std::{fs, io};
|
|
let test_dir = bundle_path.join(&ro_dir_sub);
|
|
|
|
match fs::create_dir_all(&test_dir) {
|
|
io::Result::Ok(_) => { /*This is expected*/ }
|
|
io::Result::Err(e) => {
|
|
bail!(e)
|
|
}
|
|
}
|
|
|
|
match fs::File::create(test_dir.join("tmp")) {
|
|
io::Result::Ok(_) => { /*This is expected*/ }
|
|
io::Result::Err(e) => {
|
|
bail!(e)
|
|
}
|
|
}
|
|
|
|
let test_sub_sub_file = bundle_path.join(&ro_file_sub_sub);
|
|
match fs::File::create(&test_sub_sub_file) {
|
|
io::Result::Ok(_) => { /*This is expected*/ }
|
|
io::Result::Err(e) => {
|
|
bail!(e)
|
|
}
|
|
}
|
|
|
|
let test_sub_file = bundle_path.join(&ro_file_sub);
|
|
match fs::File::create(&test_sub_file) {
|
|
io::Result::Ok(_) => { /*This is expected*/ }
|
|
io::Result::Err(e) => {
|
|
bail!(e)
|
|
}
|
|
}
|
|
|
|
let test_file = bundle_path.join(&ro_file);
|
|
match fs::File::create(&test_file) {
|
|
io::Result::Ok(_) => { /*This is expected*/ }
|
|
io::Result::Err(e) => {
|
|
bail!(e)
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
fn check_readonly_rel_path() -> TestResult {
|
|
let ro_rel_path = "readonly_relpath";
|
|
let ro_paths = vec![ro_rel_path.to_string()];
|
|
let spec = get_spec(ro_paths);
|
|
|
|
test_inside_container(spec, &|bundle_path| {
|
|
use std::{fs, io};
|
|
let test_file = bundle_path.join(ro_rel_path);
|
|
|
|
match fs::metadata(&test_file) {
|
|
io::Result::Ok(md) => {
|
|
bail!(
|
|
"reading path {:?} should have given error, found {:?} instead",
|
|
test_file,
|
|
md
|
|
)
|
|
}
|
|
io::Result::Err(e) => {
|
|
let err = e.kind();
|
|
if let io::ErrorKind::NotFound = err {
|
|
Ok(())
|
|
} else {
|
|
bail!("expected not found error, got {:?}", err);
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
fn check_readonly_symlinks() -> TestResult {
|
|
let root = PathBuf::from("/");
|
|
let ro_symlink = "readonly_symlink";
|
|
let ro_paths = vec![root.join(&ro_symlink).to_string_lossy().to_string()];
|
|
|
|
let spec = get_spec(ro_paths);
|
|
|
|
let res = test_inside_container(spec, &|bundle_path| {
|
|
use std::{fs, io};
|
|
let test_file = bundle_path.join(ro_symlink);
|
|
|
|
match std::os::unix::fs::symlink("../readonly_symlink", &test_file) {
|
|
io::Result::Ok(_) => { /* This is expected */ }
|
|
io::Result::Err(e) => {
|
|
bail!("error in creating symlink, to {:?} {:?}", test_file, e);
|
|
}
|
|
}
|
|
let r_path = match fs::read_link(&test_file) {
|
|
io::Result::Ok(p) => p,
|
|
io::Result::Err(e) => {
|
|
bail!("error in reading symlink at {:?} : {:?}", test_file, e);
|
|
}
|
|
};
|
|
|
|
match fs::metadata(&r_path) {
|
|
io::Result::Ok(md) => {
|
|
bail!(
|
|
"reading symlink for {:?} should have given error, found {:?} instead",
|
|
test_file,
|
|
md
|
|
)
|
|
}
|
|
io::Result::Err(e) => {
|
|
let err = e.kind();
|
|
if let io::ErrorKind::NotFound = err {
|
|
Ok(())
|
|
} else {
|
|
bail!("expected not found error, got {:?}", err);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
if let TestResult::Passed = res {
|
|
TestResult::Failed(anyhow!(
|
|
"expected error in container creation with invalid symlink, found no error"
|
|
))
|
|
} else {
|
|
TestResult::Passed
|
|
}
|
|
}
|
|
|
|
fn test_node(mode: u32) -> TestResult {
|
|
let root = PathBuf::from("/");
|
|
let ro_device = "readonly_device";
|
|
let ro_paths = vec![root.join(&ro_device).to_string_lossy().to_string()];
|
|
|
|
let spec = get_spec(ro_paths);
|
|
|
|
test_inside_container(spec, &|bundle_path| {
|
|
use std::os::unix::fs::OpenOptionsExt;
|
|
use std::{fs, io};
|
|
let test_file = bundle_path.join(&ro_device);
|
|
|
|
let mut opts = fs::OpenOptions::new();
|
|
opts.mode(mode);
|
|
opts.create(true);
|
|
if let io::Result::Err(e) = fs::OpenOptions::new()
|
|
.mode(mode)
|
|
.create(true)
|
|
.write(true)
|
|
.open(&test_file)
|
|
{
|
|
bail!(
|
|
"could not create device node at {:?} with mode {}, got error {:?}",
|
|
test_file,
|
|
mode ^ 0o666,
|
|
e
|
|
);
|
|
}
|
|
|
|
match fs::metadata(&test_file) {
|
|
io::Result::Ok(_) => Ok(()),
|
|
io::Result::Err(e) => {
|
|
bail!("error in creating device node, {:?}", e)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
fn check_readonly_device_nodes() -> TestResult {
|
|
let modes = [
|
|
SFlag::S_IFBLK.bits() | 0o666,
|
|
SFlag::S_IFCHR.bits() | 0o666,
|
|
SFlag::S_IFIFO.bits() | 0o666,
|
|
];
|
|
for mode in modes {
|
|
let res = test_node(mode);
|
|
if let TestResult::Failed(_) = res {
|
|
return res;
|
|
}
|
|
std::thread::sleep(std::time::Duration::from_millis(1000));
|
|
}
|
|
TestResult::Passed
|
|
}
|
|
|
|
pub fn get_ro_paths_test<'a>() -> TestGroup<'a> {
|
|
let ro_paths = Test::new("readonly_paths", Box::new(check_readonly_paths));
|
|
let ro_rel_paths = Test::new("readonly_rel_paths", Box::new(check_readonly_rel_path));
|
|
let ro_symlinks = Test::new("readonly_symlinks", Box::new(check_readonly_symlinks));
|
|
let ro_device_nodes = Test::new(
|
|
"readonly_device_nodes",
|
|
Box::new(check_readonly_device_nodes),
|
|
);
|
|
let mut tg = TestGroup::new("readonly_paths");
|
|
tg.add(vec![
|
|
Box::new(ro_paths),
|
|
Box::new(ro_rel_paths),
|
|
Box::new(ro_symlinks),
|
|
Box::new(ro_device_nodes),
|
|
]);
|
|
tg
|
|
}
|