Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

signpost: GPT priority manipulation utility #28

Merged
merged 26 commits into from Jun 13, 2019
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
29d295b
signpost: Initial commit
iliana May 31, 2019
8987bf7
signpost: Fix next set logic
iliana Jun 3, 2019
03d215c
signpost: cargo update
iliana Jun 3, 2019
d78643b
signpost: Add README.md
iliana Jun 3, 2019
0ac23ac
signpost: "clear-inactive" command; expand docs
iliana Jun 3, 2019
429da0a
signpost: Rollback errors when inactive is invalid
iliana Jun 3, 2019
2780fed
signpost: Remove type argument from PartitionSet
iliana Jun 5, 2019
4ef3b69
signpost: Improve context for Error::ActiveNotInSet
iliana Jun 5, 2019
2c3d3bc
signpost: Clarify std::ops::Not on self.active
iliana Jun 5, 2019
235d6f5
signpost: Add readability line breaks to Error
iliana Jun 5, 2019
530d873
signpost: Improve context for Error::InactiveInvalidRollback
iliana Jun 5, 2019
320f730
signpost: Better document BlockDevice return values
iliana Jun 5, 2019
4098d3c
signpost: Don't open disk for reading when writing
iliana Jun 5, 2019
57a777e
signpost: Explain effective globs in BlockDevice
iliana Jun 5, 2019
6b2990c
signpost: Use bit_field for GptPrio math
iliana Jun 5, 2019
0b56369
signpost: Drop unclear TryFrom<fs::Metadata> for BlockDevice
iliana Jun 5, 2019
954127c
signpost: Improve clarity of State::load
iliana Jun 5, 2019
4fdf00d
signpost: More accurate checks in rollback-to-inactive
iliana Jun 5, 2019
ded5805
signpost: Remove State::set()
iliana Jun 5, 2019
d6aff6a
signpost: README edits
iliana Jun 5, 2019
42b345d
signpost: Grammar nit
iliana Jun 13, 2019
6b14e35
signpost: Use SetSelect more consistently in State.fmt
iliana Jun 13, 2019
d3cd588
signpost: README.md grammar
iliana Jun 13, 2019
b488b86
signpost: Clearer `BlockDevice` constructor name
iliana Jun 13, 2019
1d22df6
signpost: Remove confusing sentence in documentation
iliana Jun 13, 2019
66ff55e
signpost: Better docstring for State::load
iliana Jun 13, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions workspaces/signpost/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions workspaces/signpost/Cargo.toml
Expand Up @@ -10,6 +10,7 @@ debug = true
lto = true

[dependencies]
bit_field = "0.10.0"
gptman = { version = "0.3.0", default-features = false }
hex-literal = "0.2.0"
serde = { version = "1.0.91", features = ["derive"] }
Expand Down
24 changes: 12 additions & 12 deletions workspaces/signpost/README.md
@@ -1,6 +1,6 @@
# signpost

si**g**n**p**os**t** is Thar's utility for modifying GPT priority bits on its OS disk.
si**g**n**p**os**t** is a utility for modifying Chrome OS-style GPT priority bits on an OS disk.

```plain
USAGE:
Expand All @@ -19,19 +19,19 @@ SUBCOMMANDS:

The Thar OS disk has two partition sets, each containing three partitions:

* the *boot* partition, containing the `vmlinuz` Linux kernel image and the GRUB configuration. The kernel command line contains the root of the dm-verity hash tree.
* the *root* partition, containing the read-only `/` filesystem.
* the *hash* partition, containing the full dm-verity hash tree for the root partition.
* the *boot* partition, containing the `vmlinuz` Linux kernel image and the GRUB configuration;
* the *root* partition, containing the read-only `/` filesystem; and
* the *hash* partition, containing the full dm-verity hash tree for the root partition;
tjkirch marked this conversation as resolved.
Show resolved Hide resolved

The Thar boot partition uses the same GPT partition attribute flags as Chrome OS, which are [used by GRUB to select the partition to read a grub.cfg from](../../packages/grub/gpt.patch):
The Thar 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-57 | Unused |
| 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
33 changes: 29 additions & 4 deletions workspaces/signpost/src/error.rs
@@ -1,3 +1,5 @@
use crate::gptprio::GptPrio;
use crate::set::PartitionSet;
use snafu::Snafu;
use std::ffi::OsString;
use std::fmt;
Expand All @@ -6,14 +8,25 @@ use std::path::PathBuf;
#[derive(Debug, Snafu)]
#[snafu(visibility = "pub(crate)")]
pub(crate) enum Error {
jahkeup marked this conversation as resolved.
Show resolved Hide resolved
#[snafu(display("Active partition not in either detected partition set"))]
ActiveNotInSet,
#[snafu(display(
"Active partition {} not in either detected partition set ({:?})",
active_partition.display(),
sets
))]
ActiveNotInSet {
active_partition: PathBuf,
sets: [PartitionSet; 2],
},

#[snafu(display("Failed to find GPT on device {}: {}", device.display(), source))]
GPTFind { device: PathBuf, source: GPTError },

#[snafu(display("Failed to write GPT onto device {}: {}", device.display(), source))]
GPTWrite { device: PathBuf, source: GPTError },
#[snafu(display("Inactive partition is not valid to roll back to"))]
InactiveInvalidRollback,

#[snafu(display("Inactive partition is not valid to roll back to ({})", flags))]
InactiveInvalidRollback { flags: GptPrio },

#[snafu(display(
"Path {} is a link to {} which does not have a final component (expected {})",
path.display(),
Expand All @@ -25,53 +38,65 @@ pub(crate) enum Error {
link_target: PathBuf,
expected: &'static str,
},

#[snafu(display("Failed to parse major:minor integers from string {:?}: {}", s, source))]
MajorMinorParseInt {
s: String,
source: std::num::ParseIntError,
},

#[snafu(display(
"Failed to parse major:minor integers from string {:?}: does not have exactly one colon",
tjkirch marked this conversation as resolved.
Show resolved Hide resolved
s
))]
MajorMinorLen { s: String },

#[snafu(display("No block device with partition {} found", device_name.to_string_lossy()))]
NoBlockDeviceForPartition { device_name: OsString },

#[snafu(display("Failed to open {} for {}: {}", path.display(), what, source))]
Open {
path: PathBuf,
what: &'static str,
source: std::io::Error,
},

#[snafu(display("Failed to find {} partition for set {}", part_type, set))]
PartitionMissingFromSet {
part_type: &'static str,
set: &'static str,
},

#[snafu(display("Failed to find device for partition {} on {}", num, device.display()))]
PartitionNotFoundOnDevice { num: u32, device: PathBuf },

#[snafu(display("Failed to parse partition number {:?} as integer: {}", s, source))]
PartitionParseInt {
s: String,
source: std::num::ParseIntError,
},

#[snafu(display("Failed to read directory {}: {}", path.display(), source))]
ReadDir {
path: PathBuf,
source: std::io::Error,
},

#[snafu(display("Failed to read from file {}: {}", path.display(), source))]
ReadFile {
path: PathBuf,
source: std::io::Error,
},

#[snafu(display("Failed to read link {}: {}", path.display(), source))]
ReadLink {
path: PathBuf,
source: std::io::Error,
},

#[snafu(display("Root device {} has no lower root devices", root_major_minor))]
RootHasNoLowerDevices { root_major_minor: String },

#[snafu(display("Failed to stat {}: {}", path.display(), source))]
Stat {
path: PathBuf,
Expand Down
31 changes: 11 additions & 20 deletions workspaces/signpost/src/gptprio.rs
@@ -1,43 +1,34 @@
use bit_field::BitField;
use std::fmt;

#[derive(Debug, Clone, Copy)]
pub(crate) struct GptPrio(u64);

impl GptPrio {
#[allow(clippy::cast_possible_truncation)]
pub(crate) fn priority(self) -> u8 {
(self.0 >> 48) as u8 & 0xf
pub(crate) fn priority(self) -> u64 {
self.0.get_bits(48..52)
tjkirch marked this conversation as resolved.
Show resolved Hide resolved
}

/// Panics if `priority > 15`.
pub(crate) fn set_priority(&mut self, priority: u8) {
if priority > 0xf {
panic!("priority cannot be greater than 15");
}

self.0 = (self.0 & !(0xf_u64 << 48)) | (u64::from(priority) << 48);
pub(crate) fn set_priority(&mut self, priority: u64) {
self.0.set_bits(48..52, priority);
}

#[allow(clippy::cast_possible_truncation)]
pub(crate) fn tries_left(self) -> u8 {
(self.0 >> 52) as u8 & 0xf
pub(crate) fn tries_left(self) -> u64 {
self.0.get_bits(52..56)
}

/// Panics if `tries_left > 15`.
pub(crate) fn set_tries_left(&mut self, tries_left: u8) {
if tries_left > 15 {
panic!("tries_left cannot be greater than 15");
}

self.0 = (self.0 & !(0xf_u64 << 52)) | (u64::from(tries_left) << 52);
pub(crate) fn set_tries_left(&mut self, tries_left: u64) {
self.0.set_bits(52..56, tries_left);
}

pub(crate) fn successful(self) -> bool {
(self.0 >> 56) & 1 == 1
self.0.get_bit(56)
}

pub(crate) fn set_successful(&mut self, successful: bool) {
self.0 = (self.0 & !(1_u64 << 56)) | (if successful { 1 } else { 0 } << 56);
self.0.set_bit(56, successful);
}

pub(crate) fn will_boot(self) -> bool {
Expand Down
18 changes: 9 additions & 9 deletions workspaces/signpost/src/set.rs
@@ -1,24 +1,24 @@
use std::fmt;
use std::ops::Not;
use std::path::Path;
use std::path::{Path, PathBuf};

#[derive(Debug, Clone)]
pub(crate) struct PartitionSet<T> {
pub(crate) struct PartitionSet {
/// The partition containing the kernel and GRUB configuration for this partition set.
pub(crate) boot: T,
pub(crate) boot: PathBuf,
/// The partition containing the root filesystem for this partition set.
pub(crate) root: T,
pub(crate) root: PathBuf,
/// The partition containing the dm-verity hashes for this partition set.
pub(crate) hash: T,
pub(crate) hash: PathBuf,
}

impl<T: PartialEq> PartitionSet<T> {
pub(crate) fn contains(&self, device: &T) -> bool {
&self.boot == device || &self.root == device || &self.hash == device
impl PartitionSet {
pub(crate) fn contains<P: AsRef<Path>>(&self, device: P) -> bool {
self.boot == device.as_ref() || self.root == device.as_ref() || self.hash == device.as_ref()
}
}

impl fmt::Display for PartitionSet<&Path> {
impl fmt::Display for PartitionSet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
Expand Down
26 changes: 22 additions & 4 deletions workspaces/signpost/src/state/block.rs
Expand Up @@ -42,7 +42,18 @@ impl BlockDevice {
})
}

fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
/// Creates a `BlockDevice` from the device where `path` resides.
pub(crate) fn from_resident<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
let metadata = fs::metadata(&path).context(error::Stat {
path: path.as_ref(),
})?;
let major = metadata.dev() >> 8;
let minor = metadata.dev() & 0xff;
Ok(Self::new(major, minor)?)
}

/// Creates a `BlockDevice` from the major:minor string from the file at `path`.
fn from_major_minor<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
tjkirch marked this conversation as resolved.
Show resolved Hide resolved
Self::from_str(&fs::read_to_string(path.as_ref()).context(error::ReadFile {
path: path.as_ref(),
})?)
Expand All @@ -58,12 +69,15 @@ impl BlockDevice {
}

/// If this device is a partition, get the disk it belongs to.
///
/// This fails if the device is not a partition.
#[allow(clippy::identity_conversion)] // https://github.com/rust-lang/rust-clippy/issues/4133
pub(crate) fn disk(&self) -> Result<Self, Error> {
iliana marked this conversation as resolved.
Show resolved Hide resolved
// Globbing for /sys/block/*/{self.device_name}/dev
for entry in fs::read_dir("/sys/block").context(error::ReadDir { path: "/sys/block" })? {
let entry = entry.context(error::ReadDir { path: "/sys/block" })?;
if entry.path().join(&self.device_name).exists() {
iliana marked this conversation as resolved.
Show resolved Hide resolved
return Self::from_file(entry.path().join("dev"));
return Self::from_major_minor(entry.path().join("dev"));
}
}

Expand All @@ -74,9 +88,13 @@ impl BlockDevice {
}

/// If this device is a disk, get one of its partitions by number.
///
/// This fails if the device is not a disk, and returns `Ok(None)` if this device is a disk,
iliana marked this conversation as resolved.
Show resolved Hide resolved
/// but there is no partition of that number.
#[allow(clippy::identity_conversion)] // https://github.com/rust-lang/rust-clippy/issues/4133
pub(crate) fn partition(&self, part_num: u32) -> Result<Option<Self>, Error> {
let sys_path = self.sys_path();
// Globbing for /sys/dev/block/{major}:{minor}/*/partition
for entry in fs::read_dir(&sys_path).context(error::ReadDir { path: &sys_path })? {
let entry = entry.context(error::ReadDir { path: &sys_path })?;
if entry.path().is_dir() {
Expand All @@ -94,7 +112,7 @@ impl BlockDevice {
let partition = u32::from_str(partition_str)
.context(error::PartitionParseInt { s: partition_str })?;
if partition == part_num {
return Self::from_file(entry.path().join("dev")).map(Some);
return Self::from_major_minor(entry.path().join("dev")).map(Some);
}
}
}
Expand All @@ -110,7 +128,7 @@ impl BlockDevice {
match fs::read_dir(&lower_path).context(error::ReadDir { path: &lower_path }) {
Ok(iter) => Box::new(iter.map(move |entry_result| {
let entry = entry_result.context(error::ReadDir { path: &lower_path })?;
Self::from_file(entry.path().join("dev"))
Self::from_major_minor(entry.path().join("dev"))
})) as Box<dyn Iterator<Item = Result<Self, Error>>>,
Err(err) => Box::new(vec![Err(err)].into_iter())
as Box<dyn Iterator<Item = Result<Self, Error>>>,
tjkirch marked this conversation as resolved.
Show resolved Hide resolved
Expand Down