diff --git a/build-aux/snap/snapcraft.yaml b/build-aux/snap/snapcraft.yaml
index 8984227644d..a72e7d3b4ee 100644
--- a/build-aux/snap/snapcraft.yaml
+++ b/build-aux/snap/snapcraft.yaml
@@ -96,6 +96,7 @@ parts:
runtime:
plugin: nil
stage-packages:
+ - libblkid1
- libbrotli1
- libc6
- libcap2
@@ -231,6 +232,7 @@ parts:
- autoconf-archive
- automake
- xfslibs-dev
+ - libblkid-dev
- libudev-dev
- libcap-dev
- libseccomp-dev
diff --git a/cmd/snap-bootstrap/blkid/blkid.go b/cmd/snap-bootstrap/blkid/blkid.go
new file mode 100644
index 00000000000..ee128762849
--- /dev/null
+++ b/cmd/snap-bootstrap/blkid/blkid.go
@@ -0,0 +1,186 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 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 blkid
+
+//#cgo CFLAGS: -D_FILE_OFFSET_BITS=64
+//#cgo pkg-config: blkid
+//#cgo LDFLAGS:
+//
+//#include
+//#include
+import "C"
+
+import (
+ "fmt"
+ "unsafe"
+)
+
+const (
+ BLKID_PARTS_ENTRY_DETAILS int = C.BLKID_PARTS_ENTRY_DETAILS
+)
+
+// AbstractBlkidProbe is wrapper for blkid_probe
+// See "Low-level" section of libblkid documentation
+type AbstractBlkidProbe interface {
+ // LookupValue is a wrapper for blkid_probe_lookup_value
+ LookupValue(entryName string) (string, error)
+ // Close is a wrapper for blkid_free_probe
+ Close()
+ // EnablePartitions is a wrapper for blkid_probe_enable_partitions
+ EnablePartitions(value bool)
+ // EnableSuperblocks is a wrapper for blkid_probe_enable_superblocks
+ EnableSuperblocks(value bool)
+ // SetPartitionsFlags is a wrapper for blkid_probe_set_partitions_flags
+ SetPartitionsFlags(flags int)
+ // DoSafeprobe is a wrapper for blkid_do_safeprobe
+ DoSafeprobe() error
+ // GetPartitions is a wrapper for blkid_probe_get_partitions
+ GetPartitions() (AbstractBlkidPartlist, error)
+}
+
+// AbstractBlkidPartlist is a wrapper for blkid_partlist
+type AbstractBlkidPartlist interface {
+ // GetPartitions is a wrapper for blkid_partlist_get_partition
+ // and blkid_partlist_numof_partitions.
+ GetPartitions() []AbstractBlkidPartition
+}
+
+// AbstractBlkidPartition is a wrapper for blkid_partition
+type AbstractBlkidPartition interface {
+ // GetName is a wrapper for blkid_partition_get_name
+ GetName() string
+ // GetUUID is a wrapper for blkid_partition_get_uuid
+ GetUUID() string
+}
+
+type blkidProbe struct {
+ probeHandle C.blkid_probe
+}
+
+func newProbeFromFilenameImpl(node string) (AbstractBlkidProbe, error) {
+ cnode := C.CString(node)
+ defer C.free(unsafe.Pointer(cnode))
+ probe, err := C.blkid_new_probe_from_filename(cnode)
+ if probe == nil {
+ if err == nil {
+ return nil, fmt.Errorf("blkid_new_probe_from_filename failed but no error was returned")
+ }
+ return nil, err
+ }
+ return &blkidProbe{probe}, nil
+}
+
+var NewProbeFromFilename = newProbeFromFilenameImpl
+
+func (p *blkidProbe) checkProbe() {
+ if p.probeHandle == nil {
+ panic("used blkid probe after Close")
+ }
+}
+
+func (p *blkidProbe) LookupValue(entryName string) (string, error) {
+ p.checkProbe()
+ var value *C.char
+ var value_len C.size_t
+ cname := C.CString(entryName)
+ defer C.free(unsafe.Pointer(cname))
+ res := C.blkid_probe_lookup_value(p.probeHandle, cname, &value, &value_len)
+ if res < 0 {
+ return "", fmt.Errorf("probe value was not found: %s", entryName)
+ }
+ if value_len > 0 {
+ return C.GoStringN(value, C.int(value_len-1)), nil
+ } else {
+ return "", fmt.Errorf("probe value has unexpected size")
+ }
+}
+
+func (p *blkidProbe) Close() {
+ p.checkProbe()
+ C.blkid_free_probe(p.probeHandle)
+ p.probeHandle = C.blkid_probe(nil)
+}
+
+func (p *blkidProbe) EnablePartitions(value bool) {
+ p.checkProbe()
+ v := 0
+ if value {
+ v = 1
+ }
+ C.blkid_probe_enable_partitions(p.probeHandle, C.int(v))
+}
+
+func (p *blkidProbe) EnableSuperblocks(value bool) {
+ p.checkProbe()
+ v := 0
+ if value {
+ v = 1
+ }
+ C.blkid_probe_enable_superblocks(p.probeHandle, C.int(v))
+}
+
+func (p *blkidProbe) SetPartitionsFlags(flags int) {
+ p.checkProbe()
+ C.blkid_probe_set_partitions_flags(p.probeHandle, C.int(flags))
+}
+
+func (p *blkidProbe) DoSafeprobe() error {
+ p.checkProbe()
+ res, err := C.blkid_do_safeprobe(p.probeHandle)
+ if res < 0 {
+ return err
+ }
+ return nil
+}
+
+type blkidPartlist struct {
+ partlistHandle C.blkid_partlist
+}
+
+func (p *blkidProbe) GetPartitions() (AbstractBlkidPartlist, error) {
+ p.checkProbe()
+ partitions, err := C.blkid_probe_get_partitions(p.probeHandle)
+ if partitions == nil {
+ return nil, err
+ }
+ return &blkidPartlist{partitions}, nil
+}
+
+type blkidPartition struct {
+ partitionHandle C.blkid_partition
+}
+
+func (p *blkidPartlist) GetPartitions() []AbstractBlkidPartition {
+ npartitions := C.blkid_partlist_numof_partitions(p.partlistHandle)
+ ret := make([]AbstractBlkidPartition, npartitions)
+ for i := 0; i < int(npartitions); i++ {
+ partition := C.blkid_partlist_get_partition(p.partlistHandle, C.int(i))
+ ret[i] = &blkidPartition{partition}
+ }
+ return ret
+}
+
+func (p *blkidPartition) GetName() string {
+ return C.GoString(C.blkid_partition_get_name(p.partitionHandle))
+}
+
+func (p *blkidPartition) GetUUID() string {
+ return C.GoString(C.blkid_partition_get_uuid(p.partitionHandle))
+}
diff --git a/cmd/snap-bootstrap/blkid/blkid_test.go b/cmd/snap-bootstrap/blkid/blkid_test.go
new file mode 100644
index 00000000000..3049cb53065
--- /dev/null
+++ b/cmd/snap-bootstrap/blkid/blkid_test.go
@@ -0,0 +1,90 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 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 blkid_test
+
+import (
+ "os/exec"
+ "path/filepath"
+ "testing"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/blkid"
+
+ "github.com/snapcore/snapd/testutil"
+)
+
+func Test(t *testing.T) { TestingT(t) }
+
+type blkidSuite struct {
+ testutil.BaseTest
+
+ image string
+}
+
+var _ = Suite(&blkidSuite{})
+
+func (s *blkidSuite) SetUpTest(c *C) {
+ systemdRepart, err := exec.LookPath("systemd-repart")
+ if err != nil {
+ c.Skip("systemd-repart is not available")
+ }
+
+ s.BaseTest.SetUpTest(c)
+
+ tmp := c.MkDir()
+ image := filepath.Join(tmp, "image")
+
+ cmd := exec.Command(systemdRepart, "--offline=yes", "--size=64M", "--empty=create", "--definitions=test-data/repart.d", image)
+ err = cmd.Run()
+ if err != nil {
+ c.Skip("systemd-repart is not working")
+ }
+
+ s.image = image
+}
+
+func (s *blkidSuite) TestScanPartitionTable(c *C) {
+ probe, err := blkid.NewProbeFromFilename(s.image)
+ c.Assert(err, IsNil)
+ defer probe.Close()
+
+ probe.EnablePartitions(true)
+
+ err = probe.DoSafeprobe()
+ c.Assert(err, IsNil)
+
+ pttype, err := probe.LookupValue("PTTYPE")
+ c.Assert(err, IsNil)
+ c.Check(pttype, Equals, "gpt")
+
+ partlist, err := probe.GetPartitions()
+ c.Assert(err, IsNil)
+
+ partitions := 0
+ for _, p := range partlist.GetPartitions() {
+ partitions++
+ label := p.GetName()
+ c.Check(label, Equals, "thelabel")
+ uuid := p.GetUUID()
+ c.Check(uuid, Equals, "93fd7d8f-a662-4451-a03f-c065f2c2e1ab")
+ }
+ c.Check(partitions, Equals, 1)
+}
diff --git a/cmd/snap-bootstrap/blkid/mock_blkid.go b/cmd/snap-bootstrap/blkid/mock_blkid.go
new file mode 100644
index 00000000000..e2cc7b97bae
--- /dev/null
+++ b/cmd/snap-bootstrap/blkid/mock_blkid.go
@@ -0,0 +1,110 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 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 blkid
+
+import (
+ "fmt"
+)
+
+type Constructor func(string) (AbstractBlkidProbe, error)
+
+func MockBlkid(constr Constructor) func() {
+ old := NewProbeFromFilename
+ NewProbeFromFilename = constr
+ return func() {
+ NewProbeFromFilename = old
+ }
+}
+
+func MockBlkidMap(probeMap map[string]*FakeBlkidProbe) func() {
+ return MockBlkid(func(name string) (AbstractBlkidProbe, error) {
+ value, ok := probeMap[name]
+ if !ok {
+ return nil, fmt.Errorf("not found")
+ }
+ return value, nil
+ })
+}
+
+func BuildFakeProbe(values map[string]string) *FakeBlkidProbe {
+ return &FakeBlkidProbe{values, &FakeBlkidPartlist{}}
+}
+
+func (p *FakeBlkidProbe) AddPartition(name, uuid string) {
+ p.partlist.partitions = append(p.partlist.partitions, &FakeBlkidPartition{name, uuid})
+}
+
+type FakeBlkidPartition struct {
+ name string
+ uuid string
+}
+
+type FakeBlkidPartlist struct {
+ partitions []*FakeBlkidPartition
+}
+
+type FakeBlkidProbe struct {
+ values map[string]string
+ partlist *FakeBlkidPartlist
+}
+
+func (p *FakeBlkidProbe) LookupValue(entryName string) (string, error) {
+ value, ok := p.values[entryName]
+ if !ok {
+ return "", fmt.Errorf("Probe value was not found: %s", entryName)
+ }
+ return value, nil
+}
+
+func (p *FakeBlkidProbe) Close() {
+}
+
+func (p *FakeBlkidProbe) EnablePartitions(value bool) {
+}
+
+func (p *FakeBlkidProbe) EnableSuperblocks(value bool) {
+}
+
+func (p *FakeBlkidProbe) SetPartitionsFlags(flags int) {
+}
+
+func (p *FakeBlkidProbe) DoSafeprobe() error {
+ return nil
+}
+
+func (p *FakeBlkidProbe) GetPartitions() (AbstractBlkidPartlist, error) {
+ return p.partlist, nil
+}
+
+func (p *FakeBlkidPartlist) GetPartitions() []AbstractBlkidPartition {
+ ret := make([]AbstractBlkidPartition, len(p.partitions))
+ for i, partition := range p.partitions {
+ ret[i] = partition
+ }
+ return ret
+}
+
+func (p *FakeBlkidPartition) GetName() string {
+ return p.name
+}
+
+func (p *FakeBlkidPartition) GetUUID() string {
+ return p.uuid
+}
diff --git a/cmd/snap-bootstrap/blkid/test-data/repart.d/10-ext4.conf b/cmd/snap-bootstrap/blkid/test-data/repart.d/10-ext4.conf
new file mode 100644
index 00000000000..f11e2304b77
--- /dev/null
+++ b/cmd/snap-bootstrap/blkid/test-data/repart.d/10-ext4.conf
@@ -0,0 +1,5 @@
+[Partition]
+Type=ea0dd8e5-b23e-4633-9137-adbd12633746
+UUID=93fd7d8f-a662-4451-a03f-c065f2c2e1ab
+Label=thelabel
+Format=ext4
diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts.go b/cmd/snap-bootstrap/cmd_initramfs_mounts.go
index 029e27ce381..7ca14580ac6 100644
--- a/cmd/snap-bootstrap/cmd_initramfs_mounts.go
+++ b/cmd/snap-bootstrap/cmd_initramfs_mounts.go
@@ -1815,30 +1815,62 @@ func getNonUEFISystemDisk(fallbacklabel string) (string, error) {
return candidate, nil
}
-// mountNonDataPartitionMatchingKernelDisk will select the partition to mount at
-// dir, using the boot package function FindPartitionUUIDForBootedKernelDisk to
-// determine what partition the booted kernel came from. If which disk the
-// kernel came from cannot be determined, then it will fallback to mounting via
-// the specified disk label.
+// mountNonDataPartitionMatchingKernelDisk will select the partition
+// to mount at dir using the boot package function
+// FindPartitionUUIDForBootedKernelDisk to determine what partition
+// the booted kernel came from.
+//
+// If "snap-bootstrap scan-disk" was run as part of udev it will
+// restrict the search of the partition from the boot disk it found.
+//
+// If "snap-bootstrap scan-disk" is not in use (legacy case),
+// it will look for any partition that matches the boot.
+//
+// If which disk the kernel came from cannot be determined, then it
+// will fallback to mounting via the specified disk label. If
+// "snap-bootstrap scan-disk" was used, it will restrict the search to
+// the boot disk.
func mountNonDataPartitionMatchingKernelDisk(dir, fallbacklabel string, opts *systemdMountOptions) error {
- partuuid, err := bootFindPartitionUUIDForBootedKernelDisk()
var partSrc string
- if err == nil {
- // TODO: the by-partuuid is only available on gpt disks, on mbr we need
- // to use by-uuid or by-id
- partSrc = filepath.Join("/dev/disk/by-partuuid", partuuid)
- } else {
- partSrc, err = getNonUEFISystemDisk(fallbacklabel)
+
+ if osutil.FileExists(filepath.Join(dirs.GlobalRootDir, "/dev/disk/snapd/disk")) {
+ disk, err := disks.DiskFromDeviceName("/dev/disk/snapd/disk")
if err != nil {
return err
}
- }
+ partuuid, err := bootFindPartitionUUIDForBootedKernelDisk()
+ if err == nil {
+ partition, err := disk.FindMatchingPartitionWithPartUUID(partuuid)
+ if err != nil {
+ return err
+ }
+ partSrc = partition.KernelDeviceNode
+ } else {
+ partition, err := disk.FindMatchingPartitionWithFsLabel(fallbacklabel)
+ if err != nil {
+ return err
+ }
+ partSrc = partition.KernelDeviceNode
+ }
+ } else {
+ partuuid, err := bootFindPartitionUUIDForBootedKernelDisk()
+ if err == nil {
+ // TODO: the by-partuuid is only available on gpt disks, on mbr we need
+ // to use by-uuid or by-id
+ partSrc = filepath.Join("/dev/disk/by-partuuid", partuuid)
+ } else {
+ partSrc, err = getNonUEFISystemDisk(fallbacklabel)
+ if err != nil {
+ return err
+ }
+ }
- // The partition uuid is read from the EFI variables. At this point
- // the kernel may not have initialized the storage HW yet so poll
- // here.
- if err := waitForDevice(partSrc); err != nil {
- return err
+ // The partition uuid is read from the EFI variables. At this point
+ // the kernel may not have initialized the storage HW yet so poll
+ // here.
+ if err := waitForDevice(partSrc); err != nil {
+ return err
+ }
}
return doSystemdMount(partSrc, dir, opts)
}
diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
index bd69f170033..2d70a47b252 100644
--- a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
+++ b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
@@ -34,6 +34,7 @@ import (
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/assertstest"
"github.com/snapcore/snapd/boot"
+ "github.com/snapcore/snapd/boot/boottest"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/bootloader/bootloadertest"
main "github.com/snapcore/snapd/cmd/snap-bootstrap"
@@ -1489,3 +1490,189 @@ func (s *initramfsMountsSuite) TestGetDiskNotUEFISeedPartCapitalFsLabel(c *C) {
c.Assert(err.Error(), Equals, `filesystem label "UBUNTU-BOOT" not found`)
c.Assert(path, Equals, "")
}
+
+func (s *initramfsClassicMountsSuite) TestInitramfsMountsObeyDevLink(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_system_disk=/should/be/ignored snapd_recovery_mode=run")
+
+ devLink := filepath.Join(dirs.GlobalRootDir, "/dev/disk/snapd/disk")
+ c.Assert(os.MkdirAll(filepath.Dir(devLink), 0755), IsNil)
+ fakeDevice := filepath.Join(dirs.GlobalRootDir, "/dev/sda")
+ c.Assert(os.WriteFile(fakeDevice, []byte{}, 0644), IsNil)
+ c.Assert(os.Symlink(fakeDevice, devLink), IsNil)
+
+ restoreDiskMapping := disks.MockDeviceNameToDiskMapping(map[string]*disks.MockDiskMapping{
+ "/dev/sda": defaultBootWithSaveDisk,
+ })
+ defer restoreDiskMapping()
+
+ restore := main.MockPartitionUUIDForBootedKernelDisk("ubuntu-boot-partuuid")
+ defer restore()
+
+ restore = disks.MockMountPointDisksToPartitionMapping(
+ map[disks.Mountpoint]*disks.MockDiskMapping{
+ {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultBootWithSaveDisk,
+ {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootWithSaveDisk,
+ {Mountpoint: boot.InitramfsDataDir}: defaultBootWithSaveDisk,
+ {Mountpoint: boot.InitramfsUbuntuSaveDir}: defaultBootWithSaveDisk,
+ },
+ )
+ defer restore()
+
+ restore = s.mockSystemdMountSequence(c, []systemdMount{
+ {
+ "/dev/sda3",
+ boot.InitramfsUbuntuBootDir,
+ needsFsckDiskMountOpts,
+ nil,
+ },
+ s.ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"),
+ s.ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"),
+ s.ubuntuPartUUIDMount("ubuntu-save-partuuid", "run"),
+ s.makeRunSnapSystemdMount(snap.TypeGadget, s.gadget),
+ s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel),
+ }, nil)
+ defer restore()
+
+ // mock a bootloader
+ bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir()))
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+
+ // set the current kernel
+ restore = bloader.SetEnabledKernel(s.kernel)
+ defer restore()
+
+ writeGadget(c, "ubuntu-seed", "system-seed", "")
+
+ s.makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20, s.gadget)
+
+ // write modeenv
+ modeEnv := boot.Modeenv{
+ Mode: "run",
+ Base: s.core20.Filename(),
+ Gadget: s.gadget.Filename(),
+ CurrentKernels: []string{s.kernel.Filename()},
+ }
+ err := modeEnv.WriteTo(boot.InitramfsDataDir)
+ c.Assert(err, IsNil)
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+}
+
+func (s *initramfsClassicMountsSuite) TestInitramfsMountsObeyDevLinkFallback(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_system_disk=/should/be/ignored snapd_recovery_mode=run")
+
+ devLink := filepath.Join(dirs.GlobalRootDir, "/dev/disk/snapd/disk")
+ c.Assert(os.MkdirAll(filepath.Dir(devLink), 0755), IsNil)
+ fakeDevice := filepath.Join(dirs.GlobalRootDir, "/dev/sda")
+ c.Assert(os.WriteFile(fakeDevice, []byte{}, 0644), IsNil)
+ c.Assert(os.Symlink(fakeDevice, devLink), IsNil)
+
+ restoreDiskMapping := disks.MockDeviceNameToDiskMapping(map[string]*disks.MockDiskMapping{
+ "/dev/sda": defaultBootWithSaveDisk,
+ })
+ defer restoreDiskMapping()
+
+ // NO UEFI
+ restore := main.MockPartitionUUIDForBootedKernelDisk("")
+ defer restore()
+
+ restore = disks.MockMountPointDisksToPartitionMapping(
+ map[disks.Mountpoint]*disks.MockDiskMapping{
+ {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultBootWithSaveDisk,
+ {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootWithSaveDisk,
+ {Mountpoint: boot.InitramfsDataDir}: defaultBootWithSaveDisk,
+ {Mountpoint: boot.InitramfsUbuntuSaveDir}: defaultBootWithSaveDisk,
+ },
+ )
+ defer restore()
+
+ restore = s.mockSystemdMountSequence(c, []systemdMount{
+ {
+ "/dev/sda3",
+ boot.InitramfsUbuntuBootDir,
+ needsFsckDiskMountOpts,
+ nil,
+ },
+ s.ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"),
+ s.ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"),
+ s.ubuntuPartUUIDMount("ubuntu-save-partuuid", "run"),
+ s.makeRunSnapSystemdMount(snap.TypeGadget, s.gadget),
+ s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel),
+ }, nil)
+ defer restore()
+
+ // mock a bootloader
+ bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir()))
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+
+ // set the current kernel
+ restore = bloader.SetEnabledKernel(s.kernel)
+ defer restore()
+
+ writeGadget(c, "ubuntu-seed", "system-seed", "")
+
+ s.makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20, s.gadget)
+
+ // write modeenv
+ modeEnv := boot.Modeenv{
+ Mode: "run",
+ Base: s.core20.Filename(),
+ Gadget: s.gadget.Filename(),
+ CurrentKernels: []string{s.kernel.Filename()},
+ }
+ err := modeEnv.WriteTo(boot.InitramfsDataDir)
+ c.Assert(err, IsNil)
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+}
+
+func (s *initramfsClassicMountsSuite) TestInitramfsMountsInstallObeyDevLink(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_system_disk=/should/be/ignored snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel)
+
+ devLink := filepath.Join(dirs.GlobalRootDir, "/dev/disk/snapd/disk")
+ c.Assert(os.MkdirAll(filepath.Dir(devLink), 0755), IsNil)
+ fakeDevice := filepath.Join(dirs.GlobalRootDir, "/dev/sda")
+ c.Assert(os.WriteFile(fakeDevice, []byte{}, 0644), IsNil)
+ c.Assert(os.Symlink(fakeDevice, devLink), IsNil)
+
+ restoreDiskMapping := disks.MockDeviceNameToDiskMapping(map[string]*disks.MockDiskMapping{
+ "/dev/sda": defaultBootWithSaveDisk,
+ })
+ defer restoreDiskMapping()
+
+ restore := main.MockPartitionUUIDForBootedKernelDisk("ubuntu-seed-partuuid")
+ defer restore()
+
+ restore = disks.MockMountPointDisksToPartitionMapping(
+ map[disks.Mountpoint]*disks.MockDiskMapping{
+ {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultBootWithSaveDisk,
+ },
+ )
+ defer restore()
+
+ restore = s.mockSystemdMountSequence(c, []systemdMount{
+ {
+ "/dev/sda2",
+ boot.InitramfsUbuntuSeedDir,
+ needsFsckAndNoSuidNoDevNoExecMountOpts,
+ nil,
+ },
+ s.makeSeedSnapSystemdMount(snap.TypeKernel),
+ s.makeSeedSnapSystemdMount(snap.TypeBase),
+ s.makeSeedSnapSystemdMount(snap.TypeGadget),
+ {
+ "tmpfs",
+ boot.InitramfsDataDir,
+ tmpfsMountOpts,
+ nil,
+ },
+ }, nil)
+ defer restore()
+
+ _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+}
diff --git a/cmd/snap-bootstrap/cmd_scan_disk.go b/cmd/snap-bootstrap/cmd_scan_disk.go
new file mode 100644
index 00000000000..7cab42a6c37
--- /dev/null
+++ b/cmd/snap-bootstrap/cmd_scan_disk.go
@@ -0,0 +1,301 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 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 .
+ *
+ */
+
+/*
+ * This tool expects to be called from a udev rules file such as:
+ *
+ * ```
+ * SUBSYSTEM!="block", GOTO="ubuntu_core_partitions_end"
+ *
+ * ENV{DEVTYPE}=="disk", IMPORT{program}="/usr/lib/snapd/snap-bootstrap scan-disk"
+ * ENV{DEVTYPE}=="partition", IMPORT{parent}="UBUNTU_DISK"
+ * ENV{UBUNTU_DISK}!="1", GOTO="ubuntu_core_partitions_end"
+ *
+ * ENV{DEVTYPE}=="disk", SYMLINK+="disk/snapd/disk"
+ * ENV{DEVTYPE}=="partition", ENV{ID_PART_ENTRY_NAME}=="ubuntu-seed", SYMLINK+="disk/snapd/ubuntu-seed"
+ * ENV{DEVTYPE}=="partition", ENV{ID_PART_ENTRY_NAME}=="ubuntu-boot", SYMLINK+="disk/snapd/ubuntu-boot"
+ * ENV{DEVTYPE}=="partition", ENV{ID_PART_ENTRY_NAME}=="ubuntu-data", ENV{ID_FS_TYPE}=="crypto_LUKS", SYMLINK+="disk/snapd/ubuntu-data-luks"
+ * ENV{DEVTYPE}=="partition", ENV{ID_PART_ENTRY_NAME}=="ubuntu-data", ENV{ID_FS_TYPE}!="crypto_LUKS", SYMLINK+="disk/snapd/ubuntu-data"
+ * ENV{DEVTYPE}=="partition", ENV{ID_PART_ENTRY_NAME}=="ubuntu-save", ENV{ID_FS_TYPE}=="crypto_LUKS", SYMLINK+="disk/snapd/ubuntu-save-luks"
+ * ENV{DEVTYPE}=="partition", ENV{ID_PART_ENTRY_NAME}=="ubuntu-save", ENV{ID_FS_TYPE}!="crypto_LUKS", SYMLINK+="disk/snapd/ubuntu-save"
+ *
+ * LABEL="ubuntu_core_partitions_end"
+ *
+ * ENV{DM_UUID}=="CRYPT-*", ENV{DM_NAME}=="ubuntu-data-*", SYMLINK+="disk/snapd/ubuntu-data"
+ * ENV{DM_UUID}=="CRYPT-*", ENV{DM_NAME}=="ubuntu-save-*", SYMLINK+="disk/snapd/ubuntu-save"
+ * ```
+ *
+ * See
+ * core-initrd/latest/factory/usr/lib/udev/rules.d/90-ubuntu-core-partitions.rules
+ * for implementation.
+ *
+ * Note that symlink /dev/disk/snapd/disk can be expected by
+ * snap-bootstrap. In that case, snap-initramfs-mounts.service should
+ * have:
+ *
+ * ```
+ * BindsTo=dev-disk-snapd--disk.device
+ * After=dev-disk-snapd-disk.device
+ * ```
+ *
+ */
+
+package main
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/boot"
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/blkid"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/osutil/kcmdline"
+)
+
+func init() {
+ const (
+ short = "Verify that a disk is the booting disk"
+ long = "This tool is expected to be called from udev"
+ )
+
+ addCommandBuilder(func(parser *flags.Parser) {
+ if _, err := parser.AddCommand("scan-disk", short, long, &cmdScanDisk{}); err != nil {
+ panic(err)
+ }
+ })
+}
+
+type cmdScanDisk struct{}
+
+func (c *cmdScanDisk) Execute([]string) error {
+ return ScanDisk(os.Stdout)
+}
+
+type Partition struct {
+ Name string
+ UUID string
+}
+
+func isGpt(probe blkid.AbstractBlkidProbe) bool {
+ pttype, err := probe.LookupValue("PTTYPE")
+ if err != nil {
+ return false
+ }
+ return pttype == "gpt"
+}
+
+func probePartitions(node string) ([]Partition, error) {
+ probe, err := blkid.NewProbeFromFilename(node)
+ if err != nil {
+ return nil, err
+ }
+ defer probe.Close()
+
+ probe.EnablePartitions(true)
+ probe.SetPartitionsFlags(blkid.BLKID_PARTS_ENTRY_DETAILS)
+ probe.EnableSuperblocks(true)
+
+ if err := probe.DoSafeprobe(); err != nil {
+ return nil, err
+ }
+
+ if !isGpt(probe) {
+ return nil, nil
+ }
+
+ partitions, err := probe.GetPartitions()
+ if err != nil {
+ return nil, err
+ }
+
+ ret := make([]Partition, 0)
+ for _, partition := range partitions.GetPartitions() {
+ label := partition.GetName()
+ uuid := partition.GetUUID()
+ ret = append(ret, Partition{label, uuid})
+ }
+
+ return ret, nil
+}
+
+func samePath(a, b string) (bool, error) {
+ aSt, err := os.Stat(a)
+ if err != nil {
+ return false, err
+ }
+ bSt, err := os.Stat(b)
+ if err != nil {
+ return false, err
+ }
+ return os.SameFile(aSt, bSt), nil
+}
+
+func scanDiskNodeFallback(output io.Writer, node string) error {
+ var fallbackPartition string
+
+ partitions, err := probePartitions(node)
+ if err != nil {
+ return fmt.Errorf("cannot get partitions: %s\n", err)
+ }
+ /*
+ * If LoaderDevicePartUUID was not set, it is probably because
+ * we did not boot with UEFI. In that case we try to detect
+ * disk with partition labels.
+ */
+
+ mode, _, err := boot.ModeAndRecoverySystemFromKernelCommandLine()
+ if err != nil {
+ return err
+ }
+ switch mode {
+ case "recover":
+ fallbackPartition = "ubuntu-seed"
+ case "install":
+ fallbackPartition = "ubuntu-seed"
+ case "factory-reset":
+ fallbackPartition = "ubuntu-seed"
+ case "run":
+ fallbackPartition = "ubuntu-boot"
+ case "cloudimg-rootfs":
+ fallbackPartition = "ubuntu-boot"
+ default:
+ return fmt.Errorf("internal error: mode not handled")
+ }
+
+ /*
+ * If we are not in UEFI mode and snapd_system_disk is
+ * defined, we need to verify the disk also matches that. If
+ * not, we just return, ignoring this disk.
+ */
+ values, err := kcmdline.KeyValues("snapd_system_disk")
+ if err != nil {
+ return fmt.Errorf("cannot read kernel command line: %s\n", err)
+ }
+
+ if value, ok := values["snapd_system_disk"]; ok {
+ var currentPath string
+ var expectedPath string
+ if strings.HasPrefix(value, "/dev/") || !strings.HasPrefix(value, "/") {
+ name := strings.TrimPrefix(value, "/dev/")
+ expectedPath = fmt.Sprintf("/dev/%s", name)
+ currentPath = node
+ } else {
+ expectedPath = value
+ currentPath = osGetenv("DEVPATH")
+ }
+
+ same, err := samePath(filepath.Join(dirs.GlobalRootDir, expectedPath),
+ filepath.Join(dirs.GlobalRootDir, currentPath))
+ if err != nil {
+ return fmt.Errorf("cannot check snapd_system_disk kernel parameter: %s\n", err)
+ }
+ if !same {
+ /*
+ * This block device is not the device
+ * requested from the command line. But this
+ * is not an error. There are lots of block
+ * devices.
+ */
+ return nil
+ }
+ }
+
+ for _, part := range partitions {
+ if part.Name == fallbackPartition {
+ fmt.Fprintf(output, "UBUNTU_DISK=1\n")
+ return nil
+ }
+ }
+
+ /*
+ * We have found the block device is not a boot device. But
+ * this is not an error. There are plenty of block devices
+ * that are not the boot device.
+ */
+ return nil
+}
+
+func scanDiskNode(output io.Writer, node string) error {
+ /*
+ * We need to find out if the given node contains the ESP that
+ * was booted. The boot loader will set
+ * LoaderDevicePartUUID. We will need to scan all the
+ * partitions for that UUID.
+ */
+
+ bootUUID, err := bootFindPartitionUUIDForBootedKernelDisk()
+ if err != nil {
+ return scanDiskNodeFallback(output, node)
+ }
+
+ partitions, err := probePartitions(node)
+ if err != nil {
+ return fmt.Errorf("cannot get partitions: %s\n", err)
+ }
+
+ /*
+ * Now we scan the partitions. We need to find the partition
+ * grub booted from.
+ */
+ found := false
+ hasSeed := false
+ hasBoot := false
+ for _, part := range partitions {
+ if part.UUID == bootUUID {
+ /*
+ * We have just found the ESP boot partition!
+ */
+ found = true
+ }
+
+ if part.Name == "ubuntu-seed" {
+ hasSeed = true
+ } else if part.Name == "ubuntu-boot" {
+ hasBoot = true
+ }
+ }
+
+ /*
+ * We now print the result if we confirmed we found the boot ESP.
+ */
+ if found && (hasSeed || hasBoot) {
+ fmt.Fprintf(output, "UBUNTU_DISK=1\n")
+ }
+
+ /*
+ * We have found the block device is not a boot device. But
+ * this is not an error. There are plenty of block devices
+ * that are not the boot device.
+ */
+ return nil
+}
+
+func ScanDisk(output io.Writer) error {
+ devname := osGetenv("DEVNAME")
+ if osGetenv("DEVTYPE") == "disk" {
+ return scanDiskNode(output, devname)
+ } else {
+ return fmt.Errorf("unknown type for block device %s\n", devname)
+ }
+}
diff --git a/cmd/snap-bootstrap/cmd_scan_disk_test.go b/cmd/snap-bootstrap/cmd_scan_disk_test.go
new file mode 100644
index 00000000000..e2a13ef25d7
--- /dev/null
+++ b/cmd/snap-bootstrap/cmd_scan_disk_test.go
@@ -0,0 +1,292 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 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 main_test
+
+import (
+ "bufio"
+ "bytes"
+ "os"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+
+ main "github.com/snapcore/snapd/cmd/snap-bootstrap"
+
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/blkid"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/osutil/kcmdline"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type scanDiskSuite struct {
+ testutil.BaseTest
+
+ probeMap map[string]*blkid.FakeBlkidProbe
+ cmdlineFile string
+ env map[string]string
+}
+
+var _ = Suite(&scanDiskSuite{})
+
+func (s *scanDiskSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+
+ dirs.SetRootDir(c.MkDir())
+
+ s.probeMap = make(map[string]*blkid.FakeBlkidProbe)
+ cleanupBlkid := blkid.MockBlkidMap(s.probeMap)
+ s.AddCleanup(cleanupBlkid)
+
+ disk_values := make(map[string]string)
+ disk_values["PTTYPE"] = "gpt"
+ disk_probe := blkid.BuildFakeProbe(disk_values)
+ for _, partition := range []struct {
+ node string
+ label string
+ uuid string
+ }{
+ {"/dev/foop1", "ubuntu-seed", "6ae5a792-912e-43c9-ac92-e36723bbda12"},
+ {"/dev/foop2", "ubuntu-boot", "29261148-b8ba-4335-b934-417ed71e9e91"},
+ {"/dev/foop3", "ubuntu-data-enc", "c01a272d-fc72-40de-92fb-242c2da82533"},
+ {"/dev/foop4", "ubuntu-save-enc", "050ee326-ab58-4eb4-ba7d-13694b2d0c8a"},
+ } {
+ values := make(map[string]string)
+ values["PART_ENTRY_UUID"] = partition.uuid
+ s.probeMap[partition.node] = blkid.BuildFakeProbe(values)
+ disk_probe.AddPartition(partition.label, partition.uuid)
+ }
+ s.probeMap["/dev/foo"] = disk_probe
+
+ s.cmdlineFile = filepath.Join(c.MkDir(), "proc-cmdline")
+ err := os.WriteFile(s.cmdlineFile, []byte("snapd_recovery_mode=run"), 0644)
+ c.Assert(err, IsNil)
+ cmdlineCleanup := kcmdline.MockProcCmdline(s.cmdlineFile)
+ s.AddCleanup(cmdlineCleanup)
+
+ s.env = make(map[string]string)
+ cleanupEnv := main.MockOsGetenv(func(envVar string) string {
+ return s.env[envVar]
+ })
+ s.AddCleanup(cleanupEnv)
+}
+
+func (s *scanDiskSuite) setCmdLine(c *C, value string) {
+ err := os.WriteFile(s.cmdlineFile, []byte(value), 0644)
+ c.Assert(err, IsNil)
+}
+
+type outputScanner struct {
+ buffer *bytes.Buffer
+}
+
+func newBuffer() *outputScanner {
+ return &outputScanner{&bytes.Buffer{}}
+}
+
+func (o *outputScanner) File() *bytes.Buffer {
+ return o.buffer
+}
+
+func (o *outputScanner) GetLines() map[string]struct{} {
+ scanner := bufio.NewScanner(bytes.NewReader(o.buffer.Bytes()))
+ lines := make(map[string]struct{})
+ for scanner.Scan() {
+ lines[scanner.Text()] = struct{}{}
+ }
+ return lines
+}
+
+func (s *scanDiskSuite) TestDetectBootDisk(c *C) {
+ main.MockPartitionUUIDForBootedKernelDisk("29261148-b8ba-4335-b934-417ed71e9e91")
+
+ s.env["DEVNAME"] = "/dev/foo"
+ s.env["DEVTYPE"] = "disk"
+
+ output := newBuffer()
+ err := main.ScanDisk(output.File())
+ c.Assert(err, IsNil)
+ lines := output.GetLines()
+
+ _, hasDisk := lines["UBUNTU_DISK=1"]
+ c.Assert(hasDisk, Equals, true)
+ c.Assert(len(lines), Equals, 1)
+}
+
+func (s *scanDiskSuite) TestDetectBootDiskNotUEFIBoot(c *C) {
+ main.MockPartitionUUIDForBootedKernelDisk("ffffffff-ffff-ffff-ffff-ffffffffffff")
+
+ s.env["DEVNAME"] = "/dev/foo"
+ s.env["DEVTYPE"] = "disk"
+
+ output := newBuffer()
+ err := main.ScanDisk(output.File())
+ c.Assert(err, IsNil)
+ lines := output.GetLines()
+
+ c.Assert(len(lines), Equals, 0)
+}
+
+func (s *scanDiskSuite) TestDetectBootDiskFallback(c *C) {
+ main.MockPartitionUUIDForBootedKernelDisk("")
+
+ s.env["DEVNAME"] = "/dev/foo"
+ s.env["DEVTYPE"] = "disk"
+
+ output := newBuffer()
+ err := main.ScanDisk(output.File())
+ c.Assert(err, IsNil)
+ lines := output.GetLines()
+
+ _, hasDisk := lines["UBUNTU_DISK=1"]
+ c.Assert(hasDisk, Equals, true)
+ c.Assert(len(lines), Equals, 1)
+}
+
+func (s *scanDiskSuite) TestDetectBootDiskFallbackInstall(c *C) {
+ s.setCmdLine(c, "snapd_recovery_mode=install snapd_recovery_system=20191118")
+ s.env["DEVNAME"] = "/dev/foo"
+ s.env["DEVTYPE"] = "disk"
+
+ disk_values := make(map[string]string)
+ disk_values["PTTYPE"] = "gpt"
+ disk_probe := blkid.BuildFakeProbe(disk_values)
+ disk_probe.AddPartition("ubuntu-seed", "6ae5a792-912e-43c9-ac92-e36723bbda12")
+ s.probeMap["/dev/foo"] = disk_probe
+ delete(s.probeMap, "/dev/foop2")
+ delete(s.probeMap, "/dev/foop3")
+ delete(s.probeMap, "/dev/foop4")
+
+ output := newBuffer()
+ err := main.ScanDisk(output.File())
+ c.Assert(err, IsNil)
+ lines := output.GetLines()
+
+ _, hasDisk := lines["UBUNTU_DISK=1"]
+ c.Assert(hasDisk, Equals, true)
+ c.Assert(len(lines), Equals, 1)
+}
+
+func (s *scanDiskSuite) TestDetectBootDiskFallbackMissingBoot(c *C) {
+ s.env["DEVNAME"] = "/dev/foo"
+ s.env["DEVTYPE"] = "disk"
+
+ disk_values := make(map[string]string)
+ disk_values["PTTYPE"] = "gpt"
+ disk_probe := blkid.BuildFakeProbe(disk_values)
+ disk_probe.AddPartition("ubuntu-seed", "6ae5a792-912e-43c9-ac92-e36723bbda12")
+ disk_probe.AddPartition("ubuntu-data-enc", "c01a272d-fc72-40de-92fb-242c2da82533")
+ disk_probe.AddPartition("ubuntu-save-enc", "050ee326-ab58-4eb4-ba7d-13694b2d0c8a")
+ s.probeMap["/dev/foo"] = disk_probe
+ delete(s.probeMap, "/dev/foop2")
+
+ output := newBuffer()
+ err := main.ScanDisk(output.File())
+ c.Assert(err, IsNil)
+ lines := output.GetLines()
+
+ c.Assert(len(lines), Equals, 0)
+}
+
+func (s *scanDiskSuite) TestDetectBootDiskFallbackMissingSeedRecover(c *C) {
+ s.setCmdLine(c, "snapd_recovery_mode=recover")
+
+ s.env["DEVNAME"] = "/dev/foo"
+ s.env["DEVTYPE"] = "disk"
+
+ disk_values := make(map[string]string)
+ disk_values["PTTYPE"] = "gpt"
+ disk_probe := blkid.BuildFakeProbe(disk_values)
+ disk_probe.AddPartition("ubuntu-boot", "29261148-b8ba-4335-b934-417ed71e9e91")
+ disk_probe.AddPartition("ubuntu-data-enc", "c01a272d-fc72-40de-92fb-242c2da82533")
+ disk_probe.AddPartition("ubuntu-save-enc", "050ee326-ab58-4eb4-ba7d-13694b2d0c8a")
+ s.probeMap["/dev/foo"] = disk_probe
+ delete(s.probeMap, "/dev/foop1")
+
+ output := newBuffer()
+ err := main.ScanDisk(output.File())
+ c.Assert(err, IsNil)
+ lines := output.GetLines()
+
+ c.Assert(len(lines), Equals, 0)
+}
+
+func (s *scanDiskSuite) TestDetectBootDiskFallbackKernelParam(c *C) {
+ devFoo := filepath.Join(dirs.GlobalRootDir, "/dev/foo")
+ c.Assert(os.MkdirAll(filepath.Dir(devFoo), 0755), IsNil)
+ c.Assert(os.WriteFile(devFoo, []byte{}, 0644), IsNil)
+
+ s.setCmdLine(c, "snapd_system_disk=/dev/foo snapd_recovery_mode=run")
+
+ s.env["DEVPATH"] = "/sys/devices/foo"
+ s.env["DEVNAME"] = "/dev/foo"
+ s.env["DEVTYPE"] = "disk"
+
+ output := newBuffer()
+ err := main.ScanDisk(output.File())
+ c.Assert(err, IsNil)
+ lines := output.GetLines()
+
+ _, hasDisk := lines["UBUNTU_DISK=1"]
+ c.Assert(hasDisk, Equals, true)
+ c.Assert(len(lines), Equals, 1)
+}
+
+func (s *scanDiskSuite) TestDetectBootDiskFallbackKernelParamDevPath(c *C) {
+ devFoo := filepath.Join(dirs.GlobalRootDir, "/sys/devices/foo")
+ c.Assert(os.MkdirAll(filepath.Dir(devFoo), 0755), IsNil)
+ c.Assert(os.WriteFile(devFoo, []byte{}, 0644), IsNil)
+
+ s.setCmdLine(c, "snapd_system_disk=/sys/devices/foo snapd_recovery_mode=run")
+
+ s.env["DEVPATH"] = "/sys/devices/foo"
+ s.env["DEVNAME"] = "/dev/foo"
+ s.env["DEVTYPE"] = "disk"
+
+ output := newBuffer()
+ err := main.ScanDisk(output.File())
+ c.Assert(err, IsNil)
+ lines := output.GetLines()
+
+ _, hasDisk := lines["UBUNTU_DISK=1"]
+ c.Assert(hasDisk, Equals, true)
+ c.Assert(len(lines), Equals, 1)
+}
+
+func (s *scanDiskSuite) TestDetectBootDiskFallbackKernelParamNotMatching(c *C) {
+ devFoo := filepath.Join(dirs.GlobalRootDir, "/dev/foo")
+ c.Assert(os.MkdirAll(filepath.Dir(devFoo), 0755), IsNil)
+ c.Assert(os.WriteFile(devFoo, []byte{}, 0644), IsNil)
+ devBar := filepath.Join(dirs.GlobalRootDir, "/dev/bar")
+ c.Assert(os.MkdirAll(filepath.Dir(devBar), 0755), IsNil)
+ c.Assert(os.WriteFile(devBar, []byte{}, 0644), IsNil)
+
+ // Ask for /dev/bar instead of /dev/foo
+ s.setCmdLine(c, "snapd_system_disk=/dev/bar snapd_recovery_mode=run")
+
+ s.env["DEVPATH"] = "/sys/devices/foo"
+ s.env["DEVNAME"] = "/dev/foo"
+ s.env["DEVTYPE"] = "disk"
+
+ output := newBuffer()
+ err := main.ScanDisk(output.File())
+ c.Assert(err, IsNil)
+ lines := output.GetLines()
+ c.Check(len(lines), Equals, 0)
+}
diff --git a/core-initrd/latest/factory/usr/lib/systemd/system/snap-initramfs-mounts.service b/core-initrd/latest/factory/usr/lib/systemd/system/snap-initramfs-mounts.service
index a1fe3ae8bb1..90e84f2b179 100644
--- a/core-initrd/latest/factory/usr/lib/systemd/system/snap-initramfs-mounts.service
+++ b/core-initrd/latest/factory/usr/lib/systemd/system/snap-initramfs-mounts.service
@@ -7,6 +7,9 @@ DefaultDependencies=no
After=sysinit.target
Before=initrd-root-device.target
+BindsTo=dev-disk-snapd-disk.device
+After=dev-disk-snapd-disk.device
+
Wants=dbus.socket
After=dbus.socket
diff --git a/core-initrd/latest/factory/usr/lib/udev/rules.d/90-ubuntu-core-partitions.rules b/core-initrd/latest/factory/usr/lib/udev/rules.d/90-ubuntu-core-partitions.rules
new file mode 100644
index 00000000000..fdf52c78749
--- /dev/null
+++ b/core-initrd/latest/factory/usr/lib/udev/rules.d/90-ubuntu-core-partitions.rules
@@ -0,0 +1,18 @@
+SUBSYSTEM!="block", GOTO="ubuntu_core_partitions_end"
+
+ENV{DEVTYPE}=="disk", IMPORT{program}="/usr/lib/snapd/snap-bootstrap scan-disk"
+ENV{DEVTYPE}=="partition", IMPORT{parent}="UBUNTU_DISK"
+ENV{UBUNTU_DISK}!="1", GOTO="ubuntu_core_partitions_end"
+
+ENV{DEVTYPE}=="disk", SYMLINK+="disk/snapd/disk"
+ENV{DEVTYPE}=="partition", ENV{ID_PART_ENTRY_NAME}=="ubuntu-seed", SYMLINK+="disk/snapd/ubuntu-seed"
+ENV{DEVTYPE}=="partition", ENV{ID_PART_ENTRY_NAME}=="ubuntu-boot", SYMLINK+="disk/snapd/ubuntu-boot"
+ENV{DEVTYPE}=="partition", ENV{ID_PART_ENTRY_NAME}=="ubuntu-data", ENV{ID_FS_TYPE}=="crypto_LUKS", SYMLINK+="disk/snapd/ubuntu-data-luks"
+ENV{DEVTYPE}=="partition", ENV{ID_PART_ENTRY_NAME}=="ubuntu-data", ENV{ID_FS_TYPE}!="crypto_LUKS", SYMLINK+="disk/snapd/ubuntu-data"
+ENV{DEVTYPE}=="partition", ENV{ID_PART_ENTRY_NAME}=="ubuntu-save", ENV{ID_FS_TYPE}=="crypto_LUKS", SYMLINK+="disk/snapd/ubuntu-save-luks"
+ENV{DEVTYPE}=="partition", ENV{ID_PART_ENTRY_NAME}=="ubuntu-save", ENV{ID_FS_TYPE}!="crypto_LUKS", SYMLINK+="disk/snapd/ubuntu-save"
+
+LABEL="ubuntu_core_partitions_end"
+
+ENV{DM_UUID}=="CRYPT-*", ENV{DM_NAME}=="ubuntu-data-*", SYMLINK+="disk/snapd/ubuntu-data"
+ENV{DM_UUID}=="CRYPT-*", ENV{DM_NAME}=="ubuntu-save-*", SYMLINK+="disk/snapd/ubuntu-save"
diff --git a/osutil/disks/disks.go b/osutil/disks/disks.go
index cc78f11257e..4f04f14870a 100644
--- a/osutil/disks/disks.go
+++ b/osutil/disks/disks.go
@@ -35,6 +35,11 @@ type Options struct {
// Disk is a single physical disk device that contains partitions.
type Disk interface {
+ // FindMatchingPartitionWithUUID finds a partition with a matching
+ // partition UUID on the disk. If no matching partition is found,
+ // a PartitionNotFoundError will be returned.
+ FindMatchingPartitionWithPartUUID(string) (Partition, error)
+
// FindMatchingPartitionWithFsLabel finds the partition with a matching
// filesystem label on the disk. Note that for non-ascii labels like
// "Some label", the label will be encoded using \x for potentially
@@ -207,6 +212,8 @@ func (e PartitionNotFoundError) Error() string {
t = "partition label"
case "filesystem-label":
t = "filesystem label"
+ case "partition-uuid":
+ t = "partition uuid"
default:
return fmt.Sprintf("searching with unknown search type %q and search query %q did not return a partition", e.SearchType, e.SearchQuery)
}
diff --git a/osutil/disks/disks_linux.go b/osutil/disks/disks_linux.go
index 39978d46340..1b4bdf3b80f 100644
--- a/osutil/disks/disks_linux.go
+++ b/osutil/disks/disks_linux.go
@@ -848,6 +848,23 @@ func (d *disk) populatePartitions() error {
return nil
}
+func (d *disk) FindMatchingPartitionWithPartUUID(uuid string) (Partition, error) {
+ if err := d.populatePartitions(); err != nil {
+ return Partition{}, err
+ }
+
+ for _, p := range d.partitions {
+ if p.PartitionUUID == uuid {
+ return p, nil
+ }
+ }
+
+ return Partition{}, PartitionNotFoundError{
+ SearchType: "partition-uuid",
+ SearchQuery: uuid,
+ }
+}
+
func (d *disk) FindMatchingPartitionWithPartLabel(label string) (Partition, error) {
// always encode the label
encodedLabel := BlkIDEncodeLabel(label)
diff --git a/osutil/disks/disks_linux_test.go b/osutil/disks/disks_linux_test.go
index d38ad981be7..31751e6ad40 100644
--- a/osutil/disks/disks_linux_test.go
+++ b/osutil/disks/disks_linux_test.go
@@ -2079,3 +2079,21 @@ func (s *diskSuite) TestDevlinks(c *C) {
_, err = disks.Devlinks("/dev/some/error")
c.Check(err, ErrorMatches, `cannot process udev properties: some error`)
}
+
+func (s *diskSuite) TestFindMatchingPartitionWithPartUUID(c *C) {
+ restore := disks.MockDeviceNameToDiskMapping(map[string]*disks.MockDiskMapping{
+ "/dev/vda": gadgettest.VMSystemVolumeDiskMappingSeedFsLabelCaps,
+ })
+ defer restore()
+
+ d, err := disks.DiskFromDeviceName("/dev/vda")
+ c.Assert(err, IsNil)
+
+ p, err := d.FindMatchingPartitionWithPartUUID("ade3ba65-7831-fd40-bbe2-e01c9774ed5b")
+ c.Assert(err, IsNil)
+ c.Check(p.KernelDeviceNode, Equals, "/dev/vda2")
+ c.Check(p.PartitionUUID, Equals, "ade3ba65-7831-fd40-bbe2-e01c9774ed5b")
+
+ _, err = d.FindMatchingPartitionWithPartUUID("fe1ec853-15b1-4c72-a207-6a9b185dcbbb")
+ c.Assert(err, ErrorMatches, "partition uuid \"fe1ec853-15b1-4c72-a207-6a9b185dcbbb\" not found")
+}
diff --git a/osutil/disks/mockdisk.go b/osutil/disks/mockdisk.go
index 5583c51d4b1..7c5f550381b 100644
--- a/osutil/disks/mockdisk.go
+++ b/osutil/disks/mockdisk.go
@@ -62,6 +62,22 @@ type MockDiskMapping struct {
DiskSizeInBytes uint64
}
+func (d *MockDiskMapping) FindMatchingPartitionWithPartUUID(uuid string) (Partition, error) {
+ // TODO: this should just iterate over the static list when that is a thing
+ osutil.MustBeTestBinary("mock disks only to be used in tests")
+
+ for _, p := range d.Structure {
+ if p.PartitionUUID == uuid {
+ return p, nil
+ }
+ }
+
+ return Partition{}, PartitionNotFoundError{
+ SearchType: "partition-uuid",
+ SearchQuery: uuid,
+ }
+}
+
// FindMatchingPartitionUUIDWithFsLabel returns a matching PartitionUUID
// for the specified filesystem label if it exists. Part of the Disk interface.
func (d *MockDiskMapping) FindMatchingPartitionWithFsLabel(label string) (Partition, error) {
diff --git a/osutil/disks/mockdisk_test.go b/osutil/disks/mockdisk_test.go
index 72f8b0b656c..0b5257fedd3 100644
--- a/osutil/disks/mockdisk_test.go
+++ b/osutil/disks/mockdisk_test.go
@@ -395,6 +395,14 @@ func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMapping(c *C) {
PartitionUUID: "part1",
})
+ part, err = foundDisk.FindMatchingPartitionWithPartUUID("part1")
+ c.Assert(err, IsNil)
+ c.Assert(part, DeepEquals, disks.Partition{
+ PartitionLabel: "part-label1",
+ FilesystemLabel: "label1",
+ PartitionUUID: "part1",
+ })
+
// and it has the right set of partitions
parts, err := foundDisk.Partitions()
c.Assert(err, IsNil)
@@ -447,6 +455,14 @@ func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMapping(c *C) {
PartitionUUID: "part2",
})
+ part, err = foundDisk2.FindMatchingPartitionWithPartUUID("part2")
+ c.Assert(err, IsNil)
+ c.Assert(part, DeepEquals, disks.Partition{
+ PartitionLabel: "part-label2",
+ FilesystemLabel: "label2",
+ PartitionUUID: "part2",
+ })
+
// and it has the right set of partitions
parts, err = foundDisk2.Partitions()
c.Assert(err, IsNil)
diff --git a/packaging/debian-sid/control b/packaging/debian-sid/control
index c0fd9a08e44..78374c4d79e 100644
--- a/packaging/debian-sid/control
+++ b/packaging/debian-sid/control
@@ -43,6 +43,7 @@ Build-Depends: autoconf,
grub-common,
indent,
libapparmor-dev,
+ libblkid-dev,
libcap-dev,
libglib2.0-dev,
liblzo2-dev,
diff --git a/packaging/ubuntu-14.04/control b/packaging/ubuntu-14.04/control
index 72fd10c18bb..1bc359a9591 100644
--- a/packaging/ubuntu-14.04/control
+++ b/packaging/ubuntu-14.04/control
@@ -23,6 +23,7 @@ Build-Depends: autoconf,
indent,
init-system-helpers,
libapparmor-dev,
+ libblkid-dev,
libcap-dev,
libglib2.0-dev,
libseccomp-dev (>= 2.1.1-1ubuntu1~trusty4),
diff --git a/packaging/ubuntu-16.04/control b/packaging/ubuntu-16.04/control
index c166025d58b..578a2d79ede 100644
--- a/packaging/ubuntu-16.04/control
+++ b/packaging/ubuntu-16.04/control
@@ -24,6 +24,7 @@ Build-Depends: autoconf,
indent,
init-system-helpers,
libapparmor-dev,
+ libblkid-dev,
libcap-dev,
libfuse3-dev (>= 3.10.5-1) | libfuse-dev,
libglib2.0-dev,
diff --git a/tests/cross/go-build/task.yaml b/tests/cross/go-build/task.yaml
index f7334b0f9c8..d73aa49528c 100644
--- a/tests/cross/go-build/task.yaml
+++ b/tests/cross/go-build/task.yaml
@@ -50,7 +50,7 @@ prepare: |
EOF
dpkg --add-architecture "$X_DEBARCH"
apt --quiet -o Dpkg::Progress-Fancy=false update
- apt --yes --quiet -o Dpkg::Progress-Fancy=false install "$X_GCC" libseccomp2:"$X_DEBARCH" libseccomp-dev:"$X_DEBARCH"
+ apt --yes --quiet -o Dpkg::Progress-Fancy=false install "$X_GCC" libseccomp2:"$X_DEBARCH" libseccomp-dev:"$X_DEBARCH" libblkid-dev:"$X_DEBARCH"
restore: |
rm -rf /tmp/cross-build
diff --git a/tests/nested/manual/core22-basic/task.yaml b/tests/nested/manual/core22-basic/task.yaml
index 297098fe87a..30f1eb84470 100644
--- a/tests/nested/manual/core22-basic/task.yaml
+++ b/tests/nested/manual/core22-basic/task.yaml
@@ -81,3 +81,14 @@ execute: |
remote.exec "test -f /run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key"
remote.exec "test -f /run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key"
fi
+
+ if os.query is-ubuntu-ge 24.04; then
+ remote.exec "udevadm info --query=name /dev/disk/snapd/disk" | MATCH "."
+ remote.exec "udevadm info --query=property --property=ID_PART_ENTRY_NAME --value /dev/disk/snapd/ubuntu-seed" | MATCH "^ubuntu-seed$"
+ remote.exec "udevadm info --query=property --property=ID_PART_ENTRY_NAME --value /dev/disk/snapd/ubuntu-boot" | MATCH "^ubuntu-boot$"
+ remote.exec "udevadm info --query=property --property=ID_PART_ENTRY_NAME --value /dev/disk/snapd/ubuntu-data-luks" | MATCH "^ubuntu-data$"
+ remote.exec "udevadm info --query=property --property=ID_PART_ENTRY_NAME --value /dev/disk/snapd/ubuntu-save-luks" | MATCH "^ubuntu-save$"
+ # TODO: when udev rules are available in core-base, we should also test those
+ #remote.exec "udevadm info --query=property --property=DM_NAME --value /dev/disk/snapd/ubuntu-data" | MATCH "^ubuntu-data"
+ #remote.exec "udevadm info --query=property --property=DM_NAME --value /dev/disk/snapd/ubuntu-save" | MATCH "^ubuntu-save"
+ fi
diff --git a/tests/nested/manual/remodel-uc-to-next-version-fakestore/repack-kernel.sh b/tests/nested/manual/remodel-uc-to-next-version-fakestore/repack-kernel.sh
index 16f24d890e3..d23190e1855 100644
--- a/tests/nested/manual/remodel-uc-to-next-version-fakestore/repack-kernel.sh
+++ b/tests/nested/manual/remodel-uc-to-next-version-fakestore/repack-kernel.sh
@@ -24,7 +24,7 @@ done
add-apt-repository ppa:snappy-dev/image -y
# TODO:FDEM:FIX: this will need changes for UC24.
-apt-get install -y golang ubuntu-core-initramfs
+apt-get install -y golang ubuntu-core-initramfs libblkid-dev
snap download pc-kernel --channel="${version}/${branch}" --basename=pc-kernel --target-directory="${tmpd}"
unsquashfs -d "${tmpd}/pc-kernel" "${tmpd}/pc-kernel.snap"