diff options
103 files changed, 4166 insertions, 1622 deletions
diff --git a/Android.bp b/Android.bp index f92078256..0eb5fd9e5 100644 --- a/Android.bp +++ b/Android.bp @@ -76,7 +76,6 @@ cc_defaults { // external dependencies "libhealthhalutils", - "libfstab", ], } @@ -150,7 +149,6 @@ cc_binary { static_libs: [ "libotautil", - "libfstab", ], init_rc: [ @@ -177,7 +175,6 @@ cc_binary { static_libs: [ "libotautil", - "libfstab", ], init_rc: [ diff --git a/CleanSpec.mk b/CleanSpec.mk index a7ab0d9be..8405d20e1 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -51,6 +51,13 @@ $(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) + # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ @@ -22,11 +22,9 @@ Running the tests # 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..42aa52954 100644 --- a/applypatch/Android.bp +++ b/applypatch/Android.bp @@ -114,11 +114,9 @@ cc_binary { ], } -cc_library_static { +cc_library_host_static { name: "libimgdiff", - host_supported: true, - defaults: [ "applypatch_defaults", ], @@ -170,35 +168,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/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/bootloader_message/Android.bp b/bootloader_message/Android.bp index 450dad08b..8d72a1163 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 { 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..5c0a450fc 100644 --- a/bootloader_message/include/bootloader_message/bootloader_message.h +++ b/bootloader_message/include/bootloader_message/bootloader_message.h @@ -208,6 +208,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/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..9dc0fd8ec 100644 --- a/fsck_unshare_blocks.cpp +++ b/fsck_unshare_blocks.cpp @@ -34,7 +34,7 @@ #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" @@ -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..89cc3f23e 100644 --- a/install/Android.bp +++ b/install/Android.bp @@ -47,7 +47,6 @@ cc_defaults { // external dependencies "libvintf_recovery", "libvintf", - "libfstab", ], } @@ -62,11 +61,12 @@ 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", ], shared_libs: [ diff --git a/install/adb_install.cpp b/install/adb_install.cpp index 9497df501..ed664429a 100644 --- a/install/adb_install.cpp +++ b/install/adb_install.cpp @@ -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..8a7a278e0 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> @@ -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..9d67b0105 100644 --- a/install/install.cpp +++ b/install/install.cpp @@ -60,7 +60,8 @@ 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_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..afd57ad2d 100644 --- a/minadbd/Android.bp +++ b/minadbd/Android.bp @@ -43,6 +43,10 @@ cc_library { "minadbd_services.cpp", ], + static_libs: [ + "libotautil", + ], + shared_libs: [ "libadbd", "libbase", @@ -96,6 +100,7 @@ cc_test { static_libs: [ "libminadbd_services", "libfusesideload", + "libotautil", "libadbd", "libcrypto", ], 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_services.cpp b/minadbd/minadbd_services.cpp index 6c10274dc..c31afbe06 100644 --- a/minadbd/minadbd_services.cpp +++ b/minadbd/minadbd_services.cpp @@ -25,10 +25,10 @@ #include <functional> #include <memory> +#include <set> #include <string> #include <string_view> #include <thread> -#include <unordered_set> #include <android-base/file.h> #include <android-base/logging.h> @@ -41,7 +41,6 @@ #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" @@ -156,19 +155,36 @@ static void RescueInstallHostService(unique_fd sfd, const std::string& args) { } } +// Answers the query on a given property |prop|, by writing the result to the given |sfd|. The +// result will be newline-terminated, so nonexistent or nonallowed query will be answered with "\n". +// 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) { - static const std::unordered_set<std::string> kGetpropAllowedProps = { - "ro.build.fingerprint", + static const std::set<std::string> kGetpropAllowedProps = { "ro.build.date.utc", + "ro.build.fingerprint", + "ro.build.flavor", + "ro.build.id", + "ro.build.product", + "ro.build.tags", + "ro.build.version.incremental", + "ro.product.device", + "ro.product.vendor.device", }; - auto allowed = kGetpropAllowedProps.find(prop) != kGetpropAllowedProps.end(); - if (!allowed) { - return; + std::string result; + if (prop.empty()) { + for (const auto& key : kGetpropAllowedProps) { + auto value = android::base::GetProperty(key, ""); + if (value.empty()) { + continue; + } + result += "[" + key + "]: [" + value + "]\n"; + } + } else if (kGetpropAllowedProps.find(prop) != kGetpropAllowedProps.end()) { + result = android::base::GetProperty(prop, "") + "\n"; } - - auto result = android::base::GetProperty(prop, ""); if (result.empty()) { - return; + result = "\n"; } if (!android::base::WriteFully(sfd, result.data(), result.size())) { exit(kMinadbdHostSocketIOError); @@ -238,7 +254,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" || @@ -251,17 +267,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", @@ -276,7 +292,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/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..871dcae9a 100644 --- a/otautil/Android.bp +++ b/otautil/Android.bp @@ -24,12 +24,16 @@ 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: [ @@ -39,12 +43,10 @@ cc_library_static { target: { android: { srcs: [ - "dirutil.cpp", "logging.cpp", "mounts.cpp", "parse_install_logs.cpp", "roots.cpp", - "sysutil.cpp", "thermalutil.cpp", ], @@ -57,10 +59,12 @@ cc_library_static { ], shared_libs: [ - "libcutils", "libext4_utils", "libfs_mgr", - "libselinux", + ], + + export_static_lib_headers: [ + "libfstab", ], }, }, 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/logging.h b/otautil/include/otautil/logging.h index 608349785..4462eca6e 100644 --- a/otautil/include/otautil/logging.h +++ b/otautil/include/otautil/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/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/roots.h b/otautil/include/otautil/roots.h index 482f3d050..92ee756f0 100644 --- a/otautil/include/otautil/roots.h +++ b/otautil/include/otautil/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/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/logging.cpp b/otautil/logging.cpp index 484f1150f..3db0e8ac2 100644 --- a/otautil/logging.cpp +++ b/otautil/logging.cpp @@ -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/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/roots.cpp b/otautil/roots.cpp index 815d644e5..431551785 100644 --- a/otautil/roots.cpp +++ b/otautil/roots.cpp @@ -51,6 +51,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"; @@ -276,10 +278,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/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.cpp b/recovery.cpp index 5fc673ec2..4862dfccb 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,19 +40,20 @@ #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 <fs_mgr/roots.h> #include <healthhalutils/HealthHalUtils.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" @@ -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; @@ -726,6 +575,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 +596,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 +632,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 +671,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 +703,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; @@ -881,7 +733,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 +763,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 +781,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 +809,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 +839,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 +864,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 +872,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..28197bf40 100644 --- a/recovery_main.cpp +++ b/recovery_main.cpp @@ -41,16 +41,16 @@ #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/boot_state.h" #include "otautil/logging.h" #include "otautil/paths.h" #include "otautil/roots.h" @@ -63,12 +63,11 @@ 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,7 @@ int main(int argc, char** argv) { bool show_text = false; bool fastboot = false; std::string locale; + std::string reason; int arg; int option_index; @@ -359,6 +363,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; @@ -370,12 +376,11 @@ int main(int argc, char** argv) { optind = 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 +420,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 +433,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 +443,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 +456,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 +480,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 +515,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 +534,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/tests/Android.bp b/tests/Android.bp index 09ef716d6..a86704015 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", @@ -79,6 +77,8 @@ librecovery_static_libs = [ "libinstall", "librecovery_ui", "libminui", + "libfusesideload", + "libbootloader_message", "libotautil", "libhealthhalutils", @@ -87,10 +87,8 @@ 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", @@ -107,6 +105,8 @@ cc_test { defaults: [ "recovery_test_defaults", + "libupdater_defaults", + "libupdater_device_defaults", ], test_suites: ["device-tests"], @@ -115,16 +115,23 @@ cc_test { "unit/*.cpp", ], - static_libs: libapplypatch_static_libs + [ - "libinstall", + static_libs: libapplypatch_static_libs + librecovery_static_libs + [ "librecovery_ui", + "libfusesideload", "libminui", "libotautil", - "libupdater", + "libupdater_device", + "libupdater_core", + "libupdate_verifier", + "libgtest_prod", + "libprotobuf-cpp-lite", ], - data: ["testdata/*"], + data: [ + "testdata/*", + ":res-testdata", + ], } cc_test { @@ -142,66 +149,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/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/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/tools/recovery_l10n/res/values-gl/strings.xml b/tools/recovery_l10n/res/values-gl/strings.xml index e6f2ffd84..e51b36dfb 100644 --- a/tools/recovery_l10n/res/values-gl/strings.xml +++ b/tools/recovery_l10n/res/values-gl/strings.xml @@ -6,9 +6,9 @@ <string name="recovery_no_command" msgid="4465476568623024327">"Non hai ningún comando"</string> <string name="recovery_error" msgid="5748178989622716736">"Erro"</string> <string name="recovery_installing_security" msgid="9184031299717114342">"Instalando actualización de seguranza"</string> - <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Non se puido cargar o sistema Android. Os teus datos poden estar danados. Se segue aparecendo esta mensaxe, pode ser necesario restablecer os datos de fábrica e borrar todos os datos do usuario almacenados neste dispositivo."</string> + <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Non se puido cargar o sistema Android. Os teus datos poden estar danados. Se segue aparecendo esta mensaxe, pode ser necesario restablecer os datos de fábrica e borrar todos os datos de usuario almacenados neste dispositivo."</string> <string name="recovery_try_again" msgid="7168248750158873496">"Tentar de novo"</string> <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Restablecemento dos datos de fábrica"</string> - <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Queres borrar todos os datos do usuario?\n\n ESTA ACCIÓN NON SE PODE DESFACER."</string> + <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Queres borrar todos os datos de usuario?\n\n ESTA ACCIÓN NON SE PODE DESFACER."</string> <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Cancelar"</string> </resources> diff --git a/tools/recovery_l10n/res/values-in/strings.xml b/tools/recovery_l10n/res/values-in/strings.xml index 43c9deb94..15a78ec48 100644 --- a/tools/recovery_l10n/res/values-in/strings.xml +++ b/tools/recovery_l10n/res/values-in/strings.xml @@ -9,6 +9,6 @@ <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Tidak dapat memuat sistem Android. Data Anda mungkin rusak. Jika terus mendapatkan pesan ini, Anda mungkin perlu melakukan reset ke setelan pabrik dan menghapus semua data pengguna yang disimpan di perangkat ini."</string> <string name="recovery_try_again" msgid="7168248750158873496">"Coba lagi"</string> <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Reset ke setelan pabrik"</string> - <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Hapus total semua data pengguna?\n\n TINDAKAN INI TIDAK DAPAT DIURUNGKAN!"</string> + <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Wipe semua data pengguna?\n\n TINDAKAN INI TIDAK DAPAT DIURUNGKAN!"</string> <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Batal"</string> </resources> diff --git a/tools/recovery_l10n/res/values-ja/strings.xml b/tools/recovery_l10n/res/values-ja/strings.xml index 2d6c0abc4..3d6637278 100644 --- a/tools/recovery_l10n/res/values-ja/strings.xml +++ b/tools/recovery_l10n/res/values-ja/strings.xml @@ -6,7 +6,7 @@ <string name="recovery_no_command" msgid="4465476568623024327">"コマンドが指定されていません"</string> <string name="recovery_error" msgid="5748178989622716736">"エラーが発生しました。"</string> <string name="recovery_installing_security" msgid="9184031299717114342">"セキュリティ アップデートをインストールしています"</string> - <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android システムを読み込めません。データが破損している可能性があります。このメッセージが引き続き表示される場合は、データの初期化を行い、このデバイスに保存されているすべてのユーザー データを消去することが必要な場合があります。"</string> + <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android システムを読み込めません。データが破損している可能性があります。このメッセージが引き続き表示される場合は、データの初期化を行い、この端末に保存されているすべてのユーザー データを消去することが必要な場合があります。"</string> <string name="recovery_try_again" msgid="7168248750158873496">"再試行"</string> <string name="recovery_factory_data_reset" msgid="7321351565602894783">"データの初期化"</string> <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"すべてのユーザー データをワイプしますか?\n\nこの操作は元に戻せません。"</string> diff --git a/updater/Android.bp b/updater/Android.bp index b80cdb3a0..063366e5e 100644 --- a/updater/Android.bp +++ b/updater/Android.bp @@ -30,7 +30,6 @@ cc_defaults { "libfec", "libfec_rs", "libverity_tree", - "libfs_mgr", "libgtest_prod", "liblog", "liblp", @@ -46,6 +45,14 @@ cc_defaults { "libcrypto_utils", "libcutils", "libutils", + ], +} + +cc_defaults { + name: "libupdater_device_defaults", + + static_libs: [ + "libfs_mgr", "libtune2fs", "libext2_com_err", @@ -54,11 +61,13 @@ cc_defaults { "libext2_uuid", "libext2_e2p", "libext2fs", - ], + ] } cc_library_static { - name: "libupdater", + name: "libupdater_core", + + host_supported: true, defaults: [ "recovery_defaults", @@ -68,8 +77,38 @@ cc_library_static { srcs: [ "blockimg.cpp", "commands.cpp", - "dynamic_partitions.cpp", "install.cpp", + "updater.cpp", + ], + + target: { + darwin: { + enabled: false, + }, + }, + + export_include_dirs: [ + "include", + ], +} + +cc_library_static { + name: "libupdater_device", + + defaults: [ + "recovery_defaults", + "libupdater_defaults", + "libupdater_device_defaults", + ], + + srcs: [ + "dynamic_partitions.cpp", + "updater_runtime.cpp", + "updater_runtime_dynamic_partitions.cpp", + ], + + static_libs: [ + "libupdater_core", ], include_dirs: [ @@ -80,3 +119,35 @@ cc_library_static { "include", ], } + +cc_library_host_static { + name: "libupdater_host", + + defaults: [ + "recovery_defaults", + "libupdater_defaults", + ], + + srcs: [ + "build_info.cpp", + "dynamic_partitions.cpp", + "simulator_runtime.cpp", + "target_files.cpp", + ], + + static_libs: [ + "libupdater_core", + "libfstab", + "libc++fs", + ], + + target: { + darwin: { + enabled: false, + }, + }, + + export_include_dirs: [ + "include", + ], +} diff --git a/updater/Android.mk b/updater/Android.mk index c7a6ba989..93525c12a 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -33,7 +33,6 @@ updater_common_static_libraries := \ libfec \ libfec_rs \ libverity_tree \ - libfs_mgr \ libgtest_prod \ liblog \ liblp \ @@ -48,9 +47,24 @@ updater_common_static_libraries := \ libcrypto \ libcrypto_utils \ libcutils \ - libutils \ - libtune2fs \ - $(tune2fs_static_libraries) + libutils + + +# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function +# named "Register_<libname>()". Here we emit a little C function that +# gets #included by updater.cpp. It calls all those registration +# functions. +# $(1): the path to the register.inc file +# $(2): a list of TARGET_RECOVERY_UPDATER_LIBS +define generate-register-inc + $(hide) mkdir -p $(dir $(1)) + $(hide) echo "" > $(1) + $(hide) $(foreach lib,$(2),echo "extern void Register_$(lib)(void);" >> $(1);) + $(hide) echo "void RegisterDeviceExtensions() {" >> $(1) + $(hide) $(foreach lib,$(2),echo " Register_$(lib)();" >> $(1);) + $(hide) echo "}" >> $(1) +endef + # updater (static executable) # =============================== @@ -59,7 +73,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := updater LOCAL_SRC_FILES := \ - updater.cpp + updater_main.cpp LOCAL_C_INCLUDES := \ $(LOCAL_PATH)/include @@ -69,33 +83,26 @@ LOCAL_CFLAGS := \ -Werror LOCAL_STATIC_LIBRARIES := \ - libupdater \ + libupdater_device \ + libupdater_core \ $(TARGET_RECOVERY_UPDATER_LIBS) \ $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) \ - $(updater_common_static_libraries) + $(updater_common_static_libraries) \ + libfs_mgr \ + libtune2fs \ + $(tune2fs_static_libraries) -# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function -# named "Register_<libname>()". Here we emit a little C function that -# gets #included by updater.c. It calls all those registration -# functions. +LOCAL_MODULE_CLASS := EXECUTABLES +inc := $(call local-generated-sources-dir)/register.inc # Devices can also add libraries to TARGET_RECOVERY_UPDATER_EXTRA_LIBS. # These libs are also linked in with updater, but we don't try to call # any sort of registration function for these. Use this variable for # any subsidiary static libraries required for your registered # extension libs. - -LOCAL_MODULE_CLASS := EXECUTABLES -inc := $(call local-generated-sources-dir)/register.inc - $(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS) $(inc) : - $(hide) mkdir -p $(dir $@) - $(hide) echo "" > $@ - $(hide) $(foreach lib,$(libs),echo "extern void Register_$(lib)(void);" >> $@;) - $(hide) echo "void RegisterDeviceExtensions() {" >> $@ - $(hide) $(foreach lib,$(libs),echo " Register_$(lib)();" >> $@;) - $(hide) echo "}" >> $@ + $(call generate-register-inc,$@,$(libs)) LOCAL_GENERATED_SOURCES := $(inc) @@ -104,3 +111,32 @@ inc := LOCAL_FORCE_STATIC_EXECUTABLE := true include $(BUILD_EXECUTABLE) + +# TODO(xunchang) move to bp file +# update_host_simulator (host executable) +# =============================== +include $(CLEAR_VARS) + +LOCAL_MODULE := update_host_simulator +LOCAL_MODULE_HOST_OS := linux + +LOCAL_SRC_FILES := \ + update_simulator_main.cpp + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include + +LOCAL_CFLAGS := \ + -Wall \ + -Werror + +LOCAL_STATIC_LIBRARIES := \ + libupdater_host \ + libupdater_core \ + $(updater_common_static_libraries) \ + libfstab \ + libc++fs + +LOCAL_MODULE_CLASS := EXECUTABLES + +include $(BUILD_HOST_EXECUTABLE) diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index 07c3c7b52..2d41f610b 100644 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -42,17 +42,18 @@ #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/parseint.h> +#include <android-base/stringprintf.h> #include <android-base/strings.h> #include <android-base/unique_fd.h> #include <applypatch/applypatch.h> #include <brotli/decode.h> #include <fec/io.h> #include <openssl/sha.h> -#include <private/android_filesystem_config.h> #include <verity/hash_tree_builder.h> #include <ziparchive/zip_archive.h> #include "edify/expr.h" +#include "edify/updater_interface.h" #include "otautil/dirutil.h" #include "otautil/error_code.h" #include "otautil/paths.h" @@ -60,12 +61,16 @@ #include "otautil/rangeset.h" #include "private/commands.h" #include "updater/install.h" -#include "updater/updater.h" -// Set this to 0 to interpret 'erase' transfers to mean do a -// BLKDISCARD ioctl (the normal behavior). Set to 1 to interpret -// erase to mean fill the region with zeroes. +#ifdef __ANDROID__ +#include <private/android_filesystem_config.h> +// Set this to 0 to interpret 'erase' transfers to mean do a BLKDISCARD ioctl (the normal behavior). +// Set to 1 to interpret erase to mean fill the region with zeroes. #define DEBUG_ERASE 0 +#else +#define DEBUG_ERASE 1 +#define AID_SYSTEM -1 +#endif // __ANDROID__ static constexpr size_t BLOCKSIZE = 4096; static constexpr mode_t STASH_DIRECTORY_MODE = 0700; @@ -1668,42 +1673,43 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, return StringValue(""); } - UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie); - if (ui == nullptr) { + auto updater = state->updater; + auto block_device_path = updater->FindBlockDeviceName(blockdev_filename->data); + if (block_device_path.empty()) { + LOG(ERROR) << "Block device path for " << blockdev_filename->data << " not found. " << name + << " failed."; return StringValue(""); } - FILE* cmd_pipe = ui->cmd_pipe; - ZipArchiveHandle za = ui->package_zip; - - if (cmd_pipe == nullptr || za == nullptr) { + ZipArchiveHandle za = updater->GetPackageHandle(); + if (za == nullptr) { return StringValue(""); } - ZipString path_data(patch_data_fn->data.c_str()); + std::string_view path_data(patch_data_fn->data); ZipEntry patch_entry; if (FindEntry(za, path_data, &patch_entry) != 0) { LOG(ERROR) << name << "(): no file \"" << patch_data_fn->data << "\" in package"; return StringValue(""); } + params.patch_start = updater->GetMappedPackageAddress() + patch_entry.offset; - params.patch_start = ui->package_zip_addr + patch_entry.offset; - ZipString new_data(new_data_fn->data.c_str()); + std::string_view new_data(new_data_fn->data); ZipEntry new_entry; if (FindEntry(za, new_data, &new_entry) != 0) { LOG(ERROR) << name << "(): no file \"" << new_data_fn->data << "\" in package"; return StringValue(""); } - params.fd.reset(TEMP_FAILURE_RETRY(open(blockdev_filename->data.c_str(), O_RDWR))); + params.fd.reset(TEMP_FAILURE_RETRY(open(block_device_path.c_str(), O_RDWR))); if (params.fd == -1) { failure_type = errno == EIO ? kEioFailure : kFileOpenFailure; - PLOG(ERROR) << "open \"" << blockdev_filename->data << "\" failed"; + PLOG(ERROR) << "open \"" << block_device_path << "\" failed"; return StringValue(""); } uint8_t digest[SHA_DIGEST_LENGTH]; - if (!Sha1DevicePath(blockdev_filename->data, digest)) { + if (!Sha1DevicePath(block_device_path, digest)) { return StringValue(""); } params.stashbase = print_sha1(digest); @@ -1716,8 +1722,7 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, struct stat sb; int result = stat(updated_marker.c_str(), &sb); if (result == 0) { - LOG(INFO) << "Skipping already updated partition " << blockdev_filename->data - << " based on marker"; + LOG(INFO) << "Skipping already updated partition " << block_device_path << " based on marker"; return StringValue("t"); } } else { @@ -1887,8 +1892,10 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, LOG(WARNING) << "Failed to update the last command file."; } - fprintf(cmd_pipe, "set_progress %.4f\n", static_cast<double>(params.written) / total_blocks); - fflush(cmd_pipe); + updater->WriteToCommandPipe( + android::base::StringPrintf("set_progress %.4f", + static_cast<double>(params.written) / total_blocks), + true); } } @@ -1913,13 +1920,15 @@ pbiudone: LOG(INFO) << "stashed " << params.stashed << " blocks"; LOG(INFO) << "max alloc needed was " << params.buffer.size(); - const char* partition = strrchr(blockdev_filename->data.c_str(), '/'); + const char* partition = strrchr(block_device_path.c_str(), '/'); if (partition != nullptr && *(partition + 1) != 0) { - fprintf(cmd_pipe, "log bytes_written_%s: %" PRIu64 "\n", partition + 1, - static_cast<uint64_t>(params.written) * BLOCKSIZE); - fprintf(cmd_pipe, "log bytes_stashed_%s: %" PRIu64 "\n", partition + 1, - static_cast<uint64_t>(params.stashed) * BLOCKSIZE); - fflush(cmd_pipe); + updater->WriteToCommandPipe( + android::base::StringPrintf("log bytes_written_%s: %" PRIu64, partition + 1, + static_cast<uint64_t>(params.written) * BLOCKSIZE)); + updater->WriteToCommandPipe( + android::base::StringPrintf("log bytes_stashed_%s: %" PRIu64, partition + 1, + static_cast<uint64_t>(params.stashed) * BLOCKSIZE), + true); } // Delete stash only after successfully completing the update, as it may contain blocks needed // to complete the update later. @@ -2019,7 +2028,7 @@ Value* BlockImageVerifyFn(const char* name, State* state, // clang-format off { Command::Type::ABORT, PerformCommandAbort }, { Command::Type::BSDIFF, PerformCommandDiff }, - { Command::Type::COMPUTE_HASH_TREE, PerformCommandComputeHashTree }, + { Command::Type::COMPUTE_HASH_TREE, nullptr }, { Command::Type::ERASE, nullptr }, { Command::Type::FREE, PerformCommandFree }, { Command::Type::IMGDIFF, PerformCommandDiff }, @@ -2079,10 +2088,17 @@ Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique return StringValue(""); } - android::base::unique_fd fd(open(blockdev_filename->data.c_str(), O_RDWR)); + auto block_device_path = state->updater->FindBlockDeviceName(blockdev_filename->data); + if (block_device_path.empty()) { + LOG(ERROR) << "Block device path for " << blockdev_filename->data << " not found. " << name + << " failed."; + return StringValue(""); + } + + android::base::unique_fd fd(open(block_device_path.c_str(), O_RDWR)); if (fd == -1) { CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure; - ErrorAbort(state, cause_code, "open \"%s\" failed: %s", blockdev_filename->data.c_str(), + ErrorAbort(state, cause_code, "open \"%s\" failed: %s", block_device_path.c_str(), strerror(errno)); return StringValue(""); } @@ -2096,7 +2112,7 @@ Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique std::vector<uint8_t> buffer(BLOCKSIZE); for (const auto& [begin, end] : rs) { if (!check_lseek(fd, static_cast<off64_t>(begin) * BLOCKSIZE, SEEK_SET)) { - ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", blockdev_filename->data.c_str(), + ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", block_device_path.c_str(), strerror(errno)); return StringValue(""); } @@ -2104,7 +2120,7 @@ Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique for (size_t j = begin; j < end; ++j) { if (!android::base::ReadFully(fd, buffer.data(), BLOCKSIZE)) { CauseCode cause_code = errno == EIO ? kEioFailure : kFreadFailure; - ErrorAbort(state, cause_code, "failed to read %s: %s", blockdev_filename->data.c_str(), + ErrorAbort(state, cause_code, "failed to read %s: %s", block_device_path.c_str(), strerror(errno)); return StringValue(""); } @@ -2143,10 +2159,17 @@ Value* CheckFirstBlockFn(const char* name, State* state, return StringValue(""); } - android::base::unique_fd fd(open(arg_filename->data.c_str(), O_RDONLY)); + auto block_device_path = state->updater->FindBlockDeviceName(arg_filename->data); + if (block_device_path.empty()) { + LOG(ERROR) << "Block device path for " << arg_filename->data << " not found. " << name + << " failed."; + return StringValue(""); + } + + android::base::unique_fd fd(open(block_device_path.c_str(), O_RDONLY)); if (fd == -1) { CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure; - ErrorAbort(state, cause_code, "open \"%s\" failed: %s", arg_filename->data.c_str(), + ErrorAbort(state, cause_code, "open \"%s\" failed: %s", block_device_path.c_str(), strerror(errno)); return StringValue(""); } @@ -2156,7 +2179,7 @@ Value* CheckFirstBlockFn(const char* name, State* state, if (ReadBlocks(blk0, &block0_buffer, fd) == -1) { CauseCode cause_code = errno == EIO ? kEioFailure : kFreadFailure; - ErrorAbort(state, cause_code, "failed to read %s: %s", arg_filename->data.c_str(), + ErrorAbort(state, cause_code, "failed to read %s: %s", block_device_path.c_str(), strerror(errno)); return StringValue(""); } @@ -2172,8 +2195,10 @@ Value* CheckFirstBlockFn(const char* name, State* state, uint16_t mount_count = *reinterpret_cast<uint16_t*>(&block0_buffer[0x400 + 0x34]); if (mount_count > 0) { - uiPrintf(state, "Device was remounted R/W %" PRIu16 " times", mount_count); - uiPrintf(state, "Last remount happened on %s", ctime(&mount_time)); + state->updater->UiPrint( + android::base::StringPrintf("Device was remounted R/W %" PRIu16 " times", mount_count)); + state->updater->UiPrint( + android::base::StringPrintf("Last remount happened on %s", ctime(&mount_time))); } return StringValue("t"); @@ -2209,14 +2234,21 @@ Value* BlockImageRecoverFn(const char* name, State* state, return StringValue(""); } + auto block_device_path = state->updater->FindBlockDeviceName(filename->data); + if (block_device_path.empty()) { + LOG(ERROR) << "Block device path for " << filename->data << " not found. " << name + << " failed."; + return StringValue(""); + } + // Output notice to log when recover is attempted - LOG(INFO) << filename->data << " image corrupted, attempting to recover..."; + LOG(INFO) << block_device_path << " image corrupted, attempting to recover..."; // When opened with O_RDWR, libfec rewrites corrupted blocks when they are read - fec::io fh(filename->data, O_RDWR); + fec::io fh(block_device_path, O_RDWR); if (!fh) { - ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data.c_str(), + ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", block_device_path.c_str(), strerror(errno)); return StringValue(""); } @@ -2242,7 +2274,7 @@ Value* BlockImageRecoverFn(const char* name, State* state, if (fh.pread(buffer, BLOCKSIZE, static_cast<off64_t>(j) * BLOCKSIZE) != BLOCKSIZE) { ErrorAbort(state, kLibfecFailure, "failed to recover %s (block %zu): %s", - filename->data.c_str(), j, strerror(errno)); + block_device_path.c_str(), j, strerror(errno)); return StringValue(""); } @@ -2258,7 +2290,7 @@ Value* BlockImageRecoverFn(const char* name, State* state, // read and check if the errors field value has increased. } } - LOG(INFO) << "..." << filename->data << " image recovered successfully."; + LOG(INFO) << "..." << block_device_path << " image recovered successfully."; return StringValue("t"); } diff --git a/updater/build_info.cpp b/updater/build_info.cpp new file mode 100644 index 000000000..f168008ec --- /dev/null +++ b/updater/build_info.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "updater/build_info.h" + +#include <stdio.h> + +#include <set> +#include <vector> + +#include <android-base/logging.h> +#include <android-base/stringprintf.h> +#include <android-base/strings.h> + +#include "updater/target_files.h" + +bool BuildInfo::ParseTargetFile(const std::string_view target_file_path, bool extracted_input) { + TargetFile target_file(std::string(target_file_path), extracted_input); + if (!target_file.Open()) { + return false; + } + + if (!target_file.GetBuildProps(&build_props_)) { + return false; + } + + std::vector<FstabInfo> fstab_info_list; + if (!target_file.ParseFstabInfo(&fstab_info_list)) { + return false; + } + + for (const auto& fstab_info : fstab_info_list) { + for (const auto& directory : { "IMAGES", "RADIO" }) { + std::string entry_name = directory + fstab_info.mount_point + ".img"; + if (!target_file.EntryExists(entry_name)) { + LOG(WARNING) << "Failed to find the image entry in the target file: " << entry_name; + continue; + } + + temp_files_.emplace_back(work_dir_); + auto& image_file = temp_files_.back(); + if (!target_file.ExtractImage(entry_name, fstab_info, work_dir_, &image_file)) { + LOG(ERROR) << "Failed to set up source image files."; + return false; + } + + std::string mapped_path = image_file.path; + // Rename the images to more readable ones if we want to keep the image. + if (keep_images_) { + mapped_path = work_dir_ + fstab_info.mount_point + ".img"; + image_file.release(); + if (rename(image_file.path, mapped_path.c_str()) != 0) { + PLOG(ERROR) << "Failed to rename " << image_file.path << " to " << mapped_path; + return false; + } + } + + LOG(INFO) << "Mounted " << fstab_info.mount_point << "\nMapping: " << fstab_info.blockdev_name + << " to " << mapped_path; + + blockdev_map_.emplace( + fstab_info.blockdev_name, + FakeBlockDevice(fstab_info.blockdev_name, fstab_info.mount_point, mapped_path)); + break; + } + } + + return true; +} + +std::string BuildInfo::GetProperty(const std::string_view key, + const std::string_view default_value) const { + // The logic to parse the ro.product properties should be in line with the generation script. + // More details in common.py BuildInfo.GetBuildProp. + // TODO(xunchang) handle the oem property and the source order defined in + // ro.product.property_source_order + const std::set<std::string, std::less<>> ro_product_props = { + "ro.product.brand", "ro.product.device", "ro.product.manufacturer", "ro.product.model", + "ro.product.name" + }; + const std::vector<std::string> source_order = { + "product", "odm", "vendor", "system_ext", "system", + }; + if (ro_product_props.find(key) != ro_product_props.end()) { + std::string_view key_suffix(key); + CHECK(android::base::ConsumePrefix(&key_suffix, "ro.product")); + for (const auto& source : source_order) { + std::string resolved_key = "ro.product." + source + std::string(key_suffix); + if (auto entry = build_props_.find(resolved_key); entry != build_props_.end()) { + return entry->second; + } + } + LOG(WARNING) << "Failed to find property: " << key; + return std::string(default_value); + } else if (key == "ro.build.fingerprint") { + // clang-format off + return android::base::StringPrintf("%s/%s/%s:%s/%s/%s:%s/%s", + GetProperty("ro.product.brand", "").c_str(), + GetProperty("ro.product.name", "").c_str(), + GetProperty("ro.product.device", "").c_str(), + GetProperty("ro.build.version.release", "").c_str(), + GetProperty("ro.build.id", "").c_str(), + GetProperty("ro.build.version.incremental", "").c_str(), + GetProperty("ro.build.type", "").c_str(), + GetProperty("ro.build.tags", "").c_str()); + // clang-format on + } + + auto entry = build_props_.find(key); + if (entry == build_props_.end()) { + LOG(WARNING) << "Failed to find property: " << key; + return std::string(default_value); + } + + return entry->second; +} + +std::string BuildInfo::FindBlockDeviceName(const std::string_view name) const { + auto entry = blockdev_map_.find(name); + if (entry == blockdev_map_.end()) { + LOG(WARNING) << "Failed to find path to block device " << name; + return ""; + } + + return entry->second.mounted_file_path; +} diff --git a/updater/dynamic_partitions.cpp b/updater/dynamic_partitions.cpp index b50dd75f9..a340116fe 100644 --- a/updater/dynamic_partitions.cpp +++ b/updater/dynamic_partitions.cpp @@ -19,46 +19,20 @@ #include <sys/stat.h> #include <sys/types.h> -#include <algorithm> -#include <chrono> -#include <iterator> #include <memory> -#include <optional> #include <string> -#include <type_traits> #include <vector> #include <android-base/file.h> #include <android-base/logging.h> -#include <android-base/parseint.h> #include <android-base/strings.h> -#include <fs_mgr.h> -#include <fs_mgr_dm_linear.h> -#include <libdm/dm.h> -#include <liblp/builder.h> #include "edify/expr.h" +#include "edify/updater_runtime_interface.h" #include "otautil/error_code.h" #include "otautil/paths.h" #include "private/utils.h" -using android::base::ParseUint; -using android::dm::DeviceMapper; -using android::dm::DmDeviceState; -using android::fs_mgr::CreateLogicalPartition; -using android::fs_mgr::DestroyLogicalPartition; -using android::fs_mgr::LpMetadata; -using android::fs_mgr::MetadataBuilder; -using android::fs_mgr::Partition; -using android::fs_mgr::PartitionOpener; - -static constexpr std::chrono::milliseconds kMapTimeout{ 1000 }; -static constexpr char kMetadataUpdatedMarker[] = "/dynamic_partition_metadata.UPDATED"; - -static std::string GetSuperDevice() { - return "/dev/block/by-name/" + fs_mgr_get_super_partition_name(); -} - static std::vector<std::string> ReadStringArgs(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv, const std::vector<std::string>& arg_names) { @@ -89,40 +63,14 @@ static std::vector<std::string> ReadStringArgs(const char* name, State* state, return ret; } -static bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) { - auto state = DeviceMapper::Instance().GetState(partition_name); - if (state == DmDeviceState::INVALID) { - return true; - } - if (state == DmDeviceState::ACTIVE) { - return DestroyLogicalPartition(partition_name, kMapTimeout); - } - LOG(ERROR) << "Unknown device mapper state: " - << static_cast<std::underlying_type_t<DmDeviceState>>(state); - return false; -} - -static bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) { - auto state = DeviceMapper::Instance().GetState(partition_name); - if (state == DmDeviceState::INVALID) { - return CreateLogicalPartition(GetSuperDevice(), 0 /* metadata slot */, partition_name, - true /* force writable */, kMapTimeout, path); - } - - if (state == DmDeviceState::ACTIVE) { - return DeviceMapper::Instance().GetDmDevicePathByName(partition_name, path); - } - LOG(ERROR) << "Unknown device mapper state: " - << static_cast<std::underlying_type_t<DmDeviceState>>(state); - return false; -} - Value* UnmapPartitionFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { auto args = ReadStringArgs(name, state, argv, { "name" }); if (args.empty()) return StringValue(""); - return UnmapPartitionOnDeviceMapper(args[0]) ? StringValue("t") : StringValue(""); + auto updater_runtime = state->updater->GetRuntime(); + return updater_runtime->UnmapPartitionOnDeviceMapper(args[0]) ? StringValue("t") + : StringValue(""); } Value* MapPartitionFn(const char* name, State* state, @@ -131,207 +79,12 @@ Value* MapPartitionFn(const char* name, State* state, if (args.empty()) return StringValue(""); std::string path; - bool result = MapPartitionOnDeviceMapper(args[0], &path); + auto updater_runtime = state->updater->GetRuntime(); + bool result = updater_runtime->MapPartitionOnDeviceMapper(args[0], &path); return result ? StringValue(path) : StringValue(""); } -namespace { // Ops - -struct OpParameters { - std::vector<std::string> tokens; - MetadataBuilder* builder; - - bool ExpectArgSize(size_t size) const { - CHECK(!tokens.empty()); - auto actual = tokens.size() - 1; - if (actual != size) { - LOG(ERROR) << "Op " << op() << " expects " << size << " args, got " << actual; - return false; - } - return true; - } - const std::string& op() const { - CHECK(!tokens.empty()); - return tokens[0]; - } - const std::string& arg(size_t pos) const { - CHECK_LE(pos + 1, tokens.size()); - return tokens[pos + 1]; - } - std::optional<uint64_t> uint_arg(size_t pos, const std::string& name) const { - auto str = arg(pos); - uint64_t ret; - if (!ParseUint(str, &ret)) { - LOG(ERROR) << "Op " << op() << " expects uint64 for argument " << name << ", got " << str; - return std::nullopt; - } - return ret; - } -}; - -using OpFunction = std::function<bool(const OpParameters&)>; -using OpMap = std::map<std::string, OpFunction>; - -bool PerformOpResize(const OpParameters& params) { - if (!params.ExpectArgSize(2)) return false; - const auto& partition_name = params.arg(0); - auto size = params.uint_arg(1, "size"); - if (!size.has_value()) return false; - - auto partition = params.builder->FindPartition(partition_name); - if (partition == nullptr) { - LOG(ERROR) << "Failed to find partition " << partition_name - << " in dynamic partition metadata."; - return false; - } - if (!UnmapPartitionOnDeviceMapper(partition_name)) { - LOG(ERROR) << "Cannot unmap " << partition_name << " before resizing."; - return false; - } - if (!params.builder->ResizePartition(partition, size.value())) { - LOG(ERROR) << "Failed to resize partition " << partition_name << " to size " << *size << "."; - return false; - } - return true; -} - -bool PerformOpRemove(const OpParameters& params) { - if (!params.ExpectArgSize(1)) return false; - const auto& partition_name = params.arg(0); - - if (!UnmapPartitionOnDeviceMapper(partition_name)) { - LOG(ERROR) << "Cannot unmap " << partition_name << " before removing."; - return false; - } - params.builder->RemovePartition(partition_name); - return true; -} - -bool PerformOpAdd(const OpParameters& params) { - if (!params.ExpectArgSize(2)) return false; - const auto& partition_name = params.arg(0); - const auto& group_name = params.arg(1); - - if (params.builder->AddPartition(partition_name, group_name, LP_PARTITION_ATTR_READONLY) == - nullptr) { - LOG(ERROR) << "Failed to add partition " << partition_name << " to group " << group_name << "."; - return false; - } - return true; -} - -bool PerformOpMove(const OpParameters& params) { - if (!params.ExpectArgSize(2)) return false; - const auto& partition_name = params.arg(0); - const auto& new_group = params.arg(1); - - auto partition = params.builder->FindPartition(partition_name); - if (partition == nullptr) { - LOG(ERROR) << "Cannot move partition " << partition_name << " to group " << new_group - << " because it is not found."; - return false; - } - - auto old_group = partition->group_name(); - if (old_group != new_group) { - if (!params.builder->ChangePartitionGroup(partition, new_group)) { - LOG(ERROR) << "Cannot move partition " << partition_name << " from group " << old_group - << " to group " << new_group << "."; - return false; - } - } - return true; -} - -bool PerformOpAddGroup(const OpParameters& params) { - if (!params.ExpectArgSize(2)) return false; - const auto& group_name = params.arg(0); - auto maximum_size = params.uint_arg(1, "maximum_size"); - if (!maximum_size.has_value()) return false; - - auto group = params.builder->FindGroup(group_name); - if (group != nullptr) { - LOG(ERROR) << "Cannot add group " << group_name << " because it already exists."; - return false; - } - - if (maximum_size.value() == 0) { - LOG(WARNING) << "Adding group " << group_name << " with no size limits."; - } - - if (!params.builder->AddGroup(group_name, maximum_size.value())) { - LOG(ERROR) << "Failed to add group " << group_name << " with maximum size " - << maximum_size.value() << "."; - return false; - } - return true; -} - -bool PerformOpResizeGroup(const OpParameters& params) { - if (!params.ExpectArgSize(2)) return false; - const auto& group_name = params.arg(0); - auto new_size = params.uint_arg(1, "maximum_size"); - if (!new_size.has_value()) return false; - - auto group = params.builder->FindGroup(group_name); - if (group == nullptr) { - LOG(ERROR) << "Cannot resize group " << group_name << " because it is not found."; - return false; - } - - auto old_size = group->maximum_size(); - if (old_size != new_size.value()) { - if (!params.builder->ChangeGroupSize(group_name, new_size.value())) { - LOG(ERROR) << "Cannot resize group " << group_name << " from " << old_size << " to " - << new_size.value() << "."; - return false; - } - } - return true; -} - -std::vector<std::string> ListPartitionNamesInGroup(MetadataBuilder* builder, - const std::string& group_name) { - auto partitions = builder->ListPartitionsInGroup(group_name); - std::vector<std::string> partition_names; - std::transform(partitions.begin(), partitions.end(), std::back_inserter(partition_names), - [](Partition* partition) { return partition->name(); }); - return partition_names; -} - -bool PerformOpRemoveGroup(const OpParameters& params) { - if (!params.ExpectArgSize(1)) return false; - const auto& group_name = params.arg(0); - - auto partition_names = ListPartitionNamesInGroup(params.builder, group_name); - if (!partition_names.empty()) { - LOG(ERROR) << "Cannot remove group " << group_name << " because it still contains partitions [" - << android::base::Join(partition_names, ", ") << "]"; - return false; - } - params.builder->RemoveGroupAndPartitions(group_name); - return true; -} - -bool PerformOpRemoveAllGroups(const OpParameters& params) { - if (!params.ExpectArgSize(0)) return false; - - auto group_names = params.builder->ListGroups(); - for (const auto& group_name : group_names) { - auto partition_names = ListPartitionNamesInGroup(params.builder, group_name); - for (const auto& partition_name : partition_names) { - if (!UnmapPartitionOnDeviceMapper(partition_name)) { - LOG(ERROR) << "Cannot unmap " << partition_name << " before removing group " << group_name - << "."; - return false; - } - } - params.builder->RemoveGroupAndPartitions(group_name); - } - return true; -} - -} // namespace +static constexpr char kMetadataUpdatedMarker[] = "/dynamic_partition_metadata.UPDATED"; Value* UpdateDynamicPartitionsFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { @@ -367,56 +120,8 @@ Value* UpdateDynamicPartitionsFn(const char* name, State* state, } } - auto super_device = GetSuperDevice(); - auto builder = MetadataBuilder::New(PartitionOpener(), super_device, 0); - if (builder == nullptr) { - LOG(ERROR) << "Failed to load dynamic partition metadata."; - return StringValue(""); - } - - static const OpMap op_map{ - // clang-format off - {"resize", PerformOpResize}, - {"remove", PerformOpRemove}, - {"add", PerformOpAdd}, - {"move", PerformOpMove}, - {"add_group", PerformOpAddGroup}, - {"resize_group", PerformOpResizeGroup}, - {"remove_group", PerformOpRemoveGroup}, - {"remove_all_groups", PerformOpRemoveAllGroups}, - // clang-format on - }; - - std::vector<std::string> lines = android::base::Split(op_list_value->data, "\n"); - for (const auto& line : lines) { - auto comment_idx = line.find('#'); - auto op_and_args = comment_idx == std::string::npos ? line : line.substr(0, comment_idx); - op_and_args = android::base::Trim(op_and_args); - if (op_and_args.empty()) continue; - - auto tokens = android::base::Split(op_and_args, " "); - const auto& op = tokens[0]; - auto it = op_map.find(op); - if (it == op_map.end()) { - LOG(ERROR) << "Unknown operation in op_list: " << op; - return StringValue(""); - } - OpParameters params; - params.tokens = tokens; - params.builder = builder.get(); - if (!it->second(params)) { - return StringValue(""); - } - } - - auto metadata = builder->Export(); - if (metadata == nullptr) { - LOG(ERROR) << "Failed to export metadata."; - return StringValue(""); - } - - if (!UpdatePartitionTable(super_device, *metadata, 0)) { - LOG(ERROR) << "Failed to write metadata."; + auto updater_runtime = state->updater->GetRuntime(); + if (!updater_runtime->UpdateDynamicPartitions(op_list_value->data)) { return StringValue(""); } diff --git a/updater/include/updater/build_info.h b/updater/include/updater/build_info.h new file mode 100644 index 000000000..0073bfa4a --- /dev/null +++ b/updater/include/updater/build_info.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <list> +#include <map> +#include <string> +#include <string_view> + +#include <android-base/file.h> + +// This class serves as the aggregation of the fake block device information during update +// simulation on host. In specific, it has the name of the block device, its mount point, and the +// path to the temporary file that fakes this block device. +class FakeBlockDevice { + public: + FakeBlockDevice(std::string block_device, std::string mount_point, std::string temp_file_path) + : blockdev_name(std::move(block_device)), + mount_point(std::move(mount_point)), + mounted_file_path(std::move(temp_file_path)) {} + + std::string blockdev_name; + std::string mount_point; + std::string mounted_file_path; // path to the temp file that mocks the block device +}; + +// This class stores the information of the source build. For example, it creates and maintains +// the temporary files to simulate the block devices on host. Therefore, the simulator runtime can +// query the information and run the update on host. +class BuildInfo { + public: + BuildInfo(const std::string_view work_dir, bool keep_images) + : work_dir_(work_dir), keep_images_(keep_images) {} + // Returns the value of the build properties. + std::string GetProperty(const std::string_view key, const std::string_view default_value) const; + // Returns the path to the mock block device. + std::string FindBlockDeviceName(const std::string_view name) const; + // Parses the given target-file, initializes the build properties and extracts the images. + bool ParseTargetFile(const std::string_view target_file_path, bool extracted_input); + + std::string GetOemSettings() const { + return oem_settings_; + } + void SetOemSettings(const std::string_view oem_settings) { + oem_settings_ = oem_settings; + } + + private: + // A map to store the system properties during simulation. + std::map<std::string, std::string, std::less<>> build_props_; + // A file that contains the oem properties. + std::string oem_settings_; + // A map from the blockdev_name to the FakeBlockDevice object, which contains the path to the + // temporary file. + std::map<std::string, FakeBlockDevice, std::less<>> blockdev_map_; + + std::list<TemporaryFile> temp_files_; + std::string work_dir_; // A temporary directory to store the extracted image files + bool keep_images_; +}; diff --git a/updater/include/updater/install.h b/updater/include/updater/install.h index 8d6ca4728..9fe203149 100644 --- a/updater/include/updater/install.h +++ b/updater/include/updater/install.h @@ -14,15 +14,6 @@ * limitations under the License. */ -#ifndef _UPDATER_INSTALL_H_ -#define _UPDATER_INSTALL_H_ - -struct State; +#pragma once void RegisterInstallFunctions(); - -// uiPrintf function prints msg to screen as well as logs -void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...) - __attribute__((__format__(printf, 2, 3))); - -#endif diff --git a/updater/include/updater/simulator_runtime.h b/updater/include/updater/simulator_runtime.h new file mode 100644 index 000000000..9f7847b4f --- /dev/null +++ b/updater/include/updater/simulator_runtime.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <map> +#include <memory> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +#include "edify/updater_runtime_interface.h" +#include "updater/build_info.h" + +class SimulatorRuntime : public UpdaterRuntimeInterface { + public: + explicit SimulatorRuntime(BuildInfo* source) : source_(source) {} + + bool IsSimulator() const override { + return true; + } + + std::string GetProperty(const std::string_view key, + const std::string_view default_value) const override; + + int Mount(const std::string_view location, const std::string_view mount_point, + const std::string_view fs_type, const std::string_view mount_options) override; + bool IsMounted(const std::string_view mount_point) const override; + std::pair<bool, int> Unmount(const std::string_view mount_point) override; + + bool ReadFileToString(const std::string_view filename, std::string* content) const override; + bool WriteStringToFile(const std::string_view content, + const std::string_view filename) const override; + + int WipeBlockDevice(const std::string_view filename, size_t len) const override; + int RunProgram(const std::vector<std::string>& args, bool is_vfork) const override; + int Tune2Fs(const std::vector<std::string>& args) const override; + + bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) override; + bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) override; + bool UpdateDynamicPartitions(const std::string_view op_list_value) override; + + private: + std::string FindBlockDeviceName(const std::string_view name) const override; + + BuildInfo* source_; + std::map<std::string, std::string, std::less<>> mounted_partitions_; +}; diff --git a/updater/include/updater/target_files.h b/updater/include/updater/target_files.h new file mode 100644 index 000000000..860d47a35 --- /dev/null +++ b/updater/include/updater/target_files.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <map> +#include <string> +#include <string_view> +#include <vector> + +#include <android-base/file.h> +#include <ziparchive/zip_archive.h> + +// This class represents the mount information for each line in a fstab file. +class FstabInfo { + public: + FstabInfo(std::string blockdev_name, std::string mount_point, std::string fs_type) + : blockdev_name(std::move(blockdev_name)), + mount_point(std::move(mount_point)), + fs_type(std::move(fs_type)) {} + + std::string blockdev_name; + std::string mount_point; + std::string fs_type; +}; + +// This class parses a target file from a zip file or an extracted directory. It also provides the +// function to read the its content for simulation. +class TargetFile { + public: + TargetFile(std::string path, bool extracted_input) + : path_(std::move(path)), extracted_input_(extracted_input) {} + + // Opens the input target file (or extracted directory) and parses the misc_info.txt. + bool Open(); + // Parses the build properties in all possible locations and save them in |props_map| + bool GetBuildProps(std::map<std::string, std::string, std::less<>>* props_map) const; + // Parses the fstab and save the information about each partition to mount into |fstab_info_list|. + bool ParseFstabInfo(std::vector<FstabInfo>* fstab_info_list) const; + // Returns true if the given entry exists in the target file. + bool EntryExists(const std::string_view name) const; + // Extracts the image file |entry_name|. Returns true on success. + bool ExtractImage(const std::string_view entry_name, const FstabInfo& fstab_info, + const std::string_view work_dir, TemporaryFile* image_file) const; + + private: + // Wrapper functions to read the entry from either the zipped target-file, or the extracted input + // directory. + bool ReadEntryToString(const std::string_view name, std::string* content) const; + bool ExtractEntryToTempFile(const std::string_view name, TemporaryFile* temp_file) const; + + std::string path_; // Path to the zipped target-file or an extracted directory. + bool extracted_input_; // True if the target-file has been extracted. + ZipArchiveHandle handle_{ nullptr }; + + // The properties under META/misc_info.txt + std::map<std::string, std::string, std::less<>> misc_info_; +}; diff --git a/updater/include/updater/updater.h b/updater/include/updater/updater.h index f4a2fe874..8676b6038 100644 --- a/updater/include/updater/updater.h +++ b/updater/include/updater/updater.h @@ -14,22 +14,83 @@ * limitations under the License. */ -#ifndef _UPDATER_UPDATER_H_ -#define _UPDATER_UPDATER_H_ +#pragma once +#include <stdint.h> #include <stdio.h> + +#include <memory> +#include <string> +#include <string_view> + #include <ziparchive/zip_archive.h> -typedef struct { - FILE* cmd_pipe; - ZipArchiveHandle package_zip; - int version; +#include "edify/expr.h" +#include "edify/updater_interface.h" +#include "otautil/error_code.h" +#include "otautil/sysutil.h" + +class Updater : public UpdaterInterface { + public: + explicit Updater(std::unique_ptr<UpdaterRuntimeInterface> run_time) + : runtime_(std::move(run_time)) {} + + ~Updater() override; + + // Memory-maps the OTA package and opens it as a zip file. Also sets up the command pipe and + // UpdaterRuntime. + bool Init(int fd, const std::string_view package_filename, bool is_retry); + + // Parses and evaluates the updater-script in the OTA package. Reports the error code if the + // evaluation fails. + bool RunUpdate(); + + // Writes the message to command pipe, adds a new line in the end. + void WriteToCommandPipe(const std::string_view message, bool flush = false) const override; + + // Sends over the message to recovery to print it on the screen. + void UiPrint(const std::string_view message) const override; + + std::string FindBlockDeviceName(const std::string_view name) const override; + + UpdaterRuntimeInterface* GetRuntime() const override { + return runtime_.get(); + } + ZipArchiveHandle GetPackageHandle() const override { + return package_handle_; + } + std::string GetResult() const override { + return result_; + } + uint8_t* GetMappedPackageAddress() const override { + return mapped_package_.addr; + } + size_t GetMappedPackageLength() const override { + return mapped_package_.length; + } + + private: + friend class UpdaterTestBase; + friend class UpdaterTest; + // Where in the package we expect to find the edify script to execute. + // (Note it's "updateR-script", not the older "update-script".) + static constexpr const char* SCRIPT_NAME = "META-INF/com/google/android/updater-script"; + + // Reads the entry |name| in the zip archive and put the result in |content|. + bool ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name, std::string* content); + + // Parses the error code embedded in state->errmsg; and reports the error code and cause code. + void ParseAndReportErrorCode(State* state); + + std::unique_ptr<UpdaterRuntimeInterface> runtime_; - uint8_t* package_zip_addr; - size_t package_zip_len; -} UpdaterInfo; + MemMapping mapped_package_; + ZipArchiveHandle package_handle_{ nullptr }; + std::string updater_script_; -struct selabel_handle; -extern struct selabel_handle *sehandle; + bool is_retry_{ false }; + std::unique_ptr<FILE, decltype(&fclose)> cmd_pipe_{ nullptr, fclose }; -#endif + std::string result_; + std::vector<std::string> skipped_functions_; +}; diff --git a/updater/include/updater/updater_runtime.h b/updater/include/updater/updater_runtime.h new file mode 100644 index 000000000..8fc066f6a --- /dev/null +++ b/updater/include/updater/updater_runtime.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <memory> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +#include "edify/updater_runtime_interface.h" + +struct selabel_handle; + +class UpdaterRuntime : public UpdaterRuntimeInterface { + public: + explicit UpdaterRuntime(struct selabel_handle* sehandle) : sehandle_(sehandle) {} + ~UpdaterRuntime() override = default; + + bool IsSimulator() const override { + return false; + } + + std::string GetProperty(const std::string_view key, + const std::string_view default_value) const override; + + std::string FindBlockDeviceName(const std::string_view name) const override; + + int Mount(const std::string_view location, const std::string_view mount_point, + const std::string_view fs_type, const std::string_view mount_options) override; + bool IsMounted(const std::string_view mount_point) const override; + std::pair<bool, int> Unmount(const std::string_view mount_point) override; + + bool ReadFileToString(const std::string_view filename, std::string* content) const override; + bool WriteStringToFile(const std::string_view content, + const std::string_view filename) const override; + + int WipeBlockDevice(const std::string_view filename, size_t len) const override; + int RunProgram(const std::vector<std::string>& args, bool is_vfork) const override; + int Tune2Fs(const std::vector<std::string>& args) const override; + + bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) override; + bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) override; + bool UpdateDynamicPartitions(const std::string_view op_list_value) override; + + private: + struct selabel_handle* sehandle_{ nullptr }; +}; diff --git a/updater/install.cpp b/updater/install.cpp index 20a204a83..be0ceb06c 100644 --- a/updater/install.cpp +++ b/updater/install.cpp @@ -53,45 +53,31 @@ #include <openssl/sha.h> #include <selinux/label.h> #include <selinux/selinux.h> -#include <tune2fs.h> #include <ziparchive/zip_archive.h> #include "edify/expr.h" +#include "edify/updater_interface.h" +#include "edify/updater_runtime_interface.h" #include "otautil/dirutil.h" #include "otautil/error_code.h" #include "otautil/mounts.h" #include "otautil/print_sha1.h" #include "otautil/sysutil.h" -#include "updater/updater.h" -// Send over the buffer to recovery though the command pipe. -static void uiPrint(State* state, const std::string& buffer) { - UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie); +#ifndef __ANDROID__ +#include <cutils/memory.h> // for strlcpy +#endif - // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "". - // So skip sending empty strings to UI. - std::vector<std::string> lines = android::base::Split(buffer, "\n"); - for (auto& line : lines) { - if (!line.empty()) { - fprintf(ui->cmd_pipe, "ui_print %s\n", line.c_str()); - } +static bool UpdateBlockDeviceNameForPartition(UpdaterInterface* updater, Partition* partition) { + CHECK(updater); + std::string name = updater->FindBlockDeviceName(partition->name); + if (name.empty()) { + LOG(ERROR) << "Failed to find the block device " << partition->name; + return false; } - // On the updater side, we need to dump the contents to stderr (which has - // been redirected to the log file). Because the recovery will only print - // the contents to screen when processing pipe command ui_print. - LOG(INFO) << buffer; -} - -void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...) { - std::string error_msg; - - va_list ap; - va_start(ap, format); - android::base::StringAppendV(&error_msg, format, ap); - va_end(ap); - - uiPrint(state, error_msg); + partition->name = std::move(name); + return true; } // This is the updater side handler for ui_print() in edify script. Contents will be sent over to @@ -103,7 +89,7 @@ Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_p } std::string buffer = android::base::Join(args, ""); - uiPrint(state, buffer); + state->updater->UiPrint(buffer); return StringValue(buffer); } @@ -127,16 +113,22 @@ Value* PackageExtractFileFn(const char* name, State* state, argv.size()); } const std::string& zip_path = args[0]; - const std::string& dest_path = args[1]; + std::string dest_path = args[1]; - ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip; - ZipString zip_string_path(zip_path.c_str()); + ZipArchiveHandle za = state->updater->GetPackageHandle(); ZipEntry entry; - if (FindEntry(za, zip_string_path, &entry) != 0) { + if (FindEntry(za, zip_path, &entry) != 0) { LOG(ERROR) << name << ": no " << zip_path << " in package"; return StringValue(""); } + // Update the destination of package_extract_file if it's a block device. During simulation the + // destination will map to a fake file. + if (std::string block_device_name = state->updater->FindBlockDeviceName(dest_path); + !block_device_name.empty()) { + dest_path = block_device_name; + } + android::base::unique_fd fd(TEMP_FAILURE_RETRY( open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR))); if (fd == -1) { @@ -173,10 +165,9 @@ Value* PackageExtractFileFn(const char* name, State* state, } const std::string& zip_path = args[0]; - ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip; - ZipString zip_string_path(zip_path.c_str()); + ZipArchiveHandle za = state->updater->GetPackageHandle(); ZipEntry entry; - if (FindEntry(za, zip_string_path, &entry) != 0) { + if (FindEntry(za, zip_path, &entry) != 0) { return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name, zip_path.c_str()); } @@ -229,6 +220,11 @@ Value* PatchPartitionCheckFn(const char* name, State* state, args[1].c_str(), err.c_str()); } + if (!UpdateBlockDeviceNameForPartition(state->updater, &source) || + !UpdateBlockDeviceNameForPartition(state->updater, &target)) { + return StringValue(""); + } + bool result = PatchPartitionCheck(target, source); return StringValue(result ? "t" : ""); } @@ -270,6 +266,11 @@ Value* PatchPartitionFn(const char* name, State* state, return ErrorAbort(state, kArgsParsingFailure, "%s(): Invalid patch arg", name); } + if (!UpdateBlockDeviceNameForPartition(state->updater, &source) || + !UpdateBlockDeviceNameForPartition(state->updater, &target)) { + return StringValue(""); + } + bool result = PatchPartition(target, source, *values[0], nullptr); return StringValue(result ? "t" : ""); } @@ -313,26 +314,11 @@ Value* MountFn(const char* name, State* state, const std::vector<std::unique_ptr name); } - { - char* secontext = nullptr; - - if (sehandle) { - selabel_lookup(sehandle, &secontext, mount_point.c_str(), 0755); - setfscreatecon(secontext); - } - - mkdir(mount_point.c_str(), 0755); - - if (secontext) { - freecon(secontext); - setfscreatecon(nullptr); - } - } - - if (mount(location.c_str(), mount_point.c_str(), fs_type.c_str(), - MS_NOATIME | MS_NODEV | MS_NODIRATIME, mount_options.c_str()) < 0) { - uiPrintf(state, "%s: Failed to mount %s at %s: %s", name, location.c_str(), mount_point.c_str(), - strerror(errno)); + auto updater = state->updater; + if (updater->GetRuntime()->Mount(location, mount_point, fs_type, mount_options) != 0) { + updater->UiPrint(android::base::StringPrintf("%s: Failed to mount %s at %s: %s", name, + location.c_str(), mount_point.c_str(), + strerror(errno))); return StringValue(""); } @@ -355,9 +341,8 @@ Value* IsMountedFn(const char* name, State* state, const std::vector<std::unique "mount_point argument to unmount() can't be empty"); } - scan_mounted_volumes(); - MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str()); - if (vol == nullptr) { + auto updater_runtime = state->updater->GetRuntime(); + if (!updater_runtime->IsMounted(mount_point)) { return StringValue(""); } @@ -378,39 +363,20 @@ Value* UnmountFn(const char* name, State* state, const std::vector<std::unique_p "mount_point argument to unmount() can't be empty"); } - scan_mounted_volumes(); - MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str()); - if (vol == nullptr) { - uiPrintf(state, "Failed to unmount %s: No such volume", mount_point.c_str()); + auto updater = state->updater; + auto [mounted, result] = updater->GetRuntime()->Unmount(mount_point); + if (!mounted) { + updater->UiPrint( + android::base::StringPrintf("Failed to unmount %s: No such volume", mount_point.c_str())); return nullptr; - } else { - int ret = unmount_mounted_volume(vol); - if (ret != 0) { - uiPrintf(state, "Failed to unmount %s: %s", mount_point.c_str(), strerror(errno)); - } + } else if (result != 0) { + updater->UiPrint(android::base::StringPrintf("Failed to unmount %s: %s", mount_point.c_str(), + strerror(errno))); } return StringValue(mount_point); } -static int exec_cmd(const std::vector<std::string>& args) { - CHECK(!args.empty()); - auto argv = StringVectorToNullTerminatedArray(args); - - pid_t child; - if ((child = vfork()) == 0) { - execv(argv[0], argv.data()); - _exit(EXIT_FAILURE); - } - - int status; - waitpid(child, &status, 0); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - LOG(ERROR) << args[0] << " failed with status " << WEXITSTATUS(status); - } - return WEXITSTATUS(status); -} - // format(fs_type, partition_type, location, fs_size, mount_point) // // fs_type="ext4" partition_type="EMMC" location=device fs_size=<bytes> mount_point=<location> @@ -455,6 +421,7 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt fs_size.c_str()); } + auto updater_runtime = state->updater->GetRuntime(); if (fs_type == "ext4") { std::vector<std::string> mke2fs_args = { "/system/bin/mke2fs", "-t", "ext4", "-b", "4096", location @@ -463,12 +430,13 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt mke2fs_args.push_back(std::to_string(size / 4096LL)); } - if (auto status = exec_cmd(mke2fs_args); status != 0) { + if (auto status = updater_runtime->RunProgram(mke2fs_args, true); status != 0) { LOG(ERROR) << name << ": mke2fs failed (" << status << ") on " << location; return StringValue(""); } - if (auto status = exec_cmd({ "/system/bin/e2fsdroid", "-e", "-a", mount_point, location }); + if (auto status = updater_runtime->RunProgram( + { "/system/bin/e2fsdroid", "-e", "-a", mount_point, location }, true); status != 0) { LOG(ERROR) << name << ": e2fsdroid failed (" << status << ") on " << location; return StringValue(""); @@ -487,12 +455,13 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt if (size >= 512) { f2fs_args.push_back(std::to_string(size / 512)); } - if (auto status = exec_cmd(f2fs_args); status != 0) { + if (auto status = updater_runtime->RunProgram(f2fs_args, true); status != 0) { LOG(ERROR) << name << ": make_f2fs failed (" << status << ") on " << location; return StringValue(""); } - if (auto status = exec_cmd({ "/system/bin/sload_f2fs", "-t", mount_point, location }); + if (auto status = updater_runtime->RunProgram( + { "/system/bin/sload_f2fs", "-t", mount_point, location }, true); status != 0) { LOG(ERROR) << name << ": sload_f2fs failed (" << status << ") on " << location; return StringValue(""); @@ -531,8 +500,7 @@ Value* ShowProgressFn(const char* name, State* state, sec_str.c_str()); } - UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie); - fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec); + state->updater->WriteToCommandPipe(android::base::StringPrintf("progress %f %d", frac, sec)); return StringValue(frac_str); } @@ -555,8 +523,7 @@ Value* SetProgressFn(const char* name, State* state, frac_str.c_str()); } - UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie); - fprintf(ui->cmd_pipe, "set_progress %f\n", frac); + state->updater->WriteToCommandPipe(android::base::StringPrintf("set_progress %f", frac)); return StringValue(frac_str); } @@ -569,7 +536,9 @@ Value* GetPropFn(const char* name, State* state, const std::vector<std::unique_p if (!Evaluate(state, argv[0], &key)) { return nullptr; } - std::string value = android::base::GetProperty(key, ""); + + auto updater_runtime = state->updater->GetRuntime(); + std::string value = updater_runtime->GetProperty(key, ""); return StringValue(value); } @@ -594,7 +563,8 @@ Value* FileGetPropFn(const char* name, State* state, const std::string& key = args[1]; std::string buffer; - if (!android::base::ReadFileToString(filename, &buffer)) { + auto updater_runtime = state->updater->GetRuntime(); + if (!updater_runtime->ReadFileToString(filename, &buffer)) { ErrorAbort(state, kFreadFailure, "%s: failed to read %s", name, filename.c_str()); return nullptr; } @@ -655,7 +625,8 @@ Value* WipeCacheFn(const char* name, State* state, const std::vector<std::unique return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name, argv.size()); } - fprintf(static_cast<UpdaterInfo*>(state->cookie)->cmd_pipe, "wipe_cache\n"); + + state->updater->WriteToCommandPipe("wipe_cache"); return StringValue("t"); } @@ -669,26 +640,8 @@ Value* RunProgramFn(const char* name, State* state, const std::vector<std::uniqu return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); } - auto exec_args = StringVectorToNullTerminatedArray(args); - LOG(INFO) << "about to run program [" << exec_args[0] << "] with " << argv.size() << " args"; - - pid_t child = fork(); - if (child == 0) { - execv(exec_args[0], exec_args.data()); - PLOG(ERROR) << "run_program: execv failed"; - _exit(EXIT_FAILURE); - } - - int status; - waitpid(child, &status, 0); - if (WIFEXITED(status)) { - if (WEXITSTATUS(status) != 0) { - LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status); - } - } else if (WIFSIGNALED(status)) { - LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status); - } - + auto updater_runtime = state->updater->GetRuntime(); + auto status = updater_runtime->RunProgram(args, false); return StringValue(std::to_string(status)); } @@ -706,7 +659,8 @@ Value* ReadFileFn(const char* name, State* state, const std::vector<std::unique_ const std::string& filename = args[0]; std::string contents; - if (android::base::ReadFileToString(filename, &contents)) { + auto updater_runtime = state->updater->GetRuntime(); + if (updater_runtime->ReadFileToString(filename, &contents)) { return new Value(Value::Type::STRING, std::move(contents)); } @@ -735,12 +689,12 @@ Value* WriteValueFn(const char* name, State* state, const std::vector<std::uniqu } const std::string& value = args[0]; - if (!android::base::WriteStringToFile(value, filename)) { + auto updater_runtime = state->updater->GetRuntime(); + if (!updater_runtime->WriteStringToFile(value, filename)) { PLOG(ERROR) << name << ": Failed to write to \"" << filename << "\""; return StringValue(""); - } else { - return StringValue("t"); } + return StringValue("t"); } // Immediately reboot the device. Recovery is not finished normally, @@ -778,7 +732,7 @@ Value* RebootNowFn(const char* name, State* state, const std::vector<std::unique return StringValue(""); } - reboot("reboot," + property); + Reboot(property); sleep(5); return ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name); @@ -866,16 +820,10 @@ Value* WipeBlockDeviceFn(const char* name, State* state, const std::vector<std:: if (!android::base::ParseUint(len_str.c_str(), &len)) { return nullptr; } - android::base::unique_fd fd(open(filename.c_str(), O_WRONLY)); - if (fd == -1) { - PLOG(ERROR) << "Failed to open " << filename; - return StringValue(""); - } - // The wipe_block_device function in ext4_utils returns 0 on success and 1 - // for failure. - int status = wipe_block_device(fd, len); - return StringValue((status == 0) ? "t" : ""); + auto updater_runtime = state->updater->GetRuntime(); + int status = updater_runtime->WipeBlockDevice(filename, len); + return StringValue(status == 0 ? "t" : ""); } Value* EnableRebootFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { @@ -883,8 +831,7 @@ Value* EnableRebootFn(const char* name, State* state, const std::vector<std::uni return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name, argv.size()); } - UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie); - fprintf(ui->cmd_pipe, "enable_reboot\n"); + state->updater->WriteToCommandPipe("enable_reboot"); return StringValue("t"); } @@ -900,10 +847,8 @@ Value* Tune2FsFn(const char* name, State* state, const std::vector<std::unique_p // tune2fs expects the program name as its first arg. args.insert(args.begin(), "tune2fs"); - auto tune2fs_args = StringVectorToNullTerminatedArray(args); - - // tune2fs changes the filesystem parameters on an ext2 filesystem; it returns 0 on success. - if (auto result = tune2fs_main(tune2fs_args.size() - 1, tune2fs_args.data()); result != 0) { + auto updater_runtime = state->updater->GetRuntime(); + if (auto result = updater_runtime->Tune2Fs(args); result != 0) { return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d", name, result); } return StringValue("t"); diff --git a/updater/simulator_runtime.cpp b/updater/simulator_runtime.cpp new file mode 100644 index 000000000..d2074d69a --- /dev/null +++ b/updater/simulator_runtime.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "updater/simulator_runtime.h" + +#include <string.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <unordered_set> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/properties.h> +#include <android-base/strings.h> +#include <android-base/unique_fd.h> +#include <ext4_utils/wipe.h> +#include <selinux/label.h> + +#include "otautil/mounts.h" +#include "otautil/sysutil.h" + +std::string SimulatorRuntime::GetProperty(const std::string_view key, + const std::string_view default_value) const { + return source_->GetProperty(key, default_value); +} + +int SimulatorRuntime::Mount(const std::string_view location, const std::string_view mount_point, + const std::string_view /* fs_type */, + const std::string_view /* mount_options */) { + if (auto mounted_location = mounted_partitions_.find(mount_point); + mounted_location != mounted_partitions_.end() && mounted_location->second != location) { + LOG(ERROR) << mount_point << " has been mounted at " << mounted_location->second; + return -1; + } + + mounted_partitions_.emplace(mount_point, location); + return 0; +} + +bool SimulatorRuntime::IsMounted(const std::string_view mount_point) const { + return mounted_partitions_.find(mount_point) != mounted_partitions_.end(); +} + +std::pair<bool, int> SimulatorRuntime::Unmount(const std::string_view mount_point) { + if (!IsMounted(mount_point)) { + return { false, -1 }; + } + + mounted_partitions_.erase(std::string(mount_point)); + return { true, 0 }; +} + +std::string SimulatorRuntime::FindBlockDeviceName(const std::string_view name) const { + return source_->FindBlockDeviceName(name); +} + +// TODO(xunchang) implement the utility functions in simulator. +int SimulatorRuntime::RunProgram(const std::vector<std::string>& args, bool /* is_vfork */) const { + LOG(INFO) << "Running program with args " << android::base::Join(args, " "); + return 0; +} + +int SimulatorRuntime::Tune2Fs(const std::vector<std::string>& args) const { + LOG(INFO) << "Running Tune2Fs with args " << android::base::Join(args, " "); + return 0; +} + +int SimulatorRuntime::WipeBlockDevice(const std::string_view filename, size_t /* len */) const { + LOG(INFO) << "SKip wiping block device " << filename; + return 0; +} + +bool SimulatorRuntime::ReadFileToString(const std::string_view filename, + std::string* content) const { + if (android::base::EndsWith(filename, "oem.prop")) { + return android::base::ReadFileToString(source_->GetOemSettings(), content); + } + + LOG(INFO) << "SKip reading filename " << filename; + return true; +} + +bool SimulatorRuntime::WriteStringToFile(const std::string_view content, + const std::string_view filename) const { + LOG(INFO) << "SKip writing " << content.size() << " bytes to file " << filename; + return true; +} + +bool SimulatorRuntime::MapPartitionOnDeviceMapper(const std::string& partition_name, + std::string* path) { + *path = partition_name; + return true; +} + +bool SimulatorRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) { + LOG(INFO) << "Skip unmapping " << partition_name; + return true; +} + +bool SimulatorRuntime::UpdateDynamicPartitions(const std::string_view op_list_value) { + const std::unordered_set<std::string> commands{ + "resize", "remove", "add", "move", + "add_group", "resize_group", "remove_group", "remove_all_groups", + }; + + std::vector<std::string> lines = android::base::Split(std::string(op_list_value), "\n"); + for (const auto& line : lines) { + if (line.empty() || line[0] == '#') continue; + auto tokens = android::base::Split(line, " "); + if (commands.find(tokens[0]) == commands.end()) { + LOG(ERROR) << "Unknown operation in op_list: " << line; + return false; + } + } + return true; +} diff --git a/updater/target_files.cpp b/updater/target_files.cpp new file mode 100644 index 000000000..919ec4e04 --- /dev/null +++ b/updater/target_files.cpp @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "updater/target_files.h" + +#include <unistd.h> + +#include <algorithm> +#include <filesystem> +#include <memory> + +#include <android-base/logging.h> +#include <android-base/strings.h> +#include <sparse/sparse.h> + +static bool SimgToImg(int input_fd, int output_fd) { + if (lseek64(input_fd, 0, SEEK_SET) == -1) { + PLOG(ERROR) << "Failed to lseek64 on the input sparse image"; + return false; + } + + if (lseek64(output_fd, 0, SEEK_SET) == -1) { + PLOG(ERROR) << "Failed to lseek64 on the output raw image"; + return false; + } + + std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)> s_file( + sparse_file_import(input_fd, true, false), sparse_file_destroy); + if (!s_file) { + LOG(ERROR) << "Failed to import the sparse image."; + return false; + } + + if (sparse_file_write(s_file.get(), output_fd, false, false, false) < 0) { + PLOG(ERROR) << "Failed to output the raw image file."; + return false; + } + + return true; +} + +static bool ParsePropertyFile(const std::string_view prop_content, + std::map<std::string, std::string, std::less<>>* props_map) { + LOG(INFO) << "Start parsing build property\n"; + std::vector<std::string> lines = android::base::Split(std::string(prop_content), "\n"); + for (const auto& line : lines) { + if (line.empty() || line[0] == '#') continue; + auto pos = line.find('='); + if (pos == std::string::npos) continue; + std::string key = line.substr(0, pos); + std::string value = line.substr(pos + 1); + LOG(INFO) << key << ": " << value; + props_map->emplace(key, value); + } + + return true; +} + +static bool ParseFstab(const std::string_view fstab, std::vector<FstabInfo>* fstab_info_list) { + LOG(INFO) << "parsing fstab\n"; + std::vector<std::string> lines = android::base::Split(std::string(fstab), "\n"); + for (const auto& line : lines) { + if (line.empty() || line[0] == '#') continue; + + // <block_device> <mount_point> <fs_type> <mount_flags> optional:<fs_mgr_flags> + std::vector<std::string> tokens = android::base::Split(line, " "); + tokens.erase(std::remove(tokens.begin(), tokens.end(), ""), tokens.end()); + if (tokens.size() != 4 && tokens.size() != 5) { + LOG(ERROR) << "Unexpected token size: " << tokens.size() << std::endl + << "Error parsing fstab line: " << line; + return false; + } + + const auto& blockdev = tokens[0]; + const auto& mount_point = tokens[1]; + const auto& fs_type = tokens[2]; + if (!android::base::StartsWith(mount_point, "/")) { + LOG(WARNING) << "mount point '" << mount_point << "' does not start with '/'"; + continue; + } + + // The simulator only supports ext4 and emmc for now. + if (fs_type != "ext4" && fs_type != "emmc") { + LOG(WARNING) << "Unsupported fs_type in " << line; + continue; + } + + fstab_info_list->emplace_back(blockdev, mount_point, fs_type); + } + + return true; +} + +bool TargetFile::EntryExists(const std::string_view name) const { + if (extracted_input_) { + std::string entry_path = path_ + "/" + std::string(name); + if (access(entry_path.c_str(), O_RDONLY) != 0) { + PLOG(WARNING) << "Failed to access " << entry_path; + return false; + } + return true; + } + + CHECK(handle_); + ZipEntry img_entry; + return FindEntry(handle_, name, &img_entry) == 0; +} + +bool TargetFile::ReadEntryToString(const std::string_view name, std::string* content) const { + if (extracted_input_) { + std::string entry_path = path_ + "/" + std::string(name); + return android::base::ReadFileToString(entry_path, content); + } + + CHECK(handle_); + ZipEntry entry; + if (auto find_err = FindEntry(handle_, name, &entry); find_err != 0) { + LOG(ERROR) << "failed to find " << name << " in the package: " << ErrorCodeString(find_err); + return false; + } + + if (entry.uncompressed_length == 0) { + content->clear(); + return true; + } + + content->resize(entry.uncompressed_length); + if (auto extract_err = ExtractToMemory( + handle_, &entry, reinterpret_cast<uint8_t*>(&content->at(0)), entry.uncompressed_length); + extract_err != 0) { + LOG(ERROR) << "failed to read " << name << " from package: " << ErrorCodeString(extract_err); + return false; + } + + return true; +} + +bool TargetFile::ExtractEntryToTempFile(const std::string_view name, + TemporaryFile* temp_file) const { + if (extracted_input_) { + std::string entry_path = path_ + "/" + std::string(name); + return std::filesystem::copy_file(entry_path, temp_file->path, + std::filesystem::copy_options::overwrite_existing); + } + + CHECK(handle_); + ZipEntry entry; + if (auto find_err = FindEntry(handle_, name, &entry); find_err != 0) { + LOG(ERROR) << "failed to find " << name << " in the package: " << ErrorCodeString(find_err); + return false; + } + + if (auto status = ExtractEntryToFile(handle_, &entry, temp_file->fd); status != 0) { + LOG(ERROR) << "Failed to extract zip entry " << name << " : " << ErrorCodeString(status); + return false; + } + return true; +} + +bool TargetFile::Open() { + if (!extracted_input_) { + if (auto ret = OpenArchive(path_.c_str(), &handle_); ret != 0) { + LOG(ERROR) << "failed to open source target file " << path_ << ": " << ErrorCodeString(ret); + return false; + } + } + + // Parse the misc info. + std::string misc_info_content; + if (!ReadEntryToString("META/misc_info.txt", &misc_info_content)) { + return false; + } + if (!ParsePropertyFile(misc_info_content, &misc_info_)) { + return false; + } + + return true; +} + +bool TargetFile::GetBuildProps(std::map<std::string, std::string, std::less<>>* props_map) const { + props_map->clear(); + // Parse the source zip to mock the system props and block devices. We try all the possible + // locations for build props. + constexpr std::string_view kPropLocations[] = { + "SYSTEM/build.prop", + "VENDOR/build.prop", + "PRODUCT/build.prop", + "SYSTEM_EXT/build.prop", + "SYSTEM/vendor/build.prop", + "SYSTEM/product/build.prop", + "SYSTEM/system_ext/build.prop", + "ODM/build.prop", // legacy + "ODM/etc/build.prop", + "VENDOR/odm/build.prop", // legacy + "VENDOR/odm/etc/build.prop", + }; + for (const auto& name : kPropLocations) { + std::string build_prop_content; + if (!ReadEntryToString(name, &build_prop_content)) { + continue; + } + std::map<std::string, std::string, std::less<>> props; + if (!ParsePropertyFile(build_prop_content, &props)) { + LOG(ERROR) << "Failed to parse build prop in " << name; + return false; + } + for (const auto& [key, value] : props) { + if (auto it = props_map->find(key); it != props_map->end() && it->second != value) { + LOG(WARNING) << "Property " << key << " has different values in property files, we got " + << it->second << " and " << value; + } + props_map->emplace(key, value); + } + } + + return true; +} + +bool TargetFile::ExtractImage(const std::string_view entry_name, const FstabInfo& fstab_info, + const std::string_view work_dir, TemporaryFile* image_file) const { + if (!EntryExists(entry_name)) { + return false; + } + + // We don't need extra work for 'emmc'; use the image file as the block device. + if (fstab_info.fs_type == "emmc" || misc_info_.find("extfs_sparse_flag") == misc_info_.end()) { + if (!ExtractEntryToTempFile(entry_name, image_file)) { + return false; + } + } else { // treated as ext4 sparse image + TemporaryFile sparse_image{ std::string(work_dir) }; + if (!ExtractEntryToTempFile(entry_name, &sparse_image)) { + return false; + } + + // Convert the sparse image to raw. + if (!SimgToImg(sparse_image.fd, image_file->fd)) { + LOG(ERROR) << "Failed to convert " << fstab_info.mount_point << " to raw."; + return false; + } + } + + return true; +} + +bool TargetFile::ParseFstabInfo(std::vector<FstabInfo>* fstab_info_list) const { + // Parse the fstab file and extract the image files. The location of the fstab actually depends + // on some flags e.g. "no_recovery", "recovery_as_boot". Here we just try all possibilities. + constexpr std::string_view kRecoveryFstabLocations[] = { + "RECOVERY/RAMDISK/system/etc/recovery.fstab", + "RECOVERY/RAMDISK/etc/recovery.fstab", + "BOOT/RAMDISK/system/etc/recovery.fstab", + "BOOT/RAMDISK/etc/recovery.fstab", + }; + std::string fstab_content; + for (const auto& name : kRecoveryFstabLocations) { + if (std::string content; ReadEntryToString(name, &content)) { + fstab_content = std::move(content); + break; + } + } + if (fstab_content.empty()) { + LOG(ERROR) << "Failed to parse the recovery fstab file"; + return false; + } + + // Extract the images and convert them to raw. + if (!ParseFstab(fstab_content, fstab_info_list)) { + LOG(ERROR) << "Failed to mount the block devices for source build."; + return false; + } + + return true; +} diff --git a/updater/update_simulator_main.cpp b/updater/update_simulator_main.cpp new file mode 100644 index 000000000..6c6989bac --- /dev/null +++ b/updater/update_simulator_main.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <getopt.h> +#include <stdlib.h> +#include <unistd.h> + +#include <string> +#include <string_view> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/strings.h> + +#include "edify/expr.h" +#include "otautil/error_code.h" +#include "otautil/paths.h" +#include "updater/blockimg.h" +#include "updater/build_info.h" +#include "updater/dynamic_partitions.h" +#include "updater/install.h" +#include "updater/simulator_runtime.h" +#include "updater/updater.h" + +using namespace std::string_literals; + +void Usage(std::string_view name) { + LOG(INFO) << "Usage: " << name << "[--oem_settings <oem_property_file>]" + << "[--skip_functions <skip_function_file>]" + << " --source <source_target_file>" + << " --ota_package <ota_package>"; +} + +Value* SimulatorPlaceHolderFn(const char* name, State* /* state */, + const std::vector<std::unique_ptr<Expr>>& /* argv */) { + LOG(INFO) << "Skip function " << name << " in host simulation"; + return StringValue("t"); +} + +int main(int argc, char** argv) { + // Write the logs to stdout. + android::base::InitLogging(argv, &android::base::StderrLogger); + + std::string oem_settings; + std::string skip_function_file; + std::string source_target_file; + std::string package_name; + std::string work_dir; + bool keep_images = false; + + constexpr struct option OPTIONS[] = { + { "keep_images", no_argument, nullptr, 0 }, + { "oem_settings", required_argument, nullptr, 0 }, + { "ota_package", required_argument, nullptr, 0 }, + { "skip_functions", required_argument, nullptr, 0 }, + { "source", required_argument, nullptr, 0 }, + { "work_dir", required_argument, nullptr, 0 }, + { nullptr, 0, nullptr, 0 }, + }; + + int arg; + int option_index; + while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) { + if (arg != 0) { + LOG(ERROR) << "Invalid command argument"; + Usage(argv[0]); + return EXIT_FAILURE; + } + auto option_name = OPTIONS[option_index].name; + // The same oem property file used during OTA generation. It's needed for file_getprop() to + // return the correct value for the source build. + if (option_name == "oem_settings"s) { + oem_settings = optarg; + } else if (option_name == "skip_functions"s) { + skip_function_file = optarg; + } else if (option_name == "source"s) { + source_target_file = optarg; + } else if (option_name == "ota_package"s) { + package_name = optarg; + } else if (option_name == "keep_images"s) { + keep_images = true; + } else if (option_name == "work_dir"s) { + work_dir = optarg; + } else { + Usage(argv[0]); + return EXIT_FAILURE; + } + } + + if (source_target_file.empty() || package_name.empty()) { + Usage(argv[0]); + return EXIT_FAILURE; + } + + // Configure edify's functions. + RegisterBuiltins(); + RegisterInstallFunctions(); + RegisterBlockImageFunctions(); + RegisterDynamicPartitionsFunctions(); + + if (!skip_function_file.empty()) { + std::string content; + if (!android::base::ReadFileToString(skip_function_file, &content)) { + PLOG(ERROR) << "Failed to read " << skip_function_file; + return EXIT_FAILURE; + } + + auto lines = android::base::Split(content, "\n"); + for (const auto& line : lines) { + if (line.empty() || android::base::StartsWith(line, "#")) { + continue; + } + RegisterFunction(line, SimulatorPlaceHolderFn); + } + } + + TemporaryFile temp_saved_source; + TemporaryFile temp_last_command; + TemporaryDir temp_stash_base; + + Paths::Get().set_cache_temp_source(temp_saved_source.path); + Paths::Get().set_last_command_file(temp_last_command.path); + Paths::Get().set_stash_directory_base(temp_stash_base.path); + + TemporaryFile cmd_pipe; + TemporaryDir source_temp_dir; + if (work_dir.empty()) { + work_dir = source_temp_dir.path; + } + + BuildInfo source_build_info(work_dir, keep_images); + if (!source_build_info.ParseTargetFile(source_target_file, false)) { + LOG(ERROR) << "Failed to parse the target file " << source_target_file; + return EXIT_FAILURE; + } + + if (!oem_settings.empty()) { + CHECK_EQ(0, access(oem_settings.c_str(), R_OK)); + source_build_info.SetOemSettings(oem_settings); + } + + Updater updater(std::make_unique<SimulatorRuntime>(&source_build_info)); + if (!updater.Init(cmd_pipe.release(), package_name, false)) { + return EXIT_FAILURE; + } + + if (!updater.RunUpdate()) { + return EXIT_FAILURE; + } + + LOG(INFO) << "\nscript succeeded, result: " << updater.GetResult(); + + return 0; +} diff --git a/updater/updater.cpp b/updater/updater.cpp index 7b5a3f938..8f4a6ede5 100644 --- a/updater/updater.cpp +++ b/updater/updater.cpp @@ -16,8 +16,6 @@ #include "updater/updater.h" -#include <stdio.h> -#include <stdlib.h> #include <string.h> #include <unistd.h> @@ -25,198 +23,162 @@ #include <android-base/logging.h> #include <android-base/strings.h> -#include <selinux/android.h> -#include <selinux/label.h> -#include <selinux/selinux.h> -#include <ziparchive/zip_archive.h> - -#include "edify/expr.h" -#include "otautil/dirutil.h" -#include "otautil/error_code.h" -#include "otautil/sysutil.h" -#include "updater/blockimg.h" -#include "updater/dynamic_partitions.h" -#include "updater/install.h" - -// Generated by the makefile, this function defines the -// RegisterDeviceExtensions() function, which calls all the -// registration functions for device-specific extensions. -#include "register.inc" - -// Where in the package we expect to find the edify script to execute. -// (Note it's "updateR-script", not the older "update-script".) -static constexpr const char* SCRIPT_NAME = "META-INF/com/google/android/updater-script"; - -struct selabel_handle *sehandle; - -static void UpdaterLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */, - const char* /* tag */, const char* /* file */, unsigned int /* line */, - const char* message) { - fprintf(stdout, "%s\n", message); -} -int main(int argc, char** argv) { - // Various things log information to stdout or stderr more or less - // at random (though we've tried to standardize on stdout). The - // log file makes more sense if buffering is turned off so things - // appear in the right order. - setbuf(stdout, nullptr); - setbuf(stderr, nullptr); - - // We don't have logcat yet under recovery. Update logs will always be written to stdout - // (which is redirected to recovery.log). - android::base::InitLogging(argv, &UpdaterLogger); - - if (argc != 4 && argc != 5) { - LOG(ERROR) << "unexpected number of arguments: " << argc; - return 1; - } +#include "edify/updater_runtime_interface.h" - char* version = argv[1]; - if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') { - // We support version 1, 2, or 3. - LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1]; - return 2; +Updater::~Updater() { + if (package_handle_) { + CloseArchive(package_handle_); } +} +bool Updater::Init(int fd, const std::string_view package_filename, bool is_retry) { // Set up the pipe for sending commands back to the parent process. + cmd_pipe_.reset(fdopen(fd, "wb")); + if (!cmd_pipe_) { + LOG(ERROR) << "Failed to open the command pipe"; + return false; + } - int fd = atoi(argv[2]); - FILE* cmd_pipe = fdopen(fd, "wb"); - setlinebuf(cmd_pipe); - - // Extract the script from the package. + setlinebuf(cmd_pipe_.get()); - const char* package_filename = argv[3]; - MemMapping map; - if (!map.MapFile(package_filename)) { - LOG(ERROR) << "failed to map package " << argv[3]; - return 3; + if (!mapped_package_.MapFile(std::string(package_filename))) { + LOG(ERROR) << "failed to map package " << package_filename; + return false; } - ZipArchiveHandle za; - int open_err = OpenArchiveFromMemory(map.addr, map.length, argv[3], &za); - if (open_err != 0) { - LOG(ERROR) << "failed to open package " << argv[3] << ": " << ErrorCodeString(open_err); - CloseArchive(za); - return 3; + if (int open_err = OpenArchiveFromMemory(mapped_package_.addr, mapped_package_.length, + std::string(package_filename).c_str(), &package_handle_); + open_err != 0) { + LOG(ERROR) << "failed to open package " << package_filename << ": " + << ErrorCodeString(open_err); + return false; } - - ZipString script_name(SCRIPT_NAME); - ZipEntry script_entry; - int find_err = FindEntry(za, script_name, &script_entry); - if (find_err != 0) { - LOG(ERROR) << "failed to find " << SCRIPT_NAME << " in " << package_filename << ": " - << ErrorCodeString(find_err); - CloseArchive(za); - return 4; + if (!ReadEntryToString(package_handle_, SCRIPT_NAME, &updater_script_)) { + return false; } - std::string script; - script.resize(script_entry.uncompressed_length); - int extract_err = ExtractToMemory(za, &script_entry, reinterpret_cast<uint8_t*>(&script[0]), - script_entry.uncompressed_length); - if (extract_err != 0) { - LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err); - CloseArchive(za); - return 5; - } + is_retry_ = is_retry; - // Configure edify's functions. + return true; +} - RegisterBuiltins(); - RegisterInstallFunctions(); - RegisterBlockImageFunctions(); - RegisterDynamicPartitionsFunctions(); - RegisterDeviceExtensions(); +bool Updater::RunUpdate() { + CHECK(runtime_); // Parse the script. - std::unique_ptr<Expr> root; int error_count = 0; - int error = ParseString(script, &root, &error_count); + int error = ParseString(updater_script_, &root, &error_count); if (error != 0 || error_count > 0) { LOG(ERROR) << error_count << " parse errors"; - CloseArchive(za); - return 6; - } - - sehandle = selinux_android_file_context_handle(); - selinux_android_set_sehandle(sehandle); - - if (!sehandle) { - fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n"); + return false; } // Evaluate the parsed script. + State state(updater_script_, this); + state.is_retry = is_retry_; + + bool status = Evaluate(&state, root, &result_); + if (status) { + fprintf(cmd_pipe_.get(), "ui_print script succeeded: result was [%s]\n", result_.c_str()); + // Even though the script doesn't abort, still log the cause code if result is empty. + if (result_.empty() && state.cause_code != kNoCause) { + fprintf(cmd_pipe_.get(), "log cause: %d\n", state.cause_code); + } + for (const auto& func : skipped_functions_) { + LOG(WARNING) << "Skipped executing function " << func; + } + return true; + } - UpdaterInfo updater_info; - updater_info.cmd_pipe = cmd_pipe; - updater_info.package_zip = za; - updater_info.version = atoi(version); - updater_info.package_zip_addr = map.addr; - updater_info.package_zip_len = map.length; + ParseAndReportErrorCode(&state); + return false; +} - State state(script, &updater_info); +void Updater::WriteToCommandPipe(const std::string_view message, bool flush) const { + fprintf(cmd_pipe_.get(), "%s\n", std::string(message).c_str()); + if (flush) { + fflush(cmd_pipe_.get()); + } +} - if (argc == 5) { - if (strcmp(argv[4], "retry") == 0) { - state.is_retry = true; - } else { - printf("unexpected argument: %s", argv[4]); +void Updater::UiPrint(const std::string_view message) const { + // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "". + // so skip sending empty strings to ui. + std::vector<std::string> lines = android::base::Split(std::string(message), "\n"); + for (const auto& line : lines) { + if (!line.empty()) { + fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str()); } } - std::string result; - bool status = Evaluate(&state, root, &result); - - if (!status) { - if (state.errmsg.empty()) { - LOG(ERROR) << "script aborted (no error message)"; - fprintf(cmd_pipe, "ui_print script aborted (no error message)\n"); - } else { - LOG(ERROR) << "script aborted: " << state.errmsg; - const std::vector<std::string> lines = android::base::Split(state.errmsg, "\n"); - for (const std::string& line : lines) { - // Parse the error code in abort message. - // Example: "E30: This package is for bullhead devices." - if (!line.empty() && line[0] == 'E') { - if (sscanf(line.c_str(), "E%d: ", &state.error_code) != 1) { - LOG(ERROR) << "Failed to parse error code: [" << line << "]"; - } + // on the updater side, we need to dump the contents to stderr (which has + // been redirected to the log file). because the recovery will only print + // the contents to screen when processing pipe command ui_print. + LOG(INFO) << message; +} + +std::string Updater::FindBlockDeviceName(const std::string_view name) const { + return runtime_->FindBlockDeviceName(name); +} + +void Updater::ParseAndReportErrorCode(State* state) { + CHECK(state); + if (state->errmsg.empty()) { + LOG(ERROR) << "script aborted (no error message)"; + fprintf(cmd_pipe_.get(), "ui_print script aborted (no error message)\n"); + } else { + LOG(ERROR) << "script aborted: " << state->errmsg; + const std::vector<std::string> lines = android::base::Split(state->errmsg, "\n"); + for (const std::string& line : lines) { + // Parse the error code in abort message. + // Example: "E30: This package is for bullhead devices." + if (!line.empty() && line[0] == 'E') { + if (sscanf(line.c_str(), "E%d: ", &state->error_code) != 1) { + LOG(ERROR) << "Failed to parse error code: [" << line << "]"; } - fprintf(cmd_pipe, "ui_print %s\n", line.c_str()); } + fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str()); } + } - // Installation has been aborted. Set the error code to kScriptExecutionFailure unless - // a more specific code has been set in errmsg. - if (state.error_code == kNoError) { - state.error_code = kScriptExecutionFailure; - } - fprintf(cmd_pipe, "log error: %d\n", state.error_code); - // Cause code should provide additional information about the abort. - if (state.cause_code != kNoCause) { - fprintf(cmd_pipe, "log cause: %d\n", state.cause_code); - if (state.cause_code == kPatchApplicationFailure) { - LOG(INFO) << "Patch application failed, retry update."; - fprintf(cmd_pipe, "retry_update\n"); - } else if (state.cause_code == kEioFailure) { - LOG(INFO) << "Update failed due to EIO, retry update."; - fprintf(cmd_pipe, "retry_update\n"); - } + // Installation has been aborted. Set the error code to kScriptExecutionFailure unless + // a more specific code has been set in errmsg. + if (state->error_code == kNoError) { + state->error_code = kScriptExecutionFailure; + } + fprintf(cmd_pipe_.get(), "log error: %d\n", state->error_code); + // Cause code should provide additional information about the abort. + if (state->cause_code != kNoCause) { + fprintf(cmd_pipe_.get(), "log cause: %d\n", state->cause_code); + if (state->cause_code == kPatchApplicationFailure) { + LOG(INFO) << "Patch application failed, retry update."; + fprintf(cmd_pipe_.get(), "retry_update\n"); + } else if (state->cause_code == kEioFailure) { + LOG(INFO) << "Update failed due to EIO, retry update."; + fprintf(cmd_pipe_.get(), "retry_update\n"); } + } +} - if (updater_info.package_zip) { - CloseArchive(updater_info.package_zip); - } - return 7; - } else { - fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result.c_str()); +bool Updater::ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name, + std::string* content) { + ZipEntry entry; + int find_err = FindEntry(za, entry_name, &entry); + if (find_err != 0) { + LOG(ERROR) << "failed to find " << entry_name + << " in the package: " << ErrorCodeString(find_err); + return false; } - if (updater_info.package_zip) { - CloseArchive(updater_info.package_zip); + content->resize(entry.uncompressed_length); + int extract_err = ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&content->at(0)), + entry.uncompressed_length); + if (extract_err != 0) { + LOG(ERROR) << "failed to read " << entry_name + << " from package: " << ErrorCodeString(extract_err); + return false; } - return 0; + return true; } diff --git a/updater/updater_main.cpp b/updater/updater_main.cpp new file mode 100644 index 000000000..055a8ac76 --- /dev/null +++ b/updater/updater_main.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <string> + +#include <android-base/logging.h> +#include <android-base/parseint.h> +#include <selinux/android.h> +#include <selinux/label.h> +#include <selinux/selinux.h> + +#include "edify/expr.h" +#include "updater/blockimg.h" +#include "updater/dynamic_partitions.h" +#include "updater/install.h" +#include "updater/updater.h" +#include "updater/updater_runtime.h" + +// Generated by the makefile, this function defines the +// RegisterDeviceExtensions() function, which calls all the +// registration functions for device-specific extensions. +#include "register.inc" + +static void UpdaterLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */, + const char* /* tag */, const char* /* file */, unsigned int /* line */, + const char* message) { + fprintf(stdout, "%s\n", message); +} + +int main(int argc, char** argv) { + // Various things log information to stdout or stderr more or less + // at random (though we've tried to standardize on stdout). The + // log file makes more sense if buffering is turned off so things + // appear in the right order. + setbuf(stdout, nullptr); + setbuf(stderr, nullptr); + + // We don't have logcat yet under recovery. Update logs will always be written to stdout + // (which is redirected to recovery.log). + android::base::InitLogging(argv, &UpdaterLogger); + + if (argc != 4 && argc != 5) { + LOG(ERROR) << "unexpected number of arguments: " << argc; + return 1; + } + + char* version = argv[1]; + if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') { + // We support version 1, 2, or 3. + LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1]; + return 1; + } + + int fd; + if (!android::base::ParseInt(argv[2], &fd)) { + LOG(ERROR) << "Failed to parse fd in " << argv[2]; + return 1; + } + + std::string package_name = argv[3]; + + bool is_retry = false; + if (argc == 5) { + if (strcmp(argv[4], "retry") == 0) { + is_retry = true; + } else { + LOG(ERROR) << "unexpected argument: " << argv[4]; + return 1; + } + } + + // Configure edify's functions. + RegisterBuiltins(); + RegisterInstallFunctions(); + RegisterBlockImageFunctions(); + RegisterDynamicPartitionsFunctions(); + RegisterDeviceExtensions(); + + auto sehandle = selinux_android_file_context_handle(); + selinux_android_set_sehandle(sehandle); + + Updater updater(std::make_unique<UpdaterRuntime>(sehandle)); + if (!updater.Init(fd, package_name, is_retry)) { + return 1; + } + + if (!updater.RunUpdate()) { + return 1; + } + + return 0; +}
\ No newline at end of file diff --git a/updater/updater_runtime.cpp b/updater/updater_runtime.cpp new file mode 100644 index 000000000..761f99975 --- /dev/null +++ b/updater/updater_runtime.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "updater/updater_runtime.h" + +#include <string.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/properties.h> +#include <android-base/strings.h> +#include <android-base/unique_fd.h> +#include <ext4_utils/wipe.h> +#include <selinux/label.h> +#include <tune2fs.h> + +#include "otautil/mounts.h" +#include "otautil/sysutil.h" + +std::string UpdaterRuntime::GetProperty(const std::string_view key, + const std::string_view default_value) const { + return android::base::GetProperty(std::string(key), std::string(default_value)); +} + +std::string UpdaterRuntime::FindBlockDeviceName(const std::string_view name) const { + return std::string(name); +} + +int UpdaterRuntime::Mount(const std::string_view location, const std::string_view mount_point, + const std::string_view fs_type, const std::string_view mount_options) { + std::string mount_point_string(mount_point); + char* secontext = nullptr; + if (sehandle_) { + selabel_lookup(sehandle_, &secontext, mount_point_string.c_str(), 0755); + setfscreatecon(secontext); + } + + mkdir(mount_point_string.c_str(), 0755); + + if (secontext) { + freecon(secontext); + setfscreatecon(nullptr); + } + + return mount(std::string(location).c_str(), mount_point_string.c_str(), + std::string(fs_type).c_str(), MS_NOATIME | MS_NODEV | MS_NODIRATIME, + std::string(mount_options).c_str()); +} + +bool UpdaterRuntime::IsMounted(const std::string_view mount_point) const { + scan_mounted_volumes(); + MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str()); + return vol != nullptr; +} + +std::pair<bool, int> UpdaterRuntime::Unmount(const std::string_view mount_point) { + scan_mounted_volumes(); + MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str()); + if (vol == nullptr) { + return { false, -1 }; + } + + int ret = unmount_mounted_volume(vol); + return { true, ret }; +} + +bool UpdaterRuntime::ReadFileToString(const std::string_view filename, std::string* content) const { + return android::base::ReadFileToString(std::string(filename), content); +} + +bool UpdaterRuntime::WriteStringToFile(const std::string_view content, + const std::string_view filename) const { + return android::base::WriteStringToFile(std::string(content), std::string(filename)); +} + +int UpdaterRuntime::WipeBlockDevice(const std::string_view filename, size_t len) const { + android::base::unique_fd fd(open(std::string(filename).c_str(), O_WRONLY)); + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << filename; + return false; + } + // The wipe_block_device function in ext4_utils returns 0 on success and 1 for failure. + return wipe_block_device(fd, len); +} + +int UpdaterRuntime::RunProgram(const std::vector<std::string>& args, bool is_vfork) const { + CHECK(!args.empty()); + auto argv = StringVectorToNullTerminatedArray(args); + LOG(INFO) << "about to run program [" << args[0] << "] with " << argv.size() << " args"; + + pid_t child = is_vfork ? vfork() : fork(); + if (child == 0) { + execv(argv[0], argv.data()); + PLOG(ERROR) << "run_program: execv failed"; + _exit(EXIT_FAILURE); + } + + int status; + waitpid(child, &status, 0); + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status); + } + } else if (WIFSIGNALED(status)) { + LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status); + } + + return status; +} + +int UpdaterRuntime::Tune2Fs(const std::vector<std::string>& args) const { + auto tune2fs_args = StringVectorToNullTerminatedArray(args); + // tune2fs changes the filesystem parameters on an ext2 filesystem; it returns 0 on success. + return tune2fs_main(tune2fs_args.size() - 1, tune2fs_args.data()); +} diff --git a/updater/updater_runtime_dynamic_partitions.cpp b/updater/updater_runtime_dynamic_partitions.cpp new file mode 100644 index 000000000..be9250a81 --- /dev/null +++ b/updater/updater_runtime_dynamic_partitions.cpp @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "updater/updater_runtime.h" + +#include <algorithm> +#include <chrono> +#include <iterator> +#include <optional> +#include <string> +#include <type_traits> +#include <vector> + +#include <android-base/logging.h> +#include <android-base/parseint.h> +#include <android-base/strings.h> +#include <fs_mgr.h> +#include <fs_mgr_dm_linear.h> +#include <libdm/dm.h> +#include <liblp/builder.h> + +using android::dm::DeviceMapper; +using android::dm::DmDeviceState; +using android::fs_mgr::CreateLogicalPartition; +using android::fs_mgr::CreateLogicalPartitionParams; +using android::fs_mgr::DestroyLogicalPartition; +using android::fs_mgr::LpMetadata; +using android::fs_mgr::MetadataBuilder; +using android::fs_mgr::Partition; +using android::fs_mgr::PartitionOpener; + +static constexpr std::chrono::milliseconds kMapTimeout{ 1000 }; + +static std::string GetSuperDevice() { + return "/dev/block/by-name/" + fs_mgr_get_super_partition_name(); +} + +static bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) { + auto state = DeviceMapper::Instance().GetState(partition_name); + if (state == DmDeviceState::INVALID) { + return true; + } + if (state == DmDeviceState::ACTIVE) { + return DestroyLogicalPartition(partition_name); + } + LOG(ERROR) << "Unknown device mapper state: " + << static_cast<std::underlying_type_t<DmDeviceState>>(state); + return false; +} + +bool UpdaterRuntime::MapPartitionOnDeviceMapper(const std::string& partition_name, + std::string* path) { + auto state = DeviceMapper::Instance().GetState(partition_name); + if (state == DmDeviceState::INVALID) { + CreateLogicalPartitionParams params = { + .block_device = GetSuperDevice(), + .metadata_slot = 0, + .partition_name = partition_name, + .force_writable = true, + .timeout_ms = kMapTimeout, + }; + return CreateLogicalPartition(params, path); + } + + if (state == DmDeviceState::ACTIVE) { + return DeviceMapper::Instance().GetDmDevicePathByName(partition_name, path); + } + LOG(ERROR) << "Unknown device mapper state: " + << static_cast<std::underlying_type_t<DmDeviceState>>(state); + return false; +} + +bool UpdaterRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) { + return ::UnmapPartitionOnDeviceMapper(partition_name); +} + +namespace { // Ops + +struct OpParameters { + std::vector<std::string> tokens; + MetadataBuilder* builder; + + bool ExpectArgSize(size_t size) const { + CHECK(!tokens.empty()); + auto actual = tokens.size() - 1; + if (actual != size) { + LOG(ERROR) << "Op " << op() << " expects " << size << " args, got " << actual; + return false; + } + return true; + } + const std::string& op() const { + CHECK(!tokens.empty()); + return tokens[0]; + } + const std::string& arg(size_t pos) const { + CHECK_LE(pos + 1, tokens.size()); + return tokens[pos + 1]; + } + std::optional<uint64_t> uint_arg(size_t pos, const std::string& name) const { + auto str = arg(pos); + uint64_t ret; + if (!android::base::ParseUint(str, &ret)) { + LOG(ERROR) << "Op " << op() << " expects uint64 for argument " << name << ", got " << str; + return std::nullopt; + } + return ret; + } +}; + +using OpFunction = std::function<bool(const OpParameters&)>; +using OpMap = std::map<std::string, OpFunction>; + +bool PerformOpResize(const OpParameters& params) { + if (!params.ExpectArgSize(2)) return false; + const auto& partition_name = params.arg(0); + auto size = params.uint_arg(1, "size"); + if (!size.has_value()) return false; + + auto partition = params.builder->FindPartition(partition_name); + if (partition == nullptr) { + LOG(ERROR) << "Failed to find partition " << partition_name + << " in dynamic partition metadata."; + return false; + } + if (!UnmapPartitionOnDeviceMapper(partition_name)) { + LOG(ERROR) << "Cannot unmap " << partition_name << " before resizing."; + return false; + } + if (!params.builder->ResizePartition(partition, size.value())) { + LOG(ERROR) << "Failed to resize partition " << partition_name << " to size " << *size << "."; + return false; + } + return true; +} + +bool PerformOpRemove(const OpParameters& params) { + if (!params.ExpectArgSize(1)) return false; + const auto& partition_name = params.arg(0); + + if (!UnmapPartitionOnDeviceMapper(partition_name)) { + LOG(ERROR) << "Cannot unmap " << partition_name << " before removing."; + return false; + } + params.builder->RemovePartition(partition_name); + return true; +} + +bool PerformOpAdd(const OpParameters& params) { + if (!params.ExpectArgSize(2)) return false; + const auto& partition_name = params.arg(0); + const auto& group_name = params.arg(1); + + if (params.builder->AddPartition(partition_name, group_name, LP_PARTITION_ATTR_READONLY) == + nullptr) { + LOG(ERROR) << "Failed to add partition " << partition_name << " to group " << group_name << "."; + return false; + } + return true; +} + +bool PerformOpMove(const OpParameters& params) { + if (!params.ExpectArgSize(2)) return false; + const auto& partition_name = params.arg(0); + const auto& new_group = params.arg(1); + + auto partition = params.builder->FindPartition(partition_name); + if (partition == nullptr) { + LOG(ERROR) << "Cannot move partition " << partition_name << " to group " << new_group + << " because it is not found."; + return false; + } + + auto old_group = partition->group_name(); + if (old_group != new_group) { + if (!params.builder->ChangePartitionGroup(partition, new_group)) { + LOG(ERROR) << "Cannot move partition " << partition_name << " from group " << old_group + << " to group " << new_group << "."; + return false; + } + } + return true; +} + +bool PerformOpAddGroup(const OpParameters& params) { + if (!params.ExpectArgSize(2)) return false; + const auto& group_name = params.arg(0); + auto maximum_size = params.uint_arg(1, "maximum_size"); + if (!maximum_size.has_value()) return false; + + auto group = params.builder->FindGroup(group_name); + if (group != nullptr) { + LOG(ERROR) << "Cannot add group " << group_name << " because it already exists."; + return false; + } + + if (maximum_size.value() == 0) { + LOG(WARNING) << "Adding group " << group_name << " with no size limits."; + } + + if (!params.builder->AddGroup(group_name, maximum_size.value())) { + LOG(ERROR) << "Failed to add group " << group_name << " with maximum size " + << maximum_size.value() << "."; + return false; + } + return true; +} + +bool PerformOpResizeGroup(const OpParameters& params) { + if (!params.ExpectArgSize(2)) return false; + const auto& group_name = params.arg(0); + auto new_size = params.uint_arg(1, "maximum_size"); + if (!new_size.has_value()) return false; + + auto group = params.builder->FindGroup(group_name); + if (group == nullptr) { + LOG(ERROR) << "Cannot resize group " << group_name << " because it is not found."; + return false; + } + + auto old_size = group->maximum_size(); + if (old_size != new_size.value()) { + if (!params.builder->ChangeGroupSize(group_name, new_size.value())) { + LOG(ERROR) << "Cannot resize group " << group_name << " from " << old_size << " to " + << new_size.value() << "."; + return false; + } + } + return true; +} + +std::vector<std::string> ListPartitionNamesInGroup(MetadataBuilder* builder, + const std::string& group_name) { + auto partitions = builder->ListPartitionsInGroup(group_name); + std::vector<std::string> partition_names; + std::transform(partitions.begin(), partitions.end(), std::back_inserter(partition_names), + [](Partition* partition) { return partition->name(); }); + return partition_names; +} + +bool PerformOpRemoveGroup(const OpParameters& params) { + if (!params.ExpectArgSize(1)) return false; + const auto& group_name = params.arg(0); + + auto partition_names = ListPartitionNamesInGroup(params.builder, group_name); + if (!partition_names.empty()) { + LOG(ERROR) << "Cannot remove group " << group_name << " because it still contains partitions [" + << android::base::Join(partition_names, ", ") << "]"; + return false; + } + params.builder->RemoveGroupAndPartitions(group_name); + return true; +} + +bool PerformOpRemoveAllGroups(const OpParameters& params) { + if (!params.ExpectArgSize(0)) return false; + + auto group_names = params.builder->ListGroups(); + for (const auto& group_name : group_names) { + auto partition_names = ListPartitionNamesInGroup(params.builder, group_name); + for (const auto& partition_name : partition_names) { + if (!UnmapPartitionOnDeviceMapper(partition_name)) { + LOG(ERROR) << "Cannot unmap " << partition_name << " before removing group " << group_name + << "."; + return false; + } + } + params.builder->RemoveGroupAndPartitions(group_name); + } + return true; +} + +} // namespace + +bool UpdaterRuntime::UpdateDynamicPartitions(const std::string_view op_list_value) { + auto super_device = GetSuperDevice(); + auto builder = MetadataBuilder::New(PartitionOpener(), super_device, 0); + if (builder == nullptr) { + LOG(ERROR) << "Failed to load dynamic partition metadata."; + return false; + } + + static const OpMap op_map{ + // clang-format off + {"resize", PerformOpResize}, + {"remove", PerformOpRemove}, + {"add", PerformOpAdd}, + {"move", PerformOpMove}, + {"add_group", PerformOpAddGroup}, + {"resize_group", PerformOpResizeGroup}, + {"remove_group", PerformOpRemoveGroup}, + {"remove_all_groups", PerformOpRemoveAllGroups}, + // clang-format on + }; + + std::vector<std::string> lines = android::base::Split(std::string(op_list_value), "\n"); + for (const auto& line : lines) { + auto comment_idx = line.find('#'); + auto op_and_args = comment_idx == std::string::npos ? line : line.substr(0, comment_idx); + op_and_args = android::base::Trim(op_and_args); + if (op_and_args.empty()) continue; + + auto tokens = android::base::Split(op_and_args, " "); + const auto& op = tokens[0]; + auto it = op_map.find(op); + if (it == op_map.end()) { + LOG(ERROR) << "Unknown operation in op_list: " << op; + return false; + } + OpParameters params; + params.tokens = tokens; + params.builder = builder.get(); + if (!it->second(params)) { + return false; + } + } + + auto metadata = builder->Export(); + if (metadata == nullptr) { + LOG(ERROR) << "Failed to export metadata."; + return false; + } + + if (!UpdatePartitionTable(super_device, *metadata, 0)) { + LOG(ERROR) << "Failed to write metadata."; + return false; + } + + return true; +} 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/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 |