summaryrefslogtreecommitdiffstats
path: root/updater
diff options
context:
space:
mode:
Diffstat (limited to 'updater')
-rw-r--r--updater/Android.bp79
-rw-r--r--updater/Android.mk78
-rw-r--r--updater/blockimg.cpp116
-rw-r--r--updater/build_info.cpp139
-rw-r--r--updater/dynamic_partitions.cpp313
-rw-r--r--updater/include/updater/build_info.h74
-rw-r--r--updater/include/updater/install.h11
-rw-r--r--updater/include/updater/simulator_runtime.h62
-rw-r--r--updater/include/updater/target_files.h71
-rw-r--r--updater/include/updater/updater.h85
-rw-r--r--updater/include/updater/updater_runtime.h62
-rw-r--r--updater/install.cpp217
-rw-r--r--updater/simulator_runtime.cpp132
-rw-r--r--updater/target_files.cpp287
-rw-r--r--updater/update_simulator_main.cpp167
-rw-r--r--updater/updater.cpp278
-rw-r--r--updater/updater_main.cpp109
-rw-r--r--updater/updater_runtime.cpp132
-rw-r--r--updater/updater_runtime_dynamic_partitions.cpp343
19 files changed, 2068 insertions, 687 deletions
diff --git a/updater/Android.bp b/updater/Android.bp
index b80cdb3a0..063366e5e 100644
--- a/updater/Android.bp
+++ b/updater/Android.bp
@@ -30,7 +30,6 @@ cc_defaults {
"libfec",
"libfec_rs",
"libverity_tree",
- "libfs_mgr",
"libgtest_prod",
"liblog",
"liblp",
@@ -46,6 +45,14 @@ cc_defaults {
"libcrypto_utils",
"libcutils",
"libutils",
+ ],
+}
+
+cc_defaults {
+ name: "libupdater_device_defaults",
+
+ static_libs: [
+ "libfs_mgr",
"libtune2fs",
"libext2_com_err",
@@ -54,11 +61,13 @@ cc_defaults {
"libext2_uuid",
"libext2_e2p",
"libext2fs",
- ],
+ ]
}
cc_library_static {
- name: "libupdater",
+ name: "libupdater_core",
+
+ host_supported: true,
defaults: [
"recovery_defaults",
@@ -68,8 +77,38 @@ cc_library_static {
srcs: [
"blockimg.cpp",
"commands.cpp",
- "dynamic_partitions.cpp",
"install.cpp",
+ "updater.cpp",
+ ],
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+
+ export_include_dirs: [
+ "include",
+ ],
+}
+
+cc_library_static {
+ name: "libupdater_device",
+
+ defaults: [
+ "recovery_defaults",
+ "libupdater_defaults",
+ "libupdater_device_defaults",
+ ],
+
+ srcs: [
+ "dynamic_partitions.cpp",
+ "updater_runtime.cpp",
+ "updater_runtime_dynamic_partitions.cpp",
+ ],
+
+ static_libs: [
+ "libupdater_core",
],
include_dirs: [
@@ -80,3 +119,35 @@ cc_library_static {
"include",
],
}
+
+cc_library_host_static {
+ name: "libupdater_host",
+
+ defaults: [
+ "recovery_defaults",
+ "libupdater_defaults",
+ ],
+
+ srcs: [
+ "build_info.cpp",
+ "dynamic_partitions.cpp",
+ "simulator_runtime.cpp",
+ "target_files.cpp",
+ ],
+
+ static_libs: [
+ "libupdater_core",
+ "libfstab",
+ "libc++fs",
+ ],
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+
+ export_include_dirs: [
+ "include",
+ ],
+}
diff --git a/updater/Android.mk b/updater/Android.mk
index c7a6ba989..93525c12a 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -33,7 +33,6 @@ updater_common_static_libraries := \
libfec \
libfec_rs \
libverity_tree \
- libfs_mgr \
libgtest_prod \
liblog \
liblp \
@@ -48,9 +47,24 @@ updater_common_static_libraries := \
libcrypto \
libcrypto_utils \
libcutils \
- libutils \
- libtune2fs \
- $(tune2fs_static_libraries)
+ libutils
+
+
+# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function
+# named "Register_<libname>()". Here we emit a little C function that
+# gets #included by updater.cpp. It calls all those registration
+# functions.
+# $(1): the path to the register.inc file
+# $(2): a list of TARGET_RECOVERY_UPDATER_LIBS
+define generate-register-inc
+ $(hide) mkdir -p $(dir $(1))
+ $(hide) echo "" > $(1)
+ $(hide) $(foreach lib,$(2),echo "extern void Register_$(lib)(void);" >> $(1);)
+ $(hide) echo "void RegisterDeviceExtensions() {" >> $(1)
+ $(hide) $(foreach lib,$(2),echo " Register_$(lib)();" >> $(1);)
+ $(hide) echo "}" >> $(1)
+endef
+
# updater (static executable)
# ===============================
@@ -59,7 +73,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE := updater
LOCAL_SRC_FILES := \
- updater.cpp
+ updater_main.cpp
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/include
@@ -69,33 +83,26 @@ LOCAL_CFLAGS := \
-Werror
LOCAL_STATIC_LIBRARIES := \
- libupdater \
+ libupdater_device \
+ libupdater_core \
$(TARGET_RECOVERY_UPDATER_LIBS) \
$(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) \
- $(updater_common_static_libraries)
+ $(updater_common_static_libraries) \
+ libfs_mgr \
+ libtune2fs \
+ $(tune2fs_static_libraries)
-# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function
-# named "Register_<libname>()". Here we emit a little C function that
-# gets #included by updater.c. It calls all those registration
-# functions.
+LOCAL_MODULE_CLASS := EXECUTABLES
+inc := $(call local-generated-sources-dir)/register.inc
# Devices can also add libraries to TARGET_RECOVERY_UPDATER_EXTRA_LIBS.
# These libs are also linked in with updater, but we don't try to call
# any sort of registration function for these. Use this variable for
# any subsidiary static libraries required for your registered
# extension libs.
-
-LOCAL_MODULE_CLASS := EXECUTABLES
-inc := $(call local-generated-sources-dir)/register.inc
-
$(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS)
$(inc) :
- $(hide) mkdir -p $(dir $@)
- $(hide) echo "" > $@
- $(hide) $(foreach lib,$(libs),echo "extern void Register_$(lib)(void);" >> $@;)
- $(hide) echo "void RegisterDeviceExtensions() {" >> $@
- $(hide) $(foreach lib,$(libs),echo " Register_$(lib)();" >> $@;)
- $(hide) echo "}" >> $@
+ $(call generate-register-inc,$@,$(libs))
LOCAL_GENERATED_SOURCES := $(inc)
@@ -104,3 +111,32 @@ inc :=
LOCAL_FORCE_STATIC_EXECUTABLE := true
include $(BUILD_EXECUTABLE)
+
+# TODO(xunchang) move to bp file
+# update_host_simulator (host executable)
+# ===============================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := update_host_simulator
+LOCAL_MODULE_HOST_OS := linux
+
+LOCAL_SRC_FILES := \
+ update_simulator_main.cpp
+
+LOCAL_C_INCLUDES := \
+ $(LOCAL_PATH)/include
+
+LOCAL_CFLAGS := \
+ -Wall \
+ -Werror
+
+LOCAL_STATIC_LIBRARIES := \
+ libupdater_host \
+ libupdater_core \
+ $(updater_common_static_libraries) \
+ libfstab \
+ libc++fs
+
+LOCAL_MODULE_CLASS := EXECUTABLES
+
+include $(BUILD_HOST_EXECUTABLE)
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index 07c3c7b52..2d41f610b 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -42,17 +42,18 @@
#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 "edify/updater_interface.h"
#include "otautil/dirutil.h"
#include "otautil/error_code.h"
#include "otautil/paths.h"
@@ -60,12 +61,16 @@
#include "otautil/rangeset.h"
#include "private/commands.h"
#include "updater/install.h"
-#include "updater/updater.h"
-// Set this to 0 to interpret 'erase' transfers to mean do a
-// BLKDISCARD ioctl (the normal behavior). Set to 1 to interpret
-// erase to mean fill the region with zeroes.
+#ifdef __ANDROID__
+#include <private/android_filesystem_config.h>
+// Set this to 0 to interpret 'erase' transfers to mean do a BLKDISCARD ioctl (the normal behavior).
+// Set to 1 to interpret erase to mean fill the region with zeroes.
#define DEBUG_ERASE 0
+#else
+#define DEBUG_ERASE 1
+#define AID_SYSTEM -1
+#endif // __ANDROID__
static constexpr size_t BLOCKSIZE = 4096;
static constexpr mode_t STASH_DIRECTORY_MODE = 0700;
@@ -1668,42 +1673,43 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
return StringValue("");
}
- UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
- if (ui == nullptr) {
+ auto updater = state->updater;
+ auto block_device_path = updater->FindBlockDeviceName(blockdev_filename->data);
+ if (block_device_path.empty()) {
+ LOG(ERROR) << "Block device path for " << blockdev_filename->data << " not found. " << name
+ << " failed.";
return StringValue("");
}
- FILE* cmd_pipe = ui->cmd_pipe;
- ZipArchiveHandle za = ui->package_zip;
-
- if (cmd_pipe == nullptr || za == nullptr) {
+ ZipArchiveHandle za = updater->GetPackageHandle();
+ if (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(open(blockdev_filename->data.c_str(), O_RDWR)));
+ params.fd.reset(TEMP_FAILURE_RETRY(open(block_device_path.c_str(), O_RDWR)));
if (params.fd == -1) {
failure_type = errno == EIO ? kEioFailure : kFileOpenFailure;
- PLOG(ERROR) << "open \"" << blockdev_filename->data << "\" failed";
+ PLOG(ERROR) << "open \"" << block_device_path << "\" failed";
return StringValue("");
}
uint8_t digest[SHA_DIGEST_LENGTH];
- if (!Sha1DevicePath(blockdev_filename->data, digest)) {
+ if (!Sha1DevicePath(block_device_path, digest)) {
return StringValue("");
}
params.stashbase = print_sha1(digest);
@@ -1716,8 +1722,7 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
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";
+ LOG(INFO) << "Skipping already updated partition " << block_device_path << " based on marker";
return StringValue("t");
}
} else {
@@ -1887,8 +1892,10 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
LOG(WARNING) << "Failed to update the last command file.";
}
- fprintf(cmd_pipe, "set_progress %.4f\n", static_cast<double>(params.written) / total_blocks);
- fflush(cmd_pipe);
+ updater->WriteToCommandPipe(
+ android::base::StringPrintf("set_progress %.4f",
+ static_cast<double>(params.written) / total_blocks),
+ true);
}
}
@@ -1913,13 +1920,15 @@ pbiudone:
LOG(INFO) << "stashed " << params.stashed << " blocks";
LOG(INFO) << "max alloc needed was " << params.buffer.size();
- const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
+ const char* partition = strrchr(block_device_path.c_str(), '/');
if (partition != nullptr && *(partition + 1) != 0) {
- fprintf(cmd_pipe, "log bytes_written_%s: %" PRIu64 "\n", partition + 1,
- static_cast<uint64_t>(params.written) * BLOCKSIZE);
- fprintf(cmd_pipe, "log bytes_stashed_%s: %" PRIu64 "\n", partition + 1,
- static_cast<uint64_t>(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.
@@ -2019,7 +2028,7 @@ Value* BlockImageVerifyFn(const char* name, State* state,
// clang-format off
{ Command::Type::ABORT, PerformCommandAbort },
{ Command::Type::BSDIFF, PerformCommandDiff },
- { Command::Type::COMPUTE_HASH_TREE, PerformCommandComputeHashTree },
+ { Command::Type::COMPUTE_HASH_TREE, nullptr },
{ Command::Type::ERASE, nullptr },
{ Command::Type::FREE, PerformCommandFree },
{ Command::Type::IMGDIFF, PerformCommandDiff },
@@ -2079,10 +2088,17 @@ Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique
return StringValue("");
}
- android::base::unique_fd fd(open(blockdev_filename->data.c_str(), O_RDWR));
+ auto block_device_path = state->updater->FindBlockDeviceName(blockdev_filename->data);
+ if (block_device_path.empty()) {
+ LOG(ERROR) << "Block device path for " << blockdev_filename->data << " not found. " << name
+ << " failed.";
+ return StringValue("");
+ }
+
+ android::base::unique_fd fd(open(block_device_path.c_str(), O_RDWR));
if (fd == -1) {
CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure;
- ErrorAbort(state, cause_code, "open \"%s\" failed: %s", blockdev_filename->data.c_str(),
+ ErrorAbort(state, cause_code, "open \"%s\" failed: %s", block_device_path.c_str(),
strerror(errno));
return StringValue("");
}
@@ -2096,7 +2112,7 @@ Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique
std::vector<uint8_t> buffer(BLOCKSIZE);
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(),
+ ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", block_device_path.c_str(),
strerror(errno));
return StringValue("");
}
@@ -2104,7 +2120,7 @@ Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique
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(),
+ ErrorAbort(state, cause_code, "failed to read %s: %s", block_device_path.c_str(),
strerror(errno));
return StringValue("");
}
@@ -2143,10 +2159,17 @@ Value* CheckFirstBlockFn(const char* name, State* state,
return StringValue("");
}
- android::base::unique_fd fd(open(arg_filename->data.c_str(), O_RDONLY));
+ auto block_device_path = state->updater->FindBlockDeviceName(arg_filename->data);
+ if (block_device_path.empty()) {
+ LOG(ERROR) << "Block device path for " << arg_filename->data << " not found. " << name
+ << " failed.";
+ return StringValue("");
+ }
+
+ android::base::unique_fd fd(open(block_device_path.c_str(), O_RDONLY));
if (fd == -1) {
CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure;
- ErrorAbort(state, cause_code, "open \"%s\" failed: %s", arg_filename->data.c_str(),
+ ErrorAbort(state, cause_code, "open \"%s\" failed: %s", block_device_path.c_str(),
strerror(errno));
return StringValue("");
}
@@ -2156,7 +2179,7 @@ Value* CheckFirstBlockFn(const char* name, State* state,
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(),
+ ErrorAbort(state, cause_code, "failed to read %s: %s", block_device_path.c_str(),
strerror(errno));
return StringValue("");
}
@@ -2172,8 +2195,10 @@ 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));
+ state->updater->UiPrint(
+ android::base::StringPrintf("Device was remounted R/W %" PRIu16 " times", mount_count));
+ state->updater->UiPrint(
+ android::base::StringPrintf("Last remount happened on %s", ctime(&mount_time)));
}
return StringValue("t");
@@ -2209,14 +2234,21 @@ Value* BlockImageRecoverFn(const char* name, State* state,
return StringValue("");
}
+ auto block_device_path = state->updater->FindBlockDeviceName(filename->data);
+ if (block_device_path.empty()) {
+ LOG(ERROR) << "Block device path for " << filename->data << " not found. " << name
+ << " failed.";
+ return StringValue("");
+ }
+
// Output notice to log when recover is attempted
- LOG(INFO) << filename->data << " image corrupted, attempting to recover...";
+ LOG(INFO) << block_device_path << " image corrupted, attempting to recover...";
// When opened with O_RDWR, libfec rewrites corrupted blocks when they are read
- fec::io fh(filename->data, O_RDWR);
+ fec::io fh(block_device_path, O_RDWR);
if (!fh) {
- ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data.c_str(),
+ ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", block_device_path.c_str(),
strerror(errno));
return StringValue("");
}
@@ -2242,7 +2274,7 @@ Value* BlockImageRecoverFn(const char* name, State* state,
if (fh.pread(buffer, BLOCKSIZE, static_cast<off64_t>(j) * BLOCKSIZE) != BLOCKSIZE) {
ErrorAbort(state, kLibfecFailure, "failed to recover %s (block %zu): %s",
- filename->data.c_str(), j, strerror(errno));
+ block_device_path.c_str(), j, strerror(errno));
return StringValue("");
}
@@ -2258,7 +2290,7 @@ Value* BlockImageRecoverFn(const char* name, State* state,
// read and check if the errors field value has increased.
}
}
- LOG(INFO) << "..." << filename->data << " image recovered successfully.";
+ LOG(INFO) << "..." << block_device_path << " image recovered successfully.";
return StringValue("t");
}
diff --git a/updater/build_info.cpp b/updater/build_info.cpp
new file mode 100644
index 000000000..f168008ec
--- /dev/null
+++ b/updater/build_info.cpp
@@ -0,0 +1,139 @@
+/*
+ * 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/build_info.h"
+
+#include <stdio.h>
+
+#include <set>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include "updater/target_files.h"
+
+bool BuildInfo::ParseTargetFile(const std::string_view target_file_path, bool extracted_input) {
+ TargetFile target_file(std::string(target_file_path), extracted_input);
+ if (!target_file.Open()) {
+ return false;
+ }
+
+ if (!target_file.GetBuildProps(&build_props_)) {
+ return false;
+ }
+
+ std::vector<FstabInfo> fstab_info_list;
+ if (!target_file.ParseFstabInfo(&fstab_info_list)) {
+ return false;
+ }
+
+ for (const auto& fstab_info : fstab_info_list) {
+ for (const auto& directory : { "IMAGES", "RADIO" }) {
+ std::string entry_name = directory + fstab_info.mount_point + ".img";
+ if (!target_file.EntryExists(entry_name)) {
+ LOG(WARNING) << "Failed to find the image entry in the target file: " << entry_name;
+ continue;
+ }
+
+ temp_files_.emplace_back(work_dir_);
+ auto& image_file = temp_files_.back();
+ if (!target_file.ExtractImage(entry_name, fstab_info, work_dir_, &image_file)) {
+ LOG(ERROR) << "Failed to set up source image files.";
+ return false;
+ }
+
+ std::string mapped_path = image_file.path;
+ // Rename the images to more readable ones if we want to keep the image.
+ if (keep_images_) {
+ mapped_path = work_dir_ + fstab_info.mount_point + ".img";
+ image_file.release();
+ if (rename(image_file.path, mapped_path.c_str()) != 0) {
+ PLOG(ERROR) << "Failed to rename " << image_file.path << " to " << mapped_path;
+ return false;
+ }
+ }
+
+ LOG(INFO) << "Mounted " << fstab_info.mount_point << "\nMapping: " << fstab_info.blockdev_name
+ << " to " << mapped_path;
+
+ blockdev_map_.emplace(
+ fstab_info.blockdev_name,
+ FakeBlockDevice(fstab_info.blockdev_name, fstab_info.mount_point, mapped_path));
+ break;
+ }
+ }
+
+ return true;
+}
+
+std::string BuildInfo::GetProperty(const std::string_view key,
+ const std::string_view default_value) const {
+ // The logic to parse the ro.product properties should be in line with the generation script.
+ // More details in common.py BuildInfo.GetBuildProp.
+ // TODO(xunchang) handle the oem property and the source order defined in
+ // ro.product.property_source_order
+ const std::set<std::string, std::less<>> ro_product_props = {
+ "ro.product.brand", "ro.product.device", "ro.product.manufacturer", "ro.product.model",
+ "ro.product.name"
+ };
+ const std::vector<std::string> source_order = {
+ "product", "odm", "vendor", "system_ext", "system",
+ };
+ if (ro_product_props.find(key) != ro_product_props.end()) {
+ std::string_view key_suffix(key);
+ CHECK(android::base::ConsumePrefix(&key_suffix, "ro.product"));
+ for (const auto& source : source_order) {
+ std::string resolved_key = "ro.product." + source + std::string(key_suffix);
+ if (auto entry = build_props_.find(resolved_key); entry != build_props_.end()) {
+ return entry->second;
+ }
+ }
+ LOG(WARNING) << "Failed to find property: " << key;
+ return std::string(default_value);
+ } else if (key == "ro.build.fingerprint") {
+ // clang-format off
+ return android::base::StringPrintf("%s/%s/%s:%s/%s/%s:%s/%s",
+ GetProperty("ro.product.brand", "").c_str(),
+ GetProperty("ro.product.name", "").c_str(),
+ GetProperty("ro.product.device", "").c_str(),
+ GetProperty("ro.build.version.release", "").c_str(),
+ GetProperty("ro.build.id", "").c_str(),
+ GetProperty("ro.build.version.incremental", "").c_str(),
+ GetProperty("ro.build.type", "").c_str(),
+ GetProperty("ro.build.tags", "").c_str());
+ // clang-format on
+ }
+
+ auto entry = build_props_.find(key);
+ if (entry == build_props_.end()) {
+ LOG(WARNING) << "Failed to find property: " << key;
+ return std::string(default_value);
+ }
+
+ return entry->second;
+}
+
+std::string BuildInfo::FindBlockDeviceName(const std::string_view name) const {
+ auto entry = blockdev_map_.find(name);
+ if (entry == blockdev_map_.end()) {
+ LOG(WARNING) << "Failed to find path to block device " << name;
+ return "";
+ }
+
+ return entry->second.mounted_file_path;
+}
diff --git a/updater/dynamic_partitions.cpp b/updater/dynamic_partitions.cpp
index b50dd75f9..a340116fe 100644
--- a/updater/dynamic_partitions.cpp
+++ b/updater/dynamic_partitions.cpp
@@ -19,46 +19,20 @@
#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 "edify/updater_runtime_interface.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) {
@@ -89,40 +63,14 @@ static std::vector<std::string> ReadStringArgs(const char* name, State* state,
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("");
+ auto updater_runtime = state->updater->GetRuntime();
+ return updater_runtime->UnmapPartitionOnDeviceMapper(args[0]) ? StringValue("t")
+ : StringValue("");
}
Value* MapPartitionFn(const char* name, State* state,
@@ -131,207 +79,12 @@ Value* MapPartitionFn(const char* name, State* state,
if (args.empty()) return StringValue("");
std::string path;
- bool result = MapPartitionOnDeviceMapper(args[0], &path);
+ auto updater_runtime = state->updater->GetRuntime();
+ bool result = updater_runtime->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
+static constexpr char kMetadataUpdatedMarker[] = "/dynamic_partition_metadata.UPDATED";
Value* UpdateDynamicPartitionsFn(const char* name, State* state,
const std::vector<std::unique_ptr<Expr>>& argv) {
@@ -367,56 +120,8 @@ Value* UpdateDynamicPartitionsFn(const char* name, State* state,
}
}
- 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.";
+ auto updater_runtime = state->updater->GetRuntime();
+ if (!updater_runtime->UpdateDynamicPartitions(op_list_value->data)) {
return StringValue("");
}
diff --git a/updater/include/updater/build_info.h b/updater/include/updater/build_info.h
new file mode 100644
index 000000000..0073bfa4a
--- /dev/null
+++ b/updater/include/updater/build_info.h
@@ -0,0 +1,74 @@
+/*
+ * 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 <list>
+#include <map>
+#include <string>
+#include <string_view>
+
+#include <android-base/file.h>
+
+// This class serves as the aggregation of the fake block device information during update
+// simulation on host. In specific, it has the name of the block device, its mount point, and the
+// path to the temporary file that fakes this block device.
+class FakeBlockDevice {
+ public:
+ FakeBlockDevice(std::string block_device, std::string mount_point, std::string temp_file_path)
+ : blockdev_name(std::move(block_device)),
+ mount_point(std::move(mount_point)),
+ mounted_file_path(std::move(temp_file_path)) {}
+
+ std::string blockdev_name;
+ std::string mount_point;
+ std::string mounted_file_path; // path to the temp file that mocks the block device
+};
+
+// This class stores the information of the source build. For example, it creates and maintains
+// the temporary files to simulate the block devices on host. Therefore, the simulator runtime can
+// query the information and run the update on host.
+class BuildInfo {
+ public:
+ BuildInfo(const std::string_view work_dir, bool keep_images)
+ : work_dir_(work_dir), keep_images_(keep_images) {}
+ // Returns the value of the build properties.
+ std::string GetProperty(const std::string_view key, const std::string_view default_value) const;
+ // Returns the path to the mock block device.
+ std::string FindBlockDeviceName(const std::string_view name) const;
+ // Parses the given target-file, initializes the build properties and extracts the images.
+ bool ParseTargetFile(const std::string_view target_file_path, bool extracted_input);
+
+ std::string GetOemSettings() const {
+ return oem_settings_;
+ }
+ void SetOemSettings(const std::string_view oem_settings) {
+ oem_settings_ = oem_settings;
+ }
+
+ private:
+ // A map to store the system properties during simulation.
+ std::map<std::string, std::string, std::less<>> build_props_;
+ // A file that contains the oem properties.
+ std::string oem_settings_;
+ // A map from the blockdev_name to the FakeBlockDevice object, which contains the path to the
+ // temporary file.
+ std::map<std::string, FakeBlockDevice, std::less<>> blockdev_map_;
+
+ std::list<TemporaryFile> temp_files_;
+ std::string work_dir_; // A temporary directory to store the extracted image files
+ bool keep_images_;
+};
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/simulator_runtime.h b/updater/include/updater/simulator_runtime.h
new file mode 100644
index 000000000..9f7847b4f
--- /dev/null
+++ b/updater/include/updater/simulator_runtime.h
@@ -0,0 +1,62 @@
+/*
+ * 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 <map>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "edify/updater_runtime_interface.h"
+#include "updater/build_info.h"
+
+class SimulatorRuntime : public UpdaterRuntimeInterface {
+ public:
+ explicit SimulatorRuntime(BuildInfo* source) : source_(source) {}
+
+ bool IsSimulator() const override {
+ return true;
+ }
+
+ std::string GetProperty(const std::string_view key,
+ const std::string_view default_value) const override;
+
+ int Mount(const std::string_view location, const std::string_view mount_point,
+ const std::string_view fs_type, const std::string_view mount_options) override;
+ bool IsMounted(const std::string_view mount_point) const override;
+ std::pair<bool, int> Unmount(const std::string_view mount_point) override;
+
+ bool ReadFileToString(const std::string_view filename, std::string* content) const override;
+ bool WriteStringToFile(const std::string_view content,
+ const std::string_view filename) const override;
+
+ int WipeBlockDevice(const std::string_view filename, size_t len) const override;
+ int RunProgram(const std::vector<std::string>& args, bool is_vfork) const override;
+ int Tune2Fs(const std::vector<std::string>& args) const override;
+
+ bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) override;
+ bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) override;
+ bool UpdateDynamicPartitions(const std::string_view op_list_value) override;
+
+ private:
+ std::string FindBlockDeviceName(const std::string_view name) const override;
+
+ BuildInfo* source_;
+ std::map<std::string, std::string, std::less<>> mounted_partitions_;
+};
diff --git a/updater/include/updater/target_files.h b/updater/include/updater/target_files.h
new file mode 100644
index 000000000..860d47a35
--- /dev/null
+++ b/updater/include/updater/target_files.h
@@ -0,0 +1,71 @@
+/*
+ * 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 <map>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include <android-base/file.h>
+#include <ziparchive/zip_archive.h>
+
+// This class represents the mount information for each line in a fstab file.
+class FstabInfo {
+ public:
+ FstabInfo(std::string blockdev_name, std::string mount_point, std::string fs_type)
+ : blockdev_name(std::move(blockdev_name)),
+ mount_point(std::move(mount_point)),
+ fs_type(std::move(fs_type)) {}
+
+ std::string blockdev_name;
+ std::string mount_point;
+ std::string fs_type;
+};
+
+// This class parses a target file from a zip file or an extracted directory. It also provides the
+// function to read the its content for simulation.
+class TargetFile {
+ public:
+ TargetFile(std::string path, bool extracted_input)
+ : path_(std::move(path)), extracted_input_(extracted_input) {}
+
+ // Opens the input target file (or extracted directory) and parses the misc_info.txt.
+ bool Open();
+ // Parses the build properties in all possible locations and save them in |props_map|
+ bool GetBuildProps(std::map<std::string, std::string, std::less<>>* props_map) const;
+ // Parses the fstab and save the information about each partition to mount into |fstab_info_list|.
+ bool ParseFstabInfo(std::vector<FstabInfo>* fstab_info_list) const;
+ // Returns true if the given entry exists in the target file.
+ bool EntryExists(const std::string_view name) const;
+ // Extracts the image file |entry_name|. Returns true on success.
+ bool ExtractImage(const std::string_view entry_name, const FstabInfo& fstab_info,
+ const std::string_view work_dir, TemporaryFile* image_file) const;
+
+ private:
+ // Wrapper functions to read the entry from either the zipped target-file, or the extracted input
+ // directory.
+ bool ReadEntryToString(const std::string_view name, std::string* content) const;
+ bool ExtractEntryToTempFile(const std::string_view name, TemporaryFile* temp_file) const;
+
+ std::string path_; // Path to the zipped target-file or an extracted directory.
+ bool extracted_input_; // True if the target-file has been extracted.
+ ZipArchiveHandle handle_{ nullptr };
+
+ // The properties under META/misc_info.txt
+ std::map<std::string, std::string, std::less<>> misc_info_;
+};
diff --git a/updater/include/updater/updater.h b/updater/include/updater/updater.h
index f4a2fe874..8676b6038 100644
--- a/updater/include/updater/updater.h
+++ b/updater/include/updater/updater.h
@@ -14,22 +14,83 @@
* limitations under the License.
*/
-#ifndef _UPDATER_UPDATER_H_
-#define _UPDATER_UPDATER_H_
+#pragma once
+#include <stdint.h>
#include <stdio.h>
+
+#include <memory>
+#include <string>
+#include <string_view>
+
#include <ziparchive/zip_archive.h>
-typedef struct {
- FILE* cmd_pipe;
- ZipArchiveHandle package_zip;
- int version;
+#include "edify/expr.h"
+#include "edify/updater_interface.h"
+#include "otautil/error_code.h"
+#include "otautil/sysutil.h"
+
+class Updater : public UpdaterInterface {
+ public:
+ explicit Updater(std::unique_ptr<UpdaterRuntimeInterface> run_time)
+ : runtime_(std::move(run_time)) {}
+
+ ~Updater() override;
+
+ // Memory-maps the OTA package and opens it as a zip file. Also sets up the command pipe and
+ // UpdaterRuntime.
+ bool Init(int fd, const std::string_view package_filename, bool is_retry);
+
+ // 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_view message, bool flush = false) const override;
+
+ // Sends over the message to recovery to print it on the screen.
+ void UiPrint(const std::string_view message) const override;
+
+ std::string FindBlockDeviceName(const std::string_view name) const override;
+
+ UpdaterRuntimeInterface* GetRuntime() const override {
+ return runtime_.get();
+ }
+ ZipArchiveHandle GetPackageHandle() const override {
+ return package_handle_;
+ }
+ std::string GetResult() const override {
+ return result_;
+ }
+ uint8_t* GetMappedPackageAddress() const override {
+ return mapped_package_.addr;
+ }
+ size_t GetMappedPackageLength() const override {
+ return mapped_package_.length;
+ }
+
+ 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);
+
+ std::unique_ptr<UpdaterRuntimeInterface> runtime_;
- uint8_t* package_zip_addr;
- size_t package_zip_len;
-} UpdaterInfo;
+ MemMapping mapped_package_;
+ ZipArchiveHandle package_handle_{ nullptr };
+ std::string updater_script_;
-struct selabel_handle;
-extern struct selabel_handle *sehandle;
+ bool is_retry_{ false };
+ std::unique_ptr<FILE, decltype(&fclose)> cmd_pipe_{ nullptr, fclose };
-#endif
+ std::string result_;
+ std::vector<std::string> skipped_functions_;
+};
diff --git a/updater/include/updater/updater_runtime.h b/updater/include/updater/updater_runtime.h
new file mode 100644
index 000000000..8fc066f6a
--- /dev/null
+++ b/updater/include/updater/updater_runtime.h
@@ -0,0 +1,62 @@
+/*
+ * 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 <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "edify/updater_runtime_interface.h"
+
+struct selabel_handle;
+
+class UpdaterRuntime : public UpdaterRuntimeInterface {
+ public:
+ explicit UpdaterRuntime(struct selabel_handle* sehandle) : sehandle_(sehandle) {}
+ ~UpdaterRuntime() override = default;
+
+ bool IsSimulator() const override {
+ return false;
+ }
+
+ std::string GetProperty(const std::string_view key,
+ const std::string_view default_value) const override;
+
+ std::string FindBlockDeviceName(const std::string_view name) const override;
+
+ int Mount(const std::string_view location, const std::string_view mount_point,
+ const std::string_view fs_type, const std::string_view mount_options) override;
+ bool IsMounted(const std::string_view mount_point) const override;
+ std::pair<bool, int> Unmount(const std::string_view mount_point) override;
+
+ bool ReadFileToString(const std::string_view filename, std::string* content) const override;
+ bool WriteStringToFile(const std::string_view content,
+ const std::string_view filename) const override;
+
+ int WipeBlockDevice(const std::string_view filename, size_t len) const override;
+ int RunProgram(const std::vector<std::string>& args, bool is_vfork) const override;
+ int Tune2Fs(const std::vector<std::string>& args) const override;
+
+ bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) override;
+ bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) override;
+ bool UpdateDynamicPartitions(const std::string_view op_list_value) override;
+
+ private:
+ struct selabel_handle* sehandle_{ nullptr };
+};
diff --git a/updater/install.cpp b/updater/install.cpp
index 20a204a83..be0ceb06c 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -53,45 +53,31 @@
#include <openssl/sha.h>
#include <selinux/label.h>
#include <selinux/selinux.h>
-#include <tune2fs.h>
#include <ziparchive/zip_archive.h>
#include "edify/expr.h"
+#include "edify/updater_interface.h"
+#include "edify/updater_runtime_interface.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);
+#ifndef __ANDROID__
+#include <cutils/memory.h> // for strlcpy
+#endif
- // "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());
- }
+static bool UpdateBlockDeviceNameForPartition(UpdaterInterface* updater, Partition* partition) {
+ CHECK(updater);
+ std::string name = updater->FindBlockDeviceName(partition->name);
+ if (name.empty()) {
+ LOG(ERROR) << "Failed to find the block device " << partition->name;
+ return false;
}
- // 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);
+ partition->name = std::move(name);
+ return true;
}
// This is the updater side handler for ui_print() in edify script. Contents will be sent over to
@@ -103,7 +89,7 @@ Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_p
}
std::string buffer = android::base::Join(args, "");
- uiPrint(state, buffer);
+ state->updater->UiPrint(buffer);
return StringValue(buffer);
}
@@ -127,16 +113,22 @@ Value* PackageExtractFileFn(const char* name, State* state,
argv.size());
}
const std::string& zip_path = args[0];
- const std::string& dest_path = args[1];
+ 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 = state->updater->GetPackageHandle();
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("");
}
+ // Update the destination of package_extract_file if it's a block device. During simulation the
+ // destination will map to a fake file.
+ if (std::string block_device_name = state->updater->FindBlockDeviceName(dest_path);
+ !block_device_name.empty()) {
+ dest_path = block_device_name;
+ }
+
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) {
@@ -173,10 +165,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 = state->updater->GetPackageHandle();
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());
}
@@ -229,6 +220,11 @@ Value* PatchPartitionCheckFn(const char* name, State* state,
args[1].c_str(), err.c_str());
}
+ if (!UpdateBlockDeviceNameForPartition(state->updater, &source) ||
+ !UpdateBlockDeviceNameForPartition(state->updater, &target)) {
+ return StringValue("");
+ }
+
bool result = PatchPartitionCheck(target, source);
return StringValue(result ? "t" : "");
}
@@ -270,6 +266,11 @@ Value* PatchPartitionFn(const char* name, State* state,
return ErrorAbort(state, kArgsParsingFailure, "%s(): Invalid patch arg", name);
}
+ if (!UpdateBlockDeviceNameForPartition(state->updater, &source) ||
+ !UpdateBlockDeviceNameForPartition(state->updater, &target)) {
+ return StringValue("");
+ }
+
bool result = PatchPartition(target, source, *values[0], nullptr);
return StringValue(result ? "t" : "");
}
@@ -313,26 +314,11 @@ Value* MountFn(const char* name, State* state, const std::vector<std::unique_ptr
name);
}
- {
- char* secontext = nullptr;
-
- if (sehandle) {
- selabel_lookup(sehandle, &secontext, mount_point.c_str(), 0755);
- setfscreatecon(secontext);
- }
-
- mkdir(mount_point.c_str(), 0755);
-
- if (secontext) {
- freecon(secontext);
- setfscreatecon(nullptr);
- }
- }
-
- 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));
+ auto updater = state->updater;
+ if (updater->GetRuntime()->Mount(location, mount_point, fs_type, mount_options) != 0) {
+ 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("");
}
@@ -355,9 +341,8 @@ Value* IsMountedFn(const char* name, State* state, const std::vector<std::unique
"mount_point argument to unmount() can't be empty");
}
- scan_mounted_volumes();
- MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str());
- if (vol == nullptr) {
+ auto updater_runtime = state->updater->GetRuntime();
+ if (!updater_runtime->IsMounted(mount_point)) {
return StringValue("");
}
@@ -378,39 +363,20 @@ Value* UnmountFn(const char* name, State* state, const std::vector<std::unique_p
"mount_point argument to unmount() can't be empty");
}
- 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());
+ auto updater = state->updater;
+ auto [mounted, result] = updater->GetRuntime()->Unmount(mount_point);
+ if (!mounted) {
+ 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));
- }
+ } else if (result != 0) {
+ 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 std::vector<std::string>& args) {
- CHECK(!args.empty());
- auto argv = StringVectorToNullTerminatedArray(args);
-
- pid_t child;
- if ((child = vfork()) == 0) {
- execv(argv[0], argv.data());
- _exit(EXIT_FAILURE);
- }
-
- int status;
- waitpid(child, &status, 0);
- if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
- LOG(ERROR) << args[0] << " failed with status " << WEXITSTATUS(status);
- }
- return WEXITSTATUS(status);
-}
-
// format(fs_type, partition_type, location, fs_size, mount_point)
//
// fs_type="ext4" partition_type="EMMC" location=device fs_size=<bytes> mount_point=<location>
@@ -455,6 +421,7 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt
fs_size.c_str());
}
+ auto updater_runtime = state->updater->GetRuntime();
if (fs_type == "ext4") {
std::vector<std::string> mke2fs_args = {
"/system/bin/mke2fs", "-t", "ext4", "-b", "4096", location
@@ -463,12 +430,13 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt
mke2fs_args.push_back(std::to_string(size / 4096LL));
}
- if (auto status = exec_cmd(mke2fs_args); status != 0) {
+ if (auto status = updater_runtime->RunProgram(mke2fs_args, true); status != 0) {
LOG(ERROR) << name << ": mke2fs failed (" << status << ") on " << location;
return StringValue("");
}
- if (auto status = exec_cmd({ "/system/bin/e2fsdroid", "-e", "-a", mount_point, location });
+ if (auto status = updater_runtime->RunProgram(
+ { "/system/bin/e2fsdroid", "-e", "-a", mount_point, location }, true);
status != 0) {
LOG(ERROR) << name << ": e2fsdroid failed (" << status << ") on " << location;
return StringValue("");
@@ -487,12 +455,13 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt
if (size >= 512) {
f2fs_args.push_back(std::to_string(size / 512));
}
- if (auto status = exec_cmd(f2fs_args); status != 0) {
+ if (auto status = updater_runtime->RunProgram(f2fs_args, true); status != 0) {
LOG(ERROR) << name << ": make_f2fs failed (" << status << ") on " << location;
return StringValue("");
}
- if (auto status = exec_cmd({ "/system/bin/sload_f2fs", "-t", mount_point, location });
+ if (auto status = updater_runtime->RunProgram(
+ { "/system/bin/sload_f2fs", "-t", mount_point, location }, true);
status != 0) {
LOG(ERROR) << name << ": sload_f2fs failed (" << status << ") on " << location;
return StringValue("");
@@ -531,8 +500,7 @@ 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);
+ state->updater->WriteToCommandPipe(android::base::StringPrintf("progress %f %d", frac, sec));
return StringValue(frac_str);
}
@@ -555,8 +523,7 @@ 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);
+ state->updater->WriteToCommandPipe(android::base::StringPrintf("set_progress %f", frac));
return StringValue(frac_str);
}
@@ -569,7 +536,9 @@ Value* GetPropFn(const char* name, State* state, const std::vector<std::unique_p
if (!Evaluate(state, argv[0], &key)) {
return nullptr;
}
- std::string value = android::base::GetProperty(key, "");
+
+ auto updater_runtime = state->updater->GetRuntime();
+ std::string value = updater_runtime->GetProperty(key, "");
return StringValue(value);
}
@@ -594,7 +563,8 @@ Value* FileGetPropFn(const char* name, State* state,
const std::string& key = args[1];
std::string buffer;
- if (!android::base::ReadFileToString(filename, &buffer)) {
+ auto updater_runtime = state->updater->GetRuntime();
+ if (!updater_runtime->ReadFileToString(filename, &buffer)) {
ErrorAbort(state, kFreadFailure, "%s: failed to read %s", name, filename.c_str());
return nullptr;
}
@@ -655,7 +625,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");
+
+ state->updater->WriteToCommandPipe("wipe_cache");
return StringValue("t");
}
@@ -669,26 +640,8 @@ Value* RunProgramFn(const char* name, State* state, const std::vector<std::uniqu
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
}
- 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(exec_args[0], exec_args.data());
- PLOG(ERROR) << "run_program: execv failed";
- _exit(EXIT_FAILURE);
- }
-
- int status;
- waitpid(child, &status, 0);
- if (WIFEXITED(status)) {
- if (WEXITSTATUS(status) != 0) {
- LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status);
- }
- } else if (WIFSIGNALED(status)) {
- LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status);
- }
-
+ auto updater_runtime = state->updater->GetRuntime();
+ auto status = updater_runtime->RunProgram(args, false);
return StringValue(std::to_string(status));
}
@@ -706,7 +659,8 @@ Value* ReadFileFn(const char* name, State* state, const std::vector<std::unique_
const std::string& filename = args[0];
std::string contents;
- if (android::base::ReadFileToString(filename, &contents)) {
+ auto updater_runtime = state->updater->GetRuntime();
+ if (updater_runtime->ReadFileToString(filename, &contents)) {
return new Value(Value::Type::STRING, std::move(contents));
}
@@ -735,12 +689,12 @@ Value* WriteValueFn(const char* name, State* state, const std::vector<std::uniqu
}
const std::string& value = args[0];
- if (!android::base::WriteStringToFile(value, filename)) {
+ auto updater_runtime = state->updater->GetRuntime();
+ if (!updater_runtime->WriteStringToFile(value, filename)) {
PLOG(ERROR) << name << ": Failed to write to \"" << filename << "\"";
return StringValue("");
- } else {
- return StringValue("t");
}
+ return StringValue("t");
}
// Immediately reboot the device. Recovery is not finished normally,
@@ -778,7 +732,7 @@ Value* RebootNowFn(const char* name, State* state, const std::vector<std::unique
return StringValue("");
}
- reboot("reboot," + property);
+ Reboot(property);
sleep(5);
return ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name);
@@ -866,16 +820,10 @@ Value* WipeBlockDeviceFn(const char* name, State* state, const std::vector<std::
if (!android::base::ParseUint(len_str.c_str(), &len)) {
return nullptr;
}
- 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);
- return StringValue((status == 0) ? "t" : "");
+ auto updater_runtime = state->updater->GetRuntime();
+ int status = updater_runtime->WipeBlockDevice(filename, len);
+ return StringValue(status == 0 ? "t" : "");
}
Value* EnableRebootFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
@@ -883,8 +831,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");
+ state->updater->WriteToCommandPipe("enable_reboot");
return StringValue("t");
}
@@ -900,10 +847,8 @@ Value* Tune2FsFn(const char* name, State* state, const std::vector<std::unique_p
// tune2fs expects the program name as its first arg.
args.insert(args.begin(), "tune2fs");
- auto tune2fs_args = StringVectorToNullTerminatedArray(args);
-
- // 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) {
+ auto updater_runtime = state->updater->GetRuntime();
+ if (auto result = updater_runtime->Tune2Fs(args); result != 0) {
return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d", name, result);
}
return StringValue("t");
diff --git a/updater/simulator_runtime.cpp b/updater/simulator_runtime.cpp
new file mode 100644
index 000000000..d2074d69a
--- /dev/null
+++ b/updater/simulator_runtime.cpp
@@ -0,0 +1,132 @@
+/*
+ * 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/simulator_runtime.h"
+
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <unordered_set>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/wipe.h>
+#include <selinux/label.h>
+
+#include "otautil/mounts.h"
+#include "otautil/sysutil.h"
+
+std::string SimulatorRuntime::GetProperty(const std::string_view key,
+ const std::string_view default_value) const {
+ return source_->GetProperty(key, default_value);
+}
+
+int SimulatorRuntime::Mount(const std::string_view location, const std::string_view mount_point,
+ const std::string_view /* fs_type */,
+ const std::string_view /* mount_options */) {
+ if (auto mounted_location = mounted_partitions_.find(mount_point);
+ mounted_location != mounted_partitions_.end() && mounted_location->second != location) {
+ LOG(ERROR) << mount_point << " has been mounted at " << mounted_location->second;
+ return -1;
+ }
+
+ mounted_partitions_.emplace(mount_point, location);
+ return 0;
+}
+
+bool SimulatorRuntime::IsMounted(const std::string_view mount_point) const {
+ return mounted_partitions_.find(mount_point) != mounted_partitions_.end();
+}
+
+std::pair<bool, int> SimulatorRuntime::Unmount(const std::string_view mount_point) {
+ if (!IsMounted(mount_point)) {
+ return { false, -1 };
+ }
+
+ mounted_partitions_.erase(std::string(mount_point));
+ return { true, 0 };
+}
+
+std::string SimulatorRuntime::FindBlockDeviceName(const std::string_view name) const {
+ return source_->FindBlockDeviceName(name);
+}
+
+// TODO(xunchang) implement the utility functions in simulator.
+int SimulatorRuntime::RunProgram(const std::vector<std::string>& args, bool /* is_vfork */) const {
+ LOG(INFO) << "Running program with args " << android::base::Join(args, " ");
+ return 0;
+}
+
+int SimulatorRuntime::Tune2Fs(const std::vector<std::string>& args) const {
+ LOG(INFO) << "Running Tune2Fs with args " << android::base::Join(args, " ");
+ return 0;
+}
+
+int SimulatorRuntime::WipeBlockDevice(const std::string_view filename, size_t /* len */) const {
+ LOG(INFO) << "SKip wiping block device " << filename;
+ return 0;
+}
+
+bool SimulatorRuntime::ReadFileToString(const std::string_view filename,
+ std::string* content) const {
+ if (android::base::EndsWith(filename, "oem.prop")) {
+ return android::base::ReadFileToString(source_->GetOemSettings(), content);
+ }
+
+ LOG(INFO) << "SKip reading filename " << filename;
+ return true;
+}
+
+bool SimulatorRuntime::WriteStringToFile(const std::string_view content,
+ const std::string_view filename) const {
+ LOG(INFO) << "SKip writing " << content.size() << " bytes to file " << filename;
+ return true;
+}
+
+bool SimulatorRuntime::MapPartitionOnDeviceMapper(const std::string& partition_name,
+ std::string* path) {
+ *path = partition_name;
+ return true;
+}
+
+bool SimulatorRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
+ LOG(INFO) << "Skip unmapping " << partition_name;
+ return true;
+}
+
+bool SimulatorRuntime::UpdateDynamicPartitions(const std::string_view op_list_value) {
+ const std::unordered_set<std::string> commands{
+ "resize", "remove", "add", "move",
+ "add_group", "resize_group", "remove_group", "remove_all_groups",
+ };
+
+ std::vector<std::string> lines = android::base::Split(std::string(op_list_value), "\n");
+ for (const auto& line : lines) {
+ if (line.empty() || line[0] == '#') continue;
+ auto tokens = android::base::Split(line, " ");
+ if (commands.find(tokens[0]) == commands.end()) {
+ LOG(ERROR) << "Unknown operation in op_list: " << line;
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/updater/target_files.cpp b/updater/target_files.cpp
new file mode 100644
index 000000000..919ec4e04
--- /dev/null
+++ b/updater/target_files.cpp
@@ -0,0 +1,287 @@
+/*
+ * 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/target_files.h"
+
+#include <unistd.h>
+
+#include <algorithm>
+#include <filesystem>
+#include <memory>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <sparse/sparse.h>
+
+static bool SimgToImg(int input_fd, int output_fd) {
+ if (lseek64(input_fd, 0, SEEK_SET) == -1) {
+ PLOG(ERROR) << "Failed to lseek64 on the input sparse image";
+ return false;
+ }
+
+ if (lseek64(output_fd, 0, SEEK_SET) == -1) {
+ PLOG(ERROR) << "Failed to lseek64 on the output raw image";
+ return false;
+ }
+
+ std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)> s_file(
+ sparse_file_import(input_fd, true, false), sparse_file_destroy);
+ if (!s_file) {
+ LOG(ERROR) << "Failed to import the sparse image.";
+ return false;
+ }
+
+ if (sparse_file_write(s_file.get(), output_fd, false, false, false) < 0) {
+ PLOG(ERROR) << "Failed to output the raw image file.";
+ return false;
+ }
+
+ return true;
+}
+
+static bool ParsePropertyFile(const std::string_view prop_content,
+ std::map<std::string, std::string, std::less<>>* props_map) {
+ LOG(INFO) << "Start parsing build property\n";
+ std::vector<std::string> lines = android::base::Split(std::string(prop_content), "\n");
+ for (const auto& line : lines) {
+ if (line.empty() || line[0] == '#') continue;
+ auto pos = line.find('=');
+ if (pos == std::string::npos) continue;
+ std::string key = line.substr(0, pos);
+ std::string value = line.substr(pos + 1);
+ LOG(INFO) << key << ": " << value;
+ props_map->emplace(key, value);
+ }
+
+ return true;
+}
+
+static bool ParseFstab(const std::string_view fstab, std::vector<FstabInfo>* fstab_info_list) {
+ LOG(INFO) << "parsing fstab\n";
+ std::vector<std::string> lines = android::base::Split(std::string(fstab), "\n");
+ for (const auto& line : lines) {
+ if (line.empty() || line[0] == '#') continue;
+
+ // <block_device> <mount_point> <fs_type> <mount_flags> optional:<fs_mgr_flags>
+ std::vector<std::string> tokens = android::base::Split(line, " ");
+ tokens.erase(std::remove(tokens.begin(), tokens.end(), ""), tokens.end());
+ if (tokens.size() != 4 && tokens.size() != 5) {
+ LOG(ERROR) << "Unexpected token size: " << tokens.size() << std::endl
+ << "Error parsing fstab line: " << line;
+ return false;
+ }
+
+ const auto& blockdev = tokens[0];
+ const auto& mount_point = tokens[1];
+ const auto& fs_type = tokens[2];
+ if (!android::base::StartsWith(mount_point, "/")) {
+ LOG(WARNING) << "mount point '" << mount_point << "' does not start with '/'";
+ continue;
+ }
+
+ // The simulator only supports ext4 and emmc for now.
+ if (fs_type != "ext4" && fs_type != "emmc") {
+ LOG(WARNING) << "Unsupported fs_type in " << line;
+ continue;
+ }
+
+ fstab_info_list->emplace_back(blockdev, mount_point, fs_type);
+ }
+
+ return true;
+}
+
+bool TargetFile::EntryExists(const std::string_view name) const {
+ if (extracted_input_) {
+ std::string entry_path = path_ + "/" + std::string(name);
+ if (access(entry_path.c_str(), O_RDONLY) != 0) {
+ PLOG(WARNING) << "Failed to access " << entry_path;
+ return false;
+ }
+ return true;
+ }
+
+ CHECK(handle_);
+ ZipEntry img_entry;
+ return FindEntry(handle_, name, &img_entry) == 0;
+}
+
+bool TargetFile::ReadEntryToString(const std::string_view name, std::string* content) const {
+ if (extracted_input_) {
+ std::string entry_path = path_ + "/" + std::string(name);
+ return android::base::ReadFileToString(entry_path, content);
+ }
+
+ CHECK(handle_);
+ ZipEntry entry;
+ if (auto find_err = FindEntry(handle_, name, &entry); find_err != 0) {
+ LOG(ERROR) << "failed to find " << name << " in the package: " << ErrorCodeString(find_err);
+ return false;
+ }
+
+ if (entry.uncompressed_length == 0) {
+ content->clear();
+ return true;
+ }
+
+ content->resize(entry.uncompressed_length);
+ if (auto extract_err = ExtractToMemory(
+ handle_, &entry, reinterpret_cast<uint8_t*>(&content->at(0)), entry.uncompressed_length);
+ extract_err != 0) {
+ LOG(ERROR) << "failed to read " << name << " from package: " << ErrorCodeString(extract_err);
+ return false;
+ }
+
+ return true;
+}
+
+bool TargetFile::ExtractEntryToTempFile(const std::string_view name,
+ TemporaryFile* temp_file) const {
+ if (extracted_input_) {
+ std::string entry_path = path_ + "/" + std::string(name);
+ return std::filesystem::copy_file(entry_path, temp_file->path,
+ std::filesystem::copy_options::overwrite_existing);
+ }
+
+ CHECK(handle_);
+ ZipEntry entry;
+ if (auto find_err = FindEntry(handle_, name, &entry); find_err != 0) {
+ LOG(ERROR) << "failed to find " << name << " in the package: " << ErrorCodeString(find_err);
+ return false;
+ }
+
+ if (auto status = ExtractEntryToFile(handle_, &entry, temp_file->fd); status != 0) {
+ LOG(ERROR) << "Failed to extract zip entry " << name << " : " << ErrorCodeString(status);
+ return false;
+ }
+ return true;
+}
+
+bool TargetFile::Open() {
+ if (!extracted_input_) {
+ if (auto ret = OpenArchive(path_.c_str(), &handle_); ret != 0) {
+ LOG(ERROR) << "failed to open source target file " << path_ << ": " << ErrorCodeString(ret);
+ return false;
+ }
+ }
+
+ // Parse the misc info.
+ std::string misc_info_content;
+ if (!ReadEntryToString("META/misc_info.txt", &misc_info_content)) {
+ return false;
+ }
+ if (!ParsePropertyFile(misc_info_content, &misc_info_)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool TargetFile::GetBuildProps(std::map<std::string, std::string, std::less<>>* props_map) const {
+ props_map->clear();
+ // Parse the source zip to mock the system props and block devices. We try all the possible
+ // locations for build props.
+ constexpr std::string_view kPropLocations[] = {
+ "SYSTEM/build.prop",
+ "VENDOR/build.prop",
+ "PRODUCT/build.prop",
+ "SYSTEM_EXT/build.prop",
+ "SYSTEM/vendor/build.prop",
+ "SYSTEM/product/build.prop",
+ "SYSTEM/system_ext/build.prop",
+ "ODM/build.prop", // legacy
+ "ODM/etc/build.prop",
+ "VENDOR/odm/build.prop", // legacy
+ "VENDOR/odm/etc/build.prop",
+ };
+ for (const auto& name : kPropLocations) {
+ std::string build_prop_content;
+ if (!ReadEntryToString(name, &build_prop_content)) {
+ continue;
+ }
+ std::map<std::string, std::string, std::less<>> props;
+ if (!ParsePropertyFile(build_prop_content, &props)) {
+ LOG(ERROR) << "Failed to parse build prop in " << name;
+ return false;
+ }
+ for (const auto& [key, value] : props) {
+ if (auto it = props_map->find(key); it != props_map->end() && it->second != value) {
+ LOG(WARNING) << "Property " << key << " has different values in property files, we got "
+ << it->second << " and " << value;
+ }
+ props_map->emplace(key, value);
+ }
+ }
+
+ return true;
+}
+
+bool TargetFile::ExtractImage(const std::string_view entry_name, const FstabInfo& fstab_info,
+ const std::string_view work_dir, TemporaryFile* image_file) const {
+ if (!EntryExists(entry_name)) {
+ return false;
+ }
+
+ // We don't need extra work for 'emmc'; use the image file as the block device.
+ if (fstab_info.fs_type == "emmc" || misc_info_.find("extfs_sparse_flag") == misc_info_.end()) {
+ if (!ExtractEntryToTempFile(entry_name, image_file)) {
+ return false;
+ }
+ } else { // treated as ext4 sparse image
+ TemporaryFile sparse_image{ std::string(work_dir) };
+ if (!ExtractEntryToTempFile(entry_name, &sparse_image)) {
+ return false;
+ }
+
+ // Convert the sparse image to raw.
+ if (!SimgToImg(sparse_image.fd, image_file->fd)) {
+ LOG(ERROR) << "Failed to convert " << fstab_info.mount_point << " to raw.";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool TargetFile::ParseFstabInfo(std::vector<FstabInfo>* fstab_info_list) const {
+ // Parse the fstab file and extract the image files. The location of the fstab actually depends
+ // on some flags e.g. "no_recovery", "recovery_as_boot". Here we just try all possibilities.
+ constexpr std::string_view kRecoveryFstabLocations[] = {
+ "RECOVERY/RAMDISK/system/etc/recovery.fstab",
+ "RECOVERY/RAMDISK/etc/recovery.fstab",
+ "BOOT/RAMDISK/system/etc/recovery.fstab",
+ "BOOT/RAMDISK/etc/recovery.fstab",
+ };
+ std::string fstab_content;
+ for (const auto& name : kRecoveryFstabLocations) {
+ if (std::string content; ReadEntryToString(name, &content)) {
+ fstab_content = std::move(content);
+ break;
+ }
+ }
+ if (fstab_content.empty()) {
+ LOG(ERROR) << "Failed to parse the recovery fstab file";
+ return false;
+ }
+
+ // Extract the images and convert them to raw.
+ if (!ParseFstab(fstab_content, fstab_info_list)) {
+ LOG(ERROR) << "Failed to mount the block devices for source build.";
+ return false;
+ }
+
+ return true;
+}
diff --git a/updater/update_simulator_main.cpp b/updater/update_simulator_main.cpp
new file mode 100644
index 000000000..6c6989bac
--- /dev/null
+++ b/updater/update_simulator_main.cpp
@@ -0,0 +1,167 @@
+/*
+ * 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 <getopt.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <string>
+#include <string_view>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include "edify/expr.h"
+#include "otautil/error_code.h"
+#include "otautil/paths.h"
+#include "updater/blockimg.h"
+#include "updater/build_info.h"
+#include "updater/dynamic_partitions.h"
+#include "updater/install.h"
+#include "updater/simulator_runtime.h"
+#include "updater/updater.h"
+
+using namespace std::string_literals;
+
+void Usage(std::string_view name) {
+ LOG(INFO) << "Usage: " << name << "[--oem_settings <oem_property_file>]"
+ << "[--skip_functions <skip_function_file>]"
+ << " --source <source_target_file>"
+ << " --ota_package <ota_package>";
+}
+
+Value* SimulatorPlaceHolderFn(const char* name, State* /* state */,
+ const std::vector<std::unique_ptr<Expr>>& /* argv */) {
+ LOG(INFO) << "Skip function " << name << " in host simulation";
+ return StringValue("t");
+}
+
+int main(int argc, char** argv) {
+ // Write the logs to stdout.
+ android::base::InitLogging(argv, &android::base::StderrLogger);
+
+ std::string oem_settings;
+ std::string skip_function_file;
+ std::string source_target_file;
+ std::string package_name;
+ std::string work_dir;
+ bool keep_images = false;
+
+ constexpr struct option OPTIONS[] = {
+ { "keep_images", no_argument, nullptr, 0 },
+ { "oem_settings", required_argument, nullptr, 0 },
+ { "ota_package", required_argument, nullptr, 0 },
+ { "skip_functions", required_argument, nullptr, 0 },
+ { "source", required_argument, nullptr, 0 },
+ { "work_dir", required_argument, nullptr, 0 },
+ { nullptr, 0, nullptr, 0 },
+ };
+
+ int arg;
+ int option_index;
+ while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
+ if (arg != 0) {
+ LOG(ERROR) << "Invalid command argument";
+ Usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+ auto option_name = OPTIONS[option_index].name;
+ // The same oem property file used during OTA generation. It's needed for file_getprop() to
+ // return the correct value for the source build.
+ if (option_name == "oem_settings"s) {
+ oem_settings = optarg;
+ } else if (option_name == "skip_functions"s) {
+ skip_function_file = optarg;
+ } else if (option_name == "source"s) {
+ source_target_file = optarg;
+ } else if (option_name == "ota_package"s) {
+ package_name = optarg;
+ } else if (option_name == "keep_images"s) {
+ keep_images = true;
+ } else if (option_name == "work_dir"s) {
+ work_dir = optarg;
+ } else {
+ Usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (source_target_file.empty() || package_name.empty()) {
+ Usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ // Configure edify's functions.
+ RegisterBuiltins();
+ RegisterInstallFunctions();
+ RegisterBlockImageFunctions();
+ RegisterDynamicPartitionsFunctions();
+
+ if (!skip_function_file.empty()) {
+ std::string content;
+ if (!android::base::ReadFileToString(skip_function_file, &content)) {
+ PLOG(ERROR) << "Failed to read " << skip_function_file;
+ return EXIT_FAILURE;
+ }
+
+ auto lines = android::base::Split(content, "\n");
+ for (const auto& line : lines) {
+ if (line.empty() || android::base::StartsWith(line, "#")) {
+ continue;
+ }
+ RegisterFunction(line, SimulatorPlaceHolderFn);
+ }
+ }
+
+ TemporaryFile temp_saved_source;
+ TemporaryFile temp_last_command;
+ TemporaryDir temp_stash_base;
+
+ Paths::Get().set_cache_temp_source(temp_saved_source.path);
+ Paths::Get().set_last_command_file(temp_last_command.path);
+ Paths::Get().set_stash_directory_base(temp_stash_base.path);
+
+ TemporaryFile cmd_pipe;
+ TemporaryDir source_temp_dir;
+ if (work_dir.empty()) {
+ work_dir = source_temp_dir.path;
+ }
+
+ BuildInfo source_build_info(work_dir, keep_images);
+ if (!source_build_info.ParseTargetFile(source_target_file, false)) {
+ LOG(ERROR) << "Failed to parse the target file " << source_target_file;
+ return EXIT_FAILURE;
+ }
+
+ if (!oem_settings.empty()) {
+ CHECK_EQ(0, access(oem_settings.c_str(), R_OK));
+ source_build_info.SetOemSettings(oem_settings);
+ }
+
+ Updater updater(std::make_unique<SimulatorRuntime>(&source_build_info));
+ if (!updater.Init(cmd_pipe.release(), package_name, false)) {
+ return EXIT_FAILURE;
+ }
+
+ if (!updater.RunUpdate()) {
+ return EXIT_FAILURE;
+ }
+
+ LOG(INFO) << "\nscript succeeded, result: " << updater.GetResult();
+
+ return 0;
+}
diff --git a/updater/updater.cpp b/updater/updater.cpp
index 7b5a3f938..8f4a6ede5 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -16,8 +16,6 @@
#include "updater/updater.h"
-#include <stdio.h>
-#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@@ -25,198 +23,162 @@
#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 "otautil/dirutil.h"
-#include "otautil/error_code.h"
-#include "otautil/sysutil.h"
-#include "updater/blockimg.h"
-#include "updater/dynamic_partitions.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";
-
-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;
- }
+#include "edify/updater_runtime_interface.h"
- 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_view package_filename, bool is_retry) {
// 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(std::string(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;
+ if (int open_err = OpenArchiveFromMemory(mapped_package_.addr, mapped_package_.length,
+ std::string(package_filename).c_str(), &package_handle_);
+ open_err != 0) {
+ LOG(ERROR) << "failed to open package " << package_filename << ": "
+ << ErrorCodeString(open_err);
+ return false;
}
-
- 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 (!ReadEntryToString(package_handle_, SCRIPT_NAME, &updater_script_)) {
+ 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;
- }
+ is_retry_ = is_retry;
- // Configure edify's functions.
+ return true;
+}
- RegisterBuiltins();
- RegisterInstallFunctions();
- RegisterBlockImageFunctions();
- RegisterDynamicPartitionsFunctions();
- RegisterDeviceExtensions();
+bool Updater::RunUpdate() {
+ CHECK(runtime_);
// Parse the script.
-
std::unique_ptr<Expr> root;
int error_count = 0;
- int error = ParseString(script, &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);
+ }
+ for (const auto& func : skipped_functions_) {
+ LOG(WARNING) << "Skipped executing function " << func;
+ }
+ 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_view message, bool flush) const {
+ fprintf(cmd_pipe_.get(), "%s\n", std::string(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_view 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(std::string(message), "\n");
+ for (const auto& line : lines) {
+ if (!line.empty()) {
+ fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str());
}
}
- std::string result;
- bool status = Evaluate(&state, root, &result);
-
- 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 << "]";
- }
+ // 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;
+}
+
+std::string Updater::FindBlockDeviceName(const std::string_view name) const {
+ return runtime_->FindBlockDeviceName(name);
+}
+
+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");
- } else if (state.cause_code == kEioFailure) {
- LOG(INFO) << "Update failed due to EIO, 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 " << entry_name
+ << " 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..055a8ac76
--- /dev/null
+++ b/updater/updater_main.cpp
@@ -0,0 +1,109 @@
+/*
+ * 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"
+#include "updater/updater_runtime.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(std::make_unique<UpdaterRuntime>(sehandle));
+ if (!updater.Init(fd, package_name, is_retry)) {
+ return 1;
+ }
+
+ if (!updater.RunUpdate()) {
+ return 1;
+ }
+
+ return 0;
+} \ No newline at end of file
diff --git a/updater/updater_runtime.cpp b/updater/updater_runtime.cpp
new file mode 100644
index 000000000..761f99975
--- /dev/null
+++ b/updater/updater_runtime.cpp
@@ -0,0 +1,132 @@
+/*
+ * 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/updater_runtime.h"
+
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/wipe.h>
+#include <selinux/label.h>
+#include <tune2fs.h>
+
+#include "otautil/mounts.h"
+#include "otautil/sysutil.h"
+
+std::string UpdaterRuntime::GetProperty(const std::string_view key,
+ const std::string_view default_value) const {
+ return android::base::GetProperty(std::string(key), std::string(default_value));
+}
+
+std::string UpdaterRuntime::FindBlockDeviceName(const std::string_view name) const {
+ return std::string(name);
+}
+
+int UpdaterRuntime::Mount(const std::string_view location, const std::string_view mount_point,
+ const std::string_view fs_type, const std::string_view mount_options) {
+ std::string mount_point_string(mount_point);
+ char* secontext = nullptr;
+ if (sehandle_) {
+ selabel_lookup(sehandle_, &secontext, mount_point_string.c_str(), 0755);
+ setfscreatecon(secontext);
+ }
+
+ mkdir(mount_point_string.c_str(), 0755);
+
+ if (secontext) {
+ freecon(secontext);
+ setfscreatecon(nullptr);
+ }
+
+ return mount(std::string(location).c_str(), mount_point_string.c_str(),
+ std::string(fs_type).c_str(), MS_NOATIME | MS_NODEV | MS_NODIRATIME,
+ std::string(mount_options).c_str());
+}
+
+bool UpdaterRuntime::IsMounted(const std::string_view mount_point) const {
+ scan_mounted_volumes();
+ MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str());
+ return vol != nullptr;
+}
+
+std::pair<bool, int> UpdaterRuntime::Unmount(const std::string_view mount_point) {
+ scan_mounted_volumes();
+ MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str());
+ if (vol == nullptr) {
+ return { false, -1 };
+ }
+
+ int ret = unmount_mounted_volume(vol);
+ return { true, ret };
+}
+
+bool UpdaterRuntime::ReadFileToString(const std::string_view filename, std::string* content) const {
+ return android::base::ReadFileToString(std::string(filename), content);
+}
+
+bool UpdaterRuntime::WriteStringToFile(const std::string_view content,
+ const std::string_view filename) const {
+ return android::base::WriteStringToFile(std::string(content), std::string(filename));
+}
+
+int UpdaterRuntime::WipeBlockDevice(const std::string_view filename, size_t len) const {
+ android::base::unique_fd fd(open(std::string(filename).c_str(), O_WRONLY));
+ if (fd == -1) {
+ PLOG(ERROR) << "Failed to open " << filename;
+ return false;
+ }
+ // The wipe_block_device function in ext4_utils returns 0 on success and 1 for failure.
+ return wipe_block_device(fd, len);
+}
+
+int UpdaterRuntime::RunProgram(const std::vector<std::string>& args, bool is_vfork) const {
+ CHECK(!args.empty());
+ auto argv = StringVectorToNullTerminatedArray(args);
+ LOG(INFO) << "about to run program [" << args[0] << "] with " << argv.size() << " args";
+
+ pid_t child = is_vfork ? vfork() : fork();
+ if (child == 0) {
+ execv(argv[0], argv.data());
+ PLOG(ERROR) << "run_program: execv failed";
+ _exit(EXIT_FAILURE);
+ }
+
+ int status;
+ waitpid(child, &status, 0);
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) != 0) {
+ LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status);
+ }
+ } else if (WIFSIGNALED(status)) {
+ LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status);
+ }
+
+ return status;
+}
+
+int UpdaterRuntime::Tune2Fs(const std::vector<std::string>& args) const {
+ auto tune2fs_args = StringVectorToNullTerminatedArray(args);
+ // tune2fs changes the filesystem parameters on an ext2 filesystem; it returns 0 on success.
+ return tune2fs_main(tune2fs_args.size() - 1, tune2fs_args.data());
+}
diff --git a/updater/updater_runtime_dynamic_partitions.cpp b/updater/updater_runtime_dynamic_partitions.cpp
new file mode 100644
index 000000000..be9250a81
--- /dev/null
+++ b/updater/updater_runtime_dynamic_partitions.cpp
@@ -0,0 +1,343 @@
+/*
+ * 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/updater_runtime.h"
+
+#include <algorithm>
+#include <chrono>
+#include <iterator>
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#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>
+
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using android::fs_mgr::CreateLogicalPartition;
+using android::fs_mgr::CreateLogicalPartitionParams;
+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 std::string GetSuperDevice() {
+ return "/dev/block/by-name/" + fs_mgr_get_super_partition_name();
+}
+
+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);
+ }
+ LOG(ERROR) << "Unknown device mapper state: "
+ << static_cast<std::underlying_type_t<DmDeviceState>>(state);
+ return false;
+}
+
+bool UpdaterRuntime::MapPartitionOnDeviceMapper(const std::string& partition_name,
+ std::string* path) {
+ auto state = DeviceMapper::Instance().GetState(partition_name);
+ if (state == DmDeviceState::INVALID) {
+ CreateLogicalPartitionParams params = {
+ .block_device = GetSuperDevice(),
+ .metadata_slot = 0,
+ .partition_name = partition_name,
+ .force_writable = true,
+ .timeout_ms = kMapTimeout,
+ };
+ return CreateLogicalPartition(params, 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;
+}
+
+bool UpdaterRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
+ return ::UnmapPartitionOnDeviceMapper(partition_name);
+}
+
+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 (!android::base::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
+
+bool UpdaterRuntime::UpdateDynamicPartitions(const std::string_view op_list_value) {
+ auto super_device = GetSuperDevice();
+ auto builder = MetadataBuilder::New(PartitionOpener(), super_device, 0);
+ if (builder == nullptr) {
+ LOG(ERROR) << "Failed to load dynamic partition metadata.";
+ return false;
+ }
+
+ 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(std::string(op_list_value), "\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 false;
+ }
+ OpParameters params;
+ params.tokens = tokens;
+ params.builder = builder.get();
+ if (!it->second(params)) {
+ return false;
+ }
+ }
+
+ auto metadata = builder->Export();
+ if (metadata == nullptr) {
+ LOG(ERROR) << "Failed to export metadata.";
+ return false;
+ }
+
+ if (!UpdatePartitionTable(super_device, *metadata, 0)) {
+ LOG(ERROR) << "Failed to write metadata.";
+ return false;
+ }
+
+ return true;
+}