. "$BST_LIB" # BST-TIMEOUT: 600 # mosquitto 2.0.22 -- Roger Light's MQTT v5.0/v3.1.1/v3.1 broker with its pub, # sub and request/response clients (meta-networking # recipes-connectivity/mosquitto). # # THE BROKER IS /usr/sbin/mosquitto AND THE CLIENTS ARE /usr/bin/*, WHICH IS A # PACKAGE SPLIT RATHER THAN A LAYOUT ACCIDENT. FILES:${PN} takes ${sbindir}/ # mosquitto plus mosquitto_passwd and mosquitto_ctrl, and FILES:${PN}-clients # takes mosquitto_pub/_sub/_rr -- so the image needs BOTH `mosquitto` and # `mosquitto-clients` named in EMCRAFT_TOOLS, and a suite that `require`d one # path for everything would skip on tools that are present. # # THE ROUND-TRIP IS ASSERTED THROUGH A RETAINED MESSAGE, AND THAT IS FORCED BY # THE PLATFORM RATHER THAN CHOSEN FOR CONVENIENCE. mosquitto_sub's only # self-bound is -W, which is implemented as alarm(2) + a SIGALRM handler # (client/sub_client.c: sigaction(SIGALRM) then alarm(cfg.timeout)). On a # kernel without the brintos/linux#77 fix alarm(2) is ENOSYS, so -W cannot # fire, and neither can a `timeout` wrapper around it. A subscriber waiting for # a message that never arrives would then run until the suite's own 600 s # bound, which is exactly how a `timeout 15 nc -u` once consumed the whole # budget in 88-dnsmasq. Publishing RETAINED first and subscribing after means # -C 1 is satisfied by the broker at subscribe time, in the message callback, # with no clock and no signal involved: the subscriber returns immediately or # not at all, and "not at all" is caught by the poll helper below instead of by # a wait(1) that could never return. The round-trip therefore does not depend # on -W on either kernel. mosquitto/sub-W-timeout scores -W itself, from the # background, so a -W that does not fire cannot hang the suite. # # EVERY SUBSCRIBER RUNS `-k 5`, AND THAT ONE FLAG IS WHAT MAKES THE TICKET'S # CRITERION OBSERVABLE AT ALL (brintos/linux#64). On this kernel a connected # TCP socket does not hand the reader data the peer has already written until # the reader itself WRITES to that socket. The broker's log says it sent the # message; the subscriber sits in select(2) -- waking on its own 1 s timeout, # every second, indefinitely -- and never sees it: # # Client null received SUBACK # Subscribed (mid: 1): 0 # # # -k 5 is the client's minimum keepalive, so libmosquitto's # mosquitto__check_keepalive() puts a PINGREQ on the wire every ~5 s, and the # pending message surfaces on the very next line after that write: # # Client null received SUBACK # Client null sending PINGREQ <-- the client writes # Client null received PUBLISH ... <-- the message, finally # # So -k 5 is a WORKAROUND, not tuning: it buys a periodic write, and the write # is what delivers the read. It costs up to 5 s per subscriber and nothing else # -- the assertions below are unchanged, and each still fails if the payload is # wrong rather than merely late. mosquitto/inbound-needs-write-kick scores the # bare wall with a default-keepalive subscriber, so the day #64 closes this # suite says so instead of the workaround quietly outliving its reason. # BRINTOS-WORKAROUND: brintos/linux#64 remove — drop every `-k 5` below and the # inbound-needs-write-kick case once a socket read no longer needs a write. # # It is also why mosquitto_rr passes with no help: rr publishes its request # AFTER subscribing, and that request is the write that surfaces the response. # # THE BROKER RUNS `user root`, AND THAT IS THE ONLY MODE THAT STARTS. # src/conf.c defaults config->user to "mosquitto" (conf.c:529) and # src/mosquitto.c's drop_privileges() calls initgroups(3) then setgid/setuid # whenever geteuid()==0 and the configured user is not literally "root" -- none # of which this kernel wires (brintos/linux#19). `user root` is the one value # that skips the block entirely, via drop_privileges()'s own # `strcmp(config->user, "root")` guard. mosquitto/default-user-refused scores # the wall rather than avoiding it, so the day #19 closes this suite notices. # # OFFLINE AND NUMERIC BY CONSTRUCTION. Every client is pointed at -h 127.0.0.1, # a numeric address, so nothing here traverses getaddrinfo(3)'s dns leg -- which # is independently broken on this image (linux-in-the-browser#96) and would # otherwise turn a broker result into a resolver result. Port 1883 on loopback: # we are root, the port is free, and it is the port a real MQTT broker uses. require /usr/sbin/mosquitto mosquitto/present MQ=/usr/sbin/mosquitto D=${TMPDIR:-/tmp}/bst-mosquitto.$$ rm -rf "$D"; mkdir -p "$D" || { bst_notok mosquitto/workdir "cannot create $D"; exit 0; } PORT=1883 TOPIC=bst/mosquitto/payload SENT=bst/mosquitto/sentinel OTHER=bst/mosquitto/never-published PAYLOAD=BST-MOSQ-PAYLOAD-0a0b0c0d SPAY=BST-MOSQ-SENTINEL # db : run a command that is EXPECTED to terminate, under a # `timeout` belt. Safe only because every caller below is a command that exits # on its own; anything that might block goes through bg() instead, since # `timeout` cannot stop what it started on this image (brintos/linux#21). db() { bst_run "$@"; } # lib/bst.sh: a bound that FIRES # (timeout(1) did not, before the # brintos/linux#77 fix) and a file capture # no wedged child can hold open. # bg : run a command that MAY NEVER EXIT in the # background and poll for up to for it to finish, without wait(1) -- # wait on a process this kernel cannot kill would block until the suite bound. # The rc is carried out through a file rather than $?, which is what makes the # poll possible at all. Sets bg_done=1/0, and bg_rc/bg_out when bg_done=1. bg() { _t=$1; _tag=$2; shift 2 rm -f "$D/$_tag.rc" "$D/$_tag.out" ( "$@" > "$D/$_tag.out" 2>&1; echo $? > "$D/$_tag.rc" ) & _i=0; bg_done=0 while [ "$_i" -lt "$_t" ]; do if [ -f "$D/$_tag.rc" ]; then bg_done=1; break; fi _i=$((_i+1)); sleep 1 done bg_secs=$_i if [ "$bg_done" -eq 1 ]; then bg_rc=$(cat "$D/$_tag.rc" 2>/dev/null) bg_out=$(cat "$D/$_tag.out" 2>/dev/null) else bg_rc=""; bg_out=$(cat "$D/$_tag.out" 2>/dev/null) fi } # has ... : true iff EVERY needle is a substring. has() { _h=$1; shift for _n in "$@"; do case "$_h" in *"$_n"*) ;; *) return 1 ;; esac; done return 0 } # ---------------------------------------------------------------- inventory -- check_eq mosquitto/inventory "yes" "$([ -x "$MQ" ] && echo yes)" # The bare name a user types must be this binary; `command -v` reads root's # actual PATH, so this also catches an image on which /usr/sbin lost its place. check_eq mosquitto/on-path "$MQ" "$(command -v mosquitto 2>/dev/null)" # The -clients package. Named separately in EMCRAFT_TOOLS from the broker, so a # dropped line shows up here rather than as a mysteriously skipped criterion. for t in mosquitto_pub mosquitto_sub mosquitto_rr; do check_eq "mosquitto/client-$t" "/usr/bin/$t" "$(command -v "$t" 2>/dev/null)" done # The two apps, which ride in the main package alongside the broker. Both are # built only under WITH_APPS and both link OpenSSL, so their presence is also # the visible half of the `ssl` PACKAGECONFIG arm being kept. for t in mosquitto_passwd mosquitto_ctrl; do check_eq "mosquitto/app-$t" "/usr/bin/$t" "$(command -v "$t" 2>/dev/null)" done # CONFFILES:${PN} and the update-rc.d inherit. The init script is installed and # registered but nothing starts it -- this image's inittab runs only the tmpfs # mount and run-postinsts, never an rc runlevel -- which is what lets every # broker case below own the one broker it started itself. check mosquitto/conffile-shipped test -f /etc/mosquitto/mosquitto.conf check mosquitto/initscript-shipped test -x /etc/init.d/mosquitto # The recipe's do_install:append writes ${systemd_unitdir}/system/ # mosquitto.service UNCONDITIONALLY; what removes it again is systemd.bbclass's # rm_systemd_unitdir, which fires because DISTRO_FEATURES has no systemd. If # that stopped firing the file would be installed-but-unpackaged and the build # would fail QA -- so this is the on-image half of a build-side invariant. check_eq mosquitto/no-systemd-unitdir "" "$(ls -d /lib/systemd 2>/dev/null)" # USERADD_PARAM:${PN} creates a system user `mosquitto`. It exists so the # default drop_privileges() target resolves -- getpwnam succeeds and the failure # lands on initgroups instead, which is what makes # mosquitto/default-user-refused a statement about the kernel rather than about # a missing passwd entry. check_eq mosquitto/useradd-ran "mosquitto" "$(awk -F: '$1=="mosquitto"{print $1}' /etc/passwd 2>/dev/null)" # THE PORT'S CENTRAL DECISION, ASSERTED ON THE IMAGE. libmosquitto is patched # from SHARED to STATIC because this arm links no side modules, and the three # plugin MODULEs are switched off with WITH_PLUGINS=OFF. Nothing that ships here # may be a shared object. A red means the patch or the switch stopped applying # and the package silently grew a .so that cannot be loaded anyway. check_eq mosquitto/no-shared-library "" "$(ls /usr/lib/libmosquitto.so* /usr/lib/libmosquittopp.so* 2>/dev/null)" check_eq mosquitto/no-plugin-module "" "$(ls /usr/lib/mosquitto_dynamic_security.so 2>/dev/null)" # ----------------------------------------------------------- the binary runs -- db 30 "$MQ" --version case "$out" in *"mosquitto 2.0.22"*) bst_ok mosquitto/version-is-2.0.22 ;; *) bst_notok mosquitto/version-is-2.0.22 "want 'mosquitto 2.0.22', got '$(_bst_1line "$out")'" ;; esac # -h is answered inside config__parse_args, before any socket, config file or # credential work, so a red here separates "the binary is broken" from every # runtime case below. It returns MOSQ_ERR_UNKNOWN rather than 0, so the text is # what is asserted and not the status. db 30 "$MQ" -h case "$out" in *"Usage: mosquitto"*) bst_ok mosquitto/help-text ;; *) bst_notok mosquitto/help-text "usage text not as expected -- $(_bst_1line "$out")" ;; esac # TLS reached the CLIENT half too, which is the observable end of keeping the # `ssl` PACKAGECONFIG arm. client_shared.c compiles the --cafile/--tls-version # family only under WITH_TLS, so their appearance in the usage text is a # statement about how this binary was built, not about what it might support. db 30 mosquitto_pub --help if has "$out" "--cafile" "--tls-version"; then bst_ok mosquitto/client-tls-built-in else bst_notok mosquitto/client-tls-built-in "no TLS options in mosquitto_pub --help, so WITH_TLS did not reach the clients -- $(_bst_1line "$out")" fi # WEBSOCKETS IS THE ONE FEATURE THIS PORT SUBTRACTED, AND THE NEGATIVE IS # ASSERTED DIRECTLY. PACKAGECONFIG:remove = "websockets" because libwebsockets # is not ported to this arm; src/conf.c answers a `protocol websockets` listener # with a fixed diagnostic under #else WITH_WEBSOCKETS. The day libwebsockets # lands here this case goes red and says so, rather than the gap quietly # closing unrecorded. printf 'listener 1884 127.0.0.1\nprotocol websockets\n' > "$D/ws.conf" db 30 "$MQ" -c "$D/ws.conf" if has "$out" "Websockets support not available"; then bst_ok mosquitto/websockets-unavailable elif [ "$rc" -eq 124 ]; then bst_notok mosquitto/websockets-unavailable "a websockets listener STARTED -- libwebsockets appears to be linked in now" else bst_notok mosquitto/websockets-unavailable "want the documented 'Websockets support not available', got rc=$rc -- $(_bst_1line "$out")" fi # An unknown option must be refused rather than ignored. db 20 "$MQ" --no-such-option if [ "$rc" -eq 124 ]; then bst_notok mosquitto/rejects-unknown-option "hung on a bad option -- $(_bst_1line "$out")" elif [ "$rc" -eq 0 ]; then bst_notok mosquitto/rejects-unknown-option "accepted an unknown option -- $(_bst_1line "$out")" elif has "$out" "Unknown option"; then bst_ok mosquitto/rejects-unknown-option else bst_notok mosquitto/rejects-unknown-option "rejected (rc=$rc) but not with the expected diagnostic -- $(_bst_1line "$out")" fi # --- THE TICKET'S NAMED CRITERION ------------------------------------------- # "Start the broker on a local listener, publish a fixed payload with # mosquitto_pub and read it back with mosquitto_sub -- a deterministic # round-trip entirely on-target, with no external peer." cat > "$D/broker.conf" < "$D/broker.log" 2>&1 & bpid=$! i=0; up=0 while [ $i -lt 40 ]; do if grep -q "mosquitto version .* running" "$D/broker.log" 2>/dev/null; then up=1; break; fi kill -0 "$bpid" 2>/dev/null || break i=$((i+1)); sleep 1 done if [ "$up" -eq 1 ]; then bst_ok mosquitto/starts else bst_notok mosquitto/starts "no 'running' line after ${i}s -- $(_bst_1line "$(cat "$D/broker.log" 2>/dev/null)")"; fi # The listener is where the config put it and nowhere else. `listener # ` binds exactly one address, so a wildcard bind would mean the bind # argument was ignored. LST="" # BST-TIMEOUT-OK: ss/netstat, one-shot. Anything that may block goes through bg(). if command -v ss >/dev/null 2>&1; then LST=$(timeout 20 ss -ltn 2>/dev/null) elif command -v netstat >/dev/null 2>&1; then LST=$(timeout 20 netstat -ltn 2>/dev/null); fi if [ -z "$LST" ]; then bst_notok mosquitto/listens-loopback-only "neither ss nor netstat listed any TCP listener" elif has "$LST" "127.0.0.1:$PORT"; then case "$LST" in *"0.0.0.0:$PORT"*) bst_notok mosquitto/listens-loopback-only "bound the wildcard address as well as loopback -- $(_bst_1line "$LST")" ;; *) bst_ok mosquitto/listens-loopback-only ;; esac else bst_notok mosquitto/listens-loopback-only "no 127.0.0.1:$PORT TCP listener -- $(_bst_1line "$LST")" fi # PUBLISH. Retained (-r) so the broker holds the message for the subscriber that # has not connected yet; see the header for why that ordering is what makes the # round-trip terminate on this image at all. bg 30 pub mosquitto_pub -h 127.0.0.1 -p "$PORT" -t "$TOPIC" -m "$PAYLOAD" -r if [ "$bg_done" -ne 1 ]; then bst_notok mosquitto/publish "mosquitto_pub did not return within ${bg_secs}s -- $(_bst_1line "$bg_out")" elif [ "$bg_rc" -eq 0 ]; then bst_ok mosquitto/publish else bst_notok mosquitto/publish "rc=$bg_rc -- $(_bst_1line "$bg_out")" fi # The sentinel, published the same way, is what makes the negative case below # terminate: a subscription that names both topics is satisfied by this one. bg 30 pubs mosquitto_pub -h 127.0.0.1 -p "$PORT" -t "$SENT" -m "$SPAY" -r check_eq mosquitto/publish-sentinel "0" "${bg_rc:-timeout}" # READ IT BACK. -C 1 is satisfied in the message callback the moment the broker # delivers the retained message at subscribe time, so this returns without any # clock, alarm or signal being involved. On a kernel with alarm(2) wired but # without the brintos/linux#127 cancel this case went red: a stale ITIMER_REAL # left by the `timeout 20 ss' above killed a later subshell. A red here on a # kernel with the brintos/linux#77 fix points at #127 first. bg 30 sub mosquitto_sub -h 127.0.0.1 -p "$PORT" -t "$TOPIC" -C 1 -k 5 if [ "$bg_done" -ne 1 ]; then bst_notok mosquitto/subscribe "mosquitto_sub did not return within ${bg_secs}s -- $(_bst_1line "$bg_out")" elif [ "$bg_rc" -ne 0 ]; then bst_notok mosquitto/subscribe "rc=$bg_rc -- $(_bst_1line "$bg_out")" else bst_ok mosquitto/subscribe fi # THE ROUND-TRIP ITSELF: byte-for-byte, not merely "something arrived". This is # the case the ticket is written against. check_eq mosquitto/roundtrip-payload "$PAYLOAD" "$bg_out" # QoS 1 is a different path through the broker -- PUBACK rather than fire and # forget -- so it gets its own publish and its own read-back rather than being # assumed from the QoS 0 result above. bg 30 pub1 mosquitto_pub -h 127.0.0.1 -p "$PORT" -t "$TOPIC/qos1" -m "$PAYLOAD-Q1" -r -q 1 check_eq mosquitto/publish-qos1 "0" "${bg_rc:-timeout}" bg 30 sub1 mosquitto_sub -h 127.0.0.1 -p "$PORT" -t "$TOPIC/qos1" -C 1 -q 1 -k 5 check_eq mosquitto/roundtrip-qos1 "$PAYLOAD-Q1" "$bg_out" # A WILDCARD SUBSCRIPTION MATCHES A DIFFERENT LOOKUP PATH from an exact topic, # and -v prints " " so the mapping is checked and not just the # set of payloads. Three retained messages exist under bst/mosquitto/# by now, # so -C 3 terminates on its own. bg 40 subw mosquitto_sub -h 127.0.0.1 -p "$PORT" -t 'bst/mosquitto/#' -C 3 -v -k 5 if [ "$bg_done" -ne 1 ]; then bst_notok mosquitto/wildcard-subscription "wildcard subscriber did not return within ${bg_secs}s -- $(_bst_1line "$bg_out")" elif has "$bg_out" "$TOPIC $PAYLOAD" "$SENT $SPAY" "$TOPIC/qos1 $PAYLOAD-Q1"; then bst_ok mosquitto/wildcard-subscription else bst_notok mosquitto/wildcard-subscription "want all three topic/payload pairs, got $(_bst_1line "$bg_out")" fi # THE COMPLEMENT, AND WHAT KEEPS EVERY GREEN ABOVE MEANINGFUL: a topic nothing # ever published must deliver nothing. Bounded WITHOUT a timeout by naming the # sentinel alongside it -- -C 1 is satisfied by the sentinel, and the assertion # is that the message which arrived was the sentinel's rather than anything # attributed to the never-published topic. A subscriber on $OTHER alone would # simply never return here. bg 30 subn mosquitto_sub -h 127.0.0.1 -p "$PORT" -t "$OTHER" -t "$SENT" -C 1 -v -k 5 if [ "$bg_done" -ne 1 ]; then bst_notok mosquitto/unpublished-topic-silent "subscriber did not return within ${bg_secs}s -- $(_bst_1line "$bg_out")" elif has "$bg_out" "$OTHER"; then bst_notok mosquitto/unpublished-topic-silent "a topic nothing published delivered a message -- $(_bst_1line "$bg_out")" elif has "$bg_out" "$SENT $SPAY"; then bst_ok mosquitto/unpublished-topic-silent else bst_notok mosquitto/unpublished-topic-silent "want the sentinel and nothing on $OTHER, got $(_bst_1line "$bg_out")" fi # The broker's own log is the provenance half: an answer alone cannot say the # broker handled it. log_type all records each client's connection by id. if has "$(cat "$D/broker.log" 2>/dev/null)" "New client connected" ; then bst_ok mosquitto/log-records-clients else bst_notok mosquitto/log-records-clients "broker log carries no client connection -- $(_bst_1line "$(cat "$D/broker.log" 2>/dev/null)")" fi # mosquitto_rr is the third client: publish a request and wait for one response # on a reply topic. The reply topic here already carries a RETAINED message, so # the response arrives at subscribe time and rr terminates -- the same trick the # round-trip uses, for the same reason. bg 30 rr mosquitto_rr -h 127.0.0.1 -p "$PORT" -t "$SENT" -e "$TOPIC" -m ping if [ "$bg_done" -ne 1 ]; then bst_notok mosquitto/rr-response "mosquitto_rr did not return within ${bg_secs}s -- $(_bst_1line "$bg_out")" elif has "$bg_out" "$PAYLOAD"; then bst_ok mosquitto/rr-response else bst_notok mosquitto/rr-response "want the retained payload on the response topic, rc=$bg_rc -- $(_bst_1line "$bg_out")" fi # --- mosquitto_passwd, which is the OpenSSL link exercised ------------------ # -c creates the file, -b takes the password on the command line. The hash is # PBKDF2-SHA512 through EVP, so a working output line is also a statement that # libcrypto linked and runs on this arm. db 60 mosquitto_passwd -c -b "$D/pwfile" bstuser bstpass if [ "$rc" -ne 0 ]; then bst_notok mosquitto/passwd-create "rc=$rc -- $(_bst_1line "$out")" elif [ ! -s "$D/pwfile" ]; then bst_notok mosquitto/passwd-create "exit 0 but no password file was written" else bst_ok mosquitto/passwd-create fi # The written line is user:$7$$$ -- $7$ is mosquitto's own # PBKDF2-SHA512 marker, so this checks the crypto path produced a real digest # rather than an empty or plaintext field. if has "$(cat "$D/pwfile" 2>/dev/null)" 'bstuser:$7$'; then bst_ok mosquitto/passwd-pbkdf2-hash else bst_notok mosquitto/passwd-pbkdf2-hash "want a \$7\$ PBKDF2-SHA512 entry for bstuser, got $(_bst_1line "$(cat "$D/pwfile" 2>/dev/null)")" fi # --- the platform walls, scored rather than avoided -------------------------- # THE WRITE-KICK WALL ITSELF, with the workaround taken off. Same subscriber as # mosquitto/subscribe above and the same topic -- which still carries its # retained message, so the broker has something to deliver the moment the # SUBACK goes out -- but at the DEFAULT 60 s keepalive, so the client puts # nothing on the wire for a minute. If it returns within 20 s then a socket read # no longer needs a write and brintos/linux#64 is fixed: drop the `-k 5` from # every subscriber above and delete this case. bg 20 subnk mosquitto_sub -h 127.0.0.1 -p "$PORT" -t "$TOPIC" -C 1 if [ "$bg_done" -eq 1 ]; then bst_ok mosquitto/inbound-needs-write-kick else bst_notok mosquitto/inbound-needs-write-kick "brintos/linux#64 (a socket read needs a write first): the broker sent the retained message at subscribe time and a default-keepalive subscriber still had not seen it after ${bg_secs}s; the same subscriber with -k 5 gets it on the line after its first PINGREQ" fi # mosquitto_sub -W FIRES, and this is the case that says so. -W is alarm(2) + # SIGALRM (client/sub_client.c); on a kernel without the brintos/linux#77 fix # alarm(2) is ENOSYS and -W never fires. Expected green on a kernel with the # fix, where a red is a regression of it; red by design on a kernel pin without # it. Run in the BACKGROUND deliberately: a -W that does not fire, # on a topic with no message, would never return in the foreground and would # cost the suite its remaining budget. Such a process is left running -- the # same accommodation 88-dnsmasq makes for its daemon. bg 20 subw3 mosquitto_sub -h 127.0.0.1 -p "$PORT" -t "$OTHER" -C 1 -W 3 if [ "$bg_done" -eq 1 ]; then bst_ok mosquitto/sub-W-timeout else bst_notok mosquitto/sub-W-timeout "-W 3 did not fire after ${bg_secs}s: mosquitto_sub implements it as alarm(2) + SIGALRM, so on a kernel with the brintos/linux#77 fix this is a regression; on a kernel pin without it the case reads red by design" fi # THE DEFAULT PRIVILEGE DROP, which is why every case above passes `user root`. # Same config with that one line removed: config->user falls back to # "mosquitto" (src/conf.c:529), the useradd entry asserted above makes getpwnam # succeed, and drop_privileges() then reaches initgroups(3). This terminates on # its own -- the broker returns 1 and exits -- so it is safe in the foreground. grep -v '^user root$' "$D/broker.conf" | sed -e "s/^listener $PORT/listener 1885/" > "$D/default-user.conf" db 60 "$MQ" -c "$D/default-user.conf" if [ "$rc" -eq 0 ]; then bst_notok mosquitto/default-user-refused "the broker daemonised under its default user -- unexpected here" elif [ "$rc" -eq 124 ]; then bst_notok mosquitto/default-user-refused "hung instead of failing or starting -- $(_bst_1line "$out")" elif bst_execfail "$out"; then bst_unrun mosquitto/default-user-refused "$out" elif has "$out" "Function not implemented" && \ { has "$out" "Error setting groups whilst dropping privileges" || has "$out" "Error setting gid" || has "$out" "Error setting uid"; }; then bst_notok mosquitto/default-user-refused "brintos/linux#19 (no credential setters wired): the stock config drops privileges to user 'mosquitto', so initgroups(3)/setgid(2) refuse and the broker cannot start at all -- rc=$rc $(_bst_1line "$out")" else bst_notok mosquitto/default-user-refused "rc=$rc -- $(_bst_1line "$out")" fi # SIGTERM is mosquitto's documented shutdown and it does not arrive: this kernel # delivers no signal at all, SIGKILL included. Asserted rather than skipped # because "the broker cannot be stopped" is a real property of running mosquitto # on this image, and the case flips green by itself when #21 closes. NEVER # wait(1) on $bpid afterwards. kill -TERM "$bpid" 2>/dev/null i=0 while [ $i -lt 5 ] && kill -0 "$bpid" 2>/dev/null; do i=$((i+1)); sleep 1; done if kill -0 "$bpid" 2>/dev/null; then kill -KILL "$bpid" 2>/dev/null sleep 1 if kill -0 "$bpid" 2>/dev/null; then bst_notok mosquitto/sigterm-stops "brintos/linux#21 (signal delivery does not work at all): the broker survived SIGTERM for ${i}s and then SIGKILL, so no signal reached it" else bst_notok mosquitto/sigterm-stops "ignored SIGTERM for ${i}s but died on SIGKILL -- that is mosquitto's handler, not the kernel" fi else bst_ok mosquitto/sigterm-stops fi rm -rf "$D"