diff options
Diffstat (limited to 'updater')
-rw-r--r-- | updater/Android.bp | 83 | ||||
-rw-r--r-- | updater/Android.mk | 44 | ||||
-rw-r--r-- | updater/blockimg.cpp | 962 | ||||
-rw-r--r-- | updater/commands.cpp | 454 | ||||
-rw-r--r-- | updater/dynamic_partitions.cpp | 435 | ||||
-rw-r--r-- | updater/include/private/commands.h | 475 | ||||
-rw-r--r-- | updater/include/private/utils.h | 21 | ||||
-rw-r--r-- | updater/include/updater/dynamic_partitions.h | 19 | ||||
-rw-r--r-- | updater/include/updater/install.h | 11 | ||||
-rw-r--r-- | updater/include/updater/updater.h | 77 | ||||
-rw-r--r-- | updater/install.cpp | 420 | ||||
-rw-r--r-- | updater/updater.cpp | 279 | ||||
-rw-r--r-- | updater/updater_main.cpp | 108 |
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, ¶ms.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, ¶ms.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, ¶ms.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, ¶ms.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, ¶ms.buffer); + if (ReadBlocks(src, ¶ms.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, ¶ms.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(¶ms.nti.mu, nullptr); - pthread_cond_init(¶ms.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(¶ms.thread, &attr, unzip_new_data, ¶ms.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(¶ms.nti.mu, nullptr); + pthread_cond_init(¶ms.nti.cv, nullptr); + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + int error = pthread_create(¶ms.thread, &attr, unzip_new_data, ¶ms.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(¶ms.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 |