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/print_sha1.h"
static int LoadPartitionContents(const std::string& filename, FileContents* file);
static int GenerateTarget(const FileContents& source_file, const std::unique_ptr<Value>& patch,
const std::string& target_filename,
const uint8_t target_sha1[SHA_DIGEST_LENGTH], const Value* bonus_data);
using namespace std::string_literals;
static bool GenerateTarget(const Partition& target, const FileContents& source_file,
const Value& patch, const Value* bonus_data);
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:")) {
return LoadPartitionContents(filename, file);
return -1;
}
std::string data;
@ -66,101 +66,44 @@ int LoadFileContents(const std::string& filename, FileContents* file) {
return 0;
}
// Loads the contents of an EMMC partition into the provided FileContents. filename should be a
// string of the form "EMMC:<partition_device>:...". The smallest size_n bytes for which that prefix
// of the partition contents has the corresponding sha1 hash will be loaded. It is acceptable for a
// size value to be repeated with different sha1s. Returns 0 on success.
//
// This complexity is needed because if an OTA installation is interrupted, the partition might
// contain either the source or the target data, which might be of different lengths. We need to
// 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;
// Reads the contents of a Partition to the given FileContents buffer.
static bool ReadPartitionToBuffer(const Partition& partition, FileContents* out,
bool check_backup) {
uint8_t expected_sha1[SHA_DIGEST_LENGTH];
if (ParseSha1(partition.hash, expected_sha1) != 0) {
LOG(ERROR) << "Failed to parse target hash \"" << partition.hash << "\"";
return false;
}
size_t pair_count = (pieces.size() - 2) / 2; // # of (size, sha1) pairs in filename
std::vector<std::pair<size_t, std::string>> pairs;
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) {
android::base::unique_fd dev(open(partition.name.c_str(), O_RDONLY));
if (!dev) {
PLOG(ERROR) << "Failed to open eMMC partition \"" << partition << "\"";
return -1;
}
SHA_CTX sha_ctx;
SHA1_Init(&sha_ctx);
// Allocate enough memory to hold the largest size.
std::vector<unsigned char> buffer(pairs[pair_count - 1].first);
size_t offset = 0; // # bytes read so far
bool found = false;
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;
} else {
std::vector<unsigned char> buffer(partition.size);
if (!android::base::ReadFully(dev, buffer.data(), buffer.size())) {
PLOG(ERROR) << "Failed to read " << buffer.size() << " bytes of data for partition "
<< partition;
} else {
SHA1(buffer.data(), buffer.size(), out->sha1);
if (memcmp(out->sha1, expected_sha1, SHA_DIGEST_LENGTH) == 0) {
out->data = std::move(buffer);
return true;
}
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) {
// Ran off the end of the list of (size, sha1) pairs without finding a match.
LOG(ERROR) << "Contents of partition \"" << partition << "\" didn't match " << filename;
return -1;
if (!check_backup) {
LOG(ERROR) << "Partition contents don't have the expected checksum";
return false;
}
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);
file->data = std::move(buffer);
return 0;
LOG(ERROR) << "Both of partition contents and backup don't have the expected checksum";
return false;
}
int SaveFileContents(const std::string& filename, const FileContents* file) {
@ -189,49 +132,42 @@ int SaveFileContents(const std::string& filename, const FileContents* file) {
return 0;
}
// Writes a memory buffer to 'target' partition, a string of the form
// "EMMC:<partition_device>[:...]". The target name might contain multiple colons, but
// WriteToPartition() only uses the first two and ignores the rest. Returns 0 on success.
static int WriteToPartition(const unsigned char* data, size_t len, const std::string& target) {
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;
}
// Writes a memory buffer to 'target' Partition.
static bool WriteBufferToPartition(const FileContents& file_contents, const Partition& partition) {
const unsigned char* data = file_contents.data.data();
size_t len = file_contents.data.size();
size_t start = 0;
bool success = false;
for (size_t attempt = 0; attempt < 2; ++attempt) {
std::string partition = pieces[1];
android::base::unique_fd fd(open(partition.c_str(), O_RDWR));
android::base::unique_fd fd(open(partition.name.c_str(), O_RDWR));
if (fd == -1) {
PLOG(ERROR) << "Failed to open \"" << partition << "\"";
return -1;
return false;
}
if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) {
PLOG(ERROR) << "Failed to seek to " << start << " on \"" << partition << "\"";
return -1;
return false;
}
if (!android::base::WriteFully(fd, data + start, len - start)) {
PLOG(ERROR) << "Failed to write " << len - start << " bytes to \"" << partition << "\"";
return -1;
return false;
}
if (fsync(fd) != 0) {
PLOG(ERROR) << "Failed to sync \"" << partition << "\"";
return -1;
return false;
}
if (close(fd.release()) != 0) {
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) {
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.
@ -247,7 +183,7 @@ static int WriteToPartition(const unsigned char* data, size_t len, const std::st
// Verify.
if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) {
PLOG(ERROR) << "Failed to seek to 0 on " << partition;
return -1;
return false;
}
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)) {
PLOG(ERROR) << "Failed to verify-read " << partition << " at " << p;
return -1;
return false;
}
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) {
PLOG(ERROR) << "Failed to close " << partition;
return -1;
return false;
}
}
if (!success) {
LOG(ERROR) << "Failed to verify after all attempts";
return -1;
return false;
}
sync();
return 0;
return true;
}
int ParseSha1(const std::string& str, uint8_t* digest) {
@ -317,44 +253,11 @@ int ParseSha1(const std::string& str, uint8_t* digest) {
return 0;
}
// Searches a vector of SHA-1 strings for one matching the given SHA-1. Returns the index of the
// match on success, or -1 if no match is found.
static int FindMatchingPatch(const uint8_t* sha1, const std::vector<std::string>& patch_sha1s) {
for (size_t i = 0; i < patch_sha1s.size(); ++i) {
uint8_t patch_sha1[SHA_DIGEST_LENGTH];
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;
bool PatchPartitionCheck(const Partition& target, const Partition& source) {
FileContents target_file;
FileContents source_file;
return (ReadPartitionToBuffer(target, &target_file, false) ||
ReadPartitionToBuffer(source, &source_file, true));
}
int ShowLicenses() {
@ -362,124 +265,81 @@ int ShowLicenses() {
return 0;
}
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) {
LOG(INFO) << "Patching " << source_filename;
bool PatchPartition(const Partition& target, const Partition& source, const Value& patch,
const Value* bonus) {
LOG(INFO) << "Patching " << target.name;
if (target_filename[0] == '-' && target_filename[1] == '\0') {
target_filename = source_filename;
// We try to load and check against the target hash first.
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;
if (LoadFileContents(target_filename, &source_file) == 0) {
if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) {
// 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 (ReadPartitionToBuffer(source, &source_file, true)) {
return GenerateTarget(target, source_file, patch, bonus);
}
if (source_file.data.empty() ||
(target_filename != source_filename && strcmp(target_filename, source_filename) != 0)) {
// 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);
LOG(ERROR) << "Failed to find any match";
return false;
}
int applypatch_flash(const char* source_filename, const char* target_filename,
const char* target_sha1_str, size_t target_size) {
LOG(INFO) << "Flashing " << target_filename;
bool FlashPartition(const Partition& partition, const std::string& source_filename) {
LOG(INFO) << "Flashing " << partition;
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 and check against the target hash first.
FileContents target_file;
if (ReadPartitionToBuffer(partition, &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 " << 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;
if (LoadPartitionContents(target_filename, &source_file) == 0 &&
memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) {
// The early-exit case: the image was already applied, this partition has the desired hash,
// nothing for us to do.
LOG(INFO) << " already " << short_sha1(target_sha1);
return 0;
if (LoadFileContents(source_filename, &source_file) != 0) {
LOG(ERROR) << "Failed to load source file";
return false;
}
if (LoadFileContents(source_filename, &source_file) == 0) {
if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) {
// The source doesn't have desired checksum.
LOG(ERROR) << "source \"" << source_filename << "\" doesn't have expected SHA-1 sum";
LOG(ERROR) << "expected: " << short_sha1(target_sha1)
<< ", found: " << short_sha1(source_file.sha1);
return 1;
}
uint8_t expected_sha1[SHA_DIGEST_LENGTH];
if (ParseSha1(partition.hash, expected_sha1) != 0) {
LOG(ERROR) << "Failed to parse source hash \"" << partition.hash << "\"";
return false;
}
if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) {
LOG(ERROR) << "Failed to write copied data to " << target_filename;
return 1;
if (memcmp(source_file.sha1, expected_sha1, SHA_DIGEST_LENGTH) != 0) {
// The source doesn't have desired checksum.
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,
const std::string& target_filename,
const uint8_t target_sha1[SHA_DIGEST_LENGTH], const Value* bonus_data) {
if (patch->type != Value::Type::BLOB) {
static bool GenerateTarget(const Partition& target, const FileContents& source_file,
const Value& patch, const Value* bonus_data) {
uint8_t expected_sha1[SHA_DIGEST_LENGTH];
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";
return 1;
return false;
}
const char* header = &patch->data[0];
size_t header_bytes_read = patch->data.size();
const char* header = patch.data.data();
size_t header_bytes_read = patch.data.size();
bool use_bsdiff = false;
if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) {
use_bsdiff = true;
@ -487,57 +347,53 @@ static int GenerateTarget(const FileContents& source_file, const std::unique_ptr
use_bsdiff = false;
} else {
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.
if (!CheckAndFreeSpaceOnCache(source_file.data.size())) {
LOG(ERROR) << "Not enough free space on /cache";
return 1;
return false;
}
if (SaveFileContents(Paths::Get().cache_temp_source(), &source_file) < 0) {
LOG(ERROR) << "Failed to back up source file";
return 1;
return false;
}
// We store the decoded output in memory.
std::string memory_sink_str; // Don't need to reserve space.
FileContents patched;
SHA_CTX 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);
memory_sink_str.append(reinterpret_cast<const char*>(data), len);
patched.data.insert(patched.data.end(), data, data + len);
return len;
};
int result;
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 {
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) {
LOG(ERROR) << "Failed to apply the patch: " << result;
return 1;
return false;
}
uint8_t current_target_sha1[SHA_DIGEST_LENGTH];
SHA1_Final(current_target_sha1, &ctx);
if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) {
LOG(ERROR) << "Patching did not produce the expected SHA-1 of " << short_sha1(target_sha1);
SHA1_Final(patched.sha1, &ctx);
if (memcmp(patched.sha1, expected_sha1, SHA_DIGEST_LENGTH) != 0) {
LOG(ERROR) << "Patching did not produce the expected SHA-1 of " << short_sha1(expected_sha1);
LOG(ERROR) << "target size " << memory_sink_str.size() << " SHA-1 "
<< short_sha1(current_target_sha1);
LOG(ERROR) << "target size " << patched.data.size() << " SHA-1 " << short_sha1(patched.sha1);
LOG(ERROR) << "source size " << source_file.data.size() << " SHA-1 "
<< short_sha1(source_file.sha1);
uint8_t patch_digest[SHA_DIGEST_LENGTH];
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);
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);
if (bonus_data != nullptr) {
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);
}
return 1;
} else {
LOG(INFO) << " now " << short_sha1(target_sha1);
return false;
}
LOG(INFO) << " now " << short_sha1(expected_sha1);
// Write back the temp file to the partition.
if (WriteToPartition(reinterpret_cast<const unsigned char*>(memory_sink_str.c_str()),
memory_sink_str.size(), target_filename) != 0) {
LOG(ERROR) << "Failed to write patched data to " << target_filename;
return 1;
if (!WriteBufferToPartition(patched, target)) {
LOG(ERROR) << "Failed to write patched data to " << target.name;
return false;
}
// Delete the backup copy of the source.
unlink(Paths::Get().cache_temp_source().c_str());
// 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 <android-base/logging.h>
// See the comments for applypatch() function.
int main(int argc, char** argv) {
android::base::InitLogging(argv);
return applypatch_modes(argc, argv);
}

View file

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

View file

@ -21,6 +21,7 @@
#include <functional>
#include <memory>
#include <ostream>
#include <string>
#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.
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
// touched until we have the desired replacement for it) and idempotent (it's okay to run this
// 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);
struct Partition {
Partition() = default;
// Returns 0 if the contents of the eMMC target or the cached file match any of the given SHA-1's.
// Returns nonzero otherwise. 'filename' must refer to an eMMC partition target. It would only use
// '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);
Partition(const std::string& name, size_t size, const std::string& hash)
: name(name), size(size), hash(hash) {}
// Flashes a given image to the eMMC target partition. It verifies the target cheksum first, and
// will return if target already has the desired hash. Otherwise it checks the checksum of the
// given source image before flashing, and verifies the target partition afterwards.
// 'target_filename' must refer to an eMMC partition, of the form "EMMC:<device>:<size>:<hash>".
// The function is idempotent. Returns zero on success.
int applypatch_flash(const char* source_filename, const char* target_filename,
const char* target_sha1_str, size_t target_size);
// Parses and returns the given string into a Partition object. The input string is of the form
// "EMMC:<device>:<size>:<hash>". Returns the parsed Partition, or an empty object on error.
static Partition Parse(const std::string& partition, std::string* err);
std::string ToString() const;
// 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
// on success, or -1 on error.

View file

@ -41,6 +41,7 @@
#include <ziparchive/zip_archive.h>
#include <ziparchive/zip_writer.h>
#include "applypatch/applypatch.h"
#include "common/test_constants.h"
#include "edify/expr.h"
#include "otautil/error_code.h"
@ -214,55 +215,47 @@ TEST_F(UpdaterTest, getprop) {
expect(nullptr, "getprop(\"arg1\", \"arg2\")", kArgsParsingFailure);
}
TEST_F(UpdaterTest, apply_patch_check) {
// Zero-argument is not valid.
expect(nullptr, "apply_patch_check()", kArgsParsingFailure);
TEST_F(UpdaterTest, patch_partition_check) {
// Zero argument is not valid.
expect(nullptr, "patch_partition_check()", kArgsParsingFailure);
// File not found.
expect("", "apply_patch_check(\"/doesntexist\")", kNoCause);
std::string source_file = from_testdata_base("boot.img");
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 src_content;
ASSERT_TRUE(android::base::ReadFileToString(src_file, &src_content));
size_t src_size = src_content.size();
std::string src_hash = get_sha1(src_content);
std::string target_file = from_testdata_base("recovery.img");
std::string target_content;
ASSERT_TRUE(android::base::ReadFileToString(target_file, &target_content));
size_t target_size = target_content.size();
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.
std::string filename = android::base::Join(
std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size), src_hash }, ":");
std::string cmd = "apply_patch_check(\"" + filename + "\")";
// One argument is not valid.
expect(nullptr, "patch_partition_check(\"" + source.ToString() + "\")", kArgsParsingFailure);
expect(nullptr, "patch_partition_check(\"" + target.ToString() + "\")", kArgsParsingFailure);
// Both of the source and target have the desired checksum.
std::string cmd =
"patch_partition_check(\"" + source.ToString() + "\", \"" + target.ToString() + "\")";
expect("t", cmd, kNoCause);
// EMMC:file:(size-1):sha1:(size+1):sha1 should fail the check.
std::string 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 + 1), src_hash },
":");
cmd = "apply_patch_check(\"" + filename_bad + "\")";
// Only source partition has the desired checksum.
Partition bad_target(target_file, target_size - 1, target_hash);
cmd = "patch_partition_check(\"" + source.ToString() + "\", \"" + bad_target.ToString() + "\")";
expect("t", cmd, kNoCause);
// 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);
// 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) {

View file

@ -38,6 +38,7 @@
#include "applypatch/applypatch.h"
#include "common/test_constants.h"
#include "edify/expr.h"
#include "otautil/paths.h"
#include "otautil/print_sha1.h"
@ -58,12 +59,19 @@ class ApplyPatchTest : public ::testing::Test {
target_size = recovery_fc.data.size();
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));
bad_sha1_a = android::base::StringPrintf("%040x", rand());
bad_sha1_b = android::base::StringPrintf("%040x", rand());
// 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;
@ -76,74 +84,75 @@ class ApplyPatchTest : public ::testing::Test {
std::string bad_sha1_a;
std::string bad_sha1_b;
Partition source_partition;
Partition target_partition;
private:
TemporaryFile partition_file;
TemporaryFile cache_temp_source;
};
TEST_F(ApplyPatchTest, CheckMode) {
std::string 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, CheckPartition) {
ASSERT_TRUE(CheckPartition(source_partition));
}
TEST_F(ApplyPatchTest, CheckMode_NonEmmcTarget) {
ASSERT_NE(0, applypatch_check(source_file, {}));
ASSERT_NE(0, applypatch_check(source_file, { source_sha1 }));
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 }));
TEST_F(ApplyPatchTest, CheckPartition_Mismatching) {
ASSERT_FALSE(CheckPartition(Partition(source_file, target_size, target_sha1)));
ASSERT_FALSE(CheckPartition(Partition(source_file, source_size, bad_sha1_a)));
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) {
// EMMC:source_file:size:sha1 should pass the check.
std::string src_file =
"EMMC:" + source_file + ":" + std::to_string(source_size) + ":" + source_sha1;
ASSERT_EQ(0, applypatch_check(src_file, {}));
TEST_F(ApplyPatchTest, PatchPartitionCheck) {
ASSERT_TRUE(PatchPartitionCheck(target_partition, source_partition));
// EMMC:source_file:(size-1):sha1:(size+1):sha1 should fail the check.
src_file = "EMMC:" + source_file + ":" + std::to_string(source_size - 1) + ":" + source_sha1 +
":" + std::to_string(source_size + 1) + ":" + source_sha1;
ASSERT_NE(0, applypatch_check(src_file, {}));
ASSERT_TRUE(
PatchPartitionCheck(Partition(source_file, source_size - 1, source_sha1), source_partition));
// EMMC:source_file:(size-1):sha1:size:sha1:(size+1):sha1 should pass the check.
src_file = "EMMC:" + source_file + ":" + std::to_string(source_size - 1) + ":" + source_sha1 +
":" + 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, {}));
ASSERT_TRUE(
PatchPartitionCheck(Partition(source_file, source_size + 1, source_sha1), source_partition));
}
TEST_F(ApplyPatchTest, CheckMode_UseBackup) {
std::string corrupted =
"EMMC:" + source_file + ":" + std::to_string(source_size) + ":" + bad_sha1_a;
ASSERT_NE(0, applypatch_check(corrupted, { source_sha1 }));
TEST_F(ApplyPatchTest, PatchPartitionCheck_UseBackup) {
ASSERT_FALSE(
PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
Paths::Get().set_cache_temp_source(source_file);
ASSERT_EQ(0, applypatch_check(corrupted, { source_sha1 }));
ASSERT_EQ(0, applypatch_check(corrupted, { bad_sha1_a, source_sha1, bad_sha1_b }));
ASSERT_TRUE(
PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
}
TEST_F(ApplyPatchTest, CheckMode_UseBackup_BothCorrupted) {
std::string corrupted =
"EMMC:" + source_file + ":" + std::to_string(source_size) + ":" + bad_sha1_a;
ASSERT_NE(0, applypatch_check(corrupted, {}));
ASSERT_NE(0, applypatch_check(corrupted, { source_sha1 }));
TEST_F(ApplyPatchTest, PatchPartitionCheck_UseBackup_BothCorrupted) {
ASSERT_FALSE(
PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
Paths::Get().set_cache_temp_source(source_file);
ASSERT_NE(0, applypatch_check(corrupted, { bad_sha1_a, bad_sha1_b }));
Paths::Get().set_cache_temp_source(target_file);
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 {

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