From 8e9bfc770d9f37117fe57fddc2eda31640b6bfee Mon Sep 17 00:00:00 2001 From: Valentin David Date: Thu, 28 Jul 2022 17:37:48 +0200 Subject: [PATCH] snap-bootstrap: Add scan-disk subcommand This command is expected to be called from udev. This creates symlinks `/dev/ubuntu/ubuntu-{seed,boot,data,save}`. Then we can bind `snap-bootstrap initramfs-mounts` to `dev-ubuntu-ubuntu-seed.device`. --- cmd/snap-bootstrap/cmd_scan_disk.go | 253 ++++++++++++++++++++++++++++ packaging/debian-sid/control | 1 + packaging/ubuntu-14.04/control | 1 + packaging/ubuntu-16.04/control | 1 + tests/cross/go-build/task.yaml | 2 +- 5 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 cmd/snap-bootstrap/cmd_scan_disk.go 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