Skip to content
37 changes: 23 additions & 14 deletions src/cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"time"

"github.com/briandowns/spinner"
"github.com/containers/toolbox/pkg/architecture"
"github.com/containers/toolbox/pkg/podman"
"github.com/containers/toolbox/pkg/shell"
"github.com/containers/toolbox/pkg/skopeo"
Expand Down Expand Up @@ -486,19 +487,15 @@ func createContainer(container, image, release, authFile string, showCommandToEn
logrus.Debugf("%s", arg)
}

s := spinner.New(spinner.CharSets[9], 500*time.Millisecond, spinner.WithWriterFile(os.Stdout))
if logLevel := logrus.GetLevel(); logLevel < logrus.DebugLevel {
s.Prefix = fmt.Sprintf("Creating container %s: ", container)
s.Start()
defer s.Stop()
}
s := startSpinner(fmt.Sprintf("Creating container %s: ", container))
defer stopSpinner(s)

if err := shell.Run("podman", nil, nil, nil, createArgs...); err != nil {
return fmt.Errorf("failed to create container %s", container)
}

// The spinner must be stopped before showing the 'enter' hint below.
s.Stop()
stopSpinner(s)

if showCommandToEnter {
fmt.Printf("Created container: %s\n", container)
Expand Down Expand Up @@ -568,7 +565,7 @@ func getEnterCommand(container string) string {
}

func getImageSizeFromRegistry(ctx context.Context, imageFull string) (string, error) {
image, err := skopeo.Inspect(ctx, imageFull)
image, err := skopeo.Inspect(ctx, imageFull, architecture.HostArchID, "")
if err != nil {
return "", err
}
Expand Down Expand Up @@ -735,12 +732,8 @@ func pullImage(image, release, authFile string) (bool, error) {

logrus.Debugf("Pulling image %s", imageFull)

if logLevel := logrus.GetLevel(); logLevel < logrus.DebugLevel {
s := spinner.New(spinner.CharSets[9], 500*time.Millisecond, spinner.WithWriterFile(os.Stdout))
s.Prefix = fmt.Sprintf("Pulling %s: ", imageFull)
s.Start()
defer s.Stop()
}
s := startSpinner(fmt.Sprintf("Pulling %s: ", imageFull))
defer stopSpinner(s)

if err := podman.Pull(imageFull, authFile); err != nil {
var builder strings.Builder
Expand Down Expand Up @@ -963,6 +956,22 @@ func showPromptForDownload(imageFull string) bool {
return shouldPullImage
}

func startSpinner(message string) *spinner.Spinner {
if logLevel := logrus.GetLevel(); logLevel < logrus.DebugLevel {
s := spinner.New(spinner.CharSets[9], 500*time.Millisecond, spinner.WithWriterFile(os.Stdout))
s.Prefix = message
s.Start()
return s
}
return nil
}

func stopSpinner(s *spinner.Spinner) {
if s != nil {
s.Stop()
}
}

// systemdNeedsEscape checks whether a byte in a potential dbus ObjectPath needs to be escaped
func systemdNeedsEscape(i int, b byte) bool {
// Escape everything that is not a-z-A-Z-0-9
Expand Down
293 changes: 293 additions & 0 deletions src/pkg/architecture/architecture.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
/*
* Copyright © 2019 – 2026 Red Hat Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package architecture

import (
"debug/elf"
"errors"
"fmt"
"os"
"os/exec"
"runtime"
"strings"

"github.com/containers/toolbox/pkg/utils"
"github.com/sirupsen/logrus"
)

type Architecture struct {
ID int
NameBinfmt string
NameOCI string
Aliases []string
ELFMagic []byte
ELFMask []byte

BinfmtFlags string
BinfmtName string
BinfmtMagicType string
BinfmtOffset string
}

type Config struct {
ID int
QemuEmulatorPath string
}

const (
NotSpecified = iota
Aarch64
Ppc64le
X86_64
)

var supportedArchitectures = map[int]Architecture{
Aarch64: {
ID: Aarch64,
NameBinfmt: "aarch64",
NameOCI: "arm64",
Aliases: []string{"aarch64", "arm64"},
ELFMagic: []byte{0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xb7, 0x00},
ELFMask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff},
},
Ppc64le: {
ID: Ppc64le,
NameBinfmt: "ppc64le",
NameOCI: "ppc64le",
Aliases: []string{"ppc64le"},
ELFMagic: []byte{0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x15, 0x00},
ELFMask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0x00},
},
X86_64: {
ID: X86_64,
NameBinfmt: "x86_64",
NameOCI: "amd64",
Aliases: []string{"x86_64", "amd64"},
ELFMagic: []byte{0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00},
ELFMask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff},
},
}

var (
HostArchID int
supportedArgArchValues map[string]int
)

func init() {
supportedArgArchValues = make(map[string]int)
for archID, arch := range supportedArchitectures {
for _, alias := range arch.Aliases {
supportedArgArchValues[alias] = archID
}
}

HostArchID, _ = ParseArgArchValue(runtime.GOARCH)
}

func GetArchConfigDefault() Config {
return Config{
ID: HostArchID,
QemuEmulatorPath: "",
}
}

func getArchitecture(archID int) (Architecture, bool) {
arch, exists := supportedArchitectures[archID]
return arch, exists
}

func getArchNameBinfmt(arch int) string {
if arch == NotSpecified {
logrus.Warnf("Getting arch name for not specified architecture")
return "arch_not_specified"
}
if archObj, exists := supportedArchitectures[arch]; exists {
return archObj.NameBinfmt
}
return ""
}

func GetArchNameOCI(arch int) string {
if arch == NotSpecified {
logrus.Warnf("Getting arch name for not specified architecture")
return "arch_not_specified"
}
if archObj, exists := supportedArchitectures[arch]; exists {
return archObj.NameOCI
}
return ""
}
Comment on lines +124 to +133

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Returning the string "arch_not_specified" for an OCI architecture name is problematic because it can be passed directly to external tools like skopeo, which will fail to recognize it. It would be safer to return an empty string or handle the NotSpecified case explicitly in callers.

func GetArchNameOCI(arch int) string {
	if arch == NotSpecified {
		return ""
	}
	if archObj, exists := supportedArchitectures[arch]; exists {
		return archObj.NameOCI
	}
	return ""
}


func HasContainerNativeArch(archID int) bool {
return archID == HostArchID
}

func ImageReferenceGetArchFromTag(image string) int {
tag := utils.ImageReferenceGetTag(image)

if tag == "" {
return NotSpecified
}

i := strings.LastIndexByte(tag, '-')
if i == -1 {
return NotSpecified
}

archInTag := tag[i+1:]

for archID, arch := range supportedArchitectures {
if arch.NameBinfmt == archInTag || arch.NameOCI == archInTag {
return archID
}
}

return NotSpecified
}

func IsArchSupportedOnCreation(archID int) (string, error) {
archName := getArchNameBinfmt(archID)
archNameDebug := GetArchNameOCI(archID)
logrus.Debugf("Checking QEMU emulation support for architecture %s", archNameDebug)

qemuBinaryPossibleNames := []string{
fmt.Sprintf("qemu-%s-static", archName),
fmt.Sprintf("qemu-%s", archName),
}

foundQemuBinaryPath := ""
for _, qemuName := range qemuBinaryPossibleNames {
qemuBinaryPath, err := exec.LookPath(qemuName)

if err != nil {
if errors.Is(err, exec.ErrNotFound) {
continue
}

return "", fmt.Errorf("failed to look up binary '%s': %w", qemuName, err)
}

if isStaticallyLinkedELF(qemuBinaryPath) {
foundQemuBinaryPath = qemuBinaryPath
break
}
}

if foundQemuBinaryPath == "" {
err := fmt.Errorf("The host system does not have the required support: No %s statically linked QEMU emulator binary found", archNameDebug)
return "", err
}

if !validateBinfmtRegistration(archID, false) {
err := fmt.Errorf("The host system does not have the required support: No %s binfmt_misc registration found", archNameDebug)
return "", err
}

return foundQemuBinaryPath, nil
}

func IsArchSupportedOnInitialization(archID int, interpreterPath string) (string, error) {
archName := getArchNameBinfmt(archID)
archNameDebug := GetArchNameOCI(archID)
logrus.Debugf("Checking QEMU emulation support for architecture %s", archNameDebug)

if isStaticallyLinkedELF(interpreterPath) {
if !validateBinfmtRegistration(archID, true) {
return "", fmt.Errorf("The host system does not have the required support: No %s binfmt_misc registration found", archNameDebug)
}
return interpreterPath, nil
}

// Fallback: check standard locations on the host
logrus.Debugf("Interpreter at %s not found or not statically linked, checking fallback locations in '/run/host/usr/bin/'", interpreterPath)
fmt.Fprintf(os.Stderr, "Warning: QEMU emulator not found at expected path '%s', using fallback at '/run/host/usr/bin/'\n", interpreterPath)

qemuBinaryPossiblePaths := []string{
fmt.Sprintf("/run/host/usr/bin/qemu-%s-static", archName),
fmt.Sprintf("/run/host/usr/bin/qemu-%s", archName),
}

for _, qemuPath := range qemuBinaryPossiblePaths {
if isStaticallyLinkedELF(qemuPath) {
logrus.Debugf("Found valid QEMU binary at %s", qemuPath)

if !validateBinfmtRegistration(archID, true) {
return "", fmt.Errorf("The host system does not have the required support: No %s binfmt_misc registration found", archNameDebug)
}
return qemuPath, nil
}
}

return "", fmt.Errorf("The host system does not have the required support: No %s statically linked QEMU emulator binary found", archNameDebug)
}

func isStaticallyLinkedELF(filePath string) bool {
if !utils.PathExists(filePath) {
logrus.Debugf("File '%s' does not exist\n", filePath)
return false
}

f, err := elf.Open(filePath)
if err != nil {
logrus.Debugf("File '%s' is not an ELF file\n", filePath)
return false
}
defer f.Close()

// Check for PT_INTERP program header
for _, prog := range f.Progs {
if prog.Type == elf.PT_INTERP {
// Dynamically linked
logrus.Debugf("File '%s' is dynamically linked\n", filePath)
return false
}
}

// Statically linked
return true
}

func ParseArgArchValue(value string) (int, error) {
archID, exists := supportedArgArchValues[value]
if !exists {
return NotSpecified, fmt.Errorf("architecture '%s' is not supported by Toolbx", value)
}

return archID, nil
}

func validateBinfmtRegistration(archID int, withinContainer bool) bool {
archName := getArchNameBinfmt(archID)
inContainerPathPrefix := ""

if withinContainer {
inContainerPathPrefix = "/run/host"
}

qemuBinfmtPossiblePaths := []string{
fmt.Sprintf("%s/proc/sys/fs/binfmt_misc/qemu-%s", inContainerPathPrefix, archName),
fmt.Sprintf("%s/proc/sys/fs/binfmt_misc/qemu-%s-static", inContainerPathPrefix, archName),
}

for _, binfmtPath := range qemuBinfmtPossiblePaths {
if utils.PathExists(binfmtPath) {
logrus.Debugf("Architecture %s is supported", archName)
return true
}
}
return false
}
Loading
Loading