use std::fmt;
use libseccomp::ScmpArch;
use nix::errno::Errno;
use serde::{Serialize, Serializer};
use crate::confine::SCMP_ARCH;
pub type Ioctl = u32;
pub enum IoctlName {
Name(String),
Val(Ioctl),
}
impl fmt::Display for IoctlName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Name(ref s) => write!(f, "{s}"),
Self::Val(v) => write!(f, "{v:#x}"),
}
}
}
impl Serialize for IoctlName {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::Name(ref s) => serializer.serialize_str(s),
Self::Val(v) => serializer.serialize_u32(*v),
}
}
}
pub type IoctlList = &'static [(&'static str, Ioctl)];
include!("ioctl/ioctls_aarch64.rs");
include!("ioctl/ioctls_arm.rs");
include!("ioctl/ioctls_loongarch64.rs");
include!("ioctl/ioctls_m68k.rs");
include!("ioctl/ioctls_mips.rs");
include!("ioctl/ioctls_mips64.rs");
include!("ioctl/ioctls_mips64n32.rs");
include!("ioctl/ioctls_mipsel.rs");
include!("ioctl/ioctls_mipsel64.rs");
include!("ioctl/ioctls_mipsel64n32.rs");
include!("ioctl/ioctls_ppc.rs");
include!("ioctl/ioctls_ppc64.rs");
include!("ioctl/ioctls_ppc64le.rs");
include!("ioctl/ioctls_riscv64.rs");
include!("ioctl/ioctls_s390.rs");
include!("ioctl/ioctls_s390x.rs");
include!("ioctl/ioctls_x32.rs");
include!("ioctl/ioctls_x86.rs");
include!("ioctl/ioctls_x8664.rs");
const ARCH_TABLES: &[(ScmpArch, IoctlList)] = &[
(ScmpArch::Aarch64, IOCTL_ARCH_AARCH64),
(ScmpArch::Arm, IOCTL_ARCH_ARM),
(ScmpArch::Loongarch64, IOCTL_ARCH_LOONGARCH64),
(ScmpArch::M68k, IOCTL_ARCH_M68K),
(ScmpArch::Mips, IOCTL_ARCH_MIPS),
(ScmpArch::Mips64, IOCTL_ARCH_MIPS64),
(ScmpArch::Mips64N32, IOCTL_ARCH_MIPS64N32),
(ScmpArch::Mipsel, IOCTL_ARCH_MIPSEL),
(ScmpArch::Mipsel64, IOCTL_ARCH_MIPSEL64),
(ScmpArch::Mipsel64N32, IOCTL_ARCH_MIPSEL64N32),
(ScmpArch::Ppc, IOCTL_ARCH_PPC),
(ScmpArch::Ppc64, IOCTL_ARCH_PPC64),
(ScmpArch::Ppc64Le, IOCTL_ARCH_PPC64LE),
(ScmpArch::Riscv64, IOCTL_ARCH_RISCV64),
(ScmpArch::S390, IOCTL_ARCH_S390),
(ScmpArch::S390X, IOCTL_ARCH_S390X),
(ScmpArch::X32, IOCTL_ARCH_X32),
(ScmpArch::X86, IOCTL_ARCH_X86),
(ScmpArch::X8664, IOCTL_ARCH_X8664),
];
pub struct IoctlMap {
target: Option<ScmpArch>,
native: bool,
}
impl IoctlMap {
pub fn new(target: Option<ScmpArch>, native: bool) -> Self {
Self { target, native }
}
fn should_check(&self, arch: ScmpArch) -> bool {
if let Some(target_arch) = self.target {
if arch != target_arch {
return false;
}
} else if self.native && !SCMP_ARCH.contains(&arch) {
return false;
}
true
}
pub fn get_names(
&self,
value: Ioctl,
arch: ScmpArch,
) -> Result<Option<Vec<&'static str>>, Errno> {
if !self.should_check(arch) {
return Ok(None);
}
for &(a, table) in ARCH_TABLES {
if a == arch {
let mut names = Vec::new();
for &(n, v) in table {
if Ioctl::from(v) == value {
if names.len() == names.capacity() {
names.try_reserve(1).or(Err(Errno::ENOMEM))?;
}
names.push(n);
}
}
if names.is_empty() {
return Ok(None);
}
return Ok(Some(names));
}
}
Ok(None)
}
pub fn get_log(&self, value: Ioctl, arch: ScmpArch) -> Result<Option<Vec<IoctlName>>, Errno> {
if !self.should_check(arch) {
return Ok(None);
}
for &(a, table) in ARCH_TABLES {
if a == arch {
let mut names = Vec::new();
for &(n, v) in table {
if Ioctl::from(v) == value {
if names.len() == names.capacity() {
names.try_reserve(1).or(Err(Errno::ENOMEM))?;
}
let mut s = String::new();
s.try_reserve(n.len()).or(Err(Errno::ENOMEM))?;
s.push_str(n);
names.push(IoctlName::Name(s));
}
}
if names.is_empty() {
return Ok(None);
}
return Ok(Some(names));
}
}
Ok(None)
}
pub fn get_value(&self, name: &str, arch: ScmpArch) -> Option<Ioctl> {
if !self.should_check(arch) {
return None;
}
for &(a, table) in ARCH_TABLES {
if a == arch {
return table
.binary_search_by_key(&name, |&(n, _)| n)
.ok()
.map(|idx| Ioctl::from(table[idx].1));
}
}
None
}
pub fn iter(&self, arch: ScmpArch) -> Option<impl Iterator<Item = (&'static str, Ioctl)>> {
if !self.should_check(arch) {
return None;
}
for &(a, table) in ARCH_TABLES {
if a == arch {
return Some(table.iter().map(|&(name, val)| (name, Ioctl::from(val))));
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ioctlmap_1() {
let map = IoctlMap::new(None, false);
assert!(map.target.is_none());
assert!(!map.native);
}
#[test]
fn test_ioctlmap_2() {
let map = IoctlMap::new(Some(ScmpArch::X8664), true);
assert_eq!(map.target, Some(ScmpArch::X8664));
assert!(map.native);
}
#[test]
fn test_ioctlmap_3() {
let map = IoctlMap::new(Some(ScmpArch::X8664), false);
let result = map.get_names(0x5413, ScmpArch::X8664).unwrap();
if let Some(names) = result {
assert!(names.contains(&"TIOCGWINSZ"));
}
}
#[test]
fn test_ioctlmap_4() {
let map = IoctlMap::new(Some(ScmpArch::X8664), false);
let result = map.get_names(0xDEADBEEF, ScmpArch::X8664).unwrap();
assert!(result.is_none());
}
#[test]
fn test_ioctlmap_5() {
let map = IoctlMap::new(Some(ScmpArch::Arm), false);
let result = map.get_names(0x5413, ScmpArch::X8664).unwrap();
assert!(result.is_none());
}
#[test]
fn test_ioctlmap_6() {
let map = IoctlMap::new(Some(ScmpArch::X8664), false);
let result = map.get_value("TIOCGWINSZ", ScmpArch::X8664);
assert_eq!(result, Some(0x5413));
}
#[test]
fn test_ioctlmap_7() {
let map = IoctlMap::new(Some(ScmpArch::X8664), false);
let result = map.get_value("NONEXISTENT_IOCTL", ScmpArch::X8664);
assert!(result.is_none());
}
#[test]
fn test_ioctlmap_8() {
let map = IoctlMap::new(Some(ScmpArch::Arm), false);
let result = map.get_value("TIOCGWINSZ", ScmpArch::X8664);
assert!(result.is_none());
}
#[test]
fn test_ioctlmap_9() {
let map = IoctlMap::new(Some(ScmpArch::X8664), false);
let result = map.get_log(0x5413, ScmpArch::X8664).unwrap();
if let Some(names) = result {
assert!(!names.is_empty());
let display = format!("{}", names[0]);
assert!(display.contains("TIOCGWINSZ"));
}
}
#[test]
fn test_ioctlmap_10() {
let map = IoctlMap::new(Some(ScmpArch::X8664), false);
let result = map.get_log(0xDEADBEEF, ScmpArch::X8664).unwrap();
assert!(result.is_none());
}
#[test]
fn test_ioctlmap_11() {
let map = IoctlMap::new(Some(ScmpArch::X8664), false);
let iter = map.iter(ScmpArch::X8664);
assert!(iter.is_some());
let count = iter.unwrap().count();
assert!(count > 0);
}
#[test]
fn test_ioctlmap_12() {
let map = IoctlMap::new(Some(ScmpArch::Arm), false);
let iter = map.iter(ScmpArch::X8664);
assert!(iter.is_none());
}
#[test]
fn test_ioctlname_1() {
let name = IoctlName::Name("TIOCGWINSZ".into());
assert_eq!(format!("{name}"), "TIOCGWINSZ");
}
#[test]
fn test_ioctlname_2() {
let val = IoctlName::Val(0x5413);
assert_eq!(format!("{val}"), "0x5413");
}
#[test]
fn test_ioctlname_3() {
let name = IoctlName::Name("TIOCGWINSZ".into());
let json = serde_json::to_string(&name).unwrap();
assert_eq!(json, "\"TIOCGWINSZ\"");
}
#[test]
fn test_ioctlname_4() {
let val = IoctlName::Val(0x5413);
let json = serde_json::to_string(&val).unwrap();
assert_eq!(json, "21523");
}
#[test]
fn test_should_check_1() {
let map = IoctlMap::new(None, false);
assert!(map.should_check(ScmpArch::X8664));
assert!(map.should_check(ScmpArch::Arm));
}
#[test]
fn test_should_check_2() {
let map = IoctlMap::new(Some(ScmpArch::X8664), false);
assert!(map.should_check(ScmpArch::X8664));
assert!(!map.should_check(ScmpArch::Arm));
}
}