fastboot driver: repack vendor boot ramdisk

When a user issues `fastboot flash vendor_boot:foo ramdisk.img`, the fastboot driver
fetches the vendor_boot image from the device,
determines if `foo` is a valid vendor ramdisk fragment,
repacks a new vendor boot image, then
flash the vendor boot image back.
This requires vendor boot header V4.

As a convinent alias, `fastboot flash vendor_boot:default ramdisk.img`
flashes the whole vendor ramdisk image. This works on vendor boot header
V3 & 4.

Fixes: 173654501
Test: pass

Change-Id: I42b2483a736ea8aa9fd9372b960502a642934cdc
This commit is contained in:
Yifan Hong 2021-02-22 15:00:15 -08:00
parent 6d7da63014
commit e71fe2441a
7 changed files with 1124 additions and 1 deletions

View file

@ -55,6 +55,7 @@ cc_library_host_static {
"tcp.cpp",
"udp.cpp",
"util.cpp",
"vendor_boot_img_utils.cpp",
"fastboot_driver.cpp",
],
@ -75,6 +76,7 @@ cc_library_host_static {
],
header_libs: [
"avb_headers",
"bootimg_headers",
"libstorage_literals_headers",
],
@ -270,6 +272,7 @@ cc_library_host_static {
"tcp.cpp",
"udp.cpp",
"util.cpp",
"vendor_boot_img_utils.cpp",
"fastboot_driver.cpp",
],
@ -277,6 +280,7 @@ cc_library_host_static {
use_version_lib: false,
static_libs: ["libbuildversion"],
header_libs: [
"avb_headers",
"libstorage_literals_headers",
],
@ -370,3 +374,33 @@ cc_test_host {
},
},
}
cc_test_host {
name: "fastboot_vendor_boot_img_utils_test",
srcs: ["vendor_boot_img_utils_test.cpp"],
static_libs: [
"libbase",
"libc++fs",
"libfastboot",
"libgmock",
"liblog",
],
header_libs: [
"avb_headers",
"bootimg_headers",
],
cflags: [
"-Wall",
"-Werror",
],
data: [
":fastboot_test_dtb",
":fastboot_test_bootconfig",
":fastboot_test_vendor_ramdisk_none",
":fastboot_test_vendor_ramdisk_platform",
":fastboot_test_vendor_ramdisk_replace",
":fastboot_test_vendor_boot_v3",
":fastboot_test_vendor_boot_v4_without_frag",
":fastboot_test_vendor_boot_v4_with_frag"
],
}

View file

@ -76,6 +76,7 @@
#include "udp.h"
#include "usb.h"
#include "util.h"
#include "vendor_boot_img_utils.h"
using android::base::borrowed_fd;
using android::base::ReadFully;
@ -1292,6 +1293,40 @@ static void do_fetch(const std::string& partition, const std::string& slot_overr
do_for_partitions(partition, slot_override, fetch, false /* force slot */);
}
// Return immediately if not flashing a vendor boot image. If flashing a vendor boot image,
// repack vendor_boot image with an updated ramdisk. After execution, buf is set
// to the new image to flash, and return value is the real partition name to flash.
static std::string repack_ramdisk(const char* pname, struct fastboot_buffer* buf) {
std::string_view pname_sv{pname};
if (!android::base::StartsWith(pname_sv, "vendor_boot:") &&
!android::base::StartsWith(pname_sv, "vendor_boot_a:") &&
!android::base::StartsWith(pname_sv, "vendor_boot_b:")) {
return std::string(pname_sv);
}
if (buf->type != FB_BUFFER_FD) {
die("Flashing sparse vendor ramdisk image is not supported.");
}
if (buf->sz <= 0) {
die("repack_ramdisk() sees negative size: %" PRId64, buf->sz);
}
std::string partition(pname_sv.substr(0, pname_sv.find(':')));
std::string ramdisk(pname_sv.substr(pname_sv.find(':') + 1));
unique_fd vendor_boot(make_temporary_fd("vendor boot repack"));
uint64_t vendor_boot_size = fetch_partition(partition, vendor_boot);
auto repack_res = replace_vendor_ramdisk(vendor_boot, vendor_boot_size, ramdisk, buf->fd,
static_cast<uint64_t>(buf->sz));
if (!repack_res.ok()) {
die("%s", repack_res.error().message().c_str());
}
buf->fd = std::move(vendor_boot);
buf->sz = vendor_boot_size;
buf->image_size = vendor_boot_size;
return partition;
}
static void do_flash(const char* pname, const char* fname) {
verbose("Do flash %s %s", pname, fname);
struct fastboot_buffer buf;
@ -1302,7 +1337,8 @@ static void do_flash(const char* pname, const char* fname) {
if (is_logical(pname)) {
fb->ResizePartition(pname, std::to_string(buf.image_size));
}
flash_buf(pname, &buf);
std::string flash_pname = repack_ramdisk(pname, &buf);
flash_buf(flash_pname, &buf);
}
// Sets slot_override as the active slot. If slot_override is blank,

136
fastboot/testdata/Android.bp vendored Normal file
View file

@ -0,0 +1,136 @@
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
python_binary_host {
name: "fastboot_gen_rand",
visibility: [":__subpackages__"],
srcs: ["fastboot_gen_rand.py"],
}
genrule_defaults {
name: "fastboot_test_data_gen_defaults",
visibility: ["//system/core/fastboot"],
tools: [
"fastboot_gen_rand",
],
}
// Genrules for components of test vendor boot image.
// Fake dtb image.
genrule {
name: "fastboot_test_dtb",
defaults: ["fastboot_test_data_gen_defaults"],
out: ["test_dtb.img"],
cmd: "$(location fastboot_gen_rand) --seed dtb --length 1024 > $(out)",
}
// Fake bootconfig image.
genrule {
name: "fastboot_test_bootconfig",
defaults: ["fastboot_test_data_gen_defaults"],
out: ["test_bootconfig.img"],
cmd: "$(location fastboot_gen_rand) --seed bootconfig --length 1024 > $(out)",
}
// Fake vendor ramdisk with type "none".
genrule {
name: "fastboot_test_vendor_ramdisk_none",
defaults: ["fastboot_test_data_gen_defaults"],
out: ["test_vendor_ramdisk_none.img"],
cmd: "$(location fastboot_gen_rand) --seed vendor_ramdisk_none --length 1024 > $(out)",
}
// Fake vendor ramdisk with type "platform".
genrule {
name: "fastboot_test_vendor_ramdisk_platform",
defaults: ["fastboot_test_data_gen_defaults"],
out: ["test_vendor_ramdisk_platform.img"],
cmd: "$(location fastboot_gen_rand) --seed vendor_ramdisk_platform --length 1024 > $(out)",
}
// Fake replacement ramdisk.
genrule {
name: "fastboot_test_vendor_ramdisk_replace",
defaults: ["fastboot_test_data_gen_defaults"],
out: ["test_vendor_ramdisk_replace.img"],
cmd: "$(location fastboot_gen_rand) --seed replace --length 3072 > $(out)",
}
// Genrules for test vendor boot images.
fastboot_sign_test_image = "$(location avbtool) add_hash_footer --salt 00 --image $(out) " +
"--partition_name vendor_boot --partition_size $$(( 1 * 1024 * 1024 ))"
genrule_defaults {
name: "fastboot_test_vendor_boot_gen_defaults",
defaults: ["fastboot_test_data_gen_defaults"],
tools: [
"avbtool",
"mkbootimg",
],
}
genrule {
name: "fastboot_test_vendor_boot_v3",
defaults: ["fastboot_test_vendor_boot_gen_defaults"],
out: ["vendor_boot_v3.img"],
srcs: [
":fastboot_test_dtb",
":fastboot_test_vendor_ramdisk_none",
],
cmd: "$(location mkbootimg) --header_version 3 " +
"--vendor_ramdisk $(location :fastboot_test_vendor_ramdisk_none) " +
"--dtb $(location :fastboot_test_dtb) " +
"--vendor_boot $(out) && " +
fastboot_sign_test_image,
}
genrule {
name: "fastboot_test_vendor_boot_v4_without_frag",
defaults: ["fastboot_test_vendor_boot_gen_defaults"],
out: ["vendor_boot_v4_without_frag.img"],
srcs: [
":fastboot_test_dtb",
":fastboot_test_vendor_ramdisk_none",
":fastboot_test_bootconfig",
],
cmd: "$(location mkbootimg) --header_version 4 " +
"--vendor_ramdisk $(location :fastboot_test_vendor_ramdisk_none) " +
"--dtb $(location :fastboot_test_dtb) " +
"--vendor_bootconfig $(location :fastboot_test_bootconfig) " +
"--vendor_boot $(out) && " +
fastboot_sign_test_image,
}
genrule {
name: "fastboot_test_vendor_boot_v4_with_frag",
defaults: ["fastboot_test_vendor_boot_gen_defaults"],
out: ["vendor_boot_v4_with_frag.img"],
srcs: [
":fastboot_test_dtb",
":fastboot_test_vendor_ramdisk_none",
":fastboot_test_vendor_ramdisk_platform",
":fastboot_test_bootconfig",
],
cmd: "$(location mkbootimg) --header_version 4 " +
"--dtb $(location :fastboot_test_dtb) " +
"--vendor_bootconfig $(location :fastboot_test_bootconfig) " +
"--ramdisk_type none --ramdisk_name none_ramdisk " +
"--vendor_ramdisk_fragment $(location :fastboot_test_vendor_ramdisk_none) " +
"--ramdisk_type platform --ramdisk_name platform_ramdisk " +
"--vendor_ramdisk_fragment $(location :fastboot_test_vendor_ramdisk_platform) " +
"--vendor_boot $(out) && " +
fastboot_sign_test_image,
}

32
fastboot/testdata/fastboot_gen_rand.py vendored Normal file
View file

@ -0,0 +1,32 @@
#!/usr/bin/env python3
# Copyright (C) 2021 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Write given number of random bytes, generated with optional seed.
"""
import random, argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--seed', help='Seed to random generator')
parser.add_argument('--length', type=int, required=True, help='Length of output')
args = parser.parse_args()
if args.seed:
random.seed(args.seed)
print(''.join(chr(random.randrange(0,0xff)) for _ in range(args.length)))

View file

@ -0,0 +1,422 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "vendor_boot_img_utils.h"
#include <string.h>
#include <android-base/file.h>
#include <android-base/result.h>
#include <bootimg.h>
#include <libavb/libavb.h>
namespace {
using android::base::Result;
// Updates a given buffer by creating a new one.
class DataUpdater {
public:
DataUpdater(const std::string& old_data) : old_data_(&old_data) {
old_data_ptr_ = old_data_->data();
new_data_.resize(old_data_->size(), '\0');
new_data_ptr_ = new_data_.data();
}
// Copy |num_bytes| from src to dst.
[[nodiscard]] Result<void> Copy(uint32_t num_bytes) {
if (num_bytes == 0) return {};
if (auto res = CheckAdvance(old_data_ptr_, old_end(), num_bytes, __FUNCTION__); !res.ok())
return res;
if (auto res = CheckAdvance(new_data_ptr_, new_end(), num_bytes, __FUNCTION__); !res.ok())
return res;
memcpy(new_data_ptr_, old_data_ptr_, num_bytes);
old_data_ptr_ += num_bytes;
new_data_ptr_ += num_bytes;
return {};
}
// Replace |old_num_bytes| from src with new data.
[[nodiscard]] Result<void> Replace(uint32_t old_num_bytes, const std::string& new_data) {
return Replace(old_num_bytes, new_data.data(), new_data.size());
}
[[nodiscard]] Result<void> Replace(uint32_t old_num_bytes, const void* new_data,
uint32_t new_data_size) {
if (auto res = CheckAdvance(old_data_ptr_, old_end(), old_num_bytes, __FUNCTION__);
!res.ok())
return res;
old_data_ptr_ += old_num_bytes;
if (new_data_size == 0) return {};
if (auto res = CheckAdvance(new_data_ptr_, new_end(), new_data_size, __FUNCTION__);
!res.ok())
return res;
memcpy(new_data_ptr_, new_data, new_data_size);
new_data_ptr_ += new_data_size;
return {};
}
// Skip |old_skip| from src and |new_skip| from dst, respectively.
[[nodiscard]] Result<void> Skip(uint32_t old_skip, uint32_t new_skip) {
if (auto res = CheckAdvance(old_data_ptr_, old_end(), old_skip, __FUNCTION__); !res.ok())
return res;
old_data_ptr_ += old_skip;
if (auto res = CheckAdvance(new_data_ptr_, new_end(), new_skip, __FUNCTION__); !res.ok())
return res;
new_data_ptr_ += new_skip;
return {};
}
[[nodiscard]] Result<void> Seek(uint32_t offset) {
if (offset > size()) return Errorf("Cannot seek 0x{:x}, size is 0x{:x}", offset, size());
old_data_ptr_ = old_begin() + offset;
new_data_ptr_ = new_begin() + offset;
return {};
}
std::string Finish() {
new_data_ptr_ = nullptr;
return std::move(new_data_);
}
[[nodiscard]] Result<void> CheckOffset(uint32_t old_offset, uint32_t new_offset) {
if (old_begin() + old_offset != old_cur())
return Errorf("Old offset mismatch: expected: 0x{:x}, actual: 0x{:x}", old_offset,
old_cur() - old_begin());
if (new_begin() + new_offset != new_cur())
return Errorf("New offset mismatch: expected: 0x{:x}, actual: 0x{:x}", new_offset,
new_cur() - new_begin());
return {};
}
uint64_t size() const { return old_data_->size(); }
const char* old_begin() const { return old_data_->data(); }
const char* old_cur() { return old_data_ptr_; }
const char* old_end() const { return old_data_->data() + old_data_->size(); }
char* new_begin() { return new_data_.data(); }
char* new_cur() { return new_data_ptr_; }
char* new_end() { return new_data_.data() + new_data_.size(); }
private:
// Check if it is okay to advance |num_bytes| from |current|.
[[nodiscard]] Result<void> CheckAdvance(const char* current, const char* end,
uint32_t num_bytes, const char* op) {
auto new_end = current + num_bytes;
if (new_end < current /* add overflow */)
return Errorf("{}: Addition overflow: 0x{} + 0x{:x} < 0x{}", op, fmt::ptr(current),
num_bytes, fmt::ptr(current));
if (new_end > end)
return Errorf("{}: Boundary overflow: 0x{} + 0x{:x} > 0x{}", op, fmt::ptr(current),
num_bytes, fmt::ptr(end));
return {};
}
const std::string* old_data_;
std::string new_data_;
const char* old_data_ptr_;
char* new_data_ptr_;
};
// Get the size of vendor boot header.
[[nodiscard]] Result<uint32_t> get_vendor_boot_header_size(const vendor_boot_img_hdr_v3* hdr) {
if (hdr->header_version == 3) return sizeof(vendor_boot_img_hdr_v3);
if (hdr->header_version == 4) return sizeof(vendor_boot_img_hdr_v4);
return Errorf("Unrecognized vendor boot header version {}", hdr->header_version);
}
// Check that content contains a valid vendor boot image header with a version at least |version|.
[[nodiscard]] Result<void> check_vendor_boot_hdr(const std::string& content, uint32_t version) {
// get_vendor_boot_header_size reads header_version, so make sure reading it does not
// go out of bounds by ensuring that the content has at least the size of V3 header.
if (content.size() < sizeof(vendor_boot_img_hdr_v3)) {
return Errorf("Size of vendor boot is 0x{:x}, less than size of V3 header: 0x{:x}",
content.size(), sizeof(vendor_boot_img_hdr_v3));
}
// Now read hdr->header_version and assert the size.
auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v3*>(content.data());
auto expect_header_size = get_vendor_boot_header_size(hdr);
if (!expect_header_size.ok()) return expect_header_size.error();
if (content.size() < *expect_header_size) {
return Errorf("Size of vendor boot is 0x{:x}, less than size of V{} header: 0x{:x}",
content.size(), version, *expect_header_size);
}
if (memcmp(hdr->magic, VENDOR_BOOT_MAGIC, VENDOR_BOOT_MAGIC_SIZE) != 0) {
return Errorf("Vendor boot image magic mismatch");
}
if (hdr->header_version < version) {
return Errorf("Require vendor boot header V{} but is V{}", version, hdr->header_version);
}
return {};
}
// Wrapper of ReadFdToString. Seek to the beginning and read the whole file to string.
[[nodiscard]] Result<std::string> load_file(android::base::borrowed_fd fd, uint64_t expected_size,
const char* what) {
if (lseek(fd.get(), 0, SEEK_SET) != 0) {
return ErrnoErrorf("Can't seek to the beginning of {} image", what);
}
std::string content;
if (!android::base::ReadFdToString(fd, &content)) {
return ErrnoErrorf("Cannot read {} to string", what);
}
if (content.size() != expected_size) {
return Errorf("Size of {} does not match, expected 0x{:x}, read 0x{:x}", what,
expected_size, content.size());
}
return content;
}
// Wrapper of WriteStringToFd. Seek to the beginning and write the whole file to string.
[[nodiscard]] Result<void> store_file(android::base::borrowed_fd fd, const std::string& data,
const char* what) {
if (lseek(fd.get(), 0, SEEK_SET) != 0) {
return ErrnoErrorf("Cannot seek to beginning of {} before writing", what);
}
if (!android::base::WriteStringToFd(data, fd)) {
return ErrnoErrorf("Cannot write new content to {}", what);
}
if (TEMP_FAILURE_RETRY(ftruncate64(fd.get(), data.size())) == -1) {
return ErrnoErrorf("Truncating new vendor boot image to 0x{:x} fails", data.size());
}
return {};
}
// Copy AVB footer if it exists in the old buffer.
[[nodiscard]] Result<void> copy_avb_footer(DataUpdater* updater) {
if (updater->size() < AVB_FOOTER_SIZE) return {};
if (auto res = updater->Seek(updater->size() - AVB_FOOTER_SIZE); !res.ok()) return res;
if (memcmp(updater->old_cur(), AVB_FOOTER_MAGIC, AVB_FOOTER_MAGIC_LEN) != 0) return {};
return updater->Copy(AVB_FOOTER_SIZE);
}
// round |value| up to a multiple of |page_size|.
inline uint32_t round_up(uint32_t value, uint32_t page_size) {
return (value + page_size - 1) / page_size * page_size;
}
// Replace the vendor ramdisk as a whole.
[[nodiscard]] Result<std::string> replace_default_vendor_ramdisk(const std::string& vendor_boot,
const std::string& new_ramdisk) {
if (auto res = check_vendor_boot_hdr(vendor_boot, 3); !res.ok()) return res.error();
auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v3*>(vendor_boot.data());
auto hdr_size = get_vendor_boot_header_size(hdr);
if (!hdr_size.ok()) return hdr_size.error();
// Refer to bootimg.h for details. Numbers are in bytes.
const uint32_t o = round_up(*hdr_size, hdr->page_size);
const uint32_t p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
const uint32_t q = round_up(hdr->dtb_size, hdr->page_size);
DataUpdater updater(vendor_boot);
// Copy header (O bytes), then update fields in header.
if (auto res = updater.Copy(o); !res.ok()) return res.error();
auto new_hdr = reinterpret_cast<vendor_boot_img_hdr_v3*>(updater.new_begin());
new_hdr->vendor_ramdisk_size = new_ramdisk.size();
// Because it is unknown how the new ramdisk is fragmented, the whole table is replaced
// with a single entry representing the full ramdisk.
if (new_hdr->header_version >= 4) {
auto new_hdr_v4 = static_cast<vendor_boot_img_hdr_v4*>(new_hdr);
new_hdr_v4->vendor_ramdisk_table_entry_size = sizeof(vendor_ramdisk_table_entry_v4);
new_hdr_v4->vendor_ramdisk_table_entry_num = 1;
new_hdr_v4->vendor_ramdisk_table_size = new_hdr_v4->vendor_ramdisk_table_entry_num *
new_hdr_v4->vendor_ramdisk_table_entry_size;
}
// Copy the new ramdisk.
if (auto res = updater.Replace(hdr->vendor_ramdisk_size, new_ramdisk); !res.ok())
return res.error();
const uint32_t new_p = round_up(new_hdr->vendor_ramdisk_size, new_hdr->page_size);
if (auto res = updater.Skip(p - hdr->vendor_ramdisk_size, new_p - new_hdr->vendor_ramdisk_size);
!res.ok())
return res.error();
if (auto res = updater.CheckOffset(o + p, o + new_p); !res.ok()) return res.error();
// Copy DTB (Q bytes).
if (auto res = updater.Copy(q); !res.ok()) return res.error();
if (new_hdr->header_version >= 4) {
auto hdr_v4 = static_cast<const vendor_boot_img_hdr_v4*>(hdr);
const uint32_t r = round_up(hdr_v4->vendor_ramdisk_table_size, hdr_v4->page_size);
const uint32_t s = round_up(hdr_v4->bootconfig_size, hdr_v4->page_size);
auto new_entry = reinterpret_cast<vendor_ramdisk_table_entry_v4*>(updater.new_cur());
auto new_hdr_v4 = static_cast<const vendor_boot_img_hdr_v4*>(new_hdr);
auto new_r = round_up(new_hdr_v4->vendor_ramdisk_table_size, new_hdr->page_size);
if (auto res = updater.Skip(r, new_r); !res.ok()) return res.error();
if (auto res = updater.CheckOffset(o + p + q + r, o + new_p + q + new_r); !res.ok())
return res.error();
// Replace table with single entry representing the full ramdisk.
new_entry->ramdisk_size = new_hdr->vendor_ramdisk_size;
new_entry->ramdisk_offset = 0;
new_entry->ramdisk_type = VENDOR_RAMDISK_TYPE_NONE;
memset(new_entry->ramdisk_name, '\0', VENDOR_RAMDISK_NAME_SIZE);
memset(new_entry->board_id, '\0', VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE);
// Copy bootconfig (S bytes).
if (auto res = updater.Copy(s); !res.ok()) return res.error();
}
if (auto res = copy_avb_footer(&updater); !res.ok()) return res.error();
return updater.Finish();
}
// Find a ramdisk fragment with a unique name. Abort if none or multiple fragments are found.
[[nodiscard]] Result<const vendor_ramdisk_table_entry_v4*> find_unique_ramdisk(
const std::string& ramdisk_name, const vendor_ramdisk_table_entry_v4* table,
uint32_t size) {
const vendor_ramdisk_table_entry_v4* ret = nullptr;
uint32_t idx = 0;
const vendor_ramdisk_table_entry_v4* entry = table;
for (; idx < size; idx++, entry++) {
auto entry_name_c_str = reinterpret_cast<const char*>(entry->ramdisk_name);
auto entry_name_len = strnlen(entry_name_c_str, VENDOR_RAMDISK_NAME_SIZE);
std::string_view entry_name(entry_name_c_str, entry_name_len);
if (entry_name == ramdisk_name) {
if (ret != nullptr) {
return Errorf("Multiple vendor ramdisk '{}' found, name should be unique",
ramdisk_name.c_str());
}
ret = entry;
}
}
if (ret == nullptr) {
return Errorf("Vendor ramdisk '{}' not found", ramdisk_name.c_str());
}
return ret;
}
// Find the vendor ramdisk fragment with |ramdisk_name| within the content of |vendor_boot|, and
// replace it with the content of |new_ramdisk|.
[[nodiscard]] Result<std::string> replace_vendor_ramdisk_fragment(const std::string& ramdisk_name,
const std::string& vendor_boot,
const std::string& new_ramdisk) {
if (auto res = check_vendor_boot_hdr(vendor_boot, 4); !res.ok()) return res.error();
auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v4*>(vendor_boot.data());
auto hdr_size = get_vendor_boot_header_size(hdr);
if (!hdr_size.ok()) return hdr_size.error();
// Refer to bootimg.h for details. Numbers are in bytes.
const uint32_t o = round_up(*hdr_size, hdr->page_size);
const uint32_t p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
const uint32_t q = round_up(hdr->dtb_size, hdr->page_size);
const uint32_t r = round_up(hdr->vendor_ramdisk_table_size, hdr->page_size);
const uint32_t s = round_up(hdr->bootconfig_size, hdr->page_size);
if (hdr->vendor_ramdisk_table_entry_num == std::numeric_limits<uint32_t>::max()) {
return Errorf("Too many vendor ramdisk entries in table, overflow");
}
// Find entry with name |ramdisk_name|.
auto old_table_start =
reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(vendor_boot.data() + o + p + q);
auto find_res =
find_unique_ramdisk(ramdisk_name, old_table_start, hdr->vendor_ramdisk_table_entry_num);
if (!find_res.ok()) return find_res.error();
const vendor_ramdisk_table_entry_v4* replace_entry = *find_res;
uint32_t replace_idx = replace_entry - old_table_start;
// Now reconstruct.
DataUpdater updater(vendor_boot);
// Copy header (O bytes), then update fields in header.
if (auto res = updater.Copy(o); !res.ok()) return res.error();
auto new_hdr = reinterpret_cast<vendor_boot_img_hdr_v4*>(updater.new_begin());
// Copy ramdisk fragments, replace for the matching index.
{
auto old_ramdisk_entry = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(
vendor_boot.data() + o + p + q);
uint32_t new_total_ramdisk_size = 0;
for (uint32_t new_ramdisk_idx = 0; new_ramdisk_idx < hdr->vendor_ramdisk_table_entry_num;
new_ramdisk_idx++, old_ramdisk_entry++) {
if (new_ramdisk_idx == replace_idx) {
if (auto res = updater.Replace(replace_entry->ramdisk_size, new_ramdisk); !res.ok())
return res.error();
new_total_ramdisk_size += new_ramdisk.size();
} else {
if (auto res = updater.Copy(old_ramdisk_entry->ramdisk_size); !res.ok())
return res.error();
new_total_ramdisk_size += old_ramdisk_entry->ramdisk_size;
}
}
new_hdr->vendor_ramdisk_size = new_total_ramdisk_size;
}
// Pad ramdisk to page boundary.
const uint32_t new_p = round_up(new_hdr->vendor_ramdisk_size, new_hdr->page_size);
if (auto res = updater.Skip(p - hdr->vendor_ramdisk_size, new_p - new_hdr->vendor_ramdisk_size);
!res.ok())
return res.error();
if (auto res = updater.CheckOffset(o + p, o + new_p); !res.ok()) return res.error();
// Copy DTB (Q bytes).
if (auto res = updater.Copy(q); !res.ok()) return res.error();
// Copy table, but with corresponding entries modified, including:
// - ramdisk_size of the entry replaced
// - ramdisk_offset of subsequent entries.
for (uint32_t new_total_ramdisk_size = 0, new_entry_idx = 0;
new_entry_idx < hdr->vendor_ramdisk_table_entry_num; new_entry_idx++) {
auto new_entry = reinterpret_cast<vendor_ramdisk_table_entry_v4*>(updater.new_cur());
if (auto res = updater.Copy(hdr->vendor_ramdisk_table_entry_size); !res.ok())
return res.error();
new_entry->ramdisk_offset = new_total_ramdisk_size;
if (new_entry_idx == replace_idx) {
new_entry->ramdisk_size = new_ramdisk.size();
}
new_total_ramdisk_size += new_entry->ramdisk_size;
}
// Copy padding of R pages; this is okay because table size is not changed.
if (auto res = updater.Copy(r - hdr->vendor_ramdisk_table_entry_num *
hdr->vendor_ramdisk_table_entry_size);
!res.ok())
return res.error();
if (auto res = updater.CheckOffset(o + p + q + r, o + new_p + q + r); !res.ok())
return res.error();
// Copy bootconfig (S bytes).
if (auto res = updater.Copy(s); !res.ok()) return res.error();
if (auto res = copy_avb_footer(&updater); !res.ok()) return res.error();
return updater.Finish();
}
} // namespace
[[nodiscard]] Result<void> replace_vendor_ramdisk(android::base::borrowed_fd vendor_boot_fd,
uint64_t vendor_boot_size,
const std::string& ramdisk_name,
android::base::borrowed_fd new_ramdisk_fd,
uint64_t new_ramdisk_size) {
if (new_ramdisk_size > std::numeric_limits<uint32_t>::max()) {
return Errorf("New vendor ramdisk is too big");
}
auto vendor_boot = load_file(vendor_boot_fd, vendor_boot_size, "vendor boot");
if (!vendor_boot.ok()) return vendor_boot.error();
auto new_ramdisk = load_file(new_ramdisk_fd, new_ramdisk_size, "new vendor ramdisk");
if (!new_ramdisk.ok()) return new_ramdisk.error();
Result<std::string> new_vendor_boot;
if (ramdisk_name == "default") {
new_vendor_boot = replace_default_vendor_ramdisk(*vendor_boot, *new_ramdisk);
} else {
new_vendor_boot = replace_vendor_ramdisk_fragment(ramdisk_name, *vendor_boot, *new_ramdisk);
}
if (!new_vendor_boot.ok()) return new_vendor_boot.error();
if (auto res = store_file(vendor_boot_fd, *new_vendor_boot, "new vendor boot image"); !res.ok())
return res.error();
return {};
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <inttypes.h>
#include <string>
#include <android-base/result.h>
#include <android-base/unique_fd.h>
// Replace the vendor ramdisk named |ramdisk_name| within the vendor boot image,
// specified by |vendor_boot_fd|, with the ramdisk specified by |new_ramdisk_fd|. Checks
// that the size of the files are |vendor_boot_size| and |new_ramdisk_size|, respectively.
// If |ramdisk_name| is "default", replace the vendor ramdisk as a whole. Otherwise, replace
// a vendor ramdisk fragment with the given unique name.
[[nodiscard]] android::base::Result<void> replace_vendor_ramdisk(
android::base::borrowed_fd vendor_boot_fd, uint64_t vendor_boot_size,
const std::string& ramdisk_name, android::base::borrowed_fd new_ramdisk_fd,
uint64_t new_ramdisk_size);

View file

@ -0,0 +1,429 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <filesystem>
#include <optional>
#include <string_view>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/result.h>
#include <android-base/strings.h>
#include <bootimg.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <libavb/libavb.h>
#include "vendor_boot_img_utils.h"
using android::base::borrowed_fd;
using android::base::ErrnoError;
using android::base::GetExecutableDirectory;
using android::base::ReadFdToString;
using android::base::Result;
using testing::AllOf;
using testing::Each;
using testing::Eq;
using testing::HasSubstr;
using testing::Not;
using testing::Property;
using std::string_literals::operator""s;
// Expect that the Result<T> returned by |expr| is successful, and value matches |result_matcher|.
#define EXPECT_RESULT(expr, result_matcher) \
EXPECT_THAT(expr, AllOf(Property(&decltype(expr)::ok, Eq(true)), \
Property(&decltype(expr)::value, result_matcher)))
// Expect that the Result<T> returned by |expr| fails, and error message matches |error_matcher|.
#define EXPECT_ERROR(expr, error_matcher) \
do { \
EXPECT_THAT( \
expr, \
AllOf(Property(&decltype(expr)::ok, Eq(false)), \
Property(&decltype(expr)::error, \
Property(&decltype(expr)::error_type::message, error_matcher)))); \
} while (0)
namespace {
// Wrapper of fstat.
Result<uint64_t> FileSize(borrowed_fd fd, std::filesystem::path path) {
struct stat sb;
if (fstat(fd.get(), &sb) == -1) return ErrnoError() << "fstat(" << path << ")";
return sb.st_size;
}
// Seek to beginning then read the whole file.
Result<std::string> ReadStartOfFdToString(borrowed_fd fd, std::filesystem::path path) {
if (lseek64(fd.get(), 0, SEEK_SET) != 0)
return ErrnoError() << "lseek64(" << path << ", 0, SEEK_SET)";
std::string content;
if (!android::base::ReadFdToString(fd, &content)) return ErrnoError() << "read(" << path << ")";
return content;
}
// Round |value| up to page boundary.
inline uint32_t round_up(uint32_t value, uint32_t page_size) {
return (value + page_size - 1) / page_size * page_size;
}
// Match is successful if |arg| is a zero-padded version of |expected|.
MATCHER_P(IsPadded, expected, (negation ? "is" : "isn't") + " zero-padded of expected value"s) {
if (arg.size() < expected.size()) return false;
if (0 != memcmp(arg.data(), expected.data(), expected.size())) return false;
auto remainder = std::string_view(arg).substr(expected.size());
for (char e : remainder)
if (e != '\0') return false;
return true;
}
// Same as Eq, but don't print the content to avoid spam.
MATCHER_P(MemEq, expected, (negation ? "is" : "isn't") + " expected value"s) {
if (arg.size() != expected.size()) return false;
return 0 == memcmp(arg.data(), expected.data(), expected.size());
}
// Expect that |arg| and |expected| has the same AVB footer.
MATCHER_P(HasSameAvbFooter, expected,
(negation ? "has" : "does not have") + "expected AVB footer"s) {
if (expected.size() < AVB_FOOTER_SIZE || arg.size() < AVB_FOOTER_SIZE) return false;
return std::string_view(expected).substr(expected.size() - AVB_FOOTER_SIZE) ==
std::string_view(arg).substr(arg.size() - AVB_FOOTER_SIZE);
}
// A lazy handle of a file.
struct TestFileHandle {
virtual ~TestFileHandle() = default;
// Lazily call OpenImpl(), cache result in open_result_.
android::base::Result<void> Open() {
if (!open_result_.has_value()) open_result_ = OpenImpl();
return open_result_.value();
}
// The original size at the time when the file is opened. If the file has been modified,
// this field is NOT updated.
uint64_t size() {
CHECK(open_result_.has_value());
return size_;
}
// The current size of the file. If the file has been modified since opened,
// this is updated.
Result<uint64_t> fsize() {
CHECK(open_result_.has_value());
return FileSize(fd_, abs_path_);
}
borrowed_fd fd() {
CHECK(open_result_.has_value());
return fd_;
}
Result<std::string> Read() {
CHECK(open_result_.has_value());
return ReadStartOfFdToString(fd_, abs_path_);
}
private:
std::filesystem::path abs_path_;
uint64_t size_;
std::optional<android::base::Result<void>> open_result_;
borrowed_fd fd_{-1};
// Opens |rel_path_| as a readonly fd, pass it to Transform, and store result to
// |borrowed_fd_|.
android::base::Result<void> OpenImpl() {
android::base::unique_fd read_fd(TEMP_FAILURE_RETRY(
open(abs_path_.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_BINARY)));
if (!read_fd.ok()) return ErrnoError() << "open(" << abs_path_ << ")";
auto size = FileSize(read_fd, abs_path_);
if (!size.ok()) return size.error();
size_ = *size;
auto borrowed_fd = Transform(abs_path_, std::move(read_fd));
if (!borrowed_fd.ok()) return borrowed_fd.error();
fd_ = borrowed_fd.value();
return {};
}
protected:
// |rel_path| is the relative path under test data directory.
TestFileHandle(const std::filesystem::path& rel_path)
: abs_path_(std::move(std::filesystem::path(GetExecutableDirectory()) / rel_path)) {}
// Given |read_fd|, the readonly fd on the test file, return an fd that's suitable for client
// to use. Implementation is responsible for managing the lifetime of the returned fd.
virtual android::base::Result<borrowed_fd> Transform(const std::filesystem::path& abs_path,
android::base::unique_fd read_fd) = 0;
};
// A TestFileHandle where the file is readonly.
struct ReadOnlyTestFileHandle : TestFileHandle {
ReadOnlyTestFileHandle(const std::filesystem::path& rel_path) : TestFileHandle(rel_path) {}
private:
android::base::unique_fd owned_fd_;
android::base::Result<borrowed_fd> Transform(const std::filesystem::path&,
android::base::unique_fd read_fd) override {
owned_fd_ = std::move(read_fd);
return owned_fd_;
}
};
// A TestFileHandle where the test file is copies, hence read-writable.
struct ReadWriteTestFileHandle : TestFileHandle {
ReadWriteTestFileHandle(const std::filesystem::path& rel_path) : TestFileHandle(rel_path) {}
private:
std::unique_ptr<TemporaryFile> temp_file_;
android::base::Result<borrowed_fd> Transform(const std::filesystem::path& abs_path,
android::base::unique_fd read_fd) override {
// Make a copy to avoid writing to test data. Test files are small, so it is okay
// to read the whole file.
auto content = ReadStartOfFdToString(read_fd, abs_path);
if (!content.ok()) return content.error();
temp_file_ = std::make_unique<TemporaryFile>();
if (temp_file_->fd == -1)
return ErrnoError() << "copy " << abs_path << ": open temp file failed";
if (!android::base::WriteStringToFd(*content, temp_file_->fd))
return ErrnoError() << "copy " << abs_path << ": write temp file failed";
return temp_file_->fd;
}
};
class RepackVendorBootImgTestEnv : public ::testing::Environment {
public:
virtual void SetUp() {
OpenTestFile("test_dtb.img", &dtb, &dtb_content);
OpenTestFile("test_bootconfig.img", &bootconfig, &bootconfig_content);
OpenTestFile("test_vendor_ramdisk_none.img", &none, &none_content);
OpenTestFile("test_vendor_ramdisk_platform.img", &platform, &platform_content);
OpenTestFile("test_vendor_ramdisk_replace.img", &replace, &replace_content);
}
std::unique_ptr<TestFileHandle> dtb;
std::string dtb_content;
std::unique_ptr<TestFileHandle> bootconfig;
std::string bootconfig_content;
std::unique_ptr<TestFileHandle> none;
std::string none_content;
std::unique_ptr<TestFileHandle> platform;
std::string platform_content;
std::unique_ptr<TestFileHandle> replace;
std::string replace_content;
private:
void OpenTestFile(const char* rel_path, std::unique_ptr<TestFileHandle>* handle,
std::string* content) {
*handle = std::make_unique<ReadOnlyTestFileHandle>(rel_path);
ASSERT_RESULT_OK((*handle)->Open());
auto content_res = (*handle)->Read();
ASSERT_RESULT_OK(content_res);
*content = *content_res;
}
};
RepackVendorBootImgTestEnv* env = nullptr;
struct RepackVendorBootImgTestParam {
std::string vendor_boot_file_name;
uint32_t expected_header_version;
friend std::ostream& operator<<(std::ostream& os, const RepackVendorBootImgTestParam& param) {
return os << param.vendor_boot_file_name;
}
};
class RepackVendorBootImgTest : public ::testing::TestWithParam<RepackVendorBootImgTestParam> {
public:
virtual void SetUp() {
vboot = std::make_unique<ReadWriteTestFileHandle>(GetParam().vendor_boot_file_name);
ASSERT_RESULT_OK(vboot->Open());
}
std::unique_ptr<TestFileHandle> vboot;
};
TEST_P(RepackVendorBootImgTest, InvalidSize) {
EXPECT_ERROR(replace_vendor_ramdisk(vboot->fd(), vboot->size() + 1, "default",
env->replace->fd(), env->replace->size()),
HasSubstr("Size of vendor boot does not match"));
EXPECT_ERROR(replace_vendor_ramdisk(vboot->fd(), vboot->size(), "default", env->replace->fd(),
env->replace->size() + 1),
HasSubstr("Size of new vendor ramdisk does not match"));
}
TEST_P(RepackVendorBootImgTest, ReplaceUnknown) {
auto res = replace_vendor_ramdisk(vboot->fd(), vboot->size(), "unknown", env->replace->fd(),
env->replace->size());
if (GetParam().expected_header_version == 3) {
EXPECT_ERROR(res, Eq("Require vendor boot header V4 but is V3"));
} else if (GetParam().expected_header_version == 4) {
EXPECT_ERROR(res, Eq("Vendor ramdisk 'unknown' not found"));
}
}
TEST_P(RepackVendorBootImgTest, ReplaceDefault) {
auto old_content = vboot->Read();
ASSERT_RESULT_OK(old_content);
ASSERT_RESULT_OK(replace_vendor_ramdisk(vboot->fd(), vboot->size(), "default",
env->replace->fd(), env->replace->size()));
EXPECT_RESULT(vboot->fsize(), vboot->size()) << "File size should not change after repack";
auto new_content_res = vboot->Read();
ASSERT_RESULT_OK(new_content_res);
std::string_view new_content(*new_content_res);
auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v3*>(new_content.data());
ASSERT_EQ(0, memcmp(VENDOR_BOOT_MAGIC, hdr->magic, VENDOR_BOOT_MAGIC_SIZE));
ASSERT_EQ(GetParam().expected_header_version, hdr->header_version);
EXPECT_EQ(hdr->vendor_ramdisk_size, env->replace->size());
EXPECT_EQ(hdr->dtb_size, env->dtb->size());
auto o = round_up(sizeof(vendor_boot_img_hdr_v3), hdr->page_size);
auto p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
auto q = round_up(hdr->dtb_size, hdr->page_size);
EXPECT_THAT(new_content.substr(o, p), IsPadded(env->replace_content));
EXPECT_THAT(new_content.substr(o + p, q), IsPadded(env->dtb_content));
if (hdr->header_version < 4) return;
auto hdr_v4 = static_cast<const vendor_boot_img_hdr_v4*>(hdr);
EXPECT_EQ(hdr_v4->vendor_ramdisk_table_entry_num, 1);
EXPECT_EQ(hdr_v4->vendor_ramdisk_table_size, 1 * hdr_v4->vendor_ramdisk_table_entry_size);
EXPECT_GE(hdr_v4->vendor_ramdisk_table_entry_size, sizeof(vendor_ramdisk_table_entry_v4));
auto entry = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(&new_content[o + p + q]);
EXPECT_EQ(entry->ramdisk_offset, 0);
EXPECT_EQ(entry->ramdisk_size, hdr_v4->vendor_ramdisk_size);
EXPECT_EQ(entry->ramdisk_type, VENDOR_RAMDISK_TYPE_NONE);
EXPECT_EQ(hdr_v4->bootconfig_size, env->bootconfig->size());
auto r = round_up(hdr_v4->vendor_ramdisk_table_size, hdr_v4->page_size);
auto s = round_up(hdr_v4->bootconfig_size, hdr_v4->page_size);
EXPECT_THAT(new_content.substr(o + p + q + r, s), IsPadded(env->bootconfig_content));
EXPECT_THAT(new_content, HasSameAvbFooter(*old_content));
}
INSTANTIATE_TEST_SUITE_P(
RepackVendorBootImgTest, RepackVendorBootImgTest,
::testing::Values(RepackVendorBootImgTestParam{"vendor_boot_v3.img", 3},
RepackVendorBootImgTestParam{"vendor_boot_v4_with_frag.img", 4},
RepackVendorBootImgTestParam{"vendor_boot_v4_without_frag.img", 4}),
[](const auto& info) {
return android::base::StringReplace(info.param.vendor_boot_file_name, ".", "_", false);
});
std::string_view GetRamdiskName(const vendor_ramdisk_table_entry_v4* entry) {
auto ramdisk_name = reinterpret_cast<const char*>(entry->ramdisk_name);
return std::string_view(ramdisk_name, strnlen(ramdisk_name, VENDOR_RAMDISK_NAME_SIZE));
}
class RepackVendorBootImgTestV4 : public ::testing::TestWithParam<uint32_t /* ramdisk type */> {
public:
virtual void SetUp() {
vboot = std::make_unique<ReadWriteTestFileHandle>("vendor_boot_v4_with_frag.img");
ASSERT_RESULT_OK(vboot->Open());
}
std::unique_ptr<TestFileHandle> vboot;
};
TEST_P(RepackVendorBootImgTestV4, Replace) {
uint32_t replace_ramdisk_type = GetParam();
std::string replace_ramdisk_name;
std::string expect_new_ramdisk_content;
uint32_t expect_none_size = env->none->size();
uint32_t expect_platform_size = env->platform->size();
switch (replace_ramdisk_type) {
case VENDOR_RAMDISK_TYPE_NONE:
replace_ramdisk_name = "none_ramdisk";
expect_new_ramdisk_content = env->replace_content + env->platform_content;
expect_none_size = env->replace->size();
break;
case VENDOR_RAMDISK_TYPE_PLATFORM:
replace_ramdisk_name = "platform_ramdisk";
expect_new_ramdisk_content = env->none_content + env->replace_content;
expect_platform_size = env->replace->size();
break;
default:
LOG(FATAL) << "Ramdisk type " << replace_ramdisk_type
<< " is not supported by this test.";
}
auto old_content = vboot->Read();
ASSERT_RESULT_OK(old_content);
ASSERT_RESULT_OK(replace_vendor_ramdisk(vboot->fd(), vboot->size(), replace_ramdisk_name,
env->replace->fd(), env->replace->size()));
EXPECT_RESULT(vboot->fsize(), vboot->size()) << "File size should not change after repack";
auto new_content_res = vboot->Read();
ASSERT_RESULT_OK(new_content_res);
std::string_view new_content(*new_content_res);
auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v4*>(new_content.data());
ASSERT_EQ(0, memcmp(VENDOR_BOOT_MAGIC, hdr->magic, VENDOR_BOOT_MAGIC_SIZE));
ASSERT_EQ(4, hdr->header_version);
EXPECT_EQ(hdr->vendor_ramdisk_size, expect_none_size + expect_platform_size);
EXPECT_EQ(hdr->dtb_size, env->dtb->size());
EXPECT_EQ(hdr->bootconfig_size, env->bootconfig->size());
auto o = round_up(sizeof(vendor_boot_img_hdr_v3), hdr->page_size);
auto p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
auto q = round_up(hdr->dtb_size, hdr->page_size);
auto r = round_up(hdr->vendor_ramdisk_table_size, hdr->page_size);
auto s = round_up(hdr->bootconfig_size, hdr->page_size);
EXPECT_THAT(new_content.substr(o, p), IsPadded(expect_new_ramdisk_content));
EXPECT_THAT(new_content.substr(o + p, q), IsPadded(env->dtb_content));
// Check changes in table.
EXPECT_EQ(hdr->vendor_ramdisk_table_entry_num, 2);
EXPECT_EQ(hdr->vendor_ramdisk_table_size, 2 * hdr->vendor_ramdisk_table_entry_size);
EXPECT_GE(hdr->vendor_ramdisk_table_entry_size, sizeof(vendor_ramdisk_table_entry_v4));
auto entry_none =
reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(&new_content[o + p + q]);
EXPECT_EQ(entry_none->ramdisk_offset, 0);
EXPECT_EQ(entry_none->ramdisk_size, expect_none_size);
EXPECT_EQ(entry_none->ramdisk_type, VENDOR_RAMDISK_TYPE_NONE);
EXPECT_EQ(GetRamdiskName(entry_none), "none_ramdisk");
auto entry_platform = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(
&new_content[o + p + q + hdr->vendor_ramdisk_table_entry_size]);
EXPECT_EQ(entry_platform->ramdisk_offset, expect_none_size);
EXPECT_EQ(entry_platform->ramdisk_size, expect_platform_size);
EXPECT_EQ(entry_platform->ramdisk_type, VENDOR_RAMDISK_TYPE_PLATFORM);
EXPECT_EQ(GetRamdiskName(entry_platform), "platform_ramdisk");
EXPECT_THAT(new_content.substr(o + p + q + r, s), IsPadded(env->bootconfig_content));
EXPECT_THAT(new_content, HasSameAvbFooter(*old_content));
}
INSTANTIATE_TEST_SUITE_P(RepackVendorBootImgTest, RepackVendorBootImgTestV4,
::testing::Values(VENDOR_RAMDISK_TYPE_NONE, VENDOR_RAMDISK_TYPE_PLATFORM),
[](const auto& info) {
return info.param == VENDOR_RAMDISK_TYPE_NONE ? "none" : "platform";
});
} // namespace
int main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
env = static_cast<RepackVendorBootImgTestEnv*>(
testing::AddGlobalTestEnvironment(new RepackVendorBootImgTestEnv));
return RUN_ALL_TESTS();
}