# POSIX.1e Access Control Lists (acl 2.3.2): getfacl, setfacl, chacl over libacl. # # ACLs are STORED in the system.posix_acl_access / _default extended attributes, # and this kernel implements no xattr syscall at all -- the whole family answers # ENOSYS (38), measured for BRINTOS-231 and filed as brintos/linux#27. That does # NOT make the tools useless here, and the split is the point of this suite: # # getfacl falls back to acl_get_file_mode() on ENOSYS (getfacl.c:471) and is # fully correct -- it prints the ACL the mode bits imply. # setfacl does the same for the read half, and for the write half checks # acl_equiv_mode(): an ACL equivalent to a mode is applied with # chmod() instead (do_set.c:476-489), so every MINIMAL setfacl works. # --test computes any ACL, extended ones included, without touching # the filesystem, so the whole ACL algebra is exercisable on target. # chacl carries no ENOSYS fallback at all and therefore cannot read an ACL. # # So only an EXTENDED ACL -- a named user or group, or a mask -- has nowhere to # be stored. The three cases that need that storage are left RED and attributed # to brintos/linux#27 rather than skipped: they are the standing regression probe # that flips green by itself the day the kernel answers those syscalls. . "$BST_LIB" require getfacl acl/present require setfacl acl/setfacl-present # Substring assertions against an ALREADY-CAPTURED output. Every case below # reuses one capture per command: the runner's budget here is dominated by # process count, not by work (56-lz4.test was timed out by ~80 forked greps), # and `case` costs no fork at all. has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "want '$2' in: $(_bst_1line "$3")" ;; esac; } hasnt() { case "$3" in *"$2"*) bst_notok "$1" "unexpected '$2' in: $(_bst_1line "$3")" ;; *) bst_ok "$1" ;; esac; } nonzero() { if [ "$2" -ne 0 ]; then bst_ok "$1"; else bst_notok "$1" "expected a non-zero exit, got 0"; fi; } d=${TMPDIR:-/tmp}/bst-acl.$$ mkdir -p "$d/sub" 2>/dev/null || { bst_notok acl/stage "cannot mkdir $d"; exit 0; } f=$d/f printf 'acl\n' > "$f" 2>/dev/null || { bst_notok acl/stage "cannot write $f"; rm -rf "$d"; exit 0; } chmod 0644 "$f" 2>/dev/null chmod 0755 "$d" 2>/dev/null # --- the tools are the ones the recipe built --------------------------------- out=$(getfacl --version 2>&1); has acl/getfacl-version "2.3.2" "$out" out=$(setfacl --version 2>&1); has acl/setfacl-version "2.3.2" "$out" check acl/chacl-present command -v chacl # --- getfacl: the mode-derived base ACL, which is the ENOSYS fallback path ---- # 0644 must read back as exactly the three base entries. If the xattr syscalls # were merely failing UNTOLERATED, these would error out instead. out=$(getfacl -c "$f" 2>&1) has acl/base-user "user::rw-" "$out" has acl/base-group "group::r--" "$out" has acl/base-other "other::r--" "$out" hasnt acl/base-no-mask "mask::" "$out" # The fallback tracks the mode rather than caching it: chmod, and read again. chmod 0750 "$f" 2>/dev/null out=$(getfacl -c "$f" 2>&1) has acl/mode-user "user::rwx" "$out" has acl/mode-group "group::r-x" "$out" has acl/mode-other "other::---" "$out" # The header block is getfacl's own formatting, unrelated to storage. out=$(getfacl "$f" 2>&1) has acl/header-file "# file:" "$out" has acl/header-owner "# owner:" "$out" has acl/header-group "# group:" "$out" out=$(getfacl -c -n "$f" 2>&1); has acl/numeric "user::rwx" "$out" out=$(getfacl -c "$d" 2>&1); has acl/dir-base "user::rwx" "$out" # -d asks for the DEFAULT ACL, which lives in a second xattr. getfacl tolerates # ENOSYS there too (getfacl.c:478) -- so this must exit 0 and print nothing, # NOT fail. An empty answer is the correct answer for a kernel with no store. # -p is REQUIRED here and is not cosmetic: without it getfacl strips the leading # '/' from an absolute path and says so on stderr, so "prints nothing" is false # against a perfectly correct tool. That cost this suite a red on its first run. out=$(getfacl -p -c -d "$d" 2>&1); rc=$? if [ "$rc" -eq 0 ] && [ -z "$out" ]; then bst_ok acl/default-tolerated else bst_notok acl/default-tolerated "rc=$rc $(_bst_1line "$out")"; fi # -s prints nothing for a file that has only base entries. out=$(getfacl -s "$f" 2>&1); rc=$? if [ "$rc" -eq 0 ] && [ -z "$out" ]; then bst_ok acl/skip-base else bst_notok acl/skip-base "rc=$rc $(_bst_1line "$out")"; fi # -R walks the tree; the needles carry the temp dir's own name so they cannot be # satisfied by getfacl's "# file:" header text alone. b=${d##*/} out=$(getfacl -R "$d" 2>&1) has acl/recursive-file "$b/f" "$out" has acl/recursive-dir "$b/sub" "$out" out=$(getfacl "$d/definitely-absent" 2>&1); rc=$? nonzero acl/missing-rc "$rc" has acl/missing-msg "No such file" "$out" # --- setfacl: a MINIMAL ACL is applied through the chmod() fallback ---------- # This is do_set.c's ENOSYS branch doing real work: acl_set_file fails, the ACL # is equivalent to a mode, so chmod carries it. The permissions really change. chmod 0644 "$f" 2>/dev/null check acl/set-minimal setfacl -m u::rwx,g::r-x,o::--- "$f" out=$(getfacl -c "$f" 2>&1) has acl/set-minimal-user "user::rwx" "$out" has acl/set-minimal-group "group::r-x" "$out" has acl/set-minimal-other "other::---" "$out" # ...and it is the FILE MODE that changed, not a private ACL record: ls agrees. out=$(ls -l "$f" 2>&1); has acl/set-minimal-mode "rwxr-x---" "$out" check acl/set-full setfacl --set u::rw-,g::r--,o::r-- "$f" out=$(ls -l "$f" 2>&1); has acl/set-full-mode "rw-r--r--" "$out" # -b removes extended entries; with none present it reduces to the mode path. check acl/remove-all setfacl -b "$f" # --- the ACL algebra, computed on target without touching the filesystem ----- # --test prints the ACL it WOULD write. Named entries, and the mask libacl # recalculates for them, are exercised here and are storage-independent. out=$(setfacl --test -m u:1000:rwx "$f" 2>&1) has acl/test-named "u:1000:rwx" "$out" has acl/test-mask "m::rwx" "$out" has acl/test-base "u::rw-" "$out" out=$(setfacl --test --set u::rw-,g::r--,o::---,u:500:rw-,m::rw- "$f" 2>&1) has acl/test-set-named "u:500:rw-" "$out" has acl/test-set-mask "m::rw-" "$out" has acl/test-set-other "o::---" "$out" out=$(setfacl --test -d -m u:501:rwx "$d" 2>&1) has acl/test-default "501" "$out" # Bad ACL text must be rejected by the parser, not silently accepted. out=$(setfacl --test -m 'this-is-not-an-acl' "$f" 2>&1); rc=$? nonzero acl/bad-text-rc "$rc" # --- the storage gap: RED on purpose, attributed to brintos/linux#27 --------- # An extended ACL is not equivalent to any mode, so there is no chmod fallback # and it needs the xattr the kernel does not implement. These three flip green # on their own once brintos/linux#27 lands; do NOT downgrade them to skip. check acl/extended-store setfacl -m u:1000:rwx "$f" out=$(getfacl -p -c -n "$f" 2>&1) has acl/extended-readback "user:1000:rwx" "$out" check acl/chacl-list chacl -l "$f" rm -rf "$d"