Permissions
Your shared project directory needs three kinds of access: reading source files, writing build artifacts, and executing scripts. Every permission decision in POSIX ACLs ultimately reduces to a combination of these three bits. This page covers the types that model them and the algebraic structure that makes combining permissions predictable.
PosixPerm
PosixPerm represents the classic rwx permission triple as a 3-bit
bitmask. Eight named constants cover every combination:
#![allow(unused)]
fn main() {
use posix_acls::PosixPerm;
assert_eq!(PosixPerm::NONE.to_string(), "---");
assert_eq!(PosixPerm::R.to_string(), "r--");
assert_eq!(PosixPerm::RW.to_string(), "rw-");
assert_eq!(PosixPerm::RWX.to_string(), "rwx");
// Individual bit checks
assert!(PosixPerm::RW.read());
assert!(PosixPerm::RW.write());
assert!(!PosixPerm::RW.execute());
}
All eight constants: NONE, X, W, WX, R, RX, RW, RWX.
You can also construct from raw bits – only the low 3 bits are kept:
#![allow(unused)]
fn main() {
use posix_acls::PosixPerm;
let p = PosixPerm::new(0b101);
assert_eq!(p, PosixPerm::RX);
assert_eq!(p.bits(), 5);
}
Containment
contains checks whether one permission set is a superset of another – the
fundamental operation behind access checks:
#![allow(unused)]
fn main() {
use posix_acls::PosixPerm;
assert!(PosixPerm::RWX.contains(PosixPerm::R));
assert!(PosixPerm::RW.contains(PosixPerm::RW));
assert!(!PosixPerm::R.contains(PosixPerm::W));
}
Masking
POSIX.1e defines an effective-rights mask that limits the permissions of
named user and group entries. The mask_with method applies it:
#![allow(unused)]
fn main() {
use posix_acls::PosixPerm;
// Bob has rwx, but the mask is r-- -- effective permissions are r--
assert_eq!(PosixPerm::RWX.mask_with(PosixPerm::R), PosixPerm::R);
// The mask does not restrict the owner or other entries --
// those are applied directly in the access-check algorithm
assert_eq!(PosixPerm::RW.mask_with(PosixPerm::RX), PosixPerm::R);
}
Lattice algebra
PosixPerm implements the algebraic traits from acls_rs::algebra. The
partial order is the subset relation on bits: R and W are incomparable,
but both are less than RW.
#![allow(unused)]
fn main() {
use posix_acls::PosixPerm;
use acls_rs::algebra::{JoinSemilattice, MeetSemilattice, Monoid};
// Join (OR) is the least upper bound -- the least restrictive merge
assert_eq!(PosixPerm::R.join(PosixPerm::W), PosixPerm::RW);
// Meet (AND) is the greatest lower bound -- the most restrictive merge
assert_eq!(PosixPerm::RW.meet(PosixPerm::RX), PosixPerm::R);
// Identity element is NONE
assert_eq!(PosixPerm::RWX.join(PosixPerm::identity()), PosixPerm::RWX);
}
The partial order:
#![allow(unused)]
fn main() {
use posix_acls::PosixPerm;
assert!(PosixPerm::R < PosixPerm::RW);
assert!(PosixPerm::RWX > PosixPerm::R);
// R and W are incomparable -- neither is a subset of the other
assert_eq!(PosixPerm::R.partial_cmp(&PosixPerm::W), None);
}
These properties – commutativity, idempotence, associativity, and the
absorption law – are verified by property-based tests using proptest.
They guarantee that merging permissions from multiple sources (roles, group
memberships, policy rules) always produces a well-defined result regardless
of evaluation order.
| Trait | Operation | Meaning |
|---|---|---|
Semigroup | combine (OR) | Merge two permission sets |
Monoid | identity (NONE) | Empty permission set |
JoinSemilattice | join (OR) | Least upper bound |
MeetSemilattice | meet (AND) | Greatest lower bound |
BoundedJoinSemilattice | bottom (NONE) | Minimal element |
BoundedMeetSemilattice | top (RWX) | Maximal element |
Parsing and display
PosixPerm implements Display and FromStr for the standard rwx string
format:
#![allow(unused)]
fn main() {
use posix_acls::PosixPerm;
let p: PosixPerm = "rw-".parse().unwrap();
assert_eq!(p, PosixPerm::RW);
// Display produces the same format
assert_eq!(format!("{p}"), "rw-");
}
The parser also handles s, t, S, and T in the execute position (as
produced by some getfacl implementations when SUID/SGID/sticky bits
interact with the execute bit):
#![allow(unused)]
fn main() {
use posix_acls::PosixPerm;
// Lowercase 's'/'t' means execute IS set (plus the special bit)
let p: PosixPerm = "rws".parse().unwrap();
assert_eq!(p, PosixPerm::RWX);
// Uppercase 'S'/'T' means execute is NOT set (only the special bit)
let p: PosixPerm = "rwS".parse().unwrap();
assert_eq!(p, PosixPerm::RW);
}
SpecialBits
SpecialBits represents the three special Unix permission bits – SUID,
SGID, and sticky – which occupy bits 11-9 of the 12-bit mode word. These
are not part of the ACL entry system but are carried alongside the ACL for
mode word conversion.
#![allow(unused)]
fn main() {
use posix_acls::perm::SpecialBits;
let bits = SpecialBits::SGID;
assert!(bits.sgid());
assert!(!bits.suid());
assert!(!bits.sticky());
// Display uses the getfacl # flags: format
assert_eq!(bits.to_string(), "-s-");
}
SGID on a directory is particularly relevant: it causes new files to inherit the directory’s owning group, which is often combined with default ACLs to enforce consistent group ownership in shared project directories.
Constants: NONE, STICKY, SGID, SUID. Construct from raw bits with
SpecialBits::new(bits).
Next steps
You now know how individual permissions work. The next page introduces ACL
tags and entries – the structures that attach permissions to specific users
and groups – and the AclBuilder for constructing complete ACLs.
See Building ACLs.