summaryrefslogtreecommitdiffstats
path: root/updater
diff options
context:
space:
mode:
Diffstat (limited to 'updater')
-rw-r--r--updater/Android.bp83
-rw-r--r--updater/Android.mk44
-rw-r--r--updater/blockimg.cpp962
-rw-r--r--updater/commands.cpp454
-rw-r--r--updater/dynamic_partitions.cpp435
-rw-r--r--updater/include/private/commands.h475
-rw-r--r--updater/include/private/utils.h21
-rw-r--r--updater/include/updater/dynamic_partitions.h19
-rw-r--r--updater/include/updater/install.h11
-rw-r--r--updater/include/updater/updater.h77
-rw-r--r--updater/install.cpp420
-rw-r--r--updater/updater.cpp279
-rw-r--r--updater/updater_main.cpp108
13 files changed, 2482 insertions, 906 deletions
diff --git a/updater/Android.bp b/updater/Android.bp
new file mode 100644
index 000000000..daf7e3277
--- /dev/null
+++ b/updater/Android.bp
@@ -0,0 +1,83 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_defaults {
+ name: "libupdater_defaults",
+
+ defaults: [
+ "recovery_defaults",
+ ],
+
+ static_libs: [
+ "libapplypatch",
+ "libbootloader_message",
+ "libbspatch",
+ "libedify",
+ "libotautil",
+ "libext4_utils",
+ "libdm",
+ "libfec",
+ "libfec_rs",
+ "libverity_tree",
+ "libfs_mgr",
+ "libgtest_prod",
+ "liblog",
+ "liblp",
+ "libselinux",
+ "libsparse",
+ "libsquashfs_utils",
+ "libbrotli",
+ "libbz",
+ "libziparchive",
+ "libz",
+ "libbase",
+ "libcrypto",
+ "libcrypto_utils",
+ "libcutils",
+ "libutils",
+ "libtune2fs",
+
+ "libext2_com_err",
+ "libext2_blkid",
+ "libext2_quota",
+ "libext2_uuid",
+ "libext2_e2p",
+ "libext2fs",
+ ],
+}
+
+cc_library_static {
+ name: "libupdater",
+
+ defaults: [
+ "recovery_defaults",
+ "libupdater_defaults",
+ ],
+
+ srcs: [
+ "blockimg.cpp",
+ "commands.cpp",
+ "dynamic_partitions.cpp",
+ "install.cpp",
+ "updater.cpp",
+ ],
+
+ include_dirs: [
+ "external/e2fsprogs/misc",
+ ],
+
+ export_include_dirs: [
+ "include",
+ ],
+}
diff --git a/updater/Android.mk b/updater/Android.mk
index 6f334ee18..0178239e0 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -24,59 +24,34 @@ tune2fs_static_libraries := \
updater_common_static_libraries := \
libapplypatch \
+ libbootloader_message \
libbspatch \
libedify \
- libziparchive \
libotautil \
- libbootloader_message \
- libutils \
- libmounts \
- libotafault \
libext4_utils \
+ libdm \
libfec \
libfec_rs \
+ libverity_tree \
libfs_mgr \
+ libgtest_prod \
liblog \
+ liblp \
libselinux \
libsparse \
libsquashfs_utils \
+ libbrotli \
libbz \
+ libziparchive \
libz \
libbase \
libcrypto \
libcrypto_utils \
libcutils \
+ libutils \
libtune2fs \
- libbrotli \
$(tune2fs_static_libraries)
-# libupdater (static library)
-# ===============================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libupdater
-
-LOCAL_SRC_FILES := \
- install.cpp \
- blockimg.cpp
-
-LOCAL_C_INCLUDES := \
- $(LOCAL_PATH)/.. \
- $(LOCAL_PATH)/include \
- external/e2fsprogs/misc
-
-LOCAL_CFLAGS := \
- -Wall \
- -Werror
-
-LOCAL_EXPORT_C_INCLUDE_DIRS := \
- $(LOCAL_PATH)/include
-
-LOCAL_STATIC_LIBRARIES := \
- $(updater_common_static_libraries)
-
-include $(BUILD_STATIC_LIBRARY)
-
# updater (static executable)
# ===============================
include $(CLEAR_VARS)
@@ -84,10 +59,9 @@ include $(CLEAR_VARS)
LOCAL_MODULE := updater
LOCAL_SRC_FILES := \
- updater.cpp
+ updater_main.cpp
LOCAL_C_INCLUDES := \
- $(LOCAL_PATH)/.. \
$(LOCAL_PATH)/include
LOCAL_CFLAGS := \
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index e93196b27..3b2b2c02d 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -15,8 +15,8 @@
*/
#include <ctype.h>
-#include <errno.h>
#include <dirent.h>
+#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <linux/fs.h>
@@ -25,13 +25,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
-#include <sys/ioctl.h>
#include <time.h>
#include <unistd.h>
-#include <fec/io.h>
#include <functional>
#include <limits>
@@ -43,20 +42,24 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <applypatch/applypatch.h>
#include <brotli/decode.h>
+#include <fec/io.h>
#include <openssl/sha.h>
#include <private/android_filesystem_config.h>
+#include <verity/hash_tree_builder.h>
#include <ziparchive/zip_archive.h>
#include "edify/expr.h"
-#include "otafault/ota_io.h"
-#include "otautil/cache_location.h"
+#include "otautil/dirutil.h"
#include "otautil/error_code.h"
+#include "otautil/paths.h"
#include "otautil/print_sha1.h"
#include "otautil/rangeset.h"
+#include "private/commands.h"
#include "updater/install.h"
#include "updater/updater.h"
@@ -68,13 +71,14 @@
static constexpr size_t BLOCKSIZE = 4096;
static constexpr mode_t STASH_DIRECTORY_MODE = 0700;
static constexpr mode_t STASH_FILE_MODE = 0600;
+static constexpr mode_t MARKER_DIRECTORY_MODE = 0700;
static CauseCode failure_type = kNoCause;
static bool is_retry = false;
static std::unordered_map<std::string, RangeSet> stash_map;
static void DeleteLastCommandFile() {
- std::string last_command_file = CacheLocation::location().last_command_file();
+ const std::string& last_command_file = Paths::Get().last_command_file();
if (unlink(last_command_file.c_str()) == -1 && errno != ENOENT) {
PLOG(ERROR) << "Failed to unlink: " << last_command_file;
}
@@ -82,8 +86,8 @@ static void DeleteLastCommandFile() {
// 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) {
- std::string last_command_file = CacheLocation::location().last_command_file();
+static bool ParseLastCommandFile(size_t* last_command_index) {
+ const std::string& last_command_file = Paths::Get().last_command_file();
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(last_command_file.c_str(), O_RDONLY)));
if (fd == -1) {
if (errno != ENOENT) {
@@ -108,7 +112,7 @@ static bool ParseLastCommandFile(int* last_command_index) {
return false;
}
- if (!android::base::ParseInt(lines[0], last_command_index)) {
+ if (!android::base::ParseUint(lines[0], last_command_index)) {
LOG(ERROR) << "Failed to parse integer in: " << lines[0];
return false;
}
@@ -116,10 +120,24 @@ static bool ParseLastCommandFile(int* last_command_index) {
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_file = CacheLocation::location().last_command_file();
+static bool FsyncDir(const std::string& dirname) {
+ android::base::unique_fd dfd(TEMP_FAILURE_RETRY(open(dirname.c_str(), O_RDONLY | O_DIRECTORY)));
+ if (dfd == -1) {
+ failure_type = errno == EIO ? kEioFailure : kFileOpenFailure;
+ PLOG(ERROR) << "Failed to open " << dirname;
+ return false;
+ }
+ if (fsync(dfd) == -1) {
+ failure_type = errno == EIO ? kEioFailure : kFsyncFailure;
+ PLOG(ERROR) << "Failed to fsync " << dirname;
+ return false;
+ }
+ return true;
+}
+
+// Update the last executed command index in the last_command_file.
+static bool UpdateLastCommandIndex(size_t command_index, const std::string& command_string) {
+ const std::string& last_command_file = Paths::Get().last_command_file();
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(
@@ -144,71 +162,44 @@ static bool UpdateLastCommandIndex(int command_index, const std::string& command
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;
+ if (!FsyncDir(android::base::Dirname(last_command_file))) {
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) {
- ssize_t r = TEMP_FAILURE_RETRY(ota_read(fd, data+so_far, size-so_far));
- if (r == -1) {
- failure_type = kFreadFailure;
- PLOG(ERROR) << "read failed";
- return -1;
- } else if (r == 0) {
- failure_type = kFreadFailure;
- LOG(ERROR) << "read reached unexpected EOF.";
- return -1;
- }
- so_far += r;
- }
- return 0;
-}
-
-static int read_all(int fd, std::vector<uint8_t>& buffer, size_t size) {
- return read_all(fd, buffer.data(), size);
-}
-
-static int write_all(int fd, const uint8_t* data, size_t size) {
- size_t written = 0;
- while (written < size) {
- ssize_t w = TEMP_FAILURE_RETRY(ota_write(fd, data+written, size-written));
- if (w == -1) {
- failure_type = kFwriteFailure;
- PLOG(ERROR) << "write failed";
- return -1;
- }
- written += w;
- }
-
- return 0;
-}
+bool SetUpdatedMarker(const std::string& marker) {
+ auto dirname = android::base::Dirname(marker);
+ auto res = mkdir(dirname.c_str(), MARKER_DIRECTORY_MODE);
+ if (res == -1 && errno != EEXIST) {
+ PLOG(ERROR) << "Failed to create directory for marker: " << dirname;
+ return false;
+ }
-static int write_all(int fd, const std::vector<uint8_t>& buffer, size_t size) {
- return write_all(fd, buffer.data(), size);
+ if (!android::base::WriteStringToFile("", marker)) {
+ PLOG(ERROR) << "Failed to write to marker file " << marker;
+ return false;
+ }
+ if (!FsyncDir(dirname)) {
+ return false;
+ }
+ LOG(INFO) << "Wrote updated marker to " << marker;
+ return true;
}
-static bool discard_blocks(int fd, off64_t offset, uint64_t size) {
- // Don't discard blocks unless the update is a retry run.
- if (!is_retry) {
+static bool discard_blocks(int fd, off64_t offset, uint64_t size, bool force = false) {
+ // Don't discard blocks unless the update is a retry run or force == true
+ if (!is_retry && !force) {
return true;
}
uint64_t args[2] = { static_cast<uint64_t>(offset), size };
if (ioctl(fd, BLKDISCARD, &args) == -1) {
+ // On devices that does not support BLKDISCARD, ignore the error.
+ if (errno == EOPNOTSUPP) {
+ return true;
+ }
PLOG(ERROR) << "BLKDISCARD ioctl failed";
return false;
}
@@ -225,11 +216,10 @@ static bool check_lseek(int fd, off64_t offset, int whence) {
return true;
}
-static void allocate(size_t size, std::vector<uint8_t>& buffer) {
- // if the buffer's big enough, reuse it.
- if (size <= buffer.size()) return;
-
- buffer.resize(size);
+static void allocate(size_t size, std::vector<uint8_t>* buffer) {
+ // If the buffer's big enough, reuse it.
+ if (size <= buffer->size()) return;
+ buffer->resize(size);
}
/**
@@ -274,7 +264,9 @@ class RangeSinkWriter {
write_now = current_range_left_;
}
- if (write_all(fd_, data, write_now) == -1) {
+ if (!android::base::WriteFully(fd_, data, write_now)) {
+ failure_type = errno == EIO ? kEioFailure : kFwriteFailure;
+ PLOG(ERROR) << "Failed to write " << write_now << " bytes of data";
break;
}
@@ -483,15 +475,17 @@ static void* unzip_new_data(void* cookie) {
return nullptr;
}
-static int ReadBlocks(const RangeSet& src, std::vector<uint8_t>& buffer, int fd) {
+static int ReadBlocks(const RangeSet& src, std::vector<uint8_t>* buffer, int fd) {
size_t p = 0;
- for (const auto& range : src) {
- if (!check_lseek(fd, static_cast<off64_t>(range.first) * BLOCKSIZE, SEEK_SET)) {
+ for (const auto& [begin, end] : src) {
+ if (!check_lseek(fd, static_cast<off64_t>(begin) * BLOCKSIZE, SEEK_SET)) {
return -1;
}
- size_t size = (range.second - range.first) * BLOCKSIZE;
- if (read_all(fd, buffer.data() + p, size) == -1) {
+ size_t size = (end - begin) * BLOCKSIZE;
+ if (!android::base::ReadFully(fd, buffer->data() + p, size)) {
+ failure_type = errno == EIO ? kEioFailure : kFreadFailure;
+ PLOG(ERROR) << "Failed to read " << size << " bytes of data";
return -1;
}
@@ -503,9 +497,9 @@ static int ReadBlocks(const RangeSet& src, std::vector<uint8_t>& buffer, int fd)
static int WriteBlocks(const RangeSet& tgt, const std::vector<uint8_t>& buffer, int fd) {
size_t written = 0;
- for (const auto& range : tgt) {
- off64_t offset = static_cast<off64_t>(range.first) * BLOCKSIZE;
- size_t size = (range.second - range.first) * BLOCKSIZE;
+ for (const auto& [begin, end] : tgt) {
+ off64_t offset = static_cast<off64_t>(begin) * BLOCKSIZE;
+ size_t size = (end - begin) * BLOCKSIZE;
if (!discard_blocks(fd, offset, size)) {
return -1;
}
@@ -514,7 +508,9 @@ static int WriteBlocks(const RangeSet& tgt, const std::vector<uint8_t>& buffer,
return -1;
}
- if (write_all(fd, buffer.data() + written, size) == -1) {
+ if (!android::base::WriteFully(fd, buffer.data() + written, size)) {
+ failure_type = errno == EIO ? kEioFailure : kFwriteFailure;
+ PLOG(ERROR) << "Failed to write " << size << " bytes of data";
return -1;
}
@@ -528,9 +524,8 @@ 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 cmdname;
+ std::string cmdline;
std::string freestash;
std::string stashbase;
bool canwrite;
@@ -644,43 +639,43 @@ static void PrintHashForMissingStashedBlocks(const std::string& id, int fd) {
LOG(INFO) << "print hash in hex for source blocks in missing stash: " << id;
const RangeSet& src = stash_map[id];
std::vector<uint8_t> buffer(src.blocks() * BLOCKSIZE);
- if (ReadBlocks(src, buffer, fd) == -1) {
- LOG(ERROR) << "failed to read source blocks for stash: " << id;
- return;
+ if (ReadBlocks(src, &buffer, fd) == -1) {
+ LOG(ERROR) << "failed to read source blocks for stash: " << id;
+ return;
}
PrintHashForCorruptedStashedBlocks(id, buffer, src);
}
static int VerifyBlocks(const std::string& expected, const std::vector<uint8_t>& buffer,
- const size_t blocks, bool printerror) {
- uint8_t digest[SHA_DIGEST_LENGTH];
- const uint8_t* data = buffer.data();
+ const size_t blocks, bool printerror) {
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ const uint8_t* data = buffer.data();
- SHA1(data, blocks * BLOCKSIZE, digest);
+ SHA1(data, blocks * BLOCKSIZE, digest);
- std::string hexdigest = print_sha1(digest);
+ std::string hexdigest = print_sha1(digest);
- if (hexdigest != expected) {
- if (printerror) {
- LOG(ERROR) << "failed to verify blocks (expected " << expected << ", read "
- << hexdigest << ")";
- }
- return -1;
+ if (hexdigest != expected) {
+ if (printerror) {
+ LOG(ERROR) << "failed to verify blocks (expected " << expected << ", read " << hexdigest
+ << ")";
}
+ return -1;
+ }
- return 0;
+ return 0;
}
static std::string GetStashFileName(const std::string& base, const std::string& id,
- const std::string& postfix) {
- if (base.empty()) {
- return "";
- }
-
- std::string fn(CacheLocation::location().stash_directory_base());
- fn += "/" + base + "/" + id + postfix;
-
- return fn;
+ const std::string& postfix) {
+ if (base.empty()) {
+ return "";
+ }
+ std::string filename = Paths::Get().stash_directory_base() + "/" + base;
+ if (id.empty() && postfix.empty()) {
+ return filename;
+ }
+ return filename + "/" + id + postfix;
}
// Does a best effort enumeration of stash files. Ignores possible non-file items in the stash
@@ -733,8 +728,8 @@ static void DeleteStash(const std::string& base) {
}
}
-static int LoadStash(CommandParameters& params, const std::string& id, bool verify, size_t* blocks,
- std::vector<uint8_t>& buffer, bool printnoent) {
+static int LoadStash(const CommandParameters& params, const std::string& id, bool verify,
+ std::vector<uint8_t>* buffer, bool printnoent) {
// In verify mode, if source range_set was saved for the given hash, check contents in the source
// blocks first. If the check fails, search for the stashed files on /cache as usual.
if (!params.canwrite) {
@@ -746,20 +741,17 @@ static int LoadStash(CommandParameters& params, const std::string& id, bool veri
LOG(ERROR) << "failed to read source blocks in stash map.";
return -1;
}
- if (VerifyBlocks(id, buffer, src.blocks(), true) != 0) {
+ if (VerifyBlocks(id, *buffer, src.blocks(), true) != 0) {
LOG(ERROR) << "failed to verify loaded source blocks in stash map.";
- PrintHashForCorruptedStashedBlocks(id, buffer, src);
+ if (!is_retry) {
+ PrintHashForCorruptedStashedBlocks(id, *buffer, src);
+ }
return -1;
}
return 0;
}
}
- size_t blockcount = 0;
- if (!blocks) {
- blocks = &blockcount;
- }
-
std::string fn = GetStashFileName(params.stashbase, id, "");
struct stat sb;
@@ -778,28 +770,30 @@ static int LoadStash(CommandParameters& params, const std::string& id, bool veri
return -1;
}
- android::base::unique_fd fd(TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_RDONLY)));
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(fn.c_str(), O_RDONLY)));
if (fd == -1) {
+ failure_type = errno == EIO ? kEioFailure : kFileOpenFailure;
PLOG(ERROR) << "open \"" << fn << "\" failed";
return -1;
}
allocate(sb.st_size, buffer);
- if (read_all(fd, buffer, sb.st_size) == -1) {
+ if (!android::base::ReadFully(fd, buffer->data(), sb.st_size)) {
+ failure_type = errno == EIO ? kEioFailure : kFreadFailure;
+ PLOG(ERROR) << "Failed to read " << sb.st_size << " bytes of data";
return -1;
}
- *blocks = sb.st_size / BLOCKSIZE;
-
- if (verify && VerifyBlocks(id, buffer, *blocks, true) != 0) {
+ size_t blocks = sb.st_size / BLOCKSIZE;
+ if (verify && VerifyBlocks(id, *buffer, blocks, true) != 0) {
LOG(ERROR) << "unexpected contents in " << fn;
if (stash_map.find(id) == stash_map.end()) {
LOG(ERROR) << "failed to find source blocks number for stash " << id
<< " when executing command: " << params.cmdname;
} else {
const RangeSet& src = stash_map[id];
- PrintHashForCorruptedStashedBlocks(id, buffer, src);
+ PrintHashForCorruptedStashedBlocks(id, *buffer, src);
}
DeleteFile(fn);
return -1;
@@ -809,111 +803,92 @@ static int LoadStash(CommandParameters& params, const std::string& id, bool veri
}
static int WriteStash(const std::string& base, const std::string& id, int blocks,
- std::vector<uint8_t>& buffer, bool checkspace, bool* exists) {
- if (base.empty()) {
- return -1;
- }
-
- if (checkspace && CacheSizeCheck(blocks * BLOCKSIZE) != 0) {
- LOG(ERROR) << "not enough space to write stash";
- return -1;
- }
-
- std::string fn = GetStashFileName(base, id, ".partial");
- std::string cn = GetStashFileName(base, id, "");
+ const std::vector<uint8_t>& buffer, bool checkspace, bool* exists) {
+ if (base.empty()) {
+ return -1;
+ }
- if (exists) {
- struct stat sb;
- int res = stat(cn.c_str(), &sb);
+ if (checkspace && !CheckAndFreeSpaceOnCache(blocks * BLOCKSIZE)) {
+ LOG(ERROR) << "not enough space to write stash";
+ return -1;
+ }
- if (res == 0) {
- // The file already exists and since the name is the hash of the contents,
- // it's safe to assume the contents are identical (accidental hash collisions
- // are unlikely)
- LOG(INFO) << " skipping " << blocks << " existing blocks in " << cn;
- *exists = true;
- return 0;
- }
+ std::string fn = GetStashFileName(base, id, ".partial");
+ std::string cn = GetStashFileName(base, id, "");
- *exists = false;
+ if (exists) {
+ struct stat sb;
+ int res = stat(cn.c_str(), &sb);
+
+ if (res == 0) {
+ // The file already exists and since the name is the hash of the contents,
+ // it's safe to assume the contents are identical (accidental hash collisions
+ // are unlikely)
+ LOG(INFO) << " skipping " << blocks << " existing blocks in " << cn;
+ *exists = true;
+ return 0;
}
- LOG(INFO) << " writing " << blocks << " blocks to " << cn;
+ *exists = false;
+ }
- android::base::unique_fd fd(
- TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_WRONLY | O_CREAT | O_TRUNC, STASH_FILE_MODE)));
- if (fd == -1) {
- PLOG(ERROR) << "failed to create \"" << fn << "\"";
- return -1;
- }
+ LOG(INFO) << " writing " << blocks << " blocks to " << cn;
- if (fchown(fd, AID_SYSTEM, AID_SYSTEM) != 0) { // system user
- PLOG(ERROR) << "failed to chown \"" << fn << "\"";
- return -1;
- }
+ android::base::unique_fd fd(
+ TEMP_FAILURE_RETRY(open(fn.c_str(), O_WRONLY | O_CREAT | O_TRUNC, STASH_FILE_MODE)));
+ if (fd == -1) {
+ failure_type = errno == EIO ? kEioFailure : kFileOpenFailure;
+ PLOG(ERROR) << "failed to create \"" << fn << "\"";
+ return -1;
+ }
- if (write_all(fd, buffer, blocks * BLOCKSIZE) == -1) {
- return -1;
- }
+ if (fchown(fd, AID_SYSTEM, AID_SYSTEM) != 0) { // system user
+ PLOG(ERROR) << "failed to chown \"" << fn << "\"";
+ return -1;
+ }
- if (ota_fsync(fd) == -1) {
- failure_type = kFsyncFailure;
- PLOG(ERROR) << "fsync \"" << fn << "\" failed";
- return -1;
- }
+ if (!android::base::WriteFully(fd, buffer.data(), blocks * BLOCKSIZE)) {
+ failure_type = errno == EIO ? kEioFailure : kFwriteFailure;
+ PLOG(ERROR) << "Failed to write " << blocks * BLOCKSIZE << " bytes of data";
+ return -1;
+ }
- if (rename(fn.c_str(), cn.c_str()) == -1) {
- PLOG(ERROR) << "rename(\"" << fn << "\", \"" << cn << "\") failed";
- return -1;
- }
+ if (fsync(fd) == -1) {
+ failure_type = errno == EIO ? kEioFailure : kFsyncFailure;
+ PLOG(ERROR) << "fsync \"" << fn << "\" failed";
+ return -1;
+ }
- std::string dname = GetStashFileName(base, "", "");
- android::base::unique_fd dfd(TEMP_FAILURE_RETRY(ota_open(dname.c_str(),
- O_RDONLY | O_DIRECTORY)));
- if (dfd == -1) {
- failure_type = kFileOpenFailure;
- PLOG(ERROR) << "failed to open \"" << dname << "\" failed";
- return -1;
- }
+ if (rename(fn.c_str(), cn.c_str()) == -1) {
+ PLOG(ERROR) << "rename(\"" << fn << "\", \"" << cn << "\") failed";
+ return -1;
+ }
- if (ota_fsync(dfd) == -1) {
- failure_type = kFsyncFailure;
- PLOG(ERROR) << "fsync \"" << dname << "\" failed";
- return -1;
- }
+ std::string dname = GetStashFileName(base, "", "");
+ if (!FsyncDir(dname)) {
+ return -1;
+ }
- return 0;
+ return 0;
}
// Creates a directory for storing stash files and checks if the /cache partition
// hash enough space for the expected amount of blocks we need to store. Returns
// >0 if we created the directory, zero if it existed already, and <0 of failure.
-
-static int CreateStash(State* state, size_t maxblocks, const std::string& blockdev,
- std::string& base) {
- if (blockdev.empty()) {
- return -1;
- }
-
- // Stash directory should be different for each partition to avoid conflicts
- // when updating multiple partitions at the same time, so we use the hash of
- // the block device name as the base directory
- uint8_t digest[SHA_DIGEST_LENGTH];
- SHA1(reinterpret_cast<const uint8_t*>(blockdev.data()), blockdev.size(), digest);
- base = print_sha1(digest);
-
+static int CreateStash(State* state, size_t maxblocks, const std::string& base) {
std::string dirname = GetStashFileName(base, "", "");
struct stat sb;
int res = stat(dirname.c_str(), &sb);
- size_t max_stash_size = maxblocks * BLOCKSIZE;
-
if (res == -1 && errno != ENOENT) {
ErrorAbort(state, kStashCreationFailure, "stat \"%s\" failed: %s", dirname.c_str(),
strerror(errno));
return -1;
- } else if (res != 0) {
+ }
+
+ size_t max_stash_size = maxblocks * BLOCKSIZE;
+ if (res == -1) {
LOG(INFO) << "creating stash " << dirname;
- res = mkdir(dirname.c_str(), STASH_DIRECTORY_MODE);
+ res = mkdir_recursively(dirname, STASH_DIRECTORY_MODE, false, nullptr);
if (res != 0) {
ErrorAbort(state, kStashCreationFailure, "mkdir \"%s\" failed: %s", dirname.c_str(),
@@ -927,7 +902,7 @@ static int CreateStash(State* state, size_t maxblocks, const std::string& blockd
return -1;
}
- if (CacheSizeCheck(max_stash_size) != 0) {
+ if (!CheckAndFreeSpaceOnCache(max_stash_size)) {
ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu needed)",
max_stash_size);
return -1;
@@ -959,7 +934,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) {
+ if (!CheckAndFreeSpaceOnCache(needed)) {
ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu more needed)",
needed);
return -1;
@@ -1023,7 +998,7 @@ static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size
return -1;
}
- allocate(*src_blocks * BLOCKSIZE, params.buffer);
+ allocate(*src_blocks * BLOCKSIZE, &params.buffer);
// "-" or <src_range> [<src_loc>]
if (params.tokens[params.cpos] == "-") {
@@ -1034,7 +1009,7 @@ static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size
CHECK(static_cast<bool>(src));
*overlap = src.Overlaps(tgt);
- if (ReadBlocks(src, params.buffer, params.fd) == -1) {
+ if (ReadBlocks(src, &params.buffer, params.fd) == -1) {
return -1;
}
@@ -1059,7 +1034,7 @@ static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size
}
std::vector<uint8_t> stash;
- if (LoadStash(params, tokens[0], false, nullptr, stash, true) == -1) {
+ if (LoadStash(params, tokens[0], false, &stash, true) == -1) {
// These source blocks will fail verification if used later, but we
// will let the caller decide if this is a fatal failure
LOG(ERROR) << "failed to load stash " << tokens[0];
@@ -1100,10 +1075,9 @@ static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size
*
* If the return value is 0, source blocks have expected content and the command can be performed.
*/
-static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t* src_blocks,
- bool onehash, bool* overlap) {
+static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet* tgt, size_t* src_blocks,
+ bool onehash) {
CHECK(src_blocks != nullptr);
- CHECK(overlap != nullptr);
if (params.cpos >= params.tokens.size()) {
LOG(ERROR) << "missing source hash";
@@ -1131,29 +1105,30 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t*
}
// <tgt_range>
- tgt = RangeSet::Parse(params.tokens[params.cpos++]);
- CHECK(static_cast<bool>(tgt));
+ *tgt = RangeSet::Parse(params.tokens[params.cpos++]);
+ CHECK(static_cast<bool>(*tgt));
- std::vector<uint8_t> tgtbuffer(tgt.blocks() * BLOCKSIZE);
- if (ReadBlocks(tgt, tgtbuffer, params.fd) == -1) {
+ std::vector<uint8_t> tgtbuffer(tgt->blocks() * BLOCKSIZE);
+ if (ReadBlocks(*tgt, &tgtbuffer, params.fd) == -1) {
return -1;
}
// Return now if target blocks already have expected content.
- if (VerifyBlocks(tgthash, tgtbuffer, tgt.blocks(), false) == 0) {
+ if (VerifyBlocks(tgthash, tgtbuffer, tgt->blocks(), false) == 0) {
return 1;
}
// Load source blocks.
- if (LoadSourceBlocks(params, tgt, src_blocks, overlap) == -1) {
+ bool overlap = false;
+ if (LoadSourceBlocks(params, *tgt, src_blocks, &overlap) == -1) {
return -1;
}
if (VerifyBlocks(srchash, params.buffer, *src_blocks, true) == 0) {
- // If source and target blocks overlap, stash the source blocks so we can
- // resume from possible write errors. In verify mode, we can skip stashing
- // because the source blocks won't be overwritten.
- if (*overlap && params.canwrite) {
+ // If source and target blocks overlap, stash the source blocks so we can resume from possible
+ // write errors. In verify mode, we can skip stashing because the source blocks won't be
+ // overwritten.
+ if (overlap && params.canwrite) {
LOG(INFO) << "stashing " << *src_blocks << " overlapping blocks to " << srchash;
bool stash_exists = false;
@@ -1163,10 +1138,6 @@ 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) {
@@ -1178,7 +1149,7 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t*
return 0;
}
- if (*overlap && LoadStash(params, srchash, true, nullptr, params.buffer, true) == 0) {
+ if (overlap && LoadStash(params, srchash, true, &params.buffer, true) == 0) {
// Overlapping source blocks were previously stashed, command can proceed. We are recovering
// from an interrupted command, so we don't know if the stash can safely be deleted after this
// command.
@@ -1196,9 +1167,8 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t*
static int PerformCommandMove(CommandParameters& params) {
size_t blocks = 0;
- bool overlap = false;
RangeSet tgt;
- int status = LoadSrcTgtVersion3(params, tgt, &blocks, true, &overlap);
+ int status = LoadSrcTgtVersion3(params, &tgt, &blocks, true);
if (status == -1) {
LOG(ERROR) << "failed to read blocks for move";
@@ -1244,8 +1214,7 @@ static int PerformCommandStash(CommandParameters& params) {
}
const std::string& id = params.tokens[params.cpos++];
- size_t blocks = 0;
- if (LoadStash(params, id, true, &blocks, params.buffer, false) == 0) {
+ if (LoadStash(params, id, true, &params.buffer, false) == 0) {
// Stash file already exists and has expected contents. Do not read from source again, as the
// source may have been already overwritten during a previous attempt.
return 0;
@@ -1254,11 +1223,11 @@ static int PerformCommandStash(CommandParameters& params) {
RangeSet src = RangeSet::Parse(params.tokens[params.cpos++]);
CHECK(static_cast<bool>(src));
- allocate(src.blocks() * BLOCKSIZE, params.buffer);
- if (ReadBlocks(src, params.buffer, params.fd) == -1) {
+ size_t blocks = src.blocks();
+ allocate(blocks * BLOCKSIZE, &params.buffer);
+ if (ReadBlocks(src, &params.buffer, params.fd) == -1) {
return -1;
}
- blocks = src.blocks();
stash_map[id] = src;
if (VerifyBlocks(id, params.buffer, blocks, true) != 0) {
@@ -1277,10 +1246,6 @@ static int PerformCommandStash(CommandParameters& params) {
LOG(INFO) << "stashing " << blocks << " blocks to " << id;
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;
@@ -1314,13 +1279,13 @@ static int PerformCommandZero(CommandParameters& params) {
LOG(INFO) << " zeroing " << tgt.blocks() << " blocks";
- allocate(BLOCKSIZE, params.buffer);
+ allocate(BLOCKSIZE, &params.buffer);
memset(params.buffer.data(), 0, BLOCKSIZE);
if (params.canwrite) {
- for (const auto& range : tgt) {
- off64_t offset = static_cast<off64_t>(range.first) * BLOCKSIZE;
- size_t size = (range.second - range.first) * BLOCKSIZE;
+ for (const auto& [begin, end] : tgt) {
+ off64_t offset = static_cast<off64_t>(begin) * BLOCKSIZE;
+ size_t size = (end - begin) * BLOCKSIZE;
if (!discard_blocks(params.fd, offset, size)) {
return -1;
}
@@ -1329,8 +1294,10 @@ static int PerformCommandZero(CommandParameters& params) {
return -1;
}
- for (size_t j = range.first; j < range.second; ++j) {
- if (write_all(params.fd, params.buffer, BLOCKSIZE) == -1) {
+ for (size_t j = begin; j < end; ++j) {
+ if (!android::base::WriteFully(params.fd, params.buffer.data(), BLOCKSIZE)) {
+ failure_type = errno == EIO ? kEioFailure : kFwriteFailure;
+ PLOG(ERROR) << "Failed to write " << BLOCKSIZE << " bytes of data";
return -1;
}
}
@@ -1401,8 +1368,7 @@ static int PerformCommandDiff(CommandParameters& params) {
RangeSet tgt;
size_t blocks = 0;
- bool overlap = false;
- int status = LoadSrcTgtVersion3(params, tgt, &blocks, false, &overlap);
+ int status = LoadSrcTgtVersion3(params, &tgt, &blocks, false);
if (status == -1) {
LOG(ERROR) << "failed to read blocks for diff";
@@ -1422,14 +1388,15 @@ static int PerformCommandDiff(CommandParameters& params) {
if (status == 0) {
LOG(INFO) << "patching " << blocks << " blocks to " << tgt.blocks();
Value patch_value(
- VAL_BLOB, std::string(reinterpret_cast<const char*>(params.patch_start + offset), len));
+ Value::Type::BLOB,
+ std::string(reinterpret_cast<const char*>(params.patch_start + offset), len));
RangeSinkWriter writer(params.fd, tgt);
if (params.cmdname[0] == 'i') { // imgdiff
if (ApplyImagePatch(params.buffer.data(), blocks * BLOCKSIZE, patch_value,
std::bind(&RangeSinkWriter::Write, &writer, std::placeholders::_1,
std::placeholders::_2),
- nullptr, nullptr) != 0) {
+ nullptr) != 0) {
LOG(ERROR) << "Failed to apply image patch.";
failure_type = kPatchApplicationFailure;
return -1;
@@ -1437,8 +1404,7 @@ static int PerformCommandDiff(CommandParameters& params) {
} else {
if (ApplyBSDiffPatch(params.buffer.data(), blocks * BLOCKSIZE, patch_value, 0,
std::bind(&RangeSinkWriter::Write, &writer, std::placeholders::_1,
- std::placeholders::_2),
- nullptr) != 0) {
+ std::placeholders::_2)) != 0) {
LOG(ERROR) << "Failed to apply bsdiff patch.";
failure_type = kPatchApplicationFailure;
return -1;
@@ -1447,7 +1413,10 @@ static int PerformCommandDiff(CommandParameters& params) {
// We expect the output of the patcher to fill the tgt ranges exactly.
if (!writer.Finished()) {
- LOG(ERROR) << "range sink underrun?";
+ LOG(ERROR) << "Failed to fully write target blocks (range sink underrun): Missing "
+ << writer.AvailableSpace() << " bytes";
+ failure_type = kPatchApplicationFailure;
+ return -1;
}
} else {
LOG(INFO) << "skipping " << blocks << " blocks already patched to " << tgt.blocks() << " ["
@@ -1492,41 +1461,169 @@ static int PerformCommandErase(CommandParameters& params) {
if (params.canwrite) {
LOG(INFO) << " erasing " << tgt.blocks() << " blocks";
- for (const auto& range : tgt) {
- uint64_t blocks[2];
- // offset in bytes
- blocks[0] = range.first * static_cast<uint64_t>(BLOCKSIZE);
- // length in bytes
- blocks[1] = (range.second - range.first) * static_cast<uint64_t>(BLOCKSIZE);
+ for (const auto& [begin, end] : tgt) {
+ off64_t offset = static_cast<off64_t>(begin) * BLOCKSIZE;
+ size_t size = (end - begin) * BLOCKSIZE;
+ if (!discard_blocks(params.fd, offset, size, true /* force */)) {
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int PerformCommandAbort(CommandParameters&) {
+ LOG(INFO) << "Aborting as instructed";
+ return -1;
+}
+
+// Computes the hash_tree bytes based on the parameters, checks if the root hash of the tree
+// matches the expected hash and writes the result to the specified range on the block_device.
+// Hash_tree computation arguments:
+// hash_tree_ranges
+// source_ranges
+// hash_algorithm
+// salt_hex
+// root_hash
+static int PerformCommandComputeHashTree(CommandParameters& params) {
+ if (params.cpos + 5 != params.tokens.size()) {
+ LOG(ERROR) << "Invaild arguments count in hash computation " << params.cmdline;
+ return -1;
+ }
+
+ // Expects the hash_tree data to be contiguous.
+ RangeSet hash_tree_ranges = RangeSet::Parse(params.tokens[params.cpos++]);
+ if (!hash_tree_ranges || hash_tree_ranges.size() != 1) {
+ LOG(ERROR) << "Invalid hash tree ranges in " << params.cmdline;
+ return -1;
+ }
+
+ RangeSet source_ranges = RangeSet::Parse(params.tokens[params.cpos++]);
+ if (!source_ranges) {
+ LOG(ERROR) << "Invalid source ranges in " << params.cmdline;
+ return -1;
+ }
+
+ auto hash_function = HashTreeBuilder::HashFunction(params.tokens[params.cpos++]);
+ if (hash_function == nullptr) {
+ LOG(ERROR) << "Invalid hash algorithm in " << params.cmdline;
+ return -1;
+ }
+
+ std::vector<unsigned char> salt;
+ std::string salt_hex = params.tokens[params.cpos++];
+ if (salt_hex.empty() || !HashTreeBuilder::ParseBytesArrayFromString(salt_hex, &salt)) {
+ LOG(ERROR) << "Failed to parse salt in " << params.cmdline;
+ return -1;
+ }
+
+ std::string expected_root_hash = params.tokens[params.cpos++];
+ if (expected_root_hash.empty()) {
+ LOG(ERROR) << "Invalid root hash in " << params.cmdline;
+ return -1;
+ }
+
+ // Starts the hash_tree computation.
+ HashTreeBuilder builder(BLOCKSIZE, hash_function);
+ if (!builder.Initialize(static_cast<int64_t>(source_ranges.blocks()) * BLOCKSIZE, salt)) {
+ LOG(ERROR) << "Failed to initialize hash tree computation, source " << source_ranges.ToString()
+ << ", salt " << salt_hex;
+ return -1;
+ }
+
+ // Iterates through every block in the source_ranges and updates the hash tree structure
+ // accordingly.
+ for (const auto& [begin, end] : source_ranges) {
+ uint8_t buffer[BLOCKSIZE];
+ if (!check_lseek(params.fd, static_cast<off64_t>(begin) * BLOCKSIZE, SEEK_SET)) {
+ PLOG(ERROR) << "Failed to seek to block: " << begin;
+ return -1;
+ }
+
+ for (size_t i = begin; i < end; i++) {
+ if (!android::base::ReadFully(params.fd, buffer, BLOCKSIZE)) {
+ failure_type = errno == EIO ? kEioFailure : kFreadFailure;
+ LOG(ERROR) << "Failed to read data in " << begin << ":" << end;
+ return -1;
+ }
- if (ioctl(params.fd, BLKDISCARD, &blocks) == -1) {
- PLOG(ERROR) << "BLKDISCARD ioctl failed";
+ if (!builder.Update(reinterpret_cast<unsigned char*>(buffer), BLOCKSIZE)) {
+ LOG(ERROR) << "Failed to update hash tree builder";
return -1;
}
}
}
+ if (!builder.BuildHashTree()) {
+ LOG(ERROR) << "Failed to build hash tree";
+ return -1;
+ }
+
+ std::string root_hash_hex = HashTreeBuilder::BytesArrayToString(builder.root_hash());
+ if (root_hash_hex != expected_root_hash) {
+ LOG(ERROR) << "Root hash of the verity hash tree doesn't match the expected value. Expected: "
+ << expected_root_hash << ", actual: " << root_hash_hex;
+ return -1;
+ }
+
+ uint64_t write_offset = static_cast<uint64_t>(hash_tree_ranges.GetBlockNumber(0)) * BLOCKSIZE;
+ if (params.canwrite && !builder.WriteHashTreeToFd(params.fd, write_offset)) {
+ LOG(ERROR) << "Failed to write hash tree to output";
+ return -1;
+ }
+
+ // TODO(xunchang) validates the written bytes
+
return 0;
}
-// Definitions for transfer list command functions
-typedef int (*CommandFunction)(CommandParameters&);
+using CommandFunction = std::function<int(CommandParameters&)>;
-struct Command {
- const char* name;
- CommandFunction f;
-};
+using CommandMap = std::unordered_map<Command::Type, CommandFunction>;
-// args:
-// - block device (or file) to modify in-place
-// - transfer list (blob)
-// - new data stream (filename within package.zip)
-// - patch stream (filename within package.zip, must be uncompressed)
+static bool Sha1DevicePath(const std::string& path, uint8_t digest[SHA_DIGEST_LENGTH]) {
+ auto device_name = android::base::Basename(path);
+ auto dm_target_name_path = "/sys/block/" + device_name + "/dm/name";
+
+ struct stat sb;
+ if (stat(dm_target_name_path.c_str(), &sb) == 0) {
+ // This is a device mapper target. Use partition name as part of the hash instead. Do not
+ // include extents as part of the hash, because the size of a partition may be shrunk after
+ // the patches are applied.
+ std::string dm_target_name;
+ if (!android::base::ReadFileToString(dm_target_name_path, &dm_target_name)) {
+ PLOG(ERROR) << "Cannot read " << dm_target_name_path;
+ return false;
+ }
+ SHA1(reinterpret_cast<const uint8_t*>(dm_target_name.data()), dm_target_name.size(), digest);
+ return true;
+ }
+
+ if (errno != ENOENT) {
+ // This is a device mapper target, but its name cannot be retrieved.
+ PLOG(ERROR) << "Cannot get dm target name for " << path;
+ return false;
+ }
+
+ // This doesn't appear to be a device mapper target, but if its name starts with dm-, something
+ // else might have gone wrong.
+ if (android::base::StartsWith(device_name, "dm-")) {
+ LOG(WARNING) << "Device " << path << " starts with dm- but is not mapped by device-mapper.";
+ }
+
+ // Stash directory should be different for each partition to avoid conflicts when updating
+ // multiple partitions at the same time, so we use the hash of the block device name as the base
+ // directory.
+ SHA1(reinterpret_cast<const uint8_t*>(path.data()), path.size(), digest);
+ return true;
+}
static Value* PerformBlockImageUpdate(const char* name, State* state,
const std::vector<std::unique_ptr<Expr>>& argv,
- const Command* commands, size_t cmdcount, bool dryrun) {
+ const CommandMap& command_map, bool dryrun) {
CommandParameters params = {};
+ stash_map.clear();
params.canwrite = !dryrun;
LOG(INFO) << "performing " << (dryrun ? "verification" : "update");
@@ -1545,87 +1642,92 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
return nullptr;
}
+ // args:
+ // - block device (or file) to modify in-place
+ // - transfer list (blob)
+ // - new data stream (filename within package.zip)
+ // - patch stream (filename within package.zip, must be uncompressed)
const std::unique_ptr<Value>& blockdev_filename = args[0];
const std::unique_ptr<Value>& transfer_list_value = args[1];
const std::unique_ptr<Value>& new_data_fn = args[2];
const std::unique_ptr<Value>& patch_data_fn = args[3];
- if (blockdev_filename->type != VAL_STRING) {
+ if (blockdev_filename->type != Value::Type::STRING) {
ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", name);
return StringValue("");
}
- if (transfer_list_value->type != VAL_BLOB) {
+ if (transfer_list_value->type != Value::Type::BLOB) {
ErrorAbort(state, kArgsParsingFailure, "transfer_list argument to %s must be blob", name);
return StringValue("");
}
- if (new_data_fn->type != VAL_STRING) {
+ if (new_data_fn->type != Value::Type::STRING) {
ErrorAbort(state, kArgsParsingFailure, "new_data_fn argument to %s must be string", name);
return StringValue("");
}
- if (patch_data_fn->type != VAL_STRING) {
+ if (patch_data_fn->type != Value::Type::STRING) {
ErrorAbort(state, kArgsParsingFailure, "patch_data_fn argument to %s must be string", name);
return StringValue("");
}
- UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
- if (ui == nullptr) {
+ auto updater = static_cast<Updater*>(state->cookie);
+ ZipArchiveHandle za = updater->package_handle();
+ if (za == nullptr) {
return StringValue("");
}
- FILE* cmd_pipe = ui->cmd_pipe;
- ZipArchiveHandle za = ui->package_zip;
-
- if (cmd_pipe == nullptr || za == nullptr) {
- return StringValue("");
- }
-
- ZipString path_data(patch_data_fn->data.c_str());
+ std::string_view path_data(patch_data_fn->data);
ZipEntry patch_entry;
if (FindEntry(za, path_data, &patch_entry) != 0) {
LOG(ERROR) << name << "(): no file \"" << patch_data_fn->data << "\" in package";
return StringValue("");
}
+ params.patch_start = updater->GetMappedPackageAddress() + patch_entry.offset;
- params.patch_start = ui->package_zip_addr + patch_entry.offset;
- ZipString new_data(new_data_fn->data.c_str());
+ std::string_view new_data(new_data_fn->data);
ZipEntry new_entry;
if (FindEntry(za, new_data, &new_entry) != 0) {
LOG(ERROR) << name << "(): no file \"" << new_data_fn->data << "\" in package";
return StringValue("");
}
- params.fd.reset(TEMP_FAILURE_RETRY(ota_open(blockdev_filename->data.c_str(), O_RDWR)));
+ params.fd.reset(TEMP_FAILURE_RETRY(open(blockdev_filename->data.c_str(), O_RDWR)));
if (params.fd == -1) {
+ failure_type = errno == EIO ? kEioFailure : kFileOpenFailure;
PLOG(ERROR) << "open \"" << blockdev_filename->data << "\" failed";
return StringValue("");
}
- if (params.canwrite) {
- params.nti.za = za;
- params.nti.entry = new_entry;
- params.nti.brotli_compressed = android::base::EndsWith(new_data_fn->data, ".br");
- if (params.nti.brotli_compressed) {
- // Initialize brotli decoder state.
- params.nti.brotli_decoder_state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
- }
- params.nti.receiver_available = true;
-
- pthread_mutex_init(&params.nti.mu, nullptr);
- pthread_cond_init(&params.nti.cv, nullptr);
- pthread_attr_t attr;
- pthread_attr_init(&attr);
- pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ if (!Sha1DevicePath(blockdev_filename->data, digest)) {
+ return StringValue("");
+ }
+ params.stashbase = print_sha1(digest);
- int error = pthread_create(&params.thread, &attr, unzip_new_data, &params.nti);
- if (error != 0) {
- PLOG(ERROR) << "pthread_create failed";
+ // Possibly do return early on retry, by checking the marker. If the update on this partition has
+ // been finished (but interrupted at a later point), there could be leftover on /cache that would
+ // fail the no-op retry.
+ std::string updated_marker = GetStashFileName(params.stashbase + ".UPDATED", "", "");
+ if (is_retry) {
+ struct stat sb;
+ int result = stat(updated_marker.c_str(), &sb);
+ if (result == 0) {
+ LOG(INFO) << "Skipping already updated partition " << blockdev_filename->data
+ << " based on marker";
+ return StringValue("t");
+ }
+ } else {
+ // Delete the obsolete marker if any.
+ std::string err;
+ if (!android::base::RemoveFileIfExists(updated_marker, &err)) {
+ LOG(ERROR) << "Failed to remove partition updated marker " << updated_marker << ": " << err;
return StringValue("");
}
}
+ static constexpr size_t kTransferListHeaderLines = 4;
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]",
+ if (lines.size() < kTransferListHeaderLines) {
+ ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]",
lines.size());
return StringValue("");
}
@@ -1649,13 +1751,6 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
return StringValue("t");
}
- size_t start = 2;
- if (lines.size() < 4) {
- ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]",
- lines.size());
- return StringValue("");
- }
-
// Third line is how many stash entries are needed simultaneously.
LOG(INFO) << "maximum stash entries " << lines[2];
@@ -1667,15 +1762,38 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
return StringValue("");
}
- int res = CreateStash(state, stash_max_blocks, blockdev_filename->data, params.stashbase);
+ int res = CreateStash(state, stash_max_blocks, params.stashbase);
if (res == -1) {
return StringValue("");
}
-
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.
+ // Set up the new data writer.
+ if (params.canwrite) {
+ params.nti.za = za;
+ params.nti.entry = new_entry;
+ params.nti.brotli_compressed = android::base::EndsWith(new_data_fn->data, ".br");
+ if (params.nti.brotli_compressed) {
+ // Initialize brotli decoder state.
+ params.nti.brotli_decoder_state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
+ }
+ params.nti.receiver_available = true;
+
+ pthread_mutex_init(&params.nti.mu, nullptr);
+ pthread_cond_init(&params.nti.cv, nullptr);
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+ int error = pthread_create(&params.thread, &attr, unzip_new_data, &params.nti);
+ if (error != 0) {
+ LOG(ERROR) << "pthread_create failed: " << strerror(error);
+ return StringValue("");
+ }
+ }
+
+ // When performing an update, save the index and cmdline of the current command into the
+ // last_command_file.
// 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
@@ -1684,92 +1802,90 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
// 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;
+ bool skip_executed_command = true;
+ size_t 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
- std::unordered_map<std::string, const Command*> cmd_map;
- for (size_t i = 0; i < cmdcount; ++i) {
- if (cmd_map.find(commands[i].name) != cmd_map.end()) {
- LOG(ERROR) << "Error: command [" << commands[i].name << "] already exists in the cmd map.";
- return StringValue(strdup(""));
- }
- cmd_map[commands[i].name] = &commands[i];
+ // We failed to parse the last command. Disallow skipping executed commands.
+ skip_executed_command = false;
}
int rc = -1;
// Subsequent lines are all individual transfer commands
- for (size_t i = start; i < lines.size(); i++) {
+ for (size_t i = kTransferListHeaderLines; i < lines.size(); i++) {
const std::string& line = lines[i];
if (line.empty()) continue;
+ size_t cmdindex = i - kTransferListHeaderLines;
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.cmdname = params.tokens[params.cpos++];
+ params.cmdline = line;
params.target_verified = false;
- if (cmd_map.find(params.cmdname) == cmd_map.end()) {
+ Command::Type cmd_type = Command::ParseType(params.cmdname);
+ if (cmd_type == Command::Type::LAST) {
LOG(ERROR) << "unexpected command [" << params.cmdname << "]";
goto pbiudone;
}
- const Command* cmd = cmd_map[params.cmdname];
+ const CommandFunction& performer = command_map.at(cmd_type);
// Skip the command if we explicitly set the corresponding function pointer to nullptr, e.g.
// "erase" during block_image_verify.
- if (cmd->f == nullptr) {
+ if (performer == nullptr) {
LOG(DEBUG) << "skip executing command [" << line << "]";
continue;
}
- // 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
+ // Skip all commands before the saved last command index when resuming an update, except for
+ // "new" command. Because new commands read in the data sequentially.
+ if (params.canwrite && skip_executed_command && cmdindex <= saved_last_command_index &&
+ cmd_type != Command::Type::NEW) {
+ LOG(INFO) << "Skipping already executed command: " << cmdindex
<< ", last executed command for previous update: " << saved_last_command_index;
continue;
}
- if (cmd->f(params) == -1) {
+ if (performer(params) == -1) {
LOG(ERROR) << "failed to execute command [" << line << "]";
+ if (cmd_type == Command::Type::COMPUTE_HASH_TREE && failure_type == kNoCause) {
+ failure_type = kHashTreeComputationFailure;
+ }
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) {
+ // 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 && skip_executed_command && 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") &&
+ if ((cmd_type == Command::Type::MOVE || cmd_type == Command::Type::BSDIFF ||
+ cmd_type == Command::Type::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;
+ skip_executed_command = false;
DeleteLastCommandFile();
}
}
+
if (params.canwrite) {
- if (ota_fsync(params.fd) == -1) {
- failure_type = kFsyncFailure;
+ if (fsync(params.fd) == -1) {
+ failure_type = errno == EIO ? kEioFailure : kFsyncFailure;
PLOG(ERROR) << "fsync failed";
goto pbiudone;
}
- fprintf(cmd_pipe, "set_progress %.4f\n", static_cast<double>(params.written) / total_blocks);
- fflush(cmd_pipe);
+
+ if (!UpdateLastCommandIndex(cmdindex, params.cmdline)) {
+ LOG(WARNING) << "Failed to update the last command file.";
+ }
+
+ updater->WriteToCommandPipe(
+ android::base::StringPrintf("set_progress %.4f",
+ static_cast<double>(params.written) / total_blocks),
+ true);
}
}
@@ -1796,14 +1912,25 @@ pbiudone:
const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
if (partition != nullptr && *(partition + 1) != 0) {
- fprintf(cmd_pipe, "log bytes_written_%s: %zu\n", partition + 1, params.written * BLOCKSIZE);
- fprintf(cmd_pipe, "log bytes_stashed_%s: %zu\n", partition + 1, params.stashed * BLOCKSIZE);
- fflush(cmd_pipe);
+ updater->WriteToCommandPipe(
+ android::base::StringPrintf("log bytes_written_%s: %" PRIu64, partition + 1,
+ static_cast<uint64_t>(params.written) * BLOCKSIZE));
+ updater->WriteToCommandPipe(
+ android::base::StringPrintf("log bytes_stashed_%s: %" PRIu64, partition + 1,
+ static_cast<uint64_t>(params.stashed) * BLOCKSIZE),
+ true);
}
// Delete stash only after successfully completing the update, as it may contain blocks needed
// to complete the update later.
DeleteStash(params.stashbase);
DeleteLastCommandFile();
+
+ // Create a marker on /cache partition, which allows skipping the update on this partition on
+ // retry. The marker will be removed once booting into normal boot, or before starting next
+ // fresh install.
+ if (!SetUpdatedMarker(updated_marker)) {
+ LOG(WARNING) << "Failed to set updated marker; continuing";
+ }
}
pthread_mutex_destroy(&params.nti.mu);
@@ -1812,8 +1939,8 @@ pbiudone:
LOG(INFO) << "verified partition contents; update may be resumed";
}
- if (ota_fsync(params.fd) == -1) {
- failure_type = kFsyncFailure;
+ if (fsync(params.fd) == -1) {
+ failure_type = errno == EIO ? kEioFailure : kFsyncFailure;
PLOG(ERROR) << "fsync failed";
}
// params.fd will be automatically closed because it's a unique_fd.
@@ -1886,38 +2013,46 @@ pbiudone:
*/
Value* BlockImageVerifyFn(const char* name, State* state,
const std::vector<std::unique_ptr<Expr>>& argv) {
- // Commands which are not tested are set to nullptr to skip them completely
- const Command commands[] = {
- { "bsdiff", PerformCommandDiff },
- { "erase", nullptr },
- { "free", PerformCommandFree },
- { "imgdiff", PerformCommandDiff },
- { "move", PerformCommandMove },
- { "new", nullptr },
- { "stash", PerformCommandStash },
- { "zero", nullptr }
- };
-
- // Perform a dry run without writing to test if an update can proceed
- return PerformBlockImageUpdate(name, state, argv, commands,
- sizeof(commands) / sizeof(commands[0]), true);
+ // Commands which are not allowed are set to nullptr to skip them completely.
+ const CommandMap command_map{
+ // clang-format off
+ { Command::Type::ABORT, PerformCommandAbort },
+ { Command::Type::BSDIFF, PerformCommandDiff },
+ { Command::Type::COMPUTE_HASH_TREE, nullptr },
+ { Command::Type::ERASE, nullptr },
+ { Command::Type::FREE, PerformCommandFree },
+ { Command::Type::IMGDIFF, PerformCommandDiff },
+ { Command::Type::MOVE, PerformCommandMove },
+ { Command::Type::NEW, nullptr },
+ { Command::Type::STASH, PerformCommandStash },
+ { Command::Type::ZERO, nullptr },
+ // clang-format on
+ };
+ CHECK_EQ(static_cast<size_t>(Command::Type::LAST), command_map.size());
+
+ // Perform a dry run without writing to test if an update can proceed.
+ return PerformBlockImageUpdate(name, state, argv, command_map, true);
}
Value* BlockImageUpdateFn(const char* name, State* state,
const std::vector<std::unique_ptr<Expr>>& argv) {
- const Command commands[] = {
- { "bsdiff", PerformCommandDiff },
- { "erase", PerformCommandErase },
- { "free", PerformCommandFree },
- { "imgdiff", PerformCommandDiff },
- { "move", PerformCommandMove },
- { "new", PerformCommandNew },
- { "stash", PerformCommandStash },
- { "zero", PerformCommandZero }
- };
-
- return PerformBlockImageUpdate(name, state, argv, commands,
- sizeof(commands) / sizeof(commands[0]), false);
+ const CommandMap command_map{
+ // clang-format off
+ { Command::Type::ABORT, PerformCommandAbort },
+ { Command::Type::BSDIFF, PerformCommandDiff },
+ { Command::Type::COMPUTE_HASH_TREE, PerformCommandComputeHashTree },
+ { Command::Type::ERASE, PerformCommandErase },
+ { Command::Type::FREE, PerformCommandFree },
+ { Command::Type::IMGDIFF, PerformCommandDiff },
+ { Command::Type::MOVE, PerformCommandMove },
+ { Command::Type::NEW, PerformCommandNew },
+ { Command::Type::STASH, PerformCommandStash },
+ { Command::Type::ZERO, PerformCommandZero },
+ // clang-format on
+ };
+ CHECK_EQ(static_cast<size_t>(Command::Type::LAST), command_map.size());
+
+ return PerformBlockImageUpdate(name, state, argv, command_map, false);
}
Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
@@ -1934,18 +2069,19 @@ Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique
const std::unique_ptr<Value>& blockdev_filename = args[0];
const std::unique_ptr<Value>& ranges = args[1];
- if (blockdev_filename->type != VAL_STRING) {
+ if (blockdev_filename->type != Value::Type::STRING) {
ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", name);
return StringValue("");
}
- if (ranges->type != VAL_STRING) {
+ if (ranges->type != Value::Type::STRING) {
ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name);
return StringValue("");
}
- android::base::unique_fd fd(ota_open(blockdev_filename->data.c_str(), O_RDWR));
+ android::base::unique_fd fd(open(blockdev_filename->data.c_str(), O_RDWR));
if (fd == -1) {
- ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", blockdev_filename->data.c_str(),
+ CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure;
+ ErrorAbort(state, cause_code, "open \"%s\" failed: %s", blockdev_filename->data.c_str(),
strerror(errno));
return StringValue("");
}
@@ -1957,16 +2093,17 @@ Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique
SHA1_Init(&ctx);
std::vector<uint8_t> buffer(BLOCKSIZE);
- for (const auto& range : rs) {
- if (!check_lseek(fd, static_cast<off64_t>(range.first) * BLOCKSIZE, SEEK_SET)) {
+ for (const auto& [begin, end] : rs) {
+ if (!check_lseek(fd, static_cast<off64_t>(begin) * BLOCKSIZE, SEEK_SET)) {
ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", blockdev_filename->data.c_str(),
strerror(errno));
return StringValue("");
}
- for (size_t j = range.first; j < range.second; ++j) {
- if (read_all(fd, buffer, BLOCKSIZE) == -1) {
- ErrorAbort(state, kFreadFailure, "failed to read %s: %s", blockdev_filename->data.c_str(),
+ for (size_t j = begin; j < end; ++j) {
+ if (!android::base::ReadFully(fd, buffer.data(), BLOCKSIZE)) {
+ CauseCode cause_code = errno == EIO ? kEioFailure : kFreadFailure;
+ ErrorAbort(state, cause_code, "failed to read %s: %s", blockdev_filename->data.c_str(),
strerror(errno));
return StringValue("");
}
@@ -2000,14 +2137,15 @@ Value* CheckFirstBlockFn(const char* name, State* state,
const std::unique_ptr<Value>& arg_filename = args[0];
- if (arg_filename->type != VAL_STRING) {
+ if (arg_filename->type != Value::Type::STRING) {
ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name);
return StringValue("");
}
- android::base::unique_fd fd(ota_open(arg_filename->data.c_str(), O_RDONLY));
+ android::base::unique_fd fd(open(arg_filename->data.c_str(), O_RDONLY));
if (fd == -1) {
- ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", arg_filename->data.c_str(),
+ CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure;
+ ErrorAbort(state, cause_code, "open \"%s\" failed: %s", arg_filename->data.c_str(),
strerror(errno));
return StringValue("");
}
@@ -2015,8 +2153,9 @@ Value* CheckFirstBlockFn(const char* name, State* state,
RangeSet blk0(std::vector<Range>{ Range{ 0, 1 } });
std::vector<uint8_t> block0_buffer(BLOCKSIZE);
- if (ReadBlocks(blk0, block0_buffer, fd) == -1) {
- ErrorAbort(state, kFreadFailure, "failed to read %s: %s", arg_filename->data.c_str(),
+ if (ReadBlocks(blk0, &block0_buffer, fd) == -1) {
+ CauseCode cause_code = errno == EIO ? kEioFailure : kFreadFailure;
+ ErrorAbort(state, cause_code, "failed to read %s: %s", arg_filename->data.c_str(),
strerror(errno));
return StringValue("");
}
@@ -2032,8 +2171,11 @@ Value* CheckFirstBlockFn(const char* name, State* state,
uint16_t mount_count = *reinterpret_cast<uint16_t*>(&block0_buffer[0x400 + 0x34]);
if (mount_count > 0) {
- uiPrintf(state, "Device was remounted R/W %" PRIu16 " times", mount_count);
- uiPrintf(state, "Last remount happened on %s", ctime(&mount_time));
+ auto updater = static_cast<Updater*>(state->cookie);
+ updater->UiPrint(
+ android::base::StringPrintf("Device was remounted R/W %" PRIu16 " times", mount_count));
+ updater->UiPrint(
+ android::base::StringPrintf("Last remount happened on %s", ctime(&mount_time)));
}
return StringValue("t");
@@ -2055,11 +2197,11 @@ Value* BlockImageRecoverFn(const char* name, State* state,
const std::unique_ptr<Value>& filename = args[0];
const std::unique_ptr<Value>& ranges = args[1];
- if (filename->type != VAL_STRING) {
+ if (filename->type != Value::Type::STRING) {
ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name);
return StringValue("");
}
- if (ranges->type != VAL_STRING) {
+ if (ranges->type != Value::Type::STRING) {
ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name);
return StringValue("");
}
@@ -2093,8 +2235,8 @@ Value* BlockImageRecoverFn(const char* name, State* state,
}
uint8_t buffer[BLOCKSIZE];
- for (const auto& range : rs) {
- for (size_t j = range.first; j < range.second; ++j) {
+ for (const auto& [begin, end] : rs) {
+ for (size_t j = begin; j < end; ++j) {
// Stay within the data area, libfec validates and corrects metadata
if (status.data_size <= static_cast<uint64_t>(j) * BLOCKSIZE) {
continue;
diff --git a/updater/commands.cpp b/updater/commands.cpp
new file mode 100644
index 000000000..aed63369c
--- /dev/null
+++ b/updater/commands.cpp
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "private/commands.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include <functional>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <openssl/sha.h>
+
+#include "otautil/print_sha1.h"
+#include "otautil/rangeset.h"
+
+using namespace std::string_literals;
+
+bool Command::abort_allowed_ = false;
+
+Command::Command(Type type, size_t index, std::string cmdline, HashTreeInfo hash_tree_info)
+ : type_(type),
+ index_(index),
+ cmdline_(std::move(cmdline)),
+ hash_tree_info_(std::move(hash_tree_info)) {
+ CHECK(type == Type::COMPUTE_HASH_TREE);
+}
+
+Command::Type Command::ParseType(const std::string& type_str) {
+ if (type_str == "abort") {
+ if (!abort_allowed_) {
+ LOG(ERROR) << "ABORT disallowed";
+ return Type::LAST;
+ }
+ return Type::ABORT;
+ } else if (type_str == "bsdiff") {
+ return Type::BSDIFF;
+ } else if (type_str == "compute_hash_tree") {
+ return Type::COMPUTE_HASH_TREE;
+ } else if (type_str == "erase") {
+ return Type::ERASE;
+ } else if (type_str == "free") {
+ return Type::FREE;
+ } else if (type_str == "imgdiff") {
+ return Type::IMGDIFF;
+ } else if (type_str == "move") {
+ return Type::MOVE;
+ } else if (type_str == "new") {
+ return Type::NEW;
+ } else if (type_str == "stash") {
+ return Type::STASH;
+ } else if (type_str == "zero") {
+ return Type::ZERO;
+ }
+ return Type::LAST;
+};
+
+bool Command::ParseTargetInfoAndSourceInfo(const std::vector<std::string>& tokens,
+ const std::string& tgt_hash, TargetInfo* target,
+ const std::string& src_hash, SourceInfo* source,
+ std::string* err) {
+ // We expect the given args (in 'tokens' vector) in one of the following formats.
+ //
+ // <tgt_ranges> <src_block_count> - <[stash_id:location] ...>
+ // (loads data from stashes only)
+ //
+ // <tgt_ranges> <src_block_count> <src_ranges>
+ // (loads data from source image only)
+ //
+ // <tgt_ranges> <src_block_count> <src_ranges> <src_ranges_location> <[stash_id:location] ...>
+ // (loads data from both of source image and stashes)
+
+ // At least it needs to provide three args: <tgt_ranges>, <src_block_count> and "-"/<src_ranges>.
+ if (tokens.size() < 3) {
+ *err = "invalid number of args";
+ return false;
+ }
+
+ size_t pos = 0;
+ RangeSet tgt_ranges = RangeSet::Parse(tokens[pos++]);
+ if (!tgt_ranges) {
+ *err = "invalid target ranges";
+ return false;
+ }
+ *target = TargetInfo(tgt_hash, tgt_ranges);
+
+ // <src_block_count>
+ const std::string& token = tokens[pos++];
+ size_t src_blocks;
+ if (!android::base::ParseUint(token, &src_blocks)) {
+ *err = "invalid src_block_count \""s + token + "\"";
+ return false;
+ }
+
+ RangeSet src_ranges;
+ RangeSet src_ranges_location;
+ // "-" or <src_ranges> [<src_ranges_location>]
+ if (tokens[pos] == "-") {
+ // no source ranges, only stashes
+ pos++;
+ } else {
+ src_ranges = RangeSet::Parse(tokens[pos++]);
+ if (!src_ranges) {
+ *err = "invalid source ranges";
+ return false;
+ }
+
+ if (pos >= tokens.size()) {
+ // No stashes, only source ranges.
+ SourceInfo result(src_hash, src_ranges, {}, {});
+
+ // Sanity check the block count.
+ if (result.blocks() != src_blocks) {
+ *err =
+ android::base::StringPrintf("mismatching block count: %zu (%s) vs %zu", result.blocks(),
+ src_ranges.ToString().c_str(), src_blocks);
+ return false;
+ }
+
+ *source = result;
+ return true;
+ }
+
+ src_ranges_location = RangeSet::Parse(tokens[pos++]);
+ if (!src_ranges_location) {
+ *err = "invalid source ranges location";
+ return false;
+ }
+ }
+
+ // <[stash_id:stash_location]>
+ std::vector<StashInfo> stashes;
+ while (pos < tokens.size()) {
+ // Each word is a an index into the stash table, a colon, and then a RangeSet describing where
+ // in the source block that stashed data should go.
+ std::vector<std::string> pairs = android::base::Split(tokens[pos++], ":");
+ if (pairs.size() != 2) {
+ *err = "invalid stash info";
+ return false;
+ }
+ RangeSet stash_location = RangeSet::Parse(pairs[1]);
+ if (!stash_location) {
+ *err = "invalid stash location";
+ return false;
+ }
+ stashes.emplace_back(pairs[0], stash_location);
+ }
+
+ SourceInfo result(src_hash, src_ranges, src_ranges_location, stashes);
+ if (src_blocks != result.blocks()) {
+ *err = android::base::StringPrintf("mismatching block count: %zu (%s) vs %zu", result.blocks(),
+ src_ranges.ToString().c_str(), src_blocks);
+ return false;
+ }
+
+ *source = result;
+ return true;
+}
+
+Command Command::Parse(const std::string& line, size_t index, std::string* err) {
+ std::vector<std::string> tokens = android::base::Split(line, " ");
+ size_t pos = 0;
+ // tokens.size() will be 1 at least.
+ Type op = ParseType(tokens[pos++]);
+ if (op == Type::LAST) {
+ *err = "invalid type";
+ return {};
+ }
+
+ PatchInfo patch_info;
+ TargetInfo target_info;
+ SourceInfo source_info;
+ StashInfo stash_info;
+
+ if (op == Type::ZERO || op == Type::NEW || op == Type::ERASE) {
+ // zero/new/erase <rangeset>
+ if (pos + 1 != tokens.size()) {
+ *err = android::base::StringPrintf("invalid number of args: %zu (expected 1)",
+ tokens.size() - pos);
+ return {};
+ }
+ RangeSet tgt_ranges = RangeSet::Parse(tokens[pos++]);
+ if (!tgt_ranges) {
+ return {};
+ }
+ static const std::string kUnknownHash{ "unknown-hash" };
+ target_info = TargetInfo(kUnknownHash, tgt_ranges);
+ } else if (op == Type::STASH) {
+ // stash <stash_id> <src_ranges>
+ if (pos + 2 != tokens.size()) {
+ *err = android::base::StringPrintf("invalid number of args: %zu (expected 2)",
+ tokens.size() - pos);
+ return {};
+ }
+ const std::string& id = tokens[pos++];
+ RangeSet src_ranges = RangeSet::Parse(tokens[pos++]);
+ if (!src_ranges) {
+ *err = "invalid token";
+ return {};
+ }
+ stash_info = StashInfo(id, src_ranges);
+ } else if (op == Type::FREE) {
+ // free <stash_id>
+ if (pos + 1 != tokens.size()) {
+ *err = android::base::StringPrintf("invalid number of args: %zu (expected 1)",
+ tokens.size() - pos);
+ return {};
+ }
+ stash_info = StashInfo(tokens[pos++], {});
+ } else if (op == Type::MOVE) {
+ // <hash>
+ if (pos + 1 > tokens.size()) {
+ *err = "missing hash";
+ return {};
+ }
+ std::string hash = tokens[pos++];
+ if (!ParseTargetInfoAndSourceInfo(
+ std::vector<std::string>(tokens.cbegin() + pos, tokens.cend()), hash, &target_info,
+ hash, &source_info, err)) {
+ return {};
+ }
+ } else if (op == Type::BSDIFF || op == Type::IMGDIFF) {
+ // <offset> <length> <srchash> <dsthash>
+ if (pos + 4 > tokens.size()) {
+ *err = android::base::StringPrintf("invalid number of args: %zu (expected 4+)",
+ tokens.size() - pos);
+ return {};
+ }
+ size_t offset;
+ size_t length;
+ if (!android::base::ParseUint(tokens[pos++], &offset) ||
+ !android::base::ParseUint(tokens[pos++], &length)) {
+ *err = "invalid patch offset/length";
+ return {};
+ }
+ patch_info = PatchInfo(offset, length);
+
+ std::string src_hash = tokens[pos++];
+ std::string dst_hash = tokens[pos++];
+ if (!ParseTargetInfoAndSourceInfo(
+ std::vector<std::string>(tokens.cbegin() + pos, tokens.cend()), dst_hash, &target_info,
+ src_hash, &source_info, err)) {
+ return {};
+ }
+ } else if (op == Type::ABORT) {
+ // No-op, other than sanity checking the input args.
+ if (pos != tokens.size()) {
+ *err = android::base::StringPrintf("invalid number of args: %zu (expected 0)",
+ tokens.size() - pos);
+ return {};
+ }
+ } else if (op == Type::COMPUTE_HASH_TREE) {
+ // <hash_tree_ranges> <source_ranges> <hash_algorithm> <salt_hex> <root_hash>
+ if (pos + 5 != tokens.size()) {
+ *err = android::base::StringPrintf("invalid number of args: %zu (expected 5)",
+ tokens.size() - pos);
+ return {};
+ }
+
+ // Expects the hash_tree data to be contiguous.
+ RangeSet hash_tree_ranges = RangeSet::Parse(tokens[pos++]);
+ if (!hash_tree_ranges || hash_tree_ranges.size() != 1) {
+ *err = "invalid hash tree ranges in: " + line;
+ return {};
+ }
+
+ RangeSet source_ranges = RangeSet::Parse(tokens[pos++]);
+ if (!source_ranges) {
+ *err = "invalid source ranges in: " + line;
+ return {};
+ }
+
+ std::string hash_algorithm = tokens[pos++];
+ std::string salt_hex = tokens[pos++];
+ std::string root_hash = tokens[pos++];
+ if (hash_algorithm.empty() || salt_hex.empty() || root_hash.empty()) {
+ *err = "invalid hash tree arguments in " + line;
+ return {};
+ }
+
+ HashTreeInfo hash_tree_info(std::move(hash_tree_ranges), std::move(source_ranges),
+ std::move(hash_algorithm), std::move(salt_hex),
+ std::move(root_hash));
+ return Command(op, index, line, std::move(hash_tree_info));
+ } else {
+ *err = "invalid op";
+ return {};
+ }
+
+ return Command(op, index, line, patch_info, target_info, source_info, stash_info);
+}
+
+bool SourceInfo::Overlaps(const TargetInfo& target) const {
+ return ranges_.Overlaps(target.ranges());
+}
+
+// Moves blocks in the 'source' vector to the specified locations (as in 'locs') in the 'dest'
+// vector. Note that source and dest may be the same buffer.
+static void MoveRange(std::vector<uint8_t>* dest, const RangeSet& locs,
+ const std::vector<uint8_t>& source, size_t block_size) {
+ const uint8_t* from = source.data();
+ uint8_t* to = dest->data();
+ size_t start = locs.blocks();
+ // Must do the movement backward.
+ for (auto it = locs.crbegin(); it != locs.crend(); it++) {
+ size_t blocks = it->second - it->first;
+ start -= blocks;
+ memmove(to + (it->first * block_size), from + (start * block_size), blocks * block_size);
+ }
+}
+
+bool SourceInfo::ReadAll(
+ std::vector<uint8_t>* buffer, size_t block_size,
+ const std::function<int(const RangeSet&, std::vector<uint8_t>*)>& block_reader,
+ const std::function<int(const std::string&, std::vector<uint8_t>*)>& stash_reader) const {
+ if (buffer->size() < blocks() * block_size) {
+ return false;
+ }
+
+ // Read in the source ranges.
+ if (ranges_) {
+ if (block_reader(ranges_, buffer) != 0) {
+ return false;
+ }
+ if (location_) {
+ MoveRange(buffer, location_, *buffer, block_size);
+ }
+ }
+
+ // Read in the stashes.
+ for (const StashInfo& stash : stashes_) {
+ std::vector<uint8_t> stash_buffer(stash.blocks() * block_size);
+ if (stash_reader(stash.id(), &stash_buffer) != 0) {
+ return false;
+ }
+ MoveRange(buffer, stash.ranges(), stash_buffer, block_size);
+ }
+ return true;
+}
+
+void SourceInfo::DumpBuffer(const std::vector<uint8_t>& buffer, size_t block_size) const {
+ LOG(INFO) << "Dumping hashes in hex for " << ranges_.blocks() << " source blocks";
+
+ const RangeSet& location = location_ ? location_ : RangeSet({ Range{ 0, ranges_.blocks() } });
+ for (size_t i = 0; i < ranges_.blocks(); i++) {
+ size_t block_num = ranges_.GetBlockNumber(i);
+ size_t buffer_index = location.GetBlockNumber(i);
+ CHECK_LE((buffer_index + 1) * block_size, buffer.size());
+
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1(buffer.data() + buffer_index * block_size, block_size, digest);
+ std::string hexdigest = print_sha1(digest);
+ LOG(INFO) << " block number: " << block_num << ", SHA-1: " << hexdigest;
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, const Command& command) {
+ os << command.index() << ": " << command.cmdline();
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const TargetInfo& target) {
+ os << target.blocks() << " blocks (" << target.hash_ << "): " << target.ranges_.ToString();
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const StashInfo& stash) {
+ os << stash.blocks() << " blocks (" << stash.id_ << "): " << stash.ranges_.ToString();
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const SourceInfo& source) {
+ os << source.blocks_ << " blocks (" << source.hash_ << "): ";
+ if (source.ranges_) {
+ os << source.ranges_.ToString();
+ if (source.location_) {
+ os << " (location: " << source.location_.ToString() << ")";
+ }
+ }
+ if (!source.stashes_.empty()) {
+ os << " " << source.stashes_.size() << " stash(es)";
+ }
+ return os;
+}
+
+TransferList TransferList::Parse(const std::string& transfer_list_str, std::string* err) {
+ TransferList result{};
+
+ std::vector<std::string> lines = android::base::Split(transfer_list_str, "\n");
+ if (lines.size() < kTransferListHeaderLines) {
+ *err = android::base::StringPrintf("too few lines in the transfer list [%zu]", lines.size());
+ return TransferList{};
+ }
+
+ // First line in transfer list is the version number.
+ if (!android::base::ParseInt(lines[0], &result.version_, 3, 4)) {
+ *err = "unexpected transfer list version ["s + lines[0] + "]";
+ return TransferList{};
+ }
+
+ // Second line in transfer list is the total number of blocks we expect to write.
+ if (!android::base::ParseUint(lines[1], &result.total_blocks_)) {
+ *err = "unexpected block count ["s + lines[1] + "]";
+ return TransferList{};
+ }
+
+ // Third line is how many stash entries are needed simultaneously.
+ if (!android::base::ParseUint(lines[2], &result.stash_max_entries_)) {
+ return TransferList{};
+ }
+
+ // Fourth line is the maximum number of blocks that will be stashed simultaneously.
+ if (!android::base::ParseUint(lines[3], &result.stash_max_blocks_)) {
+ *err = "unexpected maximum stash blocks ["s + lines[3] + "]";
+ return TransferList{};
+ }
+
+ // Subsequent lines are all individual transfer commands.
+ for (size_t i = kTransferListHeaderLines; i < lines.size(); i++) {
+ const std::string& line = lines[i];
+ if (line.empty()) continue;
+
+ size_t cmdindex = i - kTransferListHeaderLines;
+ std::string parsing_error;
+ Command command = Command::Parse(line, cmdindex, &parsing_error);
+ if (!command) {
+ *err = android::base::StringPrintf("Failed to parse command %zu [%s]: %s", cmdindex,
+ line.c_str(), parsing_error.c_str());
+ return TransferList{};
+ }
+ result.commands_.push_back(command);
+ }
+
+ return result;
+}
diff --git a/updater/dynamic_partitions.cpp b/updater/dynamic_partitions.cpp
new file mode 100644
index 000000000..b50dd75f9
--- /dev/null
+++ b/updater/dynamic_partitions.cpp
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "updater/dynamic_partitions.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <chrono>
+#include <iterator>
+#include <memory>
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <fs_mgr.h>
+#include <fs_mgr_dm_linear.h>
+#include <libdm/dm.h>
+#include <liblp/builder.h>
+
+#include "edify/expr.h"
+#include "otautil/error_code.h"
+#include "otautil/paths.h"
+#include "private/utils.h"
+
+using android::base::ParseUint;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using android::fs_mgr::CreateLogicalPartition;
+using android::fs_mgr::DestroyLogicalPartition;
+using android::fs_mgr::LpMetadata;
+using android::fs_mgr::MetadataBuilder;
+using android::fs_mgr::Partition;
+using android::fs_mgr::PartitionOpener;
+
+static constexpr std::chrono::milliseconds kMapTimeout{ 1000 };
+static constexpr char kMetadataUpdatedMarker[] = "/dynamic_partition_metadata.UPDATED";
+
+static std::string GetSuperDevice() {
+ return "/dev/block/by-name/" + fs_mgr_get_super_partition_name();
+}
+
+static std::vector<std::string> ReadStringArgs(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv,
+ const std::vector<std::string>& arg_names) {
+ if (argv.size() != arg_names.size()) {
+ ErrorAbort(state, kArgsParsingFailure, "%s expects %zu arguments, got %zu", name,
+ arg_names.size(), argv.size());
+ return {};
+ }
+
+ std::vector<std::unique_ptr<Value>> args;
+ if (!ReadValueArgs(state, argv, &args)) {
+ return {};
+ }
+
+ CHECK_EQ(args.size(), arg_names.size());
+
+ for (size_t i = 0; i < arg_names.size(); ++i) {
+ if (args[i]->type != Value::Type::STRING) {
+ ErrorAbort(state, kArgsParsingFailure, "%s argument to %s must be string",
+ arg_names[i].c_str(), name);
+ return {};
+ }
+ }
+
+ std::vector<std::string> ret;
+ std::transform(args.begin(), args.end(), std::back_inserter(ret),
+ [](const auto& arg) { return arg->data; });
+ return ret;
+}
+
+static bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
+ auto state = DeviceMapper::Instance().GetState(partition_name);
+ if (state == DmDeviceState::INVALID) {
+ return true;
+ }
+ if (state == DmDeviceState::ACTIVE) {
+ return DestroyLogicalPartition(partition_name, kMapTimeout);
+ }
+ LOG(ERROR) << "Unknown device mapper state: "
+ << static_cast<std::underlying_type_t<DmDeviceState>>(state);
+ return false;
+}
+
+static bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) {
+ auto state = DeviceMapper::Instance().GetState(partition_name);
+ if (state == DmDeviceState::INVALID) {
+ return CreateLogicalPartition(GetSuperDevice(), 0 /* metadata slot */, partition_name,
+ true /* force writable */, kMapTimeout, path);
+ }
+
+ if (state == DmDeviceState::ACTIVE) {
+ return DeviceMapper::Instance().GetDmDevicePathByName(partition_name, path);
+ }
+ LOG(ERROR) << "Unknown device mapper state: "
+ << static_cast<std::underlying_type_t<DmDeviceState>>(state);
+ return false;
+}
+
+Value* UnmapPartitionFn(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
+ auto args = ReadStringArgs(name, state, argv, { "name" });
+ if (args.empty()) return StringValue("");
+
+ return UnmapPartitionOnDeviceMapper(args[0]) ? StringValue("t") : StringValue("");
+}
+
+Value* MapPartitionFn(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
+ auto args = ReadStringArgs(name, state, argv, { "name" });
+ if (args.empty()) return StringValue("");
+
+ std::string path;
+ bool result = MapPartitionOnDeviceMapper(args[0], &path);
+ return result ? StringValue(path) : StringValue("");
+}
+
+namespace { // Ops
+
+struct OpParameters {
+ std::vector<std::string> tokens;
+ MetadataBuilder* builder;
+
+ bool ExpectArgSize(size_t size) const {
+ CHECK(!tokens.empty());
+ auto actual = tokens.size() - 1;
+ if (actual != size) {
+ LOG(ERROR) << "Op " << op() << " expects " << size << " args, got " << actual;
+ return false;
+ }
+ return true;
+ }
+ const std::string& op() const {
+ CHECK(!tokens.empty());
+ return tokens[0];
+ }
+ const std::string& arg(size_t pos) const {
+ CHECK_LE(pos + 1, tokens.size());
+ return tokens[pos + 1];
+ }
+ std::optional<uint64_t> uint_arg(size_t pos, const std::string& name) const {
+ auto str = arg(pos);
+ uint64_t ret;
+ if (!ParseUint(str, &ret)) {
+ LOG(ERROR) << "Op " << op() << " expects uint64 for argument " << name << ", got " << str;
+ return std::nullopt;
+ }
+ return ret;
+ }
+};
+
+using OpFunction = std::function<bool(const OpParameters&)>;
+using OpMap = std::map<std::string, OpFunction>;
+
+bool PerformOpResize(const OpParameters& params) {
+ if (!params.ExpectArgSize(2)) return false;
+ const auto& partition_name = params.arg(0);
+ auto size = params.uint_arg(1, "size");
+ if (!size.has_value()) return false;
+
+ auto partition = params.builder->FindPartition(partition_name);
+ if (partition == nullptr) {
+ LOG(ERROR) << "Failed to find partition " << partition_name
+ << " in dynamic partition metadata.";
+ return false;
+ }
+ if (!UnmapPartitionOnDeviceMapper(partition_name)) {
+ LOG(ERROR) << "Cannot unmap " << partition_name << " before resizing.";
+ return false;
+ }
+ if (!params.builder->ResizePartition(partition, size.value())) {
+ LOG(ERROR) << "Failed to resize partition " << partition_name << " to size " << *size << ".";
+ return false;
+ }
+ return true;
+}
+
+bool PerformOpRemove(const OpParameters& params) {
+ if (!params.ExpectArgSize(1)) return false;
+ const auto& partition_name = params.arg(0);
+
+ if (!UnmapPartitionOnDeviceMapper(partition_name)) {
+ LOG(ERROR) << "Cannot unmap " << partition_name << " before removing.";
+ return false;
+ }
+ params.builder->RemovePartition(partition_name);
+ return true;
+}
+
+bool PerformOpAdd(const OpParameters& params) {
+ if (!params.ExpectArgSize(2)) return false;
+ const auto& partition_name = params.arg(0);
+ const auto& group_name = params.arg(1);
+
+ if (params.builder->AddPartition(partition_name, group_name, LP_PARTITION_ATTR_READONLY) ==
+ nullptr) {
+ LOG(ERROR) << "Failed to add partition " << partition_name << " to group " << group_name << ".";
+ return false;
+ }
+ return true;
+}
+
+bool PerformOpMove(const OpParameters& params) {
+ if (!params.ExpectArgSize(2)) return false;
+ const auto& partition_name = params.arg(0);
+ const auto& new_group = params.arg(1);
+
+ auto partition = params.builder->FindPartition(partition_name);
+ if (partition == nullptr) {
+ LOG(ERROR) << "Cannot move partition " << partition_name << " to group " << new_group
+ << " because it is not found.";
+ return false;
+ }
+
+ auto old_group = partition->group_name();
+ if (old_group != new_group) {
+ if (!params.builder->ChangePartitionGroup(partition, new_group)) {
+ LOG(ERROR) << "Cannot move partition " << partition_name << " from group " << old_group
+ << " to group " << new_group << ".";
+ return false;
+ }
+ }
+ return true;
+}
+
+bool PerformOpAddGroup(const OpParameters& params) {
+ if (!params.ExpectArgSize(2)) return false;
+ const auto& group_name = params.arg(0);
+ auto maximum_size = params.uint_arg(1, "maximum_size");
+ if (!maximum_size.has_value()) return false;
+
+ auto group = params.builder->FindGroup(group_name);
+ if (group != nullptr) {
+ LOG(ERROR) << "Cannot add group " << group_name << " because it already exists.";
+ return false;
+ }
+
+ if (maximum_size.value() == 0) {
+ LOG(WARNING) << "Adding group " << group_name << " with no size limits.";
+ }
+
+ if (!params.builder->AddGroup(group_name, maximum_size.value())) {
+ LOG(ERROR) << "Failed to add group " << group_name << " with maximum size "
+ << maximum_size.value() << ".";
+ return false;
+ }
+ return true;
+}
+
+bool PerformOpResizeGroup(const OpParameters& params) {
+ if (!params.ExpectArgSize(2)) return false;
+ const auto& group_name = params.arg(0);
+ auto new_size = params.uint_arg(1, "maximum_size");
+ if (!new_size.has_value()) return false;
+
+ auto group = params.builder->FindGroup(group_name);
+ if (group == nullptr) {
+ LOG(ERROR) << "Cannot resize group " << group_name << " because it is not found.";
+ return false;
+ }
+
+ auto old_size = group->maximum_size();
+ if (old_size != new_size.value()) {
+ if (!params.builder->ChangeGroupSize(group_name, new_size.value())) {
+ LOG(ERROR) << "Cannot resize group " << group_name << " from " << old_size << " to "
+ << new_size.value() << ".";
+ return false;
+ }
+ }
+ return true;
+}
+
+std::vector<std::string> ListPartitionNamesInGroup(MetadataBuilder* builder,
+ const std::string& group_name) {
+ auto partitions = builder->ListPartitionsInGroup(group_name);
+ std::vector<std::string> partition_names;
+ std::transform(partitions.begin(), partitions.end(), std::back_inserter(partition_names),
+ [](Partition* partition) { return partition->name(); });
+ return partition_names;
+}
+
+bool PerformOpRemoveGroup(const OpParameters& params) {
+ if (!params.ExpectArgSize(1)) return false;
+ const auto& group_name = params.arg(0);
+
+ auto partition_names = ListPartitionNamesInGroup(params.builder, group_name);
+ if (!partition_names.empty()) {
+ LOG(ERROR) << "Cannot remove group " << group_name << " because it still contains partitions ["
+ << android::base::Join(partition_names, ", ") << "]";
+ return false;
+ }
+ params.builder->RemoveGroupAndPartitions(group_name);
+ return true;
+}
+
+bool PerformOpRemoveAllGroups(const OpParameters& params) {
+ if (!params.ExpectArgSize(0)) return false;
+
+ auto group_names = params.builder->ListGroups();
+ for (const auto& group_name : group_names) {
+ auto partition_names = ListPartitionNamesInGroup(params.builder, group_name);
+ for (const auto& partition_name : partition_names) {
+ if (!UnmapPartitionOnDeviceMapper(partition_name)) {
+ LOG(ERROR) << "Cannot unmap " << partition_name << " before removing group " << group_name
+ << ".";
+ return false;
+ }
+ }
+ params.builder->RemoveGroupAndPartitions(group_name);
+ }
+ return true;
+}
+
+} // namespace
+
+Value* UpdateDynamicPartitionsFn(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
+ if (argv.size() != 1) {
+ ErrorAbort(state, kArgsParsingFailure, "%s expects 1 arguments, got %zu", name, argv.size());
+ return StringValue("");
+ }
+ std::vector<std::unique_ptr<Value>> args;
+ if (!ReadValueArgs(state, argv, &args)) {
+ return nullptr;
+ }
+ const std::unique_ptr<Value>& op_list_value = args[0];
+ if (op_list_value->type != Value::Type::BLOB) {
+ ErrorAbort(state, kArgsParsingFailure, "op_list argument to %s must be blob", name);
+ return StringValue("");
+ }
+
+ std::string updated_marker = Paths::Get().stash_directory_base() + kMetadataUpdatedMarker;
+ if (state->is_retry) {
+ struct stat sb;
+ int result = stat(updated_marker.c_str(), &sb);
+ if (result == 0) {
+ LOG(INFO) << "Skipping already updated dynamic partition metadata based on marker";
+ return StringValue("t");
+ }
+ } else {
+ // Delete the obsolete marker if any.
+ std::string err;
+ if (!android::base::RemoveFileIfExists(updated_marker, &err)) {
+ LOG(ERROR) << "Failed to remove dynamic partition metadata updated marker " << updated_marker
+ << ": " << err;
+ return StringValue("");
+ }
+ }
+
+ auto super_device = GetSuperDevice();
+ auto builder = MetadataBuilder::New(PartitionOpener(), super_device, 0);
+ if (builder == nullptr) {
+ LOG(ERROR) << "Failed to load dynamic partition metadata.";
+ return StringValue("");
+ }
+
+ static const OpMap op_map{
+ // clang-format off
+ {"resize", PerformOpResize},
+ {"remove", PerformOpRemove},
+ {"add", PerformOpAdd},
+ {"move", PerformOpMove},
+ {"add_group", PerformOpAddGroup},
+ {"resize_group", PerformOpResizeGroup},
+ {"remove_group", PerformOpRemoveGroup},
+ {"remove_all_groups", PerformOpRemoveAllGroups},
+ // clang-format on
+ };
+
+ std::vector<std::string> lines = android::base::Split(op_list_value->data, "\n");
+ for (const auto& line : lines) {
+ auto comment_idx = line.find('#');
+ auto op_and_args = comment_idx == std::string::npos ? line : line.substr(0, comment_idx);
+ op_and_args = android::base::Trim(op_and_args);
+ if (op_and_args.empty()) continue;
+
+ auto tokens = android::base::Split(op_and_args, " ");
+ const auto& op = tokens[0];
+ auto it = op_map.find(op);
+ if (it == op_map.end()) {
+ LOG(ERROR) << "Unknown operation in op_list: " << op;
+ return StringValue("");
+ }
+ OpParameters params;
+ params.tokens = tokens;
+ params.builder = builder.get();
+ if (!it->second(params)) {
+ return StringValue("");
+ }
+ }
+
+ auto metadata = builder->Export();
+ if (metadata == nullptr) {
+ LOG(ERROR) << "Failed to export metadata.";
+ return StringValue("");
+ }
+
+ if (!UpdatePartitionTable(super_device, *metadata, 0)) {
+ LOG(ERROR) << "Failed to write metadata.";
+ return StringValue("");
+ }
+
+ if (!SetUpdatedMarker(updated_marker)) {
+ LOG(ERROR) << "Failed to set metadata updated marker.";
+ return StringValue("");
+ }
+
+ return StringValue("t");
+}
+
+void RegisterDynamicPartitionsFunctions() {
+ RegisterFunction("unmap_partition", UnmapPartitionFn);
+ RegisterFunction("map_partition", MapPartitionFn);
+ RegisterFunction("update_dynamic_partitions", UpdateDynamicPartitionsFn);
+}
diff --git a/updater/include/private/commands.h b/updater/include/private/commands.h
new file mode 100644
index 000000000..79f915434
--- /dev/null
+++ b/updater/include/private/commands.h
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <functional>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest_prod.h> // FRIEND_TEST
+
+#include "otautil/rangeset.h"
+
+// Represents the target info used in a Command. TargetInfo contains the ranges of the blocks and
+// the expected hash.
+class TargetInfo {
+ public:
+ TargetInfo() = default;
+
+ TargetInfo(std::string hash, RangeSet ranges)
+ : hash_(std::move(hash)), ranges_(std::move(ranges)) {}
+
+ const std::string& hash() const {
+ return hash_;
+ }
+
+ const RangeSet& ranges() const {
+ return ranges_;
+ }
+
+ size_t blocks() const {
+ return ranges_.blocks();
+ }
+
+ bool operator==(const TargetInfo& other) const {
+ return hash_ == other.hash_ && ranges_ == other.ranges_;
+ }
+
+ private:
+ friend std::ostream& operator<<(std::ostream& os, const TargetInfo& source);
+
+ // The hash of the data represented by the object.
+ std::string hash_;
+ // The block ranges that the data should be written to.
+ RangeSet ranges_;
+};
+
+std::ostream& operator<<(std::ostream& os, const TargetInfo& source);
+
+// Represents the stash info used in a Command.
+class StashInfo {
+ public:
+ StashInfo() = default;
+
+ StashInfo(std::string id, RangeSet ranges) : id_(std::move(id)), ranges_(std::move(ranges)) {}
+
+ size_t blocks() const {
+ return ranges_.blocks();
+ }
+
+ const std::string& id() const {
+ return id_;
+ }
+
+ const RangeSet& ranges() const {
+ return ranges_;
+ }
+
+ bool operator==(const StashInfo& other) const {
+ return id_ == other.id_ && ranges_ == other.ranges_;
+ }
+
+ private:
+ friend std::ostream& operator<<(std::ostream& os, const StashInfo& stash);
+
+ // The id (i.e. hash) of the stash.
+ std::string id_;
+ // The matching location of the stash.
+ RangeSet ranges_;
+};
+
+std::ostream& operator<<(std::ostream& os, const StashInfo& stash);
+
+// Represents the source info in a Command, whose data could come from source image, stashed blocks,
+// or both.
+class SourceInfo {
+ public:
+ SourceInfo() = default;
+
+ SourceInfo(std::string hash, RangeSet ranges, RangeSet location, std::vector<StashInfo> stashes)
+ : hash_(std::move(hash)),
+ ranges_(std::move(ranges)),
+ location_(std::move(location)),
+ stashes_(std::move(stashes)) {
+ blocks_ = ranges_.blocks();
+ for (const auto& stash : stashes_) {
+ blocks_ += stash.ranges().blocks();
+ }
+ }
+
+ // Reads all the data specified by this SourceInfo object into the given 'buffer', by calling the
+ // given readers. Caller needs to specify the block size for the represented blocks. The given
+ // buffer needs to be sufficiently large. Otherwise it returns false. 'block_reader' and
+ // 'stash_reader' read the specified data into the given buffer (guaranteed to be large enough)
+ // respectively. The readers should return 0 on success, or -1 on error.
+ bool ReadAll(
+ std::vector<uint8_t>* buffer, size_t block_size,
+ const std::function<int(const RangeSet&, std::vector<uint8_t>*)>& block_reader,
+ const std::function<int(const std::string&, std::vector<uint8_t>*)>& stash_reader) const;
+
+ // Whether this SourceInfo overlaps with the given TargetInfo object.
+ bool Overlaps(const TargetInfo& target) const;
+
+ // Dumps the hashes in hex for the given buffer that's loaded from this SourceInfo object
+ // (excluding the stashed blocks which are handled separately).
+ void DumpBuffer(const std::vector<uint8_t>& buffer, size_t block_size) const;
+
+ const std::string& hash() const {
+ return hash_;
+ }
+
+ size_t blocks() const {
+ return blocks_;
+ }
+
+ bool operator==(const SourceInfo& other) const {
+ return hash_ == other.hash_ && ranges_ == other.ranges_ && location_ == other.location_ &&
+ stashes_ == other.stashes_;
+ }
+
+ private:
+ friend std::ostream& operator<<(std::ostream& os, const SourceInfo& source);
+
+ // The hash of the data represented by the object.
+ std::string hash_;
+ // The block ranges from the source image to read data from. This could be a subset of all the
+ // blocks represented by the object, or empty if all the data should be loaded from stash.
+ RangeSet ranges_;
+ // The location in the buffer to load ranges_ into. Empty if ranges_ alone covers all the blocks
+ // (i.e. nothing needs to be loaded from stash).
+ RangeSet location_;
+ // The info for the stashed blocks that are part of the source. Empty if there's none.
+ std::vector<StashInfo> stashes_;
+ // Total number of blocks represented by the object.
+ size_t blocks_{ 0 };
+};
+
+std::ostream& operator<<(std::ostream& os, const SourceInfo& source);
+
+class PatchInfo {
+ public:
+ PatchInfo() = default;
+
+ PatchInfo(size_t offset, size_t length) : offset_(offset), length_(length) {}
+
+ size_t offset() const {
+ return offset_;
+ }
+
+ size_t length() const {
+ return length_;
+ }
+
+ bool operator==(const PatchInfo& other) const {
+ return offset_ == other.offset_ && length_ == other.length_;
+ }
+
+ private:
+ size_t offset_{ 0 };
+ size_t length_{ 0 };
+};
+
+// The arguments to build a hash tree from blocks on the block device.
+class HashTreeInfo {
+ public:
+ HashTreeInfo() = default;
+
+ HashTreeInfo(RangeSet hash_tree_ranges, RangeSet source_ranges, std::string hash_algorithm,
+ std::string salt_hex, std::string root_hash)
+ : hash_tree_ranges_(std::move(hash_tree_ranges)),
+ source_ranges_(std::move(source_ranges)),
+ hash_algorithm_(std::move(hash_algorithm)),
+ salt_hex_(std::move(salt_hex)),
+ root_hash_(std::move(root_hash)) {}
+
+ const RangeSet& hash_tree_ranges() const {
+ return hash_tree_ranges_;
+ }
+ const RangeSet& source_ranges() const {
+ return source_ranges_;
+ }
+
+ const std::string& hash_algorithm() const {
+ return hash_algorithm_;
+ }
+ const std::string& salt_hex() const {
+ return salt_hex_;
+ }
+ const std::string& root_hash() const {
+ return root_hash_;
+ }
+
+ bool operator==(const HashTreeInfo& other) const {
+ return hash_tree_ranges_ == other.hash_tree_ranges_ && source_ranges_ == other.source_ranges_ &&
+ hash_algorithm_ == other.hash_algorithm_ && salt_hex_ == other.salt_hex_ &&
+ root_hash_ == other.root_hash_;
+ }
+
+ private:
+ RangeSet hash_tree_ranges_;
+ RangeSet source_ranges_;
+ std::string hash_algorithm_;
+ std::string salt_hex_;
+ std::string root_hash_;
+};
+
+// Command class holds the info for an update command that performs block-based OTA (BBOTA). Each
+// command consists of one or several args, namely TargetInfo, SourceInfo, StashInfo and PatchInfo.
+// The currently used BBOTA version is v4.
+//
+// zero <tgt_ranges>
+// - Fill the indicated blocks with zeros.
+// - Meaningful args: TargetInfo
+//
+// new <tgt_ranges>
+// - Fill the blocks with data read from the new_data file.
+// - Meaningful args: TargetInfo
+//
+// erase <tgt_ranges>
+// - Mark the given blocks as empty.
+// - Meaningful args: TargetInfo
+//
+// move <hash> <...>
+// - Read the source blocks, write result to target blocks.
+// - Meaningful args: TargetInfo, SourceInfo
+//
+// See the note below for <...>.
+//
+// bsdiff <patchstart> <patchlen> <srchash> <dsthash> <...>
+// imgdiff <patchstart> <patchlen> <srchash> <dsthash> <...>
+// - Read the source blocks, apply a patch, and write result to target blocks.
+// - Meaningful args: PatchInfo, TargetInfo, SourceInfo
+//
+// It expects <...> in one of the following formats:
+//
+// <tgt_ranges> <src_block_count> - <[stash_id:stash_location] ...>
+// (loads data from stashes only)
+//
+// <tgt_ranges> <src_block_count> <src_ranges>
+// (loads data from source image only)
+//
+// <tgt_ranges> <src_block_count> <src_ranges> <src_ranges_location>
+// <[stash_id:stash_location] ...>
+// (loads data from both of source image and stashes)
+//
+// stash <stash_id> <src_ranges>
+// - Load the given source blocks and stash the data in the given slot of the stash table.
+// - Meaningful args: StashInfo
+//
+// free <stash_id>
+// - Free the given stash data.
+// - Meaningful args: StashInfo
+//
+// compute_hash_tree <hash_tree_ranges> <source_ranges> <hash_algorithm> <salt_hex> <root_hash>
+// - Computes the hash_tree bytes and writes the result to the specified range on the
+// block_device.
+//
+// abort
+// - Abort the current update. Allowed for testing code only.
+//
+class Command {
+ public:
+ enum class Type {
+ ABORT,
+ BSDIFF,
+ COMPUTE_HASH_TREE,
+ ERASE,
+ FREE,
+ IMGDIFF,
+ MOVE,
+ NEW,
+ STASH,
+ ZERO,
+ LAST, // Not a valid type.
+ };
+
+ Command() = default;
+
+ Command(Type type, size_t index, std::string cmdline, PatchInfo patch, TargetInfo target,
+ SourceInfo source, StashInfo stash)
+ : type_(type),
+ index_(index),
+ cmdline_(std::move(cmdline)),
+ patch_(std::move(patch)),
+ target_(std::move(target)),
+ source_(std::move(source)),
+ stash_(std::move(stash)) {}
+
+ Command(Type type, size_t index, std::string cmdline, HashTreeInfo hash_tree_info);
+
+ // Parses the given command 'line' into a Command object and returns it. The 'index' is specified
+ // by the caller to index the object. On parsing error, it returns an empty Command object that
+ // evaluates to false, and the specific error message will be set in 'err'.
+ static Command Parse(const std::string& line, size_t index, std::string* err);
+
+ // Parses the command type from the given string.
+ static Type ParseType(const std::string& type_str);
+
+ Type type() const {
+ return type_;
+ }
+
+ size_t index() const {
+ return index_;
+ }
+
+ const std::string& cmdline() const {
+ return cmdline_;
+ }
+
+ const PatchInfo& patch() const {
+ return patch_;
+ }
+
+ const TargetInfo& target() const {
+ return target_;
+ }
+
+ const SourceInfo& source() const {
+ return source_;
+ }
+
+ const StashInfo& stash() const {
+ return stash_;
+ }
+
+ const HashTreeInfo& hash_tree_info() const {
+ return hash_tree_info_;
+ }
+
+ size_t block_size() const {
+ return block_size_;
+ }
+
+ constexpr explicit operator bool() const {
+ return type_ != Type::LAST;
+ }
+
+ private:
+ friend class ResumableUpdaterTest;
+ friend class UpdaterTest;
+
+ FRIEND_TEST(CommandsTest, Parse_ABORT_Allowed);
+ FRIEND_TEST(CommandsTest, Parse_InvalidNumberOfArgs);
+ FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_InvalidInput);
+ FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_StashesOnly);
+ FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksAndStashes);
+ FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksOnly);
+
+ // Parses the target and source info from the given 'tokens' vector. Saves the parsed info into
+ // 'target' and 'source' objects. Returns the parsing result. Error message will be set in 'err'
+ // on parsing error, and the contents in 'target' and 'source' will be undefined.
+ static bool ParseTargetInfoAndSourceInfo(const std::vector<std::string>& tokens,
+ const std::string& tgt_hash, TargetInfo* target,
+ const std::string& src_hash, SourceInfo* source,
+ std::string* err);
+
+ // Allows parsing ABORT command, which should be used for testing purpose only.
+ static bool abort_allowed_;
+
+ // The type of the command.
+ Type type_{ Type::LAST };
+ // The index of the Command object, which is specified by the caller.
+ size_t index_{ 0 };
+ // The input string that the Command object is parsed from.
+ std::string cmdline_;
+ // The patch info. Only meaningful for BSDIFF and IMGDIFF commands.
+ PatchInfo patch_;
+ // The target info, where the command should be written to.
+ TargetInfo target_;
+ // The source info to load the source blocks for the command.
+ SourceInfo source_;
+ // The stash info. Only meaningful for STASH and FREE commands. Note that although SourceInfo may
+ // also load data from stash, such info will be owned and managed by SourceInfo (i.e. in source_).
+ StashInfo stash_;
+ // The hash_tree info. Only meaningful for COMPUTE_HASH_TREE.
+ HashTreeInfo hash_tree_info_;
+ // The unit size of each block to be used in this command.
+ size_t block_size_{ 4096 };
+};
+
+std::ostream& operator<<(std::ostream& os, const Command& command);
+
+// TransferList represents the info for a transfer list, which is parsed from input text lines
+// containing commands to transfer data from one place to another on the target partition.
+//
+// The creator of the transfer list will guarantee that no block is read (i.e., used as the source
+// for a patch or move) after it has been written.
+//
+// The creator will guarantee that a given stash is loaded (with a stash command) before it's used
+// in a move/bsdiff/imgdiff command.
+//
+// Within one command the source and target ranges may overlap so in general we need to read the
+// entire source into memory before writing anything to the target blocks.
+//
+// All the patch data is concatenated into one patch_data file in the update package. It must be
+// stored uncompressed because we memory-map it in directly from the archive. (Since patches are
+// already compressed, we lose very little by not compressing their concatenation.)
+//
+// Commands that read data from the partition (i.e. move/bsdiff/imgdiff/stash) have one or more
+// additional hashes before the range parameters, which are used to check if the command has
+// already been completed and verify the integrity of the source data.
+class TransferList {
+ public:
+ // Number of header lines.
+ static constexpr size_t kTransferListHeaderLines = 4;
+
+ TransferList() = default;
+
+ // Parses the given input string and returns a TransferList object. Sets error message if any.
+ static TransferList Parse(const std::string& transfer_list_str, std::string* err);
+
+ int version() const {
+ return version_;
+ }
+
+ size_t total_blocks() const {
+ return total_blocks_;
+ }
+
+ size_t stash_max_entries() const {
+ return stash_max_entries_;
+ }
+
+ size_t stash_max_blocks() const {
+ return stash_max_blocks_;
+ }
+
+ const std::vector<Command>& commands() const {
+ return commands_;
+ }
+
+ // Returns whether the TransferList is valid.
+ constexpr explicit operator bool() const {
+ return version_ != 0;
+ }
+
+ private:
+ // BBOTA version.
+ int version_{ 0 };
+ // Total number of blocks to be written in this transfer.
+ size_t total_blocks_;
+ // Maximum number of stashes that exist at the same time.
+ size_t stash_max_entries_;
+ // Maximum number of blocks to be stashed.
+ size_t stash_max_blocks_;
+ // Commands in this transfer.
+ std::vector<Command> commands_;
+};
diff --git a/updater/include/private/utils.h b/updater/include/private/utils.h
new file mode 100644
index 000000000..33cf6155d
--- /dev/null
+++ b/updater/include/private/utils.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+bool SetUpdatedMarker(const std::string& marker);
diff --git a/updater/include/updater/dynamic_partitions.h b/updater/include/updater/dynamic_partitions.h
new file mode 100644
index 000000000..31cf859c6
--- /dev/null
+++ b/updater/include/updater/dynamic_partitions.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+void RegisterDynamicPartitionsFunctions();
diff --git a/updater/include/updater/install.h b/updater/include/updater/install.h
index 8d6ca4728..9fe203149 100644
--- a/updater/include/updater/install.h
+++ b/updater/include/updater/install.h
@@ -14,15 +14,6 @@
* limitations under the License.
*/
-#ifndef _UPDATER_INSTALL_H_
-#define _UPDATER_INSTALL_H_
-
-struct State;
+#pragma once
void RegisterInstallFunctions();
-
-// uiPrintf function prints msg to screen as well as logs
-void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...)
- __attribute__((__format__(printf, 2, 3)));
-
-#endif
diff --git a/updater/include/updater/updater.h b/updater/include/updater/updater.h
index f4a2fe874..d5468292b 100644
--- a/updater/include/updater/updater.h
+++ b/updater/include/updater/updater.h
@@ -14,22 +14,75 @@
* limitations under the License.
*/
-#ifndef _UPDATER_UPDATER_H_
-#define _UPDATER_UPDATER_H_
+#pragma once
+#include <stdint.h>
#include <stdio.h>
-#include <ziparchive/zip_archive.h>
-typedef struct {
- FILE* cmd_pipe;
- ZipArchiveHandle package_zip;
- int version;
+#include <memory>
+#include <string>
+
+#include <ziparchive/zip_archive.h>
- uint8_t* package_zip_addr;
- size_t package_zip_len;
-} UpdaterInfo;
+#include "edify/expr.h"
+#include "otautil/error_code.h"
+#include "otautil/sysutil.h"
struct selabel_handle;
-extern struct selabel_handle *sehandle;
-#endif
+class Updater {
+ public:
+ ~Updater();
+
+ // Memory-maps the OTA package and opens it as a zip file. Also sets up the command pipe and
+ // selabel handle. TODO(xunchang) implement a run time environment class and move sehandle there.
+ bool Init(int fd, const std::string& package_filename, bool is_retry,
+ struct selabel_handle* sehandle);
+
+ // Parses and evaluates the updater-script in the OTA package. Reports the error code if the
+ // evaluation fails.
+ bool RunUpdate();
+
+ // Writes the message to command pipe, adds a new line in the end.
+ void WriteToCommandPipe(const std::string& message, bool flush = false) const;
+
+ // Sends over the message to recovery to print it on the screen.
+ void UiPrint(const std::string& message) const;
+
+ ZipArchiveHandle package_handle() const {
+ return package_handle_;
+ }
+ struct selabel_handle* sehandle() const {
+ return sehandle_;
+ }
+ std::string result() const {
+ return result_;
+ }
+
+ uint8_t* GetMappedPackageAddress() const {
+ return mapped_package_.addr;
+ }
+
+ private:
+ friend class UpdaterTestBase;
+ friend class UpdaterTest;
+ // Where in the package we expect to find the edify script to execute.
+ // (Note it's "updateR-script", not the older "update-script".)
+ static constexpr const char* SCRIPT_NAME = "META-INF/com/google/android/updater-script";
+
+ // Reads the entry |name| in the zip archive and put the result in |content|.
+ bool ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name, std::string* content);
+
+ // Parses the error code embedded in state->errmsg; and reports the error code and cause code.
+ void ParseAndReportErrorCode(State* state);
+
+ MemMapping mapped_package_;
+ ZipArchiveHandle package_handle_{ nullptr };
+ std::string updater_script_;
+
+ bool is_retry_{ false };
+ std::unique_ptr<FILE, decltype(&fclose)> cmd_pipe_{ nullptr, fclose };
+ struct selabel_handle* sehandle_{ nullptr };
+
+ std::string result_;
+};
diff --git a/updater/install.cpp b/updater/install.cpp
index 9be7645f3..b4d88403c 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -46,9 +46,9 @@
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
#include <applypatch/applypatch.h>
#include <bootloader_message/bootloader_message.h>
-#include <cutils/android_reboot.h>
#include <ext4_utils/wipe.h>
#include <openssl/sha.h>
#include <selinux/label.h>
@@ -57,43 +57,13 @@
#include <ziparchive/zip_archive.h>
#include "edify/expr.h"
-#include "mounts.h"
-#include "otafault/ota_io.h"
-#include "otautil/DirUtil.h"
+#include "otautil/dirutil.h"
#include "otautil/error_code.h"
+#include "otautil/mounts.h"
#include "otautil/print_sha1.h"
+#include "otautil/sysutil.h"
#include "updater/updater.h"
-// Send over the buffer to recovery though the command pipe.
-static void uiPrint(State* state, const std::string& buffer) {
- UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
-
- // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "".
- // So skip sending empty strings to UI.
- std::vector<std::string> lines = android::base::Split(buffer, "\n");
- for (auto& line : lines) {
- if (!line.empty()) {
- fprintf(ui->cmd_pipe, "ui_print %s\n", line.c_str());
- }
- }
-
- // On the updater side, we need to dump the contents to stderr (which has
- // been redirected to the log file). Because the recovery will only print
- // the contents to screen when processing pipe command ui_print.
- LOG(INFO) << buffer;
-}
-
-void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...) {
- std::string error_msg;
-
- va_list ap;
- va_start(ap, format);
- android::base::StringAppendV(&error_msg, format, ap);
- va_end(ap);
-
- 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) {
@@ -103,7 +73,7 @@ Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_p
}
std::string buffer = android::base::Join(args, "");
- uiPrint(state, buffer);
+ static_cast<Updater*>(state->cookie)->UiPrint(buffer);
return StringValue(buffer);
}
@@ -129,16 +99,15 @@ Value* PackageExtractFileFn(const char* name, State* state,
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());
+ ZipArchiveHandle za = static_cast<Updater*>(state->cookie)->package_handle();
ZipEntry entry;
- if (FindEntry(za, zip_string_path, &entry) != 0) {
+ if (FindEntry(za, zip_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)));
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(
+ 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("");
@@ -152,11 +121,12 @@ Value* PackageExtractFileFn(const char* name, State* state,
<< "\": " << ErrorCodeString(ret);
success = false;
}
- if (ota_fsync(fd) == -1) {
+ if (fsync(fd) == -1) {
PLOG(ERROR) << "fsync of \"" << dest_path << "\" failed";
success = false;
}
- if (ota_close(fd) == -1) {
+
+ if (close(fd.release()) != 0) {
PLOG(ERROR) << "close of \"" << dest_path << "\" failed";
success = false;
}
@@ -172,10 +142,9 @@ Value* PackageExtractFileFn(const char* name, State* state,
}
const std::string& zip_path = args[0];
- ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
- ZipString zip_string_path(zip_path.c_str());
+ ZipArchiveHandle za = static_cast<Updater*>(state->cookie)->package_handle();
ZipEntry entry;
- if (FindEntry(za, zip_string_path, &entry) != 0) {
+ if (FindEntry(za, zip_path, &entry) != 0) {
return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name,
zip_path.c_str());
}
@@ -191,145 +160,86 @@ Value* PackageExtractFileFn(const char* name, State* state,
zip_path.c_str(), buffer.size(), ErrorCodeString(ret));
}
- return new Value(VAL_BLOB, buffer);
+ return new Value(Value::Type::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.
+// patch_partition_check(target_partition, source_partition)
+// Checks if the target and source partitions have the desired checksums to be patched. It returns
+// directly, if the target partition already has the expected checksum. Otherwise it in turn
+// checks the integrity of the source partition and the backup file on /cache.
//
-// 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) {
+// For example, patch_partition_check(
+// "EMMC:/dev/block/boot:12342568:8aaacf187a6929d0e9c3e9e46ea7ff495b43424d",
+// "EMMC:/dev/block/boot:12363048:06b0b16299dcefc94900efed01e0763ff644ffa4")
+Value* PatchPartitionCheckFn(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
+ if (argv.size() != 2) {
return ErrorAbort(state, kArgsParsingFailure,
- "%s(): expected at least 6 args and an "
- "even number, got %zu",
- name, argv.size());
+ "%s(): Invalid number of args (expected 2, 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());
+ if (!ReadArgs(state, argv, &args, 0, 2)) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
}
- std::vector<std::string> args;
- if (!ReadArgs(state, argv, &args, 0, 1)) {
- return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+ std::string err;
+ auto target = Partition::Parse(args[0], &err);
+ if (!target) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse target \"%s\": %s", name,
+ args[0].c_str(), err.c_str());
}
- 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);
+ auto source = Partition::Parse(args[1], &err);
+ if (!source) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse source \"%s\": %s", name,
+ args[1].c_str(), err.c_str());
}
- int result = applypatch_check(filename.c_str(), sha1s);
- return StringValue(result == 0 ? "t" : "");
+ bool result = PatchPartitionCheck(target, source);
+ return StringValue(result ? "t" : "");
}
-// sha1_check(data)
-// to return the sha1 of the data (given in the format returned by
-// read_file).
+// patch_partition(target, source, patch)
+// Applies the given patch to the source partition, and writes the result to the target partition.
//
-// 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);
+// For example, patch_partition(
+// "EMMC:/dev/block/boot:12342568:8aaacf187a6929d0e9c3e9e46ea7ff495b43424d",
+// "EMMC:/dev/block/boot:12363048:06b0b16299dcefc94900efed01e0763ff644ffa4",
+// package_extract_file("boot.img.p"))
+Value* PatchPartitionFn(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
+ if (argv.size() != 3) {
+ return ErrorAbort(state, kArgsParsingFailure,
+ "%s(): Invalid number of args (expected 3, got %zu)", name, argv.size());
}
- std::vector<std::unique_ptr<Value>> args;
- if (!ReadValueArgs(state, argv, &args)) {
- return nullptr;
+ std::vector<std::string> args;
+ if (!ReadArgs(state, argv, &args, 0, 2)) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
}
- if (args[0]->type == VAL_INVALID) {
- return StringValue("");
+ std::string err;
+ auto target = Partition::Parse(args[0], &err);
+ if (!target) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse target \"%s\": %s", name,
+ args[0].c_str(), err.c_str());
}
- 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));
+ auto source = Partition::Parse(args[1], &err);
+ if (!source) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse source \"%s\": %s", name,
+ args[1].c_str(), err.c_str());
}
- 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();
- }
+ std::vector<std::unique_ptr<Value>> values;
+ if (!ReadValueArgs(state, argv, &values, 2, 1) || values[0]->type != Value::Type::BLOB) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s(): Invalid patch arg", name);
}
- // Didn't match any of the hex strings; return false.
- return StringValue("");
+ bool result = PatchPartition(target, source, *values[0], nullptr);
+ return StringValue(result ? "t" : "");
}
// mount(fs_type, partition_type, location, mount_point)
@@ -371,11 +281,11 @@ Value* MountFn(const char* name, State* state, const std::vector<std::unique_ptr
name);
}
+ auto updater = static_cast<Updater*>(state->cookie);
{
char* secontext = nullptr;
-
- if (sehandle) {
- selabel_lookup(sehandle, &secontext, mount_point.c_str(), 0755);
+ if (updater->sehandle()) {
+ selabel_lookup(updater->sehandle(), &secontext, mount_point.c_str(), 0755);
setfscreatecon(secontext);
}
@@ -389,8 +299,9 @@ Value* MountFn(const char* name, State* state, const std::vector<std::unique_ptr
if (mount(location.c_str(), mount_point.c_str(), fs_type.c_str(),
MS_NOATIME | MS_NODEV | MS_NODIRATIME, mount_options.c_str()) < 0) {
- uiPrintf(state, "%s: Failed to mount %s at %s: %s", name, location.c_str(), mount_point.c_str(),
- strerror(errno));
+ updater->UiPrint(android::base::StringPrintf("%s: Failed to mount %s at %s: %s", name,
+ location.c_str(), mount_point.c_str(),
+ strerror(errno)));
return StringValue("");
}
@@ -436,32 +347,38 @@ Value* UnmountFn(const char* name, State* state, const std::vector<std::unique_p
"mount_point argument to unmount() can't be empty");
}
+ auto updater = static_cast<Updater*>(state->cookie);
scan_mounted_volumes();
MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str());
if (vol == nullptr) {
- uiPrintf(state, "Failed to unmount %s: No such volume", mount_point.c_str());
+ updater->UiPrint(
+ android::base::StringPrintf("Failed to unmount %s: No such volume", mount_point.c_str()));
return nullptr;
} else {
int ret = unmount_mounted_volume(vol);
if (ret != 0) {
- uiPrintf(state, "Failed to unmount %s: %s", mount_point.c_str(), strerror(errno));
+ updater->UiPrint(android::base::StringPrintf("Failed to unmount %s: %s", mount_point.c_str(),
+ strerror(errno)));
}
}
return StringValue(mount_point);
}
-static int exec_cmd(const char* path, char* const argv[]) {
+static int exec_cmd(const std::vector<std::string>& args) {
+ CHECK(!args.empty());
+ auto argv = StringVectorToNullTerminatedArray(args);
+
pid_t child;
if ((child = vfork()) == 0) {
- execv(path, argv);
+ execv(argv[0], argv.data());
_exit(EXIT_FAILURE);
}
int status;
waitpid(child, &status, 0);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
- LOG(ERROR) << path << " failed with status " << WEXITSTATUS(status);
+ LOG(ERROR) << args[0] << " failed with status " << WEXITSTATUS(status);
}
return WEXITSTATUS(status);
}
@@ -511,66 +428,53 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt
}
if (fs_type == "ext4") {
- const char* mke2fs_argv[] = { "/sbin/mke2fs_static", "-t", "ext4", "-b", "4096",
- location.c_str(), nullptr, nullptr };
- std::string size_str;
+ std::vector<std::string> mke2fs_args = {
+ "/system/bin/mke2fs", "-t", "ext4", "-b", "4096", location
+ };
if (size != 0) {
- size_str = std::to_string(size / 4096LL);
- mke2fs_argv[6] = size_str.c_str();
+ mke2fs_args.push_back(std::to_string(size / 4096LL));
}
- int status = exec_cmd(mke2fs_argv[0], const_cast<char**>(mke2fs_argv));
- if (status != 0) {
+ if (auto status = exec_cmd(mke2fs_args); status != 0) {
LOG(ERROR) << name << ": mke2fs failed (" << status << ") on " << location;
return StringValue("");
}
- const char* e2fsdroid_argv[] = { "/sbin/e2fsdroid_static", "-e", "-a", mount_point.c_str(),
- location.c_str(), nullptr };
- status = exec_cmd(e2fsdroid_argv[0], const_cast<char**>(e2fsdroid_argv));
- if (status != 0) {
+ if (auto status = exec_cmd({ "/system/bin/e2fsdroid", "-e", "-a", mount_point, location });
+ status != 0) {
LOG(ERROR) << name << ": e2fsdroid failed (" << status << ") on " << location;
return StringValue("");
}
return StringValue(location);
- } else if (fs_type == "f2fs") {
+ }
+
+ if (fs_type == "f2fs") {
if (size < 0) {
LOG(ERROR) << name << ": fs_size can't be negative for f2fs: " << fs_size;
return StringValue("");
}
- std::string num_sectors = std::to_string(size / 512);
-
- const char* f2fs_path = "/sbin/mkfs.f2fs";
- const char* f2fs_argv[] = { "mkfs.f2fs",
- "-d1",
- "-f",
- "-O", "encrypt",
- "-O", "quota",
- "-O", "verity",
- "-w", "512",
- location.c_str(),
- (size < 512) ? nullptr : num_sectors.c_str(),
- nullptr };
- int status = exec_cmd(f2fs_path, const_cast<char**>(f2fs_argv));
- if (status != 0) {
- LOG(ERROR) << name << ": mkfs.f2fs failed (" << status << ") on " << location;
+ std::vector<std::string> f2fs_args = {
+ "/system/bin/make_f2fs", "-g", "android", "-w", "512", location
+ };
+ if (size >= 512) {
+ f2fs_args.push_back(std::to_string(size / 512));
+ }
+ if (auto status = exec_cmd(f2fs_args); status != 0) {
+ LOG(ERROR) << name << ": make_f2fs failed (" << status << ") on " << location;
return StringValue("");
}
- const char* sload_argv[] = { "/sbin/sload.f2fs", "-t", mount_point.c_str(), location.c_str(),
- nullptr };
- status = exec_cmd(sload_argv[0], const_cast<char**>(sload_argv));
- if (status != 0) {
- LOG(ERROR) << name << ": sload.f2fs failed (" << status << ") on " << location;
+ if (auto status = exec_cmd({ "/system/bin/sload_f2fs", "-t", mount_point, location });
+ status != 0) {
+ LOG(ERROR) << name << ": sload_f2fs failed (" << status << ") on " << location;
return StringValue("");
}
return StringValue(location);
- } else {
- LOG(ERROR) << name << ": unsupported fs_type \"" << fs_type << "\" partition_type \""
- << partition_type << "\"";
}
+ LOG(ERROR) << name << ": unsupported fs_type \"" << fs_type << "\" partition_type \""
+ << partition_type << "\"";
return nullptr;
}
@@ -599,8 +503,8 @@ Value* ShowProgressFn(const char* name, State* state,
sec_str.c_str());
}
- UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
- fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec);
+ auto updater = static_cast<Updater*>(state->cookie);
+ updater->WriteToCommandPipe(android::base::StringPrintf("progress %f %d", frac, sec));
return StringValue(frac_str);
}
@@ -623,8 +527,8 @@ Value* SetProgressFn(const char* name, State* state,
frac_str.c_str());
}
- UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
- fprintf(ui->cmd_pipe, "set_progress %f\n", frac);
+ auto updater = static_cast<Updater*>(state->cookie);
+ updater->WriteToCommandPipe(android::base::StringPrintf("set_progress %f", frac));
return StringValue(frac_str);
}
@@ -661,33 +565,12 @@ Value* FileGetPropFn(const char* name, State* state,
const std::string& filename = args[0];
const std::string& key = args[1];
- struct stat st;
- if (stat(filename.c_str(), &st) < 0) {
- return ErrorAbort(state, kFileGetPropFailure, "%s: failed to stat \"%s\": %s", name,
- filename.c_str(), strerror(errno));
- }
-
- constexpr off_t MAX_FILE_GETPROP_SIZE = 65536;
- if (st.st_size > MAX_FILE_GETPROP_SIZE) {
- return ErrorAbort(state, kFileGetPropFailure, "%s too large for %s (max %lld)",
- filename.c_str(), name, static_cast<long long>(MAX_FILE_GETPROP_SIZE));
- }
-
- std::string buffer(st.st_size, '\0');
- unique_file f(ota_fopen(filename.c_str(), "rb"));
- if (f == nullptr) {
- return ErrorAbort(state, kFileOpenFailure, "%s: failed to open %s: %s", name, filename.c_str(),
- strerror(errno));
- }
-
- if (ota_fread(&buffer[0], 1, st.st_size, f.get()) != static_cast<size_t>(st.st_size)) {
- ErrorAbort(state, kFreadFailure, "%s: failed to read %zu bytes from %s", name,
- static_cast<size_t>(st.st_size), filename.c_str());
+ std::string buffer;
+ if (!android::base::ReadFileToString(filename, &buffer)) {
+ ErrorAbort(state, kFreadFailure, "%s: failed to read %s", name, filename.c_str());
return nullptr;
}
- ota_fclose(f);
-
std::vector<std::string> lines = android::base::Split(buffer, "\n");
for (size_t i = 0; i < lines.size(); i++) {
std::string line = android::base::Trim(lines[i]);
@@ -733,7 +616,7 @@ Value* ApplyPatchSpaceFn(const char* name, State* state,
}
// Skip the cache size check if the update is a retry.
- if (state->is_retry || CacheSizeCheck(bytes) == 0) {
+ if (state->is_retry || CheckAndFreeSpaceOnCache(bytes)) {
return StringValue("t");
}
return StringValue("");
@@ -744,7 +627,8 @@ Value* WipeCacheFn(const char* name, State* state, const std::vector<std::unique
return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name,
argv.size());
}
- fprintf(static_cast<UpdaterInfo*>(state->cookie)->cmd_pipe, "wipe_cache\n");
+
+ static_cast<Updater*>(state->cookie)->WriteToCommandPipe("wipe_cache");
return StringValue("t");
}
@@ -758,17 +642,12 @@ Value* RunProgramFn(const char* name, State* state, const std::vector<std::uniqu
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
}
- char* args2[argv.size() + 1];
- for (size_t i = 0; i < argv.size(); i++) {
- args2[i] = &args[i][0];
- }
- args2[argv.size()] = nullptr;
-
- LOG(INFO) << "about to run program [" << args2[0] << "] with " << argv.size() << " args";
+ auto exec_args = StringVectorToNullTerminatedArray(args);
+ LOG(INFO) << "about to run program [" << exec_args[0] << "] with " << argv.size() << " args";
pid_t child = fork();
if (child == 0) {
- execv(args2[0], args2);
+ execv(exec_args[0], exec_args.data());
PLOG(ERROR) << "run_program: execv failed";
_exit(EXIT_FAILURE);
}
@@ -786,8 +665,8 @@ Value* RunProgramFn(const char* name, State* state, const std::vector<std::uniqu
return StringValue(std::to_string(status));
}
-// Read a local file and return its contents (the Value* returned
-// is actually a FileContents*).
+// read_file(filename)
+// Reads a local file 'filename' and returns its contents as a string Value.
Value* ReadFileFn(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());
@@ -795,18 +674,18 @@ Value* ReadFileFn(const char* name, State* state, const std::vector<std::unique_
std::vector<std::string> args;
if (!ReadArgs(state, argv, &args)) {
- return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+ return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
}
const std::string& filename = args[0];
- Value* v = new Value(VAL_INVALID, "");
-
- FileContents fc;
- if (LoadFileContents(filename.c_str(), &fc) == 0) {
- v->type = VAL_BLOB;
- v->data = std::string(fc.data.begin(), fc.data.end());
+ std::string contents;
+ if (android::base::ReadFileToString(filename, &contents)) {
+ return new Value(Value::Type::STRING, std::move(contents));
}
- return v;
+
+ // Leave it to caller to handle the failure.
+ PLOG(ERROR) << name << ": Failed to read " << filename;
+ return StringValue("");
}
// write_value(value, filename)
@@ -872,11 +751,7 @@ Value* RebootNowFn(const char* name, State* state, const std::vector<std::unique
return StringValue("");
}
- std::string reboot_cmd = "reboot," + property;
- if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
- reboot_cmd += ",quiescent";
- }
- android::base::SetProperty(ANDROID_RB_PROPERTY, reboot_cmd);
+ Reboot(property);
sleep(5);
return ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name);
@@ -964,7 +839,12 @@ Value* WipeBlockDeviceFn(const char* name, State* state, const std::vector<std::
if (!android::base::ParseUint(len_str.c_str(), &len)) {
return nullptr;
}
- unique_fd fd(ota_open(filename.c_str(), O_WRONLY, 0644));
+ android::base::unique_fd fd(open(filename.c_str(), O_WRONLY));
+ if (fd == -1) {
+ PLOG(ERROR) << "Failed to open " << filename;
+ return StringValue("");
+ }
+
// The wipe_block_device function in ext4_utils returns 0 on success and 1
// for failure.
int status = wipe_block_device(fd, len);
@@ -976,8 +856,7 @@ Value* EnableRebootFn(const char* name, State* state, const std::vector<std::uni
return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name,
argv.size());
}
- UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
- fprintf(ui->cmd_pipe, "enable_reboot\n");
+ static_cast<Updater*>(state->cookie)->WriteToCommandPipe("enable_reboot");
return StringValue("t");
}
@@ -991,20 +870,12 @@ Value* Tune2FsFn(const char* name, State* state, const std::vector<std::unique_p
return ErrorAbort(state, kArgsParsingFailure, "%s() could not read args", name);
}
- char* args2[argv.size() + 1];
- // Tune2fs expects the program name as its args[0]
- args2[0] = const_cast<char*>(name);
- if (args2[0] == nullptr) {
- return nullptr;
- }
- for (size_t i = 0; i < argv.size(); ++i) {
- args2[i + 1] = &args[i][0];
- }
+ // tune2fs expects the program name as its first arg.
+ args.insert(args.begin(), "tune2fs");
+ auto tune2fs_args = StringVectorToNullTerminatedArray(args);
- // tune2fs changes the file system parameters on an ext2 file system; it
- // returns 0 on success.
- int result = tune2fs_main(argv.size() + 1, args2);
- if (result != 0) {
+ // tune2fs changes the filesystem parameters on an ext2 filesystem; it returns 0 on success.
+ if (auto result = tune2fs_main(tune2fs_args.size() - 1, tune2fs_args.data()); result != 0) {
return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d", name, result);
}
return StringValue("t");
@@ -1022,14 +893,13 @@ void RegisterInstallFunctions() {
RegisterFunction("getprop", GetPropFn);
RegisterFunction("file_getprop", FileGetPropFn);
- RegisterFunction("apply_patch", ApplyPatchFn);
- RegisterFunction("apply_patch_check", ApplyPatchCheckFn);
RegisterFunction("apply_patch_space", ApplyPatchSpaceFn);
+ RegisterFunction("patch_partition", PatchPartitionFn);
+ RegisterFunction("patch_partition_check", PatchPartitionCheckFn);
RegisterFunction("wipe_block_device", WipeBlockDeviceFn);
RegisterFunction("read_file", ReadFileFn);
- RegisterFunction("sha1_check", Sha1CheckFn);
RegisterFunction("write_value", WriteValueFn);
RegisterFunction("wipe_cache", WipeCacheFn);
diff --git a/updater/updater.cpp b/updater/updater.cpp
index 1d6b172bb..e0679fb0c 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -16,211 +16,162 @@
#include "updater/updater.h"
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
#include <string.h>
+#include <unistd.h>
#include <string>
#include <android-base/logging.h>
#include <android-base/strings.h>
-#include <selinux/android.h>
-#include <selinux/label.h>
-#include <selinux/selinux.h>
-#include <ziparchive/zip_archive.h>
-
-#include "edify/expr.h"
-#include "otafault/config.h"
-#include "otautil/DirUtil.h"
-#include "otautil/SysUtil.h"
-#include "otautil/cache_location.h"
-#include "otautil/error_code.h"
-#include "updater/blockimg.h"
-#include "updater/install.h"
-
-// Generated by the makefile, this function defines the
-// RegisterDeviceExtensions() function, which calls all the
-// registration functions for device-specific extensions.
-#include "register.inc"
-
-// Where in the package we expect to find the edify script to execute.
-// (Note it's "updateR-script", not the older "update-script".)
-static constexpr const char* SCRIPT_NAME = "META-INF/com/google/android/updater-script";
-
-extern bool have_eio_error;
-
-struct selabel_handle *sehandle;
-
-static void UpdaterLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */,
- const char* /* tag */, const char* /* file */, unsigned int /* line */,
- const char* message) {
- fprintf(stdout, "%s\n", message);
-}
-
-int main(int argc, char** argv) {
- // Various things log information to stdout or stderr more or less
- // at random (though we've tried to standardize on stdout). The
- // log file makes more sense if buffering is turned off so things
- // appear in the right order.
- setbuf(stdout, nullptr);
- setbuf(stderr, nullptr);
-
- // We don't have logcat yet under recovery. Update logs will always be written to stdout
- // (which is redirected to recovery.log).
- android::base::InitLogging(argv, &UpdaterLogger);
-
- if (argc != 4 && argc != 5) {
- LOG(ERROR) << "unexpected number of arguments: " << argc;
- return 1;
- }
- char* version = argv[1];
- if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') {
- // We support version 1, 2, or 3.
- LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1];
- return 2;
+Updater::~Updater() {
+ if (package_handle_) {
+ CloseArchive(package_handle_);
}
+}
+bool Updater::Init(int fd, const std::string& package_filename, bool is_retry,
+ struct selabel_handle* sehandle) {
// Set up the pipe for sending commands back to the parent process.
+ cmd_pipe_.reset(fdopen(fd, "wb"));
+ if (!cmd_pipe_) {
+ LOG(ERROR) << "Failed to open the command pipe";
+ return false;
+ }
- int fd = atoi(argv[2]);
- FILE* cmd_pipe = fdopen(fd, "wb");
- setlinebuf(cmd_pipe);
-
- // Extract the script from the package.
+ setlinebuf(cmd_pipe_.get());
- const char* package_filename = argv[3];
- MemMapping map;
- if (!map.MapFile(package_filename)) {
- LOG(ERROR) << "failed to map package " << argv[3];
- return 3;
+ if (!mapped_package_.MapFile(package_filename)) {
+ LOG(ERROR) << "failed to map package " << package_filename;
+ return false;
}
- ZipArchiveHandle za;
- int open_err = OpenArchiveFromMemory(map.addr, map.length, argv[3], &za);
- if (open_err != 0) {
- LOG(ERROR) << "failed to open package " << argv[3] << ": " << ErrorCodeString(open_err);
- CloseArchive(za);
- return 3;
- }
-
- ZipString script_name(SCRIPT_NAME);
- ZipEntry script_entry;
- int find_err = FindEntry(za, script_name, &script_entry);
- if (find_err != 0) {
- LOG(ERROR) << "failed to find " << SCRIPT_NAME << " in " << package_filename << ": "
- << ErrorCodeString(find_err);
- CloseArchive(za);
- return 4;
+ if (int open_err = OpenArchiveFromMemory(mapped_package_.addr, mapped_package_.length,
+ package_filename.c_str(), &package_handle_);
+ open_err != 0) {
+ LOG(ERROR) << "failed to open package " << package_filename << ": "
+ << ErrorCodeString(open_err);
+ return false;
}
-
- std::string script;
- script.resize(script_entry.uncompressed_length);
- int extract_err = ExtractToMemory(za, &script_entry, reinterpret_cast<uint8_t*>(&script[0]),
- script_entry.uncompressed_length);
- if (extract_err != 0) {
- LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err);
- CloseArchive(za);
- return 5;
+ if (!ReadEntryToString(package_handle_, SCRIPT_NAME, &updater_script_)) {
+ return false;
}
- // Configure edify's functions.
+ is_retry_ = is_retry;
- RegisterBuiltins();
- RegisterInstallFunctions();
- RegisterBlockImageFunctions();
- RegisterDeviceExtensions();
+ sehandle_ = sehandle;
+ if (!sehandle_) {
+ fprintf(cmd_pipe_.get(), "ui_print Warning: No file_contexts\n");
+ }
+ return true;
+}
+bool Updater::RunUpdate() {
// Parse the script.
-
std::unique_ptr<Expr> root;
int error_count = 0;
- int error = parse_string(script.c_str(), &root, &error_count);
+ int error = ParseString(updater_script_, &root, &error_count);
if (error != 0 || error_count > 0) {
LOG(ERROR) << error_count << " parse errors";
- CloseArchive(za);
- return 6;
- }
-
- sehandle = selinux_android_file_context_handle();
- selinux_android_set_sehandle(sehandle);
-
- if (!sehandle) {
- fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
+ return false;
}
// Evaluate the parsed script.
+ State state(updater_script_, this);
+ state.is_retry = is_retry_;
+
+ bool status = Evaluate(&state, root, &result_);
+ if (status) {
+ fprintf(cmd_pipe_.get(), "ui_print script succeeded: result was [%s]\n", result_.c_str());
+ // Even though the script doesn't abort, still log the cause code if result is empty.
+ if (result_.empty() && state.cause_code != kNoCause) {
+ fprintf(cmd_pipe_.get(), "log cause: %d\n", state.cause_code);
+ }
+ return true;
+ }
- UpdaterInfo updater_info;
- updater_info.cmd_pipe = cmd_pipe;
- updater_info.package_zip = za;
- updater_info.version = atoi(version);
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
+ ParseAndReportErrorCode(&state);
+ return false;
+}
- State state(script, &updater_info);
+void Updater::WriteToCommandPipe(const std::string& message, bool flush) const {
+ fprintf(cmd_pipe_.get(), "%s\n", message.c_str());
+ if (flush) {
+ fflush(cmd_pipe_.get());
+ }
+}
- if (argc == 5) {
- if (strcmp(argv[4], "retry") == 0) {
- state.is_retry = true;
- } else {
- printf("unexpected argument: %s", argv[4]);
+void Updater::UiPrint(const std::string& message) const {
+ // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "".
+ // so skip sending empty strings to ui.
+ std::vector<std::string> lines = android::base::Split(message, "\n");
+ for (const auto& line : lines) {
+ if (!line.empty()) {
+ fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str());
}
}
- ota_io_init(za, state.is_retry);
- std::string result;
- bool status = Evaluate(&state, root, &result);
-
- if (have_eio_error) {
- fprintf(cmd_pipe, "retry_update\n");
- }
+ // on the updater side, we need to dump the contents to stderr (which has
+ // been redirected to the log file). because the recovery will only print
+ // the contents to screen when processing pipe command ui_print.
+ LOG(INFO) << message;
+}
- if (!status) {
- if (state.errmsg.empty()) {
- LOG(ERROR) << "script aborted (no error message)";
- fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
- } else {
- LOG(ERROR) << "script aborted: " << state.errmsg;
- const std::vector<std::string> lines = android::base::Split(state.errmsg, "\n");
- for (const std::string& line : lines) {
- // Parse the error code in abort message.
- // Example: "E30: This package is for bullhead devices."
- if (!line.empty() && line[0] == 'E') {
- if (sscanf(line.c_str(), "E%d: ", &state.error_code) != 1) {
- LOG(ERROR) << "Failed to parse error code: [" << line << "]";
- }
+void Updater::ParseAndReportErrorCode(State* state) {
+ CHECK(state);
+ if (state->errmsg.empty()) {
+ LOG(ERROR) << "script aborted (no error message)";
+ fprintf(cmd_pipe_.get(), "ui_print script aborted (no error message)\n");
+ } else {
+ LOG(ERROR) << "script aborted: " << state->errmsg;
+ const std::vector<std::string> lines = android::base::Split(state->errmsg, "\n");
+ for (const std::string& line : lines) {
+ // Parse the error code in abort message.
+ // Example: "E30: This package is for bullhead devices."
+ if (!line.empty() && line[0] == 'E') {
+ if (sscanf(line.c_str(), "E%d: ", &state->error_code) != 1) {
+ LOG(ERROR) << "Failed to parse error code: [" << line << "]";
}
- fprintf(cmd_pipe, "ui_print %s\n", line.c_str());
}
+ fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str());
}
+ }
- // Installation has been aborted. Set the error code to kScriptExecutionFailure unless
- // a more specific code has been set in errmsg.
- if (state.error_code == kNoError) {
- state.error_code = kScriptExecutionFailure;
- }
- fprintf(cmd_pipe, "log error: %d\n", state.error_code);
- // Cause code should provide additional information about the abort.
- if (state.cause_code != kNoCause) {
- fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
- if (state.cause_code == kPatchApplicationFailure) {
- LOG(INFO) << "Patch application failed, retry update.";
- fprintf(cmd_pipe, "retry_update\n");
- }
+ // Installation has been aborted. Set the error code to kScriptExecutionFailure unless
+ // a more specific code has been set in errmsg.
+ if (state->error_code == kNoError) {
+ state->error_code = kScriptExecutionFailure;
+ }
+ fprintf(cmd_pipe_.get(), "log error: %d\n", state->error_code);
+ // Cause code should provide additional information about the abort.
+ if (state->cause_code != kNoCause) {
+ fprintf(cmd_pipe_.get(), "log cause: %d\n", state->cause_code);
+ if (state->cause_code == kPatchApplicationFailure) {
+ LOG(INFO) << "Patch application failed, retry update.";
+ fprintf(cmd_pipe_.get(), "retry_update\n");
+ } else if (state->cause_code == kEioFailure) {
+ LOG(INFO) << "Update failed due to EIO, retry update.";
+ fprintf(cmd_pipe_.get(), "retry_update\n");
}
+ }
+}
- if (updater_info.package_zip) {
- CloseArchive(updater_info.package_zip);
- }
- return 7;
- } else {
- fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result.c_str());
+bool Updater::ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name,
+ std::string* content) {
+ ZipEntry entry;
+ int find_err = FindEntry(za, entry_name, &entry);
+ if (find_err != 0) {
+ LOG(ERROR) << "failed to find " << entry_name
+ << " in the package: " << ErrorCodeString(find_err);
+ return false;
}
- if (updater_info.package_zip) {
- CloseArchive(updater_info.package_zip);
+ content->resize(entry.uncompressed_length);
+ int extract_err = ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&content->at(0)),
+ entry.uncompressed_length);
+ if (extract_err != 0) {
+ LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err);
+ return false;
}
- return 0;
+ return true;
}
diff --git a/updater/updater_main.cpp b/updater/updater_main.cpp
new file mode 100644
index 000000000..dd22c137d
--- /dev/null
+++ b/updater/updater_main.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <selinux/android.h>
+#include <selinux/label.h>
+#include <selinux/selinux.h>
+
+#include "edify/expr.h"
+#include "updater/blockimg.h"
+#include "updater/dynamic_partitions.h"
+#include "updater/install.h"
+#include "updater/updater.h"
+
+// Generated by the makefile, this function defines the
+// RegisterDeviceExtensions() function, which calls all the
+// registration functions for device-specific extensions.
+#include "register.inc"
+
+static void UpdaterLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */,
+ const char* /* tag */, const char* /* file */, unsigned int /* line */,
+ const char* message) {
+ fprintf(stdout, "%s\n", message);
+}
+
+int main(int argc, char** argv) {
+ // Various things log information to stdout or stderr more or less
+ // at random (though we've tried to standardize on stdout). The
+ // log file makes more sense if buffering is turned off so things
+ // appear in the right order.
+ setbuf(stdout, nullptr);
+ setbuf(stderr, nullptr);
+
+ // We don't have logcat yet under recovery. Update logs will always be written to stdout
+ // (which is redirected to recovery.log).
+ android::base::InitLogging(argv, &UpdaterLogger);
+
+ if (argc != 4 && argc != 5) {
+ LOG(ERROR) << "unexpected number of arguments: " << argc;
+ return 1;
+ }
+
+ char* version = argv[1];
+ if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') {
+ // We support version 1, 2, or 3.
+ LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1];
+ return 1;
+ }
+
+ int fd;
+ if (!android::base::ParseInt(argv[2], &fd)) {
+ LOG(ERROR) << "Failed to parse fd in " << argv[2];
+ return 1;
+ }
+
+ std::string package_name = argv[3];
+
+ bool is_retry = false;
+ if (argc == 5) {
+ if (strcmp(argv[4], "retry") == 0) {
+ is_retry = true;
+ } else {
+ LOG(ERROR) << "unexpected argument: " << argv[4];
+ return 1;
+ }
+ }
+
+ // Configure edify's functions.
+ RegisterBuiltins();
+ RegisterInstallFunctions();
+ RegisterBlockImageFunctions();
+ RegisterDynamicPartitionsFunctions();
+ RegisterDeviceExtensions();
+
+ auto sehandle = selinux_android_file_context_handle();
+ selinux_android_set_sehandle(sehandle);
+
+ Updater updater;
+ if (!updater.Init(fd, package_name, is_retry, sehandle)) {
+ return 1;
+ }
+
+ if (!updater.RunUpdate()) {
+ return 1;
+ }
+
+ return 0;
+} \ No newline at end of file