Skip to content

Commit 111802a

Browse files
committed
cmd/initContainer: Set up QEMU emulation for cross-arch containers
Add the --arch and --arch-emulator-path flags to the init-container command, passed from the create command when creating a cross-architecture container. The --arch flag defaults to the host architecture ID so that existing native containers continue to work without changes. When the container's architecture differs from the host, the init-container entry point configures QEMU emulation inside the container before any foreign-architecture binaries can run: 1. Validate QEMU emulation by running the 'true' command, which fails with ENOEXEC if the host's binfmt_misc registration is not working (detected via RunWithExitCode2() added in [1]), because it is necessary to have host emulation working to emulate the binfmt_misc registration in the following step. 2. Mount a fresh binfmt_misc filesystem inside the container via MountBinfmtMisc() (added in [2]) to create a sandboxed binfmt_misc registration with the C flag. 3. Validate architecture support via IsArchSupportedOnInitialization() (added in [3]), which verifies the QEMU interpreter at the host-mounted path under /run/host. 4. Register the QEMU interpreter with the C flag via RegisterBinfmtMisc() (added and explained in [2]) The binfmt_misc registration is performed inside the container rather than relying on the host's registration, as explained in [2]. Update showEntryPointLog() in run.go to propagate lines prefixed with 'Warning:' to stderr on the host, instead of treating them as errors. This is needed because the cross-architecture initialization may emit warnings that should be visible to the user but are not fatal. [1] #1780 [2] #1782 [3] #1783 #1788 Signed-off-by: Dalibor Kricka <dalidalk@seznam.cz>
1 parent 5d2af16 commit 111802a

3 files changed

Lines changed: 71 additions & 2 deletions

File tree

src/cmd/create.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,8 @@ func createContainer(container, image, release, authFile string, archConfig arch
449449
entryPoint := []string{
450450
"toolbox", "--log-level", "debug",
451451
"init-container",
452+
"--arch", fmt.Sprintf("%d", archConfig.ID),
453+
"--arch-emulator-path", archConfig.QemuEmulatorPath,
452454
"--gid", currentUser.Gid,
453455
"--home", currentUserHomeDir,
454456
"--shell", userShell,

src/cmd/initContainer.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"syscall"
2929
"time"
3030

31+
"github.com/containers/toolbox/pkg/architecture"
3132
"github.com/containers/toolbox/pkg/shell"
3233
"github.com/containers/toolbox/pkg/utils"
3334
"github.com/fsnotify/fsnotify"
@@ -41,6 +42,8 @@ import (
4142

4243
var (
4344
initContainerFlags struct {
45+
archID int
46+
archInterp string
4447
gid int
4548
home string
4649
homeLink bool
@@ -85,6 +88,16 @@ var initContainerCmd = &cobra.Command{
8588
func init() {
8689
flags := initContainerCmd.Flags()
8790

91+
flags.IntVar(&initContainerFlags.archID,
92+
"arch",
93+
architecture.HostArchID,
94+
"Specify the Toolbx container's architecture ID.")
95+
96+
flags.StringVar(&initContainerFlags.archInterp,
97+
"arch-emulator-path",
98+
"",
99+
"Register an emulator using binfmt_misc with PATH as the interpreter for a non-native architecture container.")
100+
88101
flags.IntVar(&initContainerFlags.gid,
89102
"gid",
90103
0,
@@ -257,6 +270,31 @@ func initContainer(cmd *cobra.Command, args []string) error {
257270
}
258271
}
259272

273+
if !architecture.HasContainerNativeArch(initContainerFlags.archID) {
274+
archName := architecture.GetArchNameOCI(initContainerFlags.archID)
275+
interpreterPath := "/run/host" + initContainerFlags.archInterp
276+
277+
if err := validateCrossArchEmulation(initContainerFlags.archID); err != nil {
278+
return err
279+
}
280+
281+
logrus.Debugf("Mounting binfmt_misc file system in container for architecture %s", archName)
282+
if err := architecture.MountBinfmtMisc(); err != nil {
283+
return err
284+
}
285+
286+
err := architecture.IsArchSupportedOnInitialization(initContainerFlags.archID, interpreterPath)
287+
if err != nil {
288+
errNotSupported := fmt.Errorf("Cannot run container for architecture %s:\n%s", archName, err)
289+
return errNotSupported
290+
}
291+
292+
logrus.Debugf("Registering QEMU emulator for architecture %s in binfmt_mist", archName)
293+
if err := architecture.RegisterBinfmtMisc(initContainerFlags.archID, interpreterPath); err != nil {
294+
return err
295+
}
296+
}
297+
260298
for _, mount := range initContainerMounts {
261299
if err := mountBind(mount.containerPath, mount.source, mount.flags); err != nil {
262300
return err
@@ -1223,6 +1261,28 @@ func updateTimeZoneFromLocalTime() error {
12231261
return nil
12241262
}
12251263

1264+
func validateCrossArchEmulation(archID int) error {
1265+
archName := architecture.GetArchNameOCI(archID)
1266+
logrus.Debugf("Testing QEMU emulation for architecture %s", archName)
1267+
1268+
_, err := shell.RunWithExitCode2("true", nil, nil, nil)
1269+
1270+
if err != nil {
1271+
if errors.Is(err, syscall.ENOEXEC) {
1272+
return fmt.Errorf(
1273+
"QEMU emulation for architecture %s is not working\n"+
1274+
"Please verify that:\n"+
1275+
" 1. QEMU user-mode emulation is installed on the host system: qemu-user-static package\n"+
1276+
" 2. binfmt_misc is properly configured on the host system",
1277+
archName)
1278+
}
1279+
return fmt.Errorf("failed to test QEMU emulation for architecture %s: %w", archName, err)
1280+
}
1281+
1282+
logrus.Debugf("Test of QEMU emulation for architecture %s has succeeded", archName)
1283+
return nil
1284+
}
1285+
12261286
func writeTimeZone(timeZone string) error {
12271287
const etcTimeZone = "/etc/timezone"
12281288

src/cmd/run.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -964,8 +964,15 @@ func showEntryPointLog(line string) error {
964964
}
965965

966966
if !logLevelFound {
967-
errMsg, _ := strings.CutPrefix(line, "Error: ")
968-
return &entryPointError{errMsg}
967+
// Messages sent to stderr with a 'Warning:' prefix in the entry point
968+
// are propagated to stderr on the host
969+
if strings.HasPrefix(line, "Warning:") {
970+
fmt.Fprintf(os.Stderr, "%s\n", line)
971+
return nil
972+
} else {
973+
errMsg, _ := strings.CutPrefix(line, "Error: ")
974+
return &entryPointError{errMsg}
975+
}
969976
}
970977

971978
logger := logrus.StandardLogger()

0 commit comments

Comments
 (0)