Skip to content

Instantly share code, notes, and snippets.

@larsrh
Last active April 9, 2023 09:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save larsrh/bb533e0742c7f49910056ede2ce06d9e to your computer and use it in GitHub Desktop.
Save larsrh/bb533e0742c7f49910056ede2ce06d9e to your computer and use it in GitHub Desktop.
Rust implementation of high-level Unix file permission handling
use anyhow::{Error, Result};
use bitflags::bitflags;
use std::convert::TryFrom;
use std::fs::Permissions;
use std::os::unix::fs::PermissionsExt;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Perms: u16 {
const UR = 0o400;
const UW = 0o200;
const UX = 0o100;
const GR = 0o040;
const GW = 0o020;
const GX = 0o010;
const OR = 0o004;
const OW = 0o002;
const OX = 0o001;
}
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Perm: u8 {
const R = 4;
const W = 2;
const X = 1;
const RWX = 7;
const RW = 6;
const RX = 5;
const WX = 3;
}
}
impl Perms {
pub fn map(
self: &Perms,
user: fn(Perm) -> Perm,
group: fn(Perm) -> Perm,
other: fn(Perm) -> Perm,
) -> Perms {
Perms::from(user(self.user()), group(self.group()), other(self.other()))
}
pub fn map_user(self: &Perms, f: fn(Perm) -> Perm) -> Perms {
self.map(f, |x| x, |x| x)
}
pub fn map_group(self: &Perms, f: fn(Perm) -> Perm) -> Perms {
self.map(|x| x, f, |x| x)
}
pub fn map_other(self: &Perms, f: fn(Perm) -> Perm) -> Perms {
self.map(|x| x, |x| x, f)
}
pub fn user(self: &Perms) -> Perm {
Perm::from_bits_truncate((self.bits() >> 6) as u8)
}
pub fn group(self: &Perms) -> Perm {
Perm::from_bits_truncate((self.bits() >> 3) as u8)
}
pub fn other(self: &Perms) -> Perm {
Perm::from_bits_truncate(self.bits() as u8)
}
pub fn from(user: Perm, group: Perm, other: Perm) -> Perms {
Perms::from_bits_truncate(
((user.bits() as u16) << 6) | ((group.bits() as u16) << 3) | other.bits() as u16,
)
}
}
impl TryFrom<Permissions> for Perms {
type Error = Error;
fn try_from(permissions: Permissions) -> Result<Self> {
let mode: u16 = permissions.mode().try_into()?;
match Perms::from_bits(mode) {
Some(perms) => Ok(perms),
None => Err(anyhow!(
"Unknown bits set, possibly sticky: {}",
permissions.mode()
)),
}
}
}
impl From<Perms> for Permissions {
fn from(perms: Perms) -> Self {
Permissions::from_mode(perms.bits() as u32)
}
}
pub fn map_permissions(permissions: Permissions, f: fn(Perms) -> Perms) -> Result<Permissions> {
Ok(f(permissions.try_into()?).into())
}
#[cfg(test)]
mod test {
use crate::perm::{Perm, Perms};
use std::fs::Permissions;
fn all_perms<F: Fn(Perm) -> ()>(f: F) {
for p in 0..7 {
f(Perm::from_bits_truncate(p))
}
}
fn all_permss<F: Fn(Perms) -> ()>(f: F) {
for p in 0..511 {
f(Perms::from_bits_truncate(p))
}
}
#[test]
fn test_convert1() {
all_permss(|perms| {
assert_eq!(
perms,
Perms::from(perms.user(), perms.group(), perms.other())
)
})
}
#[test]
fn test_convert2() {
all_perms(|user| {
all_perms(|group| {
all_perms(|other| {
let perms = Perms::from(user, group, other);
assert_eq!(user, perms.user());
assert_eq!(group, perms.group());
assert_eq!(other, perms.other())
})
})
})
}
#[test]
fn test_convert3() {
all_permss(|perms| {
let lib: Permissions = perms.clone().into();
let actual: Perms = lib.try_into().unwrap();
assert_eq!(perms, actual)
})
}
#[test]
fn test_combined() {
assert_eq!(Perm::R | Perm::W | Perm::X, Perm::RWX);
assert_eq!(Perm::R | Perm::W, Perm::RW);
assert_eq!(Perm::R | Perm::X, Perm::RX);
assert_eq!(Perm::W | Perm::X, Perm::WX)
}
#[test]
fn test_map_id() {
all_permss(|perms| assert_eq!(perms, perms.map(|x| x, |x| x, |x| x)))
}
#[test]
fn test_map_user_id() {
all_permss(|perms| assert_eq!(perms, perms.map_user(|x| x)))
}
#[test]
fn test_map_group_id() {
all_permss(|perms| assert_eq!(perms, perms.map_group(|x| x)))
}
#[test]
fn test_map_other_id() {
all_permss(|perms| assert_eq!(perms, perms.map_other(|x| x)))
}
#[test]
fn test_map() {
all_permss(|perms| {
let expected = Perms::from(
perms.user() | Perm::X,
perms.group() | Perm::W,
perms.other() | Perm::R,
);
let actual = perms.map(|x| x | Perm::X, |x| x | Perm::W, |x| x | Perm::R);
assert_eq!(expected, actual)
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment