diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts.go b/cmd/snap-bootstrap/cmd_initramfs_mounts.go index 6fcd1b04b3a..fae026e0b11 100644 --- a/cmd/snap-bootstrap/cmd_initramfs_mounts.go +++ b/cmd/snap-bootstrap/cmd_initramfs_mounts.go @@ -329,7 +329,21 @@ func doInstall(mst *initramfsMountsState, model *asserts.Model, sysSnaps map[sna } bootDevice := "" - installedSystem, err := gadgetInstallRun(model, gadgetMountDir, kernelMountDir, bootDevice, options, installObserver, timings.New(nil)) + 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 + } + + installedSystem, err := gadgetInstallRun(model, gadgetMountDir, kernelSnapInfo, bootDevice, options, installObserver, timings.New(nil)) if 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 8ddac8566d4..ba1897444ce 100644 --- a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go +++ b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go @@ -8150,7 +8150,7 @@ echo '{"features":[]}' saveKey := keys.EncryptionKey{'s', 'a', 'v', 'e', 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} gadgetInstallCalled := false - restoreGadgetInstall := main.MockGadgetInstallRun(func(model gadget.Model, gadgetRoot, kernelRoot, bootDevice string, options gadgetInstall.Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*gadgetInstall.InstalledSystemSideData, error) { + 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, "cryptsetup") @@ -8158,7 +8158,7 @@ echo '{"features":[]}' c.Assert(model.Classic(), Equals, false) c.Assert(string(model.Grade()), Equals, "signed") c.Assert(gadgetRoot, Equals, filepath.Join(boot.InitramfsRunMntDir, "gadget")) - c.Assert(kernelRoot, Equals, filepath.Join(boot.InitramfsRunMntDir, "kernel")) + c.Assert(kernelSnapInfo.MountPoint, Equals, filepath.Join(boot.InitramfsRunMntDir, "kernel")) keyForRole := map[string]keys.EncryptionKey{ gadget.SystemData: dataKey, @@ -8284,7 +8284,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsInstallAndRunFdeSetupNotPresen writeGadget(c, "ubuntu-seed", "system-seed", "") gadgetInstallCalled := false - restoreGadgetInstall := main.MockGadgetInstallRun(func(model gadget.Model, gadgetRoot, kernelRoot, bootDevice string, options gadgetInstall.Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*gadgetInstall.InstalledSystemSideData, error) { + 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, "") @@ -8292,7 +8292,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsInstallAndRunFdeSetupNotPresen c.Assert(model.Classic(), Equals, false) c.Assert(string(model.Grade()), Equals, "signed") c.Assert(gadgetRoot, Equals, filepath.Join(boot.InitramfsRunMntDir, "gadget")) - c.Assert(kernelRoot, Equals, filepath.Join(boot.InitramfsRunMntDir, "kernel")) + c.Assert(kernelSnapInfo.MountPoint, Equals, filepath.Join(boot.InitramfsRunMntDir, "kernel")) return &gadgetInstall.InstalledSystemSideData{}, nil }) defer restoreGadgetInstall() diff --git a/cmd/snap-bootstrap/export_test.go b/cmd/snap-bootstrap/export_test.go index f1f369adced..f67b9511fbf 100644 --- a/cmd/snap-bootstrap/export_test.go +++ b/cmd/snap-bootstrap/export_test.go @@ -202,7 +202,7 @@ func MockWaitFile(f func(string, time.Duration, int) error) (restore func()) { var WaitFile = waitFile -func MockGadgetInstallRun(f func(model gadget.Model, gadgetRoot, kernelRoot, bootDevice string, options gadgetInstall.Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*gadgetInstall.InstalledSystemSideData, error)) (restore func()) { +func MockGadgetInstallRun(f func(model gadget.Model, gadgetRoot string, kernelSnapInfo *gadgetInstall.KernelSnapInfo, bootDevice string, options gadgetInstall.Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*gadgetInstall.InstalledSystemSideData, error)) (restore func()) { old := gadgetInstallRun gadgetInstallRun = f return func() { diff --git a/dirs/dirs.go b/dirs/dirs.go index f2209c9d7b4..94cfc9c52d5 100644 --- a/dirs/dirs.go +++ b/dirs/dirs.go @@ -414,6 +414,12 @@ func SnapRepairConfigFileUnder(rootdir string) string { return filepath.Join(rootdir, snappyDir, "repair.json") } +// SnapKernelTreesDirUnder returns the path to the snap kernel drivers trees +// dir under rootdir. +func SnapKernelDriversTreesDirUnder(rootdir string) string { + return filepath.Join(rootdir, snappyDir, "kernel") +} + // AddRootDirCallback registers a callback for whenever the global root // directory (set by SetRootDir) is changed to enable updates to variables in // other packages that depend on its location. diff --git a/gadget/install/content.go b/gadget/install/content.go index 44a942aec19..c4d925be485 100644 --- a/gadget/install/content.go +++ b/gadget/install/content.go @@ -29,11 +29,30 @@ import ( "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/gadget" "github.com/snapcore/snapd/gadget/quantity" + "github.com/snapcore/snapd/kernel" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil/mkfs" + "github.com/snapcore/snapd/snap" ) -var mkfsImpl = mkfs.Make +var ( + mkfsImpl = mkfs.Make + kernelEnsureKernelDriversTree = kernel.EnsureKernelDriversTree +) + +// KernelSnapInfo includes information from the kernel snap that is +// needed to build a drivers tree. Defin +type KernelSnapInfo struct { + Name string + Revision snap.Revision + // MountPoint is the root of the files from the kernel snap + MountPoint string + // NeedsDriversTree will be set if a drivers tree needs to be + // build on installation + NeedsDriversTree bool + // IsCore is set if this is UC + IsCore bool +} type mkfsParams struct { Type string @@ -83,7 +102,7 @@ func unmountWithFallbackToLazy(mntPt, operationMsg string) error { // writeContent populates the given on-disk filesystem structure with a // corresponding filesystem device, according to the contents defined in the // gadget. -func writeFilesystemContent(laidOut *gadget.LaidOutStructure, fsDevice string, observer gadget.ContentObserver) (err error) { +func writeFilesystemContent(laidOut *gadget.LaidOutStructure, kSnapInfo *KernelSnapInfo, fsDevice string, observer gadget.ContentObserver) (err error) { mountpoint := filepath.Join(dirs.SnapRunDir, "gadget-install", strings.ReplaceAll(strings.Trim(fsDevice, "/"), "/", "-")) if err := os.MkdirAll(mountpoint, 0755); err != nil { return err @@ -110,5 +129,21 @@ func writeFilesystemContent(laidOut *gadget.LaidOutStructure, fsDevice string, o return fmt.Errorf("cannot create filesystem image: %v", err) } + // For data partition, build drivers tree if required, so kernel + // drivers are available on first boot of the installed system. + if laidOut.Role() == gadget.SystemData && kSnapInfo != nil && kSnapInfo.NeedsDriversTree { + destRoot := mountpoint + if kSnapInfo.IsCore { + destRoot = filepath.Join(mountpoint, "system-data") + } + destDir := kernel.DriversTreeDir(destRoot, kSnapInfo.Name, kSnapInfo.Revision) + logger.Noticef("building drivers tree in %s", destDir) + + if err := kernelEnsureKernelDriversTree(kSnapInfo.MountPoint, destDir, nil, + &kernel.KernelDriversTreeOptions{KernelInstall: true}); err != nil { + return err + } + } + return nil } diff --git a/gadget/install/content_test.go b/gadget/install/content_test.go index 666eaef0d80..c27c6c34c39 100644 --- a/gadget/install/content_test.go +++ b/gadget/install/content_test.go @@ -34,7 +34,9 @@ import ( "github.com/snapcore/snapd/gadget/gadgettest" "github.com/snapcore/snapd/gadget/install" "github.com/snapcore/snapd/gadget/quantity" + "github.com/snapcore/snapd/kernel" "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/testutil" ) @@ -88,6 +90,7 @@ func mockOnDiskStructureSystemSeed(gadgetRoot string) *gadget.LaidOutStructure { return &gadget.LaidOutStructure{ VolumeStructure: &gadget.VolumeStructure{ Filesystem: "vfat", + Role: gadget.SystemSeed, Content: []gadget.VolumeContent{ { UnresolvedSource: "grubx64.efi", @@ -108,6 +111,16 @@ func mockOnDiskStructureSystemSeed(gadgetRoot string) *gadget.LaidOutStructure { } } +func mockOnDiskStructureSystemData() *gadget.LaidOutStructure { + return &gadget.LaidOutStructure{ + VolumeStructure: &gadget.VolumeStructure{ + Filesystem: "ext4", + Role: gadget.SystemData, + YamlIndex: 1000, // to demonstrate we do not use the laid out index + }, + } +} + const gadgetContent = `volumes: pc: bootloader: grub @@ -211,7 +224,7 @@ func (s *contentTestSuite) TestWriteFilesystemContent(c *C) { observeErr: tc.observeErr, expectedRole: m.Role(), } - err := install.WriteFilesystemContent(m, "/dev/node2", obs) + err := install.WriteFilesystemContent(m, nil, "/dev/node2", obs) if tc.err == "" { c.Assert(err, IsNil) } else { @@ -235,6 +248,76 @@ func (s *contentTestSuite) TestWriteFilesystemContent(c *C) { } } +func (s *contentTestSuite) testWriteFilesystemContentDriversTree(c *C, isCore bool) { + defer dirs.SetRootDir(dirs.GlobalRootDir) + dirs.SetRootDir(c.MkDir()) + + dataMntPoint := filepath.Join(dirs.SnapRunDir, "gadget-install/dev-node2") + restore := install.MockSysMount(func(source, target, fstype string, flags uintptr, data string) error { + c.Check(source, Equals, "/dev/node2") + c.Check(fstype, Equals, "ext4") + c.Check(target, Equals, filepath.Join(dirs.SnapRunDir, "gadget-install/dev-node2")) + return nil + }) + defer restore() + + restore = install.MockSysUnmount(func(target string, flags int) error { + return nil + }) + defer restore() + + // copy existing mock + m := mockOnDiskStructureSystemData() + obs := &mockWriteObserver{ + c: c, + observeErr: nil, + expectedRole: m.Role(), + } + // mock drivers tree + treesDir := dirs.SnapKernelDriversTreesDirUnder(dirs.GlobalRootDir) + modsSubDir := "pc-kernel/111/lib/modules/6.8.0-31-generic" + modsDir := filepath.Join(treesDir, modsSubDir) + c.Assert(os.MkdirAll(modsDir, 0755), IsNil) + someFile := filepath.Join(modsDir, "modules.alias") + c.Assert(os.WriteFile(someFile, []byte("blah"), 0644), IsNil) + + kMntPoint := filepath.Join(dirs.GlobalRootDir, "snap/pc-kernel/111") + kInfo := &install.KernelSnapInfo{ + Name: "pc-kernel", + Revision: snap.R(111), + MountPoint: kMntPoint, + NeedsDriversTree: true, + IsCore: isCore, + } + + restore = install.MockKernelEnsureKernelDriversTree(func(kSnapRoot, destDir string, kmodsConts []snap.ContainerPlaceInfo, opts *kernel.KernelDriversTreeOptions) (err error) { + c.Check(kSnapRoot, Equals, kMntPoint) + if isCore { + c.Check(destDir, Equals, filepath.Join(dataMntPoint, + "system-data/var/lib/snapd/kernel/pc-kernel/111")) + } else { + c.Check(destDir, Equals, filepath.Join(dataMntPoint, + "var/lib/snapd/kernel/pc-kernel/111")) + } + return nil + }) + defer restore() + + err := install.WriteFilesystemContent(m, kInfo, "/dev/node2", obs) + c.Assert(err, IsNil) + +} + +func (s *contentTestSuite) TestWriteFilesystemContentDriversTreeCore(c *C) { + isCore := true + s.testWriteFilesystemContentDriversTree(c, isCore) +} + +func (s *contentTestSuite) TestWriteFilesystemContentDriversTreeHybrid(c *C) { + isCore := false + s.testWriteFilesystemContentDriversTree(c, isCore) +} + func (s *contentTestSuite) TestWriteFilesystemContentUnmountErrHandling(c *C) { dirs.SetRootDir(c.MkDir()) defer dirs.SetRootDir(dirs.GlobalRootDir) @@ -295,7 +378,7 @@ func (s *contentTestSuite) TestWriteFilesystemContentUnmountErrHandling(c *C) { }) defer restore() - err := install.WriteFilesystemContent(m, "/dev/node2", obs) + err := install.WriteFilesystemContent(m, nil, "/dev/node2", obs) if tc.expectedErr == "" { c.Assert(err, IsNil) } else { diff --git a/gadget/install/export_test.go b/gadget/install/export_test.go index 7e00da2b841..7e3e5df2556 100644 --- a/gadget/install/export_test.go +++ b/gadget/install/export_test.go @@ -25,6 +25,8 @@ import ( "github.com/snapcore/snapd/gadget" "github.com/snapcore/snapd/gadget/quantity" + "github.com/snapcore/snapd/kernel" + "github.com/snapcore/snapd/snap" ) type MkfsParams = mkfsParams @@ -74,6 +76,14 @@ func MockMkfsMake(f func(typ, img, label string, devSize, sectorSize quantity.Si } } +func MockKernelEnsureKernelDriversTree(f func(kSnapRoot, destDir string, kmodsConts []snap.ContainerPlaceInfo, opts *kernel.KernelDriversTreeOptions) (err error)) (restore func()) { + old := kernelEnsureKernelDriversTree + kernelEnsureKernelDriversTree = f + return func() { + kernelEnsureKernelDriversTree = old + } +} + func CheckEncryptionSetupData(encryptSetup *EncryptionSetupData, labelToEncDevice map[string]string) error { for label, part := range encryptSetup.parts { switch part.role { diff --git a/gadget/install/install.go b/gadget/install/install.go index c0bf59a068b..4603162ce25 100644 --- a/gadget/install/install.go +++ b/gadget/install/install.go @@ -175,12 +175,12 @@ func createFilesystem(part *gadget.OnDiskStructure, fsParams *mkfsParams, partDi } // TODO probably we won't need to pass partDisp when we include storage in laidOut -func writePartitionContent(laidOut *gadget.LaidOutStructure, fsDevice string, observer gadget.ContentObserver, partDisp string, perfTimings timings.Measurer) error { +func writePartitionContent(laidOut *gadget.LaidOutStructure, kSnapInfo *KernelSnapInfo, fsDevice string, observer gadget.ContentObserver, partDisp string, perfTimings timings.Measurer) error { var err error timings.Run(perfTimings, fmt.Sprintf("write-content[%s]", partDisp), fmt.Sprintf("Write content for %s", partDisp), func(timings.Measurer) { - err = writeFilesystemContent(laidOut, fsDevice, observer) + err = writeFilesystemContent(laidOut, kSnapInfo, fsDevice, observer) }) if err != nil { return err @@ -188,7 +188,11 @@ func writePartitionContent(laidOut *gadget.LaidOutStructure, fsDevice string, ob return nil } -func installOnePartition(dgpair *gadget.OnDiskAndGadgetStructurePair, kernelInfo *kernel.Info, gadgetRoot, kernelRoot string, encryptionType secboot.EncryptionType, sectorSize quantity.Size, observer gadget.ContentObserver, perfTimings timings.Measurer) (fsDevice string, encryptionKey keys.EncryptionKey, err error) { +func installOnePartition(dgpair *gadget.OnDiskAndGadgetStructurePair, + kernelInfo *kernel.Info, kernelSnapInfo *KernelSnapInfo, gadgetRoot string, + encryptionType secboot.EncryptionType, sectorSize quantity.Size, + observer gadget.ContentObserver, perfTimings timings.Measurer, +) (fsDevice string, encryptionKey keys.EncryptionKey, err error) { // 1. Encrypt diskPart := dgpair.DiskStructure vs := dgpair.GadgetStructure @@ -207,14 +211,14 @@ func installOnePartition(dgpair *gadget.OnDiskAndGadgetStructurePair, kernelInfo // 3. Write content opts := &gadget.LayoutOptions{ GadgetRootDir: gadgetRoot, - KernelRootDir: kernelRoot, + KernelRootDir: kernelSnapInfo.MountPoint, EncType: encryptionType, } los, err := gadget.LayoutVolumeStructure(dgpair, kernelInfo, opts) if err != nil { return "", nil, err } - if err := writePartitionContent(los, fsDevice, observer, role, perfTimings); err != nil { + if err := writePartitionContent(los, kernelSnapInfo, fsDevice, observer, role, perfTimings); err != nil { return "", nil, err } @@ -322,7 +326,7 @@ func onDiskStructsSortedIdx(vss map[int]*gadget.OnDiskStructure) []int { // Run creates partitions, encrypts them when expected, creates // filesystems, and finally writes content on them. -func Run(model gadget.Model, gadgetRoot, kernelRoot, bootDevice string, options Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*InstalledSystemSideData, error) { +func Run(model gadget.Model, gadgetRoot string, kernelSnapInfo *KernelSnapInfo, bootDevice string, options Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*InstalledSystemSideData, error) { logger.Noticef("installing a new system") logger.Noticef(" gadget data from: %v", gadgetRoot) logger.Noticef(" encryption: %v", options.EncryptionType) @@ -340,6 +344,7 @@ func Run(model gadget.Model, gadgetRoot, kernelRoot, bootDevice string, options } // Step 1: create partitions + kernelRoot := kernelSnapInfo.MountPoint bootVolGadgetName, created, bootVolSectorSize, err := createPartitions(model, info, gadgetRoot, kernelRoot, bootDevice, options, perfTimings) if err != nil { @@ -379,7 +384,7 @@ func Run(model gadget.Model, gadgetRoot, kernelRoot, bootDevice string, options // for encrypted device the filesystem device it will point to // the mapper device otherwise it's the raw device node fsDevice, encryptionKey, err := installOnePartition(dgpair, - kernelInfo, gadgetRoot, kernelRoot, options.EncryptionType, + kernelInfo, kernelSnapInfo, gadgetRoot, options.EncryptionType, bootVolSectorSize, observer, perfTimings) if err != nil { return nil, err @@ -493,7 +498,7 @@ func deviceForMaybeEncryptedVolume(volStruct *gadget.VolumeStructure, encSetupDa // WriteContent writes gadget content to the devices specified in // onVolumes. It returns the resolved on disk volumes. -func WriteContent(onVolumes map[string]*gadget.Volume, allLaidOutVols map[string]*gadget.LaidOutVolume, encSetupData *EncryptionSetupData, observer gadget.ContentObserver, perfTimings timings.Measurer) ([]*gadget.OnDiskVolume, error) { +func WriteContent(onVolumes map[string]*gadget.Volume, allLaidOutVols map[string]*gadget.LaidOutVolume, encSetupData *EncryptionSetupData, kSnapInfo *KernelSnapInfo, observer gadget.ContentObserver, perfTimings timings.Measurer) ([]*gadget.OnDiskVolume, error) { // TODO this taking onVolumes and allLaidOutVols is odd, // we should try to avoid this when we have partial @@ -529,7 +534,7 @@ func WriteContent(onVolumes map[string]*gadget.Volume, allLaidOutVols map[string device := deviceForMaybeEncryptedVolume(&volStruct, encSetupData) logger.Debugf("writing content on partition %s", device) partDisp := roleOrLabelOrName(laidOut.Role(), &laidOut.OnDiskStructure) - if err := writePartitionContent(laidOut, device, observer, partDisp, perfTimings); err != nil { + if err := writePartitionContent(laidOut, kSnapInfo, device, observer, partDisp, perfTimings); err != nil { return nil, err } } @@ -679,7 +684,7 @@ func KeysForRole(setupData *EncryptionSetupData) map[string]keys.EncryptionKey { return keyForRole } -func FactoryReset(model gadget.Model, gadgetRoot, kernelRoot, bootDevice string, options Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*InstalledSystemSideData, error) { +func FactoryReset(model gadget.Model, gadgetRoot string, kernelSnapInfo *KernelSnapInfo, bootDevice string, options Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*InstalledSystemSideData, error) { logger.Noticef("performing factory reset on an installed system") logger.Noticef(" gadget data from: %v", gadgetRoot) logger.Noticef(" encryption: %v", options.EncryptionType) @@ -737,7 +742,7 @@ func FactoryReset(model gadget.Model, gadgetRoot, kernelRoot, bootDevice string, return nil, fmt.Errorf("gadget and system-boot device %v partition table not compatible: %v", bootDevice, err) } - kernelInfo, err := kernel.ReadInfo(kernelRoot) + kernelInfo, err := kernel.ReadInfo(kernelSnapInfo.MountPoint) if err != nil { return nil, err } @@ -769,7 +774,7 @@ func FactoryReset(model gadget.Model, gadgetRoot, kernelRoot, bootDevice string, fsDevice, encryptionKey, err := installOnePartition( &gadget.OnDiskAndGadgetStructurePair{ DiskStructure: onDiskStruct, GadgetStructure: vs}, - kernelInfo, gadgetRoot, kernelRoot, options.EncryptionType, + kernelInfo, kernelSnapInfo, gadgetRoot, options.EncryptionType, diskLayout.SectorSize, observer, perfTimings) if err != nil { return nil, err diff --git a/gadget/install/install_dummy.go b/gadget/install/install_dummy.go index c987fd5a058..2dfb2840f8a 100644 --- a/gadget/install/install_dummy.go +++ b/gadget/install/install_dummy.go @@ -30,15 +30,15 @@ import ( "github.com/snapcore/snapd/timings" ) -func Run(model gadget.Model, gadgetRoot, kernelRoot, device string, options Options, _ gadget.ContentObserver, _ timings.Measurer) (*InstalledSystemSideData, error) { +func Run(model gadget.Model, gadgetRoot string, kernelSnapInfo *KernelSnapInfo, bootDevice string, options Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*InstalledSystemSideData, error) { return nil, fmt.Errorf("build without secboot support") } -func FactoryReset(model gadget.Model, gadgetRoot, kernelRoot, device string, options Options, _ gadget.ContentObserver, _ timings.Measurer) (*InstalledSystemSideData, error) { +func FactoryReset(model gadget.Model, gadgetRoot string, kernelSnapInfo *KernelSnapInfo, bootDevice string, options Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*InstalledSystemSideData, error) { return nil, fmt.Errorf("build without secboot support") } -func WriteContent(onVolumes map[string]*gadget.Volume, allLaidOutVols map[string]*gadget.LaidOutVolume, encSetupData *EncryptionSetupData, observer gadget.ContentObserver, perfTimings timings.Measurer) ([]*gadget.OnDiskVolume, error) { +func WriteContent(onVolumes map[string]*gadget.Volume, allLaidOutVols map[string]*gadget.LaidOutVolume, encSetupData *EncryptionSetupData, kSnapInfo *KernelSnapInfo, observer gadget.ContentObserver, perfTimings timings.Measurer) ([]*gadget.OnDiskVolume, error) { return nil, fmt.Errorf("build without secboot support") } diff --git a/gadget/install/install_test.go b/gadget/install/install_test.go index b41c722a87f..5e2bd6ea3c0 100644 --- a/gadget/install/install_test.go +++ b/gadget/install/install_test.go @@ -68,11 +68,11 @@ func (s *installSuite) SetUpTest(c *C) { } func (s *installSuite) TestInstallRunError(c *C) { - sys, err := install.Run(nil, "", "", "", install.Options{}, nil, timings.New(nil)) + sys, err := install.Run(nil, "", &install.KernelSnapInfo{}, "", install.Options{}, nil, timings.New(nil)) c.Assert(err, ErrorMatches, "cannot use empty gadget root directory") c.Check(sys, IsNil) - sys, err = install.Run(&gadgettest.ModelCharacteristics{}, c.MkDir(), "", "", install.Options{}, nil, timings.New(nil)) + sys, err = install.Run(&gadgettest.ModelCharacteristics{}, c.MkDir(), &install.KernelSnapInfo{}, "", install.Options{}, nil, timings.New(nil)) c.Assert(err, ErrorMatches, `cannot run install mode on pre-UC20 system`) c.Check(sys, IsNil) } @@ -370,7 +370,7 @@ fi if opts.encryption { runOpts.EncryptionType = secboot.EncryptionTypeLUKS } - sys, err := install.Run(uc20Mod, gadgetRoot, "", "", runOpts, nil, timings.New(nil)) + sys, err := install.Run(uc20Mod, gadgetRoot, &install.KernelSnapInfo{}, "", runOpts, nil, timings.New(nil)) c.Assert(err, IsNil) if opts.encryption { c.Check(sys, Not(IsNil)) @@ -776,7 +776,7 @@ fi if opts.encryption { runOpts.EncryptionType = secboot.EncryptionTypeLUKS } - sys, err := install.FactoryReset(uc20Mod, gadgetRoot, "", "", runOpts, nil, timings.New(nil)) + sys, err := install.FactoryReset(uc20Mod, gadgetRoot, &install.KernelSnapInfo{}, "", runOpts, nil, timings.New(nil)) if opts.err != "" { c.Check(sys, IsNil) c.Check(err, ErrorMatches, opts.err) @@ -1028,7 +1028,7 @@ func (s *installSuite) testWriteContent(c *C, opts writeContentOpts) { } esd = install.MockEncryptionSetupData(labelToEncData) } - onDiskVols, err := install.WriteContent(ginfo.Volumes, allLaidOutVols, esd, nil, timings.New(nil)) + onDiskVols, err := install.WriteContent(ginfo.Volumes, allLaidOutVols, esd, nil, nil, timings.New(nil)) c.Assert(err, IsNil) c.Assert(len(onDiskVols), Equals, 1) @@ -1067,7 +1067,7 @@ func (s *installSuite) TestInstallWriteContentDeviceNotFound(c *C) { }, }, } - onDiskVols, err := install.WriteContent(vols, nil, nil, nil, timings.New(nil)) + onDiskVols, err := install.WriteContent(vols, nil, nil, nil, nil, timings.New(nil)) c.Check(err.Error(), testutil.Contains, "readlink /sys/class/block/randomdev: no such file or directory") c.Check(onDiskVols, IsNil) } diff --git a/kernel/kernel_drivers.go b/kernel/kernel_drivers.go index 4c5af7e3907..2f30129aa06 100644 --- a/kernel/kernel_drivers.go +++ b/kernel/kernel_drivers.go @@ -26,12 +26,14 @@ import ( "os" "path/filepath" "regexp" + "strings" "syscall" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/naming" ) // For testing purposes @@ -122,7 +124,7 @@ func createFirmwareSymlinks(fwMount, fwDest string) error { return nil } -func createModulesSubtree(kernelMount, kernelTree, kversion, kname string, krev snap.Revision, compInfos []*snap.ComponentSideInfo) error { +func createModulesSubtree(kernelMount, kernelTree, kversion string, kmodsConts []snap.ContainerPlaceInfo) error { // Although empty we need "lib" because "depmod" always appends // "/lib/modules/" to the directory passed with option // "-b". @@ -157,10 +159,10 @@ func createModulesSubtree(kernelMount, kernelTree, kversion, kname string, krev } // If necessary, add modules from components and run depmod - return setupModsFromComp(kernelTree, kversion, kname, krev, compInfos) + return setupModsFromComp(kernelTree, kversion, kmodsConts) } -func setupModsFromComp(kernelTree, kversion, kname string, krev snap.Revision, compInfos []*snap.ComponentSideInfo) error { +func setupModsFromComp(kernelTree, kversion string, kmodsConts []snap.ContainerPlaceInfo) error { // This folder needs to exist always to allow for directory swapping // in the future, even if right now we don't have components. compsRoot := filepath.Join(kernelTree, "lib", "modules", kversion, "updates") @@ -168,16 +170,19 @@ func setupModsFromComp(kernelTree, kversion, kname string, krev snap.Revision, c return err } - if len(compInfos) == 0 { + if len(kmodsConts) == 0 { return nil } // Symbolic links to components - for _, ci := range compInfos { - compPI := snap.MinimalComponentContainerPlaceInfo(ci.Component.ComponentName, - ci.Revision, kname) - lname := filepath.Join(compsRoot, ci.Component.ComponentName) - to := filepath.Join(compPI.MountDir(), "modules", kversion) + for _, kmc := range kmodsConts { + _, comp, err := naming.SplitFullComponentName(kmc.ContainerName()) + if err != nil { + return err + } + + lname := filepath.Join(compsRoot, comp) + to := filepath.Join(kmc.MountDir(), "modules", kversion) if err := osSymlink(to, lname); err != nil { return err } @@ -193,14 +198,17 @@ func setupModsFromComp(kernelTree, kversion, kname string, krev snap.Revision, c return nil } -func driversTreeDir(kernelSubdir string, rev snap.Revision) string { - return filepath.Join(dirs.SnapdStateDir(dirs.GlobalRootDir), "kernel", kernelSubdir, rev.String()) +// DriversTreeDir returns the directory for a given kernel and revision under +// rootdir. +func DriversTreeDir(rootdir, kernelName string, rev snap.Revision) string { + return filepath.Join(dirs.SnapKernelDriversTreesDirUnder(rootdir), + kernelName, rev.String()) } // RemoveKernelDriversTree cleans-up the writable kernel tree in snapd data // folder, under kernelSubdir/ (kernelSubdir is usually the snap name). -func RemoveKernelDriversTree(kernelSubdir string, rev snap.Revision) (err error) { - treeRoot := driversTreeDir(kernelSubdir, rev) +// When called from the kernel package might be _tmp. +func RemoveKernelDriversTree(treeRoot string) (err error) { return os.RemoveAll(treeRoot) } @@ -214,28 +222,46 @@ type KernelDriversTreeOptions struct { // this is a kernel install (which might be installing components at the same // time) or an only components install. // -// For kernel installs, this function creates a tree in -// /var/lib/snapd/kernel//, which is bind-mounted after a -// reboot to /usr/lib/{modules,firmware} (the currently active kernel is using -// a different path as it has a different revision). This tree contains files -// from the kernel snap mounted on kernelMount, as well as symlinks to it. +// For kernel installs, this function creates a tree in destDir (should be of +// the form /var/lib/snapd/kernel//), which is +// bind-mounted after a reboot to /usr/lib/{modules,firmware} (the currently +// active kernel is using a different path as it has a different revision). +// This tree contains files from the kernel snap content in kSnapRoot, as well +// as symlinks to it. Information from modules is found by looking at +// kmodsConts slice. // // For components-only install, we want the components to be available without // rebooting. For this, we work on a temporary tree, and after finishing it we // swap atomically the affected modules/firmware folders with those of the // currently active kernel drivers tree. -func EnsureKernelDriversTree(ksnapName string, rev snap.Revision, kernelMount string, kmodsInfos []*snap.ComponentSideInfo, opts *KernelDriversTreeOptions) (err error) { +// +// TODO When adding support to install kernel+components jointly we will need +// the actual directories where components content can be found, because +// depending on the installation type (from initramfs, snapd API or ephemeral +// system) the content might be in a different place to the mount points in a +// running system. In that case, we need to run depmod with links to the real +// content, and then replace those links with the expected mounts in the +// running system. When we do this, consider some clean-up of the function +// arguments. +func EnsureKernelDriversTree(kSnapRoot, destDir string, kmodsConts []snap.ContainerPlaceInfo, opts *KernelDriversTreeOptions) (err error) { // The temporal dir when installing only components can be fixed as a // task installing/updating a kernel-modules component must conflict // with changes containing this same task. This helps with clean-ups if // something goes wrong. Note that this folder needs to be in the same // filesystem as the final one so we can atomically switch the folders. - ksnapDir := ksnapName + "_tmp" + destDir = strings.TrimSuffix(destDir, "/") + targetDir := destDir + "_tmp" if opts.KernelInstall { - ksnapDir = ksnapName + targetDir = destDir + exists, isDir, _ := osutil.DirExists(targetDir) + if exists && isDir { + logger.Debugf("device tree %q already created on installation, not re-creating", + targetDir) + return nil + } } // Initial clean-up to make the function idempotent - if rmErr := RemoveKernelDriversTree(ksnapDir, rev); rmErr != nil && + if rmErr := RemoveKernelDriversTree(targetDir); rmErr != nil && !errors.Is(err, fs.ErrNotExist) { logger.Noticef("while removing old kernel tree: %v", rmErr) } @@ -245,32 +271,30 @@ func EnsureKernelDriversTree(ksnapName string, rev snap.Revision, kernelMount st if err == nil && opts.KernelInstall { return } - if rmErr := RemoveKernelDriversTree(ksnapDir, rev); rmErr != nil && + if rmErr := RemoveKernelDriversTree(targetDir); rmErr != nil && !errors.Is(err, fs.ErrNotExist) { logger.Noticef("while cleaning up kernel tree: %v", rmErr) } }() - treeRoot := driversTreeDir(ksnapDir, rev) - // Create drivers tree - kversion, err := KernelVersionFromModulesDir(kernelMount) + kversion, err := KernelVersionFromModulesDir(kSnapRoot) if err == nil { - if err := createModulesSubtree(kernelMount, treeRoot, - kversion, ksnapName, rev, kmodsInfos); err != nil { + if err := createModulesSubtree(kSnapRoot, targetDir, + kversion, kmodsConts); err != nil { return err } } else { // Bit of a corner case, but maybe possible. Log anyway. // TODO detect this issue in snap pack, should be enforced // if the snap declares kernel-modules components. - logger.Noticef("no modules found in %q", kernelMount) + logger.Noticef("no modules found in %q", kSnapRoot) } - fwDir := filepath.Join(treeRoot, "lib", "firmware") + fwDir := filepath.Join(targetDir, "lib", "firmware") if opts.KernelInstall { // symlinks in /lib/firmware are not affected by components - if err := createFirmwareSymlinks(kernelMount, fwDir); err != nil { + if err := createFirmwareSymlinks(kSnapRoot, fwDir); err != nil { return err } } @@ -280,10 +304,8 @@ func EnsureKernelDriversTree(ksnapName string, rev snap.Revision, kernelMount st if err := os.MkdirAll(updateFwDir, 0755); err != nil { return err } - for _, kmi := range kmodsInfos { - compPI := snap.MinimalComponentContainerPlaceInfo(kmi.Component.ComponentName, - kmi.Revision, ksnapName) - if err := createFirmwareSymlinks(compPI.MountDir(), updateFwDir); err != nil { + for _, kmc := range kmodsConts { + if err := createFirmwareSymlinks(kmc.MountDir(), updateFwDir); err != nil { return err } } @@ -302,7 +324,7 @@ func EnsureKernelDriversTree(ksnapName string, rev snap.Revision, kernelMount st // in principle the system should recover. // Swap modules directories - oldRoot := driversTreeDir(ksnapName, rev) + oldRoot := destDir // Swap updates directory inside firmware dir oldFwUpdates := filepath.Join(oldRoot, "lib", "firmware", "updates") @@ -310,7 +332,7 @@ func EnsureKernelDriversTree(ksnapName string, rev snap.Revision, kernelMount st return fmt.Errorf("while swapping %q <-> %q: %w", oldFwUpdates, updateFwDir, err) } - newMods := filepath.Join(treeRoot, "lib", "modules", kversion) + newMods := filepath.Join(targetDir, "lib", "modules", kversion) oldMods := filepath.Join(oldRoot, "lib", "modules", kversion) if err := osutil.SwapDirs(oldMods, newMods); err != nil { // Undo firmware swap diff --git a/kernel/kernel_drivers_test.go b/kernel/kernel_drivers_test.go index 29af0eb6cf9..61d8179c93c 100644 --- a/kernel/kernel_drivers_test.go +++ b/kernel/kernel_drivers_test.go @@ -33,7 +33,6 @@ import ( "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/snap" - "github.com/snapcore/snapd/snap/naming" "github.com/snapcore/snapd/testutil" ) @@ -131,8 +130,8 @@ func (s *kernelDriversTestSuite) TestBuildKernelDriversTree(c *C) { testBuildKernelDriversTree(c) // Now remove and check - kernel.RemoveKernelDriversTree("pc-kernel", snap.R(1)) treeRoot := filepath.Join(dirs.SnapdStateDir(dirs.GlobalRootDir), "kernel", "pc-kernel", "1") + kernel.RemoveKernelDriversTree(treeRoot) c.Assert(osutil.FileExists(treeRoot), Equals, false) } @@ -163,7 +162,8 @@ func testBuildKernelDriversTree(c *C) { createKernelSnapFiles(c, kversion, mountDir) // Now build the tree - c.Assert(kernel.EnsureKernelDriversTree("pc-kernel", snap.R(1), mountDir, nil, + destDir := kernel.DriversTreeDir(dirs.GlobalRootDir, "pc-kernel", snap.R(1)) + c.Assert(kernel.EnsureKernelDriversTree(mountDir, destDir, nil, &kernel.KernelDriversTreeOptions{KernelInstall: true}), IsNil) // Check content is as expected @@ -215,8 +215,9 @@ func (s *kernelDriversTestSuite) TestBuildKernelDriversNoModsOrFw(c *C) { c.Assert(os.MkdirAll(mountDir, 0755), IsNil) // Build the tree should not fail - err := kernel.EnsureKernelDriversTree("pc-kernel", snap.R(1), mountDir, - nil, &kernel.KernelDriversTreeOptions{KernelInstall: true}) + destDir := kernel.DriversTreeDir(dirs.GlobalRootDir, "pc-kernel", snap.R(1)) + err := kernel.EnsureKernelDriversTree(mountDir, destDir, nil, + &kernel.KernelDriversTreeOptions{KernelInstall: true}) c.Assert(err, IsNil) // but log should warn about this @@ -242,7 +243,8 @@ func (s *kernelDriversTestSuite) TestBuildKernelDriversOnlyMods(c *C) { createKernelSnapFilesOnlyModules(c, kversion, mountDir) // Build the tree should not fail - err := kernel.EnsureKernelDriversTree("pc-kernel", snap.R(1), mountDir, nil, + destDir := kernel.DriversTreeDir(dirs.GlobalRootDir, "pc-kernel", snap.R(1)) + err := kernel.EnsureKernelDriversTree(mountDir, destDir, nil, &kernel.KernelDriversTreeOptions{KernelInstall: true}) c.Assert(err, IsNil) @@ -274,8 +276,9 @@ func (s *kernelDriversTestSuite) TestBuildKernelDriversOnlyFw(c *C) { createKernelSnapFilesOnlyFw(c, mountDir) // Build the tree should not fail - err := kernel.EnsureKernelDriversTree("pc-kernel", snap.R(1), mountDir, - nil, &kernel.KernelDriversTreeOptions{KernelInstall: true}) + destDir := kernel.DriversTreeDir(dirs.GlobalRootDir, "pc-kernel", snap.R(1)) + err := kernel.EnsureKernelDriversTree(mountDir, destDir, nil, + &kernel.KernelDriversTreeOptions{KernelInstall: true}) c.Assert(err, IsNil) // check link @@ -296,8 +299,9 @@ func (s *kernelDriversTestSuite) TestBuildKernelDriversAbsFwSymlink(c *C) { os.Symlink("/absdir/blob3", filepath.Join(fwDir, "ln_to_abs")) // Fails on the absolute path in the link - err := kernel.EnsureKernelDriversTree("pc-kernel", snap.R(1), mountDir, - nil, &kernel.KernelDriversTreeOptions{KernelInstall: true}) + destDir := kernel.DriversTreeDir(dirs.GlobalRootDir, "pc-kernel", snap.R(1)) + err := kernel.EnsureKernelDriversTree(mountDir, destDir, nil, + &kernel.KernelDriversTreeOptions{KernelInstall: true}) c.Assert(err, ErrorMatches, `symlink \".*lib/firmware/ln_to_abs\" points to absolute path \"/absdir/blob3\"`) // Make sure the tree has been deleted @@ -316,8 +320,9 @@ func (s *kernelDriversTestSuite) TestBuildKernelDriversTreeCleanup(c *C) { defer restore() // Now build the tree - err := kernel.EnsureKernelDriversTree("pc-kernel", snap.R(1), mountDir, - nil, &kernel.KernelDriversTreeOptions{KernelInstall: true}) + destDir := kernel.DriversTreeDir(dirs.GlobalRootDir, "pc-kernel", snap.R(1)) + err := kernel.EnsureKernelDriversTree(mountDir, destDir, nil, + &kernel.KernelDriversTreeOptions{KernelInstall: true}) c.Assert(err, ErrorMatches, "mocked symlink error") // Make sure the tree has been deleted @@ -335,8 +340,9 @@ func (s *kernelDriversTestSuite) TestBuildKernelDriversBadFileType(c *C) { c.Assert(syscall.Mkfifo(filepath.Join(fwDir, "fifo"), 0666), IsNil) // Now build the tree - err := kernel.EnsureKernelDriversTree("pc-kernel", snap.R(1), mountDir, - nil, &kernel.KernelDriversTreeOptions{KernelInstall: true}) + destDir := kernel.DriversTreeDir(dirs.GlobalRootDir, "pc-kernel", snap.R(1)) + err := kernel.EnsureKernelDriversTree(mountDir, destDir, nil, + &kernel.KernelDriversTreeOptions{KernelInstall: true}) c.Assert(err, ErrorMatches, `"fifo" has unexpected file type: p---------`) // Make sure the tree has been deleted @@ -365,8 +371,8 @@ func (s *kernelDriversTestSuite) TestBuildKernelDriversTreeWithKernelAndComps(c testBuildKernelDriversTreeWithComps(c, opts) // Now remove and check - kernel.RemoveKernelDriversTree("pc-kernel", snap.R(1)) treeRoot := filepath.Join(dirs.SnapdStateDir(dirs.GlobalRootDir), "kernel", "pc-kernel", "1") + kernel.RemoveKernelDriversTree(treeRoot) c.Assert(osutil.FileExists(treeRoot), Equals, false) } @@ -379,12 +385,12 @@ func (s *kernelDriversTestSuite) TestBuildKernelDriversTreeCompsNoKernelInstall( testBuildKernelDriversTreeWithComps(c, opts) // Now remove and check - kernel.RemoveKernelDriversTree("pc-kernel", snap.R(1)) treeRoot := filepath.Join(dirs.SnapdStateDir(dirs.GlobalRootDir), "kernel", "pc-kernel", "1") + kernel.RemoveKernelDriversTree(treeRoot) c.Assert(osutil.FileExists(treeRoot), Equals, false) // No _tmp folder should be around - treeRoot = filepath.Join(dirs.SnapdStateDir(dirs.GlobalRootDir), "kernel", "pc-kernel_tmp", "1") + treeRoot = filepath.Join(dirs.SnapdStateDir(dirs.GlobalRootDir), "kernel", "pc-kernel", "1_tmp") c.Assert(osutil.FileExists(treeRoot), Equals, false) } @@ -400,13 +406,14 @@ func (s *kernelDriversTestSuite) TestBuildKernelDriversTreeCompsNoKernel(c *C) { compMntDir2 := filepath.Join(dirs.RunDir, "mnt/kernel-snaps/comp2") createKernelModulesCompFiles(c, kversion, compMntDir1, "comp1") createKernelModulesCompFiles(c, kversion, compMntDir2, "comp2") - kmods := []*snap.ComponentSideInfo{ - snap.NewComponentSideInfo(naming.NewComponentRef("pc-kernel", "comp1"), snap.R(11)), - snap.NewComponentSideInfo(naming.NewComponentRef("pc-kernel", "comp2"), snap.R(22)), + kmodsConts := []snap.ContainerPlaceInfo{ + snap.MinimalComponentContainerPlaceInfo("comp1", snap.R(11), "pc-kernel"), + snap.MinimalComponentContainerPlaceInfo("comp2", snap.R(22), "pc-kernel"), } // Now build the tree, will fail as no kernel was installed previously - err := kernel.EnsureKernelDriversTree("pc-kernel", snap.R(1), mountDir, kmods, &kernel.KernelDriversTreeOptions{KernelInstall: false}) + destDir := kernel.DriversTreeDir(dirs.GlobalRootDir, "pc-kernel", snap.R(1)) + err := kernel.EnsureKernelDriversTree(mountDir, destDir, kmodsConts, &kernel.KernelDriversTreeOptions{KernelInstall: false}) c.Assert(err, ErrorMatches, `while swapping .*: no such file or directory`) } @@ -422,22 +429,33 @@ func testBuildKernelDriversTreeWithComps(c *C, opts *kernel.KernelDriversTreeOpt compMntDir2 := filepath.Join(dirs.SnapMountDir, "pc-kernel/components/mnt/comp2/22") createKernelModulesCompFiles(c, kversion, compMntDir1, "comp1") createKernelModulesCompFiles(c, kversion, compMntDir2, "comp2") - kmods := []*snap.ComponentSideInfo{ - snap.NewComponentSideInfo(naming.NewComponentRef("pc-kernel", "comp1"), snap.R(11)), - snap.NewComponentSideInfo(naming.NewComponentRef("pc-kernel", "comp2"), snap.R(22)), + kmodsConts := []snap.ContainerPlaceInfo{ + snap.MinimalComponentContainerPlaceInfo("comp1", snap.R(11), "pc-kernel"), + snap.MinimalComponentContainerPlaceInfo("comp2", snap.R(22), "pc-kernel"), } - // Now build the tree - c.Assert(kernel.EnsureKernelDriversTree("pc-kernel", snap.R(1), mountDir, kmods, opts), IsNil) - - ksubdir := "pc-kernel_tmp" + workSubdir := "1_tmp" if opts.KernelInstall { - ksubdir = "pc-kernel" + workSubdir = "1" + } + treeRoot := filepath.Join(dirs.SnapdStateDir(dirs.GlobalRootDir), "kernel", "pc-kernel", workSubdir) + // Find out if the directory already exists, as in that case + // there are no calls to depmod + exists, isDir, err := osutil.DirExists(treeRoot) + c.Assert(err, IsNil) + + // Now build the tree + destDir := kernel.DriversTreeDir(dirs.GlobalRootDir, "pc-kernel", snap.R(1)) + c.Assert(kernel.EnsureKernelDriversTree(mountDir, destDir, kmodsConts, opts), IsNil) + + if exists { + c.Assert(isDir, Equals, true) + c.Assert(mockCmd.Calls(), IsNil) + } else { + c.Assert(mockCmd.Calls(), DeepEquals, [][]string{ + {"depmod", "-b", treeRoot, kversion}, + }) } - treeRoot := filepath.Join(dirs.SnapdStateDir(dirs.GlobalRootDir), "kernel", ksubdir, "1") - c.Assert(mockCmd.Calls(), DeepEquals, [][]string{ - {"depmod", "-b", treeRoot, kversion}, - }) // Check modules root dir is as expected modsRoot := filepath.Join(dirs.SnapdStateDir(dirs.GlobalRootDir), "kernel", "pc-kernel", "1", "lib", "modules", kversion) @@ -498,4 +516,11 @@ func testBuildKernelDriversTreeWithComps(c *C, opts *kernel.KernelDriversTreeOpt c.Check(exists, Equals, true) c.Check(isReg, Equals, true) } + + if !opts.KernelInstall { + // Check that there is no tmp folder left behind + tmpDir := filepath.Join(dirs.SnapdStateDir(dirs.GlobalRootDir), "kernel", "pc-kernel", "1_tmp") + exists, _, _ = osutil.RegularFileExists(tmpDir) + c.Check(exists, Equals, false) + } } diff --git a/overlord/devicestate/devicestate_install_api_test.go b/overlord/devicestate/devicestate_install_api_test.go index 184eaad4c84..1ce0be9cc4a 100644 --- a/overlord/devicestate/devicestate_install_api_test.go +++ b/overlord/devicestate/devicestate_install_api_test.go @@ -442,7 +442,7 @@ func (s *deviceMgrInstallAPISuite) testInstallFinishStep(c *C, opts finishStepOp // Mock writing of contents writeContentCalls := 0 - restore = devicestate.MockInstallWriteContent(func(onVolumes map[string]*gadget.Volume, allLaidOutVols map[string]*gadget.LaidOutVolume, encSetupData *install.EncryptionSetupData, observer gadget.ContentObserver, perfTimings timings.Measurer) ([]*gadget.OnDiskVolume, error) { + restore = devicestate.MockInstallWriteContent(func(onVolumes map[string]*gadget.Volume, allLaidOutVols map[string]*gadget.LaidOutVolume, encSetupData *install.EncryptionSetupData, kSnapInfo *install.KernelSnapInfo, observer gadget.ContentObserver, perfTimings timings.Measurer) ([]*gadget.OnDiskVolume, error) { writeContentCalls++ vol := onVolumes["pc"] for sIdx, vs := range vol.Structure { diff --git a/overlord/devicestate/devicestate_install_mode_test.go b/overlord/devicestate/devicestate_install_mode_test.go index f9a3f156546..278112b900e 100644 --- a/overlord/devicestate/devicestate_install_mode_test.go +++ b/overlord/devicestate/devicestate_install_mode_test.go @@ -243,7 +243,7 @@ func (s *deviceMgrInstallModeSuite) doRunChangeTestWithEncryption(c *C, grade st var brOpts install.Options var installRunCalled int var installSealingObserver gadget.ContentObserver - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, obs gadget.ContentObserver, pertTimings timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, obs gadget.ContentObserver, pertTimings timings.Measurer) (*install.InstalledSystemSideData, error) { // ensure we can grab the lock here, i.e. that it's not taken s.state.Lock() s.state.Unlock() @@ -386,7 +386,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallTaskErrors(c *C) { restore := release.MockOnClassic(false) defer restore() - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { return nil, fmt.Errorf("The horror, The horror") }) defer restore() @@ -417,7 +417,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallExpTasks(c *C) { restore := release.MockOnClassic(false) defer restore() - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { return nil, nil }) defer restore() @@ -464,7 +464,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallRestoresPreseedArtifact(c *C) { restore := release.MockOnClassic(false) defer restore() - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { return nil, nil }) defer restore() @@ -515,7 +515,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallNoPreseedArtifact(c *C) { restore := release.MockOnClassic(false) defer restore() - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { return nil, nil }) defer restore() @@ -564,7 +564,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallRestoresPreseedArtifactError(c *C restore := release.MockOnClassic(false) defer restore() - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { return nil, nil }) defer restore() @@ -612,7 +612,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallRestoresPreseedArtifactModelMisma restore := release.MockOnClassic(false) defer restore() - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { return nil, nil }) defer restore() @@ -745,7 +745,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallWithInstallDeviceHookExpTasks(c * restore := release.MockOnClassic(false) defer restore() - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { return nil, nil }) defer restore() @@ -828,7 +828,7 @@ func (s *deviceMgrInstallModeSuite) testInstallWithInstallDeviceHookSnapctlReboo restore := release.MockOnClassic(false) defer restore() - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { return nil, nil }) defer restore() @@ -879,7 +879,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallWithBrokenInstallDeviceHookUnhapp restore := release.MockOnClassic(false) defer restore() - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { return nil, nil }) defer restore() @@ -945,7 +945,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallSetupRunSystemTaskNoRestarts(c *C restore := release.MockOnClassic(false) defer restore() - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { return nil, nil }) defer restore() @@ -1126,7 +1126,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallSecuredBypassEncryption(c *C) { } func (s *deviceMgrInstallModeSuite) TestInstallBootloaderVarSetFails(c *C) { - restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { c.Check(options.EncryptionType, Equals, secboot.EncryptionTypeNone) // no keys set return &install.InstalledSystemSideData{}, nil @@ -1194,7 +1194,7 @@ func (s *deviceMgrInstallModeSuite) testInstallEncryptionValidityChecks(c *C, er } func (s *deviceMgrInstallModeSuite) TestInstallEncryptionValidityChecksNoKeys(c *C) { - restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { c.Check(options.EncryptionType, Equals, secboot.EncryptionTypeLUKS) // no keys set return &install.InstalledSystemSideData{}, nil @@ -1205,7 +1205,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallEncryptionValidityChecksNoKeys(c } func (s *deviceMgrInstallModeSuite) TestInstallEncryptionValidityChecksNoSystemDataKey(c *C) { - restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { c.Check(options.EncryptionType, Equals, secboot.EncryptionTypeLUKS) // no keys set return &install.InstalledSystemSideData{ @@ -1222,7 +1222,7 @@ func (s *deviceMgrInstallModeSuite) mockInstallModeChange(c *C, modelGrade, gadg restore := release.MockOnClassic(false) defer restore() - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { return nil, nil }) defer restore() @@ -1315,7 +1315,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallWithEncryptionValidatesGadgetErr( restore := release.MockOnClassic(false) defer restore() - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { return nil, fmt.Errorf("unexpected call") }) defer restore() @@ -1344,7 +1344,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallWithEncryptionValidatesGadgetWarn restore := release.MockOnClassic(false) defer restore() - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { return nil, nil }) defer restore() @@ -1371,7 +1371,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallWithoutEncryptionValidatesGadgetW restore := release.MockOnClassic(false) defer restore() - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { return nil, nil }) defer restore() @@ -1475,7 +1475,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallHappyLogfiles(c *C) { restore := release.MockOnClassic(false) defer restore() - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { return nil, nil }) defer restore() @@ -1556,7 +1556,7 @@ func (s *deviceMgrInstallModeSuite) doRunFactoryResetChange(c *C, model *asserts var brOpts install.Options var installFactoryResetCalled int var installSealingObserver gadget.ContentObserver - restore = devicestate.MockInstallFactoryReset(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, obs gadget.ContentObserver, pertTimings timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallFactoryReset(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, obs gadget.ContentObserver, pertTimings timings.Measurer) (*install.InstalledSystemSideData, error) { // ensure we can grab the lock here, i.e. that it's not taken s.state.Lock() s.state.Unlock() @@ -2204,7 +2204,7 @@ func (s *deviceMgrInstallModeSuite) TestFactoryResetExpectedTasks(c *C) { }) defer restore() - restore = devicestate.MockInstallFactoryReset(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, obs gadget.ContentObserver, pertTimings timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallFactoryReset(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, obs gadget.ContentObserver, pertTimings timings.Measurer) (*install.InstalledSystemSideData, error) { c.Assert(os.MkdirAll(dirs.SnapDeviceDirUnder(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")), 0755), IsNil) return &install.InstalledSystemSideData{ DeviceForRole: map[string]string{ @@ -2275,7 +2275,7 @@ func (s *deviceMgrInstallModeSuite) TestFactoryResetInstallDeviceHook(c *C) { }) defer restore() - restore = devicestate.MockInstallFactoryReset(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, obs gadget.ContentObserver, pertTimings timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallFactoryReset(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, obs gadget.ContentObserver, pertTimings timings.Measurer) (*install.InstalledSystemSideData, error) { c.Assert(os.MkdirAll(dirs.SnapDeviceDirUnder(boot.InstallHostWritableDir(mod)), 0755), IsNil) return &install.InstalledSystemSideData{ DeviceForRole: map[string]string{ @@ -2373,7 +2373,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallWithUbuntuSaveSnapFoldersHappy(c restore := release.MockOnClassic(false) defer restore() - restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { + restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) { return nil, nil }) defer restore() diff --git a/overlord/devicestate/export_test.go b/overlord/devicestate/export_test.go index 3464cd96539..0235ef9797e 100644 --- a/overlord/devicestate/export_test.go +++ b/overlord/devicestate/export_test.go @@ -381,7 +381,7 @@ func MockInstallLogicPrepareRunSystemData(f func(mod *asserts.Model, gadgetDir s return r } -func MockInstallRun(f func(model gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*install.InstalledSystemSideData, error)) (restore func()) { +func MockInstallRun(f func(model gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*install.InstalledSystemSideData, error)) (restore func()) { old := installRun installRun = f return func() { @@ -389,13 +389,13 @@ func MockInstallRun(f func(model gadget.Model, gadgetRoot, kernelRoot, device st } } -func MockInstallFactoryReset(f func(model gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*install.InstalledSystemSideData, error)) (restore func()) { +func MockInstallFactoryReset(f func(model gadget.Model, gadgetRoot string, kernelSnapInfo *install.KernelSnapInfo, device string, options install.Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*install.InstalledSystemSideData, error)) (restore func()) { restore = testutil.Backup(&installFactoryReset) installFactoryReset = f return restore } -func MockInstallWriteContent(f func(onVolumes map[string]*gadget.Volume, allLaidOutVols map[string]*gadget.LaidOutVolume, encSetupData *install.EncryptionSetupData, observer gadget.ContentObserver, perfTimings timings.Measurer) ([]*gadget.OnDiskVolume, error)) (restore func()) { +func MockInstallWriteContent(f func(onVolumes map[string]*gadget.Volume, allLaidOutVols map[string]*gadget.LaidOutVolume, encSetupData *install.EncryptionSetupData, kSnapInfo *install.KernelSnapInfo, observer gadget.ContentObserver, perfTimings timings.Measurer) ([]*gadget.OnDiskVolume, error)) (restore func()) { old := installWriteContent installWriteContent = f return func() { diff --git a/overlord/devicestate/handlers_install.go b/overlord/devicestate/handlers_install.go index 4ba34ea82f4..c0d9d466b75 100644 --- a/overlord/devicestate/handlers_install.go +++ b/overlord/devicestate/handlers_install.go @@ -273,10 +273,19 @@ func (m *DeviceManager) doSetupRunSystem(t *state.Task, _ *tomb.Tomb) error { var installedSystem *install.InstalledSystemSideData // run the create partition code logger.Noticef("create and deploy partitions") + kSnapInfo := &install.KernelSnapInfo{ + Name: kernelInfo.SnapName(), + MountPoint: kernelDir, + Revision: kernelInfo.Revision, + IsCore: !deviceCtx.Classic(), + } + if snapstate.NeedsKernelSetup(deviceCtx) { + kSnapInfo.NeedsDriversTree = true + } timings.Run(perfTimings, "install-run", "Install the run system", func(tm timings.Measurer) { st.Unlock() defer st.Lock() - installedSystem, err = installRun(model, gadgetDir, kernelDir, "", bopts, installObserver, tm) + installedSystem, err = installRun(model, gadgetDir, kSnapInfo, "", bopts, installObserver, tm) }) if err != nil { return fmt.Errorf("cannot install system: %v", err) @@ -537,10 +546,19 @@ func (m *DeviceManager) doFactoryResetRunSystem(t *state.Task, _ *tomb.Tomb) err var installedSystem *install.InstalledSystemSideData // run the create partition code logger.Noticef("create and deploy partitions") + kSnapInfo := &install.KernelSnapInfo{ + Name: kernelInfo.SnapName(), + MountPoint: kernelDir, + Revision: kernelInfo.Revision, + IsCore: !deviceCtx.Classic(), + } + if snapstate.NeedsKernelSetup(deviceCtx) { + kSnapInfo.NeedsDriversTree = true + } timings.Run(perfTimings, "factory-reset", "Factory reset", func(tm timings.Measurer) { st.Unlock() defer st.Lock() - installedSystem, err = installFactoryReset(model, gadgetDir, kernelDir, "", bopts, installObserver, tm) + installedSystem, err = installFactoryReset(model, gadgetDir, kSnapInfo, "", bopts, installObserver, tm) }) if err != nil { return fmt.Errorf("cannot perform factory reset: %v", err) @@ -988,18 +1006,36 @@ func (m *DeviceManager) doInstallFinish(t *state.Task, _ *tomb.Tomb) error { if useEncryption { encType = secboot.EncryptionTypeLUKS } + kernMntPoint := mntPtForType[snap.TypeKernel] allLaidOutVols, err := gadget.LaidOutVolumesFromGadget(mergedVols, - mntPtForType[snap.TypeGadget], mntPtForType[snap.TypeKernel], + mntPtForType[snap.TypeGadget], kernMntPoint, encType, volToGadgetToDiskStruct) if err != nil { return fmt.Errorf("on finish install: cannot layout volumes: %v", err) } + snapInfos := systemAndSnaps.InfosByType + snapSeeds := systemAndSnaps.SeedSnapsByType + kernInfo := snapInfos[snap.TypeKernel] + deviceCtx, err := DeviceCtx(st, t, nil) + if err != nil { + return fmt.Errorf("cannot get device context: %v", err) + } + logger.Debugf("writing content to partitions") + kSnapInfo := &install.KernelSnapInfo{ + Name: kernInfo.SnapName(), + Revision: kernInfo.Revision, + MountPoint: kernMntPoint, + IsCore: !deviceCtx.Classic(), + } + if snapstate.NeedsKernelSetup(deviceCtx) { + kSnapInfo.NeedsDriversTree = true + } timings.Run(perfTimings, "install-content", "Writing content to partitions", func(tm timings.Measurer) { st.Unlock() defer st.Lock() - _, err = installWriteContent(mergedVols, allLaidOutVols, encryptSetupData, installObserver, perfTimings) + _, err = installWriteContent(mergedVols, allLaidOutVols, encryptSetupData, kSnapInfo, installObserver, perfTimings) }) if err != nil { return fmt.Errorf("cannot write content: %v", err) @@ -1036,9 +1072,6 @@ func (m *DeviceManager) doInstallFinish(t *state.Task, _ *tomb.Tomb) error { } } - snapInfos := systemAndSnaps.InfosByType - snapSeeds := systemAndSnaps.SeedSnapsByType - bootWith := &boot.BootableSet{ Base: snapInfos[snap.TypeBase], BasePath: snapSeeds[snap.TypeBase].Path, diff --git a/overlord/snapstate/backend/setup.go b/overlord/snapstate/backend/setup.go index f09a2dec452..0d2848c1b3d 100644 --- a/overlord/snapstate/backend/setup.go +++ b/overlord/snapstate/backend/setup.go @@ -126,12 +126,15 @@ func (b Backend) SetupSnap(snapFilePath, instanceName string, sideInfo *snap.Sid func (b Backend) SetupKernelSnap(instanceName string, rev snap.Revision, meter progress.Meter) (err error) { // Build kernel tree that will be mounted from initramfs cpi := snap.MinimalSnapContainerPlaceInfo(instanceName, rev) - return kernel.EnsureKernelDriversTree(instanceName, rev, - cpi.MountDir(), nil, &kernel.KernelDriversTreeOptions{KernelInstall: true}) + destDir := kernel.DriversTreeDir(dirs.GlobalRootDir, instanceName, rev) + + return kernel.EnsureKernelDriversTree(cpi.MountDir(), destDir, nil, + &kernel.KernelDriversTreeOptions{KernelInstall: true}) } func (b Backend) RemoveKernelSnapSetup(instanceName string, rev snap.Revision, meter progress.Meter) error { - return kernel.RemoveKernelDriversTree(instanceName, rev) + kernelTree := kernel.DriversTreeDir(dirs.GlobalRootDir, instanceName, rev) + return kernel.RemoveKernelDriversTree(kernelTree) } // SetupComponent prepares and mounts a component for further processing. @@ -300,26 +303,22 @@ func (b Backend) RemoveSnapInhibitLock(instanceName string) error { // compsToInstall. The components currently active are currentComps, while // ksnapName and ksnapRev identify the currently active kernel. func (b Backend) SetupKernelModulesComponents(compsToInstall, currentComps []*snap.ComponentSideInfo, ksnapName string, ksnapRev snap.Revision, meter progress.Meter) (err error) { - sysd := newSystemd(b.preseed, meter) - // newActiveComps will contain the new revisions of components, taken from compsToInstall newActiveComps := mergeCompSideInfosUpdatingRev(currentComps, compsToInstall) return moveKModsComponentsState( - currentComps, newActiveComps, ksnapName, ksnapRev, sysd, + currentComps, newActiveComps, ksnapName, ksnapRev, "after failure to set-up kernel modules components") } // RemoveKernelModulesComponentsSetup changes kernel-modules configuration by // removing compsToRemove and making the final state consider only finalComps. func (b Backend) RemoveKernelModulesComponentsSetup(compsToRemove, finalComps []*snap.ComponentSideInfo, ksnapName string, ksnapRev snap.Revision, meter progress.Meter) (err error) { - sysd := newSystemd(b.preseed, meter) - // currentActiveComps will contain the current revision, taken from compsToRemove currentActiveComps := mergeCompSideInfosUpdatingRev(finalComps, compsToRemove) return moveKModsComponentsState( - currentActiveComps, finalComps, ksnapName, ksnapRev, sysd, + currentActiveComps, finalComps, ksnapName, ksnapRev, "after failure to remove set-up of kernel modules components") } @@ -347,17 +346,25 @@ func mergeCompSideInfosUpdatingRev(comps1, comps2 []*snap.ComponentSideInfo) (me // moveKModsComponentsState changes kernel-modules set-up from currentComps to // finalComps, for the kernel/revision specified by ksnapName/ksnapRev. -func moveKModsComponentsState(currentComps, finalComps []*snap.ComponentSideInfo, ksnapName string, ksnapRev snap.Revision, sysd systemd.Systemd, cleanErrMsg string) (err error) { +func moveKModsComponentsState(currentComps, finalComps []*snap.ComponentSideInfo, ksnapName string, ksnapRev snap.Revision, cleanErrMsg string) (err error) { cpi := snap.MinimalSnapContainerPlaceInfo(ksnapName, ksnapRev) - if err := kernel.EnsureKernelDriversTree(ksnapName, ksnapRev, - cpi.MountDir(), finalComps, + destDir := kernel.DriversTreeDir(dirs.GlobalRootDir, ksnapName, ksnapRev) + finalCompsConts := make([]snap.ContainerPlaceInfo, len(finalComps)) + for i, csi := range finalComps { + finalCompsConts[i] = snap.MinimalComponentContainerPlaceInfo(csi.Component.ComponentName, + csi.Revision, ksnapName) + } + + if err := kernel.EnsureKernelDriversTree(cpi.MountDir(), destDir, finalCompsConts, &kernel.KernelDriversTreeOptions{KernelInstall: false}); err != nil { - if e := kernel.EnsureKernelDriversTree(ksnapName, ksnapRev, - cpi.MountDir(), - currentComps, - &kernel.KernelDriversTreeOptions{ - KernelInstall: false}); e != nil { + currentCompsConts := make([]snap.ContainerPlaceInfo, len(currentComps)) + for i, csi := range currentComps { + currentCompsConts[i] = snap.MinimalComponentContainerPlaceInfo( + csi.Component.ComponentName, csi.Revision, ksnapName) + } + if e := kernel.EnsureKernelDriversTree(cpi.MountDir(), destDir, currentCompsConts, + &kernel.KernelDriversTreeOptions{KernelInstall: false}); e != nil { logger.Noticef("while restoring kernel tree %s: %v", cleanErrMsg, e) } diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index f52f4ad1b45..b1280b7e1f3 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -552,7 +552,7 @@ func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int } // This task is necessary only for UC20+ and hybrid - if snapsup.Type == snap.TypeKernel && needsKernelSetup(deviceCtx) { + if snapsup.Type == snap.TypeKernel && NeedsKernelSetup(deviceCtx) { setupKernel := st.NewTask("prepare-kernel-snap", fmt.Sprintf(i18n.G("Prepare kernel driver tree for %q%s"), snapsup.InstanceName(), revisionStr)) addTask(setupKernel) prev = setupKernel @@ -616,10 +616,10 @@ func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int addTask(autoConnect) prev = autoConnect - if snapsup.Type == snap.TypeKernel && needsKernelSetup(deviceCtx) { + if snapsup.Type == snap.TypeKernel && NeedsKernelSetup(deviceCtx) { // This task needs to run after we're back and running the new // kernel after a reboot was requested in link-snap handler. - setupKernel := st.NewTask("discard-old-kernel-snap-setup", fmt.Sprintf(i18n.G("Discard kernel driver tree for %q%s"), snapsup.InstanceName(), revisionStr)) + setupKernel := st.NewTask("discard-old-kernel-snap-setup", fmt.Sprintf(i18n.G("Discard previous kernel driver tree for %q%s"), snapsup.InstanceName(), revisionStr)) addTask(setupKernel) prev = setupKernel } @@ -794,7 +794,7 @@ func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int return installSet, nil } -func needsKernelSetup(devCtx DeviceContext) bool { +func NeedsKernelSetup(devCtx DeviceContext) bool { // Must be UC20+ or hybrid if !devCtx.HasModeenv() { return false @@ -3359,7 +3359,7 @@ func LinkNewBaseOrKernel(st *state.State, name string, fromChange string) (*stat if err != nil { return nil, err } - if needsKernelSetup(deviceCtx) { + if NeedsKernelSetup(deviceCtx) { 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()) @@ -3418,7 +3418,7 @@ func AddLinkNewBaseOrKernel(st *state.State, ts *state.TaskSet) (*state.TaskSet, if err != nil { return nil, err } - if needsKernelSetup(deviceCtx) { + if NeedsKernelSetup(deviceCtx) { 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/snap/naming/componentref.go b/snap/naming/componentref.go index 3ae05d50c21..e6e1cdf054c 100644 --- a/snap/naming/componentref.go +++ b/snap/naming/componentref.go @@ -35,6 +35,16 @@ func NewComponentRef(snapName, componentName string) ComponentRef { return ComponentRef{SnapName: snapName, ComponentName: componentName} } +// SplitFullComponentName splits + in and strings. +func SplitFullComponentName(fullComp string) (string, string, error) { + names := strings.Split(fullComp, "+") + if len(names) != 2 { + return "", "", fmt.Errorf("incorrect component name %q", fullComp) + } + + return names[0], names[1], nil +} + func (cr ComponentRef) String() string { return fmt.Sprintf("%s+%s", cr.SnapName, cr.ComponentName) } @@ -56,12 +66,12 @@ func (cid *ComponentRef) UnmarshalYAML(unmarshall func(interface{}) error) error return err } - names := strings.Split(idStr, "+") - if len(names) != 2 { - return fmt.Errorf("incorrect component name %q", idStr) + snap, comp, err := SplitFullComponentName(idStr) + if err != nil { + return err } - *cid = ComponentRef{SnapName: names[0], ComponentName: names[1]} + *cid = ComponentRef{SnapName: snap, ComponentName: comp} return nil } diff --git a/snap/naming/componentref_test.go b/snap/naming/componentref_test.go index 97a058031ea..d0043b5d2f9 100644 --- a/snap/naming/componentref_test.go +++ b/snap/naming/componentref_test.go @@ -20,6 +20,8 @@ package naming_test import ( + "fmt" + . "gopkg.in/check.v1" "gopkg.in/yaml.v2" @@ -54,3 +56,32 @@ func (s *componentRefSuite) TestUnmarshal(c *C) { yamlData = []byte(`mysnap`) c.Check(yaml.UnmarshalStrict(yamlData, &cr).Error(), Equals, `incorrect component name "mysnap"`) } + +func (s *componentRefSuite) TestSplitFullComponentNameOk(c *C) { + for _, tc := range []naming.ComponentRef{ + naming.NewComponentRef("foo", "foo-comp"), + naming.NewComponentRef("a-b_c", "d_j-p"), + naming.NewComponentRef("_", "c"), + } { + snap, comp, err := naming.SplitFullComponentName(tc.String()) + c.Logf("testing %q", tc) + c.Assert(err, IsNil) + c.Check(snap, Equals, tc.SnapName) + c.Check(comp, Equals, tc.ComponentName) + } +} + +func (s *componentRefSuite) TestSplitFullComponentNameErr(c *C) { + for _, tc := range []string{ + "blah", + "snap++comp", + "ff-rb", + } { + c.Logf("testing %q", tc) + snap, comp, err := naming.SplitFullComponentName(tc) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, fmt.Sprintf("incorrect component name %q", tc)) + c.Check(snap, Equals, "") + c.Check(comp, Equals, "") + } +} diff --git a/tests/lib/nested.sh b/tests/lib/nested.sh index e530c6cd7b0..055299ff655 100755 --- a/tests/lib/nested.sh +++ b/tests/lib/nested.sh @@ -1318,9 +1318,13 @@ nested_start_core_vm_unit() { # Wait for cloud init to be done if the system is using cloud-init if [ "$NESTED_USE_CLOUD_INIT" = true ]; then if ! remote.exec "retry --wait 1 -n 5 sh -c 'cloud-init status --wait'"; then - # In uc24 the command `cloud-init status --wait` fails even when - # the status is done. - remote.exec "cloud-init status" | MATCH "status: done" + # Error 2 means 'recoverable error', ignore that case + ret=0 + remote.exec "cloud-init status" || ret=$? + if "$ret" -ne 0 && "$ret" -ne 2; then + echo "cloud-init finished with error $ret" + exit 1 + fi fi fi fi diff --git a/tests/lib/uc20-create-partitions/main.go b/tests/lib/uc20-create-partitions/main.go index de9f8fd4919..a386134eeaa 100644 --- a/tests/lib/uc20-create-partitions/main.go +++ b/tests/lib/uc20-create-partitions/main.go @@ -32,6 +32,7 @@ import ( "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/secboot" "github.com/snapcore/snapd/secboot/keys" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/timings" ) @@ -42,9 +43,11 @@ type cmdCreatePartitions struct { Encrypt bool `long:"encrypt" description:"Encrypt the data partition"` Positional struct { - GadgetRoot string `positional-arg-name:""` - KernelRoot string `positional-arg-name:""` - Device string `positional-arg-name:""` + GadgetRoot string `positional-arg-name:""` + KernelName string `positional-arg-name:""` + KernelRoot string `positional-arg-name:""` + KernelRevision string `positional-arg-name:""` + Device string `positional-arg-name:""` } `positional-args:"yes"` } @@ -82,7 +85,13 @@ func main() { Mount: args.Mount, EncryptionType: encryptionType, } - installSideData, err := installRun(uc20Constraints{}, args.Positional.GadgetRoot, args.Positional.KernelRoot, args.Positional.Device, options, obs, timings.New(nil)) + kernelSnapInfo := &install.KernelSnapInfo{ + Name: args.Positional.KernelName, + MountPoint: args.Positional.KernelRoot, + Revision: snap.R(args.Positional.KernelRevision), + IsCore: true, + } + installSideData, err := installRun(uc20Constraints{}, args.Positional.GadgetRoot, kernelSnapInfo, args.Positional.Device, options, obs, timings.New(nil)) if err != nil { panic(err) } diff --git a/tests/main/uc20-create-partitions-encrypt/task.yaml b/tests/main/uc20-create-partitions-encrypt/task.yaml index e1dd10ac18d..2eb81067518 100644 --- a/tests/main/uc20-create-partitions-encrypt/task.yaml +++ b/tests/main/uc20-create-partitions-encrypt/task.yaml @@ -150,7 +150,7 @@ execute: | echo "Run the snap-bootstrap tool" uc20-create-partitions \ --encrypt \ - ./gadget-dir "$kerneldir" "$LOOP" + ./gadget-dir pc-kernel "$kerneldir" x1 "$LOOP" # keep for later echo "Check that the ubuntu-data key files were created" diff --git a/tests/main/uc20-create-partitions-reinstall/task.yaml b/tests/main/uc20-create-partitions-reinstall/task.yaml index e0f597ba4df..1c7b3f87ef6 100644 --- a/tests/main/uc20-create-partitions-reinstall/task.yaml +++ b/tests/main/uc20-create-partitions-reinstall/task.yaml @@ -97,7 +97,7 @@ execute: | # TODO:UC20: make kernel-dir non-empty once we have a gadget that has a # "$kernel:" style ref in the meta/gadget.yaml kerneldir="" - uc20-create-partitions ./gadget-dir "$kerneldir" + uc20-create-partitions ./gadget-dir pc-kernel "$kerneldir" x1 echo "And check that the partitions are created" sfdisk -l "$LOOP" | MATCH '750M Linux filesystem' @@ -119,7 +119,7 @@ execute: | # re-create partitions on a new install attempt echo "Run the snap-bootstrap again" - uc20-create-partitions ./gadget-dir "$kerneldir" + uc20-create-partitions ./gadget-dir pc-kernel "$kerneldir" x1 echo "And check that the partitions are there" sfdisk -l "$LOOP" | MATCH '750M Linux filesystem' diff --git a/tests/main/uc20-create-partitions/task.yaml b/tests/main/uc20-create-partitions/task.yaml index 377e3836889..4d84b709f6d 100644 --- a/tests/main/uc20-create-partitions/task.yaml +++ b/tests/main/uc20-create-partitions/task.yaml @@ -112,7 +112,7 @@ execute: | # TODO:UC20: make kernel-dir non-empty once we have a gadget that has a # "$kernel:" style ref in the meta/gadget.yaml kerneldir="" - uc20-create-partitions ./gadget-dir "$kerneldir" "$LOOP" + uc20-create-partitions ./gadget-dir pc-kernel "$kerneldir" x1 "$LOOP" echo "And check that the partitions are created" sfdisk -l "$LOOP" | MATCH '750M Linux filesystem' @@ -185,14 +185,14 @@ execute: | filesystem-label: other-ext4 size: 110M EOF - uc20-create-partitions ./gadget-dir "$kerneldir" "$LOOP" 2>&1 | + uc20-create-partitions ./gadget-dir pc-kernel "$kerneldir" x1 "$LOOP" 2>&1 | MATCH "panic: gadget and system-boot device ${LOOP} partition table not compatible: cannot find gadget structure \"Other ext4\" on disk" # replace the gadget.yaml without the extra partition cp gadget.yaml.backup gadget-dir/meta/gadget.yaml echo "Ensure we can deploy with mounting" - uc20-create-partitions --mount ./gadget-dir "$kerneldir" "$LOOP" + uc20-create-partitions --mount ./gadget-dir pc-kernel "$kerneldir" x1 "$LOOP" sfdisk -l "$LOOP" | MATCH "${LOOP}p1 .* 1M\s* BIOS boot" sfdisk -l "$LOOP" | MATCH "${LOOP}p2 .* 1\.2G\s* EFI System" sfdisk -l "$LOOP" | MATCH "${LOOP}p3 .* 750M\s* Linux filesystem" diff --git a/tests/nested/manual/kernel-modules-components/component.yaml b/tests/nested/manual/kernel-modules-components/component.yaml new file mode 100644 index 00000000000..6f7de4a6b6b --- /dev/null +++ b/tests/nested/manual/kernel-modules-components/component.yaml @@ -0,0 +1,5 @@ +component: pc-kernel+wifi-comp +type: kernel-modules +version: 1.0 +summary: wifi simulator +description: wifi simulator for testing purposes diff --git a/tests/nested/manual/kernel-modules-components/task.yaml b/tests/nested/manual/kernel-modules-components/task.yaml new file mode 100644 index 00000000000..7d6e6c47d3a --- /dev/null +++ b/tests/nested/manual/kernel-modules-components/task.yaml @@ -0,0 +1,68 @@ +summary: verify kernel modules components work as expected +details: | + Install a kernel-modules component and verify that the shipped + kernel module is installed. + +systems: [ubuntu-24.04-64] +environment: + NESTED_REPACK_KERNEL_SNAP: false + # TODO No FDE test for the moment (we would need to sign the built kernel) + NESTED_ENABLE_SECURE_BOOT: false + NESTED_ENABLE_TPM: false + +prepare: | + # Modify kernel and create a component + snap download --channel=24 pc-kernel + unsquashfs -d kernel pc-kernel_*.snap + kern_ver=$(find kernel/modules/* -maxdepth 0 -printf "%f\n") + comp_ko_dir=wifi-comp/modules/"$kern_ver"/wireless/ + mkdir -p "$comp_ko_dir" + mkdir -p wifi-comp/meta/ + cp component.yaml wifi-comp/meta/ + hwsim_path=$(find kernel -name mac80211_hwsim.ko\*) + cp "$hwsim_path" "$comp_ko_dir" + snap pack wifi-comp + + # Create kernel without the kernel module + rm "$hwsim_path" + # depmod wants a lib subdir, fake it and remove after invocation + mkdir kernel/lib + ln -s ../modules kernel/lib/modules + depmod -b kernel/ "$kern_ver" + rm -rf kernel/lib + rm pc-kernel_*.snap + # append component meta-information + printf 'components:\n wifi-comp:\n type: kernel-modules\n' >> kernel/meta/snap.yaml + snap pack kernel + + cp pc-kernel_*.snap "$(tests.nested get extra-snaps-path)" + tests.nested build-image core + tests.nested create-vm core + +execute: | + # Compare times to check that drivers tree was created on + # installation, not on seeding + # shellcheck disable=SC2016 + tree_birth=$(remote.exec 'date -d"$(stat --printf="%w\n" /var/lib/snapd/kernel/pc-kernel)" +%s') + reboot_time=$(remote.exec 'last reboot --time-format full | sed -n "s/wtmp begins //p"') + reboot_time=$(date -d"$reboot_time" +%s) + test "$reboot_time" -gt "$tree_birth" + + # Loading the module fails + not remote.exec modprobe mac80211_hwsim + + # install the component + comp_file=pc-kernel+wifi-comp_1.0.comp + remote.push "$comp_file" + remote.exec sudo snap install --dangerous "$comp_file" + + # check that the component is in place + kern_ver=$(remote.exec uname -r) + comp_install_dir=/var/lib/snapd/kernel/pc-kernel/x1/lib/modules/"$kern_ver"/updates/wifi-comp + comp_dir=/snap/pc-kernel/components/mnt/wifi-comp/x1/modules/"$kern_ver" + test "$(remote.exec readlink -f "$comp_install_dir")" = "$comp_dir" + + # TODO check that module can be loaded + # This can be done uncommented changes in initramfs are in the kernel snap + # remote.exec modprobe mac80211_hwsim +