diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts.go b/cmd/snap-bootstrap/cmd_initramfs_mounts.go index 81c2c58f9a15..f14c75edee53 100644 --- a/cmd/snap-bootstrap/cmd_initramfs_mounts.go +++ b/cmd/snap-bootstrap/cmd_initramfs_mounts.go @@ -57,6 +57,7 @@ import ( "github.com/snapcore/snapd/seed" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/naming" + "github.com/snapcore/snapd/snap/snapdir" "github.com/snapcore/snapd/snap/squashfs" "github.com/snapcore/snapd/sysconfig" "github.com/snapcore/snapd/timings" @@ -258,6 +259,7 @@ func readSnapInfo(sysSnaps map[snap.Type]*seed.Snap, snapType snap.Type) (*snap. if err != nil { return nil, err } + // Comes from the seed and it might be unasserted, set revision in that case if info.Revision.Unset() { info.Revision = snap.R(-1) } @@ -265,6 +267,19 @@ func readSnapInfo(sysSnaps map[snap.Type]*seed.Snap, snapType snap.Type) (*snap. } +func readComponentInfo(seedComp *seed.Component, mntPt string, snapInfo *snap.Info, csi *snap.ComponentSideInfo) (*snap.ComponentInfo, error) { + container := snapdir.New(mntPt) + ci, err := snap.ReadComponentInfoFromContainer(container, snapInfo, csi) + if err != nil { + return nil, err + } + // Comes from the seed and it might be unasserted, set revision in that case + if ci.Revision.Unset() { + ci.Revision = snap.R(-1) + } + return ci, nil +} + func runFDESetupHook(req *fde.SetupRequest) ([]byte, error) { // TODO: use systemd-run encoded, err := json.Marshal(req) @@ -331,26 +346,77 @@ func doInstall(mst *initramfsMountsState, model *asserts.Model, sysSnaps map[sna return fmt.Errorf("cannot use gadget: %v", err) } - // TODO:COMPS take into account kernel-modules components, see - // DeviceManager,doSetupRunSystem and other parts of - // handlers_install.go. + // Get kernel-modules information to have them ready early on first boot - bootDevice := "" - kernelSnapInfo := &gadgetInstall.KernelSnapInfo{ - Name: kernelSnap.SnapName(), - MountPoint: kernelMountDir, - Revision: kernelSnap.Revision, - // Should be true always anyway - IsCore: !model.Classic(), - } - switch model.Base() { - case "core20", "core22", "core22-desktop": - kernelSnapInfo.NeedsDriversTree = false - default: - kernelSnapInfo.NeedsDriversTree = true + kernCompsByName := make(map[string]*snap.Component) + for _, c := range kernelSnap.Components { + kernCompsByName[c.Name] = c + } + + kernelSeed := sysSnaps[snap.TypeKernel] + kernCompsMntPts := make(map[string]string) + compSeedInfos := []install.ComponentSeedInfo{} + for _, sc := range kernelSeed.Components { + seedComp := sc + comp, ok := kernCompsByName[seedComp.CompSideInfo.Component.ComponentName] + if !ok { + return fmt.Errorf("component %s in seed but not defined by snap!", + seedComp.CompSideInfo.Component.ComponentName) + } + if comp.Type != snap.KernelModulesComponent { + continue + } + + // Mount ephemerally the kernel-modules components to read + // their metadata and also to make them accessible if building + // the drivers tree. + mntPt := filepath.Join(filepath.Join(boot.InitramfsRunMntDir, "snap-content", + seedComp.CompSideInfo.Component.String())) + if err := doSystemdMount(seedComp.Path, mntPt, &systemdMountOptions{ + ReadOnly: true, + Private: true, + Ephemeral: true}); err != nil { + return err + } + kernCompsMntPts[seedComp.CompSideInfo.Component.String()] = mntPt + + defer func() { + stdout, stderr, err := osutil.RunSplitOutput("systemd-mount", "--umount", mntPt) + if err != nil { + logger.Noticef("cannot unmount component in %s: %v", + mntPt, osutil.OutputErrCombine(stdout, stderr, err)) + } + }() + + compInfo, err := readComponentInfo(&seedComp, mntPt, kernelSnap, &seedComp.CompSideInfo) + if err != nil { + return err + } + compSeedInfos = append(compSeedInfos, install.ComponentSeedInfo{ + Info: compInfo, + Seed: &seedComp, + }) } - installedSystem, err := gadgetInstallRun(model, gadgetMountDir, kernelSnapInfo, bootDevice, options, installObserver, timings.New(nil)) + currentSeed, err := mst.LoadSeed(mst.recoverySystem) + if err != nil { + return err + } + preseedSeed, ok := currentSeed.(seed.PreseedCapable) + preseed := false + if ok && preseedSeed.HasArtifact("preseed.tgz") { + preseed = true + } + // Drivers tree will already be built if using the preseed tarball + needsKernelSetup := kernel.NeedsKernelDriversTree(model) && !preseed + + isCore := !model.Classic() + kernelBootInfo := install.BuildKernelBootInfo( + kernelSnap, compSeedInfos, kernelMountDir, kernCompsMntPts, + install.BuildKernelBootInfoOpts{IsCore: isCore, NeedsDriversTree: needsKernelSetup}) + + bootDevice := "" + installedSystem, err := gadgetInstallRun(model, gadgetMountDir, kernelBootInfo.KSnapInfo, bootDevice, options, installObserver, timings.New(nil)) if err != nil { return err } @@ -382,6 +448,7 @@ func doInstall(mst *initramfsMountsState, model *asserts.Model, sysSnaps map[sna KernelPath: sysSnaps[snap.TypeKernel].Path, UnpackedGadgetDir: gadgetMountDir, RecoverySystemLabel: mst.recoverySystem, + KernelMods: kernelBootInfo.BootableKMods, } if err := bootMakeRunnableStandaloneSystem(model, bootWith, trustedInstallObserver); err != nil { @@ -395,15 +462,27 @@ func doInstall(mst *initramfsMountsState, model *asserts.Model, sysSnaps map[sna return err } - currentSeed, err := mst.LoadSeed(mst.recoverySystem) + if preseed { + // Extract pre-seed tarball + runMode := false + if err := installApplyPreseededData(preseedSeed, + boot.InitramfsWritableDir(model, runMode)); err != nil { + return err + } + } + + // Create drivers tree mount units to make it available before switch root + rootfsDir := filepath.Join(boot.InitramfsDataDir, "system-data") + hasDriversTree, err := createKernelMounts( + rootfsDir, kernelSnap.SnapName(), kernelSnap.Revision, !isCore) if err != nil { return err } - preseedSeed, ok := currentSeed.(seed.PreseedCapable) - if ok && preseedSeed.HasArtifact("preseed.tgz") { - runMode := false - if err := installApplyPreseededData(preseedSeed, boot.InitramfsWritableDir(model, runMode)); err != nil { - return err + if hasDriversTree { + // Unmount the kernel snap mount, we keep it only for UC20/22 + stdout, stderr, err := osutil.RunSplitOutput("systemd-mount", "--umount", kernelMountDir) + if err != nil { + return osutil.OutputErrCombine(stdout, stderr, err) } } @@ -432,6 +511,25 @@ func generateMountsModeInstall(mst *initramfsMountsState) error { } if installAndRun { + kernSnap := snaps[snap.TypeKernel] + // seed is cached at this point + theSeed, err := mst.LoadSeed("") + if err != nil { + return fmt.Errorf("internal error: cannot load seed: %v", err) + } + // Filter by mode, this is relevant only to get the + // kernel-modules components that are used in run mode and + // therefore need to be considered when installing from the + // initramfs to have the modules available early on first boot. + // TODO when running normal install or recover/factory-reset, + // we would need also this if we want the modules to be + // available early. + kernSnap, err = theSeed.ModeSnap(kernSnap.SnapName(), "run") + if err != nil { + return err + } + snaps[snap.TypeKernel] = kernSnap + if err := doInstall(mst, model, snaps); err != nil { return err } @@ -1979,9 +2077,9 @@ func createKernelModulesMountUnits(writableRootDir, snapRoot, driversDir, kernel // First in modules (we might not have a kernel version subdir if there // are no kernel modules). - kversion, kverr := kernel.KernelVersionFromModulesDir(filepath.Join(driversDir, "lib")) + kversion, kver := kernel.KernelVersionFromModulesDir(filepath.Join(driversDir, "lib")) compSet := map[snap.ComponentSideInfo]bool{} - if kverr == nil { + if kver == nil { modUpdatesDir := filepath.Join(driversDir, "lib", "modules", kversion, "updates") if err := getCompsFromSymlinks(modUpdatesDir, kernelName, compSet); err != nil { return err diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go index db29b52213b1..a04f27c9ef41 100644 --- a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go +++ b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2019-2024 Canonical Ltd + * Copyright (C)20 19-2024 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -22,6 +22,7 @@ package main_test import ( "bytes" "encoding/json" + "errors" "fmt" "os" "path/filepath" @@ -40,6 +41,7 @@ import ( main "github.com/snapcore/snapd/cmd/snap-bootstrap" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/gadget" + "github.com/snapcore/snapd/gadget/install" gadgetInstall "github.com/snapcore/snapd/gadget/install" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" @@ -235,30 +237,49 @@ var ( mockStateContent = `{"data":{"auth":{"users":[{"id":1,"name":"mvo"}],"macaroon-key":"not-a-cookie","last-id":1}},"some":{"other":"stuff"}}` ) -func (s *baseInitramfsMountsSuite) setupSeed(c *C, modelAssertTime time.Time, gadgetSnapFiles [][]string) { +type setupSeedOpts struct { + hasKModsComps bool +} + +func (s *baseInitramfsMountsSuite) setupSeed(c *C, modelAssertTime time.Time, gadgetSnapFiles [][]string, opts setupSeedOpts) { + base := "core20" + channel := "20" + if opts.hasKModsComps { + base = "core24" + channel = "24" + } // pretend /run/mnt/ubuntu-seed has a valid seed s.seedDir = boot.InitramfsUbuntuSeedDir - // now create a minimal uc20 seed dir with snaps/assertions - seed20 := &seedtest.TestingSeed20{SeedDir: s.seedDir} - seed20.SetupAssertSigning("canonical") - restore := seed.MockTrusted(seed20.StoreSigning.Trusted) + // now create a minimal uc20+ seed dir with snaps/assertions + testSeed := &seedtest.TestingSeed20{SeedDir: s.seedDir} + testSeed.SetupAssertSigning("canonical") + restore := seed.MockTrusted(testSeed.StoreSigning.Trusted) s.AddCleanup(restore) // XXX: we don't really use this but seedtest always expects my-brand - seed20.Brands.Register("my-brand", brandPrivKey, map[string]interface{}{ + testSeed.Brands.Register("my-brand", brandPrivKey, map[string]interface{}{ "verification": "verified", }) // make sure all the assertions use the same time - seed20.SetSnapAssertionNow(s.snapDeclAssertsTime) + testSeed.SetSnapAssertionNow(s.snapDeclAssertsTime) // add a bunch of snaps - seed20.MakeAssertedSnap(c, "name: snapd\nversion: 1\ntype: snapd", nil, snap.R(1), "canonical", seed20.StoreSigning.Database) - seed20.MakeAssertedSnap(c, "name: pc\nversion: 1\ntype: gadget\nbase: core20", gadgetSnapFiles, snap.R(1), "canonical", seed20.StoreSigning.Database) - seed20.MakeAssertedSnap(c, "name: pc-kernel\nversion: 1\ntype: kernel", nil, snap.R(1), "canonical", seed20.StoreSigning.Database) - seed20.MakeAssertedSnap(c, "name: core20\nversion: 1\ntype: base", nil, snap.R(1), "canonical", seed20.StoreSigning.Database) + testSeed.MakeAssertedSnap(c, "name: snapd\nversion: 1\ntype: snapd", nil, snap.R(1), "canonical", testSeed.StoreSigning.Database) + testSeed.MakeAssertedSnap(c, fmt.Sprintf("name: pc\nversion: 1\ntype: gadget\nbase: %s", base), + gadgetSnapFiles, snap.R(1), "canonical", testSeed.StoreSigning.Database) + testSeed.MakeAssertedSnap(c, fmt.Sprintf("name: %s\nversion: 1\ntype: base", base), + nil, snap.R(1), "canonical", testSeed.StoreSigning.Database) + + if opts.hasKModsComps { + testSeed.MakeAssertedSnapWithComps(c, seedtest.SampleSnapYaml["pc-kernel=24+kmods"], + nil, snap.R(1), nil, "canonical", testSeed.StoreSigning.Database) + } else { + testSeed.MakeAssertedSnap(c, "name: pc-kernel\nversion: 1\ntype: kernel", nil, + snap.R(1), "canonical", testSeed.StoreSigning.Database) + } // pretend that by default, the model uses an older timestamp than the // snap assertions @@ -267,30 +288,49 @@ func (s *baseInitramfsMountsSuite) setupSeed(c *C, modelAssertTime time.Time, ga } s.sysLabel = "20191118" + var kernel map[string]interface{} + if opts.hasKModsComps { + kernel = map[string]interface{}{ + "name": "pc-kernel", + "id": testSeed.AssertedSnapID("pc-kernel"), + "type": "kernel", + "default-channel": channel, + "components": map[string]interface{}{ + "kcomp1": "required", + "kcomp2": "required", + "kcomp3": map[string]interface{}{ + "presence": "optional", + "modes": []interface{}{"ephemeral"}, + }, + }, + } + } else { + kernel = map[string]interface{}{ + "name": "pc-kernel", + "id": testSeed.AssertedSnapID("pc-kernel"), + "type": "kernel", + "default-channel": channel, + } + } model := map[string]interface{}{ "display-name": "my model", "architecture": "amd64", - "base": "core20", + "base": base, "timestamp": modelAssertTime.Format(time.RFC3339), "snaps": []interface{}{ - map[string]interface{}{ - "name": "pc-kernel", - "id": seed20.AssertedSnapID("pc-kernel"), - "type": "kernel", - "default-channel": "20", - }, + kernel, map[string]interface{}{ "name": "pc", - "id": seed20.AssertedSnapID("pc"), + "id": testSeed.AssertedSnapID("pc"), "type": "gadget", - "default-channel": "20", + "default-channel": channel, }}, } if s.isClassic { model["classic"] = "true" model["distribution"] = "ubuntu" } - s.model = seed20.MakeSeed(c, s.sysLabel, "my-brand", "my-model", model, nil) + s.model = testSeed.MakeSeed(c, s.sysLabel, "my-brand", "my-model", model, nil) } func (s *baseInitramfsMountsSuite) SetUpTest(c *C) { @@ -322,7 +362,7 @@ func (s *baseInitramfsMountsSuite) SetUpTest(c *C) { s.snapDeclAssertsTime = time.Now().Add(60 * time.Minute) // setup the seed - s.setupSeed(c, time.Time{}, nil) + s.setupSeed(c, time.Time{}, nil, setupSeedOpts{}) // Make sure we have a model assertion in the ubuntu-boot partition var err error @@ -621,6 +661,10 @@ func (s *baseInitramfsMountsSuite) ubuntuPartUUIDMount(partuuid string, mode str } func (s *baseInitramfsMountsSuite) makeSeedSnapSystemdMount(typ snap.Type) systemdMount { + return s.makeSeedSnapSystemdMountForBase(typ, "core20") +} + +func (s *baseInitramfsMountsSuite) makeSeedSnapSystemdMountForBase(typ snap.Type, base string) systemdMount { mnt := systemdMount{} var name, dir string switch typ { @@ -628,7 +672,7 @@ func (s *baseInitramfsMountsSuite) makeSeedSnapSystemdMount(typ snap.Type) syste name = "snapd" dir = "snapd" case snap.TypeBase: - name = "core20" + name = base dir = "base" case snap.TypeGadget: name = "pc" @@ -743,6 +787,306 @@ grade=signed c.Check(logbuf.String(), testutil.Contains, "snap-bootstrap version 1.2.3 starting\n") } +func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeWithCompsHappy(c *C) { + failMount := false + s.testInitramfsMountsInstallModeWithCompsHappy(c, failMount) +} + +func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeWithCompsFailMount(c *C) { + failMount := true + s.testInitramfsMountsInstallModeWithCompsHappy(c, failMount) +} + +func (s *initramfsMountsSuite) testInitramfsMountsInstallModeWithCompsHappy(c *C, failMount bool) { + var efiArch string + switch runtime.GOARCH { + case "amd64": + efiArch = "x64" + case "arm64": + efiArch = "aa64" + default: + c.Skip("Unknown EFI arch") + } + + // setup the seed + // always remove the ubuntu-seed dir, otherwise setupSeed complains the + // model file already exists and can't setup the seed + err := os.RemoveAll(filepath.Join(boot.InitramfsUbuntuSeedDir)) + c.Assert(err, IsNil) + s.setupSeed(c, time.Time{}, nil, setupSeedOpts{hasKModsComps: true}) + + s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel) + + systemDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems", s.sysLabel) + c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems", s.sysLabel), 0755), IsNil) + c.Assert(os.WriteFile(filepath.Join(systemDir, "preseed.tgz"), []byte{}, 0640), IsNil) + + kernelSnapYaml := filepath.Join(boot.InitramfsRunMntDir, "kernel", "meta", "snap.yaml") + c.Assert(os.MkdirAll(filepath.Dir(kernelSnapYaml), 0755), IsNil) + kernelSnapYamlContent := seedtest.SampleSnapYaml["pc-kernel=24+kmods"] + c.Assert(os.WriteFile(kernelSnapYaml, []byte(kernelSnapYamlContent), 0555), IsNil) + + for _, compName := range []string{"kcomp1", "kcomp2"} { + compFullName := fmt.Sprintf("pc-kernel+%s", compName) + compSnapYaml := filepath.Join(boot.InitramfsRunMntDir, fmt.Sprintf("snap-content/%s/meta/component.yaml", compFullName)) + c.Assert(os.MkdirAll(filepath.Dir(compSnapYaml), 0755), IsNil) + compYamlContent := seedtest.SampleSnapYaml[compFullName] + c.Assert(os.WriteFile(compSnapYaml, []byte(compYamlContent), 0555), IsNil) + } + + baseSnapYaml := filepath.Join(boot.InitramfsRunMntDir, "base", "meta", "snap.yaml") + c.Assert(os.MkdirAll(filepath.Dir(baseSnapYaml), 0755), IsNil) + baseSnapYamlContent := `{}` + c.Assert(os.WriteFile(baseSnapYaml, []byte(baseSnapYamlContent), 0555), IsNil) + + gadgetSnapYaml := filepath.Join(boot.InitramfsRunMntDir, "gadget", "meta", "snap.yaml") + c.Assert(os.MkdirAll(filepath.Dir(gadgetSnapYaml), 0755), IsNil) + gadgetSnapYamlContent := `{}` + c.Assert(os.WriteFile(gadgetSnapYaml, []byte(gadgetSnapYamlContent), 0555), IsNil) + + grubConf := filepath.Join(boot.InitramfsRunMntDir, "gadget", "grub.conf") + c.Assert(os.MkdirAll(filepath.Dir(grubConf), 0755), IsNil) + c.Assert(os.WriteFile(grubConf, nil, 0555), IsNil) + + bootloader := filepath.Join(boot.InitramfsRunMntDir, "ubuntu-seed", "EFI", "boot", fmt.Sprintf("boot%s.efi", efiArch)) + c.Assert(os.MkdirAll(filepath.Dir(bootloader), 0755), IsNil) + c.Assert(os.WriteFile(bootloader, nil, 0555), IsNil) + grub := filepath.Join(boot.InitramfsRunMntDir, "ubuntu-seed", "EFI", "boot", fmt.Sprintf("grub%s.efi", efiArch)) + c.Assert(os.MkdirAll(filepath.Dir(grub), 0755), IsNil) + c.Assert(os.WriteFile(grub, nil, 0555), IsNil) + + writeGadget(c, "ubuntu-seed", "system-seed", "") + + gadgetInstallCalled := false + restoreGadgetInstall := main.MockGadgetInstallRun(func(model gadget.Model, gadgetRoot string, kernelSnapInfo *gadgetInstall.KernelSnapInfo, bootDevice string, options gadgetInstall.Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*gadgetInstall.InstalledSystemSideData, error) { + gadgetInstallCalled = true + c.Assert(options.Mount, Equals, true) + c.Assert(string(options.EncryptionType), Equals, "") + c.Assert(bootDevice, Equals, "") + c.Assert(model.Classic(), Equals, false) + c.Assert(string(model.Grade()), Equals, "signed") + c.Assert(gadgetRoot, Equals, filepath.Join(boot.InitramfsRunMntDir, "gadget")) + c.Assert(kernelSnapInfo, DeepEquals, &gadgetInstall.KernelSnapInfo{ + Name: "pc-kernel", + Revision: snap.R(1), + MountPoint: filepath.Join(boot.InitramfsRunMntDir, "kernel"), + // As the drivers tree is already in the preseed tarball + NeedsDriversTree: false, + IsCore: true, + ModulesComps: []install.KernelModulesComponentInfo{ + { + Name: "kcomp1", + Revision: snap.R(77), + MountPoint: filepath.Join(boot.InitramfsRunMntDir, "snap-content/pc-kernel+kcomp1"), + }, + { + Name: "kcomp2", + Revision: snap.R(77), + MountPoint: filepath.Join(boot.InitramfsRunMntDir, "snap-content/pc-kernel+kcomp2"), + }, + }, + }) + // Simulate creation of drivers tree + kernelVer := "6.8.0-51-generic" + updatesDir := filepath.Join(dirs.GlobalRootDir, + "run/mnt/data/system-data/_writable_defaults/var/lib/snapd/kernel/pc-kernel/1/lib/modules", kernelVer, "updates") + c.Assert(os.MkdirAll(updatesDir, 0755), IsNil) + os.Symlink(filepath.Join(dirs.SnapMountDir, + "pc-kernel/components/mnt/kcomp1/77/modules", kernelVer), + filepath.Join(updatesDir, "kcomp1")) + os.Symlink(filepath.Join(dirs.SnapMountDir, + "pc-kernel/components/mnt/kcomp2/77/modules", kernelVer), + filepath.Join(updatesDir, "kcomp2")) + return &gadgetInstall.InstalledSystemSideData{}, nil + }) + defer restoreGadgetInstall() + + makeRunnableCalled := false + restoreMakeRunnableStandaloneSystem := main.MockMakeRunnableStandaloneSystem(func(model *asserts.Model, bootWith *boot.BootableSet, obs boot.TrustedAssetsInstallObserver) error { + makeRunnableCalled = true + c.Assert(model.Model(), Equals, "my-model") + c.Assert(bootWith.RecoverySystemLabel, Equals, s.sysLabel) + c.Assert(bootWith.BasePath, Equals, filepath.Join(s.seedDir, "snaps", "core24_1.snap")) + c.Assert(bootWith.KernelPath, Equals, filepath.Join(s.seedDir, "snaps", "pc-kernel_1.snap")) + c.Assert(bootWith.GadgetPath, Equals, filepath.Join(s.seedDir, "snaps", "pc_1.snap")) + c.Assert(len(bootWith.KernelMods), Equals, 2) + c.Check(bootWith.KernelMods, DeepEquals, []boot.BootableKModsComponents{ + { + CompPlaceInfo: snap.MinimalComponentContainerPlaceInfo("kcomp1", snap.R(77), "pc-kernel"), + CompPath: filepath.Join(s.seedDir, "snaps/pc-kernel+kcomp1_77.comp"), + }, + { + CompPlaceInfo: snap.MinimalComponentContainerPlaceInfo("kcomp2", snap.R(77), "pc-kernel"), + CompPath: filepath.Join(s.seedDir, "snaps/pc-kernel+kcomp2_77.comp"), + }, + }) + return nil + }) + defer restoreMakeRunnableStandaloneSystem() + + applyPreseedCalled := false + restoreApplyPreseededData := main.MockApplyPreseededData(func(preseedSeed seed.PreseedCapable, writableDir string) error { + applyPreseedCalled = true + c.Assert(preseedSeed.ArtifactPath("preseed.tgz"), Equals, filepath.Join(systemDir, "preseed.tgz")) + c.Assert(writableDir, Equals, filepath.Join(dirs.GlobalRootDir, "/run/mnt/data/system-data")) + return nil + }) + defer restoreApplyPreseededData() + + // ensure that we check that access to sealed keys were locked + sealedKeysLocked := false + defer main.MockSecbootLockSealedKeys(func() error { + sealedKeysLocked = true + return nil + })() + + nextBootEnsured := false + defer main.MockEnsureNextBootToRunMode(func(systemLabel string) error { + nextBootEnsured = true + c.Assert(systemLabel, Equals, s.sysLabel) + return nil + })() + + observeExistingTrustedRecoveryAssetsCalled := 0 + mockObserver := &MockObserver{ + BootLoaderSupportsEfiVariablesFunc: func() bool { + return true + }, + ObserveExistingTrustedRecoveryAssetsFunc: func(recoveryRootDir string) error { + observeExistingTrustedRecoveryAssetsCalled += 1 + return nil + }, + ChosenEncryptionKeysFunc: func(key, saveKey keys.EncryptionKey) { + }, + UpdateBootEntryFunc: func() error { + return nil + }, + ObserveFunc: func(op gadget.ContentOperation, partRole, root, relativeTarget string, data *gadget.ContentChange) (gadget.ContentChangeAction, error) { + return gadget.ChangeApply, nil + }, + } + + defer main.MockBuildInstallObserver(func(model *asserts.Model, gadgetDir string, useEncryption bool) (observer gadget.ContentObserver, trustedObserver boot.TrustedAssetsInstallObserver, err error) { + c.Check(model.Classic(), Equals, false) + c.Check(string(model.Grade()), Equals, "signed") + c.Check(gadgetDir, Equals, filepath.Join(boot.InitramfsRunMntDir, "gadget")) + + return mockObserver, mockObserver, nil + })() + + mounts := []systemdMount{ + s.ubuntuLabelMount("ubuntu-seed", "install"), + s.makeSeedSnapSystemdMount(snap.TypeSnapd), + s.makeSeedSnapSystemdMount(snap.TypeKernel), + s.makeSeedSnapSystemdMountForBase(snap.TypeBase, "core24"), + s.makeSeedSnapSystemdMount(snap.TypeGadget), + { + filepath.Join(s.seedDir, "snaps/pc-kernel+kcomp1_77.comp"), + filepath.Join(boot.InitramfsRunMntDir, "snap-content/pc-kernel+kcomp1"), + &main.SystemdMountOptions{ReadOnly: true, + Private: true, + Ephemeral: true}, + nil, + }, + { + filepath.Join(s.seedDir, "snaps/pc-kernel+kcomp2_77.comp"), + filepath.Join(boot.InitramfsRunMntDir, "snap-content/pc-kernel+kcomp2"), + &main.SystemdMountOptions{ReadOnly: true, + Private: true, + Ephemeral: true}, + nil, + }, + { + filepath.Join(s.tmpDir, "/run/mnt/ubuntu-data"), + boot.InitramfsDataDir, + bindOpts, + nil, + }} + if failMount { + mounts = []systemdMount{ + s.ubuntuLabelMount("ubuntu-seed", "install"), + s.makeSeedSnapSystemdMount(snap.TypeSnapd), + s.makeSeedSnapSystemdMount(snap.TypeKernel), + s.makeSeedSnapSystemdMountForBase(snap.TypeBase, "core24"), + s.makeSeedSnapSystemdMount(snap.TypeGadget), + { + filepath.Join(s.seedDir, "snaps/pc-kernel+kcomp1_77.comp"), + filepath.Join(boot.InitramfsRunMntDir, "snap-content/pc-kernel+kcomp1"), + &main.SystemdMountOptions{ReadOnly: true, + Private: true, + Ephemeral: true}, + nil, + }, + { + filepath.Join(s.seedDir, "snaps/pc-kernel+kcomp2_77.comp"), + filepath.Join(boot.InitramfsRunMntDir, "snap-content/pc-kernel+kcomp2"), + &main.SystemdMountOptions{ReadOnly: true, + Private: true, + Ephemeral: true}, + errors.New("error mounting"), + }, + } + } + restore := s.mockSystemdMountSequence(c, mounts, nil) + defer restore() + + cmd := testutil.MockCommand(c, "systemd-mount", ``) + defer cmd.Restore() + + c.Assert(os.Remove(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model")), IsNil) + + _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) + result := true + expectedCallsObserve := 1 + if failMount { + c.Assert(err, ErrorMatches, "error mounting") + result = false + expectedCallsObserve = 0 + } else { + c.Assert(err, IsNil) + } + c.Check(sealedKeysLocked, Equals, true) + + c.Assert(applyPreseedCalled, Equals, result) + c.Assert(makeRunnableCalled, Equals, result) + c.Assert(gadgetInstallCalled, Equals, result) + c.Assert(nextBootEnsured, Equals, result) + c.Check(observeExistingTrustedRecoveryAssetsCalled, Equals, expectedCallsObserve) + + if !failMount { + checkKernelMounts(c, "/run/mnt/data/system-data", "/writable/system-data", + []string{"kcomp1", "kcomp2"}, []string{"77", "77"}, nil, nil) + } + + if failMount { + c.Assert(cmd.Calls(), DeepEquals, [][]string{ + { + "systemd-mount", + "--umount", + filepath.Join(s.tmpDir, "/run/mnt/snap-content/pc-kernel+kcomp1"), + }, + }) + } else { + c.Assert(cmd.Calls(), DeepEquals, [][]string{ + { + "systemd-mount", + "--umount", + filepath.Join(s.tmpDir, "/run/mnt/kernel"), + }, + { + "systemd-mount", + "--umount", + filepath.Join(s.tmpDir, "/run/mnt/snap-content/pc-kernel+kcomp2"), + }, + { + "systemd-mount", + "--umount", + filepath.Join(s.tmpDir, "/run/mnt/snap-content/pc-kernel+kcomp1"), + }, + }) + } +} + func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeBootFlagsSet(c *C) { s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel) @@ -888,7 +1232,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeTimeMovesForwardHap // model file already exists and can't setup the seed err := os.RemoveAll(filepath.Join(boot.InitramfsUbuntuSeedDir)) c.Assert(err, IsNil, comment) - s.setupSeed(c, tc.modelTime, nil) + s.setupSeed(c, tc.modelTime, nil, setupSeedOpts{}) restore := main.MockTimeNow(func() time.Time { return tc.now @@ -949,7 +1293,7 @@ defaults: c.Assert(os.RemoveAll(s.seedDir), IsNil) s.setupSeed(c, time.Time{}, - [][]string{{"meta/gadget.yaml", gadgetYamlDefaults}}) + [][]string{{"meta/gadget.yaml", gadgetYamlDefaults}}, setupSeedOpts{}) s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel) @@ -1150,7 +1494,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRunModeTimeMovesForwardHappy(c // model file already exists and can't setup the seed err := os.RemoveAll(filepath.Join(boot.InitramfsUbuntuSeedDir)) c.Assert(err, IsNil, comment) - s.setupSeed(c, tc.modelTime, nil) + s.setupSeed(c, tc.modelTime, nil, setupSeedOpts{}) restore := main.MockTimeNow(func() time.Time { return tc.now @@ -2621,28 +2965,32 @@ func (s *initramfsMountsSuite) testInitramfsMountsEncryptedNoModel(c *C, mode, l return fmt.Errorf("blocking keys failed") })() - var restore func() - if mode == "run" { + // in install mode we fail before any mount happens + // in install / recover mode the code doesn't make it far enough to do + // any disk cross checking + switch mode { + case "run": // run mode will mount ubuntu-boot only before failing - restore = s.mockSystemdMountSequence(c, []systemdMount{ + restore := s.mockSystemdMountSequence(c, []systemdMount{ s.ubuntuLabelMount("ubuntu-boot", mode), }, nil) - restore2 := disks.MockMountPointDisksToPartitionMapping( + defer restore() + restore = disks.MockMountPointDisksToPartitionMapping( map[disks.Mountpoint]*disks.MockDiskMapping{ {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultEncBootDisk, }, ) - defer restore2() - } else { + defer restore() + default: // install and recover mounts are just ubuntu-seed before we fail - restore = s.mockSystemdMountSequence(c, []systemdMount{ + restore := s.mockSystemdMountSequence(c, []systemdMount{ s.ubuntuLabelMount("ubuntu-seed", mode), }, nil) // in install / recover mode the code doesn't make it far enough to do // any disk cross checking + defer restore() } - defer restore() if label != "" { s.mockProcCmdlineContent(c, @@ -2653,7 +3001,7 @@ func (s *initramfsMountsSuite) testInitramfsMountsEncryptedNoModel(c *C, mode, l } measureEpochCalls := 0 - restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { + restore := main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { measureEpochCalls++ return nil }) @@ -3295,7 +3643,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeTimeMovesForwardHap err = os.RemoveAll(filepath.Join(boot.InitramfsDataDir)) c.Assert(err, IsNil, comment) - s.setupSeed(c, tc.modelTime, nil) + s.setupSeed(c, tc.modelTime, nil, setupSeedOpts{}) restore := main.MockTimeNow(func() time.Time { return tc.now @@ -3386,7 +3734,7 @@ defaults: s.setupSeed(c, time.Time{}, [][]string{ {"meta/gadget.yaml", gadgetYamlDefaults}, - }) + }, setupSeedOpts{}) s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) @@ -8670,7 +9018,7 @@ func compareLoginFiles(c *C, etcDir string, passwd, shadow, group, gshadow strin } } -func checkKernelMounts(c *C, dataRootfs, snapRoot string, compsExist, compsNotExist []int) { +func checkKernelMounts(c *C, dataRootfs, snapRoot string, compsExist, compsExistRevs, compsNotExist, compsNotExistRevs []string) { // Check mount units for the drivers tree unitsPath := filepath.Join(dirs.GlobalRootDir, "run/systemd/system") snapMntDir := dirs.StripRootDir(dirs.SnapMountDir) @@ -8695,9 +9043,9 @@ Options=nodev,ro,x-gdu.hide,x-gvfs-hide `, what, where)) // kernel-modules components - for _, comp := range compsExist { - compName := fmt.Sprint("comp", comp) - compRev := fmt.Sprintf("%d%d", comp, comp) + for i, comp := range compsExist { + compName := comp + compRev := compsExistRevs[i] what := filepath.Join(dataRootfs, "var/lib/snapd/snaps/pc-kernel+"+compName+"_"+compRev+".comp") where := filepath.Join(snapRoot, snapMntDir, "pc-kernel/components/mnt", @@ -8719,9 +9067,9 @@ Options=nodev,ro,x-gdu.hide,x-gvfs-hide `, what, where)) } - for _, comp := range compsNotExist { - compName := fmt.Sprint("comp", comp) - compRev := fmt.Sprintf("%d%d", comp, comp) + for i, comp := range compsNotExist { + compName := comp + compRev := compsNotExistRevs[i] where := filepath.Join(snapRoot, snapMntDir, "pc-kernel/components/mnt", compName, compRev) unit := systemd.EscapeUnitNamePath(where) + ".mount" @@ -8815,7 +9163,7 @@ func (s *initramfsMountsSuite) testInitramfsMountsRunModeWithDriversTreeHappy(c _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) c.Assert(err, IsNil) - checkKernelMounts(c, "/run/mnt/data/system-data", "/writable/system-data", nil, nil) + checkKernelMounts(c, "/run/mnt/data/system-data", "/writable/system-data", nil, nil, nil, nil) } func (s *initramfsClassicMountsSuite) TestInitramfsMountsRunModeWithDriversTreeHappyClassic(c *C) { @@ -8873,7 +9221,7 @@ func (s *initramfsClassicMountsSuite) TestInitramfsMountsRunModeWithDriversTreeH _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) c.Assert(err, IsNil) - checkKernelMounts(c, "/run/mnt/data", "/sysroot", nil, nil) + checkKernelMounts(c, "/run/mnt/data", "/sysroot", nil, nil, nil, nil) } func (s *initramfsMountsSuite) TestInitramfsMountsRunModeWithComponentsHappy(c *C) { @@ -8944,7 +9292,8 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRunModeWithComponentsHappy(c * _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) c.Assert(err, IsNil) - checkKernelMounts(c, "/run/mnt/data/system-data", "/writable/system-data", []int{1, 2, 3}, nil) + checkKernelMounts(c, "/run/mnt/data/system-data", "/writable/system-data", + []string{"comp1", "comp2", "comp3"}, []string{"11", "22", "33"}, nil, nil) } func (s *initramfsMountsSuite) TestInitramfsMountsRunModeWithComponentsBadComps(c *C) { @@ -9022,7 +9371,8 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRunModeWithComponentsBadComps( _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) c.Assert(err, IsNil) - checkKernelMounts(c, "/run/mnt/data/system-data", "/writable/system-data", nil, []int{1, 2, 3, 4}) + checkKernelMounts(c, "/run/mnt/data/system-data", "/writable/system-data", + nil, nil, []string{"comp1", "comp2", "comp3", "comp4"}, []string{"11", "22", "33", "44"}) } func (s *initramfsClassicMountsSuite) TestInitramfsMountsRunModeWithComponentsHappyClassic(c *C) { @@ -9096,5 +9446,6 @@ func (s *initramfsClassicMountsSuite) TestInitramfsMountsRunModeWithComponentsHa _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) c.Assert(err, IsNil) - checkKernelMounts(c, "/run/mnt/data", "/sysroot", []int{1, 2, 3}, nil) + checkKernelMounts(c, "/run/mnt/data", "/sysroot", + []string{"comp1", "comp2", "comp3"}, []string{"11", "22", "33"}, nil, nil) } diff --git a/kernel/kernel_drivers.go b/kernel/kernel_drivers.go index a7a37bb648c7..d3434ae9f992 100644 --- a/kernel/kernel_drivers.go +++ b/kernel/kernel_drivers.go @@ -29,6 +29,7 @@ import ( "strings" "syscall" + "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" @@ -408,3 +409,22 @@ func EnsureKernelDriversTree(kMntPts MountPoints, compsMntPts []ModulesCompMount return nil } + +// NeedsKernelDriversTree returns true if we need a kernel drivers tree for this model. +func NeedsKernelDriversTree(mod *asserts.Model) bool { + // Checking if it has modeenv - it must be UC20+ or hybrid + if mod.Grade() == asserts.ModelGradeUnset { + return false + } + + // We assume core24/hybrid 24.04 onwards have the generator, for older + // boot bases we return false. + // TODO this won't work for a UC2{0,2} -> UC24+ remodel as we need the + // new model here. Get to this ASAP after snapd 2.62 release. + switch mod.Base() { + case "core20", "core22", "core22-desktop": + return false + default: + return true + } +} diff --git a/kernel/kernel_drivers_test.go b/kernel/kernel_drivers_test.go index fc752d7dc090..55d780458863 100644 --- a/kernel/kernel_drivers_test.go +++ b/kernel/kernel_drivers_test.go @@ -25,13 +25,17 @@ import ( "os" "path/filepath" "syscall" + "time" . "gopkg.in/check.v1" + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/asserts/assertstest" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/kernel" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -656,3 +660,57 @@ func (s *kernelDriversTestSuite) TestBuildKernelDriversTreeCompsWithTargetDir(c } doDirChecks(c, fwUpdates, expected) } + +func mockModel16(override map[string]interface{}) *asserts.Model { + model := map[string]interface{}{ + "type": "model", + "authority-id": "my-brand", + "series": "16", + "brand-id": "my-brand", + "model": "my-model", + "gadget": "gadget", + "kernel": "krnl", + "architecture": "amd64", + "timestamp": time.Now().Format(time.RFC3339), + } + return assertstest.FakeAssertion(model, override).(*asserts.Model) +} + +func mockModel20plus(override map[string]interface{}) *asserts.Model { + model := map[string]interface{}{ + "type": "model", + "authority-id": "my-brand", + "series": "16", + "brand-id": "my-brand", + "model": "my-model", + "display-name": "my model", + "architecture": "amd64", + "base": "core20", + "grade": "dangerous", + "snaps": []interface{}{ + map[string]interface{}{ + "name": "pc-kernel", + "id": snaptest.AssertedSnapID("pc-kernel"), + "type": "kernel", + "default-channel": "20", + }, + map[string]interface{}{ + "name": "pc", + "id": snaptest.AssertedSnapID("pc"), + "type": "gadget", + "default-channel": "20", + }}} + for n, v := range override { + model[n] = v + } + return assertstest.FakeAssertion(model, override).(*asserts.Model) +} + +func (s *kernelDriversTestSuite) TestNeedsKernelDriversTree(c *C) { + uc16model := mockModel16(nil) + c.Assert(kernel.NeedsKernelDriversTree(uc16model), Equals, false) + uc20model := mockModel20plus(nil) + c.Assert(kernel.NeedsKernelDriversTree(uc20model), Equals, false) + uc24model := mockModel20plus(map[string]interface{}{"base": "core24"}) + c.Assert(kernel.NeedsKernelDriversTree(uc24model), Equals, true) +} diff --git a/overlord/devicestate/devicemgr.go b/overlord/devicestate/devicemgr.go index f9708cdbdc9d..35c6fe7a9418 100644 --- a/overlord/devicestate/devicemgr.go +++ b/overlord/devicestate/devicemgr.go @@ -2248,15 +2248,10 @@ type systemAndEssentialSnaps struct { *System Seed seed.Seed InfosByType map[snap.Type]*snap.Info - CompsByType map[snap.Type][]compSeedInfo + CompsByType map[snap.Type][]install.ComponentSeedInfo SeedSnapsByType map[snap.Type]*seed.Snap } -type compSeedInfo struct { - CompInfo *snap.ComponentInfo - CompSeed *seed.Component -} - // DefaultRecoverySystem returns the default recovery system, if there is one. // state.ErrNoState is returned if a default recovery system has not been set. func (m *DeviceManager) DefaultRecoverySystem() (*DefaultRecoverySystem, error) { @@ -2310,7 +2305,7 @@ func (m *DeviceManager) loadSystemAndEssentialSnaps(wantedSystemLabel string, ty // like "snapd" will be skipped and not part of the EssentialSnaps list // snapInfos := make(map[snap.Type]*snap.Info) - compInfos := make(map[snap.Type][]compSeedInfo) + compInfos := make(map[snap.Type][]install.ComponentSeedInfo) seedSnaps := make(map[snap.Type]*seed.Snap) for _, seedSnap := range s.EssentialSnaps() { typ := seedSnap.EssentialType @@ -2334,9 +2329,9 @@ func (m *DeviceManager) loadSystemAndEssentialSnaps(wantedSystemLabel string, ty return nil, fmt.Errorf("internal error while retrieving %s for %s mode: %v", seedSnap.SnapName(), modeForComps, err) } - var compInfosForType []compSeedInfo + var compInfosForType []install.ComponentSeedInfo if len(snapForMode.Components) > 0 { - compInfosForType = make([]compSeedInfo, 0, len(snapForMode.Components)) + compInfosForType = make([]install.ComponentSeedInfo, 0, len(snapForMode.Components)) for _, sc := range snapForMode.Components { seedComp := sc compf, err := snapfile.Open(seedComp.Path) @@ -2348,9 +2343,9 @@ func (m *DeviceManager) loadSystemAndEssentialSnaps(wantedSystemLabel string, ty if err != nil { return nil, err } - compInfosForType = append(compInfosForType, compSeedInfo{ - CompInfo: compInfo, - CompSeed: &seedComp, + compInfosForType = append(compInfosForType, install.ComponentSeedInfo{ + Info: compInfo, + Seed: &seedComp, }) } } diff --git a/overlord/devicestate/devicestate_install_api_test.go b/overlord/devicestate/devicestate_install_api_test.go index cf45b5e39a97..27091e0cdec7 100644 --- a/overlord/devicestate/devicestate_install_api_test.go +++ b/overlord/devicestate/devicestate_install_api_test.go @@ -353,7 +353,7 @@ func (s *deviceMgrInstallAPISuite) testInstallFinishStep(c *C, opts finishStepOp var kModsRevs map[string]snap.Revision if opts.hasKernelModsComps { - kModsRevs = map[string]snap.Revision{"kcomp1": snap.R(77), "kcomp2": snap.R(77)} + kModsRevs = map[string]snap.Revision{"kcomp1": snap.R(77), "kcomp2": snap.R(77), "kcomp3": snap.R(77)} } seedOpts := mockSystemSeedWithLabelOpts{ isClassic: opts.installClassic, diff --git a/overlord/devicestate/devicestate_install_mode_test.go b/overlord/devicestate/devicestate_install_mode_test.go index cf7d7c36b87e..95f07847dad6 100644 --- a/overlord/devicestate/devicestate_install_mode_test.go +++ b/overlord/devicestate/devicestate_install_mode_test.go @@ -886,7 +886,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallExpTasksWithKMods(c *C) { isClassic: false, hasSystemSeed: true, hasPartial: false, - kModsRevs: map[string]snap.Revision{"kcomp1": snap.R(7), "kcomp2": snap.R(14)}, + kModsRevs: map[string]snap.Revision{"kcomp1": snap.R(7), "kcomp2": snap.R(14), "kcomp3": snap.R(21)}, types: []snap.Type{snap.TypeKernel}, } s.mockSystemSeedWithLabel(c, "1234", seedCopyFn, seedOpts) @@ -966,7 +966,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallExpTasksWithKModsTestMode(c *C) { hasSystemSeed: true, hasPartial: false, testCompsMode: true, - kModsRevs: map[string]snap.Revision{"kcomp1": snap.R(7), "kcomp2": snap.R(14)}, + kModsRevs: map[string]snap.Revision{"kcomp1": snap.R(7), "kcomp2": snap.R(14), "kcomp3": snap.R(21)}, types: []snap.Type{snap.TypeKernel}, } s.mockSystemSeedWithLabel(c, "1234", seedCopyFn, seedOpts) @@ -2509,7 +2509,8 @@ func (s *deviceMgrInstallModeSuite) testFactoryResetNoEncryptionHappyFull(c *C, } var kModsRevs map[string]snap.Revision if withKMods { - kModsRevs = map[string]snap.Revision{"kcomp1": snap.R(7), "kcomp2": snap.R(14)} + kModsRevs = map[string]snap.Revision{"kcomp1": snap.R(7), + "kcomp2": snap.R(14), "kcomp3": snap.R(21)} } seedOpts := mockSystemSeedWithLabelOpts{ isClassic: false, @@ -2608,7 +2609,8 @@ func (s *deviceMgrInstallModeSuite) testFactoryResetEncryptionHappyFull(c *C, wi } var kModsRevs map[string]snap.Revision if withKMods { - kModsRevs = map[string]snap.Revision{"kcomp1": snap.R(7), "kcomp2": snap.R(14)} + kModsRevs = map[string]snap.Revision{"kcomp1": snap.R(7), "kcomp2": snap.R(14), + "kcomp3": snap.R(21)} } seedOpts := mockSystemSeedWithLabelOpts{ isClassic: false, diff --git a/overlord/devicestate/handlers_install.go b/overlord/devicestate/handlers_install.go index dac831e97033..ff4b25dd6d15 100644 --- a/overlord/devicestate/handlers_install.go +++ b/overlord/devicestate/handlers_install.go @@ -42,6 +42,7 @@ import ( "github.com/snapcore/snapd/gadget" "github.com/snapcore/snapd/gadget/device" "github.com/snapcore/snapd/gadget/install" + "github.com/snapcore/snapd/kernel" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" installLogic "github.com/snapcore/snapd/overlord/install" @@ -285,12 +286,13 @@ func (m *DeviceManager) doSetupRunSystem(t *state.Task, _ *tomb.Tomb) error { kernMntPoint := mntPtForType[snap.TypeKernel] isCore := !deviceCtx.Classic() - kSnapInfo, bootKMods := kModsInfo(systemAndSnaps, kernMntPoint, mntPtForComps, isCore) + kBootInfo := kBootInfo(systemAndSnaps, kernMntPoint, mntPtForComps, isCore) timings.Run(perfTimings, "install-run", "Install the run system", func(tm timings.Measurer) { st.Unlock() defer st.Lock() - installedSystem, err = installRun(model, gadgetDir, kSnapInfo, "", bopts, installObserver, tm) + installedSystem, err = installRun(model, gadgetDir, kBootInfo.KSnapInfo, "", + bopts, installObserver, tm) }) if err != nil { return fmt.Errorf("cannot install system: %v", err) @@ -330,7 +332,7 @@ func (m *DeviceManager) doSetupRunSystem(t *state.Task, _ *tomb.Tomb) error { UnpackedGadgetDir: gadgetDir, RecoverySystemLabel: modeEnv.RecoverySystem, - KernelMods: bootKMods, + KernelMods: kBootInfo.BootableKMods, } timings.Run(perfTimings, "boot-make-runnable", "Make target system runnable", func(timings.Measurer) { err = bootMakeRunnable(deviceCtx.Model(), bootWith, trustedInstallObserver) @@ -566,7 +568,7 @@ func (m *DeviceManager) doFactoryResetRunSystem(t *state.Task, _ *tomb.Tomb) err kernMntPoint := mntPtForType[snap.TypeKernel] isCore := !deviceCtx.Classic() - kSnapInfo, bootKMods := kModsInfo(systemAndSnaps, kernMntPoint, mntPtForComps, isCore) + kBootInfo := kBootInfo(systemAndSnaps, kernMntPoint, mntPtForComps, isCore) // run the create partition code logger.Noticef("create and deploy partitions") @@ -574,7 +576,8 @@ func (m *DeviceManager) doFactoryResetRunSystem(t *state.Task, _ *tomb.Tomb) err timings.Run(perfTimings, "factory-reset", "Factory reset", func(tm timings.Measurer) { st.Unlock() defer st.Lock() - installedSystem, err = installFactoryReset(model, gadgetDir, kSnapInfo, "", bopts, installObserver, tm) + installedSystem, err = installFactoryReset(model, gadgetDir, kBootInfo.KSnapInfo, + "", bopts, installObserver, tm) }) if err != nil { return fmt.Errorf("cannot perform factory reset: %v", err) @@ -659,7 +662,7 @@ func (m *DeviceManager) doFactoryResetRunSystem(t *state.Task, _ *tomb.Tomb) err UnpackedGadgetDir: gadgetDir, RecoverySystemLabel: modeEnv.RecoverySystem, - KernelMods: bootKMods, + KernelMods: kBootInfo.BootableKMods, } timings.Run(perfTimings, "boot-make-runnable", "Make target system runnable", func(timings.Measurer) { err = bootMakeRunnableAfterDataReset(deviceCtx.Model(), bootWith, trustedInstallObserver) @@ -944,57 +947,29 @@ func (m *DeviceManager) loadAndMountSystemLabelSnaps(systemLabel string, essenti mntPtForComp := make(map[string]string) for _, seedComp := range systemAndSnaps.CompsByType[snap.TypeKernel] { - if seedComp.CompInfo.Type != snap.KernelModulesComponent { + if seedComp.Info.Type != snap.KernelModulesComponent { continue } - mntPt, unmountComp, err := mountSeedContainer(seedComp.CompSeed.Path, - seedComp.CompInfo.FullName()) + mntPt, unmountComp, err := mountSeedContainer(seedComp.Seed.Path, + seedComp.Info.FullName()) if err != nil { unmount() return nil, nil, nil, nil, err } unmountFuncs = append(unmountFuncs, unmountComp) - mntPtForComp[seedComp.CompInfo.FullName()] = mntPt + mntPtForComp[seedComp.Info.FullName()] = mntPt } return systemAndSnaps, mntPtForType, mntPtForComp, unmount, nil } -func kModsInfo(systemAndSnaps *systemAndEssentialSnaps, kernMntPoint string, mntPtForComps map[string]string, isCore bool) (*install.KernelSnapInfo, []boot.BootableKModsComponents) { +func kBootInfo(systemAndSnaps *systemAndEssentialSnaps, kernMntPoint string, mntPtForComps map[string]string, isCore bool) installLogic.KernelBootInfo { kernInfo := systemAndSnaps.InfosByType[snap.TypeKernel] - - // Find out kernel-modules components in the seed compSeedInfos := systemAndSnaps.CompsByType[snap.TypeKernel] - bootKMods := make([]boot.BootableKModsComponents, 0, len(compSeedInfos)) - modulesComps := make([]install.KernelModulesComponentInfo, 0, len(compSeedInfos)) - for _, compSeedInfo := range compSeedInfos { - ci := compSeedInfo.CompInfo - if ci.Type == snap.KernelModulesComponent { - cpi := snap.MinimalComponentContainerPlaceInfo(ci.Component.ComponentName, - ci.Revision, kernInfo.SnapName()) - modulesComps = append(modulesComps, install.KernelModulesComponentInfo{ - Name: ci.Component.ComponentName, - Revision: ci.Revision, - MountPoint: mntPtForComps[ci.FullName()], - }) - bootKMods = append(bootKMods, boot.BootableKModsComponents{ - CompPlaceInfo: cpi, - CompPath: compSeedInfo.CompSeed.Path, - }) - } - } - - kSnapInfo := &install.KernelSnapInfo{ - Name: kernInfo.SnapName(), - Revision: kernInfo.Revision, - MountPoint: kernMntPoint, - IsCore: isCore, - ModulesComps: modulesComps, - NeedsDriversTree: snapstate.NeedsKernelSetup(systemAndSnaps.Model), - } - - return kSnapInfo, bootKMods + return installLogic.BuildKernelBootInfo(kernInfo, compSeedInfos, kernMntPoint, + mntPtForComps, installLogic.BuildKernelBootInfoOpts{ + IsCore: isCore, NeedsDriversTree: kernel.NeedsKernelDriversTree(systemAndSnaps.Model)}) } // doInstallFinish performs the finish step of the install. It will @@ -1108,13 +1083,14 @@ func (m *DeviceManager) doInstallFinish(t *state.Task, _ *tomb.Tomb) error { // Find out kernel-modules components in the seed isCore := !deviceCtx.Classic() - kSnapInfo, bootKMods := kModsInfo(systemAndSnaps, kernMntPoint, mntPtForComps, isCore) + kBootInfo := kBootInfo(systemAndSnaps, kernMntPoint, mntPtForComps, isCore) logger.Debugf("writing content to partitions") timings.Run(perfTimings, "install-content", "Writing content to partitions", func(tm timings.Measurer) { st.Unlock() defer st.Lock() - _, err = installWriteContent(mergedVols, allLaidOutVols, encryptSetupData, kSnapInfo, installObserver, perfTimings) + _, err = installWriteContent(mergedVols, allLaidOutVols, encryptSetupData, + kBootInfo.KSnapInfo, installObserver, perfTimings) }) if err != nil { return fmt.Errorf("cannot write content: %v", err) @@ -1184,7 +1160,7 @@ func (m *DeviceManager) doInstallFinish(t *state.Task, _ *tomb.Tomb) error { UnpackedGadgetDir: mntPtForType[snap.TypeGadget], RecoverySystemLabel: systemLabel, - KernelMods: bootKMods, + KernelMods: kBootInfo.BootableKMods, } // installs in system-seed{,-null} partition: grub.cfg, grubenv diff --git a/overlord/install/install.go b/overlord/install/install.go index f1629648bdc7..e5053cdce3a3 100644 --- a/overlord/install/install.go +++ b/overlord/install/install.go @@ -38,6 +38,7 @@ import ( "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/gadget" "github.com/snapcore/snapd/gadget/device" + gadgetInstall "github.com/snapcore/snapd/gadget/install" "github.com/snapcore/snapd/kernel/fde" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" @@ -81,6 +82,19 @@ type EncryptionSupportInfo struct { UnavailableWarning string } +// ComponentSeedInfo contains information for a component from the seed and +// from its metadata. +type ComponentSeedInfo struct { + Info *snap.ComponentInfo + Seed *seed.Component +} + +// KernelBootInfo contains information related to the kernel used on installation. +type KernelBootInfo struct { + KSnapInfo *gadgetInstall.KernelSnapInfo + BootableKMods []boot.BootableKModsComponents +} + var ( timeNow = time.Now @@ -88,6 +102,50 @@ var ( sysconfigConfigureTargetSystem = sysconfig.ConfigureTargetSystem ) +// BuildKernelBootInfoOpts contains options for BuildKernelBootInfo. +type BuildKernelBootInfoOpts struct { + // IsCore is true for UC, and false for hybrid systems + IsCore bool + // NeedsDriversTree is true if we need a drivers tree (UC/hybrid 24+) + NeedsDriversTree bool +} + +// BuildKernelBootInfo constructs a KernelBootInfo. +func BuildKernelBootInfo(kernInfo *snap.Info, compSeedInfos []ComponentSeedInfo, kernMntPoint string, mntPtForComps map[string]string, opts BuildKernelBootInfoOpts) KernelBootInfo { + bootKMods := make([]boot.BootableKModsComponents, 0, len(compSeedInfos)) + modulesComps := make([]gadgetInstall.KernelModulesComponentInfo, 0, len(compSeedInfos)) + for _, compSeedInfo := range compSeedInfos { + ci := compSeedInfo.Info + if ci.Type == snap.KernelModulesComponent { + cpi := snap.MinimalComponentContainerPlaceInfo(ci.Component.ComponentName, + ci.Revision, kernInfo.SnapName()) + modulesComps = append(modulesComps, gadgetInstall.KernelModulesComponentInfo{ + Name: ci.Component.ComponentName, + Revision: ci.Revision, + MountPoint: mntPtForComps[ci.FullName()], + }) + bootKMods = append(bootKMods, boot.BootableKModsComponents{ + CompPlaceInfo: cpi, + CompPath: compSeedInfo.Seed.Path, + }) + } + } + + kSnapInfo := &gadgetInstall.KernelSnapInfo{ + Name: kernInfo.SnapName(), + Revision: kernInfo.Revision, + MountPoint: kernMntPoint, + IsCore: opts.IsCore, + ModulesComps: modulesComps, + NeedsDriversTree: opts.NeedsDriversTree, + } + + return KernelBootInfo{ + KSnapInfo: kSnapInfo, + BootableKMods: bootKMods, + } +} + // MockSecbootCheckTPMKeySealingSupported mocks secboot.CheckTPMKeySealingSupported usage by the package for testing. func MockSecbootCheckTPMKeySealingSupported(f func(tpmMode secboot.TPMProvisionMode) error) (restore func()) { old := secbootCheckTPMKeySealingSupported diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index dc596b256cb5..ba96c63c1fa2 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -42,6 +42,7 @@ import ( "github.com/snapcore/snapd/features" "github.com/snapcore/snapd/gadget" "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/kernel" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/overlord/auth" @@ -573,7 +574,7 @@ func doInstall(st *state.State, snapst *SnapState, snapsup SnapSetup, compsups [ } // This task is necessary only for UC24+ and hybrid 24.04+ - if snapsup.Type == snap.TypeKernel && NeedsKernelSetup(deviceCtx.Model()) { + if snapsup.Type == snap.TypeKernel && kernel.NeedsKernelDriversTree(deviceCtx.Model()) { setupKernel := st.NewTask("prepare-kernel-snap", fmt.Sprintf(i18n.G("Prepare kernel driver tree for %q%s"), snapsup.InstanceName(), revisionStr)) addTask(setupKernel) } @@ -641,6 +642,25 @@ func doInstall(st *state.State, snapst *SnapState, snapsup SnapSetup, compsups [ setupAliases := st.NewTask("setup-aliases", fmt.Sprintf(i18n.G("Setup snap %q aliases"), snapsup.InstanceName())) addTask(setupAliases) + var setupKmodComponentsPreseed *state.Task + if snapdenv.Preseeding() && requiresKmodSetup(snapst, compsups) { + // We need this task as the other + // prepare-kernel-modules-components defined below will not be + // run when creating a preseeding tarball, but we still need to + // have a correct driver tree in the tarball. This implies that + // if some kernel module is created by the install hook, it + // will be available only after full installation on first + // boot, but static modules in the components where be + // available early. + logger.Noticef("kernel-modules components present, creating preseed task for them") + // TODO move the setupKernel task here and make it configure + // kernel-modules components too so we can remove this task. + setupKmodComponentsPreseed = st.NewTask("prepare-kernel-modules-components", + fmt.Sprintf(i18n.G("Prepare kernel-modules components for %q%s"), + snapsup.InstanceName(), revisionStr)) + addTask(setupKmodComponentsPreseed) + } + if snapsup.Flags.Prefer { prefer := st.NewTask("prefer-aliases", fmt.Sprintf(i18n.G("Prefer aliases for snap %q"), snapsup.InstanceName())) addTask(prefer) @@ -685,7 +705,7 @@ func doInstall(st *state.State, snapst *SnapState, snapsup SnapSetup, compsups [ addTask(setupKmodComponents) } - if snapsup.Type == snap.TypeKernel && NeedsKernelSetup(deviceCtx.Model()) { + if snapsup.Type == snap.TypeKernel && kernel.NeedsKernelDriversTree(deviceCtx.Model()) { // This task needs to run after we're back and running the new // kernel after a reboot was requested in link-snap handler. discardOldKernelSetup := st.NewTask("discard-old-kernel-snap-setup", @@ -788,7 +808,12 @@ func doInstall(st *state.State, snapst *SnapState, snapsup SnapSetup, compsups [ installSet := state.NewTaskSet(tasks...) installSet.MarkEdge(prereq, BeginEdge) installSet.MarkEdge(prepare, SnapSetupEdge) - installSet.MarkEdge(setupAliases, BeforeHooksEdge) + // BeforeHooksEdge is used by preseeding to know up to which task to run + beforeHooksEdgeTask := setupAliases + if setupKmodComponentsPreseed != nil { + beforeHooksEdgeTask = setupKmodComponentsPreseed + } + installSet.MarkEdge(beforeHooksEdgeTask, BeforeHooksEdge) // Let tasks know if they have to do something about restarts if setupKmodComponents == nil { @@ -905,24 +930,6 @@ func splitComponentTasksForInstall( return newMultiComponentInstallTaskSet(componentTSS...), nil } -func NeedsKernelSetup(model *asserts.Model) bool { - // Checking if it has modeenv - it must be UC20+ or hybrid - if model.Grade() == asserts.ModelGradeUnset { - return false - } - - // We assume core24/hybrid 24.04 onwards have the generator, for older - // boot bases we return false. - // TODO this won't work for a UC2{0,2} -> UC24+ remodel as we need the - // new model here. Get to this ASAP after snapd 2.62 release. - switch model.Base() { - case "core20", "core22", "core22-desktop": - return false - default: - return true - } -} - func findTasksMatchingKindAndSnap(st *state.State, kind string, snapName string, revision snap.Revision) ([]*state.Task, error) { var tasks []*state.Task for _, t := range st.Tasks() { @@ -3409,7 +3416,7 @@ func LinkNewBaseOrKernel(st *state.State, name string, fromChange string) (*stat if err != nil { return nil, err } - if NeedsKernelSetup(deviceCtx.Model()) { + if kernel.NeedsKernelDriversTree(deviceCtx.Model()) { setupKernel := st.NewTask("prepare-kernel-snap", fmt.Sprintf(i18n.G("Prepare kernel driver tree for %q (%s) for remodel"), snapsup.InstanceName(), snapst.Current)) ts.AddTask(setupKernel) setupKernel.Set("snap-setup-task", prepareSnap.ID()) @@ -3469,7 +3476,7 @@ func AddLinkNewBaseOrKernel(st *state.State, ts *state.TaskSet) (*state.TaskSet, if err != nil { return nil, err } - if NeedsKernelSetup(deviceCtx.Model()) { + if kernel.NeedsKernelDriversTree(deviceCtx.Model()) { setupKernel := st.NewTask("prepare-kernel-snap", fmt.Sprintf(i18n.G("Prepare kernel driver tree for %q (%s) for remodel"), snapsup.InstanceName(), snapsup.Revision())) setupKernel.Set("snap-setup-task", snapSetupTask.ID()) setupKernel.WaitFor(prev) diff --git a/overlord/snapstate/snapstate_install_test.go b/overlord/snapstate/snapstate_install_test.go index f93f089b4caf..e18d13a614a5 100644 --- a/overlord/snapstate/snapstate_install_test.go +++ b/overlord/snapstate/snapstate_install_test.go @@ -47,6 +47,7 @@ import ( "github.com/snapcore/snapd/overlord/assertstate" "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/overlord/restart" + "github.com/snapcore/snapd/snapdenv" "github.com/snapcore/snapd/store/storetest" // So it registers Configure. @@ -373,6 +374,8 @@ func (s *snapmgrTestSuite) TestInstallTaskEdgesForPreseeding(c *C) { mockSnap := makeTestSnap(c, `name: some-snap version: 1.0 `) + restorePreseeding := snapdenv.MockPreseeding(true) + defer restorePreseeding() for _, skipConfig := range []bool{false, true} { ts, _, err := snapstate.InstallPath(s.state, &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(8)}, mockSnap, "", "", snapstate.Flags{SkipConfigure: skipConfig}, nil) @@ -6455,6 +6458,15 @@ func (s *snapmgrTestSuite) TestInstallManyComponentsRunThrough(c *C) { }) } +func (s *snapmgrTestSuite) TestInstallManyComponentsRunThroughPreseed(c *C) { + s.testInstallComponentsRunThrough(c, testInstallComponentsRunThroughOpts{ + snapName: "some-kernel", + snapType: snap.TypeKernel, + components: []string{"standard-component", "kernel-modules-component"}, + preseed: true, + }) +} + func (s *snapmgrTestSuite) TestInstallManyComponentsUndoRunThrough(c *C) { s.testInstallComponentsRunThrough(c, testInstallComponentsRunThroughOpts{ snapName: "some-kernel", @@ -6646,12 +6658,20 @@ type testInstallComponentsRunThroughOpts struct { snapType snap.Type components []string undo bool + preseed bool } func (s *snapmgrTestSuite) testInstallComponentsRunThrough(c *C, opts testInstallComponentsRunThroughOpts) { s.state.Lock() defer s.state.Unlock() + if opts.preseed { + restorePreseeding := snapdenv.MockPreseeding(opts.preseed) + defer restorePreseeding() + mockCmd := testutil.MockCommand(c, "mount", "") + defer mockCmd.Restore() + } + r := snapstatetest.MockDeviceModel(MakeModel20("pc", map[string]interface{}{"base": "core24"})) defer r() @@ -6913,6 +6933,13 @@ func (s *snapmgrTestSuite) testInstallComponentsRunThrough(c *C, opts testInstal }}...) if len(kmodComps) > 0 { + if opts.preseed { + expected = append(expected, fakeOp{ + op: "prepare-kernel-modules-components", + currentComps: []*snap.ComponentSideInfo{}, + finalComps: kmodComps, + }) + } expected = append(expected, fakeOp{ op: "prepare-kernel-modules-components", currentComps: []*snap.ComponentSideInfo{}, diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go index f3a544bca54d..bb6f3100d5a3 100644 --- a/overlord/snapstate/snapstate_test.go +++ b/overlord/snapstate/snapstate_test.go @@ -135,6 +135,10 @@ func (s *snapmgrBaseTest) mockSystemctlCallsUpdateMounts(c *C) (restore func()) if len(args) == 3 && args[0] == "--no-reload" && args[1] == "enable" { return []byte(""), nil } + if len(args) == 4 && args[0] == "--root" && args[2] == "enable" { + // This command is run on preseeding + return []byte(""), nil + } if len(args) == 2 && args[0] == "restart" { value, ok := s.restarts[args[1]] if ok { diff --git a/seed/seedtest/sample.go b/seed/seedtest/sample.go index 44e0911fb2ff..82d6be2f70e0 100644 --- a/seed/seedtest/sample.go +++ b/seed/seedtest/sample.go @@ -212,6 +212,8 @@ components: type: kernel-modules kcomp2: type: kernel-modules + kcomp3: + type: kernel-modules `, "pc-kernel+kcomp1": `component: pc-kernel+kcomp1 type: kernel-modules @@ -220,6 +222,10 @@ version: 1.0 "pc-kernel+kcomp2": `component: pc-kernel+kcomp2 type: kernel-modules version: 1.0 +`, + "pc-kernel+kcomp3": `component: pc-kernel+kcomp3 +type: kernel-modules +version: 1.0 `, "pc=22": `name: pc type: gadget diff --git a/spread.yaml b/spread.yaml index 8f2da5ba95a1..5b2cdbb854fa 100644 --- a/spread.yaml +++ b/spread.yaml @@ -110,6 +110,7 @@ environment: NESTED_REPACK_GADGET_SNAP: '$(HOST: echo "${NESTED_REPACK_GADGET_SNAP:-true}")' NESTED_REPACK_BASE_SNAP: '$(HOST: echo "${NESTED_REPACK_BASE_SNAP:-true}")' NESTED_FORCE_MS_KEYS: '$(HOST: echo "${NESTED_FORCE_MS_KEYS:-false}")' + NESTED_KERNEL_MODULES_COMP: '$(HOST: echo "${NESTED_KERNEL_MODULES_COMP:-}")' # Whether we should use snapd snap ./built-snap/ directory USE_PREBUILT_SNAPD_SNAP: '$(HOST: echo "${SPREAD_USE_PREBUILT_SNAPD_SNAP:-false}")' diff --git a/tests/lib/nested.sh b/tests/lib/nested.sh index 52d461a51380..079ce0f86829 100755 --- a/tests/lib/nested.sh +++ b/tests/lib/nested.sh @@ -930,6 +930,9 @@ nested_create_core_vm() { for mycomp in $(nested_get_extra_comps); do EXTRA_SNAPS="$EXTRA_SNAPS --comp $mycomp" done + if [ -n "$NESTED_KERNEL_MODULES_COMP" ] && [ "$(nested_get_version)" -ge "24" ]; then + EXTRA_SNAPS="$EXTRA_SNAPS --comp pc-kernel+${NESTED_KERNEL_MODULES_COMP}.comp" + fi # only set SNAPPY_FORCE_SAS_URL because we don't need it defined # anywhere else but here, where snap prepare-image as called by diff --git a/tests/lib/prepare.sh b/tests/lib/prepare.sh index 4b58e299d72e..3e3fee9ed120 100755 --- a/tests/lib/prepare.sh +++ b/tests/lib/prepare.sh @@ -942,6 +942,40 @@ uc20_build_initramfs_kernel_snap() { rm -rf repacked-kernel } + +# Modify kernel and create a component, kernel content expected in pc-kernel +move_module_to_component() { + mod_name=$1 + comp_name=$2 + + kern_ver=$(find pc-kernel/modules/* -maxdepth 0 -printf "%f\n") + comp_ko_dir=$comp_name/modules/"$kern_ver"/kmod/ + mkdir -p "$comp_ko_dir" + mkdir -p "$comp_name"/meta/ + cat << EOF > "$comp_name"/meta/component.yaml +component: pc-kernel+$comp_name +type: kernel-modules +version: 1.0 +summary: kernel component +description: kernel component for testing purposes +EOF + # Replace _ or - with [_-], as it can be any of these + glob_mod_name=$(printf '%s' "$mod_name" | sed -r 's/[-_]/[-_]/g') + module_path=$(find pc-kernel -name "${glob_mod_name}.ko*") + cp "$module_path" "$comp_ko_dir" + snap pack --filename=pc-kernel+"$comp_name".comp "$comp_name" + + # remove the kernel module from the kernel snap + rm "$module_path" + # depmod wants a lib subdir, fake it and remove after invocation + mkdir pc-kernel/lib + ln -s ../modules pc-kernel/lib/modules + depmod -b pc-kernel/ "$kern_ver" + rm -rf pc-kernel/lib + # append component meta-information + printf 'components:\n %s:\n type: kernel-modules\n' "$comp_name" >> pc-kernel/meta/snap.yaml +} + uc24_build_initramfs_kernel_snap() { local ORIG_SNAP="$1" local TARGET="$2" @@ -992,6 +1026,11 @@ uc24_build_initramfs_kernel_snap() { cp -a ./extra-kernel-snap/* ./pc-kernel fi + if [ -n "$NESTED_KERNEL_MODULES_COMP" ] && is_test_target_core_ge 24; then + # "split" kernel in kernel-modules component and kernel + move_module_to_component "$NESTED_COMP_KERNEL_MODULE_NAME" "$NESTED_KERNEL_MODULES_COMP" + fi + snap pack pc-kernel if [ "$(pwd)" != "$TARGET" ]; then mv pc-kernel_*.snap "$TARGET" diff --git a/tests/nested/manual/uc20-install-in-initrd/task.yaml b/tests/nested/manual/uc20-install-in-initrd/task.yaml index c5de317a4208..d2d77430daed 100644 --- a/tests/nested/manual/uc20-install-in-initrd/task.yaml +++ b/tests/nested/manual/uc20-install-in-initrd/task.yaml @@ -36,6 +36,10 @@ environment: NESTED_UBUNTU_IMAGE_SNAPPY_FORCE_SAS_URL: http://localhost:11028 NESTED_SNAPD_DEBUG_TO_SERIAL: true + # create a kernel-modules component for efi-pstore + NESTED_KERNEL_MODULES_COMP: efi-pstore + NESTED_COMP_KERNEL_MODULE_NAME: efi-pstore + prepare: | "$TESTSTOOLS"/store-state setup-fake-store "$NESTED_FAKESTORE_BLOB_DIR" cp "$TESTSLIB"/assertions/developer1.account "$NESTED_FAKESTORE_BLOB_DIR"/asserts @@ -70,11 +74,44 @@ restore: | rm -rf ~/.snap/gnupg execute: | + #shellcheck source=tests/lib/nested.sh + . "$TESTSLIB"/nested.sh + remote.exec "cat /proc/cmdline" | MATCH "snapd_recovery_mode=install" remote.exec "cat /var/lib/snapd/modeenv" > modeenv MATCH "mode=run" is used instead of # just "ubuntu-data". We need to figure out if this is OK.