Skip to content

Commit 8af82c9

Browse files
The127claude
andauthored
feat: run the steps of another architecture through the host's qemu-user-static (#52)
* feat: register an emulator under its own name Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: karo <karolin.kostial@gmail.com> * feat: run the steps of another architecture through the host's qemu-user-static Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: karo <karolin.kostial@gmail.com> * build: recommend qemu-user-static Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: karo <karolin.kostial@gmail.com> * feat: refuse an emulator the builder VM cannot run, naming where it is from Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: karo <karolin.kostial@gmail.com> --------- Signed-off-by: karo <karolin.kostial@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5196ee1 commit 8af82c9

20 files changed

Lines changed: 175 additions & 227 deletions

‎.goreleaser.yaml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,13 @@ nfpms:
4848
- qemu-system-x86
4949
recommends:
5050
- qemu-system-arm
51+
- qemu-user-static
5152
rpm:
5253
dependencies:
5354
- qemu-system-x86-core
5455
recommends:
5556
- qemu-system-aarch64-core
57+
- qemu-user-static-aarch64
5658
- id: arm64
5759
ids: [arm64]
5860
package_name: miso
@@ -72,11 +74,13 @@ nfpms:
7274
- qemu-system-arm
7375
recommends:
7476
- qemu-system-x86
77+
- qemu-user-static
7578
rpm:
7679
dependencies:
7780
- qemu-system-aarch64-core
7881
recommends:
7982
- qemu-system-x86-core
83+
- qemu-user-static-x86
8084

8185
checksum:
8286
name_template: checksums.txt

‎arch-go.yml‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -452,16 +452,14 @@ dependenciesRules:
452452
- os/exec
453453
- net
454454
- syscall
455-
# the emulator takes miso's pinned qemu-user package apart into what runs
456-
# the programs of an image of another architecture. It has the download
457-
# fetch the package, and never starts a process, opens a connection or
458-
# touches the kernel
455+
# the emulator finds the host's QEMU that runs the programs of an image of
456+
# another architecture. It knows nothing of ours, and never starts a
457+
# process, opens a connection or touches the kernel
459458
- package: '**.internal.emulator$'
460-
shouldOnlyDependsOn:
461-
internal:
462-
- '**.internal.deb$'
463-
- '**.internal.download$'
464459
shouldNotDependsOn:
460+
internal:
461+
- '**.internal.**'
462+
- '**.cmd.**'
465463
standard:
466464
- os/exec
467465
- net

‎cmd/miso/arch_test.go‎

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
package main_test
22

33
import (
4-
"context"
5-
"errors"
6-
"net/http"
74
"testing"
85

96
"github.com/stretchr/testify/assert"
107
"github.com/stretchr/testify/require"
118

129
main "github.com/The127/miso/cmd/miso"
13-
"github.com/The127/miso/internal/download"
1410
)
1511

1612
func TestAHostMisoDoesNotBuildOnIsRefusedNamingItAndWhereMisoBuilds(t *testing.T) {
@@ -23,24 +19,14 @@ func TestAHostMisoDoesNotBuildOnIsRefusedNamingItAndWhereMisoBuilds(t *testing.T
2319
assert.ErrorContains(t, err, "amd64 and arm64")
2420
}
2521

26-
func TestABuildForTheBuildersOwnArchitectureFetchesNoEmulator(t *testing.T) {
22+
func TestABuildForTheBuildersOwnArchitectureNeedsNoEmulator(t *testing.T) {
2723
// arrange
28-
offline := &http.Client{Transport: roundTrip(func(*http.Request) (*http.Response, error) {
29-
return nil, errors.New("offline")
30-
})}
31-
store := download.Open(t.TempDir(), offline)
24+
t.Setenv("PATH", t.TempDir())
3225

3326
// act
34-
emulators, err := main.EmulatorsFor(context.Background(), store, main.BuilderArch)
27+
emulators, err := main.EmulatorsFor(main.BuilderArch)
3528

3629
// assert
3730
require.NoError(t, err)
3831
assert.Empty(t, emulators)
3932
}
40-
41-
// roundTrip is an HTTP transport that is a function.
42-
type roundTrip func(*http.Request) (*http.Response, error)
43-
44-
func (f roundTrip) RoundTrip(request *http.Request) (*http.Response, error) {
45-
return f(request)
46-
}

‎cmd/miso/vm.go‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,17 @@ func hostRefusal(host string) error {
3838

3939
// emulatorsFor are what the builder VM needs to run the programs of a build
4040
// for target, nothing for a build of its own architecture.
41-
func emulatorsFor(ctx context.Context, blobs *download.Store, target string) ([]initramfs.Emulator, error) {
41+
func emulatorsFor(target string) ([]initramfs.Emulator, error) {
4242
if target == builderArch {
4343
return nil, nil
4444
}
4545

46-
program, err := emulator.Ready(ctx, blobs, builderArch, target)
46+
from, program, err := emulator.Host(target)
4747
if err != nil {
4848
return nil, err
4949
}
5050

51-
return []initramfs.Emulator{{Arch: target, Program: program}}, nil
51+
return []initramfs.Emulator{{Arch: target, From: from, Program: program}}, nil
5252
}
5353

5454
// bootFiles writes the builder kernel and an initramfs with this very miso
@@ -60,7 +60,7 @@ func bootFiles(ctx context.Context, blobs *download.Store, target string) (build
6060
return builder.Boot{}, err
6161
}
6262

63-
emulators, err := emulatorsFor(ctx, blobs, target)
63+
emulators, err := emulatorsFor(target)
6464
if err != nil {
6565
return builder.Boot{}, err
6666
}

‎internal/boot/emulators.go‎

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import (
99
)
1010

1111
// magics are what the header of a program of an architecture looks like to
12-
// binfmt_misc, and the mask it is compared under, as Debian's qemu-user
13-
// registers them.
12+
// binfmt_misc, and the mask it is compared under, as QEMU registers them.
1413
var magics = map[string]struct{ magic, mask string }{
1514
"amd64": {
1615
magic: `\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00`,
@@ -55,9 +54,8 @@ const binfmtMisc = "/proc/sys/fs/binfmt_misc"
5554

5655
// register has each emulator in dir, named by its architecture, run the
5756
// programs of that architecture, through a write of its binfmt_misc entry.
58-
// Debian's QEMU keeps a program's argv[0] only when its own name ends in
59-
// -binfmt-P, so it is registered through a link of that name. F opens it
60-
// right away, so it runs in the root of a run too.
57+
// P has the kernel tell QEMU to keep a program's argv[0]. F opens it right
58+
// away, so it runs in the root of a run too.
6159
func register(dir string, write func(entry string) error) error {
6260
found, err := os.ReadDir(dir)
6361
if err != nil {
@@ -71,12 +69,8 @@ func register(dir string, write func(entry string) error) error {
7169
return fmt.Errorf("no binfmt_misc entry for an emulator of %s", arch)
7270
}
7371

74-
link := filepath.Join(dir, arch+"-binfmt-P")
75-
if err := os.Symlink(arch, link); err != nil {
76-
return err
77-
}
78-
79-
if err := write(fmt.Sprintf(":%s:M::%s:%s:%s:OPF", arch, magic.magic, magic.mask, link)); err != nil {
72+
program := filepath.Join(dir, arch)
73+
if err := write(fmt.Sprintf(":%s:M::%s:%s:%s:OPF", arch, magic.magic, magic.mask, program)); err != nil {
8074
return err
8175
}
8276
}

‎internal/boot/emulators_test.go‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"github.com/The127/miso/internal/boot"
1212
)
1313

14-
func TestAnArm64EmulatorIsRegisteredAsDebianRegistersQEMUForAArch64(t *testing.T) {
14+
func TestAnArm64EmulatorIsRegisteredToKeepTheNameOfTheProgramItRuns(t *testing.T) {
1515
// arrange
1616
dir := t.TempDir()
1717
require.NoError(t, os.WriteFile(filepath.Join(dir, "arm64"), []byte("qemu"), 0o600))
@@ -27,11 +27,11 @@ func TestAnArm64EmulatorIsRegisteredAsDebianRegistersQEMUForAArch64(t *testing.T
2727
// assert
2828
require.NoError(t, err)
2929
assert.Equal(t, []string{
30-
`:arm64:M::\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:` + filepath.Join(dir, "arm64-binfmt-P") + `:OPF`,
30+
`:arm64:M::\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:` + filepath.Join(dir, "arm64") + `:OPF`,
3131
}, written)
3232
}
3333

34-
func TestAnAmd64EmulatorIsRegisteredAsDebianRegistersQEMUForX86_64(t *testing.T) {
34+
func TestAnAmd64EmulatorIsRegisteredToKeepTheNameOfTheProgramItRuns(t *testing.T) {
3535
// arrange
3636
dir := t.TempDir()
3737
require.NoError(t, os.WriteFile(filepath.Join(dir, "amd64"), []byte("qemu"), 0o600))
@@ -47,7 +47,7 @@ func TestAnAmd64EmulatorIsRegisteredAsDebianRegistersQEMUForX86_64(t *testing.T)
4747
// assert
4848
require.NoError(t, err)
4949
assert.Equal(t, []string{
50-
`:amd64:M::\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00:\xff\xff\xff\xff\xff\xfe\xfe\xfc\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:` + filepath.Join(dir, "amd64-binfmt-P") + `:OPF`,
50+
`:amd64:M::\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00:\xff\xff\xff\xff\xff\xfe\xfe\xfc\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:` + filepath.Join(dir, "amd64") + `:OPF`,
5151
}, written)
5252
}
5353

‎internal/builder/boot_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func TestTheBootFilesHoldTheEmulatorOfTheArchitectureTheBuildIsFor(t *testing.T)
5151
// arrange
5252
kernel := builderkernel.Kernel{Image: []byte("the kernel")}
5353
init := static(t)
54-
emulator := initramfs.Emulator{Arch: "arm64", Program: []byte("qemu")}
54+
emulator := initramfs.Emulator{Arch: "arm64", From: "/usr/bin/qemu-aarch64-static", Program: static(t)}
5555
var want bytes.Buffer
5656
require.NoError(t, initramfs.Write(&want, "amd64", init, nil, emulator))
5757

‎internal/emulator/doc.go‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Package emulator readies the QEMU that runs the programs of an image of
2-
// another architecture than the builder VM's: it takes the pinned Debian
3-
// qemu-user package of the builder's architecture apart.
1+
// Package emulator finds the host's QEMU that runs the programs of an image
2+
// of another architecture than the builder VM's. The package manager keeps
3+
// it up to date, not miso.
44
package emulator

‎internal/emulator/export_test.go‎

Lines changed: 0 additions & 5 deletions
This file was deleted.

‎internal/emulator/host.go‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package emulator
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
)
8+
9+
// statics are what qemu-user-static calls the program that runs the
10+
// programs of an architecture, on Debian, Fedora and Arch alike.
11+
var statics = map[string]string{
12+
"amd64": "qemu-x86_64-static",
13+
"arm64": "qemu-aarch64-static",
14+
}
15+
16+
// Host is the host's QEMU that runs the programs of target, found on PATH,
17+
// and where it was found.
18+
func Host(target string) (string, []byte, error) {
19+
name, known := statics[target]
20+
if !known {
21+
return "", nil, fmt.Errorf("miso has no QEMU that runs programs of %s", target)
22+
}
23+
24+
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
25+
path := filepath.Join(dir, name)
26+
if found, err := os.ReadFile(path); err == nil { //nolint:gosec // the user's PATH is where their QEMU is
27+
return path, found, nil
28+
}
29+
}
30+
31+
return "", nil, fmt.Errorf("a build for %s runs its steps through %s, which the qemu-user-static package installs", target, name)
32+
}

0 commit comments

Comments
 (0)