Access Checks
You have built an ACL and serialised it. Now the question that matters: can
user X do operation Y on this file? This page covers the POSIX.1e
access-check algorithm as implemented by PosixAcl::check_access, the role
of the mask entry, and conversion between ACLs and Unix mode words.
The access-check algorithm
check_access implements POSIX.1e section 17.4. It walks the ACL entries
in a fixed order and returns on the first match:
- Owner – if the user is the file owner, use the
user::permissions. No masking is applied. - Named user – if a
user:name:entry matches, return its permissions masked with the mask entry. - Group – collect all matching
group:name:entries (by checking the user’s group list) plus thegroup::entry if the owning group is in the user’s groups. If any match, return the join (bitwise OR) of all matched entries’ masked permissions. - Other – if nothing else matched, use
other::permissions. No masking is applied.
#![allow(unused)]
fn main() {
use posix_acls::{AclBuilder, PosixPerm};
let acl = AclBuilder::new("alice", "devs")
.owner_perms(PosixPerm::RWX)
.owning_group_perms(PosixPerm::RX)
.other_perms(PosixPerm::R)
.add_user("bob", PosixPerm::RW)
.add_group("ops", PosixPerm::R)
.build();
// 1. Owner match -- uses user:: directly, no masking
let r = acl.check_access("alice", &[] as &[&str], PosixPerm::RWX);
assert!(r.granted);
// 2. Named user match -- masked with mask entry
let r = acl.check_access("bob", &["staff"] as &[&str], PosixPerm::R);
assert!(r.granted);
// 3. Group match -- owning group via group list
let r = acl.check_access("charlie", &["devs"] as &[&str], PosixPerm::R);
assert!(r.granted);
// 3b. Named group match
let r = acl.check_access("dave", &["ops"] as &[&str], PosixPerm::R);
assert!(r.granted);
// 4. Other fallback
let r = acl.check_access("eve", &["guests"] as &[&str], PosixPerm::R);
assert!(r.granted);
}
AccessResult
check_access returns an AccessResult with three fields:
| Field | Type | Meaning |
|---|---|---|
granted | bool | Whether the requested permissions are fully held |
matched_tag | AclTag | Which entry determined the result |
effective_perms | PosixPerm | The actual permissions after masking |
The matched_tag field tells you not just yes/no, but why – useful for
auditing and debugging:
#![allow(unused)]
fn main() {
use posix_acls::{AclBuilder, AclTag, PosixPerm};
let acl = AclBuilder::new("alice", "devs")
.owner_perms(PosixPerm::RWX)
.owning_group_perms(PosixPerm::RX)
.other_perms(PosixPerm::NONE)
.add_user("bob", PosixPerm::RW)
.build();
let r = acl.check_access("alice", &[] as &[&str], PosixPerm::RWX);
assert_eq!(r.matched_tag, AclTag::UserObj);
assert_eq!(r.effective_perms, PosixPerm::RWX);
let r = acl.check_access("bob", &[] as &[&str], PosixPerm::R);
assert!(matches!(r.matched_tag, AclTag::User(ref n) if n == "bob"));
let r = acl.check_access("eve", &[] as &[&str], PosixPerm::R);
assert_eq!(r.matched_tag, AclTag::Other);
assert!(!r.granted);
}
The mask’s role
The mask entry limits effective permissions for named users and named groups.
It does not affect the owner (user::) or other (other::) entries.
This design lets the file owner set an upper bound on everyone else’s
access:
#![allow(unused)]
fn main() {
use posix_acls::{AclBuilder, PosixPerm};
let mut acl = AclBuilder::new("alice", "devs")
.owner_perms(PosixPerm::RWX)
.owning_group_perms(PosixPerm::NONE)
.other_perms(PosixPerm::NONE)
.add_user("bob", PosixPerm::RWX)
.build();
// Auto-computed mask is RWX -- bob gets full access
let r = acl.check_access("bob", &[] as &[&str], PosixPerm::W);
assert!(r.granted);
// Override mask to R only
acl.set_mask(PosixPerm::R);
// Now bob's effective permissions are RWX & R = R
let r = acl.check_access("bob", &[] as &[&str], PosixPerm::W);
assert!(!r.granted);
assert_eq!(r.effective_perms, PosixPerm::R);
}
The mask is auto-computed by AclBuilder as the bitwise OR of all named
user, named group, and owning group entries. You can also compute it
manually with acl.compute_mask() and set it with acl.set_mask(mask).
Group accumulation
When a user is a member of multiple groups that have ACL entries, the algorithm joins (ORs) all matching group permissions. This is a least-restrictive merge:
#![allow(unused)]
fn main() {
use posix_acls::{AclBuilder, PosixPerm};
let acl = AclBuilder::new("alice", "devs")
.owner_perms(PosixPerm::RWX)
.owning_group_perms(PosixPerm::R)
.other_perms(PosixPerm::NONE)
.add_group("ops", PosixPerm::W)
.add_group("deploy", PosixPerm::X)
.build();
// Charlie is in both ops and deploy -- effective = W | X = WX (after masking)
let r = acl.check_access(
"charlie",
&["ops", "deploy"] as &[&str],
PosixPerm::WX,
);
assert!(r.granted);
}
Mode word conversion
POSIX ACLs and traditional Unix mode words are two views of the same
permission model. PosixAcl::from_mode builds a minimal (non-extended)
ACL from a 12-bit mode word, and PosixAcl::to_mode converts back:
#![allow(unused)]
fn main() {
use posix_acls::{PosixAcl, PosixPerm};
let acl = PosixAcl::from_mode("alice", "devs", 0o755);
assert_eq!(acl.entries()[0].perms, PosixPerm::RWX); // user::rwx
assert_eq!(acl.entries()[1].perms, PosixPerm::RX); // group::r-x
assert_eq!(acl.entries()[2].perms, PosixPerm::RX); // other::r-x
assert_eq!(acl.to_mode(), 0o755);
}
Mode words include the special bits in the high 3 bits:
#![allow(unused)]
fn main() {
use posix_acls::PosixAcl;
// 0o2755 = SGID + rwxr-xr-x
let acl = PosixAcl::from_mode("alice", "devs", 0o2755);
assert!(acl.special_bits().sgid());
assert_eq!(acl.to_mode(), 0o2755);
}
For extended ACLs, to_mode follows POSIX.1e section 23.1.6: the group
bits in the mode word reflect the mask entry, not the group::
permissions. This is what stat(2) and ls -l show:
#![allow(unused)]
fn main() {
use posix_acls::{AclBuilder, PosixPerm};
let acl = AclBuilder::new("alice", "devs")
.owner_perms(PosixPerm::RWX)
.owning_group_perms(PosixPerm::RX)
.other_perms(PosixPerm::R)
.add_user("bob", PosixPerm::RW)
.build();
// mask = RW | RX = RWX → stat() shows group bits as rwx
let mode = acl.to_mode();
assert_eq!((mode >> 3) & 0o7, 0o7);
}
Minimal ACL mode-word round-trips are exhaustively tested for all 4096 values (9 permission bits plus 3 special bits).
Next steps
You know how permissions are checked and how ACLs map to mode words. When your organisation has many users and groups, managing ACLs manually becomes error-prone. The next page introduces the user/group model and policy system for declaring and validating ACLs at a higher level.