1
0

18 Commits

Author SHA1 Message Date
8fa62e9a6e Bump version 2023-04-26 14:36:25 +00:00
e04154b235 Fix stderr log 2023-04-26 14:35:20 +00:00
096cad8701 Cleanup also -core/-modules 2023-04-09 17:13:54 +00:00
70d464f0e2 Fix directory name 2023-04-09 14:38:39 +00:00
d65d683dfc Fix name of ubuntu image generator 2023-04-09 14:16:29 +00:00
bde115f5df Bump version 2023-04-08 14:57:39 +00:00
d972bae547 Send SIGINT first while killing the container by timeout 2023-04-08 14:50:18 +00:00
b3d4a0dbc2 Update changelog 2023-04-07 21:38:30 +00:00
4a3d739b85 Implements dry run for image edit 2023-04-07 21:30:03 +00:00
bb319a9ff6 Export qemu arguments 2023-04-07 21:17:34 +00:00
21daac4fbc Check for shutdown before log current kernel 2023-04-07 21:03:31 +00:00
841fd7f585 Graceful shutdown on ^C 2023-04-07 20:52:45 +00:00
b812048408 Typo 2023-04-07 19:11:42 +00:00
a5edc4837f Update readme 2023-04-07 19:09:33 +00:00
9e55ebd44e Add a flag to set the container runtime binary 2023-04-07 18:57:18 +00:00
e35e030c54 Install the kernel in a single container run 2023-04-07 17:47:54 +00:00
a4f2a31819 Correctly handle empty workdir 2023-04-07 17:46:36 +00:00
c3cf25e523 Allow to disable container volumes mount 2023-04-07 17:35:00 +00:00
16 changed files with 205 additions and 90 deletions

View File

@ -24,7 +24,7 @@ jobs:
sudo apt-get install qemu-system-x86 sudo apt-get install qemu-system-x86
- name: Bootstrap - name: Bootstrap
run: ./tools/qemu-debian-img/bootstrap.sh run: ./tools/qemu-ubuntu-img/bootstrap.sh
- name: Unit Testing - name: Unit Testing
run: go test -parallel 1 -v ./... run: go test -parallel 1 -v ./...

View File

@ -4,6 +4,30 @@
[Semantic Versioning](https://semver.org/spec/v2.0.0.html). [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.1.0]
### Added
- Graceful shutdown on ^C while kernels generation.
- Flag to set the container runtime command.
- out-of-tree image --dry-run for printing full qemu command.
### Changed
- No exit at the end of the retries, will continue with the other
kernels.
- All temporary files moved to ~/.out-of-tree/tmp/.
### Fixed
- Discrepancies between /lib/modules and /boot should no longer lead
to fatal errors.
- Podman support on macOS.
## [2.0.0] ## [2.0.0]
### Breaking ### Breaking

View File

@ -23,8 +23,13 @@ Note that adding a user to group *docker* has serious security implications. Che
### macOS ### macOS
$ brew install --cask docker Note: case-sensitive FS is required for the ~/.out-of-tree directory.
$ open --background -a Docker && sleep 1m
$ brew install podman
$ podman machine stop || true
$ podman machine rm || true
$ podman machine init --cpus=4 --memory=4096 -v $HOME:$HOME
$ podman machine start
$ brew tap out-of-tree/repo $ brew tap out-of-tree/repo
$ brew install out-of-tree $ brew install out-of-tree

View File

@ -20,6 +20,8 @@ import (
"code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/config"
) )
var containerRuntime = "docker"
type ContainerCmd struct { type ContainerCmd struct {
Filter string `help:"filter by name"` Filter string `help:"filter by name"`
@ -56,7 +58,8 @@ type ContainerCleanupCmd struct{}
func (cmd ContainerCleanupCmd) Run(containerCmd *ContainerCmd) (err error) { func (cmd ContainerCleanupCmd) Run(containerCmd *ContainerCmd) (err error) {
var output []byte var output []byte
for _, name := range containerCmd.Containers() { for _, name := range containerCmd.Containers() {
output, err = exec.Command("docker", "image", "rm", name).CombinedOutput() output, err = exec.Command(containerRuntime, "image", "rm", name).
CombinedOutput()
if err != nil { if err != nil {
log.Error().Err(err).Str("output", string(output)).Msg("") log.Error().Err(err).Str("output", string(output)).Msg("")
return return
@ -72,7 +75,7 @@ type containerImageInfo struct {
} }
func listContainerImages() (diis []containerImageInfo, err error) { func listContainerImages() (diis []containerImageInfo, err error) {
cmd := exec.Command("docker", "images") cmd := exec.Command(containerRuntime, "images")
log.Debug().Msgf("%v", cmd) log.Debug().Msgf("%v", cmd)
rawOutput, err := cmd.CombinedOutput() rawOutput, err := cmd.CombinedOutput()
@ -148,7 +151,7 @@ func (c container) Build(imagePath string) (output string, err error) {
args := []string{"build"} args := []string{"build"}
args = append(args, "-t", c.name, imagePath) args = append(args, "-t", c.name, imagePath)
cmd := exec.Command("docker", args...) cmd := exec.Command(containerRuntime, args...)
flog := log.With(). flog := log.With().
Str("command", fmt.Sprintf("%v", cmd)). Str("command", fmt.Sprintf("%v", cmd)).
@ -188,14 +191,26 @@ func (c container) Run(workdir string, command string) (output string, err error
var args []string var args []string
args = append(args, "run", "--rm") args = append(args, "run", "--rm")
args = append(args, c.Args...) args = append(args, c.Args...)
args = append(args, if workdir != "" {
"-v", workdir+":/work", args = append(args, "-v", workdir+":/work")
"-v", c.Volumes.LibModules+":/lib/modules", }
"-v", c.Volumes.UsrSrc+":/usr/src", if c.Volumes.LibModules != "" {
"-v", c.Volumes.Boot+":/boot") args = append(args, "-v", c.Volumes.LibModules+":/lib/modules")
args = append(args, c.name, "bash", "-c", "cd /work && "+command) }
if c.Volumes.UsrSrc != "" {
args = append(args, "-v", c.Volumes.UsrSrc+":/usr/src")
}
if c.Volumes.Boot != "" {
args = append(args, "-v", c.Volumes.Boot+":/boot")
}
args = append(args, c.name, "bash", "-c")
if workdir != "" {
args = append(args, "cd /work && "+command)
} else {
args = append(args, command)
}
cmd := exec.Command("docker", args...) cmd := exec.Command(containerRuntime, args...)
log.Debug().Msgf("%v", cmd) log.Debug().Msgf("%v", cmd)
@ -207,6 +222,13 @@ func (c container) Run(workdir string, command string) (output string, err error
timer := time.AfterFunc(c.timeout, func() { timer := time.AfterFunc(c.timeout, func() {
flog.Info().Msg("killing container by timeout") flog.Info().Msg("killing container by timeout")
flog.Debug().Msg("SIGINT")
cmd.Process.Signal(os.Interrupt)
time.Sleep(time.Minute)
flog.Debug().Msg("SIGKILL")
cmd.Process.Kill() cmd.Process.Kill()
}) })
defer timer.Stop() defer timer.Stop()

View File

@ -13,6 +13,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"os/user" "os/user"
"strings"
"time" "time"
"code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/config"
@ -47,6 +48,7 @@ func (cmd *ImageListCmd) Run(g *Globals) (err error) {
type ImageEditCmd struct { type ImageEditCmd struct {
Name string `help:"image name" required:""` Name string `help:"image name" required:""`
DryRun bool `help:"do nothing, just print commands"`
} }
func (cmd *ImageEditCmd) Run(g *Globals) (err error) { func (cmd *ImageEditCmd) Run(g *Globals) (err error) {
@ -86,6 +88,22 @@ func (cmd *ImageEditCmd) Run(g *Globals) (err error) {
q.Mutable = true q.Mutable = true
if cmd.DryRun {
s := q.Executable()
for _, arg := range q.Args() {
if strings.Contains(arg, " ") ||
strings.Contains(arg, ",") {
s += fmt.Sprintf(` "%s"`, arg)
} else {
s += fmt.Sprintf(" %s", arg)
}
}
fmt.Println(s)
fmt.Println(q.GetSSHCommand())
return
}
err = q.Start() err = q.Start()
if err != nil { if err != nil {
fmt.Println("Qemu start error:", err) fmt.Println("Qemu start error:", err)

132
kernel.go
View File

@ -12,6 +12,7 @@ import (
"math/rand" "math/rand"
"os" "os"
"os/exec" "os/exec"
"os/signal"
"os/user" "os/user"
"regexp" "regexp"
"runtime" "runtime"
@ -68,6 +69,9 @@ func (cmd KernelAutogenCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) {
return return
} }
shutdown := false
setSigintHandler(&shutdown)
for _, sk := range ka.SupportedKernels { for _, sk := range ka.SupportedKernels {
if sk.DistroRelease == "" { if sk.DistroRelease == "" {
err = errors.New("Please set distro_release") err = errors.New("Please set distro_release")
@ -82,10 +86,14 @@ func (cmd KernelAutogenCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) {
kernelCmd.Force, kernelCmd.Force,
!kernelCmd.NoHeaders, !kernelCmd.NoHeaders,
kernelCmd.Shuffle, kernelCmd.Shuffle,
&shutdown,
) )
if err != nil { if err != nil {
return return
} }
if shutdown {
break
}
} }
return updateKernelsCfg(kernelCmd.UseHost, !kernelCmd.NoDownload) return updateKernelsCfg(kernelCmd.UseHost, !kernelCmd.NoDownload)
@ -102,6 +110,9 @@ func (cmd *KernelGenallCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) {
return return
} }
shutdown := false
setSigintHandler(&shutdown)
km := config.KernelMask{ km := config.KernelMask{
DistroType: distroType, DistroType: distroType,
DistroRelease: cmd.Ver, DistroRelease: cmd.Ver,
@ -115,6 +126,7 @@ func (cmd *KernelGenallCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) {
kernelCmd.Force, kernelCmd.Force,
!kernelCmd.NoHeaders, !kernelCmd.NoHeaders,
kernelCmd.Shuffle, kernelCmd.Shuffle,
&shutdown,
) )
if err != nil { if err != nil {
return return
@ -135,6 +147,9 @@ func (cmd *KernelInstallCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) {
return return
} }
shutdown := false
setSigintHandler(&shutdown)
km := config.KernelMask{ km := config.KernelMask{
DistroType: distroType, DistroType: distroType,
DistroRelease: cmd.Ver, DistroRelease: cmd.Ver,
@ -148,6 +163,7 @@ func (cmd *KernelInstallCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) {
kernelCmd.Force, kernelCmd.Force,
!kernelCmd.NoHeaders, !kernelCmd.NoHeaders,
kernelCmd.Shuffle, kernelCmd.Shuffle,
&shutdown,
) )
if err != nil { if err != nil {
return return
@ -262,7 +278,8 @@ func generateBaseDockerImage(registry string, commands []config.DockerCommand,
d := "# BASE\n" d := "# BASE\n"
cmd := exec.Command("docker", "images", "-q", sk.DockerName()) // TODO move as function to container.go
cmd := exec.Command(containerRuntime, "images", "-q", sk.DockerName())
log.Debug().Msgf("run %v", cmd) log.Debug().Msgf("run %v", cmd)
rawOutput, err := cmd.CombinedOutput() rawOutput, err := cmd.CombinedOutput()
@ -356,7 +373,10 @@ func generateBaseDockerImage(registry string, commands []config.DockerCommand,
// Cache kernel package dependencies // Cache kernel package dependencies
d += "RUN export PKGNAME=$(yum search kernel-devel --showduplicates | grep '^kernel-devel' | cut -d ' ' -f 1 | head -n 1); " + d += "RUN export PKGNAME=$(yum search kernel-devel --showduplicates | grep '^kernel-devel' | cut -d ' ' -f 1 | head -n 1); " +
"yum -y install $PKGNAME $(echo $PKGNAME | sed 's/-devel//'); " + "yum -y install $PKGNAME $(echo $PKGNAME | sed 's/-devel//'); " +
fmt.Sprintf("yum -y remove $PKGNAME $(echo $PKGNAME | sed 's/-devel//') %s\n", flags) fmt.Sprintf("yum -y remove $PKGNAME "+
"$(echo $PKGNAME | sed 's/-devel//') "+
"$(echo $PKGNAME | sed 's/-devel/-modules/') "+
"$(echo $PKGNAME | sed 's/-devel/-core/') %s\n", flags)
default: default:
err = fmt.Errorf("%s not yet supported", sk.DistroType.String()) err = fmt.Errorf("%s not yet supported", sk.DistroType.String())
return return
@ -389,17 +409,10 @@ func generateBaseDockerImage(registry string, commands []config.DockerCommand,
} }
func installKernel(sk config.KernelMask, pkgname string, force, headers bool) (err error) { func installKernel(sk config.KernelMask, pkgname string, force, headers bool) (err error) {
tmpdir, err := os.MkdirTemp(tempDirBase, "out-of-tree-"+pkgname+"-")
if err != nil {
log.Fatal().Err(err).Msg("make tmp directory")
}
defer os.RemoveAll(tmpdir)
slog := log.With(). slog := log.With().
Str("distro_type", sk.DistroType.String()). Str("distro_type", sk.DistroType.String()).
Str("distro_release", sk.DistroRelease). Str("distro_release", sk.DistroRelease).
Str("pkg", pkgname). Str("pkg", pkgname).
Str("tmpdir", tmpdir).
Logger() Logger()
c, err := NewContainer(sk.DockerName(), time.Hour) // TODO conf c, err := NewContainer(sk.DockerName(), time.Hour) // TODO conf
@ -425,17 +438,14 @@ func installKernel(sk config.KernelMask, pkgname string, force, headers bool) (e
volumes := c.Volumes volumes := c.Volumes
c.Volumes.LibModules = fmt.Sprintf("%s/libmodules", tmpdir) c.Volumes.LibModules = ""
os.MkdirAll(c.Volumes.LibModules, 0777) c.Volumes.UsrSrc = ""
c.Volumes.Boot = ""
c.Volumes.UsrSrc = fmt.Sprintf("%s/usrsrc", tmpdir)
os.MkdirAll(c.Volumes.UsrSrc, 0777)
c.Volumes.Boot = fmt.Sprintf("%s/boot", tmpdir)
os.MkdirAll(c.Volumes.Boot, 0777)
slog.Debug().Msgf("Installing kernel") slog.Debug().Msgf("Installing kernel")
cmd := "true"
switch sk.DistroType { switch sk.DistroType {
case config.Ubuntu: case config.Ubuntu:
var headerspkg string var headerspkg string
@ -443,12 +453,7 @@ func installKernel(sk config.KernelMask, pkgname string, force, headers bool) (e
headerspkg = strings.Replace(pkgname, "image", "headers", -1) headerspkg = strings.Replace(pkgname, "image", "headers", -1)
} }
cmd := fmt.Sprintf("apt-get install -y %s %s", pkgname, headerspkg) cmd += fmt.Sprintf(" && apt-get install -y %s %s", pkgname, headerspkg)
_, err = c.Run(tempDirBase, cmd)
if err != nil {
return
}
case config.CentOS: case config.CentOS:
imagepkg := strings.Replace(pkgname, "-devel", "", -1) imagepkg := strings.Replace(pkgname, "-devel", "", -1)
@ -457,19 +462,11 @@ func installKernel(sk config.KernelMask, pkgname string, force, headers bool) (e
if !headers { if !headers {
pkgname = "" pkgname = ""
} }
cmd := fmt.Sprintf("yum -y install %s %s\n", imagepkg, cmd += fmt.Sprintf(" && yum -y install %s %s", imagepkg,
pkgname) pkgname)
_, err = c.Run(tempDirBase, cmd)
if err != nil {
return
}
cmd = fmt.Sprintf("dracut --add-drivers 'e1000 ext4' -f "+ cmd += fmt.Sprintf(" && dracut --add-drivers 'e1000 ext4' -f "+
"/boot/initramfs-%s.img %s\n", version, version) "/boot/initramfs-%s.img %s", version, version)
_, err = c.Run(tempDirBase, cmd)
if err != nil {
return
}
default: default:
err = fmt.Errorf("%s not yet supported", sk.DistroType.String()) err = fmt.Errorf("%s not yet supported", sk.DistroType.String())
return return
@ -479,33 +476,11 @@ func installKernel(sk config.KernelMask, pkgname string, force, headers bool) (e
c.Args = append(c.Args, "-v", volumes.UsrSrc+":/target/usr/src") c.Args = append(c.Args, "-v", volumes.UsrSrc+":/target/usr/src")
c.Args = append(c.Args, "-v", volumes.Boot+":/target/boot") c.Args = append(c.Args, "-v", volumes.Boot+":/target/boot")
cmd := "true" cmd += " && cp -r /boot /target/"
cmd += " && cp -r /lib/modules /target/lib/"
cmd += " && cp -r /usr/src /target/usr/"
files, err := ioutil.ReadDir(c.Volumes.Boot) _, err = c.Run("", cmd)
if err != nil {
return
}
if len(files) != 0 {
cmd += " && cp -r /boot/* /target/boot/"
}
files, err = ioutil.ReadDir(c.Volumes.LibModules)
if err != nil {
return
}
if len(files) != 0 {
cmd += " && cp -r /lib/modules/* /target/lib/modules/"
}
files, err = ioutil.ReadDir(c.Volumes.UsrSrc)
if err != nil {
return
}
if len(files) != 0 {
cmd += " && cp -r /usr/src/* /target/usr/src/"
}
_, err = c.Run(tempDirBase, cmd)
if err != nil { if err != nil {
return return
} }
@ -703,20 +678,40 @@ func shuffleStrings(a []string) []string {
return a return a
} }
func setSigintHandler(variable *bool) {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
counter := 0
for _ = range c {
if counter == 0 {
*variable = true
log.Warn().Msg("shutdown requested, finishing work")
log.Info().Msg("^C a couple of times more for an unsafe exit")
} else if counter >= 3 {
log.Fatal().Msg("unsafe exit")
}
counter += 1
}
}()
}
func generateKernels(km config.KernelMask, registry string, func generateKernels(km config.KernelMask, registry string,
commands []config.DockerCommand, max, retries int64, commands []config.DockerCommand, max, retries int64,
download, force, headers, shuffle bool) (err error) { download, force, headers, shuffle bool, shutdown *bool) (err error) {
log.Info().Msgf("Generating for kernel mask %v", km) log.Info().Msgf("Generating for kernel mask %v", km)
_, err = genRootfsImage(containerImageInfo{Name: km.DockerName()}, _, err = genRootfsImage(containerImageInfo{Name: km.DockerName()},
download) download)
if err != nil { if err != nil || *shutdown {
return return
} }
err = generateBaseDockerImage(registry, commands, km) err = generateBaseDockerImage(registry, commands, km)
if err != nil { if err != nil || *shutdown {
return return
} }
@ -730,7 +725,7 @@ func generateKernels(km config.KernelMask, registry string,
default: default:
err = fmt.Errorf("%s not yet supported", km.DistroType.String()) err = fmt.Errorf("%s not yet supported", km.DistroType.String())
} }
if err != nil { if err != nil || *shutdown {
return return
} }
@ -743,12 +738,21 @@ func generateKernels(km config.KernelMask, registry string,
break break
} }
if *shutdown {
err = nil
return
}
log.Info().Msgf("%d/%d %s", i+1, len(pkgs), pkg) log.Info().Msgf("%d/%d %s", i+1, len(pkgs), pkg)
var attempt int64 var attempt int64
for { for {
attempt++ attempt++
if *shutdown {
err = nil
return
}
err = installKernel(km, pkg, force, headers) err = installKernel(km, pkg, force, headers)
if err == nil { if err == nil {
max-- max--

21
main.go
View File

@ -9,6 +9,7 @@ import (
"io" "io"
"math/rand" "math/rand"
"os" "os"
"os/exec"
"os/user" "os/user"
"runtime/debug" "runtime/debug"
"strconv" "strconv"
@ -44,6 +45,8 @@ type CLI struct {
Version VersionFlag `name:"version" help:"print version information and quit"` Version VersionFlag `name:"version" help:"print version information and quit"`
LogLevel LogLevelFlag `enum:"trace,debug,info,warn,error" default:"info"` LogLevel LogLevelFlag `enum:"trace,debug,info,warn,error" default:"info"`
ContainerRuntime string `enum:"podman,docker" default:"podman"`
} }
type LogLevelFlag string type LogLevelFlag string
@ -103,7 +106,7 @@ func main() {
Compact: true, Compact: true,
}), }),
kong.Vars{ kong.Vars{
"version": "2.0.6", "version": "2.1.2",
}, },
) )
@ -153,6 +156,22 @@ func main() {
log.Debug().Msgf("%v", buildInfo.Settings) log.Debug().Msgf("%v", buildInfo.Settings)
} }
_, err = exec.LookPath(cli.ContainerRuntime)
if err != nil {
if cli.ContainerRuntime == "podman" { // default value
log.Debug().Msgf("podman is not found in $PATH, " +
"fall back to docker")
cli.ContainerRuntime = "docker"
}
_, err = exec.LookPath(cli.ContainerRuntime)
if err != nil {
log.Fatal().Msgf("%v is not found in $PATH",
cli.ContainerRuntime)
}
}
containerRuntime = cli.ContainerRuntime
err = ctx.Run(&cli.Globals) err = ctx.Run(&cli.Globals)
ctx.FatalIfErrorf(err) ctx.FatalIfErrorf(err)
} }

View File

@ -30,7 +30,7 @@ Note: qemu on macOS since v2.12 (24 April 2018) supports Hypervisor.framework.
#### Generate image #### Generate image
$ cd $GOPATH/src/code.dumpstack.io/tools/out-of-tree/tools/qemu-debian-img $ cd $GOPATH/src/code.dumpstack.io/tools/out-of-tree/tools/qemu-ubuntu-img
$ ./bootstrap.sh $ ./bootstrap.sh
### Fill configuration file ### Fill configuration file

View File

@ -208,14 +208,16 @@ func (q System) cmdline() (s string) {
return return
} }
// Start qemu process func (q System) Executable() string {
func (q *System) Start() (err error) { return "qemu-system-" + string(q.arch)
rand.Seed(time.Now().UnixNano()) // Are you sure? }
func (q *System) Args() (qemuArgs []string) {
if q.sshAddrPort == "" { if q.sshAddrPort == "" {
q.sshAddrPort = getFreeAddrPort() q.sshAddrPort = getFreeAddrPort()
} }
hostfwd := fmt.Sprintf("hostfwd=tcp:%s-:22", q.sshAddrPort) hostfwd := fmt.Sprintf("hostfwd=tcp:%s-:22", q.sshAddrPort)
qemuArgs := []string{"-nographic", qemuArgs = []string{"-nographic",
"-hda", q.drivePath, "-hda", q.drivePath,
"-kernel", q.kernel.KernelPath, "-kernel", q.kernel.KernelPath,
"-smp", fmt.Sprintf("%d", q.Cpus), "-smp", fmt.Sprintf("%d", q.Cpus),
@ -245,8 +247,14 @@ func (q *System) Start() (err error) {
} }
qemuArgs = append(qemuArgs, "-append", q.cmdline()) qemuArgs = append(qemuArgs, "-append", q.cmdline())
return
}
q.cmd = exec.Command("qemu-system-"+string(q.arch), qemuArgs...) // Start qemu process
func (q *System) Start() (err error) {
rand.Seed(time.Now().UnixNano()) // Are you sure?
q.cmd = exec.Command(q.Executable(), q.Args()...)
q.log.Debug().Msgf("%v", q.cmd) q.log.Debug().Msgf("%v", q.cmd)
if q.pipe.stdin, err = q.cmd.StdinPipe(); err != nil { if q.pipe.stdin, err = q.cmd.StdinPipe(); err != nil {
@ -370,7 +378,11 @@ func (q System) Command(user, cmd string) (output string, err error) {
if err != nil { if err != nil {
return return
} }
session.Stderr = session.Stdout
stderr, err := session.StderrPipe()
if err != nil {
return
}
err = session.Start(cmd) err = session.Start(cmd)
if err != nil { if err != nil {
@ -387,6 +399,17 @@ func (q System) Command(user, cmd string) (output string, err error) {
output = strings.TrimSuffix(output, "\n") output = strings.TrimSuffix(output, "\n")
}() }()
go func() {
scanner := bufio.NewScanner(stderr)
for scanner.Scan() {
m := scanner.Text()
output += m + "\n"
// Note: it prints stderr as stdout
flog.Trace().Str("stdout", m).Msg("")
}
output = strings.TrimSuffix(output, "\n")
}()
err = session.Wait() err = session.Wait()
return return
} }

View File

@ -4,7 +4,7 @@
package qemu package qemu
const testConfigVmlinuz = "../tools/qemu-debian-img/ubuntu2204.vmlinuz" const testConfigVmlinuz = "../tools/qemu-ubuntu-img/ubuntu2204.vmlinuz"
const testConfigInitrd = "../tools/qemu-debian-img/ubuntu2204.initrd" const testConfigInitrd = "../tools/qemu-ubuntu-img/ubuntu2204.initrd"
const testConfigRootfs = "../tools/qemu-debian-img/ubuntu2204.img" const testConfigRootfs = "../tools/qemu-ubuntu-img/ubuntu2204.img"
const testConfigSampleKo = "../tools/qemu-debian-img/ubuntu2204.ko" const testConfigSampleKo = "../tools/qemu-ubuntu-img/ubuntu2204.ko"