diff options
130 files changed, 4870 insertions, 1934 deletions
diff --git a/Android.bp b/Android.bp index f92078256..45aafb043 100644 --- a/Android.bp +++ b/Android.bp @@ -58,7 +58,6 @@ cc_defaults { ], shared_libs: [ - "android.hardware.health@2.0", "libbase", "libbootloader_message", "libcrypto", @@ -72,11 +71,8 @@ cc_defaults { "libinstall", "librecovery_fastboot", "libminui", + "librecovery_utils", "libotautil", - - // external dependencies - "libhealthhalutils", - "libfstab", ], } @@ -105,6 +101,7 @@ cc_binary { defaults: [ "libinstall_defaults", "librecovery_defaults", + "librecovery_utils_defaults", ], srcs: [ @@ -149,8 +146,7 @@ cc_binary { ], static_libs: [ - "libotautil", - "libfstab", + "librecovery_utils", ], init_rc: [ @@ -176,8 +172,7 @@ cc_binary { ], static_libs: [ - "libotautil", - "libfstab", + "librecovery_utils", ], init_rc: [ diff --git a/CleanSpec.mk b/CleanSpec.mk index a7ab0d9be..d4e9e437b 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -51,6 +51,24 @@ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/sbin) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libinstall.recovery_intermediates) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/system/lib64/libinstall.so) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/nativetest/recovery_component_test) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/nativetest64/recovery_component_test) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/testcases/recovery_component_test) + +$(call add-clean-step, find $(OUT_DIR) -type f -name "SystemUpdaterSample*" -print0 | xargs -0 rm -f) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/SystemUpdaterSample) + +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib*/libbrotli.so) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib*/libbz.so) + +# Move recovery resources from /system to /vendor. +$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/bin/applypatch) +$(call add-clean-step, rm -r $(PRODUCT_OUT)/symbols/system/bin/applypatch) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/PACKAGING/target_files_intermediates/*-target_files-*/SYSTEM/bin/applypatch) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/PACKAGING/target_files_intermediates/*-target_files-*/SYSTEM/bin/install-recovery.sh) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/PACKAGING/target_files_intermediates/*-target_files-*/SYSTEM/etc/recovery-resource.dat) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/PACKAGING/target_files_intermediates/*-target_files-*/SYSTEM/recovery-from-boot.p) + # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ @@ -1,3 +1,4 @@ enh@google.com -tbao@google.com +nhdo@google.com xunchang@google.com +zhaojiac@google.com @@ -4,29 +4,41 @@ The Recovery Image Quick turn-around testing ------------------------- - mm -j && m ramdisk-nodeps && m recoveryimage-nodeps +* Devices using recovery-as-boot (e.g. Pixels, which set BOARD\_USES\_RECOVERY\_AS\_BOOT) - # To boot into the new recovery image - # without flashing the recovery partition: - adb reboot bootloader - fastboot boot $ANDROID_PRODUCT_OUT/recovery.img + # After setting up environment and lunch. + m -j bootimage + adb reboot bootloader + + # Pixel devices don't support booting into recovery mode with `fastboot boot`. + fastboot flash boot + + # Manually choose `Recovery mode` from bootloader menu. + +* Devices with a separate recovery image (e.g. Nexus) + + # After setting up environment and lunch. + mm -j && m ramdisk-nodeps && m recoveryimage-nodeps + adb reboot bootloader + + # To boot into the new recovery image without flashing the recovery partition: + fastboot boot $ANDROID_PRODUCT_OUT/recovery.img Running the tests ----------------- + # After setting up environment and lunch. mmma -j bootable/recovery - # Running the tests on device. + # Running the tests on device (under normal boot). adb root adb sync data # 32-bit device adb shell /data/nativetest/recovery_unit_test/recovery_unit_test - adb shell /data/nativetest/recovery_component_test/recovery_component_test # Or 64-bit device adb shell /data/nativetest64/recovery_unit_test/recovery_unit_test - adb shell /data/nativetest64/recovery_component_test/recovery_component_test Running the manual tests ------------------------ diff --git a/TEST_MAPPING b/TEST_MAPPING new file mode 100644 index 000000000..a3045828e --- /dev/null +++ b/TEST_MAPPING @@ -0,0 +1,14 @@ +{ + "presubmit": [ + { + "name": "minadbd_test" + }, + { + "name": "recovery_unit_test" + }, + { + "name": "recovery_host_test", + "host": true + } + ] +} diff --git a/applypatch/Android.bp b/applypatch/Android.bp index 620ca6cc9..13a962584 100644 --- a/applypatch/Android.bp +++ b/applypatch/Android.bp @@ -31,6 +31,7 @@ cc_library_static { name: "libapplypatch", host_supported: true, + vendor_available: true, defaults: [ "applypatch_defaults", @@ -51,12 +52,15 @@ cc_library_static { "libbase", "libbspatch", "libbz", - "libcrypto", "libedify", "libotautil", "libz", ], + shared_libs: [ + "libcrypto", + ], + target: { darwin: { enabled: false, @@ -66,6 +70,7 @@ cc_library_static { cc_library_static { name: "libapplypatch_modes", + vendor_available: true, defaults: [ "applypatch_defaults", @@ -78,14 +83,18 @@ cc_library_static { static_libs: [ "libapplypatch", "libbase", - "libcrypto", "libedify", "libotautil", ], + + shared_libs: [ + "libcrypto", + ], } cc_binary { name: "applypatch", + vendor: true, defaults: [ "applypatch_defaults", @@ -100,25 +109,29 @@ cc_binary { "libapplypatch", "libedify", "libotautil", + + // External dependencies. "libbspatch", + "libbrotli", + "libbz", ], shared_libs: [ "libbase", - "libbrotli", - "libbz", "libcrypto", "liblog", "libz", "libziparchive", ], + + init_rc: [ + "vendor_flash_recovery.rc", + ], } -cc_library_static { +cc_library_host_static { name: "libimgdiff", - host_supported: true, - defaults: [ "applypatch_defaults", ], @@ -170,35 +183,3 @@ cc_binary_host { "libz", ], } - -cc_library_static { - name: "libimgpatch", - - // The host module is for recovery_host_test (Linux only). - host_supported: true, - - defaults: [ - "applypatch_defaults", - ], - - srcs: [ - "bspatch.cpp", - "imgpatch.cpp", - ], - - static_libs: [ - "libbase", - "libbspatch", - "libbz", - "libcrypto", - "libedify", - "libotautil", - "libz", - ], - - target: { - darwin: { - enabled: false, - }, - }, -} diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp index 90d8e8604..adda6976d 100644 --- a/applypatch/applypatch.cpp +++ b/applypatch/applypatch.cpp @@ -47,7 +47,7 @@ using namespace std::string_literals; static bool GenerateTarget(const Partition& target, const FileContents& source_file, - const Value& patch, const Value* bonus_data); + const Value& patch, const Value* bonus_data, bool backup_source); bool LoadFileContents(const std::string& filename, FileContents* file) { // No longer allow loading contents from eMMC partitions. @@ -266,7 +266,7 @@ int ShowLicenses() { } bool PatchPartition(const Partition& target, const Partition& source, const Value& patch, - const Value* bonus) { + const Value* bonus, bool backup_source) { LOG(INFO) << "Patching " << target.name; // We try to load and check against the target hash first. @@ -279,8 +279,8 @@ bool PatchPartition(const Partition& target, const Partition& source, const Valu } FileContents source_file; - if (ReadPartitionToBuffer(source, &source_file, true)) { - return GenerateTarget(target, source_file, patch, bonus); + if (ReadPartitionToBuffer(source, &source_file, backup_source)) { + return GenerateTarget(target, source_file, patch, bonus, backup_source); } LOG(ERROR) << "Failed to find any match"; @@ -326,7 +326,7 @@ bool FlashPartition(const Partition& partition, const std::string& source_filena } static bool GenerateTarget(const Partition& target, const FileContents& source_file, - const Value& patch, const Value* bonus_data) { + const Value& patch, const Value* bonus_data, bool backup_source) { uint8_t expected_sha1[SHA_DIGEST_LENGTH]; if (ParseSha1(target.hash, expected_sha1) != 0) { LOG(ERROR) << "Failed to parse target hash \"" << target.hash << "\""; @@ -351,11 +351,11 @@ static bool GenerateTarget(const Partition& target, const FileContents& source_f } // We write the original source to cache, in case the partition write is interrupted. - if (!CheckAndFreeSpaceOnCache(source_file.data.size())) { + if (backup_source && !CheckAndFreeSpaceOnCache(source_file.data.size())) { LOG(ERROR) << "Not enough free space on /cache"; return false; } - if (!SaveFileContents(Paths::Get().cache_temp_source(), &source_file)) { + if (backup_source && !SaveFileContents(Paths::Get().cache_temp_source(), &source_file)) { LOG(ERROR) << "Failed to back up source file"; return false; } @@ -415,7 +415,9 @@ static bool GenerateTarget(const Partition& target, const FileContents& source_f } // Delete the backup copy of the source. - unlink(Paths::Get().cache_temp_source().c_str()); + if (backup_source) { + unlink(Paths::Get().cache_temp_source().c_str()); + } // Success! return true; diff --git a/applypatch/applypatch_modes.cpp b/applypatch/applypatch_modes.cpp index b46659808..bb5eeae9d 100644 --- a/applypatch/applypatch_modes.cpp +++ b/applypatch/applypatch_modes.cpp @@ -87,7 +87,7 @@ static int PatchMode(const std::string& target_emmc, const std::string& source_e bonus = std::make_unique<Value>(Value::Type::BLOB, std::move(bonus_contents)); } - return PatchPartition(target, source, patch, bonus.get()) ? 0 : 1; + return PatchPartition(target, source, patch, bonus.get(), false) ? 0 : 1; } static void Usage() { diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp index 415d95f14..6ad4a6105 100644 --- a/applypatch/imgdiff.cpp +++ b/applypatch/imgdiff.cpp @@ -675,7 +675,7 @@ bool ZipModeImage::Initialize(const std::string& filename) { // Iterate the zip entries and compose the image chunks accordingly. bool ZipModeImage::InitializeChunks(const std::string& filename, ZipArchiveHandle handle) { void* cookie; - int ret = StartIteration(handle, &cookie, nullptr, nullptr); + int ret = StartIteration(handle, &cookie); if (ret != 0) { LOG(ERROR) << "Failed to iterate over entries in " << filename << ": " << ErrorCodeString(ret); return false; @@ -683,12 +683,11 @@ bool ZipModeImage::InitializeChunks(const std::string& filename, ZipArchiveHandl // Create a list of deflated zip entries, sorted by offset. std::vector<std::pair<std::string, ZipEntry>> temp_entries; - ZipString name; + std::string name; ZipEntry entry; while ((ret = Next(cookie, &entry, &name)) == 0) { if (entry.method == kCompressDeflated || limit_ > 0) { - std::string entry_name(name.name, name.name + name.name_length); - temp_entries.emplace_back(entry_name, entry); + temp_entries.emplace_back(name, entry); } } diff --git a/applypatch/include/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h index 6fc6f0fc9..799f4b2d7 100644 --- a/applypatch/include/applypatch/applypatch.h +++ b/applypatch/include/applypatch/applypatch.h @@ -73,10 +73,11 @@ std::ostream& operator<<(std::ostream& os, const Partition& partition); // the 'target' Partition. While patching, it will backup the data on the source partition to // /cache, so that the patching could be resumed on interruption even if both of the source and // target partitions refer to the same device. The function is idempotent if called multiple times. -// An optional arg 'bonus' can be provided, if the patch was generated with a bonus output. -// Returns the patching result. +// 'bonus' can be provided if the patch was generated with a bonus output, or nullptr. +// 'backup_source' indicates whether the source partition should be backed up prior to the update +// (e.g. when doing in-place update). Returns the patching result. bool PatchPartition(const Partition& target, const Partition& source, const Value& patch, - const Value* bonus); + const Value* bonus, bool backup_source); // Returns whether the contents of the eMMC target or the cached file match the embedded hash. // It will look for the backup on /cache if the given partition doesn't match the checksum. diff --git a/applypatch/vendor_flash_recovery.rc b/applypatch/vendor_flash_recovery.rc new file mode 100644 index 000000000..37a7c2be7 --- /dev/null +++ b/applypatch/vendor_flash_recovery.rc @@ -0,0 +1,3 @@ +service vendor_flash_recovery /vendor/bin/install-recovery.sh + class main + oneshot diff --git a/boot_control/Android.bp b/boot_control/Android.bp index 7720ead50..b2e68dfd4 100644 --- a/boot_control/Android.bp +++ b/boot_control/Android.bp @@ -14,13 +14,12 @@ // limitations under the License. // -cc_library_shared { - name: "bootctrl.default", +cc_defaults { + name: "libboot_control_defaults", + vendor: true, recovery_available: true, relative_install_path: "hw", - srcs: ["boot_control.cpp"], - cflags: [ "-D_FILE_OFFSET_BITS=64", "-Werror", @@ -29,9 +28,34 @@ cc_library_shared { ], shared_libs: [ + "android.hardware.boot@1.1", "libbase", - "libbootloader_message", - "libfs_mgr", "liblog", ], + static_libs: [ + "libbootloader_message_vendor", + "libfstab", + ], +} + +cc_library_static { + name: "libboot_control", + defaults: ["libboot_control_defaults"], + export_include_dirs: ["include"], + + srcs: ["libboot_control.cpp"], +} + +cc_library_shared { + name: "bootctrl.default", + defaults: ["libboot_control_defaults"], + + srcs: ["legacy_boot_control.cpp"], + + static_libs: [ + "libboot_control", + ], + shared_libs: [ + "libhardware", + ], } diff --git a/boot_control/include/libboot_control/libboot_control.h b/boot_control/include/libboot_control/libboot_control.h new file mode 100644 index 000000000..34a9affe1 --- /dev/null +++ b/boot_control/include/libboot_control/libboot_control.h @@ -0,0 +1,66 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#pragma once + +#include <string> + +#include <android/hardware/boot/1.1/IBootControl.h> + +namespace android { +namespace bootable { + +// Helper library to implement the IBootControl HAL using the misc partition. +class BootControl { + using MergeStatus = ::android::hardware::boot::V1_1::MergeStatus; + + public: + bool Init(); + unsigned int GetNumberSlots(); + unsigned int GetCurrentSlot(); + bool MarkBootSuccessful(); + bool SetActiveBootSlot(unsigned int slot); + bool SetSlotAsUnbootable(unsigned int slot); + bool SetSlotBootable(unsigned int slot); + bool IsSlotBootable(unsigned int slot); + const char* GetSuffix(unsigned int slot); + bool IsSlotMarkedSuccessful(unsigned int slot); + bool SetSnapshotMergeStatus(MergeStatus status); + MergeStatus GetSnapshotMergeStatus(); + + bool IsValidSlot(unsigned int slot); + + const std::string& misc_device() const { + return misc_device_; + } + + private: + // Whether this object was initialized with data from the bootloader message + // that doesn't change until next reboot. + bool initialized_ = false; + + // The path to the misc_device as reported in the fstab. + std::string misc_device_; + + // The number of slots present on the device. + unsigned int num_slots_ = 0; + + // The slot where we are running from. + unsigned int current_slot_ = 0; +}; + +} // namespace bootable +} // namespace android diff --git a/boot_control/legacy_boot_control.cpp b/boot_control/legacy_boot_control.cpp new file mode 100644 index 000000000..73d3a5841 --- /dev/null +++ b/boot_control/legacy_boot_control.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2015 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 <string> + +#include <hardware/boot_control.h> +#include <hardware/hardware.h> + +#include <libboot_control/libboot_control.h> + +using android::bootable::BootControl; + +struct boot_control_private_t { + // The base struct needs to be first in the list. + boot_control_module_t base; + + BootControl impl; +}; + +namespace { + +void BootControl_init(boot_control_module_t* module) { + auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl; + impl.Init(); +} + +unsigned int BootControl_getNumberSlots(boot_control_module_t* module) { + auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl; + return impl.GetNumberSlots(); +} + +unsigned int BootControl_getCurrentSlot(boot_control_module_t* module) { + auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl; + return impl.GetCurrentSlot(); +} + +int BootControl_markBootSuccessful(boot_control_module_t* module) { + auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl; + return impl.MarkBootSuccessful() ? 0 : -1; +} + +int BootControl_setActiveBootSlot(boot_control_module_t* module, unsigned int slot) { + auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl; + return impl.SetActiveBootSlot(slot) ? 0 : -1; +} + +int BootControl_setSlotAsUnbootable(struct boot_control_module* module, unsigned int slot) { + auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl; + return impl.SetSlotAsUnbootable(slot) ? 0 : -1; +} + +int BootControl_isSlotBootable(struct boot_control_module* module, unsigned int slot) { + auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl; + return impl.IsSlotBootable(slot) ? 0 : -1; +} + +int BootControl_isSlotMarkedSuccessful(struct boot_control_module* module, unsigned int slot) { + auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl; + return impl.IsSlotMarkedSuccessful(slot) ? 0 : -1; +} + +const char* BootControl_getSuffix(boot_control_module_t* module, unsigned int slot) { + auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl; + return impl.GetSuffix(slot); +} + +static int BootControl_open(const hw_module_t* module __unused, const char* id __unused, + hw_device_t** device __unused) { + /* Nothing to do currently. */ + return 0; +} + +struct hw_module_methods_t BootControl_methods = { + .open = BootControl_open, +}; + +} // namespace + +boot_control_private_t HAL_MODULE_INFO_SYM = { + .base = + { + .common = + { + .tag = HARDWARE_MODULE_TAG, + .module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1, + .hal_api_version = HARDWARE_HAL_API_VERSION, + .id = BOOT_CONTROL_HARDWARE_MODULE_ID, + .name = "AOSP reference bootctrl HAL", + .author = "The Android Open Source Project", + .methods = &BootControl_methods, + }, + .init = BootControl_init, + .getNumberSlots = BootControl_getNumberSlots, + .getCurrentSlot = BootControl_getCurrentSlot, + .markBootSuccessful = BootControl_markBootSuccessful, + .setActiveBootSlot = BootControl_setActiveBootSlot, + .setSlotAsUnbootable = BootControl_setSlotAsUnbootable, + .isSlotBootable = BootControl_isSlotBootable, + .getSuffix = BootControl_getSuffix, + .isSlotMarkedSuccessful = BootControl_isSlotMarkedSuccessful, + }, +}; diff --git a/boot_control/boot_control.cpp b/boot_control/libboot_control.cpp index ec97b6ced..e3bff9ff3 100644 --- a/boot_control/boot_control.cpp +++ b/boot_control/libboot_control.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include <libboot_control/libboot_control.h> + #include <endian.h> #include <errno.h> #include <fcntl.h> @@ -26,30 +28,13 @@ #include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android-base/unique_fd.h> -#include <hardware/boot_control.h> -#include <hardware/hardware.h> #include <bootloader_message/bootloader_message.h> -struct boot_control_private_t { - // The base struct needs to be first in the list. - boot_control_module_t base; - - // Whether this struct was initialized with data from the bootloader message - // that doesn't change until next reboot. - bool initialized; - - // The path to the misc_device as reported in the fstab. - const char* misc_device; - - // The number of slots present on the device. - unsigned int num_slots; - - // The slot where we are running from. - unsigned int current_slot; -}; +namespace android { +namespace bootable { -namespace { +using ::android::hardware::boot::V1_1::MergeStatus; // The number of boot attempts that should be made from a new slot before // rolling back to the previous slot. @@ -91,8 +76,8 @@ uint32_t BootloaderControlLECRC(const bootloader_control* boot_ctrl) { CRC32(reinterpret_cast<const uint8_t*>(boot_ctrl), offsetof(bootloader_control, crc32_le))); } -bool LoadBootloaderControl(const char* misc_device, bootloader_control* buffer) { - android::base::unique_fd fd(open(misc_device, O_RDONLY)); +bool LoadBootloaderControl(const std::string& misc_device, bootloader_control* buffer) { + android::base::unique_fd fd(open(misc_device.c_str(), O_RDONLY)); if (fd.get() == -1) { PLOG(ERROR) << "failed to open " << misc_device; return false; @@ -108,9 +93,9 @@ bool LoadBootloaderControl(const char* misc_device, bootloader_control* buffer) return true; } -bool UpdateAndSaveBootloaderControl(const char* misc_device, bootloader_control* buffer) { +bool UpdateAndSaveBootloaderControl(const std::string& misc_device, bootloader_control* buffer) { buffer->crc32_le = BootloaderControlLECRC(buffer); - android::base::unique_fd fd(open(misc_device, O_WRONLY | O_SYNC)); + android::base::unique_fd fd(open(misc_device.c_str(), O_WRONLY | O_SYNC)); if (fd.get() == -1) { PLOG(ERROR) << "failed to open " << misc_device; return false; @@ -126,13 +111,12 @@ bool UpdateAndSaveBootloaderControl(const char* misc_device, bootloader_control* return true; } -void InitDefaultBootloaderControl(const boot_control_private_t* module, - bootloader_control* boot_ctrl) { +void InitDefaultBootloaderControl(BootControl* control, bootloader_control* boot_ctrl) { memset(boot_ctrl, 0, sizeof(*boot_ctrl)); - if (module->current_slot < kMaxNumSlots) { - strlcpy(boot_ctrl->slot_suffix, kSlotSuffixes[module->current_slot], - sizeof(boot_ctrl->slot_suffix)); + unsigned int current_slot = control->GetCurrentSlot(); + if (current_slot < kMaxNumSlots) { + strlcpy(boot_ctrl->slot_suffix, kSlotSuffixes[current_slot], sizeof(boot_ctrl->slot_suffix)); } boot_ctrl->magic = BOOT_CTRL_MAGIC; boot_ctrl->version = BOOT_CTRL_VERSION; @@ -140,7 +124,7 @@ void InitDefaultBootloaderControl(const boot_control_private_t* module, // Figure out the number of slots by checking if the partitions exist, // otherwise assume the maximum supported by the header. boot_ctrl->nb_slot = kMaxNumSlots; - std::string base_path = module->misc_device; + std::string base_path = control->misc_device(); size_t last_path_sep = base_path.rfind('/'); if (last_path_sep != std::string::npos) { // We test the existence of the "boot" partition on each possible slot, @@ -185,7 +169,7 @@ void InitDefaultBootloaderControl(const boot_control_private_t* module, // current slot is successful. The bootloader should repair this situation // before booting and write a valid boot_control slot, so if we reach this // stage it means that the misc partition was corrupted since boot. - if (module->current_slot == slot) { + if (current_slot == slot) { entry.successful_boot = 1; } @@ -207,25 +191,35 @@ int SlotSuffixToIndex(const char* suffix) { // Initialize the boot_control_private struct with the information from // the bootloader_message buffer stored in |boot_ctrl|. Returns whether the // initialization succeeded. -bool BootControl_lazyInitialization(boot_control_private_t* module) { - if (module->initialized) return true; +bool BootControl::Init() { + if (initialized_) return true; // Initialize the current_slot from the read-only property. If the property // was not set (from either the command line or the device tree), we can later // initialize it from the bootloader_control struct. std::string suffix_prop = android::base::GetProperty("ro.boot.slot_suffix", ""); - module->current_slot = SlotSuffixToIndex(suffix_prop.c_str()); + if (suffix_prop.empty()) { + LOG(ERROR) << "Slot suffix property is not set"; + return false; + } + current_slot_ = SlotSuffixToIndex(suffix_prop.c_str()); std::string err; std::string device = get_bootloader_message_blk_device(&err); - if (device.empty()) return false; + if (device.empty()) { + LOG(ERROR) << "Could not find bootloader message block device: " << err; + return false; + } bootloader_control boot_ctrl; - if (!LoadBootloaderControl(device.c_str(), &boot_ctrl)) return false; + if (!LoadBootloaderControl(device.c_str(), &boot_ctrl)) { + LOG(ERROR) << "Failed to load bootloader control block"; + return false; + } // Note that since there isn't a module unload function this memory is leaked. - module->misc_device = strdup(device.c_str()); - module->initialized = true; + misc_device_ = strdup(device.c_str()); + initialized_ = true; // Validate the loaded data, otherwise we will destroy it and re-initialize it // with the current information. @@ -233,56 +227,47 @@ bool BootControl_lazyInitialization(boot_control_private_t* module) { if (boot_ctrl.crc32_le != computed_crc32) { LOG(WARNING) << "Invalid boot control found, expected CRC-32 0x" << std::hex << computed_crc32 << " but found 0x" << std::hex << boot_ctrl.crc32_le << ". Re-initializing."; - InitDefaultBootloaderControl(module, &boot_ctrl); + InitDefaultBootloaderControl(this, &boot_ctrl); UpdateAndSaveBootloaderControl(device.c_str(), &boot_ctrl); } - module->num_slots = boot_ctrl.nb_slot; + num_slots_ = boot_ctrl.nb_slot; return true; } -void BootControl_init(boot_control_module_t* module) { - BootControl_lazyInitialization(reinterpret_cast<boot_control_private_t*>(module)); -} - -unsigned int BootControl_getNumberSlots(boot_control_module_t* module) { - return reinterpret_cast<boot_control_private_t*>(module)->num_slots; +unsigned int BootControl::GetNumberSlots() { + return num_slots_; } -unsigned int BootControl_getCurrentSlot(boot_control_module_t* module) { - return reinterpret_cast<boot_control_private_t*>(module)->current_slot; +unsigned int BootControl::GetCurrentSlot() { + return current_slot_; } -int BootControl_markBootSuccessful(boot_control_module_t* module) { - boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module); - +bool BootControl::MarkBootSuccessful() { bootloader_control bootctrl; - if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false; - bootctrl.slot_info[bootctrl_module->current_slot].successful_boot = 1; + bootctrl.slot_info[current_slot_].successful_boot = 1; // tries_remaining == 0 means that the slot is not bootable anymore, make // sure we mark the current slot as bootable if it succeeds in the last // attempt. - bootctrl.slot_info[bootctrl_module->current_slot].tries_remaining = 1; - if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; - return 0; + bootctrl.slot_info[current_slot_].tries_remaining = 1; + return UpdateAndSaveBootloaderControl(misc_device_, &bootctrl); } -int BootControl_setActiveBootSlot(boot_control_module_t* module, unsigned int slot) { - boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module); - - if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { +bool BootControl::SetActiveBootSlot(unsigned int slot) { + if (slot >= kMaxNumSlots || slot >= num_slots_) { // Invalid slot number. - return -1; + return false; } bootloader_control bootctrl; - if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false; // Set every other slot with a lower priority than the new "active" slot. const unsigned int kActivePriority = 15; const unsigned int kActiveTries = 6; - for (unsigned int i = 0; i < bootctrl_module->num_slots; ++i) { + for (unsigned int i = 0; i < num_slots_; ++i) { if (i != slot) { if (bootctrl.slot_info[i].priority >= kActivePriority) bootctrl.slot_info[i].priority = kActivePriority - 1; @@ -299,103 +284,76 @@ int BootControl_setActiveBootSlot(boot_control_module_t* module, unsigned int sl // used to cancel the pending update. We should only reset the verity_corrpted // bit when attempting a new slot, otherwise the verity bit on the current // slot would be flip. - if (slot != bootctrl_module->current_slot) bootctrl.slot_info[slot].verity_corrupted = 0; + if (slot != current_slot_) bootctrl.slot_info[slot].verity_corrupted = 0; - if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; - return 0; + return UpdateAndSaveBootloaderControl(misc_device_, &bootctrl); } -int BootControl_setSlotAsUnbootable(struct boot_control_module* module, unsigned int slot) { - boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module); - - if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { +bool BootControl::SetSlotAsUnbootable(unsigned int slot) { + if (slot >= kMaxNumSlots || slot >= num_slots_) { // Invalid slot number. - return -1; + return false; } bootloader_control bootctrl; - if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false; // The only way to mark a slot as unbootable, regardless of the priority is to // set the tries_remaining to 0. bootctrl.slot_info[slot].successful_boot = 0; bootctrl.slot_info[slot].tries_remaining = 0; - if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; - return 0; + return UpdateAndSaveBootloaderControl(misc_device_, &bootctrl); } -int BootControl_isSlotBootable(struct boot_control_module* module, unsigned int slot) { - boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module); - - if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { +bool BootControl::IsSlotBootable(unsigned int slot) { + if (slot >= kMaxNumSlots || slot >= num_slots_) { // Invalid slot number. - return -1; + return false; } bootloader_control bootctrl; - if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false; - return bootctrl.slot_info[slot].tries_remaining; + return bootctrl.slot_info[slot].tries_remaining != 0; } -int BootControl_isSlotMarkedSuccessful(struct boot_control_module* module, unsigned int slot) { - boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module); - - if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { +bool BootControl::IsSlotMarkedSuccessful(unsigned int slot) { + if (slot >= kMaxNumSlots || slot >= num_slots_) { // Invalid slot number. - return -1; + return false; } bootloader_control bootctrl; - if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false; return bootctrl.slot_info[slot].successful_boot && bootctrl.slot_info[slot].tries_remaining; } -const char* BootControl_getSuffix(boot_control_module_t* module, unsigned int slot) { - if (slot >= kMaxNumSlots || slot >= reinterpret_cast<boot_control_private_t*>(module)->num_slots) { - return NULL; - } - return kSlotSuffixes[slot]; +bool BootControl::IsValidSlot(unsigned int slot) { + return slot < kMaxNumSlots && slot < num_slots_; } -static int BootControl_open(const hw_module_t* module __unused, const char* id __unused, - hw_device_t** device __unused) { - /* Nothing to do currently. */ - return 0; +bool BootControl::SetSnapshotMergeStatus(MergeStatus status) { + bootloader_control bootctrl; + if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false; + + bootctrl.merge_status = (unsigned int)status; + return UpdateAndSaveBootloaderControl(misc_device_, &bootctrl); +} + +MergeStatus BootControl::GetSnapshotMergeStatus() { + bootloader_control bootctrl; + if (!LoadBootloaderControl(misc_device_, &bootctrl)) return MergeStatus::UNKNOWN; + + return (MergeStatus)bootctrl.merge_status; +} + +const char* BootControl::GetSuffix(unsigned int slot) { + if (slot >= kMaxNumSlots || slot >= num_slots_) { + return nullptr; + } + return kSlotSuffixes[slot]; } -struct hw_module_methods_t BootControl_methods = { - .open = BootControl_open, -}; - -} // namespace - -boot_control_private_t HAL_MODULE_INFO_SYM = { - .base = - { - .common = - { - .tag = HARDWARE_MODULE_TAG, - .module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1, - .hal_api_version = HARDWARE_HAL_API_VERSION, - .id = BOOT_CONTROL_HARDWARE_MODULE_ID, - .name = "AOSP reference bootctrl HAL", - .author = "The Android Open Source Project", - .methods = &BootControl_methods, - }, - .init = BootControl_init, - .getNumberSlots = BootControl_getNumberSlots, - .getCurrentSlot = BootControl_getCurrentSlot, - .markBootSuccessful = BootControl_markBootSuccessful, - .setActiveBootSlot = BootControl_setActiveBootSlot, - .setSlotAsUnbootable = BootControl_setSlotAsUnbootable, - .isSlotBootable = BootControl_isSlotBootable, - .getSuffix = BootControl_getSuffix, - .isSlotMarkedSuccessful = BootControl_isSlotMarkedSuccessful, - }, - .initialized = false, - .misc_device = nullptr, - .num_slots = 0, - .current_slot = 0, -}; +} // namespace bootable +} // namespace android diff --git a/bootloader_message/Android.bp b/bootloader_message/Android.bp index 450dad08b..6443a077c 100644 --- a/bootloader_message/Android.bp +++ b/bootloader_message/Android.bp @@ -36,6 +36,18 @@ cc_library { "libbootloader_message_defaults", ], recovery_available: true, + host_supported: true, + + target: { + host: { + shared_libs: [ + "libcutils", // for strlcpy + ], + }, + darwin: { + enabled: false, + }, + } } cc_library_static { @@ -44,4 +56,5 @@ cc_library_static { "libbootloader_message_defaults", ], vendor: true, + recovery_available: true, } diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp index c1ebeaa82..b15a9b9fd 100644 --- a/bootloader_message/bootloader_message.cpp +++ b/bootloader_message/bootloader_message.cpp @@ -20,6 +20,7 @@ #include <fcntl.h> #include <string.h> +#include <optional> #include <string> #include <string_view> #include <vector> @@ -30,10 +31,14 @@ #include <android-base/unique_fd.h> #include <fstab/fstab.h> +#ifndef __ANDROID__ +#include <cutils/memory.h> // for strlcpy +#endif + using android::fs_mgr::Fstab; using android::fs_mgr::ReadDefaultFstab; -static std::string g_misc_device_for_test; +static std::optional<std::string> g_misc_device_for_test; // Exposed for test purpose. void SetMiscBlockDeviceForTest(std::string_view misc_device) { @@ -41,8 +46,8 @@ void SetMiscBlockDeviceForTest(std::string_view misc_device) { } static std::string get_misc_blk_device(std::string* err) { - if (!g_misc_device_for_test.empty()) { - return g_misc_device_for_test; + if (g_misc_device_for_test.has_value() && !g_misc_device_for_test->empty()) { + return *g_misc_device_for_test; } Fstab fstab; if (!ReadDefaultFstab(&fstab)) { @@ -179,6 +184,14 @@ bool write_bootloader_message(const std::vector<std::string>& options, std::stri return write_bootloader_message(boot, err); } +bool write_bootloader_message_to(const std::vector<std::string>& options, + const std::string& misc_blk_device, std::string* err) { + bootloader_message boot = {}; + update_bootloader_message_in_struct(&boot, options); + + return write_bootloader_message_to(boot, misc_blk_device, err); +} + bool update_bootloader_message(const std::vector<std::string>& options, std::string* err) { bootloader_message boot; if (!read_bootloader_message(&boot, err)) { @@ -197,13 +210,15 @@ bool update_bootloader_message_in_struct(bootloader_message* boot, memset(boot->recovery, 0, sizeof(boot->recovery)); strlcpy(boot->command, "boot-recovery", sizeof(boot->command)); - strlcpy(boot->recovery, "recovery\n", sizeof(boot->recovery)); + + std::string recovery = "recovery\n"; for (const auto& s : options) { - strlcat(boot->recovery, s.c_str(), sizeof(boot->recovery)); + recovery += s; if (s.back() != '\n') { - strlcat(boot->recovery, "\n", sizeof(boot->recovery)); + recovery += '\n'; } } + strlcpy(boot->recovery, recovery.c_str(), sizeof(boot->recovery)); return true; } diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h index 95dd8f4c9..b78783083 100644 --- a/bootloader_message/include/bootloader_message/bootloader_message.h +++ b/bootloader_message/include/bootloader_message/bootloader_message.h @@ -163,8 +163,10 @@ struct bootloader_control { uint8_t nb_slot : 3; // Number of times left attempting to boot recovery. uint8_t recovery_tries_remaining : 3; + // Status of any pending snapshot merge of dynamic partitions. + uint8_t merge_status : 3; // Ensure 4-bytes alignment for slot_info field. - uint8_t reserved0[2]; + uint8_t reserved0[1]; // Per-slot information. Up to 4 slots. struct slot_metadata slot_info[4]; // Reserved for further use. @@ -208,6 +210,11 @@ bool write_bootloader_message_to(const bootloader_message& boot, // set the command and recovery fields, and reset the rest. bool write_bootloader_message(const std::vector<std::string>& options, std::string* err); +// Write bootloader message (boots into recovery with the options) to the specific BCB device. Will +// set the command and recovery fields, and reset the rest. +bool write_bootloader_message_to(const std::vector<std::string>& options, + const std::string& misc_blk_device, std::string* err); + // Update bootloader message (boots into recovery with the options) to BCB. Will // only update the command and recovery fields. bool update_bootloader_message(const std::vector<std::string>& options, std::string* err); diff --git a/edify/Android.bp b/edify/Android.bp index 42947eb4e..73048d21c 100644 --- a/edify/Android.bp +++ b/edify/Android.bp @@ -16,6 +16,7 @@ cc_library_static { name: "libedify", host_supported: true, + vendor_available: true, srcs: [ "expr.cpp", diff --git a/edify/expr.cpp b/edify/expr.cpp index c090eb28a..e5e0e240a 100644 --- a/edify/expr.cpp +++ b/edify/expr.cpp @@ -421,5 +421,5 @@ Value* ErrorAbort(State* state, CauseCode cause_code, const char* format, ...) { return nullptr; } -State::State(const std::string& script, void* cookie) - : script(script), cookie(cookie), error_code(kNoError), cause_code(kNoCause) {} +State::State(const std::string& script, UpdaterInterface* interface) + : script(script), updater(interface), error_code(kNoError), cause_code(kNoCause) {} diff --git a/edify/include/edify/expr.h b/edify/include/edify/expr.h index 5cbd5e15d..cd9c70120 100644 --- a/edify/include/edify/expr.h +++ b/edify/include/edify/expr.h @@ -23,19 +23,20 @@ #include <string> #include <vector> +#include "edify/updater_interface.h" + // Forward declaration to avoid including "otautil/error_code.h". enum ErrorCode : int; enum CauseCode : int; struct State { - State(const std::string& script, void* cookie); + State(const std::string& script, UpdaterInterface* cookie); // The source of the original script. const std::string& script; - // Optional pointer to app-specific data; the core of edify never - // uses this value. - void* cookie; + // A pointer to app-specific data; the libedify doesn't use this value. + UpdaterInterface* updater; // The error message (if any) returned if the evaluation aborts. // Should be empty initially, will be either empty or a string that diff --git a/edify/include/edify/updater_interface.h b/edify/include/edify/updater_interface.h new file mode 100644 index 000000000..aa977e3c8 --- /dev/null +++ b/edify/include/edify/updater_interface.h @@ -0,0 +1,48 @@ +/* + * 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 <stdint.h> + +#include <string> +#include <string_view> + +struct ZipArchive; +typedef ZipArchive* ZipArchiveHandle; + +class UpdaterRuntimeInterface; + +class UpdaterInterface { + public: + virtual ~UpdaterInterface() = default; + + // Writes the message to command pipe, adds a new line in the end. + virtual void WriteToCommandPipe(const std::string_view message, bool flush = false) const = 0; + + // Sends over the message to recovery to print it on the screen. + virtual void UiPrint(const std::string_view message) const = 0; + + // Given the name of the block device, returns |name| for updates on the device; or the file path + // to the fake block device for simulations. + virtual std::string FindBlockDeviceName(const std::string_view name) const = 0; + + virtual UpdaterRuntimeInterface* GetRuntime() const = 0; + virtual ZipArchiveHandle GetPackageHandle() const = 0; + virtual std::string GetResult() const = 0; + virtual uint8_t* GetMappedPackageAddress() const = 0; + virtual size_t GetMappedPackageLength() const = 0; +}; diff --git a/edify/include/edify/updater_runtime_interface.h b/edify/include/edify/updater_runtime_interface.h new file mode 100644 index 000000000..d3d26da64 --- /dev/null +++ b/edify/include/edify/updater_runtime_interface.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 <string> +#include <string_view> +#include <vector> + +// This class serves as the base to updater runtime. It wraps the runtime dependent functions; and +// updates on device and host simulations can have different implementations. e.g. block devices +// during host simulation merely a temporary file. With this class, the caller side in registered +// updater's functions will stay the same for both update and simulation. +class UpdaterRuntimeInterface { + public: + virtual ~UpdaterRuntimeInterface() = default; + + // Returns true if it's a runtime instance for simulation. + virtual bool IsSimulator() const = 0; + + // Returns the value of system property |key|. If the property doesn't exist, returns + // |default_value|. + virtual std::string GetProperty(const std::string_view key, + const std::string_view default_value) const = 0; + + // Given the name of the block device, returns |name| for updates on the device; or the file path + // to the fake block device for simulations. + virtual std::string FindBlockDeviceName(const std::string_view name) const = 0; + + // Mounts the |location| on |mount_point|. Returns 0 on success. + virtual 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) = 0; + + // Returns true if |mount_point| is mounted. + virtual bool IsMounted(const std::string_view mount_point) const = 0; + + // Unmounts the |mount_point|. Returns a pair of results with the first value indicating + // if the |mount_point| is mounted, and the second value indicating the result of umount(2). + virtual std::pair<bool, int> Unmount(const std::string_view mount_point) = 0; + + // Reads |filename| and puts its value to |content|. + virtual bool ReadFileToString(const std::string_view filename, std::string* content) const = 0; + + // Updates the content of |filename| with |content|. + virtual bool WriteStringToFile(const std::string_view content, + const std::string_view filename) const = 0; + + // Wipes the first |len| bytes of block device in |filename|. + virtual int WipeBlockDevice(const std::string_view filename, size_t len) const = 0; + + // Starts a child process and runs the program with |args|. Uses vfork(2) if |is_vfork| is true. + virtual int RunProgram(const std::vector<std::string>& args, bool is_vfork) const = 0; + + // Runs tune2fs with arguments |args|. + virtual int Tune2Fs(const std::vector<std::string>& args) const = 0; + + // Dynamic partition related functions. + virtual bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) = 0; + virtual bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) = 0; + virtual bool UpdateDynamicPartitions(const std::string_view op_list_value) = 0; +}; diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp index 14f5e4bdc..202334997 100644 --- a/fastboot/fastboot.cpp +++ b/fastboot/fastboot.cpp @@ -30,10 +30,10 @@ #include "recovery_ui/ui.h" static const std::vector<std::pair<std::string, Device::BuiltinAction>> kFastbootMenuActions{ - { "Reboot system now", Device::REBOOT }, + { "Reboot system now", Device::REBOOT_FROM_FASTBOOT }, { "Enter recovery", Device::ENTER_RECOVERY }, { "Reboot to bootloader", Device::REBOOT_BOOTLOADER }, - { "Power off", Device::SHUTDOWN }, + { "Power off", Device::SHUTDOWN_FROM_FASTBOOT }, }; Device::BuiltinAction StartFastboot(Device* device, const std::vector<std::string>& /* args */) { diff --git a/fsck_unshare_blocks.cpp b/fsck_unshare_blocks.cpp index e74f8ba6f..e0b2d966b 100644 --- a/fsck_unshare_blocks.cpp +++ b/fsck_unshare_blocks.cpp @@ -34,9 +34,9 @@ #include <android-base/logging.h> #include <android-base/properties.h> #include <android-base/unique_fd.h> -#include <fstab/fstab.h> +#include <fs_mgr/roots.h> -#include "otautil/roots.h" +#include "recovery_utils/roots.h" static constexpr const char* SYSTEM_E2FSCK_BIN = "/system/bin/e2fsck_static"; static constexpr const char* TMP_E2FSCK_BIN = "/tmp/e2fsck.bin"; @@ -120,7 +120,7 @@ bool do_fsck_unshare_blocks() { std::vector<std::string> partitions = { "/odm", "/oem", "/product", "/vendor" }; // Temporarily mount system so we can copy e2fsck_static. - std::string system_root = get_system_root(); + auto system_root = android::fs_mgr::GetSystemRoot(); bool mounted = ensure_path_mounted_at(system_root, "/mnt/system") != -1; partitions.push_back(system_root); diff --git a/fuse_sideload/Android.bp b/fuse_sideload/Android.bp index 8548548d2..9bf19eb85 100644 --- a/fuse_sideload/Android.bp +++ b/fuse_sideload/Android.bp @@ -34,6 +34,10 @@ cc_library { "include", ], + static_libs: [ + "libotautil", + ], + shared_libs: [ "libbase", "libcrypto", diff --git a/fuse_sideload/fuse_provider.cpp b/fuse_sideload/fuse_provider.cpp index 58786f5f3..8fa1b5c2e 100644 --- a/fuse_sideload/fuse_provider.cpp +++ b/fuse_sideload/fuse_provider.cpp @@ -27,8 +27,11 @@ #include <functional> #include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/strings.h> #include "fuse_sideload.h" +#include "otautil/sysutil.h" FuseFileDataProvider::FuseFileDataProvider(const std::string& path, uint32_t block_size) { struct stat sb; @@ -46,6 +49,11 @@ FuseFileDataProvider::FuseFileDataProvider(const std::string& path, uint32_t blo fuse_block_size_ = block_size; } +std::unique_ptr<FuseDataProvider> FuseFileDataProvider::CreateFromFile(const std::string& path, + uint32_t block_size) { + return std::make_unique<FuseFileDataProvider>(path, block_size); +} + bool FuseFileDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, uint32_t start_block) const { uint64_t offset = static_cast<uint64_t>(start_block) * fuse_block_size_; @@ -69,3 +77,79 @@ bool FuseFileDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_ void FuseFileDataProvider::Close() { fd_.reset(); } + +FuseBlockDataProvider::FuseBlockDataProvider(uint64_t file_size, uint32_t fuse_block_size, + android::base::unique_fd&& fd, + uint32_t source_block_size, RangeSet ranges) + : FuseDataProvider(file_size, fuse_block_size), + fd_(std::move(fd)), + source_block_size_(source_block_size), + ranges_(std::move(ranges)) { + // Make sure the offset is also aligned with the blocks on the block device when we call + // ReadBlockAlignedData(). + CHECK_EQ(0, fuse_block_size_ % source_block_size_); +} + +bool FuseBlockDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, + uint32_t start_block) const { + uint64_t offset = static_cast<uint64_t>(start_block) * fuse_block_size_; + if (fetch_size > file_size_ || offset > file_size_ - fetch_size) { + LOG(ERROR) << "Out of bound read, offset: " << offset << ", fetch size: " << fetch_size + << ", file size " << file_size_; + return false; + } + + auto read_ranges = + ranges_.GetSubRanges(offset / source_block_size_, fetch_size / source_block_size_); + if (!read_ranges) { + return false; + } + + uint8_t* next_out = buffer; + for (const auto& [range_start, range_end] : read_ranges.value()) { + uint64_t bytes_start = static_cast<uint64_t>(range_start) * source_block_size_; + uint64_t bytes_to_read = static_cast<uint64_t>(range_end - range_start) * source_block_size_; + if (!android::base::ReadFullyAtOffset(fd_, next_out, bytes_to_read, bytes_start)) { + PLOG(ERROR) << "Failed to read " << bytes_to_read << " bytes at offset " << bytes_start; + return false; + } + + next_out += bytes_to_read; + } + + if (uint64_t tailing_bytes = fetch_size % source_block_size_; tailing_bytes != 0) { + // Calculate the offset to last partial block. + uint64_t tailing_offset = + read_ranges.value() + ? static_cast<uint64_t>((read_ranges->cend() - 1)->second) * source_block_size_ + : static_cast<uint64_t>(start_block) * source_block_size_; + if (!android::base::ReadFullyAtOffset(fd_, next_out, tailing_bytes, tailing_offset)) { + PLOG(ERROR) << "Failed to read tailing " << tailing_bytes << " bytes at offset " + << tailing_offset; + return false; + } + } + return true; +} + +std::unique_ptr<FuseDataProvider> FuseBlockDataProvider::CreateFromBlockMap( + const std::string& block_map_path, uint32_t fuse_block_size) { + auto block_map = BlockMapData::ParseBlockMapFile(block_map_path); + if (!block_map) { + return nullptr; + } + + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(block_map.path().c_str(), O_RDONLY))); + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << block_map.path(); + return nullptr; + } + + return std::unique_ptr<FuseBlockDataProvider>( + new FuseBlockDataProvider(block_map.file_size(), fuse_block_size, std::move(fd), + block_map.block_size(), block_map.block_ranges())); +} + +void FuseBlockDataProvider::Close() { + fd_.reset(); +} diff --git a/fuse_sideload/include/fuse_provider.h b/fuse_sideload/include/fuse_provider.h index 59059cf9b..3cdaef33d 100644 --- a/fuse_sideload/include/fuse_provider.h +++ b/fuse_sideload/include/fuse_provider.h @@ -18,10 +18,13 @@ #include <stdint.h> +#include <memory> #include <string> #include <android-base/unique_fd.h> +#include "otautil/rangeset.h" + // This is the base class to read data from source and provide the data to FUSE. class FuseDataProvider { public: @@ -41,6 +44,8 @@ class FuseDataProvider { virtual bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, uint32_t start_block) const = 0; + virtual bool Valid() const = 0; + virtual void Close() {} protected: @@ -57,10 +62,13 @@ class FuseFileDataProvider : public FuseDataProvider { public: FuseFileDataProvider(const std::string& path, uint32_t block_size); + static std::unique_ptr<FuseDataProvider> CreateFromFile(const std::string& path, + uint32_t block_size); + bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, uint32_t start_block) const override; - bool Valid() const { + bool Valid() const override { return fd_ != -1; } @@ -70,3 +78,34 @@ class FuseFileDataProvider : public FuseDataProvider { // The underlying source to read data from. android::base::unique_fd fd_; }; + +// This class parses a block map and reads data from the underlying block device. +class FuseBlockDataProvider : public FuseDataProvider { + public: + // Constructs the fuse provider from the block map. + static std::unique_ptr<FuseDataProvider> CreateFromBlockMap(const std::string& block_map_path, + uint32_t fuse_block_size); + + RangeSet ranges() const { + return ranges_; + } + + bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, + uint32_t start_block) const override; + + bool Valid() const override { + return fd_ != -1; + } + + void Close() override; + + private: + FuseBlockDataProvider(uint64_t file_size, uint32_t fuse_block_size, android::base::unique_fd&& fd, + uint32_t source_block_size, RangeSet ranges); + // The underlying block device to read data from. + android::base::unique_fd fd_; + // The block size of the source block device. + uint32_t source_block_size_; + // The block ranges from the source block device that consist of the file + RangeSet ranges_; +}; diff --git a/install/Android.bp b/install/Android.bp index ea893a075..d4606e92c 100644 --- a/install/Android.bp +++ b/install/Android.bp @@ -19,10 +19,6 @@ cc_defaults { "recovery_defaults", ], - header_libs: [ - "libminadbd_headers", - ], - shared_libs: [ "libbase", "libbootloader_message", @@ -32,7 +28,6 @@ cc_defaults { "libfusesideload", "libhidl-gen-utils", "libhidlbase", - "libhidltransport", "liblog", "libselinux", "libtinyxml2", @@ -42,12 +37,12 @@ cc_defaults { ], static_libs: [ + "librecovery_utils", "libotautil", // external dependencies "libvintf_recovery", "libvintf", - "libfstab", ], } @@ -62,11 +57,16 @@ cc_library_static { srcs: [ "adb_install.cpp", "asn1_decoder.cpp", - "fuse_sdcard_install.cpp", + "fuse_install.cpp", "install.cpp", "package.cpp", "verifier.cpp", "wipe_data.cpp", + "wipe_device.cpp", + ], + + header_libs: [ + "libminadbd_headers", ], shared_libs: [ diff --git a/install/adb_install.cpp b/install/adb_install.cpp index 9497df501..ee79a32c0 100644 --- a/install/adb_install.cpp +++ b/install/adb_install.cpp @@ -44,7 +44,7 @@ #include "fuse_sideload.h" #include "install/install.h" #include "install/wipe_data.h" -#include "minadbd_types.h" +#include "minadbd/types.h" #include "otautil/sysutil.h" #include "recovery_ui/device.h" #include "recovery_ui/ui.h" @@ -90,7 +90,7 @@ static bool WriteStatusToFd(MinadbdCommandStatus status, int fd) { // Installs the package from FUSE. Returns the installation result and whether it should continue // waiting for new commands. -static auto AdbInstallPackageHandler(RecoveryUI* ui, int* result) { +static auto AdbInstallPackageHandler(RecoveryUI* ui, InstallResult* result) { // How long (in seconds) we wait for the package path to be ready. It doesn't need to be too long // because the minadbd service has already issued an install command. FUSE_SIDELOAD_HOST_PATHNAME // will start to exist once the host connects and starts serving a package. Poll for its @@ -110,7 +110,11 @@ static auto AdbInstallPackageHandler(RecoveryUI* ui, int* result) { break; } } - *result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0, ui); + + auto package = + Package::CreateFilePackage(FUSE_SIDELOAD_HOST_PATHNAME, + std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1)); + *result = InstallPackage(package.get(), FUSE_SIDELOAD_HOST_PATHNAME, false, 0, ui); break; } @@ -120,7 +124,7 @@ static auto AdbInstallPackageHandler(RecoveryUI* ui, int* result) { return std::make_pair(*result == INSTALL_SUCCESS, should_continue); } -static auto AdbRebootHandler(MinadbdCommand command, int* result, +static auto AdbRebootHandler(MinadbdCommand command, InstallResult* result, Device::BuiltinAction* reboot_action) { // Use Device::REBOOT_{FASTBOOT,RECOVERY,RESCUE}, instead of the ones with ENTER_. This allows // rebooting back into fastboot/recovery/rescue mode through bootloader, which may use a newly @@ -331,7 +335,7 @@ static void CreateMinadbdServiceAndExecuteCommands( signal(SIGPIPE, SIG_DFL); } -int ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action) { +InstallResult ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action) { // Save the usb state to restore after the sideload operation. std::string usb_state = android::base::GetProperty("sys.usb.state", "none"); // Clean up state and stop adbd. @@ -342,7 +346,7 @@ int ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot RecoveryUI* ui = device->GetUI(); - int install_result = INSTALL_ERROR; + InstallResult install_result = INSTALL_ERROR; std::map<MinadbdCommand, CommandFunction> command_map{ { MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, ui, &install_result) }, { MinadbdCommand::kRebootAndroid, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootAndroid, diff --git a/install/fuse_sdcard_install.cpp b/install/fuse_install.cpp index 1aa8768e7..143b5d3fb 100644 --- a/install/fuse_sdcard_install.cpp +++ b/install/fuse_install.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "install/fuse_sdcard_install.h" +#include "install/fuse_install.h" #include <dirent.h> #include <signal.h> @@ -27,6 +27,7 @@ #include <algorithm> #include <functional> #include <memory> +#include <string> #include <vector> #include <android-base/logging.h> @@ -36,7 +37,7 @@ #include "fuse_provider.h" #include "fuse_sideload.h" #include "install/install.h" -#include "otautil/roots.h" +#include "recovery_utils/roots.h" static constexpr const char* SDCARD_ROOT = "/sdcard"; // How long (in seconds) we wait for the fuse-provided package file to @@ -74,7 +75,8 @@ static std::string BrowseDirectory(const std::string& path, Device* device, Reco // Skip "." and ".." entries. if (name == "." || name == "..") continue; dirs.push_back(name + "/"); - } else if (de->d_type == DT_REG && android::base::EndsWithIgnoreCase(name, ".zip")) { + } else if (de->d_type == DT_REG && (android::base::EndsWithIgnoreCase(name, ".zip") || + android::base::EndsWithIgnoreCase(name, ".map"))) { entries.push_back(name); } } @@ -119,49 +121,44 @@ static std::string BrowseDirectory(const std::string& path, Device* device, Reco // Unreachable. } -static bool StartSdcardFuse(const std::string& path) { - auto file_data_reader = std::make_unique<FuseFileDataProvider>(path, 65536); - - if (!file_data_reader->Valid()) { +static bool StartInstallPackageFuse(std::string_view path) { + if (path.empty()) { return false; } - // The installation process expects to find the sdcard unmounted. Unmount it with MNT_DETACH so - // that our open file continues to work but new references see it as unmounted. - umount2("/sdcard", MNT_DETACH); + constexpr auto FUSE_BLOCK_SIZE = 65536; + bool is_block_map = android::base::ConsumePrefix(&path, "@"); + auto fuse_data_provider = + is_block_map ? FuseBlockDataProvider::CreateFromBlockMap(std::string(path), FUSE_BLOCK_SIZE) + : FuseFileDataProvider::CreateFromFile(std::string(path), FUSE_BLOCK_SIZE); - return run_fuse_sideload(std::move(file_data_reader)) == 0; -} - -int ApplyFromSdcard(Device* device, RecoveryUI* ui) { - if (ensure_path_mounted(SDCARD_ROOT) != 0) { - LOG(ERROR) << "\n-- Couldn't mount " << SDCARD_ROOT << ".\n"; - return INSTALL_ERROR; + if (!fuse_data_provider || !fuse_data_provider->Valid()) { + LOG(ERROR) << "Failed to create fuse data provider."; + return false; } - std::string path = BrowseDirectory(SDCARD_ROOT, device, ui); - if (path.empty()) { - LOG(ERROR) << "\n-- No package file selected.\n"; - ensure_path_unmounted(SDCARD_ROOT); - return INSTALL_ERROR; + if (android::base::StartsWith(path, SDCARD_ROOT)) { + // The installation process expects to find the sdcard unmounted. Unmount it with MNT_DETACH so + // that our open file continues to work but new references see it as unmounted. + umount2(SDCARD_ROOT, MNT_DETACH); } - ui->Print("\n-- Install %s ...\n", path.c_str()); - SetSdcardUpdateBootloaderMessage(); + return run_fuse_sideload(std::move(fuse_data_provider)) == 0; +} +InstallResult InstallWithFuseFromPath(std::string_view path, RecoveryUI* ui) { // We used to use fuse in a thread as opposed to a process. Since accessing // through fuse involves going from kernel to userspace to kernel, it leads // to deadlock when a page fault occurs. (Bug: 26313124) pid_t child; if ((child = fork()) == 0) { - bool status = StartSdcardFuse(path); + bool status = StartInstallPackageFuse(path); _exit(status ? EXIT_SUCCESS : EXIT_FAILURE); } - // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the fuse in child - // process is ready. - int result = INSTALL_ERROR; + // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the fuse in child process is ready. + InstallResult result = INSTALL_ERROR; int status; bool waited = false; for (int i = 0; i < SDCARD_INSTALL_TIMEOUT; ++i) { @@ -183,8 +180,11 @@ int ApplyFromSdcard(Device* device, RecoveryUI* ui) { break; } } - - result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0 /*retry_count*/, ui); + auto package = + Package::CreateFilePackage(FUSE_SIDELOAD_HOST_PATHNAME, + std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1)); + result = + InstallPackage(package.get(), FUSE_SIDELOAD_HOST_PATHNAME, false, 0 /* retry_count */, ui); break; } @@ -201,6 +201,32 @@ int ApplyFromSdcard(Device* device, RecoveryUI* ui) { LOG(ERROR) << "Error exit from the fuse process: " << WEXITSTATUS(status); } + return result; +} + +InstallResult ApplyFromSdcard(Device* device) { + auto ui = device->GetUI(); + if (ensure_path_mounted(SDCARD_ROOT) != 0) { + LOG(ERROR) << "\n-- Couldn't mount " << SDCARD_ROOT << ".\n"; + return INSTALL_ERROR; + } + + std::string path = BrowseDirectory(SDCARD_ROOT, device, ui); + if (path.empty()) { + LOG(ERROR) << "\n-- No package file selected.\n"; + ensure_path_unmounted(SDCARD_ROOT); + return INSTALL_ERROR; + } + + // Hint the install function to read from a block map file. + if (android::base::EndsWithIgnoreCase(path, ".map")) { + path = "@" + path; + } + + ui->Print("\n-- Install %s ...\n", path.c_str()); + SetSdcardUpdateBootloaderMessage(); + + auto result = InstallWithFuseFromPath(path, ui); ensure_path_unmounted(SDCARD_ROOT); return result; } diff --git a/install/include/install/adb_install.h b/install/include/install/adb_install.h index 3a0a81747..880022361 100644 --- a/install/include/install/adb_install.h +++ b/install/include/install/adb_install.h @@ -16,9 +16,10 @@ #pragma once -#include <recovery_ui/device.h> +#include "install/install.h" +#include "recovery_ui/device.h" -// Applies a package via `adb sideload` or `adb rescue`. Returns the install result (in `enum -// InstallResult`). When a reboot has been requested, INSTALL_REBOOT will be the return value, with -// the reboot target set in reboot_action. -int ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action); +// Applies a package via `adb sideload` or `adb rescue`. Returns the install result. When a reboot +// has been requested, INSTALL_REBOOT will be the return value, with the reboot target set in +// reboot_action. +InstallResult ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action); diff --git a/install/include/install/fuse_install.h b/install/include/install/fuse_install.h new file mode 100644 index 000000000..63b116aeb --- /dev/null +++ b/install/include/install/fuse_install.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <string_view> + +#include "install/install.h" +#include "recovery_ui/device.h" +#include "recovery_ui/ui.h" + +// Starts FUSE with the package from |path| as the data source. And installs the package from +// |FUSE_SIDELOAD_HOST_PATHNAME|. The |path| can point to the location of a package zip file or a +// block map file with the prefix '@'; e.g. /sdcard/package.zip, @/cache/recovery/block.map. +InstallResult InstallWithFuseFromPath(std::string_view path, RecoveryUI* ui); + +InstallResult ApplyFromSdcard(Device* device); diff --git a/install/include/install/install.h b/install/include/install/install.h index c0a8f1f4c..b4b3a9149 100644 --- a/install/include/install/install.h +++ b/install/include/install/install.h @@ -44,11 +44,12 @@ enum class OtaType { BRICK, }; -// Installs the given update package. This function should also wipe the cache partition after a -// successful installation if |should_wipe_cache| is true or an updater command asks to wipe the -// cache. -int install_package(const std::string& package, bool should_wipe_cache, bool needs_mount, - int retry_count, RecoveryUI* ui); +// Installs the given update package. The package_id is a string provided by the caller (e.g. the +// package path) to identify the package and log to last_install. This function should also wipe the +// cache partition after a successful installation if |should_wipe_cache| is true or an updater +// command asks to wipe the cache. +InstallResult InstallPackage(Package* package, const std::string_view package_id, + bool should_wipe_cache, int retry_count, RecoveryUI* ui); // Verifies the package by ota keys. Returns true if the package is verified successfully, // otherwise returns false. @@ -58,14 +59,11 @@ bool verify_package(Package* package, RecoveryUI* ui); // result to |metadata|. Return true if succeed, otherwise return false. bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata); -// Reads the "recovery.wipe" entry in the zip archive returns a list of partitions to wipe. -std::vector<std::string> GetWipePartitionList(Package* wipe_package); - // Verifies the compatibility info in a Treble-compatible package. Returns true directly if the // entry doesn't exist. bool verify_package_compatibility(ZipArchiveHandle package_zip); -// Checks if the the metadata in the OTA package has expected values. Returns 0 on success. -// Mandatory checks: ota-type, pre-device and serial number(if presents) -// AB OTA specific checks: pre-build version, fingerprint, timestamp. -int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type); +// Checks if the metadata in the OTA package has expected values. Mandatory checks: ota-type, +// pre-device and serial number (if presents). A/B OTA specific checks: pre-build version, +// fingerprint, timestamp. +bool CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type); diff --git a/install/include/install/package.h b/install/include/install/package.h index cd44d10be..0b4233238 100644 --- a/install/include/install/package.h +++ b/install/include/install/package.h @@ -28,6 +28,11 @@ #include "verifier.h" +enum class PackageType { + kMemory, + kFile, +}; + // This class serves as a wrapper for an OTA update package. It aims to provide the common // interface for both packages loaded in memory and packages read from fd. class Package : public VerifierInterface { @@ -41,6 +46,10 @@ class Package : public VerifierInterface { virtual ~Package() = default; + virtual PackageType GetType() const = 0; + + virtual std::string GetPath() const = 0; + // Opens the package as a zip file and returns the ZipArchiveHandle. virtual ZipArchiveHandle GetZipArchiveHandle() = 0; diff --git a/install/include/install/fuse_sdcard_install.h b/install/include/install/wipe_device.h index d9214ca3b..c60b99997 100644 --- a/install/include/install/fuse_sdcard_install.h +++ b/install/include/install/wipe_device.h @@ -16,7 +16,14 @@ #pragma once +#include <string> +#include <vector> + +#include "install/package.h" #include "recovery_ui/device.h" -#include "recovery_ui/ui.h" -int ApplyFromSdcard(Device* device, RecoveryUI* ui); +// Wipes the current A/B device, with a secure wipe of all the partitions in RECOVERY_WIPE. +bool WipeAbDevice(Device* device, size_t wipe_package_size); + +// Reads the "recovery.wipe" entry in the zip archive returns a list of partitions to wipe. +std::vector<std::string> GetWipePartitionList(Package* wipe_package); diff --git a/install/include/private/setup_commands.h b/install/include/private/setup_commands.h index 7fdc741d6..dcff76112 100644 --- a/install/include/private/setup_commands.h +++ b/install/include/private/setup_commands.h @@ -27,13 +27,13 @@ // |zip| located at |package|. Stores the command line that should be called into |cmd|. The // |status_fd| is the file descriptor the child process should use to report back the progress of // the update. -int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count, - int status_fd, std::vector<std::string>* cmd); +bool SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count, + int status_fd, std::vector<std::string>* cmd); // Sets up the commands for an A/B update. Extracts the needed entries from the open zip archive // |zip| located at |package|. Stores the command line that should be called into |cmd|. The // |status_fd| is the file descriptor the child process should use to report back the progress of // the update. Note that since this applies to the sideloading flow only, it takes one less // parameter |retry_count| than the non-A/B version. -int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd, - std::vector<std::string>* cmd); +bool SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd, + std::vector<std::string>* cmd); diff --git a/install/install.cpp b/install/install.cpp index e2d470096..9166f9cfb 100644 --- a/install/install.cpp +++ b/install/install.cpp @@ -51,16 +51,17 @@ #include "install/wipe_data.h" #include "otautil/error_code.h" #include "otautil/paths.h" -#include "otautil/roots.h" #include "otautil/sysutil.h" -#include "otautil/thermalutil.h" #include "private/setup_commands.h" #include "recovery_ui/ui.h" +#include "recovery_utils/roots.h" +#include "recovery_utils/thermalutil.h" using namespace std::chrono_literals; static constexpr int kRecoveryApiVersion = 3; -// Assert the version defined in code and in Android.mk are consistent. +// We define RECOVERY_API_VERSION in Android.mk, which will be picked up by build system and packed +// into target_files.zip. Assert the version defined in code and in Android.mk are consistent. static_assert(kRecoveryApiVersion == RECOVERY_API_VERSION, "Mismatching recovery API versions."); // Default allocation of progress bar segments to operations @@ -73,9 +74,8 @@ bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::st CHECK(metadata != nullptr); static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata"; - ZipString path(METADATA_PATH); ZipEntry entry; - if (FindEntry(zip, path, &entry) != 0) { + if (FindEntry(zip, METADATA_PATH, &entry) != 0) { LOG(ERROR) << "Failed to find " << METADATA_PATH; return false; } @@ -139,14 +139,14 @@ static void ReadSourceTargetBuild(const std::map<std::string, std::string>& meta // Checks the build version, fingerprint and timestamp in the metadata of the A/B package. // Downgrading is not allowed unless explicitly enabled in the package and only for // incremental packages. -static int CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata) { +static bool CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata) { // Incremental updates should match the current build. auto device_pre_build = android::base::GetProperty("ro.build.version.incremental", ""); auto pkg_pre_build = get_value(metadata, "pre-build-incremental"); if (!pkg_pre_build.empty() && pkg_pre_build != device_pre_build) { LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected " << device_pre_build; - return INSTALL_ERROR; + return false; } auto device_fingerprint = android::base::GetProperty("ro.build.fingerprint", ""); @@ -154,7 +154,7 @@ static int CheckAbSpecificMetadata(const std::map<std::string, std::string>& met if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != device_fingerprint) { LOG(ERROR) << "Package is for source build " << pkg_pre_build_fingerprint << " but expected " << device_fingerprint; - return INSTALL_ERROR; + return false; } // Check for downgrade version. @@ -172,36 +172,36 @@ static int CheckAbSpecificMetadata(const std::map<std::string, std::string>& met "newer than timestamp " << build_timestamp << " but package has timestamp " << pkg_post_timestamp << " and downgrade not allowed."; - return INSTALL_ERROR; + return false; } if (pkg_pre_build_fingerprint.empty()) { LOG(ERROR) << "Downgrade package must have a pre-build version set, not allowed."; - return INSTALL_ERROR; + return false; } } - return 0; + return true; } -int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type) { +bool CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type) { auto package_ota_type = get_value(metadata, "ota-type"); auto expected_ota_type = OtaTypeToString(ota_type); if (ota_type != OtaType::AB && ota_type != OtaType::BRICK) { LOG(INFO) << "Skip package metadata check for ota type " << expected_ota_type; - return 0; + return true; } if (package_ota_type != expected_ota_type) { LOG(ERROR) << "Unexpected ota package type, expects " << expected_ota_type << ", actual " << package_ota_type; - return INSTALL_ERROR; + return false; } auto device = android::base::GetProperty("ro.product.device", ""); auto pkg_device = get_value(metadata, "pre-device"); if (pkg_device != device || pkg_device.empty()) { LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << device; - return INSTALL_ERROR; + return false; } // We allow the package to not have any serialno; and we also allow it to carry multiple serial @@ -218,7 +218,7 @@ int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, Ota } if (!serial_number_match) { LOG(ERROR) << "Package is for serial " << pkg_serial_no; - return INSTALL_ERROR; + return false; } } @@ -226,21 +226,20 @@ int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, Ota return CheckAbSpecificMetadata(metadata); } - return 0; + return true; } -int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd, - std::vector<std::string>* cmd) { +bool SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd, + std::vector<std::string>* cmd) { CHECK(cmd != nullptr); // For A/B updates we extract the payload properties to a buffer and obtain the RAW payload offset // in the zip file. static constexpr const char* AB_OTA_PAYLOAD_PROPERTIES = "payload_properties.txt"; - ZipString property_name(AB_OTA_PAYLOAD_PROPERTIES); ZipEntry properties_entry; - if (FindEntry(zip, property_name, &properties_entry) != 0) { + if (FindEntry(zip, AB_OTA_PAYLOAD_PROPERTIES, &properties_entry) != 0) { LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD_PROPERTIES; - return INSTALL_CORRUPT; + return false; } uint32_t properties_entry_length = properties_entry.uncompressed_length; std::vector<uint8_t> payload_properties(properties_entry_length); @@ -248,15 +247,14 @@ int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int ExtractToMemory(zip, &properties_entry, payload_properties.data(), properties_entry_length); if (err != 0) { LOG(ERROR) << "Failed to extract " << AB_OTA_PAYLOAD_PROPERTIES << ": " << ErrorCodeString(err); - return INSTALL_CORRUPT; + return false; } static constexpr const char* AB_OTA_PAYLOAD = "payload.bin"; - ZipString payload_name(AB_OTA_PAYLOAD); ZipEntry payload_entry; - if (FindEntry(zip, payload_name, &payload_entry) != 0) { + if (FindEntry(zip, AB_OTA_PAYLOAD, &payload_entry) != 0) { LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD; - return INSTALL_CORRUPT; + return false; } long payload_offset = payload_entry.offset; *cmd = { @@ -266,20 +264,19 @@ int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int "--headers=" + std::string(payload_properties.begin(), payload_properties.end()), android::base::StringPrintf("--status_fd=%d", status_fd), }; - return 0; + return true; } -int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count, - int status_fd, std::vector<std::string>* cmd) { +bool SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count, + int status_fd, std::vector<std::string>* cmd) { CHECK(cmd != nullptr); // In non-A/B updates we extract the update binary from the package. static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary"; - ZipString binary_name(UPDATE_BINARY_NAME); ZipEntry binary_entry; - if (FindEntry(zip, binary_name, &binary_entry) != 0) { + if (FindEntry(zip, UPDATE_BINARY_NAME, &binary_entry) != 0) { LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME; - return INSTALL_CORRUPT; + return false; } const std::string binary_path = Paths::Get().temporary_update_binary(); @@ -288,13 +285,12 @@ int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, i open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755)); if (fd == -1) { PLOG(ERROR) << "Failed to create " << binary_path; - return INSTALL_ERROR; + return false; } - int32_t error = ExtractEntryToFile(zip, &binary_entry, fd); - if (error != 0) { + if (auto error = ExtractEntryToFile(zip, &binary_entry, fd); error != 0) { LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error); - return INSTALL_ERROR; + return false; } // When executing the update binary contained in the package, the arguments passed are: @@ -311,7 +307,7 @@ int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, i if (retry_count > 0) { cmd->push_back("retry"); } - return 0; + return true; } static void log_max_temperature(int* max_temperature, const std::atomic<bool>& logger_finished) { @@ -325,21 +321,25 @@ static void log_max_temperature(int* max_temperature, const std::atomic<bool>& l } // If the package contains an update binary, extract it and run it. -static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache, - std::vector<std::string>* log_buffer, int retry_count, - int* max_temperature, RecoveryUI* ui) { +static InstallResult TryUpdateBinary(Package* package, bool* wipe_cache, + std::vector<std::string>* log_buffer, int retry_count, + int* max_temperature, RecoveryUI* ui) { std::map<std::string, std::string> metadata; + auto zip = package->GetZipArchiveHandle(); if (!ReadMetadataFromPackage(zip, &metadata)) { LOG(ERROR) << "Failed to parse metadata in the zip file"; return INSTALL_CORRUPT; } bool is_ab = android::base::GetBoolProperty("ro.build.ab_update", false); - // Verifies against the metadata in the package first. - if (int check_status = is_ab ? CheckPackageMetadata(metadata, OtaType::AB) : 0; - check_status != 0) { + if (is_ab) { + CHECK(package->GetType() == PackageType::kFile); + } + + // Verify against the metadata in the package first. + if (is_ab && !CheckPackageMetadata(metadata, OtaType::AB)) { log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure)); - return check_status; + return INSTALL_ERROR; } ReadSourceTargetBuild(metadata, log_buffer); @@ -385,13 +385,15 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b // updater requests logging the string (e.g. cause of the failure). // + std::string package_path = package->GetPath(); + std::vector<std::string> args; - if (int update_status = - is_ab ? SetUpAbUpdateCommands(package, zip, pipe_write.get(), &args) - : SetUpNonAbUpdateCommands(package, zip, retry_count, pipe_write.get(), &args); - update_status != 0) { + if (auto setup_result = + is_ab ? SetUpAbUpdateCommands(package_path, zip, pipe_write.get(), &args) + : SetUpNonAbUpdateCommands(package_path, zip, retry_count, pipe_write.get(), &args); + !setup_result) { log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure)); - return update_status; + return INSTALL_CORRUPT; } pid_t pid = fork(); @@ -490,11 +492,11 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b } if (WIFEXITED(status)) { if (WEXITSTATUS(status) != EXIT_SUCCESS) { - LOG(ERROR) << "Error in " << package << " (status " << WEXITSTATUS(status) << ")"; + LOG(ERROR) << "Error in " << package_path << " (status " << WEXITSTATUS(status) << ")"; return INSTALL_ERROR; } } else if (WIFSIGNALED(status)) { - LOG(ERROR) << "Error in " << package << " (killed by signal " << WTERMSIG(status) << ")"; + LOG(ERROR) << "Error in " << package_path << " (killed by signal " << WTERMSIG(status) << ")"; return INSTALL_ERROR; } else { LOG(FATAL) << "Invalid status code " << status; @@ -503,16 +505,15 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b return INSTALL_SUCCESS; } -// Verifes the compatibility info in a Treble-compatible package. Returns true directly if the +// Verifies the compatibility info in a Treble-compatible package. Returns true directly if the // entry doesn't exist. Note that the compatibility info is packed in a zip file inside the OTA // package. bool verify_package_compatibility(ZipArchiveHandle package_zip) { LOG(INFO) << "Verifying package compatibility..."; static constexpr const char* COMPATIBILITY_ZIP_ENTRY = "compatibility.zip"; - ZipString compatibility_entry_name(COMPATIBILITY_ZIP_ENTRY); ZipEntry compatibility_entry; - if (FindEntry(package_zip, compatibility_entry_name, &compatibility_entry) != 0) { + if (FindEntry(package_zip, COMPATIBILITY_ZIP_ENTRY, &compatibility_entry) != 0) { LOG(INFO) << "Package doesn't contain " << COMPATIBILITY_ZIP_ENTRY << " entry"; return true; } @@ -536,7 +537,7 @@ bool verify_package_compatibility(ZipArchiveHandle package_zip) { // Iterate all the entries inside COMPATIBILITY_ZIP_ENTRY and read the contents. void* cookie; - ret = StartIteration(zip_handle, &cookie, nullptr, nullptr); + ret = StartIteration(zip_handle, &cookie); if (ret != 0) { LOG(ERROR) << "Failed to start iterating zip entries: " << ErrorCodeString(ret); CloseArchive(zip_handle); @@ -546,13 +547,13 @@ bool verify_package_compatibility(ZipArchiveHandle package_zip) { std::vector<std::string> compatibility_info; ZipEntry info_entry; - ZipString info_name; + std::string_view info_name; while (Next(cookie, &info_entry, &info_name) == 0) { std::string content(info_entry.uncompressed_length, '\0'); int32_t ret = ExtractToMemory(zip_handle, &info_entry, reinterpret_cast<uint8_t*>(&content[0]), info_entry.uncompressed_length); if (ret != 0) { - LOG(ERROR) << "Failed to read " << info_name.name << ": " << ErrorCodeString(ret); + LOG(ERROR) << "Failed to read " << info_name << ": " << ErrorCodeString(ret); CloseArchive(zip_handle); return false; } @@ -571,36 +572,16 @@ bool verify_package_compatibility(ZipArchiveHandle package_zip) { return false; } -static int really_install_package(const std::string& path, bool* wipe_cache, bool needs_mount, - std::vector<std::string>* log_buffer, int retry_count, - int* max_temperature, RecoveryUI* ui) { +static InstallResult VerifyAndInstallPackage(Package* package, bool* wipe_cache, + std::vector<std::string>* log_buffer, int retry_count, + int* max_temperature, RecoveryUI* ui) { ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); - ui->Print("Finding update package...\n"); // Give verification half the progress bar... ui->SetProgressType(RecoveryUI::DETERMINATE); ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME); - LOG(INFO) << "Update location: " << path; - - // Map the update package into memory. - ui->Print("Opening update package...\n"); - - if (needs_mount) { - if (path[0] == '@') { - ensure_path_mounted(path.substr(1)); - } else { - ensure_path_mounted(path); - } - } - - auto package = Package::CreateMemoryPackage( - path, std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1)); - if (!package) { - log_buffer->push_back(android::base::StringPrintf("error: %d", kMapFileFailure)); - return INSTALL_CORRUPT; - } // Verify package. - if (!verify_package(package.get(), ui)) { + if (!verify_package(package, ui)) { log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure)); return INSTALL_CORRUPT; } @@ -624,32 +605,37 @@ static int really_install_package(const std::string& path, bool* wipe_cache, boo ui->Print("Retry attempt: %d\n", retry_count); } ui->SetEnableReboot(false); - int result = - try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature, ui); + auto result = TryUpdateBinary(package, wipe_cache, log_buffer, retry_count, max_temperature, ui); ui->SetEnableReboot(true); ui->Print("\n"); return result; } -int install_package(const std::string& path, bool should_wipe_cache, bool needs_mount, - int retry_count, RecoveryUI* ui) { - CHECK(!path.empty()); - +InstallResult InstallPackage(Package* package, const std::string_view package_id, + bool should_wipe_cache, int retry_count, RecoveryUI* ui) { auto start = std::chrono::system_clock::now(); int start_temperature = GetMaxValueFromThermalZone(); int max_temperature = start_temperature; - int result; + InstallResult result; std::vector<std::string> log_buffer; - if (setup_install_mounts() != 0) { + + ui->Print("Supported API: %d\n", kRecoveryApiVersion); + + ui->Print("Finding update package...\n"); + LOG(INFO) << "Update package id: " << package_id; + if (!package) { + log_buffer.push_back(android::base::StringPrintf("error: %d", kMapFileFailure)); + result = INSTALL_CORRUPT; + } else if (setup_install_mounts() != 0) { LOG(ERROR) << "failed to set up expected mounts for install; aborting"; result = INSTALL_ERROR; } else { bool updater_wipe_cache = false; - result = really_install_package(path, &updater_wipe_cache, needs_mount, &log_buffer, - retry_count, &max_temperature, ui); + result = VerifyAndInstallPackage(package, &updater_wipe_cache, &log_buffer, retry_count, + &max_temperature, ui); should_wipe_cache = should_wipe_cache || updater_wipe_cache; } @@ -677,7 +663,7 @@ int install_package(const std::string& path, bool should_wipe_cache, bool needs_ // The first two lines need to be the package name and install result. std::vector<std::string> log_header = { - path, + std::string(package_id), result == INSTALL_SUCCESS ? "1" : "0", "time_total: " + std::to_string(time_total), "retry: " + std::to_string(retry_count), diff --git a/install/package.cpp b/install/package.cpp index 4402f4855..86fc0647d 100644 --- a/install/package.cpp +++ b/install/package.cpp @@ -40,12 +40,20 @@ class MemoryPackage : public Package { ~MemoryPackage() override; + PackageType GetType() const override { + return PackageType::kMemory; + } + // Memory maps the package file if necessary. Initializes the start address and size of the // package. uint64_t GetPackageSize() const override { return package_size_; } + std::string GetPath() const override { + return path_; + } + bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) override; ZipArchiveHandle GetZipArchiveHandle() override; @@ -82,10 +90,18 @@ class FilePackage : public Package { ~FilePackage() override; + PackageType GetType() const override { + return PackageType::kFile; + } + uint64_t GetPackageSize() const override { return package_size_; } + std::string GetPath() const override { + return path_; + } + bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) override; ZipArchiveHandle GetZipArchiveHandle() override; @@ -253,7 +269,7 @@ ZipArchiveHandle FilePackage::GetZipArchiveHandle() { return zip_handle_; } - if (auto err = OpenArchiveFd(fd_.get(), path_.c_str(), &zip_handle_); err != 0) { + if (auto err = OpenArchiveFd(fd_.get(), path_.c_str(), &zip_handle_, false); err != 0) { LOG(ERROR) << "Can't open package" << path_ << " : " << ErrorCodeString(err); return nullptr; } diff --git a/install/verifier.cpp b/install/verifier.cpp index 6ba1d77c3..ab750442d 100644 --- a/install/verifier.cpp +++ b/install/verifier.cpp @@ -311,8 +311,7 @@ int verify_file(VerifierInterface* package, const std::vector<Certificate>& keys static std::vector<Certificate> IterateZipEntriesAndSearchForKeys(const ZipArchiveHandle& handle) { void* cookie; - ZipString suffix("x509.pem"); - int32_t iter_status = StartIteration(handle, &cookie, nullptr, &suffix); + int32_t iter_status = StartIteration(handle, &cookie, "", "x509.pem"); if (iter_status != 0) { LOG(ERROR) << "Failed to iterate over entries in the certificate zipfile: " << ErrorCodeString(iter_status); @@ -321,22 +320,21 @@ static std::vector<Certificate> IterateZipEntriesAndSearchForKeys(const ZipArchi std::vector<Certificate> result; - ZipString name; + std::string_view name; ZipEntry entry; while ((iter_status = Next(cookie, &entry, &name)) == 0) { std::vector<uint8_t> pem_content(entry.uncompressed_length); if (int32_t extract_status = ExtractToMemory(handle, &entry, pem_content.data(), pem_content.size()); extract_status != 0) { - LOG(ERROR) << "Failed to extract " << std::string(name.name, name.name + name.name_length); + LOG(ERROR) << "Failed to extract " << name; return {}; } Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); // Aborts the parsing if we fail to load one of the key file. if (!LoadCertificateFromBuffer(pem_content, &cert)) { - LOG(ERROR) << "Failed to load keys from " - << std::string(name.name, name.name + name.name_length); + LOG(ERROR) << "Failed to load keys from " << name; return {}; } diff --git a/install/wipe_data.cpp b/install/wipe_data.cpp index 765a8152b..82660bef0 100644 --- a/install/wipe_data.cpp +++ b/install/wipe_data.cpp @@ -28,9 +28,9 @@ #include <android-base/stringprintf.h> #include "otautil/dirutil.h" -#include "otautil/logging.h" -#include "otautil/roots.h" #include "recovery_ui/ui.h" +#include "recovery_utils/logging.h" +#include "recovery_utils/roots.h" constexpr const char* CACHE_ROOT = "/cache"; constexpr const char* DATA_ROOT = "/data"; @@ -120,4 +120,4 @@ bool WipeData(Device* device, bool convert_fbe) { } ui->Print("Data wipe %s.\n", success ? "complete" : "failed"); return success; -}
\ No newline at end of file +} diff --git a/install/wipe_device.cpp b/install/wipe_device.cpp new file mode 100644 index 000000000..89d5d31a3 --- /dev/null +++ b/install/wipe_device.cpp @@ -0,0 +1,197 @@ +/* + * 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 "install/wipe_device.h" + +#include <errno.h> +#include <fcntl.h> +#include <linux/fs.h> +#include <stdint.h> +#include <sys/ioctl.h> + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/strings.h> +#include <android-base/unique_fd.h> +#include <ziparchive/zip_archive.h> + +#include "bootloader_message/bootloader_message.h" +#include "install/install.h" +#include "install/package.h" +#include "recovery_ui/device.h" +#include "recovery_ui/ui.h" + +std::vector<std::string> GetWipePartitionList(Package* wipe_package) { + ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle(); + if (!zip) { + LOG(ERROR) << "Failed to get ZipArchiveHandle"; + return {}; + } + + constexpr char RECOVERY_WIPE_ENTRY_NAME[] = "recovery.wipe"; + + std::string partition_list_content; + ZipEntry entry; + if (FindEntry(zip, RECOVERY_WIPE_ENTRY_NAME, &entry) == 0) { + uint32_t length = entry.uncompressed_length; + partition_list_content = std::string(length, '\0'); + if (auto err = ExtractToMemory( + zip, &entry, reinterpret_cast<uint8_t*>(partition_list_content.data()), length); + err != 0) { + LOG(ERROR) << "Failed to extract " << RECOVERY_WIPE_ENTRY_NAME << ": " + << ErrorCodeString(err); + return {}; + } + } else { + LOG(INFO) << "Failed to find " << RECOVERY_WIPE_ENTRY_NAME + << ", falling back to use the partition list on device."; + + constexpr char RECOVERY_WIPE_ON_DEVICE[] = "/etc/recovery.wipe"; + if (!android::base::ReadFileToString(RECOVERY_WIPE_ON_DEVICE, &partition_list_content)) { + PLOG(ERROR) << "failed to read \"" << RECOVERY_WIPE_ON_DEVICE << "\""; + return {}; + } + } + + std::vector<std::string> result; + auto lines = android::base::Split(partition_list_content, "\n"); + for (const auto& line : lines) { + auto partition = android::base::Trim(line); + // Ignore '#' comment or empty lines. + if (android::base::StartsWith(partition, "#") || partition.empty()) { + continue; + } + result.push_back(line); + } + + return result; +} + +// Secure-wipes a given partition. It uses BLKSECDISCARD, if supported. Otherwise, it goes with +// BLKDISCARD (if device supports BLKDISCARDZEROES) or BLKZEROOUT. +static bool SecureWipePartition(const std::string& partition) { + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY))); + if (fd == -1) { + PLOG(ERROR) << "Failed to open \"" << partition << "\""; + return false; + } + + uint64_t range[2] = { 0, 0 }; + if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) { + PLOG(ERROR) << "Failed to get partition size"; + return false; + } + LOG(INFO) << "Secure-wiping \"" << partition << "\" from " << range[0] << " to " << range[1]; + + LOG(INFO) << " Trying BLKSECDISCARD..."; + if (ioctl(fd, BLKSECDISCARD, &range) == -1) { + PLOG(WARNING) << " Failed"; + + // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT. + unsigned int zeroes; + if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) { + LOG(INFO) << " Trying BLKDISCARD..."; + if (ioctl(fd, BLKDISCARD, &range) == -1) { + PLOG(ERROR) << " Failed"; + return false; + } + } else { + LOG(INFO) << " Trying BLKZEROOUT..."; + if (ioctl(fd, BLKZEROOUT, &range) == -1) { + PLOG(ERROR) << " Failed"; + return false; + } + } + } + + LOG(INFO) << " Done"; + return true; +} + +static std::unique_ptr<Package> ReadWipePackage(size_t wipe_package_size) { + if (wipe_package_size == 0) { + LOG(ERROR) << "wipe_package_size is zero"; + return nullptr; + } + + std::string wipe_package; + if (std::string err_str; !read_wipe_package(&wipe_package, wipe_package_size, &err_str)) { + PLOG(ERROR) << "Failed to read wipe package" << err_str; + return nullptr; + } + + return Package::CreateMemoryPackage( + std::vector<uint8_t>(wipe_package.begin(), wipe_package.end()), nullptr); +} + +// Checks if the wipe package matches expectation. If the check passes, reads the list of +// partitions to wipe from the package. Checks include +// 1. verify the package. +// 2. check metadata (ota-type, pre-device and serial number if having one). +static bool CheckWipePackage(Package* wipe_package, RecoveryUI* ui) { + if (!verify_package(wipe_package, ui)) { + LOG(ERROR) << "Failed to verify package"; + return false; + } + + ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle(); + if (!zip) { + LOG(ERROR) << "Failed to get ZipArchiveHandle"; + return false; + } + + std::map<std::string, std::string> metadata; + if (!ReadMetadataFromPackage(zip, &metadata)) { + LOG(ERROR) << "Failed to parse metadata in the zip file"; + return false; + } + + return CheckPackageMetadata(metadata, OtaType::BRICK); +} + +bool WipeAbDevice(Device* device, size_t wipe_package_size) { + auto ui = device->GetUI(); + ui->SetBackground(RecoveryUI::ERASING); + ui->SetProgressType(RecoveryUI::INDETERMINATE); + + auto wipe_package = ReadWipePackage(wipe_package_size); + if (!wipe_package) { + LOG(ERROR) << "Failed to open wipe package"; + return false; + } + + if (!CheckWipePackage(wipe_package.get(), ui)) { + LOG(ERROR) << "Failed to verify wipe package"; + return false; + } + + auto partition_list = GetWipePartitionList(wipe_package.get()); + if (partition_list.empty()) { + LOG(ERROR) << "Empty wipe ab partition list"; + return false; + } + + for (const auto& partition : partition_list) { + // Proceed anyway even if it fails to wipe some partition. + SecureWipePartition(partition); + } + return true; +} diff --git a/minadbd/Android.bp b/minadbd/Android.bp index 007e5057b..b7075e670 100644 --- a/minadbd/Android.bp +++ b/minadbd/Android.bp @@ -26,6 +26,10 @@ cc_defaults { include_dirs: [ "system/core/adb", ], + + header_libs: [ + "libminadbd_headers", + ], } // `libminadbd_services` is analogous to the `libadbd_services` for regular `adbd`, but providing @@ -36,6 +40,7 @@ cc_library { defaults: [ "minadbd_defaults", + "librecovery_utils_defaults", ], srcs: [ @@ -43,6 +48,11 @@ cc_library { "minadbd_services.cpp", ], + static_libs: [ + "librecovery_utils", + "libotautil", + ], + shared_libs: [ "libadbd", "libbase", @@ -54,9 +64,12 @@ cc_library { cc_library_headers { name: "libminadbd_headers", recovery_available: true, - // TODO create a include dir export_include_dirs: [ - ".", + "include", + ], + // adb_install.cpp + visibility: [ + "//bootable/recovery/install", ], } @@ -86,6 +99,7 @@ cc_test { defaults: [ "minadbd_defaults", + "librecovery_utils_defaults", ], srcs: [ @@ -96,12 +110,14 @@ cc_test { static_libs: [ "libminadbd_services", "libfusesideload", + "librecovery_utils", + "libotautil", "libadbd", - "libcrypto", ], shared_libs: [ "libbase", + "libcrypto", "libcutils", "liblog", ], diff --git a/minadbd/README.md b/minadbd/README.md index 5a0a067de..9a1958309 100644 --- a/minadbd/README.md +++ b/minadbd/README.md @@ -1,8 +1,24 @@ -minadbd is now mostly built from libadbd. The fuse features are unique to -minadbd, and services.c has been modified as follows: - - - all services removed - - all host mode support removed - - `sideload_service()` added; this is the only service supported. It - receives a single blob of data, writes it to a fixed filename, and - makes the process exit. +minadbd +======= + +`minadbd` is analogous to the regular `adbd`, but providing the minimal services to support +recovery-specific use cases. Generally speaking, `adbd` = `libadbd` + `libadbd_services`, whereas +`minadbd` = `libadbd` + `libminadbd_services`. + +Although both modules may be installed into the recovery image, only one of them, or none, can be +active at any given time. + +- The start / stop of `adbd` is managed via system property `sys.usb.config`, when setting to `adb` + or `none` respectively. Upon starting recovery mode, `adbd` is started in debuggable builds by + default; otherwise `adbd` will stay off at all times in user builds. See the triggers in + `bootable/recovery/etc/init.rc`. + +- `minadbd` is started by `recovery` as needed. + - When requested to start `minadbd`, `recovery` stops `adbd` first, if it's running; it then forks + and execs `minadbd` in a separate process. + - `minadbd` talks to host-side `adb` server to get user requests. + - `minadbd` handles some requests directly, e.g. querying device properties for rescue service. + - `minadbd` communicates with `recovery` to fulfill requests regarding package installation. See + the comments in `bootable/recovery/install/adb_install.cpp` for the IPC protocol between + `recovery` and `minadbd`. + - Upon exiting `minadbd`, `recovery` restarts `adbd` if it was previously running. diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h index c5561e57d..43c07d28e 100644 --- a/minadbd/fuse_adb_provider.h +++ b/minadbd/fuse_adb_provider.h @@ -29,6 +29,10 @@ class FuseAdbDataProvider : public FuseDataProvider { bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, uint32_t start_block) const override; + bool Valid() const override { + return fd_ != -1; + } + private: // The underlying source to read data from (i.e. the one that talks to the host). int fd_; diff --git a/minadbd/minadbd_types.h b/minadbd/include/minadbd/types.h index 002523f1f..002523f1f 100644 --- a/minadbd/minadbd_types.h +++ b/minadbd/include/minadbd/types.h diff --git a/minadbd/minadbd.cpp b/minadbd/minadbd.cpp index c80d5490a..7b82faa05 100644 --- a/minadbd/minadbd.cpp +++ b/minadbd/minadbd.cpp @@ -28,8 +28,8 @@ #include "adb_auth.h" #include "transport.h" +#include "minadbd/types.h" #include "minadbd_services.h" -#include "minadbd_types.h" using namespace std::string_literals; diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp index 03341e4b8..eb91fb3e4 100644 --- a/minadbd/minadbd_services.cpp +++ b/minadbd/minadbd_services.cpp @@ -41,10 +41,10 @@ #include "adb.h" #include "adb_unique_fd.h" #include "adb_utils.h" -#include "fdevent.h" #include "fuse_adb_provider.h" #include "fuse_sideload.h" -#include "minadbd_types.h" +#include "minadbd/types.h" +#include "recovery_utils/battery_utils.h" #include "services.h" #include "sysdeps.h" @@ -161,7 +161,10 @@ static void RescueInstallHostService(unique_fd sfd, const std::string& args) { // If given an empty string, dumps all the supported properties (analogous to `adb shell getprop`) // in lines, e.g. "[prop]: [value]". static void RescueGetpropHostService(unique_fd sfd, const std::string& prop) { + constexpr const char* kRescueBatteryLevelProp = "rescue.battery_level"; static const std::set<std::string> kGetpropAllowedProps = { + // clang-format off + kRescueBatteryLevelProp, "ro.build.date.utc", "ro.build.fingerprint", "ro.build.flavor", @@ -171,18 +174,28 @@ static void RescueGetpropHostService(unique_fd sfd, const std::string& prop) { "ro.build.version.incremental", "ro.product.device", "ro.product.vendor.device", + // clang-format on }; + + auto query_prop = [](const std::string& key) { + if (key == kRescueBatteryLevelProp) { + auto battery_info = GetBatteryInfo(); + return std::to_string(battery_info.capacity); + } + return android::base::GetProperty(key, ""); + }; + std::string result; if (prop.empty()) { for (const auto& key : kGetpropAllowedProps) { - auto value = android::base::GetProperty(key, ""); + auto value = query_prop(key); if (value.empty()) { continue; } result += "[" + key + "]: [" + value + "]\n"; } } else if (kGetpropAllowedProps.find(prop) != kGetpropAllowedProps.end()) { - result = android::base::GetProperty(prop, "") + "\n"; + result = query_prop(prop) + "\n"; } if (result.empty()) { result = "\n"; @@ -255,7 +268,7 @@ static void WipeDeviceService(unique_fd fd, const std::string& args) { unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport */) { // Common services that are supported both in sideload and rescue modes. - if (ConsumePrefix(&name, "reboot:")) { + if (android::base::ConsumePrefix(&name, "reboot:")) { // "reboot:<target>", where target must be one of the following. std::string args(name); if (args.empty() || args == "bootloader" || args == "rescue" || args == "recovery" || @@ -268,17 +281,17 @@ unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport * // Rescue-specific services. if (rescue_mode) { - if (ConsumePrefix(&name, "rescue-install:")) { + if (android::base::ConsumePrefix(&name, "rescue-install:")) { // rescue-install:<file-size>:<block-size> std::string args(name); return create_service_thread( "rescue-install", std::bind(RescueInstallHostService, std::placeholders::_1, args)); - } else if (ConsumePrefix(&name, "rescue-getprop:")) { + } else if (android::base::ConsumePrefix(&name, "rescue-getprop:")) { // rescue-getprop:<prop> std::string args(name); return create_service_thread( "rescue-getprop", std::bind(RescueGetpropHostService, std::placeholders::_1, args)); - } else if (ConsumePrefix(&name, "rescue-wipe:")) { + } else if (android::base::ConsumePrefix(&name, "rescue-wipe:")) { // rescue-wipe:target:<message-size> std::string args(name); return create_service_thread("rescue-wipe", @@ -293,7 +306,7 @@ unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport * // This exit status causes recovery to print a special error message saying to use a newer adb // (that supports sideload-host). exit(kMinadbdAdbVersionError); - } else if (ConsumePrefix(&name, "sideload-host:")) { + } else if (android::base::ConsumePrefix(&name, "sideload-host:")) { // sideload-host:<file-size>:<block-size> std::string args(name); return create_service_thread("sideload-host", diff --git a/minadbd/minadbd_services_test.cpp b/minadbd/minadbd_services_test.cpp index f87873792..b694a57d1 100644 --- a/minadbd/minadbd_services_test.cpp +++ b/minadbd/minadbd_services_test.cpp @@ -35,8 +35,8 @@ #include "adb_io.h" #include "fuse_adb_provider.h" #include "fuse_sideload.h" +#include "minadbd/types.h" #include "minadbd_services.h" -#include "minadbd_types.h" #include "socket.h" class MinadbdServicesTest : public ::testing::Test { diff --git a/minui/events.cpp b/minui/events.cpp index 7d0250e97..f331ed68a 100644 --- a/minui/events.cpp +++ b/minui/events.cpp @@ -22,6 +22,7 @@ #include <stdlib.h> #include <string.h> #include <sys/epoll.h> +#include <sys/inotify.h> #include <sys/ioctl.h> #include <sys/types.h> #include <unistd.h> @@ -33,6 +34,8 @@ #include "minui/minui.h" +constexpr const char* INPUT_DEV_DIR = "/dev/input"; + constexpr size_t MAX_DEVICES = 16; constexpr size_t MAX_MISC_FDS = 16; @@ -46,6 +49,8 @@ struct FdInfo { ev_callback cb; }; +static bool g_allow_touch_inputs = true; +static ev_callback g_saved_input_cb; static android::base::unique_fd g_epoll_fd; static epoll_event g_polled_events[MAX_DEVICES + MAX_MISC_FDS]; static int g_polled_events_count; @@ -60,6 +65,78 @@ static bool test_bit(size_t bit, unsigned long* array) { // NOLINT return (array[bit / BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0; } +static bool should_add_input_device(int fd, bool allow_touch_inputs) { + // Use unsigned long to match ioctl's parameter type. + unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT + + // Read the evbits of the input device. + if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { + return false; + } + + // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. EV_ABS is also + // allowed if allow_touch_inputs is set. + if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) { + if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) { + return false; + } + } + + return true; +} + +static int inotify_cb(int fd, __unused uint32_t epevents) { + if (g_saved_input_cb == nullptr) return -1; + + // The inotify will put one or several complete events. + // Should not read part of one event. + size_t event_len; + int ret = ioctl(fd, FIONREAD, &event_len); + if (ret != 0) return -1; + + std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(INPUT_DEV_DIR), closedir); + if (!dir) { + return -1; + } + + std::vector<int8_t> buf(event_len); + + ssize_t r = TEMP_FAILURE_RETRY(read(fd, buf.data(), event_len)); + if (r != event_len) { + return -1; + } + + size_t offset = 0; + while (offset < event_len) { + struct inotify_event* pevent = reinterpret_cast<struct inotify_event*>(buf.data() + offset); + if (offset + sizeof(inotify_event) + pevent->len > event_len) { + // The pevent->len is too large and buffer will over flow. + // In general, should not happen, just make more stable. + return -1; + } + offset += sizeof(inotify_event) + pevent->len; + + pevent->name[pevent->len] = '\0'; + if (strncmp(pevent->name, "event", 5)) { + continue; + } + + android::base::unique_fd dfd(openat(dirfd(dir.get()), pevent->name, O_RDONLY)); + if (dfd == -1) { + break; + } + + if (!should_add_input_device(dfd, g_allow_touch_inputs)) { + continue; + } + + // Only add, we assume the user will not plug out and plug in USB device again and again :) + ev_add_fd(std::move(dfd), g_saved_input_cb); + } + + return 0; +} + int ev_init(ev_callback input_cb, bool allow_touch_inputs) { g_epoll_fd.reset(); @@ -68,7 +145,16 @@ int ev_init(ev_callback input_cb, bool allow_touch_inputs) { return -1; } - std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/dev/input"), closedir); + android::base::unique_fd inotify_fd(inotify_init1(IN_CLOEXEC)); + if (inotify_fd.get() == -1) { + return -1; + } + + if (inotify_add_watch(inotify_fd, INPUT_DEV_DIR, IN_CREATE) < 0) { + return -1; + } + + std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(INPUT_DEV_DIR), closedir); if (!dir) { return -1; } @@ -80,22 +166,10 @@ int ev_init(ev_callback input_cb, bool allow_touch_inputs) { android::base::unique_fd fd(openat(dirfd(dir.get()), de->d_name, O_RDONLY | O_CLOEXEC)); if (fd == -1) continue; - // Use unsigned long to match ioctl's parameter type. - unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT - - // Read the evbits of the input device. - if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { + if (!should_add_input_device(fd, allow_touch_inputs)) { continue; } - // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. EV_ABS is also - // allowed if allow_touch_inputs is set. - if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) { - if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) { - continue; - } - } - epoll_event ev; ev.events = EPOLLIN | EPOLLWAKEUP; ev.data.ptr = &ev_fdinfo[g_ev_count]; @@ -116,6 +190,11 @@ int ev_init(ev_callback input_cb, bool allow_touch_inputs) { } g_epoll_fd.reset(epoll_fd.release()); + + g_saved_input_cb = input_cb; + g_allow_touch_inputs = allow_touch_inputs; + ev_add_fd(std::move(inotify_fd), inotify_cb); + return 0; } @@ -148,6 +227,7 @@ void ev_exit(void) { } g_ev_misc_count = 0; g_ev_dev_count = 0; + g_saved_input_cb = nullptr; g_epoll_fd.reset(); } @@ -170,13 +250,17 @@ void ev_dispatch(void) { } int ev_get_input(int fd, uint32_t epevents, input_event* ev) { - if (epevents & EPOLLIN) { - ssize_t r = TEMP_FAILURE_RETRY(read(fd, ev, sizeof(*ev))); - if (r == sizeof(*ev)) { - return 0; - } + if (epevents & EPOLLIN) { + ssize_t r = TEMP_FAILURE_RETRY(read(fd, ev, sizeof(*ev))); + if (r == sizeof(*ev)) { + return 0; } - return -1; + } + if (epevents & EPOLLHUP) { + // Delete this watch + epoll_ctl(g_epoll_fd, EPOLL_CTL_DEL, fd, nullptr); + } + return -1; } int ev_sync_key_state(const ev_set_key_callback& set_key_cb) { diff --git a/minui/resources.cpp b/minui/resources.cpp index 069a49529..00d36d5fb 100644 --- a/minui/resources.cpp +++ b/minui/resources.cpp @@ -347,6 +347,10 @@ bool matches_locale(const std::string& prefix, const std::string& locale) { // match the locale string without the {script} section. // For instance, prefix == "en" matches locale == "en-US", prefix == "sr-Latn" matches locale // == "sr-Latn-BA", and prefix == "zh-CN" matches locale == "zh-Hans-CN". + if (prefix.empty()) { + return false; + } + if (android::base::StartsWith(locale, prefix)) { return true; } @@ -414,12 +418,18 @@ int res_create_localized_alpha_surface(const char* name, __unused int len = row[4]; char* loc = reinterpret_cast<char*>(&row[5]); - if (y + 1 + h >= height || matches_locale(loc, locale)) { + // We need to include one additional line for the metadata of the localized image. + if (y + 1 + h > height) { + printf("Read exceeds the image boundary, y %u, h %d, height %u\n", y, h, height); + return -8; + } + + if (matches_locale(loc, locale)) { printf(" %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y); auto surface = GRSurface::Create(w, h, w, 1); if (!surface) { - return -8; + return -9; } for (int i = 0; i < h; ++i, ++y) { @@ -428,7 +438,7 @@ int res_create_localized_alpha_surface(const char* name, } *pSurface = surface.release(); - break; + return 0; } for (int i = 0; i < h; ++i, ++y) { @@ -436,7 +446,7 @@ int res_create_localized_alpha_surface(const char* name, } } - return 0; + return -10; } void res_free_surface(GRSurface* surface) { diff --git a/otautil/Android.bp b/otautil/Android.bp index 0a21731e8..3b3f9cbc4 100644 --- a/otautil/Android.bp +++ b/otautil/Android.bp @@ -16,6 +16,7 @@ cc_library_static { name: "libotautil", host_supported: true, + vendor_available: true, recovery_available: true, defaults: [ @@ -24,44 +25,19 @@ cc_library_static { // Minimal set of files to support host build. srcs: [ + "dirutil.cpp", "paths.cpp", "rangeset.cpp", + "sysutil.cpp", ], shared_libs: [ "libbase", + "libcutils", + "libselinux", ], export_include_dirs: [ "include", ], - - target: { - android: { - srcs: [ - "dirutil.cpp", - "logging.cpp", - "mounts.cpp", - "parse_install_logs.cpp", - "roots.cpp", - "sysutil.cpp", - "thermalutil.cpp", - ], - - include_dirs: [ - "system/vold", - ], - - static_libs: [ - "libfstab", - ], - - shared_libs: [ - "libcutils", - "libext4_utils", - "libfs_mgr", - "libselinux", - ], - }, - }, } diff --git a/common.h b/otautil/include/otautil/boot_state.h index a524a4184..6c877baef 100644 --- a/common.h +++ b/otautil/include/otautil/boot_state.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * 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. @@ -17,22 +17,20 @@ #pragma once #include <string> - -// Not using the command-line defined macro here because this header could be included by -// device-specific recovery libraries. We static assert the value consistency in recovery.cpp. -static constexpr int kRecoveryApiVersion = 3; - -class RecoveryUI; -struct selabel_handle; - -extern struct selabel_handle* sehandle; -extern RecoveryUI* ui; -extern bool has_cache; - -// The current stage, e.g. "1/2". -extern std::string stage; - -// The reason argument provided in "--reason=". -extern const char* reason; - -bool is_ro_debuggable(); +#include <string_view> + +class BootState { + public: + BootState(std::string_view reason, std::string_view stage) : reason_(reason), stage_(stage) {} + + std::string reason() const { + return reason_; + } + std::string stage() const { + return stage_; + } + + private: + std::string reason_; // The reason argument provided in "--reason=". + std::string stage_; // The current stage, e.g. "1/2". +}; diff --git a/otautil/include/otautil/rangeset.h b/otautil/include/otautil/rangeset.h index e91d02ca6..a18c30e29 100644 --- a/otautil/include/otautil/rangeset.h +++ b/otautil/include/otautil/rangeset.h @@ -18,6 +18,7 @@ #include <stddef.h> +#include <optional> #include <string> #include <utility> #include <vector> @@ -49,6 +50,12 @@ class RangeSet { // bounds. For example, "3,5" contains blocks 3 and 4. So "3,5" and "5,7" are not overlapped. bool Overlaps(const RangeSet& other) const; + // Returns a subset of ranges starting from |start_index| with respect to the original range. The + // output range will have |num_of_blocks| blocks in size. Returns std::nullopt if the input is + // invalid. e.g. RangeSet({{0, 5}, {10, 15}}).GetSubRanges(1, 5) returns + // RangeSet({{1, 5}, {10, 11}}). + std::optional<RangeSet> GetSubRanges(size_t start_index, size_t num_of_blocks) const; + // Returns a vector of RangeSets that contain the same set of blocks represented by the current // RangeSet. The RangeSets in the vector contain similar number of blocks, with a maximum delta // of 1-block between any two of them. For example, 14 blocks would be split into 4 + 4 + 3 + 3, diff --git a/otautil/include/otautil/sysutil.h b/otautil/include/otautil/sysutil.h index 692a99e9d..326db8644 100644 --- a/otautil/include/otautil/sysutil.h +++ b/otautil/include/otautil/sysutil.h @@ -14,12 +14,12 @@ * limitations under the License. */ -#ifndef _OTAUTIL_SYSUTIL -#define _OTAUTIL_SYSUTIL +#pragma once #include <sys/types.h> #include <string> +#include <string_view> #include <vector> #include "rangeset.h" @@ -101,13 +101,14 @@ class MemMapping { std::vector<MappedRange> ranges_; }; -// Wrapper function to trigger a reboot, by additionally handling quiescent reboot mode. The -// command should start with "reboot," (e.g. "reboot,bootloader" or "reboot,"). -bool reboot(const std::string& command); +// Reboots the device into the specified target, by additionally handling quiescent reboot mode. +// All unknown targets reboot into Android. +bool Reboot(std::string_view target); + +// Triggers a shutdown. +bool Shutdown(std::string_view target); // Returns a null-terminated char* array, where the elements point to the C-strings in the given // vector, plus an additional nullptr at the end. This is a helper function that facilitates // calling C functions (such as getopt(3)) that expect an array of C-strings. std::vector<char*> StringVectorToNullTerminatedArray(const std::vector<std::string>& args); - -#endif // _OTAUTIL_SYSUTIL diff --git a/otautil/rangeset.cpp b/otautil/rangeset.cpp index 5ab8e08fe..8ee99dd7a 100644 --- a/otautil/rangeset.cpp +++ b/otautil/rangeset.cpp @@ -184,6 +184,58 @@ bool RangeSet::Overlaps(const RangeSet& other) const { return false; } +std::optional<RangeSet> RangeSet::GetSubRanges(size_t start_index, size_t num_of_blocks) const { + size_t end_index = start_index + num_of_blocks; // The index of final block to read plus one + if (start_index > end_index || end_index > blocks_) { + LOG(ERROR) << "Failed to get the sub ranges for start_index " << start_index + << " num_of_blocks " << num_of_blocks + << " total number of blocks the range contains is " << blocks_; + return std::nullopt; + } + + if (num_of_blocks == 0) { + LOG(WARNING) << "num_of_blocks is zero when calling GetSubRanges()"; + return RangeSet(); + } + + RangeSet result; + size_t current_index = 0; + for (const auto& [range_start, range_end] : ranges_) { + CHECK_LT(range_start, range_end); + size_t blocks_in_range = range_end - range_start; + // Linear search to skip the ranges until we reach start_block. + if (current_index + blocks_in_range <= start_index) { + current_index += blocks_in_range; + continue; + } + + size_t trimmed_range_start = range_start; + // We have found the first block range to read, trim the heading blocks. + if (current_index < start_index) { + trimmed_range_start += start_index - current_index; + } + // Trim the trailing blocks if the last range has more blocks than desired; also return the + // result. + if (current_index + blocks_in_range >= end_index) { + size_t trimmed_range_end = range_end - (current_index + blocks_in_range - end_index); + if (!result.PushBack({ trimmed_range_start, trimmed_range_end })) { + return std::nullopt; + } + + return result; + } + + if (!result.PushBack({ trimmed_range_start, range_end })) { + return std::nullopt; + } + current_index += blocks_in_range; + } + + LOG(ERROR) << "Failed to construct byte ranges to read, start_block: " << start_index + << ", num_of_blocks: " << num_of_blocks << " total number of blocks: " << blocks_; + return std::nullopt; +} + // Ranges in the the set should be mutually exclusive; and they're sorted by the start block. SortedRangeSet::SortedRangeSet(std::vector<Range>&& pairs) : RangeSet(std::move(pairs)) { std::sort(ranges_.begin(), ranges_.end()); diff --git a/otautil/sysutil.cpp b/otautil/sysutil.cpp index 8366fa0ac..6cd46c6a9 100644 --- a/otautil/sysutil.cpp +++ b/otautil/sysutil.cpp @@ -38,7 +38,7 @@ BlockMapData BlockMapData::ParseBlockMapFile(const std::string& block_map_path) { std::string content; if (!android::base::ReadFileToString(block_map_path, &content)) { - LOG(ERROR) << "Failed to read " << block_map_path; + PLOG(ERROR) << "Failed to read " << block_map_path; return {}; } @@ -94,6 +94,11 @@ BlockMapData BlockMapData::ParseBlockMapFile(const std::string& block_map_path) remaining_blocks -= range_blocks; } + if (remaining_blocks != 0) { + LOG(ERROR) << "Invalid ranges: remaining blocks " << remaining_blocks; + return {}; + } + return BlockMapData(block_dev, file_size, blksize, std::move(ranges)); } @@ -214,14 +219,21 @@ MemMapping::~MemMapping() { ranges_.clear(); } -bool reboot(const std::string& command) { - std::string cmd = command; - if (android::base::GetBoolProperty("ro.boot.quiescent", false)) { +bool Reboot(std::string_view target) { + std::string cmd = "reboot," + std::string(target); + // Honor the quiescent mode if applicable. + if (target != "bootloader" && target != "fastboot" && + android::base::GetBoolProperty("ro.boot.quiescent", false)) { cmd += ",quiescent"; } return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd); } +bool Shutdown(std::string_view target) { + std::string cmd = "shutdown," + std::string(target); + return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd); +} + std::vector<char*> StringVectorToNullTerminatedArray(const std::vector<std::string>& args) { std::vector<char*> result(args.size()); std::transform(args.cbegin(), args.cend(), result.begin(), diff --git a/recovery-persist.cpp b/recovery-persist.cpp index 294017a12..6dbf86253 100644 --- a/recovery-persist.cpp +++ b/recovery-persist.cpp @@ -43,8 +43,8 @@ #include <metricslogger/metrics_logger.h> #include <private/android_logger.h> /* private pmsg functions */ -#include "otautil/logging.h" -#include "otautil/parse_install_logs.h" +#include "recovery_utils/logging.h" +#include "recovery_utils/parse_install_logs.h" constexpr const char* LAST_LOG_FILE = "/data/misc/recovery/last_log"; constexpr const char* LAST_PMSG_FILE = "/sys/fs/pstore/pmsg-ramoops-0"; diff --git a/recovery-refresh.cpp b/recovery-refresh.cpp index d41755d0a..42acd05be 100644 --- a/recovery-refresh.cpp +++ b/recovery-refresh.cpp @@ -38,11 +38,12 @@ // #include <string.h> + #include <string> #include <private/android_logger.h> /* private pmsg functions */ -#include "otautil/logging.h" +#include "recovery_utils/logging.h" int main(int argc, char **argv) { static const char filter[] = "recovery/"; diff --git a/recovery.cpp b/recovery.cpp index 5fc673ec2..f59a940fc 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -18,11 +18,9 @@ #include <ctype.h> #include <errno.h> -#include <fcntl.h> #include <getopt.h> #include <inttypes.h> #include <limits.h> -#include <linux/fs.h> #include <linux/input.h> #include <stdio.h> #include <stdlib.h> @@ -30,8 +28,8 @@ #include <sys/types.h> #include <unistd.h> -#include <algorithm> #include <functional> +#include <iterator> #include <memory> #include <string> #include <vector> @@ -42,26 +40,27 @@ #include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> -#include <android-base/unique_fd.h> -#include <bootloader_message/bootloader_message.h> #include <cutils/properties.h> /* for property_list */ -#include <healthhalutils/HealthHalUtils.h> +#include <fs_mgr/roots.h> #include <ziparchive/zip_archive.h> -#include "common.h" +#include "bootloader_message/bootloader_message.h" #include "fsck_unshare_blocks.h" #include "install/adb_install.h" -#include "install/fuse_sdcard_install.h" +#include "install/fuse_install.h" #include "install/install.h" #include "install/package.h" #include "install/wipe_data.h" +#include "install/wipe_device.h" +#include "otautil/boot_state.h" #include "otautil/error_code.h" -#include "otautil/logging.h" #include "otautil/paths.h" -#include "otautil/roots.h" #include "otautil/sysutil.h" #include "recovery_ui/screen_ui.h" #include "recovery_ui/ui.h" +#include "recovery_utils/battery_utils.h" +#include "recovery_utils/logging.h" +#include "recovery_utils/roots.h" static constexpr const char* COMMAND_FILE = "/cache/recovery/command"; static constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; @@ -70,13 +69,7 @@ static constexpr const char* LOCALE_FILE = "/cache/recovery/last_locale"; static constexpr const char* CACHE_ROOT = "/cache"; -// We define RECOVERY_API_VERSION in Android.mk, which will be picked up by build system and packed -// into target_files.zip. Assert the version defined in code and in Android.mk are consistent. -static_assert(kRecoveryApiVersion == RECOVERY_API_VERSION, "Mismatching recovery API versions."); - static bool save_current_log = false; -std::string stage; -const char* reason = nullptr; /* * The recovery tool communicates with the main system through /cache files. @@ -85,6 +78,8 @@ const char* reason = nullptr; * * The arguments which may be supplied in the recovery.command file: * --update_package=path - verify install an OTA package file + * --install_with_fuse - install the update package with FUSE. This allows installation of large + * packages on LP32 builds. Since the mmap will otherwise fail due to out of memory. * --wipe_data - erase user data (and cache), then reboot * --prompt_and_wipe_data - prompt the user that data is corrupt, with their consent erase user * data (and cache), then reboot @@ -105,7 +100,7 @@ const char* reason = nullptr; * -- after this, rebooting will restart the erase -- * 5. erase_volume() reformats /data * 6. erase_volume() reformats /cache - * 7. finish_recovery() erases BCB + * 7. FinishRecovery() erases BCB * -- after this, rebooting will restart the main system -- * 8. main() calls reboot() to boot main system * @@ -115,27 +110,27 @@ const char* reason = nullptr; * 3. main system reboots into recovery * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..." * -- after this, rebooting will attempt to reinstall the update -- - * 5. install_package() attempts to install the update + * 5. InstallPackage() attempts to install the update * NOTE: the package install must itself be restartable from any point - * 6. finish_recovery() erases BCB + * 6. FinishRecovery() erases BCB * -- after this, rebooting will (try to) restart the main system -- * 7. ** if install failed ** - * 7a. prompt_and_wait() shows an error icon and waits for the user + * 7a. PromptAndWait() shows an error icon and waits for the user * 7b. the user reboots (pulling the battery, etc) into the main system */ -bool is_ro_debuggable() { - return android::base::GetBoolProperty("ro.debuggable", false); +static bool IsRoDebuggable() { + return android::base::GetBoolProperty("ro.debuggable", false); } // Clear the recovery command and prepare to boot a (hopefully working) system, // copy our log file to cache as well (for the system to read). This function is // idempotent: call it as many times as you like. -static void finish_recovery() { +static void FinishRecovery(RecoveryUI* ui) { std::string locale = ui->GetLocale(); // Save the locale to cache, so if recovery is next started up without a '--locale' argument // (e.g., directly from the bootloader) it will use the last-known locale. - if (!locale.empty() && has_cache) { + if (!locale.empty() && HasCache()) { LOG(INFO) << "Saving locale \"" << locale << "\""; if (ensure_path_mounted(LOCALE_FILE) != 0) { LOG(ERROR) << "Failed to mount " << LOCALE_FILE; @@ -144,7 +139,7 @@ static void finish_recovery() { } } - copy_logs(save_current_log, has_cache, sehandle); + copy_logs(save_current_log); // Reset to normal system boot so recovery won't cycle indefinitely. std::string err; @@ -153,7 +148,7 @@ static void finish_recovery() { } // Remove the command file, so recovery won't repeat indefinitely. - if (has_cache) { + if (HasCache()) { if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) { LOG(WARNING) << "Can't unlink " << COMMAND_FILE; } @@ -167,7 +162,7 @@ static bool yes_no(Device* device, const char* question1, const char* question2) std::vector<std::string> headers{ question1, question2 }; std::vector<std::string> items{ " No", " Yes" }; - size_t chosen_item = ui->ShowMenu( + size_t chosen_item = device->GetUI()->ShowMenu( headers, items, 0, true, std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); return (chosen_item == 1); @@ -177,7 +172,7 @@ static bool ask_to_wipe_data(Device* device) { std::vector<std::string> headers{ "Wipe all user data?", " THIS CAN NOT BE UNDONE!" }; std::vector<std::string> items{ " Cancel", " Factory data reset" }; - size_t chosen_item = ui->ShowPromptWipeDataConfirmationMenu( + size_t chosen_item = device->GetUI()->ShowPromptWipeDataConfirmationMenu( headers, items, std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); @@ -199,7 +194,7 @@ static InstallResult prompt_and_wipe_data(Device* device) { }; // clang-format on for (;;) { - size_t chosen_item = ui->ShowPromptWipeDataMenu( + size_t chosen_item = device->GetUI()->ShowPromptWipeDataMenu( wipe_data_menu_headers, wipe_data_menu_items, std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); // If ShowMenu() returned RecoveryUI::KeyError::INTERRUPTED, WaitKey() was interrupted. @@ -211,7 +206,8 @@ static InstallResult prompt_and_wipe_data(Device* device) { } if (ask_to_wipe_data(device)) { - bool convert_fbe = reason && strcmp(reason, "convert_fbe") == 0; + CHECK(device->GetReason().has_value()); + bool convert_fbe = device->GetReason().value() == "convert_fbe"; if (WipeData(device, convert_fbe)) { return INSTALL_SUCCESS; } else { @@ -221,168 +217,9 @@ static InstallResult prompt_and_wipe_data(Device* device) { } } -// Secure-wipe a given partition. It uses BLKSECDISCARD, if supported. Otherwise, it goes with -// BLKDISCARD (if device supports BLKDISCARDZEROES) or BLKZEROOUT. -static bool secure_wipe_partition(const std::string& partition) { - android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY))); - if (fd == -1) { - PLOG(ERROR) << "Failed to open \"" << partition << "\""; - return false; - } - - uint64_t range[2] = { 0, 0 }; - if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) { - PLOG(ERROR) << "Failed to get partition size"; - return false; - } - LOG(INFO) << "Secure-wiping \"" << partition << "\" from " << range[0] << " to " << range[1]; - - LOG(INFO) << " Trying BLKSECDISCARD..."; - if (ioctl(fd, BLKSECDISCARD, &range) == -1) { - PLOG(WARNING) << " Failed"; - - // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT. - unsigned int zeroes; - if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) { - LOG(INFO) << " Trying BLKDISCARD..."; - if (ioctl(fd, BLKDISCARD, &range) == -1) { - PLOG(ERROR) << " Failed"; - return false; - } - } else { - LOG(INFO) << " Trying BLKZEROOUT..."; - if (ioctl(fd, BLKZEROOUT, &range) == -1) { - PLOG(ERROR) << " Failed"; - return false; - } - } - } - - LOG(INFO) << " Done"; - return true; -} - -static std::unique_ptr<Package> ReadWipePackage(size_t wipe_package_size) { - if (wipe_package_size == 0) { - LOG(ERROR) << "wipe_package_size is zero"; - return nullptr; - } - - std::string wipe_package; - std::string err_str; - if (!read_wipe_package(&wipe_package, wipe_package_size, &err_str)) { - PLOG(ERROR) << "Failed to read wipe package" << err_str; - return nullptr; - } - - return Package::CreateMemoryPackage( - std::vector<uint8_t>(wipe_package.begin(), wipe_package.end()), nullptr); -} - -// Checks if the wipe package matches expectation. If the check passes, reads the list of -// partitions to wipe from the package. Checks include -// 1. verify the package. -// 2. check metadata (ota-type, pre-device and serial number if having one). -static bool CheckWipePackage(Package* wipe_package) { - if (!verify_package(wipe_package, ui)) { - LOG(ERROR) << "Failed to verify package"; - return false; - } - - ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle(); - if (!zip) { - LOG(ERROR) << "Failed to get ZipArchiveHandle"; - return false; - } - - std::map<std::string, std::string> metadata; - if (!ReadMetadataFromPackage(zip, &metadata)) { - LOG(ERROR) << "Failed to parse metadata in the zip file"; - return false; - } - - return CheckPackageMetadata(metadata, OtaType::BRICK) == 0; -} - -std::vector<std::string> GetWipePartitionList(Package* wipe_package) { - ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle(); - if (!zip) { - LOG(ERROR) << "Failed to get ZipArchiveHandle"; - return {}; - } - - static constexpr const char* RECOVERY_WIPE_ENTRY_NAME = "recovery.wipe"; - - std::string partition_list_content; - ZipString path(RECOVERY_WIPE_ENTRY_NAME); - ZipEntry entry; - if (FindEntry(zip, path, &entry) == 0) { - uint32_t length = entry.uncompressed_length; - partition_list_content = std::string(length, '\0'); - if (auto err = ExtractToMemory( - zip, &entry, reinterpret_cast<uint8_t*>(partition_list_content.data()), length); - err != 0) { - LOG(ERROR) << "Failed to extract " << RECOVERY_WIPE_ENTRY_NAME << ": " - << ErrorCodeString(err); - return {}; - } - } else { - LOG(INFO) << "Failed to find " << RECOVERY_WIPE_ENTRY_NAME - << ", falling back to use the partition list on device."; - - static constexpr const char* RECOVERY_WIPE_ON_DEVICE = "/etc/recovery.wipe"; - if (!android::base::ReadFileToString(RECOVERY_WIPE_ON_DEVICE, &partition_list_content)) { - PLOG(ERROR) << "failed to read \"" << RECOVERY_WIPE_ON_DEVICE << "\""; - return {}; - } - } - - std::vector<std::string> result; - std::vector<std::string> lines = android::base::Split(partition_list_content, "\n"); - for (const std::string& line : lines) { - std::string partition = android::base::Trim(line); - // Ignore '#' comment or empty lines. - if (android::base::StartsWith(partition, "#") || partition.empty()) { - continue; - } - result.push_back(line); - } - - return result; -} - -// Wipes the current A/B device, with a secure wipe of all the partitions in RECOVERY_WIPE. -static bool wipe_ab_device(size_t wipe_package_size) { - ui->SetBackground(RecoveryUI::ERASING); - ui->SetProgressType(RecoveryUI::INDETERMINATE); - - auto wipe_package = ReadWipePackage(wipe_package_size); - if (!wipe_package) { - LOG(ERROR) << "Failed to open wipe package"; - return false; - } - - if (!CheckWipePackage(wipe_package.get())) { - LOG(ERROR) << "Failed to verify wipe package"; - return false; - } - - auto partition_list = GetWipePartitionList(wipe_package.get()); - if (partition_list.empty()) { - LOG(ERROR) << "Empty wipe ab partition list"; - return false; - } - - for (const auto& partition : partition_list) { - // Proceed anyway even if it fails to wipe some partition. - secure_wipe_partition(partition); - } - return true; -} - static void choose_recovery_file(Device* device) { std::vector<std::string> entries; - if (has_cache) { + if (HasCache()) { for (int i = 0; i < KEEP_LOG_COUNT; i++) { auto add_to_entries = [&](const char* filename) { std::string log_file(filename); @@ -416,7 +253,7 @@ static void choose_recovery_file(Device* device) { size_t chosen_item = 0; while (true) { - chosen_item = ui->ShowMenu( + chosen_item = device->GetUI()->ShowMenu( headers, entries, chosen_item, true, std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); @@ -426,11 +263,11 @@ static void choose_recovery_file(Device* device) { } if (entries[chosen_item] == "Back") break; - ui->ShowFile(entries[chosen_item]); + device->GetUI()->ShowFile(entries[chosen_item]); } } -static void run_graphics_test() { +static void run_graphics_test(RecoveryUI* ui) { // Switch to graphics screen. ui->ShowText(false); @@ -473,14 +310,19 @@ static void run_graphics_test() { ui->ShowText(true); } -// Returns REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default, -// which is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery. -static Device::BuiltinAction prompt_and_wait(Device* device, int status) { +// Shows the recovery UI and waits for user input. Returns one of the device builtin actions, such +// as REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default, which +// is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery. +static Device::BuiltinAction PromptAndWait(Device* device, InstallResult status) { + auto ui = device->GetUI(); for (;;) { - finish_recovery(); + FinishRecovery(ui); switch (status) { case INSTALL_SUCCESS: case INSTALL_NONE: + case INSTALL_SKIPPED: + case INSTALL_RETRY: + case INSTALL_KEY_INTERRUPTED: ui->SetBackground(RecoveryUI::NO_COMMAND); break; @@ -488,6 +330,12 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { case INSTALL_CORRUPT: ui->SetBackground(RecoveryUI::ERROR); break; + + case INSTALL_REBOOT: + // All the reboots should have been handled prior to entering PromptAndWait() or immediately + // after installing a package. + LOG(FATAL) << "Invalid status code of INSTALL_REBOOT"; + break; } ui->SetProgressType(RecoveryUI::EMPTY); @@ -506,6 +354,8 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { : device->InvokeMenuItem(chosen_item); switch (chosen_action) { + case Device::REBOOT_FROM_FASTBOOT: // Can not happen + case Device::SHUTDOWN_FROM_FASTBOOT: // Can not happen case Device::NO_ACTION: break; @@ -556,7 +406,7 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { status = ApplyFromAdb(device, false /* rescue_mode */, &reboot_action); } else { adb = false; - status = ApplyFromSdcard(device, ui); + status = ApplyFromSdcard(device); } ui->Print("\nInstall from %s completed with status %d.\n", adb ? "ADB" : "SD card", status); @@ -567,7 +417,7 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { if (status != INSTALL_SUCCESS) { ui->SetBackground(RecoveryUI::ERROR); ui->Print("Installation aborted.\n"); - copy_logs(save_current_log, has_cache, sehandle); + copy_logs(save_current_log); } else if (!ui->IsTextVisible()) { return Device::NO_ACTION; // reboot if logs aren't visible } @@ -579,7 +429,7 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { break; case Device::RUN_GRAPHICS_TEST: - run_graphics_test(); + run_graphics_test(ui); break; case Device::RUN_LOCALE_TEST: { @@ -588,8 +438,7 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { break; } case Device::MOUNT_SYSTEM: - // the system partition is mounted at /mnt/system - if (ensure_path_mounted_at(get_system_root(), "/mnt/system") != -1) { + if (ensure_path_mounted_at(android::fs_mgr::GetSystemRoot(), "/mnt/system") != -1) { ui->Print("Mounted /system.\n"); } break; @@ -604,74 +453,17 @@ static void print_property(const char* key, const char* name, void* /* cookie */ printf("%s=%s\n", key, name); } -static bool is_battery_ok(int* required_battery_level) { - using android::hardware::health::V1_0::BatteryStatus; - using android::hardware::health::V2_0::get_health_service; - using android::hardware::health::V2_0::IHealth; - using android::hardware::health::V2_0::Result; - using android::hardware::health::V2_0::toString; - - android::sp<IHealth> health = get_health_service(); - - static constexpr int BATTERY_READ_TIMEOUT_IN_SEC = 10; - int wait_second = 0; - while (true) { - auto charge_status = BatteryStatus::UNKNOWN; - - if (health == nullptr) { - LOG(WARNING) << "no health implementation is found, assuming defaults"; - } else { - health - ->getChargeStatus([&charge_status](auto res, auto out_status) { - if (res == Result::SUCCESS) { - charge_status = out_status; - } - }) - .isOk(); // should not have transport error - } - - // Treat unknown status as charged. - bool charged = (charge_status != BatteryStatus::DISCHARGING && - charge_status != BatteryStatus::NOT_CHARGING); - - Result res = Result::UNKNOWN; - int32_t capacity = INT32_MIN; - if (health != nullptr) { - health - ->getCapacity([&res, &capacity](auto out_res, auto out_capacity) { - res = out_res; - capacity = out_capacity; - }) - .isOk(); // should not have transport error - } - - LOG(INFO) << "charge_status " << toString(charge_status) << ", charged " << charged - << ", status " << toString(res) << ", capacity " << capacity; - // At startup, the battery drivers in devices like N5X/N6P take some time to load - // the battery profile. Before the load finishes, it reports value 50 as a fake - // capacity. BATTERY_READ_TIMEOUT_IN_SEC is set that the battery drivers are expected - // to finish loading the battery profile earlier than 10 seconds after kernel startup. - if (res == Result::SUCCESS && capacity == 50) { - if (wait_second < BATTERY_READ_TIMEOUT_IN_SEC) { - sleep(1); - wait_second++; - continue; - } - } - // If we can't read battery percentage, it may be a device without battery. In this - // situation, use 100 as a fake battery percentage. - if (res != Result::SUCCESS) { - capacity = 100; - } - - // GmsCore enters recovery mode to install package when having enough battery percentage. - // Normally, the threshold is 40% without charger and 20% with charger. So we should check - // battery with a slightly lower limitation. - static constexpr int BATTERY_OK_PERCENTAGE = 20; - static constexpr int BATTERY_WITH_CHARGER_OK_PERCENTAGE = 15; - *required_battery_level = charged ? BATTERY_WITH_CHARGER_OK_PERCENTAGE : BATTERY_OK_PERCENTAGE; - return capacity >= *required_battery_level; - } +static bool IsBatteryOk(int* required_battery_level) { + // GmsCore enters recovery mode to install package when having enough battery percentage. + // Normally, the threshold is 40% without charger and 20% with charger. So we check the battery + // level against a slightly lower limit. + constexpr int BATTERY_OK_PERCENTAGE = 20; + constexpr int BATTERY_WITH_CHARGER_OK_PERCENTAGE = 15; + + auto battery_info = GetBatteryInfo(); + *required_battery_level = + battery_info.charging ? BATTERY_WITH_CHARGER_OK_PERCENTAGE : BATTERY_OK_PERCENTAGE; + return battery_info.capacity >= *required_battery_level; } // Set the retry count to |retry_count| in BCB. @@ -726,6 +518,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri static constexpr struct option OPTIONS[] = { { "fastboot", no_argument, nullptr, 0 }, { "fsck_unshare_blocks", no_argument, nullptr, 0 }, + { "install_with_fuse", no_argument, nullptr, 0 }, { "just_exit", no_argument, nullptr, 'x' }, { "locale", required_argument, nullptr, 0 }, { "prompt_and_wipe_data", no_argument, nullptr, 0 }, @@ -746,6 +539,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri }; const char* update_package = nullptr; + bool install_with_fuse = false; // memory map the update package by default. bool should_wipe_data = false; bool should_prompt_and_wipe_data = false; bool should_wipe_cache = false; @@ -781,12 +575,12 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri std::string option = OPTIONS[option_index].name; if (option == "fsck_unshare_blocks") { fsck_unshare_blocks = true; - } else if (option == "locale" || option == "fastboot") { + } else if (option == "install_with_fuse") { + install_with_fuse = true; + } else if (option == "locale" || option == "fastboot" || option == "reason") { // Handled in recovery_main.cpp } else if (option == "prompt_and_wipe_data") { should_prompt_and_wipe_data = true; - } else if (option == "reason") { - reason = optarg; } else if (option == "rescue") { rescue = true; } else if (option == "retry_count") { @@ -820,15 +614,18 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri } optind = 1; - printf("stage is [%s]\n", stage.c_str()); - printf("reason is [%s]\n", reason); + printf("stage is [%s]\n", device->GetStage().value_or("").c_str()); + printf("reason is [%s]\n", device->GetReason().value_or("").c_str()); + + auto ui = device->GetUI(); // Set background string to "installing security update" for security update, // otherwise set it to "installing system update". ui->SetSystemUpdateText(security_update); int st_cur, st_max; - if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) { + if (!device->GetStage().has_value() && + sscanf(device->GetStage().value().c_str(), "%d/%d", &st_cur, &st_max) == 2) { ui->SetStage(st_cur, st_max); } @@ -849,9 +646,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri property_list(print_property, nullptr); printf("\n"); - ui->Print("Supported API: %d\n", kRecoveryApiVersion); - - int status = INSTALL_SUCCESS; + InstallResult status = INSTALL_SUCCESS; // next_action indicates the next target to reboot into upon finishing the install. It could be // overridden to a different reboot target per user request. Device::BuiltinAction next_action = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; @@ -861,12 +656,10 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri // to log the update attempt since update_package is non-NULL. save_current_log = true; - int required_battery_level; - if (retry_count == 0 && !is_battery_ok(&required_battery_level)) { + if (int required_battery_level; retry_count == 0 && !IsBatteryOk(&required_battery_level)) { ui->Print("battery capacity is not enough for installing package: %d%% needed\n", required_battery_level); - // Log the error code to last_install when installation skips due to - // low battery. + // Log the error code to last_install when installation skips due to low battery. log_failure_code(kLowBattery, update_package); status = INSTALL_SKIPPED; } else if (retry_count == 0 && bootreason_in_blacklist()) { @@ -881,7 +674,29 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri set_retry_bootloader_message(retry_count + 1, args); } - status = install_package(update_package, should_wipe_cache, true, retry_count, ui); + if (update_package[0] == '@') { + ensure_path_mounted(update_package + 1); + } else { + ensure_path_mounted(update_package); + } + + if (install_with_fuse) { + LOG(INFO) << "Installing package " << update_package << " with fuse"; + status = InstallWithFuseFromPath(update_package, ui); + } else if (auto memory_package = Package::CreateMemoryPackage( + update_package, + std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1)); + memory_package != nullptr) { + status = InstallPackage(memory_package.get(), update_package, should_wipe_cache, + retry_count, ui); + } else { + // We may fail to memory map the package on 32 bit builds for packages with 2GiB+ size. + // In such cases, we will try to install the package with fuse. This is not the default + // installation method because it introduces a layer of indirection from the kernel space. + LOG(WARNING) << "Failed to memory map package " << update_package + << "; falling back to install with fuse"; + status = InstallWithFuseFromPath(update_package, ui); + } if (status != INSTALL_SUCCESS) { ui->Print("Installation aborted.\n"); @@ -889,14 +704,14 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri // RETRY_LIMIT times before we abandon this OTA update. static constexpr int RETRY_LIMIT = 4; if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) { - copy_logs(save_current_log, has_cache, sehandle); + copy_logs(save_current_log); retry_count += 1; set_retry_bootloader_message(retry_count, args); // Print retry count on screen. ui->Print("Retry attempt %d\n", retry_count); - // Reboot and retry the update - if (!reboot("reboot,recovery")) { + // Reboot back into recovery to retry the update. + if (!Reboot("recovery")) { ui->Print("Reboot failed\n"); } else { while (true) { @@ -907,14 +722,15 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri // If this is an eng or userdebug build, then automatically // turn the text display on if the script fails so the error // message is visible. - if (is_ro_debuggable()) { + if (IsRoDebuggable()) { ui->ShowText(true); } } } } else if (should_wipe_data) { save_current_log = true; - bool convert_fbe = reason && strcmp(reason, "convert_fbe") == 0; + CHECK(device->GetReason().has_value()); + bool convert_fbe = device->GetReason().value() == "convert_fbe"; if (!WipeData(device, convert_fbe)) { status = INSTALL_ERROR; } @@ -934,7 +750,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri status = INSTALL_ERROR; } } else if (should_wipe_ab) { - if (!wipe_ab_device(wipe_package_size)) { + if (!WipeAbDevice(device, wipe_package_size)) { status = INSTALL_ERROR; } } else if (sideload) { @@ -964,7 +780,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri // If this is an eng or userdebug build, automatically turn on the text display if no command // is specified. Note that this should be called before setting the background to avoid // flickering the background image. - if (is_ro_debuggable()) { + if (IsRoDebuggable()) { ui->ShowText(true); } status = INSTALL_NONE; // No command specified @@ -989,7 +805,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri // for 5s followed by an automatic reboot. if (status != INSTALL_REBOOT) { if (status == INSTALL_NONE || ui->IsTextVisible()) { - Device::BuiltinAction temp = prompt_and_wait(device, status); + auto temp = PromptAndWait(device, status); if (temp != Device::NO_ACTION) { next_action = temp; } @@ -997,7 +813,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri } // Save logs and clean up before rebooting or shutting down. - finish_recovery(); + FinishRecovery(ui); return next_action; } diff --git a/recovery_main.cpp b/recovery_main.cpp index de8ac1f42..89253dcd2 100644 --- a/recovery_main.cpp +++ b/recovery_main.cpp @@ -41,34 +41,33 @@ #include <android-base/strings.h> #include <android-base/unique_fd.h> #include <bootloader_message/bootloader_message.h> -#include <cutils/android_reboot.h> #include <cutils/sockets.h> +#include <fs_mgr/roots.h> #include <private/android_logger.h> /* private pmsg functions */ #include <selinux/android.h> #include <selinux/label.h> #include <selinux/selinux.h> -#include "common.h" #include "fastboot/fastboot.h" #include "install/wipe_data.h" -#include "otautil/logging.h" +#include "otautil/boot_state.h" #include "otautil/paths.h" -#include "otautil/roots.h" #include "otautil/sysutil.h" #include "recovery.h" #include "recovery_ui/device.h" #include "recovery_ui/stub_ui.h" #include "recovery_ui/ui.h" +#include "recovery_utils/logging.h" +#include "recovery_utils/roots.h" static constexpr const char* COMMAND_FILE = "/cache/recovery/command"; static constexpr const char* LOCALE_FILE = "/cache/recovery/last_locale"; -static constexpr const char* CACHE_ROOT = "/cache"; +static RecoveryUI* ui = nullptr; -bool has_cache = false; - -RecoveryUI* ui = nullptr; -struct selabel_handle* sehandle; +static bool IsRoDebuggable() { + return android::base::GetBoolProperty("ro.debuggable", false); +} static void UiLogger(android::base::LogId /* id */, android::base::LogSeverity severity, const char* /* tag */, const char* /* file */, unsigned int /* line */, @@ -81,11 +80,12 @@ static void UiLogger(android::base::LogId /* id */, android::base::LogSeverity s } } +// Parses the command line argument from various sources; and reads the stage field from BCB. // command line args come from, in decreasing precedence: // - the actual command line // - the bootloader control block (one per line, after "recovery") // - the contents of COMMAND_FILE (one per line) -static std::vector<std::string> get_args(const int argc, char** const argv) { +static std::vector<std::string> get_args(const int argc, char** const argv, std::string* stage) { CHECK_GT(argc, 0); bootloader_message boot = {}; @@ -95,7 +95,9 @@ static std::vector<std::string> get_args(const int argc, char** const argv) { // If fails, leave a zeroed bootloader_message. boot = {}; } - stage = std::string(boot.stage); + if (stage) { + *stage = std::string(boot.stage); + } std::string boot_command; if (boot.command[0] != 0) { @@ -131,7 +133,7 @@ static std::vector<std::string> get_args(const int argc, char** const argv) { } // --- if that doesn't work, try the command file (if we have /cache). - if (args.size() == 1 && has_cache) { + if (args.size() == 1 && HasCache()) { std::string content; if (ensure_path_mounted(COMMAND_FILE) == 0 && android::base::ReadFileToString(COMMAND_FILE, &content)) { @@ -148,7 +150,7 @@ static std::vector<std::string> get_args(const int argc, char** const argv) { // Write the arguments (excluding the filename in args[0]) back into the // bootloader control block. So the device will always boot into recovery to - // finish the pending work, until finish_recovery() is called. + // finish the pending work, until FinishRecovery() is called. std::vector<std::string> options(args.cbegin() + 1, args.cend()); if (!update_bootloader_message(options, &err)) { LOG(ERROR) << "Failed to set BCB message: " << err; @@ -331,14 +333,15 @@ int main(int argc, char** argv) { redirect_stdio(Paths::Get().temporary_log_file().c_str()); load_volume_table(); - has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr; - std::vector<std::string> args = get_args(argc, argv); + std::string stage; + std::vector<std::string> args = get_args(argc, argv, &stage); auto args_to_parse = StringVectorToNullTerminatedArray(args); static constexpr struct option OPTIONS[] = { { "fastboot", no_argument, nullptr, 0 }, { "locale", required_argument, nullptr, 0 }, + { "reason", required_argument, nullptr, 0 }, { "show_text", no_argument, nullptr, 't' }, { nullptr, 0, nullptr, 0 }, }; @@ -346,6 +349,13 @@ int main(int argc, char** argv) { bool show_text = false; bool fastboot = false; std::string locale; + std::string reason; + + // The code here is only interested in the options that signal the intent to start fastbootd or + // recovery. Unrecognized options are likely meant for recovery, which will be processed later in + // start_recovery(). Suppress the warnings for such -- even if some flags were indeed invalid, the + // code in start_recovery() will capture and report them. + opterr = 0; int arg; int option_index; @@ -359,6 +369,8 @@ int main(int argc, char** argv) { std::string option = OPTIONS[option_index].name; if (option == "locale") { locale = optarg; + } else if (option == "reason") { + reason = optarg; } else if (option == "fastboot" && android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) { fastboot = true; @@ -368,14 +380,14 @@ int main(int argc, char** argv) { } } optind = 1; + opterr = 1; if (locale.empty()) { - if (has_cache) { + if (HasCache()) { locale = load_locale_from_cache(); } if (locale.empty()) { - static constexpr const char* DEFAULT_LOCALE = "en-US"; locale = DEFAULT_LOCALE; } } @@ -415,9 +427,12 @@ int main(int argc, char** argv) { device->ResetUI(new StubRecoveryUI()); } } + + BootState boot_state(reason, stage); // recovery_main owns the state of boot. + device->SetBootState(&boot_state); ui = device->GetUI(); - if (!has_cache) { + if (!HasCache()) { device->RemoveMenuItemForAction(Device::WIPE_CACHE); } @@ -425,7 +440,7 @@ int main(int argc, char** argv) { device->RemoveMenuItemForAction(Device::ENTER_FASTBOOT); } - if (!is_ro_debuggable()) { + if (!IsRoDebuggable()) { device->RemoveMenuItemForAction(Device::ENTER_RESCUE); } @@ -435,7 +450,7 @@ int main(int argc, char** argv) { LOG(INFO) << "Starting recovery (pid " << getpid() << ") on " << ctime(&start); LOG(INFO) << "locale is [" << locale << "]"; - sehandle = selinux_android_file_context_handle(); + auto sehandle = selinux_android_file_context_handle(); selinux_android_set_sehandle(sehandle); if (!sehandle) { ui->Print("Warning: No file_contexts\n"); @@ -448,7 +463,7 @@ int main(int argc, char** argv) { listener_thread.detach(); while (true) { - std::string usb_config = fastboot ? "fastboot" : is_ro_debuggable() ? "adb" : "none"; + std::string usb_config = fastboot ? "fastboot" : IsRoDebuggable() ? "adb" : "none"; std::string usb_state = android::base::GetProperty("sys.usb.state", "none"); if (usb_config != usb_state) { if (!SetUsbConfig("none")) { @@ -472,27 +487,31 @@ int main(int argc, char** argv) { switch (ret) { case Device::SHUTDOWN: ui->Print("Shutting down...\n"); - // TODO: Move all the reboots to reboot(), which should conditionally set quiescent flag. - android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,"); + Shutdown("userrequested,recovery"); + break; + + case Device::SHUTDOWN_FROM_FASTBOOT: + ui->Print("Shutting down...\n"); + Shutdown("userrequested,fastboot"); break; case Device::REBOOT_BOOTLOADER: ui->Print("Rebooting to bootloader...\n"); - android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader"); + Reboot("bootloader"); break; case Device::REBOOT_FASTBOOT: ui->Print("Rebooting to recovery/fastboot...\n"); - android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,fastboot"); + Reboot("fastboot"); break; case Device::REBOOT_RECOVERY: ui->Print("Rebooting to recovery...\n"); - reboot("reboot,recovery"); + Reboot("recovery"); break; case Device::REBOOT_RESCUE: { - // Not using `reboot("reboot,rescue")`, as it requires matching support in kernel and/or + // Not using `Reboot("rescue")`, as it requires matching support in kernel and/or // bootloader. bootloader_message boot = {}; strlcpy(boot.command, "boot-rescue", sizeof(boot.command)); @@ -503,14 +522,14 @@ int main(int argc, char** argv) { continue; } ui->Print("Rebooting to recovery/rescue...\n"); - reboot("reboot,recovery"); + Reboot("recovery"); break; } case Device::ENTER_FASTBOOT: - if (logical_partitions_mapped()) { + if (android::fs_mgr::LogicalPartitionsMapped()) { ui->Print("Partitions may be mounted - rebooting to enter fastboot."); - android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,fastboot"); + Reboot("fastboot"); } else { LOG(INFO) << "Entering fastboot"; fastboot = true; @@ -522,9 +541,19 @@ int main(int argc, char** argv) { fastboot = false; break; + case Device::REBOOT: + ui->Print("Rebooting...\n"); + Reboot("userrequested,recovery"); + break; + + case Device::REBOOT_FROM_FASTBOOT: + ui->Print("Rebooting...\n"); + Reboot("userrequested,fastboot"); + break; + default: ui->Print("Rebooting...\n"); - reboot("reboot,"); + Reboot("unknown" + std::to_string(ret)); break; } } diff --git a/recovery_ui/Android.bp b/recovery_ui/Android.bp index ee3149d5e..149ef8acc 100644 --- a/recovery_ui/Android.bp +++ b/recovery_ui/Android.bp @@ -23,6 +23,7 @@ cc_library { srcs: [ "device.cpp", "screen_ui.cpp", + "stub_ui.cpp", "ui.cpp", "vr_ui.cpp", "wear_ui.cpp", diff --git a/recovery_ui/device.cpp b/recovery_ui/device.cpp index e7ae1a3e1..d46df92d3 100644 --- a/recovery_ui/device.cpp +++ b/recovery_ui/device.cpp @@ -23,6 +23,7 @@ #include <android-base/logging.h> +#include "otautil/boot_state.h" #include "recovery_ui/ui.h" static std::vector<std::pair<std::string, Device::BuiltinAction>> g_menu_actions{ @@ -95,3 +96,15 @@ int Device::HandleMenuKey(int key, bool visible) { return ui_->HasThreeButtons() ? kNoAction : kHighlightDown; } } + +void Device::SetBootState(const BootState* state) { + boot_state_ = state; +} + +std::optional<std::string> Device::GetReason() const { + return boot_state_ ? std::make_optional(boot_state_->reason()) : std::nullopt; +} + +std::optional<std::string> Device::GetStage() const { + return boot_state_ ? std::make_optional(boot_state_->stage()) : std::nullopt; +} diff --git a/recovery_ui/include/recovery_ui/device.h b/recovery_ui/include/recovery_ui/device.h index 7c76cdb0a..f4f993638 100644 --- a/recovery_ui/include/recovery_ui/device.h +++ b/recovery_ui/include/recovery_ui/device.h @@ -20,12 +20,15 @@ #include <stddef.h> #include <memory> +#include <optional> #include <string> #include <vector> // Forward declaration to avoid including "ui.h". class RecoveryUI; +class BootState; + class Device { public: static constexpr const int kNoAction = -1; @@ -58,6 +61,8 @@ class Device { REBOOT_FASTBOOT = 17, REBOOT_RECOVERY = 18, REBOOT_RESCUE = 19, + REBOOT_FROM_FASTBOOT = 20, + SHUTDOWN_FROM_FASTBOOT = 21, }; explicit Device(RecoveryUI* ui); @@ -124,9 +129,16 @@ class Device { return true; } + void SetBootState(const BootState* state); + // The getters for reason and stage may return std::nullopt until StartRecovery() is called. It's + // the caller's responsibility to perform the check and handle the exception. + std::optional<std::string> GetReason() const; + std::optional<std::string> GetStage() const; + private: // The RecoveryUI object that should be used to display the user interface for this device. std::unique_ptr<RecoveryUI> ui_; + const BootState* boot_state_{ nullptr }; }; // Disable name mangling, as this function will be loaded via dlsym(3). diff --git a/recovery_ui/include/recovery_ui/screen_ui.h b/recovery_ui/include/recovery_ui/screen_ui.h index 5cda2a2e5..92b3c2546 100644 --- a/recovery_ui/include/recovery_ui/screen_ui.h +++ b/recovery_ui/include/recovery_ui/screen_ui.h @@ -286,6 +286,9 @@ class ScreenRecoveryUI : public RecoveryUI, public DrawInterface { // selected. virtual int SelectMenu(int sel); + // Returns the help message displayed on top of the menu. + virtual std::vector<std::string> GetMenuHelpMessage() const; + virtual void draw_background_locked(); virtual void draw_foreground_locked(); virtual void draw_screen_locked(); diff --git a/recovery_ui/include/recovery_ui/stub_ui.h b/recovery_ui/include/recovery_ui/stub_ui.h index fb1d8c7a6..511b1314a 100644 --- a/recovery_ui/include/recovery_ui/stub_ui.h +++ b/recovery_ui/include/recovery_ui/stub_ui.h @@ -62,11 +62,9 @@ class StubRecoveryUI : public RecoveryUI { // menu display size_t ShowMenu(const std::vector<std::string>& /* headers */, - const std::vector<std::string>& /* items */, size_t initial_selection, + const std::vector<std::string>& /* items */, size_t /* initial_selection */, bool /* menu_only */, - const std::function<int(int, bool)>& /* key_handler */) override { - return initial_selection; - } + const std::function<int(int, bool)>& /* key_handler */) override; size_t ShowPromptWipeDataMenu(const std::vector<std::string>& /* backup_headers */, const std::vector<std::string>& /* backup_items */, diff --git a/recovery_ui/include/recovery_ui/ui.h b/recovery_ui/include/recovery_ui/ui.h index d55322cf0..08ec1d76a 100644 --- a/recovery_ui/include/recovery_ui/ui.h +++ b/recovery_ui/include/recovery_ui/ui.h @@ -27,6 +27,8 @@ #include <thread> #include <vector> +static constexpr const char* DEFAULT_LOCALE = "en-US"; + // Abstract class for controlling the user interface during recovery. class RecoveryUI { public: @@ -116,7 +118,7 @@ class RecoveryUI { // Returns true if you have the volume up/down and power trio typical of phones and tablets, false // otherwise. - virtual bool HasThreeButtons(); + virtual bool HasThreeButtons() const; // Returns true if it has a power key. virtual bool HasPowerKey() const; @@ -228,20 +230,23 @@ class RecoveryUI { bool InitScreensaver(); void SetScreensaverState(ScreensaverState state); + // Key event input queue std::mutex key_queue_mutex; std::condition_variable key_queue_cond; bool key_interrupted_; int key_queue[256], key_queue_len; - char key_pressed[KEY_MAX + 1]; // under key_queue_mutex - int key_last_down; // under key_queue_mutex - bool key_long_press; // under key_queue_mutex - int key_down_count; // under key_queue_mutex - bool enable_reboot; // under key_queue_mutex - int rel_sum; + // key press events + std::mutex key_press_mutex; + char key_pressed[KEY_MAX + 1]; + int key_last_down; + bool key_long_press; + int key_down_count; + bool enable_reboot; + + int rel_sum; int consecutive_power_keys; - int last_key; bool has_power_key; bool has_up_key; diff --git a/recovery_ui/screen_ui.cpp b/recovery_ui/screen_ui.cpp index 870db621c..087fc0e84 100644 --- a/recovery_ui/screen_ui.cpp +++ b/recovery_ui/screen_ui.cpp @@ -673,6 +673,19 @@ void ScreenRecoveryUI::SetTitle(const std::vector<std::string>& lines) { title_lines_ = lines; } +std::vector<std::string> ScreenRecoveryUI::GetMenuHelpMessage() const { + // clang-format off + static std::vector<std::string> REGULAR_HELP{ + "Use volume up/down and power.", + }; + static std::vector<std::string> LONG_PRESS_HELP{ + "Any button cycles highlight.", + "Long-press activates.", + }; + // clang-format on + return HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP; +} + // Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex // locked. void ScreenRecoveryUI::draw_screen_locked() { @@ -685,16 +698,7 @@ void ScreenRecoveryUI::draw_screen_locked() { gr_color(0, 0, 0, 255); gr_clear(); - // clang-format off - static std::vector<std::string> REGULAR_HELP{ - "Use volume up/down and power.", - }; - static std::vector<std::string> LONG_PRESS_HELP{ - "Any button cycles highlight.", - "Long-press activates.", - }; - // clang-format on - draw_menu_and_text_buffer_locked(HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); + draw_menu_and_text_buffer_locked(GetMenuHelpMessage()); } // Draws the menu and text buffer on the screen. Should only be called with updateMutex locked. @@ -817,12 +821,22 @@ std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadBitmap(const std::string& filen std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadLocalizedBitmap(const std::string& filename) { GRSurface* surface; - if (auto result = res_create_localized_alpha_surface(filename.c_str(), locale_.c_str(), &surface); - result < 0) { - LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")"; - return nullptr; + auto result = res_create_localized_alpha_surface(filename.c_str(), locale_.c_str(), &surface); + if (result == 0) { + return std::unique_ptr<GRSurface>(surface); } - return std::unique_ptr<GRSurface>(surface); + // TODO(xunchang) create a error code enum to refine the retry condition. + LOG(WARNING) << "Failed to load bitmap " << filename << " for locale " << locale_ << " (error " + << result << "). Falling back to use default locale."; + + result = res_create_localized_alpha_surface(filename.c_str(), DEFAULT_LOCALE, &surface); + if (result == 0) { + return std::unique_ptr<GRSurface>(surface); + } + + LOG(ERROR) << "Failed to load bitmap " << filename << " for locale " << DEFAULT_LOCALE + << " (error " << result << ")"; + return nullptr; } static char** Alloc2d(size_t rows, size_t cols) { @@ -1253,7 +1267,7 @@ size_t ScreenRecoveryUI::ShowMenu(const std::vector<std::string>& headers, return initial_selection; } - return ShowMenu(CreateMenu(headers, items, initial_selection), menu_only, key_handler); + return ShowMenu(std::move(menu), menu_only, key_handler); } size_t ScreenRecoveryUI::ShowPromptWipeDataMenu(const std::vector<std::string>& backup_headers, diff --git a/recovery_ui/stub_ui.cpp b/recovery_ui/stub_ui.cpp new file mode 100644 index 000000000..a56b3f725 --- /dev/null +++ b/recovery_ui/stub_ui.cpp @@ -0,0 +1,36 @@ +/* + * 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 "recovery_ui/stub_ui.h" + +#include <android-base/logging.h> + +#include "recovery_ui/device.h" + +size_t StubRecoveryUI::ShowMenu(const std::vector<std::string>& /* headers */, + const std::vector<std::string>& /* items */, + size_t /* initial_selection */, bool /* menu_only */, + const std::function<int(int, bool)>& /*key_handler*/) { + while (true) { + int key = WaitKey(); + // Exit the loop in the case of interruption or time out. + if (key == static_cast<int>(KeyError::INTERRUPTED) || + key == static_cast<int>(KeyError::TIMED_OUT)) { + return static_cast<size_t>(key); + } + } + LOG(FATAL) << "Unreachable key selected in ShowMenu of stub UI"; +} diff --git a/recovery_ui/ui.cpp b/recovery_ui/ui.cpp index b7107ff21..6f5cbbca6 100644 --- a/recovery_ui/ui.cpp +++ b/recovery_ui/ui.cpp @@ -70,7 +70,6 @@ RecoveryUI::RecoveryUI() key_down_count(0), enable_reboot(true), consecutive_power_keys(0), - last_key(-1), has_power_key(false), has_up_key(false), has_down_key(false), @@ -346,7 +345,7 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { bool long_press = false; { - std::lock_guard<std::mutex> lg(key_queue_mutex); + std::lock_guard<std::mutex> lg(key_press_mutex); key_pressed[key_code] = updown; if (updown) { ++key_down_count; @@ -375,7 +374,7 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { case RecoveryUI::REBOOT: if (reboot_enabled) { - reboot("reboot,"); + Reboot("userrequested,recovery,ui"); while (true) { pause(); } @@ -393,7 +392,7 @@ void RecoveryUI::TimeKey(int key_code, int count) { std::this_thread::sleep_for(750ms); // 750 ms == "long" bool long_press = false; { - std::lock_guard<std::mutex> lg(key_queue_mutex); + std::lock_guard<std::mutex> lg(key_press_mutex); if (key_last_down == key_code && key_down_count == count) { long_press = key_long_press = true; } @@ -419,7 +418,7 @@ void RecoveryUI::SetScreensaverState(ScreensaverState state) { LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_ << "%)"; } else { - LOG(ERROR) << "Unable to set brightness to normal"; + LOG(WARNING) << "Unable to set brightness to normal"; } break; case ScreensaverState::DIMMED: @@ -429,7 +428,7 @@ void RecoveryUI::SetScreensaverState(ScreensaverState state) { << "%)"; screensaver_state_ = ScreensaverState::DIMMED; } else { - LOG(ERROR) << "Unable to set brightness to dim"; + LOG(WARNING) << "Unable to set brightness to dim"; } break; case ScreensaverState::OFF: @@ -437,7 +436,7 @@ void RecoveryUI::SetScreensaverState(ScreensaverState state) { LOG(INFO) << "Brightness: 0 (off)"; screensaver_state_ = ScreensaverState::OFF; } else { - LOG(ERROR) << "Unable to set brightness to off"; + LOG(WARNING) << "Unable to set brightness to off"; } break; default: @@ -518,18 +517,18 @@ bool RecoveryUI::IsUsbConnected() { } bool RecoveryUI::IsKeyPressed(int key) { - std::lock_guard<std::mutex> lg(key_queue_mutex); + std::lock_guard<std::mutex> lg(key_press_mutex); int pressed = key_pressed[key]; return pressed; } bool RecoveryUI::IsLongPress() { - std::lock_guard<std::mutex> lg(key_queue_mutex); + std::lock_guard<std::mutex> lg(key_press_mutex); bool result = key_long_press; return result; } -bool RecoveryUI::HasThreeButtons() { +bool RecoveryUI::HasThreeButtons() const { return has_power_key && has_up_key && has_down_key; } @@ -548,7 +547,7 @@ void RecoveryUI::FlushKeys() { RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { { - std::lock_guard<std::mutex> lg(key_queue_mutex); + std::lock_guard<std::mutex> lg(key_press_mutex); key_long_press = false; } @@ -585,13 +584,12 @@ RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { consecutive_power_keys = 0; } - last_key = key; return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE; } void RecoveryUI::KeyLongPress(int) {} void RecoveryUI::SetEnableReboot(bool enabled) { - std::lock_guard<std::mutex> lg(key_queue_mutex); + std::lock_guard<std::mutex> lg(key_press_mutex); enable_reboot = enabled; } diff --git a/recovery_utils/Android.bp b/recovery_utils/Android.bp new file mode 100644 index 000000000..bf79a2e87 --- /dev/null +++ b/recovery_utils/Android.bp @@ -0,0 +1,81 @@ +// 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. + +cc_defaults { + name: "librecovery_utils_defaults", + + defaults: [ + "recovery_defaults", + ], + + shared_libs: [ + "android.hardware.health@2.0", + "libbase", + "libext4_utils", + "libfs_mgr", + "libhidlbase", + "libselinux", + "libutils", + ], + + static_libs: [ + "libotautil", + + // External dependencies. + "libfstab", + "libhealthhalutils", + ], +} + +// A utility lib that's local to recovery (in contrast, libotautil is exposed to device-specific +// recovery_ui lib as well as device-specific updater). +cc_library_static { + name: "librecovery_utils", + + recovery_available: true, + + defaults: [ + "librecovery_utils_defaults", + ], + + srcs: [ + "battery_utils.cpp", + "logging.cpp", + "parse_install_logs.cpp", + "roots.cpp", + "thermalutil.cpp", + ], + + header_libs: [ + "libvold_headers", + ], + + export_include_dirs: [ + "include", + ], + + export_static_lib_headers: [ + // roots.h includes <fstab/fstab.h>. + "libfstab", + ], + + // Should avoid exposing to the libs that might be used in device-specific codes (e.g. + // libedify, libotautil, librecovery_ui). + visibility: [ + "//bootable/recovery", + "//bootable/recovery/install", + "//bootable/recovery/minadbd", + "//bootable/recovery/tests", + ], +} diff --git a/recovery_utils/battery_utils.cpp b/recovery_utils/battery_utils.cpp new file mode 100644 index 000000000..323f52537 --- /dev/null +++ b/recovery_utils/battery_utils.cpp @@ -0,0 +1,89 @@ +/* + * 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 "recovery_utils/battery_utils.h" + +#include <stdint.h> +#include <unistd.h> + +#include <android-base/logging.h> +#include <healthhalutils/HealthHalUtils.h> + +BatteryInfo GetBatteryInfo() { + using android::hardware::health::V1_0::BatteryStatus; + using android::hardware::health::V2_0::get_health_service; + using android::hardware::health::V2_0::IHealth; + using android::hardware::health::V2_0::Result; + using android::hardware::health::V2_0::toString; + + android::sp<IHealth> health = get_health_service(); + + int wait_second = 0; + while (true) { + auto charge_status = BatteryStatus::UNKNOWN; + + if (health == nullptr) { + LOG(WARNING) << "No health implementation is found; assuming defaults"; + } else { + health + ->getChargeStatus([&charge_status](auto res, auto out_status) { + if (res == Result::SUCCESS) { + charge_status = out_status; + } + }) + .isOk(); // should not have transport error + } + + // Treat unknown status as on charger. See hardware/interfaces/health/1.0/types.hal for the + // meaning of the return values. + bool charging = (charge_status != BatteryStatus::DISCHARGING && + charge_status != BatteryStatus::NOT_CHARGING); + + Result res = Result::UNKNOWN; + int32_t capacity = INT32_MIN; + if (health != nullptr) { + health + ->getCapacity([&res, &capacity](auto out_res, auto out_capacity) { + res = out_res; + capacity = out_capacity; + }) + .isOk(); // should not have transport error + } + + LOG(INFO) << "charge_status " << toString(charge_status) << ", charging " << charging + << ", status " << toString(res) << ", capacity " << capacity; + + constexpr int BATTERY_READ_TIMEOUT_IN_SEC = 10; + // At startup, the battery drivers in devices like N5X/N6P take some time to load + // the battery profile. Before the load finishes, it reports value 50 as a fake + // capacity. BATTERY_READ_TIMEOUT_IN_SEC is set that the battery drivers are expected + // to finish loading the battery profile earlier than 10 seconds after kernel startup. + if (res == Result::SUCCESS && capacity == 50) { + if (wait_second < BATTERY_READ_TIMEOUT_IN_SEC) { + sleep(1); + wait_second++; + continue; + } + } + // If we can't read battery percentage, it may be a device without battery. In this + // situation, use 100 as a fake battery percentage. + if (res != Result::SUCCESS) { + capacity = 100; + } + + return BatteryInfo{ charging, capacity }; + } +} diff --git a/recovery_utils/include/recovery_utils/battery_utils.h b/recovery_utils/include/recovery_utils/battery_utils.h new file mode 100644 index 000000000..a95f71dca --- /dev/null +++ b/recovery_utils/include/recovery_utils/battery_utils.h @@ -0,0 +1,33 @@ +/* + * 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 <stdint.h> + +struct BatteryInfo { + // Whether the device is on charger. Note that the value will be `true` if the battery status is + // unknown (BATTERY_STATUS_UNKNOWN). + bool charging; + + // The remaining battery capacity percentage (i.e. between 0 and 100). See getCapacity in + // hardware/interfaces/health/2.0/IHealth.hal. Returns 100 in case it fails to read a value from + // the health HAL. + int32_t capacity; +}; + +// Returns the battery status for OTA installation purpose. +BatteryInfo GetBatteryInfo(); diff --git a/otautil/include/otautil/logging.h b/recovery_utils/include/recovery_utils/logging.h index 608349785..4462eca6e 100644 --- a/otautil/include/otautil/logging.h +++ b/recovery_utils/include/recovery_utils/logging.h @@ -53,7 +53,7 @@ void rotate_logs(const char* last_log_file, const char* last_kmsg_file); void check_and_fclose(FILE* fp, const std::string& name); void copy_log_file_to_pmsg(const std::string& source, const std::string& destination); -void copy_logs(bool save_current_log, bool has_cache, const selabel_handle* sehandle); +void copy_logs(bool save_current_log); void reset_tmplog_offset(); void save_kernel_log(const char* destination); diff --git a/otautil/include/otautil/parse_install_logs.h b/recovery_utils/include/recovery_utils/parse_install_logs.h index 135d29ccf..135d29ccf 100644 --- a/otautil/include/otautil/parse_install_logs.h +++ b/recovery_utils/include/recovery_utils/parse_install_logs.h diff --git a/otautil/include/otautil/roots.h b/recovery_utils/include/recovery_utils/roots.h index 482f3d050..92ee756f0 100644 --- a/otautil/include/otautil/roots.h +++ b/recovery_utils/include/recovery_utils/roots.h @@ -54,6 +54,5 @@ int format_volume(const std::string& volume, const std::string& directory); // mounted (/tmp and /cache) are mounted. Returns 0 on success. int setup_install_mounts(); -bool logical_partitions_mapped(); - -std::string get_system_root(); +// Returns true if there is /cache in the volumes. +bool HasCache(); diff --git a/otautil/include/otautil/thermalutil.h b/recovery_utils/include/recovery_utils/thermalutil.h index 43ab55940..43ab55940 100644 --- a/otautil/include/otautil/thermalutil.h +++ b/recovery_utils/include/recovery_utils/thermalutil.h diff --git a/otautil/logging.cpp b/recovery_utils/logging.cpp index 484f1150f..52f12a8d8 100644 --- a/otautil/logging.cpp +++ b/recovery_utils/logging.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "otautil/logging.h" +#include "recovery_utils/logging.h" #include <dirent.h> #include <errno.h> @@ -38,7 +38,7 @@ #include "otautil/dirutil.h" #include "otautil/paths.h" -#include "otautil/roots.h" +#include "recovery_utils/roots.h" constexpr const char* LOG_FILE = "/cache/recovery/log"; constexpr const char* LAST_INSTALL_FILE = "/cache/recovery/last_install"; @@ -178,9 +178,8 @@ void reset_tmplog_offset() { tmplog_offset = 0; } -static void copy_log_file(const std::string& source, const std::string& destination, bool append, - const selabel_handle* sehandle) { - FILE* dest_fp = fopen_path(destination, append ? "ae" : "we", sehandle); +static void copy_log_file(const std::string& source, const std::string& destination, bool append) { + FILE* dest_fp = fopen_path(destination, append ? "ae" : "we", logging_sehandle); if (dest_fp == nullptr) { PLOG(ERROR) << "Can't open " << destination; } else { @@ -203,7 +202,7 @@ static void copy_log_file(const std::string& source, const std::string& destinat } } -void copy_logs(bool save_current_log, bool has_cache, const selabel_handle* sehandle) { +void copy_logs(bool save_current_log) { // We only rotate and record the log of the current session if explicitly requested. This usually // happens after wipes, installation from BCB or menu selections. This is to avoid unnecessary // rotation (and possible deletion) of log files, if it does not do anything loggable. @@ -216,7 +215,7 @@ void copy_logs(bool save_current_log, bool has_cache, const selabel_handle* seha copy_log_file_to_pmsg(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE); // We can do nothing for now if there's no /cache partition. - if (!has_cache) { + if (!HasCache()) { return; } @@ -225,9 +224,9 @@ void copy_logs(bool save_current_log, bool has_cache, const selabel_handle* seha rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE); // Copy logs to cache so the system can find out what happened. - copy_log_file(Paths::Get().temporary_log_file(), LOG_FILE, true, sehandle); - copy_log_file(Paths::Get().temporary_log_file(), LAST_LOG_FILE, false, sehandle); - copy_log_file(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE, false, sehandle); + copy_log_file(Paths::Get().temporary_log_file(), LOG_FILE, true); + copy_log_file(Paths::Get().temporary_log_file(), LAST_LOG_FILE, false); + copy_log_file(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE, false); save_kernel_log(LAST_KMSG_FILE); chmod(LOG_FILE, 0600); chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM); @@ -319,7 +318,7 @@ bool RestoreLogFilesAfterFormat(const std::vector<saved_log_file>& log_files) { // Reset the pointer so we copy from the beginning of the temp // log. reset_tmplog_offset(); - copy_logs(true /* save_current_log */, true /* has_cache */, logging_sehandle); + copy_logs(true /* save_current_log */); return true; } diff --git a/otautil/parse_install_logs.cpp b/recovery_utils/parse_install_logs.cpp index 13a729921..c86317623 100644 --- a/otautil/parse_install_logs.cpp +++ b/recovery_utils/parse_install_logs.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "otautil/parse_install_logs.h" +#include "recovery_utils/parse_install_logs.h" #include <unistd.h> diff --git a/otautil/roots.cpp b/recovery_utils/roots.cpp index 815d644e5..fe3a07aa2 100644 --- a/otautil/roots.cpp +++ b/recovery_utils/roots.cpp @@ -14,15 +14,12 @@ * limitations under the License. */ -#include "otautil/roots.h" +#include "recovery_utils/roots.h" -#include <ctype.h> #include <fcntl.h> -#include <inttypes.h> #include <stdint.h> #include <stdlib.h> #include <string.h> -#include <sys/mount.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> @@ -33,16 +30,13 @@ #include <vector> #include <android-base/logging.h> -#include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android-base/unique_fd.h> #include <cryptfs.h> #include <ext4_utils/wipe.h> #include <fs_mgr.h> #include <fs_mgr/roots.h> -#include <fs_mgr_dm_linear.h> -#include "otautil/mounts.h" #include "otautil/sysutil.h" using android::fs_mgr::Fstab; @@ -51,6 +45,8 @@ using android::fs_mgr::ReadDefaultFstab; static Fstab fstab; +constexpr const char* CACHE_ROOT = "/cache"; + void load_volume_table() { if (!ReadDefaultFstab(&fstab)) { LOG(ERROR) << "Failed to read default fstab"; @@ -58,7 +54,11 @@ void load_volume_table() { } fstab.emplace_back(FstabEntry{ - .mount_point = "/tmp", .fs_type = "ramdisk", .blk_device = "ramdisk", .length = 0 }); + .blk_device = "ramdisk", + .mount_point = "/tmp", + .fs_type = "ramdisk", + .length = 0, + }); std::cout << "recovery filesystem table" << std::endl << "=========================" << std::endl; for (size_t i = 0; i < fstab.size(); ++i) { @@ -276,10 +276,8 @@ int setup_install_mounts() { return 0; } -bool logical_partitions_mapped() { - return android::fs_mgr::LogicalPartitionsMapped(); -} - -std::string get_system_root() { - return android::fs_mgr::GetSystemRoot(); +bool HasCache() { + CHECK(!fstab.empty()); + static bool has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr; + return has_cache; } diff --git a/otautil/thermalutil.cpp b/recovery_utils/thermalutil.cpp index 4660e057e..5436355d6 100644 --- a/otautil/thermalutil.cpp +++ b/recovery_utils/thermalutil.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "otautil/thermalutil.h" +#include "recovery_utils/thermalutil.h" #include <dirent.h> #include <stdio.h> diff --git a/tests/Android.bp b/tests/Android.bp index 09ef716d6..5b881e367 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -50,13 +50,11 @@ cc_defaults { }, } -// libapplypatch, libapplypatch_modes, libimgdiff, libimgpatch +// libapplypatch, libapplypatch_modes libapplypatch_static_libs = [ "libapplypatch_modes", "libapplypatch", "libedify", - "libimgdiff", - "libimgpatch", "libotautil", "libbsdiff", "libbspatch", @@ -66,7 +64,6 @@ libapplypatch_static_libs = [ "libbase", "libbrotli", "libbz", - "libcrypto", "libz", "libziparchive", ] @@ -79,6 +76,8 @@ librecovery_static_libs = [ "libinstall", "librecovery_ui", "libminui", + "libfusesideload", + "libbootloader_message", "libotautil", "libhealthhalutils", @@ -87,14 +86,10 @@ librecovery_static_libs = [ "android.hardware.health@2.0", "android.hardware.health@1.0", - "libbootloader_message", "libext4_utils", "libfs_mgr", - "libfusesideload", "libhidl-gen-utils", "libhidlbase", - "libhidltransport", - "libhwbinder_noltopgo", "libbinderthreadstate", "liblp", "libvndksupport", @@ -104,9 +99,12 @@ librecovery_static_libs = [ cc_test { name: "recovery_unit_test", isolated: true, + require_root: true, defaults: [ "recovery_test_defaults", + "libupdater_defaults", + "libupdater_device_defaults", ], test_suites: ["device-tests"], @@ -115,16 +113,24 @@ cc_test { "unit/*.cpp", ], - static_libs: libapplypatch_static_libs + [ - "libinstall", + static_libs: libapplypatch_static_libs + librecovery_static_libs + [ "librecovery_ui", + "libfusesideload", "libminui", + "librecovery_utils", "libotautil", - "libupdater", + "libupdater_device", + "libupdater_core", + "libupdate_verifier", + "libgtest_prod", + "libprotobuf-cpp-lite", ], - data: ["testdata/*"], + data: [ + "testdata/*", + ":res-testdata", + ], } cc_test { @@ -142,66 +148,37 @@ cc_test { ], } -cc_test { - name: "recovery_component_test", - isolated: true, - - defaults: [ - "recovery_test_defaults", - "libupdater_defaults", - ], - - test_suites: ["device-tests"], - - srcs: [ - "component/*.cpp", - ], - - static_libs: libapplypatch_static_libs + librecovery_static_libs + [ - "libupdater", - "libupdate_verifier", - "libprotobuf-cpp-lite", - ], - - data: [ - "testdata/*", - ":res-testdata", - ], -} - cc_test_host { name: "recovery_host_test", isolated: true, defaults: [ "recovery_test_defaults", + "libupdater_defaults", ], srcs: [ - "component/imgdiff_test.cpp", + "unit/host/*", ], static_libs: [ + "libupdater_host", + "libupdater_core", "libimgdiff", - "libimgpatch", - "libotautil", "libbsdiff", - "libbspatch", - "libziparchive", - "libutils", - "libcrypto", - "libbrotli", - "libbz", "libdivsufsort64", "libdivsufsort", - "libz", + "libfstab", + "libc++fs", ], + test_suites: ["general-tests"], + data: ["testdata/*"], target: { darwin: { - // libimgdiff is not available on the Mac. + // libapplypatch in "libupdater_defaults" is not available on the Mac. enabled: false, }, }, diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml deleted file mode 100644 index 6b86085aa..000000000 --- a/tests/AndroidTest.xml +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2017 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. ---> -<configuration description="Config for recovery_component_test and recovery_unit_test"> - <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> - <option name="cleanup" value="true" /> - <option name="push" value="recovery_component_test->/data/local/tmp/recovery_component_test/recovery_component_test" /> - <option name="push" value="testdata->/data/local/tmp/recovery_component_test/testdata" /> - <option name="push" value="recovery_unit_test->/data/local/tmp/recovery_unit_test/recovery_unit_test" /> - <option name="push" value="testdata->/data/local/tmp/recovery_unit_test/testdata" /> - </target_preparer> - <option name="test-suite-tag" value="apct" /> - <test class="com.android.tradefed.testtype.GTest" > - <option name="native-test-device-path" value="/data/local/tmp/recovery_component_test" /> - <option name="module-name" value="recovery_component_test" /> - </test> - <test class="com.android.tradefed.testtype.GTest" > - <option name="native-test-device-path" value="/data/local/tmp/recovery_unit_test" /> - <option name="module-name" value="recovery_unit_test" /> - </test> -</configuration> diff --git a/tests/component/resources_test.cpp b/tests/component/resources_test.cpp deleted file mode 100644 index d7fdb8fa0..000000000 --- a/tests/component/resources_test.cpp +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <dirent.h> -#include <stdio.h> -#include <stdlib.h> - -#include <memory> -#include <string> -#include <vector> - -#include <android-base/file.h> -#include <android-base/strings.h> -#include <gtest/gtest.h> -#include <png.h> - -#include "minui/minui.h" -#include "private/resources.h" - -static const std::string kLocale = "zu"; - -static const std::vector<std::string> kResourceImagesDirs{ - "res-mdpi/images/", "res-hdpi/images/", "res-xhdpi/images/", - "res-xxhdpi/images/", "res-xxxhdpi/images/", -}; - -static int png_filter(const dirent* de) { - if (de->d_type != DT_REG || !android::base::EndsWith(de->d_name, "_text.png")) { - return 0; - } - return 1; -} - -// Finds out all the PNG files to test, which stay under the same dir with the executabl.. -static std::vector<std::string> add_files() { - std::vector<std::string> files; - for (const std::string& images_dir : kResourceImagesDirs) { - static std::string exec_dir = android::base::GetExecutableDirectory(); - std::string dir_path = exec_dir + "/" + images_dir; - dirent** namelist; - int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort); - if (n == -1) { - printf("Failed to scandir %s: %s\n", dir_path.c_str(), strerror(errno)); - continue; - } - if (n == 0) { - printf("No file is added for test in %s\n", dir_path.c_str()); - } - - while (n--) { - std::string file_path = dir_path + namelist[n]->d_name; - files.push_back(file_path); - free(namelist[n]); - } - free(namelist); - } - return files; -} - -class ResourcesTest : public testing::TestWithParam<std::string> { - public: - static std::vector<std::string> png_list; - - protected: - void SetUp() override { - png_ = std::make_unique<PngHandler>(GetParam()); - ASSERT_TRUE(png_); - - ASSERT_EQ(PNG_COLOR_TYPE_GRAY, png_->color_type()) << "Recovery expects grayscale PNG file."; - ASSERT_LT(static_cast<png_uint_32>(5), png_->width()); - ASSERT_LT(static_cast<png_uint_32>(0), png_->height()); - ASSERT_EQ(1, png_->channels()) << "Recovery background text images expects 1-channel PNG file."; - } - - std::unique_ptr<PngHandler> png_{ nullptr }; -}; - -// Parses a png file and tests if it's qualified for the background text image under recovery. -TEST_P(ResourcesTest, ValidateLocale) { - std::vector<unsigned char> row(png_->width()); - for (png_uint_32 y = 0; y < png_->height(); ++y) { - png_read_row(png_->png_ptr(), row.data(), nullptr); - int w = (row[1] << 8) | row[0]; - int h = (row[3] << 8) | row[2]; - int len = row[4]; - EXPECT_LT(0, w); - EXPECT_LT(0, h); - EXPECT_LT(0, len) << "Locale string should be non-empty."; - EXPECT_NE(0, row[5]) << "Locale string is missing."; - - ASSERT_GE(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file."; - char* loc = reinterpret_cast<char*>(&row[5]); - if (matches_locale(loc, kLocale.c_str())) { - EXPECT_TRUE(android::base::StartsWith(loc, kLocale)); - break; - } - for (int i = 0; i < h; ++i, ++y) { - png_read_row(png_->png_ptr(), row.data(), nullptr); - } - } -} - -std::vector<std::string> ResourcesTest::png_list = add_files(); - -INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourcesTest, - ::testing::ValuesIn(ResourcesTest::png_list.cbegin(), - ResourcesTest::png_list.cend())); diff --git a/tests/component/applypatch_modes_test.cpp b/tests/unit/applypatch_modes_test.cpp index 08414b796..08414b796 100644 --- a/tests/component/applypatch_modes_test.cpp +++ b/tests/unit/applypatch_modes_test.cpp diff --git a/tests/unit/applypatch_test.cpp b/tests/unit/applypatch_test.cpp index 794f2c103..218a224f8 100644 --- a/tests/unit/applypatch_test.cpp +++ b/tests/unit/applypatch_test.cpp @@ -141,7 +141,7 @@ TEST_F(ApplyPatchTest, PatchPartition) { ASSERT_TRUE(LoadFileContents(from_testdata_base("bonus.file"), &bonus_fc)); Value bonus(Value::Type::BLOB, std::string(bonus_fc.data.cbegin(), bonus_fc.data.cend())); - ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, &bonus)); + ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, &bonus, false)); } // Tests patching an eMMC target without a separate bonus file (i.e. recovery-from-boot patch has @@ -151,7 +151,7 @@ TEST_F(ApplyPatchTest, PatchPartitionWithoutBonusFile) { ASSERT_TRUE(LoadFileContents(from_testdata_base("recovery-from-boot-with-bonus.p"), &patch_fc)); Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend())); - ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, nullptr)); + ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, nullptr, false)); } class FreeCacheTest : public ::testing::Test { diff --git a/tests/unit/battery_utils_test.cpp b/tests/unit/battery_utils_test.cpp new file mode 100644 index 000000000..55639fdb5 --- /dev/null +++ b/tests/unit/battery_utils_test.cpp @@ -0,0 +1,27 @@ +/* + * 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 agree 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 <android-base/logging.h> +#include <gtest/gtest.h> + +#include "recovery_utils/battery_utils.h" + +TEST(BatteryInfoTest, GetBatteryInfo) { + auto info = GetBatteryInfo(); + // 0 <= capacity <= 100 + ASSERT_LE(0, info.capacity); + ASSERT_LE(info.capacity, 100); +} diff --git a/tests/component/bootloader_message_test.cpp b/tests/unit/bootloader_message_test.cpp index 95d875e69..95d875e69 100644 --- a/tests/component/bootloader_message_test.cpp +++ b/tests/unit/bootloader_message_test.cpp diff --git a/tests/component/edify_test.cpp b/tests/unit/edify_test.cpp index 8397bd38e..8397bd38e 100644 --- a/tests/component/edify_test.cpp +++ b/tests/unit/edify_test.cpp diff --git a/tests/unit/fuse_provider_test.cpp b/tests/unit/fuse_provider_test.cpp new file mode 100644 index 000000000..37f99f92e --- /dev/null +++ b/tests/unit/fuse_provider_test.cpp @@ -0,0 +1,104 @@ +/* + * 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 <stdint.h> +#include <unistd.h> + +#include <functional> +#include <string> +#include <vector> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/strings.h> +#include <android-base/unique_fd.h> +#include <gtest/gtest.h> + +#include "fuse_provider.h" +#include "fuse_sideload.h" +#include "install/install.h" + +TEST(FuseBlockMapTest, CreateFromBlockMap_smoke) { + TemporaryFile fake_block_device; + std::vector<std::string> lines = { + fake_block_device.path, "10000 4096", "3", "10 11", "20 21", "22 23", + }; + + TemporaryFile temp_file; + android::base::WriteStringToFile(android::base::Join(lines, '\n'), temp_file.path); + auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(temp_file.path, 4096); + + ASSERT_TRUE(block_map_data); + ASSERT_EQ(10000, block_map_data->file_size()); + ASSERT_EQ(4096, block_map_data->fuse_block_size()); + ASSERT_EQ(RangeSet({ { 10, 11 }, { 20, 21 }, { 22, 23 } }), + static_cast<FuseBlockDataProvider*>(block_map_data.get())->ranges()); +} + +TEST(FuseBlockMapTest, ReadBlockAlignedData_smoke) { + std::string content; + content.reserve(40960); + for (char c = 0; c < 10; c++) { + content += std::string(4096, c); + } + TemporaryFile fake_block_device; + ASSERT_TRUE(android::base::WriteStringToFile(content, fake_block_device.path)); + + std::vector<std::string> lines = { + fake_block_device.path, + "20000 4096", + "1", + "0 5", + }; + TemporaryFile temp_file; + android::base::WriteStringToFile(android::base::Join(lines, '\n'), temp_file.path); + auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(temp_file.path, 4096); + + std::vector<uint8_t> result(2000); + ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 2000, 1)); + ASSERT_EQ(std::vector<uint8_t>(content.begin() + 4096, content.begin() + 6096), result); + + result.resize(20000); + ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 0)); + ASSERT_EQ(std::vector<uint8_t>(content.begin(), content.begin() + 20000), result); +} + +TEST(FuseBlockMapTest, ReadBlockAlignedData_large_fuse_block) { + std::string content; + for (char c = 0; c < 10; c++) { + content += std::string(4096, c); + } + + TemporaryFile temp_file; + ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path)); + + std::vector<std::string> lines = { + temp_file.path, "36384 4096", "2", "0 5", "6 10", + }; + TemporaryFile block_map; + ASSERT_TRUE(android::base::WriteStringToFile(android::base::Join(lines, '\n'), block_map.path)); + + auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(block_map.path, 16384); + ASSERT_TRUE(block_map_data); + + std::vector<uint8_t> result(20000); + // Out of bound read + ASSERT_FALSE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 2)); + ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 1)); + // expected source block contains: 4, 6-9 + std::string expected = content.substr(16384, 4096) + content.substr(24576, 15904); + ASSERT_EQ(std::vector<uint8_t>(expected.begin(), expected.end()), result); +} diff --git a/tests/component/sideload_test.cpp b/tests/unit/fuse_sideload_test.cpp index 6add99f41..ea895038c 100644 --- a/tests/component/sideload_test.cpp +++ b/tests/unit/fuse_sideload_test.cpp @@ -40,6 +40,10 @@ class FuseTestDataProvider : public FuseDataProvider { bool ReadBlockAlignedData(uint8_t*, uint32_t, uint32_t) const override { return true; } + + bool Valid() const override { + return true; + } }; TEST(SideloadTest, run_fuse_sideload_wrong_parameters) { diff --git a/tests/component/imgdiff_test.cpp b/tests/unit/host/imgdiff_test.cpp index e76ccbdfb..e76ccbdfb 100644 --- a/tests/component/imgdiff_test.cpp +++ b/tests/unit/host/imgdiff_test.cpp diff --git a/tests/unit/host/update_simulator_test.cpp b/tests/unit/host/update_simulator_test.cpp new file mode 100644 index 000000000..fb1217877 --- /dev/null +++ b/tests/unit/host/update_simulator_test.cpp @@ -0,0 +1,403 @@ +/* + * 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 <stdint.h> +#include <stdio.h> + +#include <map> +#include <string> +#include <vector> + +#include <android-base/file.h> +#include <android-base/stringprintf.h> +#include <android-base/strings.h> +#include <bsdiff/bsdiff.h> +#include <gtest/gtest.h> +#include <ziparchive/zip_writer.h> + +#include "otautil/paths.h" +#include "otautil/print_sha1.h" +#include "updater/blockimg.h" +#include "updater/build_info.h" +#include "updater/install.h" +#include "updater/simulator_runtime.h" +#include "updater/target_files.h" +#include "updater/updater.h" + +using std::string; + +// echo -n "system.img" > system.img && img2simg system.img sparse_system_string_.img 4096 && +// hexdump -v -e '" " 12/1 "0x%02x, " "\n"' sparse_system_string_.img +// The total size of the result sparse image is 4136 bytes; and we can append 0s in the end to get +// the full image. +constexpr uint8_t SPARSE_SYSTEM_HEADER[] = { + 0x3a, 0xff, 0x26, 0xed, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0xca, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x10, 0x00, 0x00, 0x73, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x2e, 0x69, 0x6d, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static void AddZipEntries(int fd, const std::map<string, string>& entries) { + FILE* zip_file = fdopen(fd, "w"); + ZipWriter writer(zip_file); + for (const auto& pair : entries) { + ASSERT_EQ(0, writer.StartEntry(pair.first.c_str(), 0)); + ASSERT_EQ(0, writer.WriteBytes(pair.second.data(), pair.second.size())); + ASSERT_EQ(0, writer.FinishEntry()); + } + ASSERT_EQ(0, writer.Finish()); + ASSERT_EQ(0, fclose(zip_file)); +} + +static string CalculateSha1(const string& data) { + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest); + return print_sha1(digest); +} + +static void CreateBsdiffPatch(const string& src, const string& tgt, string* patch) { + TemporaryFile patch_file; + ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src.data()), src.size(), + reinterpret_cast<const uint8_t*>(tgt.data()), tgt.size(), + patch_file.path, nullptr)); + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, patch)); +} + +static void RunSimulation(std::string_view src_tf, std::string_view ota_package, bool expected) { + TemporaryFile cmd_pipe; + 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); + + // Configure edify's functions. + RegisterBuiltins(); + RegisterInstallFunctions(); + RegisterBlockImageFunctions(); + + // Run the update simulation and check the result. + TemporaryDir work_dir; + BuildInfo build_info(work_dir.path, false); + ASSERT_TRUE(build_info.ParseTargetFile(src_tf, false)); + Updater updater(std::make_unique<SimulatorRuntime>(&build_info)); + ASSERT_TRUE(updater.Init(cmd_pipe.release(), ota_package, false)); + ASSERT_EQ(expected, updater.RunUpdate()); + // TODO(xunchang) check the recovery&system has the expected contents. +} + +class UpdateSimulatorTest : public ::testing::Test { + protected: + void SetUp() override { + std::vector<string> props = { + "import /oem/oem.prop oem*", + "# begin build properties", + "# autogenerated by buildinfo.sh", + "ro.build.id=OPR1.170510.001", + "ro.build.display.id=OPR1.170510.001 dev-keys", + "ro.build.version.incremental=3993052", + "ro.build.version.release=O", + "ro.build.date=Wed May 10 11:10:29 UTC 2017", + "ro.build.date.utc=1494414629", + "ro.build.type=user", + "ro.build.tags=dev-keys", + "ro.build.flavor=angler-user", + "ro.product.system.brand=google", + "ro.product.system.name=angler", + "ro.product.system.device=angler", + }; + build_prop_string_ = android::base::Join(props, "\n"); + + fstab_content_ = R"( +#<src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags> +# More comments..... + +/dev/block/by-name/system /system ext4 ro,barrier=1 wait +/dev/block/by-name/vendor /vendor ext4 ro wait,verify=/dev/metadata +/dev/block/by-name/cache /cache ext4 noatime,errors=panic wait,check +/dev/block/by-name/modem /firmware vfat ro,uid=1000,gid=1000, wait +/dev/block/by-name/boot /boot emmc defaults defaults +/dev/block/by-name/recovery /recovery emmc defaults defaults +/dev/block/by-name/misc /misc emmc defaults +/dev/block/by-name/modem /modem emmc defaults defaults)"; + + raw_system_string_ = "system.img" + string(4086, '\0'); // raw image is 4096 bytes in total + sparse_system_string_ = string(SPARSE_SYSTEM_HEADER, std::end(SPARSE_SYSTEM_HEADER)) + + string(4136 - sizeof(SPARSE_SYSTEM_HEADER), '\0'); + } + + string build_prop_string_; + string fstab_content_; + string raw_system_string_; + string sparse_system_string_; +}; + +TEST_F(UpdateSimulatorTest, TargetFile_ExtractImage) { + TemporaryFile zip_file; + AddZipEntries(zip_file.release(), { { "META/misc_info.txt", "extfs_sparse_flag=-s" }, + { "IMAGES/system.img", sparse_system_string_ } }); + TargetFile target_file(zip_file.path, false); + ASSERT_TRUE(target_file.Open()); + + TemporaryDir temp_dir; + TemporaryFile raw_image; + ASSERT_TRUE(target_file.ExtractImage( + "IMAGES/system.img", FstabInfo("/dev/system", "system", "ext4"), temp_dir.path, &raw_image)); + + // Check the raw image has expected contents. + string content; + ASSERT_TRUE(android::base::ReadFileToString(raw_image.path, &content)); + string expected_content = "system.img" + string(4086, '\0'); + ASSERT_EQ(expected_content, content); +} + +TEST_F(UpdateSimulatorTest, TargetFile_ParseFstabInfo) { + TemporaryFile zip_file; + AddZipEntries(zip_file.release(), + { { "META/misc_info.txt", "" }, + { "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ } }); + TargetFile target_file(zip_file.path, false); + ASSERT_TRUE(target_file.Open()); + + std::vector<FstabInfo> fstab_info; + EXPECT_TRUE(target_file.ParseFstabInfo(&fstab_info)); + + std::vector<std::vector<string>> transformed; + std::transform(fstab_info.begin(), fstab_info.end(), std::back_inserter(transformed), + [](const FstabInfo& info) { + return std::vector<string>{ info.blockdev_name, info.mount_point, info.fs_type }; + }); + + std::vector<std::vector<string>> expected = { + { "/dev/block/by-name/system", "/system", "ext4" }, + { "/dev/block/by-name/vendor", "/vendor", "ext4" }, + { "/dev/block/by-name/cache", "/cache", "ext4" }, + { "/dev/block/by-name/boot", "/boot", "emmc" }, + { "/dev/block/by-name/recovery", "/recovery", "emmc" }, + { "/dev/block/by-name/misc", "/misc", "emmc" }, + { "/dev/block/by-name/modem", "/modem", "emmc" }, + }; + EXPECT_EQ(expected, transformed); +} + +TEST_F(UpdateSimulatorTest, BuildInfo_ParseTargetFile) { + std::map<string, string> entries = { + { "META/misc_info.txt", "" }, + { "SYSTEM/build.prop", build_prop_string_ }, + { "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ }, + { "IMAGES/recovery.img", "" }, + { "IMAGES/boot.img", "" }, + { "IMAGES/misc.img", "" }, + { "IMAGES/system.map", "" }, + { "IMAGES/system.img", sparse_system_string_ }, + }; + + TemporaryFile zip_file; + AddZipEntries(zip_file.release(), entries); + + TemporaryDir temp_dir; + BuildInfo build_info(temp_dir.path, false); + ASSERT_TRUE(build_info.ParseTargetFile(zip_file.path, false)); + + std::map<string, string> expected_result = { + { "ro.build.id", "OPR1.170510.001" }, + { "ro.build.display.id", "OPR1.170510.001 dev-keys" }, + { "ro.build.version.incremental", "3993052" }, + { "ro.build.version.release", "O" }, + { "ro.build.date", "Wed May 10 11:10:29 UTC 2017" }, + { "ro.build.date.utc", "1494414629" }, + { "ro.build.type", "user" }, + { "ro.build.tags", "dev-keys" }, + { "ro.build.flavor", "angler-user" }, + { "ro.product.brand", "google" }, + { "ro.product.name", "angler" }, + { "ro.product.device", "angler" }, + }; + + for (const auto& [key, value] : expected_result) { + ASSERT_EQ(value, build_info.GetProperty(key, "")); + } + + // Check that the temp files for each block device are created successfully. + for (auto name : { "/dev/block/by-name/system", "/dev/block/by-name/recovery", + "/dev/block/by-name/boot", "/dev/block/by-name/misc" }) { + ASSERT_EQ(0, access(build_info.FindBlockDeviceName(name).c_str(), R_OK)); + } +} + +TEST_F(UpdateSimulatorTest, RunUpdateSmoke) { + string recovery_img_string = "recovery.img"; + string boot_img_string = "boot.img"; + + std::map<string, string> src_entries{ + { "META/misc_info.txt", "extfs_sparse_flag=-s" }, + { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ }, + { "SYSTEM/build.prop", build_prop_string_ }, + { "IMAGES/recovery.img", "" }, + { "IMAGES/boot.img", boot_img_string }, + { "IMAGES/system.img", sparse_system_string_ }, + }; + + // Construct the source target-files. + TemporaryFile src_tf; + AddZipEntries(src_tf.release(), src_entries); + + string recovery_from_boot; + CreateBsdiffPatch(boot_img_string, recovery_img_string, &recovery_from_boot); + + // Set up the apply patch commands to patch the recovery image. + string recovery_sha1 = CalculateSha1(recovery_img_string); + string boot_sha1 = CalculateSha1(boot_img_string); + string apply_patch_source_string = android::base::StringPrintf( + "EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str()); + string apply_patch_target_string = android::base::StringPrintf( + "EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str()); + string check_command = android::base::StringPrintf( + R"(patch_partition_check("%s", "%s") || abort("check failed");)", + apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); + string patch_command = android::base::StringPrintf( + R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("patch failed");)", + apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); + + // Add the commands to update the system image. Test common commands: + // * getprop + // * ui_print + // * patch_partition + // * package_extract_file (single argument) + // * block_image_verify, block_image_update + string tgt_system_string = string(4096, 'a'); + string system_patch; + CreateBsdiffPatch(raw_system_string_, tgt_system_string, &system_patch); + + string tgt_system_hash = CalculateSha1(tgt_system_string); + string src_system_hash = CalculateSha1(raw_system_string_); + + std::vector<std::string> transfer_list = { + "4", + "1", + "0", + "0", + android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,1 1 2,0,1", system_patch.size(), + src_system_hash.c_str(), tgt_system_hash.c_str()), + }; + + // Construct the updater_script. + std::vector<string> updater_commands = { + R"(getprop("ro.product.device") == "angler" || abort("This package is for \"angler\"");)", + R"(ui_print("Source: angler/OPR1.170510.001");)", + check_command, + patch_command, + R"(block_image_verify("/dev/block/by-name/system", )" + R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )" + R"(abort("Failed to verify system.");)", + R"(block_image_update("/dev/block/by-name/system", )" + R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )" + R"(abort("Failed to verify system.");)", + }; + string updater_script = android::base::Join(updater_commands, '\n'); + + // Construct the ota update package. + std::map<string, string> ota_entries{ + { "system.new.dat", "" }, + { "system.patch.dat", system_patch }, + { "system.transfer.list", android::base::Join(transfer_list, '\n') }, + { "META-INF/com/google/android/updater-script", updater_script }, + { "patch.p", recovery_from_boot }, + }; + + TemporaryFile ota_package; + AddZipEntries(ota_package.release(), ota_entries); + + RunSimulation(src_tf.path, ota_package.path, true); +} + +TEST_F(UpdateSimulatorTest, RunUpdateUnrecognizedFunction) { + std::map<string, string> src_entries{ + { "META/misc_info.txt", "extfs_sparse_flag=-s" }, + { "IMAGES/system.img", sparse_system_string_ }, + { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ }, + { "SYSTEM/build.prop", build_prop_string_ }, + }; + + TemporaryFile src_tf; + AddZipEntries(src_tf.release(), src_entries); + + std::map<string, string> ota_entries{ + { "system.new.dat", "" }, + { "system.patch.dat", "" }, + { "system.transfer.list", "" }, + { "META-INF/com/google/android/updater-script", R"(bad_function("");)" }, + }; + + TemporaryFile ota_package; + AddZipEntries(ota_package.release(), ota_entries); + + RunSimulation(src_tf.path, ota_package.path, false); +} + +TEST_F(UpdateSimulatorTest, RunUpdateApplyPatchFailed) { + string recovery_img_string = "recovery.img"; + string boot_img_string = "boot.img"; + + std::map<string, string> src_entries{ + { "META/misc_info.txt", "extfs_sparse_flag=-s" }, + { "IMAGES/recovery.img", "" }, + { "IMAGES/boot.img", boot_img_string }, + { "IMAGES/system.img", sparse_system_string_ }, + { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ }, + { "SYSTEM/build.prop", build_prop_string_ }, + }; + + TemporaryFile src_tf; + AddZipEntries(src_tf.release(), src_entries); + + string recovery_sha1 = CalculateSha1(recovery_img_string); + string boot_sha1 = CalculateSha1(boot_img_string); + string apply_patch_source_string = android::base::StringPrintf( + "EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str()); + string apply_patch_target_string = android::base::StringPrintf( + "EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str()); + string check_command = android::base::StringPrintf( + R"(patch_partition_check("%s", "%s") || abort("check failed");)", + apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); + string patch_command = android::base::StringPrintf( + R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("failed");)", + apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); + + // Give an invalid recovery patch and expect the apply patch to fail. + // TODO(xunchang) check the cause code. + std::vector<string> updater_commands = { + R"(ui_print("Source: angler/OPR1.170510.001");)", + check_command, + patch_command, + }; + + string updater_script = android::base::Join(updater_commands, '\n'); + std::map<string, string> ota_entries{ + { "system.new.dat", "" }, + { "system.patch.dat", "" }, + { "system.transfer.list", "" }, + { "META-INF/com/google/android/updater-script", updater_script }, + { "patch.p", "random string" }, + }; + + TemporaryFile ota_package; + AddZipEntries(ota_package.release(), ota_entries); + + RunSimulation(src_tf.path, ota_package.path, false); +} diff --git a/tests/component/install_test.cpp b/tests/unit/install_test.cpp index 385132939..4ec409908 100644 --- a/tests/component/install_test.cpp +++ b/tests/unit/install_test.cpp @@ -33,6 +33,7 @@ #include <ziparchive/zip_writer.h> #include "install/install.h" +#include "install/wipe_device.h" #include "otautil/paths.h" #include "private/setup_commands.h" @@ -204,7 +205,7 @@ TEST(InstallTest, SetUpNonAbUpdateCommands) { std::string binary_path = std::string(td.path) + "/update_binary"; Paths::Get().set_temporary_update_binary(binary_path); std::vector<std::string> cmd; - ASSERT_EQ(0, SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd)); + ASSERT_TRUE(SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd)); ASSERT_EQ(4U, cmd.size()); ASSERT_EQ(binary_path, cmd[0]); ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION @@ -216,7 +217,7 @@ TEST(InstallTest, SetUpNonAbUpdateCommands) { // With non-zero retry count. update_binary will be removed automatically. cmd.clear(); - ASSERT_EQ(0, SetUpNonAbUpdateCommands(package, zip, 2, status_fd, &cmd)); + ASSERT_TRUE(SetUpNonAbUpdateCommands(package, zip, 2, status_fd, &cmd)); ASSERT_EQ(5U, cmd.size()); ASSERT_EQ(binary_path, cmd[0]); ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION @@ -243,7 +244,7 @@ TEST(InstallTest, SetUpNonAbUpdateCommands_MissingUpdateBinary) { TemporaryDir td; Paths::Get().set_temporary_update_binary(std::string(td.path) + "/update_binary"); std::vector<std::string> cmd; - ASSERT_EQ(INSTALL_CORRUPT, SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd)); + ASSERT_FALSE(SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd)); CloseArchive(zip); } @@ -270,19 +271,18 @@ static void VerifyAbUpdateCommands(const std::string& serialno, bool success = t ZipArchiveHandle zip; ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); - ZipString payload_name("payload.bin"); ZipEntry payload_entry; - ASSERT_EQ(0, FindEntry(zip, payload_name, &payload_entry)); + ASSERT_EQ(0, FindEntry(zip, "payload.bin", &payload_entry)); std::map<std::string, std::string> metadata; ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata)); if (success) { - ASSERT_EQ(0, CheckPackageMetadata(metadata, OtaType::AB)); + ASSERT_TRUE(CheckPackageMetadata(metadata, OtaType::AB)); int status_fd = 10; std::string package = "/path/to/update.zip"; std::vector<std::string> cmd; - ASSERT_EQ(0, SetUpAbUpdateCommands(package, zip, status_fd, &cmd)); + ASSERT_TRUE(SetUpAbUpdateCommands(package, zip, status_fd, &cmd)); ASSERT_EQ(5U, cmd.size()); ASSERT_EQ("/system/bin/update_engine_sideload", cmd[0]); ASSERT_EQ("--payload=file://" + package, cmd[1]); @@ -290,7 +290,7 @@ static void VerifyAbUpdateCommands(const std::string& serialno, bool success = t ASSERT_EQ("--headers=" + properties, cmd[3]); ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]); } else { - ASSERT_EQ(INSTALL_ERROR, CheckPackageMetadata(metadata, OtaType::AB)); + ASSERT_FALSE(CheckPackageMetadata(metadata, OtaType::AB)); } CloseArchive(zip); } @@ -325,7 +325,7 @@ TEST(InstallTest, SetUpAbUpdateCommands_MissingPayloadPropertiesTxt) { int status_fd = 10; std::string package = "/path/to/update.zip"; std::vector<std::string> cmd; - ASSERT_EQ(INSTALL_CORRUPT, SetUpAbUpdateCommands(package, zip, status_fd, &cmd)); + ASSERT_FALSE(SetUpAbUpdateCommands(package, zip, status_fd, &cmd)); CloseArchive(zip); } @@ -358,8 +358,8 @@ TEST(InstallTest, SetUpAbUpdateCommands_MultipleSerialnos) { VerifyAbUpdateCommands(long_serialno); } -static void test_check_package_metadata(const std::string& metadata_string, OtaType ota_type, - int exptected_result) { +static void TestCheckPackageMetadata(const std::string& metadata_string, OtaType ota_type, + bool exptected_result) { TemporaryFile temp_file; BuildZipArchive( { @@ -387,7 +387,7 @@ TEST(InstallTest, CheckPackageMetadata_ota_type) { "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()), }, "\n"); - test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR); + TestCheckPackageMetadata(metadata, OtaType::AB, false); // Checks if ota-type matches metadata = android::base::Join( @@ -397,9 +397,9 @@ TEST(InstallTest, CheckPackageMetadata_ota_type) { "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()), }, "\n"); - test_check_package_metadata(metadata, OtaType::AB, 0); + TestCheckPackageMetadata(metadata, OtaType::AB, true); - test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR); + TestCheckPackageMetadata(metadata, OtaType::BRICK, false); } TEST(InstallTest, CheckPackageMetadata_device_type) { @@ -409,7 +409,7 @@ TEST(InstallTest, CheckPackageMetadata_device_type) { "ota-type=BRICK", }, "\n"); - test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR); + TestCheckPackageMetadata(metadata, OtaType::BRICK, false); // device type mismatches metadata = android::base::Join( @@ -418,7 +418,7 @@ TEST(InstallTest, CheckPackageMetadata_device_type) { "pre-device=dummy_device_type", }, "\n"); - test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR); + TestCheckPackageMetadata(metadata, OtaType::BRICK, false); } TEST(InstallTest, CheckPackageMetadata_serial_number_smoke) { @@ -432,7 +432,7 @@ TEST(InstallTest, CheckPackageMetadata_serial_number_smoke) { "pre-device=" + device, }, "\n"); - test_check_package_metadata(metadata, OtaType::BRICK, 0); + TestCheckPackageMetadata(metadata, OtaType::BRICK, true); // Serial number mismatches metadata = android::base::Join( @@ -442,7 +442,7 @@ TEST(InstallTest, CheckPackageMetadata_serial_number_smoke) { "serialno=dummy_serial", }, "\n"); - test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR); + TestCheckPackageMetadata(metadata, OtaType::BRICK, false); std::string serialno = android::base::GetProperty("ro.serialno", ""); ASSERT_NE("", serialno); @@ -453,7 +453,7 @@ TEST(InstallTest, CheckPackageMetadata_serial_number_smoke) { "serialno=" + serialno, }, "\n"); - test_check_package_metadata(metadata, OtaType::BRICK, 0); + TestCheckPackageMetadata(metadata, OtaType::BRICK, true); } TEST(InstallTest, CheckPackageMetadata_multiple_serial_number) { @@ -477,7 +477,7 @@ TEST(InstallTest, CheckPackageMetadata_multiple_serial_number) { "serialno=" + android::base::Join(serial_numbers, '|'), }, "\n"); - test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR); + TestCheckPackageMetadata(metadata, OtaType::BRICK, false); serial_numbers.emplace_back(serialno); std::shuffle(serial_numbers.begin(), serial_numbers.end(), std::default_random_engine()); @@ -488,7 +488,7 @@ TEST(InstallTest, CheckPackageMetadata_multiple_serial_number) { "serialno=" + android::base::Join(serial_numbers, '|'), }, "\n"); - test_check_package_metadata(metadata, OtaType::BRICK, 0); + TestCheckPackageMetadata(metadata, OtaType::BRICK, true); } TEST(InstallTest, CheckPackageMetadata_ab_build_version) { @@ -506,7 +506,7 @@ TEST(InstallTest, CheckPackageMetadata_ab_build_version) { "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()), }, "\n"); - test_check_package_metadata(metadata, OtaType::AB, 0); + TestCheckPackageMetadata(metadata, OtaType::AB, true); metadata = android::base::Join( std::vector<std::string>{ @@ -516,7 +516,7 @@ TEST(InstallTest, CheckPackageMetadata_ab_build_version) { "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()), }, "\n"); - test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR); + TestCheckPackageMetadata(metadata, OtaType::AB, false); } TEST(InstallTest, CheckPackageMetadata_ab_fingerprint) { @@ -534,7 +534,7 @@ TEST(InstallTest, CheckPackageMetadata_ab_fingerprint) { "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()), }, "\n"); - test_check_package_metadata(metadata, OtaType::AB, 0); + TestCheckPackageMetadata(metadata, OtaType::AB, true); metadata = android::base::Join( std::vector<std::string>{ @@ -544,7 +544,7 @@ TEST(InstallTest, CheckPackageMetadata_ab_fingerprint) { "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()), }, "\n"); - test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR); + TestCheckPackageMetadata(metadata, OtaType::AB, false); } TEST(InstallTest, CheckPackageMetadata_ab_post_timestamp) { @@ -558,7 +558,7 @@ TEST(InstallTest, CheckPackageMetadata_ab_post_timestamp) { "pre-device=" + device, }, "\n"); - test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR); + TestCheckPackageMetadata(metadata, OtaType::AB, false); // post timestamp should be larger than the timestamp on device. metadata = android::base::Join( @@ -568,7 +568,7 @@ TEST(InstallTest, CheckPackageMetadata_ab_post_timestamp) { "post-timestamp=0", }, "\n"); - test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR); + TestCheckPackageMetadata(metadata, OtaType::AB, false); // fingerprint is required for downgrade metadata = android::base::Join( @@ -579,7 +579,7 @@ TEST(InstallTest, CheckPackageMetadata_ab_post_timestamp) { "ota-downgrade=yes", }, "\n"); - test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR); + TestCheckPackageMetadata(metadata, OtaType::AB, false); std::string finger_print = android::base::GetProperty("ro.build.fingerprint", ""); ASSERT_NE("", finger_print); @@ -593,5 +593,5 @@ TEST(InstallTest, CheckPackageMetadata_ab_post_timestamp) { "ota-downgrade=yes", }, "\n"); - test_check_package_metadata(metadata, OtaType::AB, 0); + TestCheckPackageMetadata(metadata, OtaType::AB, true); } diff --git a/tests/unit/locale_test.cpp b/tests/unit/locale_test.cpp index cdaba0e8b..c69434c12 100644 --- a/tests/unit/locale_test.cpp +++ b/tests/unit/locale_test.cpp @@ -27,7 +27,7 @@ TEST(LocaleTest, Misc) { EXPECT_FALSE(matches_locale("en-GB", "en")); EXPECT_FALSE(matches_locale("en-GB", "en-US")); EXPECT_FALSE(matches_locale("en-US", "")); - // Empty locale prefix in the PNG file will match the input locale. - EXPECT_TRUE(matches_locale("", "en-US")); + // Empty locale prefix in the PNG file should not match the input locale. + EXPECT_FALSE(matches_locale("", "en-US")); EXPECT_TRUE(matches_locale("sr-Latn", "sr-Latn-BA")); } diff --git a/tests/unit/package_test.cpp b/tests/unit/package_test.cpp index a735a699e..5e31f7fa5 100644 --- a/tests/unit/package_test.cpp +++ b/tests/unit/package_test.cpp @@ -105,10 +105,9 @@ TEST_F(PackageTest, GetZipArchiveHandle_extract_entry) { ASSERT_TRUE(zip); // Check that we can extract one zip entry. - std::string entry_name = "dir1/file3.txt"; - ZipString path(entry_name.c_str()); + std::string_view entry_name = "dir1/file3.txt"; ZipEntry entry; - ASSERT_EQ(0, FindEntry(zip, path, &entry)); + ASSERT_EQ(0, FindEntry(zip, entry_name, &entry)); std::vector<uint8_t> extracted(entry_name.size()); ASSERT_EQ(0, ExtractToMemory(zip, &entry, extracted.data(), extracted.size())); diff --git a/tests/unit/parse_install_logs_test.cpp b/tests/unit/parse_install_logs_test.cpp index 72169a0c6..052f71c98 100644 --- a/tests/unit/parse_install_logs_test.cpp +++ b/tests/unit/parse_install_logs_test.cpp @@ -22,7 +22,7 @@ #include <android-base/strings.h> #include <gtest/gtest.h> -#include "otautil/parse_install_logs.h" +#include "recovery_utils/parse_install_logs.h" TEST(ParseInstallLogsTest, EmptyFile) { TemporaryFile last_install; diff --git a/tests/unit/rangeset_test.cpp b/tests/unit/rangeset_test.cpp index fc72f2f6d..699f933a0 100644 --- a/tests/unit/rangeset_test.cpp +++ b/tests/unit/rangeset_test.cpp @@ -18,6 +18,7 @@ #include <sys/types.h> #include <limits> +#include <optional> #include <vector> #include <gtest/gtest.h> @@ -248,6 +249,29 @@ TEST(RangeSetTest, ToString) { ASSERT_EQ("6,1,3,4,6,15,22", RangeSet::Parse("6,1,3,4,6,15,22").ToString()); } +TEST(RangeSetTest, GetSubRanges_invalid) { + RangeSet range0({ { 1, 11 }, { 20, 30 } }); + ASSERT_FALSE(range0.GetSubRanges(0, 21)); // too many blocks + ASSERT_FALSE(range0.GetSubRanges(21, 1)); // start block OOB +} + +TEST(RangeSetTest, GetSubRanges_empty) { + RangeSet range0({ { 1, 11 }, { 20, 30 } }); + ASSERT_EQ(RangeSet{}, range0.GetSubRanges(1, 0)); // empty num_of_blocks +} + +TEST(RangeSetTest, GetSubRanges_smoke) { + RangeSet range0({ { 10, 11 } }); + ASSERT_EQ(RangeSet({ { 10, 11 } }), range0.GetSubRanges(0, 1)); + + RangeSet range1({ { 10, 11 }, { 20, 21 }, { 30, 31 } }); + ASSERT_EQ(range1, range1.GetSubRanges(0, 3)); + ASSERT_EQ(RangeSet({ { 20, 21 } }), range1.GetSubRanges(1, 1)); + + RangeSet range2({ { 1, 11 }, { 20, 25 }, { 30, 35 } }); + ASSERT_EQ(RangeSet({ { 10, 11 }, { 20, 25 }, { 30, 31 } }), range2.GetSubRanges(9, 7)); +} + TEST(SortedRangeSetTest, Insert) { SortedRangeSet rs({ { 2, 3 }, { 4, 6 }, { 8, 14 } }); rs.Insert({ 1, 2 }); diff --git a/tests/unit/resources_test.cpp b/tests/unit/resources_test.cpp index c3f72718f..302744308 100644 --- a/tests/unit/resources_test.cpp +++ b/tests/unit/resources_test.cpp @@ -14,12 +14,62 @@ * limitations under the License. */ +#include <dirent.h> +#include <stdio.h> +#include <stdlib.h> + +#include <memory> #include <string> +#include <vector> +#include <android-base/file.h> +#include <android-base/strings.h> #include <gtest/gtest.h> +#include <png.h> #include "common/test_constants.h" #include "minui/minui.h" +#include "private/resources.h" + +static const std::string kLocale = "zu"; + +static const std::vector<std::string> kResourceImagesDirs{ + "res-mdpi/images/", "res-hdpi/images/", "res-xhdpi/images/", + "res-xxhdpi/images/", "res-xxxhdpi/images/", +}; + +static int png_filter(const dirent* de) { + if (de->d_type != DT_REG || !android::base::EndsWith(de->d_name, "_text.png")) { + return 0; + } + return 1; +} + +// Finds out all the PNG files to test, which stay under the same dir with the executabl.. +static std::vector<std::string> add_files() { + std::vector<std::string> files; + for (const std::string& images_dir : kResourceImagesDirs) { + static std::string exec_dir = android::base::GetExecutableDirectory(); + std::string dir_path = exec_dir + "/" + images_dir; + dirent** namelist; + int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort); + if (n == -1) { + printf("Failed to scandir %s: %s\n", dir_path.c_str(), strerror(errno)); + continue; + } + if (n == 0) { + printf("No file is added for test in %s\n", dir_path.c_str()); + } + + while (n--) { + std::string file_path = dir_path + namelist[n]->d_name; + files.push_back(file_path); + free(namelist[n]); + } + free(namelist); + } + return files; +} TEST(ResourcesTest, res_create_multi_display_surface) { GRSurface** frames; @@ -35,3 +85,52 @@ TEST(ResourcesTest, res_create_multi_display_surface) { } free(frames); } + +class ResourcesTest : public testing::TestWithParam<std::string> { + public: + static std::vector<std::string> png_list; + + protected: + void SetUp() override { + png_ = std::make_unique<PngHandler>(GetParam()); + ASSERT_TRUE(png_); + + ASSERT_EQ(PNG_COLOR_TYPE_GRAY, png_->color_type()) << "Recovery expects grayscale PNG file."; + ASSERT_LT(static_cast<png_uint_32>(5), png_->width()); + ASSERT_LT(static_cast<png_uint_32>(0), png_->height()); + ASSERT_EQ(1, png_->channels()) << "Recovery background text images expects 1-channel PNG file."; + } + + std::unique_ptr<PngHandler> png_{ nullptr }; +}; + +// Parses a png file and tests if it's qualified for the background text image under recovery. +TEST_P(ResourcesTest, ValidateLocale) { + std::vector<unsigned char> row(png_->width()); + for (png_uint_32 y = 0; y < png_->height(); ++y) { + png_read_row(png_->png_ptr(), row.data(), nullptr); + int w = (row[1] << 8) | row[0]; + int h = (row[3] << 8) | row[2]; + int len = row[4]; + EXPECT_LT(0, w); + EXPECT_LT(0, h); + EXPECT_LT(0, len) << "Locale string should be non-empty."; + EXPECT_NE(0, row[5]) << "Locale string is missing."; + + ASSERT_GE(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file."; + char* loc = reinterpret_cast<char*>(&row[5]); + if (matches_locale(loc, kLocale.c_str())) { + EXPECT_TRUE(android::base::StartsWith(loc, kLocale)); + break; + } + for (int i = 0; i < h; ++i, ++y) { + png_read_row(png_->png_ptr(), row.data(), nullptr); + } + } +} + +std::vector<std::string> ResourcesTest::png_list = add_files(); + +INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourcesTest, + ::testing::ValuesIn(ResourcesTest::png_list.cbegin(), + ResourcesTest::png_list.cend())); diff --git a/tests/unit/sysutil_test.cpp b/tests/unit/sysutil_test.cpp index 3466e8eec..64b8956f7 100644 --- a/tests/unit/sysutil_test.cpp +++ b/tests/unit/sysutil_test.cpp @@ -67,7 +67,7 @@ TEST(SysUtilTest, ParseBlockMapFile_invalid_size) { "/dev/abc", "42949672950 4294967295", "1", - "0 9", + "0 10", }; TemporaryFile temp_file; diff --git a/tests/component/uncrypt_test.cpp b/tests/unit/uncrypt_test.cpp index e97d589a6..e97d589a6 100644 --- a/tests/component/uncrypt_test.cpp +++ b/tests/unit/uncrypt_test.cpp diff --git a/tests/component/update_verifier_test.cpp b/tests/unit/update_verifier_test.cpp index e27e58c22..e27e58c22 100644 --- a/tests/component/update_verifier_test.cpp +++ b/tests/unit/update_verifier_test.cpp diff --git a/tests/component/updater_test.cpp b/tests/unit/updater_test.cpp index a0a7b66ab..8993dd8b7 100644 --- a/tests/component/updater_test.cpp +++ b/tests/unit/updater_test.cpp @@ -52,21 +52,20 @@ #include "updater/blockimg.h" #include "updater/install.h" #include "updater/updater.h" +#include "updater/updater_runtime.h" using namespace std::string_literals; using PackageEntries = std::unordered_map<std::string, std::string>; -struct selabel_handle* sehandle = nullptr; - static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code, - UpdaterInfo* info = nullptr) { + Updater* updater) { std::unique_ptr<Expr> e; int error_count = 0; ASSERT_EQ(0, ParseString(expr_str, &e, &error_count)); ASSERT_EQ(0, error_count); - State state(expr_str, info); + State state(expr_str, updater); std::string result; bool status = Evaluate(&state, e, &result); @@ -85,6 +84,11 @@ static void expect(const char* expected, const std::string& expr_str, CauseCode ASSERT_EQ(cause_code, state.cause_code); } +static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code) { + Updater updater(std::make_unique<UpdaterRuntime>(nullptr)); + expect(expected, expr_str, cause_code, &updater); +} + static void BuildUpdatePackage(const PackageEntries& entries, int fd) { FILE* zip_file_ptr = fdopen(fd, "wb"); ZipWriter zip_writer(zip_file_ptr); @@ -102,38 +106,6 @@ static void BuildUpdatePackage(const PackageEntries& entries, int fd) { ASSERT_EQ(0, fclose(zip_file_ptr)); } -static void RunBlockImageUpdate(bool is_verify, const PackageEntries& entries, - const std::string& image_file, const std::string& result, - CauseCode cause_code = kNoCause) { - CHECK(entries.find("transfer_list") != entries.end()); - - // Build the update package. - TemporaryFile zip_file; - BuildUpdatePackage(entries, zip_file.release()); - - MemMapping map; - ASSERT_TRUE(map.MapFile(zip_file.path)); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle)); - - // Set up the handler, command_pipe, patch offset & length. - UpdaterInfo updater_info; - updater_info.package_zip = handle; - TemporaryFile temp_pipe; - updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe"); - updater_info.package_zip_addr = map.addr; - updater_info.package_zip_len = map.length; - - std::string new_data = entries.find("new_data.br") != entries.end() ? "new_data.br" : "new_data"; - std::string script = is_verify ? "block_image_verify" : "block_image_update"; - script += R"((")" + image_file + R"(", package_extract_file("transfer_list"), ")" + new_data + - R"(", "patch_data"))"; - expect(result.c_str(), script, cause_code, &updater_info); - - ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); - CloseArchive(handle); -} - static std::string GetSha1(std::string_view content) { uint8_t digest[SHA_DIGEST_LENGTH]; SHA1(reinterpret_cast<const uint8_t*>(content.data()), content.size(), digest); @@ -159,29 +131,26 @@ static Value* BlobToString(const char* name, State* state, return args[0].release(); } -class UpdaterTest : public ::testing::Test { +class UpdaterTestBase { protected: - void SetUp() override { + UpdaterTestBase() : updater_(std::make_unique<UpdaterRuntime>(nullptr)) {} + + void SetUp() { RegisterBuiltins(); RegisterInstallFunctions(); RegisterBlockImageFunctions(); - RegisterFunction("blob_to_string", BlobToString); - // Each test is run in a separate process (isolated mode). Shared temporary files won't cause // conflicts. 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); - // Enable a special command "abort" to simulate interruption. - Command::abort_allowed_ = true; - last_command_file_ = temp_last_command_.path; image_file_ = image_temp_file_.path; } - void TearDown() override { + void TearDown() { // Clean up the last_command_file if any. ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_)); @@ -191,16 +160,80 @@ class UpdaterTest : public ::testing::Test { ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker)); } + void RunBlockImageUpdate(bool is_verify, PackageEntries entries, const std::string& image_file, + const std::string& result, CauseCode cause_code = kNoCause) { + CHECK(entries.find("transfer_list") != entries.end()); + std::string new_data = + entries.find("new_data.br") != entries.end() ? "new_data.br" : "new_data"; + std::string script = is_verify ? "block_image_verify" : "block_image_update"; + script += R"((")" + image_file + R"(", package_extract_file("transfer_list"), ")" + new_data + + R"(", "patch_data"))"; + entries.emplace(Updater::SCRIPT_NAME, script); + + // Build the update package. + TemporaryFile zip_file; + BuildUpdatePackage(entries, zip_file.release()); + + // Set up the handler, command_pipe, patch offset & length. + TemporaryFile temp_pipe; + ASSERT_TRUE(updater_.Init(temp_pipe.release(), zip_file.path, false)); + ASSERT_TRUE(updater_.RunUpdate()); + ASSERT_EQ(result, updater_.GetResult()); + + // Parse the cause code written to the command pipe. + int received_cause_code = kNoCause; + std::string pipe_content; + ASSERT_TRUE(android::base::ReadFileToString(temp_pipe.path, &pipe_content)); + auto lines = android::base::Split(pipe_content, "\n"); + for (std::string_view line : lines) { + if (android::base::ConsumePrefix(&line, "log cause: ")) { + ASSERT_TRUE(android::base::ParseInt(line.data(), &received_cause_code)); + } + } + ASSERT_EQ(cause_code, received_cause_code); + } + TemporaryFile temp_saved_source_; TemporaryDir temp_stash_base_; std::string last_command_file_; std::string image_file_; + Updater updater_; + private: TemporaryFile temp_last_command_; TemporaryFile image_temp_file_; }; +class UpdaterTest : public UpdaterTestBase, public ::testing::Test { + protected: + void SetUp() override { + UpdaterTestBase::SetUp(); + + RegisterFunction("blob_to_string", BlobToString); + // Enable a special command "abort" to simulate interruption. + Command::abort_allowed_ = true; + } + + void TearDown() override { + UpdaterTestBase::TearDown(); + } + + void SetUpdaterCmdPipe(int fd) { + FILE* cmd_pipe = fdopen(fd, "w"); + ASSERT_NE(nullptr, cmd_pipe); + updater_.cmd_pipe_.reset(cmd_pipe); + } + + void SetUpdaterOtaPackageHandle(ZipArchiveHandle handle) { + updater_.package_handle_ = handle; + } + + void FlushUpdaterCommandPipe() const { + fflush(updater_.cmd_pipe_.get()); + } +}; + TEST_F(UpdaterTest, getprop) { expect(android::base::GetProperty("ro.product.device", "").c_str(), "getprop(\"ro.product.device\")", @@ -317,13 +350,12 @@ TEST_F(UpdaterTest, package_extract_file) { ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); // Need to set up the ziphandle. - UpdaterInfo updater_info; - updater_info.package_zip = handle; + SetUpdaterOtaPackageHandle(handle); // Two-argument version. TemporaryFile temp_file1; std::string script("package_extract_file(\"a.txt\", \"" + std::string(temp_file1.path) + "\")"); - expect("t", script, kNoCause, &updater_info); + expect("t", script, kNoCause, &updater_); // Verify the extracted entry. std::string data; @@ -332,32 +364,30 @@ TEST_F(UpdaterTest, package_extract_file) { // Now extract another entry to the same location, which should overwrite. script = "package_extract_file(\"b.txt\", \"" + std::string(temp_file1.path) + "\")"; - expect("t", script, kNoCause, &updater_info); + expect("t", script, kNoCause, &updater_); ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data)); ASSERT_EQ(kBTxtContents, data); // Missing zip entry. The two-argument version doesn't abort. script = "package_extract_file(\"doesntexist\", \"" + std::string(temp_file1.path) + "\")"; - expect("", script, kNoCause, &updater_info); + expect("", script, kNoCause, &updater_); // Extract to /dev/full should fail. script = "package_extract_file(\"a.txt\", \"/dev/full\")"; - expect("", script, kNoCause, &updater_info); + expect("", script, kNoCause, &updater_); // One-argument version. package_extract_file() gives a VAL_BLOB, which needs to be converted to // VAL_STRING for equality test. script = "blob_to_string(package_extract_file(\"a.txt\")) == \"" + kATxtContents + "\""; - expect("t", script, kNoCause, &updater_info); + expect("t", script, kNoCause, &updater_); script = "blob_to_string(package_extract_file(\"b.txt\")) == \"" + kBTxtContents + "\""; - expect("t", script, kNoCause, &updater_info); + expect("t", script, kNoCause, &updater_); // Missing entry. The one-argument version aborts the evaluation. script = "package_extract_file(\"doesntexist\")"; - expect(nullptr, script, kPackageExtractFileFailure, &updater_info); - - CloseArchive(handle); + expect(nullptr, script, kPackageExtractFileFailure, &updater_); } TEST_F(UpdaterTest, read_file) { @@ -563,17 +593,15 @@ TEST_F(UpdaterTest, set_progress) { expect(nullptr, "set_progress(\".3.5\")", kArgsParsingFailure); TemporaryFile tf; - UpdaterInfo updater_info; - updater_info.cmd_pipe = fdopen(tf.release(), "w"); - expect(".52", "set_progress(\".52\")", kNoCause, &updater_info); - fflush(updater_info.cmd_pipe); + SetUpdaterCmdPipe(tf.release()); + expect(".52", "set_progress(\".52\")", kNoCause, &updater_); + FlushUpdaterCommandPipe(); std::string cmd; ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd)); ASSERT_EQ(android::base::StringPrintf("set_progress %f\n", .52), cmd); // recovery-updater protocol expects 2 tokens ("set_progress <frac>"). ASSERT_EQ(2U, android::base::Split(cmd, " ").size()); - ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); } TEST_F(UpdaterTest, show_progress) { @@ -588,17 +616,15 @@ TEST_F(UpdaterTest, show_progress) { expect(nullptr, "show_progress(\".3\", \"5a\")", kArgsParsingFailure); TemporaryFile tf; - UpdaterInfo updater_info; - updater_info.cmd_pipe = fdopen(tf.release(), "w"); - expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_info); - fflush(updater_info.cmd_pipe); + SetUpdaterCmdPipe(tf.release()); + expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_); + FlushUpdaterCommandPipe(); std::string cmd; ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd)); ASSERT_EQ(android::base::StringPrintf("progress %f %d\n", .52, 10), cmd); // recovery-updater protocol expects 3 tokens ("progress <frac> <secs>"). ASSERT_EQ(3U, android::base::Split(cmd, " ").size()); - ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); } TEST_F(UpdaterTest, block_image_update_parsing_error) { @@ -993,44 +1019,20 @@ TEST_F(UpdaterTest, last_command_verify) { ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK)); } -class ResumableUpdaterTest : public testing::TestWithParam<size_t> { +class ResumableUpdaterTest : public UpdaterTestBase, public testing::TestWithParam<size_t> { protected: void SetUp() override { - RegisterBuiltins(); - RegisterInstallFunctions(); - RegisterBlockImageFunctions(); - - 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); - + UpdaterTestBase::SetUp(); // Enable a special command "abort" to simulate interruption. Command::abort_allowed_ = true; - index_ = GetParam(); - image_file_ = image_temp_file_.path; - last_command_file_ = temp_last_command_.path; } void TearDown() override { - // Clean up the last_command_file if any. - ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_)); - - // Clear partition updated marker if any. - std::string updated_marker{ temp_stash_base_.path }; - updated_marker += "/" + GetSha1(image_temp_file_.path) + ".UPDATED"; - ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker)); + UpdaterTestBase::TearDown(); } - TemporaryFile temp_saved_source_; - TemporaryDir temp_stash_base_; - std::string last_command_file_; - std::string image_file_; size_t index_; - - private: - TemporaryFile temp_last_command_; - TemporaryFile image_temp_file_; }; static std::string g_source_image; diff --git a/tests/component/verifier_test.cpp b/tests/unit/verifier_test.cpp index ded23c52f..ded23c52f 100644 --- a/tests/component/verifier_test.cpp +++ b/tests/unit/verifier_test.cpp diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp index dfe617ebe..0753d64e1 100644 --- a/tests/unit/zip_test.cpp +++ b/tests/unit/zip_test.cpp @@ -37,10 +37,9 @@ TEST(ZipTest, OpenFromMemory) { ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_path.c_str(), &handle)); static constexpr const char* BINARY_PATH = "META-INF/com/google/android/update-binary"; - ZipString binary_path(BINARY_PATH); ZipEntry binary_entry; // Make sure the package opens correctly and its entry can be read. - ASSERT_EQ(0, FindEntry(handle, binary_path, &binary_entry)); + ASSERT_EQ(0, FindEntry(handle, BINARY_PATH, &binary_entry)); TemporaryFile tmp_binary; ASSERT_NE(-1, tmp_binary.fd); diff --git a/tools/image_generator/Android.bp b/tools/image_generator/Android.bp index 2afdd5a84..83000407c 100644 --- a/tools/image_generator/Android.bp +++ b/tools/image_generator/Android.bp @@ -19,7 +19,7 @@ java_library_host { static_libs: [ "commons-cli-1.2", - "icu4j-host", + "icu4j", ], srcs: [ diff --git a/updater/Android.bp b/updater/Android.bp index b80cdb3a0..8a60ef76a 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", @@ -42,10 +41,21 @@ cc_defaults { "libziparchive", "libz", "libbase", - "libcrypto", "libcrypto_utils", "libcutils", "libutils", + ], + + shared_libs: [ + "libcrypto", + ], +} + +cc_defaults { + name: "libupdater_device_defaults", + + static_libs: [ + "libfs_mgr", "libtune2fs", "libext2_com_err", @@ -54,11 +64,13 @@ cc_defaults { "libext2_uuid", "libext2_e2p", "libext2fs", - ], + ] } cc_library_static { - name: "libupdater", + name: "libupdater_core", + + host_supported: true, defaults: [ "recovery_defaults", @@ -68,8 +80,39 @@ cc_library_static { srcs: [ "blockimg.cpp", "commands.cpp", - "dynamic_partitions.cpp", "install.cpp", + "mounts.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 +123,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..6f54d89b8 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 \ @@ -45,12 +44,27 @@ updater_common_static_libraries := \ libziparchive \ libz \ libbase \ - libcrypto \ + libcrypto_static \ 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..62ff87e76 100644 --- a/updater/install.cpp +++ b/updater/install.cpp @@ -53,45 +53,30 @@ #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 +88,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 +112,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 +164,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 +219,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,7 +265,12 @@ Value* PatchPartitionFn(const char* name, State* state, return ErrorAbort(state, kArgsParsingFailure, "%s(): Invalid patch arg", name); } - bool result = PatchPartition(target, source, *values[0], nullptr); + if (!UpdateBlockDeviceNameForPartition(state->updater, &source) || + !UpdateBlockDeviceNameForPartition(state->updater, &target)) { + return StringValue(""); + } + + bool result = PatchPartition(target, source, *values[0], nullptr, true); return StringValue(result ? "t" : ""); } @@ -313,26 +313,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 +340,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 +362,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 +420,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 +429,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 +454,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 +499,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 +522,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 +535,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 +562,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 +624,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 +639,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 +658,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 +688,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 +731,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 +819,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 +830,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 +846,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/otautil/mounts.cpp b/updater/mounts.cpp index 951311bf3..943d35c75 100644 --- a/otautil/mounts.cpp +++ b/updater/mounts.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "otautil/mounts.h" +#include "mounts.h" #include <errno.h> #include <fcntl.h> diff --git a/otautil/include/otautil/mounts.h b/updater/mounts.h index 6786c8d2e..6786c8d2e 100644 --- a/otautil/include/otautil/mounts.h +++ b/updater/mounts.h diff --git a/updater/simulator_runtime.cpp b/updater/simulator_runtime.cpp new file mode 100644 index 000000000..3ed7bf337 --- /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 "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..33d5b5b47 --- /dev/null +++ b/updater/updater_main.cpp @@ -0,0 +1,116 @@ +/* + * 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 <openssl/crypto.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); + + // Run the libcrypto KAT(known answer tests) based self tests. + if (BORINGSSL_self_test() != 1) { + LOG(ERROR) << "Failed to run the boringssl self tests"; + return EXIT_FAILURE; + } + + if (argc != 4 && argc != 5) { + LOG(ERROR) << "unexpected number of arguments: " << argc; + return EXIT_FAILURE; + } + + 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 EXIT_FAILURE; + } + + int fd; + if (!android::base::ParseInt(argv[2], &fd)) { + LOG(ERROR) << "Failed to parse fd in " << argv[2]; + return EXIT_FAILURE; + } + + 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 EXIT_FAILURE; + } + } + + // 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 EXIT_FAILURE; + } + + if (!updater.RunUpdate()) { + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +}
\ No newline at end of file diff --git a/updater/updater_runtime.cpp b/updater/updater_runtime.cpp new file mode 100644 index 000000000..c4222a56e --- /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 "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; +} diff --git a/updater_sample/Android.bp b/updater_sample/Android.bp index 845e07b70..a014248b0 100644 --- a/updater_sample/Android.bp +++ b/updater_sample/Android.bp @@ -15,7 +15,6 @@ android_app { name: "SystemUpdaterSample", sdk_version: "system_current", - privileged: true, srcs: ["src/**/*.java"], diff --git a/updater_sample/OWNERS b/updater_sample/OWNERS deleted file mode 100644 index 5c1c3706c..000000000 --- a/updater_sample/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -zhaojiac@google.com -zhomart@google.com diff --git a/updater_sample/README.md b/updater_sample/README.md index 2070ebc21..2e12a2fb9 100644 --- a/updater_sample/README.md +++ b/updater_sample/README.md @@ -191,6 +191,8 @@ privileged system app, so it's granted the required permissions to access </privapp-permissions> ``` to `frameworks/base/data/etc/privapp-permissions-platform.xml` +4. Add `privileged: true` to SystemUpdaterSample + [building rule](https://android.googlesource.com/platform/bootable/recovery/+/refs/heads/master/updater_sample/Android.bp). 5. Build sample app `make -j SystemUpdaterSample`. 6. Build Android `make -j` 7. [Flash the device](https://source.android.com/setup/build/running) @@ -229,9 +231,9 @@ The commands are expected to be run from `$ANDROID_BUILD_TOP`. 1. Build `make -j SystemUpdaterSample` and `make -j SystemUpdaterSampleTests`. 2. Install app - `adb install $OUT/system/priv-app/SystemUpdaterSample/SystemUpdaterSample.apk` + `adb install $OUT/system/app/SystemUpdaterSample/SystemUpdaterSample.apk` 3. Install tests - `adb install $OUT/testcases/SystemUpdaterSampleTests/SystemUpdaterSampleTests.apk` + `adb install $OUT/testcases/SystemUpdaterSampleTests/arm64/SystemUpdaterSampleTests.apk` 4. Run tests `adb shell am instrument -w com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner` 5. Run a test file |