Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions src/cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,19 +486,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 @@ -735,12 +731,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 +955,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 ""
}

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)
Comment on lines +162 to +163
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

If archID is NotSpecified, this function proceeds to look for a binary named qemu-arch_not_specified-static, which will fail and result in a confusing error message for the user. It would be better to validate that archID is a supported architecture at the beginning of the function.

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)
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

Bypassing the logging system by writing directly to os.Stderr is inconsistent with the rest of the codebase. Using logrus.Warnf ensures that the message follows the configured log format and level.

Suggested change
fmt.Fprintf(os.Stderr, "Warning: QEMU emulator not found at expected path '%s', using fallback at '/run/host/usr/bin/'\n", interpreterPath)
logrus.Warnf("QEMU emulator not found at expected path '%s', using fallback at '/run/host/usr/bin/'", 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