Skip to content

Commit 152d4f6

Browse files
committed
feat(rootfs): native armhf on aarch64 host via runtime-disable of qemu-arm
Continues #9284 (arm64-compat-vdso extension + custom_kernel_make_params hook). That PR enabled COMPAT_VDSO in arm64 kernels and wired CROSS_COMPILE_COMPAT through the build; this PR uses the resulting 32-bit-capable kernels to run 32-bit ARM ELF directly instead of through qemu-arm emulation in the image-build chroot work. Disable qemu-arm in /proc/sys/fs/binfmt_misc for the duration of the image build so 32-bit ARM ELF falls through to kernel binfmt_elf and runs natively via CONFIG_COMPAT — typically ~12x faster than qemu-arm emulation on common ARM cores (Cortex-A53/A55/A72/A73/A76). Empirical: helios4 BUILD_MINIMAL armhf rootfs cache-miss build on RK3568 (Cortex-A55) drops from 60:35 to 19:27 — 3.12x speedup; same build with rootfs cache-hit drops to 6:38 (~5x). Activation point: AFTER mmdebstrap (in create_new_rootfs_cache_via_debootstrap, right past the bash-presence check) and at build_rootfs_and_image entry for the rootfs-cache hit path. Earlier activation breaks mmdebstrap because its cross-arch machinery requires a working qemu-arm in binfmt_misc and the chroot is not yet populated for native exec. Out-of-scope cases (target arch != armhf, host arch != aarch64) return without touching binfmt_misc. NATIVE_ARMHF_ON_ARM64=no (or never/disabled) disables the entire code path before any detection runs. CONCURRENCY: per-host owner-flock pattern /proc/sys/fs/binfmt_misc is per-kernel state shared by every build process on the host (under docker --privileged also shared with the host's init user-ns). Coordination uses long-lived flock'ed owner files under /run/lock/armbian-native-armhf/owners/${ARMBIAN_BUILD_UUID}.lock. The first owner saves prior qemu-arm state and disables it; the last owner restores the saved state. Live owner count = number of owner files whose flock is held by a live process — checked by attempting non-blocking flock on each: success means the owner is dead and the file is pruned. Crash recovery is automatic (kernel releases the fd on process exit). Refcount/pidlist explicitly avoided — breaks under PID namespaces and PID reuse. Live-flock is the source of truth. Per-host scope by design: shared NFS for the lockdir would be incorrect — binfmt_misc is per-kernel-instance, no cross-host coordination possible. The lockdir at /run/lock is local-to-host. The Armbian build container bind-mounts /run from the host, so multiple containers on the same host share the same lockdir. Critical-path invariants: - State-preserving restore: the first owner records the observed prior state (1 / 0 / missing); the last owner restores from that file. Never blindly writes 1. - Stale active window recovery: if a state file exists when we become first-owner (previous build crashed pre-cleanup), reconcile qemu-arm back to the saved prior state and remove the stale file before observing our own prior. A single SIGKILL'ed build cannot leave qemu-arm permanently disabled. - Disable-failure full abort: if the echo-0 write itself fails (no CAP_SYS_ADMIN, etc.), setup releases its owner lock and removes the state file under the held control lock. A failed disable does not leave a live native-mode owner — otherwise a concurrent builder would join the ghost owner, skip qemu binary deployment, and fail at chroot exec. - Join-state verification: a builder taking the join branch checks the actual qemu-arm state and aborts to qemu fallback if it is observably enabled. Catches an external agent (admin / update-binfmts) re-enabling qemu-arm during the active window — without this our pre-flight arch-test would pass as a false positive (qemu interprets the stub). - Critical-section ordering: the cleanup trap takes the control lock BEFORE releasing its owner lock, so prune+count+restore is atomic with respect to any concurrent setup. Releasing owner before control would open a race where a fresh first-owner could observe the just-disabled state and record it as its own prior. Reviewed iteratively over four rounds with codex (gpt-5.5) on the synchronisation design and the race fixes that followed; coderabbitai flagged the same join-state check independently and self-confirmed the implementation as addressed. Assisted-by: Claude:claude-opus-4.7 Signed-off-by: Igor Velkov <325961+iav@users.noreply.github.com>
1 parent 1bac6d9 commit 152d4f6

3 files changed

Lines changed: 365 additions & 4 deletions

File tree

lib/functions/main/rootfs-image.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ function build_rootfs_and_image() {
1313
# get a basic rootfs, either from cache or from scratch
1414
get_or_create_rootfs_cache_chroot_sdcard # only occurrence of this; has its own logging sections
1515

16+
# Cache-hit path also benefits — the extracted rootfs has libc/ld-linux, so kernel
17+
# binfmt_elf can run 32-bit ARM ELF natively. Idempotent on cache-miss path where
18+
# create_new_rootfs_cache_via_debootstrap already activated this.
19+
_native_armhf_setup_binfmt_elf || true
20+
1621
# deploy the qemu binary, no matter where the rootfs came from (built or cached)
1722
LOG_SECTION="deploy_qemu_binary_to_chroot_image" do_with_logging deploy_qemu_binary_to_chroot "${SDCARD}" "image" # undeployed at end of this function
1823

lib/functions/rootfs/qemu-static.sh

Lines changed: 353 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ function deploy_qemu_binary_to_chroot() {
1717
return 0
1818
fi
1919

20+
# Native armhf path is active: kernel binfmt_elf executes 32-bit ARM ELF via
21+
# CONFIG_COMPAT, no qemu-arm-static needed inside the chroot.
22+
if [[ "${ARMBIAN_NATIVE_ARMHF_VIA_BINFMT_ELF:-no}" == "yes" ]]; then
23+
display_alert "Native armhf via binfmt_elf" "skipping qemu binary deployment during ${caller}" "info"
24+
return 0
25+
fi
26+
2027
declare src_host="/usr/bin/${QEMU_BINARY}"
2128
declare dst_target="${chroot_target}/usr/bin/${QEMU_BINARY}"
2229
declare dst_target_bkp="${dst_target}.armbian.orig"
@@ -48,8 +55,19 @@ function undeploy_qemu_binary_from_chroot() {
4855
declare dst_target="${chroot_target}/usr/bin/${QEMU_BINARY}"
4956
declare dst_target_bkp="${dst_target}.armbian.orig"
5057

51-
# Check the binary we deployed is there. If not, panic, as we've lost control.
58+
# Check the binary we deployed is there. Two reasons it might be missing:
59+
# 1. ARMBIAN_NATIVE_ARMHF_VIA_BINFMT_ELF was active when the matching deploy
60+
# ran, so nothing was copied — graceful no-op.
61+
# 2. Genuine state loss — panic, we lost control.
62+
# We must NOT skip the removal solely on the native-armhf flag, because deploy
63+
# may have run before that flag was set (rootfs-create deploys at line 134,
64+
# native-armhf flips at line 149); skipping the undeploy in that case leaks
65+
# the host's qemu-arm-static into the rootfs cache tarball.
5266
if [[ ! -f "${dst_target}" ]]; then
67+
if [[ "${ARMBIAN_NATIVE_ARMHF_VIA_BINFMT_ELF:-no}" == "yes" ]]; then
68+
display_alert "Native armhf via binfmt_elf" "no qemu binary to remove during ${caller}" "debug"
69+
return 0
70+
fi
5371
exit_with_error "Missing qemu binary during undeploy_qemu_binary_from_chroot from ${caller}"
5472
fi
5573

@@ -97,6 +115,339 @@ function prepare_host_binfmt_qemu() {
97115
return 0
98116
}
99117

118+
# Native armhf on aarch64 host via runtime-disable of qemu-arm in binfmt_misc.
119+
# With qemu-arm disabled, 32-bit ARM ELF falls through to kernel binfmt_elf, which
120+
# executes it natively via CONFIG_COMPAT (~12x faster than qemu-arm emulation on
121+
# common ARM cores). Setting NATIVE_ARMHF_ON_ARM64=no (or never/disabled) disables
122+
# the entire code path before any detection runs.
123+
#
124+
# Concurrency: /proc/sys/fs/binfmt_misc is per-kernel state, shared by every build
125+
# process on the same host (under docker --privileged it's also shared with the
126+
# host's init user-ns). Coordination uses per-host long-lived flock'ed owner files
127+
# under ${_NATIVE_ARMHF_LOCKDIR}/owners/. The first owner saves prior qemu-arm
128+
# state and disables it; the last owner restores the saved state. Live-owner count
129+
# is the number of owner files whose flock is held by a live process — checked by
130+
# attempting non-blocking flock on each: success means the owner is dead and the
131+
# file is pruned. This means crash recovery is automatic (kernel releases the fd
132+
# on process exit). Refcount is not used as source of truth; PID-based pidlists
133+
# break under PID namespaces and PID reuse.
134+
#
135+
# Per-host scope by design: shared NFS for ${_NATIVE_ARMHF_LOCKDIR} is incorrect —
136+
# binfmt_misc is per kernel instance, host A's qemu-arm state is independent of
137+
# host B's, so cross-host serialisation has no meaning here.
138+
#
139+
# Docker note: the lockdir must be a host-wide path that is shared across every
140+
# build container running on the same host (typically a bind-mount of /run/lock
141+
# from the host). If the path is container-local, two parallel containers on the
142+
# same host see independent locks but share /proc/sys/fs/binfmt_misc — race
143+
# remains. The current Armbian build container mounts /run from the host, which
144+
# satisfies this.
145+
#
146+
# Returns 0 on success (sets global ARMBIAN_NATIVE_ARMHF_VIA_BINFMT_ELF=yes);
147+
# 1 if preconditions are not met, the user opted out, or disable failed — caller
148+
# falls back to the qemu-arm path. Cleanup is registered via add_cleanup_handler.
149+
150+
declare -gr _NATIVE_ARMHF_LOCKDIR="/run/lock/armbian-native-armhf"
151+
declare -gr _NATIVE_ARMHF_OWNERS_DIR="${_NATIVE_ARMHF_LOCKDIR}/owners"
152+
declare -gr _NATIVE_ARMHF_CONTROL_LOCK="${_NATIVE_ARMHF_LOCKDIR}/control.lock"
153+
declare -gr _NATIVE_ARMHF_PRIOR_STATE="${_NATIVE_ARMHF_LOCKDIR}/prior-qemu-arm-state"
154+
155+
# Read the qemu-arm 'enabled' flag without touching it. Echoes one of:
156+
# 1 — registered and enabled
157+
# 0 — registered and disabled
158+
# missing — not registered
159+
function _native_armhf_observe_qemu_arm_state() {
160+
if [[ ! -e /proc/sys/fs/binfmt_misc/qemu-arm ]]; then
161+
echo "missing"
162+
return 0
163+
fi
164+
if head -1 /proc/sys/fs/binfmt_misc/qemu-arm 2> /dev/null | grep -q '^enabled'; then
165+
echo "1"
166+
else
167+
echo "0"
168+
fi
169+
}
170+
171+
# Count owner files whose flock is currently held. An owner file we cannot
172+
# acquire non-blocking means a live process holds it. An owner file we can
173+
# acquire is stale — remove it and don't count it. Caller must hold the
174+
# control lock so prune+count is atomic w.r.t. other builders.
175+
function _native_armhf_live_owner_count_locked() {
176+
declare count=0
177+
declare owner_lock
178+
for owner_lock in "${_NATIVE_ARMHF_OWNERS_DIR}"/*.lock; do
179+
[[ -e "${owner_lock}" ]] || continue
180+
# Our own owner counts as live; flock would deadlock on the same fd
181+
# in some shells, so check by name first.
182+
if [[ "${owner_lock}" == "${_native_armhf_owner_lock_path:-}" ]]; then
183+
count=$((count + 1))
184+
continue
185+
fi
186+
# Probe with non-blocking flock on a fresh fd; if it succeeds the
187+
# owner is dead — kernel released their fd on process exit — and we
188+
# remove the file. If it blocks, the owner is alive — count it.
189+
if (flock -n 9) 9> "${owner_lock}"; then
190+
rm -f "${owner_lock}"
191+
else
192+
count=$((count + 1))
193+
fi
194+
done
195+
echo "${count}"
196+
}
197+
198+
# Acquire the per-build owner lock and the control lock for the critical
199+
# section that decides whether this build is the first or n-th owner. Holds
200+
# the owner fd as a long-lived global so the lock survives the rest of the
201+
# build. Caller must release via _native_armhf_release_owner_lock at end.
202+
function _native_armhf_setup_binfmt_elf() {
203+
# Killswitch — accept no/never/disabled as synonyms; bail before detection.
204+
case "${NATIVE_ARMHF_ON_ARM64:-auto}" in
205+
no | never | disabled) return 1 ;;
206+
esac
207+
208+
# Idempotent: callers may invoke this both after debootstrap (in
209+
# create_new_rootfs_cache_via_debootstrap) and after rootfs-cache extract
210+
# (in build_rootfs_and_image, for cache-hit). Once active, return success.
211+
[[ "${ARMBIAN_NATIVE_ARMHF_VIA_BINFMT_ELF:-no}" == "yes" ]] && return 0
212+
213+
# Scope: armhf-target on aarch64-host only. Out-of-scope cases stay silent.
214+
[[ "${ARCH}" == "armhf" ]] || return 1
215+
[[ "$(arch)" == "aarch64" ]] || return 1
216+
217+
# Cheap pre-flight: arch-test must at least pass under the current binfmt
218+
# state. NOT trustworthy alone — when qemu-arm is registered it interprets
219+
# its stub and arch-test passes even without real native AArch32 support.
220+
# The authoritative check is the post-disable arch-test below.
221+
if ! arch-test armhf > /dev/null 2>&1; then
222+
display_alert "Native armhf via binfmt_elf" "arch-test pre-flight failed; falling back to qemu-arm-static emulation" "info"
223+
return 1
224+
fi
225+
226+
# Establish the lock root. The directory itself we just create; missing
227+
# /run/lock parent (very minimal hosts) is a hard fail since we have no
228+
# safe place to coordinate.
229+
if ! mkdir -p "${_NATIVE_ARMHF_OWNERS_DIR}" 2> /dev/null; then
230+
display_alert "Native armhf via binfmt_elf" "cannot create lockdir ${_NATIVE_ARMHF_LOCKDIR}; falling back to qemu-arm-static emulation" "wrn"
231+
return 1
232+
fi
233+
234+
# Per-build owner identity. ARMBIAN_BUILD_UUID is generated in
235+
# entrypoint.sh and propagated into docker; if absent (early hook /
236+
# unusual entrypoint), fall back to a synthetic id that is still unique
237+
# per process lifetime.
238+
declare uuid="${ARMBIAN_BUILD_UUID:-pid${BASHPID}-${RANDOM}-${RANDOM}}"
239+
declare owner_lock="${_NATIVE_ARMHF_OWNERS_DIR}/${uuid}.lock"
240+
241+
# Open the owner file with a long-lived fd and grab its flock; the kernel
242+
# will release this fd on process exit (crash safety). The fd lives as a
243+
# global so subsequent shells/subshells (cleanup trap) can find it.
244+
exec {_native_armhf_owner_fd}> "${owner_lock}"
245+
if ! flock -n "${_native_armhf_owner_fd}"; then
246+
# Same uuid already locked — most likely a duplicate setup call by
247+
# the same build. Idempotent: return success without re-acquiring.
248+
exec {_native_armhf_owner_fd}>&-
249+
unset _native_armhf_owner_fd
250+
display_alert "Native armhf via binfmt_elf" "owner lock for uuid ${uuid} already held; idempotent return" "debug"
251+
return 1
252+
fi
253+
declare -g _native_armhf_owner_lock_path="${owner_lock}"
254+
255+
# Critical section: decide first-owner vs n-th-owner under the control
256+
# lock. Stale owners are pruned here so the count reflects only live ones.
257+
declare control_fd
258+
exec {control_fd}> "${_NATIVE_ARMHF_CONTROL_LOCK}"
259+
if ! flock "${control_fd}"; then
260+
display_alert "Native armhf via binfmt_elf" "could not acquire control lock; falling back to qemu-arm-static emulation" "wrn"
261+
exec {control_fd}>&-
262+
_native_armhf_release_owner_lock
263+
return 1
264+
fi
265+
266+
declare live_count
267+
live_count="$(_native_armhf_live_owner_count_locked)"
268+
269+
if [[ "${live_count}" -eq 1 ]]; then
270+
# We are the only live owner — first-owner. Before recording our
271+
# prior_state, reconcile any stale state file from a previous
272+
# active window that ended without running cleanup (e.g. SIGKILL /
273+
# OOM kill / `kill -9` of the build between the post-disable echo 0
274+
# and the trap firing). Without this branch the crashed window's
275+
# left-behind disabled state would be observed as our new
276+
# prior_state, and our own clean shutdown would never re-enable
277+
# qemu-arm — turning a single crash into a permanent disablement.
278+
if [[ -f "${_NATIVE_ARMHF_PRIOR_STATE}" ]]; then
279+
declare stale_prior
280+
stale_prior="$(cat "${_NATIVE_ARMHF_PRIOR_STATE}" 2> /dev/null || echo missing)"
281+
if [[ "${stale_prior}" == "1" && -e /proc/sys/fs/binfmt_misc/qemu-arm ]]; then
282+
declare crashed_observed
283+
crashed_observed="$(_native_armhf_observe_qemu_arm_state)"
284+
if [[ "${crashed_observed}" != "1" ]]; then
285+
echo 1 > /proc/sys/fs/binfmt_misc/qemu-arm 2> /dev/null || true
286+
display_alert "Native armhf via binfmt_elf" "stale active window detected (previous build crashed pre-cleanup, prior=${stale_prior}); qemu-arm reconciled to enabled before our first-owner observation" "wrn"
287+
fi
288+
fi
289+
rm -f "${_NATIVE_ARMHF_PRIOR_STATE}"
290+
fi
291+
292+
# Record current qemu-arm state so cleanup can restore it state-
293+
# preserving (codex: never blindly write 1 — if prior was disabled,
294+
# leave disabled; if prior was missing, don't recreate the handler).
295+
declare prior_state
296+
prior_state="$(_native_armhf_observe_qemu_arm_state)"
297+
echo "${prior_state}" > "${_NATIVE_ARMHF_PRIOR_STATE}"
298+
299+
# Register cleanup BEFORE any destructive write so an interrupt mid-
300+
# setup still triggers state-preserving restore.
301+
add_cleanup_handler trap_handler_native_armhf_restore_qemu_arm
302+
303+
case "${prior_state}" in
304+
missing)
305+
display_alert "Native armhf via binfmt_elf" "qemu-arm not registered; native armhf already in effect" "info"
306+
;;
307+
0)
308+
display_alert "Native armhf via binfmt_elf" "qemu-arm already disabled by another agent; native armhf already in effect" "info"
309+
;;
310+
1)
311+
if ! echo 0 > /proc/sys/fs/binfmt_misc/qemu-arm 2> /dev/null; then
312+
display_alert "Native armhf via binfmt_elf" "could not disable qemu-arm (no CAP_SYS_ADMIN?); falling back to qemu-arm-static emulation" "wrn"
313+
# Full abort: failed disable must NOT leave us as a live
314+
# native-mode owner. Otherwise a concurrent builder sees
315+
# live_count > 1, takes the join branch, skips qemu
316+
# binary deployment, and runs its chroot against still-
317+
# enabled qemu-arm without the binary inside — runtime
318+
# failure (codex review of fix2). Cleanup is performed
319+
# UNDER control so prune+count by other builders sees
320+
# the abort atomically.
321+
rm -f "${_NATIVE_ARMHF_PRIOR_STATE}"
322+
_native_armhf_release_owner_lock
323+
exec {control_fd}>&-
324+
return 1
325+
fi
326+
;;
327+
esac
328+
else
329+
# Another live owner already disabled qemu-arm — or so we think.
330+
# Verify the kernel state matches: if qemu-arm is observably
331+
# enabled here despite a live first owner, an external agent
332+
# (admin / `update-binfmts --enable qemu-arm`) flipped it back on
333+
# during the active window. Our pre-flight arch-test then passes
334+
# as a false positive (qemu interprets the stub) and we'd skip
335+
# the qemu binary deployment, then run chroot exec against
336+
# globally-enabled qemu-arm with no binary inside the chroot —
337+
# runtime failure (codex review of fix2).
338+
declare observed_join
339+
observed_join="$(_native_armhf_observe_qemu_arm_state)"
340+
if [[ "${observed_join}" == "1" ]]; then
341+
display_alert "Native armhf via binfmt_elf" "${live_count} live owner(s) but qemu-arm is observably enabled (external agent re-enabled it); aborting join, falling back to qemu-arm-static emulation" "wrn"
342+
_native_armhf_release_owner_lock
343+
exec {control_fd}>&-
344+
return 1
345+
fi
346+
# Cleanup is still registered so this owner's release decrements
347+
# live_count back toward zero.
348+
add_cleanup_handler trap_handler_native_armhf_restore_qemu_arm
349+
display_alert "Native armhf via binfmt_elf" "joining ${live_count} existing native-armhf owner(s); shared qemu-arm state preserved" "info"
350+
fi
351+
352+
# Authoritative check: re-run arch-test with qemu-arm now disabled. This
353+
# reflects what the chroot exec will actually face. Pre-disable arch-test
354+
# can be a false positive when host kernel lacks COMPAT_VDSO (extensions/
355+
# arm64-compat-vdso, PR #9284) — qemu-arm interprets the stub.
356+
if ! arch-test armhf > /dev/null 2>&1; then
357+
display_alert "Native armhf via binfmt_elf" "post-disable verification failed (host kernel lacks COMPAT_VDSO — see extensions/arm64-compat-vdso); restoring and falling back to emulation" "wrn"
358+
# Drop control lock so the handler can re-acquire it. Do NOT
359+
# release the owner lock here — the handler must release it UNDER
360+
# control so live_count reflects only OTHER owners and the
361+
# state-preserving restore decision uses a consistent snapshot.
362+
# Releasing owner outside the control section was the race codex
363+
# flagged in the previous round.
364+
exec {control_fd}>&-
365+
trap_handler_native_armhf_restore_qemu_arm
366+
return 1
367+
fi
368+
369+
exec {control_fd}>&-
370+
display_alert "Native armhf via binfmt_elf" "kernel $(uname -r), aarch64 host with COMPAT_VDSO; qemu-arm disabled, kernel binfmt_elf takes over" "info"
371+
declare -g ARMBIAN_NATIVE_ARMHF_VIA_BINFMT_ELF=yes
372+
return 0
373+
}
374+
375+
# Release this builder's owner lock. Closing the fd is what the kernel uses
376+
# to detect liveness, so we close before unlinking — order matters for any
377+
# concurrent live-owner-count probe in another builder.
378+
function _native_armhf_release_owner_lock() {
379+
[[ -n "${_native_armhf_owner_fd:-}" ]] || return 0
380+
exec {_native_armhf_owner_fd}>&-
381+
[[ -n "${_native_armhf_owner_lock_path:-}" ]] && rm -f "${_native_armhf_owner_lock_path}"
382+
unset _native_armhf_owner_fd _native_armhf_owner_lock_path
383+
}
384+
385+
# Cleanup handler: state-preserving restore of qemu-arm. Only runs the
386+
# restore when this builder is the LAST live owner; otherwise it just
387+
# releases its own owner lock and lets the surviving owner(s) keep the
388+
# disabled state. Restore writes the prior state observed at first-owner
389+
# time (1 / 0 / missing) — never blindly 1.
390+
function trap_handler_native_armhf_restore_qemu_arm() {
391+
# Skip cleanup entirely if we never acquired an owner lock.
392+
[[ -n "${_native_armhf_owner_fd:-}" ]] || return 0
393+
394+
# Take the control lock BEFORE releasing our owner lock. The previous
395+
# revision did the reverse and codex flagged the race: between fd
396+
# close and control acquire, a fresh setup could enter, see itself as
397+
# the only live owner (live_count == 1), observe the now-disabled
398+
# qemu-arm and write that as its prior_state — so our subsequent
399+
# restore would read the overwritten state and fail to re-enable.
400+
# Holding control across release+count+restore makes the transition
401+
# atomic with respect to any concurrent setup.
402+
declare control_fd
403+
exec {control_fd}> "${_NATIVE_ARMHF_CONTROL_LOCK}"
404+
if ! flock "${control_fd}"; then
405+
# Lock failed — best effort. Drop our owner so we are not counted
406+
# live forever; next build will reconcile.
407+
exec {control_fd}>&-
408+
_native_armhf_release_owner_lock
409+
return 0
410+
fi
411+
412+
# Drop our own ownership UNDER control. live_count below now reflects
413+
# only OTHER live owners.
414+
_native_armhf_release_owner_lock
415+
416+
declare live_count
417+
live_count="$(_native_armhf_live_owner_count_locked)"
418+
419+
if [[ "${live_count}" -eq 0 ]]; then
420+
declare prior_state="missing"
421+
[[ -f "${_NATIVE_ARMHF_PRIOR_STATE}" ]] && prior_state="$(cat "${_NATIVE_ARMHF_PRIOR_STATE}" 2> /dev/null || echo missing)"
422+
case "${prior_state}" in
423+
1)
424+
if [[ -e /proc/sys/fs/binfmt_misc/qemu-arm ]]; then
425+
declare current
426+
current="$(_native_armhf_observe_qemu_arm_state)"
427+
if [[ "${current}" != "1" ]]; then
428+
echo 1 > /proc/sys/fs/binfmt_misc/qemu-arm 2> /dev/null || true
429+
display_alert "Native armhf via binfmt_elf" "last owner; qemu-arm restored to enabled" "info"
430+
fi
431+
fi
432+
;;
433+
0 | missing)
434+
# Prior was already disabled or unregistered — leave as is.
435+
display_alert "Native armhf via binfmt_elf" "last owner; prior qemu-arm state was ${prior_state}, leaving alone" "info"
436+
;;
437+
esac
438+
# Warn if observed state diverged from prior_state without our
439+
# action — admin or another tool changed it during the window.
440+
declare observed
441+
observed="$(_native_armhf_observe_qemu_arm_state)"
442+
if [[ "${prior_state}" == "1" && "${observed}" != "1" ]]; then
443+
display_alert "Native armhf via binfmt_elf" "qemu-arm could not be restored to enabled (now ${observed}); host policy or another tool changed it" "wrn"
444+
fi
445+
rm -f "${_NATIVE_ARMHF_PRIOR_STATE}"
446+
fi
447+
448+
exec {control_fd}>&-
449+
}
450+
100451
# The actual binfmt manipulations when cross-build is confirmed above.
101452
function prepare_host_binfmt_qemu_cross() {
102453
local failed_binfmt_modprobe=0
@@ -171,7 +522,7 @@ function prepare_host_binfmt_qemu_cross_arm64_host_armhf_target() {
171522
display_alert "Host can run armhf natively or emulation is correctly setup already" "no need to enable qemu-arm" "debug"
172523
else
173524
display_alert "arm64 host can't run armhf natively" "importing enabling qemu-arm" "debug"
174-
cat <<-BINFMT_ARM_MAGIC >/usr/share/binfmts/qemu-arm
525+
cat <<- BINFMT_ARM_MAGIC > /usr/share/binfmts/qemu-arm
175526
package qemu-user-static
176527
interpreter /usr/bin/qemu-arm-static
177528
magic \x7f\x45\x4c\x46\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00

0 commit comments

Comments
 (0)