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
1180 lines
41 KiB
C++
1180 lines
41 KiB
C++
/*
|
|
* Copyright (C) 2016 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/parseint.h>
|
|
#include <android-base/properties.h>
|
|
#include <android-base/stringprintf.h>
|
|
#include <android-base/strings.h>
|
|
#include <android-base/test_utils.h>
|
|
#include <bootloader_message/bootloader_message.h>
|
|
#include <brotli/encode.h>
|
|
#include <bsdiff/bsdiff.h>
|
|
#include <gtest/gtest.h>
|
|
#include <verity/hash_tree_builder.h>
|
|
#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"
|
|
#include "otautil/paths.h"
|
|
#include "otautil/print_sha1.h"
|
|
#include "otautil/sysutil.h"
|
|
#include "private/commands.h"
|
|
#include "updater/blockimg.h"
|
|
#include "updater/install.h"
|
|
#include "updater/updater.h"
|
|
|
|
using namespace std::string_literals;
|
|
|
|
using PackageEntries = std::unordered_map<std::string, std::string>;
|
|
|
|
struct selabel_handle* sehandle = nullptr;
|
|
|
|
static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code,
|
|
UpdaterInfo* info = nullptr) {
|
|
std::unique_ptr<Expr> e;
|
|
int error_count = 0;
|
|
ASSERT_EQ(0, ParseString(expr_str, &e, &error_count));
|
|
ASSERT_EQ(0, error_count);
|
|
|
|
State state(expr_str, info);
|
|
|
|
std::string result;
|
|
bool status = Evaluate(&state, e, &result);
|
|
|
|
if (expected == nullptr) {
|
|
ASSERT_FALSE(status);
|
|
} else {
|
|
ASSERT_TRUE(status) << "Evaluate() finished with error message: " << state.errmsg;
|
|
ASSERT_STREQ(expected, result.c_str());
|
|
}
|
|
|
|
// Error code is set in updater/updater.cpp only, by parsing State.errmsg.
|
|
ASSERT_EQ(kNoError, state.error_code);
|
|
|
|
// Cause code should always be available.
|
|
ASSERT_EQ(cause_code, state.cause_code);
|
|
}
|
|
|
|
static void BuildUpdatePackage(const PackageEntries& entries, int fd) {
|
|
FILE* zip_file_ptr = fdopen(fd, "wb");
|
|
ZipWriter zip_writer(zip_file_ptr);
|
|
|
|
for (const auto& entry : entries) {
|
|
// All the entries are written as STORED.
|
|
ASSERT_EQ(0, zip_writer.StartEntry(entry.first.c_str(), 0));
|
|
if (!entry.second.empty()) {
|
|
ASSERT_EQ(0, zip_writer.WriteBytes(entry.second.data(), entry.second.size()));
|
|
}
|
|
ASSERT_EQ(0, zip_writer.FinishEntry());
|
|
}
|
|
|
|
ASSERT_EQ(0, zip_writer.Finish());
|
|
ASSERT_EQ(0, fclose(zip_file_ptr));
|
|
}
|
|
|
|
static void RunBlockImageUpdate(bool is_verify, const PackageEntries& entries,
|
|
const std::string& image_file, const std::string& result,
|
|
CauseCode cause_code = kNoCause) {
|
|
CHECK(entries.find("transfer_list") != entries.end());
|
|
|
|
// Build the update package.
|
|
TemporaryFile zip_file;
|
|
BuildUpdatePackage(entries, zip_file.release());
|
|
|
|
MemMapping map;
|
|
ASSERT_TRUE(map.MapFile(zip_file.path));
|
|
ZipArchiveHandle handle;
|
|
ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
|
|
|
|
// Set up the handler, command_pipe, patch offset & length.
|
|
UpdaterInfo updater_info;
|
|
updater_info.package_zip = handle;
|
|
TemporaryFile temp_pipe;
|
|
updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
|
|
updater_info.package_zip_addr = map.addr;
|
|
updater_info.package_zip_len = map.length;
|
|
|
|
std::string new_data = entries.find("new_data.br") != entries.end() ? "new_data.br" : "new_data";
|
|
std::string script = is_verify ? "block_image_verify" : "block_image_update";
|
|
script += R"((")" + image_file + R"(", package_extract_file("transfer_list"), ")" + new_data +
|
|
R"(", "patch_data"))";
|
|
expect(result.c_str(), script, cause_code, &updater_info);
|
|
|
|
ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
|
|
CloseArchive(handle);
|
|
}
|
|
|
|
static std::string get_sha1(const std::string& content) {
|
|
uint8_t digest[SHA_DIGEST_LENGTH];
|
|
SHA1(reinterpret_cast<const uint8_t*>(content.c_str()), content.size(), digest);
|
|
return print_sha1(digest);
|
|
}
|
|
|
|
static Value* BlobToString(const char* name, State* state,
|
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
|
if (argv.size() != 1) {
|
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
|
|
}
|
|
|
|
std::vector<std::unique_ptr<Value>> args;
|
|
if (!ReadValueArgs(state, argv, &args)) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (args[0]->type != Value::Type::BLOB) {
|
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects a BLOB argument", name);
|
|
}
|
|
|
|
args[0]->type = Value::Type::STRING;
|
|
return args[0].release();
|
|
}
|
|
|
|
class UpdaterTest : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override {
|
|
RegisterBuiltins();
|
|
RegisterInstallFunctions();
|
|
RegisterBlockImageFunctions();
|
|
|
|
RegisterFunction("blob_to_string", BlobToString);
|
|
|
|
// Each test is run in a separate process (isolated mode). Shared temporary files won't cause
|
|
// conflicts.
|
|
Paths::Get().set_cache_temp_source(temp_saved_source_.path);
|
|
Paths::Get().set_last_command_file(temp_last_command_.path);
|
|
Paths::Get().set_stash_directory_base(temp_stash_base_.path);
|
|
|
|
// Enable a special command "abort" to simulate interruption.
|
|
Command::abort_allowed_ = true;
|
|
|
|
last_command_file_ = temp_last_command_.path;
|
|
image_file_ = image_temp_file_.path;
|
|
}
|
|
|
|
void TearDown() override {
|
|
// Clean up the last_command_file if any.
|
|
ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_));
|
|
|
|
// Clear partition updated marker if any.
|
|
std::string updated_marker{ temp_stash_base_.path };
|
|
updated_marker += "/" + get_sha1(image_temp_file_.path) + ".UPDATED";
|
|
ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker));
|
|
}
|
|
|
|
TemporaryFile temp_saved_source_;
|
|
TemporaryDir temp_stash_base_;
|
|
std::string last_command_file_;
|
|
std::string image_file_;
|
|
|
|
private:
|
|
TemporaryFile temp_last_command_;
|
|
TemporaryFile image_temp_file_;
|
|
};
|
|
|
|
TEST_F(UpdaterTest, getprop) {
|
|
expect(android::base::GetProperty("ro.product.device", "").c_str(),
|
|
"getprop(\"ro.product.device\")",
|
|
kNoCause);
|
|
|
|
expect(android::base::GetProperty("ro.build.fingerprint", "").c_str(),
|
|
"getprop(\"ro.build.fingerprint\")",
|
|
kNoCause);
|
|
|
|
// getprop() accepts only one parameter.
|
|
expect(nullptr, "getprop()", kArgsParsingFailure);
|
|
expect(nullptr, "getprop(\"arg1\", \"arg2\")", kArgsParsingFailure);
|
|
}
|
|
|
|
TEST_F(UpdaterTest, patch_partition_check) {
|
|
// Zero argument is not valid.
|
|
expect(nullptr, "patch_partition_check()", kArgsParsingFailure);
|
|
|
|
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 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 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);
|
|
|
|
// 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);
|
|
}
|
|
|
|
TEST_F(UpdaterTest, file_getprop) {
|
|
// file_getprop() expects two arguments.
|
|
expect(nullptr, "file_getprop()", kArgsParsingFailure);
|
|
expect(nullptr, "file_getprop(\"arg1\")", kArgsParsingFailure);
|
|
expect(nullptr, "file_getprop(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
|
|
|
|
// File doesn't exist.
|
|
expect(nullptr, "file_getprop(\"/doesntexist\", \"key1\")", kFreadFailure);
|
|
|
|
// Reject too large files (current limit = 65536).
|
|
TemporaryFile temp_file1;
|
|
std::string buffer(65540, '\0');
|
|
ASSERT_TRUE(android::base::WriteStringToFile(buffer, temp_file1.path));
|
|
|
|
// Read some keys.
|
|
TemporaryFile temp_file2;
|
|
std::string content("ro.product.name=tardis\n"
|
|
"# comment\n\n\n"
|
|
"ro.product.model\n"
|
|
"ro.product.board = magic \n");
|
|
ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file2.path));
|
|
|
|
std::string script1("file_getprop(\"" + std::string(temp_file2.path) +
|
|
"\", \"ro.product.name\")");
|
|
expect("tardis", script1, kNoCause);
|
|
|
|
std::string script2("file_getprop(\"" + std::string(temp_file2.path) +
|
|
"\", \"ro.product.board\")");
|
|
expect("magic", script2, kNoCause);
|
|
|
|
// No match.
|
|
std::string script3("file_getprop(\"" + std::string(temp_file2.path) +
|
|
"\", \"ro.product.wrong\")");
|
|
expect("", script3, kNoCause);
|
|
|
|
std::string script4("file_getprop(\"" + std::string(temp_file2.path) +
|
|
"\", \"ro.product.name=\")");
|
|
expect("", script4, kNoCause);
|
|
|
|
std::string script5("file_getprop(\"" + std::string(temp_file2.path) +
|
|
"\", \"ro.product.nam\")");
|
|
expect("", script5, kNoCause);
|
|
|
|
std::string script6("file_getprop(\"" + std::string(temp_file2.path) +
|
|
"\", \"ro.product.model\")");
|
|
expect("", script6, kNoCause);
|
|
}
|
|
|
|
// TODO: Test extracting to block device.
|
|
TEST_F(UpdaterTest, package_extract_file) {
|
|
// package_extract_file expects 1 or 2 arguments.
|
|
expect(nullptr, "package_extract_file()", kArgsParsingFailure);
|
|
expect(nullptr, "package_extract_file(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
|
|
|
|
std::string zip_path = from_testdata_base("ziptest_valid.zip");
|
|
ZipArchiveHandle handle;
|
|
ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
|
|
|
|
// Need to set up the ziphandle.
|
|
UpdaterInfo updater_info;
|
|
updater_info.package_zip = handle;
|
|
|
|
// Two-argument version.
|
|
TemporaryFile temp_file1;
|
|
std::string script("package_extract_file(\"a.txt\", \"" + std::string(temp_file1.path) + "\")");
|
|
expect("t", script, kNoCause, &updater_info);
|
|
|
|
// Verify the extracted entry.
|
|
std::string data;
|
|
ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data));
|
|
ASSERT_EQ(kATxtContents, data);
|
|
|
|
// Now extract another entry to the same location, which should overwrite.
|
|
script = "package_extract_file(\"b.txt\", \"" + std::string(temp_file1.path) + "\")";
|
|
expect("t", script, kNoCause, &updater_info);
|
|
|
|
ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data));
|
|
ASSERT_EQ(kBTxtContents, data);
|
|
|
|
// Missing zip entry. The two-argument version doesn't abort.
|
|
script = "package_extract_file(\"doesntexist\", \"" + std::string(temp_file1.path) + "\")";
|
|
expect("", script, kNoCause, &updater_info);
|
|
|
|
// Extract to /dev/full should fail.
|
|
script = "package_extract_file(\"a.txt\", \"/dev/full\")";
|
|
expect("", script, kNoCause, &updater_info);
|
|
|
|
// One-argument version. package_extract_file() gives a VAL_BLOB, which needs to be converted to
|
|
// VAL_STRING for equality test.
|
|
script = "blob_to_string(package_extract_file(\"a.txt\")) == \"" + kATxtContents + "\"";
|
|
expect("t", script, kNoCause, &updater_info);
|
|
|
|
script = "blob_to_string(package_extract_file(\"b.txt\")) == \"" + kBTxtContents + "\"";
|
|
expect("t", script, kNoCause, &updater_info);
|
|
|
|
// Missing entry. The one-argument version aborts the evaluation.
|
|
script = "package_extract_file(\"doesntexist\")";
|
|
expect(nullptr, script, kPackageExtractFileFailure, &updater_info);
|
|
|
|
CloseArchive(handle);
|
|
}
|
|
|
|
TEST_F(UpdaterTest, read_file) {
|
|
// read_file() expects one argument.
|
|
expect(nullptr, "read_file()", kArgsParsingFailure);
|
|
expect(nullptr, "read_file(\"arg1\", \"arg2\")", kArgsParsingFailure);
|
|
|
|
// Write some value to file and read back.
|
|
TemporaryFile temp_file;
|
|
std::string script("write_value(\"foo\", \""s + temp_file.path + "\");");
|
|
expect("t", script, kNoCause);
|
|
|
|
script = "read_file(\""s + temp_file.path + "\") == \"foo\"";
|
|
expect("t", script, kNoCause);
|
|
|
|
script = "read_file(\""s + temp_file.path + "\") == \"bar\"";
|
|
expect("", script, kNoCause);
|
|
|
|
// It should fail gracefully when read fails.
|
|
script = "read_file(\"/doesntexist\")";
|
|
expect("", script, kNoCause);
|
|
}
|
|
|
|
TEST_F(UpdaterTest, compute_hash_tree_smoke) {
|
|
std::string data;
|
|
for (unsigned char i = 0; i < 128; i++) {
|
|
data += std::string(4096, i);
|
|
}
|
|
// Appends an additional block for verity data.
|
|
data += std::string(4096, 0);
|
|
ASSERT_EQ(129 * 4096, data.size());
|
|
ASSERT_TRUE(android::base::WriteStringToFile(data, image_file_));
|
|
|
|
std::string salt = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7";
|
|
std::string expected_root_hash =
|
|
"7e0a8d8747f54384014ab996f5b2dc4eb7ff00c630eede7134c9e3f05c0dd8ca";
|
|
// hash_tree_ranges, source_ranges, hash_algorithm, salt_hex, root_hash
|
|
std::vector<std::string> tokens{ "compute_hash_tree", "2,128,129", "2,0,128", "sha256", salt,
|
|
expected_root_hash };
|
|
std::string hash_tree_command = android::base::Join(tokens, " ");
|
|
|
|
std::vector<std::string> transfer_list{
|
|
"4", "2", "0", "2", hash_tree_command,
|
|
};
|
|
|
|
PackageEntries entries{
|
|
{ "new_data", "" },
|
|
{ "patch_data", "" },
|
|
{ "transfer_list", android::base::Join(transfer_list, "\n") },
|
|
};
|
|
|
|
RunBlockImageUpdate(false, entries, image_file_, "t");
|
|
|
|
std::string updated;
|
|
ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated));
|
|
ASSERT_EQ(129 * 4096, updated.size());
|
|
ASSERT_EQ(data.substr(0, 128 * 4096), updated.substr(0, 128 * 4096));
|
|
|
|
// Computes the SHA256 of the salt + hash_tree_data and expects the result to match with the
|
|
// root_hash.
|
|
std::vector<unsigned char> salt_bytes;
|
|
ASSERT_TRUE(HashTreeBuilder::ParseBytesArrayFromString(salt, &salt_bytes));
|
|
std::vector<unsigned char> hash_tree = std::move(salt_bytes);
|
|
hash_tree.insert(hash_tree.end(), updated.begin() + 128 * 4096, updated.end());
|
|
|
|
std::vector<unsigned char> digest(SHA256_DIGEST_LENGTH);
|
|
SHA256(hash_tree.data(), hash_tree.size(), digest.data());
|
|
ASSERT_EQ(expected_root_hash, HashTreeBuilder::BytesArrayToString(digest));
|
|
}
|
|
|
|
TEST_F(UpdaterTest, compute_hash_tree_root_mismatch) {
|
|
std::string data;
|
|
for (size_t i = 0; i < 128; i++) {
|
|
data += std::string(4096, i);
|
|
}
|
|
// Appends an additional block for verity data.
|
|
data += std::string(4096, 0);
|
|
ASSERT_EQ(129 * 4096, data.size());
|
|
// Corrupts one bit
|
|
data[4096] = 'A';
|
|
ASSERT_TRUE(android::base::WriteStringToFile(data, image_file_));
|
|
|
|
std::string salt = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7";
|
|
std::string expected_root_hash =
|
|
"7e0a8d8747f54384014ab996f5b2dc4eb7ff00c630eede7134c9e3f05c0dd8ca";
|
|
// hash_tree_ranges, source_ranges, hash_algorithm, salt_hex, root_hash
|
|
std::vector<std::string> tokens{ "compute_hash_tree", "2,128,129", "2,0,128", "sha256", salt,
|
|
expected_root_hash };
|
|
std::string hash_tree_command = android::base::Join(tokens, " ");
|
|
|
|
std::vector<std::string> transfer_list{
|
|
"4", "2", "0", "2", hash_tree_command,
|
|
};
|
|
|
|
PackageEntries entries{
|
|
{ "new_data", "" },
|
|
{ "patch_data", "" },
|
|
{ "transfer_list", android::base::Join(transfer_list, "\n") },
|
|
};
|
|
|
|
RunBlockImageUpdate(false, entries, image_file_, "", kHashTreeComputationFailure);
|
|
}
|
|
|
|
TEST_F(UpdaterTest, write_value) {
|
|
// write_value() expects two arguments.
|
|
expect(nullptr, "write_value()", kArgsParsingFailure);
|
|
expect(nullptr, "write_value(\"arg1\")", kArgsParsingFailure);
|
|
expect(nullptr, "write_value(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
|
|
|
|
// filename cannot be empty.
|
|
expect(nullptr, "write_value(\"value\", \"\")", kArgsParsingFailure);
|
|
|
|
// Write some value to file.
|
|
TemporaryFile temp_file;
|
|
std::string value = "magicvalue";
|
|
std::string script("write_value(\"" + value + "\", \"" + std::string(temp_file.path) + "\")");
|
|
expect("t", script, kNoCause);
|
|
|
|
// Verify the content.
|
|
std::string content;
|
|
ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content));
|
|
ASSERT_EQ(value, content);
|
|
|
|
// Allow writing empty string.
|
|
script = "write_value(\"\", \"" + std::string(temp_file.path) + "\")";
|
|
expect("t", script, kNoCause);
|
|
|
|
// Verify the content.
|
|
ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content));
|
|
ASSERT_EQ("", content);
|
|
|
|
// It should fail gracefully when write fails.
|
|
script = "write_value(\"value\", \"/proc/0/file1\")";
|
|
expect("", script, kNoCause);
|
|
}
|
|
|
|
TEST_F(UpdaterTest, get_stage) {
|
|
// get_stage() expects one argument.
|
|
expect(nullptr, "get_stage()", kArgsParsingFailure);
|
|
expect(nullptr, "get_stage(\"arg1\", \"arg2\")", kArgsParsingFailure);
|
|
expect(nullptr, "get_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
|
|
|
|
// Set up a local file as BCB.
|
|
TemporaryFile tf;
|
|
std::string temp_file(tf.path);
|
|
bootloader_message boot;
|
|
strlcpy(boot.stage, "2/3", sizeof(boot.stage));
|
|
std::string err;
|
|
ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err));
|
|
|
|
// Can read the stage value.
|
|
std::string script("get_stage(\"" + temp_file + "\")");
|
|
expect("2/3", script, kNoCause);
|
|
|
|
// Bad BCB path.
|
|
script = "get_stage(\"doesntexist\")";
|
|
expect("", script, kNoCause);
|
|
}
|
|
|
|
TEST_F(UpdaterTest, set_stage) {
|
|
// set_stage() expects two arguments.
|
|
expect(nullptr, "set_stage()", kArgsParsingFailure);
|
|
expect(nullptr, "set_stage(\"arg1\")", kArgsParsingFailure);
|
|
expect(nullptr, "set_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
|
|
|
|
// Set up a local file as BCB.
|
|
TemporaryFile tf;
|
|
std::string temp_file(tf.path);
|
|
bootloader_message boot;
|
|
strlcpy(boot.command, "command", sizeof(boot.command));
|
|
strlcpy(boot.stage, "2/3", sizeof(boot.stage));
|
|
std::string err;
|
|
ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err));
|
|
|
|
// Write with set_stage().
|
|
std::string script("set_stage(\"" + temp_file + "\", \"1/3\")");
|
|
expect(tf.path, script, kNoCause);
|
|
|
|
// Verify.
|
|
bootloader_message boot_verify;
|
|
ASSERT_TRUE(read_bootloader_message_from(&boot_verify, temp_file, &err));
|
|
|
|
// Stage should be updated, with command part untouched.
|
|
ASSERT_STREQ("1/3", boot_verify.stage);
|
|
ASSERT_STREQ(boot.command, boot_verify.command);
|
|
|
|
// Bad BCB path.
|
|
script = "set_stage(\"doesntexist\", \"1/3\")";
|
|
expect("", script, kNoCause);
|
|
|
|
script = "set_stage(\"/dev/full\", \"1/3\")";
|
|
expect("", script, kNoCause);
|
|
}
|
|
|
|
TEST_F(UpdaterTest, set_progress) {
|
|
// set_progress() expects one argument.
|
|
expect(nullptr, "set_progress()", kArgsParsingFailure);
|
|
expect(nullptr, "set_progress(\"arg1\", \"arg2\")", kArgsParsingFailure);
|
|
|
|
// Invalid progress argument.
|
|
expect(nullptr, "set_progress(\"arg1\")", kArgsParsingFailure);
|
|
expect(nullptr, "set_progress(\"3x+5\")", kArgsParsingFailure);
|
|
expect(nullptr, "set_progress(\".3.5\")", kArgsParsingFailure);
|
|
|
|
TemporaryFile tf;
|
|
UpdaterInfo updater_info;
|
|
updater_info.cmd_pipe = fdopen(tf.release(), "w");
|
|
expect(".52", "set_progress(\".52\")", kNoCause, &updater_info);
|
|
fflush(updater_info.cmd_pipe);
|
|
|
|
std::string cmd;
|
|
ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd));
|
|
ASSERT_EQ(android::base::StringPrintf("set_progress %f\n", .52), cmd);
|
|
// recovery-updater protocol expects 2 tokens ("set_progress <frac>").
|
|
ASSERT_EQ(2U, android::base::Split(cmd, " ").size());
|
|
ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
|
|
}
|
|
|
|
TEST_F(UpdaterTest, show_progress) {
|
|
// show_progress() expects two arguments.
|
|
expect(nullptr, "show_progress()", kArgsParsingFailure);
|
|
expect(nullptr, "show_progress(\"arg1\")", kArgsParsingFailure);
|
|
expect(nullptr, "show_progress(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
|
|
|
|
// Invalid progress arguments.
|
|
expect(nullptr, "show_progress(\"arg1\", \"arg2\")", kArgsParsingFailure);
|
|
expect(nullptr, "show_progress(\"3x+5\", \"10\")", kArgsParsingFailure);
|
|
expect(nullptr, "show_progress(\".3\", \"5a\")", kArgsParsingFailure);
|
|
|
|
TemporaryFile tf;
|
|
UpdaterInfo updater_info;
|
|
updater_info.cmd_pipe = fdopen(tf.release(), "w");
|
|
expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_info);
|
|
fflush(updater_info.cmd_pipe);
|
|
|
|
std::string cmd;
|
|
ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd));
|
|
ASSERT_EQ(android::base::StringPrintf("progress %f %d\n", .52, 10), cmd);
|
|
// recovery-updater protocol expects 3 tokens ("progress <frac> <secs>").
|
|
ASSERT_EQ(3U, android::base::Split(cmd, " ").size());
|
|
ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
|
|
}
|
|
|
|
TEST_F(UpdaterTest, block_image_update_parsing_error) {
|
|
std::vector<std::string> transfer_list{
|
|
// clang-format off
|
|
"4",
|
|
"2",
|
|
"0",
|
|
// clang-format on
|
|
};
|
|
|
|
PackageEntries entries{
|
|
{ "new_data", "" },
|
|
{ "patch_data", "" },
|
|
{ "transfer_list", android::base::Join(transfer_list, '\n') },
|
|
};
|
|
|
|
RunBlockImageUpdate(false, entries, image_file_, "", kArgsParsingFailure);
|
|
}
|
|
|
|
TEST_F(UpdaterTest, block_image_update_patch_data) {
|
|
std::string src_content = std::string(4096, 'a') + std::string(4096, 'c');
|
|
std::string tgt_content = std::string(4096, 'b') + std::string(4096, 'd');
|
|
|
|
// Generate the patch data.
|
|
TemporaryFile patch_file;
|
|
ASSERT_EQ(0,
|
|
bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()), src_content.size(),
|
|
reinterpret_cast<const uint8_t*>(tgt_content.data()), tgt_content.size(),
|
|
patch_file.path, nullptr));
|
|
std::string patch_content;
|
|
ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content));
|
|
|
|
// Create the transfer list that contains a bsdiff.
|
|
std::string src_hash = get_sha1(src_content);
|
|
std::string tgt_hash = get_sha1(tgt_content);
|
|
std::vector<std::string> transfer_list{
|
|
// clang-format off
|
|
"4",
|
|
"2",
|
|
"0",
|
|
"2",
|
|
"stash " + src_hash + " 2,0,2",
|
|
android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,2 2 - %s:2,0,2", patch_content.size(),
|
|
src_hash.c_str(), tgt_hash.c_str(), src_hash.c_str()),
|
|
"free " + src_hash,
|
|
// clang-format on
|
|
};
|
|
|
|
PackageEntries entries{
|
|
{ "new_data", "" },
|
|
{ "patch_data", patch_content },
|
|
{ "transfer_list", android::base::Join(transfer_list, '\n') },
|
|
};
|
|
|
|
ASSERT_TRUE(android::base::WriteStringToFile(src_content, image_file_));
|
|
|
|
RunBlockImageUpdate(false, entries, image_file_, "t");
|
|
|
|
// The update_file should be patched correctly.
|
|
std::string updated_content;
|
|
ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_content));
|
|
ASSERT_EQ(tgt_content, updated_content);
|
|
}
|
|
|
|
TEST_F(UpdaterTest, block_image_update_fail) {
|
|
std::string src_content(4096 * 2, 'e');
|
|
std::string src_hash = get_sha1(src_content);
|
|
// Stash and free some blocks, then fail the update intentionally.
|
|
std::vector<std::string> transfer_list{
|
|
// clang-format off
|
|
"4",
|
|
"2",
|
|
"0",
|
|
"2",
|
|
"stash " + src_hash + " 2,0,2",
|
|
"free " + src_hash,
|
|
"abort",
|
|
// clang-format on
|
|
};
|
|
|
|
// Add a new data of 10 bytes to test the deadlock.
|
|
PackageEntries entries{
|
|
{ "new_data", std::string(10, 0) },
|
|
{ "patch_data", "" },
|
|
{ "transfer_list", android::base::Join(transfer_list, '\n') },
|
|
};
|
|
|
|
ASSERT_TRUE(android::base::WriteStringToFile(src_content, image_file_));
|
|
|
|
RunBlockImageUpdate(false, entries, image_file_, "");
|
|
|
|
// Updater generates the stash name based on the input file name.
|
|
std::string name_digest = get_sha1(image_file_);
|
|
std::string stash_base = std::string(temp_stash_base_.path) + "/" + name_digest;
|
|
ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
|
|
// Expect the stashed blocks to be freed.
|
|
ASSERT_EQ(-1, access((stash_base + src_hash).c_str(), F_OK));
|
|
ASSERT_EQ(0, rmdir(stash_base.c_str()));
|
|
}
|
|
|
|
TEST_F(UpdaterTest, new_data_over_write) {
|
|
std::vector<std::string> transfer_list{
|
|
// clang-format off
|
|
"4",
|
|
"1",
|
|
"0",
|
|
"0",
|
|
"new 2,0,1",
|
|
// clang-format on
|
|
};
|
|
|
|
// Write 4096 + 100 bytes of new data.
|
|
PackageEntries entries{
|
|
{ "new_data", std::string(4196, 0) },
|
|
{ "patch_data", "" },
|
|
{ "transfer_list", android::base::Join(transfer_list, '\n') },
|
|
};
|
|
|
|
RunBlockImageUpdate(false, entries, image_file_, "t");
|
|
}
|
|
|
|
TEST_F(UpdaterTest, new_data_short_write) {
|
|
std::vector<std::string> transfer_list{
|
|
// clang-format off
|
|
"4",
|
|
"1",
|
|
"0",
|
|
"0",
|
|
"new 2,0,1",
|
|
// clang-format on
|
|
};
|
|
|
|
PackageEntries entries{
|
|
{ "patch_data", "" },
|
|
{ "transfer_list", android::base::Join(transfer_list, '\n') },
|
|
};
|
|
|
|
// Updater should report the failure gracefully rather than stuck in deadlock.
|
|
entries["new_data"] = "";
|
|
RunBlockImageUpdate(false, entries, image_file_, "");
|
|
|
|
entries["new_data"] = std::string(10, 'a');
|
|
RunBlockImageUpdate(false, entries, image_file_, "");
|
|
|
|
// Expect to write 1 block of new data successfully.
|
|
entries["new_data"] = std::string(4096, 'a');
|
|
RunBlockImageUpdate(false, entries, image_file_, "t");
|
|
}
|
|
|
|
TEST_F(UpdaterTest, brotli_new_data) {
|
|
auto generator = []() { return rand() % 128; };
|
|
// Generate 100 blocks of random data.
|
|
std::string brotli_new_data;
|
|
brotli_new_data.reserve(4096 * 100);
|
|
generate_n(back_inserter(brotli_new_data), 4096 * 100, generator);
|
|
|
|
size_t encoded_size = BrotliEncoderMaxCompressedSize(brotli_new_data.size());
|
|
std::string encoded_data(encoded_size, 0);
|
|
ASSERT_TRUE(BrotliEncoderCompress(
|
|
BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, brotli_new_data.size(),
|
|
reinterpret_cast<const uint8_t*>(brotli_new_data.data()), &encoded_size,
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(encoded_data.data()))));
|
|
encoded_data.resize(encoded_size);
|
|
|
|
// Write a few small chunks of new data, then a large chunk, and finally a few small chunks.
|
|
// This helps us to catch potential short writes.
|
|
std::vector<std::string> transfer_list = {
|
|
"4",
|
|
"100",
|
|
"0",
|
|
"0",
|
|
"new 2,0,1",
|
|
"new 2,1,2",
|
|
"new 4,2,50,50,97",
|
|
"new 2,97,98",
|
|
"new 2,98,99",
|
|
"new 2,99,100",
|
|
};
|
|
|
|
PackageEntries entries{
|
|
{ "new_data.br", std::move(encoded_data) },
|
|
{ "patch_data", "" },
|
|
{ "transfer_list", android::base::Join(transfer_list, '\n') },
|
|
};
|
|
|
|
RunBlockImageUpdate(false, entries, image_file_, "t");
|
|
|
|
std::string updated_content;
|
|
ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_content));
|
|
ASSERT_EQ(brotli_new_data, updated_content);
|
|
}
|
|
|
|
TEST_F(UpdaterTest, last_command_update) {
|
|
std::string block1(4096, '1');
|
|
std::string block2(4096, '2');
|
|
std::string block3(4096, '3');
|
|
std::string block1_hash = get_sha1(block1);
|
|
std::string block2_hash = get_sha1(block2);
|
|
std::string block3_hash = get_sha1(block3);
|
|
|
|
// Compose the transfer list to fail the first update.
|
|
std::vector<std::string> transfer_list_fail{
|
|
// clang-format off
|
|
"4",
|
|
"2",
|
|
"0",
|
|
"2",
|
|
"stash " + block1_hash + " 2,0,1",
|
|
"move " + block1_hash + " 2,1,2 1 2,0,1",
|
|
"stash " + block3_hash + " 2,2,3",
|
|
"abort",
|
|
// clang-format on
|
|
};
|
|
|
|
// Mimic a resumed update with the same transfer commands.
|
|
std::vector<std::string> transfer_list_continue{
|
|
// clang-format off
|
|
"4",
|
|
"2",
|
|
"0",
|
|
"2",
|
|
"stash " + block1_hash + " 2,0,1",
|
|
"move " + block1_hash + " 2,1,2 1 2,0,1",
|
|
"stash " + block3_hash + " 2,2,3",
|
|
"move " + block1_hash + " 2,2,3 1 2,0,1",
|
|
// clang-format on
|
|
};
|
|
|
|
ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_));
|
|
|
|
PackageEntries entries{
|
|
{ "new_data", "" },
|
|
{ "patch_data", "" },
|
|
{ "transfer_list", android::base::Join(transfer_list_fail, '\n') },
|
|
};
|
|
|
|
// "2\nstash " + block3_hash + " 2,2,3"
|
|
std::string last_command_content =
|
|
"2\n" + transfer_list_fail[TransferList::kTransferListHeaderLines + 2];
|
|
|
|
RunBlockImageUpdate(false, entries, image_file_, "");
|
|
|
|
// Expect last_command to contain the last stash command.
|
|
std::string last_command_actual;
|
|
ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual));
|
|
EXPECT_EQ(last_command_content, last_command_actual);
|
|
|
|
std::string updated_contents;
|
|
ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_contents));
|
|
ASSERT_EQ(block1 + block1 + block3, updated_contents);
|
|
|
|
// "Resume" the update. Expect the first 'move' to be skipped but the second 'move' to be
|
|
// executed. Note that we intentionally reset the image file.
|
|
entries["transfer_list"] = android::base::Join(transfer_list_continue, '\n');
|
|
ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_));
|
|
RunBlockImageUpdate(false, entries, image_file_, "t");
|
|
|
|
ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_contents));
|
|
ASSERT_EQ(block1 + block2 + block1, updated_contents);
|
|
}
|
|
|
|
TEST_F(UpdaterTest, last_command_update_unresumable) {
|
|
std::string block1(4096, '1');
|
|
std::string block2(4096, '2');
|
|
std::string block1_hash = get_sha1(block1);
|
|
std::string block2_hash = get_sha1(block2);
|
|
|
|
// Construct an unresumable update with source blocks mismatch.
|
|
std::vector<std::string> transfer_list_unresumable{
|
|
// clang-format off
|
|
"4",
|
|
"2",
|
|
"0",
|
|
"2",
|
|
"stash " + block1_hash + " 2,0,1",
|
|
"move " + block2_hash + " 2,1,2 1 2,0,1",
|
|
// clang-format on
|
|
};
|
|
|
|
PackageEntries entries{
|
|
{ "new_data", "" },
|
|
{ "patch_data", "" },
|
|
{ "transfer_list", android::base::Join(transfer_list_unresumable, '\n') },
|
|
};
|
|
|
|
ASSERT_TRUE(android::base::WriteStringToFile(block1 + block1, image_file_));
|
|
|
|
std::string last_command_content =
|
|
"0\n" + transfer_list_unresumable[TransferList::kTransferListHeaderLines];
|
|
ASSERT_TRUE(android::base::WriteStringToFile(last_command_content, last_command_file_));
|
|
|
|
RunBlockImageUpdate(false, entries, image_file_, "");
|
|
|
|
// The last_command_file will be deleted if the update encounters an unresumable failure later.
|
|
ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
|
|
}
|
|
|
|
TEST_F(UpdaterTest, last_command_verify) {
|
|
std::string block1(4096, '1');
|
|
std::string block2(4096, '2');
|
|
std::string block3(4096, '3');
|
|
std::string block1_hash = get_sha1(block1);
|
|
std::string block2_hash = get_sha1(block2);
|
|
std::string block3_hash = get_sha1(block3);
|
|
|
|
std::vector<std::string> transfer_list_verify{
|
|
// clang-format off
|
|
"4",
|
|
"2",
|
|
"0",
|
|
"2",
|
|
"stash " + block1_hash + " 2,0,1",
|
|
"move " + block1_hash + " 2,0,1 1 2,0,1",
|
|
"move " + block1_hash + " 2,1,2 1 2,0,1",
|
|
"stash " + block3_hash + " 2,2,3",
|
|
// clang-format on
|
|
};
|
|
|
|
PackageEntries entries{
|
|
{ "new_data", "" },
|
|
{ "patch_data", "" },
|
|
{ "transfer_list", android::base::Join(transfer_list_verify, '\n') },
|
|
};
|
|
|
|
ASSERT_TRUE(android::base::WriteStringToFile(block1 + block1 + block3, image_file_));
|
|
|
|
// Last command: "move " + block1_hash + " 2,1,2 1 2,0,1"
|
|
std::string last_command_content =
|
|
"2\n" + transfer_list_verify[TransferList::kTransferListHeaderLines + 2];
|
|
|
|
// First run: expect the verification to succeed and the last_command_file is intact.
|
|
ASSERT_TRUE(android::base::WriteStringToFile(last_command_content, last_command_file_));
|
|
|
|
RunBlockImageUpdate(true, entries, image_file_, "t");
|
|
|
|
std::string last_command_actual;
|
|
ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual));
|
|
EXPECT_EQ(last_command_content, last_command_actual);
|
|
|
|
// Second run with a mismatching block image: expect the verification to succeed but
|
|
// last_command_file to be deleted; because the target blocks in the last command don't have the
|
|
// expected contents for the second move command.
|
|
ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_));
|
|
RunBlockImageUpdate(true, entries, image_file_, "t");
|
|
ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
|
|
}
|
|
|
|
class ResumableUpdaterTest : public testing::TestWithParam<size_t> {
|
|
protected:
|
|
void SetUp() override {
|
|
RegisterBuiltins();
|
|
RegisterInstallFunctions();
|
|
RegisterBlockImageFunctions();
|
|
|
|
Paths::Get().set_cache_temp_source(temp_saved_source_.path);
|
|
Paths::Get().set_last_command_file(temp_last_command_.path);
|
|
Paths::Get().set_stash_directory_base(temp_stash_base_.path);
|
|
|
|
// Enable a special command "abort" to simulate interruption.
|
|
Command::abort_allowed_ = true;
|
|
|
|
index_ = GetParam();
|
|
image_file_ = image_temp_file_.path;
|
|
last_command_file_ = temp_last_command_.path;
|
|
}
|
|
|
|
void TearDown() override {
|
|
// Clean up the last_command_file if any.
|
|
ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_));
|
|
|
|
// Clear partition updated marker if any.
|
|
std::string updated_marker{ temp_stash_base_.path };
|
|
updated_marker += "/" + get_sha1(image_temp_file_.path) + ".UPDATED";
|
|
ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker));
|
|
}
|
|
|
|
TemporaryFile temp_saved_source_;
|
|
TemporaryDir temp_stash_base_;
|
|
std::string last_command_file_;
|
|
std::string image_file_;
|
|
size_t index_;
|
|
|
|
private:
|
|
TemporaryFile temp_last_command_;
|
|
TemporaryFile image_temp_file_;
|
|
};
|
|
|
|
static std::string g_source_image;
|
|
static std::string g_target_image;
|
|
static PackageEntries g_entries;
|
|
|
|
static std::vector<std::string> GenerateTransferList() {
|
|
std::string a(4096, 'a');
|
|
std::string b(4096, 'b');
|
|
std::string c(4096, 'c');
|
|
std::string d(4096, 'd');
|
|
std::string e(4096, 'e');
|
|
std::string f(4096, 'f');
|
|
std::string g(4096, 'g');
|
|
std::string h(4096, 'h');
|
|
std::string i(4096, 'i');
|
|
std::string zero(4096, '\0');
|
|
|
|
std::string a_hash = get_sha1(a);
|
|
std::string b_hash = get_sha1(b);
|
|
std::string c_hash = get_sha1(c);
|
|
std::string e_hash = get_sha1(e);
|
|
|
|
auto loc = [](const std::string& range_text) {
|
|
std::vector<std::string> pieces = android::base::Split(range_text, "-");
|
|
size_t left;
|
|
size_t right;
|
|
if (pieces.size() == 1) {
|
|
CHECK(android::base::ParseUint(pieces[0], &left));
|
|
right = left + 1;
|
|
} else {
|
|
CHECK_EQ(2u, pieces.size());
|
|
CHECK(android::base::ParseUint(pieces[0], &left));
|
|
CHECK(android::base::ParseUint(pieces[1], &right));
|
|
right++;
|
|
}
|
|
return android::base::StringPrintf("2,%zu,%zu", left, right);
|
|
};
|
|
|
|
// patch 1: "b d c" -> "g"
|
|
TemporaryFile patch_file_bdc_g;
|
|
std::string bdc = b + d + c;
|
|
std::string bdc_hash = get_sha1(bdc);
|
|
std::string g_hash = get_sha1(g);
|
|
CHECK_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(bdc.data()), bdc.size(),
|
|
reinterpret_cast<const uint8_t*>(g.data()), g.size(),
|
|
patch_file_bdc_g.path, nullptr));
|
|
std::string patch_bdc_g;
|
|
CHECK(android::base::ReadFileToString(patch_file_bdc_g.path, &patch_bdc_g));
|
|
|
|
// patch 2: "a b c d" -> "d c b"
|
|
TemporaryFile patch_file_abcd_dcb;
|
|
std::string abcd = a + b + c + d;
|
|
std::string abcd_hash = get_sha1(abcd);
|
|
std::string dcb = d + c + b;
|
|
std::string dcb_hash = get_sha1(dcb);
|
|
CHECK_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(abcd.data()), abcd.size(),
|
|
reinterpret_cast<const uint8_t*>(dcb.data()), dcb.size(),
|
|
patch_file_abcd_dcb.path, nullptr));
|
|
std::string patch_abcd_dcb;
|
|
CHECK(android::base::ReadFileToString(patch_file_abcd_dcb.path, &patch_abcd_dcb));
|
|
|
|
std::vector<std::string> transfer_list{
|
|
"4",
|
|
"10", // total blocks written
|
|
"2", // maximum stash entries
|
|
"2", // maximum number of stashed blocks
|
|
|
|
// a b c d e a b c d e
|
|
"stash " + b_hash + " " + loc("1"),
|
|
// a b c d e a b c d e [b(1)]
|
|
"stash " + c_hash + " " + loc("2"),
|
|
// a b c d e a b c d e [b(1)][c(2)]
|
|
"new " + loc("1-2"),
|
|
// a i h d e a b c d e [b(1)][c(2)]
|
|
"zero " + loc("0"),
|
|
// 0 i h d e a b c d e [b(1)][c(2)]
|
|
|
|
// bsdiff "b d c" (from stash, 3, stash) to get g(3)
|
|
android::base::StringPrintf(
|
|
"bsdiff 0 %zu %s %s %s 3 %s %s %s:%s %s:%s",
|
|
patch_bdc_g.size(), // patch start (0), patch length
|
|
bdc_hash.c_str(), // source hash
|
|
g_hash.c_str(), // target hash
|
|
loc("3").c_str(), // target range
|
|
loc("3").c_str(), loc("1").c_str(), // load "d" from block 3, into buffer at offset 1
|
|
b_hash.c_str(), loc("0").c_str(), // load "b" from stash, into buffer at offset 0
|
|
c_hash.c_str(), loc("2").c_str()), // load "c" from stash, into buffer at offset 2
|
|
|
|
// 0 i h g e a b c d e [b(1)][c(2)]
|
|
"free " + b_hash,
|
|
// 0 i h g e a b c d e [c(2)]
|
|
"free " + a_hash,
|
|
// 0 i h g e a b c d e
|
|
"stash " + a_hash + " " + loc("5"),
|
|
// 0 i h g e a b c d e [a(5)]
|
|
"move " + e_hash + " " + loc("5") + " 1 " + loc("4"),
|
|
// 0 i h g e e b c d e [a(5)]
|
|
|
|
// bsdiff "a b c d" (from stash, 6-8) to "d c b" (6-8)
|
|
android::base::StringPrintf( //
|
|
"bsdiff %zu %zu %s %s %s 4 %s %s %s:%s",
|
|
patch_bdc_g.size(), // patch start
|
|
patch_bdc_g.size() + patch_abcd_dcb.size(), // patch length
|
|
abcd_hash.c_str(), // source hash
|
|
dcb_hash.c_str(), // target hash
|
|
loc("6-8").c_str(), // target range
|
|
loc("6-8").c_str(), // load "b c d" from blocks 6-8
|
|
loc("1-3").c_str(), // into buffer at offset 1-3
|
|
a_hash.c_str(), // load "a" from stash
|
|
loc("0").c_str()), // into buffer at offset 0
|
|
|
|
// 0 i h g e e d c b e [a(5)]
|
|
"new " + loc("4"),
|
|
// 0 i h g f e d c b e [a(5)]
|
|
"move " + a_hash + " " + loc("9") + " 1 - " + a_hash + ":" + loc("0"),
|
|
// 0 i h g f e d c b a [a(5)]
|
|
"free " + a_hash,
|
|
// 0 i h g f e d c b a
|
|
};
|
|
|
|
std::string new_data = i + h + f;
|
|
std::string patch_data = patch_bdc_g + patch_abcd_dcb;
|
|
|
|
g_entries = {
|
|
{ "new_data", new_data },
|
|
{ "patch_data", patch_data },
|
|
};
|
|
g_source_image = a + b + c + d + e + a + b + c + d + e;
|
|
g_target_image = zero + i + h + g + f + e + d + c + b + a;
|
|
|
|
return transfer_list;
|
|
}
|
|
|
|
static const std::vector<std::string> g_transfer_list = GenerateTransferList();
|
|
|
|
INSTANTIATE_TEST_CASE_P(InterruptAfterEachCommand, ResumableUpdaterTest,
|
|
::testing::Range(static_cast<size_t>(0),
|
|
g_transfer_list.size() -
|
|
TransferList::kTransferListHeaderLines));
|
|
|
|
TEST_P(ResumableUpdaterTest, InterruptVerifyResume) {
|
|
ASSERT_TRUE(android::base::WriteStringToFile(g_source_image, image_file_));
|
|
|
|
LOG(INFO) << "Interrupting at line " << index_ << " ("
|
|
<< g_transfer_list[TransferList::kTransferListHeaderLines + index_] << ")";
|
|
|
|
std::vector<std::string> transfer_list_copy{ g_transfer_list };
|
|
transfer_list_copy[TransferList::kTransferListHeaderLines + index_] = "abort";
|
|
|
|
g_entries["transfer_list"] = android::base::Join(transfer_list_copy, '\n');
|
|
|
|
// Run update that's expected to fail.
|
|
RunBlockImageUpdate(false, g_entries, image_file_, "");
|
|
|
|
std::string last_command_expected;
|
|
|
|
// Assert the last_command_file.
|
|
if (index_ == 0) {
|
|
ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
|
|
} else {
|
|
last_command_expected = std::to_string(index_ - 1) + "\n" +
|
|
g_transfer_list[TransferList::kTransferListHeaderLines + index_ - 1];
|
|
std::string last_command_actual;
|
|
ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual));
|
|
ASSERT_EQ(last_command_expected, last_command_actual);
|
|
}
|
|
|
|
g_entries["transfer_list"] = android::base::Join(g_transfer_list, '\n');
|
|
|
|
// Resume the interrupted update, by doing verification first.
|
|
RunBlockImageUpdate(true, g_entries, image_file_, "t");
|
|
|
|
// last_command_file should remain intact.
|
|
if (index_ == 0) {
|
|
ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
|
|
} else {
|
|
std::string last_command_actual;
|
|
ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual));
|
|
ASSERT_EQ(last_command_expected, last_command_actual);
|
|
}
|
|
|
|
// Resume the update.
|
|
RunBlockImageUpdate(false, g_entries, image_file_, "t");
|
|
|
|
// last_command_file should be gone after successful update.
|
|
ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
|
|
|
|
std::string updated_image_actual;
|
|
ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_image_actual));
|
|
ASSERT_EQ(g_target_image, updated_image_actual);
|
|
}
|