First working implementation

This commit is contained in:
Fabian Stamm 2024-08-17 23:59:08 +02:00
commit 62899a7d26
4 changed files with 402 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
test.txt

39
Cargo.lock generated Normal file
View File

@ -0,0 +1,39 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "acl-sys"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbc079f9bdd3124fd18df23c67f7e0f79d24751ae151dcffd095fcade07a3eb2"
dependencies = [
"libc",
]
[[package]]
name = "anyhow"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "libc"
version = "0.2.156"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5f43f184355eefb8d17fc948dbecf6c13be3c141f20d834ae842193a448c72a"
[[package]]
name = "posix-acl"
version = "0.1.0"
dependencies = [
"acl-sys",
"anyhow",
"bitflags",
]

9
Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "posix-acl"
version = "0.1.0"
edition = "2021"
[dependencies]
acl-sys = "1.2"
anyhow = "1"
bitflags = "2"

352
src/lib.rs Normal file
View File

@ -0,0 +1,352 @@
use std::{
os::raw::c_void,
path::{Path, PathBuf},
ptr::{addr_of, null_mut},
};
use acl_sys::{
acl_add_perm, acl_clear_perms, acl_create_entry, acl_free, acl_get_entry, acl_get_file,
acl_get_permset, acl_get_qualifier, acl_get_tag_type, acl_init, acl_permset_t, acl_set_file,
acl_set_permset, acl_set_qualifier, acl_set_tag_type, acl_type_t, ACL_FIRST_ENTRY, ACL_GROUP,
ACL_GROUP_OBJ, ACL_MASK, ACL_NEXT_ENTRY, ACL_OTHER, ACL_TYPE_ACCESS, ACL_TYPE_DEFAULT,
ACL_USER, ACL_USER_OBJ,
};
use anyhow::{anyhow, Result};
use bitflags::bitflags;
use std::ffi::CString;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct PermSet: u32 {
const ACL_READ = acl_sys::ACL_READ;
const ACL_WRITE = acl_sys::ACL_WRITE;
const ACL_EXECUTE = acl_sys::ACL_EXECUTE;
const ACL_RWX = Self::ACL_READ.bits() | Self::ACL_WRITE.bits() | Self::ACL_EXECUTE.bits();
}
}
pub static ACL_READ: PermSet = PermSet::ACL_READ;
pub static ACL_WRITE: PermSet = PermSet::ACL_WRITE;
pub static ACL_EXECUTE: PermSet = PermSet::ACL_EXECUTE;
pub static ACL_RWX: PermSet = PermSet::ACL_RWX;
pub static ACL_NONE: PermSet = PermSet::empty();
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Qualifier {
UserObj,
GroupObj,
Other,
User(u32),
Group(u32),
Mask,
}
impl Qualifier {
pub fn get_tag(&self) -> i32 {
match self {
Qualifier::UserObj => ACL_USER_OBJ,
Qualifier::GroupObj => ACL_GROUP_OBJ,
Qualifier::Other => ACL_OTHER,
Qualifier::User(_) => ACL_USER,
Qualifier::Group(_) => ACL_GROUP,
Qualifier::Mask => ACL_MASK,
}
}
pub fn get_uid(&self) -> Option<u32> {
match self {
Qualifier::User(uid) => Some(*uid),
Qualifier::Group(uid) => Some(*uid),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct ACLEntry(Qualifier, PermSet);
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct PosixACL {
entries: Vec<ACLEntry>,
}
impl PosixACL {
pub fn new_from_file(path: impl Into<PathBuf>, default: bool) -> Result<Self> {
let path: PathBuf = path.into();
let cpath = CString::new(path.to_str().unwrap()).unwrap();
let acl = AclPtr(unsafe {
acl_get_file(
cpath.as_ptr(),
if default {
ACL_TYPE_DEFAULT
} else {
ACL_TYPE_ACCESS
},
)
});
if acl.0.is_null() {
// TODO: When trying to get default ACL from a file, it returns null.
return Err(anyhow!("Failed to get ACL"));
}
let mut entries = Vec::new();
let mut first = true;
loop {
let mut entry = null_mut();
let ret = unsafe {
acl_get_entry(
acl.0,
if first {
ACL_FIRST_ENTRY
} else {
ACL_NEXT_ENTRY
},
&mut entry,
)
};
first = false;
if ret == 0 {
break;
} else if ret != 1 {
return Err(anyhow!("Failed to get ACL entry"));
}
println!("entry: {:?}", entry);
let mut tag_type: i32 = 0;
check_return(
unsafe { acl_get_tag_type(entry, &mut tag_type) },
"acl_get_tag_type",
);
let mut tag_type = 0;
check_return(
unsafe { acl_get_tag_type(entry, &mut tag_type) },
"acl_get_tag_type",
);
let qual = match tag_type {
ACL_USER_OBJ => Qualifier::UserObj,
ACL_GROUP_OBJ => Qualifier::GroupObj,
ACL_OTHER => Qualifier::Other,
ACL_USER => {
let uid: AclPtr<u32> = AclPtr(unsafe { acl_get_qualifier(entry).cast() });
if uid.0.is_null() {
return Err(anyhow!("Failed to get qualifier"));
}
Qualifier::User(unsafe { *uid.0 })
}
ACL_GROUP => {
let gid: AclPtr<u32> = AclPtr(unsafe { acl_get_qualifier(entry).cast() });
if gid.0.is_null() {
return Err(anyhow!("Failed to get qualifier"));
}
Qualifier::Group(unsafe { *gid.0 })
}
ACL_MASK => Qualifier::Mask,
_ => panic!("Unknown tag type"),
};
let mut permset: *mut c_void = null_mut();
check_return(
unsafe { acl_get_permset(entry, &mut permset) },
"acl_get_permset",
);
println!("permset: {:?}", permset);
let perm = if permset.is_null() {
0
} else {
unsafe { *(permset.cast()) }
};
let permset = PermSet::from_bits_truncate(perm);
entries.push(ACLEntry(qual, permset));
}
Ok(PosixACL { entries })
}
pub fn new(user: PermSet, group: PermSet, others: PermSet) -> Self {
let entries = vec![
ACLEntry(Qualifier::UserObj, user),
ACLEntry(Qualifier::GroupObj, group),
ACLEntry(Qualifier::Other, others),
ACLEntry(Qualifier::Mask, ACL_RWX),
];
PosixACL { entries }
}
pub fn set(&mut self, entry: ACLEntry) {
// if entry already exists, replace it
if let Some(i) = self.entries.iter().position(|x| x.0 == entry.0) {
self.entries[i] = entry;
return;
} else {
// if entry does not exist, add it
self.entries.push(entry);
}
}
pub fn write<P: AsRef<Path>>(&self, path: P) -> Result<()> {
// Write ACL to file
self.write_type(path, ACL_TYPE_ACCESS)?;
Ok(())
}
pub fn write_default<P: AsRef<Path>>(&self, path: P) -> Result<()> {
// Write default ACL to file
self.write_type(path, ACL_TYPE_DEFAULT)?;
Ok(())
}
fn write_type<P: AsRef<Path>>(&self, path: P, acl_type: acl_type_t) -> Result<()> {
let mut acl_buf = AclPtr(unsafe { acl_init(self.entries.len() as i32) });
if acl_buf.0.is_null() {
return Err(anyhow!("Failed to initialize ACL"));
}
let has_user = self
.entries
.iter()
.any(|x| matches!(x.0, Qualifier::UserObj));
if !has_user {
return Err(anyhow!("UserObj entry is required"));
}
let has_group = self
.entries
.iter()
.any(|x| matches!(x.0, Qualifier::GroupObj));
if !has_group {
return Err(anyhow!("GroupObj entry is required"));
}
let has_other = self.entries.iter().any(|x| matches!(x.0, Qualifier::Other));
if !has_other {
return Err(anyhow!("Other entry is required"));
}
let has_mask = self.entries.iter().any(|x| matches!(x.0, Qualifier::Mask));
if !has_mask {
return Err(anyhow!("Mask entry is required"));
}
for acl_entry in &self.entries {
let mut entry = null_mut();
check_return(
unsafe { acl_create_entry(&mut acl_buf.0, &mut entry) },
"acl_create_entry",
);
if entry.is_null() {
return Err(anyhow!("Failed to create ACL entry"));
}
check_return(
unsafe { acl_set_tag_type(entry, acl_entry.0.get_tag()) },
"acl_set_tag_type",
);
if let Some(uid) = acl_entry.0.get_uid() {
check_return(
unsafe { acl_set_qualifier(entry, addr_of!(uid).cast::<c_void>()) },
"acl_set_qualifier",
);
}
let mut permset: acl_permset_t = null_mut();
check_return(
unsafe { acl_get_permset(entry, &mut permset) },
"acl_get_permset",
);
check_return(unsafe { acl_clear_perms(permset) }, "acl_clear_perms");
check_return(
unsafe { acl_add_perm(permset, acl_entry.1.bits()) },
"acl_add_perm",
);
check_return(
unsafe { acl_set_permset(entry, permset) },
"acl_set_permset",
);
}
let cpath = CString::new(path.as_ref().to_str().unwrap()).unwrap();
unsafe { acl_set_file(cpath.as_ptr(), acl_type, acl_buf.0) };
Ok(())
}
}
struct AclPtr<T>(pub(crate) *mut T);
impl<T> Drop for AclPtr<T> {
fn drop(&mut self) {
if !self.0.is_null() {
check_return(unsafe { acl_free(self.0.cast()) }, "acl_free");
}
}
}
pub(crate) fn check_return(ret: i32, func: &str) {
println!("ret: {} fnc: {}", ret, func);
assert_eq!(
ret,
0,
"Error in {}: {}",
func,
std::io::Error::last_os_error()
);
}
#[cfg(test)]
mod test {
use super::*;
use std::fs::File;
use std::io::Write;
#[test]
fn test_acl() {
let mut acl = PosixACL::new(ACL_RWX, ACL_RWX, ACL_RWX);
acl.set(ACLEntry(Qualifier::User(1000), ACL_RWX));
acl.set(ACLEntry(Qualifier::Group(1000), ACL_RWX));
acl.set(ACLEntry(Qualifier::Other, ACL_RWX));
acl.set(ACLEntry(Qualifier::Mask, ACL_READ));
let path = "test.txt";
let mut file = File::create(path).unwrap();
file.write_all(b"Hello, world!").unwrap();
acl.write(path).unwrap();
let acl_r = PosixACL::new_from_file(path, false).unwrap();
println!("{:?}", acl);
assert_eq!(acl.entries.len(), acl_r.entries.len());
let missing = acl
.entries
.iter()
.filter(|x| acl_r.entries.iter().find(|y| x == y).is_none());
assert_eq!(missing.count(), 0);
let additional = acl_r
.entries
.iter()
.filter(|x| acl.entries.iter().find(|y| x == y).is_none());
assert_eq!(additional.count(), 0);
}
}