diff options
-rw-r--r-- | README.md | 91 | ||||
-rw-r--r-- | bootloader_message/include/bootloader_message/bootloader_message.h | 6 | ||||
-rw-r--r-- | edify/expr.cpp | 19 | ||||
-rw-r--r-- | minui/resources.cpp | 2 | ||||
-rw-r--r-- | tests/component/updater_test.cpp | 217 | ||||
-rw-r--r-- | tests/manual/recovery_test.cpp | 2 | ||||
-rw-r--r-- | updater/blockimg.cpp | 199 | ||||
-rw-r--r-- | updater/include/updater/blockimg.h | 3 | ||||
-rw-r--r-- | updater/install.cpp | 495 |
9 files changed, 760 insertions, 274 deletions
@@ -47,3 +47,94 @@ image under recovery. 1. `adb sync data` to make sure the test-dir has the images to test. 2. The test will automatically pickup and verify all `_text.png` files in the test dir. + +Using `adb` under recovery +-------------------------- + +When running recovery image from debuggable builds (i.e. `-eng` or `-userdebug` build variants, or +`ro.debuggable=1` in `/prop.default`), `adbd` service is enabled and started by default, which +allows `adb` communication. A device should be listed under `adb devices`, either in `recovery` or +`sideload` state. + + $ adb devices + List of devices attached + 1234567890abcdef recovery + +Although `/sbin/adbd` shares the same binary between normal boot and recovery images, only a subset +of `adb` commands are meaningful under recovery, such as `adb root`, `adb shell`, `adb push`, `adb +pull` etc. `adb shell` works only after manually mounting `/system` from recovery menu (assuming a +valid system image on device). + +## Troubleshooting + +### `adb devices` doesn't show the device. + + $ adb devices + List of devices attached + + * Ensure `adbd` is built and running. + +By default, `adbd` is always included into recovery image, as `/sbin/adbd`. `init` starts `adbd` +service automatically only in debuggable builds. This behavior is controlled by the recovery +specific `/init.rc`, whose source code is at `bootable/recovery/etc/init.rc`. + +The best way to confirm a running `adbd` is by checking the serial output, which shows a service +start log as below. + + [ 18.961986] c1 1 init: starting service 'adbd'... + + * Ensure USB gadget has been enabled. + +If `adbd` service has been started but device not shown under `adb devices`, use `lsusb(8)` (on +host) to check if the device is visible to the host. + +`bootable/recovery/etc/init.rc` disables Android USB gadget (via sysfs) as part of the `fs` action +trigger, and will only re-enable it in debuggable builds (the `on property` rule will always run +_after_ `on fs`). + + on fs + write /sys/class/android_usb/android0/enable 0 + + # Always start adbd on userdebug and eng builds + on property:ro.debuggable=1 + write /sys/class/android_usb/android0/enable 1 + start adbd + +If device is using [configfs](https://www.kernel.org/doc/Documentation/usb/gadget_configfs.txt), +check if configfs has been properly set up in init rc scripts. See the [example +configuration](https://android.googlesource.com/device/google/wahoo/+/master/init.recovery.hardware.rc) +for Pixel 2 devices. Note that the flag set via sysfs (i.e. the one above) is no-op when using +configfs. + +### `adb devices` shows the device, but in `unauthorized` state. + + $ adb devices + List of devices attached + 1234567890abcdef unauthorized + +recovery image doesn't honor the USB debugging toggle and the authorizations added under normal boot +(because such authorization data stays in /data, which recovery doesn't mount), nor does it support +authorizing a host device under recovery. We can use one of the following options instead. + + * **Option 1 (Recommended):** Authorize a host device with adb vendor keys. + +For debuggable builds, an RSA keypair can be used to authorize a host device that has the private +key. The public key, defined via `PRODUCT_ADB_KEYS`, will be copied to `/adb_keys`. When starting +the host-side `adbd`, make sure the filename (or the directory) of the matching private key has been +added to `$ADB_VENDOR_KEYS`. + + $ export ADB_VENDOR_KEYS=/path/to/adb/private/key + $ adb kill-server + $ adb devices + +`-user` builds filter out `PRODUCT_ADB_KEYS`, so no `/adb_keys` will be included there. + +Note that this mechanism applies to both of normal boot and recovery modes. + + * **Option 2:** Allow `adbd` to connect without authentication. + * `adbd` is compiled with `ALLOW_ADBD_NO_AUTH` (only on debuggable builds). + * `ro.adb.secure` has a value of `0`. + +Both of the two conditions need to be satisfied. Although `ro.adb.secure` is a runtime property, its +value is set at build time (written into `/prop.default`). It defaults to `1` on `-user` builds, and +`0` for other build variants. The value is overridable via `PRODUCT_DEFAULT_PROPERTY_OVERRIDES`. diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h index 798f3bb8c..95c19ae54 100644 --- a/bootloader_message/include/bootloader_message/bootloader_message.h +++ b/bootloader_message/include/bootloader_message/bootloader_message.h @@ -103,13 +103,17 @@ static_assert(sizeof(struct bootloader_message) == 2048, * implementations are free to use all 32 bytes and may store private * data past the first NUL-byte in this field. It is encouraged, but * not mandatory, to use 'struct bootloader_control' described below. + * + * The update_channel field is used to store the Omaha update channel + * if update_engine is compiled with Omaha support. */ struct bootloader_message_ab { struct bootloader_message message; char slot_suffix[32]; + char update_channel[128]; // Round up the entire struct to 4096-byte. - char reserved[2016]; + char reserved[1888]; }; /** diff --git a/edify/expr.cpp b/edify/expr.cpp index 1b8623f03..6823b7339 100644 --- a/edify/expr.cpp +++ b/edify/expr.cpp @@ -114,9 +114,9 @@ Value* IfElseFn(const char* name, State* state, const std::vector<std::unique_pt Value* AbortFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { std::string msg; if (!argv.empty() && Evaluate(state, argv[0], &msg)) { - state->errmsg = msg; + state->errmsg += msg; } else { - state->errmsg = "called abort()"; + state->errmsg += "called abort()"; } return nullptr; } @@ -410,12 +410,15 @@ Value* ErrorAbort(State* state, const char* format, ...) { } Value* ErrorAbort(State* state, CauseCode cause_code, const char* format, ...) { - va_list ap; - va_start(ap, format); - android::base::StringAppendV(&state->errmsg, format, ap); - va_end(ap); - state->cause_code = cause_code; - return nullptr; + std::string err_message; + va_list ap; + va_start(ap, format); + android::base::StringAppendV(&err_message, format, ap); + va_end(ap); + // Ensure that there's exactly one line break at the end of the error message. + state->errmsg = android::base::Trim(err_message) + "\n"; + state->cause_code = cause_code; + return nullptr; } State::State(const std::string& script, void* cookie) diff --git a/minui/resources.cpp b/minui/resources.cpp index 837f5ebca..52ab60b1b 100644 --- a/minui/resources.cpp +++ b/minui/resources.cpp @@ -397,7 +397,7 @@ bool matches_locale(const std::string& prefix, const std::string& locale) { // match the locale string without the {script} section. // For instance, prefix == "en" matches locale == "en-US", prefix == "sr-Latn" matches locale // == "sr-Latn-BA", and prefix == "zh-CN" matches locale == "zh-Hans-CN". - if (android::base::StartsWith(locale, prefix.c_str())) { + if (android::base::StartsWith(locale, prefix)) { return true; } diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp index d9d01d427..448fe4935 100644 --- a/tests/component/updater_test.cpp +++ b/tests/component/updater_test.cpp @@ -707,3 +707,220 @@ TEST_F(UpdaterTest, brotli_new_data) { ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); CloseArchive(handle); } + +TEST_F(UpdaterTest, last_command_update) { + TemporaryFile temp_file; + last_command_file = temp_file.path; + + 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) { + TemporaryFile temp_file; + last_command_file = temp_file.path; + + 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) { + TemporaryFile temp_file; + last_command_file = temp_file.path; + + 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); +} diff --git a/tests/manual/recovery_test.cpp b/tests/manual/recovery_test.cpp index 92c6ef2d4..64e3b59e6 100644 --- a/tests/manual/recovery_test.cpp +++ b/tests/manual/recovery_test.cpp @@ -209,7 +209,7 @@ TEST_P(ResourceTest, ValidateLocale) { ASSERT_GT(height, y + 1 + h) << "Locale: " << kLocale << " is not found in the file."; char* loc = reinterpret_cast<char*>(&row[5]); if (matches_locale(loc, kLocale.c_str())) { - EXPECT_TRUE(android::base::StartsWith(loc, kLocale.c_str())); + EXPECT_TRUE(android::base::StartsWith(loc, kLocale)); break; } else { for (int i = 0; i < h; ++i, ++y) { diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index 08f9930ea..feb2aeb27 100644 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -34,11 +34,13 @@ #include <fec/io.h> #include <functional> +#include <limits> #include <memory> #include <string> #include <unordered_map> #include <vector> +#include <android-base/file.h> #include <android-base/logging.h> #include <android-base/parseint.h> #include <android-base/strings.h> @@ -67,10 +69,96 @@ static constexpr const char* STASH_DIRECTORY_BASE = "/cache/recovery"; static constexpr mode_t STASH_DIRECTORY_MODE = 0700; static constexpr mode_t STASH_FILE_MODE = 0600; +std::string last_command_file = "/cache/recovery/last_command"; + static CauseCode failure_type = kNoCause; static bool is_retry = false; static std::unordered_map<std::string, RangeSet> stash_map; +static void DeleteLastCommandFile() { + if (unlink(last_command_file.c_str()) == -1 && errno != ENOENT) { + PLOG(ERROR) << "Failed to unlink: " << last_command_file; + } +} + +// Parse the last command index of the last update and save the result to |last_command_index|. +// Return true if we successfully read the index. +static bool ParseLastCommandFile(int* last_command_index) { + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(last_command_file.c_str(), O_RDONLY))); + if (fd == -1) { + if (errno != ENOENT) { + PLOG(ERROR) << "Failed to open " << last_command_file; + return false; + } + + LOG(INFO) << last_command_file << " doesn't exist."; + return false; + } + + // Now that the last_command file exists, parse the last command index of previous update. + std::string content; + if (!android::base::ReadFdToString(fd.get(), &content)) { + LOG(ERROR) << "Failed to read: " << last_command_file; + return false; + } + + std::vector<std::string> lines = android::base::Split(android::base::Trim(content), "\n"); + if (lines.size() != 2) { + LOG(ERROR) << "Unexpected line counts in last command file: " << content; + return false; + } + + if (!android::base::ParseInt(lines[0], last_command_index)) { + LOG(ERROR) << "Failed to parse integer in: " << lines[0]; + return false; + } + + return true; +} + +// Update the last command index in the last_command_file if the current command writes to the +// stash either explicitly or implicitly. +static bool UpdateLastCommandIndex(int command_index, const std::string& command_string) { + std::string last_command_tmp = last_command_file + ".tmp"; + std::string content = std::to_string(command_index) + "\n" + command_string; + android::base::unique_fd wfd( + TEMP_FAILURE_RETRY(open(last_command_tmp.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0660))); + if (wfd == -1 || !android::base::WriteStringToFd(content, wfd)) { + PLOG(ERROR) << "Failed to update last command"; + return false; + } + + if (fsync(wfd) == -1) { + PLOG(ERROR) << "Failed to fsync " << last_command_tmp; + return false; + } + + if (chown(last_command_tmp.c_str(), AID_SYSTEM, AID_SYSTEM) == -1) { + PLOG(ERROR) << "Failed to change owner for " << last_command_tmp; + return false; + } + + if (rename(last_command_tmp.c_str(), last_command_file.c_str()) == -1) { + PLOG(ERROR) << "Failed to rename" << last_command_tmp; + return false; + } + + std::string last_command_dir = android::base::Dirname(last_command_file); + android::base::unique_fd dfd( + TEMP_FAILURE_RETRY(ota_open(last_command_dir.c_str(), O_RDONLY | O_DIRECTORY))); + if (dfd == -1) { + PLOG(ERROR) << "Failed to open " << last_command_dir; + return false; + } + + if (fsync(dfd) == -1) { + PLOG(ERROR) << "Failed to fsync " << last_command_dir; + return false; + } + + return true; +} + static int read_all(int fd, uint8_t* data, size_t size) { size_t so_far = 0; while (so_far < size) { @@ -439,6 +527,7 @@ static int WriteBlocks(const RangeSet& tgt, const std::vector<uint8_t>& buffer, struct CommandParameters { std::vector<std::string> tokens; size_t cpos; + int cmdindex; const char* cmdname; const char* cmdline; std::string freestash; @@ -455,6 +544,7 @@ struct CommandParameters { pthread_t thread; std::vector<uint8_t> buffer; uint8_t* patch_start; + bool target_verified; // The target blocks have expected contents already. }; // Print the hash in hex for corrupted source blocks (excluding the stashed blocks which is @@ -817,7 +907,7 @@ static int CreateStash(State* state, size_t maxblocks, const std::string& blockd size_t max_stash_size = maxblocks * BLOCKSIZE; if (res == -1 && errno != ENOENT) { - ErrorAbort(state, kStashCreationFailure, "stat \"%s\" failed: %s\n", dirname.c_str(), + ErrorAbort(state, kStashCreationFailure, "stat \"%s\" failed: %s", dirname.c_str(), strerror(errno)); return -1; } else if (res != 0) { @@ -825,19 +915,19 @@ static int CreateStash(State* state, size_t maxblocks, const std::string& blockd res = mkdir(dirname.c_str(), STASH_DIRECTORY_MODE); if (res != 0) { - ErrorAbort(state, kStashCreationFailure, "mkdir \"%s\" failed: %s\n", dirname.c_str(), + ErrorAbort(state, kStashCreationFailure, "mkdir \"%s\" failed: %s", dirname.c_str(), strerror(errno)); return -1; } if (chown(dirname.c_str(), AID_SYSTEM, AID_SYSTEM) != 0) { // system user - ErrorAbort(state, kStashCreationFailure, "chown \"%s\" failed: %s\n", dirname.c_str(), + ErrorAbort(state, kStashCreationFailure, "chown \"%s\" failed: %s", dirname.c_str(), strerror(errno)); return -1; } if (CacheSizeCheck(max_stash_size) != 0) { - ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu needed)\n", + ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu needed)", max_stash_size); return -1; } @@ -869,7 +959,7 @@ static int CreateStash(State* state, size_t maxblocks, const std::string& blockd if (max_stash_size > existing) { size_t needed = max_stash_size - existing; if (CacheSizeCheck(needed) != 0) { - ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu more needed)\n", + ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu more needed)", needed); return -1; } @@ -1072,6 +1162,10 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t* return -1; } + if (!UpdateLastCommandIndex(params.cmdindex, params.cmdline)) { + LOG(WARNING) << "Failed to update the last command file."; + } + params.stashed += *src_blocks; // Can be deleted when the write has completed. if (!stash_exists) { @@ -1112,8 +1206,11 @@ static int PerformCommandMove(CommandParameters& params) { if (status == 0) { params.foundwrites = true; - } else if (params.foundwrites) { - LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]"; + } else { + params.target_verified = true; + if (params.foundwrites) { + LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]"; + } } if (params.canwrite) { @@ -1177,8 +1274,15 @@ static int PerformCommandStash(CommandParameters& params) { } LOG(INFO) << "stashing " << blocks << " blocks to " << id; - params.stashed += blocks; - return WriteStash(params.stashbase, id, blocks, params.buffer, false, nullptr); + int result = WriteStash(params.stashbase, id, blocks, params.buffer, false, nullptr); + if (result == 0) { + if (!UpdateLastCommandIndex(params.cmdindex, params.cmdline)) { + LOG(WARNING) << "Failed to update the last command file."; + } + + params.stashed += blocks; + } + return result; } static int PerformCommandFree(CommandParameters& params) { @@ -1306,8 +1410,11 @@ static int PerformCommandDiff(CommandParameters& params) { if (status == 0) { params.foundwrites = true; - } else if (params.foundwrites) { - LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]"; + } else { + params.target_verified = true; + if (params.foundwrites) { + LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]"; + } } if (params.canwrite) { @@ -1517,7 +1624,7 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, std::vector<std::string> lines = android::base::Split(transfer_list_value->data, "\n"); if (lines.size() < 2) { - ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zd]\n", + ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zd]", lines.size()); return StringValue(""); } @@ -1533,7 +1640,7 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, // Second line in transfer list is the total number of blocks we expect to write. size_t total_blocks; if (!android::base::ParseUint(lines[1], &total_blocks)) { - ErrorAbort(state, kArgsParsingFailure, "unexpected block count [%s]\n", lines[1].c_str()); + ErrorAbort(state, kArgsParsingFailure, "unexpected block count [%s]", lines[1].c_str()); return StringValue(""); } @@ -1543,7 +1650,7 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, size_t start = 2; if (lines.size() < 4) { - ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]\n", + ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]", lines.size()); return StringValue(""); } @@ -1554,7 +1661,7 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, // Fourth line is the maximum number of blocks that will be stashed simultaneously size_t stash_max_blocks; if (!android::base::ParseUint(lines[3], &stash_max_blocks)) { - ErrorAbort(state, kArgsParsingFailure, "unexpected maximum stash blocks [%s]\n", + ErrorAbort(state, kArgsParsingFailure, "unexpected maximum stash blocks [%s]", lines[3].c_str()); return StringValue(""); } @@ -1566,6 +1673,23 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, params.createdstash = res; + // When performing an update, save the index and cmdline of the current command into + // the last_command_file if this command writes to the stash either explicitly of implicitly. + // Upon resuming an update, read the saved index first; then + // 1. In verification mode, check if the 'move' or 'diff' commands before the saved index has + // the expected target blocks already. If not, these commands cannot be skipped and we need + // to attempt to execute them again. Therefore, we will delete the last_command_file so that + // the update will resume from the start of the transfer list. + // 2. In update mode, skip all commands before the saved index. Therefore, we can avoid deleting + // stashes with duplicate id unintentionally (b/69858743); and also speed up the update. + // If an update succeeds or is unresumable, delete the last_command_file. + int saved_last_command_index; + if (!ParseLastCommandFile(&saved_last_command_index)) { + DeleteLastCommandFile(); + // We failed to parse the last command, set it explicitly to -1. + saved_last_command_index = -1; + } + start += 2; // Build a map of the available commands @@ -1581,14 +1705,20 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, int rc = -1; // Subsequent lines are all individual transfer commands - for (auto it = lines.cbegin() + start; it != lines.cend(); it++) { - const std::string& line(*it); + for (size_t i = start; i < lines.size(); i++) { + const std::string& line = lines[i]; if (line.empty()) continue; params.tokens = android::base::Split(line, " "); params.cpos = 0; + if (i - start > std::numeric_limits<int>::max()) { + params.cmdindex = -1; + } else { + params.cmdindex = i - start; + } params.cmdname = params.tokens[params.cpos++].c_str(); params.cmdline = line.c_str(); + params.target_verified = false; if (cmd_map.find(params.cmdname) == cmd_map.end()) { LOG(ERROR) << "unexpected command [" << params.cmdname << "]"; @@ -1597,11 +1727,38 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, const Command* cmd = cmd_map[params.cmdname]; - if (cmd->f != nullptr && cmd->f(params) == -1) { + if (cmd->f == nullptr) { + LOG(ERROR) << "failed to find the function for command [" << line << "]"; + goto pbiudone; + } + + // Skip all commands before the saved last command index when resuming an update. + if (params.canwrite && params.cmdindex != -1 && params.cmdindex <= saved_last_command_index) { + LOG(INFO) << "Skipping already executed command: " << params.cmdindex + << ", last executed command for previous update: " << saved_last_command_index; + continue; + } + + if (cmd->f(params) == -1) { LOG(ERROR) << "failed to execute command [" << line << "]"; goto pbiudone; } + // In verify mode, check if the commands before the saved last_command_index have been + // executed correctly. If some target blocks have unexpected contents, delete the last command + // file so that we will resume the update from the first command in the transfer list. + if (!params.canwrite && saved_last_command_index != -1 && params.cmdindex != -1 && + params.cmdindex <= saved_last_command_index) { + // TODO(xunchang) check that the cmdline of the saved index is correct. + std::string cmdname = std::string(params.cmdname); + if ((cmdname == "move" || cmdname == "bsdiff" || cmdname == "imgdiff") && + !params.target_verified) { + LOG(WARNING) << "Previously executed command " << saved_last_command_index << ": " + << params.cmdline << " doesn't produce expected target blocks."; + saved_last_command_index = -1; + DeleteLastCommandFile(); + } + } if (params.canwrite) { if (ota_fsync(params.fd) == -1) { failure_type = kFsyncFailure; @@ -1643,6 +1800,7 @@ pbiudone: // Delete stash only after successfully completing the update, as it may contain blocks needed // to complete the update later. DeleteStash(params.stashbase); + DeleteLastCommandFile(); } pthread_mutex_destroy(¶ms.nti.mu); @@ -1661,6 +1819,11 @@ pbiudone: BrotliDecoderDestroyInstance(params.nti.brotli_decoder_state); } + // Delete the last command file if the update cannot be resumed. + if (params.isunresumable) { + DeleteLastCommandFile(); + } + // Only delete the stash if the update cannot be resumed, or it's a verification run and we // created the stash. if (params.isunresumable || (!params.canwrite && params.createdstash)) { diff --git a/updater/include/updater/blockimg.h b/updater/include/updater/blockimg.h index 2f4ad3c04..2cc68ce9d 100644 --- a/updater/include/updater/blockimg.h +++ b/updater/include/updater/blockimg.h @@ -17,6 +17,9 @@ #ifndef _UPDATER_BLOCKIMG_H_ #define _UPDATER_BLOCKIMG_H_ +#include <string> + +extern std::string last_command_file; void RegisterBlockImageFunctions(); #endif diff --git a/updater/install.cpp b/updater/install.cpp index b83d30ff3..2b6c20fe3 100644 --- a/updater/install.cpp +++ b/updater/install.cpp @@ -94,6 +94,244 @@ void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...) { uiPrint(state, error_msg); } +// This is the updater side handler for ui_print() in edify script. Contents will be sent over to +// the recovery side for on-screen display. +Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); + } + + std::string buffer = android::base::Join(args, ""); + uiPrint(state, buffer); + return StringValue(buffer); +} + +// package_extract_file(package_file[, dest_file]) +// Extracts a single package_file from the update package and writes it to dest_file, +// overwriting existing files if necessary. Without the dest_file argument, returns the +// contents of the package file as a binary blob. +Value* PackageExtractFileFn(const char* name, State* state, + const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() < 1 || argv.size() > 2) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 or 2 args, got %zu", name, + argv.size()); + } + + if (argv.size() == 2) { + // The two-argument version extracts to a file. + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name, + argv.size()); + } + const std::string& zip_path = args[0]; + const std::string& dest_path = args[1]; + + ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip; + ZipString zip_string_path(zip_path.c_str()); + ZipEntry entry; + if (FindEntry(za, zip_string_path, &entry) != 0) { + LOG(ERROR) << name << ": no " << zip_path << " in package"; + return StringValue(""); + } + + unique_fd fd(TEMP_FAILURE_RETRY( + ota_open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR))); + if (fd == -1) { + PLOG(ERROR) << name << ": can't open " << dest_path << " for write"; + return StringValue(""); + } + + bool success = true; + int32_t ret = ExtractEntryToFile(za, &entry, fd); + if (ret != 0) { + LOG(ERROR) << name << ": Failed to extract entry \"" << zip_path << "\" (" + << entry.uncompressed_length << " bytes) to \"" << dest_path + << "\": " << ErrorCodeString(ret); + success = false; + } + if (ota_fsync(fd) == -1) { + PLOG(ERROR) << "fsync of \"" << dest_path << "\" failed"; + success = false; + } + if (ota_close(fd) == -1) { + PLOG(ERROR) << "close of \"" << dest_path << "\" failed"; + success = false; + } + + return StringValue(success ? "t" : ""); + } else { + // The one-argument version returns the contents of the file as the result. + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name, + argv.size()); + } + const std::string& zip_path = args[0]; + + ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip; + ZipString zip_string_path(zip_path.c_str()); + ZipEntry entry; + if (FindEntry(za, zip_string_path, &entry) != 0) { + return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name, + zip_path.c_str()); + } + + std::string buffer; + buffer.resize(entry.uncompressed_length); + + int32_t ret = + ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&buffer[0]), buffer.size()); + if (ret != 0) { + return ErrorAbort(state, kPackageExtractFileFailure, + "%s: Failed to extract entry \"%s\" (%zu bytes) to memory: %s", name, + zip_path.c_str(), buffer.size(), ErrorCodeString(ret)); + } + + return new Value(VAL_BLOB, buffer); + } +} + +// apply_patch(src_file, tgt_file, tgt_sha1, tgt_size, patch1_sha1, patch1_blob, [...]) +// Applies a binary patch to the src_file to produce the tgt_file. If the desired target is the +// same as the source, pass "-" for tgt_file. tgt_sha1 and tgt_size are the expected final SHA1 +// hash and size of the target file. The remaining arguments must come in pairs: a SHA1 hash (a +// 40-character hex string) and a blob. The blob is the patch to be applied when the source +// file's current contents have the given SHA1. +// +// The patching is done in a safe manner that guarantees the target file either has the desired +// SHA1 hash and size, or it is untouched -- it will not be left in an unrecoverable intermediate +// state. If the process is interrupted during patching, the target file may be in an intermediate +// state; a copy exists in the cache partition so restarting the update can successfully update +// the file. +Value* ApplyPatchFn(const char* name, State* state, + const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() < 6 || (argv.size() % 2) == 1) { + return ErrorAbort(state, kArgsParsingFailure, + "%s(): expected at least 6 args and an " + "even number, got %zu", + name, argv.size()); + } + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args, 0, 4)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& source_filename = args[0]; + const std::string& target_filename = args[1]; + const std::string& target_sha1 = args[2]; + const std::string& target_size_str = args[3]; + + size_t target_size; + if (!android::base::ParseUint(target_size_str.c_str(), &target_size)) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count", name, + target_size_str.c_str()); + } + + int patchcount = (argv.size() - 4) / 2; + std::vector<std::unique_ptr<Value>> arg_values; + if (!ReadValueArgs(state, argv, &arg_values, 4, argv.size() - 4)) { + return nullptr; + } + + for (int i = 0; i < patchcount; ++i) { + if (arg_values[i * 2]->type != VAL_STRING) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): sha-1 #%d is not string", name, i * 2); + } + if (arg_values[i * 2 + 1]->type != VAL_BLOB) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): patch #%d is not blob", name, i * 2 + 1); + } + } + + std::vector<std::string> patch_sha_str; + std::vector<std::unique_ptr<Value>> patches; + for (int i = 0; i < patchcount; ++i) { + patch_sha_str.push_back(arg_values[i * 2]->data); + patches.push_back(std::move(arg_values[i * 2 + 1])); + } + + int result = applypatch(source_filename.c_str(), target_filename.c_str(), target_sha1.c_str(), + target_size, patch_sha_str, patches, nullptr); + + return StringValue(result == 0 ? "t" : ""); +} + +// apply_patch_check(filename, [sha1, ...]) +// Returns true if the contents of filename or the temporary copy in the cache partition (if +// present) have a SHA-1 checksum equal to one of the given sha1 values. sha1 values are +// specified as 40 hex digits. This function differs from sha1_check(read_file(filename), +// sha1 [, ...]) in that it knows to check the cache partition copy, so apply_patch_check() will +// succeed even if the file was corrupted by an interrupted apply_patch() update. +Value* ApplyPatchCheckFn(const char* name, State* state, + const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() < 1) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 1 arg, got %zu", name, + argv.size()); + } + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args, 0, 1)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& filename = args[0]; + + std::vector<std::string> sha1s; + if (argv.size() > 1 && !ReadArgs(state, argv, &sha1s, 1, argv.size() - 1)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + int result = applypatch_check(filename.c_str(), sha1s); + + return StringValue(result == 0 ? "t" : ""); +} + +// sha1_check(data) +// to return the sha1 of the data (given in the format returned by +// read_file). +// +// sha1_check(data, sha1_hex, [sha1_hex, ...]) +// returns the sha1 of the file if it matches any of the hex +// strings passed, or "" if it does not equal any of them. +// +Value* Sha1CheckFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() < 1) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name); + } + + std::vector<std::unique_ptr<Value>> args; + if (!ReadValueArgs(state, argv, &args)) { + return nullptr; + } + + if (args[0]->type == VAL_INVALID) { + return StringValue(""); + } + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast<const uint8_t*>(args[0]->data.c_str()), args[0]->data.size(), digest); + + if (argv.size() == 1) { + return StringValue(print_sha1(digest)); + } + + for (size_t i = 1; i < argv.size(); ++i) { + uint8_t arg_digest[SHA_DIGEST_LENGTH]; + if (args[i]->type != VAL_STRING) { + LOG(ERROR) << name << "(): arg " << i << " is not a string; skipping"; + } else if (ParseSha1(args[i]->data.c_str(), arg_digest) != 0) { + // Warn about bad args and skip them. + LOG(ERROR) << name << "(): error parsing \"" << args[i]->data << "\" as sha-1; skipping"; + } else if (memcmp(digest, arg_digest, SHA_DIGEST_LENGTH) == 0) { + // Found a match. + return args[i].release(); + } + } + + // Didn't match any of the hex strings; return false. + return StringValue(""); +} + // mount(fs_type, partition_type, location, mount_point) // mount(fs_type, partition_type, location, mount_point, mount_options) @@ -268,7 +506,7 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt int64_t size; if (!android::base::ParseInt(fs_size, &size)) { - return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s\n", name, + return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s", name, fs_size.c_str()); } @@ -352,12 +590,12 @@ Value* ShowProgressFn(const char* name, State* state, double frac; if (!android::base::ParseDouble(frac_str.c_str(), &frac)) { - return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s\n", name, + return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s", name, frac_str.c_str()); } int sec; if (!android::base::ParseInt(sec_str.c_str(), &sec)) { - return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s\n", name, + return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s", name, sec_str.c_str()); } @@ -367,7 +605,8 @@ Value* ShowProgressFn(const char* name, State* state, return StringValue(frac_str); } -Value* SetProgressFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { +Value* SetProgressFn(const char* name, State* state, + const std::vector<std::unique_ptr<Expr>>& argv) { if (argv.size() != 1) { return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); } @@ -380,7 +619,7 @@ Value* SetProgressFn(const char* name, State* state, const std::vector<std::uniq double frac; if (!android::base::ParseDouble(frac_str.c_str(), &frac)) { - return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s\n", name, + return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s", name, frac_str.c_str()); } @@ -390,93 +629,6 @@ Value* SetProgressFn(const char* name, State* state, const std::vector<std::uniq return StringValue(frac_str); } -// package_extract_file(package_file[, dest_file]) -// Extracts a single package_file from the update package and writes it to dest_file, -// overwriting existing files if necessary. Without the dest_file argument, returns the -// contents of the package file as a binary blob. -Value* PackageExtractFileFn(const char* name, State* state, - const std::vector<std::unique_ptr<Expr>>& argv) { - if (argv.size() < 1 || argv.size() > 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 or 2 args, got %zu", name, - argv.size()); - } - - if (argv.size() == 2) { - // The two-argument version extracts to a file. - - std::vector<std::string> args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name, - argv.size()); - } - const std::string& zip_path = args[0]; - const std::string& dest_path = args[1]; - - ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip; - ZipString zip_string_path(zip_path.c_str()); - ZipEntry entry; - if (FindEntry(za, zip_string_path, &entry) != 0) { - LOG(ERROR) << name << ": no " << zip_path << " in package"; - return StringValue(""); - } - - unique_fd fd(TEMP_FAILURE_RETRY( - ota_open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR))); - if (fd == -1) { - PLOG(ERROR) << name << ": can't open " << dest_path << " for write"; - return StringValue(""); - } - - bool success = true; - int32_t ret = ExtractEntryToFile(za, &entry, fd); - if (ret != 0) { - LOG(ERROR) << name << ": Failed to extract entry \"" << zip_path << "\" (" - << entry.uncompressed_length << " bytes) to \"" << dest_path - << "\": " << ErrorCodeString(ret); - success = false; - } - if (ota_fsync(fd) == -1) { - PLOG(ERROR) << "fsync of \"" << dest_path << "\" failed"; - success = false; - } - if (ota_close(fd) == -1) { - PLOG(ERROR) << "close of \"" << dest_path << "\" failed"; - success = false; - } - - return StringValue(success ? "t" : ""); - } else { - // The one-argument version returns the contents of the file as the result. - - std::vector<std::string> args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name, - argv.size()); - } - const std::string& zip_path = args[0]; - - ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip; - ZipString zip_string_path(zip_path.c_str()); - ZipEntry entry; - if (FindEntry(za, zip_string_path, &entry) != 0) { - return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name, - zip_path.c_str()); - } - - std::string buffer; - buffer.resize(entry.uncompressed_length); - - int32_t ret = ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&buffer[0]), buffer.size()); - if (ret != 0) { - return ErrorAbort(state, kPackageExtractFileFailure, - "%s: Failed to extract entry \"%s\" (%zu bytes) to memory: %s", name, - zip_path.c_str(), buffer.size(), ErrorCodeString(ret)); - } - - return new Value(VAL_BLOB, buffer); - } -} - Value* GetPropFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { if (argv.size() != 1) { return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); @@ -495,7 +647,8 @@ Value* GetPropFn(const char* name, State* state, const std::vector<std::unique_p // interprets 'file' as a getprop-style file (key=value pairs, one // per line. # comment lines, blank lines, lines without '=' ignored), // and returns the value for 'key' (or "" if it isn't defined). -Value* FileGetPropFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { +Value* FileGetPropFn(const char* name, State* state, + const std::vector<std::unique_ptr<Expr>>& argv) { if (argv.size() != 2) { return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, argv.size()); @@ -561,7 +714,8 @@ Value* FileGetPropFn(const char* name, State* state, const std::vector<std::uniq } // apply_patch_space(bytes) -Value* ApplyPatchSpaceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { +Value* ApplyPatchSpaceFn(const char* name, State* state, + const std::vector<std::unique_ptr<Expr>>& argv) { if (argv.size() != 1) { return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 args, got %zu", name, argv.size()); @@ -574,8 +728,8 @@ Value* ApplyPatchSpaceFn(const char* name, State* state, const std::vector<std:: size_t bytes; if (!android::base::ParseUint(bytes_str.c_str(), &bytes)) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count\n\n", - name, bytes_str.c_str()); + return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count", name, + bytes_str.c_str()); } // Skip the cache size check if the update is a retry. @@ -585,110 +739,6 @@ Value* ApplyPatchSpaceFn(const char* name, State* state, const std::vector<std:: return StringValue(""); } -// apply_patch(src_file, tgt_file, tgt_sha1, tgt_size, patch1_sha1, patch1_blob, [...]) -// Applies a binary patch to the src_file to produce the tgt_file. If the desired target is the -// same as the source, pass "-" for tgt_file. tgt_sha1 and tgt_size are the expected final SHA1 -// hash and size of the target file. The remaining arguments must come in pairs: a SHA1 hash (a -// 40-character hex string) and a blob. The blob is the patch to be applied when the source -// file's current contents have the given SHA1. -// -// The patching is done in a safe manner that guarantees the target file either has the desired -// SHA1 hash and size, or it is untouched -- it will not be left in an unrecoverable intermediate -// state. If the process is interrupted during patching, the target file may be in an intermediate -// state; a copy exists in the cache partition so restarting the update can successfully update -// the file. -Value* ApplyPatchFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { - if (argv.size() < 6 || (argv.size() % 2) == 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 6 args and an " - "even number, got %zu", name, argv.size()); - } - - std::vector<std::string> args; - if (!ReadArgs(state, argv, &args, 0, 4)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& source_filename = args[0]; - const std::string& target_filename = args[1]; - const std::string& target_sha1 = args[2]; - const std::string& target_size_str = args[3]; - - size_t target_size; - if (!android::base::ParseUint(target_size_str.c_str(), &target_size)) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count", - name, target_size_str.c_str()); - } - - int patchcount = (argv.size()-4) / 2; - std::vector<std::unique_ptr<Value>> arg_values; - if (!ReadValueArgs(state, argv, &arg_values, 4, argv.size() - 4)) { - return nullptr; - } - - for (int i = 0; i < patchcount; ++i) { - if (arg_values[i * 2]->type != VAL_STRING) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): sha-1 #%d is not string", name, - i * 2); - } - if (arg_values[i * 2 + 1]->type != VAL_BLOB) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): patch #%d is not blob", name, - i * 2 + 1); - } - } - - std::vector<std::string> patch_sha_str; - std::vector<std::unique_ptr<Value>> patches; - for (int i = 0; i < patchcount; ++i) { - patch_sha_str.push_back(arg_values[i * 2]->data); - patches.push_back(std::move(arg_values[i * 2 + 1])); - } - - int result = applypatch(source_filename.c_str(), target_filename.c_str(), - target_sha1.c_str(), target_size, - patch_sha_str, patches, nullptr); - - return StringValue(result == 0 ? "t" : ""); -} - -// apply_patch_check(filename, [sha1, ...]) -// Returns true if the contents of filename or the temporary copy in the cache partition (if -// present) have a SHA-1 checksum equal to one of the given sha1 values. sha1 values are -// specified as 40 hex digits. This function differs from sha1_check(read_file(filename), -// sha1 [, ...]) in that it knows to check the cache partition copy, so apply_patch_check() will -// succeed even if the file was corrupted by an interrupted apply_patch() update. -Value* ApplyPatchCheckFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { - if (argv.size() < 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 1 arg, got %zu", name, - argv.size()); - } - - std::vector<std::string> args; - if (!ReadArgs(state, argv, &args, 0, 1)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& filename = args[0]; - - std::vector<std::string> sha1s; - if (argv.size() > 1 && !ReadArgs(state, argv, &sha1s, 1, argv.size() - 1)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - int result = applypatch_check(filename.c_str(), sha1s); - - return StringValue(result == 0 ? "t" : ""); -} - -// This is the updater side handler for ui_print() in edify script. Contents will be sent over to -// the recovery side for on-screen display. -Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { - std::vector<std::string> args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); - } - - std::string buffer = android::base::Join(args, ""); - uiPrint(state, buffer); - return StringValue(buffer); -} - Value* WipeCacheFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { if (!argv.empty()) { return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name, @@ -736,51 +786,6 @@ Value* RunProgramFn(const char* name, State* state, const std::vector<std::uniqu return StringValue(std::to_string(status)); } -// sha1_check(data) -// to return the sha1 of the data (given in the format returned by -// read_file). -// -// sha1_check(data, sha1_hex, [sha1_hex, ...]) -// returns the sha1 of the file if it matches any of the hex -// strings passed, or "" if it does not equal any of them. -// -Value* Sha1CheckFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { - if (argv.size() < 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name); - } - - std::vector<std::unique_ptr<Value>> args; - if (!ReadValueArgs(state, argv, &args)) { - return nullptr; - } - - if (args[0]->type == VAL_INVALID) { - return StringValue(""); - } - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast<const uint8_t*>(args[0]->data.c_str()), args[0]->data.size(), digest); - - if (argv.size() == 1) { - return StringValue(print_sha1(digest)); - } - - for (size_t i = 1; i < argv.size(); ++i) { - uint8_t arg_digest[SHA_DIGEST_LENGTH]; - if (args[i]->type != VAL_STRING) { - LOG(ERROR) << name << "(): arg " << i << " is not a string; skipping"; - } else if (ParseSha1(args[i]->data.c_str(), arg_digest) != 0) { - // Warn about bad args and skip them. - LOG(ERROR) << name << "(): error parsing \"" << args[i]->data << "\" as sha-1; skipping"; - } else if (memcmp(digest, arg_digest, SHA_DIGEST_LENGTH) == 0) { - // Found a match. - return args[i].release(); - } - } - - // Didn't match any of the hex strings; return false. - return StringValue(""); -} - // Read a local file and return its contents (the Value* returned // is actually a FileContents*). Value* ReadFileFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { |