/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include "otautil/paths.h" #include "otautil/print_sha1.h" #include "updater/blockimg.h" #include "updater/build_info.h" #include "updater/install.h" #include "updater/simulator_runtime.h" #include "updater/target_files.h" #include "updater/updater.h" using std::string; // echo -n "system.img" > system.img && img2simg system.img sparse_system_string_.img 4096 && // hexdump -v -e '" " 12/1 "0x%02x, " "\n"' sparse_system_string_.img // The total size of the result sparse image is 4136 bytes; and we can append 0s in the end to get // the full image. constexpr uint8_t SPARSE_SYSTEM_HEADER[] = { 0x3a, 0xff, 0x26, 0xed, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x00, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0xca, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x10, 0x00, 0x00, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x69, 0x6d, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; static void AddZipEntries(int fd, const std::map& entries) { FILE* zip_file = fdopen(fd, "w"); ZipWriter writer(zip_file); for (const auto& pair : entries) { ASSERT_EQ(0, writer.StartEntry(pair.first.c_str(), 0)); ASSERT_EQ(0, writer.WriteBytes(pair.second.data(), pair.second.size())); ASSERT_EQ(0, writer.FinishEntry()); } ASSERT_EQ(0, writer.Finish()); ASSERT_EQ(0, fclose(zip_file)); } static string CalculateSha1(const string& data) { uint8_t digest[SHA_DIGEST_LENGTH]; SHA1(reinterpret_cast(data.c_str()), data.size(), digest); return print_sha1(digest); } static void CreateBsdiffPatch(const string& src, const string& tgt, string* patch) { TemporaryFile patch_file; ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast(src.data()), src.size(), reinterpret_cast(tgt.data()), tgt.size(), patch_file.path, nullptr)); ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, patch)); } static void RunSimulation(std::string_view src_tf, std::string_view ota_package, bool expected) { TemporaryFile cmd_pipe; TemporaryFile temp_saved_source; TemporaryFile temp_last_command; TemporaryDir temp_stash_base; Paths::Get().set_cache_temp_source(temp_saved_source.path); Paths::Get().set_last_command_file(temp_last_command.path); Paths::Get().set_stash_directory_base(temp_stash_base.path); // Configure edify's functions. RegisterBuiltins(); RegisterInstallFunctions(); RegisterBlockImageFunctions(); // Run the update simulation and check the result. TemporaryDir work_dir; BuildInfo build_info(work_dir.path, false); ASSERT_TRUE(build_info.ParseTargetFile(src_tf, false)); Updater updater(std::make_unique(&build_info)); ASSERT_TRUE(updater.Init(cmd_pipe.release(), ota_package, false)); ASSERT_EQ(expected, updater.RunUpdate()); // TODO(xunchang) check the recovery&system has the expected contents. } class UpdateSimulatorTest : public ::testing::Test { protected: void SetUp() override { std::vector props = { "import /oem/oem.prop oem*", "# begin build properties", "# autogenerated by buildinfo.sh", "ro.build.id=OPR1.170510.001", "ro.build.display.id=OPR1.170510.001 dev-keys", "ro.build.version.incremental=3993052", "ro.build.version.release=O", "ro.build.date=Wed May 10 11:10:29 UTC 2017", "ro.build.date.utc=1494414629", "ro.build.type=user", "ro.build.tags=dev-keys", "ro.build.flavor=angler-user", "ro.product.system.brand=google", "ro.product.system.name=angler", "ro.product.system.device=angler", }; build_prop_string_ = android::base::Join(props, "\n"); fstab_content_ = R"( # # More comments..... /dev/block/by-name/system /system ext4 ro,barrier=1 wait /dev/block/by-name/vendor /vendor ext4 ro wait,verify=/dev/metadata /dev/block/by-name/cache /cache ext4 noatime,errors=panic wait,check /dev/block/by-name/modem /firmware vfat ro,uid=1000,gid=1000, wait /dev/block/by-name/boot /boot emmc defaults defaults /dev/block/by-name/recovery /recovery emmc defaults defaults /dev/block/by-name/misc /misc emmc defaults /dev/block/by-name/modem /modem emmc defaults defaults)"; raw_system_string_ = "system.img" + string(4086, '\0'); // raw image is 4096 bytes in total sparse_system_string_ = string(SPARSE_SYSTEM_HEADER, std::end(SPARSE_SYSTEM_HEADER)) + string(4136 - sizeof(SPARSE_SYSTEM_HEADER), '\0'); } string build_prop_string_; string fstab_content_; string raw_system_string_; string sparse_system_string_; }; TEST_F(UpdateSimulatorTest, TargetFile_ExtractImage) { TemporaryFile zip_file; AddZipEntries(zip_file.release(), { { "META/misc_info.txt", "extfs_sparse_flag=-s" }, { "IMAGES/system.img", sparse_system_string_ } }); TargetFile target_file(zip_file.path, false); ASSERT_TRUE(target_file.Open()); TemporaryDir temp_dir; TemporaryFile raw_image; ASSERT_TRUE(target_file.ExtractImage( "IMAGES/system.img", FstabInfo("/dev/system", "system", "ext4"), temp_dir.path, &raw_image)); // Check the raw image has expected contents. string content; ASSERT_TRUE(android::base::ReadFileToString(raw_image.path, &content)); string expected_content = "system.img" + string(4086, '\0'); ASSERT_EQ(expected_content, content); } TEST_F(UpdateSimulatorTest, TargetFile_ParseFstabInfo) { TemporaryFile zip_file; AddZipEntries(zip_file.release(), { { "META/misc_info.txt", "" }, { "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ } }); TargetFile target_file(zip_file.path, false); ASSERT_TRUE(target_file.Open()); std::vector fstab_info; EXPECT_TRUE(target_file.ParseFstabInfo(&fstab_info)); std::vector> transformed; std::transform(fstab_info.begin(), fstab_info.end(), std::back_inserter(transformed), [](const FstabInfo& info) { return std::vector{ info.blockdev_name, info.mount_point, info.fs_type }; }); std::vector> expected = { { "/dev/block/by-name/system", "/system", "ext4" }, { "/dev/block/by-name/vendor", "/vendor", "ext4" }, { "/dev/block/by-name/cache", "/cache", "ext4" }, { "/dev/block/by-name/boot", "/boot", "emmc" }, { "/dev/block/by-name/recovery", "/recovery", "emmc" }, { "/dev/block/by-name/misc", "/misc", "emmc" }, { "/dev/block/by-name/modem", "/modem", "emmc" }, }; EXPECT_EQ(expected, transformed); } TEST_F(UpdateSimulatorTest, BuildInfo_ParseTargetFile) { std::map entries = { { "META/misc_info.txt", "" }, { "SYSTEM/build.prop", build_prop_string_ }, { "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ }, { "IMAGES/recovery.img", "" }, { "IMAGES/boot.img", "" }, { "IMAGES/misc.img", "" }, { "IMAGES/system.map", "" }, { "IMAGES/system.img", sparse_system_string_ }, }; TemporaryFile zip_file; AddZipEntries(zip_file.release(), entries); TemporaryDir temp_dir; BuildInfo build_info(temp_dir.path, false); ASSERT_TRUE(build_info.ParseTargetFile(zip_file.path, false)); std::map expected_result = { { "ro.build.id", "OPR1.170510.001" }, { "ro.build.display.id", "OPR1.170510.001 dev-keys" }, { "ro.build.version.incremental", "3993052" }, { "ro.build.version.release", "O" }, { "ro.build.date", "Wed May 10 11:10:29 UTC 2017" }, { "ro.build.date.utc", "1494414629" }, { "ro.build.type", "user" }, { "ro.build.tags", "dev-keys" }, { "ro.build.flavor", "angler-user" }, { "ro.product.brand", "google" }, { "ro.product.name", "angler" }, { "ro.product.device", "angler" }, }; for (const auto& [key, value] : expected_result) { ASSERT_EQ(value, build_info.GetProperty(key, "")); } // Check that the temp files for each block device are created successfully. for (auto name : { "/dev/block/by-name/system", "/dev/block/by-name/recovery", "/dev/block/by-name/boot", "/dev/block/by-name/misc" }) { ASSERT_EQ(0, access(build_info.FindBlockDeviceName(name).c_str(), R_OK)); } } TEST_F(UpdateSimulatorTest, RunUpdateSmoke) { string recovery_img_string = "recovery.img"; string boot_img_string = "boot.img"; std::map src_entries{ { "META/misc_info.txt", "extfs_sparse_flag=-s" }, { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ }, { "SYSTEM/build.prop", build_prop_string_ }, { "IMAGES/recovery.img", "" }, { "IMAGES/boot.img", boot_img_string }, { "IMAGES/system.img", sparse_system_string_ }, }; // Construct the source target-files. TemporaryFile src_tf; AddZipEntries(src_tf.release(), src_entries); string recovery_from_boot; CreateBsdiffPatch(boot_img_string, recovery_img_string, &recovery_from_boot); // Set up the apply patch commands to patch the recovery image. string recovery_sha1 = CalculateSha1(recovery_img_string); string boot_sha1 = CalculateSha1(boot_img_string); string apply_patch_source_string = android::base::StringPrintf( "EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str()); string apply_patch_target_string = android::base::StringPrintf( "EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str()); string check_command = android::base::StringPrintf( R"(patch_partition_check("%s", "%s") || abort("check failed");)", apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); string patch_command = android::base::StringPrintf( R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("patch failed");)", apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); // Add the commands to update the system image. Test common commands: // * getprop // * ui_print // * patch_partition // * package_extract_file (single argument) // * block_image_verify, block_image_update string tgt_system_string = string(4096, 'a'); string system_patch; CreateBsdiffPatch(raw_system_string_, tgt_system_string, &system_patch); string tgt_system_hash = CalculateSha1(tgt_system_string); string src_system_hash = CalculateSha1(raw_system_string_); std::vector transfer_list = { "4", "1", "0", "0", android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,1 1 2,0,1", system_patch.size(), src_system_hash.c_str(), tgt_system_hash.c_str()), }; // Construct the updater_script. std::vector updater_commands = { R"(getprop("ro.product.device") == "angler" || abort("This package is for \"angler\"");)", R"(ui_print("Source: angler/OPR1.170510.001");)", check_command, patch_command, R"(block_image_verify("/dev/block/by-name/system", )" R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )" R"(abort("Failed to verify system.");)", R"(block_image_update("/dev/block/by-name/system", )" R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )" R"(abort("Failed to verify system.");)", }; string updater_script = android::base::Join(updater_commands, '\n'); // Construct the ota update package. std::map ota_entries{ { "system.new.dat", "" }, { "system.patch.dat", system_patch }, { "system.transfer.list", android::base::Join(transfer_list, '\n') }, { "META-INF/com/google/android/updater-script", updater_script }, { "patch.p", recovery_from_boot }, }; TemporaryFile ota_package; AddZipEntries(ota_package.release(), ota_entries); RunSimulation(src_tf.path, ota_package.path, true); } TEST_F(UpdateSimulatorTest, RunUpdateUnrecognizedFunction) { std::map src_entries{ { "META/misc_info.txt", "extfs_sparse_flag=-s" }, { "IMAGES/system.img", sparse_system_string_ }, { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ }, { "SYSTEM/build.prop", build_prop_string_ }, }; TemporaryFile src_tf; AddZipEntries(src_tf.release(), src_entries); std::map ota_entries{ { "system.new.dat", "" }, { "system.patch.dat", "" }, { "system.transfer.list", "" }, { "META-INF/com/google/android/updater-script", R"(bad_function("");)" }, }; TemporaryFile ota_package; AddZipEntries(ota_package.release(), ota_entries); RunSimulation(src_tf.path, ota_package.path, false); } TEST_F(UpdateSimulatorTest, RunUpdateApplyPatchFailed) { string recovery_img_string = "recovery.img"; string boot_img_string = "boot.img"; std::map src_entries{ { "META/misc_info.txt", "extfs_sparse_flag=-s" }, { "IMAGES/recovery.img", "" }, { "IMAGES/boot.img", boot_img_string }, { "IMAGES/system.img", sparse_system_string_ }, { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ }, { "SYSTEM/build.prop", build_prop_string_ }, }; TemporaryFile src_tf; AddZipEntries(src_tf.release(), src_entries); string recovery_sha1 = CalculateSha1(recovery_img_string); string boot_sha1 = CalculateSha1(boot_img_string); string apply_patch_source_string = android::base::StringPrintf( "EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str()); string apply_patch_target_string = android::base::StringPrintf( "EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str()); string check_command = android::base::StringPrintf( R"(patch_partition_check("%s", "%s") || abort("check failed");)", apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); string patch_command = android::base::StringPrintf( R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("failed");)", apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); // Give an invalid recovery patch and expect the apply patch to fail. // TODO(xunchang) check the cause code. std::vector updater_commands = { R"(ui_print("Source: angler/OPR1.170510.001");)", check_command, patch_command, }; string updater_script = android::base::Join(updater_commands, '\n'); std::map ota_entries{ { "system.new.dat", "" }, { "system.patch.dat", "" }, { "system.transfer.list", "" }, { "META-INF/com/google/android/updater-script", updater_script }, { "patch.p", "random string" }, }; TemporaryFile ota_package; AddZipEntries(ota_package.release(), ota_entries); RunSimulation(src_tf.path, ota_package.path, false); }