use crate::bititer::BitIter;
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::{CString, c_void};
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use uuid::Uuid;
pub use util_common::{xacl_create_entry, xacl_foreach, xacl_free, xacl_init, xacl_is_empty};
use util_common::*;
fn path_exists(path: &Path, symlink_only: bool) -> bool {
if symlink_only {
path.symlink_metadata().is_ok()
} else {
path.exists()
}
}
pub fn xacl_get_file(path: &Path, symlink_acl: bool, default_acl: bool) -> io::Result<acl_t> {
if default_acl {
return fail_custom("macOS does not support default ACL");
}
let c_path = CString::new(path.as_os_str().as_bytes())?;
let acl = if symlink_acl {
unsafe { acl_get_link_np(c_path.as_ptr(), acl_type_t_ACL_TYPE_EXTENDED) }
} else {
unsafe { acl_get_file(c_path.as_ptr(), acl_type_t_ACL_TYPE_EXTENDED) }
};
if acl.is_null() {
let func = if symlink_acl {
"acl_get_link_np"
} else {
"acl_get_file"
};
let err = log_err("null", func, &c_path);
if err.raw_os_error() == Some(sg::ENOENT) && path_exists(path, symlink_acl) {
return xacl_init(1);
}
return Err(err);
}
Ok(acl)
}
fn xacl_set_file_symlink_alt(c_path: &CString, acl: acl_t) -> io::Result<()> {
let fd = unsafe { open(c_path.as_ptr(), sg::O_SYMLINK) };
if fd < 0 {
return fail_err(fd, "open", c_path);
}
defer! { unsafe{ close(fd) }; }
let ret = unsafe { acl_set_fd(fd, acl) };
if ret != 0 {
return fail_err(ret, "acl_set_fd", fd);
}
Ok(())
}
pub fn xacl_set_file(
path: &Path,
acl: acl_t,
symlink_acl: bool,
default_acl: bool,
) -> io::Result<()> {
if default_acl {
return fail_custom("macOS does not support default ACL");
}
let c_path = CString::new(path.as_os_str().as_bytes())?;
let ret = if symlink_acl {
unsafe { acl_set_link_np(c_path.as_ptr(), acl_type_t_ACL_TYPE_EXTENDED, acl) }
} else {
unsafe { acl_set_file(c_path.as_ptr(), acl_type_t_ACL_TYPE_EXTENDED, acl) }
};
if ret != 0 {
let err = log_err(ret, "acl_set_link_np", &c_path);
if err.raw_os_error() == Some(sg::ENOTSUP) && symlink_acl {
return xacl_set_file_symlink_alt(&c_path, acl);
}
return Err(err);
}
Ok(())
}
fn xacl_get_qualifier(entry: acl_entry_t) -> io::Result<Qualifier> {
let uuid_ptr = unsafe { acl_get_qualifier(entry).cast::<Uuid>() };
if uuid_ptr.is_null() {
return fail_err("null", "acl_get_qualifier", ());
}
defer! { xacl_free(uuid_ptr) }
let guid = unsafe { *uuid_ptr };
Qualifier::from_guid(guid)
}
fn xacl_get_tag_qualifier(_acl: acl_t, entry: acl_entry_t) -> io::Result<(bool, Qualifier)> {
let tag = xacl_get_tag_type(entry)?;
let result = match tag {
sg::ACL_EXTENDED_ALLOW => (true, xacl_get_qualifier(entry)?),
sg::ACL_EXTENDED_DENY => (false, xacl_get_qualifier(entry)?),
_ => (false, Qualifier::Unknown(format!("@tag {tag}"))),
};
Ok(result)
}
fn xacl_get_flags_np(obj: *mut c_void) -> io::Result<Flag> {
assert!(!obj.is_null());
let mut flagset: acl_flagset_t = std::ptr::null_mut();
let ret = unsafe { acl_get_flagset_np(obj, &raw mut flagset) };
if ret != 0 {
return fail_err(ret, "acl_get_flagset_np", ());
}
assert!(!flagset.is_null());
let mut flags = Flag::empty();
for flag in BitIter(Flag::all()) {
let res = unsafe { acl_get_flag_np(flagset, flag.bits()) };
debug_assert!((0..=1).contains(&res));
if res == 1 {
flags |= flag;
}
}
Ok(flags)
}
fn xacl_get_flags(_acl: acl_t, entry: acl_entry_t) -> io::Result<Flag> {
xacl_get_flags_np(entry.cast::<c_void>())
}
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, qualifier: &Qualifier) -> io::Result<()> {
let mut bytes = qualifier.guid()?.into_bytes();
let ret = unsafe { acl_set_qualifier(entry, bytes.as_mut_ptr().cast::<c_void>()) };
if ret != 0 {
return fail_err(ret, "acl_set_qualifier", ());
}
Ok(())
}
fn xacl_set_tag_qualifier(
entry: acl_entry_t,
allow: bool,
qualifier: &Qualifier,
) -> io::Result<()> {
let tag = if let Qualifier::Unknown(_) = qualifier {
debug_assert!(!allow);
sg::ACL_EXTENDED_DENY
} else if allow {
sg::ACL_EXTENDED_ALLOW
} else {
sg::ACL_EXTENDED_DENY
};
xacl_set_tag_type(entry, tag)?;
xacl_set_qualifier(entry, qualifier)?;
Ok(())
}
fn xacl_set_flags_np(obj: *mut c_void, flags: Flag) -> io::Result<()> {
assert!(!obj.is_null());
let mut flagset: acl_flagset_t = std::ptr::null_mut();
let ret_get = unsafe { acl_get_flagset_np(obj, &raw mut flagset) };
if ret_get != 0 {
return fail_err(ret_get, "acl_get_flagset_np", ());
}
assert!(!flagset.is_null());
let ret_clear = unsafe { acl_clear_flags_np(flagset) };
if ret_clear != 0 {
return fail_err(ret_clear, "acl_clear_flags_np", ());
}
for flag in BitIter(flags) {
let ret = unsafe { acl_add_flag_np(flagset, flag.bits()) };
debug_assert!(ret == 0);
}
Ok(())
}
fn xacl_set_flags(entry: acl_entry_t, flags: Flag) -> io::Result<()> {
xacl_set_flags_np(entry.cast::<c_void>(), flags)
}
pub fn xacl_add_entry(
acl: &mut acl_t,
allow: bool,
qualifier: &Qualifier,
perms: Perm,
flags: Flag,
) -> io::Result<acl_entry_t> {
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 {
false
}
#[cfg(test)]
mod util_macos_test {
use super::*;
#[test]
fn test_acl_init() {
use std::convert::TryInto;
let max_entries: usize = ACL_MAX_ENTRIES.try_into().unwrap();
let acl = xacl_init(max_entries).ok().unwrap();
assert!(!acl.is_null());
xacl_free(acl);
let err = xacl_init(max_entries + 1).unwrap_err();
assert_eq!(err.to_string(), "Too many ACL entries");
}
#[test]
fn test_acl_too_big() {
let mut acl = xacl_init(3).ok().unwrap();
assert!(!acl.is_null());
for _ in 0..ACL_MAX_ENTRIES {
xacl_create_entry(&mut acl).unwrap();
}
let err = xacl_create_entry(&mut acl).unwrap_err();
assert_eq!(err.raw_os_error(), Some(sg::ENOMEM));
xacl_free(acl);
}
#[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, &Qualifier::Guid(Uuid::nil())).unwrap_err();
assert_eq!(err.raw_os_error(), Some(sg::EINVAL));
let entry2 = xacl_create_entry(&mut acl).unwrap();
xacl_set_tag_type(entry2, 1).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);
}
}