applypatch: Refactor applypatch().

applypatch() was initially designed for file-based OTA, operating on
individual files. It was later extended to allow patching eMMC targets
as a whole, in favor of block-based updates.

As we have deprecated file-based OTA since Oreo, part of the code in
applypatch() has become obsolete. This CL refactors the related
functions, by removing the obsolete logic and focusing on eMMC targets.

Since this CL substantially changes applypatch APIs, it adds new
functions to avoid unintentionally mixing them together. In particular,
it removes `applypatch()`, `applypatch_check()`, `applypatch_flash()`,
and adds `PatchPartition()`, `PatchPartitionCheck()`, `FlashPartition()`
and `CheckPartition()`. It also replaces the old Edify functions
`apply_patch()` and `apply_patch_check()` with `patch_partition()` and
`patch_partition_check()` respectively.

This CL requires matching changes to OTA generation script (in the same
topic).

Bug: 110106408
Test: Run recovery_unit_test and recovery_component_test on marlin.
Test: `m dist` with non-A/B target. Verify
      /system/bin/install-recovery.sh on device.
Test: `m dist` with non-A/B target using BOARD_USES_FULL_RECOVERY_IMAGE.
      Verify /system/bin/install-recovery.sh on device.
Test: Install an incremental OTA with the new updater and scripts.
Change-Id: Ia34a90114bb227f4216eb478c22dc98c8194cb7f
This commit is contained in:
Tao Bao 2018-06-20 00:30:48 -07:00
parent 6a333cde10
commit 5609bc8b34
7 changed files with 393 additions and 512 deletions

View file

@ -44,15 +44,15 @@
#include "otautil/paths.h" #include "otautil/paths.h"
#include "otautil/print_sha1.h" #include "otautil/print_sha1.h"
static int LoadPartitionContents(const std::string& filename, FileContents* file); using namespace std::string_literals;
static int GenerateTarget(const FileContents& source_file, const std::unique_ptr<Value>& patch,
const std::string& target_filename, static bool GenerateTarget(const Partition& target, const FileContents& source_file,
const uint8_t target_sha1[SHA_DIGEST_LENGTH], const Value* bonus_data); const Value& patch, const Value* bonus_data);
int LoadFileContents(const std::string& filename, FileContents* file) { int LoadFileContents(const std::string& filename, FileContents* file) {
// A special 'filename' beginning with "EMMC:" means to load the contents of a partition. // No longer allow loading contents from eMMC partitions.
if (android::base::StartsWith(filename, "EMMC:")) { if (android::base::StartsWith(filename, "EMMC:")) {
return LoadPartitionContents(filename, file); return -1;
} }
std::string data; std::string data;
@ -66,101 +66,44 @@ int LoadFileContents(const std::string& filename, FileContents* file) {
return 0; return 0;
} }
// Loads the contents of an EMMC partition into the provided FileContents. filename should be a // Reads the contents of a Partition to the given FileContents buffer.
// string of the form "EMMC:<partition_device>:...". The smallest size_n bytes for which that prefix static bool ReadPartitionToBuffer(const Partition& partition, FileContents* out,
// of the partition contents has the corresponding sha1 hash will be loaded. It is acceptable for a bool check_backup) {
// size value to be repeated with different sha1s. Returns 0 on success. uint8_t expected_sha1[SHA_DIGEST_LENGTH];
// if (ParseSha1(partition.hash, expected_sha1) != 0) {
// This complexity is needed because if an OTA installation is interrupted, the partition might LOG(ERROR) << "Failed to parse target hash \"" << partition.hash << "\"";
// contain either the source or the target data, which might be of different lengths. We need to return false;
// know the length in order to read from a partition (there is no "end-of-file" marker), so the
// caller must specify the possible lengths and the hash of the data, and we'll do the load
// expecting to find one of those hashes.
static int LoadPartitionContents(const std::string& filename, FileContents* file) {
std::vector<std::string> pieces = android::base::Split(filename, ":");
if (pieces.size() < 4 || pieces.size() % 2 != 0 || pieces[0] != "EMMC") {
LOG(ERROR) << "LoadPartitionContents called with bad filename \"" << filename << "\"";
return -1;
} }
size_t pair_count = (pieces.size() - 2) / 2; // # of (size, sha1) pairs in filename android::base::unique_fd dev(open(partition.name.c_str(), O_RDONLY));
std::vector<std::pair<size_t, std::string>> pairs; if (!dev) {
for (size_t i = 0; i < pair_count; ++i) {
size_t size;
if (!android::base::ParseUint(pieces[i * 2 + 2], &size) || size == 0) {
LOG(ERROR) << "LoadPartitionContents called with bad size \"" << pieces[i * 2 + 2] << "\"";
return -1;
}
pairs.push_back({ size, pieces[i * 2 + 3] });
}
// Sort the pairs array so that they are in order of increasing size.
std::sort(pairs.begin(), pairs.end());
const char* partition = pieces[1].c_str();
android::base::unique_fd dev(open(partition, O_RDONLY));
if (dev == -1) {
PLOG(ERROR) << "Failed to open eMMC partition \"" << partition << "\""; PLOG(ERROR) << "Failed to open eMMC partition \"" << partition << "\"";
return -1; } else {
} std::vector<unsigned char> buffer(partition.size);
if (!android::base::ReadFully(dev, buffer.data(), buffer.size())) {
SHA_CTX sha_ctx; PLOG(ERROR) << "Failed to read " << buffer.size() << " bytes of data for partition "
SHA1_Init(&sha_ctx); << partition;
} else {
// Allocate enough memory to hold the largest size. SHA1(buffer.data(), buffer.size(), out->sha1);
std::vector<unsigned char> buffer(pairs[pair_count - 1].first); if (memcmp(out->sha1, expected_sha1, SHA_DIGEST_LENGTH) == 0) {
size_t offset = 0; // # bytes read so far out->data = std::move(buffer);
bool found = false; return true;
for (const auto& pair : pairs) {
size_t current_size = pair.first;
const std::string& current_sha1 = pair.second;
// Read enough additional bytes to get us up to the next size. (Again,
// we're trying the possibilities in order of increasing size).
if (current_size - offset > 0) {
if (!android::base::ReadFully(dev, buffer.data() + offset, current_size - offset)) {
PLOG(ERROR) << "Failed to read " << current_size - offset << " bytes of data at offset "
<< offset << " for partition " << partition;
return -1;
} }
SHA1_Update(&sha_ctx, buffer.data() + offset, current_size - offset);
offset = current_size;
}
// Duplicate the SHA context and finalize the duplicate so we can
// check it against this pair's expected hash.
SHA_CTX temp_ctx;
memcpy(&temp_ctx, &sha_ctx, sizeof(SHA_CTX));
uint8_t sha_so_far[SHA_DIGEST_LENGTH];
SHA1_Final(sha_so_far, &temp_ctx);
uint8_t parsed_sha[SHA_DIGEST_LENGTH];
if (ParseSha1(current_sha1, parsed_sha) != 0) {
LOG(ERROR) << "Failed to parse SHA-1 \"" << current_sha1 << "\" in " << filename;
return -1;
}
if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_LENGTH) == 0) {
// We have a match. Stop reading the partition; we'll return the data we've read so far.
LOG(INFO) << "Partition read matched size " << current_size << " SHA-1 " << current_sha1;
found = true;
break;
} }
} }
if (!found) { if (!check_backup) {
// Ran off the end of the list of (size, sha1) pairs without finding a match. LOG(ERROR) << "Partition contents don't have the expected checksum";
LOG(ERROR) << "Contents of partition \"" << partition << "\" didn't match " << filename; return false;
return -1;
} }
SHA1_Final(file->sha1, &sha_ctx); if (LoadFileContents(Paths::Get().cache_temp_source(), out) == 0 &&
memcmp(out->sha1, expected_sha1, SHA_DIGEST_LENGTH) == 0) {
return true;
}
buffer.resize(offset); LOG(ERROR) << "Both of partition contents and backup don't have the expected checksum";
file->data = std::move(buffer); return false;
return 0;
} }
int SaveFileContents(const std::string& filename, const FileContents* file) { int SaveFileContents(const std::string& filename, const FileContents* file) {
@ -189,49 +132,42 @@ int SaveFileContents(const std::string& filename, const FileContents* file) {
return 0; return 0;
} }
// Writes a memory buffer to 'target' partition, a string of the form // Writes a memory buffer to 'target' Partition.
// "EMMC:<partition_device>[:...]". The target name might contain multiple colons, but static bool WriteBufferToPartition(const FileContents& file_contents, const Partition& partition) {
// WriteToPartition() only uses the first two and ignores the rest. Returns 0 on success. const unsigned char* data = file_contents.data.data();
static int WriteToPartition(const unsigned char* data, size_t len, const std::string& target) { size_t len = file_contents.data.size();
std::vector<std::string> pieces = android::base::Split(target, ":");
if (pieces.size() < 2 || pieces[0] != "EMMC") {
LOG(ERROR) << "WriteToPartition called with bad target \"" << target << "\"";
return -1;
}
size_t start = 0; size_t start = 0;
bool success = false; bool success = false;
for (size_t attempt = 0; attempt < 2; ++attempt) { for (size_t attempt = 0; attempt < 2; ++attempt) {
std::string partition = pieces[1]; android::base::unique_fd fd(open(partition.name.c_str(), O_RDWR));
android::base::unique_fd fd(open(partition.c_str(), O_RDWR));
if (fd == -1) { if (fd == -1) {
PLOG(ERROR) << "Failed to open \"" << partition << "\""; PLOG(ERROR) << "Failed to open \"" << partition << "\"";
return -1; return false;
} }
if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) { if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) {
PLOG(ERROR) << "Failed to seek to " << start << " on \"" << partition << "\""; PLOG(ERROR) << "Failed to seek to " << start << " on \"" << partition << "\"";
return -1; return false;
} }
if (!android::base::WriteFully(fd, data + start, len - start)) { if (!android::base::WriteFully(fd, data + start, len - start)) {
PLOG(ERROR) << "Failed to write " << len - start << " bytes to \"" << partition << "\""; PLOG(ERROR) << "Failed to write " << len - start << " bytes to \"" << partition << "\"";
return -1; return false;
} }
if (fsync(fd) != 0) { if (fsync(fd) != 0) {
PLOG(ERROR) << "Failed to sync \"" << partition << "\""; PLOG(ERROR) << "Failed to sync \"" << partition << "\"";
return -1; return false;
} }
if (close(fd.release()) != 0) { if (close(fd.release()) != 0) {
PLOG(ERROR) << "Failed to close \"" << partition << "\""; PLOG(ERROR) << "Failed to close \"" << partition << "\"";
return -1; return false;
} }
fd.reset(open(partition.c_str(), O_RDONLY)); fd.reset(open(partition.name.c_str(), O_RDONLY));
if (fd == -1) { if (fd == -1) {
PLOG(ERROR) << "Failed to reopen \"" << partition << "\" for verification"; PLOG(ERROR) << "Failed to reopen \"" << partition << "\" for verification";
return -1; return false;
} }
// Drop caches so our subsequent verification read won't just be reading the cache. // Drop caches so our subsequent verification read won't just be reading the cache.
@ -247,7 +183,7 @@ static int WriteToPartition(const unsigned char* data, size_t len, const std::st
// Verify. // Verify.
if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) { if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) {
PLOG(ERROR) << "Failed to seek to 0 on " << partition; PLOG(ERROR) << "Failed to seek to 0 on " << partition;
return -1; return false;
} }
unsigned char buffer[4096]; unsigned char buffer[4096];
@ -260,7 +196,7 @@ static int WriteToPartition(const unsigned char* data, size_t len, const std::st
if (!android::base::ReadFully(fd, buffer, to_read)) { if (!android::base::ReadFully(fd, buffer, to_read)) {
PLOG(ERROR) << "Failed to verify-read " << partition << " at " << p; PLOG(ERROR) << "Failed to verify-read " << partition << " at " << p;
return -1; return false;
} }
if (memcmp(buffer, data + p, to_read) != 0) { if (memcmp(buffer, data + p, to_read) != 0) {
@ -278,18 +214,18 @@ static int WriteToPartition(const unsigned char* data, size_t len, const std::st
if (close(fd.release()) != 0) { if (close(fd.release()) != 0) {
PLOG(ERROR) << "Failed to close " << partition; PLOG(ERROR) << "Failed to close " << partition;
return -1; return false;
} }
} }
if (!success) { if (!success) {
LOG(ERROR) << "Failed to verify after all attempts"; LOG(ERROR) << "Failed to verify after all attempts";
return -1; return false;
} }
sync(); sync();
return 0; return true;
} }
int ParseSha1(const std::string& str, uint8_t* digest) { int ParseSha1(const std::string& str, uint8_t* digest) {
@ -317,44 +253,11 @@ int ParseSha1(const std::string& str, uint8_t* digest) {
return 0; return 0;
} }
// Searches a vector of SHA-1 strings for one matching the given SHA-1. Returns the index of the bool PatchPartitionCheck(const Partition& target, const Partition& source) {
// match on success, or -1 if no match is found. FileContents target_file;
static int FindMatchingPatch(const uint8_t* sha1, const std::vector<std::string>& patch_sha1s) { FileContents source_file;
for (size_t i = 0; i < patch_sha1s.size(); ++i) { return (ReadPartitionToBuffer(target, &target_file, false) ||
uint8_t patch_sha1[SHA_DIGEST_LENGTH]; ReadPartitionToBuffer(source, &source_file, true));
if (ParseSha1(patch_sha1s[i], patch_sha1) == 0 &&
memcmp(patch_sha1, sha1, SHA_DIGEST_LENGTH) == 0) {
return i;
}
}
return -1;
}
int applypatch_check(const std::string& filename, const std::vector<std::string>& sha1s) {
if (!android::base::StartsWith(filename, "EMMC:")) {
return 1;
}
// The check will pass if LoadPartitionContents is successful, because the filename already
// encodes the desired SHA-1s.
FileContents file;
if (LoadPartitionContents(filename, &file) != 0) {
LOG(INFO) << "\"" << filename << "\" doesn't have any of expected SHA-1 sums; checking cache";
// If the partition is corrupted, it might be because we were killed in the middle of patching
// it. A copy should have been made in cache_temp_source. If that file exists and matches the
// SHA-1 we're looking for, the check still passes.
if (LoadFileContents(Paths::Get().cache_temp_source(), &file) != 0) {
LOG(ERROR) << "Failed to load cache file";
return 1;
}
if (FindMatchingPatch(file.sha1, sha1s) < 0) {
LOG(ERROR) << "The cache bits don't match any SHA-1 for \"" << filename << "\"";
return 1;
}
}
return 0;
} }
int ShowLicenses() { int ShowLicenses() {
@ -362,124 +265,81 @@ int ShowLicenses() {
return 0; return 0;
} }
int applypatch(const char* source_filename, const char* target_filename, bool PatchPartition(const Partition& target, const Partition& source, const Value& patch,
const char* target_sha1_str, size_t /* target_size */, const Value* bonus) {
const std::vector<std::string>& patch_sha1s, LOG(INFO) << "Patching " << target.name;
const std::vector<std::unique_ptr<Value>>& patch_data, const Value* bonus_data) {
LOG(INFO) << "Patching " << source_filename;
if (target_filename[0] == '-' && target_filename[1] == '\0') { // We try to load and check against the target hash first.
target_filename = source_filename; FileContents target_file;
if (ReadPartitionToBuffer(target, &target_file, false)) {
// The early-exit case: the patch was already applied, this file has the desired hash, nothing
// for us to do.
LOG(INFO) << " already " << target.hash.substr(0, 8);
return true;
} }
if (strncmp(target_filename, "EMMC:", 5) != 0) {
LOG(ERROR) << "Supporting patching EMMC targets only";
return 1;
}
uint8_t target_sha1[SHA_DIGEST_LENGTH];
if (ParseSha1(target_sha1_str, target_sha1) != 0) {
LOG(ERROR) << "Failed to parse target SHA-1 \"" << target_sha1_str << "\"";
return 1;
}
// We try to load the target file into the source_file object.
FileContents source_file; FileContents source_file;
if (LoadFileContents(target_filename, &source_file) == 0) { if (ReadPartitionToBuffer(source, &source_file, true)) {
if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) { return GenerateTarget(target, source_file, patch, bonus);
// The early-exit case: the patch was already applied, this file has the desired hash, nothing
// for us to do.
LOG(INFO) << " already " << short_sha1(target_sha1);
return 0;
}
} }
if (source_file.data.empty() || LOG(ERROR) << "Failed to find any match";
(target_filename != source_filename && strcmp(target_filename, source_filename) != 0)) { return false;
// Need to load the source file: either we failed to load the target file, or we did but it's
// different from the expected.
source_file.data.clear();
LoadFileContents(source_filename, &source_file);
}
if (!source_file.data.empty()) {
int to_use = FindMatchingPatch(source_file.sha1, patch_sha1s);
if (to_use != -1) {
return GenerateTarget(source_file, patch_data[to_use], target_filename, target_sha1,
bonus_data);
}
}
LOG(INFO) << "Source file is bad; trying copy";
FileContents copy_file;
if (LoadFileContents(Paths::Get().cache_temp_source(), &copy_file) < 0) {
LOG(ERROR) << "Failed to read copy file";
return 1;
}
int to_use = FindMatchingPatch(copy_file.sha1, patch_sha1s);
if (to_use == -1) {
LOG(ERROR) << "The copy on /cache doesn't match source SHA-1s either";
return 1;
}
return GenerateTarget(copy_file, patch_data[to_use], target_filename, target_sha1, bonus_data);
} }
int applypatch_flash(const char* source_filename, const char* target_filename, bool FlashPartition(const Partition& partition, const std::string& source_filename) {
const char* target_sha1_str, size_t target_size) { LOG(INFO) << "Flashing " << partition;
LOG(INFO) << "Flashing " << target_filename;
uint8_t target_sha1[SHA_DIGEST_LENGTH]; // We try to load and check against the target hash first.
if (ParseSha1(target_sha1_str, target_sha1) != 0) { FileContents target_file;
LOG(ERROR) << "Failed to parse target SHA-1 \"" << target_sha1_str << "\""; if (ReadPartitionToBuffer(partition, &target_file, false)) {
return 1; // The early-exit case: the patch was already applied, this file has the desired hash, nothing
// for us to do.
LOG(INFO) << " already " << partition.hash.substr(0, 8);
return true;
} }
std::vector<std::string> pieces = android::base::Split(target_filename, ":");
if (pieces.size() != 4 || pieces[0] != "EMMC") {
LOG(ERROR) << "Invalid target name \"" << target_filename << "\"";
return 1;
}
// Load the target into the source_file object to see if already applied.
FileContents source_file; FileContents source_file;
if (LoadPartitionContents(target_filename, &source_file) == 0 && if (LoadFileContents(source_filename, &source_file) != 0) {
memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) { LOG(ERROR) << "Failed to load source file";
// The early-exit case: the image was already applied, this partition has the desired hash, return false;
// nothing for us to do.
LOG(INFO) << " already " << short_sha1(target_sha1);
return 0;
} }
if (LoadFileContents(source_filename, &source_file) == 0) { uint8_t expected_sha1[SHA_DIGEST_LENGTH];
if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) { if (ParseSha1(partition.hash, expected_sha1) != 0) {
// The source doesn't have desired checksum. LOG(ERROR) << "Failed to parse source hash \"" << partition.hash << "\"";
LOG(ERROR) << "source \"" << source_filename << "\" doesn't have expected SHA-1 sum"; return false;
LOG(ERROR) << "expected: " << short_sha1(target_sha1)
<< ", found: " << short_sha1(source_file.sha1);
return 1;
}
} }
if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) { if (memcmp(source_file.sha1, expected_sha1, SHA_DIGEST_LENGTH) != 0) {
LOG(ERROR) << "Failed to write copied data to " << target_filename; // The source doesn't have desired checksum.
return 1; LOG(ERROR) << "source \"" << source_filename << "\" doesn't have expected SHA-1 sum";
LOG(ERROR) << "expected: " << partition.hash.substr(0, 8)
<< ", found: " << short_sha1(source_file.sha1);
return false;
} }
return 0; if (!WriteBufferToPartition(source_file, partition)) {
LOG(ERROR) << "Failed to write to " << partition;
return false;
}
return true;
} }
static int GenerateTarget(const FileContents& source_file, const std::unique_ptr<Value>& patch, static bool GenerateTarget(const Partition& target, const FileContents& source_file,
const std::string& target_filename, const Value& patch, const Value* bonus_data) {
const uint8_t target_sha1[SHA_DIGEST_LENGTH], const Value* bonus_data) { uint8_t expected_sha1[SHA_DIGEST_LENGTH];
if (patch->type != Value::Type::BLOB) { if (ParseSha1(target.hash, expected_sha1) != 0) {
LOG(ERROR) << "Failed to parse target hash \"" << target.hash << "\"";
return false;
}
if (patch.type != Value::Type::BLOB) {
LOG(ERROR) << "patch is not a blob"; LOG(ERROR) << "patch is not a blob";
return 1; return false;
} }
const char* header = &patch->data[0]; const char* header = patch.data.data();
size_t header_bytes_read = patch->data.size(); size_t header_bytes_read = patch.data.size();
bool use_bsdiff = false; bool use_bsdiff = false;
if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) { if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) {
use_bsdiff = true; use_bsdiff = true;
@ -487,57 +347,53 @@ static int GenerateTarget(const FileContents& source_file, const std::unique_ptr
use_bsdiff = false; use_bsdiff = false;
} else { } else {
LOG(ERROR) << "Unknown patch file format"; LOG(ERROR) << "Unknown patch file format";
return 1; return false;
} }
CHECK(android::base::StartsWith(target_filename, "EMMC:"));
// We write the original source to cache, in case the partition write is interrupted. // We write the original source to cache, in case the partition write is interrupted.
if (!CheckAndFreeSpaceOnCache(source_file.data.size())) { if (!CheckAndFreeSpaceOnCache(source_file.data.size())) {
LOG(ERROR) << "Not enough free space on /cache"; LOG(ERROR) << "Not enough free space on /cache";
return 1; return false;
} }
if (SaveFileContents(Paths::Get().cache_temp_source(), &source_file) < 0) { if (SaveFileContents(Paths::Get().cache_temp_source(), &source_file) < 0) {
LOG(ERROR) << "Failed to back up source file"; LOG(ERROR) << "Failed to back up source file";
return 1; return false;
} }
// We store the decoded output in memory. // We store the decoded output in memory.
std::string memory_sink_str; // Don't need to reserve space. FileContents patched;
SHA_CTX ctx; SHA_CTX ctx;
SHA1_Init(&ctx); SHA1_Init(&ctx);
SinkFn sink = [&memory_sink_str, &ctx](const unsigned char* data, size_t len) { SinkFn sink = [&patched, &ctx](const unsigned char* data, size_t len) {
SHA1_Update(&ctx, data, len); SHA1_Update(&ctx, data, len);
memory_sink_str.append(reinterpret_cast<const char*>(data), len); patched.data.insert(patched.data.end(), data, data + len);
return len; return len;
}; };
int result; int result;
if (use_bsdiff) { if (use_bsdiff) {
result = ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), *patch, 0, sink); result = ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), patch, 0, sink);
} else { } else {
result = result =
ApplyImagePatch(source_file.data.data(), source_file.data.size(), *patch, sink, bonus_data); ApplyImagePatch(source_file.data.data(), source_file.data.size(), patch, sink, bonus_data);
} }
if (result != 0) { if (result != 0) {
LOG(ERROR) << "Failed to apply the patch: " << result; LOG(ERROR) << "Failed to apply the patch: " << result;
return 1; return false;
} }
uint8_t current_target_sha1[SHA_DIGEST_LENGTH]; SHA1_Final(patched.sha1, &ctx);
SHA1_Final(current_target_sha1, &ctx); if (memcmp(patched.sha1, expected_sha1, SHA_DIGEST_LENGTH) != 0) {
if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) { LOG(ERROR) << "Patching did not produce the expected SHA-1 of " << short_sha1(expected_sha1);
LOG(ERROR) << "Patching did not produce the expected SHA-1 of " << short_sha1(target_sha1);
LOG(ERROR) << "target size " << memory_sink_str.size() << " SHA-1 " LOG(ERROR) << "target size " << patched.data.size() << " SHA-1 " << short_sha1(patched.sha1);
<< short_sha1(current_target_sha1);
LOG(ERROR) << "source size " << source_file.data.size() << " SHA-1 " LOG(ERROR) << "source size " << source_file.data.size() << " SHA-1 "
<< short_sha1(source_file.sha1); << short_sha1(source_file.sha1);
uint8_t patch_digest[SHA_DIGEST_LENGTH]; uint8_t patch_digest[SHA_DIGEST_LENGTH];
SHA1(reinterpret_cast<const uint8_t*>(patch->data.data()), patch->data.size(), patch_digest); SHA1(reinterpret_cast<const uint8_t*>(patch.data.data()), patch.data.size(), patch_digest);
LOG(ERROR) << "patch size " << patch->data.size() << " SHA-1 " << short_sha1(patch_digest); LOG(ERROR) << "patch size " << patch.data.size() << " SHA-1 " << short_sha1(patch_digest);
if (bonus_data != nullptr) { if (bonus_data != nullptr) {
uint8_t bonus_digest[SHA_DIGEST_LENGTH]; uint8_t bonus_digest[SHA_DIGEST_LENGTH];
@ -547,21 +403,53 @@ static int GenerateTarget(const FileContents& source_file, const std::unique_ptr
<< short_sha1(bonus_digest); << short_sha1(bonus_digest);
} }
return 1; return false;
} else {
LOG(INFO) << " now " << short_sha1(target_sha1);
} }
LOG(INFO) << " now " << short_sha1(expected_sha1);
// Write back the temp file to the partition. // Write back the temp file to the partition.
if (WriteToPartition(reinterpret_cast<const unsigned char*>(memory_sink_str.c_str()), if (!WriteBufferToPartition(patched, target)) {
memory_sink_str.size(), target_filename) != 0) { LOG(ERROR) << "Failed to write patched data to " << target.name;
LOG(ERROR) << "Failed to write patched data to " << target_filename; return false;
return 1;
} }
// Delete the backup copy of the source. // Delete the backup copy of the source.
unlink(Paths::Get().cache_temp_source().c_str()); unlink(Paths::Get().cache_temp_source().c_str());
// Success! // Success!
return 0; return true;
}
bool CheckPartition(const Partition& partition) {
FileContents target_file;
return ReadPartitionToBuffer(partition, &target_file, false);
}
Partition Partition::Parse(const std::string& input_str, std::string* err) {
std::vector<std::string> pieces = android::base::Split(input_str, ":");
if (pieces.size() != 4 || pieces[0] != "EMMC") {
*err = "Invalid number of tokens or non-eMMC target";
return {};
}
size_t size;
if (!android::base::ParseUint(pieces[2], &size) || size == 0) {
*err = "Failed to parse \"" + pieces[2] + "\" as byte count";
return {};
}
return Partition(pieces[1], size, pieces[3]);
}
std::string Partition::ToString() const {
if (*this) {
return "EMMC:"s + name + ":" + std::to_string(size) + ":" + hash;
}
return "<invalid-partition>";
}
std::ostream& operator<<(std::ostream& os, const Partition& partition) {
os << partition.ToString();
return os;
} }

View file

@ -16,7 +16,10 @@
#include "applypatch_modes.h" #include "applypatch_modes.h"
#include <android-base/logging.h>
// See the comments for applypatch() function. // See the comments for applypatch() function.
int main(int argc, char** argv) { int main(int argc, char** argv) {
android::base::InitLogging(argv);
return applypatch_modes(argc, argv); return applypatch_modes(argc, argv);
} }

View file

@ -35,56 +35,48 @@
#include "applypatch/applypatch.h" #include "applypatch/applypatch.h"
#include "edify/expr.h" #include "edify/expr.h"
static int CheckMode(const std::string& target) { static int CheckMode(const std::string& target_emmc) {
return applypatch_check(target, {}); std::string err;
auto target = Partition::Parse(target_emmc, &err);
if (!target) {
LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err;
return 2;
}
return CheckPartition(target) ? 0 : 1;
} }
static int FlashMode(const std::string& target_emmc, const std::string& source_file) { static int FlashMode(const std::string& target_emmc, const std::string& source_file) {
std::vector<std::string> pieces = android::base::Split(target_emmc, ":"); std::string err;
if (pieces.size() != 4 || pieces[0] != "EMMC") { auto target = Partition::Parse(target_emmc, &err);
if (!target) {
LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err;
return 2; return 2;
} }
size_t target_size; return FlashPartition(target, source_file) ? 0 : 1;
if (!android::base::ParseUint(pieces[2], &target_size) || target_size == 0) {
LOG(ERROR) << "Failed to parse \"" << pieces[2] << "\" as byte count";
return 1;
}
return applypatch_flash(source_file.c_str(), target_emmc.c_str(), pieces[3].c_str(), target_size);
} }
static int PatchMode(const std::string& target_emmc, const std::string& source_emmc, static int PatchMode(const std::string& target_emmc, const std::string& source_emmc,
const std::string& patch_file, const std::string& bonus_file) { const std::string& patch_file, const std::string& bonus_file) {
std::vector<std::string> target_pieces = android::base::Split(target_emmc, ":"); std::string err;
if (target_pieces.size() != 4 || target_pieces[0] != "EMMC") { auto target = Partition::Parse(target_emmc, &err);
if (!target) {
LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err;
return 2; return 2;
} }
size_t target_size; auto source = Partition::Parse(source_emmc, &err);
if (!android::base::ParseUint(target_pieces[2], &target_size) || target_size == 0) { if (!source) {
LOG(ERROR) << "Failed to parse \"" << target_pieces[2] << "\" as byte count"; LOG(ERROR) << "Failed to parse source \"" << source_emmc << "\": " << err;
return 1;
}
std::vector<std::string> source_pieces = android::base::Split(source_emmc, ":");
if (source_pieces.size() != 4 || source_pieces[0] != "EMMC") {
return 2; return 2;
} }
size_t source_size; std::string patch_contents;
if (!android::base::ParseUint(source_pieces[2], &source_size) || source_size == 0) { if (!android::base::ReadFileToString(patch_file, &patch_contents)) {
LOG(ERROR) << "Failed to parse \"" << source_pieces[2] << "\" as byte count";
return 1;
}
std::string contents;
if (!android::base::ReadFileToString(patch_file, &contents)) {
PLOG(ERROR) << "Failed to read patch file \"" << patch_file << "\""; PLOG(ERROR) << "Failed to read patch file \"" << patch_file << "\"";
return 1; return 1;
} }
std::vector<std::unique_ptr<Value>> patches;
patches.push_back(std::make_unique<Value>(Value::Type::BLOB, std::move(contents)));
std::vector<std::string> sha1s{ source_pieces[3] };
Value patch(Value::Type::BLOB, std::move(patch_contents));
std::unique_ptr<Value> bonus; std::unique_ptr<Value> bonus;
if (!bonus_file.empty()) { if (!bonus_file.empty()) {
std::string bonus_contents; std::string bonus_contents;
@ -95,8 +87,7 @@ static int PatchMode(const std::string& target_emmc, const std::string& source_e
bonus = std::make_unique<Value>(Value::Type::BLOB, std::move(bonus_contents)); bonus = std::make_unique<Value>(Value::Type::BLOB, std::move(bonus_contents));
} }
return applypatch(source_emmc.c_str(), target_emmc.c_str(), target_pieces[3].c_str(), target_size, return PatchPartition(target, source, patch, bonus.get()) ? 0 : 1;
sha1s, patches, bonus.get());
} }
static void Usage() { static void Usage() {

View file

@ -21,6 +21,7 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <ostream>
#include <string> #include <string>
#include <vector> #include <vector>
@ -44,44 +45,52 @@ int ShowLicenses();
// digest or be of the form "<digest>:<anything>". Returns 0 on success, or -1 on any error. // digest or be of the form "<digest>:<anything>". Returns 0 on success, or -1 on any error.
int ParseSha1(const std::string& str, uint8_t* digest); int ParseSha1(const std::string& str, uint8_t* digest);
// Applies binary patches to eMMC target files in a way that is safe (the original file is not struct Partition {
// touched until we have the desired replacement for it) and idempotent (it's okay to run this Partition() = default;
// program multiple times).
//
// - If the SHA-1 hash of 'target_filename' is 'target_sha1_string', does nothing and returns
// successfully.
//
// - Otherwise, if the SHA-1 hash of 'source_filename' is one of the entries in 'patch_sha1s', the
// corresponding patch from 'patch_data' (which must be a VAL_BLOB) is applied to produce a new
// file (the type of patch is automatically detected from the blob data). If that new file has
// SHA-1 hash 'target_sha1_str', moves it to replace 'target_filename', and exits successfully.
// Note that if 'source_filename' and 'target_filename' are not the same, 'source_filename' is
// NOT deleted on success. 'target_filename' may be the string "-" to mean
// "the same as 'source_filename'".
//
// - Otherwise, or if any error is encountered, exits with non-zero status.
//
// 'source_filename' must refer to an eMMC partition to read the source data. See the comments for
// the LoadPartitionContents() function for the format of such a filename. 'target_size' has become
// obsolete since we have dropped the support for patching non-eMMC targets (eMMC targets have the
// size embedded in the filename).
int applypatch(const char* source_filename, const char* target_filename,
const char* target_sha1_str, size_t target_size,
const std::vector<std::string>& patch_sha1s,
const std::vector<std::unique_ptr<Value>>& patch_data, const Value* bonus_data);
// Returns 0 if the contents of the eMMC target or the cached file match any of the given SHA-1's. Partition(const std::string& name, size_t size, const std::string& hash)
// Returns nonzero otherwise. 'filename' must refer to an eMMC partition target. It would only use : name(name), size(size), hash(hash) {}
// 'sha1s' to find a match on /cache if the hashes embedded in the filename fail to match.
int applypatch_check(const std::string& filename, const std::vector<std::string>& sha1s);
// Flashes a given image to the eMMC target partition. It verifies the target cheksum first, and // Parses and returns the given string into a Partition object. The input string is of the form
// will return if target already has the desired hash. Otherwise it checks the checksum of the // "EMMC:<device>:<size>:<hash>". Returns the parsed Partition, or an empty object on error.
// given source image before flashing, and verifies the target partition afterwards. static Partition Parse(const std::string& partition, std::string* err);
// 'target_filename' must refer to an eMMC partition, of the form "EMMC:<device>:<size>:<hash>".
// The function is idempotent. Returns zero on success. std::string ToString() const;
int applypatch_flash(const char* source_filename, const char* target_filename,
const char* target_sha1_str, size_t target_size); // Returns whether the current Partition object is valid.
explicit operator bool() const {
return !name.empty();
}
std::string name;
size_t size;
std::string hash;
};
std::ostream& operator<<(std::ostream& os, const Partition& partition);
// Applies the given 'patch' to the 'source' Partition, verifies then writes the patching result to
// the 'target' Partition. While patching, it will backup the data on the source partition to
// /cache, so that the patching could be resumed on interruption even if both of the source and
// target partitions refer to the same device. The function is idempotent if called multiple times.
// An optional arg 'bonus' can be provided, if the patch was generated with a bonus output.
// Returns the patching result.
bool PatchPartition(const Partition& target, const Partition& source, const Value& patch,
const Value* bonus);
// Returns whether the contents of the eMMC target or the cached file match the embedded hash.
// It will look for the backup on /cache if the given partition doesn't match the checksum.
bool PatchPartitionCheck(const Partition& target, const Partition& source);
// Checks whether the contents of the given partition has the desired hash. It will NOT look for
// the backup on /cache if the given partition doesn't have the expected checksum.
bool CheckPartition(const Partition& target);
// Flashes a given image in 'source_filename' to the eMMC target partition. It verifies the target
// checksum first, and will return if target already has the desired hash. Otherwise it checks the
// checksum of the given source image, flashes, and verifies the target partition afterwards. The
// function is idempotent. Returns the flashing result.
bool FlashPartition(const Partition& target, const std::string& source_filename);
// Reads a file into memory; stores the file contents and associated metadata in *file. Returns 0 // Reads a file into memory; stores the file contents and associated metadata in *file. Returns 0
// on success, or -1 on error. // on success, or -1 on error.

View file

@ -41,6 +41,7 @@
#include <ziparchive/zip_archive.h> #include <ziparchive/zip_archive.h>
#include <ziparchive/zip_writer.h> #include <ziparchive/zip_writer.h>
#include "applypatch/applypatch.h"
#include "common/test_constants.h" #include "common/test_constants.h"
#include "edify/expr.h" #include "edify/expr.h"
#include "otautil/error_code.h" #include "otautil/error_code.h"
@ -214,55 +215,47 @@ TEST_F(UpdaterTest, getprop) {
expect(nullptr, "getprop(\"arg1\", \"arg2\")", kArgsParsingFailure); expect(nullptr, "getprop(\"arg1\", \"arg2\")", kArgsParsingFailure);
} }
TEST_F(UpdaterTest, apply_patch_check) { TEST_F(UpdaterTest, patch_partition_check) {
// Zero-argument is not valid. // Zero argument is not valid.
expect(nullptr, "apply_patch_check()", kArgsParsingFailure); expect(nullptr, "patch_partition_check()", kArgsParsingFailure);
// File not found. std::string source_file = from_testdata_base("boot.img");
expect("", "apply_patch_check(\"/doesntexist\")", kNoCause); std::string source_content;
ASSERT_TRUE(android::base::ReadFileToString(source_file, &source_content));
size_t source_size = source_content.size();
std::string source_hash = get_sha1(source_content);
Partition source(source_file, source_size, source_hash);
std::string src_file = from_testdata_base("boot.img"); std::string target_file = from_testdata_base("recovery.img");
std::string src_content; std::string target_content;
ASSERT_TRUE(android::base::ReadFileToString(src_file, &src_content)); ASSERT_TRUE(android::base::ReadFileToString(target_file, &target_content));
size_t src_size = src_content.size(); size_t target_size = target_content.size();
std::string src_hash = get_sha1(src_content); std::string target_hash = get_sha1(target_content);
Partition target(target_file, target_size, target_hash);
// One-argument with EMMC:file:size:sha1 should pass the check. // One argument is not valid.
std::string filename = android::base::Join( expect(nullptr, "patch_partition_check(\"" + source.ToString() + "\")", kArgsParsingFailure);
std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size), src_hash }, ":"); expect(nullptr, "patch_partition_check(\"" + target.ToString() + "\")", kArgsParsingFailure);
std::string cmd = "apply_patch_check(\"" + filename + "\")";
// Both of the source and target have the desired checksum.
std::string cmd =
"patch_partition_check(\"" + source.ToString() + "\", \"" + target.ToString() + "\")";
expect("t", cmd, kNoCause); expect("t", cmd, kNoCause);
// EMMC:file:(size-1):sha1:(size+1):sha1 should fail the check. // Only source partition has the desired checksum.
std::string filename_bad = android::base::Join( Partition bad_target(target_file, target_size - 1, target_hash);
std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size - 1), src_hash, cmd = "patch_partition_check(\"" + source.ToString() + "\", \"" + bad_target.ToString() + "\")";
std::to_string(src_size + 1), src_hash }, expect("t", cmd, kNoCause);
":");
cmd = "apply_patch_check(\"" + filename_bad + "\")"; // Only target partition has the desired checksum.
Partition bad_source(source_file, source_size + 1, source_hash);
cmd = "patch_partition_check(\"" + bad_source.ToString() + "\", \"" + target.ToString() + "\")";
expect("t", cmd, kNoCause);
// Neither of the source or target has the desired checksum.
cmd =
"patch_partition_check(\"" + bad_source.ToString() + "\", \"" + bad_target.ToString() + "\")";
expect("", cmd, kNoCause); expect("", cmd, kNoCause);
// EMMC:file:(size-1):sha1:size:sha1:(size+1):sha1 should pass the check.
filename_bad =
android::base::Join(std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size - 1),
src_hash, std::to_string(src_size), src_hash,
std::to_string(src_size + 1), src_hash },
":");
cmd = "apply_patch_check(\"" + filename_bad + "\")";
expect("t", cmd, kNoCause);
// Multiple arguments.
// As long as it successfully loads the partition specified in filename, it won't check against
// any given SHAs.
cmd = "apply_patch_check(\"" + filename + "\", \"wrong_sha1\", \"wrong_sha2\")";
expect("t", cmd, kNoCause);
cmd = "apply_patch_check(\"" + filename + "\", \"wrong_sha1\", \"" + src_hash +
"\", \"wrong_sha2\")";
expect("t", cmd, kNoCause);
cmd = "apply_patch_check(\"" + filename_bad + "\", \"wrong_sha1\", \"" + src_hash +
"\", \"wrong_sha2\")";
expect("t", cmd, kNoCause);
} }
TEST_F(UpdaterTest, file_getprop) { TEST_F(UpdaterTest, file_getprop) {

View file

@ -38,6 +38,7 @@
#include "applypatch/applypatch.h" #include "applypatch/applypatch.h"
#include "common/test_constants.h" #include "common/test_constants.h"
#include "edify/expr.h"
#include "otautil/paths.h" #include "otautil/paths.h"
#include "otautil/print_sha1.h" #include "otautil/print_sha1.h"
@ -58,12 +59,19 @@ class ApplyPatchTest : public ::testing::Test {
target_size = recovery_fc.data.size(); target_size = recovery_fc.data.size();
target_sha1 = print_sha1(recovery_fc.sha1); target_sha1 = print_sha1(recovery_fc.sha1);
source_partition = Partition(source_file, source_size, source_sha1);
target_partition = Partition(partition_file.path, target_size, target_sha1);
srand(time(nullptr)); srand(time(nullptr));
bad_sha1_a = android::base::StringPrintf("%040x", rand()); bad_sha1_a = android::base::StringPrintf("%040x", rand());
bad_sha1_b = android::base::StringPrintf("%040x", rand()); bad_sha1_b = android::base::StringPrintf("%040x", rand());
// Reset the cache backup file. // Reset the cache backup file.
Paths::Get().set_cache_temp_source("/cache/saved.file"); Paths::Get().set_cache_temp_source(cache_temp_source.path);
}
void TearDown() override {
ASSERT_TRUE(android::base::RemoveFileIfExists(cache_temp_source.path));
} }
std::string source_file; std::string source_file;
@ -76,74 +84,75 @@ class ApplyPatchTest : public ::testing::Test {
std::string bad_sha1_a; std::string bad_sha1_a;
std::string bad_sha1_b; std::string bad_sha1_b;
Partition source_partition;
Partition target_partition;
private:
TemporaryFile partition_file;
TemporaryFile cache_temp_source;
}; };
TEST_F(ApplyPatchTest, CheckMode) { TEST_F(ApplyPatchTest, CheckPartition) {
std::string partition = ASSERT_TRUE(CheckPartition(source_partition));
"EMMC:" + source_file + ":" + std::to_string(source_size) + ":" + source_sha1;
ASSERT_EQ(0, applypatch_check(partition, {}));
ASSERT_EQ(0, applypatch_check(partition, { source_sha1 }));
ASSERT_EQ(0, applypatch_check(partition, { bad_sha1_a, bad_sha1_b }));
ASSERT_EQ(0, applypatch_check(partition, { bad_sha1_a, source_sha1, bad_sha1_b }));
} }
TEST_F(ApplyPatchTest, CheckMode_NonEmmcTarget) { TEST_F(ApplyPatchTest, CheckPartition_Mismatching) {
ASSERT_NE(0, applypatch_check(source_file, {})); ASSERT_FALSE(CheckPartition(Partition(source_file, target_size, target_sha1)));
ASSERT_NE(0, applypatch_check(source_file, { source_sha1 })); ASSERT_FALSE(CheckPartition(Partition(source_file, source_size, bad_sha1_a)));
ASSERT_NE(0, applypatch_check(source_file, { bad_sha1_a, bad_sha1_b }));
ASSERT_NE(0, applypatch_check(source_file, { bad_sha1_a, source_sha1, bad_sha1_b })); ASSERT_FALSE(CheckPartition(Partition(source_file, source_size - 1, source_sha1)));
ASSERT_FALSE(CheckPartition(Partition(source_file, source_size + 1, source_sha1)));
} }
TEST_F(ApplyPatchTest, CheckMode_EmmcTarget) { TEST_F(ApplyPatchTest, PatchPartitionCheck) {
// EMMC:source_file:size:sha1 should pass the check. ASSERT_TRUE(PatchPartitionCheck(target_partition, source_partition));
std::string src_file =
"EMMC:" + source_file + ":" + std::to_string(source_size) + ":" + source_sha1;
ASSERT_EQ(0, applypatch_check(src_file, {}));
// EMMC:source_file:(size-1):sha1:(size+1):sha1 should fail the check. ASSERT_TRUE(
src_file = "EMMC:" + source_file + ":" + std::to_string(source_size - 1) + ":" + source_sha1 + PatchPartitionCheck(Partition(source_file, source_size - 1, source_sha1), source_partition));
":" + std::to_string(source_size + 1) + ":" + source_sha1;
ASSERT_NE(0, applypatch_check(src_file, {}));
// EMMC:source_file:(size-1):sha1:size:sha1:(size+1):sha1 should pass the check. ASSERT_TRUE(
src_file = "EMMC:" + source_file + ":" + std::to_string(source_size - 1) + ":" + source_sha1 + PatchPartitionCheck(Partition(source_file, source_size + 1, source_sha1), source_partition));
":" + std::to_string(source_size) + ":" + source_sha1 + ":" +
std::to_string(source_size + 1) + ":" + source_sha1;
ASSERT_EQ(0, applypatch_check(src_file, {}));
// EMMC:source_file:(size+1):sha1:(size-1):sha1:size:sha1 should pass the check.
src_file = "EMMC:" + source_file + ":" + std::to_string(source_size + 1) + ":" + source_sha1 +
":" + std::to_string(source_size - 1) + ":" + source_sha1 + ":" +
std::to_string(source_size) + ":" + source_sha1;
ASSERT_EQ(0, applypatch_check(src_file, {}));
// EMMC:target_file:(size+1):source_sha1:(size-1):source_sha1:size:source_sha1:size:target_sha1
// should pass the check.
src_file = "EMMC:" + target_file + ":" + std::to_string(source_size + 1) + ":" + source_sha1 +
":" + std::to_string(source_size - 1) + ":" + source_sha1 + ":" +
std::to_string(source_size) + ":" + source_sha1 + ":" + std::to_string(target_size) +
":" + target_sha1;
ASSERT_EQ(0, applypatch_check(src_file, {}));
} }
TEST_F(ApplyPatchTest, CheckMode_UseBackup) { TEST_F(ApplyPatchTest, PatchPartitionCheck_UseBackup) {
std::string corrupted = ASSERT_FALSE(
"EMMC:" + source_file + ":" + std::to_string(source_size) + ":" + bad_sha1_a; PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
ASSERT_NE(0, applypatch_check(corrupted, { source_sha1 }));
Paths::Get().set_cache_temp_source(source_file); Paths::Get().set_cache_temp_source(source_file);
ASSERT_EQ(0, applypatch_check(corrupted, { source_sha1 })); ASSERT_TRUE(
ASSERT_EQ(0, applypatch_check(corrupted, { bad_sha1_a, source_sha1, bad_sha1_b })); PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
} }
TEST_F(ApplyPatchTest, CheckMode_UseBackup_BothCorrupted) { TEST_F(ApplyPatchTest, PatchPartitionCheck_UseBackup_BothCorrupted) {
std::string corrupted = ASSERT_FALSE(
"EMMC:" + source_file + ":" + std::to_string(source_size) + ":" + bad_sha1_a; PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
ASSERT_NE(0, applypatch_check(corrupted, {}));
ASSERT_NE(0, applypatch_check(corrupted, { source_sha1 }));
Paths::Get().set_cache_temp_source(source_file); Paths::Get().set_cache_temp_source(target_file);
ASSERT_NE(0, applypatch_check(corrupted, { bad_sha1_a, bad_sha1_b })); ASSERT_FALSE(
PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
}
TEST_F(ApplyPatchTest, PatchPartition) {
FileContents patch_fc;
ASSERT_EQ(0, LoadFileContents(from_testdata_base("recovery-from-boot.p"), &patch_fc));
Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend()));
FileContents bonus_fc;
ASSERT_EQ(0, LoadFileContents(from_testdata_base("bonus.file"), &bonus_fc));
Value bonus(Value::Type::BLOB, std::string(bonus_fc.data.cbegin(), bonus_fc.data.cend()));
ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, &bonus));
}
// Tests patching an eMMC target without a separate bonus file (i.e. recovery-from-boot patch has
// everything).
TEST_F(ApplyPatchTest, PatchPartitionWithoutBonusFile) {
FileContents patch_fc;
ASSERT_EQ(0, LoadFileContents(from_testdata_base("recovery-from-boot-with-bonus.p"), &patch_fc));
Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend()));
ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, nullptr));
} }
class FreeCacheTest : public ::testing::Test { class FreeCacheTest : public ::testing::Test {

View file

@ -196,94 +196,82 @@ Value* PackageExtractFileFn(const char* name, State* state,
} }
} }
// apply_patch(src_file, tgt_file, tgt_sha1, tgt_size, patch1_sha1, patch1_blob, [...]) // patch_partition_check(target_partition, source_partition)
// Applies a binary patch to the src_file to produce the tgt_file. If the desired target is the // Checks if the target and source partitions have the desired checksums to be patched. It returns
// same as the source, pass "-" for tgt_file. tgt_sha1 and tgt_size are the expected final SHA1 // directly, if the target partition already has the expected checksum. Otherwise it in turn
// hash and size of the target file. The remaining arguments must come in pairs: a SHA1 hash (a // checks the integrity of the source partition and the backup file on /cache.
// 40-character hex string) and a blob. The blob is the patch to be applied when the source
// file's current contents have the given SHA1.
// //
// The patching is done in a safe manner that guarantees the target file either has the desired // For example, patch_partition_check(
// SHA1 hash and size, or it is untouched -- it will not be left in an unrecoverable intermediate // "EMMC:/dev/block/boot:12342568:8aaacf187a6929d0e9c3e9e46ea7ff495b43424d",
// state. If the process is interrupted during patching, the target file may be in an intermediate // "EMMC:/dev/block/boot:12363048:06b0b16299dcefc94900efed01e0763ff644ffa4")
// state; a copy exists in the cache partition so restarting the update can successfully update Value* PatchPartitionCheckFn(const char* name, State* state,
// the file. const std::vector<std::unique_ptr<Expr>>& argv) {
Value* ApplyPatchFn(const char* name, State* state, if (argv.size() != 2) {
const std::vector<std::unique_ptr<Expr>>& argv) {
if (argv.size() < 6 || (argv.size() % 2) == 1) {
return ErrorAbort(state, kArgsParsingFailure, return ErrorAbort(state, kArgsParsingFailure,
"%s(): expected at least 6 args and an " "%s(): Invalid number of args (expected 2, got %zu)", name, argv.size());
"even number, got %zu",
name, argv.size());
} }
std::vector<std::string> args; std::vector<std::string> args;
if (!ReadArgs(state, argv, &args, 0, 4)) { if (!ReadArgs(state, argv, &args, 0, 2)) {
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
}
const std::string& source_filename = args[0];
const std::string& target_filename = args[1];
const std::string& target_sha1 = args[2];
const std::string& target_size_str = args[3];
size_t target_size;
if (!android::base::ParseUint(target_size_str.c_str(), &target_size)) {
return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count", name,
target_size_str.c_str());
} }
int patchcount = (argv.size() - 4) / 2; std::string err;
std::vector<std::unique_ptr<Value>> arg_values; auto target = Partition::Parse(args[0], &err);
if (!ReadValueArgs(state, argv, &arg_values, 4, argv.size() - 4)) { if (!target) {
return nullptr; return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse target \"%s\": %s", name,
args[0].c_str(), err.c_str());
} }
for (int i = 0; i < patchcount; ++i) { auto source = Partition::Parse(args[1], &err);
if (arg_values[i * 2]->type != Value::Type::STRING) { if (!source) {
return ErrorAbort(state, kArgsParsingFailure, "%s(): sha-1 #%d is not string", name, i * 2); return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse source \"%s\": %s", name,
} args[1].c_str(), err.c_str());
if (arg_values[i * 2 + 1]->type != Value::Type::BLOB) {
return ErrorAbort(state, kArgsParsingFailure, "%s(): patch #%d is not blob", name, i * 2 + 1);
}
} }
std::vector<std::string> patch_sha_str; bool result = PatchPartitionCheck(target, source);
std::vector<std::unique_ptr<Value>> patches; return StringValue(result ? "t" : "");
for (int i = 0; i < patchcount; ++i) {
patch_sha_str.push_back(arg_values[i * 2]->data);
patches.push_back(std::move(arg_values[i * 2 + 1]));
}
int result = applypatch(source_filename.c_str(), target_filename.c_str(), target_sha1.c_str(),
target_size, patch_sha_str, patches, nullptr);
return StringValue(result == 0 ? "t" : "");
} }
// apply_patch_check(filename, [sha1, ...]) // patch_partition(target, source, patch)
// Returns true if the contents of filename or the temporary copy in the cache partition (if // Applies the given patch to the source partition, and writes the result to the target partition.
// present) have a SHA-1 checksum equal to one of the given sha1 values. sha1 values are //
// specified as 40 hex digits. // For example, patch_partition(
Value* ApplyPatchCheckFn(const char* name, State* state, // "EMMC:/dev/block/boot:12342568:8aaacf187a6929d0e9c3e9e46ea7ff495b43424d",
const std::vector<std::unique_ptr<Expr>>& argv) { // "EMMC:/dev/block/boot:12363048:06b0b16299dcefc94900efed01e0763ff644ffa4",
if (argv.size() < 1) { // package_extract_file("boot.img.p"))
return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 1 arg, got %zu", name, Value* PatchPartitionFn(const char* name, State* state,
argv.size()); const std::vector<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 3) {
return ErrorAbort(state, kArgsParsingFailure,
"%s(): Invalid number of args (expected 3, got %zu)", name, argv.size());
} }
std::vector<std::string> args; std::vector<std::string> args;
if (!ReadArgs(state, argv, &args, 0, 1)) { if (!ReadArgs(state, argv, &args, 0, 2)) {
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
} }
const std::string& filename = args[0];
std::vector<std::string> sha1s; std::string err;
if (argv.size() > 1 && !ReadArgs(state, argv, &sha1s, 1, argv.size() - 1)) { auto target = Partition::Parse(args[0], &err);
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); if (!target) {
return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse target \"%s\": %s", name,
args[0].c_str(), err.c_str());
} }
int result = applypatch_check(filename.c_str(), sha1s);
return StringValue(result == 0 ? "t" : ""); auto source = Partition::Parse(args[1], &err);
if (!source) {
return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse source \"%s\": %s", name,
args[1].c_str(), err.c_str());
}
std::vector<std::unique_ptr<Value>> values;
if (!ReadValueArgs(state, argv, &values, 2, 1) || values[0]->type != Value::Type::BLOB) {
return ErrorAbort(state, kArgsParsingFailure, "%s(): Invalid patch arg", name);
}
bool result = PatchPartition(target, source, *values[0], nullptr);
return StringValue(result ? "t" : "");
} }
// mount(fs_type, partition_type, location, mount_point) // mount(fs_type, partition_type, location, mount_point)
@ -956,9 +944,9 @@ void RegisterInstallFunctions() {
RegisterFunction("getprop", GetPropFn); RegisterFunction("getprop", GetPropFn);
RegisterFunction("file_getprop", FileGetPropFn); RegisterFunction("file_getprop", FileGetPropFn);
RegisterFunction("apply_patch", ApplyPatchFn);
RegisterFunction("apply_patch_check", ApplyPatchCheckFn);
RegisterFunction("apply_patch_space", ApplyPatchSpaceFn); RegisterFunction("apply_patch_space", ApplyPatchSpaceFn);
RegisterFunction("patch_partition", PatchPartitionFn);
RegisterFunction("patch_partition_check", PatchPartitionCheckFn);
RegisterFunction("wipe_block_device", WipeBlockDeviceFn); RegisterFunction("wipe_block_device", WipeBlockDeviceFn);