1
0
out-of-tree/kernel.go

696 lines
15 KiB
Go
Raw Normal View History

// Copyright 2023 Mikhail Klementev. All rights reserved.
2018-11-17 20:24:09 +00:00
// Use of this source code is governed by a AGPLv3 license
// (or later) that can be found in the LICENSE file.
package main
import (
"errors"
"fmt"
"io/ioutil"
"math"
"math/rand"
"os"
"os/exec"
"os/user"
"regexp"
"runtime"
"strings"
"time"
2018-11-17 20:24:09 +00:00
2018-11-28 23:53:47 +00:00
"github.com/naoina/toml"
2023-03-18 21:30:07 +00:00
"github.com/rs/zerolog/log"
"code.dumpstack.io/tools/out-of-tree/config"
2018-11-17 20:24:09 +00:00
)
2023-01-31 07:13:33 +00:00
type KernelCmd struct {
NoDownload bool `help:"do not download qemu image while kernel generation"`
UseHost bool `help:"also use host kernels"`
2023-03-22 18:20:45 +00:00
Force bool `help:"force reinstall kernel"`
2023-01-31 07:13:33 +00:00
List KernelListCmd `cmd:"" help:"list kernels"`
Autogen KernelAutogenCmd `cmd:"" help:"generate kernels based on the current config"`
Genall KernelGenallCmd `cmd:"" help:"generate all kernels for distro"`
2023-03-22 18:21:21 +00:00
Install KernelInstallCmd `cmd:"" help:"install specific kernel"`
2023-01-31 07:13:33 +00:00
}
type KernelListCmd struct{}
func (cmd *KernelListCmd) Run(g *Globals) (err error) {
kcfg, err := config.ReadKernelConfig(g.Config.Kernels)
if err != nil {
log.Debug().Err(err).Msg("read kernel config")
2023-01-31 07:13:33 +00:00
}
2018-11-17 20:24:09 +00:00
if len(kcfg.Kernels) == 0 {
return errors.New("No kernels found")
}
2023-01-31 07:13:33 +00:00
2018-11-17 20:24:09 +00:00
for _, k := range kcfg.Kernels {
fmt.Println(k.DistroType, k.DistroRelease, k.KernelRelease)
}
2023-01-31 07:13:33 +00:00
2018-11-17 20:24:09 +00:00
return
}
2023-01-31 07:13:33 +00:00
type KernelAutogenCmd struct {
Max int64 `help:"download random kernels from set defined by regex in release_mask, but no more than X for each of release_mask" default:"100"`
2023-01-31 07:13:33 +00:00
}
2023-01-31 09:05:43 +00:00
func (cmd KernelAutogenCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) {
2023-01-31 07:13:33 +00:00
ka, err := config.ReadArtifactConfig(g.WorkDir + "/.out-of-tree.toml")
if err != nil {
return
}
for _, sk := range ka.SupportedKernels {
if sk.DistroRelease == "" {
err = errors.New("Please set distro_release")
return
}
2023-03-22 18:20:45 +00:00
err = generateKernels(sk, g.Config.Docker.Registry, g.Config.Docker.Commands, cmd.Max, !kernelCmd.NoDownload, kernelCmd.Force)
2023-01-31 07:13:33 +00:00
if err != nil {
return
}
}
return updateKernelsCfg(kernelCmd.UseHost, !kernelCmd.NoDownload)
}
type KernelGenallCmd struct {
Distro string `required:"" help:"distribution"`
Ver string `required:"" help:"distro version"`
}
func (cmd *KernelGenallCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) {
distroType, err := config.NewDistroType(cmd.Distro)
if err != nil {
return
}
km := config.KernelMask{
DistroType: distroType,
DistroRelease: cmd.Ver,
ReleaseMask: ".*",
}
err = generateKernels(km,
g.Config.Docker.Registry,
g.Config.Docker.Commands,
math.MaxUint32,
2023-03-22 18:21:21 +00:00
!kernelCmd.NoDownload,
kernelCmd.Force,
)
if err != nil {
return
}
return updateKernelsCfg(kernelCmd.UseHost, !kernelCmd.NoDownload)
}
type KernelInstallCmd struct {
Distro string `required:"" help:"distribution"`
Ver string `required:"" help:"distro version"`
Kernel string `required:"" help:"kernel release mask"`
}
func (cmd *KernelInstallCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) {
distroType, err := config.NewDistroType(cmd.Distro)
if err != nil {
return
}
km := config.KernelMask{
DistroType: distroType,
DistroRelease: cmd.Ver,
ReleaseMask: cmd.Kernel,
}
err = generateKernels(km,
g.Config.Docker.Registry,
g.Config.Docker.Commands,
math.MaxUint32,
!kernelCmd.NoDownload,
2023-03-22 18:20:45 +00:00
kernelCmd.Force,
)
2023-01-31 07:13:33 +00:00
if err != nil {
return
}
return updateKernelsCfg(kernelCmd.UseHost, !kernelCmd.NoDownload)
}
func matchDebianHeadersPkg(container, mask string, generic bool) (
pkgs []string, err error) {
cmd := "apt-cache search linux-headers | cut -d ' ' -f 1"
c, err := NewContainer(container, time.Minute)
if err != nil {
return
}
output, err := c.Run("/tmp", cmd)
if err != nil {
return
}
r, err := regexp.Compile("linux-headers-" + mask)
if err != nil {
return
}
kernels := r.FindAll([]byte(output), -1)
for _, k := range kernels {
pkg := string(k)
if generic && !strings.HasSuffix(pkg, "generic") {
continue
}
if pkg == "linux-headers-generic" {
continue
}
pkgs = append(pkgs, pkg)
}
return
}
2019-08-20 18:57:45 +00:00
func matchCentOSDevelPkg(container, mask string, generic bool) (
pkgs []string, err error) {
cmd := "yum search kernel-devel --showduplicates | " +
2019-08-20 18:57:45 +00:00
"grep '^kernel-devel' | cut -d ' ' -f 1"
c, err := NewContainer(container, time.Minute)
if err != nil {
return
}
output, err := c.Run("/tmp", cmd)
2019-08-20 18:57:45 +00:00
if err != nil {
return
}
r, err := regexp.Compile("kernel-devel-" + mask)
if err != nil {
return
}
for _, k := range r.FindAll([]byte(output), -1) {
pkgs = append(pkgs, string(k))
}
return
}
func dockerImagePath(sk config.KernelMask) (path string, err error) {
usr, err := user.Current()
if err != nil {
return
}
path = usr.HomeDir + "/.out-of-tree/containers/"
path += sk.DistroType.String() + "/" + sk.DistroRelease
return
}
2019-08-20 18:57:45 +00:00
func vsyscallAvailable() (available bool, err error) {
if runtime.GOOS != "linux" {
// Docker for non-Linux systems is not using the host
// kernel but uses kernel inside a virtual machine, so
// it builds by the Docker team with vsyscall support.
available = true
return
}
2019-08-20 18:57:45 +00:00
buf, err := ioutil.ReadFile("/proc/self/maps")
if err != nil {
return
}
available = strings.Contains(string(buf), "[vsyscall]")
return
}
func generateBaseDockerImage(registry string, commands []config.DockerCommand,
sk config.KernelMask) (err error) {
imagePath, err := dockerImagePath(sk)
if err != nil {
return
}
dockerPath := imagePath + "/Dockerfile"
d := "# BASE\n"
2023-01-30 11:15:12 +00:00
cmd := exec.Command("docker", "images", "-q", sk.DockerName())
2023-03-22 18:05:13 +00:00
log.Debug().Msgf("run %v", cmd)
2023-03-19 13:14:14 +00:00
2023-01-30 11:15:12 +00:00
rawOutput, err := cmd.CombinedOutput()
if err != nil {
return
}
if exists(dockerPath) && string(rawOutput) != "" {
log.Info().Msgf("Base image for %s:%s found",
sk.DistroType.String(), sk.DistroRelease)
return
}
log.Info().Msgf("Base image for %s:%s not found, start generating",
2018-12-10 02:39:55 +00:00
sk.DistroType.String(), sk.DistroRelease)
os.MkdirAll(imagePath, os.ModePerm)
d += "FROM "
if registry != "" {
d += registry + "/"
}
d += fmt.Sprintf("%s:%s\n",
strings.ToLower(sk.DistroType.String()),
sk.DistroRelease,
)
2019-08-20 18:57:45 +00:00
vsyscall, err := vsyscallAvailable()
if err != nil {
return
}
for _, c := range commands {
switch c.DistroType {
case config.Ubuntu:
d += "RUN " + c.Command + "\n"
case config.CentOS:
d += "RUN " + c.Command + "\n"
case config.Debian:
d += "RUN " + c.Command + "\n"
default:
err = fmt.Errorf("%s not yet supported",
sk.DistroType.String())
return
}
}
switch sk.DistroType {
case config.Ubuntu:
d += "ENV DEBIAN_FRONTEND=noninteractive\n"
d += "RUN apt-get update\n"
d += "RUN apt-get install -y build-essential libelf-dev\n"
d += "RUN apt-get install -y wget git\n"
if sk.DistroRelease >= "14.04" {
d += "RUN apt-get install -y libseccomp-dev\n"
}
2019-08-23 06:46:37 +00:00
d += "RUN mkdir -p /lib/modules\n"
2019-08-20 18:57:45 +00:00
case config.CentOS:
if sk.DistroRelease < "7" && !vsyscall {
2023-03-18 21:30:07 +00:00
log.Print("Old CentOS requires `vsyscall=emulate` " +
2019-08-20 18:57:45 +00:00
"on the latest kernels")
2023-03-18 21:30:07 +00:00
log.Print("Check out `A note about vsyscall` " +
2019-08-20 18:57:45 +00:00
"at https://hub.docker.com/_/centos")
2023-03-18 21:30:07 +00:00
log.Print("See also https://lwn.net/Articles/446528/")
2019-08-20 18:57:45 +00:00
err = fmt.Errorf("vsyscall is not available")
return
}
// enable rpms from old minor releases
d += "RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/CentOS-Vault.repo\n"
// do not remove old kernels
d += "RUN sed -i 's;installonly_limit=;installonly_limit=100500;' /etc/yum.conf\n"
d += "RUN yum -y update\n"
2020-05-30 14:13:03 +00:00
if sk.DistroRelease == "8" {
// FIXME CentOS Vault repository list for 8 is empty
// at the time of this fix; check for it and use a
// workaround if it's still empty
d += `RUN grep enabled /etc/yum.repos.d/CentOS-Vault.repo` +
` || echo -e '[8.0.1905]\nbaseurl=http://vault.centos.org/8.0.1905/BaseOS/$basearch/os/'` +
` >> /etc/yum.repos.d/CentOS-Vault.repo` + "\n"
}
2019-08-20 18:57:45 +00:00
d += "RUN yum -y groupinstall 'Development Tools'\n"
2020-05-30 12:40:12 +00:00
if sk.DistroRelease < "8" {
d += "RUN yum -y install deltarpm\n"
} else {
2020-05-30 13:42:04 +00:00
d += "RUN yum -y install drpm grub2-tools-minimal " +
"elfutils-libelf-devel\n"
2020-05-30 12:40:12 +00:00
}
default:
2018-12-10 02:45:17 +00:00
err = fmt.Errorf("%s not yet supported", sk.DistroType.String())
return
}
d += "# END BASE\n\n"
err = ioutil.WriteFile(dockerPath, []byte(d), 0644)
if err != nil {
return
}
c, err := NewContainer(sk.DockerName(), time.Hour)
if err != nil {
return
}
2023-03-19 13:14:14 +00:00
output, err := c.Build(imagePath)
if err != nil {
log.Error().Err(err).Msgf("Base image for %s:%s generating error",
sk.DistroType.String(), sk.DistroRelease)
log.Fatal().Msg(output)
return
}
log.Info().Msgf("Base image for %s:%s generating success",
sk.DistroType.String(), sk.DistroRelease)
return
}
2023-03-22 18:20:45 +00:00
func installKernel(sk config.KernelMask, pkgname string, force bool) (err error) {
2023-03-22 18:05:13 +00:00
slog := log.With().
Str("distro_type", sk.DistroType.String()).
Str("distro_release", sk.DistroRelease).
Str("pkg", pkgname).
Logger()
c, err := NewContainer(sk.DockerName(), time.Hour) // TODO conf
if err != nil {
return
}
2023-03-22 18:05:13 +00:00
moddirs, err := ioutil.ReadDir(c.Volumes.LibModules)
if err != nil {
return
}
2023-03-22 18:05:13 +00:00
for _, krel := range moddirs {
if strings.Contains(pkgname, krel.Name()) {
2023-03-22 18:20:45 +00:00
if force {
slog.Info().Msg("Reinstall")
} else {
slog.Debug().Msg("Already installed")
return
}
2023-03-22 18:05:13 +00:00
}
}
2023-03-22 18:08:48 +00:00
slog.Debug().Msgf("Installing kernel")
2023-03-22 18:05:13 +00:00
2019-08-20 18:57:45 +00:00
switch sk.DistroType {
case config.Ubuntu:
imagepkg := strings.Replace(pkgname, "headers", "image", -1)
cmd := fmt.Sprintf("apt-get install -y %s %s", imagepkg,
2019-08-20 18:57:45 +00:00
pkgname)
_, err = c.Run("/tmp", cmd)
if err != nil {
return
}
2019-08-20 18:57:45 +00:00
case config.CentOS:
imagepkg := strings.Replace(pkgname, "-devel", "", -1)
version := strings.Replace(pkgname, "kernel-devel-", "", -1)
cmd := fmt.Sprintf("yum -y install %s %s\n", imagepkg,
2019-08-20 18:57:45 +00:00
pkgname)
_, err = c.Run("/tmp", cmd)
if err != nil {
return
}
cmd = fmt.Sprintf("dracut --add-drivers 'e1000 ext4' -f "+
"/boot/initramfs-%s.img %s\n", version, version)
_, err = c.Run("/tmp", cmd)
2023-01-29 20:41:33 +00:00
if err != nil {
return
2023-01-29 20:41:33 +00:00
}
default:
err = fmt.Errorf("%s not yet supported", sk.DistroType.String())
2023-02-15 10:52:36 +00:00
return
}
2023-03-22 18:08:48 +00:00
slog.Debug().Msgf("Success")
return
}
2018-11-28 23:53:47 +00:00
func genKernelPath(files []os.FileInfo, kname string) string {
for _, file := range files {
2019-08-20 18:57:45 +00:00
if strings.HasPrefix(file.Name(), "vmlinuz") {
2018-11-28 23:53:47 +00:00
if strings.Contains(file.Name(), kname) {
return file.Name()
}
}
}
log.Fatal().Msgf("cannot find kernel %s", kname)
return ""
2018-11-28 23:53:47 +00:00
}
func genInitrdPath(files []os.FileInfo, kname string) string {
for _, file := range files {
2019-08-20 18:57:45 +00:00
if strings.HasPrefix(file.Name(), "initrd") ||
strings.HasPrefix(file.Name(), "initramfs") {
2018-11-28 23:53:47 +00:00
if strings.Contains(file.Name(), kname) {
return file.Name()
}
}
}
log.Fatal().Msgf("cannot find initrd %s", kname)
return ""
2018-11-28 23:53:47 +00:00
}
func genRootfsImage(d dockerImageInfo, download bool) (rootfs string, err error) {
2018-11-29 00:01:14 +00:00
usr, err := user.Current()
if err != nil {
return
2018-11-29 00:01:14 +00:00
}
imageFile := d.ContainerName + ".img"
imagesPath := usr.HomeDir + "/.out-of-tree/images/"
os.MkdirAll(imagesPath, os.ModePerm)
rootfs = imagesPath + imageFile
if !exists(rootfs) {
if download {
2023-03-18 21:30:07 +00:00
log.Print(imageFile, "not exists, start downloading...")
err = downloadImage(imagesPath, imageFile)
}
}
return
2018-11-28 23:53:47 +00:00
}
type dockerImageInfo struct {
ContainerName string
DistroType config.DistroType
DistroRelease string // 18.04/7.4.1708/9.1
}
func listDockerImages() (diis []dockerImageInfo, err error) {
cmd := exec.Command("docker", "images")
2023-03-19 13:14:14 +00:00
log.Debug().Msgf("%v", cmd)
rawOutput, err := cmd.CombinedOutput()
if err != nil {
return
}
r, err := regexp.Compile("out_of_tree_.*")
if err != nil {
return
}
containers := r.FindAll(rawOutput, -1)
for _, c := range containers {
container := strings.Fields(string(c))[0]
s := strings.Replace(container, "__", ".", -1)
values := strings.Split(s, "_")
distro, ver := values[3], values[4]
dii := dockerImageInfo{
ContainerName: container,
DistroRelease: ver,
}
dii.DistroType, err = config.NewDistroType(distro)
if err != nil {
return
}
diis = append(diis, dii)
}
return
}
func updateKernelsCfg(host, download bool) (err error) {
newkcfg := config.KernelConfig{}
if host {
// Get host kernels
newkcfg, err = genHostKernels(download)
if err != nil {
return
}
}
// Get docker kernels
dockerImages, err := listDockerImages()
if err != nil {
return
}
for _, d := range dockerImages {
err = genDockerKernels(d, &newkcfg, download)
if err != nil {
2023-03-18 21:30:07 +00:00
log.Print("gen kernels", d.ContainerName, ":", err)
continue
}
}
stripkcfg := config.KernelConfig{}
for _, nk := range newkcfg.Kernels {
if !hasKernel(nk, stripkcfg) {
stripkcfg.Kernels = append(stripkcfg.Kernels, nk)
}
}
buf, err := toml.Marshal(&stripkcfg)
if err != nil {
return
}
buf = append([]byte("# Autogenerated\n# DO NOT EDIT\n\n"), buf...)
usr, err := user.Current()
if err != nil {
return
}
// TODO move all cfg path values to one provider
kernelsCfgPath := usr.HomeDir + "/.out-of-tree/kernels.toml"
err = ioutil.WriteFile(kernelsCfgPath, buf, 0644)
if err != nil {
return
}
log.Info().Msgf("%s is successfully updated", kernelsCfgPath)
return
}
func genDockerKernels(dii dockerImageInfo, newkcfg *config.KernelConfig,
download bool) (err error) {
2018-11-28 23:53:47 +00:00
rootfs, err := genRootfsImage(dii, download)
2018-11-28 23:53:47 +00:00
if err != nil {
return
}
c, err := NewContainer(dii.ContainerName, time.Minute)
2018-11-28 23:53:47 +00:00
if err != nil {
return
}
moddirs, err := ioutil.ReadDir(c.Volumes.LibModules)
2018-11-28 23:53:47 +00:00
if err != nil {
return
}
bootfiles, err := ioutil.ReadDir(c.Volumes.Boot)
if err != nil {
return
}
for _, krel := range moddirs {
2018-11-28 23:53:47 +00:00
ki := config.KernelInfo{
DistroType: dii.DistroType,
DistroRelease: dii.DistroRelease,
KernelRelease: krel.Name(),
ContainerName: dii.ContainerName,
2018-11-28 23:53:47 +00:00
KernelPath: c.Volumes.Boot + "/" +
genKernelPath(bootfiles, krel.Name()),
InitrdPath: c.Volumes.Boot + "/" +
genInitrdPath(bootfiles, krel.Name()),
ModulesPath: c.Volumes.LibModules + "/" + krel.Name(),
2023-02-15 10:52:36 +00:00
RootFS: rootfs,
2018-11-28 23:53:47 +00:00
}
newkcfg.Kernels = append(newkcfg.Kernels, ki)
}
return
}
func hasKernel(ki config.KernelInfo, kcfg config.KernelConfig) bool {
for _, sk := range kcfg.Kernels {
if sk == ki {
return true
}
}
return false
}
func shuffle(a []string) []string {
// FisherYates shuffle
for i := len(a) - 1; i > 0; i-- {
j := rand.Intn(i + 1)
a[i], a[j] = a[j], a[i]
}
return a
}
func generateKernels(km config.KernelMask, registry string,
commands []config.DockerCommand, max int64,
2023-03-22 18:20:45 +00:00
download, force bool) (err error) {
log.Info().Msgf("Generating for kernel mask %v", km)
_, err = genRootfsImage(dockerImageInfo{ContainerName: km.DockerName()},
download)
if err != nil {
return
}
err = generateBaseDockerImage(registry, commands, km)
if err != nil {
return
}
var pkgs []string
2019-08-20 18:57:45 +00:00
switch km.DistroType {
case config.Ubuntu:
pkgs, err = matchDebianHeadersPkg(km.DockerName(),
km.ReleaseMask, true)
case config.CentOS:
pkgs, err = matchCentOSDevelPkg(km.DockerName(),
km.ReleaseMask, true)
default:
err = fmt.Errorf("%s not yet supported", km.DistroType.String())
}
if err != nil {
return
}
for i, pkg := range shuffle(pkgs) {
if max <= 0 {
2023-03-18 21:30:07 +00:00
log.Print("Max is reached")
break
}
2023-03-22 18:05:13 +00:00
log.Info().Msgf("%d/%d %s", i+1, len(pkgs), pkg)
2023-03-22 18:20:45 +00:00
err = installKernel(km, pkg, force)
if err == nil {
2019-08-17 09:35:36 +00:00
max--
} else {
log.Fatal().Err(err).Msg("install kernel")
}
}
return
}