Skip to content

Commit

Permalink
Use fastboot flashall to flash partitions initially
Browse files Browse the repository at this point in the history
`fastboot flashall` is identical to the `fastboot update` command used
by the Pixel factory images, except it reads from a directory instead of
a zip file. It knows how to flip between the fastboot and fastbootd
modes without user intervention.

Fixes: #252

Signed-off-by: Andrew Gunnerson <[email protected]>
  • Loading branch information
chenxiaolong committed Apr 15, 2024
1 parent af0fb1d commit fd0408d
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 15 deletions.
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,30 +132,32 @@ If you lose your AVB or OTA signing key, you will no longer be able to sign new
1. Reboot into fastboot mode and unlock the bootloader if it isn't already unlocked. This will trigger a data wipe.

```bash
fastboot flashing unlock
```

2. When setting things up for the first time, the device must already be running the correct OS. Flash the original unpatched OTA if needed.

3. Extract the partition images from the patched OTA that are different from the original.

```bash
avbroot ota extract \
--input /path/to/ota.zip.patched \
--directory extracted
--directory extracted \
--fastboot
```

4. Flash the partition images that were extracted.
If you prefer to extract and flash all OS partitions just to be safe, pass in `--all`.

For each partition inside `extracted/`, except for `system`, run:
4. Flash the partition images that were extracted.

```bash
fastboot flash <partition> extracted/<partition>.img
ANDROID_PRODUCT_OUT=extracted fastboot flashall --skip-reboot
```

Then, reboot into recovery's fastbootd mode and flash `system`:
Note that this only flashes the OS partitions. The bootloader and modem/radio partitions are left untouched due to fastboot limitations. If they are not already up to date or if unsure, after fastboot completes, follow the steps in the [updates section](#updates) to sideload the patched OTA once. Sideloading OTAs always ensures that all partitions are up to date.

```bash
fastboot reboot fastboot
fastboot flash system extracted/system.img
```
Alternatively, for Pixel devices, running `flash-base.sh` from the factory image will also update the bootloader and modem.

5. Set up the custom AVB public key in the bootloader after rebooting from fastbootd to bootloader.

Expand Down
156 changes: 150 additions & 6 deletions avbroot/src/cli/ota.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1346,11 +1346,12 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
.with_context(|| format!("Failed to open for reading: {:?}", cli.input))?;
let mut zip = ZipArchive::new(BufReader::new(raw_reader.reopen()?))
.with_context(|| format!("Failed to read zip: {:?}", cli.input))?;
let payload_entry = zip
.by_name(ota::PATH_PAYLOAD)
.with_context(|| format!("Failed to open zip entry: {:?}", ota::PATH_PAYLOAD))?;
let payload_offset = payload_entry.data_start();
let payload_size = payload_entry.size();
let (payload_offset, payload_size) = {
let entry = zip
.by_name(ota::PATH_PAYLOAD)
.with_context(|| format!("Failed to open zip entry: {}", ota::PATH_PAYLOAD))?;
(entry.data_start(), entry.size())
};

// Open the payload data directly.
let mut payload_reader = SectionReader::new(
Expand Down Expand Up @@ -1403,6 +1404,144 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
cancel_signal,
)?;

if cli.fastboot {
const ANDROID_INFO: &str = "android-info.txt";
const FASTBOOT_INFO: &str = "fastboot-info.txt";

// Generate android-info.txt, which is always required for fastboot's
// flashall subcommand. We only add a basic device check to avoid
// accidental flashes on the wrong device.

let mut metadata_entry = zip
.by_name(ota::PATH_METADATA_PB)
.with_context(|| format!("Failed to open zip entry: {:?}", ota::PATH_METADATA_PB))?;
let mut metadata_raw = vec![];
metadata_entry
.read_to_end(&mut metadata_raw)
.with_context(|| format!("Failed to read OTA metadata: {}", ota::PATH_METADATA_PB))?;
let metadata = ota::parse_protobuf_metadata(&metadata_raw)
.with_context(|| format!("Failed to parse OTA metadata: {}", ota::PATH_METADATA_PB))?;

let device = metadata
.precondition
.as_ref()
.and_then(|p| p.device.first())
.ok_or_else(|| anyhow!("Device codename not found in OTA metadata"))?;

directory
.write(ANDROID_INFO, format!("require board={device}\n"))
.with_context(|| format!("Failed to write file: {ANDROID_INFO}"))?;

// Find out which images can be flashed with fastboot. The bootloader
// (and potentially modem) partitions need to be flashed as a whole and
// an OTA doesn't contain sufficient information to generate the
// required combined file.
let mut flashable_images = BTreeSet::new();

for name in &unique_images {
let file = directory
.open(format!("{name}.img"))
.with_context(|| format!("Failed to open image for reading: {name}"))?;

match avb::load_image(file) {
Ok(_) => {
flashable_images.insert(name);
}
// Treat images without AVB metadata as bootloader partitions.
Err(avb::Error::InvalidHeaderMagic(_)) => continue,
Err(e) => return Err(e).with_context(|| format!("Failed to load image: {name}")),
}
}

// Generate fastboot-info.txt to be able to control how exactly the
// images are flashed. This solves two problems:
//
// 1. fastboot flashall, by default, expects super_empty.img to exist
// for A/B devices. If it doesn't exist, it assumes that all images
// are meant for non-dynamic partitions and skips the fastbootd
// reboot. We cannot generate a proper super_empty.img because it
// requires knowing the number of super partitions along with their
// names and sizes. This information is not available in an OTA. The
// fastboot info file allows us to control when to reboot to
// fastbootd and flash each partition.
//
// This approach is a bit slower because we have to reboot into
// fastbootd. When the device only has a single super partition,
// fastboot will normally locally combine super_empty.img with the
// individual partitions to form a full super image and then flash it
// via the bootloader's fastboot mode.
//
// Also, because we can't generate super_empty.img, if the super
// partition metadata on the device somehow becomes corrupted,
// running fastboot flash all using avbroot's extracted images isn't
// sufficient for recovering the device. The user will need to copy
// that file from the factory image into the output directory and add
// an `update-super` line after `reboot fastboot` the fastboot info
// file.
//
// 2. fastboot flashall does not look at all .img files in a directory.
// Instead, it has a hardcoded list of partitions known to AOSP. This
// means obscure OEM-specific partitions are just silently ignored.
// Using a fastboot info file with explicit flash instructions avoids
// this problem entirely.

let mut dynamic = BTreeSet::new();

if let Some(dpm) = &header.manifest.dynamic_partition_metadata {
for group in &dpm.groups {
for name in &group.partition_names {
if unique_images.contains(name) {
dynamic.insert(name);
}
}
}
}

let flash_command = |name| {
let (prefix, suffix) = if flashable_images.contains(name) {
("", "")
} else {
("#", " # Bootloader/modem images cannot be flashed")
};

let extra_arg = if *name == "vbmeta" {
"--apply-vbmeta "
} else {
""
};

format!("{prefix}flash {extra_arg}{name}{suffix}\n")
};

let mut fastboot_info = String::new();

fastboot_info.push_str("# Generated by avbroot\n");
fastboot_info.push_str("version 1\n");

fastboot_info.push_str("# Flash non-dynamic partitions\n");
for name in &unique_images {
if !dynamic.contains(name) {
fastboot_info.push_str(&flash_command(name));
}
}

fastboot_info.push_str("# Reboot to fastbootd\n");
fastboot_info.push_str("reboot fastboot\n");

fastboot_info.push_str("# Flash dynamic partitions\n");
for name in &dynamic {
fastboot_info.push_str(&flash_command(name));
}

fastboot_info.push_str("# Wipe data when -w flag is used\n");
fastboot_info.push_str("if-wipe erase userdata\n");
fastboot_info.push_str("if-wipe erase metadata\n");

directory
.write(FASTBOOT_INFO, fastboot_info)
.with_context(|| format!("Failed to write file: {FASTBOOT_INFO}"))?;
}

Ok(())
}

Expand Down Expand Up @@ -1712,6 +1851,7 @@ pub struct PatchCli {
#[arg(
long,
value_name = "PARTITION",
hide = true,
help_heading = HEADING_OTHER
)]
pub boot_partition: Option<String>,
Expand All @@ -1737,8 +1877,12 @@ pub struct ExtractCli {
pub boot_only: bool,

/// (Deprecated: no longer needed)
#[arg(long, value_name = "PARTITION")]
#[arg(long, value_name = "PARTITION", hide = true)]
pub boot_partition: Option<String>,

/// Generate fastboot info files.
#[arg(long)]
pub fastboot: bool,
}

/// Verify signatures of an OTA.
Expand Down

0 comments on commit fd0408d

Please sign in to comment.