summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md91
-rw-r--r--bootloader_message/include/bootloader_message/bootloader_message.h6
-rw-r--r--edify/expr.cpp19
-rw-r--r--minui/resources.cpp2
-rw-r--r--tests/component/updater_test.cpp217
-rw-r--r--tests/manual/recovery_test.cpp2
-rw-r--r--updater/blockimg.cpp199
-rw-r--r--updater/include/updater/blockimg.h3
-rw-r--r--updater/install.cpp495
9 files changed, 760 insertions, 274 deletions
diff --git a/README.md b/README.md
index 8e20b5a62..0aeadaeb4 100644
--- a/README.md
+++ b/README.md
@@ -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(&params.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) {