mirror of
https://github.com/chenxiaolong/avbroot.git
synced 2026-07-03 14:05:11 +02:00
Use fastboot flashall to flash partitions initially
`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 <accounts+github@chiller3.com>
This commit is contained in:
@@ -132,6 +132,10 @@ 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.
|
||||
@@ -139,23 +143,21 @@ If you lose your AVB or OTA signing key, you will no longer be able to sign new
|
||||
```bash
|
||||
avbroot ota extract \
|
||||
--input /path/to/ota.zip.patched \
|
||||
--directory extracted
|
||||
--directory extracted \
|
||||
--fastboot
|
||||
```
|
||||
|
||||
If you prefer to extract and flash all OS partitions just to be safe, pass in `--all`.
|
||||
|
||||
4. Flash the partition images that were extracted.
|
||||
|
||||
For each partition inside `extracted/`, except for `system`, run:
|
||||
|
||||
```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.
|
||||
|
||||
|
||||
+150
-6
@@ -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(
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -1712,6 +1851,7 @@ pub struct PatchCli {
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "PARTITION",
|
||||
hide = true,
|
||||
help_heading = HEADING_OTHER
|
||||
)]
|
||||
pub boot_partition: Option<String>,
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user