From 6a370b6e62feb827062acd57ed1fddd1d4396e3b Mon Sep 17 00:00:00 2001 From: Erikson Tung Date: Wed, 15 Feb 2023 13:33:20 -0800 Subject: [PATCH 1/3] release, rpm2img, partyplanner: label BOTTLEROCKET-DATA during firstboot Changes partitioning to always keep a data partition on the os image. We grow and label data partition at runtime. We will still create a separate data partiion on the second volume/disk for "split" partition layouts. We add two new services that run as part of 'local-fs.target' and before 'local.mount' and 'repart-local.service': * 'label-data-a.service' * 'label-data-b.service' 'label-data-a' and 'label-data-b' will "compete" and both try to label 'BOTTLEROCKET-DATA' first with the partition they're each waiting for. 'label-data-a' waits for the data partition that resides on the OS disk image. Once that device is ready, we call 'systemd-repart' to relabel it as 'BOTTLEROCKET-DATA' and grow it as much as possible. 'label-data-b' calls 'systemd-repart' to label the data partition on the data image as 'BOTTLEROCKET-DATA' and grow the partition to fill the remainder of the disk. All of this lets the host to boot if data partition on the data image doesn't exist and the root filesystem disk has leftover extra space to accommodate a reasonably-sized back-up data partition. --- packages/release/label-data-a.service | 20 ++++++++ packages/release/label-data-b.service | 20 ++++++++ packages/release/release-repart-local.conf | 3 ++ packages/release/release.spec | 8 +++- packages/release/repart-local.service | 2 +- tools/partyplanner | 53 ++++++++++++++++++---- tools/rpm2img | 44 ++++++++++++------ 7 files changed, 123 insertions(+), 27 deletions(-) create mode 100644 packages/release/label-data-a.service create mode 100644 packages/release/label-data-b.service diff --git a/packages/release/label-data-a.service b/packages/release/label-data-a.service new file mode 100644 index 00000000000..516dba658c6 --- /dev/null +++ b/packages/release/label-data-a.service @@ -0,0 +1,20 @@ +[Unit] +Description=Label data partition A +DefaultDependencies=no +Conflicts=shutdown.target +# Only run this if a partition labeled 'BOTTLEROCKET-DATA' does not exist already. +ConditionPathIsSymbolicLink=!/dev/disk/by-partlabel/BOTTLEROCKET-DATA +# This is the partition GUID for the DATA-A partition. +Wants=dev-disk-by\x2dpartuuid-5b94e8df\x2d28b8\x2d485c\x2d9d19\x2d362263b5944c.device +After=dev-disk-by\x2dpartuuid-5b94e8df\x2d28b8\x2d485c\x2d9d19\x2d362263b5944c.device + +[Service] +Type=oneshot + +# Label the partition as 'BOTTLEROCKET-DATA' and resize the partition, whether or not it resides on the same disk as /. +ExecStart=-/usr/bin/systemd-repart --dry-run=no /dev/disk/by-partuuid/5b94e8df-28b8-485c-9d19-362263b5944c + +RemainAfterExit=true + +[Install] +WantedBy=local-fs.target diff --git a/packages/release/label-data-b.service b/packages/release/label-data-b.service new file mode 100644 index 00000000000..d9ef3f4c4de --- /dev/null +++ b/packages/release/label-data-b.service @@ -0,0 +1,20 @@ +[Unit] +Description=Label data partition B +DefaultDependencies=no +Conflicts=shutdown.target +# Only run this if a partition labeled 'BOTTLEROCKET-DATA' does not exist already. +ConditionPathIsSymbolicLink=!/dev/disk/by-partlabel/BOTTLEROCKET-DATA +# This is the partition GUID for the fallback data partition. +Wants=dev-disk-by\x2dpartuuid-69040874\x2d417d\x2d4e26\x2da764\x2d7885f22007ea.device +After=dev-disk-by\x2dpartuuid-69040874\x2d417d\x2d4e26\x2da764\x2d7885f22007ea.device + +[Service] +Type=oneshot + +# Label the partition as 'BOTTLEROCKET-DATA' and resize the partition, whether or not it resides on the same disk as /. +ExecStart=-/usr/bin/systemd-repart --dry-run=no /dev/disk/by-partuuid/69040874-417d-4e26-a764-7885f22007ea + +RemainAfterExit=true + +[Install] +WantedBy=local-fs.target diff --git a/packages/release/release-repart-local.conf b/packages/release/release-repart-local.conf index c026191367a..54ff6debf59 100644 --- a/packages/release/release-repart-local.conf +++ b/packages/release/release-repart-local.conf @@ -1,4 +1,7 @@ [Partition] +# We want to label this partition 'BOTTLEROCKET-DATA' +Label=BOTTLEROCKET-DATA + # This is the partition type UUID for BOTTLEROCKET-DATA, which will be resized # to fill the remaining sectors on the disk where it resides. Type=626f7474-6c65-6474-6861-726d61726b73 diff --git a/packages/release/release.spec b/packages/release/release.spec index ebd4304d24e..cc73c31642a 100644 --- a/packages/release/release.spec +++ b/packages/release/release.spec @@ -57,6 +57,8 @@ Source1043: repart-local.service Source1044: mask-local-mnt.service Source1045: mask-local-opt.service Source1046: mask-local-var.service +Source1047: label-data-b.service +Source1048: label-data-a.service # Services for kdump support Source1060: capture-kernel-dump.service @@ -114,7 +116,7 @@ Requires: %{_cross_os}util-linux install -d %{buildroot}%{_cross_factorydir}%{_cross_sysconfdir} install -p -m 0644 %{S:11} %{buildroot}%{_cross_factorydir}%{_cross_sysconfdir} -install -d %{buildroot}%{_cross_libdir}/repart.d +install -d %{buildroot}%{_cross_libdir}/repart.d/ install -p -m 0644 %{S:96} %{buildroot}%{_cross_libdir}/repart.d/80-local.conf install -d %{buildroot}%{_cross_sysctldir} @@ -139,7 +141,7 @@ install -p -m 0644 \ %{S:1001} %{S:1002} %{S:1003} %{S:1004} %{S:1005} %{S:1006} %{S:1007} \ %{S:1008} %{S:1009} %{S:1010} %{S:1011} %{S:1012} %{S:1013} %{S:1015} \ %{S:1040} %{S:1041} %{S:1042} %{S:1043} %{S:1044} %{S:1045} %{S:1046} \ - %{S:1060} %{S:1061} %{S:1062} %{S:1080} %{S:1014} \ + %{S:1047} %{S:1048} %{S:1060} %{S:1061} %{S:1062} %{S:1080} %{S:1014} \ %{buildroot}%{_cross_unitdir} install -d %{buildroot}%{_cross_unitdir}/systemd-tmpfiles-setup.service.d @@ -220,6 +222,8 @@ ln -s preconfigured.target %{buildroot}%{_cross_unitdir}/default.target %{_cross_unitdir}/mask-local-opt.service %{_cross_unitdir}/mask-local-var.service %{_cross_unitdir}/root-.aws.mount +%{_cross_unitdir}/label-data-b.service +%{_cross_unitdir}/label-data-a.service %dir %{_cross_unitdir}/systemd-tmpfiles-setup.service.d %{_cross_unitdir}/systemd-tmpfiles-setup.service.d/00-debug.conf %dir %{_cross_templatedir} diff --git a/packages/release/repart-local.service b/packages/release/repart-local.service index bbcb18f9214..f74d74dc818 100644 --- a/packages/release/repart-local.service +++ b/packages/release/repart-local.service @@ -13,7 +13,7 @@ RequiresMountsFor=/local Type=oneshot # Resize the partition, whether or not it resides on the same disk as /. -ExecStart=/usr/bin/systemd-repart --dry-run=no /dev/disk/by-partlabel/BOTTLEROCKET-DATA +ExecStart=-/usr/bin/systemd-repart --dry-run=no /dev/disk/by-partlabel/BOTTLEROCKET-DATA # Grow the filesystem to fill the partition. Doing this in another unit could # introduce a race if the underlying block device is not ready after resizing. diff --git a/tools/partyplanner b/tools/partyplanner index b3bd1a18c77..8ad839d68a5 100755 --- a/tools/partyplanner +++ b/tools/partyplanner @@ -2,7 +2,7 @@ # shellcheck disable=SC2034 # Variables are used externally by rpm2img ############################################################################### -# Section 1: partition type GUIDs +# Section 1: partition type GUIDs and partition GUIDs # Define partition type GUIDs for all OS-managed partitions. This is required # for the boot partition, where we set gptprio bits in the GUID-specific use @@ -40,6 +40,11 @@ EFI_SYSTEM_TYPECODE="C12A7328-F81F-11D2-BA4B-00A0C93EC93B" # for an alternate bank. EFI_BACKUP_TYPECODE="B39CE39C-0A00-B4AB-2D11-F18F8237A21C" +# Define partition GUIDs for the data partitions. We use the GUID for determining +# which data partition to label and use at boot. +BOTTLEROCKET_DATA_A_PARTGUID="5b94e8df-28b8-485c-9d19-362263b5944c" +BOTTLEROCKET_DATA_B_PARTGUID="69040874-417d-4e26-a764-7885f22007ea" + ############################################################################### # Section 2: fixed size partitions and reservations @@ -62,6 +67,9 @@ OVERHEAD_MIB="$((GPT_MIB * 2 + BIOS_MIB))" # EFI partition size by taking space from the "reserved" area below. EFI_MIB="5" # one per bank +# Allocate 1 MiB for the initial data partition A. +DATA_A_MIB="1" # one per disk + ############################################################################### # Section 3: variable sized partitions @@ -102,7 +110,8 @@ PRIVATE_SCALE_FACTOR="24" # | Hash partition B 5 MiB* | 500 MiB # | Reserved partition B 10 MiB* | # +---------------------------------+ -# | Private partition 18 MiB* | (image size * 24 as MiB) - prelude +# | Private partition 17 MiB* | (image size * 24 as MiB) - prelude - DATA-A size +# | Data partition A 1 MiB | Data partition A # Postlude | GPT footer 1 MiB | GPT is fixed, private partition grows. # +---------------------------------+ @@ -139,6 +148,8 @@ set_partition_sizes() { # Private space scales per GiB, minus the BIOS and GPT partition overhead. private_mib=$((os_image_gib * PRIVATE_SCALE_FACTOR - OVERHEAD_MIB)) + # We need 1 MiB of space for data partition A. + private_mib=$((private_mib - DATA_A_MIB)) # Skip the GPT label at start of disk. local offset @@ -176,16 +187,21 @@ set_partition_sizes() { case "${partition_plan}" in split) + # For data partition A that lives on the OS image + pp_offset["DATA-A"]="${offset}" + pp_size["DATA-A"]="${DATA_A_MIB}" + ((offset += DATA_A_MIB)) + # For a split data image, the first and last MiB are reserved for the GPT - # labels, and the rest is for the "data" partition. - pp_size["DATA"]="$((data_image_gib * 1024 - GPT_MIB * 2))" - pp_offset["DATA"]="1" + # labels, and the rest is for data partition B. + pp_size["DATA-B"]="$((data_image_gib * 1024 - GPT_MIB * 2))" + pp_offset["DATA-B"]="1" ;; unified) # For a unified image, we've already accounted for the GPT label space in - # the earlier calculations, so all the space is for the "data" partition. - pp_size["DATA"]="$((data_image_gib * 1024))" - pp_offset["DATA"]="${offset}" + # the earlier calculations, so all the space is for the data partition. + pp_size["DATA-A"]="$((data_image_gib * 1024))" + pp_offset["DATA-A"]="${offset}" ((offset += data_image_gib * 1024)) ;; *) @@ -200,9 +216,12 @@ set_partition_labels() { local -n pp_label pp_label="${1:?}" pp_label["BIOS"]="BIOS-BOOT" - pp_label["DATA"]="BOTTLEROCKET-DATA" pp_label["EFI-A"]="EFI-SYSTEM" pp_label["EFI-B"]="EFI-BACKUP" + # Empty label for the data partitions. We're labeling the data partition + # during boot. + pp_label["DATA-A"]="" + pp_label["DATA-B"]="" pp_label["PRIVATE"]="BOTTLEROCKET-PRIVATE" for part in BOOT ROOT HASH RESERVED ; do for bank in A B ; do @@ -216,7 +235,8 @@ set_partition_types() { local -n pp_type pp_type="${1:?}" pp_type["BIOS"]="${BIOS_BOOT_TYPECODE}" - pp_type["DATA"]="${BOTTLEROCKET_DATA_TYPECODE}" + pp_type["DATA-A"]="${BOTTLEROCKET_DATA_TYPECODE}" + pp_type["DATA-B"]="${BOTTLEROCKET_DATA_TYPECODE}" pp_type["EFI-A"]="${EFI_SYSTEM_TYPECODE}" pp_type["EFI-B"]="${EFI_BACKUP_TYPECODE}" pp_type["PRIVATE"]="${BOTTLEROCKET_PRIVATE_TYPECODE}" @@ -229,3 +249,16 @@ set_partition_types() { done done } + +# Populate the caller's table with GPT partition UUIDs for DATA-A and +# DATA-B partitions. +set_partition_uuids() { + local -n pp_uuid + pp_uuid="${1:?}" + local uuid + for bank in A B ; do + uuid="BOTTLEROCKET_DATA_${bank}_PARTGUID" + uuid="${!uuid}" + pp_uuid["DATA-${bank}"]="${uuid}" + done +} diff --git a/tools/rpm2img b/tools/rpm2img index b05914a0374..36bf0ea865a 100755 --- a/tools/rpm2img +++ b/tools/rpm2img @@ -109,7 +109,8 @@ ROOT_IMAGE="$(mktemp)" DATA_IMAGE="$(mktemp)" EFI_IMAGE="$(mktemp)" PRIVATE_IMAGE="$(mktemp)" -BOTTLEROCKET_DATA="$(mktemp)" +BOTTLEROCKET_DATA_A="$(mktemp)" +BOTTLEROCKET_DATA_B="$(mktemp)" ROOT_MOUNT="$(mktemp -d)" BOOT_MOUNT="$(mktemp -d)" @@ -136,22 +137,23 @@ case "${PARTITION_PLAN}" in ;; esac -declare -A partlabel parttype partsize partoff +declare -A partlabel parttype partguid partsize partoff set_partition_sizes \ "${OS_IMAGE_SIZE_GIB}" "${DATA_IMAGE_SIZE_GIB}" "${PARTITION_PLAN}" \ partsize partoff set_partition_labels partlabel set_partition_types parttype +set_partition_uuids partguid declare -a partargs for part in \ BIOS \ EFI-A BOOT-A ROOT-A HASH-A RESERVED-A \ EFI-B BOOT-B ROOT-B HASH-B RESERVED-B \ - PRIVATE DATA ; + PRIVATE DATA-A DATA-B ; do - # We only append the data partition if we're using the unified layout. - if [ "${part}" == "DATA" ] && [ "${PARTITION_PLAN}" != "unified" ] ; then + # We create the DATA-B partition separately if we're using the split layout + if [ "${part}" == "DATA-B" ] ; then continue fi @@ -166,6 +168,7 @@ do partargs+=(-n "0:${part_start}M:${part_end}") partargs+=(-c "0:${partlabel[${part}]}") partargs+=(-t "0:${parttype[${part}]}") + partargs+=(-u "0:${partguid[${part}]:-R}") # Boot partition attributes: # 48 = gptprio priority bit @@ -180,13 +183,14 @@ sgdisk --clear "${partargs[@]}" --sort --print "${OS_IMAGE}" # Partition the separate data disk, if we're using the split layout. if [ "${PARTITION_PLAN}" == "split" ] ; then - data_start="${partoff[DATA]}" - data_end=$((data_start + partsize[DATA])) + data_start="${partoff[DATA-B]}" + data_end=$((data_start + partsize[DATA-B])) data_end=$((data_end * 2048 - 1)) sgdisk --clear \ -n "0:${data_start}M:${data_end}" \ - -c "0:${partlabel[DATA]}" \ - -t "0:${parttype[DATA]}" \ + -c "0:${partlabel[DATA-B]}" \ + -t "0:${parttype[DATA-B]}" \ + -u "0:${partguid[DATA-B]}" \ --sort --print "${DATA_IMAGE}" fi @@ -383,7 +387,7 @@ cp /host/tools/bootconfig/empty-bootconfig.data "${PRIVATE_MOUNT}/bootconfig.dat mkfs.ext4 -b 4096 -i 4096 -I 256 -d "${PRIVATE_MOUNT}" "${PRIVATE_IMAGE}" "${partsize[PRIVATE]}M" dd if="${PRIVATE_IMAGE}" of="${OS_IMAGE}" conv=notrunc bs=1M seek="${partoff[PRIVATE]}" -# BOTTLEROCKET-DATA +# BOTTLEROCKET-DATA-A and BOTTLEROCKET-DATA-B # If we build on a host with SELinux enabled, we could end up with labels that # do not match our policy. Since we allow replacing the data volume at runtime, @@ -391,14 +395,26 @@ dd if="${PRIVATE_IMAGE}" of="${OS_IMAGE}" conv=notrunc bs=1M seek="${partoff[PRI # remove them all. UNLABELED=$(find "${DATA_MOUNT}" \ | awk -v root="${DATA_MOUNT}" '{gsub(root"/","/"); gsub(root,"/"); print "ea_rm", $1, "security.selinux"}') -mkfs.ext4 -d "${DATA_MOUNT}" "${BOTTLEROCKET_DATA}" "${partsize[DATA]}M" -echo "${UNLABELED}" | debugfs -w -f - "${BOTTLEROCKET_DATA}" + +for side in A B ; do + if [ "${side}" == "B" ] && [ "${PARTITION_PLAN}" == "unified" ] ; then + continue + fi + dev="BOTTLEROCKET_DATA_${side}" + dev="${!dev}" + mkfs.ext4 -d "${DATA_MOUNT}" "${dev}" "${partsize["DATA-${side}"]}M" + echo "${UNLABELED}" | debugfs -w -f - "${dev}" +done + case "${PARTITION_PLAN}" in split) - dd if="${BOTTLEROCKET_DATA}" of="${DATA_IMAGE}" conv=notrunc bs=1M seek="${partoff[DATA]}" + # Only create data partition B on data image in "split" image layout configurations + dd if="${BOTTLEROCKET_DATA_B}" of="${DATA_IMAGE}" conv=notrunc bs=1M seek="${partoff[DATA-B]}" + + dd if="${BOTTLEROCKET_DATA_A}" of="${OS_IMAGE}" conv=notrunc bs=1M seek="${partoff[DATA-A]}" ;; unified) - dd if="${BOTTLEROCKET_DATA}" of="${OS_IMAGE}" conv=notrunc bs=1M seek="${partoff[DATA]}" + dd if="${BOTTLEROCKET_DATA_A}" of="${OS_IMAGE}" conv=notrunc bs=1M seek="${partoff[DATA-A]}" ;; esac From b13cab0b6a7c8d1f792f362ee0f12297cfa50688 Mon Sep 17 00:00:00 2001 From: Erikson Tung Date: Wed, 1 Mar 2023 17:50:42 -0800 Subject: [PATCH 2/3] signpost: mark if boot has succeeded before, add subcommand for checking 'mark-successful-boot' now also updates BOTTLEROCKET_PRIVATE's GPT table attribute to indicate whether boot has ever succeeded before. Add a new 'has-boot-ever-succeeded' subcommand for checking if boot has ever succeeded before --- sources/updater/signpost/README.md | 22 +++++++++++--------- sources/updater/signpost/src/gptprio.rs | 13 ++++++++++++ sources/updater/signpost/src/main.rs | 7 +++++++ sources/updater/signpost/src/state.rs | 27 +++++++++++++++++++++++-- 4 files changed, 58 insertions(+), 11 deletions(-) diff --git a/sources/updater/signpost/README.md b/sources/updater/signpost/README.md index 0244cd31cff..6a1ac847264 100644 --- a/sources/updater/signpost/README.md +++ b/sources/updater/signpost/README.md @@ -9,9 +9,12 @@ USAGE: SUBCOMMANDS: status Show partition sets and priority status mark-successful-boot Mark the active partitions as successfully booted - clear-inactive Clears inactive priority information to prepare writing images disk - upgrade-to-inactive Sets the inactive partitions as new upgrade partitions + clear-inactive Clears inactive priority information to prepare writing images to disk + mark-inactive-valid Marks the inactive partition as having a valid image + upgrade-to-inactive Sets the inactive partitions as new upgrade partitions if marked valid + cancel-upgrade Reverse upgrade-to-inactive rollback-to-inactive Deprioritizes the inactive partitions + has-boot-ever-succeeded Checks whether boot has ever succeeded rewrite-table Rewrite the partition table with no changes to disk (used for testing this code) ``` @@ -25,13 +28,14 @@ The Bottlerocket OS disk has two partition sets, each containing three partition The Bottlerocket boot partition uses the same GPT partition attribute flags as Chrome OS, which are used by GRUB to select the partition from which to read a `grub.cfg`: -| Bits | Content | -|-------|-------------------------------| -| 63-57 | Unused | -| 56 | Successful boot flag | -| 55-52 | Tries remaining | -| 51-48 | Priority | -| 47-0 | Reserved by GPT specification | +| Bits | Content | +|-------|---------------------------------| +| 63-56 | Unused | +| 57 | Have successfully booted before | +| 56 | Successful boot flag | +| 55-52 | Tries remaining | +| 51-48 | Priority | +| 47-0 | Reserved by GPT specification | The boot partition GRUB selects contains a grub.cfg which references the root and hash partitions by offset, thus selecting all three partitions of a set. diff --git a/sources/updater/signpost/src/gptprio.rs b/sources/updater/signpost/src/gptprio.rs index 8c1c28138ac..7d5a836c5e2 100644 --- a/sources/updater/signpost/src/gptprio.rs +++ b/sources/updater/signpost/src/gptprio.rs @@ -34,6 +34,14 @@ impl GptPrio { pub(crate) fn will_boot(self) -> bool { (self.priority() > 0 && self.tries_left() > 0) || self.successful() } + + pub(crate) fn boot_has_succeeded(&mut self) { + self.0.set_bit(57, true); + } + + pub(crate) fn has_boot_succeeded(&self) -> bool { + self.0.get_bit(57) + } } impl From for GptPrio { @@ -78,5 +86,10 @@ mod tests { prio.set_successful(false); assert_eq!(prio.0, 0x5400555555555555); assert_eq!(prio.will_boot(), false); + + prio = GptPrio(0x0000000000000000); + assert_eq!(prio.has_boot_succeeded(), false); + prio.boot_has_succeeded(); + assert_eq!(prio.has_boot_succeeded(), true); } } diff --git a/sources/updater/signpost/src/main.rs b/sources/updater/signpost/src/main.rs index 96877249836..d9e13b0788b 100644 --- a/sources/updater/signpost/src/main.rs +++ b/sources/updater/signpost/src/main.rs @@ -13,6 +13,7 @@ enum Command { UpgradeToInactive, CancelUpgrade, RollbackToInactive, + HasBootEverSucceeded, RewriteTable, } @@ -29,6 +30,7 @@ SUBCOMMANDS: upgrade-to-inactive Sets the inactive partitions as new upgrade partitions if marked valid cancel-upgrade Reverse upgrade-to-inactive rollback-to-inactive Deprioritizes the inactive partitions + has-boot-ever-succeeded Checks whether boot has ever succeeded rewrite-table Rewrite the partition table with no changes to disk (used for testing this code)"); std::process::exit(1) } @@ -64,6 +66,11 @@ fn main() { state.rollback_to_inactive()?; state.write()?; } + Command::HasBootEverSucceeded => { + if state.has_boot_succeeded() { + println!("true"); + } + } Command::RewriteTable => state.write()?, } Ok(()) diff --git a/sources/updater/signpost/src/state.rs b/sources/updater/signpost/src/state.rs index e7cd674c213..9cc85bc85bf 100644 --- a/sources/updater/signpost/src/state.rs +++ b/sources/updater/signpost/src/state.rs @@ -13,10 +13,13 @@ use std::path::{Path, PathBuf}; const BOTTLEROCKET_BOOT: [u8; 16] = uuid_to_guid(hex!("6b636168 7420 6568 2070 6c616e657421")); const BOTTLEROCKET_ROOT: [u8; 16] = uuid_to_guid(hex!("5526016a 1a97 4ea4 b39a b7c8c6ca4502")); const BOTTLEROCKET_HASH: [u8; 16] = uuid_to_guid(hex!("598f10af c955 4456 6a99 7720068a6cea")); +const BOTTLEROCKET_PRIVATE: [u8; 16] = uuid_to_guid(hex!("440408bb eb0b 4328 a6e5 a29038fad706")); #[derive(Debug, Clone)] pub struct State { os_disk: PathBuf, + // BOTTLEROCKET_PRIVATE partition number + private_partition_num: u32, sets: [PartitionSet; 2], /// The partition numbers that correspond to the boot partitions in each partition set, /// respectively. @@ -133,6 +136,7 @@ impl State { Ok(Self { os_disk: os_disk.path(), + private_partition_num: nth_guid(BOTTLEROCKET_PRIVATE, 0)?, sets, boot_partition_nums, table, @@ -144,12 +148,20 @@ impl State { &self.os_disk } + fn gpt_attributes(&self, num_part: u32) -> u64 { + self.table[num_part].attribute_bits + } + fn gptprio(&self, select: SetSelect) -> GptPrio { - GptPrio::from(self.table[self.boot_partition_nums[select.idx()]].attribute_bits) + GptPrio::from(self.gpt_attributes(self.boot_partition_nums[select.idx()])) + } + + fn set_gpt_attributes(&mut self, num_part: u32, flags: GptPrio) { + self.table[num_part].attribute_bits = flags.into(); } fn set_gptprio(&mut self, select: SetSelect, flags: GptPrio) { - self.table[self.boot_partition_nums[select.idx()]].attribute_bits = flags.into(); + self.set_gpt_attributes(self.boot_partition_nums[select.idx()], flags); } pub fn active(&self) -> SetSelect { @@ -187,10 +199,15 @@ impl State { } /// Sets the active partition as successfully booted, but **does not write to the disk**. + /// Marks the BOTTLEROCKET_PRIVATE partition table to indicate that boot has succeeded at least once pub fn mark_successful_boot(&mut self) { let mut flags = self.gptprio(self.active()); flags.set_successful(true); self.set_gptprio(self.active(), flags); + + let mut private_flags = GptPrio::from(self.gpt_attributes(self.private_partition_num)); + private_flags.boot_has_succeeded(); + self.set_gpt_attributes(self.private_partition_num, private_flags); } /// Clears priority bits of the inactive partition in preparation to write new images, but @@ -282,6 +299,12 @@ impl State { Ok(()) } + /// Returns whether boot has ever succeeded or not + pub fn has_boot_succeeded(&mut self) -> bool { + let private_flags = GptPrio::from(self.gpt_attributes(self.private_partition_num)); + private_flags.has_boot_succeeded() + } + /// Writes the partition table to the OS disk. pub fn write(&mut self) -> Result<(), Error> { self.table From 1ccd37cc5f4a842215afd14ac25ac485b48764a0 Mon Sep 17 00:00:00 2001 From: Erikson Tung Date: Wed, 1 Mar 2023 17:53:52 -0800 Subject: [PATCH 3/3] release,os: ensure data partition label/resize services only run once This adds additional safeguards to prevent a different data partition from being labeled and resized if the host has ever booted successfully in the past with an original data partition. --- packages/os/has-boot-ever-succeeded.service | 16 ++++++++++++++++ packages/os/os.spec | 4 +++- packages/release/label-data-a.service | 2 ++ packages/release/label-data-b.service | 4 +++- 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 packages/os/has-boot-ever-succeeded.service diff --git a/packages/os/has-boot-ever-succeeded.service b/packages/os/has-boot-ever-succeeded.service new file mode 100644 index 00000000000..afe13d2b047 --- /dev/null +++ b/packages/os/has-boot-ever-succeeded.service @@ -0,0 +1,16 @@ +[Unit] +Description=Checks and marks if boot has ever succeeded before +DefaultDependencies=no +Before=label-data-alternative.service label-data-preferred.service +RequiresMountsFor=/etc + +[Service] +Type=oneshot +# Check if boot has ever succeeded before +ExecStart=/bin/signpost has-boot-ever-succeeded +RemainAfterExit=true +# If boot has succeeded before, the marker file will be non-zero-sized +StandardOutput=file:/etc/has-boot-ever-succeeded + +[Install] +WantedBy=local-fs.target diff --git a/packages/os/os.spec b/packages/os/os.spec index 4ddba80c9eb..5f2ea083e9d 100644 --- a/packages/os/os.spec +++ b/packages/os/os.spec @@ -47,6 +47,7 @@ Source118: generate-network-config.service Source119: reboot-if-required.service Source120: warm-pool-wait.service Source121: disable-udp-offload.service +Source122: has-boot-ever-succeeded.service # 2xx sources: tmpfilesd configs Source200: migration-tmpfiles.conf @@ -411,7 +412,7 @@ install -d %{buildroot}%{_cross_unitdir} install -p -m 0644 \ %{S:100} %{S:101} %{S:102} %{S:103} %{S:105} \ %{S:106} %{S:107} %{S:110} %{S:111} %{S:112} \ - %{S:113} %{S:114} %{S:118} %{S:119} \ + %{S:113} %{S:114} %{S:118} %{S:119} %{S:122} \ %{buildroot}%{_cross_unitdir} %if %{with nvidia_flavor} @@ -526,6 +527,7 @@ install -p -m 0644 %{S:121} %{buildroot}%{_cross_unitdir} %files -n %{_cross_os}signpost %{_cross_bindir}/signpost %{_cross_unitdir}/mark-successful-boot.service +%{_cross_unitdir}/has-boot-ever-succeeded.service %files -n %{_cross_os}updog %{_cross_bindir}/updog diff --git a/packages/release/label-data-a.service b/packages/release/label-data-a.service index 516dba658c6..80033252995 100644 --- a/packages/release/label-data-a.service +++ b/packages/release/label-data-a.service @@ -4,6 +4,8 @@ DefaultDependencies=no Conflicts=shutdown.target # Only run this if a partition labeled 'BOTTLEROCKET-DATA' does not exist already. ConditionPathIsSymbolicLink=!/dev/disk/by-partlabel/BOTTLEROCKET-DATA +# Only run if this boot has never succeeded before +ConditionFileNotEmpty=!/etc/has-boot-ever-succeeded # This is the partition GUID for the DATA-A partition. Wants=dev-disk-by\x2dpartuuid-5b94e8df\x2d28b8\x2d485c\x2d9d19\x2d362263b5944c.device After=dev-disk-by\x2dpartuuid-5b94e8df\x2d28b8\x2d485c\x2d9d19\x2d362263b5944c.device diff --git a/packages/release/label-data-b.service b/packages/release/label-data-b.service index d9ef3f4c4de..49d7d4a5073 100644 --- a/packages/release/label-data-b.service +++ b/packages/release/label-data-b.service @@ -4,7 +4,9 @@ DefaultDependencies=no Conflicts=shutdown.target # Only run this if a partition labeled 'BOTTLEROCKET-DATA' does not exist already. ConditionPathIsSymbolicLink=!/dev/disk/by-partlabel/BOTTLEROCKET-DATA -# This is the partition GUID for the fallback data partition. +# Only run if this boot has never succeeded before +ConditionFileNotEmpty=!/etc/has-boot-ever-succeeded +# This is the partition GUID for DATA-B data partition. Wants=dev-disk-by\x2dpartuuid-69040874\x2d417d\x2d4e26\x2da764\x2d7885f22007ea.device After=dev-disk-by\x2dpartuuid-69040874\x2d417d\x2d4e26\x2da764\x2d7885f22007ea.device