. "$BST_LIB" # BST-TIMEOUT: 420 acpid 2.0.34 -- the ACPI event daemon (poky # recipes-bsp/acpid). What ships is /usr/sbin/acpid, /usr/bin/acpi_listen, # /usr/sbin/kacpimon, the sysvinit script /etc/init.d/acpid, the empty rule # directory /etc/acpi/events, three man pages and the upstream docs. No systemd # unit: "systemd" is not in DISTRO_FEATURES here, so systemd.bbclass drops what # the recipe's do_install:append wrote. # # THIS MACHINE HAS NO ACPI, AND THAT IS THE SUBJECT RATHER THAN AN OBSTACLE. # The guest is a wasm32 kernel under HardwareJS: there is no firmware, no # /proc/acpi, and no acpi_event generic-netlink family for the daemon's # multicast group to live in. Every event acpid could ever deliver here is one # this suite feeds it. # # WHAT MAKES THAT POSSIBLE IS -e, AND IT IS NOT A TEST HOOK. `--eventfile' is # the daemon's own documented switch for the deprecated /proc/acpi/event # interface, and open_proc() opens whatever path it is given (proc.c:87). Point # it at a regular file and the whole pipeline runs for real -- select(2) reports # the fd ready, process_proc() reads a line, acpid_handle_event() walks the drop, # client and command rule lists, do_cmd_rule() forks /bin/sh -c on a match, and # at EOF read(2) returns 0, read_line() reports EPIPE and acpid logs "events file # connection closed" and exits EXIT_FAILURE. So the daemon TERMINATES, on its own # documented path, having done everything it exists to do. That is the shape of # almost every case below, and it is why this port needs no interactive follow-on. # # THE FIRST RUN OF THIS SUITE WAS ON 2026-09-01, on the batch-1949 image, and # three of its cases had been written from the source rather than measured. Two # of them were asserting stdout and stderr writes that upstream loses before # they reach a file, and the third was asserting that a LOCKED run ends, which # proc.c's ordering says it cannot. Each is now answered where it is made -- # sections 5 and 6 -- and the shape they share is worth naming once here: a # green in this suite is acpid doing something, never acpid saying it did. # # NOTHING IS KILLED. kill(2) reports success and delivers nothing on this image, # SIGKILL included (brintos/linux#21), so a run that does not end by itself # cannot be stopped. Every acpid started here therefore either ends on the EOF # path above or is bounded by bst_probe -- and the ONE run that is a daemon in # the ordinary sense (netlink mode, last section) is started in the background, # polled for the state it should reach, and then left alone, exactly as # 88-ptpd.test leaves its ptpd2. It sits blocked in select(2); it does not spin. # # kacpimon IS SHIPPED AND IS DELIBERATELY NOT RUN. Its main() takes no options # at all (kacpimon/kacpimon.c:188) and monitors until Escape arrives on stdin, # so there is no bounded invocation of it -- not even --version. Presence, mode # and its man page are asserted; behaviour is not, and claiming otherwise would # be the only way this suite could hang the guest. # # ON TARGET: no payload in data/ -- every fixture below is written here, because # the point of each one is the exact text acpid complains about and a fixture # read from elsewhere would put that text one file further from the assertion. require /usr/sbin/acpid acpid/present A=/usr/sbin/acpid L=/usr/bin/acpi_listen K=/usr/sbin/kacpimon W=${TMPDIR:-/tmp}/bst-acpid.$$ rm -rf "$W"; mkdir -p "$W" || { bst_notok acpid/workdir "cannot create $W"; exit 0; } R=$W/rules # saw ... : ok iff EVERY needle is a substring of . # grep -F, not a case glob: the needles carry '[', '(' and '*' from acpid's own # diagnostics, and every one of them would be a pattern to a case statement. saw() { _id=$1; _f=$2; shift 2 _miss= for _n in "$@"; do grep -qF -- "$_n" "$_f" 2>/dev/null || _miss="$_miss [$_n]" done if [ -n "$_miss" ]; then bst_notok "$_id" "absent from ${_f##*/}:$(_bst_1line "$_miss")" else bst_ok "$_id" fi } # say : ok iff printed , WHATEVER its exit # status. Every refusal below is a correct refusal that exits non-zero, so # check_out -- which also requires rc=0 -- would turn each one red. The status is # asserted separately, and only where acpid documents it. say() { _id=$1; _needle=$2; shift 2 _o=$("$@" 2>&1) if bst_execfail "$_o"; then bst_unrun "$_id" "$_o" else case "$_o" in *"$_needle"*) bst_ok "$_id" ;; *) bst_notok "$_id" "want:'$_needle' got:'$(_bst_1line "$_o")'" ;; esac fi } # --------------------------------------------------- 1. the three programs --- # -v returns from handle_cmdline() before any file is touched, so this separates # "the recipe built 2.0.34" from "something else answers to acpid". Both # programs print PACKAGE "-" VERSION, so acpi_listen also says "acpid-2.0.34" -- # that is upstream's string, not a copy-paste here. check_out acpid/version "acpid-2.0.34" "$A" -v check_out acpi-listen/version "acpid-2.0.34" "$L" -v # --help EXITS 1. acpid's 'h' case falls through to the same block as an # unrecognised option and ends exit(EXIT_FAILURE) (acpid.c:299-314), so the # status is part of the interface and is asserted with the text, not instead of # it. acpi_listen's -h is the opposite -- usage(stdout) then EXIT_SUCCESS -- and # the pair is what shows each was measured rather than assumed. say acpid/help-usage "Usage: acpid [OPTIONS]" "$A" -h say acpid/help-lists-confdir "-c, --confdir" "$A" -h say acpid/help-lists-eventfile "-e, --eventfile" "$A" -h say acpid/help-lists-netlink "-n, --netlink" "$A" -h say acpid/help-lists-lockfile "-L, --lockfile" "$A" -h check_rc acpid/help-status 1 "$A" -h say acpid/bad-option-usage "Usage: acpid [OPTIONS]" "$A" -Z check_rc acpid/bad-option-status 1 "$A" -Z check_out acpi-listen/help-usage "Usage: acpi_listen [OPTIONS]" "$L" -h check_out acpi-listen/help-lists-socketfile "-s, --socketfile" "$L" -h say acpi-listen/bad-option-usage "Usage: acpi_listen [OPTIONS]" "$L" -Z check_rc acpi-listen/bad-option-status 1 "$L" -Z # kacpimon: shipped, executable, and not invoked. See the header. check acpid/kacpimon-present sh -c "[ -x $K ]" check acpid/kacpimon-man-present sh -c '[ -f /usr/share/man/man8/kacpimon.8 ]' # ------------------------------------ 2. the machine really has no ACPI ------ # The premise every case below rests on, asserted rather than assumed: if any of # these three appeared, the daemon would have a real event source and the # "absence" this port is scoped around would no longer be the thing measured. check acpid/no-proc-acpi sh -c '[ ! -e /proc/acpi ]' check acpid/no-proc-event sh -c '[ ! -e /proc/acpi/event ]' check acpid/no-sys-firmware sh -c '[ ! -e /sys/firmware/acpi ]' # ---------------------------------------- 3. the bounded refusals ----------- # Each of these ends the process, and each one names the resource it could not # have. -e points at a file that DOES exist in every case here so that open_proc # succeeds and the netlink path is never entered: what is under test is the # refusal that follows, not how this kernel answers a generic-netlink probe. : > "$W/empty.events" # opendir() on the rule directory, acpid_read_conf's first act (event.c:107). say acpid/missing-confdir-msg "No such file or directory" \ "$A" -f -S -e "$W/empty.events" -c "$W/no-such-dir" say acpid/missing-confdir-names-dir "opendir(" \ "$A" -f -S -e "$W/empty.events" -c "$W/no-such-dir" check_rc acpid/missing-confdir-status 1 \ "$A" -f -S -e "$W/empty.events" -c "$W/no-such-dir" # ud_create_socket() on a path whose directory does not exist. open_sock() is # the one opener in main() that exits rather than logging and continuing # (sock.c:128), which is why it is asserted and open_netlink's failure is not. mkdir -p "$R" say acpid/bad-socket-path-msg "can't open socket" \ "$A" -f -e "$W/empty.events" -c "$R" -s "$W/no-such-dir/acpid.socket" check_rc acpid/bad-socket-path-status 1 \ "$A" -f -e "$W/empty.events" -c "$R" -s "$W/no-such-dir/acpid.socket" # acpi_listen against a socket that is not there. THIS IS THE CASE THE TICKET IS # ABOUT for the client half: the tool reports the absence and exits, rather than # waiting on a socket no daemon on this machine will ever create. say acpi-listen/absent-socket-msg "can't open socket" \ "$L" -s "$W/no-such.socket" say acpi-listen/absent-socket-names-path "$W/no-such.socket" \ "$L" -s "$W/no-such.socket" say acpi-listen/absent-socket-errno "No such file or directory" \ "$L" -s "$W/no-such.socket" check_rc acpi-listen/absent-socket-status 1 "$L" -s "$W/no-such.socket" # ---------------------------------------- 4. the rule fixtures -------------- # Eight entries in one directory, each shaped to produce exactly one of # acpid_read_conf's and parse_file's documented outcomes. printf '%s\n' is used # throughout: an action carries a literal %e, which any format-string spelling # would consume. # # THE DROP RULE USES -r, NOT THE DEFAULT. acpid's built-in pseudo-action is the # literal DROP_ACTION (event.h:26), and its first character is '<' -- which # check-suite-redirects.py flags anywhere in this file, quoted or not, as a # top-level stdin redirect. `--dropaction' exists precisely so the token can be # chosen (acpid.c:65,289), so the runs below pass `-r BST-DROP' and the fixture # names that. It covers one more option than the default would have. FIRED=$W/fired-good printf '%s\n' '# a comment, and the blank line under it, are both ignored' \ '' \ 'event=button/power.*' \ "action=echo %e > $FIRED" > "$R/good" printf '%s\n' 'event=button/sleep.*' \ 'action=BST-DROP' > "$R/dropper" printf '%s\n' 'event=lid/never-fires.*' \ 'action=echo nomatch' > "$R/nomatch" printf '%s\n' 'this line has no equals sign' \ 'event=zzz/never-fires-malformed.*' \ 'action=echo malformed' > "$R/malformed" printf '%s\n' 'bogus=1' \ 'event=zzz/never-fires-unknown.*' \ 'action=echo unknown' > "$R/unknownkey" printf '%s\n' 'event=zzz/never-fires-incomplete.*' > "$R/incomplete" printf '%s\n' 'event=[unclosed' \ 'action=echo badregex' > "$R/badregex" printf '%s\n' 'event=zzz/never-fires-skipped.*' \ 'action=echo skipped' > "$R/skipme.conf" mkdir -p "$R/subdir" # Three events, in the order acpid will read them. Line 1 matches `good' and # fires its action; line 2 matches `dropper' and is dropped before any command # rule is reached; line 3 matches nothing at all. printf '%s\n' 'button/power PWRF 00000080 00000001' \ 'button/sleep SLPB 00000080 00000002' \ 'battery BAT0 00000080 00000001' > "$W/events" # ------------------------- 5. one run, the whole engine, and it ends by itself # -ddd is what turns on the key/value readback (parse_file logs it at # debug_level >= 3, event.c:315) and the "did not match" line; -l is logevents, # without which nothing about a matched rule is logged at all. -S keeps the run # off a UNIX socket, which is asserted separately below. # # The wrapper always exits 0, so bst_probe's own case means exactly one thing: # THE RUN TERMINATED INSIDE THE BOUND. acpid's status is captured beside it and # asserted on its own. rm -f "$W/run.status" "$FIRED" bst_probe acpid/eventfile-run-terminates "$W/run" 120 \ sh -c "$A -f -d -d -d -l -S -r BST-DROP -c '$R' -e '$W/events' -L '$W/lock' -p '$W/pid'; echo \$? > '$W/run.status'" ran=$? if [ "$ran" -ne 0 ] || [ ! -s "$W/run.status" ]; then bst_skip acpid/eventfile-cases "the bounded run produced no status; its own case above carries the reason" else E=$W/run.err O=$W/run # WHICH INTERFACE IT CAME UP ON. "proc fs" is the whole point of -e: it says # open_proc() succeeded on the file we named, so nothing below is being # measured through netlink by accident. saw acpid/startup-proc-fs "$E" 'starting up with proc fs' saw acpid/waiting-for-events "$E" 'waiting for events: event logging is on' # THE RULE DIRECTORY, file by file. Five rules load; three files are refused, # each for its own documented reason. saw acpid/rules-loaded "$E" '5 rules loaded' saw acpid/parse-announced "$E" "parsing conf file $R/good" saw acpid/readback-event "$E" 'key="event" val="button/power.*"' saw acpid/readback-action "$E" "key=\"action\" val=\"echo %e > $FIRED\"" saw acpid/malformed-line "$E" "can't parse $R/malformed at line 1" saw acpid/unknown-option "$E" "unknown option 'bogus' in $R/unknownkey at line 1" saw acpid/incomplete-file "$E" "skipping incomplete file $R/incomplete" saw acpid/bad-regex "$E" 'regcomp():' saw acpid/non-conforming-name "$E" "skipping conf file $R/skipme.conf" saw acpid/non-file "$E" "skipping non-file $R/subdir" # THE EVENT PIPELINE. Read, matched, expanded, executed, reaped -- each step # named by the line only that step produces. saw acpid/event-read "$E" 'procfs received event "button/power PWRF 00000080 00000001"' saw acpid/rule-matched "$E" "rule from $R/good matched" saw acpid/rule-not-matched "$E" "rule from $R/nomatch did not match" saw acpid/escape-expanded "$E" \ "expanded \"echo %e > $FIRED\" -> \"echo button/power PWRF 00000080 00000001 > $FIRED\"" saw acpid/action-executed "$E" \ "executing action \"echo button/power PWRF 00000080 00000001 > $FIRED\"" saw acpid/action-status "$E" 'action exited with status 0' saw acpid/one-rule-matched "$E" '1 total rule matched' saw acpid/event-completed "$E" 'procfs completed event "button/power PWRF 00000080 00000001"' # THE DROP RULE. drop_list is walked before the command list, so a dropped # event ends rule processing -- the two lines together are what say the drop # happened rather than merely that nothing matched. saw acpid/drop-rule-matched "$E" "rule from $R/dropper matched" saw acpid/event-dropped "$E" 'event dropped' # AN EVENT NOBODY WANTS. The third line matches no rule in any list. saw acpid/no-rule-matched "$E" '0 total rules matched' # THE HANDLER'S OWN STREAM. do_cmd_rule brackets the child's output on STDOUT # (event.c:687,699) while everything above is on stderr, so this proves the # fork/wait really happened rather than being inferred from a log line the # parent wrote. # # ONLY THE CLOSING LINE IS ASSERTABLE, and that is upstream stdio rather than # anything about this port. "BEGIN HANDLER MESSAGES" is written by the CHILD # with fprintf(stdout) and is followed immediately by execl (event.c:687,690) # with no fflush between them, so on any stdout that is not line-buffered -- # which is every stdout redirected to a file, here and on x86_64 alike -- the # buffer dies with the exec'd image and the line is never written. The closing # line is the parent's, and reaches the file when the parent exits. Measured # 2026-09-01: END present, BEGIN absent, which is exactly this shape. Same # class of finding as BRINTOS-247's daemonize -v. saw acpid/handler-messages "$O" 'END HANDLER MESSAGES' # WHAT THE ACTION ACTUALLY DID. The decisive case of the suite: a file on the # filesystem, written by /bin/sh under acpid, carrying the event text that # %e expanded to. Everything else here is acpid describing itself. check acpid/action-side-effect sh -c "[ -f '$FIRED' ]" check_eq acpid/action-wrote-event "button/power PWRF 00000080 00000001" \ "$(cat "$FIRED" 2>/dev/null)" # HOW THE RUN ENDED. EOF on the event file is read(2) returning 0, which # read_line reports as EPIPE and process_proc turns into a log line and # EXIT_FAILURE (proc.c:73). A daemon that ends deliberately is the entire # reason this port is non-interactive. # # THE STATUS IS THE ASSERTION, NOT THE MESSAGE. Every exit(EXIT_FAILURE) # acpid can still reach once "waiting for events" has been logged is this one # -- the others are all in startup, in netlink/input openers this run does not # use, or in acpi_listen -- so a status of 1 here names the EPIPE branch and # nothing else. Its log line is a different matter: measured 2026-09-01 the # capture carried every stderr line up to and including "0 total rules # matched" and not the one written immediately before exit(). The loss is # confined to that final write and could not be narrowed further from the # host, so the line is recorded as an observation rather than asserted -- a # case that cannot distinguish acpid misbehaving from the write being dropped # is not evidence about acpid. bst_skip acpid/eof-message "the final stderr write before exit() was absent from the capture on 2026-09-01 while every earlier line was present; the EOF path is asserted by acpid/eof-status, which is reachable from no other branch" read -r rc1 < "$W/run.status" check_eq acpid/eof-status 1 "$rc1" # -S HELD. No socket was created anywhere the run could have put one. check acpid/nosocket-honoured sh -c "[ ! -e '$W/acpid.socket' ]" fi # --------------------------- 6. the lockfile, and why it is not run here ----- # `-L' names the file whose mere EXISTENCE stops event processing (locked(), # acpid.c:492), and the obvious case -- the same fixtures and events again with # the lockfile in place -- CANNOT BE WRITTEN ON THE -e PATH. process_proc reads # a line and then tests locked() BEFORE it tests the EOF that read_line reports # as EPIPE (proc.c:46-56), so a locked run returns from the EOF without ever # reaching the exit(EXIT_FAILURE) that ends the runs above. What it does instead # is worse than not terminating: select(2) reports an EOF'd regular file ready # every time, so acpid spins on it at full tilt for as long as it is left alive. # # Measured 2026-09-01 on the batch-1949 image: acpid/locked-run-terminates ran # to its 120s bound and was killed there, having produced nothing, and 120s of # that boot went to a busy loop shared with every other suite on the image. The # case was asserting an outcome upstream cannot produce, and charging the whole # batch for the attempt. # # The option is covered where it can be covered without a run that cannot end: # acpid/help-lists-lockfile in section 1. Exercising locked() at runtime needs # an event source that does not sit at EOF -- a FIFO with its writer held open # -- and that run could not be ended either, so it would have to join section # 8's daemon as a second process the suite cannot stop. It is not worth a second # unkillable daemon to reach one log line. # ------------------------------------------------- 7. packaging -------------- check acpid/file-daemon sh -c "[ -x $A ]" check acpid/file-listen sh -c "[ -x $L ]" check acpid/file-initscript sh -c '[ -x /etc/init.d/acpid ]' check acpid/dir-events sh -c '[ -d /etc/acpi/events ]' check_eq acpid/dir-events-empty '' "$(find /etc/acpi/events -mindepth 1 2>/dev/null)" check acpid/man-acpid sh -c '[ -f /usr/share/man/man8/acpid.8 ]' check acpid/man-acpi-listen sh -c '[ -f /usr/share/man/man8/acpi_listen.8 ]' check acpid/doc-readme sh -c '[ -f /usr/share/doc/acpid/README ]' check acpid/doc-testplan sh -c '[ -f /usr/share/doc/acpid/TESTPLAN ]' # The systemd unit the recipe's do_install:append writes is NOT here, and its # absence is deliberate: "systemd" is not in DISTRO_FEATURES, so # systemd.bbclass removes ${systemd_system_unitdir} before packaging. A unit # appearing on this image would be a unit nothing can ever start. check acpid/no-systemd-unit sh -c '[ ! -e /lib/systemd/system/acpid.service ]' # The init script the recipe seds ${sbindir} into, and the one thing it does on # a machine with no ACPI: `test -d /proc/acpi || exit 0' on its fourth line, so # `start' is a documented no-op here rather than something that fails. It is run # only because /proc/acpi was asserted absent above -- if it were present this # would reach start-stop-daemon and leave a daemon behind. saw acpid/initscript-sbindir /etc/init.d/acpid \ 'start-stop-daemon -o -S -x /usr/sbin/acpid -- -c /etc/acpi/events' if [ -e /proc/acpi ]; then bst_skip acpid/initscript-start-is-noop "/proc/acpi exists, so the script would start a daemon" else check_rc acpid/initscript-start-is-noop 0 sh /etc/init.d/acpid start check acpid/initscript-started-nothing sh -c '[ ! -e /var/run/acpid.pid ]' fi # REGISTERED WITH THE PACKAGE MANAGER, under the name the image installs: the # .deb arriving through dpkg rather than a file tree somebody staged. check acpid/dpkg-registered sh -c 'dpkg -s acpid >/dev/null 2>&1' check_out acpid/dpkg-status "Status: install ok installed" sh -c 'dpkg -s acpid 2>&1' check_out acpid/dpkg-version "Version: 2.0.34" sh -c 'dpkg -s acpid 2>&1' check_out acpid/dpkg-owns-daemon "/usr/sbin/acpid" sh -c 'dpkg -L acpid 2>&1' check_out acpid/dpkg-owns-listen "/usr/bin/acpi_listen" sh -c 'dpkg -L acpid 2>&1' # ---------------------- 8. the daemon in its ordinary mode, LAST and unkilled # Everything above ran acpid on the -e path, which ends. This section is the one # case that asks what the daemon does when it is left to find an event source # for itself: /proc/acpi/event is absent, so main() falls through to inotify, # the input layer and generic netlink (acpid.c:94-107). None of those can # produce an ACPI event here, and open_netlink() LOGS a failure rather than # exiting (netlink.c:233), so acpid is expected to reach its wait state and # stay there -- which is correct daemon behaviour, not a fault, and is why the # criterion below is the state it reaches and never its exit status. # # It is started last because it cannot be stopped, and it is polled with # builtins only: a fork in the loop body reaps a child, after which a relative # sleep returns instantly (brintos/linux-in-the-browser#120) and the poll would # spin, growing the host process without bound (brintos/hardwarejs#20). NLE=$W/netlink.err : > "$NLE" "$A" -f -d -d -d -l -r BST-DROP -c "$R" -s "$W/nl.socket" -L "$W/lock3" > "$W/netlink.out" 2>"$NLE" & t0= if [ -r /proc/uptime ] && read -r u _ < /proc/uptime; then t0=${u%.*}; fi ticks=0; reached=0; last= while :; do # BST-REDIRECT-OK: $NLE is under $W, which is under ${TMPDIR:-/tmp}. while IFS= read -r l; do last=$l case "$l" in *'waiting for events'*) reached=1 ;; esac done < "$NLE" [ "$reached" -eq 1 ] && break d=$ticks if [ -n "$t0" ] && read -r u _ < /proc/uptime; then d=$(( ${u%.*} - t0 )); fi [ "$d" -ge 90 ] && break sleep 1 ticks=$((ticks + 1)) done if [ "$reached" -eq 1 ]; then bst_ok acpid/netlink-mode-reaches-wait else bst_notok acpid/netlink-mode-reaches-wait \ "no 'waiting for events' within 90s; last line: $(_bst_1line "$last")" fi saw acpid/netlink-mode-declared "$NLE" 'starting up with netlink and the input layer' saw acpid/netlink-mode-loads-rules "$NLE" '5 rules loaded' # The UNIX socket this run was told to create, on a path in tmpfs. Without -S # open_sock() is reached, and it is the one opener that exits on failure -- so a # socket that is here is ud_create_socket's bind(2) and listen(2) working on # this kernel, and one that is not would have ended the run above. check acpid/socket-created sh -c "[ -e '$W/nl.socket' ]"