diff options
-rw-r--r-- | tests/Android.mk | 9 | ||||
-rw-r--r-- | tests/component/updater_test.cpp | 120 | ||||
-rw-r--r-- | updater/blockimg.cpp | 146 |
3 files changed, 263 insertions, 12 deletions
diff --git a/tests/Android.mk b/tests/Android.mk index ec971b38c..65f736d13 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -81,7 +81,10 @@ include $(BUILD_NATIVE_TEST) # Component tests include $(CLEAR_VARS) -LOCAL_CFLAGS := -Werror +LOCAL_CFLAGS := \ + -Werror \ + -D_FILE_OFFSET_BITS=64 + LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk LOCAL_MODULE := recovery_component_test LOCAL_C_INCLUDES := bootable/recovery @@ -136,6 +139,10 @@ LOCAL_STATIC_LIBRARIES := \ libz \ libbase \ libtune2fs \ + libfec \ + libfec_rs \ + libsquashfs_utils \ + libcutils \ $(tune2fs_static_libraries) testdata_files := $(call find-subdir-files, testdata/*) diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp index 8c4bdbaa4..4f8349e2f 100644 --- a/tests/component/updater_test.cpp +++ b/tests/component/updater_test.cpp @@ -20,6 +20,7 @@ #include <unistd.h> #include <string> +#include <vector> #include <android-base/file.h> #include <android-base/properties.h> @@ -27,12 +28,17 @@ #include <android-base/strings.h> #include <android-base/test_utils.h> #include <bootloader_message/bootloader_message.h> +#include <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 "updater/blockimg.h" #include "updater/install.h" #include "updater/updater.h" @@ -64,12 +70,19 @@ static void expect(const char* expected, const char* expr_str, CauseCode cause_c ASSERT_EQ(cause_code, state.cause_code); } +static std::string get_sha1(const std::string& content) { + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast<const uint8_t*>(content.c_str()), content.size(), digest); + return print_sha1(digest); +} + class UpdaterTest : public ::testing::Test { - protected: - virtual void SetUp() { - RegisterBuiltins(); - RegisterInstallFunctions(); - } + protected: + virtual void SetUp() override { + RegisterBuiltins(); + RegisterInstallFunctions(); + RegisterBlockImageFunctions(); + } }; TEST_F(UpdaterTest, getprop) { @@ -447,3 +460,100 @@ TEST_F(UpdaterTest, show_progress) { // recovery-updater protocol expects 3 tokens ("progress <frac> <secs>"). ASSERT_EQ(3U, android::base::Split(cmd, " ").size()); } + +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. + std::string src_content = std::string(4096, 'a') + std::string(4096, 'c'); + std::string tgt_content = std::string(4096, 'b') + std::string(4096, 'd'); + 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. + std::string src_hash = get_sha1(src_content); + std::string tgt_hash = get_sha1(tgt_content); + std::vector<std::string> transfer_list = { + "4", + "2", + "0", + "2", + "stash " + src_hash + " 2,0,2", + android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,2 2 - %s:2,0,2", patch_content.size(), + src_hash.c_str(), tgt_hash.c_str(), src_hash.c_str()), + "free " + src_hash, + }; + 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", + }; + 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)); + + MemMapping map; + ASSERT_EQ(0, sysMapFile(zip_file.path, &map)); + 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 = fopen(temp_pipe.path, "wb"); + updater_info.package_zip_addr = map.addr; + updater_info.package_zip_len = map.length; + + // Execute the commands in the 1st 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) + + R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))"; + expect("t", script.c_str(), kNoCause, &updater_info); + // The update_file should be patched correctly. + std::string updated_content; + 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); +} diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index 6227154e5..0fa83d9d5 100644 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -67,6 +67,21 @@ struct RangeSet { size_t count; // Limit is INT_MAX. size_t size; std::vector<size_t> pos; // Actual limit is INT_MAX. + + // Get the block number for the ith(starting from 0) block in the range set. + int get_block(size_t idx) const { + if (idx >= size) { + LOG(ERROR) << "index: " << idx << " is greater than range set size: " << size; + return -1; + } + for (size_t i = 0; i < pos.size(); i += 2) { + if (idx < pos[i + 1] - pos[i]) { + return pos[i] + idx; + } + idx -= (pos[i + 1] - pos[i]); + } + return -1; + } }; static CauseCode failure_type = kNoCause; @@ -442,6 +457,117 @@ static int LoadSrcTgtVersion1(CommandParameters& params, RangeSet& tgt, size_t& return rc; } +// Print the hash in hex for corrupted source blocks (excluding the stashed blocks which is +// handled separately). +static void PrintHashForCorruptedSourceBlocks(const CommandParameters& params, + const std::vector<uint8_t>& buffer) { + LOG(INFO) << "unexpected contents of source blocks in cmd:\n" << params.cmdline; + if (params.version < 3) { + // TODO handle version 1,2 + LOG(WARNING) << "version number " << params.version << " is not supported to print hashes"; + return; + } + + CHECK(params.tokens[0] == "move" || params.tokens[0] == "bsdiff" || + params.tokens[0] == "imgdiff"); + + size_t pos = 0; + // Command example: + // move <onehash> <tgt_range> <src_blk_count> <src_range> [<loc_range> <stashed_blocks>] + // bsdiff <offset> <len> <src_hash> <tgt_hash> <tgt_range> <src_blk_count> <src_range> + // [<loc_range> <stashed_blocks>] + if (params.tokens[0] == "move") { + // src_range for move starts at the 4th position. + if (params.tokens.size() < 5) { + LOG(ERROR) << "failed to parse source range in cmd:\n" << params.cmdline; + return; + } + pos = 4; + } else { + // src_range for diff starts at the 7th position. + if (params.tokens.size() < 8) { + LOG(ERROR) << "failed to parse source range in cmd:\n" << params.cmdline; + return; + } + pos = 7; + } + + // Source blocks in stash only, no work to do. + if (params.tokens[pos] == "-") { + return; + } + + RangeSet src = parse_range(params.tokens[pos++]); + + RangeSet locs; + // If there's no stashed blocks, content in the buffer is consecutive and has the same + // order as the source blocks. + if (pos == params.tokens.size()) { + locs.count = 1; + locs.size = src.size; + locs.pos = { 0, src.size }; + } else { + // Otherwise, the next token is the offset of the source blocks in the target range. + // Example: for the tokens <4,63946,63947,63948,63979> <4,6,7,8,39> <stashed_blocks>; + // We want to print SHA-1 for the data in buffer[6], buffer[8], buffer[9] ... buffer[38]; + // this corresponds to the 32 src blocks #63946, #63948, #63949 ... #63978. + locs = parse_range(params.tokens[pos++]); + CHECK_EQ(src.size, locs.size); + CHECK_EQ(locs.pos.size() % 2, static_cast<size_t>(0)); + } + + LOG(INFO) << "printing hash in hex for " << src.size << " source blocks"; + for (size_t i = 0; i < src.size; i++) { + int block_num = src.get_block(i); + CHECK_NE(block_num, -1); + int buffer_index = locs.get_block(i); + CHECK_NE(buffer_index, -1); + CHECK_LE((buffer_index + 1) * BLOCKSIZE, buffer.size()); + + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(buffer.data() + buffer_index * BLOCKSIZE, BLOCKSIZE, digest); + std::string hexdigest = print_sha1(digest); + LOG(INFO) << " block number: " << block_num << ", SHA-1: " << hexdigest; + } +} + +// If the calculated hash for the whole stash doesn't match the stash id, print the SHA-1 +// in hex for each block. +static void PrintHashForCorruptedStashedBlocks(const std::string& id, + const std::vector<uint8_t>& buffer, + const RangeSet& src) { + LOG(INFO) << "printing hash in hex for stash_id: " << id; + CHECK_EQ(src.size * BLOCKSIZE, buffer.size()); + + for (size_t i = 0; i < src.size; i++) { + int block_num = src.get_block(i); + CHECK_NE(block_num, -1); + + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(buffer.data() + i * BLOCKSIZE, BLOCKSIZE, digest); + std::string hexdigest = print_sha1(digest); + LOG(INFO) << " block number: " << block_num << ", SHA-1: " << hexdigest; + } +} + +// If the stash file doesn't exist, read the source blocks this stash contains and print the +// SHA-1 for these blocks. +static void PrintHashForMissingStashedBlocks(const std::string& id, int fd) { + if (stash_map.find(id) == stash_map.end()) { + LOG(ERROR) << "No stash saved for id: " << id; + return; + } + + LOG(INFO) << "print hash in hex for source blocks in missing stash: " << id; + const RangeSet& src = stash_map[id]; + std::vector<uint8_t> buffer(src.size * BLOCKSIZE); + if (ReadBlocks(src, buffer, fd) == -1) { + LOG(ERROR) << "failed to read source blocks for stash: " << id; + return; + } + PrintHashForCorruptedStashedBlocks(id, buffer, src); +} + static int VerifyBlocks(const std::string& expected, const std::vector<uint8_t>& buffer, const size_t blocks, bool printerror) { uint8_t digest[SHA_DIGEST_LENGTH]; @@ -540,6 +666,7 @@ static int LoadStash(CommandParameters& params, const std::string& base, const s } if (VerifyBlocks(id, buffer, src.size, true) != 0) { LOG(ERROR) << "failed to verify loaded source blocks in stash map."; + PrintHashForCorruptedStashedBlocks(id, buffer, src); return -1; } return 0; @@ -564,6 +691,7 @@ static int LoadStash(CommandParameters& params, const std::string& base, const s if (res == -1) { if (errno != ENOENT || printnoent) { PLOG(ERROR) << "stat \"" << fn << "\" failed"; + PrintHashForMissingStashedBlocks(id, params.fd); } return -1; } @@ -591,6 +719,13 @@ static int LoadStash(CommandParameters& params, const std::string& base, const s if (verify && VerifyBlocks(id, buffer, *blocks, true) != 0) { LOG(ERROR) << "unexpected contents in " << fn; + if (stash_map.find(id) == stash_map.end()) { + LOG(ERROR) << "failed to find source blocks number for stash " << id + << " when executing command: " << params.cmdname; + } else { + const RangeSet& src = stash_map[id]; + PrintHashForCorruptedStashedBlocks(id, buffer, src); + } DeleteFile(fn); return -1; } @@ -773,6 +908,7 @@ static int SaveStash(CommandParameters& params, const std::string& base, return -1; } blocks = src.size; + stash_map[id] = src; if (usehash && VerifyBlocks(id, buffer, blocks, true) != 0) { // Source blocks have unexpected contents. If we actually need this @@ -783,9 +919,8 @@ static int SaveStash(CommandParameters& params, const std::string& base, return 0; } - // In verify mode, save source range_set instead of stashing blocks. + // In verify mode, we don't need to stash any blocks. if (!params.canwrite && usehash) { - stash_map[id] = src; return 0; } @@ -1003,6 +1138,8 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t& // Valid source data not available, update cannot be resumed LOG(ERROR) << "partition has unexpected contents"; + PrintHashForCorruptedSourceBlocks(params, params.buffer); + params.isunresumable = true; return -1; @@ -1071,9 +1208,8 @@ static int PerformCommandFree(CommandParameters& params) { const std::string& id = params.tokens[params.cpos++]; - if (!params.canwrite && stash_map.find(id) != stash_map.end()) { + if (stash_map.find(id) != stash_map.end()) { stash_map.erase(id); - return 0; } if (params.createdstash || params.canwrite) { @@ -1215,10 +1351,8 @@ static int PerformCommandDiff(CommandParameters& params) { if (params.canwrite) { if (status == 0) { LOG(INFO) << "patching " << blocks << " blocks to " << tgt.size; - Value patch_value(VAL_BLOB, std::string(reinterpret_cast<const char*>(params.patch_start + offset), len)); - RangeSinkState rss(tgt); rss.fd = params.fd; rss.p_block = 0; |