mirror of
https://github.com/kata-containers/cgroups-rs.git
synced 2026-08-05 02:13:23 +00:00
devices: parse files in the devices subsystem
Also, a test! Signed-off-by: Levente Kurusa <lkurusa@acm.org>
This commit is contained in:
174
src/devices.rs
174
src/devices.rs
@@ -5,7 +5,7 @@
|
||||
use std::path::PathBuf;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use {CgroupError, DeviceResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
|
||||
use {DeviceResource, CgroupError, DeviceResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
|
||||
|
||||
/// A controller that allows controlling the `devices` subsystem of a Cgroup.
|
||||
///
|
||||
@@ -17,6 +17,108 @@ pub struct DevicesController{
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
/// An enum holding the different types of devices that can be manipulated using this controller.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum DeviceType {
|
||||
All,
|
||||
Char,
|
||||
Block,
|
||||
}
|
||||
|
||||
impl Default for DeviceType {
|
||||
fn default() -> Self { DeviceType::All }
|
||||
}
|
||||
|
||||
impl DeviceType {
|
||||
/// Convert a DeviceType into the character that the kernel recognizes.
|
||||
pub fn to_char(self: &Self) -> char {
|
||||
match self {
|
||||
DeviceType::All => 'a',
|
||||
DeviceType::Char => 'c',
|
||||
DeviceType::Block => 'b',
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_char(c: Option<char>) -> Option<DeviceType> {
|
||||
match c {
|
||||
Some('a') => Some(DeviceType::All),
|
||||
Some('c') => Some(DeviceType::Char),
|
||||
Some('b') => Some(DeviceType::Block),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An enum with the permissions that can be allowed/denied to the control group.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum DevicePermissions {
|
||||
/// Permission to read from the device.
|
||||
Read,
|
||||
/// Permission to write to the device.
|
||||
Write,
|
||||
/// Permission to execute the `mknod(2)` system call with the device's major and minor numbers.
|
||||
/// That is, the permission to create a special file that refers to the device node.
|
||||
MkNod,
|
||||
}
|
||||
|
||||
impl DevicePermissions {
|
||||
/// Convert a DevicePermissions into the character that the kernel recognizes.
|
||||
pub fn to_char(self: &Self) -> char {
|
||||
match self {
|
||||
DevicePermissions::Read => 'r',
|
||||
DevicePermissions::Write => 'w',
|
||||
DevicePermissions::MkNod => 'm',
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a char to a DevicePermission if there is such a mapping.
|
||||
pub fn from_char(c: char) -> Option<DevicePermissions> {
|
||||
match c {
|
||||
'r' => Some(DevicePermissions::Read),
|
||||
'w' => Some(DevicePermissions::Write),
|
||||
'm' => Some(DevicePermissions::MkNod),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether the string is a valid descriptor of DevicePermissions.
|
||||
pub fn is_valid(s: &String) -> bool {
|
||||
if s == "" {
|
||||
return false;
|
||||
}
|
||||
for i in s.chars() {
|
||||
if i != 'r' && i != 'w' && i != 'm' {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Returns a Vec will all the permissions that a device can have.
|
||||
pub fn all() -> Vec<DevicePermissions> {
|
||||
vec![
|
||||
DevicePermissions::Read,
|
||||
DevicePermissions::Write,
|
||||
DevicePermissions::MkNod,
|
||||
]
|
||||
}
|
||||
|
||||
/// Convert a string into DevicePermissions.
|
||||
///
|
||||
/// NOTE: This function makes no effort in verifying the String.
|
||||
pub fn from_string(s: &String) -> Vec<DevicePermissions> {
|
||||
let mut v = Vec::new();
|
||||
if s == "" {
|
||||
return v;
|
||||
}
|
||||
for e in s.chars() {
|
||||
v.push(DevicePermissions::from_char(e).unwrap());
|
||||
}
|
||||
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
impl Controller for DevicesController {
|
||||
fn control_type(self: &Self) -> Controllers { Controllers::Devices }
|
||||
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
|
||||
@@ -29,12 +131,10 @@ impl Controller for DevicesController {
|
||||
|
||||
if res.update_values {
|
||||
for i in &res.devices {
|
||||
let wstr = format!("{} {}:{} {}",
|
||||
i.devtype, i.major, i.minor, i.access);
|
||||
if i.allow {
|
||||
let _ = self.allow_device(&wstr);
|
||||
let _ = self.allow_device(i.devtype, i.major, i.minor, &i.access);
|
||||
} else {
|
||||
let _ = self.deny_device(&wstr);
|
||||
let _ = self.deny_device(i.devtype, i.major, i.minor, &i.access);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,41 +174,73 @@ impl DevicesController {
|
||||
|
||||
/// Allow a (possibly, set of) device(s) to be used by the tasks in the control group.
|
||||
///
|
||||
/// The format of `dev` is rather simple:
|
||||
/// `$type $major:$minor $rwm`
|
||||
/// where `$rwm` is a combination of the characters `r`, `w`, `m`, each standing for read,
|
||||
/// write, mknod permissions.
|
||||
///
|
||||
/// Note that `dev` can be "regex"-like: both `$major` and `$minor` can be `*` which implies
|
||||
/// that their value does not matter.
|
||||
pub fn allow_device(self: &Self, dev: &String) -> Result<(), CgroupError> {
|
||||
pub fn allow_device(self: &Self, devtype: DeviceType, major: i64, minor: i64, perm: &Vec<DevicePermissions>) -> Result<(), CgroupError> {
|
||||
let perms = perm.iter().map(DevicePermissions::to_char).collect::<String>();
|
||||
let minor = if minor == -1 { "*".to_string() } else { format!("{}", minor) };
|
||||
let major = if major == -1 { "*".to_string() } else { format!("{}", major) };
|
||||
let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms);
|
||||
self.open_path("devices.allow", true).and_then(|mut file| {
|
||||
file.write_all(dev.as_ref()).map_err(CgroupError::WriteError)
|
||||
file.write_all(final_str.as_ref()).map_err(CgroupError::WriteError)
|
||||
})
|
||||
}
|
||||
|
||||
/// Deny the control group's tasks access to the devices covered by `dev`.
|
||||
///
|
||||
/// The format of `dev` is rather simple:
|
||||
/// `$type $major:$minor $rwm`
|
||||
/// where `$rwm` is a combination of the characters `r`, `w`, `m`, each standing for read,
|
||||
/// write, mknod permissions.
|
||||
///
|
||||
/// Note that `dev` can be "regex"-like: both `$major` and `$minor` can be `*` which implies
|
||||
/// that their value does not matter.
|
||||
pub fn deny_device(self: &Self, dev: &String) -> Result<(), CgroupError> {
|
||||
pub fn deny_device(self: &Self, devtype: DeviceType, major: i64, minor: i64, perm: &Vec<DevicePermissions>) -> Result<(), CgroupError> {
|
||||
let perms = perm.iter().map(DevicePermissions::to_char).collect::<String>();
|
||||
let minor = if minor == -1 { "*".to_string() } else { format!("{}", minor) };
|
||||
let major = if major == -1 { "*".to_string() } else { format!("{}", major) };
|
||||
let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms);
|
||||
self.open_path("devices.deny", true).and_then(|mut file| {
|
||||
file.write_all(dev.as_ref()).map_err(CgroupError::WriteError)
|
||||
file.write_all(final_str.as_ref()).map_err(CgroupError::WriteError)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the current list of allowed devices.
|
||||
pub fn allowed_devices(self: &Self) -> Result<String, CgroupError> {
|
||||
pub fn allowed_devices(self: &Self) -> Result<Vec<DeviceResource>, CgroupError> {
|
||||
self.open_path("devices.list", false).and_then(|mut file| {
|
||||
let mut s = String::new();
|
||||
let res = file.read_to_string(&mut s);
|
||||
match res {
|
||||
Ok(_) => Ok(s),
|
||||
Ok(_) => {
|
||||
s.lines().fold(Ok(Vec::new()), |acc, line| {
|
||||
let ls = line.to_string().split(|c| c == ' ' || c == ':').map(|x| x.to_string()).collect::<Vec<String>>();
|
||||
if acc.is_err() || ls.len() != 4 {
|
||||
println!("line 204: acc: {:?}, ls: {:?}", acc, ls);
|
||||
Err(CgroupError::ParseError)
|
||||
} else {
|
||||
let devtype = DeviceType::from_char(ls[0].chars().nth(0));
|
||||
let mut major = ls[1].parse::<i64>();
|
||||
let mut minor = ls[2].parse::<i64>();
|
||||
if major.is_err() && ls[1] == "*".to_string() {
|
||||
major = Ok(-1);
|
||||
}
|
||||
if minor.is_err() && ls[2] == "*".to_string() {
|
||||
minor = Ok(-1);
|
||||
}
|
||||
if devtype.is_none() || major.is_err() || minor.is_err() || !DevicePermissions::is_valid(&ls[3]) {
|
||||
println!("line 211: acc: {:?}, ls: {:?}, devtype: {:?}, major {:?} minor {:?} ls3 {:?}",
|
||||
acc, ls, devtype, major, minor, &ls[3]);
|
||||
Err(CgroupError::ParseError)
|
||||
} else {
|
||||
let access = DevicePermissions::from_string(&ls[3]);
|
||||
let mut acc = acc.unwrap();
|
||||
acc.push(DeviceResource {
|
||||
allow: true,
|
||||
devtype: devtype.unwrap(),
|
||||
major: major.unwrap(),
|
||||
minor: minor.unwrap(),
|
||||
access: access,
|
||||
});
|
||||
Ok(acc)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
Err(e) => Err(CgroupError::ReadError(e)),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -309,13 +309,13 @@ pub struct DeviceResource {
|
||||
/// If true, access to the device is allowed, otherwise it's denied.
|
||||
pub allow: bool,
|
||||
/// `'c'` for character device, `'b'` for block device; or `'a'` for all devices.
|
||||
pub devtype: String,
|
||||
pub devtype: ::devices::DeviceType,
|
||||
/// The major number of the device.
|
||||
pub major: u64,
|
||||
pub major: i64,
|
||||
/// The minor number of the device.
|
||||
pub minor: u64,
|
||||
pub minor: i64,
|
||||
/// Sequence of `'r'`, `'w'` or `'m'`, each denoting read, write or mknod permissions.
|
||||
pub access: String,
|
||||
pub access: Vec<::devices::DevicePermissions>,
|
||||
}
|
||||
|
||||
/// Limit the usage of devices for the control group's tasks.
|
||||
|
||||
45
tests/devices.rs
Normal file
45
tests/devices.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
//! Integration tests about the devices subsystem
|
||||
|
||||
extern crate cgroups;
|
||||
use cgroups::{Cgroup, DeviceResource};
|
||||
use cgroups::devices::{DevicesController, DevicePermissions, DeviceType};
|
||||
|
||||
#[test]
|
||||
fn test_devices_parsing() {
|
||||
let hier = cgroups::hierarchies::V1::new();
|
||||
let cg = Cgroup::new(&hier, String::from("test_devices_parsing"));
|
||||
{
|
||||
let devices: &DevicesController = cg.controller_of().unwrap();
|
||||
|
||||
// Deny access to all devices first
|
||||
devices.deny_device(DeviceType::All, -1, -1, &vec![DevicePermissions::Read, DevicePermissions::Write, DevicePermissions::MkNod]);
|
||||
// Acquire the list of allowed devices after we denied all
|
||||
let allowed_devices = devices.allowed_devices();
|
||||
// Verify that there are no devices that we can access.
|
||||
assert!(allowed_devices.is_ok());
|
||||
assert_eq!(allowed_devices.unwrap(), Vec::new());
|
||||
|
||||
// Now add mknod access to /dev/null device
|
||||
devices.allow_device(DeviceType::Char, 1, 3, &vec![DevicePermissions::MkNod]);
|
||||
let allowed_devices = devices.allowed_devices();
|
||||
assert!(allowed_devices.is_ok());
|
||||
let allowed_devices = allowed_devices.unwrap();
|
||||
assert_eq!(allowed_devices.len(), 1);
|
||||
assert_eq!(allowed_devices[0], DeviceResource {
|
||||
allow: true,
|
||||
devtype: DeviceType::Char,
|
||||
major: 1,
|
||||
minor: 3,
|
||||
access: vec![DevicePermissions::MkNod],
|
||||
});
|
||||
|
||||
// Now deny, this device explicitly.
|
||||
devices.deny_device(DeviceType::Char, 1, 3, &DevicePermissions::all());
|
||||
// Finally, check that.
|
||||
let allowed_devices = devices.allowed_devices();
|
||||
// Verify that there are no devices that we can access.
|
||||
assert!(allowed_devices.is_ok());
|
||||
assert_eq!(allowed_devices.unwrap(), Vec::new());
|
||||
}
|
||||
cg.delete();
|
||||
}
|
||||
Reference in New Issue
Block a user