Merge changes from topic "fastboot_fetch"
* changes: fuzzy_fastboot: Add tests for fetch:vendor_boot fuzzy_fastboot: Add conformance test for getvar:max-fetch-size. fastboot driver: repack vendor boot ramdisk fastboot driver: Allow colon in partition name when flashing fastboot driver: add fetch command in driver fastboot driver: RunAndReadBuffer don't allocate too much mem fastboot driver: add RunAndReadBuffer helper fastboot driver: Fix ownership of fd in fastboot_buffer. fastboot driver: fix message fastbootd: add O_CLOEXEC/O_BINARY for OpenPartition fastbootd: add read arg to OpenPartition fastbootd: Add fetch command on device fastbootd: Add getvar max-fetch-size.
This commit is contained in:
commit
4bf547af43
20 changed files with 1606 additions and 97 deletions
|
|
@ -55,6 +55,7 @@ cc_library_host_static {
|
|||
"tcp.cpp",
|
||||
"udp.cpp",
|
||||
"util.cpp",
|
||||
"vendor_boot_img_utils.cpp",
|
||||
"fastboot_driver.cpp",
|
||||
],
|
||||
|
||||
|
|
@ -75,7 +76,9 @@ cc_library_host_static {
|
|||
],
|
||||
|
||||
header_libs: [
|
||||
"avb_headers",
|
||||
"bootimg_headers",
|
||||
"libstorage_literals_headers",
|
||||
],
|
||||
|
||||
export_header_lib_headers: [
|
||||
|
|
@ -138,6 +141,12 @@ cc_binary {
|
|||
|
||||
recovery: true,
|
||||
|
||||
product_variables: {
|
||||
debuggable: {
|
||||
cppflags: ["-DFB_ENABLE_FETCH"],
|
||||
},
|
||||
},
|
||||
|
||||
srcs: [
|
||||
"device/commands.cpp",
|
||||
"device/fastboot_device.cpp",
|
||||
|
|
@ -183,7 +192,8 @@ cc_binary {
|
|||
header_libs: [
|
||||
"avb_headers",
|
||||
"libsnapshot_headers",
|
||||
]
|
||||
"libstorage_literals_headers",
|
||||
],
|
||||
}
|
||||
|
||||
cc_defaults {
|
||||
|
|
@ -262,12 +272,17 @@ cc_library_host_static {
|
|||
"tcp.cpp",
|
||||
"udp.cpp",
|
||||
"util.cpp",
|
||||
"vendor_boot_img_utils.cpp",
|
||||
"fastboot_driver.cpp",
|
||||
],
|
||||
|
||||
// Only version the final binaries
|
||||
use_version_lib: false,
|
||||
static_libs: ["libbuildversion"],
|
||||
header_libs: [
|
||||
"avb_headers",
|
||||
"libstorage_literals_headers",
|
||||
],
|
||||
|
||||
generated_headers: ["platform_tools_version"],
|
||||
|
||||
|
|
@ -359,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"
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#define FB_CMD_OEM "oem"
|
||||
#define FB_CMD_GSI "gsi"
|
||||
#define FB_CMD_SNAPSHOT_UPDATE "snapshot-update"
|
||||
#define FB_CMD_FETCH "fetch"
|
||||
|
||||
#define RESPONSE_OKAY "OKAY"
|
||||
#define RESPONSE_FAIL "FAIL"
|
||||
|
|
@ -77,3 +78,4 @@
|
|||
#define FB_VAR_FIRST_API_LEVEL "first-api-level"
|
||||
#define FB_VAR_SECURITY_PATCH_LEVEL "security-patch-level"
|
||||
#define FB_VAR_TREBLE_ENABLED "treble-enabled"
|
||||
#define FB_VAR_MAX_FETCH_SIZE "max-fetch-size"
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include "commands.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
|
|
@ -36,6 +37,7 @@
|
|||
#include <liblp/builder.h>
|
||||
#include <liblp/liblp.h>
|
||||
#include <libsnapshot/snapshot.h>
|
||||
#include <storage_literals/storage_literals.h>
|
||||
#include <uuid/uuid.h>
|
||||
|
||||
#include "constants.h"
|
||||
|
|
@ -43,6 +45,12 @@
|
|||
#include "flashing.h"
|
||||
#include "utility.h"
|
||||
|
||||
#ifdef FB_ENABLE_FETCH
|
||||
static constexpr bool kEnableFetch = true;
|
||||
#else
|
||||
static constexpr bool kEnableFetch = false;
|
||||
#endif
|
||||
|
||||
using android::fs_mgr::MetadataBuilder;
|
||||
using ::android::hardware::hidl_string;
|
||||
using ::android::hardware::boot::V1_0::BoolResult;
|
||||
|
|
@ -54,6 +62,8 @@ using ::android::hardware::fastboot::V1_0::Status;
|
|||
using android::snapshot::SnapshotManager;
|
||||
using IBootControl1_1 = ::android::hardware::boot::V1_1::IBootControl;
|
||||
|
||||
using namespace android::storage_literals;
|
||||
|
||||
struct VariableHandlers {
|
||||
// Callback to retrieve the value of a single variable.
|
||||
std::function<bool(FastbootDevice*, const std::vector<std::string>&, std::string*)> get;
|
||||
|
|
@ -136,7 +146,9 @@ bool GetVarHandler(FastbootDevice* device, const std::vector<std::string>& args)
|
|||
{FB_VAR_DYNAMIC_PARTITION, {GetDynamicPartition, nullptr}},
|
||||
{FB_VAR_FIRST_API_LEVEL, {GetFirstApiLevel, nullptr}},
|
||||
{FB_VAR_SECURITY_PATCH_LEVEL, {GetSecurityPatchLevel, nullptr}},
|
||||
{FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}}};
|
||||
{FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}},
|
||||
{FB_VAR_MAX_FETCH_SIZE, {GetMaxFetchSize, nullptr}},
|
||||
};
|
||||
|
||||
if (args.size() < 2) {
|
||||
return device->WriteFail("Missing argument");
|
||||
|
|
@ -671,3 +683,175 @@ bool SnapshotUpdateHandler(FastbootDevice* device, const std::vector<std::string
|
|||
}
|
||||
return device->WriteStatus(FastbootResult::OKAY, "Success");
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Helper of FetchHandler.
|
||||
class PartitionFetcher {
|
||||
public:
|
||||
static bool Fetch(FastbootDevice* device, const std::vector<std::string>& args) {
|
||||
if constexpr (!kEnableFetch) {
|
||||
return device->WriteFail("Fetch is not allowed on user build");
|
||||
}
|
||||
|
||||
if (GetDeviceLockStatus()) {
|
||||
return device->WriteFail("Fetch is not allowed on locked devices");
|
||||
}
|
||||
|
||||
PartitionFetcher fetcher(device, args);
|
||||
if (fetcher.Open()) {
|
||||
fetcher.Fetch();
|
||||
}
|
||||
CHECK(fetcher.ret_.has_value());
|
||||
return *fetcher.ret_;
|
||||
}
|
||||
|
||||
private:
|
||||
PartitionFetcher(FastbootDevice* device, const std::vector<std::string>& args)
|
||||
: device_(device), args_(&args) {}
|
||||
// Return whether the partition is successfully opened.
|
||||
// If successfully opened, ret_ is left untouched. Otherwise, ret_ is set to the value
|
||||
// that FetchHandler should return.
|
||||
bool Open() {
|
||||
if (args_->size() < 2) {
|
||||
ret_ = device_->WriteFail("Missing partition arg");
|
||||
return false;
|
||||
}
|
||||
|
||||
partition_name_ = args_->at(1);
|
||||
if (std::find(kAllowedPartitions.begin(), kAllowedPartitions.end(), partition_name_) ==
|
||||
kAllowedPartitions.end()) {
|
||||
ret_ = device_->WriteFail("Fetch is only allowed on [" +
|
||||
android::base::Join(kAllowedPartitions, ", ") + "]");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!OpenPartition(device_, partition_name_, &handle_, true /* read */)) {
|
||||
ret_ = device_->WriteFail(
|
||||
android::base::StringPrintf("Cannot open %s", partition_name_.c_str()));
|
||||
return false;
|
||||
}
|
||||
|
||||
partition_size_ = get_block_device_size(handle_.fd());
|
||||
if (partition_size_ == 0) {
|
||||
ret_ = device_->WriteOkay(android::base::StringPrintf("Partition %s has size 0",
|
||||
partition_name_.c_str()));
|
||||
return false;
|
||||
}
|
||||
|
||||
start_offset_ = 0;
|
||||
if (args_->size() >= 3) {
|
||||
if (!android::base::ParseUint(args_->at(2), &start_offset_)) {
|
||||
ret_ = device_->WriteFail("Invalid offset, must be integer");
|
||||
return false;
|
||||
}
|
||||
if (start_offset_ > std::numeric_limits<off64_t>::max()) {
|
||||
ret_ = device_->WriteFail(
|
||||
android::base::StringPrintf("Offset overflows: %" PRIx64, start_offset_));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (start_offset_ > partition_size_) {
|
||||
ret_ = device_->WriteFail(android::base::StringPrintf(
|
||||
"Invalid offset 0x%" PRIx64 ", partition %s has size 0x%" PRIx64, start_offset_,
|
||||
partition_name_.c_str(), partition_size_));
|
||||
return false;
|
||||
}
|
||||
uint64_t maximum_total_size_to_read = partition_size_ - start_offset_;
|
||||
total_size_to_read_ = maximum_total_size_to_read;
|
||||
if (args_->size() >= 4) {
|
||||
if (!android::base::ParseUint(args_->at(3), &total_size_to_read_)) {
|
||||
ret_ = device_->WriteStatus(FastbootResult::FAIL, "Invalid size, must be integer");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (total_size_to_read_ == 0) {
|
||||
ret_ = device_->WriteOkay("Read 0 bytes");
|
||||
return false;
|
||||
}
|
||||
if (total_size_to_read_ > maximum_total_size_to_read) {
|
||||
ret_ = device_->WriteFail(android::base::StringPrintf(
|
||||
"Invalid size to read 0x%" PRIx64 ", partition %s has size 0x%" PRIx64
|
||||
" and fetching from offset 0x%" PRIx64,
|
||||
total_size_to_read_, partition_name_.c_str(), partition_size_, start_offset_));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (total_size_to_read_ > kMaxFetchSizeDefault) {
|
||||
ret_ = device_->WriteFail(android::base::StringPrintf(
|
||||
"Cannot fetch 0x%" PRIx64
|
||||
" bytes because it exceeds maximum transport size 0x%x",
|
||||
partition_size_, kMaxDownloadSizeDefault));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Assume Open() returns true.
|
||||
// After execution, ret_ is set to the value that FetchHandler should return.
|
||||
void Fetch() {
|
||||
CHECK(start_offset_ <= std::numeric_limits<off64_t>::max());
|
||||
if (lseek64(handle_.fd(), start_offset_, SEEK_SET) != static_cast<off64_t>(start_offset_)) {
|
||||
ret_ = device_->WriteFail(android::base::StringPrintf(
|
||||
"On partition %s, unable to lseek(0x%" PRIx64 ": %s", partition_name_.c_str(),
|
||||
start_offset_, strerror(errno)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!device_->WriteStatus(FastbootResult::DATA,
|
||||
android::base::StringPrintf(
|
||||
"%08x", static_cast<uint32_t>(total_size_to_read_)))) {
|
||||
ret_ = false;
|
||||
return;
|
||||
}
|
||||
uint64_t end_offset = start_offset_ + total_size_to_read_;
|
||||
std::vector<char> buf(1_MiB);
|
||||
uint64_t current_offset = start_offset_;
|
||||
while (current_offset < end_offset) {
|
||||
// On any error, exit. We can't return a status message to the driver because
|
||||
// we are in the middle of writing data, so just let the driver guess what's wrong
|
||||
// by ending the data stream prematurely.
|
||||
uint64_t remaining = end_offset - current_offset;
|
||||
uint64_t chunk_size = std::min<uint64_t>(buf.size(), remaining);
|
||||
if (!android::base::ReadFully(handle_.fd(), buf.data(), chunk_size)) {
|
||||
PLOG(ERROR) << std::hex << "Unable to read 0x" << chunk_size << " bytes from "
|
||||
<< partition_name_ << " @ offset 0x" << current_offset;
|
||||
ret_ = false;
|
||||
return;
|
||||
}
|
||||
if (!device_->HandleData(false /* is read */, buf.data(), chunk_size)) {
|
||||
PLOG(ERROR) << std::hex << "Unable to send 0x" << chunk_size << " bytes of "
|
||||
<< partition_name_ << " @ offset 0x" << current_offset;
|
||||
ret_ = false;
|
||||
return;
|
||||
}
|
||||
current_offset += chunk_size;
|
||||
}
|
||||
|
||||
ret_ = device_->WriteOkay(android::base::StringPrintf(
|
||||
"Fetched %s (offset=0x%" PRIx64 ", size=0x%" PRIx64, partition_name_.c_str(),
|
||||
start_offset_, total_size_to_read_));
|
||||
}
|
||||
|
||||
static constexpr std::array<const char*, 3> kAllowedPartitions{
|
||||
"vendor_boot",
|
||||
"vendor_boot_a",
|
||||
"vendor_boot_b",
|
||||
};
|
||||
|
||||
FastbootDevice* device_;
|
||||
const std::vector<std::string>* args_ = nullptr;
|
||||
std::string partition_name_;
|
||||
PartitionHandle handle_;
|
||||
uint64_t partition_size_ = 0;
|
||||
uint64_t start_offset_ = 0;
|
||||
uint64_t total_size_to_read_ = 0;
|
||||
|
||||
// What FetchHandler should return.
|
||||
std::optional<bool> ret_ = std::nullopt;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
bool FetchHandler(FastbootDevice* device, const std::vector<std::string>& args) {
|
||||
return PartitionFetcher::Fetch(device, args);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include <vector>
|
||||
|
||||
constexpr unsigned int kMaxDownloadSizeDefault = 0x10000000;
|
||||
constexpr unsigned int kMaxFetchSizeDefault = 0x10000000;
|
||||
|
||||
class FastbootDevice;
|
||||
|
||||
|
|
@ -50,3 +51,4 @@ bool UpdateSuperHandler(FastbootDevice* device, const std::vector<std::string>&
|
|||
bool OemCmdHandler(FastbootDevice* device, const std::vector<std::string>& args);
|
||||
bool GsiHandler(FastbootDevice* device, const std::vector<std::string>& args);
|
||||
bool SnapshotUpdateHandler(FastbootDevice* device, const std::vector<std::string>& args);
|
||||
bool FetchHandler(FastbootDevice* device, const std::vector<std::string>& args);
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ FastbootDevice::FastbootDevice()
|
|||
{FB_CMD_OEM, OemCmdHandler},
|
||||
{FB_CMD_GSI, GsiHandler},
|
||||
{FB_CMD_SNAPSHOT_UPDATE, SnapshotUpdateHandler},
|
||||
{FB_CMD_FETCH, FetchHandler},
|
||||
}),
|
||||
boot_control_hal_(IBootControl::getService()),
|
||||
health_hal_(get_health_service()),
|
||||
|
|
@ -137,14 +138,18 @@ bool FastbootDevice::WriteStatus(FastbootResult result, const std::string& messa
|
|||
}
|
||||
|
||||
bool FastbootDevice::HandleData(bool read, std::vector<char>* data) {
|
||||
auto read_write_data_size = read ? this->get_transport()->Read(data->data(), data->size())
|
||||
: this->get_transport()->Write(data->data(), data->size());
|
||||
return HandleData(read, data->data(), data->size());
|
||||
}
|
||||
|
||||
bool FastbootDevice::HandleData(bool read, char* data, uint64_t size) {
|
||||
auto read_write_data_size = read ? this->get_transport()->Read(data, size)
|
||||
: this->get_transport()->Write(data, size);
|
||||
if (read_write_data_size == -1) {
|
||||
LOG(ERROR) << (read ? "read from" : "write to") << " transport failed";
|
||||
return false;
|
||||
}
|
||||
if (static_cast<size_t>(read_write_data_size) != data->size()) {
|
||||
LOG(ERROR) << (read ? "read" : "write") << " expected " << data->size() << " bytes, got "
|
||||
if (static_cast<size_t>(read_write_data_size) != size) {
|
||||
LOG(ERROR) << (read ? "read" : "write") << " expected " << size << " bytes, got "
|
||||
<< read_write_data_size;
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ class FastbootDevice {
|
|||
void ExecuteCommands();
|
||||
bool WriteStatus(FastbootResult result, const std::string& message);
|
||||
bool HandleData(bool read, std::vector<char>* data);
|
||||
bool HandleData(bool read, char* data, uint64_t size);
|
||||
std::string GetCurrentSlot();
|
||||
|
||||
// Shortcuts for writing status results.
|
||||
|
|
|
|||
|
|
@ -77,7 +77,8 @@ bool OpenLogicalPartition(FastbootDevice* device, const std::string& partition_n
|
|||
|
||||
} // namespace
|
||||
|
||||
bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle) {
|
||||
bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle,
|
||||
bool read) {
|
||||
// We prioritize logical partitions over physical ones, and do this
|
||||
// consistently for other partition operations (like getvar:partition-size).
|
||||
if (LogicalPartitionExists(device, name)) {
|
||||
|
|
@ -89,7 +90,9 @@ bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHan
|
|||
return false;
|
||||
}
|
||||
|
||||
unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), O_WRONLY | O_EXCL)));
|
||||
int flags = (read ? O_RDONLY : O_WRONLY);
|
||||
flags |= (O_EXCL | O_CLOEXEC | O_BINARY);
|
||||
unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), flags)));
|
||||
if (fd < 0) {
|
||||
PLOG(ERROR) << "Failed to open block device: " << handle->path();
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -75,7 +75,11 @@ std::string GetSuperSlotSuffix(FastbootDevice* device, const std::string& partit
|
|||
std::optional<std::string> FindPhysicalPartition(const std::string& name);
|
||||
bool LogicalPartitionExists(FastbootDevice* device, const std::string& name,
|
||||
bool* is_zero_length = nullptr);
|
||||
bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle);
|
||||
|
||||
// If read, partition is readonly. Else it is write only.
|
||||
bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle,
|
||||
bool read = false);
|
||||
|
||||
bool GetSlotNumber(const std::string& slot, android::hardware::boot::V1_0::Slot* number);
|
||||
std::vector<std::string> ListPartitions(FastbootDevice* device);
|
||||
bool GetDeviceLockStatus();
|
||||
|
|
|
|||
|
|
@ -33,6 +33,12 @@
|
|||
#include "flashing.h"
|
||||
#include "utility.h"
|
||||
|
||||
#ifdef FB_ENABLE_FETCH
|
||||
static constexpr bool kEnableFetch = true;
|
||||
#else
|
||||
static constexpr bool kEnableFetch = false;
|
||||
#endif
|
||||
|
||||
using ::android::hardware::boot::V1_0::BoolResult;
|
||||
using ::android::hardware::boot::V1_0::Slot;
|
||||
using ::android::hardware::boot::V1_1::MergeStatus;
|
||||
|
|
@ -509,3 +515,13 @@ bool GetTrebleEnabled(FastbootDevice* /* device */, const std::vector<std::strin
|
|||
*message = android::base::GetProperty("ro.treble.enabled", "");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetMaxFetchSize(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
|
||||
std::string* message) {
|
||||
if (!kEnableFetch) {
|
||||
*message = "fetch not supported on user builds";
|
||||
return false;
|
||||
}
|
||||
*message = android::base::StringPrintf("0x%X", kMaxFetchSizeDefault);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ bool GetSecurityPatchLevel(FastbootDevice* device, const std::vector<std::string
|
|||
std::string* message);
|
||||
bool GetTrebleEnabled(FastbootDevice* device, const std::vector<std::string>& args,
|
||||
std::string* message);
|
||||
bool GetMaxFetchSize(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
|
||||
std::string* message);
|
||||
|
||||
// Helpers for getvar all.
|
||||
std::vector<std::vector<std::string>> GetAllPartitionArgsWithSlot(FastbootDevice* device);
|
||||
|
|
|
|||
|
|
@ -76,12 +76,15 @@
|
|||
#include "udp.h"
|
||||
#include "usb.h"
|
||||
#include "util.h"
|
||||
#include "vendor_boot_img_utils.h"
|
||||
|
||||
using android::base::borrowed_fd;
|
||||
using android::base::ReadFully;
|
||||
using android::base::Split;
|
||||
using android::base::Trim;
|
||||
using android::base::unique_fd;
|
||||
using namespace std::string_literals;
|
||||
using namespace std::placeholders;
|
||||
|
||||
static const char* serial = nullptr;
|
||||
|
||||
|
|
@ -114,7 +117,7 @@ struct fastboot_buffer {
|
|||
enum fb_buffer_type type;
|
||||
void* data;
|
||||
int64_t sz;
|
||||
int fd;
|
||||
unique_fd fd;
|
||||
int64_t image_size;
|
||||
};
|
||||
|
||||
|
|
@ -414,6 +417,7 @@ static int show_help() {
|
|||
" snapshot-update merge On devices that support snapshot-based updates, finish\n"
|
||||
" an in-progress update if it is in the \"merging\"\n"
|
||||
" phase.\n"
|
||||
" fetch PARTITION Fetch a partition image from the device."
|
||||
"\n"
|
||||
"boot image:\n"
|
||||
" boot KERNEL [RAMDISK [SECOND]]\n"
|
||||
|
|
@ -466,7 +470,7 @@ static int show_help() {
|
|||
" --version Display version.\n"
|
||||
" --help, -h Show this message.\n"
|
||||
);
|
||||
// clang-format off
|
||||
// clang-format on
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -640,14 +644,14 @@ static void delete_fbemarker_tmpdir(const std::string& dir) {
|
|||
}
|
||||
}
|
||||
|
||||
static int unzip_to_file(ZipArchiveHandle zip, const char* entry_name) {
|
||||
static unique_fd unzip_to_file(ZipArchiveHandle zip, const char* entry_name) {
|
||||
unique_fd fd(make_temporary_fd(entry_name));
|
||||
|
||||
ZipEntry64 zip_entry;
|
||||
if (FindEntry(zip, entry_name, &zip_entry) != 0) {
|
||||
fprintf(stderr, "archive does not contain '%s'\n", entry_name);
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
return unique_fd();
|
||||
}
|
||||
|
||||
fprintf(stderr, "extracting %s (%" PRIu64 " MB) to disk...", entry_name,
|
||||
|
|
@ -664,7 +668,7 @@ static int unzip_to_file(ZipArchiveHandle zip, const char* entry_name) {
|
|||
|
||||
fprintf(stderr, " took %.3fs\n", now() - start);
|
||||
|
||||
return fd.release();
|
||||
return fd;
|
||||
}
|
||||
|
||||
static void CheckRequirement(const std::string& cur_product, const std::string& var,
|
||||
|
|
@ -851,24 +855,23 @@ static struct sparse_file** load_sparse_files(int fd, int64_t max_size) {
|
|||
return out_s;
|
||||
}
|
||||
|
||||
static int64_t get_target_sparse_limit() {
|
||||
std::string max_download_size;
|
||||
if (fb->GetVar("max-download-size", &max_download_size) != fastboot::SUCCESS ||
|
||||
max_download_size.empty()) {
|
||||
verbose("target didn't report max-download-size");
|
||||
static uint64_t get_uint_var(const char* var_name) {
|
||||
std::string value_str;
|
||||
if (fb->GetVar(var_name, &value_str) != fastboot::SUCCESS || value_str.empty()) {
|
||||
verbose("target didn't report %s", var_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Some bootloaders (angler, for example) send spurious whitespace too.
|
||||
max_download_size = android::base::Trim(max_download_size);
|
||||
value_str = android::base::Trim(value_str);
|
||||
|
||||
uint64_t limit;
|
||||
if (!android::base::ParseUint(max_download_size, &limit)) {
|
||||
fprintf(stderr, "couldn't parse max-download-size '%s'\n", max_download_size.c_str());
|
||||
uint64_t value;
|
||||
if (!android::base::ParseUint(value_str, &value)) {
|
||||
fprintf(stderr, "couldn't parse %s '%s'\n", var_name, value_str.c_str());
|
||||
return 0;
|
||||
}
|
||||
if (limit > 0) verbose("target reported max download size of %" PRId64 " bytes", limit);
|
||||
return limit;
|
||||
if (value > 0) verbose("target reported %s of %" PRId64 " bytes", var_name, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static int64_t get_sparse_limit(int64_t size) {
|
||||
|
|
@ -877,7 +880,7 @@ static int64_t get_sparse_limit(int64_t size) {
|
|||
// Unlimited, so see what the target device's limit is.
|
||||
// TODO: shouldn't we apply this limit even if you've used -S?
|
||||
if (target_sparse_limit == -1) {
|
||||
target_sparse_limit = get_target_sparse_limit();
|
||||
target_sparse_limit = static_cast<int64_t>(get_uint_var("max-download-size"));
|
||||
}
|
||||
if (target_sparse_limit > 0) {
|
||||
limit = target_sparse_limit;
|
||||
|
|
@ -893,7 +896,7 @@ static int64_t get_sparse_limit(int64_t size) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static bool load_buf_fd(int fd, struct fastboot_buffer* buf) {
|
||||
static bool load_buf_fd(unique_fd fd, struct fastboot_buffer* buf) {
|
||||
int64_t sz = get_file_size(fd);
|
||||
if (sz == -1) {
|
||||
return false;
|
||||
|
|
@ -918,7 +921,7 @@ static bool load_buf_fd(int fd, struct fastboot_buffer* buf) {
|
|||
} else {
|
||||
buf->type = FB_BUFFER_FD;
|
||||
buf->data = nullptr;
|
||||
buf->fd = fd;
|
||||
buf->fd = std::move(fd);
|
||||
buf->sz = sz;
|
||||
}
|
||||
|
||||
|
|
@ -941,7 +944,7 @@ static bool load_buf(const char* fname, struct fastboot_buffer* buf) {
|
|||
return false;
|
||||
}
|
||||
|
||||
return load_buf_fd(fd.release(), buf);
|
||||
return load_buf_fd(std::move(fd), buf);
|
||||
}
|
||||
|
||||
static void rewrite_vbmeta_buffer(struct fastboot_buffer* buf, bool vbmeta_in_boot) {
|
||||
|
|
@ -987,12 +990,11 @@ static void rewrite_vbmeta_buffer(struct fastboot_buffer* buf, bool vbmeta_in_bo
|
|||
data[flags_offset] |= 0x02;
|
||||
}
|
||||
|
||||
int fd = make_temporary_fd("vbmeta rewriting");
|
||||
unique_fd fd(make_temporary_fd("vbmeta rewriting"));
|
||||
if (!android::base::WriteStringToFd(data, fd)) {
|
||||
die("Failed writing to modified vbmeta");
|
||||
}
|
||||
close(buf->fd);
|
||||
buf->fd = fd;
|
||||
buf->fd = std::move(fd);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
}
|
||||
|
||||
|
|
@ -1012,21 +1014,28 @@ static std::string fb_fix_numeric_var(std::string var) {
|
|||
return var;
|
||||
}
|
||||
|
||||
static uint64_t get_partition_size(const std::string& partition) {
|
||||
std::string partition_size_str;
|
||||
if (fb->GetVar("partition-size:" + partition, &partition_size_str) != fastboot::SUCCESS) {
|
||||
die("cannot get partition size for %s", partition.c_str());
|
||||
}
|
||||
|
||||
partition_size_str = fb_fix_numeric_var(partition_size_str);
|
||||
uint64_t partition_size;
|
||||
if (!android::base::ParseUint(partition_size_str, &partition_size)) {
|
||||
die("Couldn't parse partition size '%s'.", partition_size_str.c_str());
|
||||
}
|
||||
return partition_size;
|
||||
}
|
||||
|
||||
static void copy_boot_avb_footer(const std::string& partition, struct fastboot_buffer* buf) {
|
||||
if (buf->sz < AVB_FOOTER_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string partition_size_str;
|
||||
if (fb->GetVar("partition-size:" + partition, &partition_size_str) != fastboot::SUCCESS) {
|
||||
die("cannot get boot partition size");
|
||||
}
|
||||
// If overflows and negative, it should be < buf->sz.
|
||||
int64_t partition_size = static_cast<int64_t>(get_partition_size(partition));
|
||||
|
||||
partition_size_str = fb_fix_numeric_var(partition_size_str);
|
||||
int64_t partition_size;
|
||||
if (!android::base::ParseInt(partition_size_str, &partition_size)) {
|
||||
die("Couldn't parse partition size '%s'.", partition_size_str.c_str());
|
||||
}
|
||||
if (partition_size == buf->sz) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1044,7 +1053,7 @@ static void copy_boot_avb_footer(const std::string& partition, struct fastboot_b
|
|||
return;
|
||||
}
|
||||
|
||||
int fd = make_temporary_fd("boot rewriting");
|
||||
unique_fd fd(make_temporary_fd("boot rewriting"));
|
||||
if (!android::base::WriteStringToFd(data, fd)) {
|
||||
die("Failed writing to modified boot");
|
||||
}
|
||||
|
|
@ -1052,8 +1061,7 @@ static void copy_boot_avb_footer(const std::string& partition, struct fastboot_b
|
|||
if (!android::base::WriteStringToFd(data.substr(footer_offset), fd)) {
|
||||
die("Failed copying AVB footer in boot");
|
||||
}
|
||||
close(buf->fd);
|
||||
buf->fd = fd;
|
||||
buf->fd = std::move(fd);
|
||||
buf->sz = partition_size;
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
}
|
||||
|
|
@ -1185,8 +1193,10 @@ static void do_for_partition(const std::string& part, const std::string& slot,
|
|||
const std::function<void(const std::string&)>& func, bool force_slot) {
|
||||
std::string has_slot;
|
||||
std::string current_slot;
|
||||
// |part| can be vendor_boot:default. Append slot to the first token.
|
||||
auto part_tokens = android::base::Split(part, ":");
|
||||
|
||||
if (fb->GetVar("has-slot:" + part, &has_slot) != fastboot::SUCCESS) {
|
||||
if (fb->GetVar("has-slot:" + part_tokens[0], &has_slot) != fastboot::SUCCESS) {
|
||||
/* If has-slot is not supported, the answer is no. */
|
||||
has_slot = "no";
|
||||
}
|
||||
|
|
@ -1196,14 +1206,15 @@ static void do_for_partition(const std::string& part, const std::string& slot,
|
|||
if (current_slot == "") {
|
||||
die("Failed to identify current slot");
|
||||
}
|
||||
func(part + "_" + current_slot);
|
||||
part_tokens[0] += "_" + current_slot;
|
||||
} else {
|
||||
func(part + '_' + slot);
|
||||
part_tokens[0] += "_" + slot;
|
||||
}
|
||||
func(android::base::Join(part_tokens, ":"));
|
||||
} else {
|
||||
if (force_slot && slot != "") {
|
||||
fprintf(stderr, "Warning: %s does not support slots, and slot %s was requested.\n",
|
||||
part.c_str(), slot.c_str());
|
||||
fprintf(stderr, "Warning: %s does not support slots, and slot %s was requested.\n",
|
||||
part_tokens[0].c_str(), slot.c_str());
|
||||
}
|
||||
func(part);
|
||||
}
|
||||
|
|
@ -1217,10 +1228,13 @@ static void do_for_partition(const std::string& part, const std::string& slot,
|
|||
static void do_for_partitions(const std::string& part, const std::string& slot,
|
||||
const std::function<void(const std::string&)>& func, bool force_slot) {
|
||||
std::string has_slot;
|
||||
// |part| can be vendor_boot:default. Query has-slot on the first token only.
|
||||
auto part_tokens = android::base::Split(part, ":");
|
||||
|
||||
if (slot == "all") {
|
||||
if (fb->GetVar("has-slot:" + part, &has_slot) != fastboot::SUCCESS) {
|
||||
die("Could not check if partition %s has slot %s", part.c_str(), slot.c_str());
|
||||
if (fb->GetVar("has-slot:" + part_tokens[0], &has_slot) != fastboot::SUCCESS) {
|
||||
die("Could not check if partition %s has slot %s", part_tokens[0].c_str(),
|
||||
slot.c_str());
|
||||
}
|
||||
if (has_slot == "yes") {
|
||||
for (int i=0; i < get_slot_count(); i++) {
|
||||
|
|
@ -1247,7 +1261,74 @@ static bool is_retrofit_device() {
|
|||
return android::base::StartsWith(value, "system_");
|
||||
}
|
||||
|
||||
// Fetch a partition from the device to a given fd. This is a wrapper over FetchToFd to fetch
|
||||
// the full image.
|
||||
static uint64_t fetch_partition(const std::string& partition, borrowed_fd fd) {
|
||||
uint64_t fetch_size = get_uint_var(FB_VAR_MAX_FETCH_SIZE);
|
||||
if (fetch_size == 0) {
|
||||
die("Unable to get %s. Device does not support fetch command.", FB_VAR_MAX_FETCH_SIZE);
|
||||
}
|
||||
uint64_t partition_size = get_partition_size(partition);
|
||||
if (partition_size <= 0) {
|
||||
die("Invalid partition size for partition %s: %" PRId64, partition.c_str(), partition_size);
|
||||
}
|
||||
|
||||
uint64_t offset = 0;
|
||||
while (offset < partition_size) {
|
||||
uint64_t chunk_size = std::min(fetch_size, partition_size - offset);
|
||||
if (fb->FetchToFd(partition, fd, offset, chunk_size) != fastboot::RetCode::SUCCESS) {
|
||||
die("Unable to fetch %s (offset=%" PRIx64 ", size=%" PRIx64 ")", partition.c_str(),
|
||||
offset, chunk_size);
|
||||
}
|
||||
offset += chunk_size;
|
||||
}
|
||||
return partition_size;
|
||||
}
|
||||
|
||||
static void do_fetch(const std::string& partition, const std::string& slot_override,
|
||||
const std::string& outfile) {
|
||||
unique_fd fd(TEMP_FAILURE_RETRY(
|
||||
open(outfile.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY, 0644)));
|
||||
auto fetch = std::bind(fetch_partition, _1, borrowed_fd(fd));
|
||||
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;
|
||||
|
||||
if (!load_buf(fname, &buf)) {
|
||||
|
|
@ -1256,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,
|
||||
|
|
@ -1310,7 +1392,7 @@ static void CancelSnapshotIfNeeded() {
|
|||
class ImageSource {
|
||||
public:
|
||||
virtual bool ReadFile(const std::string& name, std::vector<char>* out) const = 0;
|
||||
virtual int OpenFile(const std::string& name) const = 0;
|
||||
virtual unique_fd OpenFile(const std::string& name) const = 0;
|
||||
};
|
||||
|
||||
class FlashAllTool {
|
||||
|
|
@ -1428,8 +1510,8 @@ void FlashAllTool::CollectImages() {
|
|||
void FlashAllTool::FlashImages(const std::vector<std::pair<const Image*, std::string>>& images) {
|
||||
for (const auto& [image, slot] : images) {
|
||||
fastboot_buffer buf;
|
||||
int fd = source_.OpenFile(image->img_name);
|
||||
if (fd < 0 || !load_buf_fd(fd, &buf)) {
|
||||
unique_fd fd = source_.OpenFile(image->img_name);
|
||||
if (fd < 0 || !load_buf_fd(std::move(fd), &buf)) {
|
||||
if (image->optional_if_no_image) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1494,7 +1576,7 @@ class ZipImageSource final : public ImageSource {
|
|||
public:
|
||||
explicit ZipImageSource(ZipArchiveHandle zip) : zip_(zip) {}
|
||||
bool ReadFile(const std::string& name, std::vector<char>* out) const override;
|
||||
int OpenFile(const std::string& name) const override;
|
||||
unique_fd OpenFile(const std::string& name) const override;
|
||||
|
||||
private:
|
||||
ZipArchiveHandle zip_;
|
||||
|
|
@ -1504,7 +1586,7 @@ bool ZipImageSource::ReadFile(const std::string& name, std::vector<char>* out) c
|
|||
return UnzipToMemory(zip_, name, out);
|
||||
}
|
||||
|
||||
int ZipImageSource::OpenFile(const std::string& name) const {
|
||||
unique_fd ZipImageSource::OpenFile(const std::string& name) const {
|
||||
return unzip_to_file(zip_, name.c_str());
|
||||
}
|
||||
|
||||
|
|
@ -1524,7 +1606,7 @@ static void do_update(const char* filename, const std::string& slot_override, bo
|
|||
class LocalImageSource final : public ImageSource {
|
||||
public:
|
||||
bool ReadFile(const std::string& name, std::vector<char>* out) const override;
|
||||
int OpenFile(const std::string& name) const override;
|
||||
unique_fd OpenFile(const std::string& name) const override;
|
||||
};
|
||||
|
||||
bool LocalImageSource::ReadFile(const std::string& name, std::vector<char>* out) const {
|
||||
|
|
@ -1535,9 +1617,9 @@ bool LocalImageSource::ReadFile(const std::string& name, std::vector<char>* out)
|
|||
return ReadFileToVector(path, out);
|
||||
}
|
||||
|
||||
int LocalImageSource::OpenFile(const std::string& name) const {
|
||||
unique_fd LocalImageSource::OpenFile(const std::string& name) const {
|
||||
auto path = find_item_given_name(name);
|
||||
return open(path.c_str(), O_RDONLY | O_BINARY);
|
||||
return unique_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_BINARY)));
|
||||
}
|
||||
|
||||
static void do_flashall(const std::string& slot_override, bool skip_secondary, bool wipe) {
|
||||
|
|
@ -1656,7 +1738,7 @@ static void fb_perform_format(
|
|||
if (fd == -1) {
|
||||
die("Cannot open generated image: %s", strerror(errno));
|
||||
}
|
||||
if (!load_buf_fd(fd.release(), &buf)) {
|
||||
if (!load_buf_fd(std::move(fd), &buf)) {
|
||||
die("Cannot read image: %s", strerror(errno));
|
||||
}
|
||||
flash_buf(partition, &buf);
|
||||
|
|
@ -2169,6 +2251,10 @@ int FastBootTool::Main(int argc, char* argv[]) {
|
|||
syntax_error("expected: snapshot-update [cancel|merge]");
|
||||
}
|
||||
fb->SnapshotUpdateCommand(arg);
|
||||
} else if (command == FB_CMD_FETCH) {
|
||||
std::string partition = next_arg(&args);
|
||||
std::string outfile = next_arg(&args);
|
||||
do_fetch(partition, slot_override, outfile);
|
||||
} else {
|
||||
syntax_error("unknown command %s", command.c_str());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
|
@ -42,14 +43,17 @@
|
|||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/mapped_file.h>
|
||||
#include <android-base/parseint.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <storage_literals/storage_literals.h>
|
||||
|
||||
#include "constants.h"
|
||||
#include "transport.h"
|
||||
|
||||
using android::base::StringPrintf;
|
||||
using namespace android::storage_literals;
|
||||
|
||||
namespace fastboot {
|
||||
|
||||
|
|
@ -297,41 +301,85 @@ RetCode FastBootDriver::Upload(const std::string& outfile, std::string* response
|
|||
return result;
|
||||
}
|
||||
|
||||
RetCode FastBootDriver::UploadInner(const std::string& outfile, std::string* response,
|
||||
std::vector<std::string>* info) {
|
||||
// This function executes cmd, then expect a "DATA" response with a number N, followed
|
||||
// by N bytes, and another response.
|
||||
// This is the common way for the device to send data to the driver used by upload and fetch.
|
||||
RetCode FastBootDriver::RunAndReadBuffer(
|
||||
const std::string& cmd, std::string* response, std::vector<std::string>* info,
|
||||
const std::function<RetCode(const char* data, uint64_t size)>& write_fn) {
|
||||
RetCode ret;
|
||||
int dsize = 0;
|
||||
if ((ret = RawCommand(FB_CMD_UPLOAD, response, info, &dsize))) {
|
||||
error_ = "Upload request failed: " + error_;
|
||||
if ((ret = RawCommand(cmd, response, info, &dsize))) {
|
||||
error_ = android::base::StringPrintf("%s request failed: %s", cmd.c_str(), error_.c_str());
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!dsize) {
|
||||
error_ = "Upload request failed, device reports 0 bytes available";
|
||||
if (dsize <= 0) {
|
||||
error_ = android::base::StringPrintf("%s request failed, device reports %d bytes available",
|
||||
cmd.c_str(), dsize);
|
||||
return BAD_DEV_RESP;
|
||||
}
|
||||
|
||||
std::vector<char> data;
|
||||
data.resize(dsize);
|
||||
|
||||
if ((ret = ReadBuffer(data))) {
|
||||
return ret;
|
||||
const uint64_t total_size = dsize;
|
||||
const uint64_t buf_size = std::min<uint64_t>(total_size, 1_MiB);
|
||||
std::vector<char> data(buf_size);
|
||||
uint64_t current_offset = 0;
|
||||
while (current_offset < total_size) {
|
||||
uint64_t remaining = total_size - current_offset;
|
||||
uint64_t chunk_size = std::min(buf_size, remaining);
|
||||
if ((ret = ReadBuffer(data.data(), chunk_size)) != SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
if ((ret = write_fn(data.data(), chunk_size)) != SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
current_offset += chunk_size;
|
||||
}
|
||||
return HandleResponse(response, info);
|
||||
}
|
||||
|
||||
RetCode FastBootDriver::UploadInner(const std::string& outfile, std::string* response,
|
||||
std::vector<std::string>* info) {
|
||||
std::ofstream ofs;
|
||||
ofs.open(outfile, std::ofstream::out | std::ofstream::binary);
|
||||
if (ofs.fail()) {
|
||||
error_ = android::base::StringPrintf("Failed to open '%s'", outfile.c_str());
|
||||
return IO_ERROR;
|
||||
}
|
||||
ofs.write(data.data(), data.size());
|
||||
if (ofs.fail() || ofs.bad()) {
|
||||
error_ = android::base::StringPrintf("Writing to '%s' failed", outfile.c_str());
|
||||
return IO_ERROR;
|
||||
}
|
||||
auto write_fn = [&](const char* data, uint64_t size) {
|
||||
ofs.write(data, size);
|
||||
if (ofs.fail() || ofs.bad()) {
|
||||
error_ = android::base::StringPrintf("Writing to '%s' failed", outfile.c_str());
|
||||
return IO_ERROR;
|
||||
}
|
||||
return SUCCESS;
|
||||
};
|
||||
RetCode ret = RunAndReadBuffer(FB_CMD_UPLOAD, response, info, write_fn);
|
||||
ofs.close();
|
||||
return ret;
|
||||
}
|
||||
|
||||
return HandleResponse(response, info);
|
||||
RetCode FastBootDriver::FetchToFd(const std::string& partition, android::base::borrowed_fd fd,
|
||||
int64_t offset, int64_t size, std::string* response,
|
||||
std::vector<std::string>* info) {
|
||||
prolog_(android::base::StringPrintf("Fetching %s (offset=%" PRIx64 ", size=%" PRIx64 ")",
|
||||
partition.c_str(), offset, size));
|
||||
std::string cmd = FB_CMD_FETCH ":" + partition;
|
||||
if (offset >= 0) {
|
||||
cmd += android::base::StringPrintf(":0x%08" PRIx64, offset);
|
||||
if (size >= 0) {
|
||||
cmd += android::base::StringPrintf(":0x%08" PRIx64, size);
|
||||
}
|
||||
}
|
||||
RetCode ret = RunAndReadBuffer(cmd, response, info, [&](const char* data, uint64_t size) {
|
||||
if (!android::base::WriteFully(fd, data, size)) {
|
||||
error_ = android::base::StringPrintf("Cannot write: %s", strerror(errno));
|
||||
return IO_ERROR;
|
||||
}
|
||||
return SUCCESS;
|
||||
});
|
||||
epilog_(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
|
@ -524,11 +572,6 @@ RetCode FastBootDriver::SendBuffer(const void* buf, size_t size) {
|
|||
return SUCCESS;
|
||||
}
|
||||
|
||||
RetCode FastBootDriver::ReadBuffer(std::vector<char>& buf) {
|
||||
// Read the buffer
|
||||
return ReadBuffer(buf.data(), buf.size());
|
||||
}
|
||||
|
||||
RetCode FastBootDriver::ReadBuffer(void* buf, size_t size) {
|
||||
// Read the buffer
|
||||
ssize_t tmp = transport_->Read(buf, size);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <bootimg.h>
|
||||
#include <inttypes.h>
|
||||
#include <sparse/sparse.h>
|
||||
|
|
@ -106,6 +107,9 @@ class FastBootDriver {
|
|||
std::vector<std::string>* info = nullptr);
|
||||
RetCode SnapshotUpdateCommand(const std::string& command, std::string* response = nullptr,
|
||||
std::vector<std::string>* info = nullptr);
|
||||
RetCode FetchToFd(const std::string& partition, android::base::borrowed_fd fd,
|
||||
int64_t offset = -1, int64_t size = -1, std::string* response = nullptr,
|
||||
std::vector<std::string>* info = nullptr);
|
||||
|
||||
/* HIGHER LEVEL COMMANDS -- Composed of the commands above */
|
||||
RetCode FlashPartition(const std::string& partition, const std::vector<char>& data);
|
||||
|
|
@ -149,11 +153,13 @@ class FastBootDriver {
|
|||
RetCode SendBuffer(const std::vector<char>& buf);
|
||||
RetCode SendBuffer(const void* buf, size_t size);
|
||||
|
||||
RetCode ReadBuffer(std::vector<char>& buf);
|
||||
RetCode ReadBuffer(void* buf, size_t size);
|
||||
|
||||
RetCode UploadInner(const std::string& outfile, std::string* response = nullptr,
|
||||
std::vector<std::string>* info = nullptr);
|
||||
RetCode RunAndReadBuffer(const std::string& cmd, std::string* response,
|
||||
std::vector<std::string>* info,
|
||||
const std::function<RetCode(const char*, uint64_t)>& write_fn);
|
||||
|
||||
int SparseWriteCallback(std::vector<char>& tpbuf, const char* data, size_t len);
|
||||
|
||||
|
|
|
|||
|
|
@ -43,8 +43,10 @@
|
|||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/parseint.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <sparse/sparse.h>
|
||||
|
||||
|
|
@ -349,22 +351,35 @@ TEST_F(Conformance, GetVarBattVoltageOk) {
|
|||
EXPECT_TRUE(var == "yes" || var == "no") << "getvar:battery-soc-ok must be 'yes' or 'no'";
|
||||
}
|
||||
|
||||
void AssertHexUint32(const std::string& name, const std::string& var) {
|
||||
ASSERT_NE(var, "") << "getvar:" << name << " responded with empty string";
|
||||
// This must start with 0x
|
||||
ASSERT_FALSE(isspace(var.front()))
|
||||
<< "getvar:" << name << " responded with a string with leading whitespace";
|
||||
ASSERT_FALSE(var.compare(0, 2, "0x"))
|
||||
<< "getvar:" << name << " responded with a string that does not start with 0x...";
|
||||
int64_t size = strtoll(var.c_str(), nullptr, 16);
|
||||
ASSERT_GT(size, 0) << "'" + var + "' is not a valid response from getvar:" << name;
|
||||
// At most 32-bits
|
||||
ASSERT_LE(size, std::numeric_limits<uint32_t>::max())
|
||||
<< "getvar:" << name << " must fit in a uint32_t";
|
||||
ASSERT_LE(var.size(), FB_RESPONSE_SZ - 4)
|
||||
<< "getvar:" << name << " responded with too large of string: " + var;
|
||||
}
|
||||
|
||||
TEST_F(Conformance, GetVarDownloadSize) {
|
||||
std::string var;
|
||||
EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed";
|
||||
EXPECT_NE(var, "") << "getvar:max-download-size responded with empty string";
|
||||
// This must start with 0x
|
||||
EXPECT_FALSE(isspace(var.front()))
|
||||
<< "getvar:max-download-size responded with a string with leading whitespace";
|
||||
EXPECT_FALSE(var.compare(0, 2, "0x"))
|
||||
<< "getvar:max-download-size responded with a string that does not start with 0x...";
|
||||
int64_t size = strtoll(var.c_str(), nullptr, 16);
|
||||
EXPECT_GT(size, 0) << "'" + var + "' is not a valid response from getvar:max-download-size";
|
||||
// At most 32-bits
|
||||
EXPECT_LE(size, std::numeric_limits<uint32_t>::max())
|
||||
<< "getvar:max-download-size must fit in a uint32_t";
|
||||
EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4)
|
||||
<< "getvar:max-download-size responded with too large of string: " + var;
|
||||
AssertHexUint32("max-download-size", var);
|
||||
}
|
||||
|
||||
// If fetch is supported, getvar:max-fetch-size must return a hex string.
|
||||
TEST_F(Conformance, GetVarFetchSize) {
|
||||
std::string var;
|
||||
if (SUCCESS != fb->GetVar("max-fetch-size", &var)) {
|
||||
GTEST_SKIP() << "getvar:max-fetch-size failed";
|
||||
}
|
||||
AssertHexUint32("max-fetch-size", var);
|
||||
}
|
||||
|
||||
TEST_F(Conformance, GetVarAll) {
|
||||
|
|
@ -656,6 +671,33 @@ TEST_F(UnlockPermissions, DownloadFlash) {
|
|||
EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed in unlocked mode";
|
||||
}
|
||||
|
||||
// If the implementation supports getvar:max-fetch-size, it must also support fetch:vendor_boot*.
|
||||
TEST_F(UnlockPermissions, FetchVendorBoot) {
|
||||
std::string var;
|
||||
uint64_t fetch_size;
|
||||
if (fb->GetVar("max-fetch-size", &var) != SUCCESS) {
|
||||
GTEST_SKIP() << "This test is skipped because fetch is not supported.";
|
||||
}
|
||||
ASSERT_FALSE(var.empty());
|
||||
ASSERT_TRUE(android::base::ParseUint(var, &fetch_size)) << var << " is not an integer";
|
||||
std::vector<std::tuple<std::string, uint64_t>> parts;
|
||||
EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed";
|
||||
for (const auto& [partition, partition_size] : parts) {
|
||||
if (!android::base::StartsWith(partition, "vendor_boot")) continue;
|
||||
TemporaryFile fetched;
|
||||
|
||||
uint64_t offset = 0;
|
||||
while (offset < partition_size) {
|
||||
uint64_t chunk_size = std::min(fetch_size, partition_size - offset);
|
||||
auto ret = fb->FetchToFd(partition, fetched.fd, offset, chunk_size);
|
||||
ASSERT_EQ(fastboot::RetCode::SUCCESS, ret)
|
||||
<< "Unable to fetch " << partition << " (offset=" << offset
|
||||
<< ", size=" << chunk_size << ")";
|
||||
offset += chunk_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(LockPermissions, DownloadFlash) {
|
||||
std::vector<char> buf{'a', 'o', 's', 'p'};
|
||||
EXPECT_EQ(fb->Download(buf), SUCCESS) << "Download failed in locked mode";
|
||||
|
|
@ -717,6 +759,16 @@ TEST_F(LockPermissions, Boot) {
|
|||
EXPECT_GT(resp.size(), 0) << "No error message was returned by device after FAIL";
|
||||
}
|
||||
|
||||
TEST_F(LockPermissions, FetchVendorBoot) {
|
||||
std::vector<std::tuple<std::string, uint64_t>> parts;
|
||||
EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed";
|
||||
for (const auto& [partition, _] : parts) {
|
||||
TemporaryFile fetched;
|
||||
ASSERT_EQ(fb->FetchToFd(partition, fetched.fd, 0, 0), DEVICE_FAIL)
|
||||
<< "fetch:" << partition << ":0:0 did not fail in locked mode";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(Fuzz, DownloadSize) {
|
||||
std::string var;
|
||||
EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed";
|
||||
|
|
|
|||
136
fastboot/testdata/Android.bp
vendored
Normal file
136
fastboot/testdata/Android.bp
vendored
Normal 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
32
fastboot/testdata/fastboot_gen_rand.py
vendored
Normal 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)))
|
||||
422
fastboot/vendor_boot_img_utils.cpp
Normal file
422
fastboot/vendor_boot_img_utils.cpp
Normal 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 {};
|
||||
}
|
||||
34
fastboot/vendor_boot_img_utils.h
Normal file
34
fastboot/vendor_boot_img_utils.h
Normal 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);
|
||||
429
fastboot/vendor_boot_img_utils_test.cpp
Normal file
429
fastboot/vendor_boot_img_utils_test.cpp
Normal 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();
|
||||
}
|
||||
|
|
@ -8,4 +8,9 @@ cc_library_headers {
|
|||
host_supported: true,
|
||||
recovery_available: true,
|
||||
export_include_dirs: ["."],
|
||||
target: {
|
||||
windows: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue