Skip to content

Commit

Permalink
Merge pull request #2807 from etungsten/rerepart
Browse files Browse the repository at this point in the history
Label `BOTTLEROCKET-DATA` at runtime
  • Loading branch information
etungsten authored Mar 8, 2023
2 parents 46c01b2 + 1ccd37c commit 5a4c65d
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 39 deletions.
16 changes: 16 additions & 0 deletions packages/os/has-boot-ever-succeeded.service
Original file line number Diff line number Diff line change
@@ -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
4 changes: 3 additions & 1 deletion packages/os/os.spec
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions packages/release/label-data-a.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[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
# 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

[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
22 changes: 22 additions & 0 deletions packages/release/label-data-b.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[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
# 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

[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
3 changes: 3 additions & 0 deletions packages/release/release-repart-local.conf
Original file line number Diff line number Diff line change
@@ -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
Expand Down
8 changes: 6 additions & 2 deletions packages/release/release.spec
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand All @@ -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
Expand Down Expand Up @@ -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}
Expand Down
2 changes: 1 addition & 1 deletion packages/release/repart-local.service
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
22 changes: 13 additions & 9 deletions sources/updater/signpost/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
```

Expand All @@ -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.

Expand Down
13 changes: 13 additions & 0 deletions sources/updater/signpost/src/gptprio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u64> for GptPrio {
Expand Down Expand Up @@ -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);
}
}
7 changes: 7 additions & 0 deletions sources/updater/signpost/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ enum Command {
UpgradeToInactive,
CancelUpgrade,
RollbackToInactive,
HasBootEverSucceeded,
RewriteTable,
}

Expand All @@ -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)
}
Expand Down Expand Up @@ -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(())
Expand Down
27 changes: 25 additions & 2 deletions sources/updater/signpost/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 5a4c65d

Please sign in to comment.