use thiserror::Error;
pub fn syscall_name_to_nr(name: &str) -> Option<u32> {
name.parse::<syscalls::Sysno>()
.ok()
.or_else(|| libc_name_alias(name).and_then(|aka| aka.parse::<syscalls::Sysno>().ok()))
.map(|s| s.id() as u32)
}
fn libc_name_alias(name: &str) -> Option<&'static str> {
match name {
"newfstatat" => Some("fstatat"),
_ => None,
}
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum SyscallError {
#[error("syscall number {0} is negative")]
Negative(i64),
#[error("syscall number {0} is unknown for the current architecture")]
UnknownForArch(i64),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Syscall(i64);
impl Syscall {
pub fn checked(nr: i64) -> Result<Self, SyscallError> {
if nr < 0 {
return Err(SyscallError::Negative(nr));
}
if !crate::arch::is_known_syscall(nr) {
return Err(SyscallError::UnknownForArch(nr));
}
Ok(Self(nr))
}
pub fn raw(self) -> i64 {
self.0
}
}
impl TryFrom<i64> for Syscall {
type Error = SyscallError;
fn try_from(nr: i64) -> Result<Self, Self::Error> {
Self::checked(nr)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn checked_accepts_valid_openat() {
let s = Syscall::checked(libc::SYS_openat).expect("openat is valid");
assert_eq!(s.raw(), libc::SYS_openat);
}
#[test]
fn checked_rejects_negative() {
match Syscall::checked(-5) {
Err(SyscallError::Negative(-5)) => {}
other => panic!("expected Negative(-5), got {:?}", other),
}
}
#[test]
fn checked_rejects_arch_unknown() {
match Syscall::checked(99_999) {
Err(SyscallError::UnknownForArch(99_999)) => {}
other => panic!("expected UnknownForArch(99_999), got {:?}", other),
}
}
#[test]
fn try_from_i64_delegates_to_checked() {
let s: Syscall = libc::SYS_openat.try_into().expect("openat valid");
assert_eq!(s.raw(), libc::SYS_openat);
let bad: Result<Syscall, _> = (-1i64).try_into();
assert!(matches!(bad, Err(SyscallError::Negative(-1))));
}
#[test]
fn name_to_nr_matches_libc_for_stable_names() {
let cases: &[(&str, i64)] = &[
("mount", libc::SYS_mount),
("openat", libc::SYS_openat),
("connect", libc::SYS_connect),
("clone", libc::SYS_clone),
("clone3", libc::SYS_clone3),
("execve", libc::SYS_execve),
("ioctl", libc::SYS_ioctl),
("ptrace", libc::SYS_ptrace),
("userfaultfd", libc::SYS_userfaultfd),
("bpf", libc::SYS_bpf),
("statx", libc::SYS_statx),
("getrandom", libc::SYS_getrandom),
("io_uring_setup", libc::SYS_io_uring_setup),
];
for &(name, expected) in cases {
assert_eq!(
syscall_name_to_nr(name),
Some(expected as u32),
"{name} should resolve to libc::SYS_{name} = {expected}"
);
}
}
#[test]
fn name_to_nr_rejects_non_syscall_names() {
assert_eq!(syscall_name_to_nr("definitely_not_a_syscall"), None);
assert_eq!(syscall_name_to_nr(""), None);
}
#[test]
fn name_to_nr_resolves_newfstatat_alias() {
assert_eq!(
syscall_name_to_nr("newfstatat"),
Some(libc::SYS_newfstatat as u32)
);
}
}