. "$BST_LIB" # BST-TIMEOUT: 600 # ~200 cases, ~50 of them execs of a /bin/sh script that # sources two more; the 60 s default is too tight for that. # Raised from 300 after batch-1949: 300 carried the whole # 219 on an unloaded host but stopped at case 156 when three # sibling ticket lanes were verifying beside it, and a bound # that reports the host's load as this port's failure is # measuring the wrong thing. # # pm-utils 1.4.1 (BRINTOS-1969) -- freedesktop's power-management helpers, from # poky/meta recipes-bsp/pm-utils. Two tiny C programs (pm-pmu, pm-reset-swap) # and a tree of POSIX shell: pm-is-supported, on_ac_power, pm-powersave, the # pm-action driver behind the pm-suspend / pm-hibernate / pm-suspend-hybrid # symlinks, and the shell library in /usr/lib/pm-utils that all of them source. # # THE ABSENCE OF THE PLATFORM INTERFACE IS THE SUBJECT, NOT AN OBSTACLE TO IT. # arch/wasm32/configs/wasm32_defconfig sets neither CONFIG_PM nor CONFIG_SUSPEND # nor CONFIG_HIBERNATION, so there is no /sys/power on this machine at all. # pm-functions probes for a sleep method by reading /sys/power/state, finds # nothing, and leaves SUSPEND_MODULE / HIBERNATE_MODULE / SUSPEND_HYBRID_MODULE # empty -- which is precisely the state check_suspend() and its two siblings # report on. So `pm-is-supported --suspend' answering NON-ZERO with an empty # stdout is the CORRECT answer here, and it is how a caller is documented to # discover it. Nothing in that is a defect and nothing is filed for it. Same # shape as 99-rfkill's kernel with no CONFIG_RFKILL and 63-tunctl's with no # CONFIG_TUN -- and, as there, BOTH ARMS are written out and selected from a # runtime probe, so the day this kernel grows a suspend path the result changes # visibly instead of passing either way. # # THE PROBE IS NOT SILENT, AND THAT IS UPSTREAM'S SHAPE RATHER THAN THIS ARM'S. # The fallback in pm-functions is `grep -q mem /sys/power/state', then -- after a # /dev/pmu test that short-circuits -- `grep -q standby /sys/power/state'. On a # kernel without CONFIG_SUSPEND both run and both write their own no-such-file # line to STDERR, so EVERY program that sources pm-functions emits exactly TWO # stderr lines before it says anything of its own. The hibernate probe is guarded # by `[ -f /sys/power/disk ]' and adds none. Measured identically on a native # x86-64 dash and on busybox ash against a pm-functions whose /sys/power path was # redirected to a non-existent tree -- so it is upstream's code path and not this # platform's. STDOUT stays empty, which is what a caller reading the answer looks # at, so the two streams are asserted SEPARATELY below rather than folded. # # ============================================================================ # THE HOOK RUNNER REPORTS EVERY HOOK AS "success." WHATEVER IT RETURNED, AND # NOTHING EVER INHIBITS. THAT IS AN UPSTREAM BUG, IT IS PINNED AS IT BEHAVES. # ============================================================================ # # _run_hook() ends # # hook_ok "$1" && "$@" # log "" # log -n "$*: " # hook_exit_status $? && LAST_HOOK="${1##*/}" || inhibit # # and `$?' there is the status of `log -n', not of the hook: two commands # intervene. log() returns 0 on every path, so hook_exit_status() is handed 0 # every time and prints "success." for a hook that exited 3, for one that was # not executable, and for one the storage dir had disabled. inhibit() is # therefore never called, no inhibit file is written, run_hooks always returns # 0, and the documented "once a hook inhibits, no later hook runs" stop never # happens. # # Measured, not inferred, and measured OFF this target: an x86-64 control over # the identical 1.4.1 sources, under both dash and busybox ash, runs a hook tree # of five and prints "success." five times with a plain rc 0. So this is # upstream's behaviour on every platform, there is nothing here for brintOS to # fix and nothing to file. The suite asserts what the program DOES -- because a # suite written from the documentation would go red on a correct build of # pm-utils, and red for a reason no later reader could tell apart from a broken # hook runner. The two halves that DO work are asserted in full and are what # makes these cases worth having: a non-executable or disabled hook is still # not EXECUTED, and the /etc tree still overrides the package tree name for name. # # THE HOOK RUNNER IS DRIVEN AGAINST FIXTURES, NOT AGAINST THE SHIPPED HOOKS. # _run_hooks() enumerates a candidate with `[ -O "$f" ]' -- owned by the # effective uid -- and this image's rootfs is staged 1000:1001 while the guest # runs as root, so the shipped hooks may enumerate to nothing. That is a property # of how the rootfs is staged rather than of pm-utils; it is measured here as its # own case and assumed in neither direction, and everything asserted about the # runner is asserted over a scratch tree under $TMPDIR that the suite owns # outright. # # /etc/pm/config.d CANNOT BE DRIVEN FROM A SCRATCH DIRECTORY. pm-functions sets # PM_UTILS_ETCDIR itself, inside `set -a', and reads config.d in the same file -- # so a caller can only override it after the read has happened. The config-parser # cases therefore use the real /etc/pm/config.d, write two numbered fixtures into # it and remove them again. / is mounted rw under the host-side runner # (78-libutempter provisions /var the same way), and the fixtures carry a bst- # infix so a leftover is identifiable rather than anonymous. # # NO MAN PAGES SHIP, AND THAT IS A DISTRO DECISION THIS RECIPE MERELY FOLLOWS. # pm-utils gates its pages on PACKAGECONFIG[manpages], manpages.bbclass turns # that on only for DISTRO_FEATURES `api-documentation', and brintos.conf sets # DISTRO_FEATURES without "api-documentation". configure runs --disable-doc, # man/Makefile.am's HAVE_XMLTO arm is not taken, and neither the five pages nor # the three pm-action.8 symlinks upstream's install-data-hook would make exist. # Their ABSENCE is asserted rather than skipped: a reader who goes looking for # pm-suspend(8) is entitled to know it was checked and not forgotten. # # TWO SMALLER ARTIFACTS ARE PINNED AS THEY BEHAVE RATHER THAN AS THEY READ: # * `pm-powersave --help' PRINTS THE SHORT HELP. The case arm is # `--help) help && run_hooks power help', and help() tests its OWN $1 for # --help -- which is empty, because the arm calls it with no arguments. The # long "the rest of this help message" block is unreachable from the command # line. Asserted in both directions. # * THE config.d ACCUMULATOR LEAVES ITS SEPARATORS IN. The concatenating # branch does `X="$REAL_X $X"' unconditionally, so a variable no fixture sets # still collects one space per file read, and one set only by the second file # comes back with the first iteration's space in front of it as well. The # exact strings are asserted, brackets included, because that is what a # consumer splitting the value receives. require pm-is-supported pm-utils/present LIB=/usr/lib/pm-utils ETC=/etc/pm BIN=$LIB/bin work=${TMPDIR:-/tmp}/bst-pm-utils.$$ mkdir -p "$work" 2>/dev/null O=$work/o; E=$work/e # cap : run once and keep stdout, stderr and status apart. Most cases # here assert on all three, and an exec is the expensive operation on this target. cap() { "$@" >"$O" 2>"$E"; RC=$? OUT=$(cat "$O" 2>/dev/null); ERR=$(cat "$E" 2>/dev/null) } rc_is() { # -- reads $RC/$ERR from the last cap if [ "$RC" = "$2" ]; then bst_ok "$1" elif bst_execfail "$ERR"; then bst_unrun "$1" "$ERR" else bst_notok "$1" "rc=$RC want-rc=$2 err:'$(_bst_1line "$ERR")'"; fi } # brintos/linux#73 -- pm-action, and ONLY pm-action, trips it here. # # add_parameters() -> remove_parameters() ends with, at functions line 219, # # grep -vxFf "$PARAMETERS.rm" "$PARAMETERS" > "$PARAMETERS.new" # # and $PARAMETERS is under /var/run/pm-utils, which on this image is NOT a tmpfs: # /var/run -> ../run and /run is a plain directory on the virtiofs root. Opening # that redirect makes dup2(2) return the kernel's -ERESTARTNOINTR park sentinel # (513) raw to userspace -- `RESTART-SENTINEL-LEAK val=513 nr=33 site=error_label' # on the console and `line 219: 11: Unknown error 513' from the shell -- and # busybox ash treats a failed redirect on a simple command as fatal, exiting 2. # So pm-action dies between its trap and its argument parsing: BEFORE the --help # arm, which is why --help answers rc 2 with an EMPTY stdout rather than the help # text, and before the check_$METHOD arm, which is why the unsupported path # answers 2 instead of its documented 1. # # Nothing about that is pm-utils' doing and nothing about it is this port's. # pm-is-supported, on_ac_power and pm-powersave source the identical library and # are green above; they simply never call add_parameters. Measured on an x86-64 # control over these same 1.4.1 scripts, under both dash and busybox ash, with # /sys/power redirected away: rc 1 for the unsupported path and rc 0 plus the # full help text for --help -- so the divergence is the platform's, not the # shell's and not the package's. # # Both arms are written out and selected from THIS predicate rather than from a # constant, so the day #73 lands these cases assert upstream's real behaviour # instead of quietly continuing to pass. # The evidence line, NOT the whole capture: _bst_1line caps a detail at 100 # characters and the two /sys/power/state complaints every one of these runs # emits first are 95 of them -- which is precisely how the 513 signature got # truncated out of the batch verify's red-cases and left this suite's reds # looking unexplained. # Parameter expansion only -- this must NOT exec, for the same reason bst.sh says # it of _bst_1line. Every exec on this platform is expensive enough that this # file carries a raised BST-TIMEOUT to pay for the ~50 it already does, and a # `printf | grep | head' here would have added three more per red case on the # one path that is guaranteed to be taken when something is wrong. pm_513_ev() { case "$1" in *"RESTART-SENTINEL-LEAK"*) printf '%s' "RESTART-SENTINEL-LEAK${1#*RESTART-SENTINEL-LEAK}" ;; *) printf '%s' "Unknown error 513${1#*Unknown error 513}" ;; esac } pm_513() { # -- did this run die on brintos/linux#73? case "$1" in *"Unknown error 513"*|*"RESTART-SENTINEL-LEAK"*) return 0 ;; *) return 1 ;; esac } inc() { # case "$3" in *"$2"*) bst_ok "$1" ;; *) if bst_execfail "$3"; then bst_unrun "$1" "$3" else bst_notok "$1" "want:'$2' got:'$(_bst_1line "$3")'"; fi ;; esac } # exc : the needle must be ABSENT and the haystack # NON-EMPTY. Without the second half every one of these passes when the command # could not be staged at all -- a false green in exactly the cases written to # prove that the wrong thing is not answering. exc() { if [ -z "$3" ]; then bst_notok "$1" "nothing to check: the command produced no output" elif bst_execfail "$3"; then bst_unrun "$1" "$3" else case "$3" in *"$2"*) bst_notok "$1" "must not contain:'$2' got:'$(_bst_1line "$3")'" ;; *) bst_ok "$1" ;; esac; fi } is_empty() { # if [ -z "$2" ]; then bst_ok "$1" else bst_notok "$1" "expected empty, got:'$(_bst_1line "$2")'"; fi } absent() { # -- the path must not exist, as a file or as a symlink if [ -e "$2" ] || [ -L "$2" ]; then bst_notok "$1" "$2 exists, and this build ships no such file" else bst_ok "$1"; fi } # The capture files are in $TMPDIR, never on the virtiofs root: a stdin redirect # the top-level shell performs there makes dup2(2) return 513 and ash EXITS # (brintos/linux#73). nlines() { wc -l < "$O" 2>/dev/null | tr -d ' \t'; } # lines on the last cap's STDOUT count() { ls "$1" 2>/dev/null | wc -l | tr -d ' \t'; } # `[ -x ]' IS NOT AN ORACLE ON THE VIRTIOFS ROOT: the rootfs files are # owned 1000:1001 while the guest runs as root, so busybox test falls through to # the supplementary-group check and getgroups(2) answers ENOSYS -- a false result # with `sh: getgroups: Function not implemented' on stderr (brintos/linux#19). # Read the mode out of `ls -l' instead, as 88-p910nd and 99-rfkill do. mode() { ls -l "$1" 2>/dev/null | cut -c1-10; } # ------------------------------------------------------------------ packaging --- # The three entry points a caller reaches by name. pm-is-supported and # on_ac_power are bin_SCRIPTS; pm-powersave is an sbin_SCRIPT. for f in /usr/bin/pm-is-supported /usr/bin/on_ac_power /usr/sbin/pm-powersave; do check "pm-utils/file-${f##*/}" test -f "$f" check_eq "pm-utils/mode-${f##*/}" "-rwxr-xr-x" "$(mode "$f")" done # EXTRA_OECONF passes --libdir=${nonarch_libdir}, so pm_libdir is /usr/lib/pm-utils # and not a target-libdir sibling. Every @PM-UTILS-LIBDIR@ substitution in the # shipped scripts was frozen against that at BUILD time, which is why the value is # read back out of the installed pm-functions further down rather than trusted. for f in pm-action service pm-pmu pm-reset-swap; do check "pm-utils/libexec-$f" test -f "$BIN/$f" check_eq "pm-utils/libexec-mode-$f" "-rwxr-xr-x" "$(mode "$BIN/$f")" done for f in functions pm-functions defaults; do check "pm-utils/lib-$f" test -f "$LIB/$f" done # `defaults' is dist_pm_lib_DATA, not a script: 0644. The mode case is what would # catch an install that turned the package's configuration into something the # hook runner might try to execute. check_eq pm-utils/lib-defaults-mode "-rw-r--r--" "$(mode "$LIB/defaults")" # The three sleep verbs. install-data-hook writes a RELATIVE target -- measured # off the built package, `../lib/pm-utils/bin/pm-action', not the absolute # $(pm_bindir)/pm-action this suite first asserted. The literal is still pinned, # because a link that silently became absolute would resolve identically here and # break the moment /usr/sbin is reached through a different prefix; it is spelled # as $BIN made relative rather than typed out, so the two cannot drift apart. for v in pm-suspend pm-hibernate pm-suspend-hybrid; do check "pm-utils/link-$v" test -L "/usr/sbin/$v" check_eq "pm-utils/link-target-$v" "..${BIN#/usr}/pm-action" "$(readlink "/usr/sbin/$v" 2>/dev/null)" check "pm-utils/link-resolves-$v" test -f "/usr/sbin/$v" done # The two C programs are real WebAssembly modules: \0asm, not \177ELF. Each pair # is asserted from the SAME four bytes against DIFFERENT constants, so neither # can pass on an artifact the other rejects. for p in pm-pmu pm-reset-swap; do MAGIC=$(od -An -tx1 -N4 "$BIN/$p" 2>/dev/null | tr -s ' ' | sed 's/^ //;s/ *$//') check_eq "pm-utils/$p-is-wasm" "00 61 73 6d" "$MAGIC" if [ -z "$MAGIC" ]; then bst_notok "pm-utils/$p-not-elf" "could not read the first four bytes of $BIN/$p" elif [ "$MAGIC" = "7f 45 4c 46" ]; then bst_notok "pm-utils/$p-not-elf" "the shipped binary is an ELF object, not a WebAssembly module" else bst_ok "pm-utils/$p-not-elf"; fi done # RDEPENDS names bash, but every shipped script asks for /bin/sh -- which on this # image is busybox ash. The interpreter line is what decides which one runs, so a # port that quietly grew a bashism would be visible here first. for f in /usr/bin/pm-is-supported /usr/bin/on_ac_power /usr/sbin/pm-powersave "$BIN/pm-action"; do check_eq "pm-utils/shebang-${f##*/}" "#!/bin/sh" "$(head -n 1 "$f" 2>/dev/null)" done # ------------------------------------------------------------- the hook trees --- # Two halves, and the pair is the whole interface anything integrating with # pm-utils uses: the PACKAGE tree under /usr/lib/pm-utils, and the empty # ADMINISTRATOR tree under /etc/pm that overrides it name for name. SLEEPD="00logging 00powersave 01grub 49bluetooth 55NetworkManager 75modules 90clock 94cpufreq 95led 98video-quirk-db-handler 99video" POWERD="disable_wol hal-cd-polling harddrive intel-audio-powersave journal-commit laptop-mode pcie_aspm readahead sata_alpm sched-powersave wireless xfs_buffer" MODULED="kernel tuxonice uswsusp" for h in $SLEEPD; do check "pm-utils/sleep.d-$h" test -f "$LIB/sleep.d/$h"; done for h in $POWERD; do check "pm-utils/power.d-$h" test -f "$LIB/power.d/$h"; done for h in $MODULED; do check "pm-utils/module.d-$h" test -f "$LIB/module.d/$h"; done # The counts are asserted too: an install that dropped one still satisfies every # per-name case above, and one that ADDED a hook would change what a suspend runs. check_eq pm-utils/sleep.d-count 11 "$(count "$LIB/sleep.d")" check_eq pm-utils/power.d-count 12 "$(count "$LIB/power.d")" check_eq pm-utils/module.d-count 3 "$(count "$LIB/module.d")" # The /etc side is created by three install-exec-local rules and ships EMPTY. Its # emptiness is the assertion: a file appearing here would silently override a # package hook of the same name on every suspend. for d in sleep.d power.d config.d; do check "pm-utils/etc-dir-$d" test -d "$ETC/$d" done CFG_N0=$(count "$ETC/config.d") check_eq pm-utils/etc-sleep.d-empty 0 "$(count "$ETC/sleep.d")" check_eq pm-utils/etc-power.d-empty 0 "$(count "$ETC/power.d")" check_eq pm-utils/etc-config.d-empty 0 "$CFG_N0" # ------------------------------------------------ the @-substitutions, read back --- # pm-functions is generated at build time by do_subst, so these two lines are # where --libdir=${nonarch_libdir} either took or did not. Reading them out of the # installed file is the only check a recipe that merely CLAIMS the paths fails. PMF=$(cat "$LIB/pm-functions" 2>/dev/null) inc pm-utils/subst-libdir "PM_UTILS_LIBDIR=\"$LIB\"" "$PMF" inc pm-utils/subst-etcdir "PM_UTILS_ETCDIR=\"$ETC\"" "$PMF" exc pm-utils/subst-none-left "@PM-UTILS-" "$PMF" PMA=$(cat "$BIN/pm-action" 2>/dev/null) inc pm-utils/subst-action-libdir "$LIB/pm-functions" "$PMA" exc pm-utils/subst-action-none-left "@PM-UTILS-" "$PMA" # ------------------------------------------------------------ what does NOT ship --- for m in man1/on_ac_power.1 man1/pm-is-supported.1 man8/pm-pmu.8 \ man8/pm-powersave.8 man8/pm-action.8 man8/pm-suspend.8 \ man8/pm-hibernate.8 man8/pm-suspend-hybrid.8; do absent "pm-utils/no-man-${m#*/}" "/usr/share/man/$m" absent "pm-utils/no-man-gz-${m#*/}" "/usr/share/man/$m.gz" done # pm-utils.pc is packaged into pm-utils-dev, which the image does not name -- so # the .pc is legitimately absent and a consumer cannot find_package it here. absent pm-utils/no-pc /usr/lib/pkgconfig/pm-utils.pc # ------------------------------------------------------------ the dpkg record --- # The image ships a populated /var/lib/dpkg (the package-management IMAGE_FEATURE), # so what the .deb installed and where is readable at run time. This is the # packaging contract asserted from the package manager's side rather than from the # filesystem's, and the two must agree. if command -v dpkg-query >/dev/null 2>&1; then bst_ok pm-utils/dpkg-available check_out pm-utils/dpkg-installed "pm-utils" dpkg-query -W -f='${Package}' pm-utils # The version the recipe pins. pm-utils has no --version of its own; the # package database is where the version actually lives. check_out pm-utils/dpkg-version "1.4.1" dpkg-query -W -f='${Version}' pm-utils # `ii' is the only state that means unpacked AND configured. check_out pm-utils/dpkg-state "ii" dpkg-query -W -f='${db:Status-Abbrev}' pm-utils FILES=$(dpkg -L pm-utils 2>/dev/null) for f in /usr/bin/pm-is-supported /usr/bin/on_ac_power /usr/sbin/pm-powersave \ /usr/sbin/pm-suspend /usr/sbin/pm-hibernate /usr/sbin/pm-suspend-hybrid \ "$BIN/pm-action" "$BIN/service" "$BIN/pm-pmu" "$BIN/pm-reset-swap" \ "$LIB/functions" "$LIB/pm-functions" "$LIB/defaults" \ "$LIB/sleep.d/00logging" "$LIB/power.d/harddrive" "$LIB/module.d/kernel" \ "$ETC/config.d" "$ETC/sleep.d" "$ETC/power.d"; do inc "pm-utils/dpkg-owns-${f##*/}" "$f" "$FILES" done # The reverse lookup: the file on disk is attributed to this package and not # to some other recipe that happened to install the same path. check_out pm-utils/dpkg-search "pm-utils" dpkg -S /usr/bin/pm-is-supported else for c in dpkg-available dpkg-installed dpkg-version dpkg-state dpkg-search; do bst_skip "pm-utils/$c" "dpkg-query is not installed on this image" done fi # --------------------------------------------------- the platform, as measured --- # Every expectation below is selected off THESE probes rather than off the ticket. if [ -e /sys/power/state ]; then HAVE_PM=1; bst_notok pm-utils/no-sys-power \ "/sys/power/state exists: this kernel now has a suspend path, so the no-power-management arm below no longer describes it" else HAVE_PM=0; bst_ok pm-utils/no-sys-power fi if [ -e /sys/power/disk ]; then HAVE_HIB=1; bst_notok pm-utils/no-sys-disk "/sys/power/disk exists: this kernel now has a hibernate path" else HAVE_HIB=0; bst_ok pm-utils/no-sys-disk fi # on_ac_power's whole input. With no power supplies at all it is documented to # assume AC, which is the arm this machine takes. if [ -d /sys/class/power_supply ] && [ -n "$(ls /sys/class/power_supply 2>/dev/null)" ]; then HAVE_PS=1; bst_notok pm-utils/no-power-supply "/sys/class/power_supply is populated: on_ac_power now has real input" else HAVE_PS=0; bst_ok pm-utils/no-power-supply fi GREPMSG=/sys/power/state # the substring both probe complaints carry # ------------------------------------------------------------- pm-is-supported --- # `help' is the only argument that produces output on stdout, and it is where the # accepted vocabulary is written down. cap pm-is-supported help rc_is pm-utils/is-supported-help-rc 0 inc pm-utils/is-supported-help-usage \ "pm-is-supported [--suspend | --hibernate | --suspend-hybrid ]" "$OUT" check_eq pm-utils/is-supported-help-lines 2 "$(nlines)" # The three queries. Each runs check_, which is `[ -n "$_MODULE" ]' # -- so a non-zero status with NOTHING on stdout is the documented way a caller # discovers the answer, and both halves are asserted. for q in suspend hibernate suspend-hybrid; do cap pm-is-supported "--$q" if [ "$q" = hibernate ] && [ "$HAVE_HIB" = 1 ]; then rc_is "pm-utils/is-supported-$q-rc" 0 elif [ "$q" != hibernate ] && [ "$HAVE_PM" = 1 ]; then rc_is "pm-utils/is-supported-$q-rc" 0 else rc_is "pm-utils/is-supported-$q-rc" 1 fi is_empty "pm-utils/is-supported-$q-stdout" "$OUT" # The sleep-method probe's own diagnostic, on the stream that cannot be # mistaken for the answer. Two lines: `mem' and `standby'. if [ "$HAVE_PM" = 1 ]; then is_empty "pm-utils/is-supported-$q-stderr" "$ERR" else inc "pm-utils/is-supported-$q-stderr" "$GREPMSG" "$ERR" fi done # The undashed spelling is ACCEPTED, and that is not obvious from the usage line: # the parser strips a leading `--' before matching, so `suspend' reaches the same # arm as `--suspend'. Asserted because a caller that finds it working is entitled # to know it is the parser and not an accident. cap pm-is-supported suspend if [ "$HAVE_PM" = 1 ]; then rc_is pm-utils/is-supported-undashed-rc 0 else rc_is pm-utils/is-supported-undashed-rc 1; fi is_empty pm-utils/is-supported-undashed-stdout "$OUT" exc pm-utils/is-supported-undashed-no-usage "pm-is-supported [--suspend" "$ERR" # Everything else falls through to the `*)' arm: usage on STDERR this time, rc 1. # `--suspend2' and `-suspend' are deliberately near-misses -- the parser strips # `--' and translates `-' to `_', so they are what prove it is not matching # loosely. The empty case is the bare invocation. i=0 for bad in "--bogus" "--suspend2" "-suspend" "--Suspend" ""; do i=$((i + 1)) cap pm-is-supported ${bad:+"$bad"} rc_is "pm-utils/is-supported-reject$i-rc" 1 is_empty "pm-utils/is-supported-reject$i-stdout" "$OUT" inc "pm-utils/is-supported-reject$i-stderr" \ "pm-is-supported [--suspend | --hibernate | --suspend-hybrid ]" "$ERR" done # --------------------------------------------------------------- on_ac_power --- # on_ac_power sources nothing, so unlike everything else here its stderr is empty. cap on_ac_power if [ "$HAVE_PS" = 1 ]; then case "$RC" in 0|1) bst_ok pm-utils/on-ac-power-rc ;; *) bst_notok pm-utils/on-ac-power-rc "rc=$RC is neither of the two documented answers" ;; esac else rc_is pm-utils/on-ac-power-rc 0 fi is_empty pm-utils/on-ac-power-stdout "$OUT" is_empty pm-utils/on-ac-power-stderr "$ERR" # ---------------------------------------------------------- pm-action, no args --- # The central assertion of this port. pm-action reaches check_$METHOD, finds the # method unsupported, and reports through log() -- which is silent, because # LOGGING is not set until init_logfile() runs and it never runs on this path. So: # non-zero, and nothing on stdout. Exactly how a caller is meant to find out. for v in pm-suspend pm-hibernate pm-suspend-hybrid; do if [ "$HAVE_PM" = 1 ] || [ "$HAVE_HIB" = 1 ]; then bst_skip "pm-utils/$v-unsupported-rc" \ "this kernel now has a sleep path, so $v would attempt a real suspend and is not run" bst_skip "pm-utils/$v-unsupported-stdout" "not run: see $v-unsupported-rc" else cap "/usr/sbin/$v" if pm_513 "$ERR"; then bst_notok "pm-utils/$v-unsupported-rc" \ "downstream of brintos/linux#73: $(_bst_1line "$(pm_513_ev "$ERR")")" else rc_is "pm-utils/$v-unsupported-rc" 1 fi is_empty "pm-utils/$v-unsupported-stdout" "$OUT" fi done # --help is handled BEFORE the method check, so it answers on a machine that # supports nothing -- and it answers with argv[0]'s own basename, which is what # proves the run came through the symlink rather than through pm-action. for v in pm-suspend pm-hibernate pm-suspend-hybrid; do cap "/usr/sbin/$v" --help if pm_513 "$ERR"; then for c in help-rc help-argv0 help-text; do bst_notok "pm-utils/$v-$c" \ "downstream of brintos/linux#73: $(_bst_1line "$(pm_513_ev "$ERR")")" done else rc_is "pm-utils/$v-help-rc" 0 inc "pm-utils/$v-help-argv0" "$v [options]" "$OUT" inc "pm-utils/$v-help-text" "Options can change how suspend or hibernate is done." "$OUT" fi done # The lock and storage directories pm-action provisions under /var/run (a tmpfs # reached through the /var/run -> /run symlink). Their existence after the runs # above is what says try_lock() and the storage setup both worked here -- which # is a real question on this platform and not a formality, since try_lock() is # flock(2) on a descriptor and the lock file lands on whatever /run is. check pm-utils/rundir-locks test -d /var/run/pm-utils/locks check pm-utils/rundir-storage test -d /var/run/pm-utils/pm-suspend/storage # release_lock() unlinks the lock file from the exit trap however the run ended. absent pm-utils/lock-released /var/run/pm-utils/locks/pm-suspend.lock # ------------------------------------------------------------- pm-powersave --- # --help does NOT reach lock_and_load, so its output is on stdout rather than in # the logfile. And it prints the SHORT help, for the reason in the header. cap pm-powersave --help rc_is pm-utils/powersave-help-rc 0 inc pm-utils/powersave-help-true "true or battery = turn powersaving features on." "$OUT" inc pm-utils/powersave-help-false "false or ac = turn powersaving features off." "$OUT" inc pm-utils/powersave-help-short "You can get more detailed information by running pm-powersave --help" "$OUT" exc pm-utils/powersave-help-long-unreachable \ "The rest of this help message displays the variables" "$OUT" # An unrecognised argument prints the same help and exits 1. cap pm-powersave bogus rc_is pm-utils/powersave-reject-rc 1 inc pm-utils/powersave-reject-help "Valid options are:" "$OUT" # `true' and `false' take the lock, initialise the logfile and run the power # hooks. init_logfile() does `exec > "$1" 2>&1', so from that point the program is # SILENT on the terminal and everything it has to say is in the logfile -- which # is the shape both cases below assert. The logfile is legitimately EMPTY when no # hook enumerated (see shipped-hooks-enumerable), so its CONTENT is not asserted; # what is asserted is that init_logfile() ran and left the file behind. # # /var/log is a symlink to volatile/log and this image runs no populate-volatile # step, so the target directory does not exist on a fresh boot and the exec # redirect would fail -- taking the whole script down for a reason that has # nothing to do with pm-utils. That is an image gap, it gets its own named case, # and the suite then provisions it exactly as 78-libutempter does. if [ -d /var/volatile/log ]; then bst_ok pm-utils/varlog-provisioned else bst_skip pm-utils/varlog-provisioned \ "image provisions no /var/log target at boot (/var/log -> volatile/log is dangling); suite creates it" fi # In a SUBSHELL: a failed redirection on a special builtin exits the shell # outright, which would truncate this suite into a shorter clean-looking pass. ( mkdir -p /var/volatile/log ) 2>/dev/null if ( : > /var/log/.bst-pm-utils-probe ) 2>/dev/null; then rm -f /var/log/.bst-pm-utils-probe for a in true false; do rm -f /var/log/pm-powersave.log cap pm-powersave "$a" rc_is "pm-utils/powersave-$a-rc" 0 is_empty "pm-utils/powersave-$a-stdout" "$OUT" # The one thing still on stderr is the sleep-method probe, emitted while # pm-functions is sourced -- before init_logfile() captures the streams. if [ "$HAVE_PM" = 1 ]; then is_empty "pm-utils/powersave-$a-stderr" "$ERR" else inc "pm-utils/powersave-$a-stderr" "$GREPMSG" "$ERR" fi check "pm-utils/powersave-$a-logfile" test -f /var/log/pm-powersave.log absent "pm-utils/powersave-$a-lock-released" /var/run/pm-utils/locks/pm-powersave.lock done rm -f /var/log/pm-powersave.log else for a in true false; do for c in rc stdout stderr logfile lock-released; do bst_skip "pm-utils/powersave-$a-$c" \ "/var/log is not writable on this image, so init_logfile()'s exec redirect cannot run" done done fi # ---------------------------------------------- the hook runner, over fixtures --- # First the measurement the shipped tree cannot be asserted without: whether the # enumerator's `[ -O ]' gate lets a shipped hook through at all. if [ -O "$LIB/power.d/harddrive" ]; then bst_ok pm-utils/shipped-hooks-enumerable else bst_skip pm-utils/shipped-hooks-enumerable \ "the rootfs is staged uid 1000 and this runs as root, so _run_hooks' [ -O ] gate excludes every shipped hook; the runner is asserted over suite-owned fixtures instead" fi hooks=$work/hooks mkdir -p "$hooks/etc/sleep.d" "$hooks/lib/sleep.d" "$hooks/storage" 2>/dev/null # 10ok succeeds and prints, so its stdout proves it ran at all # 20fail exits 3 # 30after sorts after the failure, and DOES run (nothing inhibits -- header) # 40noexec is not executable -> hook_ok returns $NX, and it is not run # 50disabled is vetoed via storage dir -> hook_ok returns $DX, and it is not run # 60both exists in BOTH trees; the /etc copy must be the one that runs # 70libonly exists only in the package tree, and must still run printf '#!/bin/sh\necho BST-HOOK-10-RAN "$@"\nexit 0\n' > "$hooks/etc/sleep.d/10ok" printf '#!/bin/sh\necho BST-HOOK-20-RAN "$@"\nexit 3\n' > "$hooks/etc/sleep.d/20fail" printf '#!/bin/sh\necho BST-HOOK-30-RAN "$@"\nexit 0\n' > "$hooks/etc/sleep.d/30after" printf '#!/bin/sh\necho BST-HOOK-40-RAN "$@"\nexit 0\n' > "$hooks/etc/sleep.d/40noexec" printf '#!/bin/sh\necho BST-HOOK-50-RAN "$@"\nexit 0\n' > "$hooks/etc/sleep.d/50disabled" printf '#!/bin/sh\necho BST-HOOK-ETC-WON\nexit 0\n' > "$hooks/etc/sleep.d/60both" printf '#!/bin/sh\necho BST-HOOK-LIB-LOST\nexit 0\n' > "$hooks/lib/sleep.d/60both" printf '#!/bin/sh\necho BST-HOOK-LIB-ONLY\nexit 0\n' > "$hooks/lib/sleep.d/70libonly" chmod 0755 "$hooks/etc/sleep.d/10ok" "$hooks/etc/sleep.d/20fail" \ "$hooks/etc/sleep.d/30after" "$hooks/etc/sleep.d/50disabled" \ "$hooks/etc/sleep.d/60both" "$hooks/lib/sleep.d/60both" \ "$hooks/lib/sleep.d/70libonly" 2>/dev/null chmod 0644 "$hooks/etc/sleep.d/40noexec" 2>/dev/null printf 'suite\n' > "$hooks/storage/disable_hook:50disabled" # The driver is a FILE run by `sh', not an inline -c string: the redirects it # performs are then the child shell's own, and the variables it sets survive the # hook loop, which run_hooks needs. PM_UTILS_ETCDIR / PM_UTILS_LIBDIR are # overridden AFTER pm-functions has been sourced -- the only order that works, # because the file assigns them itself. cat > "$work/drive-hooks" <<'DRV' STASHNAME=bst-pm-hooks export STASHNAME . /usr/lib/pm-utils/pm-functions PM_UTILS_ETCDIR="$BSTHOOKS/etc" PM_UTILS_LIBDIR="$BSTHOOKS/lib" STORAGEDIR="$BSTHOOKS/storage" INHIBIT="$STORAGEDIR/inhibit" PARAMETERS="$STORAGEDIR/parameters" LOGGING=true rm -f "$INHIBIT" run_hooks sleep "suspend suspend" printf 'BST-RUN-HOOKS-RC=%s\n' "$?" DRV BSTHOOKS=$hooks; export BSTHOOKS cap sh "$work/drive-hooks" HK=$OUT rc_is pm-utils/hooks-driver-rc 0 # WHAT RUNS. The hooks with an executable bit and no veto, in sort order, each # receiving the two words run_hooks was given. inc pm-utils/hooks-10-ran "BST-HOOK-10-RAN suspend suspend" "$HK" inc pm-utils/hooks-20-ran "BST-HOOK-20-RAN suspend suspend" "$HK" # WHAT DOES NOT. hook_ok()'s two refusals are real and are the half of the runner # that works: neither hook is executed, only mislabelled afterwards. exc pm-utils/hooks-40-noexec-not-run "BST-HOOK-40-RAN" "$HK" exc pm-utils/hooks-50-disabled-not-run "BST-HOOK-50-RAN" "$HK" # The /etc tree overrides the package tree name for name, and only name for name. inc pm-utils/hooks-etc-overrides-lib "BST-HOOK-ETC-WON" "$HK" exc pm-utils/hooks-lib-copy-not-run "BST-HOOK-LIB-LOST" "$HK" inc pm-utils/hooks-lib-only-runs "BST-HOOK-LIB-ONLY" "$HK" # THE REPORT FORMAT a reader of /var/log/pm-*.log actually sees. inc pm-utils/hooks-report-running "Running hook $hooks/etc/sleep.d/10ok suspend suspend:" "$HK" inc pm-utils/hooks-report-success "10ok suspend suspend: success." "$HK" # THE UPSTREAM BUG, PINNED AS IT BEHAVES (header). hook_exit_status() is handed # log()'s status rather than the hook's, so a hook that exited 3, one that was # not executable and one that was disabled are all reported "success.", nothing # inhibits, and run_hooks returns 0. Reproduced on a native x86-64 control over # the same sources under both dash and busybox ash, so it is upstream's and not # this arm's -- and it will go RED here the day pm-utils fixes it, which is the # point of asserting it rather than skipping it. inc pm-utils/hooks-mislabels-failure "20fail suspend suspend: success." "$HK" inc pm-utils/hooks-mislabels-noexec "40noexec suspend suspend: success." "$HK" inc pm-utils/hooks-mislabels-disabled "50disabled suspend suspend: success." "$HK" exc pm-utils/hooks-never-reports-code "Returned exit code" "$HK" inc pm-utils/hooks-30-ran-after-failure "BST-HOOK-30-RAN suspend suspend" "$HK" inc pm-utils/hooks-rc-always-zero "BST-RUN-HOOKS-RC=0" "$HK" absent pm-utils/hooks-never-inhibits "$hooks/storage/inhibit" # ------------------------------------------------- the /etc/pm/config.d parser --- # Driven against the REAL directory, because pm-functions reads config.d in the # same file that sets PM_UTILS_ETCDIR -- there is no scratch arm available. CFGA=$ETC/config.d/50-bst-pm-utils-a CFGB=$ETC/config.d/51-bst-pm-utils-b CFGC=$ETC/config.d/52-bst-pm-utils-c~ if ( : > "$CFGA" ) 2>/dev/null; then bst_ok pm-utils/config-writable printf 'HIBERNATE_MODE="platform"\nSUSPEND_MODULES="mod_a"\nBST_PM_MARK="a"\n' > "$CFGA" printf 'SUSPEND_MODULES="mod_b"\nHOOK_BLACKLIST="hb"\nBST_PM_MARK="b"\n' > "$CFGB" # An editor backup, which the `*[!~]' glob must exclude. printf 'BST_PM_MARK="backup"\n' > "$CFGC" cat > "$work/drive-config" <<'DRV' STASHNAME=bst-pm-config export STASHNAME . /usr/lib/pm-utils/pm-functions printf 'LIBDIR=[%s]\n' "$PM_UTILS_LIBDIR" printf 'ETCDIR=[%s]\n' "$PM_UTILS_ETCDIR" printf 'HIBERNATE_MODE=[%s]\n' "$HIBERNATE_MODE" printf 'SUSPEND_MODULES=[%s]\n' "$SUSPEND_MODULES" printf 'HOOK_BLACKLIST=[%s]\n' "$HOOK_BLACKLIST" printf 'BST_PM_MARK=[%s]\n' "$BST_PM_MARK" printf 'SUSPEND_MODULE=[%s]\n' "$SUSPEND_MODULE" printf 'HIBERNATE_MODULE=[%s]\n' "$HIBERNATE_MODULE" printf 'SLEEP_MODULE=[%s]\n' "$SLEEP_MODULE" DRV cap sh "$work/drive-config" CFG=$OUT rc_is pm-utils/config-driver-rc 0 # The two paths the build substituted, read back from a RUNNING program. inc pm-utils/config-libdir "LIBDIR=[$LIB]" "$CFG" inc pm-utils/config-etcdir "ETCDIR=[$ETC]" "$CFG" # A plain variable: the last file read wins outright. inc pm-utils/config-plain-override "HIBERNATE_MODE=[platform]" "$CFG" inc pm-utils/config-last-wins "BST_PM_MARK=[b]" "$CFG" # And the backup file was not read at all -- if it had been, the mark would # be `backup', since it sorts last. exc pm-utils/config-skips-backup "BST_PM_MARK=[backup]" "$CFG" # The accumulating variables keep their separators (header). Exact strings. inc pm-utils/config-accumulates "SUSPEND_MODULES=[ mod_a mod_b]" "$CFG" inc pm-utils/config-separators "HOOK_BLACKLIST=[ hb]" "$CFG" # A config file cannot invent a suspend path the kernel does not have. if [ "$HAVE_PM" = 1 ]; then bst_skip pm-utils/config-no-suspend-module "this kernel has a suspend path" else inc pm-utils/config-no-suspend-module "SUSPEND_MODULE=[]" "$CFG" fi if [ "$HAVE_HIB" = 1 ]; then bst_skip pm-utils/config-no-hibernate-module "this kernel has a hibernate path" else inc pm-utils/config-no-hibernate-module "HIBERNATE_MODULE=[]" "$CFG" fi # SLEEP_MODULE defaults to "auto" and pm-functions rewrites it in place to the # list it will try, so its value is the autodetection having run. inc pm-utils/config-sleep-module-expanded "SLEEP_MODULE=[tuxonice uswsusp]" "$CFG" rm -f "$CFGA" "$CFGB" "$CFGC" # The directory is left exactly as it was found. check_eq pm-utils/config-cleaned "$CFG_N0" "$(count "$ETC/config.d")" else for c in config-writable config-driver-rc config-libdir config-etcdir \ config-plain-override config-last-wins config-skips-backup \ config-accumulates config-separators config-no-suspend-module \ config-no-hibernate-module config-sleep-module-expanded config-cleaned; do bst_skip "pm-utils/$c" "$ETC/config.d is not writable, so the parser cannot be given a fixture" done rm -f "$CFGA" 2>/dev/null fi rm -rf "$work"