. "$BST_LIB" # sysklogd is the first DAEMON ported to this image -- every package before it is # a program that runs, prints and exits. That difference is what this suite is # shaped around: nothing here can be asserted from one command's output, because # the answer is produced by a second process that has to come up, bind a socket, # be handed a message by a third process, and write a file. So every assertion # below is "start it, hand it something, wait for the file to say so", and the # waiting is done by POLLING, never by sleeping: # # sleep(1) does not sleep here. Measured on this image, `sleep 1`, `sleep 2`, # `sleep 3` and `sleep 5` all return after ~0.29 s -- the relative-timer path # (linux-in-the-browser#120). So a `sleep N` is a yield of unknown length, not # a delay, and wait_for() below uses it only to let the daemon be scheduled # while it re-tests the real condition. The iteration bound, not the argument # to sleep, is what keeps a failing case inside the runner's 60 s suite budget. # # sysklogd 2.5.2 has NO separate klogd: the kernel-log reader that used to be its # own daemon is inside syslogd, which reads /dev/kmsg directly. So the kern-log # cases below are what covers "klogd" on this port; there is no klogd binary to # look for, and its absence is not a gap. require syslogd sysklogd/present D=/tmp/.bst-sysklogd CONF=$D/sk.conf ALL=$D/all.log KERN=$D/kern.log ERR=$D/err.log SEQNO=$D/kmsg.seqno # wait_for -- poll until appears, bounded. Returns 1 on # timeout so a caller can report the real condition rather than hanging. One # iteration costs about a second (the grep fork dominates; the sleep does not # sleep), and 25 of them is chosen against the runner's 60 s budget: a single # genuinely-failing wait must not be able to spend the whole suite's time and # turn every case after it into a suite-timeout. wait_for() { _f=$1; _n=$2; _i=0 while [ $_i -lt 25 ]; do grep -q "$_n" "$_f" 2>/dev/null && return 0 _i=$((_i + 1)); sleep 1 done return 1 } # wait_true -- the same bounded poll for a condition with no file # to grep. wait_true() { _i=0 while [ $_i -lt 25 ]; do if "$@"; then return 0; fi _i=$((_i + 1)); sleep 1 done return 1 } # check_wait -- ok iff shows up in in time. check_wait() { if wait_for "$2" "$3"; then bst_ok "$1" else bst_notok "$1" "'$3' never appeared in $2: $(_bst_1line "$(tail -3 "$2" 2>&1)")"; fi } rm -rf "$D"; mkdir -p "$D" # --- identity and inventory -------------------------------------------------- check_out sysklogd/version "sysklogd v2.5.2" syslogd -v # The three files the package owns, at the paths oe-core's recipe puts them. # sbindir is /usr/sbin here, NOT /sbin -- which is the whole reason the next case # can be written as an absence check. inv="" for f in /usr/sbin/syslogd /etc/syslog.conf /etc/init.d/syslog; do [ -e "$f" ] || inv="$inv $f" done check_eq sysklogd/inventory "" "$inv" # busybox-syslog is EXCLUDED from this image (PACKAGE_EXCLUDE): it and sysklogd # RCONFLICT each other, both owning /etc/syslog.conf and /etc/init.d/syslog. Its # alternatives were /sbin/syslogd and /sbin/klogd -> busybox.nosuid, so their # absence is what proves the exclusion took and that a bare `syslogd` can only be # this port. (busybox's own logger and logread applets are in the MAIN busybox # package and survive -- logger is what feeds the daemon below.) bb="" [ -e /sbin/syslogd ] && bb="$bb /sbin/syslogd" [ -e /sbin/klogd ] && bb="$bb /sbin/klogd" check_eq sysklogd/no-busybox-syslog "" "$bb" check_eq sysklogd/resolves-to-port "/usr/sbin/syslogd" "$(command -v syslogd)" # --- the daemon: one instance, most of the suite ------------------------------ # `*.*` catches everything including kern, while the two narrow selectors are # what make the routing assertions falsifiable: a message must land in all.log # AND in exactly the narrow file its selector names. printf '*.*\t-%s\nkern.*\t-%s\nuser.err\t-%s\n' "$ALL" "$KERN" "$ERR" > "$CONF" # -F: foreground. Not a preference -- the daemonising path is exercised on its # own further down, but every case here needs a process this shell owns and can # signal, and one that dies with the suite if an assertion aborts it. # # -C is not decoration either. syslogd remembers how far it has read /dev/kmsg in # a seqno cache (default /run/syslogd.cache) and resumes from there, on the # assumption that the file is wiped at reboot. On this image the root filesystem # is writable in some boots, so a cache written by an earlier syslogd survives and # the next one legitimately reads NOTHING from the kernel ring -- which is exactly # how the kern.log cases below silently failed the first time they were written. # Pointing -C at this run's own scratch dir makes the read deterministic. syslogd -F -m 0 -P "$D/pid" -C "$SEQNO" -f "$CONF" & SP=$! check_wait sysklogd/daemon-start "$ALL" "syslogd v2.5.2: restart." # The UNIX socket is created by init(), i.e. after the restart banner, so this is # ordered after the wait above rather than racing it. if wait_true test -S /dev/log; then bst_ok sysklogd/dev-log-socket else bst_notok sysklogd/dev-log-socket "/dev/log is not a socket after the daemon logged its banner"; fi pid=$(cat "$D/pid" 2>/dev/null) case "$pid" in [0-9]*) bst_ok sysklogd/pidfile ;; *) bst_notok sysklogd/pidfile "no pid in $D/pid: '$pid'" ;; esac # --- delivery through /dev/log ------------------------------------------------ # The client is busybox's logger applet, which speaks glibc syslog(3) to # /dev/log. Using it rather than a sysklogd-provided one is deliberate: oe-core # builds this recipe --without-logger, so the port ships no client of its own and # what has to work is the INTERFACE, not a matched pair of our own binaries. logger -t bstsk -p user.info "SYSKLOGD-INFO-7301" check_wait sysklogd/unix-delivery "$ALL" "SYSKLOGD-INFO-7301" check_out sysklogd/tag "bstsk: SYSKLOGD-INFO-7301" cat "$ALL" logger -t bstsk -p user.err "SYSKLOGD-ERR-7302" check_wait sysklogd/facility-select "$ERR" "SYSKLOGD-ERR-7302" # The other half of the same claim, and the one that would go unnoticed if only # the case above were written: user.err must NOT also collect the user.info # message. A selector that matched everything would pass facility-select. if grep -q "SYSKLOGD-INFO-7301" "$ERR" 2>/dev/null; then bst_notok sysklogd/priority-filter "user.err collected a user.info message" else bst_ok sysklogd/priority-filter fi # --- the klogd half: /dev/kmsg ------------------------------------------------ # This is what replaces the klogd daemon in sysklogd 2.x. /dev/kmsg is readable # on this kernel only in whole records: a short read returns EINVAL (so # `head -c 200 /dev/kmsg` fails, which is the documented contract, not a bug). # syslogd reads it with a full-size buffer and re-emits each record as a kern.* # message, which is what the two cases below check -- that the file fills at all, # and that its first line is the kernel's own boot banner rather than something # a user-space message could have produced. check_wait sysklogd/kernel-log "$KERN" "kernel:" check_out sysklogd/kernel-log-banner "kernel: Linux version" head -1 "$KERN" # --- the remote-syslog receive socket ---------------------------------------- # Nothing here sends over UDP -- with no route off lo there is nowhere to send # from. What is checkable is that the listener the daemon opens by default # (:514, since this conf sets no secure_mode) actually binds on this kernel. if command -v netstat >/dev/null 2>&1; then check_out sysklogd/udp514 "0.0.0.0:514" netstat -lun else bst_skip sysklogd/udp514 "netstat absent (net-tools not installed)" fi # --- SIGHUP reload ------------------------------------------------------------ # init() re-logs the restart banner on every reload, so a second copy is the # signal that the reload ran. It does not appear on the signal alone: syslogd # installs its handlers with SA_RESTART and blocks in select(), so the reload # flag is latched and acted on at the next socket event -- hence the message # after the signal. (On a stock Linux select() is not restarted and the reload is # immediate; needing the nudge is a difference this runtime introduces.) kill -HUP "$SP" 2>/dev/null logger -t bstsk "SYSKLOGD-HUP-NUDGE-7303" twice() { [ "$(grep -c 'restart\.' "$ALL" 2>/dev/null)" -ge 2 ]; } if wait_true twice; then bst_ok sysklogd/sighup-reload else bst_notok sysklogd/sighup-reload "only $(grep -c 'restart\.' "$ALL" 2>/dev/null) restart banner(s) after SIGHUP + a message"; fi # --- shutdown ----------------------------------------------------------------- kill "$SP" 2>/dev/null check_wait sysklogd/term-exit-logged "$ALL" "exiting on signal 15" # socket_close() unlinks the AF_UNIX path, so /dev/log goes away with the daemon. if wait_true test '!' -S /dev/log; then bst_ok sysklogd/socket-unlink else bst_notok sysklogd/socket-unlink "/dev/log still present after the daemon logged its exit"; fi # --- daemonising, custom socket, rotation ------------------------------------- # Backgrounding is its own case because it is a different code path, not a flag: # waitdaemon() forks, the child setsid()s and the parent waits to be signalled by # it before exiting. setsid is one of the calls the image's inittab avoids, so # whether this path works here is a real question rather than a formality. rm -f "$ALL" syslogd -K -m 0 -P "$D/dpid" -f "$CONF"; drc=$? check_eq sysklogd/daemonize 0 "$drc" check_wait sysklogd/daemonize-banner "$ALL" "syslogd v2.5.2: restart." dpid=$(cat "$D/dpid" 2>/dev/null) case "$dpid" in [0-9]*) bst_ok sysklogd/daemonize-pidfile ;; *) bst_notok sysklogd/daemonize-pidfile "no pid: '$dpid'" ;; esac [ -n "$dpid" ] && kill "$dpid" 2>/dev/null # -p names the listening socket and -r turns on size-based rotation; one daemon # covers both. -p REPLACES the default rather than adding to it, so /dev/log is # passed explicitly alongside the extra path -- which is also what makes the two # assertions independent: the extra socket has to appear at its own path, while # the messages that drive rotation still arrive through /dev/log from busybox # logger (which has no way to address any other socket). rm -f "$ALL" syslogd -F -K -m 0 -P "$D/rpid" -f "$CONF" -p /dev/log -p "$D/mysock" -r 1k:3 & RP=$! check_wait sysklogd/rotate-start "$ALL" "syslogd v2.5.2: restart." if wait_true test -S "$D/mysock"; then bst_ok sysklogd/custom-socket else bst_notok sysklogd/custom-socket "no socket at $D/mysock"; fi # ~250 bytes of payload per line, so eight lines comfortably clear the 1 KiB # threshold. Kept to eight on purpose: each logger is a fork+exec costing about a # second here, so the burst is the suite's second-largest time item, and a long # one is also what BRINTOS-78 (execve grow-to-fit ENOMEM) trips on. PAD=ROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPADROTPAD n=0 while [ $n -lt 8 ]; do logger -t rot "$PAD-$n" n=$((n + 1)) done if wait_true test -f "$ALL.0"; then bst_ok sysklogd/rotate else bst_notok sysklogd/rotate "no rotated generation $ALL.0 after 8x250B past a 1k threshold ($(wc -c < "$ALL" 2>/dev/null) B in $ALL)"; fi kill "$RP" 2>/dev/null # --- contracts a caller must not misread -------------------------------------- # An UNKNOWN option exits 0. getopt(3) returns '?' for one, and '?' is also the # spelling of this program's own help flag, so the two share `return usage(0)`. # The diagnostic is on stderr and the status is success -- so a caller that # branches on the exit status will treat a typo'd flag as a clean run. This is # upstream sysklogd behaviour, not a wasm32 artefact, and it is asserted here so # that a future version changing it is caught rather than assumed. out=$(syslogd --no-such-option 2>&1); rc=$? case "$out" in *"Usage:"*) hit=1 ;; *) hit=0 ;; esac if [ "$rc" -eq 0 ] && [ "$hit" -eq 1 ]; then bst_ok sysklogd/badopt-exit0 else bst_notok sysklogd/badopt-exit0 "rc=$rc (want 0) usage_hit=$hit: $(_bst_1line "$out")"; fi # A missing -f file is not fatal either: cftemp() synthesises a fallback config # sending err+panic to the console, so the daemon still comes up. rm -f "$D/fb.pid" syslogd -K -m 0 -P "$D/fb.pid" -f "$D/no-such-file.conf" 2>/dev/null if wait_true test -s "$D/fb.pid"; then bst_ok sysklogd/missing-conf-fallback else bst_notok sysklogd/missing-conf-fallback "daemon did not come up with a nonexistent -f file"; fi fbp=$(cat "$D/fb.pid" 2>/dev/null); [ -n "$fbp" ] && kill "$fbp" 2>/dev/null # --- what this image cannot reach --------------------------------------------- # Each of these flips on its own if the platform gains what it names; none needs # this file edited to stay honest. bst_skip sysklogd/var-log "stock /etc/syslog.conf logs to /var/log/*, and /var/log -> volatile/log is a dangling symlink on this image (nothing mounts or populates /var/volatile) -- all six default files fail to open" bst_skip sysklogd/logread "busybox logread reads busybox-syslogd's SysV shm ring; busybox-syslog is excluded here and shmget returns ENOSYS on this kernel" # BRINTOS-WORKAROUND: linux-in-the-browser#120 remove — this case is SKIPPED, so it will never flip green by itself; drop the skip and let it run once the issue closes. bst_skip sysklogd/mark "-m MARK is driven by timer_add()->alarm(); relative timers do not elapse on this runtime (linux-in-the-browser#120)" bst_skip sysklogd/wall "*.=emerg to '*' writes to logged-in users via utmp; this image has no utmp database and no login sessions" bst_skip sysklogd/remote-send "forwarding to @host needs a second machine or a second daemon contending for /dev/log; the receive half is covered by sysklogd/udp514" rm -rf "$D"