summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.bp13
-rw-r--r--CleanSpec.mk18
-rw-r--r--OWNERS3
-rw-r--r--README.md28
-rw-r--r--TEST_MAPPING14
-rw-r--r--applypatch/Android.bp59
-rw-r--r--applypatch/applypatch.cpp18
-rw-r--r--applypatch/applypatch_modes.cpp2
-rw-r--r--applypatch/imgdiff.cpp7
-rw-r--r--applypatch/include/applypatch/applypatch.h7
-rw-r--r--applypatch/vendor_flash_recovery.rc3
-rw-r--r--boot_control/Android.bp36
-rw-r--r--boot_control/include/libboot_control/libboot_control.h66
-rw-r--r--boot_control/legacy_boot_control.cpp115
-rw-r--r--boot_control/libboot_control.cpp (renamed from boot_control/boot_control.cpp)218
-rw-r--r--bootloader_message/Android.bp13
-rw-r--r--bootloader_message/bootloader_message.cpp27
-rw-r--r--bootloader_message/include/bootloader_message/bootloader_message.h9
-rw-r--r--edify/Android.bp1
-rw-r--r--edify/expr.cpp4
-rw-r--r--edify/include/edify/expr.h9
-rw-r--r--edify/include/edify/updater_interface.h48
-rw-r--r--edify/include/edify/updater_runtime_interface.h74
-rw-r--r--fastboot/fastboot.cpp4
-rw-r--r--fsck_unshare_blocks.cpp6
-rw-r--r--fuse_sideload/Android.bp4
-rw-r--r--fuse_sideload/fuse_provider.cpp84
-rw-r--r--fuse_sideload/include/fuse_provider.h41
-rw-r--r--install/Android.bp14
-rw-r--r--install/adb_install.cpp16
-rw-r--r--install/fuse_install.cpp (renamed from install/fuse_sdcard_install.cpp)86
-rw-r--r--install/include/install/adb_install.h11
-rw-r--r--install/include/install/fuse_install.h30
-rw-r--r--install/include/install/install.h22
-rw-r--r--install/include/install/package.h9
-rw-r--r--install/include/install/wipe_device.h (renamed from install/include/install/fuse_sdcard_install.h)11
-rw-r--r--install/include/private/setup_commands.h8
-rw-r--r--install/install.cpp170
-rw-r--r--install/package.cpp18
-rw-r--r--install/verifier.cpp10
-rw-r--r--install/wipe_data.cpp6
-rw-r--r--install/wipe_device.cpp197
-rw-r--r--minadbd/Android.bp22
-rw-r--r--minadbd/README.md32
-rw-r--r--minadbd/fuse_adb_provider.h4
-rw-r--r--minadbd/include/minadbd/types.h (renamed from minadbd/minadbd_types.h)0
-rw-r--r--minadbd/minadbd.cpp2
-rw-r--r--minadbd/minadbd_services.cpp31
-rw-r--r--minadbd/minadbd_services_test.cpp2
-rw-r--r--minui/events.cpp124
-rw-r--r--minui/resources.cpp18
-rw-r--r--otautil/Android.bp34
-rw-r--r--otautil/include/otautil/boot_state.h (renamed from common.h)38
-rw-r--r--otautil/include/otautil/rangeset.h7
-rw-r--r--otautil/include/otautil/sysutil.h15
-rw-r--r--otautil/rangeset.cpp52
-rw-r--r--otautil/sysutil.cpp20
-rw-r--r--recovery-persist.cpp4
-rw-r--r--recovery-refresh.cpp3
-rw-r--r--recovery.cpp402
-rw-r--r--recovery_main.cpp91
-rw-r--r--recovery_ui/Android.bp1
-rw-r--r--recovery_ui/device.cpp13
-rw-r--r--recovery_ui/include/recovery_ui/device.h12
-rw-r--r--recovery_ui/include/recovery_ui/screen_ui.h3
-rw-r--r--recovery_ui/include/recovery_ui/stub_ui.h6
-rw-r--r--recovery_ui/include/recovery_ui/ui.h21
-rw-r--r--recovery_ui/screen_ui.cpp46
-rw-r--r--recovery_ui/stub_ui.cpp36
-rw-r--r--recovery_ui/ui.cpp24
-rw-r--r--recovery_utils/Android.bp81
-rw-r--r--recovery_utils/battery_utils.cpp89
-rw-r--r--recovery_utils/include/recovery_utils/battery_utils.h33
-rw-r--r--recovery_utils/include/recovery_utils/logging.h (renamed from otautil/include/otautil/logging.h)2
-rw-r--r--recovery_utils/include/recovery_utils/parse_install_logs.h (renamed from otautil/include/otautil/parse_install_logs.h)0
-rw-r--r--recovery_utils/include/recovery_utils/roots.h (renamed from otautil/include/otautil/roots.h)5
-rw-r--r--recovery_utils/include/recovery_utils/thermalutil.h (renamed from otautil/include/otautil/thermalutil.h)0
-rw-r--r--recovery_utils/logging.cpp (renamed from otautil/logging.cpp)21
-rw-r--r--recovery_utils/parse_install_logs.cpp (renamed from otautil/parse_install_logs.cpp)2
-rw-r--r--recovery_utils/roots.cpp (renamed from otautil/roots.cpp)26
-rw-r--r--recovery_utils/thermalutil.cpp (renamed from otautil/thermalutil.cpp)2
-rw-r--r--tests/Android.bp77
-rw-r--r--tests/AndroidTest.xml33
-rw-r--r--tests/component/resources_test.cpp120
-rw-r--r--tests/unit/applypatch_modes_test.cpp (renamed from tests/component/applypatch_modes_test.cpp)0
-rw-r--r--tests/unit/applypatch_test.cpp4
-rw-r--r--tests/unit/battery_utils_test.cpp27
-rw-r--r--tests/unit/bootloader_message_test.cpp (renamed from tests/component/bootloader_message_test.cpp)0
-rw-r--r--tests/unit/edify_test.cpp (renamed from tests/component/edify_test.cpp)0
-rw-r--r--tests/unit/fuse_provider_test.cpp104
-rw-r--r--tests/unit/fuse_sideload_test.cpp (renamed from tests/component/sideload_test.cpp)4
-rw-r--r--tests/unit/host/imgdiff_test.cpp (renamed from tests/component/imgdiff_test.cpp)0
-rw-r--r--tests/unit/host/update_simulator_test.cpp403
-rw-r--r--tests/unit/install_test.cpp (renamed from tests/component/install_test.cpp)58
-rw-r--r--tests/unit/locale_test.cpp4
-rw-r--r--tests/unit/package_test.cpp5
-rw-r--r--tests/unit/parse_install_logs_test.cpp2
-rw-r--r--tests/unit/rangeset_test.cpp24
-rw-r--r--tests/unit/resources_test.cpp99
-rw-r--r--tests/unit/sysutil_test.cpp2
-rw-r--r--tests/unit/uncrypt_test.cpp (renamed from tests/component/uncrypt_test.cpp)0
-rw-r--r--tests/unit/update_verifier_test.cpp (renamed from tests/component/update_verifier_test.cpp)0
-rw-r--r--tests/unit/updater_test.cpp (renamed from tests/component/updater_test.cpp)186
-rw-r--r--tests/unit/verifier_test.cpp (renamed from tests/component/verifier_test.cpp)0
-rw-r--r--tests/unit/zip_test.cpp3
-rw-r--r--tools/image_generator/Android.bp2
-rw-r--r--updater/Android.bp85
-rw-r--r--updater/Android.mk80
-rw-r--r--updater/blockimg.cpp116
-rw-r--r--updater/build_info.cpp139
-rw-r--r--updater/dynamic_partitions.cpp313
-rw-r--r--updater/include/updater/build_info.h74
-rw-r--r--updater/include/updater/install.h11
-rw-r--r--updater/include/updater/simulator_runtime.h62
-rw-r--r--updater/include/updater/target_files.h71
-rw-r--r--updater/include/updater/updater.h85
-rw-r--r--updater/include/updater/updater_runtime.h62
-rw-r--r--updater/install.cpp220
-rw-r--r--updater/mounts.cpp (renamed from otautil/mounts.cpp)2
-rw-r--r--updater/mounts.h (renamed from otautil/include/otautil/mounts.h)0
-rw-r--r--updater/simulator_runtime.cpp132
-rw-r--r--updater/target_files.cpp287
-rw-r--r--updater/update_simulator_main.cpp167
-rw-r--r--updater/updater.cpp278
-rw-r--r--updater/updater_main.cpp116
-rw-r--r--updater/updater_runtime.cpp132
-rw-r--r--updater/updater_runtime_dynamic_partitions.cpp343
-rw-r--r--updater_sample/Android.bp1
-rw-r--r--updater_sample/OWNERS2
-rw-r--r--updater_sample/README.md6
130 files changed, 4870 insertions, 1934 deletions
diff --git a/Android.bp b/Android.bp
index f92078256..45aafb043 100644
--- a/Android.bp
+++ b/Android.bp
@@ -58,7 +58,6 @@ cc_defaults {
],
shared_libs: [
- "android.hardware.health@2.0",
"libbase",
"libbootloader_message",
"libcrypto",
@@ -72,11 +71,8 @@ cc_defaults {
"libinstall",
"librecovery_fastboot",
"libminui",
+ "librecovery_utils",
"libotautil",
-
- // external dependencies
- "libhealthhalutils",
- "libfstab",
],
}
@@ -105,6 +101,7 @@ cc_binary {
defaults: [
"libinstall_defaults",
"librecovery_defaults",
+ "librecovery_utils_defaults",
],
srcs: [
@@ -149,8 +146,7 @@ cc_binary {
],
static_libs: [
- "libotautil",
- "libfstab",
+ "librecovery_utils",
],
init_rc: [
@@ -176,8 +172,7 @@ cc_binary {
],
static_libs: [
- "libotautil",
- "libfstab",
+ "librecovery_utils",
],
init_rc: [
diff --git a/CleanSpec.mk b/CleanSpec.mk
index a7ab0d9be..d4e9e437b 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -51,6 +51,24 @@ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/sbin)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libinstall.recovery_intermediates)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/system/lib64/libinstall.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/nativetest/recovery_component_test)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/nativetest64/recovery_component_test)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/testcases/recovery_component_test)
+
+$(call add-clean-step, find $(OUT_DIR) -type f -name "SystemUpdaterSample*" -print0 | xargs -0 rm -f)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/SystemUpdaterSample)
+
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib*/libbrotli.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib*/libbz.so)
+
+# Move recovery resources from /system to /vendor.
+$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/bin/applypatch)
+$(call add-clean-step, rm -r $(PRODUCT_OUT)/symbols/system/bin/applypatch)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/PACKAGING/target_files_intermediates/*-target_files-*/SYSTEM/bin/applypatch)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/PACKAGING/target_files_intermediates/*-target_files-*/SYSTEM/bin/install-recovery.sh)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/PACKAGING/target_files_intermediates/*-target_files-*/SYSTEM/etc/recovery-resource.dat)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/PACKAGING/target_files_intermediates/*-target_files-*/SYSTEM/recovery-from-boot.p)
+
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
diff --git a/OWNERS b/OWNERS
index b3f11dcd6..fe1c33d4b 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,4 @@
enh@google.com
-tbao@google.com
+nhdo@google.com
xunchang@google.com
+zhaojiac@google.com
diff --git a/README.md b/README.md
index efcd318b5..bd1cf7dea 100644
--- a/README.md
+++ b/README.md
@@ -4,29 +4,41 @@ The Recovery Image
Quick turn-around testing
-------------------------
- mm -j && m ramdisk-nodeps && m recoveryimage-nodeps
+* Devices using recovery-as-boot (e.g. Pixels, which set BOARD\_USES\_RECOVERY\_AS\_BOOT)
- # To boot into the new recovery image
- # without flashing the recovery partition:
- adb reboot bootloader
- fastboot boot $ANDROID_PRODUCT_OUT/recovery.img
+ # After setting up environment and lunch.
+ m -j bootimage
+ adb reboot bootloader
+
+ # Pixel devices don't support booting into recovery mode with `fastboot boot`.
+ fastboot flash boot
+
+ # Manually choose `Recovery mode` from bootloader menu.
+
+* Devices with a separate recovery image (e.g. Nexus)
+
+ # After setting up environment and lunch.
+ mm -j && m ramdisk-nodeps && m recoveryimage-nodeps
+ adb reboot bootloader
+
+ # To boot into the new recovery image without flashing the recovery partition:
+ fastboot boot $ANDROID_PRODUCT_OUT/recovery.img
Running the tests
-----------------
+
# After setting up environment and lunch.
mmma -j bootable/recovery
- # Running the tests on device.
+ # Running the tests on device (under normal boot).
adb root
adb sync data
# 32-bit device
adb shell /data/nativetest/recovery_unit_test/recovery_unit_test
- adb shell /data/nativetest/recovery_component_test/recovery_component_test
# Or 64-bit device
adb shell /data/nativetest64/recovery_unit_test/recovery_unit_test
- adb shell /data/nativetest64/recovery_component_test/recovery_component_test
Running the manual tests
------------------------
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 000000000..a3045828e
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,14 @@
+{
+ "presubmit": [
+ {
+ "name": "minadbd_test"
+ },
+ {
+ "name": "recovery_unit_test"
+ },
+ {
+ "name": "recovery_host_test",
+ "host": true
+ }
+ ]
+}
diff --git a/applypatch/Android.bp b/applypatch/Android.bp
index 620ca6cc9..13a962584 100644
--- a/applypatch/Android.bp
+++ b/applypatch/Android.bp
@@ -31,6 +31,7 @@ cc_library_static {
name: "libapplypatch",
host_supported: true,
+ vendor_available: true,
defaults: [
"applypatch_defaults",
@@ -51,12 +52,15 @@ cc_library_static {
"libbase",
"libbspatch",
"libbz",
- "libcrypto",
"libedify",
"libotautil",
"libz",
],
+ shared_libs: [
+ "libcrypto",
+ ],
+
target: {
darwin: {
enabled: false,
@@ -66,6 +70,7 @@ cc_library_static {
cc_library_static {
name: "libapplypatch_modes",
+ vendor_available: true,
defaults: [
"applypatch_defaults",
@@ -78,14 +83,18 @@ cc_library_static {
static_libs: [
"libapplypatch",
"libbase",
- "libcrypto",
"libedify",
"libotautil",
],
+
+ shared_libs: [
+ "libcrypto",
+ ],
}
cc_binary {
name: "applypatch",
+ vendor: true,
defaults: [
"applypatch_defaults",
@@ -100,25 +109,29 @@ cc_binary {
"libapplypatch",
"libedify",
"libotautil",
+
+ // External dependencies.
"libbspatch",
+ "libbrotli",
+ "libbz",
],
shared_libs: [
"libbase",
- "libbrotli",
- "libbz",
"libcrypto",
"liblog",
"libz",
"libziparchive",
],
+
+ init_rc: [
+ "vendor_flash_recovery.rc",
+ ],
}
-cc_library_static {
+cc_library_host_static {
name: "libimgdiff",
- host_supported: true,
-
defaults: [
"applypatch_defaults",
],
@@ -170,35 +183,3 @@ cc_binary_host {
"libz",
],
}
-
-cc_library_static {
- name: "libimgpatch",
-
- // The host module is for recovery_host_test (Linux only).
- host_supported: true,
-
- defaults: [
- "applypatch_defaults",
- ],
-
- srcs: [
- "bspatch.cpp",
- "imgpatch.cpp",
- ],
-
- static_libs: [
- "libbase",
- "libbspatch",
- "libbz",
- "libcrypto",
- "libedify",
- "libotautil",
- "libz",
- ],
-
- target: {
- darwin: {
- enabled: false,
- },
- },
-}
diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp
index 90d8e8604..adda6976d 100644
--- a/applypatch/applypatch.cpp
+++ b/applypatch/applypatch.cpp
@@ -47,7 +47,7 @@
using namespace std::string_literals;
static bool GenerateTarget(const Partition& target, const FileContents& source_file,
- const Value& patch, const Value* bonus_data);
+ const Value& patch, const Value* bonus_data, bool backup_source);
bool LoadFileContents(const std::string& filename, FileContents* file) {
// No longer allow loading contents from eMMC partitions.
@@ -266,7 +266,7 @@ int ShowLicenses() {
}
bool PatchPartition(const Partition& target, const Partition& source, const Value& patch,
- const Value* bonus) {
+ const Value* bonus, bool backup_source) {
LOG(INFO) << "Patching " << target.name;
// We try to load and check against the target hash first.
@@ -279,8 +279,8 @@ bool PatchPartition(const Partition& target, const Partition& source, const Valu
}
FileContents source_file;
- if (ReadPartitionToBuffer(source, &source_file, true)) {
- return GenerateTarget(target, source_file, patch, bonus);
+ if (ReadPartitionToBuffer(source, &source_file, backup_source)) {
+ return GenerateTarget(target, source_file, patch, bonus, backup_source);
}
LOG(ERROR) << "Failed to find any match";
@@ -326,7 +326,7 @@ bool FlashPartition(const Partition& partition, const std::string& source_filena
}
static bool GenerateTarget(const Partition& target, const FileContents& source_file,
- const Value& patch, const Value* bonus_data) {
+ const Value& patch, const Value* bonus_data, bool backup_source) {
uint8_t expected_sha1[SHA_DIGEST_LENGTH];
if (ParseSha1(target.hash, expected_sha1) != 0) {
LOG(ERROR) << "Failed to parse target hash \"" << target.hash << "\"";
@@ -351,11 +351,11 @@ static bool GenerateTarget(const Partition& target, const FileContents& source_f
}
// We write the original source to cache, in case the partition write is interrupted.
- if (!CheckAndFreeSpaceOnCache(source_file.data.size())) {
+ if (backup_source && !CheckAndFreeSpaceOnCache(source_file.data.size())) {
LOG(ERROR) << "Not enough free space on /cache";
return false;
}
- if (!SaveFileContents(Paths::Get().cache_temp_source(), &source_file)) {
+ if (backup_source && !SaveFileContents(Paths::Get().cache_temp_source(), &source_file)) {
LOG(ERROR) << "Failed to back up source file";
return false;
}
@@ -415,7 +415,9 @@ static bool GenerateTarget(const Partition& target, const FileContents& source_f
}
// Delete the backup copy of the source.
- unlink(Paths::Get().cache_temp_source().c_str());
+ if (backup_source) {
+ unlink(Paths::Get().cache_temp_source().c_str());
+ }
// Success!
return true;
diff --git a/applypatch/applypatch_modes.cpp b/applypatch/applypatch_modes.cpp
index b46659808..bb5eeae9d 100644
--- a/applypatch/applypatch_modes.cpp
+++ b/applypatch/applypatch_modes.cpp
@@ -87,7 +87,7 @@ static int PatchMode(const std::string& target_emmc, const std::string& source_e
bonus = std::make_unique<Value>(Value::Type::BLOB, std::move(bonus_contents));
}
- return PatchPartition(target, source, patch, bonus.get()) ? 0 : 1;
+ return PatchPartition(target, source, patch, bonus.get(), false) ? 0 : 1;
}
static void Usage() {
diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp
index 415d95f14..6ad4a6105 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -675,7 +675,7 @@ bool ZipModeImage::Initialize(const std::string& filename) {
// Iterate the zip entries and compose the image chunks accordingly.
bool ZipModeImage::InitializeChunks(const std::string& filename, ZipArchiveHandle handle) {
void* cookie;
- int ret = StartIteration(handle, &cookie, nullptr, nullptr);
+ int ret = StartIteration(handle, &cookie);
if (ret != 0) {
LOG(ERROR) << "Failed to iterate over entries in " << filename << ": " << ErrorCodeString(ret);
return false;
@@ -683,12 +683,11 @@ bool ZipModeImage::InitializeChunks(const std::string& filename, ZipArchiveHandl
// Create a list of deflated zip entries, sorted by offset.
std::vector<std::pair<std::string, ZipEntry>> temp_entries;
- ZipString name;
+ std::string name;
ZipEntry entry;
while ((ret = Next(cookie, &entry, &name)) == 0) {
if (entry.method == kCompressDeflated || limit_ > 0) {
- std::string entry_name(name.name, name.name + name.name_length);
- temp_entries.emplace_back(entry_name, entry);
+ temp_entries.emplace_back(name, entry);
}
}
diff --git a/applypatch/include/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h
index 6fc6f0fc9..799f4b2d7 100644
--- a/applypatch/include/applypatch/applypatch.h
+++ b/applypatch/include/applypatch/applypatch.h
@@ -73,10 +73,11 @@ std::ostream& operator<<(std::ostream& os, const Partition& partition);
// the 'target' Partition. While patching, it will backup the data on the source partition to
// /cache, so that the patching could be resumed on interruption even if both of the source and
// target partitions refer to the same device. The function is idempotent if called multiple times.
-// An optional arg 'bonus' can be provided, if the patch was generated with a bonus output.
-// Returns the patching result.
+// 'bonus' can be provided if the patch was generated with a bonus output, or nullptr.
+// 'backup_source' indicates whether the source partition should be backed up prior to the update
+// (e.g. when doing in-place update). Returns the patching result.
bool PatchPartition(const Partition& target, const Partition& source, const Value& patch,
- const Value* bonus);
+ const Value* bonus, bool backup_source);
// Returns whether the contents of the eMMC target or the cached file match the embedded hash.
// It will look for the backup on /cache if the given partition doesn't match the checksum.
diff --git a/applypatch/vendor_flash_recovery.rc b/applypatch/vendor_flash_recovery.rc
new file mode 100644
index 000000000..37a7c2be7
--- /dev/null
+++ b/applypatch/vendor_flash_recovery.rc
@@ -0,0 +1,3 @@
+service vendor_flash_recovery /vendor/bin/install-recovery.sh
+ class main
+ oneshot
diff --git a/boot_control/Android.bp b/boot_control/Android.bp
index 7720ead50..b2e68dfd4 100644
--- a/boot_control/Android.bp
+++ b/boot_control/Android.bp
@@ -14,13 +14,12 @@
// limitations under the License.
//
-cc_library_shared {
- name: "bootctrl.default",
+cc_defaults {
+ name: "libboot_control_defaults",
+ vendor: true,
recovery_available: true,
relative_install_path: "hw",
- srcs: ["boot_control.cpp"],
-
cflags: [
"-D_FILE_OFFSET_BITS=64",
"-Werror",
@@ -29,9 +28,34 @@ cc_library_shared {
],
shared_libs: [
+ "android.hardware.boot@1.1",
"libbase",
- "libbootloader_message",
- "libfs_mgr",
"liblog",
],
+ static_libs: [
+ "libbootloader_message_vendor",
+ "libfstab",
+ ],
+}
+
+cc_library_static {
+ name: "libboot_control",
+ defaults: ["libboot_control_defaults"],
+ export_include_dirs: ["include"],
+
+ srcs: ["libboot_control.cpp"],
+}
+
+cc_library_shared {
+ name: "bootctrl.default",
+ defaults: ["libboot_control_defaults"],
+
+ srcs: ["legacy_boot_control.cpp"],
+
+ static_libs: [
+ "libboot_control",
+ ],
+ shared_libs: [
+ "libhardware",
+ ],
}
diff --git a/boot_control/include/libboot_control/libboot_control.h b/boot_control/include/libboot_control/libboot_control.h
new file mode 100644
index 000000000..34a9affe1
--- /dev/null
+++ b/boot_control/include/libboot_control/libboot_control.h
@@ -0,0 +1,66 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#pragma once
+
+#include <string>
+
+#include <android/hardware/boot/1.1/IBootControl.h>
+
+namespace android {
+namespace bootable {
+
+// Helper library to implement the IBootControl HAL using the misc partition.
+class BootControl {
+ using MergeStatus = ::android::hardware::boot::V1_1::MergeStatus;
+
+ public:
+ bool Init();
+ unsigned int GetNumberSlots();
+ unsigned int GetCurrentSlot();
+ bool MarkBootSuccessful();
+ bool SetActiveBootSlot(unsigned int slot);
+ bool SetSlotAsUnbootable(unsigned int slot);
+ bool SetSlotBootable(unsigned int slot);
+ bool IsSlotBootable(unsigned int slot);
+ const char* GetSuffix(unsigned int slot);
+ bool IsSlotMarkedSuccessful(unsigned int slot);
+ bool SetSnapshotMergeStatus(MergeStatus status);
+ MergeStatus GetSnapshotMergeStatus();
+
+ bool IsValidSlot(unsigned int slot);
+
+ const std::string& misc_device() const {
+ return misc_device_;
+ }
+
+ private:
+ // Whether this object was initialized with data from the bootloader message
+ // that doesn't change until next reboot.
+ bool initialized_ = false;
+
+ // The path to the misc_device as reported in the fstab.
+ std::string misc_device_;
+
+ // The number of slots present on the device.
+ unsigned int num_slots_ = 0;
+
+ // The slot where we are running from.
+ unsigned int current_slot_ = 0;
+};
+
+} // namespace bootable
+} // namespace android
diff --git a/boot_control/legacy_boot_control.cpp b/boot_control/legacy_boot_control.cpp
new file mode 100644
index 000000000..73d3a5841
--- /dev/null
+++ b/boot_control/legacy_boot_control.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+
+#include <hardware/boot_control.h>
+#include <hardware/hardware.h>
+
+#include <libboot_control/libboot_control.h>
+
+using android::bootable::BootControl;
+
+struct boot_control_private_t {
+ // The base struct needs to be first in the list.
+ boot_control_module_t base;
+
+ BootControl impl;
+};
+
+namespace {
+
+void BootControl_init(boot_control_module_t* module) {
+ auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+ impl.Init();
+}
+
+unsigned int BootControl_getNumberSlots(boot_control_module_t* module) {
+ auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+ return impl.GetNumberSlots();
+}
+
+unsigned int BootControl_getCurrentSlot(boot_control_module_t* module) {
+ auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+ return impl.GetCurrentSlot();
+}
+
+int BootControl_markBootSuccessful(boot_control_module_t* module) {
+ auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+ return impl.MarkBootSuccessful() ? 0 : -1;
+}
+
+int BootControl_setActiveBootSlot(boot_control_module_t* module, unsigned int slot) {
+ auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+ return impl.SetActiveBootSlot(slot) ? 0 : -1;
+}
+
+int BootControl_setSlotAsUnbootable(struct boot_control_module* module, unsigned int slot) {
+ auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+ return impl.SetSlotAsUnbootable(slot) ? 0 : -1;
+}
+
+int BootControl_isSlotBootable(struct boot_control_module* module, unsigned int slot) {
+ auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+ return impl.IsSlotBootable(slot) ? 0 : -1;
+}
+
+int BootControl_isSlotMarkedSuccessful(struct boot_control_module* module, unsigned int slot) {
+ auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+ return impl.IsSlotMarkedSuccessful(slot) ? 0 : -1;
+}
+
+const char* BootControl_getSuffix(boot_control_module_t* module, unsigned int slot) {
+ auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+ return impl.GetSuffix(slot);
+}
+
+static int BootControl_open(const hw_module_t* module __unused, const char* id __unused,
+ hw_device_t** device __unused) {
+ /* Nothing to do currently. */
+ return 0;
+}
+
+struct hw_module_methods_t BootControl_methods = {
+ .open = BootControl_open,
+};
+
+} // namespace
+
+boot_control_private_t HAL_MODULE_INFO_SYM = {
+ .base =
+ {
+ .common =
+ {
+ .tag = HARDWARE_MODULE_TAG,
+ .module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1,
+ .hal_api_version = HARDWARE_HAL_API_VERSION,
+ .id = BOOT_CONTROL_HARDWARE_MODULE_ID,
+ .name = "AOSP reference bootctrl HAL",
+ .author = "The Android Open Source Project",
+ .methods = &BootControl_methods,
+ },
+ .init = BootControl_init,
+ .getNumberSlots = BootControl_getNumberSlots,
+ .getCurrentSlot = BootControl_getCurrentSlot,
+ .markBootSuccessful = BootControl_markBootSuccessful,
+ .setActiveBootSlot = BootControl_setActiveBootSlot,
+ .setSlotAsUnbootable = BootControl_setSlotAsUnbootable,
+ .isSlotBootable = BootControl_isSlotBootable,
+ .getSuffix = BootControl_getSuffix,
+ .isSlotMarkedSuccessful = BootControl_isSlotMarkedSuccessful,
+ },
+};
diff --git a/boot_control/boot_control.cpp b/boot_control/libboot_control.cpp
index ec97b6ced..e3bff9ff3 100644
--- a/boot_control/boot_control.cpp
+++ b/boot_control/libboot_control.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <libboot_control/libboot_control.h>
+
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
@@ -26,30 +28,13 @@
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
-#include <hardware/boot_control.h>
-#include <hardware/hardware.h>
#include <bootloader_message/bootloader_message.h>
-struct boot_control_private_t {
- // The base struct needs to be first in the list.
- boot_control_module_t base;
-
- // Whether this struct was initialized with data from the bootloader message
- // that doesn't change until next reboot.
- bool initialized;
-
- // The path to the misc_device as reported in the fstab.
- const char* misc_device;
-
- // The number of slots present on the device.
- unsigned int num_slots;
-
- // The slot where we are running from.
- unsigned int current_slot;
-};
+namespace android {
+namespace bootable {
-namespace {
+using ::android::hardware::boot::V1_1::MergeStatus;
// The number of boot attempts that should be made from a new slot before
// rolling back to the previous slot.
@@ -91,8 +76,8 @@ uint32_t BootloaderControlLECRC(const bootloader_control* boot_ctrl) {
CRC32(reinterpret_cast<const uint8_t*>(boot_ctrl), offsetof(bootloader_control, crc32_le)));
}
-bool LoadBootloaderControl(const char* misc_device, bootloader_control* buffer) {
- android::base::unique_fd fd(open(misc_device, O_RDONLY));
+bool LoadBootloaderControl(const std::string& misc_device, bootloader_control* buffer) {
+ android::base::unique_fd fd(open(misc_device.c_str(), O_RDONLY));
if (fd.get() == -1) {
PLOG(ERROR) << "failed to open " << misc_device;
return false;
@@ -108,9 +93,9 @@ bool LoadBootloaderControl(const char* misc_device, bootloader_control* buffer)
return true;
}
-bool UpdateAndSaveBootloaderControl(const char* misc_device, bootloader_control* buffer) {
+bool UpdateAndSaveBootloaderControl(const std::string& misc_device, bootloader_control* buffer) {
buffer->crc32_le = BootloaderControlLECRC(buffer);
- android::base::unique_fd fd(open(misc_device, O_WRONLY | O_SYNC));
+ android::base::unique_fd fd(open(misc_device.c_str(), O_WRONLY | O_SYNC));
if (fd.get() == -1) {
PLOG(ERROR) << "failed to open " << misc_device;
return false;
@@ -126,13 +111,12 @@ bool UpdateAndSaveBootloaderControl(const char* misc_device, bootloader_control*
return true;
}
-void InitDefaultBootloaderControl(const boot_control_private_t* module,
- bootloader_control* boot_ctrl) {
+void InitDefaultBootloaderControl(BootControl* control, bootloader_control* boot_ctrl) {
memset(boot_ctrl, 0, sizeof(*boot_ctrl));
- if (module->current_slot < kMaxNumSlots) {
- strlcpy(boot_ctrl->slot_suffix, kSlotSuffixes[module->current_slot],
- sizeof(boot_ctrl->slot_suffix));
+ unsigned int current_slot = control->GetCurrentSlot();
+ if (current_slot < kMaxNumSlots) {
+ strlcpy(boot_ctrl->slot_suffix, kSlotSuffixes[current_slot], sizeof(boot_ctrl->slot_suffix));
}
boot_ctrl->magic = BOOT_CTRL_MAGIC;
boot_ctrl->version = BOOT_CTRL_VERSION;
@@ -140,7 +124,7 @@ void InitDefaultBootloaderControl(const boot_control_private_t* module,
// Figure out the number of slots by checking if the partitions exist,
// otherwise assume the maximum supported by the header.
boot_ctrl->nb_slot = kMaxNumSlots;
- std::string base_path = module->misc_device;
+ std::string base_path = control->misc_device();
size_t last_path_sep = base_path.rfind('/');
if (last_path_sep != std::string::npos) {
// We test the existence of the "boot" partition on each possible slot,
@@ -185,7 +169,7 @@ void InitDefaultBootloaderControl(const boot_control_private_t* module,
// current slot is successful. The bootloader should repair this situation
// before booting and write a valid boot_control slot, so if we reach this
// stage it means that the misc partition was corrupted since boot.
- if (module->current_slot == slot) {
+ if (current_slot == slot) {
entry.successful_boot = 1;
}
@@ -207,25 +191,35 @@ int SlotSuffixToIndex(const char* suffix) {
// Initialize the boot_control_private struct with the information from
// the bootloader_message buffer stored in |boot_ctrl|. Returns whether the
// initialization succeeded.
-bool BootControl_lazyInitialization(boot_control_private_t* module) {
- if (module->initialized) return true;
+bool BootControl::Init() {
+ if (initialized_) return true;
// Initialize the current_slot from the read-only property. If the property
// was not set (from either the command line or the device tree), we can later
// initialize it from the bootloader_control struct.
std::string suffix_prop = android::base::GetProperty("ro.boot.slot_suffix", "");
- module->current_slot = SlotSuffixToIndex(suffix_prop.c_str());
+ if (suffix_prop.empty()) {
+ LOG(ERROR) << "Slot suffix property is not set";
+ return false;
+ }
+ current_slot_ = SlotSuffixToIndex(suffix_prop.c_str());
std::string err;
std::string device = get_bootloader_message_blk_device(&err);
- if (device.empty()) return false;
+ if (device.empty()) {
+ LOG(ERROR) << "Could not find bootloader message block device: " << err;
+ return false;
+ }
bootloader_control boot_ctrl;
- if (!LoadBootloaderControl(device.c_str(), &boot_ctrl)) return false;
+ if (!LoadBootloaderControl(device.c_str(), &boot_ctrl)) {
+ LOG(ERROR) << "Failed to load bootloader control block";
+ return false;
+ }
// Note that since there isn't a module unload function this memory is leaked.
- module->misc_device = strdup(device.c_str());
- module->initialized = true;
+ misc_device_ = strdup(device.c_str());
+ initialized_ = true;
// Validate the loaded data, otherwise we will destroy it and re-initialize it
// with the current information.
@@ -233,56 +227,47 @@ bool BootControl_lazyInitialization(boot_control_private_t* module) {
if (boot_ctrl.crc32_le != computed_crc32) {
LOG(WARNING) << "Invalid boot control found, expected CRC-32 0x" << std::hex << computed_crc32
<< " but found 0x" << std::hex << boot_ctrl.crc32_le << ". Re-initializing.";
- InitDefaultBootloaderControl(module, &boot_ctrl);
+ InitDefaultBootloaderControl(this, &boot_ctrl);
UpdateAndSaveBootloaderControl(device.c_str(), &boot_ctrl);
}
- module->num_slots = boot_ctrl.nb_slot;
+ num_slots_ = boot_ctrl.nb_slot;
return true;
}
-void BootControl_init(boot_control_module_t* module) {
- BootControl_lazyInitialization(reinterpret_cast<boot_control_private_t*>(module));
-}
-
-unsigned int BootControl_getNumberSlots(boot_control_module_t* module) {
- return reinterpret_cast<boot_control_private_t*>(module)->num_slots;
+unsigned int BootControl::GetNumberSlots() {
+ return num_slots_;
}
-unsigned int BootControl_getCurrentSlot(boot_control_module_t* module) {
- return reinterpret_cast<boot_control_private_t*>(module)->current_slot;
+unsigned int BootControl::GetCurrentSlot() {
+ return current_slot_;
}
-int BootControl_markBootSuccessful(boot_control_module_t* module) {
- boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module);
-
+bool BootControl::MarkBootSuccessful() {
bootloader_control bootctrl;
- if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
+ if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false;
- bootctrl.slot_info[bootctrl_module->current_slot].successful_boot = 1;
+ bootctrl.slot_info[current_slot_].successful_boot = 1;
// tries_remaining == 0 means that the slot is not bootable anymore, make
// sure we mark the current slot as bootable if it succeeds in the last
// attempt.
- bootctrl.slot_info[bootctrl_module->current_slot].tries_remaining = 1;
- if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
- return 0;
+ bootctrl.slot_info[current_slot_].tries_remaining = 1;
+ return UpdateAndSaveBootloaderControl(misc_device_, &bootctrl);
}
-int BootControl_setActiveBootSlot(boot_control_module_t* module, unsigned int slot) {
- boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module);
-
- if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) {
+bool BootControl::SetActiveBootSlot(unsigned int slot) {
+ if (slot >= kMaxNumSlots || slot >= num_slots_) {
// Invalid slot number.
- return -1;
+ return false;
}
bootloader_control bootctrl;
- if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
+ if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false;
// Set every other slot with a lower priority than the new "active" slot.
const unsigned int kActivePriority = 15;
const unsigned int kActiveTries = 6;
- for (unsigned int i = 0; i < bootctrl_module->num_slots; ++i) {
+ for (unsigned int i = 0; i < num_slots_; ++i) {
if (i != slot) {
if (bootctrl.slot_info[i].priority >= kActivePriority)
bootctrl.slot_info[i].priority = kActivePriority - 1;
@@ -299,103 +284,76 @@ int BootControl_setActiveBootSlot(boot_control_module_t* module, unsigned int sl
// used to cancel the pending update. We should only reset the verity_corrpted
// bit when attempting a new slot, otherwise the verity bit on the current
// slot would be flip.
- if (slot != bootctrl_module->current_slot) bootctrl.slot_info[slot].verity_corrupted = 0;
+ if (slot != current_slot_) bootctrl.slot_info[slot].verity_corrupted = 0;
- if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
- return 0;
+ return UpdateAndSaveBootloaderControl(misc_device_, &bootctrl);
}
-int BootControl_setSlotAsUnbootable(struct boot_control_module* module, unsigned int slot) {
- boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module);
-
- if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) {
+bool BootControl::SetSlotAsUnbootable(unsigned int slot) {
+ if (slot >= kMaxNumSlots || slot >= num_slots_) {
// Invalid slot number.
- return -1;
+ return false;
}
bootloader_control bootctrl;
- if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
+ if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false;
// The only way to mark a slot as unbootable, regardless of the priority is to
// set the tries_remaining to 0.
bootctrl.slot_info[slot].successful_boot = 0;
bootctrl.slot_info[slot].tries_remaining = 0;
- if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
- return 0;
+ return UpdateAndSaveBootloaderControl(misc_device_, &bootctrl);
}
-int BootControl_isSlotBootable(struct boot_control_module* module, unsigned int slot) {
- boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module);
-
- if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) {
+bool BootControl::IsSlotBootable(unsigned int slot) {
+ if (slot >= kMaxNumSlots || slot >= num_slots_) {
// Invalid slot number.
- return -1;
+ return false;
}
bootloader_control bootctrl;
- if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
+ if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false;
- return bootctrl.slot_info[slot].tries_remaining;
+ return bootctrl.slot_info[slot].tries_remaining != 0;
}
-int BootControl_isSlotMarkedSuccessful(struct boot_control_module* module, unsigned int slot) {
- boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module);
-
- if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) {
+bool BootControl::IsSlotMarkedSuccessful(unsigned int slot) {
+ if (slot >= kMaxNumSlots || slot >= num_slots_) {
// Invalid slot number.
- return -1;
+ return false;
}
bootloader_control bootctrl;
- if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
+ if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false;
return bootctrl.slot_info[slot].successful_boot && bootctrl.slot_info[slot].tries_remaining;
}
-const char* BootControl_getSuffix(boot_control_module_t* module, unsigned int slot) {
- if (slot >= kMaxNumSlots || slot >= reinterpret_cast<boot_control_private_t*>(module)->num_slots) {
- return NULL;
- }
- return kSlotSuffixes[slot];
+bool BootControl::IsValidSlot(unsigned int slot) {
+ return slot < kMaxNumSlots && slot < num_slots_;
}
-static int BootControl_open(const hw_module_t* module __unused, const char* id __unused,
- hw_device_t** device __unused) {
- /* Nothing to do currently. */
- return 0;
+bool BootControl::SetSnapshotMergeStatus(MergeStatus status) {
+ bootloader_control bootctrl;
+ if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false;
+
+ bootctrl.merge_status = (unsigned int)status;
+ return UpdateAndSaveBootloaderControl(misc_device_, &bootctrl);
+}
+
+MergeStatus BootControl::GetSnapshotMergeStatus() {
+ bootloader_control bootctrl;
+ if (!LoadBootloaderControl(misc_device_, &bootctrl)) return MergeStatus::UNKNOWN;
+
+ return (MergeStatus)bootctrl.merge_status;
+}
+
+const char* BootControl::GetSuffix(unsigned int slot) {
+ if (slot >= kMaxNumSlots || slot >= num_slots_) {
+ return nullptr;
+ }
+ return kSlotSuffixes[slot];
}
-struct hw_module_methods_t BootControl_methods = {
- .open = BootControl_open,
-};
-
-} // namespace
-
-boot_control_private_t HAL_MODULE_INFO_SYM = {
- .base =
- {
- .common =
- {
- .tag = HARDWARE_MODULE_TAG,
- .module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1,
- .hal_api_version = HARDWARE_HAL_API_VERSION,
- .id = BOOT_CONTROL_HARDWARE_MODULE_ID,
- .name = "AOSP reference bootctrl HAL",
- .author = "The Android Open Source Project",
- .methods = &BootControl_methods,
- },
- .init = BootControl_init,
- .getNumberSlots = BootControl_getNumberSlots,
- .getCurrentSlot = BootControl_getCurrentSlot,
- .markBootSuccessful = BootControl_markBootSuccessful,
- .setActiveBootSlot = BootControl_setActiveBootSlot,
- .setSlotAsUnbootable = BootControl_setSlotAsUnbootable,
- .isSlotBootable = BootControl_isSlotBootable,
- .getSuffix = BootControl_getSuffix,
- .isSlotMarkedSuccessful = BootControl_isSlotMarkedSuccessful,
- },
- .initialized = false,
- .misc_device = nullptr,
- .num_slots = 0,
- .current_slot = 0,
-};
+} // namespace bootable
+} // namespace android
diff --git a/bootloader_message/Android.bp b/bootloader_message/Android.bp
index 450dad08b..6443a077c 100644
--- a/bootloader_message/Android.bp
+++ b/bootloader_message/Android.bp
@@ -36,6 +36,18 @@ cc_library {
"libbootloader_message_defaults",
],
recovery_available: true,
+ host_supported: true,
+
+ target: {
+ host: {
+ shared_libs: [
+ "libcutils", // for strlcpy
+ ],
+ },
+ darwin: {
+ enabled: false,
+ },
+ }
}
cc_library_static {
@@ -44,4 +56,5 @@ cc_library_static {
"libbootloader_message_defaults",
],
vendor: true,
+ recovery_available: true,
}
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index c1ebeaa82..b15a9b9fd 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -20,6 +20,7 @@
#include <fcntl.h>
#include <string.h>
+#include <optional>
#include <string>
#include <string_view>
#include <vector>
@@ -30,10 +31,14 @@
#include <android-base/unique_fd.h>
#include <fstab/fstab.h>
+#ifndef __ANDROID__
+#include <cutils/memory.h> // for strlcpy
+#endif
+
using android::fs_mgr::Fstab;
using android::fs_mgr::ReadDefaultFstab;
-static std::string g_misc_device_for_test;
+static std::optional<std::string> g_misc_device_for_test;
// Exposed for test purpose.
void SetMiscBlockDeviceForTest(std::string_view misc_device) {
@@ -41,8 +46,8 @@ void SetMiscBlockDeviceForTest(std::string_view misc_device) {
}
static std::string get_misc_blk_device(std::string* err) {
- if (!g_misc_device_for_test.empty()) {
- return g_misc_device_for_test;
+ if (g_misc_device_for_test.has_value() && !g_misc_device_for_test->empty()) {
+ return *g_misc_device_for_test;
}
Fstab fstab;
if (!ReadDefaultFstab(&fstab)) {
@@ -179,6 +184,14 @@ bool write_bootloader_message(const std::vector<std::string>& options, std::stri
return write_bootloader_message(boot, err);
}
+bool write_bootloader_message_to(const std::vector<std::string>& options,
+ const std::string& misc_blk_device, std::string* err) {
+ bootloader_message boot = {};
+ update_bootloader_message_in_struct(&boot, options);
+
+ return write_bootloader_message_to(boot, misc_blk_device, err);
+}
+
bool update_bootloader_message(const std::vector<std::string>& options, std::string* err) {
bootloader_message boot;
if (!read_bootloader_message(&boot, err)) {
@@ -197,13 +210,15 @@ bool update_bootloader_message_in_struct(bootloader_message* boot,
memset(boot->recovery, 0, sizeof(boot->recovery));
strlcpy(boot->command, "boot-recovery", sizeof(boot->command));
- strlcpy(boot->recovery, "recovery\n", sizeof(boot->recovery));
+
+ std::string recovery = "recovery\n";
for (const auto& s : options) {
- strlcat(boot->recovery, s.c_str(), sizeof(boot->recovery));
+ recovery += s;
if (s.back() != '\n') {
- strlcat(boot->recovery, "\n", sizeof(boot->recovery));
+ recovery += '\n';
}
}
+ strlcpy(boot->recovery, recovery.c_str(), sizeof(boot->recovery));
return true;
}
diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h
index 95dd8f4c9..b78783083 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -163,8 +163,10 @@ struct bootloader_control {
uint8_t nb_slot : 3;
// Number of times left attempting to boot recovery.
uint8_t recovery_tries_remaining : 3;
+ // Status of any pending snapshot merge of dynamic partitions.
+ uint8_t merge_status : 3;
// Ensure 4-bytes alignment for slot_info field.
- uint8_t reserved0[2];
+ uint8_t reserved0[1];
// Per-slot information. Up to 4 slots.
struct slot_metadata slot_info[4];
// Reserved for further use.
@@ -208,6 +210,11 @@ bool write_bootloader_message_to(const bootloader_message& boot,
// set the command and recovery fields, and reset the rest.
bool write_bootloader_message(const std::vector<std::string>& options, std::string* err);
+// Write bootloader message (boots into recovery with the options) to the specific BCB device. Will
+// set the command and recovery fields, and reset the rest.
+bool write_bootloader_message_to(const std::vector<std::string>& options,
+ const std::string& misc_blk_device, std::string* err);
+
// Update bootloader message (boots into recovery with the options) to BCB. Will
// only update the command and recovery fields.
bool update_bootloader_message(const std::vector<std::string>& options, std::string* err);
diff --git a/edify/Android.bp b/edify/Android.bp
index 42947eb4e..73048d21c 100644
--- a/edify/Android.bp
+++ b/edify/Android.bp
@@ -16,6 +16,7 @@ cc_library_static {
name: "libedify",
host_supported: true,
+ vendor_available: true,
srcs: [
"expr.cpp",
diff --git a/edify/expr.cpp b/edify/expr.cpp
index c090eb28a..e5e0e240a 100644
--- a/edify/expr.cpp
+++ b/edify/expr.cpp
@@ -421,5 +421,5 @@ Value* ErrorAbort(State* state, CauseCode cause_code, const char* format, ...) {
return nullptr;
}
-State::State(const std::string& script, void* cookie)
- : script(script), cookie(cookie), error_code(kNoError), cause_code(kNoCause) {}
+State::State(const std::string& script, UpdaterInterface* interface)
+ : script(script), updater(interface), error_code(kNoError), cause_code(kNoCause) {}
diff --git a/edify/include/edify/expr.h b/edify/include/edify/expr.h
index 5cbd5e15d..cd9c70120 100644
--- a/edify/include/edify/expr.h
+++ b/edify/include/edify/expr.h
@@ -23,19 +23,20 @@
#include <string>
#include <vector>
+#include "edify/updater_interface.h"
+
// Forward declaration to avoid including "otautil/error_code.h".
enum ErrorCode : int;
enum CauseCode : int;
struct State {
- State(const std::string& script, void* cookie);
+ State(const std::string& script, UpdaterInterface* cookie);
// The source of the original script.
const std::string& script;
- // Optional pointer to app-specific data; the core of edify never
- // uses this value.
- void* cookie;
+ // A pointer to app-specific data; the libedify doesn't use this value.
+ UpdaterInterface* updater;
// The error message (if any) returned if the evaluation aborts.
// Should be empty initially, will be either empty or a string that
diff --git a/edify/include/edify/updater_interface.h b/edify/include/edify/updater_interface.h
new file mode 100644
index 000000000..aa977e3c8
--- /dev/null
+++ b/edify/include/edify/updater_interface.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <string>
+#include <string_view>
+
+struct ZipArchive;
+typedef ZipArchive* ZipArchiveHandle;
+
+class UpdaterRuntimeInterface;
+
+class UpdaterInterface {
+ public:
+ virtual ~UpdaterInterface() = default;
+
+ // Writes the message to command pipe, adds a new line in the end.
+ virtual void WriteToCommandPipe(const std::string_view message, bool flush = false) const = 0;
+
+ // Sends over the message to recovery to print it on the screen.
+ virtual void UiPrint(const std::string_view message) const = 0;
+
+ // Given the name of the block device, returns |name| for updates on the device; or the file path
+ // to the fake block device for simulations.
+ virtual std::string FindBlockDeviceName(const std::string_view name) const = 0;
+
+ virtual UpdaterRuntimeInterface* GetRuntime() const = 0;
+ virtual ZipArchiveHandle GetPackageHandle() const = 0;
+ virtual std::string GetResult() const = 0;
+ virtual uint8_t* GetMappedPackageAddress() const = 0;
+ virtual size_t GetMappedPackageLength() const = 0;
+};
diff --git a/edify/include/edify/updater_runtime_interface.h b/edify/include/edify/updater_runtime_interface.h
new file mode 100644
index 000000000..d3d26da64
--- /dev/null
+++ b/edify/include/edify/updater_runtime_interface.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+// This class serves as the base to updater runtime. It wraps the runtime dependent functions; and
+// updates on device and host simulations can have different implementations. e.g. block devices
+// during host simulation merely a temporary file. With this class, the caller side in registered
+// updater's functions will stay the same for both update and simulation.
+class UpdaterRuntimeInterface {
+ public:
+ virtual ~UpdaterRuntimeInterface() = default;
+
+ // Returns true if it's a runtime instance for simulation.
+ virtual bool IsSimulator() const = 0;
+
+ // Returns the value of system property |key|. If the property doesn't exist, returns
+ // |default_value|.
+ virtual std::string GetProperty(const std::string_view key,
+ const std::string_view default_value) const = 0;
+
+ // Given the name of the block device, returns |name| for updates on the device; or the file path
+ // to the fake block device for simulations.
+ virtual std::string FindBlockDeviceName(const std::string_view name) const = 0;
+
+ // Mounts the |location| on |mount_point|. Returns 0 on success.
+ virtual int Mount(const std::string_view location, const std::string_view mount_point,
+ const std::string_view fs_type, const std::string_view mount_options) = 0;
+
+ // Returns true if |mount_point| is mounted.
+ virtual bool IsMounted(const std::string_view mount_point) const = 0;
+
+ // Unmounts the |mount_point|. Returns a pair of results with the first value indicating
+ // if the |mount_point| is mounted, and the second value indicating the result of umount(2).
+ virtual std::pair<bool, int> Unmount(const std::string_view mount_point) = 0;
+
+ // Reads |filename| and puts its value to |content|.
+ virtual bool ReadFileToString(const std::string_view filename, std::string* content) const = 0;
+
+ // Updates the content of |filename| with |content|.
+ virtual bool WriteStringToFile(const std::string_view content,
+ const std::string_view filename) const = 0;
+
+ // Wipes the first |len| bytes of block device in |filename|.
+ virtual int WipeBlockDevice(const std::string_view filename, size_t len) const = 0;
+
+ // Starts a child process and runs the program with |args|. Uses vfork(2) if |is_vfork| is true.
+ virtual int RunProgram(const std::vector<std::string>& args, bool is_vfork) const = 0;
+
+ // Runs tune2fs with arguments |args|.
+ virtual int Tune2Fs(const std::vector<std::string>& args) const = 0;
+
+ // Dynamic partition related functions.
+ virtual bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) = 0;
+ virtual bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) = 0;
+ virtual bool UpdateDynamicPartitions(const std::string_view op_list_value) = 0;
+};
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 14f5e4bdc..202334997 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -30,10 +30,10 @@
#include "recovery_ui/ui.h"
static const std::vector<std::pair<std::string, Device::BuiltinAction>> kFastbootMenuActions{
- { "Reboot system now", Device::REBOOT },
+ { "Reboot system now", Device::REBOOT_FROM_FASTBOOT },
{ "Enter recovery", Device::ENTER_RECOVERY },
{ "Reboot to bootloader", Device::REBOOT_BOOTLOADER },
- { "Power off", Device::SHUTDOWN },
+ { "Power off", Device::SHUTDOWN_FROM_FASTBOOT },
};
Device::BuiltinAction StartFastboot(Device* device, const std::vector<std::string>& /* args */) {
diff --git a/fsck_unshare_blocks.cpp b/fsck_unshare_blocks.cpp
index e74f8ba6f..e0b2d966b 100644
--- a/fsck_unshare_blocks.cpp
+++ b/fsck_unshare_blocks.cpp
@@ -34,9 +34,9 @@
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/unique_fd.h>
-#include <fstab/fstab.h>
+#include <fs_mgr/roots.h>
-#include "otautil/roots.h"
+#include "recovery_utils/roots.h"
static constexpr const char* SYSTEM_E2FSCK_BIN = "/system/bin/e2fsck_static";
static constexpr const char* TMP_E2FSCK_BIN = "/tmp/e2fsck.bin";
@@ -120,7 +120,7 @@ bool do_fsck_unshare_blocks() {
std::vector<std::string> partitions = { "/odm", "/oem", "/product", "/vendor" };
// Temporarily mount system so we can copy e2fsck_static.
- std::string system_root = get_system_root();
+ auto system_root = android::fs_mgr::GetSystemRoot();
bool mounted = ensure_path_mounted_at(system_root, "/mnt/system") != -1;
partitions.push_back(system_root);
diff --git a/fuse_sideload/Android.bp b/fuse_sideload/Android.bp
index 8548548d2..9bf19eb85 100644
--- a/fuse_sideload/Android.bp
+++ b/fuse_sideload/Android.bp
@@ -34,6 +34,10 @@ cc_library {
"include",
],
+ static_libs: [
+ "libotautil",
+ ],
+
shared_libs: [
"libbase",
"libcrypto",
diff --git a/fuse_sideload/fuse_provider.cpp b/fuse_sideload/fuse_provider.cpp
index 58786f5f3..8fa1b5c2e 100644
--- a/fuse_sideload/fuse_provider.cpp
+++ b/fuse_sideload/fuse_provider.cpp
@@ -27,8 +27,11 @@
#include <functional>
#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
#include "fuse_sideload.h"
+#include "otautil/sysutil.h"
FuseFileDataProvider::FuseFileDataProvider(const std::string& path, uint32_t block_size) {
struct stat sb;
@@ -46,6 +49,11 @@ FuseFileDataProvider::FuseFileDataProvider(const std::string& path, uint32_t blo
fuse_block_size_ = block_size;
}
+std::unique_ptr<FuseDataProvider> FuseFileDataProvider::CreateFromFile(const std::string& path,
+ uint32_t block_size) {
+ return std::make_unique<FuseFileDataProvider>(path, block_size);
+}
+
bool FuseFileDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
uint32_t start_block) const {
uint64_t offset = static_cast<uint64_t>(start_block) * fuse_block_size_;
@@ -69,3 +77,79 @@ bool FuseFileDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_
void FuseFileDataProvider::Close() {
fd_.reset();
}
+
+FuseBlockDataProvider::FuseBlockDataProvider(uint64_t file_size, uint32_t fuse_block_size,
+ android::base::unique_fd&& fd,
+ uint32_t source_block_size, RangeSet ranges)
+ : FuseDataProvider(file_size, fuse_block_size),
+ fd_(std::move(fd)),
+ source_block_size_(source_block_size),
+ ranges_(std::move(ranges)) {
+ // Make sure the offset is also aligned with the blocks on the block device when we call
+ // ReadBlockAlignedData().
+ CHECK_EQ(0, fuse_block_size_ % source_block_size_);
+}
+
+bool FuseBlockDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+ uint32_t start_block) const {
+ uint64_t offset = static_cast<uint64_t>(start_block) * fuse_block_size_;
+ if (fetch_size > file_size_ || offset > file_size_ - fetch_size) {
+ LOG(ERROR) << "Out of bound read, offset: " << offset << ", fetch size: " << fetch_size
+ << ", file size " << file_size_;
+ return false;
+ }
+
+ auto read_ranges =
+ ranges_.GetSubRanges(offset / source_block_size_, fetch_size / source_block_size_);
+ if (!read_ranges) {
+ return false;
+ }
+
+ uint8_t* next_out = buffer;
+ for (const auto& [range_start, range_end] : read_ranges.value()) {
+ uint64_t bytes_start = static_cast<uint64_t>(range_start) * source_block_size_;
+ uint64_t bytes_to_read = static_cast<uint64_t>(range_end - range_start) * source_block_size_;
+ if (!android::base::ReadFullyAtOffset(fd_, next_out, bytes_to_read, bytes_start)) {
+ PLOG(ERROR) << "Failed to read " << bytes_to_read << " bytes at offset " << bytes_start;
+ return false;
+ }
+
+ next_out += bytes_to_read;
+ }
+
+ if (uint64_t tailing_bytes = fetch_size % source_block_size_; tailing_bytes != 0) {
+ // Calculate the offset to last partial block.
+ uint64_t tailing_offset =
+ read_ranges.value()
+ ? static_cast<uint64_t>((read_ranges->cend() - 1)->second) * source_block_size_
+ : static_cast<uint64_t>(start_block) * source_block_size_;
+ if (!android::base::ReadFullyAtOffset(fd_, next_out, tailing_bytes, tailing_offset)) {
+ PLOG(ERROR) << "Failed to read tailing " << tailing_bytes << " bytes at offset "
+ << tailing_offset;
+ return false;
+ }
+ }
+ return true;
+}
+
+std::unique_ptr<FuseDataProvider> FuseBlockDataProvider::CreateFromBlockMap(
+ const std::string& block_map_path, uint32_t fuse_block_size) {
+ auto block_map = BlockMapData::ParseBlockMapFile(block_map_path);
+ if (!block_map) {
+ return nullptr;
+ }
+
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(block_map.path().c_str(), O_RDONLY)));
+ if (fd == -1) {
+ PLOG(ERROR) << "Failed to open " << block_map.path();
+ return nullptr;
+ }
+
+ return std::unique_ptr<FuseBlockDataProvider>(
+ new FuseBlockDataProvider(block_map.file_size(), fuse_block_size, std::move(fd),
+ block_map.block_size(), block_map.block_ranges()));
+}
+
+void FuseBlockDataProvider::Close() {
+ fd_.reset();
+}
diff --git a/fuse_sideload/include/fuse_provider.h b/fuse_sideload/include/fuse_provider.h
index 59059cf9b..3cdaef33d 100644
--- a/fuse_sideload/include/fuse_provider.h
+++ b/fuse_sideload/include/fuse_provider.h
@@ -18,10 +18,13 @@
#include <stdint.h>
+#include <memory>
#include <string>
#include <android-base/unique_fd.h>
+#include "otautil/rangeset.h"
+
// This is the base class to read data from source and provide the data to FUSE.
class FuseDataProvider {
public:
@@ -41,6 +44,8 @@ class FuseDataProvider {
virtual bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
uint32_t start_block) const = 0;
+ virtual bool Valid() const = 0;
+
virtual void Close() {}
protected:
@@ -57,10 +62,13 @@ class FuseFileDataProvider : public FuseDataProvider {
public:
FuseFileDataProvider(const std::string& path, uint32_t block_size);
+ static std::unique_ptr<FuseDataProvider> CreateFromFile(const std::string& path,
+ uint32_t block_size);
+
bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
uint32_t start_block) const override;
- bool Valid() const {
+ bool Valid() const override {
return fd_ != -1;
}
@@ -70,3 +78,34 @@ class FuseFileDataProvider : public FuseDataProvider {
// The underlying source to read data from.
android::base::unique_fd fd_;
};
+
+// This class parses a block map and reads data from the underlying block device.
+class FuseBlockDataProvider : public FuseDataProvider {
+ public:
+ // Constructs the fuse provider from the block map.
+ static std::unique_ptr<FuseDataProvider> CreateFromBlockMap(const std::string& block_map_path,
+ uint32_t fuse_block_size);
+
+ RangeSet ranges() const {
+ return ranges_;
+ }
+
+ bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+ uint32_t start_block) const override;
+
+ bool Valid() const override {
+ return fd_ != -1;
+ }
+
+ void Close() override;
+
+ private:
+ FuseBlockDataProvider(uint64_t file_size, uint32_t fuse_block_size, android::base::unique_fd&& fd,
+ uint32_t source_block_size, RangeSet ranges);
+ // The underlying block device to read data from.
+ android::base::unique_fd fd_;
+ // The block size of the source block device.
+ uint32_t source_block_size_;
+ // The block ranges from the source block device that consist of the file
+ RangeSet ranges_;
+};
diff --git a/install/Android.bp b/install/Android.bp
index ea893a075..d4606e92c 100644
--- a/install/Android.bp
+++ b/install/Android.bp
@@ -19,10 +19,6 @@ cc_defaults {
"recovery_defaults",
],
- header_libs: [
- "libminadbd_headers",
- ],
-
shared_libs: [
"libbase",
"libbootloader_message",
@@ -32,7 +28,6 @@ cc_defaults {
"libfusesideload",
"libhidl-gen-utils",
"libhidlbase",
- "libhidltransport",
"liblog",
"libselinux",
"libtinyxml2",
@@ -42,12 +37,12 @@ cc_defaults {
],
static_libs: [
+ "librecovery_utils",
"libotautil",
// external dependencies
"libvintf_recovery",
"libvintf",
- "libfstab",
],
}
@@ -62,11 +57,16 @@ cc_library_static {
srcs: [
"adb_install.cpp",
"asn1_decoder.cpp",
- "fuse_sdcard_install.cpp",
+ "fuse_install.cpp",
"install.cpp",
"package.cpp",
"verifier.cpp",
"wipe_data.cpp",
+ "wipe_device.cpp",
+ ],
+
+ header_libs: [
+ "libminadbd_headers",
],
shared_libs: [
diff --git a/install/adb_install.cpp b/install/adb_install.cpp
index 9497df501..ee79a32c0 100644
--- a/install/adb_install.cpp
+++ b/install/adb_install.cpp
@@ -44,7 +44,7 @@
#include "fuse_sideload.h"
#include "install/install.h"
#include "install/wipe_data.h"
-#include "minadbd_types.h"
+#include "minadbd/types.h"
#include "otautil/sysutil.h"
#include "recovery_ui/device.h"
#include "recovery_ui/ui.h"
@@ -90,7 +90,7 @@ static bool WriteStatusToFd(MinadbdCommandStatus status, int fd) {
// Installs the package from FUSE. Returns the installation result and whether it should continue
// waiting for new commands.
-static auto AdbInstallPackageHandler(RecoveryUI* ui, int* result) {
+static auto AdbInstallPackageHandler(RecoveryUI* ui, InstallResult* result) {
// How long (in seconds) we wait for the package path to be ready. It doesn't need to be too long
// because the minadbd service has already issued an install command. FUSE_SIDELOAD_HOST_PATHNAME
// will start to exist once the host connects and starts serving a package. Poll for its
@@ -110,7 +110,11 @@ static auto AdbInstallPackageHandler(RecoveryUI* ui, int* result) {
break;
}
}
- *result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0, ui);
+
+ auto package =
+ Package::CreateFilePackage(FUSE_SIDELOAD_HOST_PATHNAME,
+ std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
+ *result = InstallPackage(package.get(), FUSE_SIDELOAD_HOST_PATHNAME, false, 0, ui);
break;
}
@@ -120,7 +124,7 @@ static auto AdbInstallPackageHandler(RecoveryUI* ui, int* result) {
return std::make_pair(*result == INSTALL_SUCCESS, should_continue);
}
-static auto AdbRebootHandler(MinadbdCommand command, int* result,
+static auto AdbRebootHandler(MinadbdCommand command, InstallResult* result,
Device::BuiltinAction* reboot_action) {
// Use Device::REBOOT_{FASTBOOT,RECOVERY,RESCUE}, instead of the ones with ENTER_. This allows
// rebooting back into fastboot/recovery/rescue mode through bootloader, which may use a newly
@@ -331,7 +335,7 @@ static void CreateMinadbdServiceAndExecuteCommands(
signal(SIGPIPE, SIG_DFL);
}
-int ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action) {
+InstallResult ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action) {
// Save the usb state to restore after the sideload operation.
std::string usb_state = android::base::GetProperty("sys.usb.state", "none");
// Clean up state and stop adbd.
@@ -342,7 +346,7 @@ int ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot
RecoveryUI* ui = device->GetUI();
- int install_result = INSTALL_ERROR;
+ InstallResult install_result = INSTALL_ERROR;
std::map<MinadbdCommand, CommandFunction> command_map{
{ MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, ui, &install_result) },
{ MinadbdCommand::kRebootAndroid, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootAndroid,
diff --git a/install/fuse_sdcard_install.cpp b/install/fuse_install.cpp
index 1aa8768e7..143b5d3fb 100644
--- a/install/fuse_sdcard_install.cpp
+++ b/install/fuse_install.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "install/fuse_sdcard_install.h"
+#include "install/fuse_install.h"
#include <dirent.h>
#include <signal.h>
@@ -27,6 +27,7 @@
#include <algorithm>
#include <functional>
#include <memory>
+#include <string>
#include <vector>
#include <android-base/logging.h>
@@ -36,7 +37,7 @@
#include "fuse_provider.h"
#include "fuse_sideload.h"
#include "install/install.h"
-#include "otautil/roots.h"
+#include "recovery_utils/roots.h"
static constexpr const char* SDCARD_ROOT = "/sdcard";
// How long (in seconds) we wait for the fuse-provided package file to
@@ -74,7 +75,8 @@ static std::string BrowseDirectory(const std::string& path, Device* device, Reco
// Skip "." and ".." entries.
if (name == "." || name == "..") continue;
dirs.push_back(name + "/");
- } else if (de->d_type == DT_REG && android::base::EndsWithIgnoreCase(name, ".zip")) {
+ } else if (de->d_type == DT_REG && (android::base::EndsWithIgnoreCase(name, ".zip") ||
+ android::base::EndsWithIgnoreCase(name, ".map"))) {
entries.push_back(name);
}
}
@@ -119,49 +121,44 @@ static std::string BrowseDirectory(const std::string& path, Device* device, Reco
// Unreachable.
}
-static bool StartSdcardFuse(const std::string& path) {
- auto file_data_reader = std::make_unique<FuseFileDataProvider>(path, 65536);
-
- if (!file_data_reader->Valid()) {
+static bool StartInstallPackageFuse(std::string_view path) {
+ if (path.empty()) {
return false;
}
- // The installation process expects to find the sdcard unmounted. Unmount it with MNT_DETACH so
- // that our open file continues to work but new references see it as unmounted.
- umount2("/sdcard", MNT_DETACH);
+ constexpr auto FUSE_BLOCK_SIZE = 65536;
+ bool is_block_map = android::base::ConsumePrefix(&path, "@");
+ auto fuse_data_provider =
+ is_block_map ? FuseBlockDataProvider::CreateFromBlockMap(std::string(path), FUSE_BLOCK_SIZE)
+ : FuseFileDataProvider::CreateFromFile(std::string(path), FUSE_BLOCK_SIZE);
- return run_fuse_sideload(std::move(file_data_reader)) == 0;
-}
-
-int ApplyFromSdcard(Device* device, RecoveryUI* ui) {
- if (ensure_path_mounted(SDCARD_ROOT) != 0) {
- LOG(ERROR) << "\n-- Couldn't mount " << SDCARD_ROOT << ".\n";
- return INSTALL_ERROR;
+ if (!fuse_data_provider || !fuse_data_provider->Valid()) {
+ LOG(ERROR) << "Failed to create fuse data provider.";
+ return false;
}
- std::string path = BrowseDirectory(SDCARD_ROOT, device, ui);
- if (path.empty()) {
- LOG(ERROR) << "\n-- No package file selected.\n";
- ensure_path_unmounted(SDCARD_ROOT);
- return INSTALL_ERROR;
+ if (android::base::StartsWith(path, SDCARD_ROOT)) {
+ // The installation process expects to find the sdcard unmounted. Unmount it with MNT_DETACH so
+ // that our open file continues to work but new references see it as unmounted.
+ umount2(SDCARD_ROOT, MNT_DETACH);
}
- ui->Print("\n-- Install %s ...\n", path.c_str());
- SetSdcardUpdateBootloaderMessage();
+ return run_fuse_sideload(std::move(fuse_data_provider)) == 0;
+}
+InstallResult InstallWithFuseFromPath(std::string_view path, RecoveryUI* ui) {
// We used to use fuse in a thread as opposed to a process. Since accessing
// through fuse involves going from kernel to userspace to kernel, it leads
// to deadlock when a page fault occurs. (Bug: 26313124)
pid_t child;
if ((child = fork()) == 0) {
- bool status = StartSdcardFuse(path);
+ bool status = StartInstallPackageFuse(path);
_exit(status ? EXIT_SUCCESS : EXIT_FAILURE);
}
- // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the fuse in child
- // process is ready.
- int result = INSTALL_ERROR;
+ // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the fuse in child process is ready.
+ InstallResult result = INSTALL_ERROR;
int status;
bool waited = false;
for (int i = 0; i < SDCARD_INSTALL_TIMEOUT; ++i) {
@@ -183,8 +180,11 @@ int ApplyFromSdcard(Device* device, RecoveryUI* ui) {
break;
}
}
-
- result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0 /*retry_count*/, ui);
+ auto package =
+ Package::CreateFilePackage(FUSE_SIDELOAD_HOST_PATHNAME,
+ std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
+ result =
+ InstallPackage(package.get(), FUSE_SIDELOAD_HOST_PATHNAME, false, 0 /* retry_count */, ui);
break;
}
@@ -201,6 +201,32 @@ int ApplyFromSdcard(Device* device, RecoveryUI* ui) {
LOG(ERROR) << "Error exit from the fuse process: " << WEXITSTATUS(status);
}
+ return result;
+}
+
+InstallResult ApplyFromSdcard(Device* device) {
+ auto ui = device->GetUI();
+ if (ensure_path_mounted(SDCARD_ROOT) != 0) {
+ LOG(ERROR) << "\n-- Couldn't mount " << SDCARD_ROOT << ".\n";
+ return INSTALL_ERROR;
+ }
+
+ std::string path = BrowseDirectory(SDCARD_ROOT, device, ui);
+ if (path.empty()) {
+ LOG(ERROR) << "\n-- No package file selected.\n";
+ ensure_path_unmounted(SDCARD_ROOT);
+ return INSTALL_ERROR;
+ }
+
+ // Hint the install function to read from a block map file.
+ if (android::base::EndsWithIgnoreCase(path, ".map")) {
+ path = "@" + path;
+ }
+
+ ui->Print("\n-- Install %s ...\n", path.c_str());
+ SetSdcardUpdateBootloaderMessage();
+
+ auto result = InstallWithFuseFromPath(path, ui);
ensure_path_unmounted(SDCARD_ROOT);
return result;
}
diff --git a/install/include/install/adb_install.h b/install/include/install/adb_install.h
index 3a0a81747..880022361 100644
--- a/install/include/install/adb_install.h
+++ b/install/include/install/adb_install.h
@@ -16,9 +16,10 @@
#pragma once
-#include <recovery_ui/device.h>
+#include "install/install.h"
+#include "recovery_ui/device.h"
-// Applies a package via `adb sideload` or `adb rescue`. Returns the install result (in `enum
-// InstallResult`). When a reboot has been requested, INSTALL_REBOOT will be the return value, with
-// the reboot target set in reboot_action.
-int ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action);
+// Applies a package via `adb sideload` or `adb rescue`. Returns the install result. When a reboot
+// has been requested, INSTALL_REBOOT will be the return value, with the reboot target set in
+// reboot_action.
+InstallResult ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action);
diff --git a/install/include/install/fuse_install.h b/install/include/install/fuse_install.h
new file mode 100644
index 000000000..63b116aeb
--- /dev/null
+++ b/install/include/install/fuse_install.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string_view>
+
+#include "install/install.h"
+#include "recovery_ui/device.h"
+#include "recovery_ui/ui.h"
+
+// Starts FUSE with the package from |path| as the data source. And installs the package from
+// |FUSE_SIDELOAD_HOST_PATHNAME|. The |path| can point to the location of a package zip file or a
+// block map file with the prefix '@'; e.g. /sdcard/package.zip, @/cache/recovery/block.map.
+InstallResult InstallWithFuseFromPath(std::string_view path, RecoveryUI* ui);
+
+InstallResult ApplyFromSdcard(Device* device);
diff --git a/install/include/install/install.h b/install/include/install/install.h
index c0a8f1f4c..b4b3a9149 100644
--- a/install/include/install/install.h
+++ b/install/include/install/install.h
@@ -44,11 +44,12 @@ enum class OtaType {
BRICK,
};
-// Installs the given update package. This function should also wipe the cache partition after a
-// successful installation if |should_wipe_cache| is true or an updater command asks to wipe the
-// cache.
-int install_package(const std::string& package, bool should_wipe_cache, bool needs_mount,
- int retry_count, RecoveryUI* ui);
+// Installs the given update package. The package_id is a string provided by the caller (e.g. the
+// package path) to identify the package and log to last_install. This function should also wipe the
+// cache partition after a successful installation if |should_wipe_cache| is true or an updater
+// command asks to wipe the cache.
+InstallResult InstallPackage(Package* package, const std::string_view package_id,
+ bool should_wipe_cache, int retry_count, RecoveryUI* ui);
// Verifies the package by ota keys. Returns true if the package is verified successfully,
// otherwise returns false.
@@ -58,14 +59,11 @@ bool verify_package(Package* package, RecoveryUI* ui);
// result to |metadata|. Return true if succeed, otherwise return false.
bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata);
-// Reads the "recovery.wipe" entry in the zip archive returns a list of partitions to wipe.
-std::vector<std::string> GetWipePartitionList(Package* wipe_package);
-
// Verifies the compatibility info in a Treble-compatible package. Returns true directly if the
// entry doesn't exist.
bool verify_package_compatibility(ZipArchiveHandle package_zip);
-// Checks if the the metadata in the OTA package has expected values. Returns 0 on success.
-// Mandatory checks: ota-type, pre-device and serial number(if presents)
-// AB OTA specific checks: pre-build version, fingerprint, timestamp.
-int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type);
+// Checks if the metadata in the OTA package has expected values. Mandatory checks: ota-type,
+// pre-device and serial number (if presents). A/B OTA specific checks: pre-build version,
+// fingerprint, timestamp.
+bool CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type);
diff --git a/install/include/install/package.h b/install/include/install/package.h
index cd44d10be..0b4233238 100644
--- a/install/include/install/package.h
+++ b/install/include/install/package.h
@@ -28,6 +28,11 @@
#include "verifier.h"
+enum class PackageType {
+ kMemory,
+ kFile,
+};
+
// This class serves as a wrapper for an OTA update package. It aims to provide the common
// interface for both packages loaded in memory and packages read from fd.
class Package : public VerifierInterface {
@@ -41,6 +46,10 @@ class Package : public VerifierInterface {
virtual ~Package() = default;
+ virtual PackageType GetType() const = 0;
+
+ virtual std::string GetPath() const = 0;
+
// Opens the package as a zip file and returns the ZipArchiveHandle.
virtual ZipArchiveHandle GetZipArchiveHandle() = 0;
diff --git a/install/include/install/fuse_sdcard_install.h b/install/include/install/wipe_device.h
index d9214ca3b..c60b99997 100644
--- a/install/include/install/fuse_sdcard_install.h
+++ b/install/include/install/wipe_device.h
@@ -16,7 +16,14 @@
#pragma once
+#include <string>
+#include <vector>
+
+#include "install/package.h"
#include "recovery_ui/device.h"
-#include "recovery_ui/ui.h"
-int ApplyFromSdcard(Device* device, RecoveryUI* ui);
+// Wipes the current A/B device, with a secure wipe of all the partitions in RECOVERY_WIPE.
+bool WipeAbDevice(Device* device, size_t wipe_package_size);
+
+// Reads the "recovery.wipe" entry in the zip archive returns a list of partitions to wipe.
+std::vector<std::string> GetWipePartitionList(Package* wipe_package);
diff --git a/install/include/private/setup_commands.h b/install/include/private/setup_commands.h
index 7fdc741d6..dcff76112 100644
--- a/install/include/private/setup_commands.h
+++ b/install/include/private/setup_commands.h
@@ -27,13 +27,13 @@
// |zip| located at |package|. Stores the command line that should be called into |cmd|. The
// |status_fd| is the file descriptor the child process should use to report back the progress of
// the update.
-int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
- int status_fd, std::vector<std::string>* cmd);
+bool SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
+ int status_fd, std::vector<std::string>* cmd);
// Sets up the commands for an A/B update. Extracts the needed entries from the open zip archive
// |zip| located at |package|. Stores the command line that should be called into |cmd|. The
// |status_fd| is the file descriptor the child process should use to report back the progress of
// the update. Note that since this applies to the sideloading flow only, it takes one less
// parameter |retry_count| than the non-A/B version.
-int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
- std::vector<std::string>* cmd);
+bool SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
+ std::vector<std::string>* cmd);
diff --git a/install/install.cpp b/install/install.cpp
index e2d470096..9166f9cfb 100644
--- a/install/install.cpp
+++ b/install/install.cpp
@@ -51,16 +51,17 @@
#include "install/wipe_data.h"
#include "otautil/error_code.h"
#include "otautil/paths.h"
-#include "otautil/roots.h"
#include "otautil/sysutil.h"
-#include "otautil/thermalutil.h"
#include "private/setup_commands.h"
#include "recovery_ui/ui.h"
+#include "recovery_utils/roots.h"
+#include "recovery_utils/thermalutil.h"
using namespace std::chrono_literals;
static constexpr int kRecoveryApiVersion = 3;
-// Assert the version defined in code and in Android.mk are consistent.
+// We define RECOVERY_API_VERSION in Android.mk, which will be picked up by build system and packed
+// into target_files.zip. Assert the version defined in code and in Android.mk are consistent.
static_assert(kRecoveryApiVersion == RECOVERY_API_VERSION, "Mismatching recovery API versions.");
// Default allocation of progress bar segments to operations
@@ -73,9 +74,8 @@ bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::st
CHECK(metadata != nullptr);
static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
- ZipString path(METADATA_PATH);
ZipEntry entry;
- if (FindEntry(zip, path, &entry) != 0) {
+ if (FindEntry(zip, METADATA_PATH, &entry) != 0) {
LOG(ERROR) << "Failed to find " << METADATA_PATH;
return false;
}
@@ -139,14 +139,14 @@ static void ReadSourceTargetBuild(const std::map<std::string, std::string>& meta
// Checks the build version, fingerprint and timestamp in the metadata of the A/B package.
// Downgrading is not allowed unless explicitly enabled in the package and only for
// incremental packages.
-static int CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata) {
+static bool CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata) {
// Incremental updates should match the current build.
auto device_pre_build = android::base::GetProperty("ro.build.version.incremental", "");
auto pkg_pre_build = get_value(metadata, "pre-build-incremental");
if (!pkg_pre_build.empty() && pkg_pre_build != device_pre_build) {
LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected "
<< device_pre_build;
- return INSTALL_ERROR;
+ return false;
}
auto device_fingerprint = android::base::GetProperty("ro.build.fingerprint", "");
@@ -154,7 +154,7 @@ static int CheckAbSpecificMetadata(const std::map<std::string, std::string>& met
if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != device_fingerprint) {
LOG(ERROR) << "Package is for source build " << pkg_pre_build_fingerprint << " but expected "
<< device_fingerprint;
- return INSTALL_ERROR;
+ return false;
}
// Check for downgrade version.
@@ -172,36 +172,36 @@ static int CheckAbSpecificMetadata(const std::map<std::string, std::string>& met
"newer than timestamp "
<< build_timestamp << " but package has timestamp " << pkg_post_timestamp
<< " and downgrade not allowed.";
- return INSTALL_ERROR;
+ return false;
}
if (pkg_pre_build_fingerprint.empty()) {
LOG(ERROR) << "Downgrade package must have a pre-build version set, not allowed.";
- return INSTALL_ERROR;
+ return false;
}
}
- return 0;
+ return true;
}
-int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type) {
+bool CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type) {
auto package_ota_type = get_value(metadata, "ota-type");
auto expected_ota_type = OtaTypeToString(ota_type);
if (ota_type != OtaType::AB && ota_type != OtaType::BRICK) {
LOG(INFO) << "Skip package metadata check for ota type " << expected_ota_type;
- return 0;
+ return true;
}
if (package_ota_type != expected_ota_type) {
LOG(ERROR) << "Unexpected ota package type, expects " << expected_ota_type << ", actual "
<< package_ota_type;
- return INSTALL_ERROR;
+ return false;
}
auto device = android::base::GetProperty("ro.product.device", "");
auto pkg_device = get_value(metadata, "pre-device");
if (pkg_device != device || pkg_device.empty()) {
LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << device;
- return INSTALL_ERROR;
+ return false;
}
// We allow the package to not have any serialno; and we also allow it to carry multiple serial
@@ -218,7 +218,7 @@ int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, Ota
}
if (!serial_number_match) {
LOG(ERROR) << "Package is for serial " << pkg_serial_no;
- return INSTALL_ERROR;
+ return false;
}
}
@@ -226,21 +226,20 @@ int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, Ota
return CheckAbSpecificMetadata(metadata);
}
- return 0;
+ return true;
}
-int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
- std::vector<std::string>* cmd) {
+bool SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
+ std::vector<std::string>* cmd) {
CHECK(cmd != nullptr);
// For A/B updates we extract the payload properties to a buffer and obtain the RAW payload offset
// in the zip file.
static constexpr const char* AB_OTA_PAYLOAD_PROPERTIES = "payload_properties.txt";
- ZipString property_name(AB_OTA_PAYLOAD_PROPERTIES);
ZipEntry properties_entry;
- if (FindEntry(zip, property_name, &properties_entry) != 0) {
+ if (FindEntry(zip, AB_OTA_PAYLOAD_PROPERTIES, &properties_entry) != 0) {
LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD_PROPERTIES;
- return INSTALL_CORRUPT;
+ return false;
}
uint32_t properties_entry_length = properties_entry.uncompressed_length;
std::vector<uint8_t> payload_properties(properties_entry_length);
@@ -248,15 +247,14 @@ int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int
ExtractToMemory(zip, &properties_entry, payload_properties.data(), properties_entry_length);
if (err != 0) {
LOG(ERROR) << "Failed to extract " << AB_OTA_PAYLOAD_PROPERTIES << ": " << ErrorCodeString(err);
- return INSTALL_CORRUPT;
+ return false;
}
static constexpr const char* AB_OTA_PAYLOAD = "payload.bin";
- ZipString payload_name(AB_OTA_PAYLOAD);
ZipEntry payload_entry;
- if (FindEntry(zip, payload_name, &payload_entry) != 0) {
+ if (FindEntry(zip, AB_OTA_PAYLOAD, &payload_entry) != 0) {
LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD;
- return INSTALL_CORRUPT;
+ return false;
}
long payload_offset = payload_entry.offset;
*cmd = {
@@ -266,20 +264,19 @@ int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int
"--headers=" + std::string(payload_properties.begin(), payload_properties.end()),
android::base::StringPrintf("--status_fd=%d", status_fd),
};
- return 0;
+ return true;
}
-int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
- int status_fd, std::vector<std::string>* cmd) {
+bool SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
+ int status_fd, std::vector<std::string>* cmd) {
CHECK(cmd != nullptr);
// In non-A/B updates we extract the update binary from the package.
static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
- ZipString binary_name(UPDATE_BINARY_NAME);
ZipEntry binary_entry;
- if (FindEntry(zip, binary_name, &binary_entry) != 0) {
+ if (FindEntry(zip, UPDATE_BINARY_NAME, &binary_entry) != 0) {
LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME;
- return INSTALL_CORRUPT;
+ return false;
}
const std::string binary_path = Paths::Get().temporary_update_binary();
@@ -288,13 +285,12 @@ int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, i
open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755));
if (fd == -1) {
PLOG(ERROR) << "Failed to create " << binary_path;
- return INSTALL_ERROR;
+ return false;
}
- int32_t error = ExtractEntryToFile(zip, &binary_entry, fd);
- if (error != 0) {
+ if (auto error = ExtractEntryToFile(zip, &binary_entry, fd); error != 0) {
LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error);
- return INSTALL_ERROR;
+ return false;
}
// When executing the update binary contained in the package, the arguments passed are:
@@ -311,7 +307,7 @@ int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, i
if (retry_count > 0) {
cmd->push_back("retry");
}
- return 0;
+ return true;
}
static void log_max_temperature(int* max_temperature, const std::atomic<bool>& logger_finished) {
@@ -325,21 +321,25 @@ static void log_max_temperature(int* max_temperature, const std::atomic<bool>& l
}
// If the package contains an update binary, extract it and run it.
-static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache,
- std::vector<std::string>* log_buffer, int retry_count,
- int* max_temperature, RecoveryUI* ui) {
+static InstallResult TryUpdateBinary(Package* package, bool* wipe_cache,
+ std::vector<std::string>* log_buffer, int retry_count,
+ int* max_temperature, RecoveryUI* ui) {
std::map<std::string, std::string> metadata;
+ auto zip = package->GetZipArchiveHandle();
if (!ReadMetadataFromPackage(zip, &metadata)) {
LOG(ERROR) << "Failed to parse metadata in the zip file";
return INSTALL_CORRUPT;
}
bool is_ab = android::base::GetBoolProperty("ro.build.ab_update", false);
- // Verifies against the metadata in the package first.
- if (int check_status = is_ab ? CheckPackageMetadata(metadata, OtaType::AB) : 0;
- check_status != 0) {
+ if (is_ab) {
+ CHECK(package->GetType() == PackageType::kFile);
+ }
+
+ // Verify against the metadata in the package first.
+ if (is_ab && !CheckPackageMetadata(metadata, OtaType::AB)) {
log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
- return check_status;
+ return INSTALL_ERROR;
}
ReadSourceTargetBuild(metadata, log_buffer);
@@ -385,13 +385,15 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b
// updater requests logging the string (e.g. cause of the failure).
//
+ std::string package_path = package->GetPath();
+
std::vector<std::string> args;
- if (int update_status =
- is_ab ? SetUpAbUpdateCommands(package, zip, pipe_write.get(), &args)
- : SetUpNonAbUpdateCommands(package, zip, retry_count, pipe_write.get(), &args);
- update_status != 0) {
+ if (auto setup_result =
+ is_ab ? SetUpAbUpdateCommands(package_path, zip, pipe_write.get(), &args)
+ : SetUpNonAbUpdateCommands(package_path, zip, retry_count, pipe_write.get(), &args);
+ !setup_result) {
log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
- return update_status;
+ return INSTALL_CORRUPT;
}
pid_t pid = fork();
@@ -490,11 +492,11 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b
}
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) != EXIT_SUCCESS) {
- LOG(ERROR) << "Error in " << package << " (status " << WEXITSTATUS(status) << ")";
+ LOG(ERROR) << "Error in " << package_path << " (status " << WEXITSTATUS(status) << ")";
return INSTALL_ERROR;
}
} else if (WIFSIGNALED(status)) {
- LOG(ERROR) << "Error in " << package << " (killed by signal " << WTERMSIG(status) << ")";
+ LOG(ERROR) << "Error in " << package_path << " (killed by signal " << WTERMSIG(status) << ")";
return INSTALL_ERROR;
} else {
LOG(FATAL) << "Invalid status code " << status;
@@ -503,16 +505,15 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b
return INSTALL_SUCCESS;
}
-// Verifes the compatibility info in a Treble-compatible package. Returns true directly if the
+// Verifies the compatibility info in a Treble-compatible package. Returns true directly if the
// entry doesn't exist. Note that the compatibility info is packed in a zip file inside the OTA
// package.
bool verify_package_compatibility(ZipArchiveHandle package_zip) {
LOG(INFO) << "Verifying package compatibility...";
static constexpr const char* COMPATIBILITY_ZIP_ENTRY = "compatibility.zip";
- ZipString compatibility_entry_name(COMPATIBILITY_ZIP_ENTRY);
ZipEntry compatibility_entry;
- if (FindEntry(package_zip, compatibility_entry_name, &compatibility_entry) != 0) {
+ if (FindEntry(package_zip, COMPATIBILITY_ZIP_ENTRY, &compatibility_entry) != 0) {
LOG(INFO) << "Package doesn't contain " << COMPATIBILITY_ZIP_ENTRY << " entry";
return true;
}
@@ -536,7 +537,7 @@ bool verify_package_compatibility(ZipArchiveHandle package_zip) {
// Iterate all the entries inside COMPATIBILITY_ZIP_ENTRY and read the contents.
void* cookie;
- ret = StartIteration(zip_handle, &cookie, nullptr, nullptr);
+ ret = StartIteration(zip_handle, &cookie);
if (ret != 0) {
LOG(ERROR) << "Failed to start iterating zip entries: " << ErrorCodeString(ret);
CloseArchive(zip_handle);
@@ -546,13 +547,13 @@ bool verify_package_compatibility(ZipArchiveHandle package_zip) {
std::vector<std::string> compatibility_info;
ZipEntry info_entry;
- ZipString info_name;
+ std::string_view info_name;
while (Next(cookie, &info_entry, &info_name) == 0) {
std::string content(info_entry.uncompressed_length, '\0');
int32_t ret = ExtractToMemory(zip_handle, &info_entry, reinterpret_cast<uint8_t*>(&content[0]),
info_entry.uncompressed_length);
if (ret != 0) {
- LOG(ERROR) << "Failed to read " << info_name.name << ": " << ErrorCodeString(ret);
+ LOG(ERROR) << "Failed to read " << info_name << ": " << ErrorCodeString(ret);
CloseArchive(zip_handle);
return false;
}
@@ -571,36 +572,16 @@ bool verify_package_compatibility(ZipArchiveHandle package_zip) {
return false;
}
-static int really_install_package(const std::string& path, bool* wipe_cache, bool needs_mount,
- std::vector<std::string>* log_buffer, int retry_count,
- int* max_temperature, RecoveryUI* ui) {
+static InstallResult VerifyAndInstallPackage(Package* package, bool* wipe_cache,
+ std::vector<std::string>* log_buffer, int retry_count,
+ int* max_temperature, RecoveryUI* ui) {
ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
- ui->Print("Finding update package...\n");
// Give verification half the progress bar...
ui->SetProgressType(RecoveryUI::DETERMINATE);
ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
- LOG(INFO) << "Update location: " << path;
-
- // Map the update package into memory.
- ui->Print("Opening update package...\n");
-
- if (needs_mount) {
- if (path[0] == '@') {
- ensure_path_mounted(path.substr(1));
- } else {
- ensure_path_mounted(path);
- }
- }
-
- auto package = Package::CreateMemoryPackage(
- path, std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
- if (!package) {
- log_buffer->push_back(android::base::StringPrintf("error: %d", kMapFileFailure));
- return INSTALL_CORRUPT;
- }
// Verify package.
- if (!verify_package(package.get(), ui)) {
+ if (!verify_package(package, ui)) {
log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
return INSTALL_CORRUPT;
}
@@ -624,32 +605,37 @@ static int really_install_package(const std::string& path, bool* wipe_cache, boo
ui->Print("Retry attempt: %d\n", retry_count);
}
ui->SetEnableReboot(false);
- int result =
- try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature, ui);
+ auto result = TryUpdateBinary(package, wipe_cache, log_buffer, retry_count, max_temperature, ui);
ui->SetEnableReboot(true);
ui->Print("\n");
return result;
}
-int install_package(const std::string& path, bool should_wipe_cache, bool needs_mount,
- int retry_count, RecoveryUI* ui) {
- CHECK(!path.empty());
-
+InstallResult InstallPackage(Package* package, const std::string_view package_id,
+ bool should_wipe_cache, int retry_count, RecoveryUI* ui) {
auto start = std::chrono::system_clock::now();
int start_temperature = GetMaxValueFromThermalZone();
int max_temperature = start_temperature;
- int result;
+ InstallResult result;
std::vector<std::string> log_buffer;
- if (setup_install_mounts() != 0) {
+
+ ui->Print("Supported API: %d\n", kRecoveryApiVersion);
+
+ ui->Print("Finding update package...\n");
+ LOG(INFO) << "Update package id: " << package_id;
+ if (!package) {
+ log_buffer.push_back(android::base::StringPrintf("error: %d", kMapFileFailure));
+ result = INSTALL_CORRUPT;
+ } else if (setup_install_mounts() != 0) {
LOG(ERROR) << "failed to set up expected mounts for install; aborting";
result = INSTALL_ERROR;
} else {
bool updater_wipe_cache = false;
- result = really_install_package(path, &updater_wipe_cache, needs_mount, &log_buffer,
- retry_count, &max_temperature, ui);
+ result = VerifyAndInstallPackage(package, &updater_wipe_cache, &log_buffer, retry_count,
+ &max_temperature, ui);
should_wipe_cache = should_wipe_cache || updater_wipe_cache;
}
@@ -677,7 +663,7 @@ int install_package(const std::string& path, bool should_wipe_cache, bool needs_
// The first two lines need to be the package name and install result.
std::vector<std::string> log_header = {
- path,
+ std::string(package_id),
result == INSTALL_SUCCESS ? "1" : "0",
"time_total: " + std::to_string(time_total),
"retry: " + std::to_string(retry_count),
diff --git a/install/package.cpp b/install/package.cpp
index 4402f4855..86fc0647d 100644
--- a/install/package.cpp
+++ b/install/package.cpp
@@ -40,12 +40,20 @@ class MemoryPackage : public Package {
~MemoryPackage() override;
+ PackageType GetType() const override {
+ return PackageType::kMemory;
+ }
+
// Memory maps the package file if necessary. Initializes the start address and size of the
// package.
uint64_t GetPackageSize() const override {
return package_size_;
}
+ std::string GetPath() const override {
+ return path_;
+ }
+
bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) override;
ZipArchiveHandle GetZipArchiveHandle() override;
@@ -82,10 +90,18 @@ class FilePackage : public Package {
~FilePackage() override;
+ PackageType GetType() const override {
+ return PackageType::kFile;
+ }
+
uint64_t GetPackageSize() const override {
return package_size_;
}
+ std::string GetPath() const override {
+ return path_;
+ }
+
bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) override;
ZipArchiveHandle GetZipArchiveHandle() override;
@@ -253,7 +269,7 @@ ZipArchiveHandle FilePackage::GetZipArchiveHandle() {
return zip_handle_;
}
- if (auto err = OpenArchiveFd(fd_.get(), path_.c_str(), &zip_handle_); err != 0) {
+ if (auto err = OpenArchiveFd(fd_.get(), path_.c_str(), &zip_handle_, false); err != 0) {
LOG(ERROR) << "Can't open package" << path_ << " : " << ErrorCodeString(err);
return nullptr;
}
diff --git a/install/verifier.cpp b/install/verifier.cpp
index 6ba1d77c3..ab750442d 100644
--- a/install/verifier.cpp
+++ b/install/verifier.cpp
@@ -311,8 +311,7 @@ int verify_file(VerifierInterface* package, const std::vector<Certificate>& keys
static std::vector<Certificate> IterateZipEntriesAndSearchForKeys(const ZipArchiveHandle& handle) {
void* cookie;
- ZipString suffix("x509.pem");
- int32_t iter_status = StartIteration(handle, &cookie, nullptr, &suffix);
+ int32_t iter_status = StartIteration(handle, &cookie, "", "x509.pem");
if (iter_status != 0) {
LOG(ERROR) << "Failed to iterate over entries in the certificate zipfile: "
<< ErrorCodeString(iter_status);
@@ -321,22 +320,21 @@ static std::vector<Certificate> IterateZipEntriesAndSearchForKeys(const ZipArchi
std::vector<Certificate> result;
- ZipString name;
+ std::string_view name;
ZipEntry entry;
while ((iter_status = Next(cookie, &entry, &name)) == 0) {
std::vector<uint8_t> pem_content(entry.uncompressed_length);
if (int32_t extract_status =
ExtractToMemory(handle, &entry, pem_content.data(), pem_content.size());
extract_status != 0) {
- LOG(ERROR) << "Failed to extract " << std::string(name.name, name.name + name.name_length);
+ LOG(ERROR) << "Failed to extract " << name;
return {};
}
Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
// Aborts the parsing if we fail to load one of the key file.
if (!LoadCertificateFromBuffer(pem_content, &cert)) {
- LOG(ERROR) << "Failed to load keys from "
- << std::string(name.name, name.name + name.name_length);
+ LOG(ERROR) << "Failed to load keys from " << name;
return {};
}
diff --git a/install/wipe_data.cpp b/install/wipe_data.cpp
index 765a8152b..82660bef0 100644
--- a/install/wipe_data.cpp
+++ b/install/wipe_data.cpp
@@ -28,9 +28,9 @@
#include <android-base/stringprintf.h>
#include "otautil/dirutil.h"
-#include "otautil/logging.h"
-#include "otautil/roots.h"
#include "recovery_ui/ui.h"
+#include "recovery_utils/logging.h"
+#include "recovery_utils/roots.h"
constexpr const char* CACHE_ROOT = "/cache";
constexpr const char* DATA_ROOT = "/data";
@@ -120,4 +120,4 @@ bool WipeData(Device* device, bool convert_fbe) {
}
ui->Print("Data wipe %s.\n", success ? "complete" : "failed");
return success;
-} \ No newline at end of file
+}
diff --git a/install/wipe_device.cpp b/install/wipe_device.cpp
new file mode 100644
index 000000000..89d5d31a3
--- /dev/null
+++ b/install/wipe_device.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "install/wipe_device.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <stdint.h>
+#include <sys/ioctl.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ziparchive/zip_archive.h>
+
+#include "bootloader_message/bootloader_message.h"
+#include "install/install.h"
+#include "install/package.h"
+#include "recovery_ui/device.h"
+#include "recovery_ui/ui.h"
+
+std::vector<std::string> GetWipePartitionList(Package* wipe_package) {
+ ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle();
+ if (!zip) {
+ LOG(ERROR) << "Failed to get ZipArchiveHandle";
+ return {};
+ }
+
+ constexpr char RECOVERY_WIPE_ENTRY_NAME[] = "recovery.wipe";
+
+ std::string partition_list_content;
+ ZipEntry entry;
+ if (FindEntry(zip, RECOVERY_WIPE_ENTRY_NAME, &entry) == 0) {
+ uint32_t length = entry.uncompressed_length;
+ partition_list_content = std::string(length, '\0');
+ if (auto err = ExtractToMemory(
+ zip, &entry, reinterpret_cast<uint8_t*>(partition_list_content.data()), length);
+ err != 0) {
+ LOG(ERROR) << "Failed to extract " << RECOVERY_WIPE_ENTRY_NAME << ": "
+ << ErrorCodeString(err);
+ return {};
+ }
+ } else {
+ LOG(INFO) << "Failed to find " << RECOVERY_WIPE_ENTRY_NAME
+ << ", falling back to use the partition list on device.";
+
+ constexpr char RECOVERY_WIPE_ON_DEVICE[] = "/etc/recovery.wipe";
+ if (!android::base::ReadFileToString(RECOVERY_WIPE_ON_DEVICE, &partition_list_content)) {
+ PLOG(ERROR) << "failed to read \"" << RECOVERY_WIPE_ON_DEVICE << "\"";
+ return {};
+ }
+ }
+
+ std::vector<std::string> result;
+ auto lines = android::base::Split(partition_list_content, "\n");
+ for (const auto& line : lines) {
+ auto partition = android::base::Trim(line);
+ // Ignore '#' comment or empty lines.
+ if (android::base::StartsWith(partition, "#") || partition.empty()) {
+ continue;
+ }
+ result.push_back(line);
+ }
+
+ return result;
+}
+
+// Secure-wipes a given partition. It uses BLKSECDISCARD, if supported. Otherwise, it goes with
+// BLKDISCARD (if device supports BLKDISCARDZEROES) or BLKZEROOUT.
+static bool SecureWipePartition(const std::string& partition) {
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY)));
+ if (fd == -1) {
+ PLOG(ERROR) << "Failed to open \"" << partition << "\"";
+ return false;
+ }
+
+ uint64_t range[2] = { 0, 0 };
+ if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) {
+ PLOG(ERROR) << "Failed to get partition size";
+ return false;
+ }
+ LOG(INFO) << "Secure-wiping \"" << partition << "\" from " << range[0] << " to " << range[1];
+
+ LOG(INFO) << " Trying BLKSECDISCARD...";
+ if (ioctl(fd, BLKSECDISCARD, &range) == -1) {
+ PLOG(WARNING) << " Failed";
+
+ // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT.
+ unsigned int zeroes;
+ if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) {
+ LOG(INFO) << " Trying BLKDISCARD...";
+ if (ioctl(fd, BLKDISCARD, &range) == -1) {
+ PLOG(ERROR) << " Failed";
+ return false;
+ }
+ } else {
+ LOG(INFO) << " Trying BLKZEROOUT...";
+ if (ioctl(fd, BLKZEROOUT, &range) == -1) {
+ PLOG(ERROR) << " Failed";
+ return false;
+ }
+ }
+ }
+
+ LOG(INFO) << " Done";
+ return true;
+}
+
+static std::unique_ptr<Package> ReadWipePackage(size_t wipe_package_size) {
+ if (wipe_package_size == 0) {
+ LOG(ERROR) << "wipe_package_size is zero";
+ return nullptr;
+ }
+
+ std::string wipe_package;
+ if (std::string err_str; !read_wipe_package(&wipe_package, wipe_package_size, &err_str)) {
+ PLOG(ERROR) << "Failed to read wipe package" << err_str;
+ return nullptr;
+ }
+
+ return Package::CreateMemoryPackage(
+ std::vector<uint8_t>(wipe_package.begin(), wipe_package.end()), nullptr);
+}
+
+// Checks if the wipe package matches expectation. If the check passes, reads the list of
+// partitions to wipe from the package. Checks include
+// 1. verify the package.
+// 2. check metadata (ota-type, pre-device and serial number if having one).
+static bool CheckWipePackage(Package* wipe_package, RecoveryUI* ui) {
+ if (!verify_package(wipe_package, ui)) {
+ LOG(ERROR) << "Failed to verify package";
+ return false;
+ }
+
+ ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle();
+ if (!zip) {
+ LOG(ERROR) << "Failed to get ZipArchiveHandle";
+ return false;
+ }
+
+ std::map<std::string, std::string> metadata;
+ if (!ReadMetadataFromPackage(zip, &metadata)) {
+ LOG(ERROR) << "Failed to parse metadata in the zip file";
+ return false;
+ }
+
+ return CheckPackageMetadata(metadata, OtaType::BRICK);
+}
+
+bool WipeAbDevice(Device* device, size_t wipe_package_size) {
+ auto ui = device->GetUI();
+ ui->SetBackground(RecoveryUI::ERASING);
+ ui->SetProgressType(RecoveryUI::INDETERMINATE);
+
+ auto wipe_package = ReadWipePackage(wipe_package_size);
+ if (!wipe_package) {
+ LOG(ERROR) << "Failed to open wipe package";
+ return false;
+ }
+
+ if (!CheckWipePackage(wipe_package.get(), ui)) {
+ LOG(ERROR) << "Failed to verify wipe package";
+ return false;
+ }
+
+ auto partition_list = GetWipePartitionList(wipe_package.get());
+ if (partition_list.empty()) {
+ LOG(ERROR) << "Empty wipe ab partition list";
+ return false;
+ }
+
+ for (const auto& partition : partition_list) {
+ // Proceed anyway even if it fails to wipe some partition.
+ SecureWipePartition(partition);
+ }
+ return true;
+}
diff --git a/minadbd/Android.bp b/minadbd/Android.bp
index 007e5057b..b7075e670 100644
--- a/minadbd/Android.bp
+++ b/minadbd/Android.bp
@@ -26,6 +26,10 @@ cc_defaults {
include_dirs: [
"system/core/adb",
],
+
+ header_libs: [
+ "libminadbd_headers",
+ ],
}
// `libminadbd_services` is analogous to the `libadbd_services` for regular `adbd`, but providing
@@ -36,6 +40,7 @@ cc_library {
defaults: [
"minadbd_defaults",
+ "librecovery_utils_defaults",
],
srcs: [
@@ -43,6 +48,11 @@ cc_library {
"minadbd_services.cpp",
],
+ static_libs: [
+ "librecovery_utils",
+ "libotautil",
+ ],
+
shared_libs: [
"libadbd",
"libbase",
@@ -54,9 +64,12 @@ cc_library {
cc_library_headers {
name: "libminadbd_headers",
recovery_available: true,
- // TODO create a include dir
export_include_dirs: [
- ".",
+ "include",
+ ],
+ // adb_install.cpp
+ visibility: [
+ "//bootable/recovery/install",
],
}
@@ -86,6 +99,7 @@ cc_test {
defaults: [
"minadbd_defaults",
+ "librecovery_utils_defaults",
],
srcs: [
@@ -96,12 +110,14 @@ cc_test {
static_libs: [
"libminadbd_services",
"libfusesideload",
+ "librecovery_utils",
+ "libotautil",
"libadbd",
- "libcrypto",
],
shared_libs: [
"libbase",
+ "libcrypto",
"libcutils",
"liblog",
],
diff --git a/minadbd/README.md b/minadbd/README.md
index 5a0a067de..9a1958309 100644
--- a/minadbd/README.md
+++ b/minadbd/README.md
@@ -1,8 +1,24 @@
-minadbd is now mostly built from libadbd. The fuse features are unique to
-minadbd, and services.c has been modified as follows:
-
- - all services removed
- - all host mode support removed
- - `sideload_service()` added; this is the only service supported. It
- receives a single blob of data, writes it to a fixed filename, and
- makes the process exit.
+minadbd
+=======
+
+`minadbd` is analogous to the regular `adbd`, but providing the minimal services to support
+recovery-specific use cases. Generally speaking, `adbd` = `libadbd` + `libadbd_services`, whereas
+`minadbd` = `libadbd` + `libminadbd_services`.
+
+Although both modules may be installed into the recovery image, only one of them, or none, can be
+active at any given time.
+
+- The start / stop of `adbd` is managed via system property `sys.usb.config`, when setting to `adb`
+ or `none` respectively. Upon starting recovery mode, `adbd` is started in debuggable builds by
+ default; otherwise `adbd` will stay off at all times in user builds. See the triggers in
+ `bootable/recovery/etc/init.rc`.
+
+- `minadbd` is started by `recovery` as needed.
+ - When requested to start `minadbd`, `recovery` stops `adbd` first, if it's running; it then forks
+ and execs `minadbd` in a separate process.
+ - `minadbd` talks to host-side `adb` server to get user requests.
+ - `minadbd` handles some requests directly, e.g. querying device properties for rescue service.
+ - `minadbd` communicates with `recovery` to fulfill requests regarding package installation. See
+ the comments in `bootable/recovery/install/adb_install.cpp` for the IPC protocol between
+ `recovery` and `minadbd`.
+ - Upon exiting `minadbd`, `recovery` restarts `adbd` if it was previously running.
diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h
index c5561e57d..43c07d28e 100644
--- a/minadbd/fuse_adb_provider.h
+++ b/minadbd/fuse_adb_provider.h
@@ -29,6 +29,10 @@ class FuseAdbDataProvider : public FuseDataProvider {
bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
uint32_t start_block) const override;
+ bool Valid() const override {
+ return fd_ != -1;
+ }
+
private:
// The underlying source to read data from (i.e. the one that talks to the host).
int fd_;
diff --git a/minadbd/minadbd_types.h b/minadbd/include/minadbd/types.h
index 002523f1f..002523f1f 100644
--- a/minadbd/minadbd_types.h
+++ b/minadbd/include/minadbd/types.h
diff --git a/minadbd/minadbd.cpp b/minadbd/minadbd.cpp
index c80d5490a..7b82faa05 100644
--- a/minadbd/minadbd.cpp
+++ b/minadbd/minadbd.cpp
@@ -28,8 +28,8 @@
#include "adb_auth.h"
#include "transport.h"
+#include "minadbd/types.h"
#include "minadbd_services.h"
-#include "minadbd_types.h"
using namespace std::string_literals;
diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp
index 03341e4b8..eb91fb3e4 100644
--- a/minadbd/minadbd_services.cpp
+++ b/minadbd/minadbd_services.cpp
@@ -41,10 +41,10 @@
#include "adb.h"
#include "adb_unique_fd.h"
#include "adb_utils.h"
-#include "fdevent.h"
#include "fuse_adb_provider.h"
#include "fuse_sideload.h"
-#include "minadbd_types.h"
+#include "minadbd/types.h"
+#include "recovery_utils/battery_utils.h"
#include "services.h"
#include "sysdeps.h"
@@ -161,7 +161,10 @@ static void RescueInstallHostService(unique_fd sfd, const std::string& args) {
// If given an empty string, dumps all the supported properties (analogous to `adb shell getprop`)
// in lines, e.g. "[prop]: [value]".
static void RescueGetpropHostService(unique_fd sfd, const std::string& prop) {
+ constexpr const char* kRescueBatteryLevelProp = "rescue.battery_level";
static const std::set<std::string> kGetpropAllowedProps = {
+ // clang-format off
+ kRescueBatteryLevelProp,
"ro.build.date.utc",
"ro.build.fingerprint",
"ro.build.flavor",
@@ -171,18 +174,28 @@ static void RescueGetpropHostService(unique_fd sfd, const std::string& prop) {
"ro.build.version.incremental",
"ro.product.device",
"ro.product.vendor.device",
+ // clang-format on
};
+
+ auto query_prop = [](const std::string& key) {
+ if (key == kRescueBatteryLevelProp) {
+ auto battery_info = GetBatteryInfo();
+ return std::to_string(battery_info.capacity);
+ }
+ return android::base::GetProperty(key, "");
+ };
+
std::string result;
if (prop.empty()) {
for (const auto& key : kGetpropAllowedProps) {
- auto value = android::base::GetProperty(key, "");
+ auto value = query_prop(key);
if (value.empty()) {
continue;
}
result += "[" + key + "]: [" + value + "]\n";
}
} else if (kGetpropAllowedProps.find(prop) != kGetpropAllowedProps.end()) {
- result = android::base::GetProperty(prop, "") + "\n";
+ result = query_prop(prop) + "\n";
}
if (result.empty()) {
result = "\n";
@@ -255,7 +268,7 @@ static void WipeDeviceService(unique_fd fd, const std::string& args) {
unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport */) {
// Common services that are supported both in sideload and rescue modes.
- if (ConsumePrefix(&name, "reboot:")) {
+ if (android::base::ConsumePrefix(&name, "reboot:")) {
// "reboot:<target>", where target must be one of the following.
std::string args(name);
if (args.empty() || args == "bootloader" || args == "rescue" || args == "recovery" ||
@@ -268,17 +281,17 @@ unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport *
// Rescue-specific services.
if (rescue_mode) {
- if (ConsumePrefix(&name, "rescue-install:")) {
+ if (android::base::ConsumePrefix(&name, "rescue-install:")) {
// rescue-install:<file-size>:<block-size>
std::string args(name);
return create_service_thread(
"rescue-install", std::bind(RescueInstallHostService, std::placeholders::_1, args));
- } else if (ConsumePrefix(&name, "rescue-getprop:")) {
+ } else if (android::base::ConsumePrefix(&name, "rescue-getprop:")) {
// rescue-getprop:<prop>
std::string args(name);
return create_service_thread(
"rescue-getprop", std::bind(RescueGetpropHostService, std::placeholders::_1, args));
- } else if (ConsumePrefix(&name, "rescue-wipe:")) {
+ } else if (android::base::ConsumePrefix(&name, "rescue-wipe:")) {
// rescue-wipe:target:<message-size>
std::string args(name);
return create_service_thread("rescue-wipe",
@@ -293,7 +306,7 @@ unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport *
// This exit status causes recovery to print a special error message saying to use a newer adb
// (that supports sideload-host).
exit(kMinadbdAdbVersionError);
- } else if (ConsumePrefix(&name, "sideload-host:")) {
+ } else if (android::base::ConsumePrefix(&name, "sideload-host:")) {
// sideload-host:<file-size>:<block-size>
std::string args(name);
return create_service_thread("sideload-host",
diff --git a/minadbd/minadbd_services_test.cpp b/minadbd/minadbd_services_test.cpp
index f87873792..b694a57d1 100644
--- a/minadbd/minadbd_services_test.cpp
+++ b/minadbd/minadbd_services_test.cpp
@@ -35,8 +35,8 @@
#include "adb_io.h"
#include "fuse_adb_provider.h"
#include "fuse_sideload.h"
+#include "minadbd/types.h"
#include "minadbd_services.h"
-#include "minadbd_types.h"
#include "socket.h"
class MinadbdServicesTest : public ::testing::Test {
diff --git a/minui/events.cpp b/minui/events.cpp
index 7d0250e97..f331ed68a 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -22,6 +22,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
+#include <sys/inotify.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <unistd.h>
@@ -33,6 +34,8 @@
#include "minui/minui.h"
+constexpr const char* INPUT_DEV_DIR = "/dev/input";
+
constexpr size_t MAX_DEVICES = 16;
constexpr size_t MAX_MISC_FDS = 16;
@@ -46,6 +49,8 @@ struct FdInfo {
ev_callback cb;
};
+static bool g_allow_touch_inputs = true;
+static ev_callback g_saved_input_cb;
static android::base::unique_fd g_epoll_fd;
static epoll_event g_polled_events[MAX_DEVICES + MAX_MISC_FDS];
static int g_polled_events_count;
@@ -60,6 +65,78 @@ static bool test_bit(size_t bit, unsigned long* array) { // NOLINT
return (array[bit / BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0;
}
+static bool should_add_input_device(int fd, bool allow_touch_inputs) {
+ // Use unsigned long to match ioctl's parameter type.
+ unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
+
+ // Read the evbits of the input device.
+ if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+ return false;
+ }
+
+ // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. EV_ABS is also
+ // allowed if allow_touch_inputs is set.
+ if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
+ if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static int inotify_cb(int fd, __unused uint32_t epevents) {
+ if (g_saved_input_cb == nullptr) return -1;
+
+ // The inotify will put one or several complete events.
+ // Should not read part of one event.
+ size_t event_len;
+ int ret = ioctl(fd, FIONREAD, &event_len);
+ if (ret != 0) return -1;
+
+ std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(INPUT_DEV_DIR), closedir);
+ if (!dir) {
+ return -1;
+ }
+
+ std::vector<int8_t> buf(event_len);
+
+ ssize_t r = TEMP_FAILURE_RETRY(read(fd, buf.data(), event_len));
+ if (r != event_len) {
+ return -1;
+ }
+
+ size_t offset = 0;
+ while (offset < event_len) {
+ struct inotify_event* pevent = reinterpret_cast<struct inotify_event*>(buf.data() + offset);
+ if (offset + sizeof(inotify_event) + pevent->len > event_len) {
+ // The pevent->len is too large and buffer will over flow.
+ // In general, should not happen, just make more stable.
+ return -1;
+ }
+ offset += sizeof(inotify_event) + pevent->len;
+
+ pevent->name[pevent->len] = '\0';
+ if (strncmp(pevent->name, "event", 5)) {
+ continue;
+ }
+
+ android::base::unique_fd dfd(openat(dirfd(dir.get()), pevent->name, O_RDONLY));
+ if (dfd == -1) {
+ break;
+ }
+
+ if (!should_add_input_device(dfd, g_allow_touch_inputs)) {
+ continue;
+ }
+
+ // Only add, we assume the user will not plug out and plug in USB device again and again :)
+ ev_add_fd(std::move(dfd), g_saved_input_cb);
+ }
+
+ return 0;
+}
+
int ev_init(ev_callback input_cb, bool allow_touch_inputs) {
g_epoll_fd.reset();
@@ -68,7 +145,16 @@ int ev_init(ev_callback input_cb, bool allow_touch_inputs) {
return -1;
}
- std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/dev/input"), closedir);
+ android::base::unique_fd inotify_fd(inotify_init1(IN_CLOEXEC));
+ if (inotify_fd.get() == -1) {
+ return -1;
+ }
+
+ if (inotify_add_watch(inotify_fd, INPUT_DEV_DIR, IN_CREATE) < 0) {
+ return -1;
+ }
+
+ std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(INPUT_DEV_DIR), closedir);
if (!dir) {
return -1;
}
@@ -80,22 +166,10 @@ int ev_init(ev_callback input_cb, bool allow_touch_inputs) {
android::base::unique_fd fd(openat(dirfd(dir.get()), de->d_name, O_RDONLY | O_CLOEXEC));
if (fd == -1) continue;
- // Use unsigned long to match ioctl's parameter type.
- unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
-
- // Read the evbits of the input device.
- if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+ if (!should_add_input_device(fd, allow_touch_inputs)) {
continue;
}
- // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. EV_ABS is also
- // allowed if allow_touch_inputs is set.
- if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
- if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) {
- continue;
- }
- }
-
epoll_event ev;
ev.events = EPOLLIN | EPOLLWAKEUP;
ev.data.ptr = &ev_fdinfo[g_ev_count];
@@ -116,6 +190,11 @@ int ev_init(ev_callback input_cb, bool allow_touch_inputs) {
}
g_epoll_fd.reset(epoll_fd.release());
+
+ g_saved_input_cb = input_cb;
+ g_allow_touch_inputs = allow_touch_inputs;
+ ev_add_fd(std::move(inotify_fd), inotify_cb);
+
return 0;
}
@@ -148,6 +227,7 @@ void ev_exit(void) {
}
g_ev_misc_count = 0;
g_ev_dev_count = 0;
+ g_saved_input_cb = nullptr;
g_epoll_fd.reset();
}
@@ -170,13 +250,17 @@ void ev_dispatch(void) {
}
int ev_get_input(int fd, uint32_t epevents, input_event* ev) {
- if (epevents & EPOLLIN) {
- ssize_t r = TEMP_FAILURE_RETRY(read(fd, ev, sizeof(*ev)));
- if (r == sizeof(*ev)) {
- return 0;
- }
+ if (epevents & EPOLLIN) {
+ ssize_t r = TEMP_FAILURE_RETRY(read(fd, ev, sizeof(*ev)));
+ if (r == sizeof(*ev)) {
+ return 0;
}
- return -1;
+ }
+ if (epevents & EPOLLHUP) {
+ // Delete this watch
+ epoll_ctl(g_epoll_fd, EPOLL_CTL_DEL, fd, nullptr);
+ }
+ return -1;
}
int ev_sync_key_state(const ev_set_key_callback& set_key_cb) {
diff --git a/minui/resources.cpp b/minui/resources.cpp
index 069a49529..00d36d5fb 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -347,6 +347,10 @@ bool matches_locale(const std::string& prefix, const std::string& locale) {
// match the locale string without the {script} section.
// For instance, prefix == "en" matches locale == "en-US", prefix == "sr-Latn" matches locale
// == "sr-Latn-BA", and prefix == "zh-CN" matches locale == "zh-Hans-CN".
+ if (prefix.empty()) {
+ return false;
+ }
+
if (android::base::StartsWith(locale, prefix)) {
return true;
}
@@ -414,12 +418,18 @@ int res_create_localized_alpha_surface(const char* name,
__unused int len = row[4];
char* loc = reinterpret_cast<char*>(&row[5]);
- if (y + 1 + h >= height || matches_locale(loc, locale)) {
+ // We need to include one additional line for the metadata of the localized image.
+ if (y + 1 + h > height) {
+ printf("Read exceeds the image boundary, y %u, h %d, height %u\n", y, h, height);
+ return -8;
+ }
+
+ if (matches_locale(loc, locale)) {
printf(" %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);
auto surface = GRSurface::Create(w, h, w, 1);
if (!surface) {
- return -8;
+ return -9;
}
for (int i = 0; i < h; ++i, ++y) {
@@ -428,7 +438,7 @@ int res_create_localized_alpha_surface(const char* name,
}
*pSurface = surface.release();
- break;
+ return 0;
}
for (int i = 0; i < h; ++i, ++y) {
@@ -436,7 +446,7 @@ int res_create_localized_alpha_surface(const char* name,
}
}
- return 0;
+ return -10;
}
void res_free_surface(GRSurface* surface) {
diff --git a/otautil/Android.bp b/otautil/Android.bp
index 0a21731e8..3b3f9cbc4 100644
--- a/otautil/Android.bp
+++ b/otautil/Android.bp
@@ -16,6 +16,7 @@ cc_library_static {
name: "libotautil",
host_supported: true,
+ vendor_available: true,
recovery_available: true,
defaults: [
@@ -24,44 +25,19 @@ cc_library_static {
// Minimal set of files to support host build.
srcs: [
+ "dirutil.cpp",
"paths.cpp",
"rangeset.cpp",
+ "sysutil.cpp",
],
shared_libs: [
"libbase",
+ "libcutils",
+ "libselinux",
],
export_include_dirs: [
"include",
],
-
- target: {
- android: {
- srcs: [
- "dirutil.cpp",
- "logging.cpp",
- "mounts.cpp",
- "parse_install_logs.cpp",
- "roots.cpp",
- "sysutil.cpp",
- "thermalutil.cpp",
- ],
-
- include_dirs: [
- "system/vold",
- ],
-
- static_libs: [
- "libfstab",
- ],
-
- shared_libs: [
- "libcutils",
- "libext4_utils",
- "libfs_mgr",
- "libselinux",
- ],
- },
- },
}
diff --git a/common.h b/otautil/include/otautil/boot_state.h
index a524a4184..6c877baef 100644
--- a/common.h
+++ b/otautil/include/otautil/boot_state.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,22 +17,20 @@
#pragma once
#include <string>
-
-// Not using the command-line defined macro here because this header could be included by
-// device-specific recovery libraries. We static assert the value consistency in recovery.cpp.
-static constexpr int kRecoveryApiVersion = 3;
-
-class RecoveryUI;
-struct selabel_handle;
-
-extern struct selabel_handle* sehandle;
-extern RecoveryUI* ui;
-extern bool has_cache;
-
-// The current stage, e.g. "1/2".
-extern std::string stage;
-
-// The reason argument provided in "--reason=".
-extern const char* reason;
-
-bool is_ro_debuggable();
+#include <string_view>
+
+class BootState {
+ public:
+ BootState(std::string_view reason, std::string_view stage) : reason_(reason), stage_(stage) {}
+
+ std::string reason() const {
+ return reason_;
+ }
+ std::string stage() const {
+ return stage_;
+ }
+
+ private:
+ std::string reason_; // The reason argument provided in "--reason=".
+ std::string stage_; // The current stage, e.g. "1/2".
+};
diff --git a/otautil/include/otautil/rangeset.h b/otautil/include/otautil/rangeset.h
index e91d02ca6..a18c30e29 100644
--- a/otautil/include/otautil/rangeset.h
+++ b/otautil/include/otautil/rangeset.h
@@ -18,6 +18,7 @@
#include <stddef.h>
+#include <optional>
#include <string>
#include <utility>
#include <vector>
@@ -49,6 +50,12 @@ class RangeSet {
// bounds. For example, "3,5" contains blocks 3 and 4. So "3,5" and "5,7" are not overlapped.
bool Overlaps(const RangeSet& other) const;
+ // Returns a subset of ranges starting from |start_index| with respect to the original range. The
+ // output range will have |num_of_blocks| blocks in size. Returns std::nullopt if the input is
+ // invalid. e.g. RangeSet({{0, 5}, {10, 15}}).GetSubRanges(1, 5) returns
+ // RangeSet({{1, 5}, {10, 11}}).
+ std::optional<RangeSet> GetSubRanges(size_t start_index, size_t num_of_blocks) const;
+
// Returns a vector of RangeSets that contain the same set of blocks represented by the current
// RangeSet. The RangeSets in the vector contain similar number of blocks, with a maximum delta
// of 1-block between any two of them. For example, 14 blocks would be split into 4 + 4 + 3 + 3,
diff --git a/otautil/include/otautil/sysutil.h b/otautil/include/otautil/sysutil.h
index 692a99e9d..326db8644 100644
--- a/otautil/include/otautil/sysutil.h
+++ b/otautil/include/otautil/sysutil.h
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-#ifndef _OTAUTIL_SYSUTIL
-#define _OTAUTIL_SYSUTIL
+#pragma once
#include <sys/types.h>
#include <string>
+#include <string_view>
#include <vector>
#include "rangeset.h"
@@ -101,13 +101,14 @@ class MemMapping {
std::vector<MappedRange> ranges_;
};
-// Wrapper function to trigger a reboot, by additionally handling quiescent reboot mode. The
-// command should start with "reboot," (e.g. "reboot,bootloader" or "reboot,").
-bool reboot(const std::string& command);
+// Reboots the device into the specified target, by additionally handling quiescent reboot mode.
+// All unknown targets reboot into Android.
+bool Reboot(std::string_view target);
+
+// Triggers a shutdown.
+bool Shutdown(std::string_view target);
// Returns a null-terminated char* array, where the elements point to the C-strings in the given
// vector, plus an additional nullptr at the end. This is a helper function that facilitates
// calling C functions (such as getopt(3)) that expect an array of C-strings.
std::vector<char*> StringVectorToNullTerminatedArray(const std::vector<std::string>& args);
-
-#endif // _OTAUTIL_SYSUTIL
diff --git a/otautil/rangeset.cpp b/otautil/rangeset.cpp
index 5ab8e08fe..8ee99dd7a 100644
--- a/otautil/rangeset.cpp
+++ b/otautil/rangeset.cpp
@@ -184,6 +184,58 @@ bool RangeSet::Overlaps(const RangeSet& other) const {
return false;
}
+std::optional<RangeSet> RangeSet::GetSubRanges(size_t start_index, size_t num_of_blocks) const {
+ size_t end_index = start_index + num_of_blocks; // The index of final block to read plus one
+ if (start_index > end_index || end_index > blocks_) {
+ LOG(ERROR) << "Failed to get the sub ranges for start_index " << start_index
+ << " num_of_blocks " << num_of_blocks
+ << " total number of blocks the range contains is " << blocks_;
+ return std::nullopt;
+ }
+
+ if (num_of_blocks == 0) {
+ LOG(WARNING) << "num_of_blocks is zero when calling GetSubRanges()";
+ return RangeSet();
+ }
+
+ RangeSet result;
+ size_t current_index = 0;
+ for (const auto& [range_start, range_end] : ranges_) {
+ CHECK_LT(range_start, range_end);
+ size_t blocks_in_range = range_end - range_start;
+ // Linear search to skip the ranges until we reach start_block.
+ if (current_index + blocks_in_range <= start_index) {
+ current_index += blocks_in_range;
+ continue;
+ }
+
+ size_t trimmed_range_start = range_start;
+ // We have found the first block range to read, trim the heading blocks.
+ if (current_index < start_index) {
+ trimmed_range_start += start_index - current_index;
+ }
+ // Trim the trailing blocks if the last range has more blocks than desired; also return the
+ // result.
+ if (current_index + blocks_in_range >= end_index) {
+ size_t trimmed_range_end = range_end - (current_index + blocks_in_range - end_index);
+ if (!result.PushBack({ trimmed_range_start, trimmed_range_end })) {
+ return std::nullopt;
+ }
+
+ return result;
+ }
+
+ if (!result.PushBack({ trimmed_range_start, range_end })) {
+ return std::nullopt;
+ }
+ current_index += blocks_in_range;
+ }
+
+ LOG(ERROR) << "Failed to construct byte ranges to read, start_block: " << start_index
+ << ", num_of_blocks: " << num_of_blocks << " total number of blocks: " << blocks_;
+ return std::nullopt;
+}
+
// Ranges in the the set should be mutually exclusive; and they're sorted by the start block.
SortedRangeSet::SortedRangeSet(std::vector<Range>&& pairs) : RangeSet(std::move(pairs)) {
std::sort(ranges_.begin(), ranges_.end());
diff --git a/otautil/sysutil.cpp b/otautil/sysutil.cpp
index 8366fa0ac..6cd46c6a9 100644
--- a/otautil/sysutil.cpp
+++ b/otautil/sysutil.cpp
@@ -38,7 +38,7 @@
BlockMapData BlockMapData::ParseBlockMapFile(const std::string& block_map_path) {
std::string content;
if (!android::base::ReadFileToString(block_map_path, &content)) {
- LOG(ERROR) << "Failed to read " << block_map_path;
+ PLOG(ERROR) << "Failed to read " << block_map_path;
return {};
}
@@ -94,6 +94,11 @@ BlockMapData BlockMapData::ParseBlockMapFile(const std::string& block_map_path)
remaining_blocks -= range_blocks;
}
+ if (remaining_blocks != 0) {
+ LOG(ERROR) << "Invalid ranges: remaining blocks " << remaining_blocks;
+ return {};
+ }
+
return BlockMapData(block_dev, file_size, blksize, std::move(ranges));
}
@@ -214,14 +219,21 @@ MemMapping::~MemMapping() {
ranges_.clear();
}
-bool reboot(const std::string& command) {
- std::string cmd = command;
- if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
+bool Reboot(std::string_view target) {
+ std::string cmd = "reboot," + std::string(target);
+ // Honor the quiescent mode if applicable.
+ if (target != "bootloader" && target != "fastboot" &&
+ android::base::GetBoolProperty("ro.boot.quiescent", false)) {
cmd += ",quiescent";
}
return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd);
}
+bool Shutdown(std::string_view target) {
+ std::string cmd = "shutdown," + std::string(target);
+ return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd);
+}
+
std::vector<char*> StringVectorToNullTerminatedArray(const std::vector<std::string>& args) {
std::vector<char*> result(args.size());
std::transform(args.cbegin(), args.cend(), result.begin(),
diff --git a/recovery-persist.cpp b/recovery-persist.cpp
index 294017a12..6dbf86253 100644
--- a/recovery-persist.cpp
+++ b/recovery-persist.cpp
@@ -43,8 +43,8 @@
#include <metricslogger/metrics_logger.h>
#include <private/android_logger.h> /* private pmsg functions */
-#include "otautil/logging.h"
-#include "otautil/parse_install_logs.h"
+#include "recovery_utils/logging.h"
+#include "recovery_utils/parse_install_logs.h"
constexpr const char* LAST_LOG_FILE = "/data/misc/recovery/last_log";
constexpr const char* LAST_PMSG_FILE = "/sys/fs/pstore/pmsg-ramoops-0";
diff --git a/recovery-refresh.cpp b/recovery-refresh.cpp
index d41755d0a..42acd05be 100644
--- a/recovery-refresh.cpp
+++ b/recovery-refresh.cpp
@@ -38,11 +38,12 @@
//
#include <string.h>
+
#include <string>
#include <private/android_logger.h> /* private pmsg functions */
-#include "otautil/logging.h"
+#include "recovery_utils/logging.h"
int main(int argc, char **argv) {
static const char filter[] = "recovery/";
diff --git a/recovery.cpp b/recovery.cpp
index 5fc673ec2..f59a940fc 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -18,11 +18,9 @@
#include <ctype.h>
#include <errno.h>
-#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h>
#include <limits.h>
-#include <linux/fs.h>
#include <linux/input.h>
#include <stdio.h>
#include <stdlib.h>
@@ -30,8 +28,8 @@
#include <sys/types.h>
#include <unistd.h>
-#include <algorithm>
#include <functional>
+#include <iterator>
#include <memory>
#include <string>
#include <vector>
@@ -42,26 +40,27 @@
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
-#include <bootloader_message/bootloader_message.h>
#include <cutils/properties.h> /* for property_list */
-#include <healthhalutils/HealthHalUtils.h>
+#include <fs_mgr/roots.h>
#include <ziparchive/zip_archive.h>
-#include "common.h"
+#include "bootloader_message/bootloader_message.h"
#include "fsck_unshare_blocks.h"
#include "install/adb_install.h"
-#include "install/fuse_sdcard_install.h"
+#include "install/fuse_install.h"
#include "install/install.h"
#include "install/package.h"
#include "install/wipe_data.h"
+#include "install/wipe_device.h"
+#include "otautil/boot_state.h"
#include "otautil/error_code.h"
-#include "otautil/logging.h"
#include "otautil/paths.h"
-#include "otautil/roots.h"
#include "otautil/sysutil.h"
#include "recovery_ui/screen_ui.h"
#include "recovery_ui/ui.h"
+#include "recovery_utils/battery_utils.h"
+#include "recovery_utils/logging.h"
+#include "recovery_utils/roots.h"
static constexpr const char* COMMAND_FILE = "/cache/recovery/command";
static constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg";
@@ -70,13 +69,7 @@ static constexpr const char* LOCALE_FILE = "/cache/recovery/last_locale";
static constexpr const char* CACHE_ROOT = "/cache";
-// We define RECOVERY_API_VERSION in Android.mk, which will be picked up by build system and packed
-// into target_files.zip. Assert the version defined in code and in Android.mk are consistent.
-static_assert(kRecoveryApiVersion == RECOVERY_API_VERSION, "Mismatching recovery API versions.");
-
static bool save_current_log = false;
-std::string stage;
-const char* reason = nullptr;
/*
* The recovery tool communicates with the main system through /cache files.
@@ -85,6 +78,8 @@ const char* reason = nullptr;
*
* The arguments which may be supplied in the recovery.command file:
* --update_package=path - verify install an OTA package file
+ * --install_with_fuse - install the update package with FUSE. This allows installation of large
+ * packages on LP32 builds. Since the mmap will otherwise fail due to out of memory.
* --wipe_data - erase user data (and cache), then reboot
* --prompt_and_wipe_data - prompt the user that data is corrupt, with their consent erase user
* data (and cache), then reboot
@@ -105,7 +100,7 @@ const char* reason = nullptr;
* -- after this, rebooting will restart the erase --
* 5. erase_volume() reformats /data
* 6. erase_volume() reformats /cache
- * 7. finish_recovery() erases BCB
+ * 7. FinishRecovery() erases BCB
* -- after this, rebooting will restart the main system --
* 8. main() calls reboot() to boot main system
*
@@ -115,27 +110,27 @@ const char* reason = nullptr;
* 3. main system reboots into recovery
* 4. get_args() writes BCB with "boot-recovery" and "--update_package=..."
* -- after this, rebooting will attempt to reinstall the update --
- * 5. install_package() attempts to install the update
+ * 5. InstallPackage() attempts to install the update
* NOTE: the package install must itself be restartable from any point
- * 6. finish_recovery() erases BCB
+ * 6. FinishRecovery() erases BCB
* -- after this, rebooting will (try to) restart the main system --
* 7. ** if install failed **
- * 7a. prompt_and_wait() shows an error icon and waits for the user
+ * 7a. PromptAndWait() shows an error icon and waits for the user
* 7b. the user reboots (pulling the battery, etc) into the main system
*/
-bool is_ro_debuggable() {
- return android::base::GetBoolProperty("ro.debuggable", false);
+static bool IsRoDebuggable() {
+ return android::base::GetBoolProperty("ro.debuggable", false);
}
// Clear the recovery command and prepare to boot a (hopefully working) system,
// copy our log file to cache as well (for the system to read). This function is
// idempotent: call it as many times as you like.
-static void finish_recovery() {
+static void FinishRecovery(RecoveryUI* ui) {
std::string locale = ui->GetLocale();
// Save the locale to cache, so if recovery is next started up without a '--locale' argument
// (e.g., directly from the bootloader) it will use the last-known locale.
- if (!locale.empty() && has_cache) {
+ if (!locale.empty() && HasCache()) {
LOG(INFO) << "Saving locale \"" << locale << "\"";
if (ensure_path_mounted(LOCALE_FILE) != 0) {
LOG(ERROR) << "Failed to mount " << LOCALE_FILE;
@@ -144,7 +139,7 @@ static void finish_recovery() {
}
}
- copy_logs(save_current_log, has_cache, sehandle);
+ copy_logs(save_current_log);
// Reset to normal system boot so recovery won't cycle indefinitely.
std::string err;
@@ -153,7 +148,7 @@ static void finish_recovery() {
}
// Remove the command file, so recovery won't repeat indefinitely.
- if (has_cache) {
+ if (HasCache()) {
if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) {
LOG(WARNING) << "Can't unlink " << COMMAND_FILE;
}
@@ -167,7 +162,7 @@ static bool yes_no(Device* device, const char* question1, const char* question2)
std::vector<std::string> headers{ question1, question2 };
std::vector<std::string> items{ " No", " Yes" };
- size_t chosen_item = ui->ShowMenu(
+ size_t chosen_item = device->GetUI()->ShowMenu(
headers, items, 0, true,
std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
return (chosen_item == 1);
@@ -177,7 +172,7 @@ static bool ask_to_wipe_data(Device* device) {
std::vector<std::string> headers{ "Wipe all user data?", " THIS CAN NOT BE UNDONE!" };
std::vector<std::string> items{ " Cancel", " Factory data reset" };
- size_t chosen_item = ui->ShowPromptWipeDataConfirmationMenu(
+ size_t chosen_item = device->GetUI()->ShowPromptWipeDataConfirmationMenu(
headers, items,
std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
@@ -199,7 +194,7 @@ static InstallResult prompt_and_wipe_data(Device* device) {
};
// clang-format on
for (;;) {
- size_t chosen_item = ui->ShowPromptWipeDataMenu(
+ size_t chosen_item = device->GetUI()->ShowPromptWipeDataMenu(
wipe_data_menu_headers, wipe_data_menu_items,
std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
// If ShowMenu() returned RecoveryUI::KeyError::INTERRUPTED, WaitKey() was interrupted.
@@ -211,7 +206,8 @@ static InstallResult prompt_and_wipe_data(Device* device) {
}
if (ask_to_wipe_data(device)) {
- bool convert_fbe = reason && strcmp(reason, "convert_fbe") == 0;
+ CHECK(device->GetReason().has_value());
+ bool convert_fbe = device->GetReason().value() == "convert_fbe";
if (WipeData(device, convert_fbe)) {
return INSTALL_SUCCESS;
} else {
@@ -221,168 +217,9 @@ static InstallResult prompt_and_wipe_data(Device* device) {
}
}
-// Secure-wipe a given partition. It uses BLKSECDISCARD, if supported. Otherwise, it goes with
-// BLKDISCARD (if device supports BLKDISCARDZEROES) or BLKZEROOUT.
-static bool secure_wipe_partition(const std::string& partition) {
- android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY)));
- if (fd == -1) {
- PLOG(ERROR) << "Failed to open \"" << partition << "\"";
- return false;
- }
-
- uint64_t range[2] = { 0, 0 };
- if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) {
- PLOG(ERROR) << "Failed to get partition size";
- return false;
- }
- LOG(INFO) << "Secure-wiping \"" << partition << "\" from " << range[0] << " to " << range[1];
-
- LOG(INFO) << " Trying BLKSECDISCARD...";
- if (ioctl(fd, BLKSECDISCARD, &range) == -1) {
- PLOG(WARNING) << " Failed";
-
- // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT.
- unsigned int zeroes;
- if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) {
- LOG(INFO) << " Trying BLKDISCARD...";
- if (ioctl(fd, BLKDISCARD, &range) == -1) {
- PLOG(ERROR) << " Failed";
- return false;
- }
- } else {
- LOG(INFO) << " Trying BLKZEROOUT...";
- if (ioctl(fd, BLKZEROOUT, &range) == -1) {
- PLOG(ERROR) << " Failed";
- return false;
- }
- }
- }
-
- LOG(INFO) << " Done";
- return true;
-}
-
-static std::unique_ptr<Package> ReadWipePackage(size_t wipe_package_size) {
- if (wipe_package_size == 0) {
- LOG(ERROR) << "wipe_package_size is zero";
- return nullptr;
- }
-
- std::string wipe_package;
- std::string err_str;
- if (!read_wipe_package(&wipe_package, wipe_package_size, &err_str)) {
- PLOG(ERROR) << "Failed to read wipe package" << err_str;
- return nullptr;
- }
-
- return Package::CreateMemoryPackage(
- std::vector<uint8_t>(wipe_package.begin(), wipe_package.end()), nullptr);
-}
-
-// Checks if the wipe package matches expectation. If the check passes, reads the list of
-// partitions to wipe from the package. Checks include
-// 1. verify the package.
-// 2. check metadata (ota-type, pre-device and serial number if having one).
-static bool CheckWipePackage(Package* wipe_package) {
- if (!verify_package(wipe_package, ui)) {
- LOG(ERROR) << "Failed to verify package";
- return false;
- }
-
- ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle();
- if (!zip) {
- LOG(ERROR) << "Failed to get ZipArchiveHandle";
- return false;
- }
-
- std::map<std::string, std::string> metadata;
- if (!ReadMetadataFromPackage(zip, &metadata)) {
- LOG(ERROR) << "Failed to parse metadata in the zip file";
- return false;
- }
-
- return CheckPackageMetadata(metadata, OtaType::BRICK) == 0;
-}
-
-std::vector<std::string> GetWipePartitionList(Package* wipe_package) {
- ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle();
- if (!zip) {
- LOG(ERROR) << "Failed to get ZipArchiveHandle";
- return {};
- }
-
- static constexpr const char* RECOVERY_WIPE_ENTRY_NAME = "recovery.wipe";
-
- std::string partition_list_content;
- ZipString path(RECOVERY_WIPE_ENTRY_NAME);
- ZipEntry entry;
- if (FindEntry(zip, path, &entry) == 0) {
- uint32_t length = entry.uncompressed_length;
- partition_list_content = std::string(length, '\0');
- if (auto err = ExtractToMemory(
- zip, &entry, reinterpret_cast<uint8_t*>(partition_list_content.data()), length);
- err != 0) {
- LOG(ERROR) << "Failed to extract " << RECOVERY_WIPE_ENTRY_NAME << ": "
- << ErrorCodeString(err);
- return {};
- }
- } else {
- LOG(INFO) << "Failed to find " << RECOVERY_WIPE_ENTRY_NAME
- << ", falling back to use the partition list on device.";
-
- static constexpr const char* RECOVERY_WIPE_ON_DEVICE = "/etc/recovery.wipe";
- if (!android::base::ReadFileToString(RECOVERY_WIPE_ON_DEVICE, &partition_list_content)) {
- PLOG(ERROR) << "failed to read \"" << RECOVERY_WIPE_ON_DEVICE << "\"";
- return {};
- }
- }
-
- std::vector<std::string> result;
- std::vector<std::string> lines = android::base::Split(partition_list_content, "\n");
- for (const std::string& line : lines) {
- std::string partition = android::base::Trim(line);
- // Ignore '#' comment or empty lines.
- if (android::base::StartsWith(partition, "#") || partition.empty()) {
- continue;
- }
- result.push_back(line);
- }
-
- return result;
-}
-
-// Wipes the current A/B device, with a secure wipe of all the partitions in RECOVERY_WIPE.
-static bool wipe_ab_device(size_t wipe_package_size) {
- ui->SetBackground(RecoveryUI::ERASING);
- ui->SetProgressType(RecoveryUI::INDETERMINATE);
-
- auto wipe_package = ReadWipePackage(wipe_package_size);
- if (!wipe_package) {
- LOG(ERROR) << "Failed to open wipe package";
- return false;
- }
-
- if (!CheckWipePackage(wipe_package.get())) {
- LOG(ERROR) << "Failed to verify wipe package";
- return false;
- }
-
- auto partition_list = GetWipePartitionList(wipe_package.get());
- if (partition_list.empty()) {
- LOG(ERROR) << "Empty wipe ab partition list";
- return false;
- }
-
- for (const auto& partition : partition_list) {
- // Proceed anyway even if it fails to wipe some partition.
- secure_wipe_partition(partition);
- }
- return true;
-}
-
static void choose_recovery_file(Device* device) {
std::vector<std::string> entries;
- if (has_cache) {
+ if (HasCache()) {
for (int i = 0; i < KEEP_LOG_COUNT; i++) {
auto add_to_entries = [&](const char* filename) {
std::string log_file(filename);
@@ -416,7 +253,7 @@ static void choose_recovery_file(Device* device) {
size_t chosen_item = 0;
while (true) {
- chosen_item = ui->ShowMenu(
+ chosen_item = device->GetUI()->ShowMenu(
headers, entries, chosen_item, true,
std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
@@ -426,11 +263,11 @@ static void choose_recovery_file(Device* device) {
}
if (entries[chosen_item] == "Back") break;
- ui->ShowFile(entries[chosen_item]);
+ device->GetUI()->ShowFile(entries[chosen_item]);
}
}
-static void run_graphics_test() {
+static void run_graphics_test(RecoveryUI* ui) {
// Switch to graphics screen.
ui->ShowText(false);
@@ -473,14 +310,19 @@ static void run_graphics_test() {
ui->ShowText(true);
}
-// Returns REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default,
-// which is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery.
-static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
+// Shows the recovery UI and waits for user input. Returns one of the device builtin actions, such
+// as REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default, which
+// is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery.
+static Device::BuiltinAction PromptAndWait(Device* device, InstallResult status) {
+ auto ui = device->GetUI();
for (;;) {
- finish_recovery();
+ FinishRecovery(ui);
switch (status) {
case INSTALL_SUCCESS:
case INSTALL_NONE:
+ case INSTALL_SKIPPED:
+ case INSTALL_RETRY:
+ case INSTALL_KEY_INTERRUPTED:
ui->SetBackground(RecoveryUI::NO_COMMAND);
break;
@@ -488,6 +330,12 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
case INSTALL_CORRUPT:
ui->SetBackground(RecoveryUI::ERROR);
break;
+
+ case INSTALL_REBOOT:
+ // All the reboots should have been handled prior to entering PromptAndWait() or immediately
+ // after installing a package.
+ LOG(FATAL) << "Invalid status code of INSTALL_REBOOT";
+ break;
}
ui->SetProgressType(RecoveryUI::EMPTY);
@@ -506,6 +354,8 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
: device->InvokeMenuItem(chosen_item);
switch (chosen_action) {
+ case Device::REBOOT_FROM_FASTBOOT: // Can not happen
+ case Device::SHUTDOWN_FROM_FASTBOOT: // Can not happen
case Device::NO_ACTION:
break;
@@ -556,7 +406,7 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
status = ApplyFromAdb(device, false /* rescue_mode */, &reboot_action);
} else {
adb = false;
- status = ApplyFromSdcard(device, ui);
+ status = ApplyFromSdcard(device);
}
ui->Print("\nInstall from %s completed with status %d.\n", adb ? "ADB" : "SD card", status);
@@ -567,7 +417,7 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
if (status != INSTALL_SUCCESS) {
ui->SetBackground(RecoveryUI::ERROR);
ui->Print("Installation aborted.\n");
- copy_logs(save_current_log, has_cache, sehandle);
+ copy_logs(save_current_log);
} else if (!ui->IsTextVisible()) {
return Device::NO_ACTION; // reboot if logs aren't visible
}
@@ -579,7 +429,7 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
break;
case Device::RUN_GRAPHICS_TEST:
- run_graphics_test();
+ run_graphics_test(ui);
break;
case Device::RUN_LOCALE_TEST: {
@@ -588,8 +438,7 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
break;
}
case Device::MOUNT_SYSTEM:
- // the system partition is mounted at /mnt/system
- if (ensure_path_mounted_at(get_system_root(), "/mnt/system") != -1) {
+ if (ensure_path_mounted_at(android::fs_mgr::GetSystemRoot(), "/mnt/system") != -1) {
ui->Print("Mounted /system.\n");
}
break;
@@ -604,74 +453,17 @@ static void print_property(const char* key, const char* name, void* /* cookie */
printf("%s=%s\n", key, name);
}
-static bool is_battery_ok(int* required_battery_level) {
- using android::hardware::health::V1_0::BatteryStatus;
- using android::hardware::health::V2_0::get_health_service;
- using android::hardware::health::V2_0::IHealth;
- using android::hardware::health::V2_0::Result;
- using android::hardware::health::V2_0::toString;
-
- android::sp<IHealth> health = get_health_service();
-
- static constexpr int BATTERY_READ_TIMEOUT_IN_SEC = 10;
- int wait_second = 0;
- while (true) {
- auto charge_status = BatteryStatus::UNKNOWN;
-
- if (health == nullptr) {
- LOG(WARNING) << "no health implementation is found, assuming defaults";
- } else {
- health
- ->getChargeStatus([&charge_status](auto res, auto out_status) {
- if (res == Result::SUCCESS) {
- charge_status = out_status;
- }
- })
- .isOk(); // should not have transport error
- }
-
- // Treat unknown status as charged.
- bool charged = (charge_status != BatteryStatus::DISCHARGING &&
- charge_status != BatteryStatus::NOT_CHARGING);
-
- Result res = Result::UNKNOWN;
- int32_t capacity = INT32_MIN;
- if (health != nullptr) {
- health
- ->getCapacity([&res, &capacity](auto out_res, auto out_capacity) {
- res = out_res;
- capacity = out_capacity;
- })
- .isOk(); // should not have transport error
- }
-
- LOG(INFO) << "charge_status " << toString(charge_status) << ", charged " << charged
- << ", status " << toString(res) << ", capacity " << capacity;
- // At startup, the battery drivers in devices like N5X/N6P take some time to load
- // the battery profile. Before the load finishes, it reports value 50 as a fake
- // capacity. BATTERY_READ_TIMEOUT_IN_SEC is set that the battery drivers are expected
- // to finish loading the battery profile earlier than 10 seconds after kernel startup.
- if (res == Result::SUCCESS && capacity == 50) {
- if (wait_second < BATTERY_READ_TIMEOUT_IN_SEC) {
- sleep(1);
- wait_second++;
- continue;
- }
- }
- // If we can't read battery percentage, it may be a device without battery. In this
- // situation, use 100 as a fake battery percentage.
- if (res != Result::SUCCESS) {
- capacity = 100;
- }
-
- // GmsCore enters recovery mode to install package when having enough battery percentage.
- // Normally, the threshold is 40% without charger and 20% with charger. So we should check
- // battery with a slightly lower limitation.
- static constexpr int BATTERY_OK_PERCENTAGE = 20;
- static constexpr int BATTERY_WITH_CHARGER_OK_PERCENTAGE = 15;
- *required_battery_level = charged ? BATTERY_WITH_CHARGER_OK_PERCENTAGE : BATTERY_OK_PERCENTAGE;
- return capacity >= *required_battery_level;
- }
+static bool IsBatteryOk(int* required_battery_level) {
+ // GmsCore enters recovery mode to install package when having enough battery percentage.
+ // Normally, the threshold is 40% without charger and 20% with charger. So we check the battery
+ // level against a slightly lower limit.
+ constexpr int BATTERY_OK_PERCENTAGE = 20;
+ constexpr int BATTERY_WITH_CHARGER_OK_PERCENTAGE = 15;
+
+ auto battery_info = GetBatteryInfo();
+ *required_battery_level =
+ battery_info.charging ? BATTERY_WITH_CHARGER_OK_PERCENTAGE : BATTERY_OK_PERCENTAGE;
+ return battery_info.capacity >= *required_battery_level;
}
// Set the retry count to |retry_count| in BCB.
@@ -726,6 +518,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri
static constexpr struct option OPTIONS[] = {
{ "fastboot", no_argument, nullptr, 0 },
{ "fsck_unshare_blocks", no_argument, nullptr, 0 },
+ { "install_with_fuse", no_argument, nullptr, 0 },
{ "just_exit", no_argument, nullptr, 'x' },
{ "locale", required_argument, nullptr, 0 },
{ "prompt_and_wipe_data", no_argument, nullptr, 0 },
@@ -746,6 +539,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri
};
const char* update_package = nullptr;
+ bool install_with_fuse = false; // memory map the update package by default.
bool should_wipe_data = false;
bool should_prompt_and_wipe_data = false;
bool should_wipe_cache = false;
@@ -781,12 +575,12 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri
std::string option = OPTIONS[option_index].name;
if (option == "fsck_unshare_blocks") {
fsck_unshare_blocks = true;
- } else if (option == "locale" || option == "fastboot") {
+ } else if (option == "install_with_fuse") {
+ install_with_fuse = true;
+ } else if (option == "locale" || option == "fastboot" || option == "reason") {
// Handled in recovery_main.cpp
} else if (option == "prompt_and_wipe_data") {
should_prompt_and_wipe_data = true;
- } else if (option == "reason") {
- reason = optarg;
} else if (option == "rescue") {
rescue = true;
} else if (option == "retry_count") {
@@ -820,15 +614,18 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri
}
optind = 1;
- printf("stage is [%s]\n", stage.c_str());
- printf("reason is [%s]\n", reason);
+ printf("stage is [%s]\n", device->GetStage().value_or("").c_str());
+ printf("reason is [%s]\n", device->GetReason().value_or("").c_str());
+
+ auto ui = device->GetUI();
// Set background string to "installing security update" for security update,
// otherwise set it to "installing system update".
ui->SetSystemUpdateText(security_update);
int st_cur, st_max;
- if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) {
+ if (!device->GetStage().has_value() &&
+ sscanf(device->GetStage().value().c_str(), "%d/%d", &st_cur, &st_max) == 2) {
ui->SetStage(st_cur, st_max);
}
@@ -849,9 +646,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri
property_list(print_property, nullptr);
printf("\n");
- ui->Print("Supported API: %d\n", kRecoveryApiVersion);
-
- int status = INSTALL_SUCCESS;
+ InstallResult status = INSTALL_SUCCESS;
// next_action indicates the next target to reboot into upon finishing the install. It could be
// overridden to a different reboot target per user request.
Device::BuiltinAction next_action = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
@@ -861,12 +656,10 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri
// to log the update attempt since update_package is non-NULL.
save_current_log = true;
- int required_battery_level;
- if (retry_count == 0 && !is_battery_ok(&required_battery_level)) {
+ if (int required_battery_level; retry_count == 0 && !IsBatteryOk(&required_battery_level)) {
ui->Print("battery capacity is not enough for installing package: %d%% needed\n",
required_battery_level);
- // Log the error code to last_install when installation skips due to
- // low battery.
+ // Log the error code to last_install when installation skips due to low battery.
log_failure_code(kLowBattery, update_package);
status = INSTALL_SKIPPED;
} else if (retry_count == 0 && bootreason_in_blacklist()) {
@@ -881,7 +674,29 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri
set_retry_bootloader_message(retry_count + 1, args);
}
- status = install_package(update_package, should_wipe_cache, true, retry_count, ui);
+ if (update_package[0] == '@') {
+ ensure_path_mounted(update_package + 1);
+ } else {
+ ensure_path_mounted(update_package);
+ }
+
+ if (install_with_fuse) {
+ LOG(INFO) << "Installing package " << update_package << " with fuse";
+ status = InstallWithFuseFromPath(update_package, ui);
+ } else if (auto memory_package = Package::CreateMemoryPackage(
+ update_package,
+ std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
+ memory_package != nullptr) {
+ status = InstallPackage(memory_package.get(), update_package, should_wipe_cache,
+ retry_count, ui);
+ } else {
+ // We may fail to memory map the package on 32 bit builds for packages with 2GiB+ size.
+ // In such cases, we will try to install the package with fuse. This is not the default
+ // installation method because it introduces a layer of indirection from the kernel space.
+ LOG(WARNING) << "Failed to memory map package " << update_package
+ << "; falling back to install with fuse";
+ status = InstallWithFuseFromPath(update_package, ui);
+ }
if (status != INSTALL_SUCCESS) {
ui->Print("Installation aborted.\n");
@@ -889,14 +704,14 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri
// RETRY_LIMIT times before we abandon this OTA update.
static constexpr int RETRY_LIMIT = 4;
if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) {
- copy_logs(save_current_log, has_cache, sehandle);
+ copy_logs(save_current_log);
retry_count += 1;
set_retry_bootloader_message(retry_count, args);
// Print retry count on screen.
ui->Print("Retry attempt %d\n", retry_count);
- // Reboot and retry the update
- if (!reboot("reboot,recovery")) {
+ // Reboot back into recovery to retry the update.
+ if (!Reboot("recovery")) {
ui->Print("Reboot failed\n");
} else {
while (true) {
@@ -907,14 +722,15 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri
// If this is an eng or userdebug build, then automatically
// turn the text display on if the script fails so the error
// message is visible.
- if (is_ro_debuggable()) {
+ if (IsRoDebuggable()) {
ui->ShowText(true);
}
}
}
} else if (should_wipe_data) {
save_current_log = true;
- bool convert_fbe = reason && strcmp(reason, "convert_fbe") == 0;
+ CHECK(device->GetReason().has_value());
+ bool convert_fbe = device->GetReason().value() == "convert_fbe";
if (!WipeData(device, convert_fbe)) {
status = INSTALL_ERROR;
}
@@ -934,7 +750,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri
status = INSTALL_ERROR;
}
} else if (should_wipe_ab) {
- if (!wipe_ab_device(wipe_package_size)) {
+ if (!WipeAbDevice(device, wipe_package_size)) {
status = INSTALL_ERROR;
}
} else if (sideload) {
@@ -964,7 +780,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri
// If this is an eng or userdebug build, automatically turn on the text display if no command
// is specified. Note that this should be called before setting the background to avoid
// flickering the background image.
- if (is_ro_debuggable()) {
+ if (IsRoDebuggable()) {
ui->ShowText(true);
}
status = INSTALL_NONE; // No command specified
@@ -989,7 +805,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri
// for 5s followed by an automatic reboot.
if (status != INSTALL_REBOOT) {
if (status == INSTALL_NONE || ui->IsTextVisible()) {
- Device::BuiltinAction temp = prompt_and_wait(device, status);
+ auto temp = PromptAndWait(device, status);
if (temp != Device::NO_ACTION) {
next_action = temp;
}
@@ -997,7 +813,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector<std::stri
}
// Save logs and clean up before rebooting or shutting down.
- finish_recovery();
+ FinishRecovery(ui);
return next_action;
}
diff --git a/recovery_main.cpp b/recovery_main.cpp
index de8ac1f42..89253dcd2 100644
--- a/recovery_main.cpp
+++ b/recovery_main.cpp
@@ -41,34 +41,33 @@
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <bootloader_message/bootloader_message.h>
-#include <cutils/android_reboot.h>
#include <cutils/sockets.h>
+#include <fs_mgr/roots.h>
#include <private/android_logger.h> /* private pmsg functions */
#include <selinux/android.h>
#include <selinux/label.h>
#include <selinux/selinux.h>
-#include "common.h"
#include "fastboot/fastboot.h"
#include "install/wipe_data.h"
-#include "otautil/logging.h"
+#include "otautil/boot_state.h"
#include "otautil/paths.h"
-#include "otautil/roots.h"
#include "otautil/sysutil.h"
#include "recovery.h"
#include "recovery_ui/device.h"
#include "recovery_ui/stub_ui.h"
#include "recovery_ui/ui.h"
+#include "recovery_utils/logging.h"
+#include "recovery_utils/roots.h"
static constexpr const char* COMMAND_FILE = "/cache/recovery/command";
static constexpr const char* LOCALE_FILE = "/cache/recovery/last_locale";
-static constexpr const char* CACHE_ROOT = "/cache";
+static RecoveryUI* ui = nullptr;
-bool has_cache = false;
-
-RecoveryUI* ui = nullptr;
-struct selabel_handle* sehandle;
+static bool IsRoDebuggable() {
+ return android::base::GetBoolProperty("ro.debuggable", false);
+}
static void UiLogger(android::base::LogId /* id */, android::base::LogSeverity severity,
const char* /* tag */, const char* /* file */, unsigned int /* line */,
@@ -81,11 +80,12 @@ static void UiLogger(android::base::LogId /* id */, android::base::LogSeverity s
}
}
+// Parses the command line argument from various sources; and reads the stage field from BCB.
// command line args come from, in decreasing precedence:
// - the actual command line
// - the bootloader control block (one per line, after "recovery")
// - the contents of COMMAND_FILE (one per line)
-static std::vector<std::string> get_args(const int argc, char** const argv) {
+static std::vector<std::string> get_args(const int argc, char** const argv, std::string* stage) {
CHECK_GT(argc, 0);
bootloader_message boot = {};
@@ -95,7 +95,9 @@ static std::vector<std::string> get_args(const int argc, char** const argv) {
// If fails, leave a zeroed bootloader_message.
boot = {};
}
- stage = std::string(boot.stage);
+ if (stage) {
+ *stage = std::string(boot.stage);
+ }
std::string boot_command;
if (boot.command[0] != 0) {
@@ -131,7 +133,7 @@ static std::vector<std::string> get_args(const int argc, char** const argv) {
}
// --- if that doesn't work, try the command file (if we have /cache).
- if (args.size() == 1 && has_cache) {
+ if (args.size() == 1 && HasCache()) {
std::string content;
if (ensure_path_mounted(COMMAND_FILE) == 0 &&
android::base::ReadFileToString(COMMAND_FILE, &content)) {
@@ -148,7 +150,7 @@ static std::vector<std::string> get_args(const int argc, char** const argv) {
// Write the arguments (excluding the filename in args[0]) back into the
// bootloader control block. So the device will always boot into recovery to
- // finish the pending work, until finish_recovery() is called.
+ // finish the pending work, until FinishRecovery() is called.
std::vector<std::string> options(args.cbegin() + 1, args.cend());
if (!update_bootloader_message(options, &err)) {
LOG(ERROR) << "Failed to set BCB message: " << err;
@@ -331,14 +333,15 @@ int main(int argc, char** argv) {
redirect_stdio(Paths::Get().temporary_log_file().c_str());
load_volume_table();
- has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;
- std::vector<std::string> args = get_args(argc, argv);
+ std::string stage;
+ std::vector<std::string> args = get_args(argc, argv, &stage);
auto args_to_parse = StringVectorToNullTerminatedArray(args);
static constexpr struct option OPTIONS[] = {
{ "fastboot", no_argument, nullptr, 0 },
{ "locale", required_argument, nullptr, 0 },
+ { "reason", required_argument, nullptr, 0 },
{ "show_text", no_argument, nullptr, 't' },
{ nullptr, 0, nullptr, 0 },
};
@@ -346,6 +349,13 @@ int main(int argc, char** argv) {
bool show_text = false;
bool fastboot = false;
std::string locale;
+ std::string reason;
+
+ // The code here is only interested in the options that signal the intent to start fastbootd or
+ // recovery. Unrecognized options are likely meant for recovery, which will be processed later in
+ // start_recovery(). Suppress the warnings for such -- even if some flags were indeed invalid, the
+ // code in start_recovery() will capture and report them.
+ opterr = 0;
int arg;
int option_index;
@@ -359,6 +369,8 @@ int main(int argc, char** argv) {
std::string option = OPTIONS[option_index].name;
if (option == "locale") {
locale = optarg;
+ } else if (option == "reason") {
+ reason = optarg;
} else if (option == "fastboot" &&
android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
fastboot = true;
@@ -368,14 +380,14 @@ int main(int argc, char** argv) {
}
}
optind = 1;
+ opterr = 1;
if (locale.empty()) {
- if (has_cache) {
+ if (HasCache()) {
locale = load_locale_from_cache();
}
if (locale.empty()) {
- static constexpr const char* DEFAULT_LOCALE = "en-US";
locale = DEFAULT_LOCALE;
}
}
@@ -415,9 +427,12 @@ int main(int argc, char** argv) {
device->ResetUI(new StubRecoveryUI());
}
}
+
+ BootState boot_state(reason, stage); // recovery_main owns the state of boot.
+ device->SetBootState(&boot_state);
ui = device->GetUI();
- if (!has_cache) {
+ if (!HasCache()) {
device->RemoveMenuItemForAction(Device::WIPE_CACHE);
}
@@ -425,7 +440,7 @@ int main(int argc, char** argv) {
device->RemoveMenuItemForAction(Device::ENTER_FASTBOOT);
}
- if (!is_ro_debuggable()) {
+ if (!IsRoDebuggable()) {
device->RemoveMenuItemForAction(Device::ENTER_RESCUE);
}
@@ -435,7 +450,7 @@ int main(int argc, char** argv) {
LOG(INFO) << "Starting recovery (pid " << getpid() << ") on " << ctime(&start);
LOG(INFO) << "locale is [" << locale << "]";
- sehandle = selinux_android_file_context_handle();
+ auto sehandle = selinux_android_file_context_handle();
selinux_android_set_sehandle(sehandle);
if (!sehandle) {
ui->Print("Warning: No file_contexts\n");
@@ -448,7 +463,7 @@ int main(int argc, char** argv) {
listener_thread.detach();
while (true) {
- std::string usb_config = fastboot ? "fastboot" : is_ro_debuggable() ? "adb" : "none";
+ std::string usb_config = fastboot ? "fastboot" : IsRoDebuggable() ? "adb" : "none";
std::string usb_state = android::base::GetProperty("sys.usb.state", "none");
if (usb_config != usb_state) {
if (!SetUsbConfig("none")) {
@@ -472,27 +487,31 @@ int main(int argc, char** argv) {
switch (ret) {
case Device::SHUTDOWN:
ui->Print("Shutting down...\n");
- // TODO: Move all the reboots to reboot(), which should conditionally set quiescent flag.
- android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,");
+ Shutdown("userrequested,recovery");
+ break;
+
+ case Device::SHUTDOWN_FROM_FASTBOOT:
+ ui->Print("Shutting down...\n");
+ Shutdown("userrequested,fastboot");
break;
case Device::REBOOT_BOOTLOADER:
ui->Print("Rebooting to bootloader...\n");
- android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader");
+ Reboot("bootloader");
break;
case Device::REBOOT_FASTBOOT:
ui->Print("Rebooting to recovery/fastboot...\n");
- android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,fastboot");
+ Reboot("fastboot");
break;
case Device::REBOOT_RECOVERY:
ui->Print("Rebooting to recovery...\n");
- reboot("reboot,recovery");
+ Reboot("recovery");
break;
case Device::REBOOT_RESCUE: {
- // Not using `reboot("reboot,rescue")`, as it requires matching support in kernel and/or
+ // Not using `Reboot("rescue")`, as it requires matching support in kernel and/or
// bootloader.
bootloader_message boot = {};
strlcpy(boot.command, "boot-rescue", sizeof(boot.command));
@@ -503,14 +522,14 @@ int main(int argc, char** argv) {
continue;
}
ui->Print("Rebooting to recovery/rescue...\n");
- reboot("reboot,recovery");
+ Reboot("recovery");
break;
}
case Device::ENTER_FASTBOOT:
- if (logical_partitions_mapped()) {
+ if (android::fs_mgr::LogicalPartitionsMapped()) {
ui->Print("Partitions may be mounted - rebooting to enter fastboot.");
- android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,fastboot");
+ Reboot("fastboot");
} else {
LOG(INFO) << "Entering fastboot";
fastboot = true;
@@ -522,9 +541,19 @@ int main(int argc, char** argv) {
fastboot = false;
break;
+ case Device::REBOOT:
+ ui->Print("Rebooting...\n");
+ Reboot("userrequested,recovery");
+ break;
+
+ case Device::REBOOT_FROM_FASTBOOT:
+ ui->Print("Rebooting...\n");
+ Reboot("userrequested,fastboot");
+ break;
+
default:
ui->Print("Rebooting...\n");
- reboot("reboot,");
+ Reboot("unknown" + std::to_string(ret));
break;
}
}
diff --git a/recovery_ui/Android.bp b/recovery_ui/Android.bp
index ee3149d5e..149ef8acc 100644
--- a/recovery_ui/Android.bp
+++ b/recovery_ui/Android.bp
@@ -23,6 +23,7 @@ cc_library {
srcs: [
"device.cpp",
"screen_ui.cpp",
+ "stub_ui.cpp",
"ui.cpp",
"vr_ui.cpp",
"wear_ui.cpp",
diff --git a/recovery_ui/device.cpp b/recovery_ui/device.cpp
index e7ae1a3e1..d46df92d3 100644
--- a/recovery_ui/device.cpp
+++ b/recovery_ui/device.cpp
@@ -23,6 +23,7 @@
#include <android-base/logging.h>
+#include "otautil/boot_state.h"
#include "recovery_ui/ui.h"
static std::vector<std::pair<std::string, Device::BuiltinAction>> g_menu_actions{
@@ -95,3 +96,15 @@ int Device::HandleMenuKey(int key, bool visible) {
return ui_->HasThreeButtons() ? kNoAction : kHighlightDown;
}
}
+
+void Device::SetBootState(const BootState* state) {
+ boot_state_ = state;
+}
+
+std::optional<std::string> Device::GetReason() const {
+ return boot_state_ ? std::make_optional(boot_state_->reason()) : std::nullopt;
+}
+
+std::optional<std::string> Device::GetStage() const {
+ return boot_state_ ? std::make_optional(boot_state_->stage()) : std::nullopt;
+}
diff --git a/recovery_ui/include/recovery_ui/device.h b/recovery_ui/include/recovery_ui/device.h
index 7c76cdb0a..f4f993638 100644
--- a/recovery_ui/include/recovery_ui/device.h
+++ b/recovery_ui/include/recovery_ui/device.h
@@ -20,12 +20,15 @@
#include <stddef.h>
#include <memory>
+#include <optional>
#include <string>
#include <vector>
// Forward declaration to avoid including "ui.h".
class RecoveryUI;
+class BootState;
+
class Device {
public:
static constexpr const int kNoAction = -1;
@@ -58,6 +61,8 @@ class Device {
REBOOT_FASTBOOT = 17,
REBOOT_RECOVERY = 18,
REBOOT_RESCUE = 19,
+ REBOOT_FROM_FASTBOOT = 20,
+ SHUTDOWN_FROM_FASTBOOT = 21,
};
explicit Device(RecoveryUI* ui);
@@ -124,9 +129,16 @@ class Device {
return true;
}
+ void SetBootState(const BootState* state);
+ // The getters for reason and stage may return std::nullopt until StartRecovery() is called. It's
+ // the caller's responsibility to perform the check and handle the exception.
+ std::optional<std::string> GetReason() const;
+ std::optional<std::string> GetStage() const;
+
private:
// The RecoveryUI object that should be used to display the user interface for this device.
std::unique_ptr<RecoveryUI> ui_;
+ const BootState* boot_state_{ nullptr };
};
// Disable name mangling, as this function will be loaded via dlsym(3).
diff --git a/recovery_ui/include/recovery_ui/screen_ui.h b/recovery_ui/include/recovery_ui/screen_ui.h
index 5cda2a2e5..92b3c2546 100644
--- a/recovery_ui/include/recovery_ui/screen_ui.h
+++ b/recovery_ui/include/recovery_ui/screen_ui.h
@@ -286,6 +286,9 @@ class ScreenRecoveryUI : public RecoveryUI, public DrawInterface {
// selected.
virtual int SelectMenu(int sel);
+ // Returns the help message displayed on top of the menu.
+ virtual std::vector<std::string> GetMenuHelpMessage() const;
+
virtual void draw_background_locked();
virtual void draw_foreground_locked();
virtual void draw_screen_locked();
diff --git a/recovery_ui/include/recovery_ui/stub_ui.h b/recovery_ui/include/recovery_ui/stub_ui.h
index fb1d8c7a6..511b1314a 100644
--- a/recovery_ui/include/recovery_ui/stub_ui.h
+++ b/recovery_ui/include/recovery_ui/stub_ui.h
@@ -62,11 +62,9 @@ class StubRecoveryUI : public RecoveryUI {
// menu display
size_t ShowMenu(const std::vector<std::string>& /* headers */,
- const std::vector<std::string>& /* items */, size_t initial_selection,
+ const std::vector<std::string>& /* items */, size_t /* initial_selection */,
bool /* menu_only */,
- const std::function<int(int, bool)>& /* key_handler */) override {
- return initial_selection;
- }
+ const std::function<int(int, bool)>& /* key_handler */) override;
size_t ShowPromptWipeDataMenu(const std::vector<std::string>& /* backup_headers */,
const std::vector<std::string>& /* backup_items */,
diff --git a/recovery_ui/include/recovery_ui/ui.h b/recovery_ui/include/recovery_ui/ui.h
index d55322cf0..08ec1d76a 100644
--- a/recovery_ui/include/recovery_ui/ui.h
+++ b/recovery_ui/include/recovery_ui/ui.h
@@ -27,6 +27,8 @@
#include <thread>
#include <vector>
+static constexpr const char* DEFAULT_LOCALE = "en-US";
+
// Abstract class for controlling the user interface during recovery.
class RecoveryUI {
public:
@@ -116,7 +118,7 @@ class RecoveryUI {
// Returns true if you have the volume up/down and power trio typical of phones and tablets, false
// otherwise.
- virtual bool HasThreeButtons();
+ virtual bool HasThreeButtons() const;
// Returns true if it has a power key.
virtual bool HasPowerKey() const;
@@ -228,20 +230,23 @@ class RecoveryUI {
bool InitScreensaver();
void SetScreensaverState(ScreensaverState state);
+
// Key event input queue
std::mutex key_queue_mutex;
std::condition_variable key_queue_cond;
bool key_interrupted_;
int key_queue[256], key_queue_len;
- char key_pressed[KEY_MAX + 1]; // under key_queue_mutex
- int key_last_down; // under key_queue_mutex
- bool key_long_press; // under key_queue_mutex
- int key_down_count; // under key_queue_mutex
- bool enable_reboot; // under key_queue_mutex
- int rel_sum;
+ // key press events
+ std::mutex key_press_mutex;
+ char key_pressed[KEY_MAX + 1];
+ int key_last_down;
+ bool key_long_press;
+ int key_down_count;
+ bool enable_reboot;
+
+ int rel_sum;
int consecutive_power_keys;
- int last_key;
bool has_power_key;
bool has_up_key;
diff --git a/recovery_ui/screen_ui.cpp b/recovery_ui/screen_ui.cpp
index 870db621c..087fc0e84 100644
--- a/recovery_ui/screen_ui.cpp
+++ b/recovery_ui/screen_ui.cpp
@@ -673,6 +673,19 @@ void ScreenRecoveryUI::SetTitle(const std::vector<std::string>& lines) {
title_lines_ = lines;
}
+std::vector<std::string> ScreenRecoveryUI::GetMenuHelpMessage() const {
+ // clang-format off
+ static std::vector<std::string> REGULAR_HELP{
+ "Use volume up/down and power.",
+ };
+ static std::vector<std::string> LONG_PRESS_HELP{
+ "Any button cycles highlight.",
+ "Long-press activates.",
+ };
+ // clang-format on
+ return HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP;
+}
+
// Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex
// locked.
void ScreenRecoveryUI::draw_screen_locked() {
@@ -685,16 +698,7 @@ void ScreenRecoveryUI::draw_screen_locked() {
gr_color(0, 0, 0, 255);
gr_clear();
- // clang-format off
- static std::vector<std::string> REGULAR_HELP{
- "Use volume up/down and power.",
- };
- static std::vector<std::string> LONG_PRESS_HELP{
- "Any button cycles highlight.",
- "Long-press activates.",
- };
- // clang-format on
- draw_menu_and_text_buffer_locked(HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP);
+ draw_menu_and_text_buffer_locked(GetMenuHelpMessage());
}
// Draws the menu and text buffer on the screen. Should only be called with updateMutex locked.
@@ -817,12 +821,22 @@ std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadBitmap(const std::string& filen
std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadLocalizedBitmap(const std::string& filename) {
GRSurface* surface;
- if (auto result = res_create_localized_alpha_surface(filename.c_str(), locale_.c_str(), &surface);
- result < 0) {
- LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")";
- return nullptr;
+ auto result = res_create_localized_alpha_surface(filename.c_str(), locale_.c_str(), &surface);
+ if (result == 0) {
+ return std::unique_ptr<GRSurface>(surface);
}
- return std::unique_ptr<GRSurface>(surface);
+ // TODO(xunchang) create a error code enum to refine the retry condition.
+ LOG(WARNING) << "Failed to load bitmap " << filename << " for locale " << locale_ << " (error "
+ << result << "). Falling back to use default locale.";
+
+ result = res_create_localized_alpha_surface(filename.c_str(), DEFAULT_LOCALE, &surface);
+ if (result == 0) {
+ return std::unique_ptr<GRSurface>(surface);
+ }
+
+ LOG(ERROR) << "Failed to load bitmap " << filename << " for locale " << DEFAULT_LOCALE
+ << " (error " << result << ")";
+ return nullptr;
}
static char** Alloc2d(size_t rows, size_t cols) {
@@ -1253,7 +1267,7 @@ size_t ScreenRecoveryUI::ShowMenu(const std::vector<std::string>& headers,
return initial_selection;
}
- return ShowMenu(CreateMenu(headers, items, initial_selection), menu_only, key_handler);
+ return ShowMenu(std::move(menu), menu_only, key_handler);
}
size_t ScreenRecoveryUI::ShowPromptWipeDataMenu(const std::vector<std::string>& backup_headers,
diff --git a/recovery_ui/stub_ui.cpp b/recovery_ui/stub_ui.cpp
new file mode 100644
index 000000000..a56b3f725
--- /dev/null
+++ b/recovery_ui/stub_ui.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "recovery_ui/stub_ui.h"
+
+#include <android-base/logging.h>
+
+#include "recovery_ui/device.h"
+
+size_t StubRecoveryUI::ShowMenu(const std::vector<std::string>& /* headers */,
+ const std::vector<std::string>& /* items */,
+ size_t /* initial_selection */, bool /* menu_only */,
+ const std::function<int(int, bool)>& /*key_handler*/) {
+ while (true) {
+ int key = WaitKey();
+ // Exit the loop in the case of interruption or time out.
+ if (key == static_cast<int>(KeyError::INTERRUPTED) ||
+ key == static_cast<int>(KeyError::TIMED_OUT)) {
+ return static_cast<size_t>(key);
+ }
+ }
+ LOG(FATAL) << "Unreachable key selected in ShowMenu of stub UI";
+}
diff --git a/recovery_ui/ui.cpp b/recovery_ui/ui.cpp
index b7107ff21..6f5cbbca6 100644
--- a/recovery_ui/ui.cpp
+++ b/recovery_ui/ui.cpp
@@ -70,7 +70,6 @@ RecoveryUI::RecoveryUI()
key_down_count(0),
enable_reboot(true),
consecutive_power_keys(0),
- last_key(-1),
has_power_key(false),
has_up_key(false),
has_down_key(false),
@@ -346,7 +345,7 @@ void RecoveryUI::ProcessKey(int key_code, int updown) {
bool long_press = false;
{
- std::lock_guard<std::mutex> lg(key_queue_mutex);
+ std::lock_guard<std::mutex> lg(key_press_mutex);
key_pressed[key_code] = updown;
if (updown) {
++key_down_count;
@@ -375,7 +374,7 @@ void RecoveryUI::ProcessKey(int key_code, int updown) {
case RecoveryUI::REBOOT:
if (reboot_enabled) {
- reboot("reboot,");
+ Reboot("userrequested,recovery,ui");
while (true) {
pause();
}
@@ -393,7 +392,7 @@ void RecoveryUI::TimeKey(int key_code, int count) {
std::this_thread::sleep_for(750ms); // 750 ms == "long"
bool long_press = false;
{
- std::lock_guard<std::mutex> lg(key_queue_mutex);
+ std::lock_guard<std::mutex> lg(key_press_mutex);
if (key_last_down == key_code && key_down_count == count) {
long_press = key_long_press = true;
}
@@ -419,7 +418,7 @@ void RecoveryUI::SetScreensaverState(ScreensaverState state) {
LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_
<< "%)";
} else {
- LOG(ERROR) << "Unable to set brightness to normal";
+ LOG(WARNING) << "Unable to set brightness to normal";
}
break;
case ScreensaverState::DIMMED:
@@ -429,7 +428,7 @@ void RecoveryUI::SetScreensaverState(ScreensaverState state) {
<< "%)";
screensaver_state_ = ScreensaverState::DIMMED;
} else {
- LOG(ERROR) << "Unable to set brightness to dim";
+ LOG(WARNING) << "Unable to set brightness to dim";
}
break;
case ScreensaverState::OFF:
@@ -437,7 +436,7 @@ void RecoveryUI::SetScreensaverState(ScreensaverState state) {
LOG(INFO) << "Brightness: 0 (off)";
screensaver_state_ = ScreensaverState::OFF;
} else {
- LOG(ERROR) << "Unable to set brightness to off";
+ LOG(WARNING) << "Unable to set brightness to off";
}
break;
default:
@@ -518,18 +517,18 @@ bool RecoveryUI::IsUsbConnected() {
}
bool RecoveryUI::IsKeyPressed(int key) {
- std::lock_guard<std::mutex> lg(key_queue_mutex);
+ std::lock_guard<std::mutex> lg(key_press_mutex);
int pressed = key_pressed[key];
return pressed;
}
bool RecoveryUI::IsLongPress() {
- std::lock_guard<std::mutex> lg(key_queue_mutex);
+ std::lock_guard<std::mutex> lg(key_press_mutex);
bool result = key_long_press;
return result;
}
-bool RecoveryUI::HasThreeButtons() {
+bool RecoveryUI::HasThreeButtons() const {
return has_power_key && has_up_key && has_down_key;
}
@@ -548,7 +547,7 @@ void RecoveryUI::FlushKeys() {
RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) {
{
- std::lock_guard<std::mutex> lg(key_queue_mutex);
+ std::lock_guard<std::mutex> lg(key_press_mutex);
key_long_press = false;
}
@@ -585,13 +584,12 @@ RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) {
consecutive_power_keys = 0;
}
- last_key = key;
return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE;
}
void RecoveryUI::KeyLongPress(int) {}
void RecoveryUI::SetEnableReboot(bool enabled) {
- std::lock_guard<std::mutex> lg(key_queue_mutex);
+ std::lock_guard<std::mutex> lg(key_press_mutex);
enable_reboot = enabled;
}
diff --git a/recovery_utils/Android.bp b/recovery_utils/Android.bp
new file mode 100644
index 000000000..bf79a2e87
--- /dev/null
+++ b/recovery_utils/Android.bp
@@ -0,0 +1,81 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_defaults {
+ name: "librecovery_utils_defaults",
+
+ defaults: [
+ "recovery_defaults",
+ ],
+
+ shared_libs: [
+ "android.hardware.health@2.0",
+ "libbase",
+ "libext4_utils",
+ "libfs_mgr",
+ "libhidlbase",
+ "libselinux",
+ "libutils",
+ ],
+
+ static_libs: [
+ "libotautil",
+
+ // External dependencies.
+ "libfstab",
+ "libhealthhalutils",
+ ],
+}
+
+// A utility lib that's local to recovery (in contrast, libotautil is exposed to device-specific
+// recovery_ui lib as well as device-specific updater).
+cc_library_static {
+ name: "librecovery_utils",
+
+ recovery_available: true,
+
+ defaults: [
+ "librecovery_utils_defaults",
+ ],
+
+ srcs: [
+ "battery_utils.cpp",
+ "logging.cpp",
+ "parse_install_logs.cpp",
+ "roots.cpp",
+ "thermalutil.cpp",
+ ],
+
+ header_libs: [
+ "libvold_headers",
+ ],
+
+ export_include_dirs: [
+ "include",
+ ],
+
+ export_static_lib_headers: [
+ // roots.h includes <fstab/fstab.h>.
+ "libfstab",
+ ],
+
+ // Should avoid exposing to the libs that might be used in device-specific codes (e.g.
+ // libedify, libotautil, librecovery_ui).
+ visibility: [
+ "//bootable/recovery",
+ "//bootable/recovery/install",
+ "//bootable/recovery/minadbd",
+ "//bootable/recovery/tests",
+ ],
+}
diff --git a/recovery_utils/battery_utils.cpp b/recovery_utils/battery_utils.cpp
new file mode 100644
index 000000000..323f52537
--- /dev/null
+++ b/recovery_utils/battery_utils.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "recovery_utils/battery_utils.h"
+
+#include <stdint.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <healthhalutils/HealthHalUtils.h>
+
+BatteryInfo GetBatteryInfo() {
+ using android::hardware::health::V1_0::BatteryStatus;
+ using android::hardware::health::V2_0::get_health_service;
+ using android::hardware::health::V2_0::IHealth;
+ using android::hardware::health::V2_0::Result;
+ using android::hardware::health::V2_0::toString;
+
+ android::sp<IHealth> health = get_health_service();
+
+ int wait_second = 0;
+ while (true) {
+ auto charge_status = BatteryStatus::UNKNOWN;
+
+ if (health == nullptr) {
+ LOG(WARNING) << "No health implementation is found; assuming defaults";
+ } else {
+ health
+ ->getChargeStatus([&charge_status](auto res, auto out_status) {
+ if (res == Result::SUCCESS) {
+ charge_status = out_status;
+ }
+ })
+ .isOk(); // should not have transport error
+ }
+
+ // Treat unknown status as on charger. See hardware/interfaces/health/1.0/types.hal for the
+ // meaning of the return values.
+ bool charging = (charge_status != BatteryStatus::DISCHARGING &&
+ charge_status != BatteryStatus::NOT_CHARGING);
+
+ Result res = Result::UNKNOWN;
+ int32_t capacity = INT32_MIN;
+ if (health != nullptr) {
+ health
+ ->getCapacity([&res, &capacity](auto out_res, auto out_capacity) {
+ res = out_res;
+ capacity = out_capacity;
+ })
+ .isOk(); // should not have transport error
+ }
+
+ LOG(INFO) << "charge_status " << toString(charge_status) << ", charging " << charging
+ << ", status " << toString(res) << ", capacity " << capacity;
+
+ constexpr int BATTERY_READ_TIMEOUT_IN_SEC = 10;
+ // At startup, the battery drivers in devices like N5X/N6P take some time to load
+ // the battery profile. Before the load finishes, it reports value 50 as a fake
+ // capacity. BATTERY_READ_TIMEOUT_IN_SEC is set that the battery drivers are expected
+ // to finish loading the battery profile earlier than 10 seconds after kernel startup.
+ if (res == Result::SUCCESS && capacity == 50) {
+ if (wait_second < BATTERY_READ_TIMEOUT_IN_SEC) {
+ sleep(1);
+ wait_second++;
+ continue;
+ }
+ }
+ // If we can't read battery percentage, it may be a device without battery. In this
+ // situation, use 100 as a fake battery percentage.
+ if (res != Result::SUCCESS) {
+ capacity = 100;
+ }
+
+ return BatteryInfo{ charging, capacity };
+ }
+}
diff --git a/recovery_utils/include/recovery_utils/battery_utils.h b/recovery_utils/include/recovery_utils/battery_utils.h
new file mode 100644
index 000000000..a95f71dca
--- /dev/null
+++ b/recovery_utils/include/recovery_utils/battery_utils.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+struct BatteryInfo {
+ // Whether the device is on charger. Note that the value will be `true` if the battery status is
+ // unknown (BATTERY_STATUS_UNKNOWN).
+ bool charging;
+
+ // The remaining battery capacity percentage (i.e. between 0 and 100). See getCapacity in
+ // hardware/interfaces/health/2.0/IHealth.hal. Returns 100 in case it fails to read a value from
+ // the health HAL.
+ int32_t capacity;
+};
+
+// Returns the battery status for OTA installation purpose.
+BatteryInfo GetBatteryInfo();
diff --git a/otautil/include/otautil/logging.h b/recovery_utils/include/recovery_utils/logging.h
index 608349785..4462eca6e 100644
--- a/otautil/include/otautil/logging.h
+++ b/recovery_utils/include/recovery_utils/logging.h
@@ -53,7 +53,7 @@ void rotate_logs(const char* last_log_file, const char* last_kmsg_file);
void check_and_fclose(FILE* fp, const std::string& name);
void copy_log_file_to_pmsg(const std::string& source, const std::string& destination);
-void copy_logs(bool save_current_log, bool has_cache, const selabel_handle* sehandle);
+void copy_logs(bool save_current_log);
void reset_tmplog_offset();
void save_kernel_log(const char* destination);
diff --git a/otautil/include/otautil/parse_install_logs.h b/recovery_utils/include/recovery_utils/parse_install_logs.h
index 135d29ccf..135d29ccf 100644
--- a/otautil/include/otautil/parse_install_logs.h
+++ b/recovery_utils/include/recovery_utils/parse_install_logs.h
diff --git a/otautil/include/otautil/roots.h b/recovery_utils/include/recovery_utils/roots.h
index 482f3d050..92ee756f0 100644
--- a/otautil/include/otautil/roots.h
+++ b/recovery_utils/include/recovery_utils/roots.h
@@ -54,6 +54,5 @@ int format_volume(const std::string& volume, const std::string& directory);
// mounted (/tmp and /cache) are mounted. Returns 0 on success.
int setup_install_mounts();
-bool logical_partitions_mapped();
-
-std::string get_system_root();
+// Returns true if there is /cache in the volumes.
+bool HasCache();
diff --git a/otautil/include/otautil/thermalutil.h b/recovery_utils/include/recovery_utils/thermalutil.h
index 43ab55940..43ab55940 100644
--- a/otautil/include/otautil/thermalutil.h
+++ b/recovery_utils/include/recovery_utils/thermalutil.h
diff --git a/otautil/logging.cpp b/recovery_utils/logging.cpp
index 484f1150f..52f12a8d8 100644
--- a/otautil/logging.cpp
+++ b/recovery_utils/logging.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "otautil/logging.h"
+#include "recovery_utils/logging.h"
#include <dirent.h>
#include <errno.h>
@@ -38,7 +38,7 @@
#include "otautil/dirutil.h"
#include "otautil/paths.h"
-#include "otautil/roots.h"
+#include "recovery_utils/roots.h"
constexpr const char* LOG_FILE = "/cache/recovery/log";
constexpr const char* LAST_INSTALL_FILE = "/cache/recovery/last_install";
@@ -178,9 +178,8 @@ void reset_tmplog_offset() {
tmplog_offset = 0;
}
-static void copy_log_file(const std::string& source, const std::string& destination, bool append,
- const selabel_handle* sehandle) {
- FILE* dest_fp = fopen_path(destination, append ? "ae" : "we", sehandle);
+static void copy_log_file(const std::string& source, const std::string& destination, bool append) {
+ FILE* dest_fp = fopen_path(destination, append ? "ae" : "we", logging_sehandle);
if (dest_fp == nullptr) {
PLOG(ERROR) << "Can't open " << destination;
} else {
@@ -203,7 +202,7 @@ static void copy_log_file(const std::string& source, const std::string& destinat
}
}
-void copy_logs(bool save_current_log, bool has_cache, const selabel_handle* sehandle) {
+void copy_logs(bool save_current_log) {
// We only rotate and record the log of the current session if explicitly requested. This usually
// happens after wipes, installation from BCB or menu selections. This is to avoid unnecessary
// rotation (and possible deletion) of log files, if it does not do anything loggable.
@@ -216,7 +215,7 @@ void copy_logs(bool save_current_log, bool has_cache, const selabel_handle* seha
copy_log_file_to_pmsg(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE);
// We can do nothing for now if there's no /cache partition.
- if (!has_cache) {
+ if (!HasCache()) {
return;
}
@@ -225,9 +224,9 @@ void copy_logs(bool save_current_log, bool has_cache, const selabel_handle* seha
rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE);
// Copy logs to cache so the system can find out what happened.
- copy_log_file(Paths::Get().temporary_log_file(), LOG_FILE, true, sehandle);
- copy_log_file(Paths::Get().temporary_log_file(), LAST_LOG_FILE, false, sehandle);
- copy_log_file(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE, false, sehandle);
+ copy_log_file(Paths::Get().temporary_log_file(), LOG_FILE, true);
+ copy_log_file(Paths::Get().temporary_log_file(), LAST_LOG_FILE, false);
+ copy_log_file(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE, false);
save_kernel_log(LAST_KMSG_FILE);
chmod(LOG_FILE, 0600);
chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM);
@@ -319,7 +318,7 @@ bool RestoreLogFilesAfterFormat(const std::vector<saved_log_file>& log_files) {
// Reset the pointer so we copy from the beginning of the temp
// log.
reset_tmplog_offset();
- copy_logs(true /* save_current_log */, true /* has_cache */, logging_sehandle);
+ copy_logs(true /* save_current_log */);
return true;
}
diff --git a/otautil/parse_install_logs.cpp b/recovery_utils/parse_install_logs.cpp
index 13a729921..c86317623 100644
--- a/otautil/parse_install_logs.cpp
+++ b/recovery_utils/parse_install_logs.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "otautil/parse_install_logs.h"
+#include "recovery_utils/parse_install_logs.h"
#include <unistd.h>
diff --git a/otautil/roots.cpp b/recovery_utils/roots.cpp
index 815d644e5..fe3a07aa2 100644
--- a/otautil/roots.cpp
+++ b/recovery_utils/roots.cpp
@@ -14,15 +14,12 @@
* limitations under the License.
*/
-#include "otautil/roots.h"
+#include "recovery_utils/roots.h"
-#include <ctype.h>
#include <fcntl.h>
-#include <inttypes.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -33,16 +30,13 @@
#include <vector>
#include <android-base/logging.h>
-#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <cryptfs.h>
#include <ext4_utils/wipe.h>
#include <fs_mgr.h>
#include <fs_mgr/roots.h>
-#include <fs_mgr_dm_linear.h>
-#include "otautil/mounts.h"
#include "otautil/sysutil.h"
using android::fs_mgr::Fstab;
@@ -51,6 +45,8 @@ using android::fs_mgr::ReadDefaultFstab;
static Fstab fstab;
+constexpr const char* CACHE_ROOT = "/cache";
+
void load_volume_table() {
if (!ReadDefaultFstab(&fstab)) {
LOG(ERROR) << "Failed to read default fstab";
@@ -58,7 +54,11 @@ void load_volume_table() {
}
fstab.emplace_back(FstabEntry{
- .mount_point = "/tmp", .fs_type = "ramdisk", .blk_device = "ramdisk", .length = 0 });
+ .blk_device = "ramdisk",
+ .mount_point = "/tmp",
+ .fs_type = "ramdisk",
+ .length = 0,
+ });
std::cout << "recovery filesystem table" << std::endl << "=========================" << std::endl;
for (size_t i = 0; i < fstab.size(); ++i) {
@@ -276,10 +276,8 @@ int setup_install_mounts() {
return 0;
}
-bool logical_partitions_mapped() {
- return android::fs_mgr::LogicalPartitionsMapped();
-}
-
-std::string get_system_root() {
- return android::fs_mgr::GetSystemRoot();
+bool HasCache() {
+ CHECK(!fstab.empty());
+ static bool has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;
+ return has_cache;
}
diff --git a/otautil/thermalutil.cpp b/recovery_utils/thermalutil.cpp
index 4660e057e..5436355d6 100644
--- a/otautil/thermalutil.cpp
+++ b/recovery_utils/thermalutil.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "otautil/thermalutil.h"
+#include "recovery_utils/thermalutil.h"
#include <dirent.h>
#include <stdio.h>
diff --git a/tests/Android.bp b/tests/Android.bp
index 09ef716d6..5b881e367 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -50,13 +50,11 @@ cc_defaults {
},
}
-// libapplypatch, libapplypatch_modes, libimgdiff, libimgpatch
+// libapplypatch, libapplypatch_modes
libapplypatch_static_libs = [
"libapplypatch_modes",
"libapplypatch",
"libedify",
- "libimgdiff",
- "libimgpatch",
"libotautil",
"libbsdiff",
"libbspatch",
@@ -66,7 +64,6 @@ libapplypatch_static_libs = [
"libbase",
"libbrotli",
"libbz",
- "libcrypto",
"libz",
"libziparchive",
]
@@ -79,6 +76,8 @@ librecovery_static_libs = [
"libinstall",
"librecovery_ui",
"libminui",
+ "libfusesideload",
+ "libbootloader_message",
"libotautil",
"libhealthhalutils",
@@ -87,14 +86,10 @@ librecovery_static_libs = [
"android.hardware.health@2.0",
"android.hardware.health@1.0",
- "libbootloader_message",
"libext4_utils",
"libfs_mgr",
- "libfusesideload",
"libhidl-gen-utils",
"libhidlbase",
- "libhidltransport",
- "libhwbinder_noltopgo",
"libbinderthreadstate",
"liblp",
"libvndksupport",
@@ -104,9 +99,12 @@ librecovery_static_libs = [
cc_test {
name: "recovery_unit_test",
isolated: true,
+ require_root: true,
defaults: [
"recovery_test_defaults",
+ "libupdater_defaults",
+ "libupdater_device_defaults",
],
test_suites: ["device-tests"],
@@ -115,16 +113,24 @@ cc_test {
"unit/*.cpp",
],
- static_libs: libapplypatch_static_libs + [
- "libinstall",
+ static_libs: libapplypatch_static_libs + librecovery_static_libs + [
"librecovery_ui",
+ "libfusesideload",
"libminui",
+ "librecovery_utils",
"libotautil",
- "libupdater",
+ "libupdater_device",
+ "libupdater_core",
+ "libupdate_verifier",
+
"libgtest_prod",
+ "libprotobuf-cpp-lite",
],
- data: ["testdata/*"],
+ data: [
+ "testdata/*",
+ ":res-testdata",
+ ],
}
cc_test {
@@ -142,66 +148,37 @@ cc_test {
],
}
-cc_test {
- name: "recovery_component_test",
- isolated: true,
-
- defaults: [
- "recovery_test_defaults",
- "libupdater_defaults",
- ],
-
- test_suites: ["device-tests"],
-
- srcs: [
- "component/*.cpp",
- ],
-
- static_libs: libapplypatch_static_libs + librecovery_static_libs + [
- "libupdater",
- "libupdate_verifier",
- "libprotobuf-cpp-lite",
- ],
-
- data: [
- "testdata/*",
- ":res-testdata",
- ],
-}
-
cc_test_host {
name: "recovery_host_test",
isolated: true,
defaults: [
"recovery_test_defaults",
+ "libupdater_defaults",
],
srcs: [
- "component/imgdiff_test.cpp",
+ "unit/host/*",
],
static_libs: [
+ "libupdater_host",
+ "libupdater_core",
"libimgdiff",
- "libimgpatch",
- "libotautil",
"libbsdiff",
- "libbspatch",
- "libziparchive",
- "libutils",
- "libcrypto",
- "libbrotli",
- "libbz",
"libdivsufsort64",
"libdivsufsort",
- "libz",
+ "libfstab",
+ "libc++fs",
],
+ test_suites: ["general-tests"],
+
data: ["testdata/*"],
target: {
darwin: {
- // libimgdiff is not available on the Mac.
+ // libapplypatch in "libupdater_defaults" is not available on the Mac.
enabled: false,
},
},
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
deleted file mode 100644
index 6b86085aa..000000000
--- a/tests/AndroidTest.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<configuration description="Config for recovery_component_test and recovery_unit_test">
- <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
- <option name="cleanup" value="true" />
- <option name="push" value="recovery_component_test->/data/local/tmp/recovery_component_test/recovery_component_test" />
- <option name="push" value="testdata->/data/local/tmp/recovery_component_test/testdata" />
- <option name="push" value="recovery_unit_test->/data/local/tmp/recovery_unit_test/recovery_unit_test" />
- <option name="push" value="testdata->/data/local/tmp/recovery_unit_test/testdata" />
- </target_preparer>
- <option name="test-suite-tag" value="apct" />
- <test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/local/tmp/recovery_component_test" />
- <option name="module-name" value="recovery_component_test" />
- </test>
- <test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/local/tmp/recovery_unit_test" />
- <option name="module-name" value="recovery_unit_test" />
- </test>
-</configuration>
diff --git a/tests/component/resources_test.cpp b/tests/component/resources_test.cpp
deleted file mode 100644
index d7fdb8fa0..000000000
--- a/tests/component/resources_test.cpp
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <dirent.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include <android-base/file.h>
-#include <android-base/strings.h>
-#include <gtest/gtest.h>
-#include <png.h>
-
-#include "minui/minui.h"
-#include "private/resources.h"
-
-static const std::string kLocale = "zu";
-
-static const std::vector<std::string> kResourceImagesDirs{
- "res-mdpi/images/", "res-hdpi/images/", "res-xhdpi/images/",
- "res-xxhdpi/images/", "res-xxxhdpi/images/",
-};
-
-static int png_filter(const dirent* de) {
- if (de->d_type != DT_REG || !android::base::EndsWith(de->d_name, "_text.png")) {
- return 0;
- }
- return 1;
-}
-
-// Finds out all the PNG files to test, which stay under the same dir with the executabl..
-static std::vector<std::string> add_files() {
- std::vector<std::string> files;
- for (const std::string& images_dir : kResourceImagesDirs) {
- static std::string exec_dir = android::base::GetExecutableDirectory();
- std::string dir_path = exec_dir + "/" + images_dir;
- dirent** namelist;
- int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort);
- if (n == -1) {
- printf("Failed to scandir %s: %s\n", dir_path.c_str(), strerror(errno));
- continue;
- }
- if (n == 0) {
- printf("No file is added for test in %s\n", dir_path.c_str());
- }
-
- while (n--) {
- std::string file_path = dir_path + namelist[n]->d_name;
- files.push_back(file_path);
- free(namelist[n]);
- }
- free(namelist);
- }
- return files;
-}
-
-class ResourcesTest : public testing::TestWithParam<std::string> {
- public:
- static std::vector<std::string> png_list;
-
- protected:
- void SetUp() override {
- png_ = std::make_unique<PngHandler>(GetParam());
- ASSERT_TRUE(png_);
-
- ASSERT_EQ(PNG_COLOR_TYPE_GRAY, png_->color_type()) << "Recovery expects grayscale PNG file.";
- ASSERT_LT(static_cast<png_uint_32>(5), png_->width());
- ASSERT_LT(static_cast<png_uint_32>(0), png_->height());
- ASSERT_EQ(1, png_->channels()) << "Recovery background text images expects 1-channel PNG file.";
- }
-
- std::unique_ptr<PngHandler> png_{ nullptr };
-};
-
-// Parses a png file and tests if it's qualified for the background text image under recovery.
-TEST_P(ResourcesTest, ValidateLocale) {
- std::vector<unsigned char> row(png_->width());
- for (png_uint_32 y = 0; y < png_->height(); ++y) {
- png_read_row(png_->png_ptr(), row.data(), nullptr);
- int w = (row[1] << 8) | row[0];
- int h = (row[3] << 8) | row[2];
- int len = row[4];
- EXPECT_LT(0, w);
- EXPECT_LT(0, h);
- EXPECT_LT(0, len) << "Locale string should be non-empty.";
- EXPECT_NE(0, row[5]) << "Locale string is missing.";
-
- ASSERT_GE(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
- char* loc = reinterpret_cast<char*>(&row[5]);
- if (matches_locale(loc, kLocale.c_str())) {
- EXPECT_TRUE(android::base::StartsWith(loc, kLocale));
- break;
- }
- for (int i = 0; i < h; ++i, ++y) {
- png_read_row(png_->png_ptr(), row.data(), nullptr);
- }
- }
-}
-
-std::vector<std::string> ResourcesTest::png_list = add_files();
-
-INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourcesTest,
- ::testing::ValuesIn(ResourcesTest::png_list.cbegin(),
- ResourcesTest::png_list.cend()));
diff --git a/tests/component/applypatch_modes_test.cpp b/tests/unit/applypatch_modes_test.cpp
index 08414b796..08414b796 100644
--- a/tests/component/applypatch_modes_test.cpp
+++ b/tests/unit/applypatch_modes_test.cpp
diff --git a/tests/unit/applypatch_test.cpp b/tests/unit/applypatch_test.cpp
index 794f2c103..218a224f8 100644
--- a/tests/unit/applypatch_test.cpp
+++ b/tests/unit/applypatch_test.cpp
@@ -141,7 +141,7 @@ TEST_F(ApplyPatchTest, PatchPartition) {
ASSERT_TRUE(LoadFileContents(from_testdata_base("bonus.file"), &bonus_fc));
Value bonus(Value::Type::BLOB, std::string(bonus_fc.data.cbegin(), bonus_fc.data.cend()));
- ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, &bonus));
+ ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, &bonus, false));
}
// Tests patching an eMMC target without a separate bonus file (i.e. recovery-from-boot patch has
@@ -151,7 +151,7 @@ TEST_F(ApplyPatchTest, PatchPartitionWithoutBonusFile) {
ASSERT_TRUE(LoadFileContents(from_testdata_base("recovery-from-boot-with-bonus.p"), &patch_fc));
Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend()));
- ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, nullptr));
+ ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, nullptr, false));
}
class FreeCacheTest : public ::testing::Test {
diff --git a/tests/unit/battery_utils_test.cpp b/tests/unit/battery_utils_test.cpp
new file mode 100644
index 000000000..55639fdb5
--- /dev/null
+++ b/tests/unit/battery_utils_test.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agree to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+
+#include "recovery_utils/battery_utils.h"
+
+TEST(BatteryInfoTest, GetBatteryInfo) {
+ auto info = GetBatteryInfo();
+ // 0 <= capacity <= 100
+ ASSERT_LE(0, info.capacity);
+ ASSERT_LE(info.capacity, 100);
+}
diff --git a/tests/component/bootloader_message_test.cpp b/tests/unit/bootloader_message_test.cpp
index 95d875e69..95d875e69 100644
--- a/tests/component/bootloader_message_test.cpp
+++ b/tests/unit/bootloader_message_test.cpp
diff --git a/tests/component/edify_test.cpp b/tests/unit/edify_test.cpp
index 8397bd38e..8397bd38e 100644
--- a/tests/component/edify_test.cpp
+++ b/tests/unit/edify_test.cpp
diff --git a/tests/unit/fuse_provider_test.cpp b/tests/unit/fuse_provider_test.cpp
new file mode 100644
index 000000000..37f99f92e
--- /dev/null
+++ b/tests/unit/fuse_provider_test.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+
+#include "fuse_provider.h"
+#include "fuse_sideload.h"
+#include "install/install.h"
+
+TEST(FuseBlockMapTest, CreateFromBlockMap_smoke) {
+ TemporaryFile fake_block_device;
+ std::vector<std::string> lines = {
+ fake_block_device.path, "10000 4096", "3", "10 11", "20 21", "22 23",
+ };
+
+ TemporaryFile temp_file;
+ android::base::WriteStringToFile(android::base::Join(lines, '\n'), temp_file.path);
+ auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(temp_file.path, 4096);
+
+ ASSERT_TRUE(block_map_data);
+ ASSERT_EQ(10000, block_map_data->file_size());
+ ASSERT_EQ(4096, block_map_data->fuse_block_size());
+ ASSERT_EQ(RangeSet({ { 10, 11 }, { 20, 21 }, { 22, 23 } }),
+ static_cast<FuseBlockDataProvider*>(block_map_data.get())->ranges());
+}
+
+TEST(FuseBlockMapTest, ReadBlockAlignedData_smoke) {
+ std::string content;
+ content.reserve(40960);
+ for (char c = 0; c < 10; c++) {
+ content += std::string(4096, c);
+ }
+ TemporaryFile fake_block_device;
+ ASSERT_TRUE(android::base::WriteStringToFile(content, fake_block_device.path));
+
+ std::vector<std::string> lines = {
+ fake_block_device.path,
+ "20000 4096",
+ "1",
+ "0 5",
+ };
+ TemporaryFile temp_file;
+ android::base::WriteStringToFile(android::base::Join(lines, '\n'), temp_file.path);
+ auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(temp_file.path, 4096);
+
+ std::vector<uint8_t> result(2000);
+ ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 2000, 1));
+ ASSERT_EQ(std::vector<uint8_t>(content.begin() + 4096, content.begin() + 6096), result);
+
+ result.resize(20000);
+ ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 0));
+ ASSERT_EQ(std::vector<uint8_t>(content.begin(), content.begin() + 20000), result);
+}
+
+TEST(FuseBlockMapTest, ReadBlockAlignedData_large_fuse_block) {
+ std::string content;
+ for (char c = 0; c < 10; c++) {
+ content += std::string(4096, c);
+ }
+
+ TemporaryFile temp_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
+
+ std::vector<std::string> lines = {
+ temp_file.path, "36384 4096", "2", "0 5", "6 10",
+ };
+ TemporaryFile block_map;
+ ASSERT_TRUE(android::base::WriteStringToFile(android::base::Join(lines, '\n'), block_map.path));
+
+ auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(block_map.path, 16384);
+ ASSERT_TRUE(block_map_data);
+
+ std::vector<uint8_t> result(20000);
+ // Out of bound read
+ ASSERT_FALSE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 2));
+ ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 1));
+ // expected source block contains: 4, 6-9
+ std::string expected = content.substr(16384, 4096) + content.substr(24576, 15904);
+ ASSERT_EQ(std::vector<uint8_t>(expected.begin(), expected.end()), result);
+}
diff --git a/tests/component/sideload_test.cpp b/tests/unit/fuse_sideload_test.cpp
index 6add99f41..ea895038c 100644
--- a/tests/component/sideload_test.cpp
+++ b/tests/unit/fuse_sideload_test.cpp
@@ -40,6 +40,10 @@ class FuseTestDataProvider : public FuseDataProvider {
bool ReadBlockAlignedData(uint8_t*, uint32_t, uint32_t) const override {
return true;
}
+
+ bool Valid() const override {
+ return true;
+ }
};
TEST(SideloadTest, run_fuse_sideload_wrong_parameters) {
diff --git a/tests/component/imgdiff_test.cpp b/tests/unit/host/imgdiff_test.cpp
index e76ccbdfb..e76ccbdfb 100644
--- a/tests/component/imgdiff_test.cpp
+++ b/tests/unit/host/imgdiff_test.cpp
diff --git a/tests/unit/host/update_simulator_test.cpp b/tests/unit/host/update_simulator_test.cpp
new file mode 100644
index 000000000..fb1217877
--- /dev/null
+++ b/tests/unit/host/update_simulator_test.cpp
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <bsdiff/bsdiff.h>
+#include <gtest/gtest.h>
+#include <ziparchive/zip_writer.h>
+
+#include "otautil/paths.h"
+#include "otautil/print_sha1.h"
+#include "updater/blockimg.h"
+#include "updater/build_info.h"
+#include "updater/install.h"
+#include "updater/simulator_runtime.h"
+#include "updater/target_files.h"
+#include "updater/updater.h"
+
+using std::string;
+
+// echo -n "system.img" > system.img && img2simg system.img sparse_system_string_.img 4096 &&
+// hexdump -v -e '" " 12/1 "0x%02x, " "\n"' sparse_system_string_.img
+// The total size of the result sparse image is 4136 bytes; and we can append 0s in the end to get
+// the full image.
+constexpr uint8_t SPARSE_SYSTEM_HEADER[] = {
+ 0x3a, 0xff, 0x26, 0xed, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0xca,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x10, 0x00, 0x00, 0x73, 0x79, 0x73, 0x74, 0x65,
+ 0x6d, 0x2e, 0x69, 0x6d, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static void AddZipEntries(int fd, const std::map<string, string>& entries) {
+ FILE* zip_file = fdopen(fd, "w");
+ ZipWriter writer(zip_file);
+ for (const auto& pair : entries) {
+ ASSERT_EQ(0, writer.StartEntry(pair.first.c_str(), 0));
+ ASSERT_EQ(0, writer.WriteBytes(pair.second.data(), pair.second.size()));
+ ASSERT_EQ(0, writer.FinishEntry());
+ }
+ ASSERT_EQ(0, writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file));
+}
+
+static string CalculateSha1(const string& data) {
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest);
+ return print_sha1(digest);
+}
+
+static void CreateBsdiffPatch(const string& src, const string& tgt, string* patch) {
+ TemporaryFile patch_file;
+ ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src.data()), src.size(),
+ reinterpret_cast<const uint8_t*>(tgt.data()), tgt.size(),
+ patch_file.path, nullptr));
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, patch));
+}
+
+static void RunSimulation(std::string_view src_tf, std::string_view ota_package, bool expected) {
+ TemporaryFile cmd_pipe;
+ TemporaryFile temp_saved_source;
+ TemporaryFile temp_last_command;
+ TemporaryDir temp_stash_base;
+
+ Paths::Get().set_cache_temp_source(temp_saved_source.path);
+ Paths::Get().set_last_command_file(temp_last_command.path);
+ Paths::Get().set_stash_directory_base(temp_stash_base.path);
+
+ // Configure edify's functions.
+ RegisterBuiltins();
+ RegisterInstallFunctions();
+ RegisterBlockImageFunctions();
+
+ // Run the update simulation and check the result.
+ TemporaryDir work_dir;
+ BuildInfo build_info(work_dir.path, false);
+ ASSERT_TRUE(build_info.ParseTargetFile(src_tf, false));
+ Updater updater(std::make_unique<SimulatorRuntime>(&build_info));
+ ASSERT_TRUE(updater.Init(cmd_pipe.release(), ota_package, false));
+ ASSERT_EQ(expected, updater.RunUpdate());
+ // TODO(xunchang) check the recovery&system has the expected contents.
+}
+
+class UpdateSimulatorTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ std::vector<string> props = {
+ "import /oem/oem.prop oem*",
+ "# begin build properties",
+ "# autogenerated by buildinfo.sh",
+ "ro.build.id=OPR1.170510.001",
+ "ro.build.display.id=OPR1.170510.001 dev-keys",
+ "ro.build.version.incremental=3993052",
+ "ro.build.version.release=O",
+ "ro.build.date=Wed May 10 11:10:29 UTC 2017",
+ "ro.build.date.utc=1494414629",
+ "ro.build.type=user",
+ "ro.build.tags=dev-keys",
+ "ro.build.flavor=angler-user",
+ "ro.product.system.brand=google",
+ "ro.product.system.name=angler",
+ "ro.product.system.device=angler",
+ };
+ build_prop_string_ = android::base::Join(props, "\n");
+
+ fstab_content_ = R"(
+#<src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
+# More comments.....
+
+/dev/block/by-name/system /system ext4 ro,barrier=1 wait
+/dev/block/by-name/vendor /vendor ext4 ro wait,verify=/dev/metadata
+/dev/block/by-name/cache /cache ext4 noatime,errors=panic wait,check
+/dev/block/by-name/modem /firmware vfat ro,uid=1000,gid=1000, wait
+/dev/block/by-name/boot /boot emmc defaults defaults
+/dev/block/by-name/recovery /recovery emmc defaults defaults
+/dev/block/by-name/misc /misc emmc defaults
+/dev/block/by-name/modem /modem emmc defaults defaults)";
+
+ raw_system_string_ = "system.img" + string(4086, '\0'); // raw image is 4096 bytes in total
+ sparse_system_string_ = string(SPARSE_SYSTEM_HEADER, std::end(SPARSE_SYSTEM_HEADER)) +
+ string(4136 - sizeof(SPARSE_SYSTEM_HEADER), '\0');
+ }
+
+ string build_prop_string_;
+ string fstab_content_;
+ string raw_system_string_;
+ string sparse_system_string_;
+};
+
+TEST_F(UpdateSimulatorTest, TargetFile_ExtractImage) {
+ TemporaryFile zip_file;
+ AddZipEntries(zip_file.release(), { { "META/misc_info.txt", "extfs_sparse_flag=-s" },
+ { "IMAGES/system.img", sparse_system_string_ } });
+ TargetFile target_file(zip_file.path, false);
+ ASSERT_TRUE(target_file.Open());
+
+ TemporaryDir temp_dir;
+ TemporaryFile raw_image;
+ ASSERT_TRUE(target_file.ExtractImage(
+ "IMAGES/system.img", FstabInfo("/dev/system", "system", "ext4"), temp_dir.path, &raw_image));
+
+ // Check the raw image has expected contents.
+ string content;
+ ASSERT_TRUE(android::base::ReadFileToString(raw_image.path, &content));
+ string expected_content = "system.img" + string(4086, '\0');
+ ASSERT_EQ(expected_content, content);
+}
+
+TEST_F(UpdateSimulatorTest, TargetFile_ParseFstabInfo) {
+ TemporaryFile zip_file;
+ AddZipEntries(zip_file.release(),
+ { { "META/misc_info.txt", "" },
+ { "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ } });
+ TargetFile target_file(zip_file.path, false);
+ ASSERT_TRUE(target_file.Open());
+
+ std::vector<FstabInfo> fstab_info;
+ EXPECT_TRUE(target_file.ParseFstabInfo(&fstab_info));
+
+ std::vector<std::vector<string>> transformed;
+ std::transform(fstab_info.begin(), fstab_info.end(), std::back_inserter(transformed),
+ [](const FstabInfo& info) {
+ return std::vector<string>{ info.blockdev_name, info.mount_point, info.fs_type };
+ });
+
+ std::vector<std::vector<string>> expected = {
+ { "/dev/block/by-name/system", "/system", "ext4" },
+ { "/dev/block/by-name/vendor", "/vendor", "ext4" },
+ { "/dev/block/by-name/cache", "/cache", "ext4" },
+ { "/dev/block/by-name/boot", "/boot", "emmc" },
+ { "/dev/block/by-name/recovery", "/recovery", "emmc" },
+ { "/dev/block/by-name/misc", "/misc", "emmc" },
+ { "/dev/block/by-name/modem", "/modem", "emmc" },
+ };
+ EXPECT_EQ(expected, transformed);
+}
+
+TEST_F(UpdateSimulatorTest, BuildInfo_ParseTargetFile) {
+ std::map<string, string> entries = {
+ { "META/misc_info.txt", "" },
+ { "SYSTEM/build.prop", build_prop_string_ },
+ { "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ },
+ { "IMAGES/recovery.img", "" },
+ { "IMAGES/boot.img", "" },
+ { "IMAGES/misc.img", "" },
+ { "IMAGES/system.map", "" },
+ { "IMAGES/system.img", sparse_system_string_ },
+ };
+
+ TemporaryFile zip_file;
+ AddZipEntries(zip_file.release(), entries);
+
+ TemporaryDir temp_dir;
+ BuildInfo build_info(temp_dir.path, false);
+ ASSERT_TRUE(build_info.ParseTargetFile(zip_file.path, false));
+
+ std::map<string, string> expected_result = {
+ { "ro.build.id", "OPR1.170510.001" },
+ { "ro.build.display.id", "OPR1.170510.001 dev-keys" },
+ { "ro.build.version.incremental", "3993052" },
+ { "ro.build.version.release", "O" },
+ { "ro.build.date", "Wed May 10 11:10:29 UTC 2017" },
+ { "ro.build.date.utc", "1494414629" },
+ { "ro.build.type", "user" },
+ { "ro.build.tags", "dev-keys" },
+ { "ro.build.flavor", "angler-user" },
+ { "ro.product.brand", "google" },
+ { "ro.product.name", "angler" },
+ { "ro.product.device", "angler" },
+ };
+
+ for (const auto& [key, value] : expected_result) {
+ ASSERT_EQ(value, build_info.GetProperty(key, ""));
+ }
+
+ // Check that the temp files for each block device are created successfully.
+ for (auto name : { "/dev/block/by-name/system", "/dev/block/by-name/recovery",
+ "/dev/block/by-name/boot", "/dev/block/by-name/misc" }) {
+ ASSERT_EQ(0, access(build_info.FindBlockDeviceName(name).c_str(), R_OK));
+ }
+}
+
+TEST_F(UpdateSimulatorTest, RunUpdateSmoke) {
+ string recovery_img_string = "recovery.img";
+ string boot_img_string = "boot.img";
+
+ std::map<string, string> src_entries{
+ { "META/misc_info.txt", "extfs_sparse_flag=-s" },
+ { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ },
+ { "SYSTEM/build.prop", build_prop_string_ },
+ { "IMAGES/recovery.img", "" },
+ { "IMAGES/boot.img", boot_img_string },
+ { "IMAGES/system.img", sparse_system_string_ },
+ };
+
+ // Construct the source target-files.
+ TemporaryFile src_tf;
+ AddZipEntries(src_tf.release(), src_entries);
+
+ string recovery_from_boot;
+ CreateBsdiffPatch(boot_img_string, recovery_img_string, &recovery_from_boot);
+
+ // Set up the apply patch commands to patch the recovery image.
+ string recovery_sha1 = CalculateSha1(recovery_img_string);
+ string boot_sha1 = CalculateSha1(boot_img_string);
+ string apply_patch_source_string = android::base::StringPrintf(
+ "EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str());
+ string apply_patch_target_string = android::base::StringPrintf(
+ "EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str());
+ string check_command = android::base::StringPrintf(
+ R"(patch_partition_check("%s", "%s") || abort("check failed");)",
+ apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
+ string patch_command = android::base::StringPrintf(
+ R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("patch failed");)",
+ apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
+
+ // Add the commands to update the system image. Test common commands:
+ // * getprop
+ // * ui_print
+ // * patch_partition
+ // * package_extract_file (single argument)
+ // * block_image_verify, block_image_update
+ string tgt_system_string = string(4096, 'a');
+ string system_patch;
+ CreateBsdiffPatch(raw_system_string_, tgt_system_string, &system_patch);
+
+ string tgt_system_hash = CalculateSha1(tgt_system_string);
+ string src_system_hash = CalculateSha1(raw_system_string_);
+
+ std::vector<std::string> transfer_list = {
+ "4",
+ "1",
+ "0",
+ "0",
+ android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,1 1 2,0,1", system_patch.size(),
+ src_system_hash.c_str(), tgt_system_hash.c_str()),
+ };
+
+ // Construct the updater_script.
+ std::vector<string> updater_commands = {
+ R"(getprop("ro.product.device") == "angler" || abort("This package is for \"angler\"");)",
+ R"(ui_print("Source: angler/OPR1.170510.001");)",
+ check_command,
+ patch_command,
+ R"(block_image_verify("/dev/block/by-name/system", )"
+ R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )"
+ R"(abort("Failed to verify system.");)",
+ R"(block_image_update("/dev/block/by-name/system", )"
+ R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )"
+ R"(abort("Failed to verify system.");)",
+ };
+ string updater_script = android::base::Join(updater_commands, '\n');
+
+ // Construct the ota update package.
+ std::map<string, string> ota_entries{
+ { "system.new.dat", "" },
+ { "system.patch.dat", system_patch },
+ { "system.transfer.list", android::base::Join(transfer_list, '\n') },
+ { "META-INF/com/google/android/updater-script", updater_script },
+ { "patch.p", recovery_from_boot },
+ };
+
+ TemporaryFile ota_package;
+ AddZipEntries(ota_package.release(), ota_entries);
+
+ RunSimulation(src_tf.path, ota_package.path, true);
+}
+
+TEST_F(UpdateSimulatorTest, RunUpdateUnrecognizedFunction) {
+ std::map<string, string> src_entries{
+ { "META/misc_info.txt", "extfs_sparse_flag=-s" },
+ { "IMAGES/system.img", sparse_system_string_ },
+ { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ },
+ { "SYSTEM/build.prop", build_prop_string_ },
+ };
+
+ TemporaryFile src_tf;
+ AddZipEntries(src_tf.release(), src_entries);
+
+ std::map<string, string> ota_entries{
+ { "system.new.dat", "" },
+ { "system.patch.dat", "" },
+ { "system.transfer.list", "" },
+ { "META-INF/com/google/android/updater-script", R"(bad_function("");)" },
+ };
+
+ TemporaryFile ota_package;
+ AddZipEntries(ota_package.release(), ota_entries);
+
+ RunSimulation(src_tf.path, ota_package.path, false);
+}
+
+TEST_F(UpdateSimulatorTest, RunUpdateApplyPatchFailed) {
+ string recovery_img_string = "recovery.img";
+ string boot_img_string = "boot.img";
+
+ std::map<string, string> src_entries{
+ { "META/misc_info.txt", "extfs_sparse_flag=-s" },
+ { "IMAGES/recovery.img", "" },
+ { "IMAGES/boot.img", boot_img_string },
+ { "IMAGES/system.img", sparse_system_string_ },
+ { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ },
+ { "SYSTEM/build.prop", build_prop_string_ },
+ };
+
+ TemporaryFile src_tf;
+ AddZipEntries(src_tf.release(), src_entries);
+
+ string recovery_sha1 = CalculateSha1(recovery_img_string);
+ string boot_sha1 = CalculateSha1(boot_img_string);
+ string apply_patch_source_string = android::base::StringPrintf(
+ "EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str());
+ string apply_patch_target_string = android::base::StringPrintf(
+ "EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str());
+ string check_command = android::base::StringPrintf(
+ R"(patch_partition_check("%s", "%s") || abort("check failed");)",
+ apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
+ string patch_command = android::base::StringPrintf(
+ R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("failed");)",
+ apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
+
+ // Give an invalid recovery patch and expect the apply patch to fail.
+ // TODO(xunchang) check the cause code.
+ std::vector<string> updater_commands = {
+ R"(ui_print("Source: angler/OPR1.170510.001");)",
+ check_command,
+ patch_command,
+ };
+
+ string updater_script = android::base::Join(updater_commands, '\n');
+ std::map<string, string> ota_entries{
+ { "system.new.dat", "" },
+ { "system.patch.dat", "" },
+ { "system.transfer.list", "" },
+ { "META-INF/com/google/android/updater-script", updater_script },
+ { "patch.p", "random string" },
+ };
+
+ TemporaryFile ota_package;
+ AddZipEntries(ota_package.release(), ota_entries);
+
+ RunSimulation(src_tf.path, ota_package.path, false);
+}
diff --git a/tests/component/install_test.cpp b/tests/unit/install_test.cpp
index 385132939..4ec409908 100644
--- a/tests/component/install_test.cpp
+++ b/tests/unit/install_test.cpp
@@ -33,6 +33,7 @@
#include <ziparchive/zip_writer.h>
#include "install/install.h"
+#include "install/wipe_device.h"
#include "otautil/paths.h"
#include "private/setup_commands.h"
@@ -204,7 +205,7 @@ TEST(InstallTest, SetUpNonAbUpdateCommands) {
std::string binary_path = std::string(td.path) + "/update_binary";
Paths::Get().set_temporary_update_binary(binary_path);
std::vector<std::string> cmd;
- ASSERT_EQ(0, SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd));
+ ASSERT_TRUE(SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd));
ASSERT_EQ(4U, cmd.size());
ASSERT_EQ(binary_path, cmd[0]);
ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION
@@ -216,7 +217,7 @@ TEST(InstallTest, SetUpNonAbUpdateCommands) {
// With non-zero retry count. update_binary will be removed automatically.
cmd.clear();
- ASSERT_EQ(0, SetUpNonAbUpdateCommands(package, zip, 2, status_fd, &cmd));
+ ASSERT_TRUE(SetUpNonAbUpdateCommands(package, zip, 2, status_fd, &cmd));
ASSERT_EQ(5U, cmd.size());
ASSERT_EQ(binary_path, cmd[0]);
ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION
@@ -243,7 +244,7 @@ TEST(InstallTest, SetUpNonAbUpdateCommands_MissingUpdateBinary) {
TemporaryDir td;
Paths::Get().set_temporary_update_binary(std::string(td.path) + "/update_binary");
std::vector<std::string> cmd;
- ASSERT_EQ(INSTALL_CORRUPT, SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd));
+ ASSERT_FALSE(SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd));
CloseArchive(zip);
}
@@ -270,19 +271,18 @@ static void VerifyAbUpdateCommands(const std::string& serialno, bool success = t
ZipArchiveHandle zip;
ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
- ZipString payload_name("payload.bin");
ZipEntry payload_entry;
- ASSERT_EQ(0, FindEntry(zip, payload_name, &payload_entry));
+ ASSERT_EQ(0, FindEntry(zip, "payload.bin", &payload_entry));
std::map<std::string, std::string> metadata;
ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata));
if (success) {
- ASSERT_EQ(0, CheckPackageMetadata(metadata, OtaType::AB));
+ ASSERT_TRUE(CheckPackageMetadata(metadata, OtaType::AB));
int status_fd = 10;
std::string package = "/path/to/update.zip";
std::vector<std::string> cmd;
- ASSERT_EQ(0, SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
+ ASSERT_TRUE(SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
ASSERT_EQ(5U, cmd.size());
ASSERT_EQ("/system/bin/update_engine_sideload", cmd[0]);
ASSERT_EQ("--payload=file://" + package, cmd[1]);
@@ -290,7 +290,7 @@ static void VerifyAbUpdateCommands(const std::string& serialno, bool success = t
ASSERT_EQ("--headers=" + properties, cmd[3]);
ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
} else {
- ASSERT_EQ(INSTALL_ERROR, CheckPackageMetadata(metadata, OtaType::AB));
+ ASSERT_FALSE(CheckPackageMetadata(metadata, OtaType::AB));
}
CloseArchive(zip);
}
@@ -325,7 +325,7 @@ TEST(InstallTest, SetUpAbUpdateCommands_MissingPayloadPropertiesTxt) {
int status_fd = 10;
std::string package = "/path/to/update.zip";
std::vector<std::string> cmd;
- ASSERT_EQ(INSTALL_CORRUPT, SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
+ ASSERT_FALSE(SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
CloseArchive(zip);
}
@@ -358,8 +358,8 @@ TEST(InstallTest, SetUpAbUpdateCommands_MultipleSerialnos) {
VerifyAbUpdateCommands(long_serialno);
}
-static void test_check_package_metadata(const std::string& metadata_string, OtaType ota_type,
- int exptected_result) {
+static void TestCheckPackageMetadata(const std::string& metadata_string, OtaType ota_type,
+ bool exptected_result) {
TemporaryFile temp_file;
BuildZipArchive(
{
@@ -387,7 +387,7 @@ TEST(InstallTest, CheckPackageMetadata_ota_type) {
"post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
},
"\n");
- test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+ TestCheckPackageMetadata(metadata, OtaType::AB, false);
// Checks if ota-type matches
metadata = android::base::Join(
@@ -397,9 +397,9 @@ TEST(InstallTest, CheckPackageMetadata_ota_type) {
"post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
},
"\n");
- test_check_package_metadata(metadata, OtaType::AB, 0);
+ TestCheckPackageMetadata(metadata, OtaType::AB, true);
- test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+ TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
}
TEST(InstallTest, CheckPackageMetadata_device_type) {
@@ -409,7 +409,7 @@ TEST(InstallTest, CheckPackageMetadata_device_type) {
"ota-type=BRICK",
},
"\n");
- test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+ TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
// device type mismatches
metadata = android::base::Join(
@@ -418,7 +418,7 @@ TEST(InstallTest, CheckPackageMetadata_device_type) {
"pre-device=dummy_device_type",
},
"\n");
- test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+ TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
}
TEST(InstallTest, CheckPackageMetadata_serial_number_smoke) {
@@ -432,7 +432,7 @@ TEST(InstallTest, CheckPackageMetadata_serial_number_smoke) {
"pre-device=" + device,
},
"\n");
- test_check_package_metadata(metadata, OtaType::BRICK, 0);
+ TestCheckPackageMetadata(metadata, OtaType::BRICK, true);
// Serial number mismatches
metadata = android::base::Join(
@@ -442,7 +442,7 @@ TEST(InstallTest, CheckPackageMetadata_serial_number_smoke) {
"serialno=dummy_serial",
},
"\n");
- test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+ TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
std::string serialno = android::base::GetProperty("ro.serialno", "");
ASSERT_NE("", serialno);
@@ -453,7 +453,7 @@ TEST(InstallTest, CheckPackageMetadata_serial_number_smoke) {
"serialno=" + serialno,
},
"\n");
- test_check_package_metadata(metadata, OtaType::BRICK, 0);
+ TestCheckPackageMetadata(metadata, OtaType::BRICK, true);
}
TEST(InstallTest, CheckPackageMetadata_multiple_serial_number) {
@@ -477,7 +477,7 @@ TEST(InstallTest, CheckPackageMetadata_multiple_serial_number) {
"serialno=" + android::base::Join(serial_numbers, '|'),
},
"\n");
- test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+ TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
serial_numbers.emplace_back(serialno);
std::shuffle(serial_numbers.begin(), serial_numbers.end(), std::default_random_engine());
@@ -488,7 +488,7 @@ TEST(InstallTest, CheckPackageMetadata_multiple_serial_number) {
"serialno=" + android::base::Join(serial_numbers, '|'),
},
"\n");
- test_check_package_metadata(metadata, OtaType::BRICK, 0);
+ TestCheckPackageMetadata(metadata, OtaType::BRICK, true);
}
TEST(InstallTest, CheckPackageMetadata_ab_build_version) {
@@ -506,7 +506,7 @@ TEST(InstallTest, CheckPackageMetadata_ab_build_version) {
"post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
},
"\n");
- test_check_package_metadata(metadata, OtaType::AB, 0);
+ TestCheckPackageMetadata(metadata, OtaType::AB, true);
metadata = android::base::Join(
std::vector<std::string>{
@@ -516,7 +516,7 @@ TEST(InstallTest, CheckPackageMetadata_ab_build_version) {
"post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
},
"\n");
- test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+ TestCheckPackageMetadata(metadata, OtaType::AB, false);
}
TEST(InstallTest, CheckPackageMetadata_ab_fingerprint) {
@@ -534,7 +534,7 @@ TEST(InstallTest, CheckPackageMetadata_ab_fingerprint) {
"post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
},
"\n");
- test_check_package_metadata(metadata, OtaType::AB, 0);
+ TestCheckPackageMetadata(metadata, OtaType::AB, true);
metadata = android::base::Join(
std::vector<std::string>{
@@ -544,7 +544,7 @@ TEST(InstallTest, CheckPackageMetadata_ab_fingerprint) {
"post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
},
"\n");
- test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+ TestCheckPackageMetadata(metadata, OtaType::AB, false);
}
TEST(InstallTest, CheckPackageMetadata_ab_post_timestamp) {
@@ -558,7 +558,7 @@ TEST(InstallTest, CheckPackageMetadata_ab_post_timestamp) {
"pre-device=" + device,
},
"\n");
- test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+ TestCheckPackageMetadata(metadata, OtaType::AB, false);
// post timestamp should be larger than the timestamp on device.
metadata = android::base::Join(
@@ -568,7 +568,7 @@ TEST(InstallTest, CheckPackageMetadata_ab_post_timestamp) {
"post-timestamp=0",
},
"\n");
- test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+ TestCheckPackageMetadata(metadata, OtaType::AB, false);
// fingerprint is required for downgrade
metadata = android::base::Join(
@@ -579,7 +579,7 @@ TEST(InstallTest, CheckPackageMetadata_ab_post_timestamp) {
"ota-downgrade=yes",
},
"\n");
- test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+ TestCheckPackageMetadata(metadata, OtaType::AB, false);
std::string finger_print = android::base::GetProperty("ro.build.fingerprint", "");
ASSERT_NE("", finger_print);
@@ -593,5 +593,5 @@ TEST(InstallTest, CheckPackageMetadata_ab_post_timestamp) {
"ota-downgrade=yes",
},
"\n");
- test_check_package_metadata(metadata, OtaType::AB, 0);
+ TestCheckPackageMetadata(metadata, OtaType::AB, true);
}
diff --git a/tests/unit/locale_test.cpp b/tests/unit/locale_test.cpp
index cdaba0e8b..c69434c12 100644
--- a/tests/unit/locale_test.cpp
+++ b/tests/unit/locale_test.cpp
@@ -27,7 +27,7 @@ TEST(LocaleTest, Misc) {
EXPECT_FALSE(matches_locale("en-GB", "en"));
EXPECT_FALSE(matches_locale("en-GB", "en-US"));
EXPECT_FALSE(matches_locale("en-US", ""));
- // Empty locale prefix in the PNG file will match the input locale.
- EXPECT_TRUE(matches_locale("", "en-US"));
+ // Empty locale prefix in the PNG file should not match the input locale.
+ EXPECT_FALSE(matches_locale("", "en-US"));
EXPECT_TRUE(matches_locale("sr-Latn", "sr-Latn-BA"));
}
diff --git a/tests/unit/package_test.cpp b/tests/unit/package_test.cpp
index a735a699e..5e31f7fa5 100644
--- a/tests/unit/package_test.cpp
+++ b/tests/unit/package_test.cpp
@@ -105,10 +105,9 @@ TEST_F(PackageTest, GetZipArchiveHandle_extract_entry) {
ASSERT_TRUE(zip);
// Check that we can extract one zip entry.
- std::string entry_name = "dir1/file3.txt";
- ZipString path(entry_name.c_str());
+ std::string_view entry_name = "dir1/file3.txt";
ZipEntry entry;
- ASSERT_EQ(0, FindEntry(zip, path, &entry));
+ ASSERT_EQ(0, FindEntry(zip, entry_name, &entry));
std::vector<uint8_t> extracted(entry_name.size());
ASSERT_EQ(0, ExtractToMemory(zip, &entry, extracted.data(), extracted.size()));
diff --git a/tests/unit/parse_install_logs_test.cpp b/tests/unit/parse_install_logs_test.cpp
index 72169a0c6..052f71c98 100644
--- a/tests/unit/parse_install_logs_test.cpp
+++ b/tests/unit/parse_install_logs_test.cpp
@@ -22,7 +22,7 @@
#include <android-base/strings.h>
#include <gtest/gtest.h>
-#include "otautil/parse_install_logs.h"
+#include "recovery_utils/parse_install_logs.h"
TEST(ParseInstallLogsTest, EmptyFile) {
TemporaryFile last_install;
diff --git a/tests/unit/rangeset_test.cpp b/tests/unit/rangeset_test.cpp
index fc72f2f6d..699f933a0 100644
--- a/tests/unit/rangeset_test.cpp
+++ b/tests/unit/rangeset_test.cpp
@@ -18,6 +18,7 @@
#include <sys/types.h>
#include <limits>
+#include <optional>
#include <vector>
#include <gtest/gtest.h>
@@ -248,6 +249,29 @@ TEST(RangeSetTest, ToString) {
ASSERT_EQ("6,1,3,4,6,15,22", RangeSet::Parse("6,1,3,4,6,15,22").ToString());
}
+TEST(RangeSetTest, GetSubRanges_invalid) {
+ RangeSet range0({ { 1, 11 }, { 20, 30 } });
+ ASSERT_FALSE(range0.GetSubRanges(0, 21)); // too many blocks
+ ASSERT_FALSE(range0.GetSubRanges(21, 1)); // start block OOB
+}
+
+TEST(RangeSetTest, GetSubRanges_empty) {
+ RangeSet range0({ { 1, 11 }, { 20, 30 } });
+ ASSERT_EQ(RangeSet{}, range0.GetSubRanges(1, 0)); // empty num_of_blocks
+}
+
+TEST(RangeSetTest, GetSubRanges_smoke) {
+ RangeSet range0({ { 10, 11 } });
+ ASSERT_EQ(RangeSet({ { 10, 11 } }), range0.GetSubRanges(0, 1));
+
+ RangeSet range1({ { 10, 11 }, { 20, 21 }, { 30, 31 } });
+ ASSERT_EQ(range1, range1.GetSubRanges(0, 3));
+ ASSERT_EQ(RangeSet({ { 20, 21 } }), range1.GetSubRanges(1, 1));
+
+ RangeSet range2({ { 1, 11 }, { 20, 25 }, { 30, 35 } });
+ ASSERT_EQ(RangeSet({ { 10, 11 }, { 20, 25 }, { 30, 31 } }), range2.GetSubRanges(9, 7));
+}
+
TEST(SortedRangeSetTest, Insert) {
SortedRangeSet rs({ { 2, 3 }, { 4, 6 }, { 8, 14 } });
rs.Insert({ 1, 2 });
diff --git a/tests/unit/resources_test.cpp b/tests/unit/resources_test.cpp
index c3f72718f..302744308 100644
--- a/tests/unit/resources_test.cpp
+++ b/tests/unit/resources_test.cpp
@@ -14,12 +14,62 @@
* limitations under the License.
*/
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <memory>
#include <string>
+#include <vector>
+#include <android-base/file.h>
+#include <android-base/strings.h>
#include <gtest/gtest.h>
+#include <png.h>
#include "common/test_constants.h"
#include "minui/minui.h"
+#include "private/resources.h"
+
+static const std::string kLocale = "zu";
+
+static const std::vector<std::string> kResourceImagesDirs{
+ "res-mdpi/images/", "res-hdpi/images/", "res-xhdpi/images/",
+ "res-xxhdpi/images/", "res-xxxhdpi/images/",
+};
+
+static int png_filter(const dirent* de) {
+ if (de->d_type != DT_REG || !android::base::EndsWith(de->d_name, "_text.png")) {
+ return 0;
+ }
+ return 1;
+}
+
+// Finds out all the PNG files to test, which stay under the same dir with the executabl..
+static std::vector<std::string> add_files() {
+ std::vector<std::string> files;
+ for (const std::string& images_dir : kResourceImagesDirs) {
+ static std::string exec_dir = android::base::GetExecutableDirectory();
+ std::string dir_path = exec_dir + "/" + images_dir;
+ dirent** namelist;
+ int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort);
+ if (n == -1) {
+ printf("Failed to scandir %s: %s\n", dir_path.c_str(), strerror(errno));
+ continue;
+ }
+ if (n == 0) {
+ printf("No file is added for test in %s\n", dir_path.c_str());
+ }
+
+ while (n--) {
+ std::string file_path = dir_path + namelist[n]->d_name;
+ files.push_back(file_path);
+ free(namelist[n]);
+ }
+ free(namelist);
+ }
+ return files;
+}
TEST(ResourcesTest, res_create_multi_display_surface) {
GRSurface** frames;
@@ -35,3 +85,52 @@ TEST(ResourcesTest, res_create_multi_display_surface) {
}
free(frames);
}
+
+class ResourcesTest : public testing::TestWithParam<std::string> {
+ public:
+ static std::vector<std::string> png_list;
+
+ protected:
+ void SetUp() override {
+ png_ = std::make_unique<PngHandler>(GetParam());
+ ASSERT_TRUE(png_);
+
+ ASSERT_EQ(PNG_COLOR_TYPE_GRAY, png_->color_type()) << "Recovery expects grayscale PNG file.";
+ ASSERT_LT(static_cast<png_uint_32>(5), png_->width());
+ ASSERT_LT(static_cast<png_uint_32>(0), png_->height());
+ ASSERT_EQ(1, png_->channels()) << "Recovery background text images expects 1-channel PNG file.";
+ }
+
+ std::unique_ptr<PngHandler> png_{ nullptr };
+};
+
+// Parses a png file and tests if it's qualified for the background text image under recovery.
+TEST_P(ResourcesTest, ValidateLocale) {
+ std::vector<unsigned char> row(png_->width());
+ for (png_uint_32 y = 0; y < png_->height(); ++y) {
+ png_read_row(png_->png_ptr(), row.data(), nullptr);
+ int w = (row[1] << 8) | row[0];
+ int h = (row[3] << 8) | row[2];
+ int len = row[4];
+ EXPECT_LT(0, w);
+ EXPECT_LT(0, h);
+ EXPECT_LT(0, len) << "Locale string should be non-empty.";
+ EXPECT_NE(0, row[5]) << "Locale string is missing.";
+
+ ASSERT_GE(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
+ char* loc = reinterpret_cast<char*>(&row[5]);
+ if (matches_locale(loc, kLocale.c_str())) {
+ EXPECT_TRUE(android::base::StartsWith(loc, kLocale));
+ break;
+ }
+ for (int i = 0; i < h; ++i, ++y) {
+ png_read_row(png_->png_ptr(), row.data(), nullptr);
+ }
+ }
+}
+
+std::vector<std::string> ResourcesTest::png_list = add_files();
+
+INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourcesTest,
+ ::testing::ValuesIn(ResourcesTest::png_list.cbegin(),
+ ResourcesTest::png_list.cend()));
diff --git a/tests/unit/sysutil_test.cpp b/tests/unit/sysutil_test.cpp
index 3466e8eec..64b8956f7 100644
--- a/tests/unit/sysutil_test.cpp
+++ b/tests/unit/sysutil_test.cpp
@@ -67,7 +67,7 @@ TEST(SysUtilTest, ParseBlockMapFile_invalid_size) {
"/dev/abc",
"42949672950 4294967295",
"1",
- "0 9",
+ "0 10",
};
TemporaryFile temp_file;
diff --git a/tests/component/uncrypt_test.cpp b/tests/unit/uncrypt_test.cpp
index e97d589a6..e97d589a6 100644
--- a/tests/component/uncrypt_test.cpp
+++ b/tests/unit/uncrypt_test.cpp
diff --git a/tests/component/update_verifier_test.cpp b/tests/unit/update_verifier_test.cpp
index e27e58c22..e27e58c22 100644
--- a/tests/component/update_verifier_test.cpp
+++ b/tests/unit/update_verifier_test.cpp
diff --git a/tests/component/updater_test.cpp b/tests/unit/updater_test.cpp
index a0a7b66ab..8993dd8b7 100644
--- a/tests/component/updater_test.cpp
+++ b/tests/unit/updater_test.cpp
@@ -52,21 +52,20 @@
#include "updater/blockimg.h"
#include "updater/install.h"
#include "updater/updater.h"
+#include "updater/updater_runtime.h"
using namespace std::string_literals;
using PackageEntries = std::unordered_map<std::string, std::string>;
-struct selabel_handle* sehandle = nullptr;
-
static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code,
- UpdaterInfo* info = nullptr) {
+ Updater* updater) {
std::unique_ptr<Expr> e;
int error_count = 0;
ASSERT_EQ(0, ParseString(expr_str, &e, &error_count));
ASSERT_EQ(0, error_count);
- State state(expr_str, info);
+ State state(expr_str, updater);
std::string result;
bool status = Evaluate(&state, e, &result);
@@ -85,6 +84,11 @@ static void expect(const char* expected, const std::string& expr_str, CauseCode
ASSERT_EQ(cause_code, state.cause_code);
}
+static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code) {
+ Updater updater(std::make_unique<UpdaterRuntime>(nullptr));
+ expect(expected, expr_str, cause_code, &updater);
+}
+
static void BuildUpdatePackage(const PackageEntries& entries, int fd) {
FILE* zip_file_ptr = fdopen(fd, "wb");
ZipWriter zip_writer(zip_file_ptr);
@@ -102,38 +106,6 @@ static void BuildUpdatePackage(const PackageEntries& entries, int fd) {
ASSERT_EQ(0, fclose(zip_file_ptr));
}
-static void RunBlockImageUpdate(bool is_verify, const PackageEntries& entries,
- const std::string& image_file, const std::string& result,
- CauseCode cause_code = kNoCause) {
- CHECK(entries.find("transfer_list") != entries.end());
-
- // Build the update package.
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
-
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- ZipArchiveHandle handle;
- ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
-
- // Set up the handler, command_pipe, patch offset & length.
- UpdaterInfo updater_info;
- updater_info.package_zip = handle;
- TemporaryFile temp_pipe;
- updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
-
- std::string new_data = entries.find("new_data.br") != entries.end() ? "new_data.br" : "new_data";
- std::string script = is_verify ? "block_image_verify" : "block_image_update";
- script += R"((")" + image_file + R"(", package_extract_file("transfer_list"), ")" + new_data +
- R"(", "patch_data"))";
- expect(result.c_str(), script, cause_code, &updater_info);
-
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
-}
-
static std::string GetSha1(std::string_view content) {
uint8_t digest[SHA_DIGEST_LENGTH];
SHA1(reinterpret_cast<const uint8_t*>(content.data()), content.size(), digest);
@@ -159,29 +131,26 @@ static Value* BlobToString(const char* name, State* state,
return args[0].release();
}
-class UpdaterTest : public ::testing::Test {
+class UpdaterTestBase {
protected:
- void SetUp() override {
+ UpdaterTestBase() : updater_(std::make_unique<UpdaterRuntime>(nullptr)) {}
+
+ void SetUp() {
RegisterBuiltins();
RegisterInstallFunctions();
RegisterBlockImageFunctions();
- RegisterFunction("blob_to_string", BlobToString);
-
// Each test is run in a separate process (isolated mode). Shared temporary files won't cause
// conflicts.
Paths::Get().set_cache_temp_source(temp_saved_source_.path);
Paths::Get().set_last_command_file(temp_last_command_.path);
Paths::Get().set_stash_directory_base(temp_stash_base_.path);
- // Enable a special command "abort" to simulate interruption.
- Command::abort_allowed_ = true;
-
last_command_file_ = temp_last_command_.path;
image_file_ = image_temp_file_.path;
}
- void TearDown() override {
+ void TearDown() {
// Clean up the last_command_file if any.
ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_));
@@ -191,16 +160,80 @@ class UpdaterTest : public ::testing::Test {
ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker));
}
+ void RunBlockImageUpdate(bool is_verify, PackageEntries entries, const std::string& image_file,
+ const std::string& result, CauseCode cause_code = kNoCause) {
+ CHECK(entries.find("transfer_list") != entries.end());
+ std::string new_data =
+ entries.find("new_data.br") != entries.end() ? "new_data.br" : "new_data";
+ std::string script = is_verify ? "block_image_verify" : "block_image_update";
+ script += R"((")" + image_file + R"(", package_extract_file("transfer_list"), ")" + new_data +
+ R"(", "patch_data"))";
+ entries.emplace(Updater::SCRIPT_NAME, script);
+
+ // Build the update package.
+ TemporaryFile zip_file;
+ BuildUpdatePackage(entries, zip_file.release());
+
+ // Set up the handler, command_pipe, patch offset & length.
+ TemporaryFile temp_pipe;
+ ASSERT_TRUE(updater_.Init(temp_pipe.release(), zip_file.path, false));
+ ASSERT_TRUE(updater_.RunUpdate());
+ ASSERT_EQ(result, updater_.GetResult());
+
+ // Parse the cause code written to the command pipe.
+ int received_cause_code = kNoCause;
+ std::string pipe_content;
+ ASSERT_TRUE(android::base::ReadFileToString(temp_pipe.path, &pipe_content));
+ auto lines = android::base::Split(pipe_content, "\n");
+ for (std::string_view line : lines) {
+ if (android::base::ConsumePrefix(&line, "log cause: ")) {
+ ASSERT_TRUE(android::base::ParseInt(line.data(), &received_cause_code));
+ }
+ }
+ ASSERT_EQ(cause_code, received_cause_code);
+ }
+
TemporaryFile temp_saved_source_;
TemporaryDir temp_stash_base_;
std::string last_command_file_;
std::string image_file_;
+ Updater updater_;
+
private:
TemporaryFile temp_last_command_;
TemporaryFile image_temp_file_;
};
+class UpdaterTest : public UpdaterTestBase, public ::testing::Test {
+ protected:
+ void SetUp() override {
+ UpdaterTestBase::SetUp();
+
+ RegisterFunction("blob_to_string", BlobToString);
+ // Enable a special command "abort" to simulate interruption.
+ Command::abort_allowed_ = true;
+ }
+
+ void TearDown() override {
+ UpdaterTestBase::TearDown();
+ }
+
+ void SetUpdaterCmdPipe(int fd) {
+ FILE* cmd_pipe = fdopen(fd, "w");
+ ASSERT_NE(nullptr, cmd_pipe);
+ updater_.cmd_pipe_.reset(cmd_pipe);
+ }
+
+ void SetUpdaterOtaPackageHandle(ZipArchiveHandle handle) {
+ updater_.package_handle_ = handle;
+ }
+
+ void FlushUpdaterCommandPipe() const {
+ fflush(updater_.cmd_pipe_.get());
+ }
+};
+
TEST_F(UpdaterTest, getprop) {
expect(android::base::GetProperty("ro.product.device", "").c_str(),
"getprop(\"ro.product.device\")",
@@ -317,13 +350,12 @@ TEST_F(UpdaterTest, package_extract_file) {
ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
// Need to set up the ziphandle.
- UpdaterInfo updater_info;
- updater_info.package_zip = handle;
+ SetUpdaterOtaPackageHandle(handle);
// Two-argument version.
TemporaryFile temp_file1;
std::string script("package_extract_file(\"a.txt\", \"" + std::string(temp_file1.path) + "\")");
- expect("t", script, kNoCause, &updater_info);
+ expect("t", script, kNoCause, &updater_);
// Verify the extracted entry.
std::string data;
@@ -332,32 +364,30 @@ TEST_F(UpdaterTest, package_extract_file) {
// Now extract another entry to the same location, which should overwrite.
script = "package_extract_file(\"b.txt\", \"" + std::string(temp_file1.path) + "\")";
- expect("t", script, kNoCause, &updater_info);
+ expect("t", script, kNoCause, &updater_);
ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data));
ASSERT_EQ(kBTxtContents, data);
// Missing zip entry. The two-argument version doesn't abort.
script = "package_extract_file(\"doesntexist\", \"" + std::string(temp_file1.path) + "\")";
- expect("", script, kNoCause, &updater_info);
+ expect("", script, kNoCause, &updater_);
// Extract to /dev/full should fail.
script = "package_extract_file(\"a.txt\", \"/dev/full\")";
- expect("", script, kNoCause, &updater_info);
+ expect("", script, kNoCause, &updater_);
// One-argument version. package_extract_file() gives a VAL_BLOB, which needs to be converted to
// VAL_STRING for equality test.
script = "blob_to_string(package_extract_file(\"a.txt\")) == \"" + kATxtContents + "\"";
- expect("t", script, kNoCause, &updater_info);
+ expect("t", script, kNoCause, &updater_);
script = "blob_to_string(package_extract_file(\"b.txt\")) == \"" + kBTxtContents + "\"";
- expect("t", script, kNoCause, &updater_info);
+ expect("t", script, kNoCause, &updater_);
// Missing entry. The one-argument version aborts the evaluation.
script = "package_extract_file(\"doesntexist\")";
- expect(nullptr, script, kPackageExtractFileFailure, &updater_info);
-
- CloseArchive(handle);
+ expect(nullptr, script, kPackageExtractFileFailure, &updater_);
}
TEST_F(UpdaterTest, read_file) {
@@ -563,17 +593,15 @@ TEST_F(UpdaterTest, set_progress) {
expect(nullptr, "set_progress(\".3.5\")", kArgsParsingFailure);
TemporaryFile tf;
- UpdaterInfo updater_info;
- updater_info.cmd_pipe = fdopen(tf.release(), "w");
- expect(".52", "set_progress(\".52\")", kNoCause, &updater_info);
- fflush(updater_info.cmd_pipe);
+ SetUpdaterCmdPipe(tf.release());
+ expect(".52", "set_progress(\".52\")", kNoCause, &updater_);
+ FlushUpdaterCommandPipe();
std::string cmd;
ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd));
ASSERT_EQ(android::base::StringPrintf("set_progress %f\n", .52), cmd);
// recovery-updater protocol expects 2 tokens ("set_progress <frac>").
ASSERT_EQ(2U, android::base::Split(cmd, " ").size());
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
}
TEST_F(UpdaterTest, show_progress) {
@@ -588,17 +616,15 @@ TEST_F(UpdaterTest, show_progress) {
expect(nullptr, "show_progress(\".3\", \"5a\")", kArgsParsingFailure);
TemporaryFile tf;
- UpdaterInfo updater_info;
- updater_info.cmd_pipe = fdopen(tf.release(), "w");
- expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_info);
- fflush(updater_info.cmd_pipe);
+ SetUpdaterCmdPipe(tf.release());
+ expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_);
+ FlushUpdaterCommandPipe();
std::string cmd;
ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd));
ASSERT_EQ(android::base::StringPrintf("progress %f %d\n", .52, 10), cmd);
// recovery-updater protocol expects 3 tokens ("progress <frac> <secs>").
ASSERT_EQ(3U, android::base::Split(cmd, " ").size());
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
}
TEST_F(UpdaterTest, block_image_update_parsing_error) {
@@ -993,44 +1019,20 @@ TEST_F(UpdaterTest, last_command_verify) {
ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
}
-class ResumableUpdaterTest : public testing::TestWithParam<size_t> {
+class ResumableUpdaterTest : public UpdaterTestBase, public testing::TestWithParam<size_t> {
protected:
void SetUp() override {
- RegisterBuiltins();
- RegisterInstallFunctions();
- RegisterBlockImageFunctions();
-
- Paths::Get().set_cache_temp_source(temp_saved_source_.path);
- Paths::Get().set_last_command_file(temp_last_command_.path);
- Paths::Get().set_stash_directory_base(temp_stash_base_.path);
-
+ UpdaterTestBase::SetUp();
// Enable a special command "abort" to simulate interruption.
Command::abort_allowed_ = true;
-
index_ = GetParam();
- image_file_ = image_temp_file_.path;
- last_command_file_ = temp_last_command_.path;
}
void TearDown() override {
- // Clean up the last_command_file if any.
- ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_));
-
- // Clear partition updated marker if any.
- std::string updated_marker{ temp_stash_base_.path };
- updated_marker += "/" + GetSha1(image_temp_file_.path) + ".UPDATED";
- ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker));
+ UpdaterTestBase::TearDown();
}
- TemporaryFile temp_saved_source_;
- TemporaryDir temp_stash_base_;
- std::string last_command_file_;
- std::string image_file_;
size_t index_;
-
- private:
- TemporaryFile temp_last_command_;
- TemporaryFile image_temp_file_;
};
static std::string g_source_image;
diff --git a/tests/component/verifier_test.cpp b/tests/unit/verifier_test.cpp
index ded23c52f..ded23c52f 100644
--- a/tests/component/verifier_test.cpp
+++ b/tests/unit/verifier_test.cpp
diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp
index dfe617ebe..0753d64e1 100644
--- a/tests/unit/zip_test.cpp
+++ b/tests/unit/zip_test.cpp
@@ -37,10 +37,9 @@ TEST(ZipTest, OpenFromMemory) {
ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_path.c_str(), &handle));
static constexpr const char* BINARY_PATH = "META-INF/com/google/android/update-binary";
- ZipString binary_path(BINARY_PATH);
ZipEntry binary_entry;
// Make sure the package opens correctly and its entry can be read.
- ASSERT_EQ(0, FindEntry(handle, binary_path, &binary_entry));
+ ASSERT_EQ(0, FindEntry(handle, BINARY_PATH, &binary_entry));
TemporaryFile tmp_binary;
ASSERT_NE(-1, tmp_binary.fd);
diff --git a/tools/image_generator/Android.bp b/tools/image_generator/Android.bp
index 2afdd5a84..83000407c 100644
--- a/tools/image_generator/Android.bp
+++ b/tools/image_generator/Android.bp
@@ -19,7 +19,7 @@ java_library_host {
static_libs: [
"commons-cli-1.2",
- "icu4j-host",
+ "icu4j",
],
srcs: [
diff --git a/updater/Android.bp b/updater/Android.bp
index b80cdb3a0..8a60ef76a 100644
--- a/updater/Android.bp
+++ b/updater/Android.bp
@@ -30,7 +30,6 @@ cc_defaults {
"libfec",
"libfec_rs",
"libverity_tree",
- "libfs_mgr",
"libgtest_prod",
"liblog",
"liblp",
@@ -42,10 +41,21 @@ cc_defaults {
"libziparchive",
"libz",
"libbase",
- "libcrypto",
"libcrypto_utils",
"libcutils",
"libutils",
+ ],
+
+ shared_libs: [
+ "libcrypto",
+ ],
+}
+
+cc_defaults {
+ name: "libupdater_device_defaults",
+
+ static_libs: [
+ "libfs_mgr",
"libtune2fs",
"libext2_com_err",
@@ -54,11 +64,13 @@ cc_defaults {
"libext2_uuid",
"libext2_e2p",
"libext2fs",
- ],
+ ]
}
cc_library_static {
- name: "libupdater",
+ name: "libupdater_core",
+
+ host_supported: true,
defaults: [
"recovery_defaults",
@@ -68,8 +80,39 @@ cc_library_static {
srcs: [
"blockimg.cpp",
"commands.cpp",
- "dynamic_partitions.cpp",
"install.cpp",
+ "mounts.cpp",
+ "updater.cpp",
+ ],
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+
+ export_include_dirs: [
+ "include",
+ ],
+}
+
+cc_library_static {
+ name: "libupdater_device",
+
+ defaults: [
+ "recovery_defaults",
+ "libupdater_defaults",
+ "libupdater_device_defaults",
+ ],
+
+ srcs: [
+ "dynamic_partitions.cpp",
+ "updater_runtime.cpp",
+ "updater_runtime_dynamic_partitions.cpp",
+ ],
+
+ static_libs: [
+ "libupdater_core",
],
include_dirs: [
@@ -80,3 +123,35 @@ cc_library_static {
"include",
],
}
+
+cc_library_host_static {
+ name: "libupdater_host",
+
+ defaults: [
+ "recovery_defaults",
+ "libupdater_defaults",
+ ],
+
+ srcs: [
+ "build_info.cpp",
+ "dynamic_partitions.cpp",
+ "simulator_runtime.cpp",
+ "target_files.cpp",
+ ],
+
+ static_libs: [
+ "libupdater_core",
+ "libfstab",
+ "libc++fs",
+ ],
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+
+ export_include_dirs: [
+ "include",
+ ],
+}
diff --git a/updater/Android.mk b/updater/Android.mk
index c7a6ba989..6f54d89b8 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -33,7 +33,6 @@ updater_common_static_libraries := \
libfec \
libfec_rs \
libverity_tree \
- libfs_mgr \
libgtest_prod \
liblog \
liblp \
@@ -45,12 +44,27 @@ updater_common_static_libraries := \
libziparchive \
libz \
libbase \
- libcrypto \
+ libcrypto_static \
libcrypto_utils \
libcutils \
- libutils \
- libtune2fs \
- $(tune2fs_static_libraries)
+ libutils
+
+
+# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function
+# named "Register_<libname>()". Here we emit a little C function that
+# gets #included by updater.cpp. It calls all those registration
+# functions.
+# $(1): the path to the register.inc file
+# $(2): a list of TARGET_RECOVERY_UPDATER_LIBS
+define generate-register-inc
+ $(hide) mkdir -p $(dir $(1))
+ $(hide) echo "" > $(1)
+ $(hide) $(foreach lib,$(2),echo "extern void Register_$(lib)(void);" >> $(1);)
+ $(hide) echo "void RegisterDeviceExtensions() {" >> $(1)
+ $(hide) $(foreach lib,$(2),echo " Register_$(lib)();" >> $(1);)
+ $(hide) echo "}" >> $(1)
+endef
+
# updater (static executable)
# ===============================
@@ -59,7 +73,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE := updater
LOCAL_SRC_FILES := \
- updater.cpp
+ updater_main.cpp
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/include
@@ -69,33 +83,26 @@ LOCAL_CFLAGS := \
-Werror
LOCAL_STATIC_LIBRARIES := \
- libupdater \
+ libupdater_device \
+ libupdater_core \
$(TARGET_RECOVERY_UPDATER_LIBS) \
$(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) \
- $(updater_common_static_libraries)
+ $(updater_common_static_libraries) \
+ libfs_mgr \
+ libtune2fs \
+ $(tune2fs_static_libraries)
-# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function
-# named "Register_<libname>()". Here we emit a little C function that
-# gets #included by updater.c. It calls all those registration
-# functions.
+LOCAL_MODULE_CLASS := EXECUTABLES
+inc := $(call local-generated-sources-dir)/register.inc
# Devices can also add libraries to TARGET_RECOVERY_UPDATER_EXTRA_LIBS.
# These libs are also linked in with updater, but we don't try to call
# any sort of registration function for these. Use this variable for
# any subsidiary static libraries required for your registered
# extension libs.
-
-LOCAL_MODULE_CLASS := EXECUTABLES
-inc := $(call local-generated-sources-dir)/register.inc
-
$(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS)
$(inc) :
- $(hide) mkdir -p $(dir $@)
- $(hide) echo "" > $@
- $(hide) $(foreach lib,$(libs),echo "extern void Register_$(lib)(void);" >> $@;)
- $(hide) echo "void RegisterDeviceExtensions() {" >> $@
- $(hide) $(foreach lib,$(libs),echo " Register_$(lib)();" >> $@;)
- $(hide) echo "}" >> $@
+ $(call generate-register-inc,$@,$(libs))
LOCAL_GENERATED_SOURCES := $(inc)
@@ -104,3 +111,32 @@ inc :=
LOCAL_FORCE_STATIC_EXECUTABLE := true
include $(BUILD_EXECUTABLE)
+
+# TODO(xunchang) move to bp file
+# update_host_simulator (host executable)
+# ===============================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := update_host_simulator
+LOCAL_MODULE_HOST_OS := linux
+
+LOCAL_SRC_FILES := \
+ update_simulator_main.cpp
+
+LOCAL_C_INCLUDES := \
+ $(LOCAL_PATH)/include
+
+LOCAL_CFLAGS := \
+ -Wall \
+ -Werror
+
+LOCAL_STATIC_LIBRARIES := \
+ libupdater_host \
+ libupdater_core \
+ $(updater_common_static_libraries) \
+ libfstab \
+ libc++fs
+
+LOCAL_MODULE_CLASS := EXECUTABLES
+
+include $(BUILD_HOST_EXECUTABLE)
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index 07c3c7b52..2d41f610b 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -42,17 +42,18 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <applypatch/applypatch.h>
#include <brotli/decode.h>
#include <fec/io.h>
#include <openssl/sha.h>
-#include <private/android_filesystem_config.h>
#include <verity/hash_tree_builder.h>
#include <ziparchive/zip_archive.h>
#include "edify/expr.h"
+#include "edify/updater_interface.h"
#include "otautil/dirutil.h"
#include "otautil/error_code.h"
#include "otautil/paths.h"
@@ -60,12 +61,16 @@
#include "otautil/rangeset.h"
#include "private/commands.h"
#include "updater/install.h"
-#include "updater/updater.h"
-// Set this to 0 to interpret 'erase' transfers to mean do a
-// BLKDISCARD ioctl (the normal behavior). Set to 1 to interpret
-// erase to mean fill the region with zeroes.
+#ifdef __ANDROID__
+#include <private/android_filesystem_config.h>
+// Set this to 0 to interpret 'erase' transfers to mean do a BLKDISCARD ioctl (the normal behavior).
+// Set to 1 to interpret erase to mean fill the region with zeroes.
#define DEBUG_ERASE 0
+#else
+#define DEBUG_ERASE 1
+#define AID_SYSTEM -1
+#endif // __ANDROID__
static constexpr size_t BLOCKSIZE = 4096;
static constexpr mode_t STASH_DIRECTORY_MODE = 0700;
@@ -1668,42 +1673,43 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
return StringValue("");
}
- UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
- if (ui == nullptr) {
+ auto updater = state->updater;
+ auto block_device_path = updater->FindBlockDeviceName(blockdev_filename->data);
+ if (block_device_path.empty()) {
+ LOG(ERROR) << "Block device path for " << blockdev_filename->data << " not found. " << name
+ << " failed.";
return StringValue("");
}
- FILE* cmd_pipe = ui->cmd_pipe;
- ZipArchiveHandle za = ui->package_zip;
-
- if (cmd_pipe == nullptr || za == nullptr) {
+ ZipArchiveHandle za = updater->GetPackageHandle();
+ if (za == nullptr) {
return StringValue("");
}
- ZipString path_data(patch_data_fn->data.c_str());
+ std::string_view path_data(patch_data_fn->data);
ZipEntry patch_entry;
if (FindEntry(za, path_data, &patch_entry) != 0) {
LOG(ERROR) << name << "(): no file \"" << patch_data_fn->data << "\" in package";
return StringValue("");
}
+ params.patch_start = updater->GetMappedPackageAddress() + patch_entry.offset;
- params.patch_start = ui->package_zip_addr + patch_entry.offset;
- ZipString new_data(new_data_fn->data.c_str());
+ std::string_view new_data(new_data_fn->data);
ZipEntry new_entry;
if (FindEntry(za, new_data, &new_entry) != 0) {
LOG(ERROR) << name << "(): no file \"" << new_data_fn->data << "\" in package";
return StringValue("");
}
- params.fd.reset(TEMP_FAILURE_RETRY(open(blockdev_filename->data.c_str(), O_RDWR)));
+ params.fd.reset(TEMP_FAILURE_RETRY(open(block_device_path.c_str(), O_RDWR)));
if (params.fd == -1) {
failure_type = errno == EIO ? kEioFailure : kFileOpenFailure;
- PLOG(ERROR) << "open \"" << blockdev_filename->data << "\" failed";
+ PLOG(ERROR) << "open \"" << block_device_path << "\" failed";
return StringValue("");
}
uint8_t digest[SHA_DIGEST_LENGTH];
- if (!Sha1DevicePath(blockdev_filename->data, digest)) {
+ if (!Sha1DevicePath(block_device_path, digest)) {
return StringValue("");
}
params.stashbase = print_sha1(digest);
@@ -1716,8 +1722,7 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
struct stat sb;
int result = stat(updated_marker.c_str(), &sb);
if (result == 0) {
- LOG(INFO) << "Skipping already updated partition " << blockdev_filename->data
- << " based on marker";
+ LOG(INFO) << "Skipping already updated partition " << block_device_path << " based on marker";
return StringValue("t");
}
} else {
@@ -1887,8 +1892,10 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
LOG(WARNING) << "Failed to update the last command file.";
}
- fprintf(cmd_pipe, "set_progress %.4f\n", static_cast<double>(params.written) / total_blocks);
- fflush(cmd_pipe);
+ updater->WriteToCommandPipe(
+ android::base::StringPrintf("set_progress %.4f",
+ static_cast<double>(params.written) / total_blocks),
+ true);
}
}
@@ -1913,13 +1920,15 @@ pbiudone:
LOG(INFO) << "stashed " << params.stashed << " blocks";
LOG(INFO) << "max alloc needed was " << params.buffer.size();
- const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
+ const char* partition = strrchr(block_device_path.c_str(), '/');
if (partition != nullptr && *(partition + 1) != 0) {
- fprintf(cmd_pipe, "log bytes_written_%s: %" PRIu64 "\n", partition + 1,
- static_cast<uint64_t>(params.written) * BLOCKSIZE);
- fprintf(cmd_pipe, "log bytes_stashed_%s: %" PRIu64 "\n", partition + 1,
- static_cast<uint64_t>(params.stashed) * BLOCKSIZE);
- fflush(cmd_pipe);
+ updater->WriteToCommandPipe(
+ android::base::StringPrintf("log bytes_written_%s: %" PRIu64, partition + 1,
+ static_cast<uint64_t>(params.written) * BLOCKSIZE));
+ updater->WriteToCommandPipe(
+ android::base::StringPrintf("log bytes_stashed_%s: %" PRIu64, partition + 1,
+ static_cast<uint64_t>(params.stashed) * BLOCKSIZE),
+ true);
}
// Delete stash only after successfully completing the update, as it may contain blocks needed
// to complete the update later.
@@ -2019,7 +2028,7 @@ Value* BlockImageVerifyFn(const char* name, State* state,
// clang-format off
{ Command::Type::ABORT, PerformCommandAbort },
{ Command::Type::BSDIFF, PerformCommandDiff },
- { Command::Type::COMPUTE_HASH_TREE, PerformCommandComputeHashTree },
+ { Command::Type::COMPUTE_HASH_TREE, nullptr },
{ Command::Type::ERASE, nullptr },
{ Command::Type::FREE, PerformCommandFree },
{ Command::Type::IMGDIFF, PerformCommandDiff },
@@ -2079,10 +2088,17 @@ Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique
return StringValue("");
}
- android::base::unique_fd fd(open(blockdev_filename->data.c_str(), O_RDWR));
+ auto block_device_path = state->updater->FindBlockDeviceName(blockdev_filename->data);
+ if (block_device_path.empty()) {
+ LOG(ERROR) << "Block device path for " << blockdev_filename->data << " not found. " << name
+ << " failed.";
+ return StringValue("");
+ }
+
+ android::base::unique_fd fd(open(block_device_path.c_str(), O_RDWR));
if (fd == -1) {
CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure;
- ErrorAbort(state, cause_code, "open \"%s\" failed: %s", blockdev_filename->data.c_str(),
+ ErrorAbort(state, cause_code, "open \"%s\" failed: %s", block_device_path.c_str(),
strerror(errno));
return StringValue("");
}
@@ -2096,7 +2112,7 @@ Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique
std::vector<uint8_t> buffer(BLOCKSIZE);
for (const auto& [begin, end] : rs) {
if (!check_lseek(fd, static_cast<off64_t>(begin) * BLOCKSIZE, SEEK_SET)) {
- ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", blockdev_filename->data.c_str(),
+ ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", block_device_path.c_str(),
strerror(errno));
return StringValue("");
}
@@ -2104,7 +2120,7 @@ Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique
for (size_t j = begin; j < end; ++j) {
if (!android::base::ReadFully(fd, buffer.data(), BLOCKSIZE)) {
CauseCode cause_code = errno == EIO ? kEioFailure : kFreadFailure;
- ErrorAbort(state, cause_code, "failed to read %s: %s", blockdev_filename->data.c_str(),
+ ErrorAbort(state, cause_code, "failed to read %s: %s", block_device_path.c_str(),
strerror(errno));
return StringValue("");
}
@@ -2143,10 +2159,17 @@ Value* CheckFirstBlockFn(const char* name, State* state,
return StringValue("");
}
- android::base::unique_fd fd(open(arg_filename->data.c_str(), O_RDONLY));
+ auto block_device_path = state->updater->FindBlockDeviceName(arg_filename->data);
+ if (block_device_path.empty()) {
+ LOG(ERROR) << "Block device path for " << arg_filename->data << " not found. " << name
+ << " failed.";
+ return StringValue("");
+ }
+
+ android::base::unique_fd fd(open(block_device_path.c_str(), O_RDONLY));
if (fd == -1) {
CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure;
- ErrorAbort(state, cause_code, "open \"%s\" failed: %s", arg_filename->data.c_str(),
+ ErrorAbort(state, cause_code, "open \"%s\" failed: %s", block_device_path.c_str(),
strerror(errno));
return StringValue("");
}
@@ -2156,7 +2179,7 @@ Value* CheckFirstBlockFn(const char* name, State* state,
if (ReadBlocks(blk0, &block0_buffer, fd) == -1) {
CauseCode cause_code = errno == EIO ? kEioFailure : kFreadFailure;
- ErrorAbort(state, cause_code, "failed to read %s: %s", arg_filename->data.c_str(),
+ ErrorAbort(state, cause_code, "failed to read %s: %s", block_device_path.c_str(),
strerror(errno));
return StringValue("");
}
@@ -2172,8 +2195,10 @@ Value* CheckFirstBlockFn(const char* name, State* state,
uint16_t mount_count = *reinterpret_cast<uint16_t*>(&block0_buffer[0x400 + 0x34]);
if (mount_count > 0) {
- uiPrintf(state, "Device was remounted R/W %" PRIu16 " times", mount_count);
- uiPrintf(state, "Last remount happened on %s", ctime(&mount_time));
+ state->updater->UiPrint(
+ android::base::StringPrintf("Device was remounted R/W %" PRIu16 " times", mount_count));
+ state->updater->UiPrint(
+ android::base::StringPrintf("Last remount happened on %s", ctime(&mount_time)));
}
return StringValue("t");
@@ -2209,14 +2234,21 @@ Value* BlockImageRecoverFn(const char* name, State* state,
return StringValue("");
}
+ auto block_device_path = state->updater->FindBlockDeviceName(filename->data);
+ if (block_device_path.empty()) {
+ LOG(ERROR) << "Block device path for " << filename->data << " not found. " << name
+ << " failed.";
+ return StringValue("");
+ }
+
// Output notice to log when recover is attempted
- LOG(INFO) << filename->data << " image corrupted, attempting to recover...";
+ LOG(INFO) << block_device_path << " image corrupted, attempting to recover...";
// When opened with O_RDWR, libfec rewrites corrupted blocks when they are read
- fec::io fh(filename->data, O_RDWR);
+ fec::io fh(block_device_path, O_RDWR);
if (!fh) {
- ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data.c_str(),
+ ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", block_device_path.c_str(),
strerror(errno));
return StringValue("");
}
@@ -2242,7 +2274,7 @@ Value* BlockImageRecoverFn(const char* name, State* state,
if (fh.pread(buffer, BLOCKSIZE, static_cast<off64_t>(j) * BLOCKSIZE) != BLOCKSIZE) {
ErrorAbort(state, kLibfecFailure, "failed to recover %s (block %zu): %s",
- filename->data.c_str(), j, strerror(errno));
+ block_device_path.c_str(), j, strerror(errno));
return StringValue("");
}
@@ -2258,7 +2290,7 @@ Value* BlockImageRecoverFn(const char* name, State* state,
// read and check if the errors field value has increased.
}
}
- LOG(INFO) << "..." << filename->data << " image recovered successfully.";
+ LOG(INFO) << "..." << block_device_path << " image recovered successfully.";
return StringValue("t");
}
diff --git a/updater/build_info.cpp b/updater/build_info.cpp
new file mode 100644
index 000000000..f168008ec
--- /dev/null
+++ b/updater/build_info.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "updater/build_info.h"
+
+#include <stdio.h>
+
+#include <set>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include "updater/target_files.h"
+
+bool BuildInfo::ParseTargetFile(const std::string_view target_file_path, bool extracted_input) {
+ TargetFile target_file(std::string(target_file_path), extracted_input);
+ if (!target_file.Open()) {
+ return false;
+ }
+
+ if (!target_file.GetBuildProps(&build_props_)) {
+ return false;
+ }
+
+ std::vector<FstabInfo> fstab_info_list;
+ if (!target_file.ParseFstabInfo(&fstab_info_list)) {
+ return false;
+ }
+
+ for (const auto& fstab_info : fstab_info_list) {
+ for (const auto& directory : { "IMAGES", "RADIO" }) {
+ std::string entry_name = directory + fstab_info.mount_point + ".img";
+ if (!target_file.EntryExists(entry_name)) {
+ LOG(WARNING) << "Failed to find the image entry in the target file: " << entry_name;
+ continue;
+ }
+
+ temp_files_.emplace_back(work_dir_);
+ auto& image_file = temp_files_.back();
+ if (!target_file.ExtractImage(entry_name, fstab_info, work_dir_, &image_file)) {
+ LOG(ERROR) << "Failed to set up source image files.";
+ return false;
+ }
+
+ std::string mapped_path = image_file.path;
+ // Rename the images to more readable ones if we want to keep the image.
+ if (keep_images_) {
+ mapped_path = work_dir_ + fstab_info.mount_point + ".img";
+ image_file.release();
+ if (rename(image_file.path, mapped_path.c_str()) != 0) {
+ PLOG(ERROR) << "Failed to rename " << image_file.path << " to " << mapped_path;
+ return false;
+ }
+ }
+
+ LOG(INFO) << "Mounted " << fstab_info.mount_point << "\nMapping: " << fstab_info.blockdev_name
+ << " to " << mapped_path;
+
+ blockdev_map_.emplace(
+ fstab_info.blockdev_name,
+ FakeBlockDevice(fstab_info.blockdev_name, fstab_info.mount_point, mapped_path));
+ break;
+ }
+ }
+
+ return true;
+}
+
+std::string BuildInfo::GetProperty(const std::string_view key,
+ const std::string_view default_value) const {
+ // The logic to parse the ro.product properties should be in line with the generation script.
+ // More details in common.py BuildInfo.GetBuildProp.
+ // TODO(xunchang) handle the oem property and the source order defined in
+ // ro.product.property_source_order
+ const std::set<std::string, std::less<>> ro_product_props = {
+ "ro.product.brand", "ro.product.device", "ro.product.manufacturer", "ro.product.model",
+ "ro.product.name"
+ };
+ const std::vector<std::string> source_order = {
+ "product", "odm", "vendor", "system_ext", "system",
+ };
+ if (ro_product_props.find(key) != ro_product_props.end()) {
+ std::string_view key_suffix(key);
+ CHECK(android::base::ConsumePrefix(&key_suffix, "ro.product"));
+ for (const auto& source : source_order) {
+ std::string resolved_key = "ro.product." + source + std::string(key_suffix);
+ if (auto entry = build_props_.find(resolved_key); entry != build_props_.end()) {
+ return entry->second;
+ }
+ }
+ LOG(WARNING) << "Failed to find property: " << key;
+ return std::string(default_value);
+ } else if (key == "ro.build.fingerprint") {
+ // clang-format off
+ return android::base::StringPrintf("%s/%s/%s:%s/%s/%s:%s/%s",
+ GetProperty("ro.product.brand", "").c_str(),
+ GetProperty("ro.product.name", "").c_str(),
+ GetProperty("ro.product.device", "").c_str(),
+ GetProperty("ro.build.version.release", "").c_str(),
+ GetProperty("ro.build.id", "").c_str(),
+ GetProperty("ro.build.version.incremental", "").c_str(),
+ GetProperty("ro.build.type", "").c_str(),
+ GetProperty("ro.build.tags", "").c_str());
+ // clang-format on
+ }
+
+ auto entry = build_props_.find(key);
+ if (entry == build_props_.end()) {
+ LOG(WARNING) << "Failed to find property: " << key;
+ return std::string(default_value);
+ }
+
+ return entry->second;
+}
+
+std::string BuildInfo::FindBlockDeviceName(const std::string_view name) const {
+ auto entry = blockdev_map_.find(name);
+ if (entry == blockdev_map_.end()) {
+ LOG(WARNING) << "Failed to find path to block device " << name;
+ return "";
+ }
+
+ return entry->second.mounted_file_path;
+}
diff --git a/updater/dynamic_partitions.cpp b/updater/dynamic_partitions.cpp
index b50dd75f9..a340116fe 100644
--- a/updater/dynamic_partitions.cpp
+++ b/updater/dynamic_partitions.cpp
@@ -19,46 +19,20 @@
#include <sys/stat.h>
#include <sys/types.h>
-#include <algorithm>
-#include <chrono>
-#include <iterator>
#include <memory>
-#include <optional>
#include <string>
-#include <type_traits>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
-#include <android-base/parseint.h>
#include <android-base/strings.h>
-#include <fs_mgr.h>
-#include <fs_mgr_dm_linear.h>
-#include <libdm/dm.h>
-#include <liblp/builder.h>
#include "edify/expr.h"
+#include "edify/updater_runtime_interface.h"
#include "otautil/error_code.h"
#include "otautil/paths.h"
#include "private/utils.h"
-using android::base::ParseUint;
-using android::dm::DeviceMapper;
-using android::dm::DmDeviceState;
-using android::fs_mgr::CreateLogicalPartition;
-using android::fs_mgr::DestroyLogicalPartition;
-using android::fs_mgr::LpMetadata;
-using android::fs_mgr::MetadataBuilder;
-using android::fs_mgr::Partition;
-using android::fs_mgr::PartitionOpener;
-
-static constexpr std::chrono::milliseconds kMapTimeout{ 1000 };
-static constexpr char kMetadataUpdatedMarker[] = "/dynamic_partition_metadata.UPDATED";
-
-static std::string GetSuperDevice() {
- return "/dev/block/by-name/" + fs_mgr_get_super_partition_name();
-}
-
static std::vector<std::string> ReadStringArgs(const char* name, State* state,
const std::vector<std::unique_ptr<Expr>>& argv,
const std::vector<std::string>& arg_names) {
@@ -89,40 +63,14 @@ static std::vector<std::string> ReadStringArgs(const char* name, State* state,
return ret;
}
-static bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
- auto state = DeviceMapper::Instance().GetState(partition_name);
- if (state == DmDeviceState::INVALID) {
- return true;
- }
- if (state == DmDeviceState::ACTIVE) {
- return DestroyLogicalPartition(partition_name, kMapTimeout);
- }
- LOG(ERROR) << "Unknown device mapper state: "
- << static_cast<std::underlying_type_t<DmDeviceState>>(state);
- return false;
-}
-
-static bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) {
- auto state = DeviceMapper::Instance().GetState(partition_name);
- if (state == DmDeviceState::INVALID) {
- return CreateLogicalPartition(GetSuperDevice(), 0 /* metadata slot */, partition_name,
- true /* force writable */, kMapTimeout, path);
- }
-
- if (state == DmDeviceState::ACTIVE) {
- return DeviceMapper::Instance().GetDmDevicePathByName(partition_name, path);
- }
- LOG(ERROR) << "Unknown device mapper state: "
- << static_cast<std::underlying_type_t<DmDeviceState>>(state);
- return false;
-}
-
Value* UnmapPartitionFn(const char* name, State* state,
const std::vector<std::unique_ptr<Expr>>& argv) {
auto args = ReadStringArgs(name, state, argv, { "name" });
if (args.empty()) return StringValue("");
- return UnmapPartitionOnDeviceMapper(args[0]) ? StringValue("t") : StringValue("");
+ auto updater_runtime = state->updater->GetRuntime();
+ return updater_runtime->UnmapPartitionOnDeviceMapper(args[0]) ? StringValue("t")
+ : StringValue("");
}
Value* MapPartitionFn(const char* name, State* state,
@@ -131,207 +79,12 @@ Value* MapPartitionFn(const char* name, State* state,
if (args.empty()) return StringValue("");
std::string path;
- bool result = MapPartitionOnDeviceMapper(args[0], &path);
+ auto updater_runtime = state->updater->GetRuntime();
+ bool result = updater_runtime->MapPartitionOnDeviceMapper(args[0], &path);
return result ? StringValue(path) : StringValue("");
}
-namespace { // Ops
-
-struct OpParameters {
- std::vector<std::string> tokens;
- MetadataBuilder* builder;
-
- bool ExpectArgSize(size_t size) const {
- CHECK(!tokens.empty());
- auto actual = tokens.size() - 1;
- if (actual != size) {
- LOG(ERROR) << "Op " << op() << " expects " << size << " args, got " << actual;
- return false;
- }
- return true;
- }
- const std::string& op() const {
- CHECK(!tokens.empty());
- return tokens[0];
- }
- const std::string& arg(size_t pos) const {
- CHECK_LE(pos + 1, tokens.size());
- return tokens[pos + 1];
- }
- std::optional<uint64_t> uint_arg(size_t pos, const std::string& name) const {
- auto str = arg(pos);
- uint64_t ret;
- if (!ParseUint(str, &ret)) {
- LOG(ERROR) << "Op " << op() << " expects uint64 for argument " << name << ", got " << str;
- return std::nullopt;
- }
- return ret;
- }
-};
-
-using OpFunction = std::function<bool(const OpParameters&)>;
-using OpMap = std::map<std::string, OpFunction>;
-
-bool PerformOpResize(const OpParameters& params) {
- if (!params.ExpectArgSize(2)) return false;
- const auto& partition_name = params.arg(0);
- auto size = params.uint_arg(1, "size");
- if (!size.has_value()) return false;
-
- auto partition = params.builder->FindPartition(partition_name);
- if (partition == nullptr) {
- LOG(ERROR) << "Failed to find partition " << partition_name
- << " in dynamic partition metadata.";
- return false;
- }
- if (!UnmapPartitionOnDeviceMapper(partition_name)) {
- LOG(ERROR) << "Cannot unmap " << partition_name << " before resizing.";
- return false;
- }
- if (!params.builder->ResizePartition(partition, size.value())) {
- LOG(ERROR) << "Failed to resize partition " << partition_name << " to size " << *size << ".";
- return false;
- }
- return true;
-}
-
-bool PerformOpRemove(const OpParameters& params) {
- if (!params.ExpectArgSize(1)) return false;
- const auto& partition_name = params.arg(0);
-
- if (!UnmapPartitionOnDeviceMapper(partition_name)) {
- LOG(ERROR) << "Cannot unmap " << partition_name << " before removing.";
- return false;
- }
- params.builder->RemovePartition(partition_name);
- return true;
-}
-
-bool PerformOpAdd(const OpParameters& params) {
- if (!params.ExpectArgSize(2)) return false;
- const auto& partition_name = params.arg(0);
- const auto& group_name = params.arg(1);
-
- if (params.builder->AddPartition(partition_name, group_name, LP_PARTITION_ATTR_READONLY) ==
- nullptr) {
- LOG(ERROR) << "Failed to add partition " << partition_name << " to group " << group_name << ".";
- return false;
- }
- return true;
-}
-
-bool PerformOpMove(const OpParameters& params) {
- if (!params.ExpectArgSize(2)) return false;
- const auto& partition_name = params.arg(0);
- const auto& new_group = params.arg(1);
-
- auto partition = params.builder->FindPartition(partition_name);
- if (partition == nullptr) {
- LOG(ERROR) << "Cannot move partition " << partition_name << " to group " << new_group
- << " because it is not found.";
- return false;
- }
-
- auto old_group = partition->group_name();
- if (old_group != new_group) {
- if (!params.builder->ChangePartitionGroup(partition, new_group)) {
- LOG(ERROR) << "Cannot move partition " << partition_name << " from group " << old_group
- << " to group " << new_group << ".";
- return false;
- }
- }
- return true;
-}
-
-bool PerformOpAddGroup(const OpParameters& params) {
- if (!params.ExpectArgSize(2)) return false;
- const auto& group_name = params.arg(0);
- auto maximum_size = params.uint_arg(1, "maximum_size");
- if (!maximum_size.has_value()) return false;
-
- auto group = params.builder->FindGroup(group_name);
- if (group != nullptr) {
- LOG(ERROR) << "Cannot add group " << group_name << " because it already exists.";
- return false;
- }
-
- if (maximum_size.value() == 0) {
- LOG(WARNING) << "Adding group " << group_name << " with no size limits.";
- }
-
- if (!params.builder->AddGroup(group_name, maximum_size.value())) {
- LOG(ERROR) << "Failed to add group " << group_name << " with maximum size "
- << maximum_size.value() << ".";
- return false;
- }
- return true;
-}
-
-bool PerformOpResizeGroup(const OpParameters& params) {
- if (!params.ExpectArgSize(2)) return false;
- const auto& group_name = params.arg(0);
- auto new_size = params.uint_arg(1, "maximum_size");
- if (!new_size.has_value()) return false;
-
- auto group = params.builder->FindGroup(group_name);
- if (group == nullptr) {
- LOG(ERROR) << "Cannot resize group " << group_name << " because it is not found.";
- return false;
- }
-
- auto old_size = group->maximum_size();
- if (old_size != new_size.value()) {
- if (!params.builder->ChangeGroupSize(group_name, new_size.value())) {
- LOG(ERROR) << "Cannot resize group " << group_name << " from " << old_size << " to "
- << new_size.value() << ".";
- return false;
- }
- }
- return true;
-}
-
-std::vector<std::string> ListPartitionNamesInGroup(MetadataBuilder* builder,
- const std::string& group_name) {
- auto partitions = builder->ListPartitionsInGroup(group_name);
- std::vector<std::string> partition_names;
- std::transform(partitions.begin(), partitions.end(), std::back_inserter(partition_names),
- [](Partition* partition) { return partition->name(); });
- return partition_names;
-}
-
-bool PerformOpRemoveGroup(const OpParameters& params) {
- if (!params.ExpectArgSize(1)) return false;
- const auto& group_name = params.arg(0);
-
- auto partition_names = ListPartitionNamesInGroup(params.builder, group_name);
- if (!partition_names.empty()) {
- LOG(ERROR) << "Cannot remove group " << group_name << " because it still contains partitions ["
- << android::base::Join(partition_names, ", ") << "]";
- return false;
- }
- params.builder->RemoveGroupAndPartitions(group_name);
- return true;
-}
-
-bool PerformOpRemoveAllGroups(const OpParameters& params) {
- if (!params.ExpectArgSize(0)) return false;
-
- auto group_names = params.builder->ListGroups();
- for (const auto& group_name : group_names) {
- auto partition_names = ListPartitionNamesInGroup(params.builder, group_name);
- for (const auto& partition_name : partition_names) {
- if (!UnmapPartitionOnDeviceMapper(partition_name)) {
- LOG(ERROR) << "Cannot unmap " << partition_name << " before removing group " << group_name
- << ".";
- return false;
- }
- }
- params.builder->RemoveGroupAndPartitions(group_name);
- }
- return true;
-}
-
-} // namespace
+static constexpr char kMetadataUpdatedMarker[] = "/dynamic_partition_metadata.UPDATED";
Value* UpdateDynamicPartitionsFn(const char* name, State* state,
const std::vector<std::unique_ptr<Expr>>& argv) {
@@ -367,56 +120,8 @@ Value* UpdateDynamicPartitionsFn(const char* name, State* state,
}
}
- auto super_device = GetSuperDevice();
- auto builder = MetadataBuilder::New(PartitionOpener(), super_device, 0);
- if (builder == nullptr) {
- LOG(ERROR) << "Failed to load dynamic partition metadata.";
- return StringValue("");
- }
-
- static const OpMap op_map{
- // clang-format off
- {"resize", PerformOpResize},
- {"remove", PerformOpRemove},
- {"add", PerformOpAdd},
- {"move", PerformOpMove},
- {"add_group", PerformOpAddGroup},
- {"resize_group", PerformOpResizeGroup},
- {"remove_group", PerformOpRemoveGroup},
- {"remove_all_groups", PerformOpRemoveAllGroups},
- // clang-format on
- };
-
- std::vector<std::string> lines = android::base::Split(op_list_value->data, "\n");
- for (const auto& line : lines) {
- auto comment_idx = line.find('#');
- auto op_and_args = comment_idx == std::string::npos ? line : line.substr(0, comment_idx);
- op_and_args = android::base::Trim(op_and_args);
- if (op_and_args.empty()) continue;
-
- auto tokens = android::base::Split(op_and_args, " ");
- const auto& op = tokens[0];
- auto it = op_map.find(op);
- if (it == op_map.end()) {
- LOG(ERROR) << "Unknown operation in op_list: " << op;
- return StringValue("");
- }
- OpParameters params;
- params.tokens = tokens;
- params.builder = builder.get();
- if (!it->second(params)) {
- return StringValue("");
- }
- }
-
- auto metadata = builder->Export();
- if (metadata == nullptr) {
- LOG(ERROR) << "Failed to export metadata.";
- return StringValue("");
- }
-
- if (!UpdatePartitionTable(super_device, *metadata, 0)) {
- LOG(ERROR) << "Failed to write metadata.";
+ auto updater_runtime = state->updater->GetRuntime();
+ if (!updater_runtime->UpdateDynamicPartitions(op_list_value->data)) {
return StringValue("");
}
diff --git a/updater/include/updater/build_info.h b/updater/include/updater/build_info.h
new file mode 100644
index 000000000..0073bfa4a
--- /dev/null
+++ b/updater/include/updater/build_info.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <list>
+#include <map>
+#include <string>
+#include <string_view>
+
+#include <android-base/file.h>
+
+// This class serves as the aggregation of the fake block device information during update
+// simulation on host. In specific, it has the name of the block device, its mount point, and the
+// path to the temporary file that fakes this block device.
+class FakeBlockDevice {
+ public:
+ FakeBlockDevice(std::string block_device, std::string mount_point, std::string temp_file_path)
+ : blockdev_name(std::move(block_device)),
+ mount_point(std::move(mount_point)),
+ mounted_file_path(std::move(temp_file_path)) {}
+
+ std::string blockdev_name;
+ std::string mount_point;
+ std::string mounted_file_path; // path to the temp file that mocks the block device
+};
+
+// This class stores the information of the source build. For example, it creates and maintains
+// the temporary files to simulate the block devices on host. Therefore, the simulator runtime can
+// query the information and run the update on host.
+class BuildInfo {
+ public:
+ BuildInfo(const std::string_view work_dir, bool keep_images)
+ : work_dir_(work_dir), keep_images_(keep_images) {}
+ // Returns the value of the build properties.
+ std::string GetProperty(const std::string_view key, const std::string_view default_value) const;
+ // Returns the path to the mock block device.
+ std::string FindBlockDeviceName(const std::string_view name) const;
+ // Parses the given target-file, initializes the build properties and extracts the images.
+ bool ParseTargetFile(const std::string_view target_file_path, bool extracted_input);
+
+ std::string GetOemSettings() const {
+ return oem_settings_;
+ }
+ void SetOemSettings(const std::string_view oem_settings) {
+ oem_settings_ = oem_settings;
+ }
+
+ private:
+ // A map to store the system properties during simulation.
+ std::map<std::string, std::string, std::less<>> build_props_;
+ // A file that contains the oem properties.
+ std::string oem_settings_;
+ // A map from the blockdev_name to the FakeBlockDevice object, which contains the path to the
+ // temporary file.
+ std::map<std::string, FakeBlockDevice, std::less<>> blockdev_map_;
+
+ std::list<TemporaryFile> temp_files_;
+ std::string work_dir_; // A temporary directory to store the extracted image files
+ bool keep_images_;
+};
diff --git a/updater/include/updater/install.h b/updater/include/updater/install.h
index 8d6ca4728..9fe203149 100644
--- a/updater/include/updater/install.h
+++ b/updater/include/updater/install.h
@@ -14,15 +14,6 @@
* limitations under the License.
*/
-#ifndef _UPDATER_INSTALL_H_
-#define _UPDATER_INSTALL_H_
-
-struct State;
+#pragma once
void RegisterInstallFunctions();
-
-// uiPrintf function prints msg to screen as well as logs
-void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...)
- __attribute__((__format__(printf, 2, 3)));
-
-#endif
diff --git a/updater/include/updater/simulator_runtime.h b/updater/include/updater/simulator_runtime.h
new file mode 100644
index 000000000..9f7847b4f
--- /dev/null
+++ b/updater/include/updater/simulator_runtime.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "edify/updater_runtime_interface.h"
+#include "updater/build_info.h"
+
+class SimulatorRuntime : public UpdaterRuntimeInterface {
+ public:
+ explicit SimulatorRuntime(BuildInfo* source) : source_(source) {}
+
+ bool IsSimulator() const override {
+ return true;
+ }
+
+ std::string GetProperty(const std::string_view key,
+ const std::string_view default_value) const override;
+
+ int Mount(const std::string_view location, const std::string_view mount_point,
+ const std::string_view fs_type, const std::string_view mount_options) override;
+ bool IsMounted(const std::string_view mount_point) const override;
+ std::pair<bool, int> Unmount(const std::string_view mount_point) override;
+
+ bool ReadFileToString(const std::string_view filename, std::string* content) const override;
+ bool WriteStringToFile(const std::string_view content,
+ const std::string_view filename) const override;
+
+ int WipeBlockDevice(const std::string_view filename, size_t len) const override;
+ int RunProgram(const std::vector<std::string>& args, bool is_vfork) const override;
+ int Tune2Fs(const std::vector<std::string>& args) const override;
+
+ bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) override;
+ bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) override;
+ bool UpdateDynamicPartitions(const std::string_view op_list_value) override;
+
+ private:
+ std::string FindBlockDeviceName(const std::string_view name) const override;
+
+ BuildInfo* source_;
+ std::map<std::string, std::string, std::less<>> mounted_partitions_;
+};
diff --git a/updater/include/updater/target_files.h b/updater/include/updater/target_files.h
new file mode 100644
index 000000000..860d47a35
--- /dev/null
+++ b/updater/include/updater/target_files.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include <android-base/file.h>
+#include <ziparchive/zip_archive.h>
+
+// This class represents the mount information for each line in a fstab file.
+class FstabInfo {
+ public:
+ FstabInfo(std::string blockdev_name, std::string mount_point, std::string fs_type)
+ : blockdev_name(std::move(blockdev_name)),
+ mount_point(std::move(mount_point)),
+ fs_type(std::move(fs_type)) {}
+
+ std::string blockdev_name;
+ std::string mount_point;
+ std::string fs_type;
+};
+
+// This class parses a target file from a zip file or an extracted directory. It also provides the
+// function to read the its content for simulation.
+class TargetFile {
+ public:
+ TargetFile(std::string path, bool extracted_input)
+ : path_(std::move(path)), extracted_input_(extracted_input) {}
+
+ // Opens the input target file (or extracted directory) and parses the misc_info.txt.
+ bool Open();
+ // Parses the build properties in all possible locations and save them in |props_map|
+ bool GetBuildProps(std::map<std::string, std::string, std::less<>>* props_map) const;
+ // Parses the fstab and save the information about each partition to mount into |fstab_info_list|.
+ bool ParseFstabInfo(std::vector<FstabInfo>* fstab_info_list) const;
+ // Returns true if the given entry exists in the target file.
+ bool EntryExists(const std::string_view name) const;
+ // Extracts the image file |entry_name|. Returns true on success.
+ bool ExtractImage(const std::string_view entry_name, const FstabInfo& fstab_info,
+ const std::string_view work_dir, TemporaryFile* image_file) const;
+
+ private:
+ // Wrapper functions to read the entry from either the zipped target-file, or the extracted input
+ // directory.
+ bool ReadEntryToString(const std::string_view name, std::string* content) const;
+ bool ExtractEntryToTempFile(const std::string_view name, TemporaryFile* temp_file) const;
+
+ std::string path_; // Path to the zipped target-file or an extracted directory.
+ bool extracted_input_; // True if the target-file has been extracted.
+ ZipArchiveHandle handle_{ nullptr };
+
+ // The properties under META/misc_info.txt
+ std::map<std::string, std::string, std::less<>> misc_info_;
+};
diff --git a/updater/include/updater/updater.h b/updater/include/updater/updater.h
index f4a2fe874..8676b6038 100644
--- a/updater/include/updater/updater.h
+++ b/updater/include/updater/updater.h
@@ -14,22 +14,83 @@
* limitations under the License.
*/
-#ifndef _UPDATER_UPDATER_H_
-#define _UPDATER_UPDATER_H_
+#pragma once
+#include <stdint.h>
#include <stdio.h>
+
+#include <memory>
+#include <string>
+#include <string_view>
+
#include <ziparchive/zip_archive.h>
-typedef struct {
- FILE* cmd_pipe;
- ZipArchiveHandle package_zip;
- int version;
+#include "edify/expr.h"
+#include "edify/updater_interface.h"
+#include "otautil/error_code.h"
+#include "otautil/sysutil.h"
+
+class Updater : public UpdaterInterface {
+ public:
+ explicit Updater(std::unique_ptr<UpdaterRuntimeInterface> run_time)
+ : runtime_(std::move(run_time)) {}
+
+ ~Updater() override;
+
+ // Memory-maps the OTA package and opens it as a zip file. Also sets up the command pipe and
+ // UpdaterRuntime.
+ bool Init(int fd, const std::string_view package_filename, bool is_retry);
+
+ // Parses and evaluates the updater-script in the OTA package. Reports the error code if the
+ // evaluation fails.
+ bool RunUpdate();
+
+ // Writes the message to command pipe, adds a new line in the end.
+ void WriteToCommandPipe(const std::string_view message, bool flush = false) const override;
+
+ // Sends over the message to recovery to print it on the screen.
+ void UiPrint(const std::string_view message) const override;
+
+ std::string FindBlockDeviceName(const std::string_view name) const override;
+
+ UpdaterRuntimeInterface* GetRuntime() const override {
+ return runtime_.get();
+ }
+ ZipArchiveHandle GetPackageHandle() const override {
+ return package_handle_;
+ }
+ std::string GetResult() const override {
+ return result_;
+ }
+ uint8_t* GetMappedPackageAddress() const override {
+ return mapped_package_.addr;
+ }
+ size_t GetMappedPackageLength() const override {
+ return mapped_package_.length;
+ }
+
+ private:
+ friend class UpdaterTestBase;
+ friend class UpdaterTest;
+ // Where in the package we expect to find the edify script to execute.
+ // (Note it's "updateR-script", not the older "update-script".)
+ static constexpr const char* SCRIPT_NAME = "META-INF/com/google/android/updater-script";
+
+ // Reads the entry |name| in the zip archive and put the result in |content|.
+ bool ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name, std::string* content);
+
+ // Parses the error code embedded in state->errmsg; and reports the error code and cause code.
+ void ParseAndReportErrorCode(State* state);
+
+ std::unique_ptr<UpdaterRuntimeInterface> runtime_;
- uint8_t* package_zip_addr;
- size_t package_zip_len;
-} UpdaterInfo;
+ MemMapping mapped_package_;
+ ZipArchiveHandle package_handle_{ nullptr };
+ std::string updater_script_;
-struct selabel_handle;
-extern struct selabel_handle *sehandle;
+ bool is_retry_{ false };
+ std::unique_ptr<FILE, decltype(&fclose)> cmd_pipe_{ nullptr, fclose };
-#endif
+ std::string result_;
+ std::vector<std::string> skipped_functions_;
+};
diff --git a/updater/include/updater/updater_runtime.h b/updater/include/updater/updater_runtime.h
new file mode 100644
index 000000000..8fc066f6a
--- /dev/null
+++ b/updater/include/updater/updater_runtime.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "edify/updater_runtime_interface.h"
+
+struct selabel_handle;
+
+class UpdaterRuntime : public UpdaterRuntimeInterface {
+ public:
+ explicit UpdaterRuntime(struct selabel_handle* sehandle) : sehandle_(sehandle) {}
+ ~UpdaterRuntime() override = default;
+
+ bool IsSimulator() const override {
+ return false;
+ }
+
+ std::string GetProperty(const std::string_view key,
+ const std::string_view default_value) const override;
+
+ std::string FindBlockDeviceName(const std::string_view name) const override;
+
+ int Mount(const std::string_view location, const std::string_view mount_point,
+ const std::string_view fs_type, const std::string_view mount_options) override;
+ bool IsMounted(const std::string_view mount_point) const override;
+ std::pair<bool, int> Unmount(const std::string_view mount_point) override;
+
+ bool ReadFileToString(const std::string_view filename, std::string* content) const override;
+ bool WriteStringToFile(const std::string_view content,
+ const std::string_view filename) const override;
+
+ int WipeBlockDevice(const std::string_view filename, size_t len) const override;
+ int RunProgram(const std::vector<std::string>& args, bool is_vfork) const override;
+ int Tune2Fs(const std::vector<std::string>& args) const override;
+
+ bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) override;
+ bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) override;
+ bool UpdateDynamicPartitions(const std::string_view op_list_value) override;
+
+ private:
+ struct selabel_handle* sehandle_{ nullptr };
+};
diff --git a/updater/install.cpp b/updater/install.cpp
index 20a204a83..62ff87e76 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -53,45 +53,30 @@
#include <openssl/sha.h>
#include <selinux/label.h>
#include <selinux/selinux.h>
-#include <tune2fs.h>
#include <ziparchive/zip_archive.h>
#include "edify/expr.h"
+#include "edify/updater_interface.h"
+#include "edify/updater_runtime_interface.h"
#include "otautil/dirutil.h"
#include "otautil/error_code.h"
-#include "otautil/mounts.h"
#include "otautil/print_sha1.h"
#include "otautil/sysutil.h"
-#include "updater/updater.h"
-// Send over the buffer to recovery though the command pipe.
-static void uiPrint(State* state, const std::string& buffer) {
- UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
+#ifndef __ANDROID__
+#include <cutils/memory.h> // for strlcpy
+#endif
- // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "".
- // So skip sending empty strings to UI.
- std::vector<std::string> lines = android::base::Split(buffer, "\n");
- for (auto& line : lines) {
- if (!line.empty()) {
- fprintf(ui->cmd_pipe, "ui_print %s\n", line.c_str());
- }
+static bool UpdateBlockDeviceNameForPartition(UpdaterInterface* updater, Partition* partition) {
+ CHECK(updater);
+ std::string name = updater->FindBlockDeviceName(partition->name);
+ if (name.empty()) {
+ LOG(ERROR) << "Failed to find the block device " << partition->name;
+ return false;
}
- // On the updater side, we need to dump the contents to stderr (which has
- // been redirected to the log file). Because the recovery will only print
- // the contents to screen when processing pipe command ui_print.
- LOG(INFO) << buffer;
-}
-
-void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...) {
- std::string error_msg;
-
- va_list ap;
- va_start(ap, format);
- android::base::StringAppendV(&error_msg, format, ap);
- va_end(ap);
-
- uiPrint(state, error_msg);
+ partition->name = std::move(name);
+ return true;
}
// This is the updater side handler for ui_print() in edify script. Contents will be sent over to
@@ -103,7 +88,7 @@ Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_p
}
std::string buffer = android::base::Join(args, "");
- uiPrint(state, buffer);
+ state->updater->UiPrint(buffer);
return StringValue(buffer);
}
@@ -127,16 +112,22 @@ Value* PackageExtractFileFn(const char* name, State* state,
argv.size());
}
const std::string& zip_path = args[0];
- const std::string& dest_path = args[1];
+ std::string dest_path = args[1];
- ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
- ZipString zip_string_path(zip_path.c_str());
+ ZipArchiveHandle za = state->updater->GetPackageHandle();
ZipEntry entry;
- if (FindEntry(za, zip_string_path, &entry) != 0) {
+ if (FindEntry(za, zip_path, &entry) != 0) {
LOG(ERROR) << name << ": no " << zip_path << " in package";
return StringValue("");
}
+ // Update the destination of package_extract_file if it's a block device. During simulation the
+ // destination will map to a fake file.
+ if (std::string block_device_name = state->updater->FindBlockDeviceName(dest_path);
+ !block_device_name.empty()) {
+ dest_path = block_device_name;
+ }
+
android::base::unique_fd fd(TEMP_FAILURE_RETRY(
open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)));
if (fd == -1) {
@@ -173,10 +164,9 @@ Value* PackageExtractFileFn(const char* name, State* state,
}
const std::string& zip_path = args[0];
- ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
- ZipString zip_string_path(zip_path.c_str());
+ ZipArchiveHandle za = state->updater->GetPackageHandle();
ZipEntry entry;
- if (FindEntry(za, zip_string_path, &entry) != 0) {
+ if (FindEntry(za, zip_path, &entry) != 0) {
return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name,
zip_path.c_str());
}
@@ -229,6 +219,11 @@ Value* PatchPartitionCheckFn(const char* name, State* state,
args[1].c_str(), err.c_str());
}
+ if (!UpdateBlockDeviceNameForPartition(state->updater, &source) ||
+ !UpdateBlockDeviceNameForPartition(state->updater, &target)) {
+ return StringValue("");
+ }
+
bool result = PatchPartitionCheck(target, source);
return StringValue(result ? "t" : "");
}
@@ -270,7 +265,12 @@ Value* PatchPartitionFn(const char* name, State* state,
return ErrorAbort(state, kArgsParsingFailure, "%s(): Invalid patch arg", name);
}
- bool result = PatchPartition(target, source, *values[0], nullptr);
+ if (!UpdateBlockDeviceNameForPartition(state->updater, &source) ||
+ !UpdateBlockDeviceNameForPartition(state->updater, &target)) {
+ return StringValue("");
+ }
+
+ bool result = PatchPartition(target, source, *values[0], nullptr, true);
return StringValue(result ? "t" : "");
}
@@ -313,26 +313,11 @@ Value* MountFn(const char* name, State* state, const std::vector<std::unique_ptr
name);
}
- {
- char* secontext = nullptr;
-
- if (sehandle) {
- selabel_lookup(sehandle, &secontext, mount_point.c_str(), 0755);
- setfscreatecon(secontext);
- }
-
- mkdir(mount_point.c_str(), 0755);
-
- if (secontext) {
- freecon(secontext);
- setfscreatecon(nullptr);
- }
- }
-
- if (mount(location.c_str(), mount_point.c_str(), fs_type.c_str(),
- MS_NOATIME | MS_NODEV | MS_NODIRATIME, mount_options.c_str()) < 0) {
- uiPrintf(state, "%s: Failed to mount %s at %s: %s", name, location.c_str(), mount_point.c_str(),
- strerror(errno));
+ auto updater = state->updater;
+ if (updater->GetRuntime()->Mount(location, mount_point, fs_type, mount_options) != 0) {
+ updater->UiPrint(android::base::StringPrintf("%s: Failed to mount %s at %s: %s", name,
+ location.c_str(), mount_point.c_str(),
+ strerror(errno)));
return StringValue("");
}
@@ -355,9 +340,8 @@ Value* IsMountedFn(const char* name, State* state, const std::vector<std::unique
"mount_point argument to unmount() can't be empty");
}
- scan_mounted_volumes();
- MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str());
- if (vol == nullptr) {
+ auto updater_runtime = state->updater->GetRuntime();
+ if (!updater_runtime->IsMounted(mount_point)) {
return StringValue("");
}
@@ -378,39 +362,20 @@ Value* UnmountFn(const char* name, State* state, const std::vector<std::unique_p
"mount_point argument to unmount() can't be empty");
}
- scan_mounted_volumes();
- MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str());
- if (vol == nullptr) {
- uiPrintf(state, "Failed to unmount %s: No such volume", mount_point.c_str());
+ auto updater = state->updater;
+ auto [mounted, result] = updater->GetRuntime()->Unmount(mount_point);
+ if (!mounted) {
+ updater->UiPrint(
+ android::base::StringPrintf("Failed to unmount %s: No such volume", mount_point.c_str()));
return nullptr;
- } else {
- int ret = unmount_mounted_volume(vol);
- if (ret != 0) {
- uiPrintf(state, "Failed to unmount %s: %s", mount_point.c_str(), strerror(errno));
- }
+ } else if (result != 0) {
+ updater->UiPrint(android::base::StringPrintf("Failed to unmount %s: %s", mount_point.c_str(),
+ strerror(errno)));
}
return StringValue(mount_point);
}
-static int exec_cmd(const std::vector<std::string>& args) {
- CHECK(!args.empty());
- auto argv = StringVectorToNullTerminatedArray(args);
-
- pid_t child;
- if ((child = vfork()) == 0) {
- execv(argv[0], argv.data());
- _exit(EXIT_FAILURE);
- }
-
- int status;
- waitpid(child, &status, 0);
- if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
- LOG(ERROR) << args[0] << " failed with status " << WEXITSTATUS(status);
- }
- return WEXITSTATUS(status);
-}
-
// format(fs_type, partition_type, location, fs_size, mount_point)
//
// fs_type="ext4" partition_type="EMMC" location=device fs_size=<bytes> mount_point=<location>
@@ -455,6 +420,7 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt
fs_size.c_str());
}
+ auto updater_runtime = state->updater->GetRuntime();
if (fs_type == "ext4") {
std::vector<std::string> mke2fs_args = {
"/system/bin/mke2fs", "-t", "ext4", "-b", "4096", location
@@ -463,12 +429,13 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt
mke2fs_args.push_back(std::to_string(size / 4096LL));
}
- if (auto status = exec_cmd(mke2fs_args); status != 0) {
+ if (auto status = updater_runtime->RunProgram(mke2fs_args, true); status != 0) {
LOG(ERROR) << name << ": mke2fs failed (" << status << ") on " << location;
return StringValue("");
}
- if (auto status = exec_cmd({ "/system/bin/e2fsdroid", "-e", "-a", mount_point, location });
+ if (auto status = updater_runtime->RunProgram(
+ { "/system/bin/e2fsdroid", "-e", "-a", mount_point, location }, true);
status != 0) {
LOG(ERROR) << name << ": e2fsdroid failed (" << status << ") on " << location;
return StringValue("");
@@ -487,12 +454,13 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt
if (size >= 512) {
f2fs_args.push_back(std::to_string(size / 512));
}
- if (auto status = exec_cmd(f2fs_args); status != 0) {
+ if (auto status = updater_runtime->RunProgram(f2fs_args, true); status != 0) {
LOG(ERROR) << name << ": make_f2fs failed (" << status << ") on " << location;
return StringValue("");
}
- if (auto status = exec_cmd({ "/system/bin/sload_f2fs", "-t", mount_point, location });
+ if (auto status = updater_runtime->RunProgram(
+ { "/system/bin/sload_f2fs", "-t", mount_point, location }, true);
status != 0) {
LOG(ERROR) << name << ": sload_f2fs failed (" << status << ") on " << location;
return StringValue("");
@@ -531,8 +499,7 @@ Value* ShowProgressFn(const char* name, State* state,
sec_str.c_str());
}
- UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
- fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec);
+ state->updater->WriteToCommandPipe(android::base::StringPrintf("progress %f %d", frac, sec));
return StringValue(frac_str);
}
@@ -555,8 +522,7 @@ Value* SetProgressFn(const char* name, State* state,
frac_str.c_str());
}
- UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
- fprintf(ui->cmd_pipe, "set_progress %f\n", frac);
+ state->updater->WriteToCommandPipe(android::base::StringPrintf("set_progress %f", frac));
return StringValue(frac_str);
}
@@ -569,7 +535,9 @@ Value* GetPropFn(const char* name, State* state, const std::vector<std::unique_p
if (!Evaluate(state, argv[0], &key)) {
return nullptr;
}
- std::string value = android::base::GetProperty(key, "");
+
+ auto updater_runtime = state->updater->GetRuntime();
+ std::string value = updater_runtime->GetProperty(key, "");
return StringValue(value);
}
@@ -594,7 +562,8 @@ Value* FileGetPropFn(const char* name, State* state,
const std::string& key = args[1];
std::string buffer;
- if (!android::base::ReadFileToString(filename, &buffer)) {
+ auto updater_runtime = state->updater->GetRuntime();
+ if (!updater_runtime->ReadFileToString(filename, &buffer)) {
ErrorAbort(state, kFreadFailure, "%s: failed to read %s", name, filename.c_str());
return nullptr;
}
@@ -655,7 +624,8 @@ Value* WipeCacheFn(const char* name, State* state, const std::vector<std::unique
return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name,
argv.size());
}
- fprintf(static_cast<UpdaterInfo*>(state->cookie)->cmd_pipe, "wipe_cache\n");
+
+ state->updater->WriteToCommandPipe("wipe_cache");
return StringValue("t");
}
@@ -669,26 +639,8 @@ Value* RunProgramFn(const char* name, State* state, const std::vector<std::uniqu
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
}
- auto exec_args = StringVectorToNullTerminatedArray(args);
- LOG(INFO) << "about to run program [" << exec_args[0] << "] with " << argv.size() << " args";
-
- pid_t child = fork();
- if (child == 0) {
- execv(exec_args[0], exec_args.data());
- PLOG(ERROR) << "run_program: execv failed";
- _exit(EXIT_FAILURE);
- }
-
- int status;
- waitpid(child, &status, 0);
- if (WIFEXITED(status)) {
- if (WEXITSTATUS(status) != 0) {
- LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status);
- }
- } else if (WIFSIGNALED(status)) {
- LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status);
- }
-
+ auto updater_runtime = state->updater->GetRuntime();
+ auto status = updater_runtime->RunProgram(args, false);
return StringValue(std::to_string(status));
}
@@ -706,7 +658,8 @@ Value* ReadFileFn(const char* name, State* state, const std::vector<std::unique_
const std::string& filename = args[0];
std::string contents;
- if (android::base::ReadFileToString(filename, &contents)) {
+ auto updater_runtime = state->updater->GetRuntime();
+ if (updater_runtime->ReadFileToString(filename, &contents)) {
return new Value(Value::Type::STRING, std::move(contents));
}
@@ -735,12 +688,12 @@ Value* WriteValueFn(const char* name, State* state, const std::vector<std::uniqu
}
const std::string& value = args[0];
- if (!android::base::WriteStringToFile(value, filename)) {
+ auto updater_runtime = state->updater->GetRuntime();
+ if (!updater_runtime->WriteStringToFile(value, filename)) {
PLOG(ERROR) << name << ": Failed to write to \"" << filename << "\"";
return StringValue("");
- } else {
- return StringValue("t");
}
+ return StringValue("t");
}
// Immediately reboot the device. Recovery is not finished normally,
@@ -778,7 +731,7 @@ Value* RebootNowFn(const char* name, State* state, const std::vector<std::unique
return StringValue("");
}
- reboot("reboot," + property);
+ Reboot(property);
sleep(5);
return ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name);
@@ -866,16 +819,10 @@ Value* WipeBlockDeviceFn(const char* name, State* state, const std::vector<std::
if (!android::base::ParseUint(len_str.c_str(), &len)) {
return nullptr;
}
- android::base::unique_fd fd(open(filename.c_str(), O_WRONLY));
- if (fd == -1) {
- PLOG(ERROR) << "Failed to open " << filename;
- return StringValue("");
- }
- // The wipe_block_device function in ext4_utils returns 0 on success and 1
- // for failure.
- int status = wipe_block_device(fd, len);
- return StringValue((status == 0) ? "t" : "");
+ auto updater_runtime = state->updater->GetRuntime();
+ int status = updater_runtime->WipeBlockDevice(filename, len);
+ return StringValue(status == 0 ? "t" : "");
}
Value* EnableRebootFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
@@ -883,8 +830,7 @@ Value* EnableRebootFn(const char* name, State* state, const std::vector<std::uni
return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name,
argv.size());
}
- UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
- fprintf(ui->cmd_pipe, "enable_reboot\n");
+ state->updater->WriteToCommandPipe("enable_reboot");
return StringValue("t");
}
@@ -900,10 +846,8 @@ Value* Tune2FsFn(const char* name, State* state, const std::vector<std::unique_p
// tune2fs expects the program name as its first arg.
args.insert(args.begin(), "tune2fs");
- auto tune2fs_args = StringVectorToNullTerminatedArray(args);
-
- // tune2fs changes the filesystem parameters on an ext2 filesystem; it returns 0 on success.
- if (auto result = tune2fs_main(tune2fs_args.size() - 1, tune2fs_args.data()); result != 0) {
+ auto updater_runtime = state->updater->GetRuntime();
+ if (auto result = updater_runtime->Tune2Fs(args); result != 0) {
return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d", name, result);
}
return StringValue("t");
diff --git a/otautil/mounts.cpp b/updater/mounts.cpp
index 951311bf3..943d35c75 100644
--- a/otautil/mounts.cpp
+++ b/updater/mounts.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "otautil/mounts.h"
+#include "mounts.h"
#include <errno.h>
#include <fcntl.h>
diff --git a/otautil/include/otautil/mounts.h b/updater/mounts.h
index 6786c8d2e..6786c8d2e 100644
--- a/otautil/include/otautil/mounts.h
+++ b/updater/mounts.h
diff --git a/updater/simulator_runtime.cpp b/updater/simulator_runtime.cpp
new file mode 100644
index 000000000..3ed7bf337
--- /dev/null
+++ b/updater/simulator_runtime.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "updater/simulator_runtime.h"
+
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <unordered_set>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/wipe.h>
+#include <selinux/label.h>
+
+#include "mounts.h"
+#include "otautil/sysutil.h"
+
+std::string SimulatorRuntime::GetProperty(const std::string_view key,
+ const std::string_view default_value) const {
+ return source_->GetProperty(key, default_value);
+}
+
+int SimulatorRuntime::Mount(const std::string_view location, const std::string_view mount_point,
+ const std::string_view /* fs_type */,
+ const std::string_view /* mount_options */) {
+ if (auto mounted_location = mounted_partitions_.find(mount_point);
+ mounted_location != mounted_partitions_.end() && mounted_location->second != location) {
+ LOG(ERROR) << mount_point << " has been mounted at " << mounted_location->second;
+ return -1;
+ }
+
+ mounted_partitions_.emplace(mount_point, location);
+ return 0;
+}
+
+bool SimulatorRuntime::IsMounted(const std::string_view mount_point) const {
+ return mounted_partitions_.find(mount_point) != mounted_partitions_.end();
+}
+
+std::pair<bool, int> SimulatorRuntime::Unmount(const std::string_view mount_point) {
+ if (!IsMounted(mount_point)) {
+ return { false, -1 };
+ }
+
+ mounted_partitions_.erase(std::string(mount_point));
+ return { true, 0 };
+}
+
+std::string SimulatorRuntime::FindBlockDeviceName(const std::string_view name) const {
+ return source_->FindBlockDeviceName(name);
+}
+
+// TODO(xunchang) implement the utility functions in simulator.
+int SimulatorRuntime::RunProgram(const std::vector<std::string>& args, bool /* is_vfork */) const {
+ LOG(INFO) << "Running program with args " << android::base::Join(args, " ");
+ return 0;
+}
+
+int SimulatorRuntime::Tune2Fs(const std::vector<std::string>& args) const {
+ LOG(INFO) << "Running Tune2Fs with args " << android::base::Join(args, " ");
+ return 0;
+}
+
+int SimulatorRuntime::WipeBlockDevice(const std::string_view filename, size_t /* len */) const {
+ LOG(INFO) << "SKip wiping block device " << filename;
+ return 0;
+}
+
+bool SimulatorRuntime::ReadFileToString(const std::string_view filename,
+ std::string* content) const {
+ if (android::base::EndsWith(filename, "oem.prop")) {
+ return android::base::ReadFileToString(source_->GetOemSettings(), content);
+ }
+
+ LOG(INFO) << "SKip reading filename " << filename;
+ return true;
+}
+
+bool SimulatorRuntime::WriteStringToFile(const std::string_view content,
+ const std::string_view filename) const {
+ LOG(INFO) << "SKip writing " << content.size() << " bytes to file " << filename;
+ return true;
+}
+
+bool SimulatorRuntime::MapPartitionOnDeviceMapper(const std::string& partition_name,
+ std::string* path) {
+ *path = partition_name;
+ return true;
+}
+
+bool SimulatorRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
+ LOG(INFO) << "Skip unmapping " << partition_name;
+ return true;
+}
+
+bool SimulatorRuntime::UpdateDynamicPartitions(const std::string_view op_list_value) {
+ const std::unordered_set<std::string> commands{
+ "resize", "remove", "add", "move",
+ "add_group", "resize_group", "remove_group", "remove_all_groups",
+ };
+
+ std::vector<std::string> lines = android::base::Split(std::string(op_list_value), "\n");
+ for (const auto& line : lines) {
+ if (line.empty() || line[0] == '#') continue;
+ auto tokens = android::base::Split(line, " ");
+ if (commands.find(tokens[0]) == commands.end()) {
+ LOG(ERROR) << "Unknown operation in op_list: " << line;
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/updater/target_files.cpp b/updater/target_files.cpp
new file mode 100644
index 000000000..919ec4e04
--- /dev/null
+++ b/updater/target_files.cpp
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "updater/target_files.h"
+
+#include <unistd.h>
+
+#include <algorithm>
+#include <filesystem>
+#include <memory>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <sparse/sparse.h>
+
+static bool SimgToImg(int input_fd, int output_fd) {
+ if (lseek64(input_fd, 0, SEEK_SET) == -1) {
+ PLOG(ERROR) << "Failed to lseek64 on the input sparse image";
+ return false;
+ }
+
+ if (lseek64(output_fd, 0, SEEK_SET) == -1) {
+ PLOG(ERROR) << "Failed to lseek64 on the output raw image";
+ return false;
+ }
+
+ std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)> s_file(
+ sparse_file_import(input_fd, true, false), sparse_file_destroy);
+ if (!s_file) {
+ LOG(ERROR) << "Failed to import the sparse image.";
+ return false;
+ }
+
+ if (sparse_file_write(s_file.get(), output_fd, false, false, false) < 0) {
+ PLOG(ERROR) << "Failed to output the raw image file.";
+ return false;
+ }
+
+ return true;
+}
+
+static bool ParsePropertyFile(const std::string_view prop_content,
+ std::map<std::string, std::string, std::less<>>* props_map) {
+ LOG(INFO) << "Start parsing build property\n";
+ std::vector<std::string> lines = android::base::Split(std::string(prop_content), "\n");
+ for (const auto& line : lines) {
+ if (line.empty() || line[0] == '#') continue;
+ auto pos = line.find('=');
+ if (pos == std::string::npos) continue;
+ std::string key = line.substr(0, pos);
+ std::string value = line.substr(pos + 1);
+ LOG(INFO) << key << ": " << value;
+ props_map->emplace(key, value);
+ }
+
+ return true;
+}
+
+static bool ParseFstab(const std::string_view fstab, std::vector<FstabInfo>* fstab_info_list) {
+ LOG(INFO) << "parsing fstab\n";
+ std::vector<std::string> lines = android::base::Split(std::string(fstab), "\n");
+ for (const auto& line : lines) {
+ if (line.empty() || line[0] == '#') continue;
+
+ // <block_device> <mount_point> <fs_type> <mount_flags> optional:<fs_mgr_flags>
+ std::vector<std::string> tokens = android::base::Split(line, " ");
+ tokens.erase(std::remove(tokens.begin(), tokens.end(), ""), tokens.end());
+ if (tokens.size() != 4 && tokens.size() != 5) {
+ LOG(ERROR) << "Unexpected token size: " << tokens.size() << std::endl
+ << "Error parsing fstab line: " << line;
+ return false;
+ }
+
+ const auto& blockdev = tokens[0];
+ const auto& mount_point = tokens[1];
+ const auto& fs_type = tokens[2];
+ if (!android::base::StartsWith(mount_point, "/")) {
+ LOG(WARNING) << "mount point '" << mount_point << "' does not start with '/'";
+ continue;
+ }
+
+ // The simulator only supports ext4 and emmc for now.
+ if (fs_type != "ext4" && fs_type != "emmc") {
+ LOG(WARNING) << "Unsupported fs_type in " << line;
+ continue;
+ }
+
+ fstab_info_list->emplace_back(blockdev, mount_point, fs_type);
+ }
+
+ return true;
+}
+
+bool TargetFile::EntryExists(const std::string_view name) const {
+ if (extracted_input_) {
+ std::string entry_path = path_ + "/" + std::string(name);
+ if (access(entry_path.c_str(), O_RDONLY) != 0) {
+ PLOG(WARNING) << "Failed to access " << entry_path;
+ return false;
+ }
+ return true;
+ }
+
+ CHECK(handle_);
+ ZipEntry img_entry;
+ return FindEntry(handle_, name, &img_entry) == 0;
+}
+
+bool TargetFile::ReadEntryToString(const std::string_view name, std::string* content) const {
+ if (extracted_input_) {
+ std::string entry_path = path_ + "/" + std::string(name);
+ return android::base::ReadFileToString(entry_path, content);
+ }
+
+ CHECK(handle_);
+ ZipEntry entry;
+ if (auto find_err = FindEntry(handle_, name, &entry); find_err != 0) {
+ LOG(ERROR) << "failed to find " << name << " in the package: " << ErrorCodeString(find_err);
+ return false;
+ }
+
+ if (entry.uncompressed_length == 0) {
+ content->clear();
+ return true;
+ }
+
+ content->resize(entry.uncompressed_length);
+ if (auto extract_err = ExtractToMemory(
+ handle_, &entry, reinterpret_cast<uint8_t*>(&content->at(0)), entry.uncompressed_length);
+ extract_err != 0) {
+ LOG(ERROR) << "failed to read " << name << " from package: " << ErrorCodeString(extract_err);
+ return false;
+ }
+
+ return true;
+}
+
+bool TargetFile::ExtractEntryToTempFile(const std::string_view name,
+ TemporaryFile* temp_file) const {
+ if (extracted_input_) {
+ std::string entry_path = path_ + "/" + std::string(name);
+ return std::filesystem::copy_file(entry_path, temp_file->path,
+ std::filesystem::copy_options::overwrite_existing);
+ }
+
+ CHECK(handle_);
+ ZipEntry entry;
+ if (auto find_err = FindEntry(handle_, name, &entry); find_err != 0) {
+ LOG(ERROR) << "failed to find " << name << " in the package: " << ErrorCodeString(find_err);
+ return false;
+ }
+
+ if (auto status = ExtractEntryToFile(handle_, &entry, temp_file->fd); status != 0) {
+ LOG(ERROR) << "Failed to extract zip entry " << name << " : " << ErrorCodeString(status);
+ return false;
+ }
+ return true;
+}
+
+bool TargetFile::Open() {
+ if (!extracted_input_) {
+ if (auto ret = OpenArchive(path_.c_str(), &handle_); ret != 0) {
+ LOG(ERROR) << "failed to open source target file " << path_ << ": " << ErrorCodeString(ret);
+ return false;
+ }
+ }
+
+ // Parse the misc info.
+ std::string misc_info_content;
+ if (!ReadEntryToString("META/misc_info.txt", &misc_info_content)) {
+ return false;
+ }
+ if (!ParsePropertyFile(misc_info_content, &misc_info_)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool TargetFile::GetBuildProps(std::map<std::string, std::string, std::less<>>* props_map) const {
+ props_map->clear();
+ // Parse the source zip to mock the system props and block devices. We try all the possible
+ // locations for build props.
+ constexpr std::string_view kPropLocations[] = {
+ "SYSTEM/build.prop",
+ "VENDOR/build.prop",
+ "PRODUCT/build.prop",
+ "SYSTEM_EXT/build.prop",
+ "SYSTEM/vendor/build.prop",
+ "SYSTEM/product/build.prop",
+ "SYSTEM/system_ext/build.prop",
+ "ODM/build.prop", // legacy
+ "ODM/etc/build.prop",
+ "VENDOR/odm/build.prop", // legacy
+ "VENDOR/odm/etc/build.prop",
+ };
+ for (const auto& name : kPropLocations) {
+ std::string build_prop_content;
+ if (!ReadEntryToString(name, &build_prop_content)) {
+ continue;
+ }
+ std::map<std::string, std::string, std::less<>> props;
+ if (!ParsePropertyFile(build_prop_content, &props)) {
+ LOG(ERROR) << "Failed to parse build prop in " << name;
+ return false;
+ }
+ for (const auto& [key, value] : props) {
+ if (auto it = props_map->find(key); it != props_map->end() && it->second != value) {
+ LOG(WARNING) << "Property " << key << " has different values in property files, we got "
+ << it->second << " and " << value;
+ }
+ props_map->emplace(key, value);
+ }
+ }
+
+ return true;
+}
+
+bool TargetFile::ExtractImage(const std::string_view entry_name, const FstabInfo& fstab_info,
+ const std::string_view work_dir, TemporaryFile* image_file) const {
+ if (!EntryExists(entry_name)) {
+ return false;
+ }
+
+ // We don't need extra work for 'emmc'; use the image file as the block device.
+ if (fstab_info.fs_type == "emmc" || misc_info_.find("extfs_sparse_flag") == misc_info_.end()) {
+ if (!ExtractEntryToTempFile(entry_name, image_file)) {
+ return false;
+ }
+ } else { // treated as ext4 sparse image
+ TemporaryFile sparse_image{ std::string(work_dir) };
+ if (!ExtractEntryToTempFile(entry_name, &sparse_image)) {
+ return false;
+ }
+
+ // Convert the sparse image to raw.
+ if (!SimgToImg(sparse_image.fd, image_file->fd)) {
+ LOG(ERROR) << "Failed to convert " << fstab_info.mount_point << " to raw.";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool TargetFile::ParseFstabInfo(std::vector<FstabInfo>* fstab_info_list) const {
+ // Parse the fstab file and extract the image files. The location of the fstab actually depends
+ // on some flags e.g. "no_recovery", "recovery_as_boot". Here we just try all possibilities.
+ constexpr std::string_view kRecoveryFstabLocations[] = {
+ "RECOVERY/RAMDISK/system/etc/recovery.fstab",
+ "RECOVERY/RAMDISK/etc/recovery.fstab",
+ "BOOT/RAMDISK/system/etc/recovery.fstab",
+ "BOOT/RAMDISK/etc/recovery.fstab",
+ };
+ std::string fstab_content;
+ for (const auto& name : kRecoveryFstabLocations) {
+ if (std::string content; ReadEntryToString(name, &content)) {
+ fstab_content = std::move(content);
+ break;
+ }
+ }
+ if (fstab_content.empty()) {
+ LOG(ERROR) << "Failed to parse the recovery fstab file";
+ return false;
+ }
+
+ // Extract the images and convert them to raw.
+ if (!ParseFstab(fstab_content, fstab_info_list)) {
+ LOG(ERROR) << "Failed to mount the block devices for source build.";
+ return false;
+ }
+
+ return true;
+}
diff --git a/updater/update_simulator_main.cpp b/updater/update_simulator_main.cpp
new file mode 100644
index 000000000..6c6989bac
--- /dev/null
+++ b/updater/update_simulator_main.cpp
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <getopt.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <string>
+#include <string_view>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include "edify/expr.h"
+#include "otautil/error_code.h"
+#include "otautil/paths.h"
+#include "updater/blockimg.h"
+#include "updater/build_info.h"
+#include "updater/dynamic_partitions.h"
+#include "updater/install.h"
+#include "updater/simulator_runtime.h"
+#include "updater/updater.h"
+
+using namespace std::string_literals;
+
+void Usage(std::string_view name) {
+ LOG(INFO) << "Usage: " << name << "[--oem_settings <oem_property_file>]"
+ << "[--skip_functions <skip_function_file>]"
+ << " --source <source_target_file>"
+ << " --ota_package <ota_package>";
+}
+
+Value* SimulatorPlaceHolderFn(const char* name, State* /* state */,
+ const std::vector<std::unique_ptr<Expr>>& /* argv */) {
+ LOG(INFO) << "Skip function " << name << " in host simulation";
+ return StringValue("t");
+}
+
+int main(int argc, char** argv) {
+ // Write the logs to stdout.
+ android::base::InitLogging(argv, &android::base::StderrLogger);
+
+ std::string oem_settings;
+ std::string skip_function_file;
+ std::string source_target_file;
+ std::string package_name;
+ std::string work_dir;
+ bool keep_images = false;
+
+ constexpr struct option OPTIONS[] = {
+ { "keep_images", no_argument, nullptr, 0 },
+ { "oem_settings", required_argument, nullptr, 0 },
+ { "ota_package", required_argument, nullptr, 0 },
+ { "skip_functions", required_argument, nullptr, 0 },
+ { "source", required_argument, nullptr, 0 },
+ { "work_dir", required_argument, nullptr, 0 },
+ { nullptr, 0, nullptr, 0 },
+ };
+
+ int arg;
+ int option_index;
+ while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
+ if (arg != 0) {
+ LOG(ERROR) << "Invalid command argument";
+ Usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+ auto option_name = OPTIONS[option_index].name;
+ // The same oem property file used during OTA generation. It's needed for file_getprop() to
+ // return the correct value for the source build.
+ if (option_name == "oem_settings"s) {
+ oem_settings = optarg;
+ } else if (option_name == "skip_functions"s) {
+ skip_function_file = optarg;
+ } else if (option_name == "source"s) {
+ source_target_file = optarg;
+ } else if (option_name == "ota_package"s) {
+ package_name = optarg;
+ } else if (option_name == "keep_images"s) {
+ keep_images = true;
+ } else if (option_name == "work_dir"s) {
+ work_dir = optarg;
+ } else {
+ Usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (source_target_file.empty() || package_name.empty()) {
+ Usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ // Configure edify's functions.
+ RegisterBuiltins();
+ RegisterInstallFunctions();
+ RegisterBlockImageFunctions();
+ RegisterDynamicPartitionsFunctions();
+
+ if (!skip_function_file.empty()) {
+ std::string content;
+ if (!android::base::ReadFileToString(skip_function_file, &content)) {
+ PLOG(ERROR) << "Failed to read " << skip_function_file;
+ return EXIT_FAILURE;
+ }
+
+ auto lines = android::base::Split(content, "\n");
+ for (const auto& line : lines) {
+ if (line.empty() || android::base::StartsWith(line, "#")) {
+ continue;
+ }
+ RegisterFunction(line, SimulatorPlaceHolderFn);
+ }
+ }
+
+ TemporaryFile temp_saved_source;
+ TemporaryFile temp_last_command;
+ TemporaryDir temp_stash_base;
+
+ Paths::Get().set_cache_temp_source(temp_saved_source.path);
+ Paths::Get().set_last_command_file(temp_last_command.path);
+ Paths::Get().set_stash_directory_base(temp_stash_base.path);
+
+ TemporaryFile cmd_pipe;
+ TemporaryDir source_temp_dir;
+ if (work_dir.empty()) {
+ work_dir = source_temp_dir.path;
+ }
+
+ BuildInfo source_build_info(work_dir, keep_images);
+ if (!source_build_info.ParseTargetFile(source_target_file, false)) {
+ LOG(ERROR) << "Failed to parse the target file " << source_target_file;
+ return EXIT_FAILURE;
+ }
+
+ if (!oem_settings.empty()) {
+ CHECK_EQ(0, access(oem_settings.c_str(), R_OK));
+ source_build_info.SetOemSettings(oem_settings);
+ }
+
+ Updater updater(std::make_unique<SimulatorRuntime>(&source_build_info));
+ if (!updater.Init(cmd_pipe.release(), package_name, false)) {
+ return EXIT_FAILURE;
+ }
+
+ if (!updater.RunUpdate()) {
+ return EXIT_FAILURE;
+ }
+
+ LOG(INFO) << "\nscript succeeded, result: " << updater.GetResult();
+
+ return 0;
+}
diff --git a/updater/updater.cpp b/updater/updater.cpp
index 7b5a3f938..8f4a6ede5 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -16,8 +16,6 @@
#include "updater/updater.h"
-#include <stdio.h>
-#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@@ -25,198 +23,162 @@
#include <android-base/logging.h>
#include <android-base/strings.h>
-#include <selinux/android.h>
-#include <selinux/label.h>
-#include <selinux/selinux.h>
-#include <ziparchive/zip_archive.h>
-
-#include "edify/expr.h"
-#include "otautil/dirutil.h"
-#include "otautil/error_code.h"
-#include "otautil/sysutil.h"
-#include "updater/blockimg.h"
-#include "updater/dynamic_partitions.h"
-#include "updater/install.h"
-
-// Generated by the makefile, this function defines the
-// RegisterDeviceExtensions() function, which calls all the
-// registration functions for device-specific extensions.
-#include "register.inc"
-
-// Where in the package we expect to find the edify script to execute.
-// (Note it's "updateR-script", not the older "update-script".)
-static constexpr const char* SCRIPT_NAME = "META-INF/com/google/android/updater-script";
-
-struct selabel_handle *sehandle;
-
-static void UpdaterLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */,
- const char* /* tag */, const char* /* file */, unsigned int /* line */,
- const char* message) {
- fprintf(stdout, "%s\n", message);
-}
-int main(int argc, char** argv) {
- // Various things log information to stdout or stderr more or less
- // at random (though we've tried to standardize on stdout). The
- // log file makes more sense if buffering is turned off so things
- // appear in the right order.
- setbuf(stdout, nullptr);
- setbuf(stderr, nullptr);
-
- // We don't have logcat yet under recovery. Update logs will always be written to stdout
- // (which is redirected to recovery.log).
- android::base::InitLogging(argv, &UpdaterLogger);
-
- if (argc != 4 && argc != 5) {
- LOG(ERROR) << "unexpected number of arguments: " << argc;
- return 1;
- }
+#include "edify/updater_runtime_interface.h"
- char* version = argv[1];
- if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') {
- // We support version 1, 2, or 3.
- LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1];
- return 2;
+Updater::~Updater() {
+ if (package_handle_) {
+ CloseArchive(package_handle_);
}
+}
+bool Updater::Init(int fd, const std::string_view package_filename, bool is_retry) {
// Set up the pipe for sending commands back to the parent process.
+ cmd_pipe_.reset(fdopen(fd, "wb"));
+ if (!cmd_pipe_) {
+ LOG(ERROR) << "Failed to open the command pipe";
+ return false;
+ }
- int fd = atoi(argv[2]);
- FILE* cmd_pipe = fdopen(fd, "wb");
- setlinebuf(cmd_pipe);
-
- // Extract the script from the package.
+ setlinebuf(cmd_pipe_.get());
- const char* package_filename = argv[3];
- MemMapping map;
- if (!map.MapFile(package_filename)) {
- LOG(ERROR) << "failed to map package " << argv[3];
- return 3;
+ if (!mapped_package_.MapFile(std::string(package_filename))) {
+ LOG(ERROR) << "failed to map package " << package_filename;
+ return false;
}
- ZipArchiveHandle za;
- int open_err = OpenArchiveFromMemory(map.addr, map.length, argv[3], &za);
- if (open_err != 0) {
- LOG(ERROR) << "failed to open package " << argv[3] << ": " << ErrorCodeString(open_err);
- CloseArchive(za);
- return 3;
+ if (int open_err = OpenArchiveFromMemory(mapped_package_.addr, mapped_package_.length,
+ std::string(package_filename).c_str(), &package_handle_);
+ open_err != 0) {
+ LOG(ERROR) << "failed to open package " << package_filename << ": "
+ << ErrorCodeString(open_err);
+ return false;
}
-
- ZipString script_name(SCRIPT_NAME);
- ZipEntry script_entry;
- int find_err = FindEntry(za, script_name, &script_entry);
- if (find_err != 0) {
- LOG(ERROR) << "failed to find " << SCRIPT_NAME << " in " << package_filename << ": "
- << ErrorCodeString(find_err);
- CloseArchive(za);
- return 4;
+ if (!ReadEntryToString(package_handle_, SCRIPT_NAME, &updater_script_)) {
+ return false;
}
- std::string script;
- script.resize(script_entry.uncompressed_length);
- int extract_err = ExtractToMemory(za, &script_entry, reinterpret_cast<uint8_t*>(&script[0]),
- script_entry.uncompressed_length);
- if (extract_err != 0) {
- LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err);
- CloseArchive(za);
- return 5;
- }
+ is_retry_ = is_retry;
- // Configure edify's functions.
+ return true;
+}
- RegisterBuiltins();
- RegisterInstallFunctions();
- RegisterBlockImageFunctions();
- RegisterDynamicPartitionsFunctions();
- RegisterDeviceExtensions();
+bool Updater::RunUpdate() {
+ CHECK(runtime_);
// Parse the script.
-
std::unique_ptr<Expr> root;
int error_count = 0;
- int error = ParseString(script, &root, &error_count);
+ int error = ParseString(updater_script_, &root, &error_count);
if (error != 0 || error_count > 0) {
LOG(ERROR) << error_count << " parse errors";
- CloseArchive(za);
- return 6;
- }
-
- sehandle = selinux_android_file_context_handle();
- selinux_android_set_sehandle(sehandle);
-
- if (!sehandle) {
- fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
+ return false;
}
// Evaluate the parsed script.
+ State state(updater_script_, this);
+ state.is_retry = is_retry_;
+
+ bool status = Evaluate(&state, root, &result_);
+ if (status) {
+ fprintf(cmd_pipe_.get(), "ui_print script succeeded: result was [%s]\n", result_.c_str());
+ // Even though the script doesn't abort, still log the cause code if result is empty.
+ if (result_.empty() && state.cause_code != kNoCause) {
+ fprintf(cmd_pipe_.get(), "log cause: %d\n", state.cause_code);
+ }
+ for (const auto& func : skipped_functions_) {
+ LOG(WARNING) << "Skipped executing function " << func;
+ }
+ return true;
+ }
- UpdaterInfo updater_info;
- updater_info.cmd_pipe = cmd_pipe;
- updater_info.package_zip = za;
- updater_info.version = atoi(version);
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
+ ParseAndReportErrorCode(&state);
+ return false;
+}
- State state(script, &updater_info);
+void Updater::WriteToCommandPipe(const std::string_view message, bool flush) const {
+ fprintf(cmd_pipe_.get(), "%s\n", std::string(message).c_str());
+ if (flush) {
+ fflush(cmd_pipe_.get());
+ }
+}
- if (argc == 5) {
- if (strcmp(argv[4], "retry") == 0) {
- state.is_retry = true;
- } else {
- printf("unexpected argument: %s", argv[4]);
+void Updater::UiPrint(const std::string_view message) const {
+ // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "".
+ // so skip sending empty strings to ui.
+ std::vector<std::string> lines = android::base::Split(std::string(message), "\n");
+ for (const auto& line : lines) {
+ if (!line.empty()) {
+ fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str());
}
}
- std::string result;
- bool status = Evaluate(&state, root, &result);
-
- if (!status) {
- if (state.errmsg.empty()) {
- LOG(ERROR) << "script aborted (no error message)";
- fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
- } else {
- LOG(ERROR) << "script aborted: " << state.errmsg;
- const std::vector<std::string> lines = android::base::Split(state.errmsg, "\n");
- for (const std::string& line : lines) {
- // Parse the error code in abort message.
- // Example: "E30: This package is for bullhead devices."
- if (!line.empty() && line[0] == 'E') {
- if (sscanf(line.c_str(), "E%d: ", &state.error_code) != 1) {
- LOG(ERROR) << "Failed to parse error code: [" << line << "]";
- }
+ // on the updater side, we need to dump the contents to stderr (which has
+ // been redirected to the log file). because the recovery will only print
+ // the contents to screen when processing pipe command ui_print.
+ LOG(INFO) << message;
+}
+
+std::string Updater::FindBlockDeviceName(const std::string_view name) const {
+ return runtime_->FindBlockDeviceName(name);
+}
+
+void Updater::ParseAndReportErrorCode(State* state) {
+ CHECK(state);
+ if (state->errmsg.empty()) {
+ LOG(ERROR) << "script aborted (no error message)";
+ fprintf(cmd_pipe_.get(), "ui_print script aborted (no error message)\n");
+ } else {
+ LOG(ERROR) << "script aborted: " << state->errmsg;
+ const std::vector<std::string> lines = android::base::Split(state->errmsg, "\n");
+ for (const std::string& line : lines) {
+ // Parse the error code in abort message.
+ // Example: "E30: This package is for bullhead devices."
+ if (!line.empty() && line[0] == 'E') {
+ if (sscanf(line.c_str(), "E%d: ", &state->error_code) != 1) {
+ LOG(ERROR) << "Failed to parse error code: [" << line << "]";
}
- fprintf(cmd_pipe, "ui_print %s\n", line.c_str());
}
+ fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str());
}
+ }
- // Installation has been aborted. Set the error code to kScriptExecutionFailure unless
- // a more specific code has been set in errmsg.
- if (state.error_code == kNoError) {
- state.error_code = kScriptExecutionFailure;
- }
- fprintf(cmd_pipe, "log error: %d\n", state.error_code);
- // Cause code should provide additional information about the abort.
- if (state.cause_code != kNoCause) {
- fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
- if (state.cause_code == kPatchApplicationFailure) {
- LOG(INFO) << "Patch application failed, retry update.";
- fprintf(cmd_pipe, "retry_update\n");
- } else if (state.cause_code == kEioFailure) {
- LOG(INFO) << "Update failed due to EIO, retry update.";
- fprintf(cmd_pipe, "retry_update\n");
- }
+ // Installation has been aborted. Set the error code to kScriptExecutionFailure unless
+ // a more specific code has been set in errmsg.
+ if (state->error_code == kNoError) {
+ state->error_code = kScriptExecutionFailure;
+ }
+ fprintf(cmd_pipe_.get(), "log error: %d\n", state->error_code);
+ // Cause code should provide additional information about the abort.
+ if (state->cause_code != kNoCause) {
+ fprintf(cmd_pipe_.get(), "log cause: %d\n", state->cause_code);
+ if (state->cause_code == kPatchApplicationFailure) {
+ LOG(INFO) << "Patch application failed, retry update.";
+ fprintf(cmd_pipe_.get(), "retry_update\n");
+ } else if (state->cause_code == kEioFailure) {
+ LOG(INFO) << "Update failed due to EIO, retry update.";
+ fprintf(cmd_pipe_.get(), "retry_update\n");
}
+ }
+}
- if (updater_info.package_zip) {
- CloseArchive(updater_info.package_zip);
- }
- return 7;
- } else {
- fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result.c_str());
+bool Updater::ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name,
+ std::string* content) {
+ ZipEntry entry;
+ int find_err = FindEntry(za, entry_name, &entry);
+ if (find_err != 0) {
+ LOG(ERROR) << "failed to find " << entry_name
+ << " in the package: " << ErrorCodeString(find_err);
+ return false;
}
- if (updater_info.package_zip) {
- CloseArchive(updater_info.package_zip);
+ content->resize(entry.uncompressed_length);
+ int extract_err = ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&content->at(0)),
+ entry.uncompressed_length);
+ if (extract_err != 0) {
+ LOG(ERROR) << "failed to read " << entry_name
+ << " from package: " << ErrorCodeString(extract_err);
+ return false;
}
- return 0;
+ return true;
}
diff --git a/updater/updater_main.cpp b/updater/updater_main.cpp
new file mode 100644
index 000000000..33d5b5b47
--- /dev/null
+++ b/updater/updater_main.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <openssl/crypto.h>
+#include <selinux/android.h>
+#include <selinux/label.h>
+#include <selinux/selinux.h>
+
+#include "edify/expr.h"
+#include "updater/blockimg.h"
+#include "updater/dynamic_partitions.h"
+#include "updater/install.h"
+#include "updater/updater.h"
+#include "updater/updater_runtime.h"
+
+// Generated by the makefile, this function defines the
+// RegisterDeviceExtensions() function, which calls all the
+// registration functions for device-specific extensions.
+#include "register.inc"
+
+static void UpdaterLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */,
+ const char* /* tag */, const char* /* file */, unsigned int /* line */,
+ const char* message) {
+ fprintf(stdout, "%s\n", message);
+}
+
+int main(int argc, char** argv) {
+ // Various things log information to stdout or stderr more or less
+ // at random (though we've tried to standardize on stdout). The
+ // log file makes more sense if buffering is turned off so things
+ // appear in the right order.
+ setbuf(stdout, nullptr);
+ setbuf(stderr, nullptr);
+
+ // We don't have logcat yet under recovery. Update logs will always be written to stdout
+ // (which is redirected to recovery.log).
+ android::base::InitLogging(argv, &UpdaterLogger);
+
+ // Run the libcrypto KAT(known answer tests) based self tests.
+ if (BORINGSSL_self_test() != 1) {
+ LOG(ERROR) << "Failed to run the boringssl self tests";
+ return EXIT_FAILURE;
+ }
+
+ if (argc != 4 && argc != 5) {
+ LOG(ERROR) << "unexpected number of arguments: " << argc;
+ return EXIT_FAILURE;
+ }
+
+ char* version = argv[1];
+ if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') {
+ // We support version 1, 2, or 3.
+ LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1];
+ return EXIT_FAILURE;
+ }
+
+ int fd;
+ if (!android::base::ParseInt(argv[2], &fd)) {
+ LOG(ERROR) << "Failed to parse fd in " << argv[2];
+ return EXIT_FAILURE;
+ }
+
+ std::string package_name = argv[3];
+
+ bool is_retry = false;
+ if (argc == 5) {
+ if (strcmp(argv[4], "retry") == 0) {
+ is_retry = true;
+ } else {
+ LOG(ERROR) << "unexpected argument: " << argv[4];
+ return EXIT_FAILURE;
+ }
+ }
+
+ // Configure edify's functions.
+ RegisterBuiltins();
+ RegisterInstallFunctions();
+ RegisterBlockImageFunctions();
+ RegisterDynamicPartitionsFunctions();
+ RegisterDeviceExtensions();
+
+ auto sehandle = selinux_android_file_context_handle();
+ selinux_android_set_sehandle(sehandle);
+
+ Updater updater(std::make_unique<UpdaterRuntime>(sehandle));
+ if (!updater.Init(fd, package_name, is_retry)) {
+ return EXIT_FAILURE;
+ }
+
+ if (!updater.RunUpdate()) {
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+} \ No newline at end of file
diff --git a/updater/updater_runtime.cpp b/updater/updater_runtime.cpp
new file mode 100644
index 000000000..c4222a56e
--- /dev/null
+++ b/updater/updater_runtime.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "updater/updater_runtime.h"
+
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/wipe.h>
+#include <selinux/label.h>
+#include <tune2fs.h>
+
+#include "mounts.h"
+#include "otautil/sysutil.h"
+
+std::string UpdaterRuntime::GetProperty(const std::string_view key,
+ const std::string_view default_value) const {
+ return android::base::GetProperty(std::string(key), std::string(default_value));
+}
+
+std::string UpdaterRuntime::FindBlockDeviceName(const std::string_view name) const {
+ return std::string(name);
+}
+
+int UpdaterRuntime::Mount(const std::string_view location, const std::string_view mount_point,
+ const std::string_view fs_type, const std::string_view mount_options) {
+ std::string mount_point_string(mount_point);
+ char* secontext = nullptr;
+ if (sehandle_) {
+ selabel_lookup(sehandle_, &secontext, mount_point_string.c_str(), 0755);
+ setfscreatecon(secontext);
+ }
+
+ mkdir(mount_point_string.c_str(), 0755);
+
+ if (secontext) {
+ freecon(secontext);
+ setfscreatecon(nullptr);
+ }
+
+ return mount(std::string(location).c_str(), mount_point_string.c_str(),
+ std::string(fs_type).c_str(), MS_NOATIME | MS_NODEV | MS_NODIRATIME,
+ std::string(mount_options).c_str());
+}
+
+bool UpdaterRuntime::IsMounted(const std::string_view mount_point) const {
+ scan_mounted_volumes();
+ MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str());
+ return vol != nullptr;
+}
+
+std::pair<bool, int> UpdaterRuntime::Unmount(const std::string_view mount_point) {
+ scan_mounted_volumes();
+ MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str());
+ if (vol == nullptr) {
+ return { false, -1 };
+ }
+
+ int ret = unmount_mounted_volume(vol);
+ return { true, ret };
+}
+
+bool UpdaterRuntime::ReadFileToString(const std::string_view filename, std::string* content) const {
+ return android::base::ReadFileToString(std::string(filename), content);
+}
+
+bool UpdaterRuntime::WriteStringToFile(const std::string_view content,
+ const std::string_view filename) const {
+ return android::base::WriteStringToFile(std::string(content), std::string(filename));
+}
+
+int UpdaterRuntime::WipeBlockDevice(const std::string_view filename, size_t len) const {
+ android::base::unique_fd fd(open(std::string(filename).c_str(), O_WRONLY));
+ if (fd == -1) {
+ PLOG(ERROR) << "Failed to open " << filename;
+ return false;
+ }
+ // The wipe_block_device function in ext4_utils returns 0 on success and 1 for failure.
+ return wipe_block_device(fd, len);
+}
+
+int UpdaterRuntime::RunProgram(const std::vector<std::string>& args, bool is_vfork) const {
+ CHECK(!args.empty());
+ auto argv = StringVectorToNullTerminatedArray(args);
+ LOG(INFO) << "about to run program [" << args[0] << "] with " << argv.size() << " args";
+
+ pid_t child = is_vfork ? vfork() : fork();
+ if (child == 0) {
+ execv(argv[0], argv.data());
+ PLOG(ERROR) << "run_program: execv failed";
+ _exit(EXIT_FAILURE);
+ }
+
+ int status;
+ waitpid(child, &status, 0);
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) != 0) {
+ LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status);
+ }
+ } else if (WIFSIGNALED(status)) {
+ LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status);
+ }
+
+ return status;
+}
+
+int UpdaterRuntime::Tune2Fs(const std::vector<std::string>& args) const {
+ auto tune2fs_args = StringVectorToNullTerminatedArray(args);
+ // tune2fs changes the filesystem parameters on an ext2 filesystem; it returns 0 on success.
+ return tune2fs_main(tune2fs_args.size() - 1, tune2fs_args.data());
+}
diff --git a/updater/updater_runtime_dynamic_partitions.cpp b/updater/updater_runtime_dynamic_partitions.cpp
new file mode 100644
index 000000000..be9250a81
--- /dev/null
+++ b/updater/updater_runtime_dynamic_partitions.cpp
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "updater/updater_runtime.h"
+
+#include <algorithm>
+#include <chrono>
+#include <iterator>
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <fs_mgr.h>
+#include <fs_mgr_dm_linear.h>
+#include <libdm/dm.h>
+#include <liblp/builder.h>
+
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using android::fs_mgr::CreateLogicalPartition;
+using android::fs_mgr::CreateLogicalPartitionParams;
+using android::fs_mgr::DestroyLogicalPartition;
+using android::fs_mgr::LpMetadata;
+using android::fs_mgr::MetadataBuilder;
+using android::fs_mgr::Partition;
+using android::fs_mgr::PartitionOpener;
+
+static constexpr std::chrono::milliseconds kMapTimeout{ 1000 };
+
+static std::string GetSuperDevice() {
+ return "/dev/block/by-name/" + fs_mgr_get_super_partition_name();
+}
+
+static bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
+ auto state = DeviceMapper::Instance().GetState(partition_name);
+ if (state == DmDeviceState::INVALID) {
+ return true;
+ }
+ if (state == DmDeviceState::ACTIVE) {
+ return DestroyLogicalPartition(partition_name);
+ }
+ LOG(ERROR) << "Unknown device mapper state: "
+ << static_cast<std::underlying_type_t<DmDeviceState>>(state);
+ return false;
+}
+
+bool UpdaterRuntime::MapPartitionOnDeviceMapper(const std::string& partition_name,
+ std::string* path) {
+ auto state = DeviceMapper::Instance().GetState(partition_name);
+ if (state == DmDeviceState::INVALID) {
+ CreateLogicalPartitionParams params = {
+ .block_device = GetSuperDevice(),
+ .metadata_slot = 0,
+ .partition_name = partition_name,
+ .force_writable = true,
+ .timeout_ms = kMapTimeout,
+ };
+ return CreateLogicalPartition(params, path);
+ }
+
+ if (state == DmDeviceState::ACTIVE) {
+ return DeviceMapper::Instance().GetDmDevicePathByName(partition_name, path);
+ }
+ LOG(ERROR) << "Unknown device mapper state: "
+ << static_cast<std::underlying_type_t<DmDeviceState>>(state);
+ return false;
+}
+
+bool UpdaterRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
+ return ::UnmapPartitionOnDeviceMapper(partition_name);
+}
+
+namespace { // Ops
+
+struct OpParameters {
+ std::vector<std::string> tokens;
+ MetadataBuilder* builder;
+
+ bool ExpectArgSize(size_t size) const {
+ CHECK(!tokens.empty());
+ auto actual = tokens.size() - 1;
+ if (actual != size) {
+ LOG(ERROR) << "Op " << op() << " expects " << size << " args, got " << actual;
+ return false;
+ }
+ return true;
+ }
+ const std::string& op() const {
+ CHECK(!tokens.empty());
+ return tokens[0];
+ }
+ const std::string& arg(size_t pos) const {
+ CHECK_LE(pos + 1, tokens.size());
+ return tokens[pos + 1];
+ }
+ std::optional<uint64_t> uint_arg(size_t pos, const std::string& name) const {
+ auto str = arg(pos);
+ uint64_t ret;
+ if (!android::base::ParseUint(str, &ret)) {
+ LOG(ERROR) << "Op " << op() << " expects uint64 for argument " << name << ", got " << str;
+ return std::nullopt;
+ }
+ return ret;
+ }
+};
+
+using OpFunction = std::function<bool(const OpParameters&)>;
+using OpMap = std::map<std::string, OpFunction>;
+
+bool PerformOpResize(const OpParameters& params) {
+ if (!params.ExpectArgSize(2)) return false;
+ const auto& partition_name = params.arg(0);
+ auto size = params.uint_arg(1, "size");
+ if (!size.has_value()) return false;
+
+ auto partition = params.builder->FindPartition(partition_name);
+ if (partition == nullptr) {
+ LOG(ERROR) << "Failed to find partition " << partition_name
+ << " in dynamic partition metadata.";
+ return false;
+ }
+ if (!UnmapPartitionOnDeviceMapper(partition_name)) {
+ LOG(ERROR) << "Cannot unmap " << partition_name << " before resizing.";
+ return false;
+ }
+ if (!params.builder->ResizePartition(partition, size.value())) {
+ LOG(ERROR) << "Failed to resize partition " << partition_name << " to size " << *size << ".";
+ return false;
+ }
+ return true;
+}
+
+bool PerformOpRemove(const OpParameters& params) {
+ if (!params.ExpectArgSize(1)) return false;
+ const auto& partition_name = params.arg(0);
+
+ if (!UnmapPartitionOnDeviceMapper(partition_name)) {
+ LOG(ERROR) << "Cannot unmap " << partition_name << " before removing.";
+ return false;
+ }
+ params.builder->RemovePartition(partition_name);
+ return true;
+}
+
+bool PerformOpAdd(const OpParameters& params) {
+ if (!params.ExpectArgSize(2)) return false;
+ const auto& partition_name = params.arg(0);
+ const auto& group_name = params.arg(1);
+
+ if (params.builder->AddPartition(partition_name, group_name, LP_PARTITION_ATTR_READONLY) ==
+ nullptr) {
+ LOG(ERROR) << "Failed to add partition " << partition_name << " to group " << group_name << ".";
+ return false;
+ }
+ return true;
+}
+
+bool PerformOpMove(const OpParameters& params) {
+ if (!params.ExpectArgSize(2)) return false;
+ const auto& partition_name = params.arg(0);
+ const auto& new_group = params.arg(1);
+
+ auto partition = params.builder->FindPartition(partition_name);
+ if (partition == nullptr) {
+ LOG(ERROR) << "Cannot move partition " << partition_name << " to group " << new_group
+ << " because it is not found.";
+ return false;
+ }
+
+ auto old_group = partition->group_name();
+ if (old_group != new_group) {
+ if (!params.builder->ChangePartitionGroup(partition, new_group)) {
+ LOG(ERROR) << "Cannot move partition " << partition_name << " from group " << old_group
+ << " to group " << new_group << ".";
+ return false;
+ }
+ }
+ return true;
+}
+
+bool PerformOpAddGroup(const OpParameters& params) {
+ if (!params.ExpectArgSize(2)) return false;
+ const auto& group_name = params.arg(0);
+ auto maximum_size = params.uint_arg(1, "maximum_size");
+ if (!maximum_size.has_value()) return false;
+
+ auto group = params.builder->FindGroup(group_name);
+ if (group != nullptr) {
+ LOG(ERROR) << "Cannot add group " << group_name << " because it already exists.";
+ return false;
+ }
+
+ if (maximum_size.value() == 0) {
+ LOG(WARNING) << "Adding group " << group_name << " with no size limits.";
+ }
+
+ if (!params.builder->AddGroup(group_name, maximum_size.value())) {
+ LOG(ERROR) << "Failed to add group " << group_name << " with maximum size "
+ << maximum_size.value() << ".";
+ return false;
+ }
+ return true;
+}
+
+bool PerformOpResizeGroup(const OpParameters& params) {
+ if (!params.ExpectArgSize(2)) return false;
+ const auto& group_name = params.arg(0);
+ auto new_size = params.uint_arg(1, "maximum_size");
+ if (!new_size.has_value()) return false;
+
+ auto group = params.builder->FindGroup(group_name);
+ if (group == nullptr) {
+ LOG(ERROR) << "Cannot resize group " << group_name << " because it is not found.";
+ return false;
+ }
+
+ auto old_size = group->maximum_size();
+ if (old_size != new_size.value()) {
+ if (!params.builder->ChangeGroupSize(group_name, new_size.value())) {
+ LOG(ERROR) << "Cannot resize group " << group_name << " from " << old_size << " to "
+ << new_size.value() << ".";
+ return false;
+ }
+ }
+ return true;
+}
+
+std::vector<std::string> ListPartitionNamesInGroup(MetadataBuilder* builder,
+ const std::string& group_name) {
+ auto partitions = builder->ListPartitionsInGroup(group_name);
+ std::vector<std::string> partition_names;
+ std::transform(partitions.begin(), partitions.end(), std::back_inserter(partition_names),
+ [](Partition* partition) { return partition->name(); });
+ return partition_names;
+}
+
+bool PerformOpRemoveGroup(const OpParameters& params) {
+ if (!params.ExpectArgSize(1)) return false;
+ const auto& group_name = params.arg(0);
+
+ auto partition_names = ListPartitionNamesInGroup(params.builder, group_name);
+ if (!partition_names.empty()) {
+ LOG(ERROR) << "Cannot remove group " << group_name << " because it still contains partitions ["
+ << android::base::Join(partition_names, ", ") << "]";
+ return false;
+ }
+ params.builder->RemoveGroupAndPartitions(group_name);
+ return true;
+}
+
+bool PerformOpRemoveAllGroups(const OpParameters& params) {
+ if (!params.ExpectArgSize(0)) return false;
+
+ auto group_names = params.builder->ListGroups();
+ for (const auto& group_name : group_names) {
+ auto partition_names = ListPartitionNamesInGroup(params.builder, group_name);
+ for (const auto& partition_name : partition_names) {
+ if (!UnmapPartitionOnDeviceMapper(partition_name)) {
+ LOG(ERROR) << "Cannot unmap " << partition_name << " before removing group " << group_name
+ << ".";
+ return false;
+ }
+ }
+ params.builder->RemoveGroupAndPartitions(group_name);
+ }
+ return true;
+}
+
+} // namespace
+
+bool UpdaterRuntime::UpdateDynamicPartitions(const std::string_view op_list_value) {
+ auto super_device = GetSuperDevice();
+ auto builder = MetadataBuilder::New(PartitionOpener(), super_device, 0);
+ if (builder == nullptr) {
+ LOG(ERROR) << "Failed to load dynamic partition metadata.";
+ return false;
+ }
+
+ static const OpMap op_map{
+ // clang-format off
+ {"resize", PerformOpResize},
+ {"remove", PerformOpRemove},
+ {"add", PerformOpAdd},
+ {"move", PerformOpMove},
+ {"add_group", PerformOpAddGroup},
+ {"resize_group", PerformOpResizeGroup},
+ {"remove_group", PerformOpRemoveGroup},
+ {"remove_all_groups", PerformOpRemoveAllGroups},
+ // clang-format on
+ };
+
+ std::vector<std::string> lines = android::base::Split(std::string(op_list_value), "\n");
+ for (const auto& line : lines) {
+ auto comment_idx = line.find('#');
+ auto op_and_args = comment_idx == std::string::npos ? line : line.substr(0, comment_idx);
+ op_and_args = android::base::Trim(op_and_args);
+ if (op_and_args.empty()) continue;
+
+ auto tokens = android::base::Split(op_and_args, " ");
+ const auto& op = tokens[0];
+ auto it = op_map.find(op);
+ if (it == op_map.end()) {
+ LOG(ERROR) << "Unknown operation in op_list: " << op;
+ return false;
+ }
+ OpParameters params;
+ params.tokens = tokens;
+ params.builder = builder.get();
+ if (!it->second(params)) {
+ return false;
+ }
+ }
+
+ auto metadata = builder->Export();
+ if (metadata == nullptr) {
+ LOG(ERROR) << "Failed to export metadata.";
+ return false;
+ }
+
+ if (!UpdatePartitionTable(super_device, *metadata, 0)) {
+ LOG(ERROR) << "Failed to write metadata.";
+ return false;
+ }
+
+ return true;
+}
diff --git a/updater_sample/Android.bp b/updater_sample/Android.bp
index 845e07b70..a014248b0 100644
--- a/updater_sample/Android.bp
+++ b/updater_sample/Android.bp
@@ -15,7 +15,6 @@
android_app {
name: "SystemUpdaterSample",
sdk_version: "system_current",
- privileged: true,
srcs: ["src/**/*.java"],
diff --git a/updater_sample/OWNERS b/updater_sample/OWNERS
deleted file mode 100644
index 5c1c3706c..000000000
--- a/updater_sample/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-zhaojiac@google.com
-zhomart@google.com
diff --git a/updater_sample/README.md b/updater_sample/README.md
index 2070ebc21..2e12a2fb9 100644
--- a/updater_sample/README.md
+++ b/updater_sample/README.md
@@ -191,6 +191,8 @@ privileged system app, so it's granted the required permissions to access
</privapp-permissions>
```
to `frameworks/base/data/etc/privapp-permissions-platform.xml`
+4. Add `privileged: true` to SystemUpdaterSample
+ [building rule](https://android.googlesource.com/platform/bootable/recovery/+/refs/heads/master/updater_sample/Android.bp).
5. Build sample app `make -j SystemUpdaterSample`.
6. Build Android `make -j`
7. [Flash the device](https://source.android.com/setup/build/running)
@@ -229,9 +231,9 @@ The commands are expected to be run from `$ANDROID_BUILD_TOP`.
1. Build `make -j SystemUpdaterSample` and `make -j SystemUpdaterSampleTests`.
2. Install app
- `adb install $OUT/system/priv-app/SystemUpdaterSample/SystemUpdaterSample.apk`
+ `adb install $OUT/system/app/SystemUpdaterSample/SystemUpdaterSample.apk`
3. Install tests
- `adb install $OUT/testcases/SystemUpdaterSampleTests/SystemUpdaterSampleTests.apk`
+ `adb install $OUT/testcases/SystemUpdaterSampleTests/arm64/SystemUpdaterSampleTests.apk`
4. Run tests
`adb shell am instrument -w com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner`
5. Run a test file