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.

This is a bit slower than the old method of only flashing the partition
images that were modified, but is much less accident-prone and is a well
supported and tested feature of fastboot.

Fixes: #252

Signed-off-by: Andrew Gunnerson <[email protected]>
  • Loading branch information
chenxiaolong committed Feb 1, 2024
1 parent 8054934 commit d33ef21
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 25 deletions.
29 changes: 10 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,39 +132,30 @@ 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.

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.
2. Extract all the partition images from the patched OTA.

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

4. Flash the partition images that were extracted.

For each partition inside `extracted/`, except for `system`, run:

```bash
fastboot flash <partition> extracted/<partition>.img
--directory extracted \
--all \
--fastboot
```

Then, reboot into recovery's fastbootd mode and flash `system`:
3. Flash the partition images that were extracted.

```bash
fastboot reboot fastboot
fastboot flash system extracted/system.img
ANDROID_PRODUCT_OUT=extracted fastboot flashall
```

5. Set up the custom AVB public key in the bootloader.
4. Set up the custom AVB public key in the bootloader.

```bash
fastboot erase avb_custom_key
fastboot flash avb_custom_key /path/to/avb_pkmd.bin
```

6. **[Optional]** Before locking the bootloader, reboot into Android once to confirm that everything is properly signed.
5. **[Optional]** Before locking the bootloader, reboot into Android once to confirm that everything is properly signed.

Install the Magisk or KernelSU app and run the following command:

Expand All @@ -178,13 +169,13 @@ If you lose your AVB or OTA signing key, you will no longer be able to sign new
init: [libfs_avb]Returning avb_handle with status: Success
```

7. Reboot back into fastboot and lock the bootloader. This will trigger a data wipe again.
6. Reboot back into fastboot and lock the bootloader. This will trigger a data wipe again.

Remember: **Do not uncheck `OEM unlocking`!**

**WARNING**: If you are flashing CalyxOS, the setup wizard will [automatically turn off the `OEM unlocking` switch](https://github.com/CalyxOS/platform_packages_apps_SetupWizard/blob/7d2df25cedcbff83ddb608e628f9d97b38259c26/src/org/lineageos/setupwizard/SetupWizardApp.java#L135-L140). Make sure to manually reenable it again from Android's developer settings. Consider using the [`OEMUnlockOnBoot` module](https://github.com/chenxiaolong/OEMUnlockOnBoot) to automatically ensure OEM unlocking is enabled on every boot.
8. That's it! To install future OS, Magisk, or KernelSU updates, see the [next section](#updates).
7. That's it! To install future OS, Magisk, or KernelSU updates, see the [next section](#updates).

## Updates

Expand Down
125 changes: 119 additions & 6 deletions avbroot/src/cli/ota.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1342,11 +1342,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 @@ -1389,6 +1390,113 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
let directory = Dir::open_ambient_dir(&cli.directory, authority)
.with_context(|| format!("Failed to open directory: {:?}", cli.directory))?;

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}"))?;

// 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 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) {
let extra_arg = if name == "vbmeta" {
"--apply-vbmeta "
} else {
""
};

fastboot_info.push_str(&format!("flash {extra_arg}{name}\n"));
}
}

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(&format!("flash {name}\n"));
}

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}"))?;
}

extract_ota_zip(
&raw_reader,
&directory,
Expand Down Expand Up @@ -1708,6 +1816,7 @@ pub struct PatchCli {
#[arg(
long,
value_name = "PARTITION",
hide = true,
help_heading = HEADING_OTHER
)]
pub boot_partition: Option<String>,
Expand All @@ -1733,8 +1842,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 d33ef21

Please sign in to comment.