This will be used for testing purpose only, replacing the previously used "fail", to intentionally abort an update. As we're separating the logic between commands parsing and execution, "abort" needs to be considered as a valid command during the parsing. Test: recovery_unit_test and recovery_component_test on marlin. Change-Id: I47c41c423e62c41cc8515fd92f3c5959be08da02
1084 lines
38 KiB
C++
1084 lines
38 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 <ziparchive/zip_archive.h>
|
|
#include <ziparchive/zip_writer.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 PackageEntries = std::unordered_map<std::string, std::string>;
|
|
|
|
static constexpr size_t kTransferListHeaderLines = 4;
|
|
|
|
struct selabel_handle* sehandle = nullptr;
|
|
|
|
static void expect(const char* expected, const char* expr_str, CauseCode cause_code,
|
|
UpdaterInfo* info = nullptr) {
|
|
std::unique_ptr<Expr> e;
|
|
int error_count = 0;
|
|
ASSERT_EQ(0, parse_string(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);
|
|
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.c_str(), 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);
|
|
}
|
|
|
|
class UpdaterTest : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override {
|
|
RegisterBuiltins();
|
|
RegisterInstallFunctions();
|
|
RegisterBlockImageFunctions();
|
|
|
|
// 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, sha1_check) {
|
|
// sha1_check(data) returns the SHA-1 of the data.
|
|
expect("81fe8bfe87576c3ecb22426f8e57847382917acf", "sha1_check(\"abcd\")", kNoCause);
|
|
expect("da39a3ee5e6b4b0d3255bfef95601890afd80709", "sha1_check(\"\")", kNoCause);
|
|
|
|
// sha1_check(data, sha1_hex, [sha1_hex, ...]) returns the matched SHA-1.
|
|
expect("81fe8bfe87576c3ecb22426f8e57847382917acf",
|
|
"sha1_check(\"abcd\", \"81fe8bfe87576c3ecb22426f8e57847382917acf\")",
|
|
kNoCause);
|
|
|
|
expect("81fe8bfe87576c3ecb22426f8e57847382917acf",
|
|
"sha1_check(\"abcd\", \"wrong_sha1\", \"81fe8bfe87576c3ecb22426f8e57847382917acf\")",
|
|
kNoCause);
|
|
|
|
// Or "" if there's no match.
|
|
expect("",
|
|
"sha1_check(\"abcd\", \"wrong_sha1\")",
|
|
kNoCause);
|
|
|
|
expect("",
|
|
"sha1_check(\"abcd\", \"wrong_sha1\", \"wrong_sha2\")",
|
|
kNoCause);
|
|
|
|
// sha1_check() expects at least one argument.
|
|
expect(nullptr, "sha1_check()", kArgsParsingFailure);
|
|
}
|
|
|
|
TEST_F(UpdaterTest, apply_patch_check) {
|
|
// Zero-argument is not valid.
|
|
expect(nullptr, "apply_patch_check()", kArgsParsingFailure);
|
|
|
|
// File not found.
|
|
expect("", "apply_patch_check(\"/doesntexist\")", kNoCause);
|
|
|
|
std::string src_file = from_testdata_base("old.file");
|
|
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);
|
|
|
|
// 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 + "\")";
|
|
expect("t", cmd.c_str(), 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 + "\")";
|
|
expect("", cmd.c_str(), 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.c_str(), kNoCause);
|
|
|
|
// Multiple arguments.
|
|
cmd = "apply_patch_check(\"" + filename + "\", \"wrong_sha1\", \"wrong_sha2\")";
|
|
expect("", cmd.c_str(), kNoCause);
|
|
|
|
cmd = "apply_patch_check(\"" + filename + "\", \"wrong_sha1\", \"" + src_hash +
|
|
"\", \"wrong_sha2\")";
|
|
expect("t", cmd.c_str(), kNoCause);
|
|
|
|
cmd = "apply_patch_check(\"" + filename_bad + "\", \"wrong_sha1\", \"" + src_hash +
|
|
"\", \"wrong_sha2\")";
|
|
expect("t", cmd.c_str(), 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\")", kFileGetPropFailure);
|
|
|
|
// 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.c_str(), kNoCause);
|
|
|
|
std::string script2("file_getprop(\"" + std::string(temp_file2.path) +
|
|
"\", \"ro.product.board\")");
|
|
expect("magic", script2.c_str(), kNoCause);
|
|
|
|
// No match.
|
|
std::string script3("file_getprop(\"" + std::string(temp_file2.path) +
|
|
"\", \"ro.product.wrong\")");
|
|
expect("", script3.c_str(), kNoCause);
|
|
|
|
std::string script4("file_getprop(\"" + std::string(temp_file2.path) +
|
|
"\", \"ro.product.name=\")");
|
|
expect("", script4.c_str(), kNoCause);
|
|
|
|
std::string script5("file_getprop(\"" + std::string(temp_file2.path) +
|
|
"\", \"ro.product.nam\")");
|
|
expect("", script5.c_str(), kNoCause);
|
|
|
|
std::string script6("file_getprop(\"" + std::string(temp_file2.path) +
|
|
"\", \"ro.product.model\")");
|
|
expect("", script6.c_str(), 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.c_str(), 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.c_str(), 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.c_str(), kNoCause, &updater_info);
|
|
|
|
// Extract to /dev/full should fail.
|
|
script = "package_extract_file(\"a.txt\", \"/dev/full\")";
|
|
expect("", script.c_str(), kNoCause, &updater_info);
|
|
|
|
// One-argument version.
|
|
script = "sha1_check(package_extract_file(\"a.txt\"))";
|
|
expect(kATxtSha1Sum.c_str(), script.c_str(), kNoCause, &updater_info);
|
|
|
|
script = "sha1_check(package_extract_file(\"b.txt\"))";
|
|
expect(kBTxtSha1Sum.c_str(), script.c_str(), kNoCause, &updater_info);
|
|
|
|
// Missing entry. The one-argument version aborts the evaluation.
|
|
script = "package_extract_file(\"doesntexist\")";
|
|
expect(nullptr, script.c_str(), kPackageExtractFileFailure, &updater_info);
|
|
|
|
CloseArchive(handle);
|
|
}
|
|
|
|
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.c_str(), 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.c_str(), 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.c_str(), 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.c_str(), kNoCause);
|
|
|
|
// Bad BCB path.
|
|
script = "get_stage(\"doesntexist\")";
|
|
expect("", script.c_str(), 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.c_str(), 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.c_str(), kNoCause);
|
|
|
|
script = "set_stage(\"/dev/full\", \"1/3\")";
|
|
expect("", script.c_str(), 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[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[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[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() - kTransferListHeaderLines));
|
|
|
|
TEST_P(ResumableUpdaterTest, InterruptVerifyResume) {
|
|
ASSERT_TRUE(android::base::WriteStringToFile(g_source_image, image_file_));
|
|
|
|
LOG(INFO) << "Interrupting at line " << index_ << " ("
|
|
<< g_transfer_list[kTransferListHeaderLines + index_] << ")";
|
|
|
|
std::vector<std::string> transfer_list_copy{ g_transfer_list };
|
|
transfer_list_copy[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[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);
|
|
}
|