diff --git a/backend.go b/backend.go index 134ca01..d394678 100644 --- a/backend.go +++ b/backend.go @@ -1,5 +1,5 @@ -//go:build linux && amd64 -// +build linux,amd64 +//go:build linux +// +build linux package fakemachine diff --git a/backend_qemu.go b/backend_qemu.go index db4425a..cd56bb7 100644 --- a/backend_qemu.go +++ b/backend_qemu.go @@ -1,5 +1,5 @@ -//go:build linux && amd64 -// +build linux,amd64 +//go:build linux +// +build linux package fakemachine @@ -35,8 +35,18 @@ func (b qemuBackend) Supported() (bool, error) { return true, nil } +var qemuBinaryNames = map[Arch]string{ + Amd64: "qemu-system-x86_64", + Arm64: "qemu-system-aarch64", +} + func (b qemuBackend) QemuPath() (string, error) { - return exec.LookPath("qemu-system-x86_64") + binary, ok := qemuBinaryNames[b.machine.arch] + if !ok { + return "", fmt.Errorf("unsupported arch for qemu: %s", b.machine.arch) + } + + return exec.LookPath(binary) } func (b qemuBackend) KernelRelease() (string, error) { @@ -173,7 +183,7 @@ func (b qemuBackend) StartQemu(kvm bool) (bool, error) { } memory := fmt.Sprintf("%d", m.memory) numcpus := fmt.Sprintf("%d", m.numcpus) - qemuargs := []string{"qemu-system-x86_64", + qemuargs := []string{qemuBinaryNames[m.arch], "-smp", numcpus, "-m", memory, "-kernel", kernelPath, @@ -187,6 +197,14 @@ func (b qemuBackend) StartQemu(kvm bool) (bool, error) { "-enable-kvm") } + if m.arch == Arm64 { + qemuargs = append(qemuargs, "-machine", "virt") + if !kvm { + // QEMU uses a 32-bit CPU by default, so override it. + qemuargs = append(qemuargs, "-cpu", "cortex-a53") + } + } + kernelargs := []string{"console=ttyS0", "panic=-1", "systemd.unit=fakemachine.service"} diff --git a/backend_uml.go b/backend_uml.go index 3f0bc6e..c185817 100644 --- a/backend_uml.go +++ b/backend_uml.go @@ -1,5 +1,5 @@ -//go:build linux && amd64 -// +build linux,amd64 +//go:build linux +// +build linux package fakemachine @@ -27,6 +27,11 @@ func (b umlBackend) Name() string { } func (b umlBackend) Supported() (bool, error) { + // only support amd64 + if b.machine.arch != Amd64 { + return false, fmt.Errorf("unsupported arch: %s", b.machine.arch) + } + // check the kernel exists if _, err := b.KernelPath(); err != nil { return false, err diff --git a/cpio/writerhelper.go b/cpio/writerhelper.go index 72cce4d..eeef5d8 100644 --- a/cpio/writerhelper.go +++ b/cpio/writerhelper.go @@ -197,7 +197,7 @@ func (w *WriterHelper) CopyFileTo(src, dst string) error { f, err := os.Open(src) if err != nil { - return fmt.Errorf("open failed: %s - %v", src, err) + return fmt.Errorf("open failed: %s - %w", src, err) } defer f.Close() diff --git a/machine.go b/machine.go index 9f70239..18babb1 100644 --- a/machine.go +++ b/machine.go @@ -1,5 +1,5 @@ -//go:build linux && amd64 -// +build linux,amd64 +//go:build linux +// +build linux package fakemachine @@ -142,6 +142,18 @@ func realDir(path string) (string, error) { return filepath.Dir(p), nil } +type Arch string + +const ( + Amd64 Arch = "amd64" + Arm64 = "arm64" +) + +var archMap = map[string]Arch{ + "amd64": Amd64, + "arm64": Arm64, +} + type mountPoint struct { hostDirectory string machineDirectory string @@ -155,6 +167,7 @@ type image struct { } type Machine struct { + arch Arch backend backend mounts []mountPoint count int @@ -181,6 +194,11 @@ func NewMachineWithBackend(backendName string) (*Machine, error) { var err error m := &Machine{memory: 2048, numcpus: runtime.NumCPU()} + var ok bool + if m.arch, ok = archMap[runtime.GOARCH]; !ok { + return nil, fmt.Errorf("unsupported arch %s", runtime.GOARCH) + } + m.backend, err = newBackend(backendName, m) if err != nil { return nil, err @@ -199,10 +217,18 @@ func NewMachineWithBackend(backendName string) (*Machine, error) { if _, err := os.Stat("/etc/ca-certificates"); err == nil { m.AddVolume("/etc/ca-certificates") } + if _, err := os.Stat("/etc/pki"); err == nil { + m.AddVolume("/etc/pki") + } if _, err := os.Stat("/etc/ssl"); err == nil { m.AddVolume("/etc/ssl") } + // Mounts for gnutls (used by wget). + if _, err := os.Stat("/etc/crypto-policies"); err == nil { + m.AddVolume("/etc/crypto-policies") + } + // Dbus configuration if _, err := os.Stat("/etc/dbus-1"); err == nil { m.AddVolume("/etc/dbus-1") @@ -663,25 +689,51 @@ func (m *Machine) startup(command string, extracontent [][2]string) (int, error) } /* Ensure systemd-resolved is available */ - if _, err := os.Stat("/lib/systemd/systemd-resolved"); err != nil { + if _, err := os.Stat(prefix + "/lib/systemd/systemd-resolved"); err != nil { return -1, err } - /* Amd64 dynamic linker */ - err = w.CopyFile("/lib64/ld-linux-x86-64.so.2") + var glibcDynamicLinker, muslDynamicLinker string + if m.arch == Arm64 { + // arm64 dynamic linker is in /lib: + // https://sourceware.org/bugzilla/show_bug.cgi?id=25129 + glibcDynamicLinker = prefix + "/lib/ld-linux-aarch64.so.1" + muslDynamicLinker = prefix + "/lib/ld-musl-aarch64.so.1" + } else { + glibcDynamicLinker = prefix + "/lib64/ld-linux-x86-64.so.2" + muslDynamicLinker = prefix + "/lib64/ld-musl-x86_64.so.1" + } + + /* dynamic linkers */ + err = w.CopyFile(glibcDynamicLinker) if err != nil { return -1, err } + // Fedora's busybox needs musl + err = w.CopyFile(muslDynamicLinker) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return -1, err + } + /* C libraries */ - libraryDir, err := realDir("/lib64/ld-linux-x86-64.so.2") + libraryDir, err := realDir(glibcDynamicLinker) if err != nil { return -1, err } + err = w.CopyFile(libraryDir + "/libc.so.6") + if err != nil && errors.Is(err, os.ErrNotExist) && m.arch == Arm64 && strings.HasSuffix(libraryDir, "/lib") { + // Because the dynamic linker is in /lib, on systems with a separate + // lib64, it might actually be a in a different folder from libc. In + // that case, try using the lib64 directory. + libraryDir += "64" + err = w.CopyFile(libraryDir + "/libc.so.6") + } if err != nil { return -1, err } + err = w.CopyFile(libraryDir + "/libresolv.so.2") if err != nil { return -1, err