@@ -99,108 +99,145 @@ native_bindir() {
9999# Pull "key:value" out of a fixture meta.txt (collect's format).
100100meta () { grep -E " ^$2 :" " $1 /meta.txt" 2> /dev/null | head -1 | sed -E ' s/^[^:]*:[[:space:]]*//' ; }
101101
102- pass=0; fail=0; skip=0; scratch_keep=" "
102+ pass=0; fail=0; skip=0
103+
104+ # Fixtures are independent (each gets its own mktemp scratch sysroot), so they
105+ # can replay JOBS at a time. The work is qemu-startup/I-O bound, not CPU bound,
106+ # so concurrency well above the core count still pays off. KEEP=1 (inspect the
107+ # scratch dir) forces serial so the kept path is unambiguous.
108+ JOBS=${JOBS:- $(nproc 2>/ dev/ null || echo 1)}
109+ [ " ${KEEP:- 0} " = 1 ] && JOBS=1
110+
111+ # run_fixture writes its human-readable line to stdout (the caller redirects it
112+ # to a per-fixture .res) and a pass/fail/skip token to $st, so invocations can
113+ # run concurrently without sharing shell state. Uses `return` (not `continue`)
114+ # since it runs as a backgrounded function.
115+ run_fixture () {
116+ d=$1 st=$2
117+ name=$( echo " $d " | sed " s#$FIXROOT /##" )
118+
119+ if [ ! -f " $d /meta.txt" ] || [ ! -f " $d /sizes.txt" ]; then
120+ printf ' SKIP %-44s (no meta.txt/sizes.txt)\n' " $name " ; echo skip > " $st " ; return
121+ fi
122+
123+ machine=$( meta " $d " uname_m_raw)
124+ release=$( meta " $d " kernel_release)
125+
126+ if [ " ${KASLD_NATIVE:- 0} " = 1 ]; then
127+ # No qemu: only fixtures the host can run directly; binary from build/.
128+ if ! native_ok " $machine " ; then
129+ printf ' SKIP %-44s (native mode: non-host arch %s)\n' " $name " " $machine " ; echo skip > " $st " ; return
130+ fi
131+ bindir=$( native_bindir " $machine " )
132+ if [ -z " $bindir " ]; then
133+ printf ' SKIP %-44s (native mode: no host binary for %s — run make)\n' " $name " " $machine " ; echo skip > " $st " ; return
134+ fi
135+ kasld=" $bindir /kasld" ; comps=" $bindir /components" ; runner=" "
136+ else
137+ map=$( target_for " $machine " )
138+ if [ -z " $map " ]; then
139+ printf ' SKIP %-44s (no target mapping for uname_m=%s)\n' " $name " " $machine " ; echo skip > " $st " ; return
140+ fi
141+ subdir=${map% * } ; qbin=${map#* }
142+ kasld=" $BUILD_DIR /$subdir /kasld"
143+ comps=" $BUILD_DIR /$subdir /components"
144+ if [ ! -x " $kasld " ]; then
145+ printf ' SKIP %-44s (no binary: %s — run make cross)\n' " $name " " $subdir /kasld" ; echo skip > " $st " ; return
146+ fi
147+ if [ ! -x " $QEMU_DIR /$qbin " ]; then
148+ printf ' SKIP %-44s (no qemu: %s)\n' " $name " " $qbin " ; echo skip > " $st " ; return
149+ fi
150+ runner=" $QEMU_DIR /$qbin "
151+ fi
152+
153+ # Reconstruct a scratch sysroot: copy captured facts, then materialise the
154+ # /boot images as sparse files of their true size (header prefix preserved).
155+ sc=$( mktemp -d)
156+ cp -a " $d /sysroot" " $sc /r"
157+ while read -r sz path; do
158+ if [ -z " $sz " ] || [ -z " $path " ]; then continue ; fi
159+ mkdir -p " $sc /r$( dirname " $path " ) " 2> /dev/null
160+ truncate -s " $sz " " $sc /r$path " 2> /dev/null
161+ done < " $d /sizes.txt"
162+
163+ # Release handling: QEMU_UNAME sets the top-level process's uname, but is
164+ # NOT honored after qemu's self-re-exec for foreign-arch component children
165+ # — so KASLD_UNAME_RELEASE (read by kasld_uname(), propagated via the
166+ # environment) is what actually gives every process the fixture's release
167+ # for /boot path construction. Both are set; the latter is the load-bearing
168+ # one.
169+ #
170+ # KASLD_EXEC_WRAPPER lets the orchestrator's component children execve
171+ # through the host-arch qemu wrapper instead of trying to execve a guest-
172+ # arch ELF directly (which the host kernel refuses with ENOEXEC unless
173+ # binfmt_misc is registered). When $runner is non-empty (foreign-arch
174+ # fixture) we point KASLD_EXEC_WRAPPER at it; for native-arch fixtures
175+ # $runner is empty and KASLD_EXEC_WRAPPER stays unset, so the orchestrator
176+ # takes the production direct-execve path.
177+ QEMU_UNAME=" $release " KASLD_UNAME_RELEASE=" $release " KASLD_SYSROOT=" $sc /r" \
178+ KASLD_COMPONENT_DIR=" $comps " KASLD_EXEC_WRAPPER=" $runner " \
179+ $runner " $kasld " -v > " $sc /replay.txt" 2> /dev/null
180+ rc=$?
181+
182+ # Smoke verdict: the run must complete without an emulation crash. kasld
183+ # exits 0 with results, 1 with no tagged results (a valid empty run);
184+ # anything killed by a signal exits >= 128. A crash on any arch's calling
185+ # convention is the failure this harness exists to catch.
186+ #
187+ # With KASLD_EXEC_WRAPPER pointed at the host qemu binary, foreign-arch
188+ # component children do exec successfully under nested qemu-user — so
189+ # both x86 (native) and foreign-arch fixtures should produce results and
190+ # render a summary.
191+ has_summary=$( grep -cE ' ^(========| KASLR analysis:|Virtual memory layout)' \
192+ " $sc /replay.txt" 2> /dev/null || true)
193+ n_results=$( grep -cE ' ^[VPD] ' " $sc /replay.txt" 2> /dev/null || true)
194+
195+ if [ " $rc " -ge 128 ]; then
196+ printf ' FAIL %-44s (crashed under emulation: signal %d)\n' \
197+ " $name " " $(( rc - 128 )) "
198+ echo fail > " $st "
199+ elif [ " $has_summary " -gt 0 ]; then
200+ printf ' PASS %-44s rc=%d, %d results, summary rendered\n' \
201+ " $name " " $rc " " $n_results "
202+ echo pass > " $st "
203+ else
204+ printf ' PASS %-44s rc=%d, no results\n' " $name " " $rc "
205+ echo pass > " $st "
206+ fi
207+
208+ if [ " ${KEEP:- 0} " = 1 ]; then echo " kept scratch sysroot: $sc " ; else rm -rf " $sc " ; fi
209+ }
103210
211+ # Launch up to JOBS fixtures concurrently, keyed by a launch index so the
212+ # summary prints in a stable order regardless of finish order. Barrier every
213+ # JOBS launches (POSIX sh has no `wait -n`).
214+ RESDIR=$( mktemp -d)
215+ n=0; running=0
104216for fx in " ${@:- $FIXROOT /*/* } " ; do
105217 # Expand the default glob; skip non-dirs.
106218 for d in $fx ; do
107219 [ -d " $d " ] || continue
108- name=$( echo " $d " | sed " s#$FIXROOT /##" )
109-
110- if [ ! -f " $d /meta.txt" ] || [ ! -f " $d /sizes.txt" ]; then
111- printf ' SKIP %-44s (no meta.txt/sizes.txt)\n' " $name " ; skip=$(( skip+ 1 )) ; continue
112- fi
113-
114- machine=$( meta " $d " uname_m_raw)
115- release=$( meta " $d " kernel_release)
116-
117- if [ " ${KASLD_NATIVE:- 0} " = 1 ]; then
118- # No qemu: only fixtures the host can run directly; binary from build/.
119- if ! native_ok " $machine " ; then
120- printf ' SKIP %-44s (native mode: non-host arch %s)\n' " $name " " $machine " ; skip=$(( skip+ 1 )) ; continue
121- fi
122- bindir=$( native_bindir " $machine " )
123- if [ -z " $bindir " ]; then
124- printf ' SKIP %-44s (native mode: no host binary for %s — run make)\n' " $name " " $machine " ; skip=$(( skip+ 1 )) ; continue
125- fi
126- kasld=" $bindir /kasld" ; comps=" $bindir /components" ; runner=" "
127- else
128- map=$( target_for " $machine " )
129- if [ -z " $map " ]; then
130- printf ' SKIP %-44s (no target mapping for uname_m=%s)\n' " $name " " $machine " ; skip=$(( skip+ 1 )) ; continue
131- fi
132- subdir=${map% * } ; qbin=${map#* }
133- kasld=" $BUILD_DIR /$subdir /kasld"
134- comps=" $BUILD_DIR /$subdir /components"
135- if [ ! -x " $kasld " ]; then
136- printf ' SKIP %-44s (no binary: %s — run make cross)\n' " $name " " $subdir /kasld" ; skip=$(( skip+ 1 )) ; continue
137- fi
138- if [ ! -x " $QEMU_DIR /$qbin " ]; then
139- printf ' SKIP %-44s (no qemu: %s)\n' " $name " " $qbin " ; skip=$(( skip+ 1 )) ; continue
140- fi
141- runner=" $QEMU_DIR /$qbin "
142- fi
143-
144- # Reconstruct a scratch sysroot: copy captured facts, then materialise the
145- # /boot images as sparse files of their true size (header prefix preserved).
146- sc=$( mktemp -d)
147- cp -a " $d /sysroot" " $sc /r"
148- while read -r sz path; do
149- if [ -z " $sz " ] || [ -z " $path " ]; then continue ; fi
150- mkdir -p " $sc /r$( dirname " $path " ) " 2> /dev/null
151- truncate -s " $sz " " $sc /r$path " 2> /dev/null
152- done < " $d /sizes.txt"
153-
154- # Release handling: QEMU_UNAME sets the top-level process's uname, but is
155- # NOT honored after qemu's self-re-exec for foreign-arch component children
156- # — so KASLD_UNAME_RELEASE (read by kasld_uname(), propagated via the
157- # environment) is what actually gives every process the fixture's release
158- # for /boot path construction. Both are set; the latter is the load-bearing
159- # one.
160- #
161- # KASLD_EXEC_WRAPPER lets the orchestrator's component children execve
162- # through the host-arch qemu wrapper instead of trying to execve a guest-
163- # arch ELF directly (which the host kernel refuses with ENOEXEC unless
164- # binfmt_misc is registered). When $runner is non-empty (foreign-arch
165- # fixture) we point KASLD_EXEC_WRAPPER at it; for native-arch fixtures
166- # $runner is empty and KASLD_EXEC_WRAPPER stays unset, so the orchestrator
167- # takes the production direct-execve path.
168- QEMU_UNAME=" $release " KASLD_UNAME_RELEASE=" $release " KASLD_SYSROOT=" $sc /r" \
169- KASLD_COMPONENT_DIR=" $comps " KASLD_EXEC_WRAPPER=" $runner " \
170- $runner " $kasld " -v > " $sc /replay.txt" 2> /dev/null
171- rc=$?
172-
173- # Smoke verdict: the run must complete without an emulation crash. kasld
174- # exits 0 with results, 1 with no tagged results (a valid empty run);
175- # anything killed by a signal exits >= 128. A crash on any arch's calling
176- # convention is the failure this harness exists to catch.
177- #
178- # With KASLD_EXEC_WRAPPER pointed at the host qemu binary, foreign-arch
179- # component children do exec successfully under nested qemu-user — so
180- # both x86 (native) and foreign-arch fixtures should produce results and
181- # render a summary.
182- has_summary=$( grep -cE ' ^(========| KASLR analysis:|Virtual memory layout)' \
183- " $sc /replay.txt" 2> /dev/null || true)
184- n_results=$( grep -cE ' ^[VPD] ' " $sc /replay.txt" 2> /dev/null || true)
185-
186- if [ " $rc " -ge 128 ]; then
187- printf ' FAIL %-44s (crashed under emulation: signal %d)\n' \
188- " $name " " $(( rc - 128 )) "
189- fail=$(( fail+ 1 ))
190- elif [ " $has_summary " -gt 0 ]; then
191- printf ' PASS %-44s rc=%d, %d results, summary rendered\n' \
192- " $name " " $rc " " $n_results "
193- pass=$(( pass+ 1 ))
194- else
195- printf ' PASS %-44s rc=%d, no results\n' " $name " " $rc "
196- pass=$(( pass+ 1 ))
197- fi
198-
199- if [ " ${KEEP:- 0} " = 1 ]; then scratch_keep=" $sc " ; else rm -rf " $sc " ; fi
220+ n=$(( n + 1 ))
221+ run_fixture " $d " " $RESDIR /$n .st" > " $RESDIR /$n .res" 2>&1 &
222+ running=$(( running + 1 ))
223+ if [ " $running " -ge " $JOBS " ]; then wait ; running=0; fi
200224 done
201225done
226+ wait
227+
228+ # Aggregate in launch order; a missing token (job died) counts as a failure.
229+ i=1
230+ while [ " $i " -le " $n " ]; do
231+ [ -f " $RESDIR /$i .res" ] && cat " $RESDIR /$i .res"
232+ case $( cat " $RESDIR /$i .st" 2> /dev/null) in
233+ pass) pass=$(( pass + 1 )) ;;
234+ skip) skip=$(( skip + 1 )) ;;
235+ * ) fail=$(( fail + 1 )) ;;
236+ esac
237+ i=$(( i + 1 ))
238+ done
239+ rm -rf " $RESDIR "
202240
203241echo " ----------------------------------------------------------------------"
204- printf ' replay: %d pass, %d fail, %d skip\n' " $pass " " $fail " " $skip "
205- [ -n " $scratch_keep " ] && echo " kept last scratch sysroot: $scratch_keep "
242+ printf ' replay: %d pass, %d fail, %d skip (JOBS=%s)\n' " $pass " " $fail " " $skip " " $JOBS "
206243[ " $fail " -eq 0 ]
0 commit comments