diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89af7ff..d1c4c2f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v4 - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 @@ -46,9 +46,9 @@ jobs: # functionality without hardware acceleration since the majority of code # is shared between the qemu and kvm backends. # See https://github.com/actions/runner-images/issues/183 - # + # # For Arch Linux uml is not yet supported, so only test under qemu there. - os: [bullseye, bookworm] + os: [bullseye, bookworm, trixie] backend: [qemu, uml] include: - os: arch @@ -79,15 +79,17 @@ jobs: - name: Ensure no tests were skipped run: "! grep -q SKIP test.out" - # Job to key the bors success status against - bors: - name: bors - if: success() + # Job to key success status against + allgreen: + name: allgreen + if: always() needs: - golangci - man-page - test runs-on: ubuntu-latest steps: - - name: Mark the job as a success - run: exit 0 + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.golangci.yml b/.golangci.yml index 72f3064..6b63b2b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,4 +1,5 @@ linters: enable: - gofmt + - stylecheck - whitespace diff --git a/backend.go b/backend.go index 5a4ae2a..478911a 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 @@ -71,7 +71,7 @@ func newBackend(name string, m *Machine) (backend, error) { // check backend is supported if supported, err := b.Supported(); !supported { - return nil, fmt.Errorf("%s backend not supported: %v", name, err) + return nil, fmt.Errorf("%s backend not supported: %w", name, err) } return b, nil diff --git a/backend_qemu.go b/backend_qemu.go index db4425a..840e7bd 100644 --- a/backend_qemu.go +++ b/backend_qemu.go @@ -1,5 +1,4 @@ -//go:build linux && amd64 -// +build linux,amd64 +//go:build linux && (arm64 || amd64) package fakemachine @@ -35,8 +34,40 @@ func (b qemuBackend) Supported() (bool, error) { return true, nil } +type qemuMachine struct { + binary string + console string + machine string + /* Cpu to use for qemu backend if the architecture doesn't have a good default */ + qemuCPU string +} + +var qemuMachines = map[Arch]qemuMachine{ + Amd64: { + binary: "qemu-system-x86_64", + console: "ttyS0", + machine: "pc", + }, + Arm64: { + binary: "qemu-system-aarch64", + console: "ttyAMA0", + machine: "virt", + /* The default cpu is a 32 bit one, which isn't very usefull + * for 64 bit arm. There is no cpu setting for "minimal" 64 + * bit linux capable processor. The only generic setting + * is "max", but that can be very slow to emulate. So pick + * a specific small cortex-a processor instead */ + qemuCPU: "cortex-a53", + }, +} + func (b qemuBackend) QemuPath() (string, error) { - return exec.LookPath("qemu-system-x86_64") + machine, ok := qemuMachines[b.machine.arch] + if !ok { + return "", fmt.Errorf("unsupported arch for qemu: %s", b.machine.arch) + } + + return exec.LookPath(machine.binary) } func (b qemuBackend) KernelRelease() (string, error) { @@ -68,7 +99,7 @@ func (b qemuBackend) KernelRelease() (string, error) { } } - return "", fmt.Errorf("No kernel found") + return "", fmt.Errorf("kernel not found") } func (b qemuBackend) KernelPath() (string, error) { @@ -166,6 +197,7 @@ func (b qemuBackend) Start() (bool, error) { func (b qemuBackend) StartQemu(kvm bool) (bool, error) { m := b.machine + qemuMachine := qemuMachines[m.arch] kernelPath, err := b.KernelPath() if err != nil { @@ -173,7 +205,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{qemuMachine.binary, "-smp", numcpus, "-m", memory, "-kernel", kernelPath, @@ -185,9 +217,13 @@ func (b qemuBackend) StartQemu(kvm bool) (bool, error) { qemuargs = append(qemuargs, "-cpu", "host", "-enable-kvm") + } else if qemuMachine.qemuCPU != "" { + qemuargs = append(qemuargs, "-cpu", qemuMachine.qemuCPU) } - kernelargs := []string{"console=ttyS0", "panic=-1", + qemuargs = append(qemuargs, "-machine", qemuMachine.machine) + console := fmt.Sprintf("console=%s", qemuMachine.console) + kernelargs := []string{console, "panic=-1", "systemd.unit=fakemachine.service"} if m.showBoot { diff --git a/backend_uml.go b/backend_uml.go index 3f0bc6e..fa5a589 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 @@ -45,7 +50,7 @@ func (b umlBackend) Supported() (bool, error) { } func (b umlBackend) KernelRelease() (string, error) { - return "", errors.New("Not implemented") + return "", errors.New("not implemented") } func (b umlBackend) KernelPath() (string, error) { @@ -162,7 +167,7 @@ func (b umlBackend) Start() (bool, error) { // one of the sockets will be attached to the slirp-helper slirpHelperSocket := os.NewFile(uintptr(netSocketpair[0]), "") if slirpHelperSocket == nil { - return false, fmt.Errorf("Creation of slirpHelperSocket failed") + return false, fmt.Errorf("creation of slirpHelperSocket failed") } defer slirpHelperSocket.Close() diff --git a/bors.toml b/bors.toml deleted file mode 100644 index 1db5825..0000000 --- a/bors.toml +++ /dev/null @@ -1,2 +0,0 @@ -status = [ "bors" ] -delete_merged_branches = true diff --git a/cmd/fakemachine/main.go b/cmd/fakemachine/main.go index bc5a028..6e69926 100644 --- a/cmd/fakemachine/main.go +++ b/cmd/fakemachine/main.go @@ -91,7 +91,7 @@ func SetupEnviron(m *fakemachine.Machine, options Options) { // These are the environment variables that will be detected on the // host and propagated to fakemachine. These are listed lower case, but // they are detected and configured in both lower case and upper case. - var environ_vars = [...]string{ + var environVars = [...]string{ "http_proxy", "https_proxy", "ftp_proxy", @@ -101,7 +101,7 @@ func SetupEnviron(m *fakemachine.Machine, options Options) { } // First add variables from host - for _, e := range environ_vars { + for _, e := range environVars { lowerVar := strings.ToLower(e) // lowercase not really needed lowerVal := os.Getenv(lowerVar) if lowerVal != "" { 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/decompressors_test.go b/decompressors_test.go index 8b1a77e..cd5aab8 100644 --- a/decompressors_test.go +++ b/decompressors_test.go @@ -58,14 +58,14 @@ func decompressorTest(t *testing.T, file, suffix string, d writerhelper.Transfor return } - check_f, err := os.Open(path.Join("testdata", file)) + checkFile, err := os.Open(path.Join("testdata", file)) if err != nil { t.Errorf("Unable to open check data: %s", err) return } - defer check_f.Close() + defer checkFile.Close() - err = checkStreamsMatch(t, output, check_f) + err = checkStreamsMatch(t, output, checkFile) if err != nil { t.Errorf("Failed to compare streams: %s", err) return diff --git a/machine.go b/machine.go index eeb3322..1fdfc35 100644 --- a/machine.go +++ b/machine.go @@ -1,5 +1,4 @@ -//go:build linux && amd64 -// +build linux,amd64 +//go:build linux && (arm64 || amd64) package fakemachine @@ -68,6 +67,16 @@ func getModDepends(modname string, kernelRelease string) []string { } } + // Busybox expects a full dependency list for each module rather than just + // direct dependencies, so recurse the module dependency tree: + // https://github.com/mirror/busybox/blob/1dd2685dcc735496d7adde87ac60b9434ed4a04c/modutils/modprobe.c#L46-L49 + var sublist []string + for _, mod := range modlist { + sublist = append(sublist, getModDepends(mod, kernelRelease)...) + } + + modlist = append(modlist, sublist...) + return modlist } @@ -82,7 +91,7 @@ func (m *Machine) copyModules(w *writerhelper.WriterHelper, modname string, copi release, _ := m.backend.KernelRelease() modpath := getModPath(modname, release) if modpath == "" { - return errors.New("Modules path couldn't be determined") + return errors.New("modules path couldn't be determined") } if modpath == "(builtin)" || copiedModules[modname] { @@ -114,7 +123,7 @@ func (m *Machine) copyModules(w *writerhelper.WriterHelper, modname string, copi } } if !found { - return errors.New("Module extension/suffix unknown") + return errors.New("module extension/suffix unknown") } copiedModules[modname] = true @@ -143,6 +152,23 @@ func realDir(path string) (string, error) { return filepath.Dir(p), nil } +type Arch string + +const ( + Amd64 Arch = "amd64" + Arm64 Arch = "arm64" +) + +var archMap = map[string]Arch{ + "amd64": Amd64, + "arm64": Arm64, +} + +var archDynamicLinker = map[Arch]string{ + Amd64: "/lib64/ld-linux-x86-64.so.2", + Arm64: "/lib/ld-linux-aarch64.so.1", +} + type mountPoint struct { hostDirectory string machineDirectory string @@ -156,6 +182,7 @@ type image struct { } type Machine struct { + arch Arch backend backend mounts []mountPoint count int @@ -183,6 +210,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 @@ -395,12 +427,12 @@ func (m *Machine) CreateImageWithLabel(path string, size int64, label string) (s } if len(label) >= 20 { - return "", fmt.Errorf("Label '%s' too long; cannot be more then 20 characters", label) + return "", fmt.Errorf("label '%s' too long; cannot be more then 20 characters", label) } for _, image := range m.images { if image.label == label { - return "", fmt.Errorf("Label '%s' already exists", label) + return "", fmt.Errorf("label '%s' already exists", label) } } @@ -496,7 +528,7 @@ func stripCompressionSuffix(module string) (string, error) { return strings.TrimSuffix(module, suffix) + ".ko", nil } } - return "", errors.New("Module extension/suffix unknown") + return "", errors.New("module extension/suffix unknown") } func (m *Machine) generateModulesDep(w *writerhelper.WriterHelper, moddir string, modules map[string]bool) error { @@ -591,17 +623,17 @@ func (m *Machine) startup(command string, extracontent [][2]string) (int, error) /* Check the directory exists on the host */ stat, err := os.Stat(v.hostDirectory) if err != nil || !stat.IsDir() { - return -1, fmt.Errorf("Couldn't mount %s inside machine: expected a directory", v.hostDirectory) + return -1, fmt.Errorf("couldn't mount %s inside machine: expected a directory", v.hostDirectory) } /* Check for whitespace in the machine directory */ if regexp.MustCompile(`\s`).MatchString(v.machineDirectory) { - return -1, fmt.Errorf("Couldn't mount %s inside machine: machine directory (%s) contains whitespace", v.hostDirectory, v.machineDirectory) + return -1, fmt.Errorf("couldn't mount %s inside machine: machine directory (%s) contains whitespace", v.hostDirectory, v.machineDirectory) } /* Check for whitespace in the label */ if regexp.MustCompile(`\s`).MatchString(v.label) { - return -1, fmt.Errorf("Couldn't mount %s inside machine: label (%s) contains whitespace", v.hostDirectory, v.label) + return -1, fmt.Errorf("couldn't mount %s inside machine: label (%s) contains whitespace", v.hostDirectory, v.label) } } @@ -659,6 +691,7 @@ func (m *Machine) startup(command string, extracontent [][2]string) (int, error) {Target: "/usr/sbin", Link: "/sbin", Perm: 0755}, {Target: "/usr/bin", Link: "/bin", Perm: 0755}, {Target: "/usr/lib", Link: "/lib", Perm: 0755}, + {Target: "/usr/lib64", Link: "/lib64", Perm: 0755}, }) if err != nil { return -1, err @@ -694,14 +727,14 @@ func (m *Machine) startup(command string, extracontent [][2]string) (int, error) return -1, err } - /* Amd64 dynamic linker */ - err = w.CopyFile("/lib64/ld-linux-x86-64.so.2") + dynamicLinker := archDynamicLinker[m.arch] + err = w.CopyFile(prefix + dynamicLinker) if err != nil { return -1, err } /* C libraries */ - libraryDir, err := realDir("/lib64/ld-linux-x86-64.so.2") + libraryDir, err := realDir(dynamicLinker) if err != nil { return -1, err } @@ -833,7 +866,7 @@ func (m *Machine) startup(command string, extracontent [][2]string) (int, error) success, err := backend.Start() if !success || err != nil { - return -1, fmt.Errorf("error starting %s backend: %v", backend.Name(), err) + return -1, fmt.Errorf("error starting %s backend: %w", backend.Name(), err) } result, err := os.Open(path.Join(tmpdir, "result")) @@ -867,7 +900,7 @@ func (m *Machine) RunInMachineWithArgs(args []string) (int, error) { executable, err := exec.LookPath(os.Args[0]) if err != nil { - return -1, fmt.Errorf("Failed to find executable: %v\n", err) + return -1, fmt.Errorf("failed to find executable: %w", err) } return m.startup(command, [][2]string{{executable, name}})