use crate::failx::*;
use crate::flag::Flag;
use crate::perm::Perm;
use crate::qualifier::Qualifier;
use crate::sys::*;
use crate::util::util_common;
use scopeguard::defer;
use std::ffi::{c_void, CString};
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
pub use util_common::{xacl_create_entry, xacl_foreach, xacl_free, xacl_init, xacl_is_empty};
use util_common::*;
const fn get_acl_type(default_acl: bool) -> acl_type_t {
if default_acl {
sg::ACL_TYPE_DEFAULT
} else {
sg::ACL_TYPE_ACCESS
}
}
pub fn xacl_get_file(path: &Path, symlink_acl: bool, default_acl: bool) -> io::Result<acl_t> {
if symlink_acl {
return fail_custom("Linux does not support symlinks with ACL's.");
}
let acl_type = get_acl_type(default_acl);
let c_path = CString::new(path.as_os_str().as_bytes())?;
let acl = unsafe { acl_get_file(c_path.as_ptr(), acl_type) };
if acl.is_null() {
let func = if default_acl {
"acl_get_file/default"
} else {
"acl_get_file/access"
};
return fail_err("null", func, &c_path);
}
Ok(acl)
}
pub fn xacl_set_file(
path: &Path,
acl: acl_t,
symlink_acl: bool,
default_acl: bool,
) -> io::Result<()> {
if symlink_acl {
return fail_custom("Linux does not support symlinks with ACL's");
}
let c_path = CString::new(path.as_os_str().as_bytes())?;
let acl_type = get_acl_type(default_acl);
let ret = unsafe { acl_set_file(c_path.as_ptr(), acl_type, acl) };
if ret != 0 {
let func = if default_acl {
"acl_set_file/default"
} else {
"acl_set_file/access"
};
return fail_err(ret, func, &c_path);
}
Ok(())
}
fn xacl_get_qualifier(entry: acl_entry_t) -> io::Result<Qualifier> {
let tag = xacl_get_tag_type(entry)?;
let id = if tag == sg::ACL_USER || tag == sg::ACL_GROUP {
let id_ptr = unsafe { acl_get_qualifier(entry).cast::<uid_t>() };
if id_ptr.is_null() {
return fail_err("null", "acl_get_qualifier", ());
}
defer! { xacl_free(id_ptr) };
Some(unsafe { *id_ptr })
} else {
None
};
let result = match tag {
sg::ACL_USER => Qualifier::User(id.unwrap()),
sg::ACL_GROUP => Qualifier::Group(id.unwrap()),
sg::ACL_USER_OBJ => Qualifier::UserObj,
sg::ACL_GROUP_OBJ => Qualifier::GroupObj,
sg::ACL_OTHER => Qualifier::Other,
sg::ACL_MASK => Qualifier::Mask,
tag => Qualifier::Unknown(format!("@tag {tag}")),
};
Ok(result)
}
fn xacl_get_tag_qualifier(_acl: acl_t, entry: acl_entry_t) -> io::Result<(bool, Qualifier)> {
let qualifier = xacl_get_qualifier(entry)?;
Ok((true, qualifier))
}
#[allow(clippy::unnecessary_wraps)]
const fn xacl_get_flags(_acl: acl_t, _entry: acl_entry_t) -> io::Result<Flag> {
Ok(Flag::empty()) }
pub fn xacl_get_entry(acl: acl_t, entry: acl_entry_t) -> io::Result<(bool, Qualifier, Perm, Flag)> {
let (allow, qualifier) = xacl_get_tag_qualifier(acl, entry)?;
let perms = xacl_get_perm(entry)?;
let flags = xacl_get_flags(acl, entry)?;
Ok((allow, qualifier, perms, flags))
}
pub fn xacl_set_qualifier(entry: acl_entry_t, mut id: uid_t) -> io::Result<()> {
let id_ptr = std::ptr::addr_of_mut!(id);
let ret = unsafe { acl_set_qualifier(entry, id_ptr.cast::<c_void>()) };
if ret != 0 {
return fail_err(ret, "acl_set_qualifier", ());
}
Ok(())
}
pub fn xacl_set_tag_qualifier(
entry: acl_entry_t,
allow: bool,
qualifier: &Qualifier,
) -> io::Result<()> {
if !allow {
return fail_custom("allow=false is not supported on Linux");
}
match qualifier {
Qualifier::User(uid) => {
xacl_set_tag_type(entry, sg::ACL_USER)?;
xacl_set_qualifier(entry, *uid)?;
}
Qualifier::Group(gid) => {
xacl_set_tag_type(entry, sg::ACL_GROUP)?;
xacl_set_qualifier(entry, *gid)?;
}
Qualifier::UserObj => {
xacl_set_tag_type(entry, sg::ACL_USER_OBJ)?;
}
Qualifier::GroupObj => {
xacl_set_tag_type(entry, sg::ACL_GROUP_OBJ)?;
}
Qualifier::Other => {
xacl_set_tag_type(entry, sg::ACL_OTHER)?;
}
Qualifier::Mask => {
xacl_set_tag_type(entry, sg::ACL_MASK)?;
}
Qualifier::Unknown(tag) => {
return fail_custom(&format!("unknown tag: {tag}"));
}
}
Ok(())
}
#[allow(clippy::unnecessary_wraps)]
const fn xacl_set_flags(_entry: acl_entry_t, _flags: Flag) -> io::Result<()> {
Ok(()) }
pub fn xacl_add_entry(
acl: &mut acl_t,
allow: bool,
qualifier: &Qualifier,
perms: Perm,
flags: Flag,
) -> io::Result<acl_entry_t> {
xacl_foreach(*acl, |entry| {
let (_, prev) = xacl_get_tag_qualifier(*acl, entry)?;
if prev == *qualifier {
let default = if flags.contains(Flag::DEFAULT) {
"default "
} else {
""
};
fail_custom(&format!("duplicate {default}entry for \"{prev}\""))?;
}
Ok(())
})?;
let entry = xacl_create_entry(acl)?;
xacl_set_tag_qualifier(entry, allow, qualifier)?;
xacl_set_perm(entry, perms)?;
xacl_set_flags(entry, flags)?;
Ok(entry)
}
pub const fn xacl_is_posix(_acl: acl_t) -> bool {
true
}
#[cfg(test)]
mod util_linux_test {
use super::*;
#[test]
fn test_acl_api_misuse() {
let mut acl = xacl_init(1).unwrap();
let entry = xacl_create_entry(&mut acl).unwrap();
let err = xacl_set_tag_type(entry, 0).unwrap_err();
assert_eq!(err.raw_os_error(), Some(sg::EINVAL));
let err = xacl_set_qualifier(entry, 500).unwrap_err();
assert_eq!(err.raw_os_error(), Some(sg::EINVAL));
let err =
xacl_set_tag_qualifier(entry, true, &Qualifier::Unknown("x".to_string())).unwrap_err();
assert!(err.to_string().contains("unknown tag: x"));
let entry2 = xacl_create_entry(&mut acl).unwrap();
xacl_set_tag_type(entry2, sg::ACL_USER_OBJ).unwrap();
xacl_free(acl);
}
#[test]
fn test_empty_acl() {
let file = tempfile::NamedTempFile::new().unwrap();
let dir = tempfile::TempDir::new().unwrap();
let acl = xacl_init(1).unwrap();
assert!(xacl_is_empty(acl));
let ret = unsafe { acl_valid(acl) };
assert_eq!(ret, -1);
xacl_set_file(file.as_ref(), acl, false, false)
.ok()
.unwrap();
xacl_set_file(dir.as_ref(), acl, false, false).ok().unwrap();
xacl_set_file(dir.as_ref(), acl, false, true).ok().unwrap();
xacl_free(acl);
}
#[test]
fn test_uninitialized_entry() {
let mut acl = xacl_init(1).unwrap();
let entry_p = xacl_create_entry(&mut acl).unwrap();
let (allow, qualifier) = xacl_get_tag_qualifier(acl, entry_p).unwrap();
assert_eq!(qualifier.name().unwrap(), "@tag 0");
assert!(allow);
xacl_free(acl);
}
}