. "$BST_LIB" # BST-TIMEOUT: 600 # lighttpd 1.4.74 -- Jan Kneschke's lightweight HTTP server (oe-core # recipes-extended/lighttpd). This image's FIRST HTTP SERVER: the networking tier # until now was clients and probes plus per-protocol daemons (dropbear, dnsmasq, # mosquitto, the inetutils set, xinetd), and nothing served HTTP. # # THE PORT IS ONE meson OPTION AND EVERY CASE BELOW IS DOWNSTREAM OF IT. # lighttpd's modules are ordinarily dlopen(3)'d mod_*.so, split into # lighttpd-module-* packages; a static-only arm can host none of that. # -Dbuild_static=true is upstream's own answer -- the module list becomes empty so # no shared object is declared, every module source is linked into the lighttpd # binary, and LIGHTTPD_STATIC makes src/plugin.c dispatch through a compiled-in # table instead of dlopen. So the interesting question on target is not "does the # binary run" but "did the modules SURVIVE THE MOVE" -- a config names them by the # same string either way, and only the running server can say whether the name # still resolves. lighttpd/shipped-conf, /mod-access-deny, /dirlisting and # /accesslog are that question asked four ways, and they are why this suite spends # its cases on module-provided behaviour rather than on more ways to fetch a file. # NOT A WORKAROUND: nothing here is held by a brintOS defect, so no # BRINTOS-WORKAROUND marker. build_static is a first-class upstream configuration # for platforms without dynamic loading. # # HTTP/2 IS THE ONE THING GENUINELY LOST, and to upstream's construction rather # than to anything wasm32 did: mod_h2 is absent from the builtin_mods list # build_static populates, so it is the single module that does not move into the # binary. lighttpd/http2-accepted-not-loaded scores that deliberately -- an # absence a later reader would otherwise have to rediscover, and one whose exact # shape is easy to get wrong, since naming mod_h2 is ACCEPTED rather than refused # (see the case for upstream's special path). # # NO PRIVILEGE DROP IS ATTEMPTED, so brintos/linux#19 never engages here. # mosquitto's stock config drops to user "mosquitto" and therefore cannot start at # all on a kernel with initgroups/setgid/setuid unwired; lighttpd's stock config # leaves server.username and server.groupname commented out, so it never reaches # that path, and the generated config below sets neither for the same reason # rather than by accident. # # IT STILL NEEDS ONE CONFIG LINE TO START, JUST A DIFFERENT ONE: epoll is unusable # here (brintos/linux#53) and it is lighttpd's default event handler, so # server.event-handler must be set to poll. The full reasoning is at that line; # lighttpd/epoll-is-the-default-and-fails is the case that keeps it honest. # # THE SERVER RUNS UNDER -D AND IS BACKGROUNDED BY THIS SHELL, NEVER DAEMONIZED. # Forking would leave a child this suite cannot name and cannot signal # (brintos/linux#21 -- no signal is delivered, SIGKILL included), so the one # process is started in the foreground and put in the background here, where $! # is a pid we hold. Nothing below ever wait(1)s on it. # # OFFLINE AND NUMERIC BY CONSTRUCTION. Every request goes to 127.0.0.1 by # address, so nothing traverses getaddrinfo(3)'s dns leg -- independently broken # on this image (linux-in-the-browser#96) and it would otherwise turn a web-server # result into a resolver result. Port 8080 on loopback: we are root, it is free, # and it is not 80, so a stray lighttpd started from the shipped init script could # not be mistaken for this one. require /usr/sbin/lighttpd lighttpd/present LT=/usr/sbin/lighttpd D=${TMPDIR:-/tmp}/bst-lighttpd.$$ rm -rf "$D"; mkdir -p "$D/www/sub" || { bst_notok lighttpd/workdir "cannot create $D"; exit 0; } PORT=8080 http_proxy=; https_proxy=; no_proxy=127.0.0.1; export http_proxy https_proxy no_proxy # bg : run something that MAY NEVER RETURN in the # background and poll up to for it to finish, without wait(1) -- waiting 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. 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 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 } # hreq : send a raw CRLF request and capture the WHOLE # response -- status line, headers and body -- into $D/.raw. HTTP/1.0 with no # keep-alive, so the server closes and nc returns on its own. Raw rather than wget # because the status line and the headers are the assertion in most cases below, # and because it is a second, independent client beside wget: a result both agree # on cannot be a quirk of either. The client WRITES before it reads, which is the # direction brintos/linux#64 leaves working. # # EVERY REQUEST GOES THROUGH bg's POLL, and that is the difference between one # broken case and a lost suite. A server that accepts the connection and then # never answers -- the shape brintos/linux#64 would produce -- leaves nc reading # forever, and `timeout` cannot stop what it started here (brintos/linux#21). So # each request is bounded at 25 s and a hang becomes this case's own `not ok` # rather than the whole file's suite-timeout. $SRV_UP short-circuits the rest once # the server is known not to be listening, so a dead daemon costs one wait, not # fifteen. _hraw() { _f=$1; shift { for _l in "$@"; do printf '%s\r\n' "$_l"; done; printf '\r\n'; } \ | nc 127.0.0.1 "$PORT" > "$_f" 2>/dev/null } hreq() { _tag=$1; shift : > "$D/$_tag.raw" [ "${SRV_UP:-1}" -eq 1 ] || return 1 bg 25 "q_$_tag" _hraw "$D/$_tag.raw" "$@" [ "$bg_done" -eq 1 ] && [ "$bg_rc" = "0" ] } ## ## 1. The shipped artifacts, and the shape build_static gave them ## out=$("$LT" -v 2>&1) if has "$out" "lighttpd/1.4.74"; then bst_ok lighttpd/version else bst_notok lighttpd/version "$(_bst_1line "$out")"; fi # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here is a conversion, not a policy breach. bst_lib_shape lighttpd/no-shared-modules "/usr/lib/mod_*.so" "/usr/lib/*/mod_*.so" \ "/lib/mod_*.so" "/lib/*/mod_*.so" check lighttpd/angel-present test -x /usr/sbin/lighttpd-angel check lighttpd/initscript test -x /etc/init.d/lighttpd check lighttpd/shipped-docroot test -f /www/pages/index.html check lighttpd/shipped-conf-file test -f /etc/lighttpd/lighttpd.conf # oe-core's STOCK config loads mod_access and mod_accesslog BY NAME. -tt is a full # startup test -- it parses, resolves every named module and initialises the # plugins, then exits without serving -- so this passing is the on-target proof # that a name written for the dlopen build resolves against the static table # unchanged. It is the single most load-bearing case in the file. 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. db 20 "$LT" -tt -f /etc/lighttpd/lighttpd.conf if [ "$rc" -eq 0 ]; then bst_ok lighttpd/shipped-conf else bst_notok lighttpd/shipped-conf "stock config rejected: rc=$rc $(_bst_1line "$out")"; fi # WHAT -tt DOES NOT CHECK, recorded here because the pair above and below reads # like a contradiction otherwise: -tt resolves modules and validates syntax, but # it does not open the error log or stat server.upload-dirs. So a config can pass # -tt and still refuse to listen -- which is exactly what happens on this image # with any config that leaves those at their defaults. # # THE STOCK CONFIG CANNOT SERVE ON THIS IMAGE, and the reason is the image's, not # lighttpd's. /var/tmp -> /var/volatile/tmp and /var/log -> /var/volatile/log are # both DANGLING here: /etc/inittab's only sysinit mount is the /tmp tmpfs, and # nothing populates /var/volatile at all. So oe-core's lighttpd.conf, whose # server.errorlog is /www/logs/lighttpd.error.log (-> /var/log) and whose # upload-dirs defaults to /var/tmp, has nowhere to write. This case scores that # directly rather than through lighttpd, so it stays true for the other daemons on # this image that write to /var/log (sysklogd, cronie, logrotate) and flips green # by itself the day the image populates /var/volatile. Ours, not brintOS's: # BRINTOS-626. if [ -d /var/tmp ] && [ -d /var/log ]; then bst_ok lighttpd/var-volatile-populated else bst_notok lighttpd/var-volatile-populated "BRINTOS-626: /var/volatile is never populated (inittab mounts only /tmp), so /var/tmp$([ -d /var/tmp ] || echo ' [dangling]') and /var/log$([ -d /var/log ] || echo ' [dangling]') do not resolve -- the SHIPPED lighttpd.conf cannot start a server on this image, though the binary and every module are fine" fi ## ## 2. A generated document root and config -- the ticket's own terms ## PAYLOAD=BST-LIGHTTPD-PAYLOAD-0a0b0c0d printf '%s\n' "$PAYLOAD" > "$D/www/index.html" printf 'plain text %s\n' "$PAYLOAD" > "$D/www/hello.txt" printf 'denied %s\n' "$PAYLOAD" > "$D/www/keep.secret" printf 'in a subdirectory\n' > "$D/www/sub/leaf.txt" # A BINARY payload built from every byte value 1..255 (NUL excluded only because # it cannot survive shell command substitution), repeated to ~32 KB so the # response crosses lighttpd's network_write chunking rather than fitting in one # buffer. Deterministic and generated ON TARGET: the comparison below is against # the very bytes this machine wrote, so it needs no oracle and no network. esc=''; i=1 while [ "$i" -lt 256 ]; do esc="$esc\\$(printf '%03o' "$i")"; i=$((i+1)); done printf "$esc" > "$D/block.bin" : > "$D/www/payload.bin" i=0 while [ "$i" -lt 128 ]; do cat "$D/block.bin" >> "$D/www/payload.bin"; i=$((i+1)); done BINSZ=$(wc -c < "$D/www/payload.bin" | tr -d ' ') check_eq lighttpd/payload-built 32640 "$BINSZ" # EVERY PATH IN THIS CONFIG IS UNDER $D, AND server.upload-dirs IS THE REASON IT # HAS TO BE SPELLED OUT. Its default is /var/tmp, which does not resolve on this # image: /var/tmp is a symlink to /var/volatile/tmp, /var/log likewise to # /var/volatile/log, and nothing ever populates /var/volatile -- the inittab's only # sysinit mount is the /tmp tmpfs. lighttpd checks upload-dirs during startup and # refuses to listen without it ("server.upload-dirs /var/tmp: No such", # configfile.c:2816), so leaving the default here would have every serving case # below fail for a reason that has nothing to do with lighttpd. That gap is the # image's and is scored on its own terms by lighttpd/var-volatile-populated below; # pointing the suite's own paths at $D is not a workaround for it but the # self-contained document root the ticket asked for. # # server.event-handler IS A WORKAROUND, and unlike the paths above it is held by a # brintOS defect. lighttpd compiles in select, poll and epoll (`lighttpd -V`) and # picks linux-sysepoll by default; epoll_create/epoll_create1 return ENOSYS on # this kernel, so fdevent_init fails and the server NEVER REACHES listen(2): # (fdevent_impl.c.240) event-handler failed: linux-sysepoll; # try to set server.event-handler = "poll" or "select" # (server.c.1986) fdevent_init failed # "poll" is upstream's own first suggestion and both poll and select were measured # working here -- each brings the listener up and serves a real 200 with the right # body. This is also why the SHIPPED /etc/lighttpd/lighttpd.conf cannot serve on # this image: it names no event-handler, so it inherits the epoll default. # lighttpd/epoll-is-the-default-and-fails scores that wall directly, so this one # line's reason stays visible and the day #53 closes is noticed. # BRINTOS-WORKAROUND: brintos/linux#53 remove — drop this server.event-handler # line and the epoll case below once epoll_create is wired. mkdir -p "$D/upload" cat > "$D/lighttpd.conf" < "text/html", ".txt" => "text/plain", ".bin" => "application/octet-stream" ) url.access-deny = ( ".secret" ) \$HTTP["url"] =~ "^/sub/" { dir-listing.activate = "enable" } EOF db 20 "$LT" -tt -f "$D/lighttpd.conf" if [ "$rc" -eq 0 ]; then bst_ok lighttpd/conf-valid else bst_notok lighttpd/conf-valid "rc=$rc $(_bst_1line "$out")"; fi # The negative that keeps the positive honest: -tt must REFUSE a config naming a # module that does not exist, or "it accepted mod_access" would say nothing about # whether it resolved anything at all. sed -e 's/"mod_access"/"mod_no_such_module"/' "$D/lighttpd.conf" > "$D/bogus.conf" db 20 "$LT" -tt -f "$D/bogus.conf" if [ "$rc" -ne 0 ]; then bst_ok lighttpd/conf-unknown-module-refused else bst_notok lighttpd/conf-unknown-module-refused "accepted a config naming mod_no_such_module"; fi # mod_h2 is NOT in build_static's builtin_mods, and naming it is ACCEPTED rather # than refused -- which is upstream's deliberate special case, not an accident of # ours, and the reason this case does not simply reuse the negative above. # plugin.c's table walk ends with an explicit `if (buffer_eq_slen(&ds->value, # CONST_STR_LEN("mod_h2"))) { srv->srvconf.h2proto = 0; continue; }` immediately # before the "plugin not found" error, so a config carrying mod_h2 starts a server # with HTTP/2 silently switched OFF. That is the exact behaviour this image has, # and it is worth pinning: a reader who assumed "not built in" meant "config # rejected" would misread every lighttpd.conf on this target. If this ever goes # red because -tt started refusing, upstream dropped the special case; if # upstream dropped the special case. # # There is deliberately NO companion case grepping the binary for a mod_h2 symbol: # plugin.c carries the literal "mod_h2" for the special case above, so the string # is in the artifact whether or not the module is, and such a case would assert # nothing while reading as if it did. sed -e 's/"mod_access"/"mod_h2"/' "$D/lighttpd.conf" > "$D/h2.conf" db 20 "$LT" -tt -f "$D/h2.conf" if [ "$rc" -eq 0 ]; then bst_ok lighttpd/http2-accepted-not-loaded else bst_notok lighttpd/http2-accepted-not-loaded "upstream's mod_h2 special case no longer applies: rc=$rc $(_bst_1line "$out")"; fi ## ## 3. Serve, and fetch back ## "$LT" -D -f "$D/lighttpd.conf" > "$D/server.log" 2>&1 & spid=$! # Wait on the LISTENER rather than on a reply: ss/netstat read kernel state and # cannot block, so this bounds startup without opening a connection that a broken # server could hold open. LST=''; i=0 while [ "$i" -lt 30 ]; do LST=$( (ss -ltn 2>/dev/null || netstat -ltn 2>/dev/null) | grep ":$PORT" ) [ -n "$LST" ] && break i=$((i+1)); sleep 1 done if [ -n "$LST" ]; then SRV_UP=1; else SRV_UP=0; fi if [ "$SRV_UP" -eq 1 ]; then bst_ok lighttpd/starts else bst_notok lighttpd/starts "no listener on :$PORT after ${i}s -- $(_bst_1line "$(cat "$D/server.log" "$D/error.log" 2>/dev/null)")"; fi # The listener is where the config put it and nowhere else -- server.bind is the # only thing between this daemon and every interface the guest has. if [ -z "$LST" ]; then bst_notok lighttpd/listens-loopback-only "no TCP listener on :$PORT (and neither ss nor netstat listed one)" elif has "$LST" "127.0.0.1:$PORT"; then bst_ok lighttpd/listens-loopback-only else bst_notok lighttpd/listens-loopback-only "listener is not loopback-bound -- $(_bst_1line "$LST")" fi hreq probe "GET /index.html HTTP/1.0" "Host: 127.0.0.1" st=$(head -n 1 "$D/probe.raw" 2>/dev/null | tr -d '\r') if has "$st" "200 OK"; then bst_ok lighttpd/status-200 else bst_notok lighttpd/status-200 "$(_bst_1line "$st")"; fi if grep -q "$PAYLOAD" "$D/probe.raw" 2>/dev/null; then bst_ok lighttpd/fetch-text else bst_notok lighttpd/fetch-text "payload absent from response -- $(_bst_1line "$(cat "$D/probe.raw" 2>/dev/null)")"; fi # THE TICKET'S NAMED CRITERION, and the reason wget carries it rather than nc: # -O writes the BODY ALONE to a file, so cmp(1) compares the served bytes against # the source file with nothing to strip and no shell in the data path (a # command-substitution round trip would eat the trailing newline and choke on the # NUL-free-but-binary payload). Byte for byte, 32640 of them. bg 60 fetchbin wget -q -O "$D/got.bin" "http://127.0.0.1:$PORT/payload.bin" if [ "$bg_done" -ne 1 ]; then bst_notok lighttpd/fetch-byte-exact "wget never returned" elif [ "$bg_rc" -ne 0 ]; then bst_notok lighttpd/fetch-byte-exact "wget rc=$bg_rc $(_bst_1line "$bg_out")" elif cmp -s "$D/www/payload.bin" "$D/got.bin"; then bst_ok lighttpd/fetch-byte-exact else bst_notok lighttpd/fetch-byte-exact "served bytes differ: $(_bst_1line "$(cmp "$D/www/payload.bin" "$D/got.bin" 2>&1)") (got $(wc -c < "$D/got.bin" | tr -d ' ') of $BINSZ)" fi check_eq lighttpd/fetch-byte-exact-size "$BINSZ" "$(wc -c < "$D/got.bin" 2>/dev/null | tr -d ' ')" # Twice, identically -- a server that serves correctly once and differently after # is a stat-cache bug, and this image's stat_cache path is one of the two places # LIGHTTPD_STATIC changes code (src/stat_cache.c). bg 60 fetchbin2 wget -q -O "$D/got2.bin" "http://127.0.0.1:$PORT/payload.bin" if [ "$bg_done" -eq 1 ] && [ "$bg_rc" -eq 0 ] && cmp -s "$D/got.bin" "$D/got2.bin"; then bst_ok lighttpd/fetch-repeatable else bst_notok lighttpd/fetch-repeatable "second fetch differs from the first (rc=$bg_rc done=$bg_done)" fi # mod_indexfile, which build_static appends to the module list rather than # building: a bare directory request must serve index.html. hreq root "GET / HTTP/1.0" "Host: 127.0.0.1" if grep -q "$PAYLOAD" "$D/root.raw" 2>/dev/null; then bst_ok lighttpd/index-file else bst_notok lighttpd/index-file "/ did not serve index.html -- $(_bst_1line "$(head -n 3 "$D/root.raw" 2>/dev/null)")"; fi hreq bin "GET /payload.bin HTTP/1.0" "Host: 127.0.0.1" hdr=$(sed -n '1,/^\r*$/p' "$D/bin.raw" 2>/dev/null | tr -d '\r') if has "$hdr" "Content-Length: $BINSZ"; then bst_ok lighttpd/content-length else bst_notok lighttpd/content-length "$(_bst_1line "$hdr")"; fi if has "$hdr" "Content-Type: application/octet-stream"; then bst_ok lighttpd/content-type else bst_notok lighttpd/content-type "mimetype.assign did not take -- $(_bst_1line "$hdr")"; fi hreq head "HEAD /payload.bin HTTP/1.0" "Host: 127.0.0.1" hsz=$(wc -c < "$D/head.raw" | tr -d ' ') hhdr=$(tr -d '\r' < "$D/head.raw") if has "$hhdr" "200 OK" "Content-Length: $BINSZ" && [ "$hsz" -lt 1000 ]; then bst_ok lighttpd/head-no-body else bst_notok lighttpd/head-no-body "HEAD returned $hsz bytes -- $(_bst_1line "$hhdr")"; fi # Range: HTTP/1.1 partial content, served out of the same file. 206 plus the # exact 10 bytes at offset 100, which for this payload are byte values 101..110. hreq range "GET /payload.bin HTTP/1.1" "Host: 127.0.0.1" "Range: bytes=100-109" "Connection: close" rst=$(head -n 1 "$D/range.raw" 2>/dev/null | tr -d '\r') rsz=$(wc -c < "$D/range.raw" | tr -d ' ') if has "$rst" "206"; then bst_ok lighttpd/range-206 else bst_notok lighttpd/range-206 "$(_bst_1line "$rst")"; fi if has "$(tr -d '\r' < "$D/range.raw")" "Content-Range: bytes 100-109/$BINSZ"; then bst_ok lighttpd/range-header else bst_notok lighttpd/range-header "$(_bst_1line "$(sed -n '1,/^\r*$/p' "$D/range.raw" | tr -d '\r')")"; fi hreq missing "GET /no-such-file HTTP/1.0" "Host: 127.0.0.1" if has "$(head -n 1 "$D/missing.raw" 2>/dev/null | tr -d '\r')" "404"; then bst_ok lighttpd/404 else bst_notok lighttpd/404 "$(_bst_1line "$(head -n 1 "$D/missing.raw" 2>/dev/null)")"; fi ## ## 4. Did the modules survive the move into the binary? ## # mod_access, doing real work rather than merely being named: url.access-deny # refuses the suffix. The file EXISTS and is readable, so a 403 here can only come # from the module. hreq denied "GET /keep.secret HTTP/1.0" "Host: 127.0.0.1" if has "$(head -n 1 "$D/denied.raw" 2>/dev/null | tr -d '\r')" "403"; then bst_ok lighttpd/mod-access-deny else bst_notok lighttpd/mod-access-deny "expected 403 from mod_access -- $(_bst_1line "$(head -n 1 "$D/denied.raw" 2>/dev/null)")"; fi # mod_dirlisting -- the module the recipe's RDEPENDS named, which is exactly why # it is worth proving present. /sub/ has no index file and dir-listing is enabled # there, so the listing can only be the module's output. hreq listing "GET /sub/ HTTP/1.0" "Host: 127.0.0.1" if has "$(cat "$D/listing.raw" 2>/dev/null)" "200" "leaf.txt"; then bst_ok lighttpd/dirlisting else bst_notok lighttpd/dirlisting "no listing for /sub/ -- $(_bst_1line "$(head -n 5 "$D/listing.raw" 2>/dev/null)")"; fi # mod_accesslog -- RRECOMMENDS' module, and the only one whose evidence is on the # filesystem rather than on the wire. if [ -s "$D/access.log" ] && grep -q "payload.bin" "$D/access.log" 2>/dev/null; then bst_ok lighttpd/accesslog else bst_notok lighttpd/accesslog "mod_accesslog wrote no request line -- $(_bst_1line "$(cat "$D/access.log" 2>/dev/null)")" fi check lighttpd/pid-file test -s "$D/lighttpd.pid" ## ## 5. The platform walls, scored rather than avoided ## # EPOLL. Everything above ran with server.event-handler = "poll" because the # default does not work. This case pays that back: a config identical to the # working one except that it names NO event-handler, so lighttpd picks its # compiled-in default (linux-sysepoll) exactly as the shipped # /etc/lighttpd/lighttpd.conf does. It is red today and flips green by itself when # brintos/linux#53 wires epoll_create -- at which point the workaround line above # and this case both come out. A different port keeps it from colliding with the # live server. sed -e 's/^server.event-handler.*//' -e "s/^server.port.*/server.port = 8081/" \ -e "s#^server.pid-file.*#server.pid-file = \"$D/ep.pid\"#" \ -e "s#^server.errorlog.*#server.errorlog = \"$D/ep.err\"#" \ "$D/lighttpd.conf" > "$D/epoll.conf" "$LT" -D -f "$D/epoll.conf" > "$D/ep.out" 2>&1 & eplst=''; i=0 while [ "$i" -lt 8 ]; do i=$((i+1)); sleep 1 eplst=$( (ss -ltn 2>/dev/null || netstat -ltn 2>/dev/null) | grep ":8081" ) [ -n "$eplst" ] && break done if [ -n "$eplst" ]; then bst_ok lighttpd/epoll-is-the-default-and-fails else bst_notok lighttpd/epoll-is-the-default-and-fails "brintos/linux#53 (epoll_create/epoll_create1 return ENOSYS): lighttpd's default handler is linux-sysepoll, so fdevent_init fails and it never listens -- $(_bst_1line "$(cat "$D/ep.err" 2>/dev/null)")" fi # RLIMIT. Non-fatal for lighttpd -- it logs, clears use_rlimit and falls back to # its built-in 4096 fd ceiling (server.c:1721-1755) -- but it is a real ENOSYS on # every start, and scoring it here is what makes the day it stops happening # visible. Detected from the error log of the server that DID start, so this case # costs no extra process. if grep -q "getrlimit()" "$D/error.log" "$D/ep.err" "$D/server.log" 2>/dev/null; then bst_notok lighttpd/getrlimit "brintos/linux#20 (no rlimit syscalls wired): getrlimit(RLIMIT_NOFILE) returns ENOSYS at every start, so lighttpd cannot size server.max-fds from the system and falls back to its built-in 4096" else bst_ok lighttpd/getrlimit fi # SIGTERM is lighttpd's documented shutdown and it does not arrive: this kernel # delivers no signal at all, SIGKILL included. Asserted rather than skipped # because "the server cannot be stopped" is a real property of running lighttpd # on this image, and the case flips green by itself when #21 closes. NEVER # wait(1) on $spid afterwards. kill -TERM "$spid" 2>/dev/null i=0 while [ $i -lt 5 ] && kill -0 "$spid" 2>/dev/null; do i=$((i+1)); sleep 1; done if kill -0 "$spid" 2>/dev/null; then kill -KILL "$spid" 2>/dev/null sleep 1 if kill -0 "$spid" 2>/dev/null; then bst_notok lighttpd/sigterm-stops "brintos/linux#21 (signal delivery does not work at all): the server survived SIGTERM for ${i}s and then SIGKILL, so no signal reached it" else bst_notok lighttpd/sigterm-stops "ignored SIGTERM for ${i}s but died on SIGKILL -- that is lighttpd's handler, not the kernel" fi else bst_ok lighttpd/sigterm-stops fi rm -rf "$D"