use std::{ffi::CStr, fmt::Display, num::ParseIntError, str::FromStr};
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GroupId(libc::gid_t);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UserId(libc::uid_t);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ProcessId(libc::pid_t);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(libc::dev_t);
impl GroupId {
pub fn new(id: libc::gid_t) -> Self {
Self(id)
}
pub fn inner(&self) -> libc::gid_t {
self.0
}
}
impl UserId {
pub fn new(id: libc::uid_t) -> Self {
Self(id)
}
pub fn inner(&self) -> libc::uid_t {
self.0
}
pub const ROOT: Self = Self(0);
}
impl ProcessId {
pub fn new(id: libc::pid_t) -> Self {
Self(id)
}
pub fn inner(&self) -> libc::pid_t {
self.0
}
pub fn is_valid(&self) -> bool {
self.0 != 0
}
}
impl DeviceId {
pub fn new(id: libc::dev_t) -> Self {
Self(id)
}
pub fn inner(&self) -> libc::dev_t {
self.0
}
}
impl Display for GroupId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Display for UserId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Display for ProcessId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Display for DeviceId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for GroupId {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<libc::gid_t>().map(GroupId::new)
}
}
impl FromStr for UserId {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<libc::uid_t>().map(UserId::new)
}
}
pub trait UnixUser {
fn has_name(&self, _name: &str) -> bool;
fn has_uid(&self, _uid: UserId) -> bool;
fn is_root(&self) -> bool;
fn in_group_by_name(&self, _name: &CStr) -> bool;
fn in_group_by_gid(&self, _gid: GroupId) -> bool;
type Group: UnixGroup;
fn group(&self) -> Self::Group;
}
pub trait UnixGroup {
fn as_gid(&self) -> GroupId;
fn try_as_name(&self) -> Option<&str>;
}
impl UnixUser for super::User {
fn has_name(&self, name: &str) -> bool {
self.name == name
}
fn has_uid(&self, uid: UserId) -> bool {
self.uid == uid
}
fn is_root(&self) -> bool {
self.has_uid(UserId::ROOT)
}
fn in_group_by_name(&self, name_c: &CStr) -> bool {
if let Ok(Some(group)) = super::Group::from_name(name_c) {
self.in_group_by_gid(group.gid)
} else {
false
}
}
fn in_group_by_gid(&self, gid: GroupId) -> bool {
self.groups.contains(&gid)
}
type Group = super::Group;
fn group(&self) -> super::Group {
Self::Group {
gid: self.gid,
name: None,
}
}
}
impl UnixGroup for super::Group {
fn as_gid(&self) -> GroupId {
self.gid
}
fn try_as_name(&self) -> Option<&str> {
self.name.as_deref()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::system::{Group, ROOT_GROUP_NAME, User};
use std::ffi::CString;
fn test_user(user: impl UnixUser, name_c: &CStr) {
let name = name_c.to_str().unwrap();
assert!(user.has_name(name));
if user.has_name("root") {
assert!(user.in_group_by_name(CString::new(ROOT_GROUP_NAME).unwrap().as_c_str()));
assert!(user.is_root());
} else {
assert!(user.in_group_by_name(name_c));
assert!(!user.is_root());
}
assert_eq!(user.is_root(), name == "root");
}
fn test_group(group: impl UnixGroup, name: &str) {
assert_eq!(name == ROOT_GROUP_NAME, group.as_gid() == GroupId::new(0));
assert_eq!(group.try_as_name(), Some(name));
}
#[test]
fn test_unix_user() {
let user = |name| User::from_name(name).unwrap().unwrap();
test_user(user(c"root"), c"root");
test_user(user(c"daemon"), c"daemon");
}
#[test]
fn test_unix_group() {
let group = |name| Group::from_name(name).unwrap().unwrap();
let root_group_cstr = CString::new(ROOT_GROUP_NAME).unwrap();
test_group(group(root_group_cstr.as_c_str()), ROOT_GROUP_NAME);
test_group(group(c"daemon"), "daemon");
}
}