diff --git a/kernel/kernel.go b/kernel/kernel.go
index d7dd00c08b2..0c5f70115cc 100644
--- a/kernel/kernel.go
+++ b/kernel/kernel.go
@@ -25,7 +25,9 @@ import (
"os"
"path/filepath"
"regexp"
+ "strings"
+ "github.com/snapcore/snapd/snap"
"gopkg.in/yaml.v2"
)
@@ -77,3 +79,23 @@ func ReadInfo(kernelSnapRootDir string) (*Info, error) {
}
return InfoFromKernelYaml(content)
}
+
+// KernelVersionFromPlaceInfo returns the kernel version for a mounted kernel
+// snap (this would be the output if "uname -r" for a running kernel).
+func KernelVersionFromPlaceInfo(spi snap.PlaceInfo) (string, error) {
+ systemMapPathPrefix := filepath.Join(spi.MountDir(), "System.map-")
+ matchPath := systemMapPathPrefix + "*"
+ matches, err := filepath.Glob(matchPath)
+ if err != nil {
+ // could be only ErrBadPattern, should not really happen
+ return "", fmt.Errorf("internal error: %w", err)
+ }
+ if len(matches) != 1 {
+ return "", fmt.Errorf("unexpected number of matches (%d) for glob %s", len(matches), matchPath)
+ }
+ version := strings.TrimPrefix(matches[0], systemMapPathPrefix)
+ if version == "" {
+ return "", fmt.Errorf("kernel version not set in %s", matches[0])
+ }
+ return version, nil
+}
diff --git a/kernel/kernel_test.go b/kernel/kernel_test.go
index fa8a7056db7..f9a804b3f62 100644
--- a/kernel/kernel_test.go
+++ b/kernel/kernel_test.go
@@ -26,7 +26,10 @@ import (
. "gopkg.in/check.v1"
+ "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/kernel"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/testutil"
)
func makeMockKernel(c *C, kernelYaml string, filesWithContent map[string]string) string {
@@ -47,9 +50,18 @@ func makeMockKernel(c *C, kernelYaml string, filesWithContent map[string]string)
return kernelRootDir
}
-type kernelYamlTestSuite struct{}
+type kernelTestSuite struct {
+ testutil.BaseTest
+}
+
+var _ = Suite(&kernelTestSuite{})
-var _ = Suite(&kernelYamlTestSuite{})
+func (s *kernelTestSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+
+ dirs.SetRootDir(c.MkDir())
+ s.AddCleanup(func() { dirs.SetRootDir("") })
+}
func TestCommand(t *testing.T) { TestingT(t) }
@@ -67,19 +79,19 @@ assets:
non-#alphanumeric:
`)
-func (s *kernelYamlTestSuite) TestInfoFromKernelYamlSad(c *C) {
+func (s *kernelTestSuite) TestInfoFromKernelYamlSad(c *C) {
ki, err := kernel.InfoFromKernelYaml([]byte("foo"))
c.Check(err, ErrorMatches, "(?m)cannot parse kernel metadata: .*")
c.Check(ki, IsNil)
}
-func (s *kernelYamlTestSuite) TestInfoFromKernelYamlBadName(c *C) {
+func (s *kernelTestSuite) TestInfoFromKernelYamlBadName(c *C) {
ki, err := kernel.InfoFromKernelYaml(mockInvalidKernelYaml)
c.Check(err, ErrorMatches, `invalid asset name "non-#alphanumeric", please use only alphanumeric characters and dashes`)
c.Check(ki, IsNil)
}
-func (s *kernelYamlTestSuite) TestInfoFromKernelYamlHappy(c *C) {
+func (s *kernelTestSuite) TestInfoFromKernelYamlHappy(c *C) {
ki, err := kernel.InfoFromKernelYaml(mockKernelYaml)
c.Check(err, IsNil)
c.Check(ki, DeepEquals, &kernel.Info{
@@ -95,13 +107,13 @@ func (s *kernelYamlTestSuite) TestInfoFromKernelYamlHappy(c *C) {
})
}
-func (s *kernelYamlTestSuite) TestReadKernelYamlOptional(c *C) {
+func (s *kernelTestSuite) TestReadKernelYamlOptional(c *C) {
ki, err := kernel.ReadInfo("this-path-does-not-exist")
c.Check(err, IsNil)
c.Check(ki, DeepEquals, &kernel.Info{})
}
-func (s *kernelYamlTestSuite) TestReadKernelYamlSad(c *C) {
+func (s *kernelTestSuite) TestReadKernelYamlSad(c *C) {
mockKernelSnapRoot := c.MkDir()
kernelYamlPath := filepath.Join(mockKernelSnapRoot, "meta/kernel.yaml")
err := os.MkdirAll(filepath.Dir(kernelYamlPath), 0755)
@@ -114,7 +126,7 @@ func (s *kernelYamlTestSuite) TestReadKernelYamlSad(c *C) {
c.Check(ki, IsNil)
}
-func (s *kernelYamlTestSuite) TestReadKernelYamlHappy(c *C) {
+func (s *kernelTestSuite) TestReadKernelYamlHappy(c *C) {
mockKernelSnapRoot := c.MkDir()
kernelYamlPath := filepath.Join(mockKernelSnapRoot, "meta/kernel.yaml")
err := os.MkdirAll(filepath.Dir(kernelYamlPath), 0755)
@@ -136,3 +148,41 @@ func (s *kernelYamlTestSuite) TestReadKernelYamlHappy(c *C) {
},
})
}
+
+func (s *kernelTestSuite) TestKernelVersionFromPlaceInfo(c *C) {
+ spi := snap.MinimalPlaceInfo("pc-kernel", snap.R(1))
+
+ c.Assert(os.MkdirAll(spi.MountDir(), 0755), IsNil)
+
+ // No map file
+ ver, err := kernel.KernelVersionFromPlaceInfo(spi)
+ c.Check(err, ErrorMatches, `unexpected number of matches \(0\) for glob .*`)
+ c.Check(ver, Equals, "")
+
+ // Create file so kernel version can be found
+ c.Assert(os.WriteFile(filepath.Join(
+ spi.MountDir(), "System.map-5.15.0-78-generic"), []byte{}, 0644), IsNil)
+ ver, err = kernel.KernelVersionFromPlaceInfo(spi)
+ c.Check(err, IsNil)
+ c.Check(ver, Equals, "5.15.0-78-generic")
+
+ // Too many matches
+ c.Assert(os.WriteFile(filepath.Join(
+ spi.MountDir(), "System.map-6.8.0-71-generic"), []byte{}, 0644), IsNil)
+ ver, err = kernel.KernelVersionFromPlaceInfo(spi)
+ c.Check(err, ErrorMatches, `unexpected number of matches \(2\) for glob .*`)
+ c.Check(ver, Equals, "")
+}
+
+func (s *kernelTestSuite) TestKernelVersionFromPlaceInfoNotSetInFile(c *C) {
+ spi := snap.MinimalPlaceInfo("pc-kernel", snap.R(1))
+
+ c.Assert(os.MkdirAll(spi.MountDir(), 0755), IsNil)
+
+ // Create bad file name
+ c.Assert(os.WriteFile(filepath.Join(
+ spi.MountDir(), "System.map-"), []byte{}, 0644), IsNil)
+ ver, err := kernel.KernelVersionFromPlaceInfo(spi)
+ c.Check(err, ErrorMatches, `kernel version not set in .*System\.map\-`)
+ c.Check(ver, Equals, "")
+}
diff --git a/overlord/snapstate/backend.go b/overlord/snapstate/backend.go
index be9b3a2f4ce..39431b8c218 100644
--- a/overlord/snapstate/backend.go
+++ b/overlord/snapstate/backend.go
@@ -80,6 +80,8 @@ type managerBackend interface {
StartServices(svcs []*snap.AppInfo, disabledSvcs []string, meter progress.Meter, tm timings.Measurer) error
StopServices(svcs []*snap.AppInfo, reason snap.ServiceStopReason, meter progress.Meter, tm timings.Measurer) error
QueryDisabledServices(info *snap.Info, pb progress.Meter) ([]string, error)
+ SetupKernelModulesComponent(cpi snap.ContainerPlaceInfo, cref naming.ComponentRef, kernelVersion string, meter progress.Meter) (err error)
+ UndoSetupKernelModulesComponent(cpi snap.ContainerPlaceInfo, cref naming.ComponentRef, kernelVersion string, meter progress.Meter) error
// the undoers for install
UndoSetupSnap(s snap.PlaceInfo, typ snap.Type, installRecord *backend.InstallRecord, dev snap.Device, meter progress.Meter) error
diff --git a/overlord/snapstate/backend/export_test.go b/overlord/snapstate/backend/export_test.go
index 1488cddfc2e..152e25074cd 100644
--- a/overlord/snapstate/backend/export_test.go
+++ b/overlord/snapstate/backend/export_test.go
@@ -73,3 +73,11 @@ func MockMkdirAllChown(f func(string, os.FileMode, sys.UserID, sys.GroupID) erro
mkdirAllChown = old
}
}
+
+func MockRunDepmod(f func(baseDir, kernelVersion string) error) func() {
+ old := runDepmod
+ runDepmod = f
+ return func() {
+ runDepmod = old
+ }
+}
diff --git a/overlord/snapstate/backend/kernel_modules_components.go b/overlord/snapstate/backend/kernel_modules_components.go
new file mode 100644
index 00000000000..d77ee05c3a0
--- /dev/null
+++ b/overlord/snapstate/backend/kernel_modules_components.go
@@ -0,0 +1,219 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 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
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package backend
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/progress"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/naming"
+ "github.com/snapcore/snapd/systemd"
+)
+
+type NoKernelDriversError struct {
+ cref naming.ComponentRef
+ kernelVersion string
+}
+
+func (e NoKernelDriversError) Error() string {
+ return fmt.Sprintf("%s does not contain firmware or components for %s",
+ e.cref, e.kernelVersion)
+}
+
+func cleanupMount(mountDir string, meter progress.Meter) error {
+ mountDir = filepath.Join(dirs.GlobalRootDir, mountDir)
+ // this also ensures that the mount unit stops
+ if err := removeMountUnit(mountDir, meter); err != nil {
+ return err
+ }
+
+ if err := os.RemoveAll(mountDir); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+type kernelModulesCleanupParts struct {
+ compMountDir string
+ modulesMountDir string
+ rerunDepmod bool
+}
+
+func cleanupKernelModulesSetup(parts *kernelModulesCleanupParts, kernelVersion string, meter progress.Meter) error {
+ if parts.modulesMountDir != "" {
+ if err := cleanupMount(parts.modulesMountDir, meter); err != nil {
+ return err
+ }
+ if parts.rerunDepmod {
+ if err := runDepmod("/usr", kernelVersion); err != nil {
+ return err
+ }
+ }
+ }
+
+ if parts.compMountDir != "" {
+ if err := cleanupMount(parts.compMountDir, meter); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func checkKernelModulesCompContent(mountDir, kernelVersion string) (bool, bool) {
+ hasModules := osutil.IsDirectory(filepath.Join(mountDir, "modules", kernelVersion))
+ hasFirmware := osutil.IsDirectory(filepath.Join(mountDir, "firmware"))
+ return hasModules, hasFirmware
+}
+
+func componentMountPoint(componentName, kernelVersion string) string {
+ return filepath.Join("/run/mnt/kernel-modules/", kernelVersion, componentName)
+}
+
+func modulesMountPoint(componentName, kernelVersion string) string {
+ return filepath.Join("/usr/lib/modules", kernelVersion, "updates", componentName)
+}
+
+// SetupKernelModulesComponent creates and starts mount units for
+// kernel-modules components.
+func (b Backend) SetupKernelModulesComponent(cpi snap.ContainerPlaceInfo, cref naming.ComponentRef, kernelVersion string, meter progress.Meter) (err error) {
+ var sysd systemd.Systemd
+ if b.preseed {
+ sysd = systemd.NewEmulationMode(dirs.GlobalRootDir)
+ } else {
+ sysd = systemd.New(systemd.SystemMode, meter)
+ }
+
+ // Restore state if something goes wrong
+ var cleanOnFailure kernelModulesCleanupParts
+ defer func() {
+ if err == nil {
+ return
+ }
+ if err := cleanupKernelModulesSetup(&cleanOnFailure, kernelVersion, meter); err != nil {
+ logger.Noticef("while cleaning up a failed kernel-modules set-up: %v", err)
+ }
+ }()
+
+ // Check that the kernel-modules component really has
+ // something we must mount on early boot.
+ hasModules, hasFirmware := checkKernelModulesCompContent(cpi.MountDir(), kernelVersion)
+ if !hasModules && !hasFirmware {
+ return &NoKernelDriversError{cref: cref, kernelVersion: kernelVersion}
+ }
+
+ // Mount the component itself (we need it early, so the mount in /snap cannot
+ // be used).
+ componentMount := componentMountPoint(cref.ComponentName, kernelVersion)
+ addMountUnitOptions := &systemd.MountUnitOptions{
+ MountUnitType: systemd.BeforeDriversLoadMountUnit,
+ Lifetime: systemd.Persistent,
+ Description: fmt.Sprintf("Mount unit for kernel-modules component %s", cref),
+ What: cpi.MountFile(),
+ Where: componentMount,
+ Fstype: "squashfs",
+ Options: []string{"nodev,ro,x-gdu.hide,x-gvfs-hide"},
+ }
+ _, err = sysd.EnsureMountUnitFileWithOptions(addMountUnitOptions)
+ if err != nil {
+ return fmt.Errorf("cannot create mount in %q: %w", componentMount, err)
+ }
+ cleanOnFailure.compMountDir = componentMount
+
+ if hasModules {
+ // systemd automatically works out dependencies on the "what"
+ // path too so this mount happens after the component one.
+ modulesDir := modulesMountPoint(cref.ComponentName, kernelVersion)
+ addMountUnitOptions = &systemd.MountUnitOptions{
+ MountUnitType: systemd.BeforeDriversLoadMountUnit,
+ Lifetime: systemd.Persistent,
+ Description: fmt.Sprintf("Mount unit for modules from %s", cref.String()),
+ What: filepath.Join(componentMount, "modules", kernelVersion),
+ Where: modulesDir,
+ Fstype: "none",
+ Options: []string{"bind"},
+ }
+ _, err = sysd.EnsureMountUnitFileWithOptions(addMountUnitOptions)
+ if err != nil {
+ return fmt.Errorf("cannot create mount in %q: %w", modulesDir, err)
+ }
+ cleanOnFailure.modulesMountDir = modulesDir
+
+ // Rebuild modinfo files
+ if err := runDepmod("/usr", kernelVersion); err != nil {
+ return err
+ }
+ cleanOnFailure.rerunDepmod = true
+ }
+
+ if hasFirmware {
+ // TODO create recursively symlinks in
+ // /usr/lib/firmware/updates while checking for conflicts with
+ // existing files.
+ }
+
+ return nil
+}
+
+var runDepmod = runDepmodImpl
+
+func runDepmodImpl(baseDir, kernelVersion string) error {
+ logger.Debugf("running depmod on %q for kernel %s", baseDir, kernelVersion)
+ stdout, stderr, err := osutil.RunSplitOutput("depmod", "-b", baseDir, kernelVersion)
+ logger.Debugf("depmod stderr:\n%s\n\ndepmod stdout:\n%s",
+ string(stderr), string(stdout))
+ if err != nil {
+ return osutil.OutputErrCombine(stdout, stderr, err)
+ }
+ return nil
+}
+
+// UndoSetupKernelModulesComponent undoes the work of SetupKernelModulesComponent
+func (b Backend) UndoSetupKernelModulesComponent(cpi snap.ContainerPlaceInfo, cref naming.ComponentRef, kernelVersion string, meter progress.Meter) error {
+ hasModules, hasFirmware := checkKernelModulesCompContent(cpi.MountDir(), kernelVersion)
+ var partsToClean kernelModulesCleanupParts
+ if hasFirmware {
+ // TODO remove recursively symlinks in
+ // /usr/lib/firmware/updates (set var in kernelModulesCleanupParts)
+ }
+
+ // Remove created mount units. If the component had modules we need to
+ // re-create the modules metainformation by running depmod so it is
+ // consistent with the currently available modules.
+ if hasModules {
+ partsToClean.modulesMountDir =
+ modulesMountPoint(cref.ComponentName, kernelVersion)
+ partsToClean.rerunDepmod = true
+ }
+
+ if hasModules || hasFirmware {
+ partsToClean.compMountDir =
+ componentMountPoint(cref.ComponentName, kernelVersion)
+ }
+
+ return cleanupKernelModulesSetup(&partsToClean, kernelVersion, meter)
+}
diff --git a/overlord/snapstate/backend/kernel_modules_components_test.go b/overlord/snapstate/backend/kernel_modules_components_test.go
new file mode 100644
index 00000000000..156d3c4f7ff
--- /dev/null
+++ b/overlord/snapstate/backend/kernel_modules_components_test.go
@@ -0,0 +1,200 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 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
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package backend_test
+
+import (
+ "errors"
+ "os"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/overlord/snapstate/backend"
+ "github.com/snapcore/snapd/progress"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/naming"
+ "github.com/snapcore/snapd/systemd"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type kernelModulesSuite struct {
+ testutil.BaseTest
+
+ be backend.Backend
+ sysctlArgs [][]string
+}
+
+var _ = Suite(&kernelModulesSuite{})
+
+func (s *kernelModulesSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+
+ dirs.SetRootDir(c.MkDir())
+ s.AddCleanup(func() { dirs.SetRootDir("") })
+
+ restore := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
+ s.sysctlArgs = append(s.sysctlArgs, cmd)
+ return []byte{}, nil
+ })
+ s.AddCleanup(restore)
+ s.AddCleanup(func() { s.sysctlArgs = nil })
+
+ s.AddCleanup(backend.MockRunDepmod(func(baseDir, kernelVersion string) error {
+ return nil
+ }))
+
+ s.AddCleanup(osutil.MockMountInfo(""))
+}
+
+func (s *kernelModulesSuite) TestSetupKernelModulesComponentNoModules(c *C) {
+ const snapName = "mysnap"
+ const compName = "mycomp"
+ const kernelVersion = "5.15.0-78-generic"
+
+ cpi := snap.MinimalComponentContainerPlaceInfo(compName, snap.R(33), snapName, snap.R(1))
+ cref := naming.NewComponentRef(snapName, compName)
+
+ err := s.be.SetupKernelModulesComponent(cpi, cref, kernelVersion, progress.Null)
+ c.Assert(err.Error(), Equals,
+ "mysnap+mycomp does not contain firmware or components for 5.15.0-78-generic")
+ _, ok := err.(*backend.NoKernelDriversError)
+ c.Assert(ok, Equals, true)
+}
+
+func (s *kernelModulesSuite) TestSetupKernelModulesComponentWithModules(c *C) {
+ const snapName = "mysnap"
+ const compName = "mycomp"
+ const kernelVersion = "5.15.0-78-generic"
+
+ cpi := snap.MinimalComponentContainerPlaceInfo(compName, snap.R(33), snapName, snap.R(1))
+ cref := naming.NewComponentRef(snapName, compName)
+ // Make directories so the method thinks there are modules
+ compDir := filepath.Join(dirs.SnapMountDir, snapName, "components/1", compName)
+ modsDir := filepath.Join(compDir, "modules", kernelVersion)
+ fwDir := filepath.Join(compDir, "firmware")
+ c.Assert(os.MkdirAll(modsDir, os.ModePerm), IsNil)
+ c.Assert(os.MkdirAll(fwDir, os.ModePerm), IsNil)
+
+ err := s.be.SetupKernelModulesComponent(cpi, cref, kernelVersion, progress.Null)
+ c.Assert(err, IsNil)
+ kmodMountUnit := "run-mnt-kernel\\x2dmodules-5.15.0\\x2d78\\x2dgeneric-mycomp.mount"
+ ktreeMountUnit := "usr-lib-modules-5.15.0\\x2d78\\x2dgeneric-updates-mycomp.mount"
+ c.Assert(s.sysctlArgs, DeepEquals, [][]string{
+ {"daemon-reload"},
+ {"--no-reload", "enable", kmodMountUnit},
+ {"reload-or-restart", kmodMountUnit},
+ {"daemon-reload"},
+ {"--no-reload", "enable", ktreeMountUnit},
+ {"reload-or-restart", ktreeMountUnit},
+ })
+ // Check mount files exist
+ c.Assert(osutil.FileExists(filepath.Join(dirs.SnapServicesDir, kmodMountUnit)), Equals, true)
+ c.Assert(osutil.FileExists(filepath.Join(dirs.SnapServicesDir, ktreeMountUnit)), Equals, true)
+
+ // Now do a clean-up
+ s.sysctlArgs = nil
+ err = s.be.UndoSetupKernelModulesComponent(cpi, cref, kernelVersion, progress.Null)
+ c.Assert(err, IsNil)
+ c.Assert(s.sysctlArgs, DeepEquals, [][]string{
+ {"--no-reload", "disable", ktreeMountUnit},
+ {"daemon-reload"},
+ {"--no-reload", "disable", kmodMountUnit},
+ {"daemon-reload"},
+ })
+ // Check mount files do not exist
+ c.Assert(osutil.FileExists(filepath.Join(dirs.SnapServicesDir, kmodMountUnit)), Equals, false)
+ c.Assert(osutil.FileExists(filepath.Join(dirs.SnapServicesDir, ktreeMountUnit)), Equals, false)
+}
+
+func (s *kernelModulesSuite) TestSetupKernelModulesComponentJustFirmware(c *C) {
+ const snapName = "mysnap"
+ const compName = "mycomp"
+ const kernelVersion = "5.15.0-78-generic"
+
+ cpi := snap.MinimalComponentContainerPlaceInfo(compName, snap.R(33), snapName, snap.R(1))
+ cref := naming.NewComponentRef(snapName, compName)
+ // Make directories so the method thinks there are modules
+ compDir := filepath.Join(dirs.SnapMountDir, snapName, "components/1", compName)
+ fwDir := filepath.Join(compDir, "firmware")
+ c.Assert(os.MkdirAll(fwDir, os.ModePerm), IsNil)
+
+ err := s.be.SetupKernelModulesComponent(cpi, cref, kernelVersion, progress.Null)
+ c.Assert(err, IsNil)
+ kmodMountUnit := "run-mnt-kernel\\x2dmodules-5.15.0\\x2d78\\x2dgeneric-mycomp.mount"
+ c.Assert(s.sysctlArgs, DeepEquals, [][]string{
+ {"daemon-reload"},
+ {"--no-reload", "enable", kmodMountUnit},
+ {"reload-or-restart", kmodMountUnit},
+ })
+ // Check mount files exist
+ c.Assert(osutil.FileExists(filepath.Join(dirs.SnapServicesDir, kmodMountUnit)), Equals, true)
+
+ // Now do a clean-up
+ s.sysctlArgs = nil
+ err = s.be.UndoSetupKernelModulesComponent(cpi, cref, kernelVersion, progress.Null)
+ c.Assert(err, IsNil)
+ c.Assert(s.sysctlArgs, DeepEquals, [][]string{
+ {"--no-reload", "disable", kmodMountUnit},
+ {"daemon-reload"},
+ })
+ // Check mount files do not exist
+ c.Assert(osutil.FileExists(filepath.Join(dirs.SnapServicesDir, kmodMountUnit)), Equals, false)
+}
+
+func (s *kernelModulesSuite) TestSetupKernelModulesComponentDepmodFailed(c *C) {
+ const snapName = "mysnap"
+ const compName = "mycomp"
+ const kernelVersion = "5.15.0-78-generic"
+
+ cpi := snap.MinimalComponentContainerPlaceInfo(compName, snap.R(33), snapName, snap.R(1))
+ cref := naming.NewComponentRef(snapName, compName)
+ // Make directories so the method thinks there are modules
+ compDir := filepath.Join(dirs.SnapMountDir, snapName, "components/1", compName)
+ modsDir := filepath.Join(compDir, "modules", kernelVersion)
+ fwDir := filepath.Join(compDir, "firmware")
+ c.Assert(os.MkdirAll(modsDir, os.ModePerm), IsNil)
+ c.Assert(os.MkdirAll(fwDir, os.ModePerm), IsNil)
+ // make depmod fail
+ s.AddCleanup(backend.MockRunDepmod(func(baseDir, kernelVersion string) error {
+ return errors.New("depmod failure")
+ }))
+
+ err := s.be.SetupKernelModulesComponent(cpi, cref, kernelVersion, progress.Null)
+ c.Assert(err.Error(), Equals, "depmod failure")
+ kmodMountUnit := "run-mnt-kernel\\x2dmodules-5.15.0\\x2d78\\x2dgeneric-mycomp.mount"
+ ktreeMountUnit := "usr-lib-modules-5.15.0\\x2d78\\x2dgeneric-updates-mycomp.mount"
+ c.Assert(s.sysctlArgs, DeepEquals, [][]string{
+ {"daemon-reload"},
+ {"--no-reload", "enable", kmodMountUnit},
+ {"reload-or-restart", kmodMountUnit},
+ {"daemon-reload"},
+ {"--no-reload", "enable", ktreeMountUnit},
+ {"reload-or-restart", ktreeMountUnit},
+ {"--no-reload", "disable", ktreeMountUnit},
+ {"daemon-reload"},
+ {"--no-reload", "disable", kmodMountUnit},
+ {"daemon-reload"},
+ })
+ // Check mount files do not exist
+ c.Assert(osutil.FileExists(filepath.Join(dirs.SnapServicesDir, kmodMountUnit)), Equals, false)
+ c.Assert(osutil.FileExists(filepath.Join(dirs.SnapServicesDir, ktreeMountUnit)), Equals, false)
+}
diff --git a/overlord/snapstate/backend_test.go b/overlord/snapstate/backend_test.go
index 67e9348e746..20391d2a8f8 100644
--- a/overlord/snapstate/backend_test.go
+++ b/overlord/snapstate/backend_test.go
@@ -43,6 +43,7 @@ import (
"github.com/snapcore/snapd/progress"
"github.com/snapcore/snapd/randutil"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/naming"
"github.com/snapcore/snapd/snap/snapfile"
"github.com/snapcore/snapd/store"
"github.com/snapcore/snapd/store/storetest"
@@ -84,6 +85,13 @@ type fakeOp struct {
dirOpts *dirs.SnapDirOptions
undoInfo *backend.UndoInfo
+
+ // Used for component related interface calls
+ compFilePath string
+ compMountDir string
+ compMountFile string
+ cref naming.ComponentRef
+ kernelVersion string
}
type fakeOps []fakeOp
@@ -904,7 +912,10 @@ func (f *fakeSnappyBackend) SetupSnap(snapFilePath, instanceName string, si *sna
func (f *fakeSnappyBackend) SetupComponent(compFilePath string, compPi snap.ContainerPlaceInfo, dev snap.Device, meter progress.Meter) (installRecord *backend.InstallRecord, err error) {
meter.Notify("setup-component")
f.appendOp(&fakeOp{
- op: "setup-component",
+ op: "setup-component",
+ compFilePath: compFilePath,
+ compMountDir: compPi.MountDir(),
+ compMountFile: compPi.MountFile(),
})
if strings.HasSuffix(compPi.ContainerName(), "+broken") {
return nil, fmt.Errorf("cannot set-up component %q", compPi.ContainerName())
@@ -912,10 +923,36 @@ func (f *fakeSnappyBackend) SetupComponent(compFilePath string, compPi snap.Cont
return &backend.InstallRecord{}, nil
}
+func (f *fakeSnappyBackend) SetupKernelModulesComponent(cpi snap.ContainerPlaceInfo, cref naming.ComponentRef, kernelVersion string, meter progress.Meter) (err error) {
+ meter.Notify("setup-kernel-modules-component")
+ f.appendOp(&fakeOp{
+ op: "setup-kernel-modules-component",
+ compMountDir: cpi.MountDir(),
+ compMountFile: cpi.MountFile(),
+ cref: cref,
+ kernelVersion: kernelVersion,
+ })
+ return nil
+}
+
+func (f *fakeSnappyBackend) UndoSetupKernelModulesComponent(cpi snap.ContainerPlaceInfo, cref naming.ComponentRef, kernelVersion string, meter progress.Meter) error {
+ meter.Notify("undo-setup-kernel-modules-component")
+ f.appendOp(&fakeOp{
+ op: "undo-setup-kernel-modules-component",
+ compMountDir: cpi.MountDir(),
+ compMountFile: cpi.MountFile(),
+ cref: cref,
+ kernelVersion: kernelVersion,
+ })
+ return nil
+}
+
func (f *fakeSnappyBackend) UndoSetupComponent(cpi snap.ContainerPlaceInfo, installRecord *backend.InstallRecord, dev snap.Device, meter progress.Meter) error {
meter.Notify("undo-setup-component")
f.appendOp(&fakeOp{
- op: "undo-setup-component",
+ op: "undo-setup-component",
+ compMountDir: cpi.MountDir(),
+ compMountFile: cpi.MountFile(),
})
if strings.HasSuffix(cpi.ContainerName(), "+brokenundo") {
return fmt.Errorf("cannot undo set-up of component %q", cpi.ContainerName())
@@ -925,7 +962,9 @@ func (f *fakeSnappyBackend) UndoSetupComponent(cpi snap.ContainerPlaceInfo, inst
func (f *fakeSnappyBackend) RemoveComponentDir(cpi snap.ContainerPlaceInfo) error {
f.appendOp(&fakeOp{
- op: "remove-component-dir",
+ op: "remove-component-dir",
+ compMountDir: cpi.MountDir(),
+ compMountFile: cpi.MountFile(),
})
return nil
}
diff --git a/overlord/snapstate/component.go b/overlord/snapstate/component.go
index d7a8c9cd85d..cf16270bd0d 100644
--- a/overlord/snapstate/component.go
+++ b/overlord/snapstate/component.go
@@ -168,6 +168,13 @@ func doInstallComponent(st *state.State, snapst *SnapState, compSetup *Component
}
}
+ if compSetup.CompType == snap.KernelModulesComponent {
+ setupKernMod := st.NewTask("setup-kernel-modules-component",
+ fmt.Sprintf(i18n.G("Set-up kernel-modules component %q%s"),
+ compSi.Component, revisionStr))
+ addTask(setupKernMod)
+ }
+
// TODO hooks for components
// We might be replacing a component if a local install, otherwise
diff --git a/overlord/snapstate/component_install_test.go b/overlord/snapstate/component_install_test.go
index 1f493fb42ac..c439edc62b9 100644
--- a/overlord/snapstate/component_install_test.go
+++ b/overlord/snapstate/component_install_test.go
@@ -41,6 +41,8 @@ const (
compOptRevisionPresent
// Component revision is used by the currently active snap revision
compOptIsActive
+ // The component is of kernel-modules type
+ compOptKernelModules
)
// opts is a bitset with compOpt* as possible values.
@@ -56,6 +58,10 @@ func expectedComponentInstallTasks(opts int) []string {
if opts&compOptRevisionPresent == 0 {
startTasks = append(startTasks, "mount-component")
}
+ // This task is only for kernel-modules
+ if opts&compOptKernelModules != 0 {
+ startTasks = append(startTasks, "setup-kernel-modules-component")
+ }
// Component is installed (implicit if compOptRevisionPresent is set)
if opts&compOptIsActive != 0 {
startTasks = append(startTasks, "unlink-current-component")
@@ -103,11 +109,11 @@ func verifyComponentInstallTasks(c *C, opts int, ts *state.TaskSet) {
}
}
-func createTestComponent(c *C, snapName, compName string) (*snap.ComponentInfo, string) {
+func createComponent(c *C, snapName, compName string, compType snap.ComponentType) (*snap.ComponentInfo, string) {
componentYaml := fmt.Sprintf(`component: %s+%s
-type: test
+type: %s
version: 1.0
-`, snapName, compName)
+`, snapName, compName, compType)
compPath := snaptest.MakeTestComponent(c, componentYaml)
compf, err := snapfile.Open(compPath)
c.Assert(err, IsNil)
@@ -118,14 +124,18 @@ version: 1.0
return ci, compPath
}
-func createTestSnapInfoForComponent(c *C, snapName string, snapRev snap.Revision, compName string) *snap.Info {
+func createTestComponent(c *C, snapName, compName string) (*snap.ComponentInfo, string) {
+ return createComponent(c, snapName, compName, snap.TestComponent)
+}
+
+func createSnapInfoForComponent(c *C, snapName string, snapRev snap.Revision, compName string, compType snap.ComponentType) *snap.Info {
snapYaml := fmt.Sprintf(`name: %s
type: app
version: 1.1
components:
%s:
- type: test
-`, snapName, compName)
+ type: %s
+`, snapName, compName, compType)
info, err := snap.InfoFromSnapYaml([]byte(snapYaml))
c.Assert(err, IsNil)
info.SideInfo = snap.SideInfo{RealName: snapName, Revision: snapRev}
@@ -133,6 +143,10 @@ components:
return info
}
+func createTestSnapInfoForComponent(c *C, snapName string, snapRev snap.Revision, compName string) *snap.Info {
+ return createSnapInfoForComponent(c, snapName, snapRev, compName, snap.TestComponent)
+}
+
func createTestSnapSetup(info *snap.Info, flags snapstate.Flags) *snapstate.SnapSetup {
return &snapstate.SnapSetup{
Base: info.Base,
@@ -202,6 +216,30 @@ func (s *snapmgrTestSuite) TestInstallComponentPath(c *C) {
c.Assert(osutil.FileExists(compPath), Equals, true)
}
+func (s *snapmgrTestSuite) TestInstallKernelModulesComponentPath(c *C) {
+ const snapName = "mysnap"
+ const compName = "mycomp"
+ snapRev := snap.R(1)
+ _, compPath := createComponent(c, snapName, compName, snap.KernelModulesComponent)
+ info := createSnapInfoForComponent(c, snapName, snapRev, compName, snap.KernelModulesComponent)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ setStateWithOneSnap(s.state, snapName, snapRev)
+
+ csi := snap.NewComponentSideInfo(naming.ComponentRef{
+ SnapName: snapName, ComponentName: compName}, snap.R(33))
+ ts, err := snapstate.InstallComponentPath(s.state, csi, info, compPath,
+ snapstate.Flags{})
+ c.Assert(err, IsNil)
+
+ verifyComponentInstallTasks(c, compOptIsLocal|compOptKernelModules, ts)
+ c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks()))
+ // File is not deleted
+ c.Assert(osutil.FileExists(compPath), Equals, true)
+}
+
func (s *snapmgrTestSuite) TestInstallComponentPathWrongComponent(c *C) {
const snapName = "mysnap"
const compName = "mycomp"
@@ -336,6 +374,33 @@ func (s *snapmgrTestSuite) TestInstallComponentPathCompRevisionPresent(c *C) {
c.Assert(osutil.FileExists(compPath), Equals, false)
}
+func (s *snapmgrTestSuite) TestInstallKernelModulesComponentPathCompPresent(c *C) {
+ const snapName = "mysnap"
+ const compName = "mycomp"
+ snapRev := snap.R(1)
+ currentCompRev := snap.R(5)
+ compRev := snap.R(7)
+ _, compPath := createComponent(c, snapName, compName, snap.KernelModulesComponent)
+ info := createSnapInfoForComponent(c, snapName, snapRev, compName, snap.KernelModulesComponent)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ // There is already an installed component
+ setStateWithOneComponent(s.state, snapName, snapRev, compName, currentCompRev)
+
+ csi := snap.NewComponentSideInfo(naming.ComponentRef{
+ SnapName: snapName, ComponentName: compName}, compRev)
+ ts, err := snapstate.InstallComponentPath(s.state, csi, info, compPath,
+ snapstate.Flags{})
+ c.Assert(err, IsNil)
+
+ verifyComponentInstallTasks(c, compOptIsLocal|compOptKernelModules|compOptIsActive, ts)
+ c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks()))
+ // File is not deleted
+ c.Assert(osutil.FileExists(compPath), Equals, true)
+}
+
func (s *snapmgrTestSuite) TestInstallComponentPathCompRevisionPresentDiffSnapRev(c *C) {
const snapName = "mysnap"
const compName = "mycomp"
diff --git a/overlord/snapstate/handlers_components.go b/overlord/snapstate/handlers_components.go
index 44fe4f26cc6..b83a4bf5083 100644
--- a/overlord/snapstate/handlers_components.go
+++ b/overlord/snapstate/handlers_components.go
@@ -24,6 +24,7 @@ import (
"fmt"
"time"
+ "github.com/snapcore/snapd/kernel"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/overlord/snapstate/backend"
"github.com/snapcore/snapd/overlord/snapstate/sequence"
@@ -396,3 +397,208 @@ func (m *SnapManager) undoUnlinkCurrentComponent(t *state.Task, _ *tomb.Tomb) (e
return nil
}
+
+func (m *SnapManager) doSetupKernelModulesComponent(t *state.Task, _ *tomb.Tomb) error {
+ // kernel snap is expected to be mounted
+ st := t.State()
+ st.Lock()
+ defer st.Unlock()
+ perfTimings := state.TimingsForTask(t)
+
+ compSetup, snapsup, snapSt, err := compSetupAndState(t)
+ if err != nil {
+ return err
+ }
+
+ csi := compSetup.CompSideInfo
+ cpi := snap.MinimalComponentContainerPlaceInfo(compSetup.ComponentName(),
+ csi.Revision, snapsup.InstanceName(), snapsup.Revision())
+ pm := NewTaskProgressAdapterUnlocked(t)
+
+ // Find kernel version from matching kernel
+ spi := snap.MinimalPlaceInfo(snapsup.InstanceName(), snapsup.Revision())
+ kernelVersion, err := kernel.KernelVersionFromPlaceInfo(spi)
+ if err != nil {
+ return err
+ }
+
+ st.Unlock()
+ timings.Run(perfTimings, "setup-kernel-modules-component",
+ fmt.Sprintf("setup of kernel-modules component %q", csi.Component),
+ func(timings.Measurer) {
+ err = m.backend.SetupKernelModulesComponent(cpi, csi.Component,
+ kernelVersion, pm)
+ })
+ st.Lock()
+ if err != nil {
+ // We need to restore the units for the previous component
+ currentCsi := snapSt.CurrentComponentSideInfo(csi.Component)
+ if currentCsi != nil {
+ currentCpi := snap.MinimalComponentContainerPlaceInfo(
+ currentCsi.Component.ComponentName,
+ currentCsi.Revision, snapsup.InstanceName(), snapsup.Revision())
+ var restoreErr error
+ timings.Run(perfTimings, "setup-kernel-modules-component",
+ fmt.Sprintf("restore setup of kernel-modules component %q", currentCsi.Component),
+ func(timings.Measurer) {
+ restoreErr = m.backend.SetupKernelModulesComponent(currentCpi, currentCsi.Component,
+ kernelVersion, pm)
+ })
+ if restoreErr != nil {
+ t.Logf("error while restoring previous component: %v", restoreErr)
+ }
+ }
+ return err
+ }
+
+ // Make sure we won't be rerun
+ t.SetStatus(state.DoneStatus)
+
+ return nil
+}
+
+func (m *SnapManager) undoSetupKernelModulesComponent(t *state.Task, _ *tomb.Tomb) error {
+ // kernel snap is expected to be mounted
+ st := t.State()
+ st.Lock()
+ defer st.Unlock()
+ perfTimings := state.TimingsForTask(t)
+
+ compSetup, snapsup, snapSt, err := compSetupAndState(t)
+ if err != nil {
+ return err
+ }
+
+ csi := compSetup.CompSideInfo
+ cpi := snap.MinimalComponentContainerPlaceInfo(compSetup.ComponentName(),
+ csi.Revision, snapsup.InstanceName(), snapsup.Revision())
+ pm := NewTaskProgressAdapterUnlocked(t)
+
+ // Find kernel version from matching kernel
+ spi := snap.MinimalPlaceInfo(snapsup.InstanceName(), snapsup.Revision())
+ kernelVersion, err := kernel.KernelVersionFromPlaceInfo(spi)
+ if err != nil {
+ return err
+ }
+
+ // Find out if a previously installed component has been restored
+ // by the undo link step.
+ restoredCsi := snapSt.CurrentComponentSideInfo(csi.Component)
+
+ st.Unlock()
+ if restoredCsi == nil {
+ timings.Run(perfTimings, "undo-setup-kernel-modules-component",
+ fmt.Sprintf("undo setup of kernel-modules component %q", csi.Component),
+ func(timings.Measurer) {
+ err = m.backend.UndoSetupKernelModulesComponent(
+ cpi, csi.Component, kernelVersion, pm)
+ })
+ } else {
+ // Set-up will replace the mount units so they point to the
+ // previous component revision (note that the mount points,
+ // therefore the file names, are the same).
+ restoredCpi := snap.MinimalComponentContainerPlaceInfo(
+ restoredCsi.Component.ComponentName,
+ restoredCsi.Revision, snapsup.InstanceName(), snapsup.Revision())
+ timings.Run(perfTimings, "setup-kernel-modules-component",
+ fmt.Sprintf("setup of kernel-modules component %q", csi.Component),
+ func(timings.Measurer) {
+ err = m.backend.SetupKernelModulesComponent(
+ restoredCpi, restoredCsi.Component, kernelVersion, pm)
+ })
+ }
+ st.Lock()
+ if err != nil {
+ return err
+ }
+
+ // Make sure we won't be rerun
+ t.SetStatus(state.UndoneStatus)
+
+ return nil
+}
+
+func (m *SnapManager) doCleanupKernelModulesComponent(t *state.Task, _ *tomb.Tomb) error {
+ st := t.State()
+ st.Lock()
+ defer st.Unlock()
+ perfTimings := state.TimingsForTask(t)
+
+ compSetup, snapsup, snapSt, err := compSetupAndState(t)
+ if err != nil {
+ return err
+ }
+ cref := compSetup.CompSideInfo.Component
+
+ // This is the currently active component
+ activeComp := snapSt.CurrentComponentSideInfo(cref)
+ cpi := snap.MinimalComponentContainerPlaceInfo(compSetup.ComponentName(),
+ activeComp.Revision, snapsup.InstanceName(), snapsup.Revision())
+ pm := NewTaskProgressAdapterUnlocked(t)
+
+ // Find kernel version from matching kernel
+ spi := snap.MinimalPlaceInfo(snapsup.InstanceName(), snapsup.Revision())
+ kernelVersion, err := kernel.KernelVersionFromPlaceInfo(spi)
+ if err != nil {
+ return err
+ }
+ st.Unlock()
+ timings.Run(perfTimings, "cleanup-kernel-modules-component",
+ fmt.Sprintf("clean-up kernel-modules component %q", activeComp.Component),
+ func(timings.Measurer) {
+ err = m.backend.UndoSetupKernelModulesComponent(cpi,
+ activeComp.Component, kernelVersion, pm)
+ })
+ st.Lock()
+ if err != nil {
+ return err
+ }
+
+ // Make sure we won't be rerun
+ t.SetStatus(state.DoneStatus)
+
+ return nil
+}
+
+func (m *SnapManager) undoCleanupKernelModulesComponent(t *state.Task, _ *tomb.Tomb) error {
+ st := t.State()
+ st.Lock()
+ defer st.Unlock()
+ perfTimings := state.TimingsForTask(t)
+
+ compSetup, snapsup, snapSt, err := compSetupAndState(t)
+ if err != nil {
+ return err
+ }
+ cref := compSetup.CompSideInfo.Component
+
+ // This is the currently active component (already restored by undo unlink step)
+ activeComp := snapSt.CurrentComponentSideInfo(cref)
+ cpi := snap.MinimalComponentContainerPlaceInfo(compSetup.ComponentName(),
+ activeComp.Revision, snapsup.InstanceName(), snapsup.Revision())
+ pm := NewTaskProgressAdapterUnlocked(t)
+
+ // Find kernel version from matching kernel
+ spi := snap.MinimalPlaceInfo(snapsup.InstanceName(), snapsup.Revision())
+ kernelVersion, err := kernel.KernelVersionFromPlaceInfo(spi)
+ if err != nil {
+ return err
+ }
+
+ st.Unlock()
+ timings.Run(perfTimings, "undo-cleanup-kernel-modules-component",
+ fmt.Sprintf("undo clean-up of kernel-modules component %q", activeComp.Component),
+ func(timings.Measurer) {
+ err = m.backend.SetupKernelModulesComponent(cpi, activeComp.Component,
+ kernelVersion, pm)
+ })
+ st.Lock()
+ if err != nil {
+ return err
+ }
+
+ // Make sure we won't be rerun
+ t.SetStatus(state.UndoneStatus)
+
+ return nil
+}
diff --git a/overlord/snapstate/handlers_components_mount_test.go b/overlord/snapstate/handlers_components_mount_test.go
index ff7594d03dc..75154c2ac51 100644
--- a/overlord/snapstate/handlers_components_mount_test.go
+++ b/overlord/snapstate/handlers_components_mount_test.go
@@ -21,7 +21,9 @@ package snapstate_test
import (
"fmt"
+ "path/filepath"
+ "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
@@ -76,7 +78,10 @@ func (s *mountCompSnapSuite) TestDoMountComponent(c *C) {
// Ensure backend calls have happened with the expected data
c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
{
- op: "setup-component",
+ op: "setup-component",
+ compFilePath: compPath,
+ compMountDir: filepath.Join(dirs.SnapMountDir, "mysnap/components/1/mycomp"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/mysnap+mycomp_7.comp"),
},
})
// File not removed
@@ -127,13 +132,20 @@ func (s *mountCompSnapSuite) TestDoUndoMountComponent(c *C) {
// ensure undo was called the right way
c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
{
- op: "setup-component",
+ op: "setup-component",
+ compFilePath: compPath,
+ compMountDir: filepath.Join(dirs.SnapMountDir, "mysnap/components/1/mycomp"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/mysnap+mycomp_7.comp"),
},
{
- op: "undo-setup-component",
+ op: "undo-setup-component",
+ compMountDir: filepath.Join(dirs.SnapMountDir, "mysnap/components/1/mycomp"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/mysnap+mycomp_7.comp"),
},
{
- op: "remove-component-dir",
+ op: "remove-component-dir",
+ compMountDir: filepath.Join(dirs.SnapMountDir, "mysnap/components/1/mycomp"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/mysnap+mycomp_7.comp"),
},
})
}
@@ -177,10 +189,15 @@ func (s *mountCompSnapSuite) TestDoMountComponentSetupFails(c *C) {
// ensure undo was called the right way
c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
{
- op: "setup-component",
+ op: "setup-component",
+ compFilePath: compPath,
+ compMountDir: filepath.Join(dirs.SnapMountDir, "mysnap/components/1/broken"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/mysnap+broken_7.comp"),
},
{
- op: "remove-component-dir",
+ op: "remove-component-dir",
+ compMountDir: filepath.Join(dirs.SnapMountDir, "mysnap/components/1/broken"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/mysnap+broken_7.comp"),
},
})
}
@@ -231,10 +248,15 @@ func (s *mountCompSnapSuite) TestDoUndoMountComponentFails(c *C) {
// ensure undo was called the right way
c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
{
- op: "setup-component",
+ op: "setup-component",
+ compFilePath: compPath,
+ compMountDir: filepath.Join(dirs.SnapMountDir, "mysnap/components/1/brokenundo"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/mysnap+brokenundo_7.comp"),
},
{
- op: "undo-setup-component",
+ op: "undo-setup-component",
+ compMountDir: filepath.Join(dirs.SnapMountDir, "mysnap/components/1/brokenundo"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/mysnap+brokenundo_7.comp"),
},
})
}
@@ -278,13 +300,20 @@ func (s *mountCompSnapSuite) TestDoMountComponentMountFails(c *C) {
// ensure undo was called the right way
c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
{
- op: "setup-component",
+ op: "setup-component",
+ compFilePath: compPath,
+ compMountDir: filepath.Join(dirs.SnapMountDir, "mysnap/components/1/mycomp"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/mysnap+mycomp_7.comp"),
},
{
- op: "undo-setup-component",
+ op: "undo-setup-component",
+ compMountDir: filepath.Join(dirs.SnapMountDir, "mysnap/components/1/mycomp"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/mysnap+mycomp_7.comp"),
},
{
- op: "remove-component-dir",
+ op: "remove-component-dir",
+ compMountDir: filepath.Join(dirs.SnapMountDir, "mysnap/components/1/mycomp"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/mysnap+mycomp_7.comp"),
},
})
}
diff --git a/overlord/snapstate/handlers_kernel_modules_components_test.go b/overlord/snapstate/handlers_kernel_modules_components_test.go
new file mode 100644
index 00000000000..6482ceb73c5
--- /dev/null
+++ b/overlord/snapstate/handlers_kernel_modules_components_test.go
@@ -0,0 +1,401 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 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
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package snapstate_test
+
+import (
+ "os"
+ "path/filepath"
+ "time"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/overlord/snapstate"
+ "github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
+ "github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/naming"
+)
+
+type kernelModulesCompSnapSuite struct {
+ baseHandlerSuite
+}
+
+var _ = Suite(&kernelModulesCompSnapSuite{})
+
+func (s *kernelModulesCompSnapSuite) SetUpTest(c *C) {
+ s.baseHandlerSuite.SetUpTest(c)
+ s.AddCleanup(snapstatetest.MockDeviceModel(DefaultModel()))
+
+ var err error
+ taskRunTime, err := time.Parse(time.RFC3339, "2024-01-01T00:00:00Z")
+ c.Assert(err, IsNil)
+ s.AddCleanup(snapstate.MockTimeNow(func() time.Time {
+ return taskRunTime
+ }))
+}
+
+func (s *kernelModulesCompSnapSuite) TestDoSetupKernelModulesComponent(c *C) {
+ const snapName = "mysnap"
+ const compName = "mycomp"
+ snapRev := snap.R(1)
+ compRev := snap.R(7)
+ ci, compPath := createTestComponent(c, snapName, compName)
+ si := createTestSnapInfoForComponent(c, snapName, snapRev, compName)
+ ssu := createTestSnapSetup(si, snapstate.Flags{})
+ s.AddCleanup(snapstate.MockReadComponentInfo(func(
+ compMntDir string) (*snap.ComponentInfo, error) {
+ return ci, nil
+ }))
+ // Create file so kernel version can be found
+ c.Assert(os.MkdirAll(si.MountDir(), 0755), IsNil)
+ c.Assert(os.WriteFile(filepath.Join(
+ si.MountDir(), "System.map-5.15.0-78-generic"), []byte{}, 0644), IsNil)
+
+ s.state.Lock()
+
+ t := s.state.NewTask("setup-kernel-modules-component", "task desc")
+ cref := naming.NewComponentRef(snapName, compName)
+ csi := snap.NewComponentSideInfo(cref, compRev)
+ t.Set("component-setup",
+ snapstate.NewComponentSetup(csi, snap.TestComponent, compPath))
+ t.Set("snap-setup", ssu)
+ chg := s.state.NewChange("test change", "change desc")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ s.se.Ensure()
+ s.se.Wait()
+
+ s.state.Lock()
+ c.Check(chg.Err(), IsNil)
+ // State of task is as expected
+ c.Assert(t.Status(), Equals, state.DoneStatus)
+ s.state.Unlock()
+
+ // Ensure backend calls have happened with the expected data
+ c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
+ {
+ op: "setup-kernel-modules-component",
+ compMountDir: filepath.Join(dirs.SnapMountDir, "mysnap/components/1/mycomp"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/mysnap+mycomp_7.comp"),
+ cref: cref,
+ kernelVersion: "5.15.0-78-generic",
+ },
+ })
+}
+
+func (s *kernelModulesCompSnapSuite) TestDoSetupKernelModulesComponentNoKernVersion(c *C) {
+ const snapName = "mysnap"
+ const compName = "mycomp"
+ snapRev := snap.R(1)
+ compRev := snap.R(7)
+ ci, compPath := createTestComponent(c, snapName, compName)
+ si := createTestSnapInfoForComponent(c, snapName, snapRev, compName)
+ ssu := createTestSnapSetup(si, snapstate.Flags{})
+ s.AddCleanup(snapstate.MockReadComponentInfo(func(
+ compMntDir string) (*snap.ComponentInfo, error) {
+ return ci, nil
+ }))
+
+ s.state.Lock()
+
+ t := s.state.NewTask("setup-kernel-modules-component", "task desc")
+ cref := naming.NewComponentRef(snapName, compName)
+ csi := snap.NewComponentSideInfo(cref, compRev)
+ t.Set("component-setup",
+ snapstate.NewComponentSetup(csi, snap.TestComponent, compPath))
+ t.Set("snap-setup", ssu)
+ chg := s.state.NewChange("test change", "change desc")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ s.se.Ensure()
+ s.se.Wait()
+
+ s.state.Lock()
+ c.Check(chg.Err(), ErrorMatches, "cannot perform the following tasks:\n"+
+ `\- task desc \(unexpected number of matches \(0\) for glob .*System.map-\*\)`)
+ c.Assert(t.Status(), Equals, state.ErrorStatus)
+ s.state.Unlock()
+
+ c.Check(len(s.fakeBackend.ops), Equals, 0)
+}
+
+func (s *kernelModulesCompSnapSuite) TestDoThenUndoSetupKernelModulesComponent(c *C) {
+ const snapName = "mysnap"
+ const compName = "mycomp"
+ snapRev := snap.R(1)
+ compRev := snap.R(7)
+ ci, compPath := createTestComponent(c, snapName, compName)
+ si := createTestSnapInfoForComponent(c, snapName, snapRev, compName)
+ ssu := createTestSnapSetup(si, snapstate.Flags{})
+ s.AddCleanup(snapstate.MockReadComponentInfo(func(
+ compMntDir string) (*snap.ComponentInfo, error) {
+ return ci, nil
+ }))
+ // Create file so kernel version can be found
+ c.Assert(os.MkdirAll(si.MountDir(), 0755), IsNil)
+ c.Assert(os.WriteFile(filepath.Join(
+ si.MountDir(), "System.map-5.15.0-78-generic"), []byte{}, 0644), IsNil)
+
+ s.state.Lock()
+
+ t := s.state.NewTask("setup-kernel-modules-component", "task desc")
+ cref := naming.NewComponentRef(snapName, compName)
+ csi := snap.NewComponentSideInfo(cref, compRev)
+ t.Set("component-setup",
+ snapstate.NewComponentSetup(csi, snap.TestComponent, compPath))
+ t.Set("snap-setup", ssu)
+ chg := s.state.NewChange("test change", "change desc")
+ chg.AddTask(t)
+
+ terr := s.state.NewTask("error-trigger", "provoking undo setup")
+ terr.WaitFor(t)
+ chg.AddTask(terr)
+
+ s.state.Unlock()
+
+ for i := 0; i < 3; i++ {
+ s.se.Ensure()
+ s.se.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Check(chg.Err().Error(), Equals, "cannot perform the following tasks:\n"+
+ "- provoking undo setup (error out)")
+ // State of task is as expected
+ c.Check(t.Status(), Equals, state.UndoneStatus)
+ s.state.Unlock()
+
+ // Ensure backend calls have happened with the expected data
+ c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
+ {
+ op: "setup-kernel-modules-component",
+ compMountDir: filepath.Join(dirs.SnapMountDir, "mysnap/components/1/mycomp"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/mysnap+mycomp_7.comp"),
+ cref: cref,
+ kernelVersion: "5.15.0-78-generic",
+ },
+ {
+ op: "undo-setup-kernel-modules-component",
+ compMountDir: filepath.Join(dirs.SnapMountDir, "mysnap/components/1/mycomp"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/mysnap+mycomp_7.comp"),
+ cref: cref,
+ kernelVersion: "5.15.0-78-generic",
+ },
+ })
+}
+
+func (s *kernelModulesCompSnapSuite) TestDoThenUndoSetupKernelModulesComponentInstalled(c *C) {
+ const snapName = "mysnap"
+ const compName = "mycomp"
+ snapRev := snap.R(2)
+ compRev := snap.R(7)
+ ci, compPath := createTestComponent(c, snapName, compName)
+ si := createTestSnapInfoForComponent(c, snapName, snapRev, compName)
+ ssu := createTestSnapSetup(si, snapstate.Flags{})
+ s.AddCleanup(snapstate.MockReadComponentInfo(func(
+ compMntDir string) (*snap.ComponentInfo, error) {
+ return ci, nil
+ }))
+ // Create file so kernel version can be found
+ c.Assert(os.MkdirAll(si.MountDir(), 0755), IsNil)
+ c.Assert(os.WriteFile(filepath.Join(
+ si.MountDir(), "System.map-5.15.0-78-generic"), []byte{}, 0644), IsNil)
+
+ s.state.Lock()
+
+ // state must contain the component
+ setStateWithOneComponent(s.state, snapName, snap.R(1), compName, compRev)
+
+ t := s.state.NewTask("setup-kernel-modules-component", "task desc")
+ cref := naming.NewComponentRef(snapName, compName)
+ csi := snap.NewComponentSideInfo(cref, compRev)
+ t.Set("component-setup",
+ snapstate.NewComponentSetup(csi, snap.TestComponent, compPath))
+ t.Set("snap-setup", ssu)
+ chg := s.state.NewChange("test change", "change desc")
+ chg.AddTask(t)
+
+ terr := s.state.NewTask("error-trigger", "provoking undo setup")
+ terr.WaitFor(t)
+ chg.AddTask(terr)
+
+ s.state.Unlock()
+
+ for i := 0; i < 3; i++ {
+ s.se.Ensure()
+ s.se.Wait()
+ }
+
+ s.state.Lock()
+
+ c.Check(chg.Err().Error(), Equals, "cannot perform the following tasks:\n"+
+ "- provoking undo setup (error out)")
+ // State of task is as expected
+ c.Check(t.Status(), Equals, state.UndoneStatus)
+ s.state.Unlock()
+
+ // Ensure backend calls have happened with the expected data
+ c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
+ {
+ op: "setup-kernel-modules-component",
+ compMountDir: filepath.Join(dirs.SnapMountDir, "mysnap/components/2/mycomp"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/mysnap+mycomp_7.comp"),
+ cref: cref,
+ kernelVersion: "5.15.0-78-generic",
+ },
+ {
+ op: "setup-kernel-modules-component",
+ compMountDir: filepath.Join(dirs.SnapMountDir, "mysnap/components/2/mycomp"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/mysnap+mycomp_7.comp"),
+ cref: cref,
+ kernelVersion: "5.15.0-78-generic",
+ },
+ })
+}
+
+func (s *kernelModulesCompSnapSuite) TestDoCleanupKernelModulesComponent(c *C) {
+ const snapName = "kernelsnap"
+ const compName = "kmodcomp"
+ snapRev := snap.R(1)
+ currentCompRev := snap.R(5)
+ compRev := snap.R(7)
+
+ ci, _ := createTestComponent(c, snapName, compName)
+ si := createTestSnapInfoForComponent(c, snapName, snapRev, compName)
+ ssu := createTestSnapSetup(si, snapstate.Flags{})
+ s.AddCleanup(snapstate.MockReadComponentInfo(func(
+ compMntDir string) (*snap.ComponentInfo, error) {
+ return ci, nil
+ }))
+ // Create file so kernel version can be found
+ c.Assert(os.MkdirAll(si.MountDir(), 0755), IsNil)
+ c.Assert(os.WriteFile(filepath.Join(
+ si.MountDir(), "System.map-5.15.0-78-generic"), []byte{}, 0644), IsNil)
+
+ s.state.Lock()
+
+ // state must contain the component
+ setStateWithOneComponent(s.state, snapName, snapRev, compName, currentCompRev)
+
+ t := s.state.NewTask("cleanup-kernel-modules-component", "task desc")
+ cref := naming.NewComponentRef(snapName, compName)
+ csi := snap.NewComponentSideInfo(cref, compRev)
+ t.Set("component-setup", snapstate.NewComponentSetup(csi, snap.TestComponent, ""))
+ t.Set("snap-setup", ssu)
+ chg := s.state.NewChange("test change", "change desc")
+ chg.AddTask(t)
+
+ s.state.Unlock()
+
+ s.se.Ensure()
+ s.se.Wait()
+
+ s.state.Lock()
+ c.Check(chg.Err(), IsNil)
+ // State of task is as expected
+ c.Assert(t.Status(), Equals, state.DoneStatus)
+ s.state.Unlock()
+
+ // Ensure backend calls have happened with the expected data
+ c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
+ {
+ op: "undo-setup-kernel-modules-component",
+ compMountDir: filepath.Join(dirs.SnapMountDir, "kernelsnap/components/1/kmodcomp"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/kernelsnap+kmodcomp_5.comp"),
+ cref: cref,
+ kernelVersion: "5.15.0-78-generic",
+ },
+ })
+}
+
+func (s *kernelModulesCompSnapSuite) TestDoThenUndoCleanupKernelModulesComponent(c *C) {
+ const snapName = "kernelsnap"
+ const compName = "kmodcomp"
+ snapRev := snap.R(1)
+ currentCompRev := snap.R(5)
+ compRev := snap.R(7)
+
+ ci, _ := createTestComponent(c, snapName, compName)
+ si := createTestSnapInfoForComponent(c, snapName, snapRev, compName)
+ ssu := createTestSnapSetup(si, snapstate.Flags{})
+ s.AddCleanup(snapstate.MockReadComponentInfo(func(
+ compMntDir string) (*snap.ComponentInfo, error) {
+ return ci, nil
+ }))
+ // Create file so kernel version can be found
+ c.Assert(os.MkdirAll(si.MountDir(), 0755), IsNil)
+ c.Assert(os.WriteFile(filepath.Join(
+ si.MountDir(), "System.map-5.15.0-78-generic"), []byte{}, 0644), IsNil)
+
+ s.state.Lock()
+
+ // state must contain the component
+ setStateWithOneComponent(s.state, snapName, snapRev, compName, currentCompRev)
+
+ t := s.state.NewTask("cleanup-kernel-modules-component", "task desc")
+ cref := naming.NewComponentRef(snapName, compName)
+ csi := snap.NewComponentSideInfo(cref, compRev)
+ t.Set("component-setup", snapstate.NewComponentSetup(csi, snap.TestComponent, ""))
+ t.Set("snap-setup", ssu)
+ chg := s.state.NewChange("test change", "change desc")
+ chg.AddTask(t)
+
+ terr := s.state.NewTask("error-trigger", "provoking undo setup")
+ terr.WaitFor(t)
+ chg.AddTask(terr)
+
+ s.state.Unlock()
+
+ for i := 0; i < 3; i++ {
+ s.se.Ensure()
+ s.se.Wait()
+ }
+
+ s.state.Lock()
+ c.Check(chg.Err().Error(), Equals, "cannot perform the following tasks:\n"+
+ "- provoking undo setup (error out)")
+ // State of task is as expected
+ c.Check(t.Status(), Equals, state.UndoneStatus)
+ s.state.Unlock()
+
+ // Ensure backend calls have happened with the expected data
+ c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
+ {
+ op: "undo-setup-kernel-modules-component",
+ compMountDir: filepath.Join(dirs.SnapMountDir, "kernelsnap/components/1/kmodcomp"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/kernelsnap+kmodcomp_5.comp"),
+ cref: cref,
+ kernelVersion: "5.15.0-78-generic",
+ },
+ {
+ op: "setup-kernel-modules-component",
+ compMountDir: filepath.Join(dirs.SnapMountDir, "kernelsnap/components/1/kmodcomp"),
+ compMountFile: filepath.Join(dirs.GlobalRootDir, "var/lib/snapd/snaps/kernelsnap+kmodcomp_5.comp"),
+ cref: cref,
+ kernelVersion: "5.15.0-78-generic",
+ },
+ })
+}
diff --git a/overlord/snapstate/snapmgr.go b/overlord/snapstate/snapmgr.go
index 2cbaeea8608..2370b55f6bd 100644
--- a/overlord/snapstate/snapmgr.go
+++ b/overlord/snapstate/snapmgr.go
@@ -657,6 +657,8 @@ func Manager(st *state.State, runner *state.TaskRunner) (*SnapManager, error) {
runner.AddHandler("mount-component", m.doMountComponent, m.undoMountComponent)
runner.AddHandler("unlink-current-component", m.doUnlinkCurrentComponent, m.undoUnlinkCurrentComponent)
runner.AddHandler("link-component", m.doLinkComponent, m.undoLinkComponent)
+ runner.AddHandler("setup-kernel-modules-component", m.doSetupKernelModulesComponent, m.undoSetupKernelModulesComponent)
+ runner.AddHandler("cleanup-kernel-modules-component", m.doCleanupKernelModulesComponent, m.undoCleanupKernelModulesComponent)
// control serialisation
runner.AddBlocked(m.blockedTask)
diff --git a/snap/types.go b/snap/types.go
index 1633cdcd354..9d609b9f010 100644
--- a/snap/types.go
+++ b/snap/types.go
@@ -188,12 +188,12 @@ type ComponentType string
const (
// TestComponent is just for testing purposes.
- // TODO add here new component when there is more progress on the
- // components implementation.
TestComponent ComponentType = "test"
+ // KernelModulesComponent is for components containing modules/firmware
+ KernelModulesComponent ComponentType = "kernel-modules"
)
-var validComponentTypes = [...]ComponentType{TestComponent}
+var validComponentTypes = [...]ComponentType{TestComponent, KernelModulesComponent}
// Component represents a snap component.
type Component struct {