Tianjie Xu | c3a161e | 2019-06-20 18:16:49 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2019 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #include <stdint.h> |
| 18 | #include <stdio.h> |
| 19 | |
| 20 | #include <map> |
| 21 | #include <string> |
| 22 | #include <vector> |
| 23 | |
| 24 | #include <android-base/file.h> |
| 25 | #include <android-base/stringprintf.h> |
| 26 | #include <android-base/strings.h> |
| 27 | #include <bsdiff/bsdiff.h> |
| 28 | #include <gtest/gtest.h> |
| 29 | #include <ziparchive/zip_writer.h> |
| 30 | |
| 31 | #include "otautil/paths.h" |
| 32 | #include "otautil/print_sha1.h" |
| 33 | #include "updater/blockimg.h" |
| 34 | #include "updater/build_info.h" |
| 35 | #include "updater/install.h" |
| 36 | #include "updater/simulator_runtime.h" |
| 37 | #include "updater/target_files.h" |
| 38 | #include "updater/updater.h" |
| 39 | |
| 40 | using std::string; |
| 41 | |
| 42 | // echo -n "system.img" > system.img && img2simg system.img sparse_system_string_.img 4096 && |
| 43 | // hexdump -v -e '" " 12/1 "0x%02x, " "\n"' sparse_system_string_.img |
| 44 | // The total size of the result sparse image is 4136 bytes; and we can append 0s in the end to get |
| 45 | // the full image. |
| 46 | constexpr uint8_t SPARSE_SYSTEM_HEADER[] = { |
| 47 | 0x3a, 0xff, 0x26, 0xed, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x00, 0x10, 0x00, |
| 48 | 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0xca, |
| 49 | 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x10, 0x00, 0x00, 0x73, 0x79, 0x73, 0x74, 0x65, |
| 50 | 0x6d, 0x2e, 0x69, 0x6d, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 51 | }; |
| 52 | |
| 53 | static void AddZipEntries(int fd, const std::map<string, string>& entries) { |
| 54 | FILE* zip_file = fdopen(fd, "w"); |
| 55 | ZipWriter writer(zip_file); |
| 56 | for (const auto& pair : entries) { |
| 57 | ASSERT_EQ(0, writer.StartEntry(pair.first.c_str(), 0)); |
| 58 | ASSERT_EQ(0, writer.WriteBytes(pair.second.data(), pair.second.size())); |
| 59 | ASSERT_EQ(0, writer.FinishEntry()); |
| 60 | } |
| 61 | ASSERT_EQ(0, writer.Finish()); |
| 62 | ASSERT_EQ(0, fclose(zip_file)); |
| 63 | } |
| 64 | |
| 65 | static string CalculateSha1(const string& data) { |
| 66 | uint8_t digest[SHA_DIGEST_LENGTH]; |
| 67 | SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest); |
| 68 | return print_sha1(digest); |
| 69 | } |
| 70 | |
| 71 | static void CreateBsdiffPatch(const string& src, const string& tgt, string* patch) { |
| 72 | TemporaryFile patch_file; |
| 73 | ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src.data()), src.size(), |
| 74 | reinterpret_cast<const uint8_t*>(tgt.data()), tgt.size(), |
| 75 | patch_file.path, nullptr)); |
| 76 | ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, patch)); |
| 77 | } |
| 78 | |
| 79 | static void RunSimulation(std::string_view src_tf, std::string_view ota_package, bool expected) { |
| 80 | TemporaryFile cmd_pipe; |
| 81 | TemporaryFile temp_saved_source; |
| 82 | TemporaryFile temp_last_command; |
| 83 | TemporaryDir temp_stash_base; |
| 84 | |
| 85 | Paths::Get().set_cache_temp_source(temp_saved_source.path); |
| 86 | Paths::Get().set_last_command_file(temp_last_command.path); |
| 87 | Paths::Get().set_stash_directory_base(temp_stash_base.path); |
| 88 | |
| 89 | // Configure edify's functions. |
| 90 | RegisterBuiltins(); |
| 91 | RegisterInstallFunctions(); |
| 92 | RegisterBlockImageFunctions(); |
| 93 | |
| 94 | // Run the update simulation and check the result. |
| 95 | TemporaryDir work_dir; |
Tianjie Xu | 60b242c | 2019-07-30 16:48:52 -0700 | [diff] [blame] | 96 | BuildInfo build_info(work_dir.path, false); |
Tianjie Xu | c3a161e | 2019-06-20 18:16:49 -0700 | [diff] [blame] | 97 | ASSERT_TRUE(build_info.ParseTargetFile(src_tf, false)); |
| 98 | Updater updater(std::make_unique<SimulatorRuntime>(&build_info)); |
| 99 | ASSERT_TRUE(updater.Init(cmd_pipe.release(), ota_package, false)); |
| 100 | ASSERT_EQ(expected, updater.RunUpdate()); |
| 101 | // TODO(xunchang) check the recovery&system has the expected contents. |
| 102 | } |
| 103 | |
| 104 | class UpdateSimulatorTest : public ::testing::Test { |
| 105 | protected: |
| 106 | void SetUp() override { |
| 107 | std::vector<string> props = { |
| 108 | "import /oem/oem.prop oem*", |
| 109 | "# begin build properties", |
| 110 | "# autogenerated by buildinfo.sh", |
| 111 | "ro.build.id=OPR1.170510.001", |
| 112 | "ro.build.display.id=OPR1.170510.001 dev-keys", |
| 113 | "ro.build.version.incremental=3993052", |
| 114 | "ro.build.version.release=O", |
| 115 | "ro.build.date=Wed May 10 11:10:29 UTC 2017", |
| 116 | "ro.build.date.utc=1494414629", |
| 117 | "ro.build.type=user", |
| 118 | "ro.build.tags=dev-keys", |
| 119 | "ro.build.flavor=angler-user", |
| 120 | "ro.product.system.brand=google", |
| 121 | "ro.product.system.name=angler", |
| 122 | "ro.product.system.device=angler", |
| 123 | }; |
| 124 | build_prop_string_ = android::base::Join(props, "\n"); |
| 125 | |
| 126 | fstab_content_ = R"( |
| 127 | #<src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags> |
| 128 | # More comments..... |
| 129 | |
| 130 | /dev/block/by-name/system /system ext4 ro,barrier=1 wait |
| 131 | /dev/block/by-name/vendor /vendor ext4 ro wait,verify=/dev/metadata |
| 132 | /dev/block/by-name/cache /cache ext4 noatime,errors=panic wait,check |
| 133 | /dev/block/by-name/modem /firmware vfat ro,uid=1000,gid=1000, wait |
| 134 | /dev/block/by-name/boot /boot emmc defaults defaults |
| 135 | /dev/block/by-name/recovery /recovery emmc defaults defaults |
| 136 | /dev/block/by-name/misc /misc emmc defaults |
| 137 | /dev/block/by-name/modem /modem emmc defaults defaults)"; |
| 138 | |
| 139 | raw_system_string_ = "system.img" + string(4086, '\0'); // raw image is 4096 bytes in total |
| 140 | sparse_system_string_ = string(SPARSE_SYSTEM_HEADER, std::end(SPARSE_SYSTEM_HEADER)) + |
| 141 | string(4136 - sizeof(SPARSE_SYSTEM_HEADER), '\0'); |
| 142 | } |
| 143 | |
| 144 | string build_prop_string_; |
| 145 | string fstab_content_; |
| 146 | string raw_system_string_; |
| 147 | string sparse_system_string_; |
| 148 | }; |
| 149 | |
| 150 | TEST_F(UpdateSimulatorTest, TargetFile_ExtractImage) { |
| 151 | TemporaryFile zip_file; |
| 152 | AddZipEntries(zip_file.release(), { { "META/misc_info.txt", "extfs_sparse_flag=-s" }, |
| 153 | { "IMAGES/system.img", sparse_system_string_ } }); |
| 154 | TargetFile target_file(zip_file.path, false); |
| 155 | ASSERT_TRUE(target_file.Open()); |
| 156 | |
| 157 | TemporaryDir temp_dir; |
| 158 | TemporaryFile raw_image; |
| 159 | ASSERT_TRUE(target_file.ExtractImage( |
| 160 | "IMAGES/system.img", FstabInfo("/dev/system", "system", "ext4"), temp_dir.path, &raw_image)); |
| 161 | |
| 162 | // Check the raw image has expected contents. |
| 163 | string content; |
| 164 | ASSERT_TRUE(android::base::ReadFileToString(raw_image.path, &content)); |
| 165 | string expected_content = "system.img" + string(4086, '\0'); |
| 166 | ASSERT_EQ(expected_content, content); |
| 167 | } |
| 168 | |
| 169 | TEST_F(UpdateSimulatorTest, TargetFile_ParseFstabInfo) { |
| 170 | TemporaryFile zip_file; |
| 171 | AddZipEntries(zip_file.release(), |
| 172 | { { "META/misc_info.txt", "" }, |
| 173 | { "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ } }); |
| 174 | TargetFile target_file(zip_file.path, false); |
| 175 | ASSERT_TRUE(target_file.Open()); |
| 176 | |
| 177 | std::vector<FstabInfo> fstab_info; |
| 178 | EXPECT_TRUE(target_file.ParseFstabInfo(&fstab_info)); |
| 179 | |
| 180 | std::vector<std::vector<string>> transformed; |
| 181 | std::transform(fstab_info.begin(), fstab_info.end(), std::back_inserter(transformed), |
| 182 | [](const FstabInfo& info) { |
| 183 | return std::vector<string>{ info.blockdev_name, info.mount_point, info.fs_type }; |
| 184 | }); |
| 185 | |
| 186 | std::vector<std::vector<string>> expected = { |
| 187 | { "/dev/block/by-name/system", "/system", "ext4" }, |
| 188 | { "/dev/block/by-name/vendor", "/vendor", "ext4" }, |
| 189 | { "/dev/block/by-name/cache", "/cache", "ext4" }, |
| 190 | { "/dev/block/by-name/boot", "/boot", "emmc" }, |
| 191 | { "/dev/block/by-name/recovery", "/recovery", "emmc" }, |
| 192 | { "/dev/block/by-name/misc", "/misc", "emmc" }, |
| 193 | { "/dev/block/by-name/modem", "/modem", "emmc" }, |
| 194 | }; |
| 195 | EXPECT_EQ(expected, transformed); |
| 196 | } |
| 197 | |
| 198 | TEST_F(UpdateSimulatorTest, BuildInfo_ParseTargetFile) { |
| 199 | std::map<string, string> entries = { |
| 200 | { "META/misc_info.txt", "" }, |
| 201 | { "SYSTEM/build.prop", build_prop_string_ }, |
| 202 | { "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ }, |
| 203 | { "IMAGES/recovery.img", "" }, |
| 204 | { "IMAGES/boot.img", "" }, |
| 205 | { "IMAGES/misc.img", "" }, |
| 206 | { "IMAGES/system.map", "" }, |
| 207 | { "IMAGES/system.img", sparse_system_string_ }, |
| 208 | }; |
| 209 | |
| 210 | TemporaryFile zip_file; |
| 211 | AddZipEntries(zip_file.release(), entries); |
| 212 | |
| 213 | TemporaryDir temp_dir; |
Tianjie Xu | 60b242c | 2019-07-30 16:48:52 -0700 | [diff] [blame] | 214 | BuildInfo build_info(temp_dir.path, false); |
Tianjie Xu | c3a161e | 2019-06-20 18:16:49 -0700 | [diff] [blame] | 215 | ASSERT_TRUE(build_info.ParseTargetFile(zip_file.path, false)); |
| 216 | |
| 217 | std::map<string, string> expected_result = { |
| 218 | { "ro.build.id", "OPR1.170510.001" }, |
| 219 | { "ro.build.display.id", "OPR1.170510.001 dev-keys" }, |
| 220 | { "ro.build.version.incremental", "3993052" }, |
| 221 | { "ro.build.version.release", "O" }, |
| 222 | { "ro.build.date", "Wed May 10 11:10:29 UTC 2017" }, |
| 223 | { "ro.build.date.utc", "1494414629" }, |
| 224 | { "ro.build.type", "user" }, |
| 225 | { "ro.build.tags", "dev-keys" }, |
| 226 | { "ro.build.flavor", "angler-user" }, |
| 227 | { "ro.product.brand", "google" }, |
| 228 | { "ro.product.name", "angler" }, |
| 229 | { "ro.product.device", "angler" }, |
| 230 | }; |
| 231 | |
| 232 | for (const auto& [key, value] : expected_result) { |
| 233 | ASSERT_EQ(value, build_info.GetProperty(key, "")); |
| 234 | } |
| 235 | |
| 236 | // Check that the temp files for each block device are created successfully. |
| 237 | for (auto name : { "/dev/block/by-name/system", "/dev/block/by-name/recovery", |
| 238 | "/dev/block/by-name/boot", "/dev/block/by-name/misc" }) { |
| 239 | ASSERT_EQ(0, access(build_info.FindBlockDeviceName(name).c_str(), R_OK)); |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | TEST_F(UpdateSimulatorTest, RunUpdateSmoke) { |
| 244 | string recovery_img_string = "recovery.img"; |
| 245 | string boot_img_string = "boot.img"; |
| 246 | |
| 247 | std::map<string, string> src_entries{ |
| 248 | { "META/misc_info.txt", "extfs_sparse_flag=-s" }, |
| 249 | { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ }, |
| 250 | { "SYSTEM/build.prop", build_prop_string_ }, |
| 251 | { "IMAGES/recovery.img", "" }, |
| 252 | { "IMAGES/boot.img", boot_img_string }, |
| 253 | { "IMAGES/system.img", sparse_system_string_ }, |
| 254 | }; |
| 255 | |
| 256 | // Construct the source target-files. |
| 257 | TemporaryFile src_tf; |
| 258 | AddZipEntries(src_tf.release(), src_entries); |
| 259 | |
| 260 | string recovery_from_boot; |
| 261 | CreateBsdiffPatch(boot_img_string, recovery_img_string, &recovery_from_boot); |
| 262 | |
| 263 | // Set up the apply patch commands to patch the recovery image. |
| 264 | string recovery_sha1 = CalculateSha1(recovery_img_string); |
| 265 | string boot_sha1 = CalculateSha1(boot_img_string); |
| 266 | string apply_patch_source_string = android::base::StringPrintf( |
| 267 | "EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str()); |
| 268 | string apply_patch_target_string = android::base::StringPrintf( |
| 269 | "EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str()); |
| 270 | string check_command = android::base::StringPrintf( |
| 271 | R"(patch_partition_check("%s", "%s") || abort("check failed");)", |
| 272 | apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); |
| 273 | string patch_command = android::base::StringPrintf( |
| 274 | R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("patch failed");)", |
| 275 | apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); |
| 276 | |
| 277 | // Add the commands to update the system image. Test common commands: |
| 278 | // * getprop |
| 279 | // * ui_print |
| 280 | // * patch_partition |
| 281 | // * package_extract_file (single argument) |
| 282 | // * block_image_verify, block_image_update |
| 283 | string tgt_system_string = string(4096, 'a'); |
| 284 | string system_patch; |
| 285 | CreateBsdiffPatch(raw_system_string_, tgt_system_string, &system_patch); |
| 286 | |
| 287 | string tgt_system_hash = CalculateSha1(tgt_system_string); |
| 288 | string src_system_hash = CalculateSha1(raw_system_string_); |
| 289 | |
| 290 | std::vector<std::string> transfer_list = { |
| 291 | "4", |
| 292 | "1", |
| 293 | "0", |
| 294 | "0", |
| 295 | android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,1 1 2,0,1", system_patch.size(), |
| 296 | src_system_hash.c_str(), tgt_system_hash.c_str()), |
| 297 | }; |
| 298 | |
| 299 | // Construct the updater_script. |
| 300 | std::vector<string> updater_commands = { |
| 301 | R"(getprop("ro.product.device") == "angler" || abort("This package is for \"angler\"");)", |
| 302 | R"(ui_print("Source: angler/OPR1.170510.001");)", |
| 303 | check_command, |
| 304 | patch_command, |
| 305 | R"(block_image_verify("/dev/block/by-name/system", )" |
| 306 | R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )" |
| 307 | R"(abort("Failed to verify system.");)", |
| 308 | R"(block_image_update("/dev/block/by-name/system", )" |
| 309 | R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )" |
| 310 | R"(abort("Failed to verify system.");)", |
| 311 | }; |
| 312 | string updater_script = android::base::Join(updater_commands, '\n'); |
| 313 | |
| 314 | // Construct the ota update package. |
| 315 | std::map<string, string> ota_entries{ |
| 316 | { "system.new.dat", "" }, |
| 317 | { "system.patch.dat", system_patch }, |
| 318 | { "system.transfer.list", android::base::Join(transfer_list, '\n') }, |
| 319 | { "META-INF/com/google/android/updater-script", updater_script }, |
| 320 | { "patch.p", recovery_from_boot }, |
| 321 | }; |
| 322 | |
| 323 | TemporaryFile ota_package; |
| 324 | AddZipEntries(ota_package.release(), ota_entries); |
| 325 | |
| 326 | RunSimulation(src_tf.path, ota_package.path, true); |
| 327 | } |
| 328 | |
| 329 | TEST_F(UpdateSimulatorTest, RunUpdateUnrecognizedFunction) { |
| 330 | std::map<string, string> src_entries{ |
| 331 | { "META/misc_info.txt", "extfs_sparse_flag=-s" }, |
| 332 | { "IMAGES/system.img", sparse_system_string_ }, |
| 333 | { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ }, |
| 334 | { "SYSTEM/build.prop", build_prop_string_ }, |
| 335 | }; |
| 336 | |
| 337 | TemporaryFile src_tf; |
| 338 | AddZipEntries(src_tf.release(), src_entries); |
| 339 | |
| 340 | std::map<string, string> ota_entries{ |
| 341 | { "system.new.dat", "" }, |
| 342 | { "system.patch.dat", "" }, |
| 343 | { "system.transfer.list", "" }, |
| 344 | { "META-INF/com/google/android/updater-script", R"(bad_function("");)" }, |
| 345 | }; |
| 346 | |
| 347 | TemporaryFile ota_package; |
| 348 | AddZipEntries(ota_package.release(), ota_entries); |
| 349 | |
| 350 | RunSimulation(src_tf.path, ota_package.path, false); |
| 351 | } |
| 352 | |
| 353 | TEST_F(UpdateSimulatorTest, RunUpdateApplyPatchFailed) { |
| 354 | string recovery_img_string = "recovery.img"; |
| 355 | string boot_img_string = "boot.img"; |
| 356 | |
| 357 | std::map<string, string> src_entries{ |
| 358 | { "META/misc_info.txt", "extfs_sparse_flag=-s" }, |
| 359 | { "IMAGES/recovery.img", "" }, |
| 360 | { "IMAGES/boot.img", boot_img_string }, |
| 361 | { "IMAGES/system.img", sparse_system_string_ }, |
| 362 | { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ }, |
| 363 | { "SYSTEM/build.prop", build_prop_string_ }, |
| 364 | }; |
| 365 | |
| 366 | TemporaryFile src_tf; |
| 367 | AddZipEntries(src_tf.release(), src_entries); |
| 368 | |
| 369 | string recovery_sha1 = CalculateSha1(recovery_img_string); |
| 370 | string boot_sha1 = CalculateSha1(boot_img_string); |
| 371 | string apply_patch_source_string = android::base::StringPrintf( |
| 372 | "EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str()); |
| 373 | string apply_patch_target_string = android::base::StringPrintf( |
| 374 | "EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str()); |
| 375 | string check_command = android::base::StringPrintf( |
| 376 | R"(patch_partition_check("%s", "%s") || abort("check failed");)", |
| 377 | apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); |
| 378 | string patch_command = android::base::StringPrintf( |
| 379 | R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("failed");)", |
| 380 | apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); |
| 381 | |
| 382 | // Give an invalid recovery patch and expect the apply patch to fail. |
| 383 | // TODO(xunchang) check the cause code. |
| 384 | std::vector<string> updater_commands = { |
| 385 | R"(ui_print("Source: angler/OPR1.170510.001");)", |
| 386 | check_command, |
| 387 | patch_command, |
| 388 | }; |
| 389 | |
| 390 | string updater_script = android::base::Join(updater_commands, '\n'); |
| 391 | std::map<string, string> ota_entries{ |
| 392 | { "system.new.dat", "" }, |
| 393 | { "system.patch.dat", "" }, |
| 394 | { "system.transfer.list", "" }, |
| 395 | { "META-INF/com/google/android/updater-script", updater_script }, |
| 396 | { "patch.p", "random string" }, |
| 397 | }; |
| 398 | |
| 399 | TemporaryFile ota_package; |
| 400 | AddZipEntries(ota_package.release(), ota_entries); |
| 401 | |
| 402 | RunSimulation(src_tf.path, ota_package.path, false); |
| 403 | } |