talos/internal/pkg/extensions/kernel_modules.go
Noel Georgi d1a61fd343
chore: bump golangci-lint
Bump golangci-lint and fixup new warnings. Ignore check that checks for
used function parameters, it's kind of noisy and makes it confusing to
read interface implementations.

Signed-off-by: Noel Georgi <git@frezbo.dev>
2023-03-22 19:55:38 +05:30

244 lines
6.6 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Package extensions provides function to manage system extensions.
package extensions
import (
"bytes"
"errors"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/freddierice/go-losetup/v2"
"github.com/u-root/u-root/pkg/cpio"
"github.com/ulikunitz/xz"
"golang.org/x/sys/unix"
"github.com/siderolabs/talos/internal/pkg/mount"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/extensions"
)
// ProvidesKernelModules returns true if the extension provides kernel modules.
func (ext *Extension) ProvidesKernelModules() bool {
if _, err := os.Stat(filepath.Join(ext.rootfsPath, constants.DefaultKernelModulesPath)); os.IsNotExist(err) {
return false
}
return true
}
// KernelModuleDirectory returns the path to the kernel modules directory.
func (ext *Extension) KernelModuleDirectory() string {
return filepath.Join(ext.rootfsPath, constants.DefaultKernelModulesPath)
}
// GenerateKernelModuleDependencyTreeExtension generates a kernel module dependency tree extension.
// nolint:gocyclo
func GenerateKernelModuleDependencyTreeExtension(extensionsPathWithKernelModules []string, arch string) (*Extension, error) {
log.Println("preparing to run depmod to generate kernel modules dependency tree")
tempDir, err := os.MkdirTemp("", "ext-modules")
if err != nil {
return nil, err
}
defer logErr(func() error {
return os.RemoveAll(tempDir)
})
initramfsxz, err := os.Open(fmt.Sprintf(constants.InitramfsAssetPath, arch))
if err != nil {
return nil, err
}
defer logErr(func() error {
return initramfsxz.Close()
})
r, err := xz.NewReader(initramfsxz)
if err != nil {
return nil, err
}
var buff bytes.Buffer
if _, err = io.Copy(&buff, r); err != nil {
return nil, err
}
tempRootfsFile := filepath.Join(tempDir, constants.RootfsAsset)
if err = extractRootfsFromInitramfs(buff, tempRootfsFile); err != nil {
return nil, err
}
// now we are ready to mount rootfs.sqsh
// create a mount point under tempDir
rootfsMountPath := filepath.Join(tempDir, "rootfs-mnt")
// create the loopback device from the squashfs file
dev, err := losetup.Attach(tempRootfsFile, 0, true)
if err != nil {
return nil, err
}
defer logErr(func() error {
if err = dev.Detach(); err != nil {
return err
}
return dev.Remove()
})
// setup a temporary mount point for the squashfs file and mount it
m := mount.NewMountPoint(dev.Path(), rootfsMountPath, "squashfs", unix.MS_RDONLY|unix.MS_I_VERSION, "", mount.WithFlags(mount.ReadOnly|mount.Shared))
if err = m.Mount(); err != nil {
return nil, err
}
defer logErr(func() error {
return m.Unmount()
})
// create an overlayfs which contains the rootfs squashfs mount as the base
// and the extension modules as subsequent lower directories
overlays := mount.NewMountPoints()
// writable overlayfs mount inside a container required a tmpfs mount
overlays.Set("overlays-tmpfs", mount.NewMountPoint("tmpfs", constants.VarSystemOverlaysPath, "tmpfs", unix.MS_I_VERSION, ""))
rootfsKernelModulesPath := filepath.Join(rootfsMountPath, constants.DefaultKernelModulesPath)
// append the rootfs mount point
extensionsPathWithKernelModules = append(extensionsPathWithKernelModules, rootfsKernelModulesPath)
// create the overlayfs mount point as read write
mp := mount.NewMountPoint(strings.Join(extensionsPathWithKernelModules, ":"), rootfsKernelModulesPath, "", unix.MS_I_VERSION, "", mount.WithFlags(mount.Overlay|mount.Shared))
overlays.Set("overlays-mnt", mp)
if err = mount.Mount(overlays); err != nil {
return nil, err
}
defer logErr(func() error {
return mount.Unmount(overlays)
})
log.Println("running depmod to generate kernel modules dependency tree")
if err = depmod(mp.Target()); err != nil {
return nil, err
}
// we want this temp directory to be present until the extension is compressed later on, so not removing it here
kernelModulesDependencyTreeStagingDir, err := os.MkdirTemp("", "module-deps")
if err != nil {
return nil, err
}
// we want to make sure the root directory has the right permissions.
if err := os.Chmod(kernelModulesDependencyTreeStagingDir, 0o755); err != nil {
return nil, err
}
kernelModulesDepenencyTreeDirectory := filepath.Join(kernelModulesDependencyTreeStagingDir, constants.DefaultKernelModulesPath)
if err := os.MkdirAll(kernelModulesDepenencyTreeDirectory, 0o755); err != nil {
return nil, err
}
if err := findAndMoveKernelModulesDepFiles(kernelModulesDepenencyTreeDirectory, mp.Target()); err != nil {
return nil, err
}
kernelModulesDepTreeExtension := newExtension(kernelModulesDependencyTreeStagingDir, "modules.dep")
kernelModulesDepTreeExtension.Manifest = extensions.Manifest{
Version: constants.DefaultKernelVersion,
Metadata: extensions.Metadata{
Name: "modules.dep",
Version: constants.DefaultKernelVersion,
Author: "Talos Machinery",
Description: "Combined modules.dep for all extensions",
},
}
return kernelModulesDepTreeExtension, nil
}
func logErr(f func() error) {
// if file is already closed, ignore the error
if err := f(); err != nil && !errors.Is(err, os.ErrClosed) {
log.Println(err)
}
}
func extractRootfsFromInitramfs(input bytes.Buffer, rootfsFilePath string) error {
recReader := cpio.Newc.Reader(bytes.NewReader(input.Bytes()))
return cpio.ForEachRecord(recReader, func(r cpio.Record) error {
if r.Name != constants.RootfsAsset {
return nil
}
reader := io.NewSectionReader(r.ReaderAt, 0, int64(r.FileSize))
f, err := os.Create(rootfsFilePath)
if err != nil {
return err
}
defer logErr(func() error {
return f.Close()
})
_, err = io.Copy(f, reader)
if err != nil {
return err
}
return f.Close()
})
}
func depmod(kernelModulesPath string) error {
baseDir := strings.TrimSuffix(kernelModulesPath, constants.DefaultKernelModulesPath)
cmd := exec.Command("depmod", "--all", "--basedir", baseDir, "--config", "/etc/modules.d/10-extra-modules.conf", constants.DefaultKernelVersion)
cmd.Stderr = os.Stderr
return cmd.Run()
}
func findAndMoveKernelModulesDepFiles(dest, kernelModulesPath string) error {
fs, err := os.ReadDir(kernelModulesPath)
if err != nil {
return err
}
for _, f := range fs {
if f.IsDir() {
continue
}
if strings.HasPrefix(f.Name(), "modules.") {
fs, err := f.Info()
if err != nil {
return err
}
if err := moveFile(fs, filepath.Join(kernelModulesPath, f.Name()), filepath.Join(dest, f.Name())); err != nil {
return err
}
}
}
return nil
}