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

Parsing and Generation

Your ACLs need to move between Rust code and the outside world. The most common external format is the text output of getfacl(1), the standard POSIX tool for displaying ACLs. posix-acls can parse this format into PosixAcl values and generate it back, with round-trip fidelity.

The getfacl format

A getfacl text block looks like this:

# owner: alice
# group: devs
# flags: -s-
user::rwx
user:bob:rw-
group::r-x
group:ops:r--
mask::rwx
other::r--
default:user::rwx
default:group::r-x
default:other::r--

Comment lines (#) carry metadata: owner name, owning group, and optional flags (SUID/SGID/sticky). Entry lines use the tag:qualifier:perms syntax. Default entries are prefixed with default:.

Parsing

parse::<GetfaclParser>(text) converts a getfacl text block into a PosixAcl:

#![allow(unused)]
fn main() {
use posix_acls::parser::{parse, GetfaclParser};

let text = "\
owner: alice
group: devs
user::rwx
user:bob:rw-
group::r-x
mask::rwx
other::r--
";

let acl = parse::<GetfaclParser>(text).unwrap();
assert_eq!(acl.owner(), "alice");
assert_eq!(acl.owning_group(), "devs");
assert!(acl.is_extended());
}

The parser validates the ACL structurally after parsing – it delegates to PosixAcl::is_valid() so there is a single authoritative validation path. If the text is syntactically valid but structurally invalid (e.g., named entries without a mask), you get a ParseError rather than a silently broken PosixAcl.

Parse errors

ParseError covers seven failure modes:

VariantCause
EmptyInputThe input string is empty or all whitespace
InvalidTagSyntaxA tag name is not user, group, mask, or other
InvalidPermStringA permission field is not a valid rwx string
MissingRequiredEntryA mandatory tag (user::, group::, other::) is absent
DuplicateEntryThe same named entry appears more than once
MissingMaskNamed entries present but no mask::
SpuriousMaskA mask:: entry with no named entries
#![allow(unused)]
fn main() {
use posix_acls::parser::{parse, GetfaclParser, ParseError};

// Named entry without a mask
let text = "user::rwx\nuser:bob:rw-\ngroup::r-x\nother::r--\n";
assert_eq!(parse::<GetfaclParser>(text), Err(ParseError::MissingMask));

// Empty input
assert_eq!(parse::<GetfaclParser>("  \n  "), Err(ParseError::EmptyInput));
}

Generation

generate::<GetfaclGenerator>(acl) produces the getfacl text format from a PosixAcl. Entries are emitted in canonical POSIX order: UserObj, named users (sorted alphabetically), GroupObj, named groups (sorted), Mask, Other:

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

let acl = AclBuilder::new("alice", "devs")
    .owner_perms(PosixPerm::RWX)
    .owning_group_perms(PosixPerm::RX)
    .other_perms(PosixPerm::NONE)
    .add_user("zoe", PosixPerm::R)
    .add_user("bob", PosixPerm::RW)
    .build();

let text = generate::<GetfaclGenerator>(&acl).unwrap();

// Named users are sorted alphabetically
let bob_pos = text.find("user:bob:").unwrap();
let zoe_pos = text.find("user:zoe:").unwrap();
assert!(bob_pos < zoe_pos);
}

The generator validates the ACL before producing output. If is_valid() fails, you get a GenerateError::InvalidAcl.

Default entries are emitted after access entries with the default: prefix:

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

let acl = AclBuilder::new("alice", "devs")
    .owner_perms(PosixPerm::RWX)
    .owning_group_perms(PosixPerm::RX)
    .other_perms(PosixPerm::NONE)
    .default_owner_perms(PosixPerm::RWX)
    .default_owning_group_perms(PosixPerm::RX)
    .default_other_perms(PosixPerm::NONE)
    .build();

let text = generate::<GetfaclGenerator>(&acl).unwrap();
assert!(text.contains("default:user::rwx"));
assert!(text.contains("default:group::r-x"));
}

Special bits are emitted as a # flags: comment line when any are set:

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

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

let text = generate::<GetfaclGenerator>(&acl).unwrap();
assert!(text.contains("# flags: -s-"));
}

Round-trip fidelity

Parse-generate-parse round-trips preserve all structural information:

#![allow(unused)]
fn main() {
use posix_acls::{AclBuilder, PosixPerm};
use posix_acls::perm::SpecialBits;
use posix_acls::parser::{parse, GetfaclParser};
use posix_acls::generator::{generate, GetfaclGenerator};

let original = 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)
    .special_bits(SpecialBits::SGID)
    .default_owner_perms(PosixPerm::RWX)
    .default_owning_group_perms(PosixPerm::RX)
    .default_other_perms(PosixPerm::NONE)
    .build();

let text = generate::<GetfaclGenerator>(&original).unwrap();
let recovered = parse::<GetfaclParser>(&text).unwrap();

assert_eq!(original.owner(), recovered.owner());
assert_eq!(original.owning_group(), recovered.owning_group());
assert_eq!(original.special_bits(), recovered.special_bits());
assert_eq!(original.entries(), recovered.entries());
assert_eq!(original.default_entries(), recovered.default_entries());
}

Custom formats

Both the parser and generator are trait-based, so you can add new text formats without modifying the crate. Implement PosixAclFormat for parsing or PosixAclGenerator for generation:

use posix_acls::parser::PosixAclFormat;
use posix_acls::{PosixAcl, ParseError};

struct MyFormat;

impl PosixAclFormat for MyFormat {
    fn parse(input: &str) -> Result<PosixAcl, ParseError> {
        // Your parsing logic here
        todo!()
    }

    fn name() -> &'static str {
        "my-format"
    }
}

The generic functions parse::<F>(text) and generate::<F>(acl) dispatch to whichever format you provide.

Next steps

You can now serialise and deserialise ACLs. The next page explains the POSIX.1e access-check algorithm – how the kernel decides whether a user can read, write, or execute a file based on its ACL.

See Access Checks.