# Extended-attribute utilities (attr 2.5.1): getfattr, setfattr and attr. # # THIS SUITE IS MOSTLY RED ON PURPOSE, AND THAT IS THE POINT OF THE PORT. # # attr is the tool family whose ENTIRE data model is the extended-attribute # syscalls, and this kernel implements none of them: getxattr/setxattr/listxattr # /removexattr and every l*/f* variant answer ENOSYS (38), measured on target for # BRINTOS-231 and filed as brintos/linux#27. Unlike acl -- which reaches the same # missing store but carries a mode-derived fallback in getfacl and a chmod() # fallback in setfacl (99-acl.test) -- attr has NO tolerance anywhere. Every # xattr call site in tools/getfattr.c and tools/setfattr.c reports strerror_ea() # and bumps had_errors, so the exit status is non-zero and nothing is printed. # # So the suite is deliberately split in two, and the split is the deliverable: # # 1. THE PORT ITSELF, which is storage-independent and must be green. The three # binaries were built by the wasm32-hwjs toolchain and they RUN: they print # their version, parse their whole option grammar, reject bad usage with the # documented exit status, and setfattr --restore rejects a malformed dump # from its parser before it ever reaches a syscall. That is a real claim # about this arm and it is what this ticket delivers today. # # 2. THE XATTR STORE, which is red and attributed to brintos/linux#27. These # cases are left FAILING rather than skipped, exactly as 99-acl.test leaves # its extended-ACL cases: they are the standing regression probe that flips # green by itself the day the kernel answers those syscalls. Every one of # them was validated against a NATIVE attr 2.5.1 build on an ext4 host, # where all of them pass -- so a red here is the kernel, not the assertion. # # Two of the red cases assert a MESSAGE rather than only an exit status, and that # is deliberate: `setfattr -x` on an absent attribute and `attr -g` on an absent # one both exit non-zero on a working kernel too, so a status-only assertion # would go GREEN here for entirely the wrong reason. Asserting the text keeps # them honest. The same effect is worth seeing once directly, which is what # attr/missing-file-msg is for: the gap masks the tool's own error reporting, so # on this target a caller cannot tell "no such file" from "no xattr support". # # Paths are RELATIVE throughout (the suite cd's into its scratch directory). # getfattr strips a leading '/' from an absolute path and says so on stderr, # which is a warning against a perfectly correct tool -- it cost 99-acl.test a # red on its first run, and staying relative avoids the whole class rather than # papering over it with --absolute-names. . "$BST_LIB" require getfattr attr/present require setfattr attr/setfattr-present # Substring assertions against an ALREADY-CAPTURED output: one capture per # command, and `case` forks nothing. The runner's 60s per-suite budget here is # dominated by process count rather than by work (~80 forked greps timed out # 56-lz4.test), so no case below spawns a grep. has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "want '$2' in: $(_bst_1line "$3")" ;; esac; } rc_is() { if [ "$2" = "$3" ]; then bst_ok "$1"; else bst_notok "$1" "rc=$3 want=$2"; fi; } # ok iff the command exits 0 AND printed nothing -- the correct answer for a # file that simply has no extended attributes. silent0() { if [ "$2" -eq 0 ] && [ -z "$3" ]; then bst_ok "$1" else bst_notok "$1" "rc=$2 out: $(_bst_1line "$3")"; fi } d=${TMPDIR:-/tmp}/bst-attr.$$ mkdir -p "$d/sub" 2>/dev/null || { bst_notok attr/stage "cannot mkdir $d"; exit 0; } cd "$d" 2>/dev/null || { bst_notok attr/stage "cannot cd $d"; rm -rf "$d"; exit 0; } printf 'attr\n' > f 2>/dev/null || { bst_notok attr/stage "cannot write f"; cd /; rm -rf "$d"; exit 0; } printf 'sub\n' > sub/h 2>/dev/null # ============================================================================= # 1. The port: the binaries this recipe built, running on wasm32-hwjs. # ============================================================================= out=$(getfattr --version 2>&1); has attr/getfattr-version "getfattr 2.5.1" "$out" out=$(setfattr --version 2>&1); has attr/setfattr-version "setfattr 2.5.1" "$out" check attr/attr-present command -v attr out=$(getfattr --help 2>&1); has attr/getfattr-help "get extended attributes" "$out" out=$(setfattr --help 2>&1); has attr/setfattr-help "set extended attributes" "$out" # `attr` with no operand: usage on stderr and exit 1 (not 0, and not 2 -- attr # has its own convention, and getfattr/setfattr's getopt exit 2 is a different # path). Asserting both halves keeps the two conventions apart. out=$(attr 2>&1); rc=$? has attr/attr-usage "Usage: attr" "$out" rc_is attr/attr-usage-rc 1 "$rc" # The option grammar is parsed entirely before any syscall, so every one of # these is a true statement about the port on a kernel with no xattr store. getfattr --definitely-not-an-option f >/dev/null 2>&1; rc_is attr/getfattr-badopt-rc 2 "$?" setfattr --definitely-not-an-option f >/dev/null 2>&1; rc_is attr/setfattr-badopt-rc 2 "$?" getfattr >/dev/null 2>&1; rc_is attr/getfattr-nooperand-rc 2 "$?" setfattr -n user.x >/dev/null 2>&1; rc_is attr/setfattr-nofile-rc 2 "$?" setfattr -v val f >/dev/null 2>&1; rc_is attr/setfattr-value-without-name-rc 2 "$?" getfattr -e bogus f >/dev/null 2>&1; rc_is attr/getfattr-bad-encoding-rc 2 "$?" # --restore parses the dump before it touches the filesystem, so a malformed # dump is rejected by attr's own parser and not by the missing syscall. This is # the one non-trivial code path in setfattr that runs to completion here. out=$(printf 'not-a-dump-line\n' | setfattr --restore=- 2>&1); rc=$? has attr/restore-malformed "No filename found" "$out" rc_is attr/restore-malformed-rc 1 "$rc" # update-alternatives: the recipe installs the binary as setfattr.attr at # priority 100 and the postinst links /usr/bin/setfattr to it. Both halves are # asserted because they fail differently -- a missing .attr means the package did # not install, a missing link means run-postinsts did not run. Nothing else on # this image claims the name (CONFIG_SETFATTR is unset in the busybox defconfig # and toybox has no applet), so there is no competing alternative. check attr/alt-target test -e /usr/bin/setfattr.attr check attr/alt-link test -e /usr/bin/setfattr # The missing syscall does NOT swallow the tool's own path diagnostics, which is # worth one green case because the opposite was the reasonable prediction: # getfattr stats each path as it walks it, so a name that does not exist fails at # the stat and is reported as such, and only paths that DO exist get as far as # listxattr and ENOSYS. A caller can still tell a typo from the platform gap. out=$(getfattr definitely-absent 2>&1); has attr/missing-file-msg "No such file or directory" "$out" # ============================================================================= # 2. The xattr store: RED, attributed to brintos/linux#27. # Do NOT downgrade these to skip -- they are the regression probe. # ============================================================================= # A file with no attributes must LIST CLEANLY: exit 0, print nothing. On a # kernel that answers the syscall this is silent success; here listxattr returns # ENOSYS and each tool reports it, so even the empty case fails. out=$(getfattr f 2>&1); silent0 attr/list-empty "$?" "$out" out=$(getfattr -d f 2>&1); silent0 attr/dump-empty "$?" "$out" out=$(attr -l f 2>&1); silent0 attr/attr-list-empty "$?" "$out" check attr/set setfattr -n user.greeting -v hello f setfattr -n user.bin -v 0sYmluYXJ5 f 2>/dev/null out=$(getfattr -d f 2>&1); has attr/get-dump 'user.greeting="hello"' "$out" out=$(getfattr -n user.greeting f 2>&1); has attr/get-named 'user.greeting="hello"' "$out" out=$(getfattr --only-values -n user.greeting f 2>&1); has attr/only-values "hello" "$out" # The value is stored as bytes; -e chooses how they are rendered back. "binary" # is 0x62696e617279 and 0sYmluYXJ5, so hex and base64 are two views of one # round trip rather than two separate claims. out=$(getfattr -e hex -n user.bin f 2>&1); has attr/encode-hex "0x62696e617279" "$out" out=$(getfattr -e base64 -n user.bin f 2>&1); has attr/encode-base64 "0sYmluYXJ5" "$out" # Name-only listing and the --match regex, which filters the listxattr result. out=$(getfattr -m 'user\.gre' f 2>&1); has attr/match "user.greeting" "$out" out=$(getfattr -m - f 2>&1) has attr/list-names-greeting "user.greeting" "$out" has attr/list-names-bin "user.bin" "$out" # -R walks the tree. The needle is the VALUE, not the path: `sub/h` would also # appear in "getfattr: ./sub/h: Function not implemented", so a path needle makes # this case pass on the very kernel it is meant to probe. It did exactly that on # the first target run -- a green among twenty reds, proving nothing. setfattr -n user.r -v 1 sub/h 2>/dev/null out=$(getfattr -R -d . 2>&1); has attr/recursive 'user.r="1"' "$out" # dump -> restore is the pair that makes attributes portable between files. The # restore input is written BY HAND rather than captured from getfattr, for the # same reason: where the store is missing `getfattr -d` writes an EMPTY dump, and # `setfattr --restore` of an empty file has nothing to do and exits 0 -- a second # false green on the first target run. A literal dump makes this a real store # test, and it also keeps the case independent of the read half. printf 'attr\n' > g printf '# file: g\nuser.greeting="hello"\n\n' > dump.txt check attr/restore setfattr --restore=dump.txt out=$(getfattr -d g 2>&1); has attr/restore-readback 'user.greeting="hello"' "$out" check attr/remove setfattr -x user.greeting f # Both of these exit non-zero on a WORKING kernel too, so the message is the # assertion -- a status-only check would go green here for the wrong reason. out=$(setfattr -x user.nosuch f 2>&1); has attr/remove-absent-msg "No such attribute" "$out" out=$(getfattr -n user.nosuch f 2>&1); has attr/get-absent-msg "No such attribute" "$out" # The attr(1) tool is a third front end over the same syscalls, with its own # output grammar -- it reports the byte count rather than echoing an assignment. out=$(attr -s myattr -V myvalue f 2>&1); has attr/tool-set "set to a 7 byte value" "$out" out=$(attr -g myattr f 2>&1); has attr/tool-get "myvalue" "$out" out=$(attr -l f 2>&1); has attr/tool-list "myattr" "$out" check attr/tool-remove attr -r myattr f out=$(attr -g nosuch f 2>&1); has attr/tool-get-absent-msg "No data available" "$out" cd / rm -rf "$d"