feat: extract firmware part of system extensions into initramfs

Fixes #4816

This changes the way system extensions are packaged into the squashfs
images: `/lib/firmware` is now moved out of the future squashfs images
and becomes part of `initramfs` to make firmware available in the early
boot.

Talos will bind-mount `/lib/firmware` into rootfs as well, so it will be
available in the rootfs as well.

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
This commit is contained in:
Andrey Smirnov 2022-01-27 19:15:25 +03:00
parent 8899dd3494
commit df0e388a4f
No known key found for this signature in database
GPG Key ID: 7B26396447AB6DFD
5 changed files with 182 additions and 35 deletions

View File

@ -360,7 +360,7 @@ local integration_provision_tests_track_2 = Step("provision-tests-track-2", priv
local integration_gvisor = Step("e2e-gvisor", target="e2e-qemu", privileged=true, depends_on=[load_artifacts], environment={
"SHORT_INTEGRATION_TEST": "yes",
"WITH_CONFIG_PATCH": '[{"op":"add","path":"/machine/install/extensions","value":[{"image":"ghcr.io/talos-systems/gvisor:933cdb8"}]},{"op":"add","path":"/machine/sysctls","value":{"user.max_user_namespaces": "11255"}}]',
"WITH_CONFIG_PATCH": '[{"op":"add","path":"/machine/install/extensions","value":[{"image":"ghcr.io/talos-systems/gvisor:54b831d"},{"image":"ghcr.io/talos-systems/intel-ucode:54b831d"}]},{"op":"add","path":"/machine/sysctls","value":{"user.max_user_namespaces": "11255"}}]',
"WITH_TEST": "run_gvisor_test",
"IMAGE_REGISTRY": local_registry,
});

View File

@ -7,6 +7,7 @@ package install
import (
"bytes"
"fmt"
"io"
"log"
"os"
"os/exec"
@ -107,7 +108,7 @@ func compressExtensions(extensions []*extensions.Extension, tempDir string) (*ex
log.Printf("compressing system extensions")
for _, ext := range extensions {
path, err := ext.Compress(tempDir)
path, err := ext.Compress(tempDir, tempDir)
if err != nil {
return nil, fmt.Errorf("error compressing extension %q: %w", ext.Manifest.Metadata.Name, err)
}
@ -121,41 +122,80 @@ func compressExtensions(extensions []*extensions.Extension, tempDir string) (*ex
return cfg, nil
}
func buildContents(path string) (io.Reader, error) {
var listing bytes.Buffer
if err := buildContentsRecursive(path, "", &listing); err != nil {
return nil, err
}
return &listing, nil
}
func buildContentsRecursive(basePath, path string, w io.Writer) error {
if path != "" {
fmt.Fprintf(w, "%s\n", path)
}
st, err := os.Stat(filepath.Join(basePath, path))
if err != nil {
return err
}
if !st.IsDir() {
return nil
}
contents, err := os.ReadDir(filepath.Join(basePath, path))
if err != nil {
return err
}
for _, item := range contents {
if err = buildContentsRecursive(basePath, filepath.Join(path, item.Name()), w); err != nil {
return err
}
}
return nil
}
func (i *Installer) rebuildInitramfs(tempDir string) error {
initramfsAsset := fmt.Sprintf(constants.InitramfsAssetPath, i.options.Arch)
log.Printf("creating system extensions initramfs archive")
log.Printf("creating system extensions initramfs archive and compressing it")
contents, err := os.ReadDir(tempDir)
// the code below runs the equivalent of:
// find $tempDir -print | cpio -H newc --create --reproducible | xz -v -C crc32 -0 -e -T 0 -z
listing, err := buildContents(tempDir)
if err != nil {
return err
}
var listing bytes.Buffer
for _, item := range contents {
fmt.Fprintf(&listing, "%s\n", item.Name())
pipeR, pipeW, err := os.Pipe()
if err != nil {
return err
}
defer pipeR.Close() //nolint:errcheck
defer pipeW.Close() //nolint:errcheck
// build cpio image which contains .sqsh images and extensions.yaml
cmd := exec.Command("cpio", "-H", "newc", "--create", "--reproducible", "-F", "initramfs.sysext")
cmd.Dir = tempDir
cmd.Stdin = &listing
cmd.Stderr = os.Stderr
cmd1 := exec.Command("cpio", "-H", "newc", "--create", "--reproducible")
cmd1.Dir = tempDir
cmd1.Stdin = listing
cmd1.Stdout = pipeW
cmd1.Stderr = os.Stderr
if err = cmd.Run(); err != nil {
if err = cmd1.Start(); err != nil {
return err
}
log.Printf("compressing system extensions initramfs archive")
source, err := os.OpenFile(filepath.Join(tempDir, "initramfs.sysext"), os.O_RDONLY, 0)
if err != nil {
if err = pipeW.Close(); err != nil {
return err
}
defer source.Close() //nolint:errcheck
destination, err := os.OpenFile(initramfsAsset, os.O_APPEND|os.O_WRONLY, 0)
if err != nil {
return err
@ -164,15 +204,35 @@ func (i *Installer) rebuildInitramfs(tempDir string) error {
defer destination.Close() //nolint:errcheck
// append compressed initramfs.sysext to the original initramfs.xz, kernel can read such format
cmd = exec.Command("xz", "-v", "-C", "crc32", "-0", "-e", "-T", "0", "-z")
cmd.Dir = tempDir
cmd.Stdin = source
cmd.Stdout = destination
cmd.Stderr = os.Stderr
cmd2 := exec.Command("xz", "-v", "-C", "crc32", "-0", "-e", "-T", "0", "-z")
cmd2.Dir = tempDir
cmd2.Stdin = pipeR
cmd2.Stdout = destination
cmd2.Stderr = os.Stderr
if err = cmd.Run(); err != nil {
if err = cmd2.Start(); err != nil {
return err
}
return nil
if err = pipeR.Close(); err != nil {
return err
}
errCh := make(chan error, 1)
go func() {
errCh <- cmd1.Wait()
}()
go func() {
errCh <- cmd2.Wait()
}()
for i := 0; i < 2; i++ {
if err = <-errCh; err != nil {
return err
}
}
return destination.Sync()
}

View File

@ -6,17 +6,91 @@ package extensions
import (
"fmt"
"io"
"io/fs"
"os"
"os/exec"
"path/filepath"
"github.com/talos-systems/talos/pkg/machinery/constants"
)
// Compress builds the squashfs image in the specified destination folder.
func (ext *Extension) Compress(destinationPath string) (string, error) {
destinationPath = filepath.Join(destinationPath, fmt.Sprintf("%s.sqsh", ext.directory))
// List of paths to be moved to the future initramfs.
var initramfsPaths = []string{
constants.FirmwarePath,
}
cmd := exec.Command("mksquashfs", ext.rootfsPath, destinationPath, "-all-root", "-noappend", "-comp", "xz", "-Xdict-size", "100%", "-no-progress")
// Compress builds the squashfs image in the specified destination folder.
//
// Components which should be placed to the initramfs are moved to the initramfsPath.
func (ext *Extension) Compress(squashPath, initramfsPath string) (string, error) {
for _, path := range initramfsPaths {
if _, err := os.Stat(filepath.Join(ext.rootfsPath, path)); err == nil {
if err = moveFiles(filepath.Join(ext.rootfsPath, path), filepath.Join(initramfsPath, path)); err != nil {
return "", err
}
}
}
squashPath = filepath.Join(squashPath, fmt.Sprintf("%s.sqsh", ext.directory))
cmd := exec.Command("mksquashfs", ext.rootfsPath, squashPath, "-all-root", "-noappend", "-comp", "xz", "-Xdict-size", "100%", "-no-progress")
cmd.Stderr = os.Stderr
return destinationPath, cmd.Run()
return squashPath, cmd.Run()
}
func moveFiles(srcPath, dstPath string) error {
st, err := os.Stat(srcPath)
if err != nil {
return err
}
if st.IsDir() {
return moveDirectory(st, srcPath, dstPath)
}
return moveFile(st, srcPath, dstPath)
}
func moveFile(st fs.FileInfo, srcPath, dstPath string) error {
src, err := os.Open(srcPath)
if err != nil {
return err
}
defer src.Close() //nolint:errcheck
dst, err := os.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY, st.Mode().Perm())
if err != nil {
return err
}
defer dst.Close() //nolint:errcheck
_, err = io.Copy(dst, src)
if err != nil {
return err
}
return os.Remove(srcPath)
}
func moveDirectory(st fs.FileInfo, srcPath, dstPath string) error {
if err := os.MkdirAll(dstPath, st.Mode().Perm()); err != nil {
return err
}
contents, err := os.ReadDir(srcPath)
if err != nil {
return err
}
for _, item := range contents {
if err = moveFiles(filepath.Join(srcPath, item.Name()), filepath.Join(dstPath, item.Name())); err != nil {
return err
}
}
return os.Remove(srcPath)
}

View File

@ -5,6 +5,7 @@
package extensions_test
import (
"os/exec"
"path/filepath"
"testing"
@ -15,7 +16,7 @@ import (
"github.com/talos-systems/talos/pkg/version"
)
func TestLoadValidateCompress(t *testing.T) {
func TestLoadValidate(t *testing.T) {
ext, err := extensions.Load("testdata/good/extension1")
require.NoError(t, err)
@ -30,11 +31,23 @@ func TestLoadValidateCompress(t *testing.T) {
})
assert.NoError(t, ext.Validate())
}
dest := t.TempDir()
_, err = ext.Compress(dest)
func TestCompress(t *testing.T) {
// Compress is going to change contents of the extension, copy to some temporary directory
extDir := t.TempDir()
require.NoError(t, exec.Command("cp", "-r", "testdata/good/extension1", extDir).Run())
ext, err := extensions.Load(filepath.Join(extDir, "extension1"))
require.NoError(t, err)
squashDest, initramfsDest := t.TempDir(), t.TempDir()
squashFile, err := ext.Compress(squashDest, initramfsDest)
assert.NoError(t, err)
assert.FileExists(t, squashFile)
assert.FileExists(t, filepath.Join(initramfsDest, "lib", "firmware", "amd", "cpu"))
}
func TestValidateFailures(t *testing.T) {

View File

@ -77,7 +77,7 @@ func (puller *Puller) PullAndMount(ctx context.Context, registryConfig config.Re
return err
}
mounts, err := snapshotService.View(ctx, path, chainID.String())
mounts, err := snapshotService.Prepare(ctx, path, chainID.String())
if err != nil {
return err
}