diff --git a/tests/contest/contest/src/main.rs b/tests/contest/contest/src/main.rs index bb1825d39..d8f8a2e15 100644 --- a/tests/contest/contest/src/main.rs +++ b/tests/contest/contest/src/main.rs @@ -1,6 +1,7 @@ mod tests; mod utils; +use crate::tests::devices::get_devices_test; use crate::tests::domainname::get_domainname_tests; use crate::tests::example::get_example_test; use crate::tests::hooks::get_hooks_tests; @@ -109,6 +110,7 @@ fn main() -> Result<()> { let sysctl = get_sysctl_test(); let scheduler = get_scheduler_test(); let io_priority_test = get_io_priority_test(); + let devices = get_devices_test(); tm.add_test_group(Box::new(cl)); tm.add_test_group(Box::new(cc)); @@ -131,6 +133,7 @@ fn main() -> Result<()> { tm.add_test_group(Box::new(intel_rdt)); tm.add_test_group(Box::new(sysctl)); tm.add_test_group(Box::new(scheduler)); + tm.add_test_group(Box::new(devices)); tm.add_test_group(Box::new(io_priority_test)); tm.add_cleanup(Box::new(cgroups::cleanup_v1)); diff --git a/tests/contest/contest/src/tests/devices/devices_test.rs b/tests/contest/contest/src/tests/devices/devices_test.rs new file mode 100644 index 000000000..c6d89f144 --- /dev/null +++ b/tests/contest/contest/src/tests/devices/devices_test.rs @@ -0,0 +1,71 @@ +use crate::utils::test_inside_container; +use anyhow::{Context, Ok, Result}; +use oci_spec::runtime::{ + LinuxBuilder, LinuxDeviceBuilder, LinuxDeviceType, ProcessBuilder, Spec, SpecBuilder, +}; +use test_framework::{test_result, Test, TestGroup, TestResult}; + +fn create_spec() -> Result { + let device1 = LinuxDeviceBuilder::default() + .path("/dev/test1") + .typ(LinuxDeviceType::C) + .major(10) + .minor(666) + .file_mode(432u32) + .uid(0u32) + .gid(0u32) + .build() + .context("failed to create device 1")?; + + let device2 = LinuxDeviceBuilder::default() + .path("/dev/test2") + .typ(LinuxDeviceType::B) + .major(8) + .minor(666) + .file_mode(432u32) + .uid(0u32) + .gid(0u32) + .build() + .context("failed to create device 2")?; + + let device3 = LinuxDeviceBuilder::default() + .path("/dev/test3") + .typ(LinuxDeviceType::P) + .major(8) + .minor(666) + .file_mode(432u32) + .build() + .context("failed to create device 3")?; + + let spec = SpecBuilder::default() + .process( + ProcessBuilder::default() + .args(vec!["runtimetest".to_string(), "devices".to_string()]) + .build() + .expect("error in creating process config"), + ) + .linux( + LinuxBuilder::default() + .devices(vec![device1, device2, device3]) + .build() + .context("failed to build linux spec")?, + ) + .build() + .context("failed to build spec")?; + + Ok(spec) +} + +fn devices_test() -> TestResult { + let spec = test_result!(create_spec()); + test_inside_container(spec, &|_| Ok(())) +} + +pub fn get_devices_test() -> TestGroup { + let mut device_test_group = TestGroup::new("devices"); + + let test = Test::new("device_test", Box::new(devices_test)); + device_test_group.add(vec![Box::new(test)]); + + device_test_group +} diff --git a/tests/contest/contest/src/tests/devices/mod.rs b/tests/contest/contest/src/tests/devices/mod.rs new file mode 100644 index 000000000..df725763e --- /dev/null +++ b/tests/contest/contest/src/tests/devices/mod.rs @@ -0,0 +1,2 @@ +mod devices_test; +pub use devices_test::get_devices_test; diff --git a/tests/contest/contest/src/tests/mod.rs b/tests/contest/contest/src/tests/mod.rs index 5847f6f8e..1fee606b1 100644 --- a/tests/contest/contest/src/tests/mod.rs +++ b/tests/contest/contest/src/tests/mod.rs @@ -1,4 +1,5 @@ pub mod cgroups; +pub mod devices; pub mod domainname; pub mod example; pub mod hooks; diff --git a/tests/contest/runtimetest/src/main.rs b/tests/contest/runtimetest/src/main.rs index 9474c4682..70a6bea09 100644 --- a/tests/contest/runtimetest/src/main.rs +++ b/tests/contest/runtimetest/src/main.rs @@ -42,6 +42,7 @@ fn main() { "io_priority_class_rt" => tests::test_io_priority_class(&spec, IoprioClassRt), "io_priority_class_be" => tests::test_io_priority_class(&spec, IoprioClassBe), "io_priority_class_idle" => tests::test_io_priority_class(&spec, IoprioClassIdle), + "devices" => tests::validate_devices(&spec), _ => eprintln!("error due to unexpected execute test name: {execute_test}"), } } diff --git a/tests/contest/runtimetest/src/tests.rs b/tests/contest/runtimetest/src/tests.rs index dee3afc79..47022465b 100644 --- a/tests/contest/runtimetest/src/tests.rs +++ b/tests/contest/runtimetest/src/tests.rs @@ -6,8 +6,11 @@ use oci_spec::runtime::{ IOPriorityClass::{self, IoprioClassBe, IoprioClassIdle, IoprioClassRt}, LinuxSchedulerPolicy, Spec, }; +use oci_spec::runtime::{LinuxDevice, LinuxDeviceType}; use std::fs::{self, read_dir}; use std::mem; +use std::os::linux::fs::MetadataExt; +use std::os::unix::fs::{FileTypeExt, PermissionsExt}; use std::path::Path; ////////// ANCHOR: example_hello_world @@ -381,6 +384,117 @@ pub fn validate_scheduler_policy(spec: &Spec) { } } +pub fn validate_devices(spec: &Spec) { + let linux = spec.linux().as_ref().unwrap(); + if let Some(devices) = linux.devices() { + for (i, device) in devices.iter().enumerate() { + validate_device( + device, + &format!( + "{} (linux.devices[{}])", + device.path().as_path().to_str().unwrap(), + i + ), + ); + } + } +} + +fn validate_device(device: &LinuxDevice, description: &str) { + let file_data = match fs::metadata(device.path()) { + Ok(data) => data, + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + eprintln!( + "error due to device not being present in path: {:?}", + device.path() + ); + } else { + eprintln!( + "error due to fail to get metadata for device path {:?}, error: {}", + device.path(), + e + ); + } + return; + } + }; + + let mut expected_type = device.typ(); + if expected_type == LinuxDeviceType::U { + expected_type = LinuxDeviceType::C; + } + + let file_type = file_data.file_type(); + let actual_type = if file_type.is_char_device() { + LinuxDeviceType::C + } else if file_type.is_block_device() { + LinuxDeviceType::B + } else if file_type.is_fifo() { + LinuxDeviceType::P + } else { + LinuxDeviceType::U + }; + + if actual_type != expected_type { + eprintln!("error due to device type want {expected_type:?}, got {actual_type:?}"); + } + + if actual_type != LinuxDeviceType::P { + let dev = file_data.st_rdev(); + let major = (dev >> 8) & 0xfff; + let minor = (dev & 0xff) | ((dev >> 12) & 0xfff00); + if major != device.major() as u64 { + eprintln!( + "error due to device major want {}, got {}", + device.major(), + major + ); + } + if minor != device.minor() as u64 { + eprintln!( + "error due to device minor want {}, got {}", + device.minor(), + minor + ); + } + } + + let expected_permissions = device.file_mode(); + if let Some(expected) = expected_permissions { + let actual_permissions = file_data.permissions().mode() & 0o777; + if actual_permissions != expected { + eprintln!( + "error due to device file mode want {expected:?}, got {actual_permissions:?}" + ); + } + } + + if description == "/dev/console (default device)" { + eprintln!("we need the major/minor from the controlling TTY"); + } + + if let Some(expected_uid) = device.uid() { + if file_data.st_uid() != expected_uid { + eprintln!( + "error due to device uid want {}, got {}", + expected_uid, + file_data.st_uid() + ); + } + } + + if let Some(expected_gid) = device.gid() { + if file_data.st_gid() != expected_gid { + eprintln!( + "error due to device gid want {}, got {}", + expected_gid, + file_data.st_gid() + ); + } + } +} + pub fn test_io_priority_class(spec: &Spec, io_priority_class: IOPriorityClass) { let io_priority_spec = spec .process()