Skip to content

Commit df0e388

Browse files
committed
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>
1 parent 8899dd3 commit df0e388

File tree

5 files changed

+180
-33
lines changed

5 files changed

+180
-33
lines changed

.drone.jsonnet

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ local integration_provision_tests_track_2 = Step("provision-tests-track-2", priv
360360

361361
local integration_gvisor = Step("e2e-gvisor", target="e2e-qemu", privileged=true, depends_on=[load_artifacts], environment={
362362
"SHORT_INTEGRATION_TEST": "yes",
363-
"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"}}]',
363+
"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"}}]',
364364
"WITH_TEST": "run_gvisor_test",
365365
"IMAGE_REGISTRY": local_registry,
366366
});

cmd/installer/pkg/install/extensions.go

Lines changed: 84 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package install
77
import (
88
"bytes"
99
"fmt"
10+
"io"
1011
"log"
1112
"os"
1213
"os/exec"
@@ -107,7 +108,7 @@ func compressExtensions(extensions []*extensions.Extension, tempDir string) (*ex
107108
log.Printf("compressing system extensions")
108109

109110
for _, ext := range extensions {
110-
path, err := ext.Compress(tempDir)
111+
path, err := ext.Compress(tempDir, tempDir)
111112
if err != nil {
112113
return nil, fmt.Errorf("error compressing extension %q: %w", ext.Manifest.Metadata.Name, err)
113114
}
@@ -121,40 +122,79 @@ func compressExtensions(extensions []*extensions.Extension, tempDir string) (*ex
121122
return cfg, nil
122123
}
123124

124-
func (i *Installer) rebuildInitramfs(tempDir string) error {
125-
initramfsAsset := fmt.Sprintf(constants.InitramfsAssetPath, i.options.Arch)
125+
func buildContents(path string) (io.Reader, error) {
126+
var listing bytes.Buffer
126127

127-
log.Printf("creating system extensions initramfs archive")
128+
if err := buildContentsRecursive(path, "", &listing); err != nil {
129+
return nil, err
130+
}
131+
132+
return &listing, nil
133+
}
128134

129-
contents, err := os.ReadDir(tempDir)
135+
func buildContentsRecursive(basePath, path string, w io.Writer) error {
136+
if path != "" {
137+
fmt.Fprintf(w, "%s\n", path)
138+
}
139+
140+
st, err := os.Stat(filepath.Join(basePath, path))
130141
if err != nil {
131142
return err
132143
}
133144

134-
var listing bytes.Buffer
145+
if !st.IsDir() {
146+
return nil
147+
}
148+
149+
contents, err := os.ReadDir(filepath.Join(basePath, path))
150+
if err != nil {
151+
return err
152+
}
135153

136154
for _, item := range contents {
137-
fmt.Fprintf(&listing, "%s\n", item.Name())
155+
if err = buildContentsRecursive(basePath, filepath.Join(path, item.Name()), w); err != nil {
156+
return err
157+
}
138158
}
139159

140-
// build cpio image which contains .sqsh images and extensions.yaml
141-
cmd := exec.Command("cpio", "-H", "newc", "--create", "--reproducible", "-F", "initramfs.sysext")
142-
cmd.Dir = tempDir
143-
cmd.Stdin = &listing
144-
cmd.Stderr = os.Stderr
160+
return nil
161+
}
162+
163+
func (i *Installer) rebuildInitramfs(tempDir string) error {
164+
initramfsAsset := fmt.Sprintf(constants.InitramfsAssetPath, i.options.Arch)
145165

146-
if err = cmd.Run(); err != nil {
166+
log.Printf("creating system extensions initramfs archive and compressing it")
167+
168+
// the code below runs the equivalent of:
169+
// find $tempDir -print | cpio -H newc --create --reproducible | xz -v -C crc32 -0 -e -T 0 -z
170+
171+
listing, err := buildContents(tempDir)
172+
if err != nil {
147173
return err
148174
}
149175

150-
log.Printf("compressing system extensions initramfs archive")
151-
152-
source, err := os.OpenFile(filepath.Join(tempDir, "initramfs.sysext"), os.O_RDONLY, 0)
176+
pipeR, pipeW, err := os.Pipe()
153177
if err != nil {
154178
return err
155179
}
156180

157-
defer source.Close() //nolint:errcheck
181+
defer pipeR.Close() //nolint:errcheck
182+
defer pipeW.Close() //nolint:errcheck
183+
184+
// build cpio image which contains .sqsh images and extensions.yaml
185+
cmd1 := exec.Command("cpio", "-H", "newc", "--create", "--reproducible")
186+
cmd1.Dir = tempDir
187+
cmd1.Stdin = listing
188+
cmd1.Stdout = pipeW
189+
cmd1.Stderr = os.Stderr
190+
191+
if err = cmd1.Start(); err != nil {
192+
return err
193+
}
194+
195+
if err = pipeW.Close(); err != nil {
196+
return err
197+
}
158198

159199
destination, err := os.OpenFile(initramfsAsset, os.O_APPEND|os.O_WRONLY, 0)
160200
if err != nil {
@@ -164,15 +204,35 @@ func (i *Installer) rebuildInitramfs(tempDir string) error {
164204
defer destination.Close() //nolint:errcheck
165205

166206
// append compressed initramfs.sysext to the original initramfs.xz, kernel can read such format
167-
cmd = exec.Command("xz", "-v", "-C", "crc32", "-0", "-e", "-T", "0", "-z")
168-
cmd.Dir = tempDir
169-
cmd.Stdin = source
170-
cmd.Stdout = destination
171-
cmd.Stderr = os.Stderr
207+
cmd2 := exec.Command("xz", "-v", "-C", "crc32", "-0", "-e", "-T", "0", "-z")
208+
cmd2.Dir = tempDir
209+
cmd2.Stdin = pipeR
210+
cmd2.Stdout = destination
211+
cmd2.Stderr = os.Stderr
172212

173-
if err = cmd.Run(); err != nil {
213+
if err = cmd2.Start(); err != nil {
174214
return err
175215
}
176216

177-
return nil
217+
if err = pipeR.Close(); err != nil {
218+
return err
219+
}
220+
221+
errCh := make(chan error, 1)
222+
223+
go func() {
224+
errCh <- cmd1.Wait()
225+
}()
226+
227+
go func() {
228+
errCh <- cmd2.Wait()
229+
}()
230+
231+
for i := 0; i < 2; i++ {
232+
if err = <-errCh; err != nil {
233+
return err
234+
}
235+
}
236+
237+
return destination.Sync()
178238
}

internal/pkg/extensions/compress.go

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,91 @@ package extensions
66

77
import (
88
"fmt"
9+
"io"
10+
"io/fs"
911
"os"
1012
"os/exec"
1113
"path/filepath"
14+
15+
"github.com/talos-systems/talos/pkg/machinery/constants"
1216
)
1317

18+
// List of paths to be moved to the future initramfs.
19+
var initramfsPaths = []string{
20+
constants.FirmwarePath,
21+
}
22+
1423
// Compress builds the squashfs image in the specified destination folder.
15-
func (ext *Extension) Compress(destinationPath string) (string, error) {
16-
destinationPath = filepath.Join(destinationPath, fmt.Sprintf("%s.sqsh", ext.directory))
24+
//
25+
// Components which should be placed to the initramfs are moved to the initramfsPath.
26+
func (ext *Extension) Compress(squashPath, initramfsPath string) (string, error) {
27+
for _, path := range initramfsPaths {
28+
if _, err := os.Stat(filepath.Join(ext.rootfsPath, path)); err == nil {
29+
if err = moveFiles(filepath.Join(ext.rootfsPath, path), filepath.Join(initramfsPath, path)); err != nil {
30+
return "", err
31+
}
32+
}
33+
}
1734

18-
cmd := exec.Command("mksquashfs", ext.rootfsPath, destinationPath, "-all-root", "-noappend", "-comp", "xz", "-Xdict-size", "100%", "-no-progress")
35+
squashPath = filepath.Join(squashPath, fmt.Sprintf("%s.sqsh", ext.directory))
36+
37+
cmd := exec.Command("mksquashfs", ext.rootfsPath, squashPath, "-all-root", "-noappend", "-comp", "xz", "-Xdict-size", "100%", "-no-progress")
1938
cmd.Stderr = os.Stderr
2039

21-
return destinationPath, cmd.Run()
40+
return squashPath, cmd.Run()
41+
}
42+
43+
func moveFiles(srcPath, dstPath string) error {
44+
st, err := os.Stat(srcPath)
45+
if err != nil {
46+
return err
47+
}
48+
49+
if st.IsDir() {
50+
return moveDirectory(st, srcPath, dstPath)
51+
}
52+
53+
return moveFile(st, srcPath, dstPath)
54+
}
55+
56+
func moveFile(st fs.FileInfo, srcPath, dstPath string) error {
57+
src, err := os.Open(srcPath)
58+
if err != nil {
59+
return err
60+
}
61+
62+
defer src.Close() //nolint:errcheck
63+
64+
dst, err := os.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY, st.Mode().Perm())
65+
if err != nil {
66+
return err
67+
}
68+
69+
defer dst.Close() //nolint:errcheck
70+
71+
_, err = io.Copy(dst, src)
72+
if err != nil {
73+
return err
74+
}
75+
76+
return os.Remove(srcPath)
77+
}
78+
79+
func moveDirectory(st fs.FileInfo, srcPath, dstPath string) error {
80+
if err := os.MkdirAll(dstPath, st.Mode().Perm()); err != nil {
81+
return err
82+
}
83+
84+
contents, err := os.ReadDir(srcPath)
85+
if err != nil {
86+
return err
87+
}
88+
89+
for _, item := range contents {
90+
if err = moveFiles(filepath.Join(srcPath, item.Name()), filepath.Join(dstPath, item.Name())); err != nil {
91+
return err
92+
}
93+
}
94+
95+
return os.Remove(srcPath)
2296
}

internal/pkg/extensions/extensions_test.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package extensions_test
66

77
import (
8+
"os/exec"
89
"path/filepath"
910
"testing"
1011

@@ -15,7 +16,7 @@ import (
1516
"github.com/talos-systems/talos/pkg/version"
1617
)
1718

18-
func TestLoadValidateCompress(t *testing.T) {
19+
func TestLoadValidate(t *testing.T) {
1920
ext, err := extensions.Load("testdata/good/extension1")
2021
require.NoError(t, err)
2122

@@ -30,11 +31,23 @@ func TestLoadValidateCompress(t *testing.T) {
3031
})
3132

3233
assert.NoError(t, ext.Validate())
34+
}
35+
36+
func TestCompress(t *testing.T) {
37+
// Compress is going to change contents of the extension, copy to some temporary directory
38+
extDir := t.TempDir()
3339

34-
dest := t.TempDir()
35-
_, err = ext.Compress(dest)
40+
require.NoError(t, exec.Command("cp", "-r", "testdata/good/extension1", extDir).Run())
3641

42+
ext, err := extensions.Load(filepath.Join(extDir, "extension1"))
43+
require.NoError(t, err)
44+
45+
squashDest, initramfsDest := t.TempDir(), t.TempDir()
46+
squashFile, err := ext.Compress(squashDest, initramfsDest)
3747
assert.NoError(t, err)
48+
49+
assert.FileExists(t, squashFile)
50+
assert.FileExists(t, filepath.Join(initramfsDest, "lib", "firmware", "amd", "cpu"))
3851
}
3952

4053
func TestValidateFailures(t *testing.T) {

internal/pkg/extensions/pull.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func (puller *Puller) PullAndMount(ctx context.Context, registryConfig config.Re
7777
return err
7878
}
7979

80-
mounts, err := snapshotService.View(ctx, path, chainID.String())
80+
mounts, err := snapshotService.Prepare(ctx, path, chainID.String())
8181
if err != nil {
8282
return err
8383
}

0 commit comments

Comments
 (0)