Files
guix-router/packages/cross-libc-fix.scm
ssmithxandClaude Opus 5.5 413859ee13 Fix cross glibc's system()/popen() shell: use /bin/sh
Guix 1.5.0 builds glibc-cross with the build host's static bash as the
shell for system(), popen() and the exec*() script fallback, so on the
cross-built Pi system they all failed with "Exec format error".
cross-libc-fix wraps cross-libc to use /bin/sh instead.  Deployed and
verified as generation 14.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:53:47 -04:00

67 lines
3.1 KiB
Scheme

;;; Cross-built glibc with a usable shell for system()/popen().
;;;
;;; Guix 1.5.0's glibc recipe points system(), popen() and the exec*() script
;;; fallback (_PATH_BSHELL) at its "static-bash" input. For a cross libc
;;; that input is a *native* one (cross-base.scm: ";FIXME: static-bash"), so
;;; on an aarch64 system cross-built from x86_64 they exec an x86_64 bash:
;;; "Exec format error", every system()/popen() fails (PITFALLS #35). A
;;; target bash can't be used instead: building it needs this very libc.
;;;
;;; Fix for cross-built *Guix systems*, which always have /bin/sh: use
;;; glibc's stock /bin/sh. Loading this module wraps `cross-libc' in
;;; (gnu packages cross-base), which the build systems look up by name
;;; (standard-cross-packages), so every cross build in the process -- the
;;; cross toolchain itself included -- uses the fixed libc.
(define-module (cross-libc-fix)
#:use-module (guix packages)
#:use-module (guix memoization)
#:use-module (guix utils))
(define %original-cross-libc
(module-ref (resolve-module '(gnu packages cross-base)) 'cross-libc))
(define fix-libc
(mlambdaq (libc)
(package
(inherit libc)
(arguments
(substitute-keyword-arguments (package-arguments libc)
((#:phases phases)
`(modify-phases ,phases
(add-after 'pre-configure 'use-/bin/sh
;; Undo pre-configure's static-bash paths.
(lambda _
(substitute* "sysdeps/posix/system.c"
(("#define[[:blank:]]+SHELL_PATH.*$")
"#define SHELL_PATH \"/bin/sh\"\n"))
(substitute* "libio/iopopen.c"
(("\"/gnu/store/[^\"]*/bin/sh\"") "\"/bin/sh\""))
(substitute* (find-files "." "^paths\\.h$")
(("#define[[:blank:]]+_PATH_BSHELL[[:blank:]].*$")
"#define _PATH_BSHELL \"/bin/sh\"\n"))))
(add-after 'patch-shebangs 'bin-sh-shebangs
;; ldd, sotruss, tzselect, xtrace: bash scripts whose
;; shebang is the host's static bash too. After
;; patch-shebangs, which would put it back; /bin/sh is bash
;; on Guix System.
(lambda* (#:key outputs #:allow-other-keys)
(let ((bin (string-append (assoc-ref outputs "out") "/bin")))
(for-each (lambda (script)
(let ((file (string-append bin "/" script)))
(when (file-exists? file)
(substitute* file
(("^#! ?/gnu/store/[^ \n]*/bin/(ba)?sh")
"#!/bin/sh")))))
'("ldd" "sotruss" "tzselect" "xtrace"))))))))))))
(define (cross-libc/bin-sh . args)
(let ((libc (apply %original-cross-libc args)))
(if (and (package? libc)
(string-prefix? "glibc-cross-" (package-name libc)))
(fix-libc libc)
libc)))
(unless (eq? %original-cross-libc cross-libc/bin-sh)
(module-set! (resolve-module '(gnu packages cross-base))
'cross-libc cross-libc/bin-sh))