Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Building ACLs

Permissions alone are not useful until they are attached to principals. A POSIX ACL is a list of entries, each pairing a tag (which principal?) with a permission triple (what can they do?). This page covers the tag types, entry structure, the central PosixAcl type, and the fluent AclBuilder.

ACL tags

AclTag identifies which principal an ACL entry applies to. POSIX.1e defines six tag types:

#![allow(unused)]
fn main() {
use posix_acls::AclTag;

// Three mandatory tags -- every valid ACL has exactly one of each
let owner = AclTag::UserObj;         // user::
let group = AclTag::GroupObj;        // group::
let other = AclTag::Other;          // other::

// Two named tags -- these make the ACL "extended"
let alice = AclTag::User("alice".to_string());  // user:alice:
let devs  = AclTag::Group("devs".to_string());  // group:devs:

// The mask tag -- required when named entries are present
let mask = AclTag::Mask;             // mask::

assert!(alice.is_named());
assert!(!owner.is_named());
assert!(owner.is_mandatory());
assert!(!mask.is_mandatory());
}
Taggetfacl syntaxRequired?
UserObjuser::permsAlways
User(name)user:name:permsMakes ACL extended
GroupObjgroup::permsAlways
Group(name)group:name:permsMakes ACL extended
Maskmask::permsRequired when extended
Otherother::permsAlways

ACL entries

An AclEntry pairs a tag with a PosixPerm:

#![allow(unused)]
fn main() {
use posix_acls::{AclEntry, AclTag, PosixPerm};

let entry = AclEntry::new(
    AclTag::User("bob".to_string()),
    PosixPerm::RW,
);

assert_eq!(entry.tag, AclTag::User("bob".to_string()));
assert_eq!(entry.perms, PosixPerm::RW);
assert_eq!(entry.to_string(), "user:bob:rw-");
}

Both tag and perms are public fields – you can inspect and modify them directly.

PosixAcl

PosixAcl is the central type: a complete ACL for a file or directory. It carries an owner name, an owning group name, optional special bits, access entries, and optional default entries (for directories).

The constructor creates a minimal ACL with UserObj, GroupObj, and Other all set to NONE:

#![allow(unused)]
fn main() {
use posix_acls::{PosixAcl, PosixPerm};

let mut acl = PosixAcl::new("alice", "devs");
acl.set_owner_perms(PosixPerm::RWX);
acl.set_group_perms(PosixPerm::RX);
acl.set_other_perms(PosixPerm::R);

assert_eq!(acl.owner(), "alice");
assert_eq!(acl.owning_group(), "devs");
assert!(!acl.is_extended());
assert!(acl.is_valid().is_ok());
}

Adding named entries makes the ACL extended and requires a mask:

#![allow(unused)]
fn main() {
use posix_acls::{PosixAcl, PosixPerm};

let mut acl = PosixAcl::new("alice", "devs");
acl.set_owner_perms(PosixPerm::RWX);
acl.set_group_perms(PosixPerm::RX);
acl.set_other_perms(PosixPerm::NONE);
acl.add_user("bob", PosixPerm::RW);

// The mask must be set explicitly when using PosixAcl directly
let mask = acl.compute_mask();
acl.set_mask(mask);

assert!(acl.is_extended());
assert!(acl.is_valid().is_ok());
}

AclBuilder

For anything beyond a minimal ACL, AclBuilder is the idiomatic way to construct PosixAcl values. It auto-computes the mask when named entries are present:

#![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::NONE)
    .add_user("bob", PosixPerm::RW)
    .add_group("ops", PosixPerm::R)
    .build();

assert!(acl.is_valid().is_ok());
assert!(acl.is_extended());

// mask = union of named + group entries = RW | R | RX = RWX
assert_eq!(acl.mask(), PosixPerm::RWX);
}

All builder methods consume and return self, so you chain them fluently. Named-entry methods use upsert semantics – if the same name appears twice, the last call wins:

#![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::NONE)
    .add_user("bob", PosixPerm::RW)
    .add_user("bob", PosixPerm::R)   // overwrites the previous grant
    .build();

let bob = acl.entries().iter()
    .find(|e| matches!(&e.tag, posix_acls::AclTag::User(n) if n == "bob"))
    .unwrap();
assert_eq!(bob.perms, PosixPerm::R);
}

Default ACLs

Directories can carry a default ACL that is inherited by files and subdirectories created within them. The builder supports default entries through a parallel set of methods:

#![allow(unused)]
fn main() {
use posix_acls::{AclBuilder, PosixPerm};

let acl = AclBuilder::new("alice", "devs")
    // Access ACL (applies to the directory itself)
    .owner_perms(PosixPerm::RWX)
    .owning_group_perms(PosixPerm::RX)
    .other_perms(PosixPerm::NONE)
    // Default ACL (inherited by new files in this directory)
    .default_owner_perms(PosixPerm::RWX)
    .default_owning_group_perms(PosixPerm::RX)
    .default_other_perms(PosixPerm::NONE)
    .default_user("bob", PosixPerm::RW)
    .default_group("ops", PosixPerm::R)
    .build();

assert!(!acl.default_entries().is_empty());
assert!(acl.is_valid().is_ok());
}

Default ACLs follow the same rules as access ACLs – they need their own mask entry when named entries are present, and AclBuilder auto-computes it.

Special bits

SUID, SGID, and sticky bits are not ACL entries, but AclBuilder carries them so that a PosixAcl can represent the full 12-bit mode word:

#![allow(unused)]
fn main() {
use posix_acls::{AclBuilder, PosixPerm};
use posix_acls::perm::SpecialBits;

let acl = AclBuilder::new("alice", "devs")
    .owner_perms(PosixPerm::RWX)
    .owning_group_perms(PosixPerm::RX)
    .other_perms(PosixPerm::RX)
    .special_bits(SpecialBits::SGID)
    .build();

assert!(acl.special_bits().sgid());
assert_eq!(acl.to_mode(), 0o2755);
}

Validation

PosixAcl::is_valid() checks structural correctness:

#![allow(unused)]
fn main() {
use posix_acls::{PosixAcl, PosixPerm};

// Missing mask on extended ACL
let mut acl = PosixAcl::new("alice", "devs");
acl.set_owner_perms(PosixPerm::RWX);
acl.set_group_perms(PosixPerm::RX);
acl.set_other_perms(PosixPerm::NONE);
acl.add_user("bob", PosixPerm::RW);
// Forgot to set mask!

let err = acl.is_valid().unwrap_err();
assert!(err.to_string().contains("mask"));
}

Validation errors (PosixAclValidationError):

VariantMeaning
MissingEntryA required tag (user::, group::, other::) is absent
DuplicateEntryThe same named entry appears more than once
MissingMaskNamed entries exist but no mask:: entry is present
SpuriousMaskA mask:: entry exists but there are no named entries

AclBuilder::build() always produces a valid ACL because it auto-computes the mask and emits entries in the correct order. You only need is_valid() when constructing ACLs manually or after parsing untrusted input.

Next steps

You can now build ACLs programmatically. The next page shows how to serialise them to the getfacl(1) text format and parse them back, with round-trip guarantees.

See Parsing and Generation.