Reading and Writing ACL Entries in Linux: Complete ACL Guide
Part 3 of 8 —Reading and Writing ACL Entries in Linux: Complete ACL Guide
When you use getfacl, setfacl, or ACL library functions, ACL entries are represented as text strings. There are two text formats: long text form (one entry per line, used by getfacl) and short text form (all entries on one line, comma-separated, used with setfacl -m). Understanding both formats is essential for working with ACLs from the command line and in C programs.
Key Terms
The Basic Format of an ACL Entry in Text
Every ACL entry in text form follows this structure:
| Field | Description | Required? | Example |
|---|---|---|---|
tag-type |
Type keyword or abbreviation | Always | user or u |
tag-qualifier |
Username, group name, UID, or GID | Only for ACL_USER & ACL_GROUP | bob or 1007 |
permissions |
Any combo of r, w, x (or – for absent) | Always | r-x or rw |
Tag Type Keywords — Full Reference
Both long-form keywords and short abbreviations are accepted everywhere:
| Short Form | Long Form | Qualifier Present? | Tag Type | Entry For |
|---|---|---|---|---|
| u | user | No | ACL_USER_OBJ | File owner |
| u | user | Yes | ACL_USER | Specific named user |
| g | group | No | ACL_GROUP_OBJ | File group |
| g | group | Yes | ACL_GROUP | Specific named group |
| m | mask | No | ACL_MASK | Mask for group class |
| o | other | No | ACL_OTHER | All other users |
u::rw-), it’s ACL_USER_OBJ. If the qualifier has a name or number (e.g., u:bob:rw-), it’s ACL_USER. Same rule applies for group vs group_obj.Long Text Form
Used by getfacl output and the setfacl -M acl-file option (read from a file).
# Long text form — this is what getfacl produces
# Lines starting with # are comments
# file: project.c
# owner: ravi
# group: users
user::rw- # ACL_USER_OBJ — owner gets rw
user:bob:r-- # ACL_USER — bob gets read only
user:alice:rwx # ACL_USER — alice gets full access
group::r-- # ACL_GROUP_OBJ — file group gets read
group:devteam:rw- # ACL_GROUP — devteam gets rw
mask::rw- # ACL_MASK — cap for group class
other::--- # ACL_OTHER — everyone else: no access
Save this to a file and apply it with:
# Apply ACL from a file (long text form)
setfacl -M acl_spec.txt project.c
# You can use this to copy ACLs between files:
getfacl source.c | setfacl -M - dest.c
Short Text Form
Used with setfacl -m on the command line and with acl_from_text() / acl_to_text() in C programs.
# Short text form examples
# Traditional 0640 permissions as ACL:
u::rw-,g::r--,o::---
# Same using abbreviated keywords (r instead of r--):
u::rw,g::r,o::-
# Same using full keywords:
user::rw,group::r,other::-
# Full extended ACL in one line:
u::rw,u:bob:r,u:alice:rwx,g::r,g:devteam:rw,m::rwx,o::---
# Apply using setfacl -m:
setfacl -m "u:bob:r--,g:devteam:rw-" project.c
# Permissions field: can use full rwx or just letters present
# These are equivalent:
# r-- = r
# rw- = rw
# rwx = rwx
# --- = - (or just empty)
Permission Field — All Valid Formats
| Format | Meaning | Example |
|---|---|---|
| rwx | Read + Write + Execute | u:alice:rwx |
| rw- | Read + Write (no execute) | g:devs:rw- |
| r-x | Read + Execute | u:bob:r-x |
| r– | Read only (same as r) | u:bob:r-- |
| — | No permissions | o::--- |
| rw | Short form: read+write (no x) | g:devs:rw |
| – | No permissions (short form) | o::- |
Coding Examples
#!/bin/bash
# Script: apply_acl_from_file.sh
TARGET="$1"
if [ -z "$TARGET" ]; then
echo "Usage: $0 filename"
exit 1
fi
# Create an ACL spec file in long text form
cat > /tmp/my_acl.txt <<'EOF'
# Team project file ACL
user::rw-
user:alice:rwx
user:bob:r--
group::r--
group:devteam:rw-
mask::rwx
other::---
EOF
# Apply the ACL from the file
setfacl -M /tmp/my_acl.txt "$TARGET"
echo "ACL applied to $TARGET"
# Verify
getfacl "$TARGET"
rm /tmp/my_acl.txt
# Method 1: pipe getfacl output into setfacl
getfacl source.c | setfacl --set-file=- destination.c
# Method 2: save and restore
getfacl source.c > /tmp/source_acl.txt
setfacl -M /tmp/source_acl.txt destination.c
# Method 3: recursive ACL backup and restore
# Save all ACLs in a directory tree:
getfacl -R /project/ > /backup/project_acls.txt
# Restore later (must be run from /):
setfacl --restore=/backup/project_acls.txt
#include <stdio.h>
#include <stdlib.h>
#include <sys/acl.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s filename\n", argv[0]);
return 1;
}
/* --- acl_get_file: disk → in-memory acl_t --- */
acl_t acl = acl_get_file(argv[1], ACL_TYPE_ACCESS);
if (!acl) { perror("acl_get_file"); return 1; }
/* --- acl_to_text: in-memory acl_t → text string --- */
ssize_t len;
char *text = acl_to_text(acl, &len);
if (!text) { perror("acl_to_text"); acl_free(acl); return 1; }
printf("=== ACL as text (long form) ===\n%s\n", text);
printf("Text length: %zd bytes\n\n", len);
/* --- acl_from_text: text string → in-memory acl_t --- */
/* Build a new ACL from a short text string */
const char *short_form = "u::rw-,u:bob:r--,g::r--,m::r--,o::---";
acl_t new_acl = acl_from_text(short_form);
if (!new_acl) {
perror("acl_from_text");
} else {
/* Validate the ACL */
if (acl_valid(new_acl) == 0) {
printf("Parsed ACL is VALID\n");
} else {
printf("Parsed ACL is INVALID\n");
}
/* Convert back to text for display */
char *new_text = acl_to_text(new_acl, NULL);
printf("Parsed ACL:\n%s\n", new_text);
acl_free(new_text);
acl_free(new_acl);
}
/* Always free memory from ACL functions */
acl_free(text);
acl_free(acl);
return 0;
}
/* Compile: gcc -o acl_text acl_text.c -lacl */
#include <stdio.h>
#include <sys/acl.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s filename\n", argv[0]);
return 1;
}
/* Define desired ACL in short text form */
/* Owner: rw, Named user bob: read, File group: read,
Group devteam: rw, Mask: rw, Others: none */
const char *acl_spec = "u::rw-,u:bob:r--,g::r--,g:devteam:rw-,m::rw-,o::---";
/* Parse text into in-memory ACL */
acl_t acl = acl_from_text(acl_spec);
if (!acl) { perror("acl_from_text"); return 1; }
/* Validate before applying */
if (acl_valid(acl) != 0) {
fprintf(stderr, "ACL spec is invalid!\n");
acl_free(acl);
return 1;
}
/* Write ACL to the file (in-memory → disk) */
if (acl_set_file(argv[1], ACL_TYPE_ACCESS, acl) != 0) {
perror("acl_set_file");
acl_free(acl);
return 1;
}
printf("ACL applied successfully to %s\n", argv[1]);
/* Verify by reading back */
acl_t verify = acl_get_file(argv[1], ACL_TYPE_ACCESS);
char *text = acl_to_text(verify, NULL);
printf("Applied ACL:\n%s\n", text);
acl_free(text);
acl_free(verify);
acl_free(acl);
return 0;
}
/* Compile: gcc -o set_acl_text set_acl_text.c -lacl */
#include <stdio.h>
#include <sys/acl.h>
void print_permset(acl_permset_t ps) {
printf("%c%c%c",
acl_get_perm(ps, ACL_READ) ? 'r' : '-',
acl_get_perm(ps, ACL_WRITE) ? 'w' : '-',
acl_get_perm(ps, ACL_EXECUTE) ? 'x' : '-'
);
}
int main(void) {
/* Parse a short form ACL string */
const char *spec = "u::rwx,u:1007:r--,g::r-x,g:60:rw-,m::rw-,o::r--";
acl_t acl = acl_from_text(spec);
if (!acl) { perror("acl_from_text"); return 1; }
printf("Parsing: %s\n\n", spec);
printf("%-6s %-14s %-10s %s\n", "Type", "Tag", "Qualifier", "Perms");
printf("%-6s %-14s %-10s %s\n", "----", "---", "---------", "-----");
acl_entry_t entry;
acl_tag_t tag;
acl_permset_t perms;
int id = ACL_FIRST_ENTRY;
while (acl_get_entry(acl, id, &entry) == 1) {
id = ACL_NEXT_ENTRY;
acl_get_tag_type(entry, &tag);
acl_get_permset(entry, &perms);
/* Tag type abbreviation */
const char *abbrev = (tag == ACL_USER_OBJ || tag == ACL_USER) ? "u" :
(tag == ACL_GROUP_OBJ || tag == ACL_GROUP) ? "g" :
(tag == ACL_MASK) ? "m" : "o";
/* Full name */
const char *full = (tag == ACL_USER_OBJ) ? "user (owner)" :
(tag == ACL_USER) ? "user (named)" :
(tag == ACL_GROUP_OBJ) ? "group (owner)" :
(tag == ACL_GROUP) ? "group (named)" :
(tag == ACL_MASK) ? "mask" : "other";
printf("%-6s %-14s ", abbrev, full);
if (tag == ACL_USER) {
uid_t *uid = acl_get_qualifier(entry);
printf("uid=%-6d ", (int)*uid);
acl_free(uid);
} else if (tag == ACL_GROUP) {
gid_t *gid = acl_get_qualifier(entry);
printf("gid=%-6d ", (int)*gid);
acl_free(gid);
} else {
printf("%-10s ", "-");
}
print_permset(perms);
printf("\n");
}
acl_free(acl);
return 0;
}
/* Compile: gcc -o parse_acl parse_acl.c -lacl */
Interview Questions
Q1. What are the two text formats for ACL entries and when is each used?
Long text form: One entry per line, supports comments (with #). Used by getfacl output and setfacl -M acl-file (read from file).
Short text form: All entries on one comma-separated line. Used with setfacl -m on command line and in C programs with acl_from_text()/acl_to_text().
Q2. How do you distinguish ACL_USER_OBJ from ACL_USER in text form?
user::rw- (empty qualifier between second and third colons) = ACL_USER_OBJ (file owner). user:bob:rw- (qualifier present) = ACL_USER for named user bob. Same logic applies to group:: (ACL_GROUP_OBJ) vs group:name: (ACL_GROUP).Q3. What C functions convert between in-memory ACL and text form?
acl_to_text(acl, &len) converts in-memory acl_t to a long-text-form string. acl_from_text(string) converts a long or short text form string to an in-memory acl_t. Both allocate memory that must be freed with acl_free().Q4. Write a setfacl command to give user ‘ravi’ read+execute, group ‘qa’ read-only, and deny everyone else.
setfacl -m "u:ravi:r-x,g:qa:r--,m::r-x,o::---" filename
Note: ACL_MASK must also be set (either explicitly or let setfacl auto-calculate). The owner’s existing entry is not changed by this command — only the named entries.
Q5. How do you back up and restore ACLs for an entire directory tree?
# Backup
getfacl -R /myproject/ > acl_backup.txt
# Restore (run from / to use correct relative paths)
cd /
setfacl --restore=acl_backup.txt
Understand how ACL_MASK interacts with chmod and protects ACL data
