diff --git a/cmd/snap-bootstrap/cmd_scan_disk.go b/cmd/snap-bootstrap/cmd_scan_disk.go
new file mode 100644
index 00000000000..98ee6f9390a
--- /dev/null
+++ b/cmd/snap-bootstrap/cmd_scan_disk.go
@@ -0,0 +1,253 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2022-2023 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
+
+//#cgo CFLAGS: -D_FILE_OFFSET_BITS=64
+//#cgo pkg-config: blkid
+//#cgo LDFLAGS:
+//
+//#include
+//#include
+import "C"
+
+import (
+ "fmt"
+ "os"
+ "strings"
+ "unsafe"
+
+ "github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/bootloader/efi"
+)
+
+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()
+}
+
+type Partition struct {
+ Name string
+ UUID string
+}
+
+func getProbeValue(probe C.blkid_probe, name string) (string, error) {
+ var value *C.char
+ var value_len C.size_t
+ entryname := C.CString(name)
+ defer C.free(unsafe.Pointer(entryname))
+ res := C.blkid_probe_lookup_value(probe, entryname, &value, &value_len)
+ if res < 0 {
+ return "", fmt.Errorf("Probe value was not found: %s", name)
+ }
+ if value_len > 0 {
+ return C.GoStringN(value, C.int(value_len-1)), nil
+ } else {
+ return "", fmt.Errorf("Probe value has unexpected size")
+ }
+}
+
+func isGpt(probe C.blkid_probe) bool {
+ pttype, err := getProbeValue(probe, "PTTYPE")
+ if err != nil {
+ return false
+ }
+ return pttype == "gpt"
+}
+
+func probePartitions(node string) ([]Partition, error) {
+ cnode := C.CString(node)
+ defer C.free(unsafe.Pointer(cnode))
+ probe, err := C.blkid_new_probe_from_filename(cnode)
+ if probe == nil {
+ return nil, err
+ }
+ defer C.blkid_free_probe(probe)
+
+ C.blkid_probe_enable_partitions(probe, 1)
+ C.blkid_probe_set_partitions_flags(probe, C.BLKID_PARTS_ENTRY_DETAILS)
+ C.blkid_probe_enable_superblocks(probe, 1)
+
+ res, err := C.blkid_do_safeprobe(probe)
+ if res < 0 {
+ return nil, err
+ }
+
+ if !isGpt(probe) {
+ return nil, nil
+ }
+
+ partitions, err := C.blkid_probe_get_partitions(probe)
+ if partitions == nil {
+ return nil, err
+ }
+
+ npartitions := C.blkid_partlist_numof_partitions(partitions)
+ ret := make([]Partition, 0)
+ for i := 0; i < int(npartitions); i++ {
+ partition := C.blkid_partlist_get_partition(partitions, C.int(i))
+ label := C.GoString(C.blkid_partition_get_name(partition))
+ uuid := C.GoString(C.blkid_partition_get_uuid(partition))
+ fmt.Fprintf(os.Stderr, "Found partition %s %s\n", label, uuid)
+ ret = append(ret, Partition{label, uuid})
+ }
+
+ return ret, nil
+}
+
+func scanDiskNode(node string) error {
+ fmt.Fprintf(os.Stderr, "Scanning disk %s\n", node)
+ fallback := false
+ bootUUID, _, err := efi.ReadVarString("LoaderDevicePartUUID-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f")
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "No efi var, falling back: %s\n", err)
+ fallback = true
+ } else {
+ bootUUID = strings.ToLower(bootUUID)
+ }
+
+ partitions, err := probePartitions(node)
+ if err != nil {
+ return fmt.Errorf("Cannot get partitions: %s\n", err)
+ }
+
+ found := false
+ has_seed := false
+ var seed_uuid string
+ has_boot := false
+ var boot_uuid string
+ has_data := false
+ var data_uuid string
+ has_save := false
+ var save_uuid string
+ for _, part := range partitions {
+ if !fallback {
+ if part.UUID == bootUUID {
+ found = true
+ }
+ }
+ if part.Name == "ubuntu-seed" {
+ has_seed = true
+ seed_uuid = part.UUID
+ } else if part.Name == "ubuntu-boot" {
+ has_boot = true
+ boot_uuid = part.UUID
+ } else if part.Name == "ubuntu-data-enc" {
+ has_data = true
+ data_uuid = part.UUID
+ } else if part.Name == "ubuntu-data" {
+ has_data = true
+ data_uuid = part.UUID
+ } else if part.Name == "ubuntu-save-enc" {
+ has_save = true
+ save_uuid = part.UUID
+ } else if part.Name == "ubuntu-save" {
+ has_save = true
+ save_uuid = part.UUID
+ }
+ }
+
+ if (!fallback && found) || (fallback && has_seed) {
+ if has_seed {
+ fmt.Fprintf(os.Stderr, "Detected partition for seed: %s\n", seed_uuid)
+ fmt.Printf("UBUNTU_SEED_UUID=%s\n", seed_uuid)
+ }
+ if has_boot {
+ fmt.Fprintf(os.Stderr, "Detected partition for boot: %s\n", boot_uuid)
+ fmt.Printf("UBUNTU_BOOT_UUID=%s\n", boot_uuid)
+ }
+ if has_data {
+ fmt.Fprintf(os.Stderr, "Detected partition for data: %s\n", data_uuid)
+ fmt.Printf("UBUNTU_DATA_UUID=%s\n", data_uuid)
+ }
+ if has_save {
+ fmt.Fprintf(os.Stderr, "Detected partition for save: %s\n", save_uuid)
+ fmt.Printf("UBUNTU_SAVE_UUID=%s\n", save_uuid)
+ }
+ }
+
+ return nil
+}
+
+func checkPartitionUUID(suffix string, partUUID string) {
+ varname := fmt.Sprintf("UBUNTU_%s_UUID", suffix)
+ expectedUUID := os.Getenv(varname)
+ if len(expectedUUID) > 0 && expectedUUID == partUUID {
+ fmt.Fprintf(os.Stderr, "Detected partition as %s\n", suffix)
+ fmt.Printf("UBUNTU_%s=1\n", suffix)
+ }
+}
+
+func scanPartitionNode(node string) error {
+ fmt.Fprintf(os.Stderr, "Scanning partition %s\n", node)
+
+ cnode := C.CString(node)
+ defer C.free(unsafe.Pointer(cnode))
+ probe, err := C.blkid_new_probe_from_filename(cnode)
+ if probe == nil {
+ return fmt.Errorf("Cannot create probe for partition %s: %s\n", node, err)
+ }
+ defer C.blkid_free_probe(probe)
+
+ C.blkid_probe_enable_partitions(probe, 1)
+ C.blkid_probe_set_partitions_flags(probe, C.BLKID_PARTS_ENTRY_DETAILS)
+ C.blkid_probe_enable_superblocks(probe, 1)
+
+ res, err := C.blkid_do_safeprobe(probe)
+ if res < 0 {
+ return fmt.Errorf("Cannot probe partition %s: %s\n", node, err)
+ }
+
+ partUUID, err := getProbeValue(probe, "PART_ENTRY_UUID")
+ if err != nil {
+ return fmt.Errorf("Cannot get uuid for partition: %s\n", err)
+ }
+
+ for _, suffix := range []string{"SEED", "BOOT", "DATA", "SAVE"} {
+ checkPartitionUUID(suffix, partUUID)
+ }
+
+ return nil
+}
+
+func scanDisk() error {
+ devname := os.Getenv("DEVNAME")
+ if os.Getenv("DEVTYPE") == "disk" {
+ return scanDiskNode(devname)
+ } else if os.Getenv("DEVTYPE") == "partition" {
+ return scanPartitionNode(devname)
+ } else {
+ return fmt.Errorf("Unknown type for block device %s\n", devname)
+ }
+}
diff --git a/packaging/debian-sid/control b/packaging/debian-sid/control
index 86e27b413f7..4f5900b5e6c 100644
--- a/packaging/debian-sid/control
+++ b/packaging/debian-sid/control
@@ -42,6 +42,7 @@ Build-Depends: autoconf,
golang-gopkg-yaml.v3-dev,
golang-go (>=2:1.13),
indent,
+ libblkid-dev,
libcap-dev,
libapparmor-dev,
libglib2.0-dev,
diff --git a/packaging/ubuntu-14.04/control b/packaging/ubuntu-14.04/control
index 29df85cef70..3046a2a43b8 100644
--- a/packaging/ubuntu-14.04/control
+++ b/packaging/ubuntu-14.04/control
@@ -21,6 +21,7 @@ Build-Depends: autoconf,
golang-any (>=2:1.13) | golang-1.13,
indent,
init-system-helpers,
+ libblkid-dev,
libcap-dev,
libapparmor-dev,
libglib2.0-dev,
diff --git a/packaging/ubuntu-16.04/control b/packaging/ubuntu-16.04/control
index eb244fe36ed..87eeec4595c 100644
--- a/packaging/ubuntu-16.04/control
+++ b/packaging/ubuntu-16.04/control
@@ -22,6 +22,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 1150d37f4f4..f148113cc1a 100644
--- a/tests/cross/go-build/task.yaml
+++ b/tests/cross/go-build/task.yaml
@@ -46,7 +46,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