use std::{fmt, path::PathBuf, str::FromStr};
use crate::{
parse::parse_next,
v1::{self, cgroup::CgroupHelper, Cgroup, CgroupPath},
Error, Result,
};
#[derive(Debug)]
pub struct Subsystem {
path: CgroupPath,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Resources {
pub deny: Vec<Access>,
pub allow: Vec<Access>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Access {
pub device_type: DeviceType,
pub device_number: crate::Device,
pub access_type: AccessType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DeviceType {
All,
Char,
Block,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AccessType {
pub read: bool,
pub write: bool,
pub mknod: bool,
}
impl_cgroup! {
Subsystem, Devices,
fn apply(&mut self, resources: &v1::Resources) -> Result<()> {
for denied in &resources.devices.deny {
self.deny(denied)?;
}
for allowed in &resources.devices.allow {
self.allow(allowed)?;
}
Ok(())
}
}
macro_rules! _gen_setter {
($desc: literal, $field: ident) => { with_doc! { concat!(
$desc, " this cgroup to perform a type of access to devices with specific type and number,",
" by writing to `devices.", stringify!($field), "` file.\n\n",
gen_doc!(see; $field),
gen_doc!(err_write; subsys_file!(devices, $field)),
gen_doc!(eg_write; devices, $field, &"c 8:0 rm".parse::<devices::Access>()?)),
pub fn $field(&mut self, access: &Access) -> Result<()> {
self.write_file(subsys_file!(devices, $field), access)
}
} };
}
impl Subsystem {
gen_getter!(
devices,
"allowed device access of this cgroup",
list,
Vec<Access>,
parse_list
);
_gen_setter!("Denies", deny);
_gen_setter!("Allows", allow);
}
fn parse_list(reader: impl std::io::Read) -> Result<Vec<Access>> {
use std::io::{BufRead, BufReader};
let mut result = Vec::new();
for line in BufReader::new(reader).lines() {
let line = line?;
result.push(line.parse::<Access>()?);
}
Ok(result)
}
impl Into<v1::Resources> for Resources {
fn into(self) -> v1::Resources {
v1::Resources {
devices: self,
..v1::Resources::default()
}
}
}
impl FromStr for Access {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let mut entry = s.split_whitespace();
let device_type = parse_next(&mut entry)?;
if let Some(device_number) = entry.next() {
let device_number = device_number.parse()?;
let access_type = parse_next(&mut entry)?;
if entry.next().is_some() {
bail_parse!();
}
Ok(Self {
device_type,
device_number,
access_type,
})
} else if device_type == DeviceType::All {
use crate::{Device, DeviceNumber};
Ok(Self {
device_type,
device_number: Device {
major: DeviceNumber::Any,
minor: DeviceNumber::Any,
},
access_type: AccessType {
read: true,
write: true,
mknod: true,
},
})
} else {
bail_parse!();
}
}
}
impl fmt::Display for Access {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} {} {}",
self.device_type, self.device_number, self.access_type
)
}
}
impl FromStr for DeviceType {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"a" => Ok(Self::All),
"c" => Ok(Self::Char),
"b" => Ok(Self::Block),
_ => {
bail_parse!();
}
}
}
}
impl fmt::Display for DeviceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use std::fmt::Write;
f.write_char(match self {
Self::All => 'a',
Self::Char => 'c',
Self::Block => 'b',
})
}
}
impl FromStr for AccessType {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let mut access = AccessType::default();
macro_rules! s {
($r: ident) => {{
if access.$r {
bail_parse!();
}
access.$r = true;
}};
}
for c in s.chars() {
match c {
'r' => s!(read),
'w' => s!(write),
'm' => s!(mknod),
_ => {
bail_parse!();
}
}
}
Ok(access)
}
}
impl fmt::Display for AccessType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use std::fmt::Write;
if self.read {
f.write_char('r')?;
}
if self.write {
f.write_char('w')?;
}
if self.mknod {
f.write_char('m')?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Device, DeviceNumber, ErrorKind};
#[test]
fn test_subsystem_create_file_exists() -> Result<()> {
gen_subsystem_test!(Devices, ["allow", "deny", "list"])
}
#[test]
fn test_subsystem_apply() -> Result<()> {
gen_subsystem_test!(
Devices,
Resources {
deny: vec!["a".parse::<Access>().unwrap()],
allow: vec!["c 1:3 rm".parse::<Access>().unwrap()],
},
(list, vec!["c 1:3 rm".parse::<Access>().unwrap()]),
)
}
#[test]
fn test_subsystem_list() -> Result<()> {
let allowed_all = Access {
device_type: DeviceType::All,
device_number: Device {
major: DeviceNumber::Any,
minor: DeviceNumber::Any,
},
access_type: AccessType {
read: true,
write: true,
mknod: true,
},
};
gen_subsystem_test!(Devices, list, vec![allowed_all])
}
#[test]
fn test_subsystem_deny_allow() -> Result<()> {
let mut cgroup = Subsystem::new(CgroupPath::new(
v1::SubsystemKind::Devices,
gen_cgroup_name!(),
));
cgroup.create()?;
let all = "a".parse::<Access>().unwrap();
cgroup.deny(&all)?;
assert!(cgroup.list()?.is_empty());
let c_1_3_rm = "c 1:3 rm".parse::<Access>().unwrap();
cgroup.allow(&c_1_3_rm)?;
assert_eq!(cgroup.list()?, vec![c_1_3_rm]);
cgroup.delete()
}
#[test]
fn err_parse_access() {
for case in &[
"c",
"d *:* rwm",
"a *:* invalid",
"a invalid rwm",
"a rwm",
"a 1:3",
"a 1:3 rwm invalid",
] {
assert_eq!(case.parse::<Access>().unwrap_err().kind(), ErrorKind::Parse);
}
}
#[test]
fn test_parse_list() -> Result<()> {
const CONTENT_OK: &str = "\
c 1:3 rm
b 8:0 rw
";
assert_eq!(
parse_list(CONTENT_OK.as_bytes())?,
vec![
Access {
device_type: DeviceType::Char,
device_number: Device {
major: DeviceNumber::Number(1),
minor: DeviceNumber::Number(3)
},
access_type: AccessType {
read: true,
write: false,
mknod: true
}
},
Access {
device_type: DeviceType::Block,
device_number: Device {
major: DeviceNumber::Number(8),
minor: DeviceNumber::Number(0)
},
access_type: AccessType {
read: true,
write: true,
mknod: false
}
},
]
);
assert_eq!(parse_list("".as_bytes())?, vec![]);
Ok(())
}
}