diff options
Diffstat (limited to 'updater')
-rw-r--r-- | updater/Android.bp | 79 | ||||
-rw-r--r-- | updater/Android.mk | 78 | ||||
-rw-r--r-- | updater/blockimg.cpp | 116 | ||||
-rw-r--r-- | updater/build_info.cpp | 139 | ||||
-rw-r--r-- | updater/dynamic_partitions.cpp | 313 | ||||
-rw-r--r-- | updater/include/updater/build_info.h | 74 | ||||
-rw-r--r-- | updater/include/updater/install.h | 11 | ||||
-rw-r--r-- | updater/include/updater/simulator_runtime.h | 62 | ||||
-rw-r--r-- | updater/include/updater/target_files.h | 71 | ||||
-rw-r--r-- | updater/include/updater/updater.h | 85 | ||||
-rw-r--r-- | updater/include/updater/updater_runtime.h | 62 | ||||
-rw-r--r-- | updater/install.cpp | 217 | ||||
-rw-r--r-- | updater/simulator_runtime.cpp | 132 | ||||
-rw-r--r-- | updater/target_files.cpp | 287 | ||||
-rw-r--r-- | updater/update_simulator_main.cpp | 167 | ||||
-rw-r--r-- | updater/updater.cpp | 278 | ||||
-rw-r--r-- | updater/updater_main.cpp | 109 | ||||
-rw-r--r-- | updater/updater_runtime.cpp | 132 | ||||
-rw-r--r-- | updater/updater_runtime_dynamic_partitions.cpp | 343 |
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; +} |