use ctor::ctor;
use exacl::{getfacl, setfacl, AclEntry, AclOption, Perm};
use log::debug;
use std::io;
#[ctor]
fn init() {
env_logger::init();
}
#[test]
fn test_getfacl_file() -> io::Result<()> {
let file = tempfile::NamedTempFile::new()?;
let entries = getfacl(&file, None)?;
#[cfg(target_os = "macos")]
assert_eq!(entries.len(), 0);
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
assert_eq!(entries.len(), 3);
debug!("test_getfacl_file: {}", exacl::to_string(&entries)?);
#[cfg(target_os = "macos")]
{
let result = getfacl(&file, AclOption::DEFAULT_ACL);
assert!(result
.unwrap_err()
.to_string()
.contains("macOS does not support default ACL"));
}
#[cfg(target_os = "linux")]
{
let result = getfacl(&file, AclOption::DEFAULT_ACL);
assert!(result
.unwrap_err()
.to_string()
.contains("Permission denied"));
}
#[cfg(target_os = "freebsd")]
{
let result = getfacl(&file, AclOption::DEFAULT_ACL);
let errmsg = result.unwrap_err().to_string();
assert!(
errmsg.contains("Default ACL not supported") || errmsg.contains("Invalid argument")
);
}
Ok(())
}
#[test]
fn test_setfacl_file() -> io::Result<()> {
let file = tempfile::NamedTempFile::new()?;
let mut entries = getfacl(&file, None)?;
entries.push(AclEntry::allow_user("500", Perm::READ, None));
setfacl(&[file], &entries, None)?;
Ok(())
}
#[cfg(target_os = "linux")]
fn get_filesystem(path: &std::path::PathBuf) -> String {
let df = std::process::Command::new("df")
.arg("-Th")
.arg(path)
.stdout(std::process::Stdio::piped())
.spawn()
.expect("df is a valid unix command");
let sed = std::process::Command::new("sed")
.arg("1d")
.stdin(df.stdout.unwrap())
.stdout(std::process::Stdio::piped())
.spawn()
.expect("sed is a valid unix command");
let tr = std::process::Command::new("tr")
.arg("-s")
.arg(" ")
.stdin(sed.stdout.unwrap())
.stdout(std::process::Stdio::piped())
.spawn()
.expect("tr is a valid unix command");
let cut = std::process::Command::new("cut")
.arg("-d")
.arg(" ")
.arg("-f2")
.stdin(tr.stdout.unwrap())
.output()
.expect("cut is a valid unix command");
String::from_utf8(cut.stdout)
.expect("FS should be valid utf8")
.trim_end()
.to_string()
}
#[test]
#[cfg(target_os = "linux")]
fn test_too_many_entries() -> io::Result<()> {
use std::collections::HashMap;
const UNTESTED: u32 = 65535;
let path = std::env::temp_dir();
let fs = get_filesystem(&path);
debug!("Running on filesystem: {{{}}} TMPDIR={:?}", fs, path);
let supported_fs = HashMap::from([
("brtfs", UNTESTED),
("xfs", 5461), ("tmpfs", 8191),
("ext2", 507),
("ext3", 507),
("ext4", 507),
("gpfs", UNTESTED),
("nss", UNTESTED),
]);
assert!(
supported_fs.contains_key(fs.as_str()),
"Not a supported filesystem: {fs}"
);
let max_entries = supported_fs[fs.as_str()];
if max_entries == UNTESTED {
debug!("Filesystem {} is not tested!", fs);
}
let mut entries = vec![
AclEntry::allow_user("", Perm::READ, None),
AclEntry::allow_group("", Perm::READ, None),
AclEntry::allow_other(Perm::empty(), None),
AclEntry::allow_mask(Perm::READ, None),
];
let max_entries = max_entries.saturating_sub(u32::try_from(entries.len()).unwrap());
let offset = 500;
for i in 0..max_entries {
entries.push(AclEntry::allow_user(
&(offset + i as usize).to_string(),
Perm::READ,
None,
));
}
let files = [tempfile::NamedTempFile::new_in(path)?];
debug!("Call setfacl with {} entries...", entries.len());
setfacl(&files, &entries, None)?;
debug!("{} entries were added and it is okay", entries.len());
entries.push(AclEntry::allow_user(
(u32::MAX - 1).to_string().as_str(),
Perm::READ,
None,
));
let err = setfacl(&files, &entries, None).unwrap_err();
debug!("Got error as expected: {}", err);
assert!(
err.to_string().contains("No space left on device")
|| err.to_string().contains("Argument list too long")
);
Ok(())
}
#[test]
fn test_reader_writer() -> io::Result<()> {
let input = r#"
u:aaa:rwx#comment
g:bbb:rwx
u:ccc:rx
"#;
let entries = exacl::from_str(input)?;
let actual = exacl::to_string(&entries)?;
let expected = r#"allow::user:aaa:read,write,execute
allow::group:bbb:read,write,execute
allow::user:ccc:read,execute
"#;
assert_eq!(expected, actual);
Ok(())
}
#[test]
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
fn test_exclusive_acloptions() {
let path = "/tmp";
let err1 = getfacl(path, AclOption::ACCESS_ACL | AclOption::DEFAULT_ACL).unwrap_err();
assert_eq!(
err1.to_string(),
"ACCESS_ACL and DEFAULT_ACL are mutually exclusive options"
);
let err2 = setfacl(&[path], &[], AclOption::ACCESS_ACL | AclOption::DEFAULT_ACL).unwrap_err();
assert_eq!(
err2.to_string(),
"ACCESS_ACL and DEFAULT_ACL are mutually exclusive options"
);
}
#[test]
#[cfg(target_os = "macos")]
fn test_exclusive_acloptions() {
let path = "/tmp";
let err1 = getfacl(path, AclOption::ACCESS_ACL | AclOption::DEFAULT_ACL).unwrap_err();
assert_eq!(
err1.to_string(),
"File \"/tmp\": macOS does not support default ACL"
);
let err2 = setfacl(&[path], &[], AclOption::ACCESS_ACL | AclOption::DEFAULT_ACL).unwrap_err();
assert_eq!(
err2.to_string(),
"File \"/tmp\": macOS does not support default ACL"
);
}
#[test]
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
fn test_from_mode() {
let acl_7777 = exacl::to_string(&exacl::from_mode(0o7777)).unwrap();
assert_eq!(acl_7777, "allow::user::read,write,execute\nallow::group::read,write,execute\nallow::other::read,write,execute\n");
let acl_000 = exacl::to_string(&exacl::from_mode(0o000)).unwrap();
assert_eq!(acl_000, "allow::user::\nallow::group::\nallow::other::\n");
let acl_123 = exacl::to_string(&exacl::from_mode(0o123)).unwrap();
assert_eq!(
acl_123,
"allow::user::execute\nallow::group::write\nallow::other::write,execute\n"
);
let acl_12345 = exacl::to_string(&exacl::from_mode(0o12345)).unwrap();
assert_eq!(
acl_12345,
"allow::user::write,execute\nallow::group::read\nallow::other::read,execute\n"
);
}