Merge AOSP android-9.0.0_r3
Fix conflicts and make it build in 5.1, 6.0, 7.1, 8.1, and 9.0 Change-Id: Ida0a64c29ff27d339b7f42a18d820930964ac6e4
This commit is contained in:
@@ -30,12 +30,16 @@
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/test_utils.h>
|
||||
#include <bsdiff/bsdiff.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include "applypatch/applypatch.h"
|
||||
#include "applypatch/applypatch_modes.h"
|
||||
#include "common/test_constants.h"
|
||||
#include "print_sha1.h"
|
||||
#include "otautil/cache_location.h"
|
||||
#include "otautil/print_sha1.h"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
static void sha1sum(const std::string& fname, std::string* sha1, size_t* fsize = nullptr) {
|
||||
ASSERT_NE(nullptr, sha1);
|
||||
@@ -53,34 +57,20 @@ static void sha1sum(const std::string& fname, std::string* sha1, size_t* fsize =
|
||||
}
|
||||
|
||||
static void mangle_file(const std::string& fname) {
|
||||
std::string content;
|
||||
content.reserve(1024);
|
||||
std::string content(1024, '\0');
|
||||
for (size_t i = 0; i < 1024; i++) {
|
||||
content[i] = rand() % 256;
|
||||
}
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(content, fname));
|
||||
}
|
||||
|
||||
static bool file_cmp(const std::string& f1, const std::string& f2) {
|
||||
std::string c1;
|
||||
android::base::ReadFileToString(f1, &c1);
|
||||
std::string c2;
|
||||
android::base::ReadFileToString(f2, &c2);
|
||||
return c1 == c2;
|
||||
}
|
||||
|
||||
class ApplyPatchTest : public ::testing::Test {
|
||||
public:
|
||||
static void SetUpTestCase() {
|
||||
virtual void SetUp() override {
|
||||
// set up files
|
||||
old_file = from_testdata_base("old.file");
|
||||
new_file = from_testdata_base("new.file");
|
||||
patch_file = from_testdata_base("patch.bsdiff");
|
||||
rand_file = "/cache/applypatch_test_rand.file";
|
||||
cache_file = "/cache/saved.file";
|
||||
|
||||
// write stuff to rand_file
|
||||
ASSERT_TRUE(android::base::WriteStringToFile("hello", rand_file));
|
||||
nonexistent_file = from_testdata_base("nonexistent.file");
|
||||
|
||||
// set up SHA constants
|
||||
sha1sum(old_file, &old_sha1, &old_size);
|
||||
@@ -90,56 +80,35 @@ class ApplyPatchTest : public ::testing::Test {
|
||||
bad_sha1_b = android::base::StringPrintf("%040x", rand());
|
||||
}
|
||||
|
||||
static std::string old_file;
|
||||
static std::string new_file;
|
||||
static std::string rand_file;
|
||||
static std::string cache_file;
|
||||
static std::string patch_file;
|
||||
std::string old_file;
|
||||
std::string new_file;
|
||||
std::string nonexistent_file;
|
||||
|
||||
static std::string old_sha1;
|
||||
static std::string new_sha1;
|
||||
static std::string bad_sha1_a;
|
||||
static std::string bad_sha1_b;
|
||||
std::string old_sha1;
|
||||
std::string new_sha1;
|
||||
std::string bad_sha1_a;
|
||||
std::string bad_sha1_b;
|
||||
|
||||
static size_t old_size;
|
||||
static size_t new_size;
|
||||
size_t old_size;
|
||||
size_t new_size;
|
||||
};
|
||||
|
||||
static void cp(const std::string& src, const std::string& tgt) {
|
||||
std::string cmd = "cp " + src + " " + tgt;
|
||||
system(cmd.c_str());
|
||||
}
|
||||
|
||||
static void backup_old() {
|
||||
cp(ApplyPatchTest::old_file, ApplyPatchTest::cache_file);
|
||||
}
|
||||
|
||||
static void restore_old() {
|
||||
cp(ApplyPatchTest::cache_file, ApplyPatchTest::old_file);
|
||||
}
|
||||
|
||||
class ApplyPatchCacheTest : public ApplyPatchTest {
|
||||
public:
|
||||
virtual void SetUp() {
|
||||
backup_old();
|
||||
}
|
||||
|
||||
virtual void TearDown() {
|
||||
restore_old();
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ApplyPatchTest::SetUp();
|
||||
CacheLocation::location().set_cache_temp_source(old_file);
|
||||
}
|
||||
};
|
||||
|
||||
std::string ApplyPatchTest::old_file;
|
||||
std::string ApplyPatchTest::new_file;
|
||||
std::string ApplyPatchTest::rand_file;
|
||||
std::string ApplyPatchTest::patch_file;
|
||||
std::string ApplyPatchTest::cache_file;
|
||||
std::string ApplyPatchTest::old_sha1;
|
||||
std::string ApplyPatchTest::new_sha1;
|
||||
std::string ApplyPatchTest::bad_sha1_a;
|
||||
std::string ApplyPatchTest::bad_sha1_b;
|
||||
size_t ApplyPatchTest::old_size;
|
||||
size_t ApplyPatchTest::new_size;
|
||||
class ApplyPatchModesTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
CacheLocation::location().set_cache_temp_source(cache_source.path);
|
||||
}
|
||||
|
||||
TemporaryFile cache_source;
|
||||
};
|
||||
|
||||
TEST_F(ApplyPatchTest, CheckModeSkip) {
|
||||
std::vector<std::string> sha1s;
|
||||
@@ -197,43 +166,31 @@ TEST_F(ApplyPatchTest, CheckModeEmmcTarget) {
|
||||
ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSingle) {
|
||||
mangle_file(old_file);
|
||||
std::vector<std::string> sha1s = { old_sha1 };
|
||||
ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
|
||||
TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSourceSingle) {
|
||||
TemporaryFile temp_file;
|
||||
mangle_file(temp_file.path);
|
||||
std::vector<std::string> sha1s_single = { old_sha1 };
|
||||
ASSERT_EQ(0, applypatch_check(temp_file.path, sha1s_single));
|
||||
ASSERT_EQ(0, applypatch_check(nonexistent_file.c_str(), sha1s_single));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedMultiple) {
|
||||
mangle_file(old_file);
|
||||
std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b };
|
||||
ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
|
||||
TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSourceMultiple) {
|
||||
TemporaryFile temp_file;
|
||||
mangle_file(temp_file.path);
|
||||
std::vector<std::string> sha1s_multiple = { bad_sha1_a, old_sha1, bad_sha1_b };
|
||||
ASSERT_EQ(0, applypatch_check(temp_file.path, sha1s_multiple));
|
||||
ASSERT_EQ(0, applypatch_check(nonexistent_file.c_str(), sha1s_multiple));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedFailure) {
|
||||
mangle_file(old_file);
|
||||
std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b };
|
||||
ASSERT_NE(0, applypatch_check(&old_file[0], sha1s));
|
||||
TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSourceFailure) {
|
||||
TemporaryFile temp_file;
|
||||
mangle_file(temp_file.path);
|
||||
std::vector<std::string> sha1s_failure = { bad_sha1_a, bad_sha1_b };
|
||||
ASSERT_NE(0, applypatch_check(temp_file.path, sha1s_failure));
|
||||
ASSERT_NE(0, applypatch_check(nonexistent_file.c_str(), sha1s_failure));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchCacheTest, CheckCacheMissingSingle) {
|
||||
unlink(&old_file[0]);
|
||||
std::vector<std::string> sha1s = { old_sha1 };
|
||||
ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchCacheTest, CheckCacheMissingMultiple) {
|
||||
unlink(&old_file[0]);
|
||||
std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b };
|
||||
ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchCacheTest, CheckCacheMissingFailure) {
|
||||
unlink(&old_file[0]);
|
||||
std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b };
|
||||
ASSERT_NE(0, applypatch_check(&old_file[0], sha1s));
|
||||
}
|
||||
|
||||
TEST(ApplyPatchModesTest, InvalidArgs) {
|
||||
TEST_F(ApplyPatchModesTest, InvalidArgs) {
|
||||
// At least two args (including the filename).
|
||||
ASSERT_EQ(2, applypatch_modes(1, (const char* []){ "applypatch" }));
|
||||
|
||||
@@ -241,7 +198,7 @@ TEST(ApplyPatchModesTest, InvalidArgs) {
|
||||
ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-x" }));
|
||||
}
|
||||
|
||||
TEST(ApplyPatchModesTest, PatchModeEmmcTarget) {
|
||||
TEST_F(ApplyPatchModesTest, PatchModeEmmcTarget) {
|
||||
std::string boot_img = from_testdata_base("boot.img");
|
||||
size_t boot_img_size;
|
||||
std::string boot_img_sha1;
|
||||
@@ -311,7 +268,55 @@ TEST(ApplyPatchModesTest, PatchModeEmmcTarget) {
|
||||
ASSERT_EQ(0, applypatch_modes(args3.size(), args3.data()));
|
||||
}
|
||||
|
||||
TEST(ApplyPatchModesTest, PatchModeInvalidArgs) {
|
||||
// Ensures that applypatch works with a bsdiff based recovery-from-boot.p.
|
||||
TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithBsdiffPatch) {
|
||||
std::string boot_img_file = from_testdata_base("boot.img");
|
||||
std::string boot_img_sha1;
|
||||
size_t boot_img_size;
|
||||
sha1sum(boot_img_file, &boot_img_sha1, &boot_img_size);
|
||||
|
||||
std::string recovery_img_file = from_testdata_base("recovery.img");
|
||||
std::string recovery_img_sha1;
|
||||
size_t recovery_img_size;
|
||||
sha1sum(recovery_img_file, &recovery_img_sha1, &recovery_img_size);
|
||||
|
||||
// Generate the bsdiff patch of recovery-from-boot.p.
|
||||
std::string src_content;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(boot_img_file, &src_content));
|
||||
|
||||
std::string tgt_content;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(recovery_img_file, &tgt_content));
|
||||
|
||||
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));
|
||||
|
||||
// applypatch <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch>
|
||||
std::string src_file_arg =
|
||||
"EMMC:" + boot_img_file + ":" + std::to_string(boot_img_size) + ":" + boot_img_sha1;
|
||||
TemporaryFile tgt_file;
|
||||
std::string tgt_file_arg = "EMMC:"s + tgt_file.path;
|
||||
std::string recovery_img_size_arg = std::to_string(recovery_img_size);
|
||||
std::string patch_arg = boot_img_sha1 + ":" + patch_file.path;
|
||||
std::vector<const char*> args = { "applypatch",
|
||||
src_file_arg.c_str(),
|
||||
tgt_file_arg.c_str(),
|
||||
recovery_img_sha1.c_str(),
|
||||
recovery_img_size_arg.c_str(),
|
||||
patch_arg.c_str() };
|
||||
ASSERT_EQ(0, applypatch_modes(args.size(), args.data()));
|
||||
|
||||
// Double check the patched recovery image.
|
||||
std::string tgt_file_sha1;
|
||||
size_t tgt_file_size;
|
||||
sha1sum(tgt_file.path, &tgt_file_sha1, &tgt_file_size);
|
||||
ASSERT_EQ(recovery_img_size, tgt_file_size);
|
||||
ASSERT_EQ(recovery_img_sha1, tgt_file_sha1);
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchModesTest, PatchModeInvalidArgs) {
|
||||
// Invalid bonus file.
|
||||
ASSERT_NE(0, applypatch_modes(3, (const char* []){ "applypatch", "-b", "/doesntexist" }));
|
||||
|
||||
@@ -372,11 +377,11 @@ TEST(ApplyPatchModesTest, PatchModeInvalidArgs) {
|
||||
ASSERT_NE(0, applypatch_modes(args6.size(), args6.data()));
|
||||
}
|
||||
|
||||
TEST(ApplyPatchModesTest, CheckModeInvalidArgs) {
|
||||
TEST_F(ApplyPatchModesTest, CheckModeInvalidArgs) {
|
||||
// Insufficient args.
|
||||
ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-c" }));
|
||||
}
|
||||
|
||||
TEST(ApplyPatchModesTest, ShowLicenses) {
|
||||
TEST_F(ApplyPatchModesTest, ShowLicenses) {
|
||||
ASSERT_EQ(0, applypatch_modes(2, (const char* []){ "applypatch", "-l" }));
|
||||
}
|
||||
|
||||
@@ -18,53 +18,12 @@
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/strings.h>
|
||||
#include <android-base/test_utils.h>
|
||||
#include <bootloader_message/bootloader_message.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
class BootloaderMessageTest : public ::testing::Test {
|
||||
protected:
|
||||
BootloaderMessageTest() : has_misc(true) {}
|
||||
|
||||
virtual void SetUp() override {
|
||||
std::string err;
|
||||
has_misc = !get_bootloader_message_blk_device(&err).empty();
|
||||
}
|
||||
|
||||
virtual void TearDown() override {
|
||||
// Clear the BCB.
|
||||
if (has_misc) {
|
||||
std::string err;
|
||||
ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
|
||||
}
|
||||
}
|
||||
|
||||
bool has_misc;
|
||||
};
|
||||
|
||||
TEST_F(BootloaderMessageTest, clear_bootloader_message) {
|
||||
if (!has_misc) {
|
||||
GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the BCB.
|
||||
std::string err;
|
||||
ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
|
||||
|
||||
// Verify the content.
|
||||
bootloader_message boot;
|
||||
ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
|
||||
|
||||
// All the bytes should be cleared.
|
||||
ASSERT_EQ(std::string(sizeof(boot), '\0'),
|
||||
std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)));
|
||||
}
|
||||
|
||||
TEST_F(BootloaderMessageTest, read_and_write_bootloader_message) {
|
||||
if (!has_misc) {
|
||||
GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
|
||||
return;
|
||||
}
|
||||
TEST(BootloaderMessageTest, read_and_write_bootloader_message) {
|
||||
TemporaryFile temp_misc;
|
||||
|
||||
// Write the BCB.
|
||||
bootloader_message boot = {};
|
||||
@@ -73,90 +32,71 @@ TEST_F(BootloaderMessageTest, read_and_write_bootloader_message) {
|
||||
strlcpy(boot.status, "status1", sizeof(boot.status));
|
||||
|
||||
std::string err;
|
||||
ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err;
|
||||
ASSERT_TRUE(write_bootloader_message_to(boot, temp_misc.path, &err))
|
||||
<< "Failed to write BCB: " << err;
|
||||
|
||||
// Read and verify.
|
||||
bootloader_message boot_verify;
|
||||
ASSERT_TRUE(read_bootloader_message(&boot_verify, &err)) << "Failed to read BCB: " << err;
|
||||
ASSERT_TRUE(read_bootloader_message_from(&boot_verify, temp_misc.path, &err))
|
||||
<< "Failed to read BCB: " << err;
|
||||
|
||||
ASSERT_EQ(std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)),
|
||||
std::string(reinterpret_cast<const char*>(&boot_verify), sizeof(boot_verify)));
|
||||
}
|
||||
|
||||
TEST_F(BootloaderMessageTest, write_bootloader_message_options) {
|
||||
if (!has_misc) {
|
||||
GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
|
||||
return;
|
||||
}
|
||||
|
||||
TEST(BootloaderMessageTest, update_bootloader_message_in_struct) {
|
||||
// Write the options to BCB.
|
||||
std::vector<std::string> options = { "option1", "option2" };
|
||||
std::string err;
|
||||
ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
|
||||
|
||||
// Inject some bytes into boot, which should be overwritten while reading.
|
||||
bootloader_message boot;
|
||||
bootloader_message boot = {};
|
||||
// Inject some bytes into boot.
|
||||
strlcpy(boot.recovery, "random message", sizeof(boot.recovery));
|
||||
strlcpy(boot.status, "status bytes", sizeof(boot.status));
|
||||
strlcpy(boot.stage, "stage bytes", sizeof(boot.stage));
|
||||
strlcpy(boot.reserved, "reserved bytes", sizeof(boot.reserved));
|
||||
|
||||
ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
|
||||
ASSERT_TRUE(update_bootloader_message_in_struct(&boot, options));
|
||||
|
||||
// Verify that command and recovery fields should be set.
|
||||
ASSERT_EQ("boot-recovery", std::string(boot.command));
|
||||
std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
|
||||
ASSERT_EQ(expected, std::string(boot.recovery));
|
||||
|
||||
// The rest should be cleared.
|
||||
ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
|
||||
ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
|
||||
ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
|
||||
std::string(boot.reserved, sizeof(boot.reserved)));
|
||||
// The rest should be intact.
|
||||
ASSERT_EQ("status bytes", std::string(boot.status));
|
||||
ASSERT_EQ("stage bytes", std::string(boot.stage));
|
||||
ASSERT_EQ("reserved bytes", std::string(boot.reserved));
|
||||
}
|
||||
|
||||
TEST_F(BootloaderMessageTest, write_bootloader_message_options_empty) {
|
||||
if (!has_misc) {
|
||||
GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
|
||||
return;
|
||||
}
|
||||
|
||||
TEST(BootloaderMessageTest, update_bootloader_message_recovery_options_empty) {
|
||||
// Write empty vector.
|
||||
std::vector<std::string> options;
|
||||
std::string err;
|
||||
ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
|
||||
|
||||
// Read and verify.
|
||||
bootloader_message boot;
|
||||
ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
|
||||
bootloader_message boot = {};
|
||||
ASSERT_TRUE(update_bootloader_message_in_struct(&boot, options));
|
||||
|
||||
// command and recovery fields should be set.
|
||||
ASSERT_EQ("boot-recovery", std::string(boot.command));
|
||||
ASSERT_EQ("recovery\n", std::string(boot.recovery));
|
||||
|
||||
// The rest should be cleared.
|
||||
// The rest should be empty.
|
||||
ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
|
||||
ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
|
||||
ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
|
||||
std::string(boot.reserved, sizeof(boot.reserved)));
|
||||
}
|
||||
|
||||
TEST_F(BootloaderMessageTest, write_bootloader_message_options_long) {
|
||||
if (!has_misc) {
|
||||
GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
|
||||
return;
|
||||
}
|
||||
|
||||
TEST(BootloaderMessageTest, update_bootloader_message_recovery_options_long) {
|
||||
// Write super long message.
|
||||
std::vector<std::string> options;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
options.push_back("option: " + std::to_string(i));
|
||||
}
|
||||
|
||||
std::string err;
|
||||
ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
|
||||
|
||||
// Read and verify.
|
||||
bootloader_message boot;
|
||||
ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
|
||||
bootloader_message boot = {};
|
||||
ASSERT_TRUE(update_bootloader_message_in_struct(&boot, options));
|
||||
|
||||
// Make sure it's long enough.
|
||||
std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
|
||||
@@ -167,40 +107,10 @@ TEST_F(BootloaderMessageTest, write_bootloader_message_options_long) {
|
||||
ASSERT_EQ(expected.substr(0, sizeof(boot.recovery) - 1), std::string(boot.recovery));
|
||||
ASSERT_EQ('\0', boot.recovery[sizeof(boot.recovery) - 1]);
|
||||
|
||||
// The rest should be cleared.
|
||||
// The rest should be empty.
|
||||
ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
|
||||
ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
|
||||
ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
|
||||
std::string(boot.reserved, sizeof(boot.reserved)));
|
||||
}
|
||||
|
||||
TEST_F(BootloaderMessageTest, update_bootloader_message) {
|
||||
if (!has_misc) {
|
||||
GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject some bytes into boot, which should be not overwritten later.
|
||||
bootloader_message boot;
|
||||
strlcpy(boot.recovery, "random message", sizeof(boot.recovery));
|
||||
strlcpy(boot.reserved, "reserved bytes", sizeof(boot.reserved));
|
||||
std::string err;
|
||||
ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err;
|
||||
|
||||
// Update the BCB message.
|
||||
std::vector<std::string> options = { "option1", "option2" };
|
||||
ASSERT_TRUE(update_bootloader_message(options, &err)) << "Failed to update BCB: " << err;
|
||||
|
||||
bootloader_message boot_verify;
|
||||
ASSERT_TRUE(read_bootloader_message(&boot_verify, &err)) << "Failed to read BCB: " << err;
|
||||
|
||||
// Verify that command and recovery fields should be set.
|
||||
ASSERT_EQ("boot-recovery", std::string(boot_verify.command));
|
||||
std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
|
||||
ASSERT_EQ(expected, std::string(boot_verify.recovery));
|
||||
|
||||
// The rest should be intact.
|
||||
ASSERT_EQ(std::string(boot.status), std::string(boot_verify.status));
|
||||
ASSERT_EQ(std::string(boot.stage), std::string(boot_verify.stage));
|
||||
ASSERT_EQ(std::string(boot.reserved), std::string(boot_verify.reserved));
|
||||
}
|
||||
|
||||
@@ -16,17 +16,24 @@
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/memory.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <android-base/test_utils.h>
|
||||
#include <applypatch/imgdiff.h>
|
||||
#include <applypatch/imgdiff_image.h>
|
||||
#include <applypatch/imgpatch.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <ziparchive/zip_writer.h>
|
||||
|
||||
#include "common/test_constants.h"
|
||||
|
||||
using android::base::get_unaligned;
|
||||
|
||||
// Sanity check for the given imgdiff patch header.
|
||||
@@ -75,15 +82,20 @@ static void verify_patch_header(const std::string& patch, size_t* num_normal, si
|
||||
if (num_deflate != nullptr) *num_deflate = deflate;
|
||||
}
|
||||
|
||||
static void GenerateTarget(const std::string& src, const std::string& patch, std::string* patched) {
|
||||
patched->clear();
|
||||
ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
|
||||
reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
|
||||
[&](const unsigned char* data, size_t len) {
|
||||
patched->append(reinterpret_cast<const char*>(data), len);
|
||||
return len;
|
||||
}));
|
||||
}
|
||||
|
||||
static void verify_patched_image(const std::string& src, const std::string& patch,
|
||||
const std::string& tgt) {
|
||||
std::string patched;
|
||||
ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
|
||||
reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
|
||||
[&patched](const unsigned char* data, size_t len) {
|
||||
patched.append(reinterpret_cast<const char*>(data), len);
|
||||
return len;
|
||||
}));
|
||||
GenerateTarget(src, patch, &patched);
|
||||
ASSERT_EQ(tgt, patched);
|
||||
}
|
||||
|
||||
@@ -138,7 +150,7 @@ TEST(ImgdiffTest, image_mode_smoke) {
|
||||
TEST(ImgdiffTest, zip_mode_smoke_store) {
|
||||
// Construct src and tgt zip files.
|
||||
TemporaryFile src_file;
|
||||
FILE* src_file_ptr = fdopen(src_file.fd, "wb");
|
||||
FILE* src_file_ptr = fdopen(src_file.release(), "wb");
|
||||
ZipWriter src_writer(src_file_ptr);
|
||||
ASSERT_EQ(0, src_writer.StartEntry("file1.txt", 0)); // Store mode.
|
||||
const std::string src_content("abcdefg");
|
||||
@@ -148,7 +160,7 @@ TEST(ImgdiffTest, zip_mode_smoke_store) {
|
||||
ASSERT_EQ(0, fclose(src_file_ptr));
|
||||
|
||||
TemporaryFile tgt_file;
|
||||
FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
|
||||
FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
|
||||
ZipWriter tgt_writer(tgt_file_ptr);
|
||||
ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", 0)); // Store mode.
|
||||
const std::string tgt_content("abcdefgxyz");
|
||||
@@ -187,7 +199,7 @@ TEST(ImgdiffTest, zip_mode_smoke_store) {
|
||||
TEST(ImgdiffTest, zip_mode_smoke_compressed) {
|
||||
// Construct src and tgt zip files.
|
||||
TemporaryFile src_file;
|
||||
FILE* src_file_ptr = fdopen(src_file.fd, "wb");
|
||||
FILE* src_file_ptr = fdopen(src_file.release(), "wb");
|
||||
ZipWriter src_writer(src_file_ptr);
|
||||
ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
|
||||
const std::string src_content("abcdefg");
|
||||
@@ -197,7 +209,7 @@ TEST(ImgdiffTest, zip_mode_smoke_compressed) {
|
||||
ASSERT_EQ(0, fclose(src_file_ptr));
|
||||
|
||||
TemporaryFile tgt_file;
|
||||
FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
|
||||
FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
|
||||
ZipWriter tgt_writer(tgt_file_ptr);
|
||||
ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
|
||||
const std::string tgt_content("abcdefgxyz");
|
||||
@@ -236,7 +248,7 @@ TEST(ImgdiffTest, zip_mode_smoke_compressed) {
|
||||
TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
|
||||
// Construct src and tgt zip files.
|
||||
TemporaryFile src_file;
|
||||
FILE* src_file_ptr = fdopen(src_file.fd, "wb");
|
||||
FILE* src_file_ptr = fdopen(src_file.release(), "wb");
|
||||
ZipWriter src_writer(src_file_ptr);
|
||||
ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
|
||||
const std::string src_content("abcdefg");
|
||||
@@ -246,7 +258,7 @@ TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
|
||||
ASSERT_EQ(0, fclose(src_file_ptr));
|
||||
|
||||
TemporaryFile tgt_file;
|
||||
FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
|
||||
FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
|
||||
ZipWriter tgt_writer(tgt_file_ptr);
|
||||
ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
|
||||
const std::string tgt_content("abcdefgxyz");
|
||||
@@ -623,3 +635,438 @@ TEST(ImgpatchTest, image_mode_patch_corruption) {
|
||||
reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
|
||||
[](const unsigned char* /*data*/, size_t len) { return len; }));
|
||||
}
|
||||
|
||||
static void construct_store_entry(const std::vector<std::tuple<std::string, size_t, char>>& info,
|
||||
ZipWriter* writer) {
|
||||
for (auto& t : info) {
|
||||
// Create t(1) blocks of t(2), and write the data to t(0)
|
||||
ASSERT_EQ(0, writer->StartEntry(std::get<0>(t).c_str(), 0));
|
||||
const std::string content(std::get<1>(t) * 4096, std::get<2>(t));
|
||||
ASSERT_EQ(0, writer->WriteBytes(content.data(), content.size()));
|
||||
ASSERT_EQ(0, writer->FinishEntry());
|
||||
}
|
||||
}
|
||||
|
||||
static void construct_deflate_entry(const std::vector<std::tuple<std::string, size_t, size_t>>& info,
|
||||
ZipWriter* writer, const std::string& data) {
|
||||
for (auto& t : info) {
|
||||
// t(0): entry_name; t(1): block offset; t(2) length in blocks.
|
||||
ASSERT_EQ(0, writer->StartEntry(std::get<0>(t).c_str(), ZipWriter::kCompress));
|
||||
ASSERT_EQ(0, writer->WriteBytes(data.data() + std::get<1>(t) * 4096, std::get<2>(t) * 4096));
|
||||
ASSERT_EQ(0, writer->FinishEntry());
|
||||
}
|
||||
}
|
||||
|
||||
// Look for the source and patch pieces in debug_dir. Generate a target piece from each pair.
|
||||
// Concatenate all the target pieces and match against the orignal one. Used pieces in debug_dir
|
||||
// will be cleaned up.
|
||||
static void GenerateAndCheckSplitTarget(const std::string& debug_dir, size_t count,
|
||||
const std::string& tgt) {
|
||||
std::string patched;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
std::string split_src_path = android::base::StringPrintf("%s/src-%zu", debug_dir.c_str(), i);
|
||||
std::string split_src;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(split_src_path, &split_src));
|
||||
ASSERT_EQ(0, unlink(split_src_path.c_str()));
|
||||
|
||||
std::string split_patch_path =
|
||||
android::base::StringPrintf("%s/patch-%zu", debug_dir.c_str(), i);
|
||||
std::string split_patch;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(split_patch_path, &split_patch));
|
||||
ASSERT_EQ(0, unlink(split_patch_path.c_str()));
|
||||
|
||||
std::string split_tgt;
|
||||
GenerateTarget(split_src, split_patch, &split_tgt);
|
||||
patched += split_tgt;
|
||||
}
|
||||
|
||||
// Verify we can get back the original target image.
|
||||
ASSERT_EQ(tgt, patched);
|
||||
}
|
||||
|
||||
std::vector<ImageChunk> ConstructImageChunks(
|
||||
const std::vector<uint8_t>& content, const std::vector<std::tuple<std::string, size_t>>& info) {
|
||||
std::vector<ImageChunk> chunks;
|
||||
size_t start = 0;
|
||||
for (const auto& t : info) {
|
||||
size_t length = std::get<1>(t);
|
||||
chunks.emplace_back(CHUNK_NORMAL, start, &content, length, std::get<0>(t));
|
||||
start += length;
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, zip_mode_split_image_smoke) {
|
||||
std::vector<uint8_t> content;
|
||||
content.reserve(4096 * 50);
|
||||
uint8_t n = 0;
|
||||
generate_n(back_inserter(content), 4096 * 50, [&n]() { return n++ / 4096; });
|
||||
|
||||
ZipModeImage tgt_image(false, 4096 * 10);
|
||||
std::vector<ImageChunk> tgt_chunks = ConstructImageChunks(content, { { "a", 100 },
|
||||
{ "b", 4096 * 2 },
|
||||
{ "c", 4096 * 3 },
|
||||
{ "d", 300 },
|
||||
{ "e-0", 4096 * 10 },
|
||||
{ "e-1", 4096 * 5 },
|
||||
{ "CD", 200 } });
|
||||
tgt_image.Initialize(std::move(tgt_chunks),
|
||||
std::vector<uint8_t>(content.begin(), content.begin() + 82520));
|
||||
|
||||
tgt_image.DumpChunks();
|
||||
|
||||
ZipModeImage src_image(true, 4096 * 10);
|
||||
std::vector<ImageChunk> src_chunks = ConstructImageChunks(content, { { "b", 4096 * 3 },
|
||||
{ "c-0", 4096 * 10 },
|
||||
{ "c-1", 4096 * 2 },
|
||||
{ "a", 4096 * 5 },
|
||||
{ "e-0", 4096 * 10 },
|
||||
{ "e-1", 10000 },
|
||||
{ "CD", 5000 } });
|
||||
src_image.Initialize(std::move(src_chunks),
|
||||
std::vector<uint8_t>(content.begin(), content.begin() + 137880));
|
||||
|
||||
std::vector<ZipModeImage> split_tgt_images;
|
||||
std::vector<ZipModeImage> split_src_images;
|
||||
std::vector<SortedRangeSet> split_src_ranges;
|
||||
|
||||
ZipModeImage::SplitZipModeImageWithLimit(tgt_image, src_image, &split_tgt_images,
|
||||
&split_src_images, &split_src_ranges);
|
||||
|
||||
// src_piece 1: a 5 blocks, b 3 blocks
|
||||
// src_piece 2: c-0 10 blocks
|
||||
// src_piece 3: d 0 block, e-0 10 blocks
|
||||
// src_piece 4: e-1 2 blocks; CD 2 blocks
|
||||
ASSERT_EQ(split_tgt_images.size(), split_src_images.size());
|
||||
ASSERT_EQ(static_cast<size_t>(4), split_tgt_images.size());
|
||||
|
||||
ASSERT_EQ(static_cast<size_t>(1), split_tgt_images[0].NumOfChunks());
|
||||
ASSERT_EQ(static_cast<size_t>(12288), split_tgt_images[0][0].DataLengthForPatch());
|
||||
ASSERT_EQ("4,0,3,15,20", split_src_ranges[0].ToString());
|
||||
|
||||
ASSERT_EQ(static_cast<size_t>(1), split_tgt_images[1].NumOfChunks());
|
||||
ASSERT_EQ(static_cast<size_t>(12288), split_tgt_images[1][0].DataLengthForPatch());
|
||||
ASSERT_EQ("2,3,13", split_src_ranges[1].ToString());
|
||||
|
||||
ASSERT_EQ(static_cast<size_t>(1), split_tgt_images[2].NumOfChunks());
|
||||
ASSERT_EQ(static_cast<size_t>(40960), split_tgt_images[2][0].DataLengthForPatch());
|
||||
ASSERT_EQ("2,20,30", split_src_ranges[2].ToString());
|
||||
|
||||
ASSERT_EQ(static_cast<size_t>(1), split_tgt_images[3].NumOfChunks());
|
||||
ASSERT_EQ(static_cast<size_t>(16984), split_tgt_images[3][0].DataLengthForPatch());
|
||||
ASSERT_EQ("2,30,34", split_src_ranges[3].ToString());
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, zip_mode_store_large_apk) {
|
||||
// Construct src and tgt zip files with limit = 10 blocks.
|
||||
// src tgt
|
||||
// 12 blocks 'd' 3 blocks 'a'
|
||||
// 8 blocks 'c' 3 blocks 'b'
|
||||
// 3 blocks 'b' 8 blocks 'c' (exceeds limit)
|
||||
// 3 blocks 'a' 12 blocks 'd' (exceeds limit)
|
||||
// 3 blocks 'e'
|
||||
TemporaryFile tgt_file;
|
||||
FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
|
||||
ZipWriter tgt_writer(tgt_file_ptr);
|
||||
construct_store_entry(
|
||||
{ { "a", 3, 'a' }, { "b", 3, 'b' }, { "c", 8, 'c' }, { "d", 12, 'd' }, { "e", 3, 'e' } },
|
||||
&tgt_writer);
|
||||
ASSERT_EQ(0, tgt_writer.Finish());
|
||||
ASSERT_EQ(0, fclose(tgt_file_ptr));
|
||||
|
||||
TemporaryFile src_file;
|
||||
FILE* src_file_ptr = fdopen(src_file.release(), "wb");
|
||||
ZipWriter src_writer(src_file_ptr);
|
||||
construct_store_entry({ { "d", 12, 'd' }, { "c", 8, 'c' }, { "b", 3, 'b' }, { "a", 3, 'a' } },
|
||||
&src_writer);
|
||||
ASSERT_EQ(0, src_writer.Finish());
|
||||
ASSERT_EQ(0, fclose(src_file_ptr));
|
||||
|
||||
// Compute patch.
|
||||
TemporaryFile patch_file;
|
||||
TemporaryFile split_info_file;
|
||||
TemporaryDir debug_dir;
|
||||
std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path);
|
||||
std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path);
|
||||
std::vector<const char*> args = {
|
||||
"imgdiff", "-z", "--block-limit=10", split_info_arg.c_str(), debug_dir_arg.c_str(),
|
||||
src_file.path, tgt_file.path, patch_file.path,
|
||||
};
|
||||
ASSERT_EQ(0, imgdiff(args.size(), args.data()));
|
||||
|
||||
std::string tgt;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
|
||||
|
||||
// Expect 4 pieces of patch. (Roughly 3'a',3'b'; 8'c'; 10'd'; 2'd'3'e')
|
||||
GenerateAndCheckSplitTarget(debug_dir.path, 4, tgt);
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, zip_mode_deflate_large_apk) {
|
||||
// Src and tgt zip files are constructed as follows.
|
||||
// src tgt
|
||||
// 22 blocks, "d" 4 blocks, "a"
|
||||
// 5 blocks, "b" 4 blocks, "b"
|
||||
// 3 blocks, "a" 8 blocks, "c" (exceeds limit)
|
||||
// 1 block, "g" 20 blocks, "d" (exceeds limit)
|
||||
// 8 blocks, "c" 2 blocks, "e"
|
||||
// 1 block, "f" 1 block , "f"
|
||||
std::string tgt_path = from_testdata_base("deflate_tgt.zip");
|
||||
std::string src_path = from_testdata_base("deflate_src.zip");
|
||||
|
||||
ZipModeImage src_image(true, 10 * 4096);
|
||||
ZipModeImage tgt_image(false, 10 * 4096);
|
||||
ASSERT_TRUE(src_image.Initialize(src_path));
|
||||
ASSERT_TRUE(tgt_image.Initialize(tgt_path));
|
||||
ASSERT_TRUE(ZipModeImage::CheckAndProcessChunks(&tgt_image, &src_image));
|
||||
|
||||
src_image.DumpChunks();
|
||||
tgt_image.DumpChunks();
|
||||
|
||||
std::vector<ZipModeImage> split_tgt_images;
|
||||
std::vector<ZipModeImage> split_src_images;
|
||||
std::vector<SortedRangeSet> split_src_ranges;
|
||||
ZipModeImage::SplitZipModeImageWithLimit(tgt_image, src_image, &split_tgt_images,
|
||||
&split_src_images, &split_src_ranges);
|
||||
|
||||
// Expected split images with limit = 10 blocks.
|
||||
// src_piece 0: a 3 blocks, b 5 blocks
|
||||
// src_piece 1: c 8 blocks
|
||||
// src_piece 2: d-0 10 block
|
||||
// src_piece 3: d-1 10 blocks
|
||||
// src_piece 4: e 1 block, CD
|
||||
ASSERT_EQ(split_tgt_images.size(), split_src_images.size());
|
||||
ASSERT_EQ(static_cast<size_t>(5), split_tgt_images.size());
|
||||
|
||||
ASSERT_EQ(static_cast<size_t>(2), split_src_images[0].NumOfChunks());
|
||||
ASSERT_EQ("a", split_src_images[0][0].GetEntryName());
|
||||
ASSERT_EQ("b", split_src_images[0][1].GetEntryName());
|
||||
|
||||
ASSERT_EQ(static_cast<size_t>(1), split_src_images[1].NumOfChunks());
|
||||
ASSERT_EQ("c", split_src_images[1][0].GetEntryName());
|
||||
|
||||
ASSERT_EQ(static_cast<size_t>(0), split_src_images[2].NumOfChunks());
|
||||
ASSERT_EQ(static_cast<size_t>(0), split_src_images[3].NumOfChunks());
|
||||
ASSERT_EQ(static_cast<size_t>(0), split_src_images[4].NumOfChunks());
|
||||
|
||||
// Compute patch.
|
||||
TemporaryFile patch_file;
|
||||
TemporaryFile split_info_file;
|
||||
TemporaryDir debug_dir;
|
||||
ASSERT_TRUE(ZipModeImage::GeneratePatches(split_tgt_images, split_src_images, split_src_ranges,
|
||||
patch_file.path, split_info_file.path, debug_dir.path));
|
||||
|
||||
// Verify the content of split info.
|
||||
// Expect 5 pieces of patch. ["a","b"; "c"; "d-0"; "d-1"; "e"]
|
||||
std::string split_info_string;
|
||||
android::base::ReadFileToString(split_info_file.path, &split_info_string);
|
||||
std::vector<std::string> info_list =
|
||||
android::base::Split(android::base::Trim(split_info_string), "\n");
|
||||
|
||||
ASSERT_EQ(static_cast<size_t>(7), info_list.size());
|
||||
ASSERT_EQ("2", android::base::Trim(info_list[0]));
|
||||
ASSERT_EQ("5", android::base::Trim(info_list[1]));
|
||||
|
||||
std::string tgt;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(tgt_path, &tgt));
|
||||
ASSERT_EQ(static_cast<size_t>(160385), tgt.size());
|
||||
std::vector<std::string> tgt_file_ranges = {
|
||||
"36864 2,22,31", "32768 2,31,40", "40960 2,0,11", "40960 2,11,21", "8833 4,21,22,40,41",
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
struct stat st;
|
||||
std::string path = android::base::StringPrintf("%s/patch-%zu", debug_dir.path, i);
|
||||
ASSERT_EQ(0, stat(path.c_str(), &st));
|
||||
ASSERT_EQ(std::to_string(st.st_size) + " " + tgt_file_ranges[i],
|
||||
android::base::Trim(info_list[i + 2]));
|
||||
}
|
||||
|
||||
GenerateAndCheckSplitTarget(debug_dir.path, 5, tgt);
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, zip_mode_no_match_source) {
|
||||
// Generate 20 blocks of random data.
|
||||
std::string random_data;
|
||||
random_data.reserve(4096 * 20);
|
||||
generate_n(back_inserter(random_data), 4096 * 20, []() { return rand() % 256; });
|
||||
|
||||
TemporaryFile tgt_file;
|
||||
FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
|
||||
ZipWriter tgt_writer(tgt_file_ptr);
|
||||
|
||||
construct_deflate_entry({ { "a", 0, 4 }, { "b", 5, 5 }, { "c", 11, 5 } }, &tgt_writer,
|
||||
random_data);
|
||||
|
||||
ASSERT_EQ(0, tgt_writer.Finish());
|
||||
ASSERT_EQ(0, fclose(tgt_file_ptr));
|
||||
|
||||
// We don't have a matching source entry.
|
||||
TemporaryFile src_file;
|
||||
FILE* src_file_ptr = fdopen(src_file.release(), "wb");
|
||||
ZipWriter src_writer(src_file_ptr);
|
||||
construct_store_entry({ { "d", 1, 'd' } }, &src_writer);
|
||||
ASSERT_EQ(0, src_writer.Finish());
|
||||
ASSERT_EQ(0, fclose(src_file_ptr));
|
||||
|
||||
// Compute patch.
|
||||
TemporaryFile patch_file;
|
||||
TemporaryFile split_info_file;
|
||||
TemporaryDir debug_dir;
|
||||
std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path);
|
||||
std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path);
|
||||
std::vector<const char*> args = {
|
||||
"imgdiff", "-z", "--block-limit=10", debug_dir_arg.c_str(), split_info_arg.c_str(),
|
||||
src_file.path, tgt_file.path, patch_file.path,
|
||||
};
|
||||
ASSERT_EQ(0, imgdiff(args.size(), args.data()));
|
||||
|
||||
std::string tgt;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
|
||||
|
||||
// Expect 1 pieces of patch due to no matching source entry.
|
||||
GenerateAndCheckSplitTarget(debug_dir.path, 1, tgt);
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, zip_mode_large_enough_limit) {
|
||||
// Generate 20 blocks of random data.
|
||||
std::string random_data;
|
||||
random_data.reserve(4096 * 20);
|
||||
generate_n(back_inserter(random_data), 4096 * 20, []() { return rand() % 256; });
|
||||
|
||||
TemporaryFile tgt_file;
|
||||
FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
|
||||
ZipWriter tgt_writer(tgt_file_ptr);
|
||||
|
||||
construct_deflate_entry({ { "a", 0, 10 }, { "b", 10, 5 } }, &tgt_writer, random_data);
|
||||
|
||||
ASSERT_EQ(0, tgt_writer.Finish());
|
||||
ASSERT_EQ(0, fclose(tgt_file_ptr));
|
||||
|
||||
// Construct 10 blocks of source.
|
||||
TemporaryFile src_file;
|
||||
FILE* src_file_ptr = fdopen(src_file.release(), "wb");
|
||||
ZipWriter src_writer(src_file_ptr);
|
||||
construct_deflate_entry({ { "a", 1, 10 } }, &src_writer, random_data);
|
||||
ASSERT_EQ(0, src_writer.Finish());
|
||||
ASSERT_EQ(0, fclose(src_file_ptr));
|
||||
|
||||
// Compute patch with a limit of 20 blocks.
|
||||
TemporaryFile patch_file;
|
||||
TemporaryFile split_info_file;
|
||||
TemporaryDir debug_dir;
|
||||
std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path);
|
||||
std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path);
|
||||
std::vector<const char*> args = {
|
||||
"imgdiff", "-z", "--block-limit=20", split_info_arg.c_str(), debug_dir_arg.c_str(),
|
||||
src_file.path, tgt_file.path, patch_file.path,
|
||||
};
|
||||
ASSERT_EQ(0, imgdiff(args.size(), args.data()));
|
||||
|
||||
std::string tgt;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
|
||||
|
||||
// Expect 1 piece of patch since limit is larger than the zip file size.
|
||||
GenerateAndCheckSplitTarget(debug_dir.path, 1, tgt);
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, zip_mode_large_apk_small_target_chunk) {
|
||||
TemporaryFile tgt_file;
|
||||
FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
|
||||
ZipWriter tgt_writer(tgt_file_ptr);
|
||||
|
||||
// The first entry is less than 4096 bytes, followed immediately by an entry that has a very
|
||||
// large counterpart in the source file. Therefore the first entry will be patched separately.
|
||||
std::string small_chunk("a", 2000);
|
||||
ASSERT_EQ(0, tgt_writer.StartEntry("a", 0));
|
||||
ASSERT_EQ(0, tgt_writer.WriteBytes(small_chunk.data(), small_chunk.size()));
|
||||
ASSERT_EQ(0, tgt_writer.FinishEntry());
|
||||
construct_store_entry(
|
||||
{
|
||||
{ "b", 12, 'b' }, { "c", 3, 'c' },
|
||||
},
|
||||
&tgt_writer);
|
||||
ASSERT_EQ(0, tgt_writer.Finish());
|
||||
ASSERT_EQ(0, fclose(tgt_file_ptr));
|
||||
|
||||
TemporaryFile src_file;
|
||||
FILE* src_file_ptr = fdopen(src_file.release(), "wb");
|
||||
ZipWriter src_writer(src_file_ptr);
|
||||
construct_store_entry({ { "a", 1, 'a' }, { "b", 13, 'b' }, { "c", 1, 'c' } }, &src_writer);
|
||||
ASSERT_EQ(0, src_writer.Finish());
|
||||
ASSERT_EQ(0, fclose(src_file_ptr));
|
||||
|
||||
// Compute patch.
|
||||
TemporaryFile patch_file;
|
||||
TemporaryFile split_info_file;
|
||||
TemporaryDir debug_dir;
|
||||
std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path);
|
||||
std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path);
|
||||
std::vector<const char*> args = {
|
||||
"imgdiff", "-z", "--block-limit=10", split_info_arg.c_str(), debug_dir_arg.c_str(),
|
||||
src_file.path, tgt_file.path, patch_file.path,
|
||||
};
|
||||
ASSERT_EQ(0, imgdiff(args.size(), args.data()));
|
||||
|
||||
std::string tgt;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
|
||||
|
||||
// Expect three split src images:
|
||||
// src_piece 0: a 1 blocks
|
||||
// src_piece 1: b-0 10 blocks
|
||||
// src_piece 2: b-1 3 blocks, c 1 blocks, CD
|
||||
GenerateAndCheckSplitTarget(debug_dir.path, 3, tgt);
|
||||
}
|
||||
|
||||
TEST(ImgdiffTest, zip_mode_large_apk_skipped_small_target_chunk) {
|
||||
TemporaryFile tgt_file;
|
||||
FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
|
||||
ZipWriter tgt_writer(tgt_file_ptr);
|
||||
|
||||
construct_store_entry(
|
||||
{
|
||||
{ "a", 11, 'a' },
|
||||
},
|
||||
&tgt_writer);
|
||||
|
||||
// Construct a tiny target entry of 1 byte, which will be skipped due to the tail alignment of
|
||||
// the previous entry.
|
||||
std::string small_chunk("b", 1);
|
||||
ASSERT_EQ(0, tgt_writer.StartEntry("b", 0));
|
||||
ASSERT_EQ(0, tgt_writer.WriteBytes(small_chunk.data(), small_chunk.size()));
|
||||
ASSERT_EQ(0, tgt_writer.FinishEntry());
|
||||
|
||||
ASSERT_EQ(0, tgt_writer.Finish());
|
||||
ASSERT_EQ(0, fclose(tgt_file_ptr));
|
||||
|
||||
TemporaryFile src_file;
|
||||
FILE* src_file_ptr = fdopen(src_file.release(), "wb");
|
||||
ZipWriter src_writer(src_file_ptr);
|
||||
construct_store_entry(
|
||||
{
|
||||
{ "a", 11, 'a' }, { "b", 11, 'b' },
|
||||
},
|
||||
&src_writer);
|
||||
ASSERT_EQ(0, src_writer.Finish());
|
||||
ASSERT_EQ(0, fclose(src_file_ptr));
|
||||
|
||||
// Compute patch.
|
||||
TemporaryFile patch_file;
|
||||
TemporaryFile split_info_file;
|
||||
TemporaryDir debug_dir;
|
||||
std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path);
|
||||
std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path);
|
||||
std::vector<const char*> args = {
|
||||
"imgdiff", "-z", "--block-limit=10", split_info_arg.c_str(), debug_dir_arg.c_str(),
|
||||
src_file.path, tgt_file.path, patch_file.path,
|
||||
};
|
||||
ASSERT_EQ(0, imgdiff(args.size(), args.data()));
|
||||
|
||||
std::string tgt;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
|
||||
|
||||
// Expect two split src images:
|
||||
// src_piece 0: a-0 10 blocks
|
||||
// src_piece 1: a-0 1 block, CD
|
||||
GenerateAndCheckSplitTarget(debug_dir.path, 2, tgt);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -36,7 +37,7 @@
|
||||
|
||||
TEST(InstallTest, verify_package_compatibility_no_entry) {
|
||||
TemporaryFile temp_file;
|
||||
FILE* zip_file = fdopen(temp_file.fd, "w");
|
||||
FILE* zip_file = fdopen(temp_file.release(), "w");
|
||||
ZipWriter writer(zip_file);
|
||||
// The archive must have something to be opened correctly.
|
||||
ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0));
|
||||
@@ -53,7 +54,7 @@ TEST(InstallTest, verify_package_compatibility_no_entry) {
|
||||
|
||||
TEST(InstallTest, verify_package_compatibility_invalid_entry) {
|
||||
TemporaryFile temp_file;
|
||||
FILE* zip_file = fdopen(temp_file.fd, "w");
|
||||
FILE* zip_file = fdopen(temp_file.release(), "w");
|
||||
ZipWriter writer(zip_file);
|
||||
ASSERT_EQ(0, writer.StartEntry("compatibility.zip", 0));
|
||||
ASSERT_EQ(0, writer.FinishEntry());
|
||||
@@ -69,7 +70,7 @@ TEST(InstallTest, verify_package_compatibility_invalid_entry) {
|
||||
|
||||
TEST(InstallTest, read_metadata_from_package_smoke) {
|
||||
TemporaryFile temp_file;
|
||||
FILE* zip_file = fdopen(temp_file.fd, "w");
|
||||
FILE* zip_file = fdopen(temp_file.release(), "w");
|
||||
ZipWriter writer(zip_file);
|
||||
ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
|
||||
const std::string content("abcdefg");
|
||||
@@ -86,7 +87,7 @@ TEST(InstallTest, read_metadata_from_package_smoke) {
|
||||
CloseArchive(zip);
|
||||
|
||||
TemporaryFile temp_file2;
|
||||
FILE* zip_file2 = fdopen(temp_file2.fd, "w");
|
||||
FILE* zip_file2 = fdopen(temp_file2.release(), "w");
|
||||
ZipWriter writer2(zip_file2);
|
||||
ASSERT_EQ(0, writer2.StartEntry("META-INF/com/android/metadata", kCompressDeflated));
|
||||
ASSERT_EQ(0, writer2.WriteBytes(content.data(), content.size()));
|
||||
@@ -103,7 +104,7 @@ TEST(InstallTest, read_metadata_from_package_smoke) {
|
||||
|
||||
TEST(InstallTest, read_metadata_from_package_no_entry) {
|
||||
TemporaryFile temp_file;
|
||||
FILE* zip_file = fdopen(temp_file.fd, "w");
|
||||
FILE* zip_file = fdopen(temp_file.release(), "w");
|
||||
ZipWriter writer(zip_file);
|
||||
ASSERT_EQ(0, writer.StartEntry("dummy_entry", kCompressStored));
|
||||
ASSERT_EQ(0, writer.FinishEntry());
|
||||
@@ -119,7 +120,7 @@ TEST(InstallTest, read_metadata_from_package_no_entry) {
|
||||
|
||||
TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
|
||||
TemporaryFile compatibility_zip_file;
|
||||
FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
|
||||
FILE* compatibility_zip = fdopen(compatibility_zip_file.release(), "w");
|
||||
ZipWriter compatibility_zip_writer(compatibility_zip);
|
||||
ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
|
||||
std::string malformed_xml = "malformed";
|
||||
@@ -129,7 +130,7 @@ TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
|
||||
ASSERT_EQ(0, fclose(compatibility_zip));
|
||||
|
||||
TemporaryFile temp_file;
|
||||
FILE* zip_file = fdopen(temp_file.fd, "w");
|
||||
FILE* zip_file = fdopen(temp_file.release(), "w");
|
||||
ZipWriter writer(zip_file);
|
||||
ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
|
||||
std::string compatibility_zip_content;
|
||||
@@ -164,7 +165,7 @@ TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml
|
||||
ASSERT_TRUE(
|
||||
android::base::ReadFileToString(system_manifest_xml_path, &system_manifest_xml_content));
|
||||
TemporaryFile compatibility_zip_file;
|
||||
FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
|
||||
FILE* compatibility_zip = fdopen(compatibility_zip_file.release(), "w");
|
||||
ZipWriter compatibility_zip_writer(compatibility_zip);
|
||||
ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
|
||||
ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(system_manifest_xml_content.data(),
|
||||
@@ -174,7 +175,7 @@ TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml
|
||||
ASSERT_EQ(0, fclose(compatibility_zip));
|
||||
|
||||
TemporaryFile temp_file;
|
||||
FILE* zip_file = fdopen(temp_file.fd, "w");
|
||||
FILE* zip_file = fdopen(temp_file.release(), "w");
|
||||
ZipWriter writer(zip_file);
|
||||
ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
|
||||
std::string compatibility_zip_content;
|
||||
@@ -198,10 +199,10 @@ TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml
|
||||
CloseArchive(zip);
|
||||
}
|
||||
|
||||
TEST(InstallTest, update_binary_command_smoke) {
|
||||
#ifdef AB_OTA_UPDATER
|
||||
static void VerifyAbUpdateBinaryCommand(const std::string& serialno, bool success = true) {
|
||||
TemporaryFile temp_file;
|
||||
FILE* zip_file = fdopen(temp_file.fd, "w");
|
||||
FILE* zip_file = fdopen(temp_file.release(), "w");
|
||||
ZipWriter writer(zip_file);
|
||||
ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
|
||||
ASSERT_EQ(0, writer.FinishEntry());
|
||||
@@ -215,11 +216,13 @@ TEST(InstallTest, update_binary_command_smoke) {
|
||||
ASSERT_NE("", device);
|
||||
std::string timestamp = android::base::GetProperty("ro.build.date.utc", "");
|
||||
ASSERT_NE("", timestamp);
|
||||
std::string metadata = android::base::Join(
|
||||
std::vector<std::string>{
|
||||
"ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp,
|
||||
},
|
||||
"\n");
|
||||
|
||||
std::vector<std::string> meta{ "ota-type=AB", "pre-device=" + device,
|
||||
"post-timestamp=" + timestamp };
|
||||
if (!serialno.empty()) {
|
||||
meta.push_back("serialno=" + serialno);
|
||||
}
|
||||
std::string metadata = android::base::Join(meta, "\n");
|
||||
ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size()));
|
||||
ASSERT_EQ(0, writer.FinishEntry());
|
||||
ASSERT_EQ(0, writer.Finish());
|
||||
@@ -234,17 +237,28 @@ TEST(InstallTest, update_binary_command_smoke) {
|
||||
std::string package = "/path/to/update.zip";
|
||||
std::string binary_path = "/sbin/update_engine_sideload";
|
||||
std::vector<std::string> cmd;
|
||||
ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
|
||||
ASSERT_EQ(5U, cmd.size());
|
||||
ASSERT_EQ(binary_path, cmd[0]);
|
||||
ASSERT_EQ("--payload=file://" + package, cmd[1]);
|
||||
ASSERT_EQ("--offset=" + std::to_string(payload_entry.offset), cmd[2]);
|
||||
ASSERT_EQ("--headers=" + properties, cmd[3]);
|
||||
ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
|
||||
if (success) {
|
||||
ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
|
||||
ASSERT_EQ(5U, cmd.size());
|
||||
ASSERT_EQ(binary_path, cmd[0]);
|
||||
ASSERT_EQ("--payload=file://" + package, cmd[1]);
|
||||
ASSERT_EQ("--offset=" + std::to_string(payload_entry.offset), cmd[2]);
|
||||
ASSERT_EQ("--headers=" + properties, cmd[3]);
|
||||
ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
|
||||
} else {
|
||||
ASSERT_EQ(INSTALL_ERROR, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
|
||||
}
|
||||
CloseArchive(zip);
|
||||
}
|
||||
#endif // AB_OTA_UPDATER
|
||||
|
||||
TEST(InstallTest, update_binary_command_smoke) {
|
||||
#ifdef AB_OTA_UPDATER
|
||||
// Empty serialno will pass the verification.
|
||||
VerifyAbUpdateBinaryCommand({});
|
||||
#else
|
||||
TemporaryFile temp_file;
|
||||
FILE* zip_file = fdopen(temp_file.fd, "w");
|
||||
FILE* zip_file = fdopen(temp_file.release(), "w");
|
||||
ZipWriter writer(zip_file);
|
||||
static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
|
||||
ASSERT_EQ(0, writer.StartEntry(UPDATE_BINARY_NAME, kCompressStored));
|
||||
@@ -289,7 +303,7 @@ TEST(InstallTest, update_binary_command_smoke) {
|
||||
TEST(InstallTest, update_binary_command_invalid) {
|
||||
#ifdef AB_OTA_UPDATER
|
||||
TemporaryFile temp_file;
|
||||
FILE* zip_file = fdopen(temp_file.fd, "w");
|
||||
FILE* zip_file = fdopen(temp_file.release(), "w");
|
||||
ZipWriter writer(zip_file);
|
||||
// Missing payload_properties.txt.
|
||||
ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
|
||||
@@ -320,7 +334,7 @@ TEST(InstallTest, update_binary_command_invalid) {
|
||||
CloseArchive(zip);
|
||||
#else
|
||||
TemporaryFile temp_file;
|
||||
FILE* zip_file = fdopen(temp_file.fd, "w");
|
||||
FILE* zip_file = fdopen(temp_file.release(), "w");
|
||||
ZipWriter writer(zip_file);
|
||||
// The archive must have something to be opened correctly.
|
||||
ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0));
|
||||
@@ -340,3 +354,34 @@ TEST(InstallTest, update_binary_command_invalid) {
|
||||
CloseArchive(zip);
|
||||
#endif // AB_OTA_UPDATER
|
||||
}
|
||||
|
||||
#ifdef AB_OTA_UPDATER
|
||||
TEST(InstallTest, update_binary_command_multiple_serialno) {
|
||||
std::string serialno = android::base::GetProperty("ro.serialno", "");
|
||||
ASSERT_NE("", serialno);
|
||||
|
||||
// Single matching serialno will pass the verification.
|
||||
VerifyAbUpdateBinaryCommand(serialno);
|
||||
|
||||
static constexpr char alphabet[] =
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
auto generator = []() { return alphabet[rand() % (sizeof(alphabet) - 1)]; };
|
||||
|
||||
// Generate 900 random serial numbers.
|
||||
std::string random_serial;
|
||||
for (size_t i = 0; i < 900; i++) {
|
||||
generate_n(back_inserter(random_serial), serialno.size(), generator);
|
||||
random_serial.append("|");
|
||||
}
|
||||
// Random serialnos should fail the verification.
|
||||
VerifyAbUpdateBinaryCommand(random_serial, false);
|
||||
|
||||
std::string long_serial = random_serial + serialno + "|";
|
||||
for (size_t i = 0; i < 99; i++) {
|
||||
generate_n(back_inserter(long_serial), serialno.size(), generator);
|
||||
long_serial.append("|");
|
||||
}
|
||||
// String with the matching serialno should pass the verification.
|
||||
VerifyAbUpdateBinaryCommand(long_serial);
|
||||
}
|
||||
#endif // AB_OTA_UPDATER
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <android-base/test_utils.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "fuse_sideload.h"
|
||||
@@ -26,11 +32,67 @@ TEST(SideloadTest, fuse_device) {
|
||||
|
||||
TEST(SideloadTest, run_fuse_sideload_wrong_parameters) {
|
||||
provider_vtab vtab;
|
||||
vtab.close = [](void*) {};
|
||||
vtab.close = [](void) {};
|
||||
|
||||
ASSERT_EQ(-1, run_fuse_sideload(&vtab, nullptr, 4096, 4095));
|
||||
ASSERT_EQ(-1, run_fuse_sideload(&vtab, nullptr, 4096, (1 << 22) + 1));
|
||||
ASSERT_EQ(-1, run_fuse_sideload(vtab, 4096, 4095));
|
||||
ASSERT_EQ(-1, run_fuse_sideload(vtab, 4096, (1 << 22) + 1));
|
||||
|
||||
// Too many blocks.
|
||||
ASSERT_EQ(-1, run_fuse_sideload(&vtab, nullptr, ((1 << 18) + 1) * 4096, 4096));
|
||||
ASSERT_EQ(-1, run_fuse_sideload(vtab, ((1 << 18) + 1) * 4096, 4096));
|
||||
}
|
||||
|
||||
TEST(SideloadTest, run_fuse_sideload) {
|
||||
const std::vector<std::string> blocks = {
|
||||
std::string(2048, 'a') + std::string(2048, 'b'),
|
||||
std::string(2048, 'c') + std::string(2048, 'd'),
|
||||
std::string(2048, 'e') + std::string(2048, 'f'),
|
||||
std::string(2048, 'g') + std::string(2048, 'h'),
|
||||
};
|
||||
const std::string content = android::base::Join(blocks, "");
|
||||
ASSERT_EQ(16384U, content.size());
|
||||
|
||||
provider_vtab vtab;
|
||||
vtab.close = [](void) {};
|
||||
vtab.read_block = [&blocks](uint32_t block, uint8_t* buffer, uint32_t fetch_size) {
|
||||
if (block >= 4) return -1;
|
||||
blocks[block].copy(reinterpret_cast<char*>(buffer), fetch_size);
|
||||
return 0;
|
||||
};
|
||||
|
||||
TemporaryDir mount_point;
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
ASSERT_EQ(0, run_fuse_sideload(vtab, 16384, 4096, mount_point.path));
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
std::string package = std::string(mount_point.path) + "/" + FUSE_SIDELOAD_HOST_FILENAME;
|
||||
int status;
|
||||
static constexpr int kSideloadInstallTimeout = 10;
|
||||
for (int i = 0; i < kSideloadInstallTimeout; ++i) {
|
||||
ASSERT_NE(-1, waitpid(pid, &status, WNOHANG));
|
||||
|
||||
struct stat sb;
|
||||
if (stat(package.c_str(), &sb) == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (errno == ENOENT && i < kSideloadInstallTimeout - 1) {
|
||||
sleep(1);
|
||||
continue;
|
||||
}
|
||||
FAIL() << "Timed out waiting for the fuse-provided package.";
|
||||
}
|
||||
|
||||
std::string content_via_fuse;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(package, &content_via_fuse));
|
||||
ASSERT_EQ(content, content_via_fuse);
|
||||
|
||||
std::string exit_flag = std::string(mount_point.path) + "/" + FUSE_SIDELOAD_HOST_EXIT_FLAG;
|
||||
struct stat sb;
|
||||
ASSERT_EQ(0, stat(exit_flag.c_str(), &sb));
|
||||
|
||||
waitpid(pid, &status, 0);
|
||||
ASSERT_EQ(0, WEXITSTATUS(status));
|
||||
ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include <android-base/file.h>
|
||||
@@ -38,43 +39,49 @@ static const std::string INIT_SVC_CLEAR_BCB = "init.svc.clear-bcb";
|
||||
static const std::string INIT_SVC_UNCRYPT = "init.svc.uncrypt";
|
||||
static constexpr int SOCKET_CONNECTION_MAX_RETRY = 30;
|
||||
|
||||
static void StopService() {
|
||||
ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
|
||||
ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
|
||||
ASSERT_TRUE(android::base::SetProperty("ctl.stop", "uncrypt"));
|
||||
|
||||
bool success = false;
|
||||
for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
|
||||
std::string setup_bcb = android::base::GetProperty(INIT_SVC_SETUP_BCB, "");
|
||||
std::string clear_bcb = android::base::GetProperty(INIT_SVC_CLEAR_BCB, "");
|
||||
std::string uncrypt = android::base::GetProperty(INIT_SVC_UNCRYPT, "");
|
||||
GTEST_LOG_(INFO) << "setup-bcb: [" << setup_bcb << "] clear-bcb: [" << clear_bcb
|
||||
<< "] uncrypt: [" << uncrypt << "]";
|
||||
if (setup_bcb != "running" && clear_bcb != "running" && uncrypt != "running") {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(success) << "uncrypt service is not available.";
|
||||
}
|
||||
|
||||
class UncryptTest : public ::testing::Test {
|
||||
protected:
|
||||
UncryptTest() : has_misc(true) {}
|
||||
|
||||
virtual void SetUp() override {
|
||||
ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
|
||||
ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
|
||||
ASSERT_TRUE(android::base::SetProperty("ctl.stop", "uncrypt"));
|
||||
|
||||
bool success = false;
|
||||
for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
|
||||
std::string setup_bcb = android::base::GetProperty(INIT_SVC_SETUP_BCB, "");
|
||||
std::string clear_bcb = android::base::GetProperty(INIT_SVC_CLEAR_BCB, "");
|
||||
std::string uncrypt = android::base::GetProperty(INIT_SVC_UNCRYPT, "");
|
||||
LOG(INFO) << "setup-bcb: [" << setup_bcb << "] clear-bcb: [" << clear_bcb << "] uncrypt: ["
|
||||
<< uncrypt << "]";
|
||||
if (setup_bcb != "running" && clear_bcb != "running" && uncrypt != "running") {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(success) << "uncrypt service is not available.";
|
||||
|
||||
void SetUp() override {
|
||||
std::string err;
|
||||
has_misc = !get_bootloader_message_blk_device(&err).empty();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
// Clear the BCB.
|
||||
if (has_misc) {
|
||||
std::string err;
|
||||
ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
|
||||
}
|
||||
}
|
||||
|
||||
void SetupOrClearBcb(bool isSetup, const std::string& message,
|
||||
const std::string& message_in_bcb) const {
|
||||
if (!has_misc) {
|
||||
GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Trigger the setup-bcb service.
|
||||
// Restart the setup-bcb service.
|
||||
StopService();
|
||||
ASSERT_TRUE(android::base::SetProperty("ctl.start", isSetup ? "setup-bcb" : "clear-bcb"));
|
||||
|
||||
// Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
|
||||
@@ -144,27 +151,49 @@ class UncryptTest : public ::testing::Test {
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyBootloaderMessage(const std::string& expected) {
|
||||
std::string err;
|
||||
bootloader_message boot;
|
||||
ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
|
||||
|
||||
// Check that we have all the expected bytes.
|
||||
ASSERT_EQ(expected, std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)));
|
||||
}
|
||||
|
||||
bool has_misc;
|
||||
};
|
||||
|
||||
TEST_F(UncryptTest, setup_bcb) {
|
||||
if (!has_misc) {
|
||||
GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
|
||||
return;
|
||||
}
|
||||
|
||||
std::string random_data;
|
||||
random_data.reserve(sizeof(bootloader_message));
|
||||
generate_n(back_inserter(random_data), sizeof(bootloader_message), []() { return rand() % 128; });
|
||||
|
||||
bootloader_message boot;
|
||||
memcpy(&boot, random_data.c_str(), random_data.size());
|
||||
|
||||
std::string err;
|
||||
ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err;
|
||||
VerifyBootloaderMessage(random_data);
|
||||
|
||||
ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
|
||||
VerifyBootloaderMessage(std::string(sizeof(bootloader_message), '\0'));
|
||||
|
||||
std::string message = "--update_message=abc value";
|
||||
std::string message_in_bcb = "recovery\n--update_message=abc value\n";
|
||||
SetupOrClearBcb(true, message, message_in_bcb);
|
||||
}
|
||||
|
||||
TEST_F(UncryptTest, clear_bcb) {
|
||||
SetupOrClearBcb(false, "", "");
|
||||
}
|
||||
|
||||
TEST_F(UncryptTest, setup_bcb_wipe_ab) {
|
||||
TemporaryFile wipe_package;
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(std::string(345, 'a'), wipe_package.path));
|
||||
|
||||
// It's expected to store a wipe package in /misc, with the package size passed to recovery.
|
||||
std::string message =
|
||||
"--wipe_ab\n--wipe_package="s + wipe_package.path + "\n--reason=wipePackage"s;
|
||||
std::string message_in_bcb =
|
||||
"recovery\n--wipe_ab\n--wipe_package_size=345\n--reason=wipePackage\n";
|
||||
message = "--wipe_ab\n--wipe_package="s + wipe_package.path + "\n--reason=wipePackage"s;
|
||||
message_in_bcb = "recovery\n--wipe_ab\n--wipe_package_size=345\n--reason=wipePackage\n";
|
||||
SetupOrClearBcb(true, message, message_in_bcb);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ TEST_F(UpdateVerifierTest, verify_image_smoke) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The care map file can have only two or four lines.
|
||||
TemporaryFile temp_file;
|
||||
std::string content = "system\n2,0,1";
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
|
||||
@@ -58,7 +57,7 @@ TEST_F(UpdateVerifierTest, verify_image_smoke) {
|
||||
}
|
||||
|
||||
TEST_F(UpdateVerifierTest, verify_image_wrong_lines) {
|
||||
// The care map file can have only two or four lines.
|
||||
// The care map file can have only 2 / 4 / 6 lines.
|
||||
TemporaryFile temp_file;
|
||||
ASSERT_FALSE(verify_image(temp_file.path));
|
||||
|
||||
|
||||
+386
-110
@@ -23,6 +23,7 @@
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/file.h>
|
||||
@@ -32,16 +33,17 @@
|
||||
#include <android-base/test_utils.h>
|
||||
#include <bootloader_message/bootloader_message.h>
|
||||
#include <brotli/encode.h>
|
||||
#include <bsdiff.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 "error_code.h"
|
||||
#include "otautil/SysUtil.h"
|
||||
#include "print_sha1.h"
|
||||
#include "otautil/cache_location.h"
|
||||
#include "otautil/error_code.h"
|
||||
#include "otautil/print_sha1.h"
|
||||
#include "updater/blockimg.h"
|
||||
#include "updater/install.h"
|
||||
#include "updater/updater.h"
|
||||
@@ -74,6 +76,23 @@ static void expect(const char* expected, const char* expr_str, CauseCode cause_c
|
||||
ASSERT_EQ(cause_code, state.cause_code);
|
||||
}
|
||||
|
||||
static void BuildUpdatePackage(const std::unordered_map<std::string, std::string>& entries,
|
||||
int fd) {
|
||||
FILE* zip_file_ptr = fdopen(fd, "wb");
|
||||
ZipWriter zip_writer(zip_file_ptr);
|
||||
|
||||
for (const auto& entry : entries) {
|
||||
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 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);
|
||||
@@ -86,7 +105,16 @@ class UpdaterTest : public ::testing::Test {
|
||||
RegisterBuiltins();
|
||||
RegisterInstallFunctions();
|
||||
RegisterBlockImageFunctions();
|
||||
|
||||
// Mock the location of last_command_file.
|
||||
CacheLocation::location().set_cache_temp_source(temp_saved_source_.path);
|
||||
CacheLocation::location().set_last_command_file(temp_last_command_.path);
|
||||
CacheLocation::location().set_stash_directory_base(temp_stash_base_.path);
|
||||
}
|
||||
|
||||
TemporaryFile temp_saved_source_;
|
||||
TemporaryFile temp_last_command_;
|
||||
TemporaryDir temp_stash_base_;
|
||||
};
|
||||
|
||||
TEST_F(UpdaterTest, getprop) {
|
||||
@@ -592,7 +620,7 @@ TEST_F(UpdaterTest, set_progress) {
|
||||
|
||||
TemporaryFile tf;
|
||||
UpdaterInfo updater_info;
|
||||
updater_info.cmd_pipe = fdopen(tf.fd, "w");
|
||||
updater_info.cmd_pipe = fdopen(tf.release(), "w");
|
||||
expect(".52", "set_progress(\".52\")", kNoCause, &updater_info);
|
||||
fflush(updater_info.cmd_pipe);
|
||||
|
||||
@@ -601,6 +629,7 @@ TEST_F(UpdaterTest, set_progress) {
|
||||
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) {
|
||||
@@ -616,7 +645,7 @@ TEST_F(UpdaterTest, show_progress) {
|
||||
|
||||
TemporaryFile tf;
|
||||
UpdaterInfo updater_info;
|
||||
updater_info.cmd_pipe = fdopen(tf.fd, "w");
|
||||
updater_info.cmd_pipe = fdopen(tf.release(), "w");
|
||||
expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_info);
|
||||
fflush(updater_info.cmd_pipe);
|
||||
|
||||
@@ -625,32 +654,22 @@ TEST_F(UpdaterTest, show_progress) {
|
||||
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) {
|
||||
// Create a zip file with new_data and patch_data.
|
||||
TemporaryFile zip_file;
|
||||
FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
|
||||
ZipWriter zip_writer(zip_file_ptr);
|
||||
|
||||
// Add a dummy new data.
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("new_data", 0));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
|
||||
// Generate and add the patch data.
|
||||
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));
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
|
||||
ASSERT_EQ(0, zip_writer.WriteBytes(patch_content.data(), patch_content.size()));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
|
||||
// Add two transfer lists. The first one contains a bsdiff; and we expect the update to succeed.
|
||||
// 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 = {
|
||||
@@ -663,27 +682,16 @@ TEST_F(UpdaterTest, block_image_update) {
|
||||
src_hash.c_str(), tgt_hash.c_str(), src_hash.c_str()),
|
||||
"free " + src_hash,
|
||||
};
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
|
||||
std::string commands = android::base::Join(transfer_list, '\n');
|
||||
ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
|
||||
// Stash and free some blocks, then fail the 2nd update intentionally.
|
||||
std::vector<std::string> fail_transfer_list = {
|
||||
"4",
|
||||
"2",
|
||||
"0",
|
||||
"2",
|
||||
"stash " + tgt_hash + " 2,0,2",
|
||||
"free " + tgt_hash,
|
||||
"fail",
|
||||
std::unordered_map<std::string, std::string> entries = {
|
||||
{ "new_data", "" },
|
||||
{ "patch_data", patch_content },
|
||||
{ "transfer_list", android::base::Join(transfer_list, '\n') },
|
||||
};
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("fail_transfer_list", 0));
|
||||
std::string fail_commands = android::base::Join(fail_transfer_list, '\n');
|
||||
ASSERT_EQ(0, zip_writer.WriteBytes(fail_commands.data(), fail_commands.size()));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
ASSERT_EQ(0, zip_writer.Finish());
|
||||
ASSERT_EQ(0, fclose(zip_file_ptr));
|
||||
|
||||
// Build the update package.
|
||||
TemporaryFile zip_file;
|
||||
BuildUpdatePackage(entries, zip_file.release());
|
||||
|
||||
MemMapping map;
|
||||
ASSERT_TRUE(map.MapFile(zip_file.path));
|
||||
@@ -694,11 +702,11 @@ TEST_F(UpdaterTest, block_image_update) {
|
||||
UpdaterInfo updater_info;
|
||||
updater_info.package_zip = handle;
|
||||
TemporaryFile temp_pipe;
|
||||
updater_info.cmd_pipe = fopen(temp_pipe.path, "wbe");
|
||||
updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
|
||||
updater_info.package_zip_addr = map.addr;
|
||||
updater_info.package_zip_len = map.length;
|
||||
|
||||
// Execute the commands in the 1st transfer list.
|
||||
// Execute the commands in the transfer list.
|
||||
TemporaryFile update_file;
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
|
||||
std::string script = "block_image_update(\"" + std::string(update_file.path) +
|
||||
@@ -709,57 +717,28 @@ TEST_F(UpdaterTest, block_image_update) {
|
||||
ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content));
|
||||
ASSERT_EQ(tgt_hash, get_sha1(updated_content));
|
||||
|
||||
// Expect the 2nd update to fail, but expect the stashed blocks to be freed.
|
||||
script = "block_image_update(\"" + std::string(update_file.path) +
|
||||
R"(", package_extract_file("fail_transfer_list"), "new_data", "patch_data"))";
|
||||
expect("", script.c_str(), kNoCause, &updater_info);
|
||||
// Updater generates the stash name based on the input file name.
|
||||
std::string name_digest = get_sha1(update_file.path);
|
||||
std::string stash_base = "/cache/recovery/" + name_digest;
|
||||
ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
|
||||
ASSERT_EQ(-1, access((stash_base + tgt_hash).c_str(), F_OK));
|
||||
ASSERT_EQ(0, rmdir(stash_base.c_str()));
|
||||
|
||||
ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
|
||||
CloseArchive(handle);
|
||||
}
|
||||
|
||||
TEST_F(UpdaterTest, new_data_short_write) {
|
||||
// Create a zip file with new_data.
|
||||
TemporaryFile zip_file;
|
||||
FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
|
||||
ZipWriter zip_writer(zip_file_ptr);
|
||||
|
||||
// Add the empty new data.
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("empty_new_data", 0));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
// Add the short written new data.
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("short_new_data", 0));
|
||||
std::string new_data_short = std::string(10, 'a');
|
||||
ASSERT_EQ(0, zip_writer.WriteBytes(new_data_short.data(), new_data_short.size()));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
// Add the data of exactly one block.
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("exact_new_data", 0));
|
||||
std::string new_data_exact = std::string(4096, 'a');
|
||||
ASSERT_EQ(0, zip_writer.WriteBytes(new_data_exact.data(), new_data_exact.size()));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
// Add a dummy patch data.
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
|
||||
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 = {
|
||||
"4",
|
||||
"1",
|
||||
"0",
|
||||
"0",
|
||||
"new 2,0,1",
|
||||
"4", "2", "0", "2", "stash " + src_hash + " 2,0,2", "free " + src_hash, "fail",
|
||||
};
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
|
||||
std::string commands = android::base::Join(transfer_list, '\n');
|
||||
ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
ASSERT_EQ(0, zip_writer.Finish());
|
||||
ASSERT_EQ(0, fclose(zip_file_ptr));
|
||||
|
||||
// Add a new data of 10 bytes to test the deadlock.
|
||||
std::unordered_map<std::string, std::string> entries = {
|
||||
{ "new_data", std::string(10, 0) },
|
||||
{ "patch_data", "" },
|
||||
{ "transfer_list", android::base::Join(transfer_list, '\n') },
|
||||
};
|
||||
|
||||
// Build the update package.
|
||||
TemporaryFile zip_file;
|
||||
BuildUpdatePackage(entries, zip_file.release());
|
||||
|
||||
MemMapping map;
|
||||
ASSERT_TRUE(map.MapFile(zip_file.path));
|
||||
@@ -770,7 +749,95 @@ TEST_F(UpdaterTest, new_data_short_write) {
|
||||
UpdaterInfo updater_info;
|
||||
updater_info.package_zip = handle;
|
||||
TemporaryFile temp_pipe;
|
||||
updater_info.cmd_pipe = fopen(temp_pipe.path, "wbe");
|
||||
updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
|
||||
updater_info.package_zip_addr = map.addr;
|
||||
updater_info.package_zip_len = map.length;
|
||||
|
||||
TemporaryFile update_file;
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
|
||||
// Expect the stashed blocks to be freed.
|
||||
std::string script = "block_image_update(\"" + std::string(update_file.path) +
|
||||
R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))";
|
||||
expect("", script.c_str(), kNoCause, &updater_info);
|
||||
// Updater generates the stash name based on the input file name.
|
||||
std::string name_digest = get_sha1(update_file.path);
|
||||
std::string stash_base = std::string(temp_stash_base_.path) + "/" + name_digest;
|
||||
ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
|
||||
ASSERT_EQ(-1, access((stash_base + src_hash).c_str(), F_OK));
|
||||
ASSERT_EQ(0, rmdir(stash_base.c_str()));
|
||||
|
||||
ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
|
||||
CloseArchive(handle);
|
||||
}
|
||||
|
||||
TEST_F(UpdaterTest, new_data_over_write) {
|
||||
std::vector<std::string> transfer_list = {
|
||||
"4", "1", "0", "0", "new 2,0,1",
|
||||
};
|
||||
|
||||
// Write 4096 + 100 bytes of new data.
|
||||
std::unordered_map<std::string, std::string> entries = {
|
||||
{ "new_data", std::string(4196, 0) },
|
||||
{ "patch_data", "" },
|
||||
{ "transfer_list", android::base::Join(transfer_list, '\n') },
|
||||
};
|
||||
|
||||
// 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;
|
||||
|
||||
TemporaryFile update_file;
|
||||
std::string script = "block_image_update(\"" + std::string(update_file.path) +
|
||||
R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))";
|
||||
expect("t", script.c_str(), kNoCause, &updater_info);
|
||||
|
||||
ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
|
||||
CloseArchive(handle);
|
||||
}
|
||||
|
||||
TEST_F(UpdaterTest, new_data_short_write) {
|
||||
std::vector<std::string> transfer_list = {
|
||||
"4",
|
||||
"1",
|
||||
"0",
|
||||
"0",
|
||||
"new 2,0,1",
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, std::string> entries = {
|
||||
{ "empty_new_data", "" },
|
||||
{ "short_new_data", std::string(10, 'a') },
|
||||
{ "exact_new_data", std::string(4096, 'a') },
|
||||
{ "patch_data", "" },
|
||||
{ "transfer_list", android::base::Join(transfer_list, '\n') },
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
@@ -788,18 +855,12 @@ TEST_F(UpdaterTest, new_data_short_write) {
|
||||
std::string script_exact_data = "block_image_update(\"" + std::string(update_file.path) +
|
||||
R"(", package_extract_file("transfer_list"), "exact_new_data", "patch_data"))";
|
||||
expect("t", script_exact_data.c_str(), kNoCause, &updater_info);
|
||||
|
||||
ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
|
||||
CloseArchive(handle);
|
||||
}
|
||||
|
||||
TEST_F(UpdaterTest, brotli_new_data) {
|
||||
// Create a zip file with new_data.
|
||||
TemporaryFile zip_file;
|
||||
FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
|
||||
ZipWriter zip_writer(zip_file_ptr);
|
||||
|
||||
// Add a brotli compressed new data entry.
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("new.dat.br", 0));
|
||||
|
||||
auto generator = []() { return rand() % 128; };
|
||||
// Generate 100 blocks of random data.
|
||||
std::string brotli_new_data;
|
||||
@@ -807,16 +868,12 @@ TEST_F(UpdaterTest, brotli_new_data) {
|
||||
generate_n(back_inserter(brotli_new_data), 4096 * 100, generator);
|
||||
|
||||
size_t encoded_size = BrotliEncoderMaxCompressedSize(brotli_new_data.size());
|
||||
std::vector<uint8_t> encoded_data(encoded_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, encoded_data.data()));
|
||||
|
||||
ASSERT_EQ(0, zip_writer.WriteBytes(encoded_data.data(), encoded_size));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
// Add a dummy patch data.
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
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.
|
||||
@@ -832,12 +889,15 @@ TEST_F(UpdaterTest, brotli_new_data) {
|
||||
"new 2,98,99",
|
||||
"new 2,99,100",
|
||||
};
|
||||
ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
|
||||
std::string commands = android::base::Join(transfer_list, '\n');
|
||||
ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
|
||||
ASSERT_EQ(0, zip_writer.FinishEntry());
|
||||
ASSERT_EQ(0, zip_writer.Finish());
|
||||
ASSERT_EQ(0, fclose(zip_file_ptr));
|
||||
|
||||
std::unordered_map<std::string, std::string> entries = {
|
||||
{ "new.dat.br", std::move(encoded_data) },
|
||||
{ "patch_data", "" },
|
||||
{ "transfer_list", android::base::Join(transfer_list, '\n') },
|
||||
};
|
||||
|
||||
TemporaryFile zip_file;
|
||||
BuildUpdatePackage(entries, zip_file.release());
|
||||
|
||||
MemMapping map;
|
||||
ASSERT_TRUE(map.MapFile(zip_file.path));
|
||||
@@ -848,7 +908,7 @@ TEST_F(UpdaterTest, brotli_new_data) {
|
||||
UpdaterInfo updater_info;
|
||||
updater_info.package_zip = handle;
|
||||
TemporaryFile temp_pipe;
|
||||
updater_info.cmd_pipe = fopen(temp_pipe.path, "wb");
|
||||
updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wb");
|
||||
updater_info.package_zip_addr = map.addr;
|
||||
updater_info.package_zip_len = map.length;
|
||||
|
||||
@@ -862,5 +922,221 @@ TEST_F(UpdaterTest, brotli_new_data) {
|
||||
std::string updated_content;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content));
|
||||
ASSERT_EQ(brotli_new_data, updated_content);
|
||||
|
||||
ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
|
||||
CloseArchive(handle);
|
||||
}
|
||||
|
||||
TEST_F(UpdaterTest, last_command_update) {
|
||||
std::string last_command_file = CacheLocation::location().last_command_file();
|
||||
|
||||
std::string block1 = std::string(4096, '1');
|
||||
std::string block2 = std::string(4096, '2');
|
||||
std::string block3 = std::string(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 = {
|
||||
"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",
|
||||
"fail",
|
||||
};
|
||||
|
||||
// Mimic a resumed update with the same transfer commands.
|
||||
std::vector<std::string> transfer_list_continue = {
|
||||
"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",
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, std::string> entries = {
|
||||
{ "new_data", "" },
|
||||
{ "patch_data", "" },
|
||||
{ "transfer_list_fail", android::base::Join(transfer_list_fail, '\n') },
|
||||
{ "transfer_list_continue", android::base::Join(transfer_list_continue, '\n') },
|
||||
};
|
||||
|
||||
// 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 src_content = block1 + block2 + block3;
|
||||
TemporaryFile update_file;
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
|
||||
std::string script =
|
||||
"block_image_update(\"" + std::string(update_file.path) +
|
||||
R"(", package_extract_file("transfer_list_fail"), "new_data", "patch_data"))";
|
||||
expect("", script.c_str(), kNoCause, &updater_info);
|
||||
|
||||
// Expect last_command to contain the last stash command.
|
||||
std::string last_command_content;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(last_command_file.c_str(), &last_command_content));
|
||||
EXPECT_EQ("2\nstash " + block3_hash + " 2,2,3", last_command_content);
|
||||
std::string updated_contents;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &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.
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
|
||||
std::string script_second_update =
|
||||
"block_image_update(\"" + std::string(update_file.path) +
|
||||
R"(", package_extract_file("transfer_list_continue"), "new_data", "patch_data"))";
|
||||
expect("t", script_second_update.c_str(), kNoCause, &updater_info);
|
||||
ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_contents));
|
||||
ASSERT_EQ(block1 + block2 + block1, updated_contents);
|
||||
|
||||
ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
|
||||
CloseArchive(handle);
|
||||
}
|
||||
|
||||
TEST_F(UpdaterTest, last_command_update_unresumable) {
|
||||
std::string last_command_file = CacheLocation::location().last_command_file();
|
||||
|
||||
std::string block1 = std::string(4096, '1');
|
||||
std::string block2 = std::string(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 = {
|
||||
"4", "2", "0", "2", "stash " + block1_hash + " 2,0,1", "move " + block2_hash + " 2,1,2 1 2,0,1",
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, std::string> entries = {
|
||||
{ "new_data", "" },
|
||||
{ "patch_data", "" },
|
||||
{ "transfer_list_unresumable", android::base::Join(transfer_list_unresumable, '\n') },
|
||||
};
|
||||
|
||||
// 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;
|
||||
|
||||
// Set up the last_command_file
|
||||
ASSERT_TRUE(
|
||||
android::base::WriteStringToFile("0\nstash " + block1_hash + " 2,0,1", last_command_file));
|
||||
|
||||
// The last_command_file will be deleted if the update encounters an unresumable failure
|
||||
// later.
|
||||
std::string src_content = block1 + block1;
|
||||
TemporaryFile update_file;
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
|
||||
std::string script =
|
||||
"block_image_update(\"" + std::string(update_file.path) +
|
||||
R"(", package_extract_file("transfer_list_unresumable"), "new_data", "patch_data"))";
|
||||
expect("", script.c_str(), kNoCause, &updater_info);
|
||||
ASSERT_EQ(-1, access(last_command_file.c_str(), R_OK));
|
||||
|
||||
ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
|
||||
CloseArchive(handle);
|
||||
}
|
||||
|
||||
TEST_F(UpdaterTest, last_command_verify) {
|
||||
std::string last_command_file = CacheLocation::location().last_command_file();
|
||||
|
||||
std::string block1 = std::string(4096, '1');
|
||||
std::string block2 = std::string(4096, '2');
|
||||
std::string block3 = std::string(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 = {
|
||||
"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",
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, std::string> entries = {
|
||||
{ "new_data", "" },
|
||||
{ "patch_data", "" },
|
||||
{ "transfer_list_verify", android::base::Join(transfer_list_verify, '\n') },
|
||||
};
|
||||
|
||||
// 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 src_content = block1 + block1 + block3;
|
||||
TemporaryFile update_file;
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
|
||||
|
||||
ASSERT_TRUE(
|
||||
android::base::WriteStringToFile("2\nstash " + block3_hash + " 2,2,3", last_command_file));
|
||||
|
||||
// Expect the verification to succeed and the last_command_file is intact.
|
||||
std::string script_verify =
|
||||
"block_image_verify(\"" + std::string(update_file.path) +
|
||||
R"(", package_extract_file("transfer_list_verify"), "new_data","patch_data"))";
|
||||
expect("t", script_verify.c_str(), kNoCause, &updater_info);
|
||||
|
||||
std::string last_command_content;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(last_command_file.c_str(), &last_command_content));
|
||||
EXPECT_EQ("2\nstash " + block3_hash + " 2,2,3", last_command_content);
|
||||
|
||||
// Expect the verification to succeed but last_command_file to be deleted; because the target
|
||||
// blocks don't have the expected contents for the second move command.
|
||||
src_content = block1 + block2 + block3;
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
|
||||
expect("t", script_verify.c_str(), kNoCause, &updater_info);
|
||||
ASSERT_EQ(-1, access(last_command_file.c_str(), R_OK));
|
||||
|
||||
ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
|
||||
CloseArchive(handle);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user