DO NOT MERGE - Skip pi-platform-release (PPRL.190505.001) in stage-aosp-master
Bug: 132622481
Change-Id: Ic07faa9aa968d11ae8f6b44d548d724fd194e047
diff --git a/.clang-format b/.clang-format
index 0e0f4d1..4a3bd2f 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,3 +1,33 @@
+# bootable/recovery project uses repohook to apply `clang-format` to the changed lines, with the
+# local style file in `.clang-format`. This will be triggered automatically with `repo upload`.
+# Alternatively, one can stage and format a change with `git clang-format` directly.
+#
+# $ git add <files>
+# $ git clang-format --style file
+#
+# Or to format a committed change.
+#
+# $ git clang-format --style file HEAD~1
+#
+# `--style file` will pick up the local style file in `.clang-format`. This can be configured as the
+# default behavior for bootable/recovery project.
+#
+# $ git config --local clangFormat.style file
+#
+# Note that `repo upload` calls the `clang-format` binary in Android repo (i.e.
+# `$ANDROID_BUILD_TOP/prebuilts/clang/host/linux-x86/clang-stable/bin/clang-format`), which might
+# give slightly different results from the one installed in host machine (e.g.
+# `/usr/bin/clang-format`). Specifying the file with `--binary` will ensure consistent results.
+#
+# $ git clang-format --binary \
+# /path/to/aosp-master/prebuilts/clang/host/linux-x86/clang-stable/bin/clang-format
+#
+# Or to do one-time setup to make it default.
+#
+# $ git config --local clangFormat.binary \
+# /path/to/aosp-master/prebuilts/clang/host/linux-x86/clang-stable/bin/clang-format
+#
+
BasedOnStyle: Google
AllowShortBlocksOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
diff --git a/Android.bp b/Android.bp
index f8c6a4b..0eb5fd9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,8 +1,191 @@
-subdirs = [
- "applypatch",
- "bootloader_message",
- "edify",
- "otafault",
- "otautil",
- "uncrypt",
-]
+// 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.
+
+cc_defaults {
+ name: "recovery_defaults",
+
+ cflags: [
+ "-D_FILE_OFFSET_BITS=64",
+
+ // Must be the same as RECOVERY_API_VERSION.
+ "-DRECOVERY_API_VERSION=3",
+
+ "-Wall",
+ "-Werror",
+ ],
+}
+
+cc_library_static {
+ name: "librecovery_fastboot",
+ recovery_available: true,
+ defaults: [
+ "recovery_defaults",
+ ],
+
+ srcs: [
+ "fastboot/fastboot.cpp",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libbootloader_message",
+ "libcutils",
+ "liblog",
+ "librecovery_ui",
+ ],
+
+ static_libs: [
+ "librecovery_ui_default",
+ ],
+}
+
+cc_defaults {
+ name: "librecovery_defaults",
+
+ defaults: [
+ "recovery_defaults",
+ ],
+
+ shared_libs: [
+ "android.hardware.health@2.0",
+ "libbase",
+ "libbootloader_message",
+ "libcrypto",
+ "libcutils",
+ "libfs_mgr",
+ "liblog",
+ "libziparchive",
+ ],
+
+ static_libs: [
+ "libinstall",
+ "librecovery_fastboot",
+ "libminui",
+ "libotautil",
+
+ // external dependencies
+ "libhealthhalutils",
+ ],
+}
+
+cc_library_static {
+ name: "librecovery",
+ recovery_available: true,
+
+ defaults: [
+ "librecovery_defaults",
+ ],
+
+ srcs: [
+ "fsck_unshare_blocks.cpp",
+ "recovery.cpp",
+ ],
+
+ shared_libs: [
+ "librecovery_ui",
+ ],
+}
+
+cc_binary {
+ name: "recovery",
+ recovery: true,
+
+ defaults: [
+ "libinstall_defaults",
+ "librecovery_defaults",
+ ],
+
+ srcs: [
+ "recovery_main.cpp",
+ ],
+
+ shared_libs: [
+ "librecovery_ui",
+ ],
+
+ static_libs: [
+ "librecovery",
+ "librecovery_ui_default",
+ ],
+
+ required: [
+ "e2fsdroid.recovery",
+ "librecovery_ui_ext",
+ "minadbd",
+ "mke2fs.conf.recovery",
+ "mke2fs.recovery",
+ "recovery_deps",
+ ],
+}
+
+// The dynamic executable that runs after /data mounts.
+cc_binary {
+ name: "recovery-persist",
+
+ defaults: [
+ "recovery_defaults",
+ ],
+
+ srcs: [
+ "recovery-persist.cpp",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libmetricslogger",
+ ],
+
+ static_libs: [
+ "libotautil",
+ ],
+
+ init_rc: [
+ "recovery-persist.rc",
+ ],
+}
+
+// The dynamic executable that runs at init.
+cc_binary {
+ name: "recovery-refresh",
+
+ defaults: [
+ "recovery_defaults",
+ ],
+
+ srcs: [
+ "recovery-refresh.cpp",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+
+ static_libs: [
+ "libotautil",
+ ],
+
+ init_rc: [
+ "recovery-refresh.rc",
+ ],
+}
+
+filegroup {
+ name: "res-testdata",
+
+ srcs: [
+ "res-*/images/*_text.png",
+ ],
+}
diff --git a/Android.mk b/Android.mk
index 7e0ad12..9806d10 100644
--- a/Android.mk
+++ b/Android.mk
@@ -14,267 +14,65 @@
LOCAL_PATH := $(call my-dir)
-# Needed by build/make/core/Makefile.
+# Needed by build/make/core/Makefile. Must be consistent with the value in Android.bp.
RECOVERY_API_VERSION := 3
RECOVERY_FSTAB_VERSION := 2
-# libfusesideload (static library)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := fuse_sideload.cpp
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
-LOCAL_MODULE := libfusesideload
-LOCAL_STATIC_LIBRARIES := \
- libcrypto \
- libbase
-include $(BUILD_STATIC_LIBRARY)
+# TARGET_RECOVERY_UI_LIB should be one of librecovery_ui_{default,wear,vr} or a device-specific
+# module that defines make_device() and the exact RecoveryUI class for the target. It defaults to
+# librecovery_ui_default, which uses ScreenRecoveryUI.
+TARGET_RECOVERY_UI_LIB ?= librecovery_ui_default
-# libmounts (static library)
-# ===============================
+# librecovery_ui_ext (shared library)
+# ===================================
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := mounts.cpp
-LOCAL_CFLAGS := \
- -Wall \
- -Werror
-LOCAL_MODULE := libmounts
-LOCAL_STATIC_LIBRARIES := libbase
-include $(BUILD_STATIC_LIBRARY)
-# librecovery (static library)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- install.cpp
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
+LOCAL_MODULE := librecovery_ui_ext
-ifeq ($(AB_OTA_UPDATER),true)
- LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
+# LOCAL_MODULE_PATH for shared libraries is unsupported in multiarch builds.
+LOCAL_MULTILIB := first
+
+ifeq ($(TARGET_IS_64_BIT),true)
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/lib64
+else
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/lib
endif
-LOCAL_MODULE := librecovery
-LOCAL_STATIC_LIBRARIES := \
- libminui \
- libotautil \
- libvintf_recovery \
- libcrypto_utils \
- libcrypto \
+LOCAL_WHOLE_STATIC_LIBRARIES := \
+ $(TARGET_RECOVERY_UI_LIB)
+
+LOCAL_SHARED_LIBRARIES := \
libbase \
- libziparchive \
+ liblog \
+ librecovery_ui.recovery
-include $(BUILD_STATIC_LIBRARY)
+include $(BUILD_SHARED_LIBRARY)
-# recovery (static executable)
-# ===============================
+# recovery_deps: A phony target that's depended on by `recovery`, which
+# builds additional modules conditionally based on Makefile variables.
+# ======================================================================
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- adb_install.cpp \
- device.cpp \
- fuse_sdcard_provider.cpp \
- recovery.cpp \
- roots.cpp \
- rotate_logs.cpp \
- screen_ui.cpp \
- ui.cpp \
- vr_ui.cpp \
- wear_ui.cpp \
-
-LOCAL_MODULE := recovery
-
-LOCAL_FORCE_STATIC_EXECUTABLE := true
-
-LOCAL_REQUIRED_MODULES := e2fsdroid_static mke2fs_static mke2fs.conf
+LOCAL_MODULE := recovery_deps
ifeq ($(TARGET_USERIMAGES_USE_F2FS),true)
ifeq ($(HOST_OS),linux)
-LOCAL_REQUIRED_MODULES += sload.f2fs mkfs.f2fs
+LOCAL_REQUIRED_MODULES += \
+ make_f2fs.recovery \
+ sload_f2fs.recovery
endif
endif
-LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
-LOCAL_CFLAGS += -Wall -Werror
-
-ifneq ($(TARGET_RECOVERY_UI_MARGIN_HEIGHT),)
-LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_HEIGHT=$(TARGET_RECOVERY_UI_MARGIN_HEIGHT)
-else
-LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_HEIGHT=0
-endif
-
-ifneq ($(TARGET_RECOVERY_UI_MARGIN_WIDTH),)
-LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_WIDTH=$(TARGET_RECOVERY_UI_MARGIN_WIDTH)
-else
-LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_WIDTH=0
-endif
-
-ifneq ($(TARGET_RECOVERY_UI_TOUCH_LOW_THRESHOLD),)
-LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_LOW_THRESHOLD=$(TARGET_RECOVERY_UI_TOUCH_LOW_THRESHOLD)
-else
-LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_LOW_THRESHOLD=50
-endif
-
-ifneq ($(TARGET_RECOVERY_UI_TOUCH_HIGH_THRESHOLD),)
-LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_HIGH_THRESHOLD=$(TARGET_RECOVERY_UI_TOUCH_HIGH_THRESHOLD)
-else
-LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_HIGH_THRESHOLD=90
-endif
-
-ifneq ($(TARGET_RECOVERY_UI_PROGRESS_BAR_BASELINE),)
-LOCAL_CFLAGS += -DRECOVERY_UI_PROGRESS_BAR_BASELINE=$(TARGET_RECOVERY_UI_PROGRESS_BAR_BASELINE)
-else
-LOCAL_CFLAGS += -DRECOVERY_UI_PROGRESS_BAR_BASELINE=259
-endif
-
-ifneq ($(TARGET_RECOVERY_UI_ANIMATION_FPS),)
-LOCAL_CFLAGS += -DRECOVERY_UI_ANIMATION_FPS=$(TARGET_RECOVERY_UI_ANIMATION_FPS)
-else
-LOCAL_CFLAGS += -DRECOVERY_UI_ANIMATION_FPS=30
-endif
-
-ifneq ($(TARGET_RECOVERY_UI_MENU_UNUSABLE_ROWS),)
-LOCAL_CFLAGS += -DRECOVERY_UI_MENU_UNUSABLE_ROWS=$(TARGET_RECOVERY_UI_MENU_UNUSABLE_ROWS)
-else
-LOCAL_CFLAGS += -DRECOVERY_UI_MENU_UNUSABLE_ROWS=9
-endif
-
-ifneq ($(TARGET_RECOVERY_UI_VR_STEREO_OFFSET),)
-LOCAL_CFLAGS += -DRECOVERY_UI_VR_STEREO_OFFSET=$(TARGET_RECOVERY_UI_VR_STEREO_OFFSET)
-else
-LOCAL_CFLAGS += -DRECOVERY_UI_VR_STEREO_OFFSET=0
-endif
-
-LOCAL_C_INCLUDES += \
- system/vold \
-
-# Health HAL dependency
-LOCAL_STATIC_LIBRARIES := \
- android.hardware.health@2.0-impl \
- android.hardware.health@2.0 \
- android.hardware.health@1.0 \
- android.hardware.health@1.0-convert \
- libhealthstoragedefault \
- libhidltransport \
- libhidlbase \
- libhwbinder_noltopgo \
- libvndksupport \
- libbatterymonitor
-
-LOCAL_STATIC_LIBRARIES += \
- librecovery \
- libverifier \
- libbootloader_message \
- libfs_mgr \
- libext4_utils \
- libsparse \
- libziparchive \
- libotautil \
- libmounts \
- libminadbd \
- libasyncio \
- libfusesideload \
- libminui \
- libpng \
- libcrypto_utils \
- libcrypto \
- libvintf_recovery \
- libvintf \
- libhidl-gen-utils \
- libtinyxml2 \
- libbase \
- libutils \
- libcutils \
- liblog \
- libselinux \
- libz
-
-LOCAL_HAL_STATIC_LIBRARIES := libhealthd
-
-ifeq ($(AB_OTA_UPDATER),true)
- LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
-endif
-
-LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
-
-ifeq ($(TARGET_RECOVERY_UI_LIB),)
- LOCAL_SRC_FILES += default_device.cpp
-else
- LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB)
-endif
-
+# On A/B devices recovery-persist reads the recovery related file from the persist storage and
+# copies them into /data/misc/recovery. Then, for both A/B and non-A/B devices, recovery-persist
+# parses the last_install file and reports the embedded update metrics. Also, the last_install file
+# will be deteleted after the report.
+LOCAL_REQUIRED_MODULES += recovery-persist
ifeq ($(BOARD_CACHEIMAGE_PARTITION_SIZE),)
-LOCAL_REQUIRED_MODULES += recovery-persist recovery-refresh
+LOCAL_REQUIRED_MODULES += recovery-refresh
endif
-include $(BUILD_EXECUTABLE)
-
-# recovery-persist (system partition dynamic executable run after /data mounts)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- recovery-persist.cpp \
- rotate_logs.cpp
-LOCAL_MODULE := recovery-persist
-LOCAL_SHARED_LIBRARIES := liblog libbase
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_INIT_RC := recovery-persist.rc
-include $(BUILD_EXECUTABLE)
-
-# recovery-refresh (system partition dynamic executable run at init)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- recovery-refresh.cpp \
- rotate_logs.cpp
-LOCAL_MODULE := recovery-refresh
-LOCAL_SHARED_LIBRARIES := liblog libbase
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_INIT_RC := recovery-refresh.rc
-include $(BUILD_EXECUTABLE)
-
-# libverifier (static library)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_MODULE := libverifier
-LOCAL_SRC_FILES := \
- asn1_decoder.cpp \
- verifier.cpp
-LOCAL_STATIC_LIBRARIES := \
- libotautil \
- libcrypto_utils \
- libcrypto \
- libbase
-LOCAL_CFLAGS := -Wall -Werror
-include $(BUILD_STATIC_LIBRARY)
-
-# Wear default device
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := wear_device.cpp
-LOCAL_CFLAGS := -Wall -Werror
-
-# Should match TARGET_RECOVERY_UI_LIB in BoardConfig.mk.
-LOCAL_MODULE := librecovery_ui_wear
-
-include $(BUILD_STATIC_LIBRARY)
-
-# vr headset default device
-# ===============================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := vr_device.cpp
-LOCAL_CFLAGS := -Wall -Werror
-
-# should match TARGET_RECOVERY_UI_LIB set in BoardConfig.mk
-LOCAL_MODULE := librecovery_ui_vr
-
-include $(BUILD_STATIC_LIBRARY)
+include $(BUILD_PHONY_PACKAGE)
include \
- $(LOCAL_PATH)/boot_control/Android.mk \
- $(LOCAL_PATH)/minadbd/Android.mk \
- $(LOCAL_PATH)/minui/Android.mk \
- $(LOCAL_PATH)/tests/Android.mk \
- $(LOCAL_PATH)/tools/Android.mk \
$(LOCAL_PATH)/updater/Android.mk \
- $(LOCAL_PATH)/update_verifier/Android.mk \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index e2d97d4..6bd1eb1 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -44,8 +44,17 @@
#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/recovery_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminui_intermediates/import_includes)
+$(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)
+
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/recovery_intermediates)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminui_intermediates/import_includes)
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index b5f5f03..28aa06f 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -4,3 +4,7 @@
[Builtin Hooks Options]
# Handle native codes only.
clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+
+[Hook Scripts]
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+ --file_whitelist tools/ updater_sample/
diff --git a/README.md b/README.md
index 0aeadae..0ccc10b 100644
--- a/README.md
+++ b/README.md
@@ -22,11 +22,9 @@
# 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
------------------------
@@ -41,13 +39,6 @@
contents of pmsg buffer into /data/misc/recovery/inject.txt. Test will pass if
this file has expected contents.
-`ResourceTest` validates whether the png files are qualified as background text
-image under recovery.
-
- 1. `adb sync data` to make sure the test-dir has the images to test.
- 2. The test will automatically pickup and verify all `_text.png` files in
- the test dir.
-
Using `adb` under recovery
--------------------------
@@ -60,10 +51,10 @@
List of devices attached
1234567890abcdef recovery
-Although `/sbin/adbd` shares the same binary between normal boot and recovery images, only a subset
-of `adb` commands are meaningful under recovery, such as `adb root`, `adb shell`, `adb push`, `adb
-pull` etc. `adb shell` works only after manually mounting `/system` from recovery menu (assuming a
-valid system image on device).
+Although `/system/bin/adbd` is built from the same code base as the one in the normal boot, only a
+subset of `adb` commands are meaningful under recovery, such as `adb root`, `adb shell`, `adb push`,
+`adb pull` etc. Since Android Q, `adb shell` no longer requires manually mounting `/system` from
+recovery menu.
## Troubleshooting
@@ -74,8 +65,8 @@
* Ensure `adbd` is built and running.
-By default, `adbd` is always included into recovery image, as `/sbin/adbd`. `init` starts `adbd`
-service automatically only in debuggable builds. This behavior is controlled by the recovery
+By default, `adbd` is always included into recovery image, as `/system/bin/adbd`. `init` starts
+`adbd` service automatically only in debuggable builds. This behavior is controlled by the recovery
specific `/init.rc`, whose source code is at `bootable/recovery/etc/init.rc`.
The best way to confirm a running `adbd` is by checking the serial output, which shows a service
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..a304582
--- /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/adb_install.cpp b/adb_install.cpp
deleted file mode 100644
index ac01306..0000000
--- a/adb_install.cpp
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2012 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 "adb_install.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.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/unique_fd.h>
-
-#include "common.h"
-#include "fuse_sideload.h"
-#include "install.h"
-#include "ui.h"
-
-static void set_usb_driver(bool enabled) {
- // USB configfs doesn't use /s/c/a/a/enable.
- if (android::base::GetBoolProperty("sys.usb.configfs", false)) {
- return;
- }
-
- static constexpr const char* USB_DRIVER_CONTROL = "/sys/class/android_usb/android0/enable";
- android::base::unique_fd fd(open(USB_DRIVER_CONTROL, O_WRONLY));
- if (fd == -1) {
- PLOG(ERROR) << "Failed to open driver control";
- return;
- }
- // Not using android::base::WriteStringToFile since that will open with O_CREAT and give EPERM
- // when USB_DRIVER_CONTROL doesn't exist. When it gives EPERM, we don't know whether that's due
- // to non-existent USB_DRIVER_CONTROL or indeed a permission issue.
- if (!android::base::WriteStringToFd(enabled ? "1" : "0", fd)) {
- PLOG(ERROR) << "Failed to set driver control";
- }
-}
-
-static void stop_adbd() {
- ui->Print("Stopping adbd...\n");
- android::base::SetProperty("ctl.stop", "adbd");
- set_usb_driver(false);
-}
-
-static void maybe_restart_adbd() {
- if (is_ro_debuggable()) {
- ui->Print("Restarting adbd...\n");
- set_usb_driver(true);
- android::base::SetProperty("ctl.start", "adbd");
- }
-}
-
-int apply_from_adb(bool* wipe_cache, const char* install_file) {
- modified_flash = true;
-
- stop_adbd();
- set_usb_driver(true);
-
- ui->Print(
- "\n\nNow send the package you want to apply\n"
- "to the device with \"adb sideload <filename>\"...\n");
-
- pid_t child;
- if ((child = fork()) == 0) {
- execl("/sbin/recovery", "recovery", "--adbd", nullptr);
- _exit(EXIT_FAILURE);
- }
-
- // How long (in seconds) we wait for the host to start sending us a package, before timing out.
- static constexpr int ADB_INSTALL_TIMEOUT = 300;
-
- // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the host connects and starts serving a
- // package. Poll for its appearance. (Note that inotify doesn't work with FUSE.)
- int result = INSTALL_ERROR;
- int status;
- bool waited = false;
- for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) {
- if (waitpid(child, &status, WNOHANG) != 0) {
- result = INSTALL_ERROR;
- waited = true;
- break;
- }
-
- struct stat st;
- if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) {
- if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT - 1) {
- sleep(1);
- continue;
- } else {
- ui->Print("\nTimed out waiting for package.\n\n");
- result = INSTALL_ERROR;
- kill(child, SIGKILL);
- break;
- }
- }
- result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, install_file, false, 0);
- break;
- }
-
- if (!waited) {
- // Calling stat() on this magic filename signals the minadbd subprocess to shut down.
- struct stat st;
- stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st);
-
- // TODO: there should be a way to cancel waiting for a package (by pushing some button combo on
- // the device). For now you just have to 'adb sideload' a file that's not a valid package, like
- // "/dev/null".
- waitpid(child, &status, 0);
- }
-
- if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
- if (WEXITSTATUS(status) == 3) {
- ui->Print("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n");
- } else if (!WIFSIGNALED(status)) {
- ui->Print("\n(adbd status %d)\n", WEXITSTATUS(status));
- }
- }
-
- set_usb_driver(false);
- maybe_restart_adbd();
-
- return result;
-}
diff --git a/applypatch/Android.bp b/applypatch/Android.bp
index cb0b367..620ca6c 100644
--- a/applypatch/Android.bp
+++ b/applypatch/Android.bp
@@ -53,7 +53,6 @@
"libbz",
"libcrypto",
"libedify",
- "libotafault",
"libotautil",
"libz",
],
@@ -100,7 +99,6 @@
"libapplypatch_modes",
"libapplypatch",
"libedify",
- "libotafault",
"libotautil",
"libbspatch",
],
diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp
index 7645a40..90d8e86 100644
--- a/applypatch/applypatch.cpp
+++ b/applypatch/applypatch.cpp
@@ -23,261 +23,167 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
-#include <sys/statfs.h>
#include <sys/types.h>
#include <unistd.h>
+#include <algorithm>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
+#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
#include <openssl/sha.h>
#include "edify/expr.h"
-#include "otafault/ota_io.h"
-#include "otautil/cache_location.h"
+#include "otautil/paths.h"
#include "otautil/print_sha1.h"
-static int LoadPartitionContents(const std::string& filename, FileContents* file);
-static size_t FileSink(const unsigned char* data, size_t len, int fd);
-static int GenerateTarget(const FileContents& source_file, const std::unique_ptr<Value>& patch,
- const std::string& target_filename,
- const uint8_t target_sha1[SHA_DIGEST_LENGTH], const Value* bonus_data);
+using namespace std::string_literals;
-// Read a file into memory; store the file contents and associated metadata in *file.
-// Return 0 on success.
-int LoadFileContents(const char* filename, FileContents* file) {
- // A special 'filename' beginning with "EMMC:" means to load the contents of a partition.
- if (strncmp(filename, "EMMC:", 5) == 0) {
- return LoadPartitionContents(filename, file);
+static bool GenerateTarget(const Partition& target, const FileContents& source_file,
+ const Value& patch, const Value* bonus_data);
+
+bool LoadFileContents(const std::string& filename, FileContents* file) {
+ // No longer allow loading contents from eMMC partitions.
+ if (android::base::StartsWith(filename, "EMMC:")) {
+ return false;
}
- struct stat sb;
- if (stat(filename, &sb) == -1) {
- printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
- return -1;
+ std::string data;
+ if (!android::base::ReadFileToString(filename, &data)) {
+ PLOG(ERROR) << "Failed to read \"" << filename << "\"";
+ return false;
}
- std::vector<unsigned char> data(sb.st_size);
- unique_file f(ota_fopen(filename, "rb"));
- if (!f) {
- printf("failed to open \"%s\": %s\n", filename, strerror(errno));
- return -1;
- }
-
- size_t bytes_read = ota_fread(data.data(), 1, data.size(), f.get());
- if (bytes_read != data.size()) {
- printf("short read of \"%s\" (%zu bytes of %zu)\n", filename, bytes_read, data.size());
- return -1;
- }
- file->data = std::move(data);
+ file->data = std::vector<unsigned char>(data.begin(), data.end());
SHA1(file->data.data(), file->data.size(), file->sha1);
- return 0;
+ return true;
}
-// Load the contents of an EMMC partition into the provided
-// FileContents. filename should be a string of the form
-// "EMMC:<partition_device>:...". The smallest size_n bytes for
-// which that prefix of the partition contents has the corresponding
-// sha1 hash will be loaded. It is acceptable for a size value to be
-// repeated with different sha1s. Will return 0 on success.
-//
-// This complexity is needed because if an OTA installation is
-// interrupted, the partition might contain either the source or the
-// target data, which might be of different lengths. We need to know
-// the length in order to read from a partition (there is no
-// "end-of-file" marker), so the caller must specify the possible
-// lengths and the hash of the data, and we'll do the load expecting
-// to find one of those hashes.
-static int LoadPartitionContents(const std::string& filename, FileContents* file) {
- std::vector<std::string> pieces = android::base::Split(filename, ":");
- if (pieces.size() < 4 || pieces.size() % 2 != 0 || pieces[0] != "EMMC") {
- printf("LoadPartitionContents called with bad filename \"%s\"\n", filename.c_str());
- return -1;
+// Reads the contents of a Partition to the given FileContents buffer.
+static bool ReadPartitionToBuffer(const Partition& partition, FileContents* out,
+ bool check_backup) {
+ uint8_t expected_sha1[SHA_DIGEST_LENGTH];
+ if (ParseSha1(partition.hash, expected_sha1) != 0) {
+ LOG(ERROR) << "Failed to parse target hash \"" << partition.hash << "\"";
+ return false;
}
- size_t pair_count = (pieces.size() - 2) / 2; // # of (size, sha1) pairs in filename
- std::vector<std::pair<size_t, std::string>> pairs;
- for (size_t i = 0; i < pair_count; ++i) {
- size_t size;
- if (!android::base::ParseUint(pieces[i * 2 + 2], &size) || size == 0) {
- printf("LoadPartitionContents called with bad size \"%s\"\n", pieces[i * 2 + 2].c_str());
- return -1;
- }
- pairs.push_back({ size, pieces[i * 2 + 3] });
- }
-
- // Sort the pairs array so that they are in order of increasing size.
- std::sort(pairs.begin(), pairs.end());
-
- const char* partition = pieces[1].c_str();
- unique_file dev(ota_fopen(partition, "rb"));
- if (!dev) {
- printf("failed to open emmc partition \"%s\": %s\n", partition, strerror(errno));
- return -1;
- }
-
- SHA_CTX sha_ctx;
- SHA1_Init(&sha_ctx);
-
- // Allocate enough memory to hold the largest size.
- std::vector<unsigned char> buffer(pairs[pair_count - 1].first);
- unsigned char* buffer_ptr = buffer.data();
- size_t buffer_size = 0; // # bytes read so far
- bool found = false;
-
- for (const auto& pair : pairs) {
- size_t current_size = pair.first;
- const std::string& current_sha1 = pair.second;
-
- // Read enough additional bytes to get us up to the next size. (Again,
- // we're trying the possibilities in order of increasing size).
- size_t next = current_size - buffer_size;
- if (next > 0) {
- size_t read = ota_fread(buffer_ptr, 1, next, dev.get());
- if (next != read) {
- printf("short read (%zu bytes of %zu) for partition \"%s\"\n", read, next, partition);
- return -1;
+ android::base::unique_fd dev(open(partition.name.c_str(), O_RDONLY));
+ if (dev == -1) {
+ PLOG(ERROR) << "Failed to open eMMC partition \"" << partition << "\"";
+ } else {
+ std::vector<unsigned char> buffer(partition.size);
+ if (!android::base::ReadFully(dev, buffer.data(), buffer.size())) {
+ PLOG(ERROR) << "Failed to read " << buffer.size() << " bytes of data for partition "
+ << partition;
+ } else {
+ SHA1(buffer.data(), buffer.size(), out->sha1);
+ if (memcmp(out->sha1, expected_sha1, SHA_DIGEST_LENGTH) == 0) {
+ out->data = std::move(buffer);
+ return true;
}
- SHA1_Update(&sha_ctx, buffer_ptr, read);
- buffer_size += read;
- buffer_ptr += read;
- }
-
- // Duplicate the SHA context and finalize the duplicate so we can
- // check it against this pair's expected hash.
- SHA_CTX temp_ctx;
- memcpy(&temp_ctx, &sha_ctx, sizeof(SHA_CTX));
- uint8_t sha_so_far[SHA_DIGEST_LENGTH];
- SHA1_Final(sha_so_far, &temp_ctx);
-
- uint8_t parsed_sha[SHA_DIGEST_LENGTH];
- if (ParseSha1(current_sha1.c_str(), parsed_sha) != 0) {
- printf("failed to parse SHA-1 %s in %s\n", current_sha1.c_str(), filename.c_str());
- return -1;
- }
-
- if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_LENGTH) == 0) {
- // We have a match. Stop reading the partition; we'll return the data we've read so far.
- printf("partition read matched size %zu SHA-1 %s\n", current_size, current_sha1.c_str());
- found = true;
- break;
}
}
- if (!found) {
- // Ran off the end of the list of (size, sha1) pairs without finding a match.
- printf("contents of partition \"%s\" didn't match %s\n", partition, filename.c_str());
- return -1;
+ if (!check_backup) {
+ LOG(ERROR) << "Partition contents don't have the expected checksum";
+ return false;
}
- SHA1_Final(file->sha1, &sha_ctx);
+ if (LoadFileContents(Paths::Get().cache_temp_source(), out) &&
+ memcmp(out->sha1, expected_sha1, SHA_DIGEST_LENGTH) == 0) {
+ return true;
+ }
- buffer.resize(buffer_size);
- file->data = std::move(buffer);
-
- return 0;
+ LOG(ERROR) << "Both of partition contents and backup don't have the expected checksum";
+ return false;
}
-// Save the contents of the given FileContents object under the given
-// filename. Return 0 on success.
-int SaveFileContents(const char* filename, const FileContents* file) {
- unique_fd fd(ota_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR));
+bool SaveFileContents(const std::string& filename, const FileContents* file) {
+ android::base::unique_fd fd(
+ open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR));
if (fd == -1) {
- printf("failed to open \"%s\" for write: %s\n", filename, strerror(errno));
- return -1;
+ PLOG(ERROR) << "Failed to open \"" << filename << "\" for write";
+ return false;
}
- size_t bytes_written = FileSink(file->data.data(), file->data.size(), fd);
- if (bytes_written != file->data.size()) {
- printf("short write of \"%s\" (%zd bytes of %zu): %s\n", filename, bytes_written,
- file->data.size(), strerror(errno));
- return -1;
- }
- if (ota_fsync(fd) != 0) {
- printf("fsync of \"%s\" failed: %s\n", filename, strerror(errno));
- return -1;
- }
- if (ota_close(fd) != 0) {
- printf("close of \"%s\" failed: %s\n", filename, strerror(errno));
- return -1;
+ if (!android::base::WriteFully(fd, file->data.data(), file->data.size())) {
+ PLOG(ERROR) << "Failed to write " << file->data.size() << " bytes of data to " << filename;
+ return false;
}
- return 0;
+ if (fsync(fd) != 0) {
+ PLOG(ERROR) << "Failed to fsync \"" << filename << "\"";
+ return false;
+ }
+
+ if (close(fd.release()) != 0) {
+ PLOG(ERROR) << "Failed to close \"" << filename << "\"";
+ return false;
+ }
+
+ return true;
}
-// Write a memory buffer to 'target' partition, a string of the form
-// "EMMC:<partition_device>[:...]". The target name
-// might contain multiple colons, but WriteToPartition() only uses the first
-// two and ignores the rest. Return 0 on success.
-int WriteToPartition(const unsigned char* data, size_t len, const std::string& target) {
- std::vector<std::string> pieces = android::base::Split(target, ":");
- if (pieces.size() < 2 || pieces[0] != "EMMC") {
- printf("WriteToPartition called with bad target (%s)\n", target.c_str());
- return -1;
- }
-
- const char* partition = pieces[1].c_str();
- unique_fd fd(ota_open(partition, O_RDWR));
- if (fd == -1) {
- printf("failed to open %s: %s\n", partition, strerror(errno));
- return -1;
- }
-
+// Writes a memory buffer to 'target' Partition.
+static bool WriteBufferToPartition(const FileContents& file_contents, const Partition& partition) {
+ const unsigned char* data = file_contents.data.data();
+ size_t len = file_contents.data.size();
size_t start = 0;
bool success = false;
for (size_t attempt = 0; attempt < 2; ++attempt) {
- if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) {
- printf("failed seek on %s: %s\n", partition, strerror(errno));
- return -1;
- }
- while (start < len) {
- size_t to_write = len - start;
- if (to_write > 1 << 20) to_write = 1 << 20;
-
- ssize_t written = TEMP_FAILURE_RETRY(ota_write(fd, data + start, to_write));
- if (written == -1) {
- printf("failed write writing to %s: %s\n", partition, strerror(errno));
- return -1;
- }
- start += written;
- }
-
- if (ota_fsync(fd) != 0) {
- printf("failed to sync to %s: %s\n", partition, strerror(errno));
- return -1;
- }
- if (ota_close(fd) != 0) {
- printf("failed to close %s: %s\n", partition, strerror(errno));
- return -1;
- }
-
- fd.reset(ota_open(partition, O_RDONLY));
+ android::base::unique_fd fd(open(partition.name.c_str(), O_RDWR));
if (fd == -1) {
- printf("failed to reopen %s for verify: %s\n", partition, strerror(errno));
- return -1;
+ PLOG(ERROR) << "Failed to open \"" << partition << "\"";
+ return false;
+ }
+
+ if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) {
+ PLOG(ERROR) << "Failed to seek to " << start << " on \"" << partition << "\"";
+ return false;
+ }
+
+ if (!android::base::WriteFully(fd, data + start, len - start)) {
+ PLOG(ERROR) << "Failed to write " << len - start << " bytes to \"" << partition << "\"";
+ return false;
+ }
+
+ if (fsync(fd) != 0) {
+ PLOG(ERROR) << "Failed to sync \"" << partition << "\"";
+ return false;
+ }
+ if (close(fd.release()) != 0) {
+ PLOG(ERROR) << "Failed to close \"" << partition << "\"";
+ return false;
+ }
+
+ fd.reset(open(partition.name.c_str(), O_RDONLY));
+ if (fd == -1) {
+ PLOG(ERROR) << "Failed to reopen \"" << partition << "\" for verification";
+ return false;
}
// Drop caches so our subsequent verification read won't just be reading the cache.
sync();
- unique_fd dc(ota_open("/proc/sys/vm/drop_caches", O_WRONLY));
- if (TEMP_FAILURE_RETRY(ota_write(dc, "3\n", 2)) == -1) {
- printf("write to /proc/sys/vm/drop_caches failed: %s\n", strerror(errno));
+ std::string drop_cache = "/proc/sys/vm/drop_caches";
+ if (!android::base::WriteStringToFile("3\n", drop_cache)) {
+ PLOG(ERROR) << "Failed to write to " << drop_cache;
} else {
- printf(" caches dropped\n");
+ LOG(INFO) << " caches dropped";
}
- ota_close(dc);
sleep(1);
// Verify.
if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) {
- printf("failed to seek back to beginning of %s: %s\n", partition, strerror(errno));
- return -1;
+ PLOG(ERROR) << "Failed to seek to 0 on " << partition;
+ return false;
}
unsigned char buffer[4096];
@@ -288,386 +194,262 @@
to_read = sizeof(buffer);
}
- size_t so_far = 0;
- while (so_far < to_read) {
- ssize_t read_count = TEMP_FAILURE_RETRY(ota_read(fd, buffer + so_far, to_read - so_far));
- if (read_count == -1) {
- printf("verify read error %s at %zu: %s\n", partition, p, strerror(errno));
- return -1;
- } else if (read_count == 0) {
- printf("verify read reached unexpected EOF, %s at %zu\n", partition, p);
- return -1;
- }
- if (static_cast<size_t>(read_count) < to_read) {
- printf("short verify read %s at %zu: %zd %zu\n", partition, p, read_count, to_read);
- }
- so_far += read_count;
+ if (!android::base::ReadFully(fd, buffer, to_read)) {
+ PLOG(ERROR) << "Failed to verify-read " << partition << " at " << p;
+ return false;
}
if (memcmp(buffer, data + p, to_read) != 0) {
- printf("verification failed starting at %zu\n", p);
+ LOG(ERROR) << "Verification failed starting at " << p;
start = p;
break;
}
}
if (start == len) {
- printf("verification read succeeded (attempt %zu)\n", attempt + 1);
+ LOG(INFO) << "Verification read succeeded (attempt " << attempt + 1 << ")";
success = true;
break;
}
- if (ota_close(fd) != 0) {
- printf("failed to close %s: %s\n", partition, strerror(errno));
- return -1;
- }
-
- fd.reset(ota_open(partition, O_RDWR));
- if (fd == -1) {
- printf("failed to reopen %s for retry write && verify: %s\n", partition, strerror(errno));
- return -1;
+ if (close(fd.release()) != 0) {
+ PLOG(ERROR) << "Failed to close " << partition;
+ return false;
}
}
if (!success) {
- printf("failed to verify after all attempts\n");
- return -1;
+ LOG(ERROR) << "Failed to verify after all attempts";
+ return false;
}
- if (ota_close(fd) == -1) {
- printf("error closing %s: %s\n", partition, strerror(errno));
- return -1;
- }
sync();
+ return true;
+}
+
+int ParseSha1(const std::string& str, uint8_t* digest) {
+ const char* ps = str.c_str();
+ uint8_t* pd = digest;
+ for (int i = 0; i < SHA_DIGEST_LENGTH * 2; ++i, ++ps) {
+ int digit;
+ if (*ps >= '0' && *ps <= '9') {
+ digit = *ps - '0';
+ } else if (*ps >= 'a' && *ps <= 'f') {
+ digit = *ps - 'a' + 10;
+ } else if (*ps >= 'A' && *ps <= 'F') {
+ digit = *ps - 'A' + 10;
+ } else {
+ return -1;
+ }
+ if (i % 2 == 0) {
+ *pd = digit << 4;
+ } else {
+ *pd |= digit;
+ ++pd;
+ }
+ }
+ if (*ps != '\0') return -1;
return 0;
}
-// Take a string 'str' of 40 hex digits and parse it into the 20
-// byte array 'digest'. 'str' may contain only the digest or be of
-// the form "<digest>:<anything>". Return 0 on success, -1 on any
-// error.
-int ParseSha1(const char* str, uint8_t* digest) {
- const char* ps = str;
- uint8_t* pd = digest;
- for (int i = 0; i < SHA_DIGEST_LENGTH * 2; ++i, ++ps) {
- int digit;
- if (*ps >= '0' && *ps <= '9') {
- digit = *ps - '0';
- } else if (*ps >= 'a' && *ps <= 'f') {
- digit = *ps - 'a' + 10;
- } else if (*ps >= 'A' && *ps <= 'F') {
- digit = *ps - 'A' + 10;
- } else {
- return -1;
- }
- if (i % 2 == 0) {
- *pd = digit << 4;
- } else {
- *pd |= digit;
- ++pd;
- }
- }
- if (*ps != '\0') return -1;
- return 0;
-}
-
-// Search an array of sha1 strings for one matching the given sha1.
-// Return the index of the match on success, or -1 if no match is
-// found.
-static int FindMatchingPatch(uint8_t* sha1, const std::vector<std::string>& patch_sha1_str) {
- for (size_t i = 0; i < patch_sha1_str.size(); ++i) {
- uint8_t patch_sha1[SHA_DIGEST_LENGTH];
- if (ParseSha1(patch_sha1_str[i].c_str(), patch_sha1) == 0 &&
- memcmp(patch_sha1, sha1, SHA_DIGEST_LENGTH) == 0) {
- return i;
- }
- }
- return -1;
-}
-
-// Returns 0 if the contents of the file (argv[2]) or the cached file
-// match any of the sha1's on the command line (argv[3:]). Returns
-// nonzero otherwise.
-int applypatch_check(const char* filename, const std::vector<std::string>& patch_sha1_str) {
- FileContents file;
-
- // It's okay to specify no sha1s; the check will pass if the
- // LoadFileContents is successful. (Useful for reading
- // partitions, where the filename encodes the sha1s; no need to
- // check them twice.)
- if (LoadFileContents(filename, &file) != 0 ||
- (!patch_sha1_str.empty() && FindMatchingPatch(file.sha1, patch_sha1_str) < 0)) {
- printf("file \"%s\" doesn't have any of expected sha1 sums; checking cache\n", filename);
-
- // If the source file is missing or corrupted, it might be because we were killed in the middle
- // of patching it. A copy of it should have been made in cache_temp_source. If that file
- // exists and matches the sha1 we're looking for, the check still passes.
- if (LoadFileContents(CacheLocation::location().cache_temp_source().c_str(), &file) != 0) {
- printf("failed to load cache file\n");
- return 1;
- }
-
- if (FindMatchingPatch(file.sha1, patch_sha1_str) < 0) {
- printf("cache bits don't match any sha1 for \"%s\"\n", filename);
- return 1;
- }
- }
- return 0;
+bool PatchPartitionCheck(const Partition& target, const Partition& source) {
+ FileContents target_file;
+ FileContents source_file;
+ return (ReadPartitionToBuffer(target, &target_file, false) ||
+ ReadPartitionToBuffer(source, &source_file, true));
}
int ShowLicenses() {
- ShowBSDiffLicense();
- return 0;
-}
-
-static size_t FileSink(const unsigned char* data, size_t len, int fd) {
- size_t done = 0;
- while (done < len) {
- ssize_t wrote = TEMP_FAILURE_RETRY(ota_write(fd, data + done, len - done));
- if (wrote == -1) {
- printf("error writing %zd bytes: %s\n", (len - done), strerror(errno));
- return done;
- }
- done += wrote;
- }
- return done;
-}
-
-// Return the amount of free space (in bytes) on the filesystem
-// containing filename. filename must exist. Return -1 on error.
-size_t FreeSpaceForFile(const char* filename) {
- struct statfs sf;
- if (statfs(filename, &sf) != 0) {
- printf("failed to statfs %s: %s\n", filename, strerror(errno));
- return -1;
- }
- return sf.f_bsize * sf.f_bavail;
-}
-
-int CacheSizeCheck(size_t bytes) {
- if (MakeFreeSpaceOnCache(bytes) < 0) {
- printf("unable to make %zu bytes available on /cache\n", bytes);
- return 1;
- }
- return 0;
-}
-
-// This function applies binary patches to EMMC target files in a way that is safe (the original
-// file is not touched until we have the desired replacement for it) and idempotent (it's okay to
-// run this program multiple times).
-//
-// - If the SHA-1 hash of <target_filename> is <target_sha1_string>, does nothing and exits
-// successfully.
-//
-// - Otherwise, if the SHA-1 hash of <source_filename> is one of the entries in <patch_sha1_str>,
-// the corresponding patch from <patch_data> (which must be a VAL_BLOB) is applied to produce a
-// new file (the type of patch is automatically detected from the blob data). If that new file
-// has SHA-1 hash <target_sha1_str>, moves it to replace <target_filename>, and exits
-// successfully. Note that if <source_filename> and <target_filename> are not the same,
-// <source_filename> is NOT deleted on success. <target_filename> may be the string "-" to mean
-// "the same as <source_filename>".
-//
-// - Otherwise, or if any error is encountered, exits with non-zero status.
-//
-// <source_filename> must refer to an EMMC partition to read the source data. See the comments for
-// the LoadPartitionContents() function above for the format of such a filename. <target_size> has
-// become obsolete since we have dropped the support for patching non-EMMC targets (EMMC targets
-// have the size embedded in the filename).
-int applypatch(const char* source_filename, const char* target_filename,
- const char* target_sha1_str, size_t /* target_size */,
- const std::vector<std::string>& patch_sha1_str,
- const std::vector<std::unique_ptr<Value>>& patch_data, const Value* bonus_data) {
- printf("patch %s: ", source_filename);
-
- if (target_filename[0] == '-' && target_filename[1] == '\0') {
- target_filename = source_filename;
- }
-
- if (strncmp(target_filename, "EMMC:", 5) != 0) {
- printf("Supporting patching EMMC targets only.\n");
- return 1;
- }
-
- uint8_t target_sha1[SHA_DIGEST_LENGTH];
- if (ParseSha1(target_sha1_str, target_sha1) != 0) {
- printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str);
- return 1;
- }
-
- // We try to load the target file into the source_file object.
- FileContents source_file;
- if (LoadFileContents(target_filename, &source_file) == 0) {
- if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) {
- // The early-exit case: the patch was already applied, this file has the desired hash, nothing
- // for us to do.
- printf("already %s\n", short_sha1(target_sha1).c_str());
- return 0;
- }
- }
-
- if (source_file.data.empty() ||
- (target_filename != source_filename && strcmp(target_filename, source_filename) != 0)) {
- // Need to load the source file: either we failed to load the target file, or we did but it's
- // different from the expected.
- source_file.data.clear();
- LoadFileContents(source_filename, &source_file);
- }
-
- if (!source_file.data.empty()) {
- int to_use = FindMatchingPatch(source_file.sha1, patch_sha1_str);
- if (to_use != -1) {
- return GenerateTarget(source_file, patch_data[to_use], target_filename, target_sha1,
- bonus_data);
- }
- }
-
- printf("source file is bad; trying copy\n");
-
- FileContents copy_file;
- if (LoadFileContents(CacheLocation::location().cache_temp_source().c_str(), ©_file) < 0) {
- printf("failed to read copy file\n");
- return 1;
- }
-
- int to_use = FindMatchingPatch(copy_file.sha1, patch_sha1_str);
- if (to_use == -1) {
- printf("copy file doesn't match source SHA-1s either\n");
- return 1;
- }
-
- return GenerateTarget(copy_file, patch_data[to_use], target_filename, target_sha1, bonus_data);
-}
-
-/*
- * This function flashes a given image to the target partition. It verifies
- * the target cheksum first, and will return if target has the desired hash.
- * It checks the checksum of the given source image before flashing, and
- * verifies the target partition afterwards. The function is idempotent.
- * Returns zero on success.
- */
-int applypatch_flash(const char* source_filename, const char* target_filename,
- const char* target_sha1_str, size_t target_size) {
- printf("flash %s: ", target_filename);
-
- uint8_t target_sha1[SHA_DIGEST_LENGTH];
- if (ParseSha1(target_sha1_str, target_sha1) != 0) {
- printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str);
- return 1;
- }
-
- std::string target_str(target_filename);
- std::vector<std::string> pieces = android::base::Split(target_str, ":");
- if (pieces.size() != 2 || pieces[0] != "EMMC") {
- printf("invalid target name \"%s\"", target_filename);
- return 1;
- }
-
- // Load the target into the source_file object to see if already applied.
- pieces.push_back(std::to_string(target_size));
- pieces.push_back(target_sha1_str);
- std::string fullname = android::base::Join(pieces, ':');
- FileContents source_file;
- if (LoadPartitionContents(fullname, &source_file) == 0 &&
- memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) {
- // The early-exit case: the image was already applied, this partition
- // has the desired hash, nothing for us to do.
- printf("already %s\n", short_sha1(target_sha1).c_str());
- return 0;
- }
-
- if (LoadFileContents(source_filename, &source_file) == 0) {
- if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) {
- // The source doesn't have desired checksum.
- printf("source \"%s\" doesn't have expected sha1 sum\n", source_filename);
- printf("expected: %s, found: %s\n", short_sha1(target_sha1).c_str(),
- short_sha1(source_file.sha1).c_str());
- return 1;
- }
- }
-
- if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) {
- printf("write of copied data to %s failed\n", target_filename);
- return 1;
- }
+ ShowBSDiffLicense();
return 0;
}
-static int GenerateTarget(const FileContents& source_file, const std::unique_ptr<Value>& patch,
- const std::string& target_filename,
- const uint8_t target_sha1[SHA_DIGEST_LENGTH], const Value* bonus_data) {
- if (patch->type != VAL_BLOB) {
- printf("patch is not a blob\n");
- return 1;
+bool PatchPartition(const Partition& target, const Partition& source, const Value& patch,
+ const Value* bonus) {
+ LOG(INFO) << "Patching " << target.name;
+
+ // We try to load and check against the target hash first.
+ FileContents target_file;
+ if (ReadPartitionToBuffer(target, &target_file, false)) {
+ // The early-exit case: the patch was already applied, this file has the desired hash, nothing
+ // for us to do.
+ LOG(INFO) << " already " << target.hash.substr(0, 8);
+ return true;
}
- const char* header = &patch->data[0];
- size_t header_bytes_read = patch->data.size();
+ FileContents source_file;
+ if (ReadPartitionToBuffer(source, &source_file, true)) {
+ return GenerateTarget(target, source_file, patch, bonus);
+ }
+
+ LOG(ERROR) << "Failed to find any match";
+ return false;
+}
+
+bool FlashPartition(const Partition& partition, const std::string& source_filename) {
+ LOG(INFO) << "Flashing " << partition;
+
+ // We try to load and check against the target hash first.
+ FileContents target_file;
+ if (ReadPartitionToBuffer(partition, &target_file, false)) {
+ // The early-exit case: the patch was already applied, this file has the desired hash, nothing
+ // for us to do.
+ LOG(INFO) << " already " << partition.hash.substr(0, 8);
+ return true;
+ }
+
+ FileContents source_file;
+ if (!LoadFileContents(source_filename, &source_file)) {
+ LOG(ERROR) << "Failed to load source file";
+ return false;
+ }
+
+ uint8_t expected_sha1[SHA_DIGEST_LENGTH];
+ if (ParseSha1(partition.hash, expected_sha1) != 0) {
+ LOG(ERROR) << "Failed to parse source hash \"" << partition.hash << "\"";
+ return false;
+ }
+
+ if (memcmp(source_file.sha1, expected_sha1, SHA_DIGEST_LENGTH) != 0) {
+ // The source doesn't have desired checksum.
+ LOG(ERROR) << "source \"" << source_filename << "\" doesn't have expected SHA-1 sum";
+ LOG(ERROR) << "expected: " << partition.hash.substr(0, 8)
+ << ", found: " << short_sha1(source_file.sha1);
+ return false;
+ }
+ if (!WriteBufferToPartition(source_file, partition)) {
+ LOG(ERROR) << "Failed to write to " << partition;
+ return false;
+ }
+ return true;
+}
+
+static bool GenerateTarget(const Partition& target, const FileContents& source_file,
+ const Value& patch, const Value* bonus_data) {
+ uint8_t expected_sha1[SHA_DIGEST_LENGTH];
+ if (ParseSha1(target.hash, expected_sha1) != 0) {
+ LOG(ERROR) << "Failed to parse target hash \"" << target.hash << "\"";
+ return false;
+ }
+
+ if (patch.type != Value::Type::BLOB) {
+ LOG(ERROR) << "patch is not a blob";
+ return false;
+ }
+
+ const char* header = patch.data.data();
+ size_t header_bytes_read = patch.data.size();
bool use_bsdiff = false;
if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) {
use_bsdiff = true;
} else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) {
use_bsdiff = false;
} else {
- printf("Unknown patch file format\n");
- return 1;
+ LOG(ERROR) << "Unknown patch file format";
+ return false;
}
- CHECK(android::base::StartsWith(target_filename, "EMMC:"));
-
- // We still write the original source to cache, in case the partition write is interrupted.
- if (MakeFreeSpaceOnCache(source_file.data.size()) < 0) {
- printf("not enough free space on /cache\n");
- return 1;
+ // We write the original source to cache, in case the partition write is interrupted.
+ if (!CheckAndFreeSpaceOnCache(source_file.data.size())) {
+ LOG(ERROR) << "Not enough free space on /cache";
+ return false;
}
- if (SaveFileContents(CacheLocation::location().cache_temp_source().c_str(), &source_file) < 0) {
- printf("failed to back up source file\n");
- return 1;
+ if (!SaveFileContents(Paths::Get().cache_temp_source(), &source_file)) {
+ LOG(ERROR) << "Failed to back up source file";
+ return false;
}
// We store the decoded output in memory.
- std::string memory_sink_str; // Don't need to reserve space.
- SinkFn sink = [&memory_sink_str](const unsigned char* data, size_t len) {
- memory_sink_str.append(reinterpret_cast<const char*>(data), len);
+ FileContents patched;
+ SHA_CTX ctx;
+ SHA1_Init(&ctx);
+ SinkFn sink = [&patched, &ctx](const unsigned char* data, size_t len) {
+ SHA1_Update(&ctx, data, len);
+ patched.data.insert(patched.data.end(), data, data + len);
return len;
};
- SHA_CTX ctx;
- SHA1_Init(&ctx);
-
int result;
if (use_bsdiff) {
- result =
- ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), *patch, 0, sink, &ctx);
+ result = ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), patch, 0, sink);
} else {
- result = ApplyImagePatch(source_file.data.data(), source_file.data.size(), *patch, sink, &ctx,
- bonus_data);
+ result =
+ ApplyImagePatch(source_file.data.data(), source_file.data.size(), patch, sink, bonus_data);
}
if (result != 0) {
- printf("applying patch failed\n");
- return 1;
+ LOG(ERROR) << "Failed to apply the patch: " << result;
+ return false;
}
- uint8_t current_target_sha1[SHA_DIGEST_LENGTH];
- SHA1_Final(current_target_sha1, &ctx);
- if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) {
- printf("patch did not produce expected sha1\n");
- return 1;
- } else {
- printf("now %s\n", short_sha1(target_sha1).c_str());
+ SHA1_Final(patched.sha1, &ctx);
+ if (memcmp(patched.sha1, expected_sha1, SHA_DIGEST_LENGTH) != 0) {
+ LOG(ERROR) << "Patching did not produce the expected SHA-1 of " << short_sha1(expected_sha1);
+
+ LOG(ERROR) << "target size " << patched.data.size() << " SHA-1 " << short_sha1(patched.sha1);
+ LOG(ERROR) << "source size " << source_file.data.size() << " SHA-1 "
+ << short_sha1(source_file.sha1);
+
+ uint8_t patch_digest[SHA_DIGEST_LENGTH];
+ SHA1(reinterpret_cast<const uint8_t*>(patch.data.data()), patch.data.size(), patch_digest);
+ LOG(ERROR) << "patch size " << patch.data.size() << " SHA-1 " << short_sha1(patch_digest);
+
+ if (bonus_data != nullptr) {
+ uint8_t bonus_digest[SHA_DIGEST_LENGTH];
+ SHA1(reinterpret_cast<const uint8_t*>(bonus_data->data.data()), bonus_data->data.size(),
+ bonus_digest);
+ LOG(ERROR) << "bonus size " << bonus_data->data.size() << " SHA-1 "
+ << short_sha1(bonus_digest);
+ }
+
+ return false;
}
+ LOG(INFO) << " now " << short_sha1(expected_sha1);
+
// Write back the temp file to the partition.
- if (WriteToPartition(reinterpret_cast<const unsigned char*>(memory_sink_str.c_str()),
- memory_sink_str.size(), target_filename) != 0) {
- printf("write of patched data to %s failed\n", target_filename.c_str());
- return 1;
+ if (!WriteBufferToPartition(patched, target)) {
+ LOG(ERROR) << "Failed to write patched data to " << target.name;
+ return false;
}
// Delete the backup copy of the source.
- unlink(CacheLocation::location().cache_temp_source().c_str());
+ unlink(Paths::Get().cache_temp_source().c_str());
// Success!
- return 0;
+ return true;
+}
+
+bool CheckPartition(const Partition& partition) {
+ FileContents target_file;
+ return ReadPartitionToBuffer(partition, &target_file, false);
+}
+
+Partition Partition::Parse(const std::string& input_str, std::string* err) {
+ std::vector<std::string> pieces = android::base::Split(input_str, ":");
+ if (pieces.size() != 4 || pieces[0] != "EMMC") {
+ *err = "Invalid number of tokens or non-eMMC target";
+ return {};
+ }
+
+ size_t size;
+ if (!android::base::ParseUint(pieces[2], &size) || size == 0) {
+ *err = "Failed to parse \"" + pieces[2] + "\" as byte count";
+ return {};
+ }
+
+ return Partition(pieces[1], size, pieces[3]);
+}
+
+std::string Partition::ToString() const {
+ if (*this) {
+ return "EMMC:"s + name + ":" + std::to_string(size) + ":" + hash;
+ }
+ return "<invalid-partition>";
+}
+
+std::ostream& operator<<(std::ostream& os, const Partition& partition) {
+ os << partition.ToString();
+ return os;
}
diff --git a/applypatch/applypatch_main.cpp b/applypatch/applypatch_main.cpp
index 197077c..92d2b3f 100644
--- a/applypatch/applypatch_main.cpp
+++ b/applypatch/applypatch_main.cpp
@@ -16,13 +16,10 @@
#include "applypatch_modes.h"
-// This program (applypatch) applies binary patches to files in a way that
-// is safe (the original file is not touched until we have the desired
-// replacement for it) and idempotent (it's okay to run this program
-// multiple times).
-//
-// See the comments to applypatch_modes() function.
+#include <android-base/logging.h>
+// See the comments for applypatch() function.
int main(int argc, char** argv) {
- return applypatch_modes(argc, const_cast<const char**>(argv));
+ android::base::InitLogging(argv);
+ return applypatch_modes(argc, argv);
}
diff --git a/applypatch/applypatch_modes.cpp b/applypatch/applypatch_modes.cpp
index aa32d57..b466598 100644
--- a/applypatch/applypatch_modes.cpp
+++ b/applypatch/applypatch_modes.cpp
@@ -16,6 +16,7 @@
#include "applypatch_modes.h"
+#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -25,6 +26,8 @@
#include <string>
#include <vector>
+#include <android-base/file.h>
+#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include <openssl/sha.h>
@@ -32,157 +35,153 @@
#include "applypatch/applypatch.h"
#include "edify/expr.h"
-static int CheckMode(int argc, const char** argv) {
- if (argc < 3) {
+static int CheckMode(const std::string& target_emmc) {
+ std::string err;
+ auto target = Partition::Parse(target_emmc, &err);
+ if (!target) {
+ LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err;
+ return 2;
+ }
+ return CheckPartition(target) ? 0 : 1;
+}
+
+static int FlashMode(const std::string& target_emmc, const std::string& source_file) {
+ std::string err;
+ auto target = Partition::Parse(target_emmc, &err);
+ if (!target) {
+ LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err;
+ return 2;
+ }
+ return FlashPartition(target, source_file) ? 0 : 1;
+}
+
+static int PatchMode(const std::string& target_emmc, const std::string& source_emmc,
+ const std::string& patch_file, const std::string& bonus_file) {
+ std::string err;
+ auto target = Partition::Parse(target_emmc, &err);
+ if (!target) {
+ LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err;
+ return 2;
+ }
+
+ auto source = Partition::Parse(source_emmc, &err);
+ if (!source) {
+ LOG(ERROR) << "Failed to parse source \"" << source_emmc << "\": " << err;
+ return 2;
+ }
+
+ std::string patch_contents;
+ if (!android::base::ReadFileToString(patch_file, &patch_contents)) {
+ PLOG(ERROR) << "Failed to read patch file \"" << patch_file << "\"";
+ return 1;
+ }
+
+ Value patch(Value::Type::BLOB, std::move(patch_contents));
+ std::unique_ptr<Value> bonus;
+ if (!bonus_file.empty()) {
+ std::string bonus_contents;
+ if (!android::base::ReadFileToString(bonus_file, &bonus_contents)) {
+ PLOG(ERROR) << "Failed to read bonus file \"" << bonus_file << "\"";
+ return 1;
+ }
+ bonus = std::make_unique<Value>(Value::Type::BLOB, std::move(bonus_contents));
+ }
+
+ return PatchPartition(target, source, patch, bonus.get()) ? 0 : 1;
+}
+
+static void Usage() {
+ printf(
+ "Usage: \n"
+ "check mode\n"
+ " applypatch --check EMMC:<target-file>:<target-size>:<target-sha1>\n\n"
+ "flash mode\n"
+ " applypatch --flash <source-file>\n"
+ " --target EMMC:<target-file>:<target-size>:<target-sha1>\n\n"
+ "patch mode\n"
+ " applypatch [--bonus <bonus-file>]\n"
+ " --patch <patch-file>\n"
+ " --target EMMC:<target-file>:<target-size>:<target-sha1>\n"
+ " --source EMMC:<source-file>:<source-size>:<source-sha1>\n\n"
+ "show license\n"
+ " applypatch --license\n"
+ "\n\n");
+}
+
+int applypatch_modes(int argc, char* argv[]) {
+ static constexpr struct option OPTIONS[]{
+ // clang-format off
+ { "bonus", required_argument, nullptr, 0 },
+ { "check", required_argument, nullptr, 0 },
+ { "flash", required_argument, nullptr, 0 },
+ { "license", no_argument, nullptr, 0 },
+ { "patch", required_argument, nullptr, 0 },
+ { "source", required_argument, nullptr, 0 },
+ { "target", required_argument, nullptr, 0 },
+ { nullptr, 0, nullptr, 0 },
+ // clang-format on
+ };
+
+ std::string check_target;
+ std::string source;
+ std::string target;
+ std::string patch;
+ std::string bonus;
+
+ bool check_mode = false;
+ bool flash_mode = false;
+ bool patch_mode = false;
+
+ optind = 1;
+
+ int arg;
+ int option_index;
+ while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
+ switch (arg) {
+ case 0: {
+ std::string option = OPTIONS[option_index].name;
+ if (option == "bonus") {
+ bonus = optarg;
+ } else if (option == "check") {
+ check_target = optarg;
+ check_mode = true;
+ } else if (option == "flash") {
+ source = optarg;
+ flash_mode = true;
+ } else if (option == "license") {
+ return ShowLicenses();
+ } else if (option == "patch") {
+ patch = optarg;
+ patch_mode = true;
+ } else if (option == "source") {
+ source = optarg;
+ } else if (option == "target") {
+ target = optarg;
+ }
+ break;
+ }
+ case '?':
+ default:
+ LOG(ERROR) << "Invalid argument";
+ Usage();
return 2;
}
- std::vector<std::string> sha1;
- for (int i = 3; i < argc; i++) {
- sha1.push_back(argv[i]);
+ }
+
+ if (check_mode) {
+ return CheckMode(check_target);
+ }
+ if (flash_mode) {
+ if (!bonus.empty()) {
+ LOG(ERROR) << "bonus file not supported in flash mode";
+ return 1;
}
+ return FlashMode(target, source);
+ }
+ if (patch_mode) {
+ return PatchMode(target, source, patch, bonus);
+ }
- return applypatch_check(argv[2], sha1);
-}
-
-// Parse arguments (which should be of the form "<sha1>:<filename>" into the
-// new parallel arrays *sha1s and *files. Returns true on success.
-static bool ParsePatchArgs(int argc, const char** argv, std::vector<std::string>* sha1s,
- std::vector<FileContents>* files) {
- if (sha1s == nullptr) {
- return false;
- }
- for (int i = 0; i < argc; ++i) {
- std::vector<std::string> pieces = android::base::Split(argv[i], ":");
- if (pieces.size() != 2) {
- printf("failed to parse patch argument \"%s\"\n", argv[i]);
- return false;
- }
-
- uint8_t digest[SHA_DIGEST_LENGTH];
- if (ParseSha1(pieces[0].c_str(), digest) != 0) {
- printf("failed to parse sha1 \"%s\"\n", argv[i]);
- return false;
- }
-
- sha1s->push_back(pieces[0]);
- FileContents fc;
- if (LoadFileContents(pieces[1].c_str(), &fc) != 0) {
- return false;
- }
- files->push_back(std::move(fc));
- }
- return true;
-}
-
-static int FlashMode(const char* src_filename, const char* tgt_filename,
- const char* tgt_sha1, size_t tgt_size) {
- return applypatch_flash(src_filename, tgt_filename, tgt_sha1, tgt_size);
-}
-
-static int PatchMode(int argc, const char** argv) {
- FileContents bonusFc;
- Value bonus(VAL_INVALID, "");
-
- if (argc >= 3 && strcmp(argv[1], "-b") == 0) {
- if (LoadFileContents(argv[2], &bonusFc) != 0) {
- printf("failed to load bonus file %s\n", argv[2]);
- return 1;
- }
- bonus.type = VAL_BLOB;
- bonus.data = std::string(bonusFc.data.cbegin(), bonusFc.data.cend());
- argc -= 2;
- argv += 2;
- }
-
- if (argc < 4) {
- return 2;
- }
-
- size_t target_size;
- if (!android::base::ParseUint(argv[4], &target_size) || target_size == 0) {
- printf("can't parse \"%s\" as byte count\n\n", argv[4]);
- return 1;
- }
-
- // If no <src-sha1>:<patch> is provided, it is in flash mode.
- if (argc == 5) {
- if (bonus.type != VAL_INVALID) {
- printf("bonus file not supported in flash mode\n");
- return 1;
- }
- return FlashMode(argv[1], argv[2], argv[3], target_size);
- }
-
- std::vector<std::string> sha1s;
- std::vector<FileContents> files;
- if (!ParsePatchArgs(argc-5, argv+5, &sha1s, &files)) {
- printf("failed to parse patch args\n");
- return 1;
- }
-
- std::vector<std::unique_ptr<Value>> patches;
- for (size_t i = 0; i < files.size(); ++i) {
- patches.push_back(std::make_unique<Value>(
- VAL_BLOB, std::string(files[i].data.cbegin(), files[i].data.cend())));
- }
- return applypatch(argv[1], argv[2], argv[3], target_size, sha1s, patches, &bonus);
-}
-
-// This program (applypatch) applies binary patches to files in a way that
-// is safe (the original file is not touched until we have the desired
-// replacement for it) and idempotent (it's okay to run this program
-// multiple times).
-//
-// - if the sha1 hash of <tgt-file> is <tgt-sha1>, does nothing and exits
-// successfully.
-//
-// - otherwise, if no <src-sha1>:<patch> is provided, flashes <tgt-file> with
-// <src-file>. <tgt-file> must be a partition name, while <src-file> must
-// be a regular image file. <src-file> will not be deleted on success.
-//
-// - otherwise, if the sha1 hash of <src-file> is <src-sha1>, applies the
-// bsdiff <patch> to <src-file> to produce a new file (the type of patch
-// is automatically detected from the file header). If that new
-// file has sha1 hash <tgt-sha1>, moves it to replace <tgt-file>, and
-// exits successfully. Note that if <src-file> and <tgt-file> are
-// not the same, <src-file> is NOT deleted on success. <tgt-file>
-// may be the string "-" to mean "the same as src-file".
-//
-// - otherwise, or if any error is encountered, exits with non-zero
-// status.
-//
-// <src-file> (or <file> in check mode) may refer to an EMMC partition
-// to read the source data. See the comments for the
-// LoadPartitionContents() function for the format of such a filename.
-
-int applypatch_modes(int argc, const char** argv) {
- if (argc < 2) {
- usage:
- printf(
- "usage: %s [-b <bonus-file>] <src-file> <tgt-file> <tgt-sha1> <tgt-size> "
- "[<src-sha1>:<patch> ...]\n"
- " or %s -c <file> [<sha1> ...]\n"
- " or %s -l\n"
- "\n"
- "Filenames may be of the form\n"
- " EMMC:<partition>:<len_1>:<sha1_1>:<len_2>:<sha1_2>:...\n"
- "to specify reading from or writing to an EMMC partition.\n\n",
- argv[0], argv[0], argv[0]);
- return 2;
- }
-
- int result;
-
- if (strncmp(argv[1], "-l", 3) == 0) {
- result = ShowLicenses();
- } else if (strncmp(argv[1], "-c", 3) == 0) {
- result = CheckMode(argc, argv);
- } else {
- result = PatchMode(argc, argv);
- }
-
- if (result == 2) {
- goto usage;
- }
- return result;
+ Usage();
+ return 2;
}
diff --git a/applypatch/applypatch_modes.h b/applypatch/applypatch_modes.h
index 3d9d08d..aa60a43 100644
--- a/applypatch/applypatch_modes.h
+++ b/applypatch/applypatch_modes.h
@@ -17,6 +17,6 @@
#ifndef _APPLYPATCH_MODES_H
#define _APPLYPATCH_MODES_H
-int applypatch_modes(int argc, const char** argv);
+int applypatch_modes(int argc, char* argv[]);
#endif // _APPLYPATCH_MODES_H
diff --git a/applypatch/bspatch.cpp b/applypatch/bspatch.cpp
index 912dbbd..ba33c3a 100644
--- a/applypatch/bspatch.cpp
+++ b/applypatch/bspatch.cpp
@@ -66,18 +66,12 @@
}
int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value& patch,
- size_t patch_offset, SinkFn sink, SHA_CTX* ctx) {
- auto sha_sink = [&sink, &ctx](const uint8_t* data, size_t len) {
- len = sink(data, len);
- if (ctx) SHA1_Update(ctx, data, len);
- return len;
- };
-
+ size_t patch_offset, SinkFn sink) {
CHECK_LE(patch_offset, patch.data.size());
int result = bsdiff::bspatch(old_data, old_size,
reinterpret_cast<const uint8_t*>(&patch.data[patch_offset]),
- patch.data.size() - patch_offset, sha_sink);
+ patch.data.size() - patch_offset, sink);
if (result != 0) {
LOG(ERROR) << "bspatch failed, result: " << result;
// print SHA1 of the patch in the case of a data error.
diff --git a/applypatch/freecache.cpp b/applypatch/freecache.cpp
index ea364d8..3868ef2 100644
--- a/applypatch/freecache.cpp
+++ b/applypatch/freecache.cpp
@@ -14,31 +14,35 @@
* limitations under the License.
*/
+#include <dirent.h>
#include <errno.h>
-#include <libgen.h>
+#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <unistd.h>
-#include <dirent.h>
-#include <ctype.h>
+#include <algorithm>
+#include <limits>
#include <memory>
#include <set>
#include <string>
+#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 "applypatch/applypatch.h"
-#include "otautil/cache_location.h"
+#include "otautil/paths.h"
-static int EliminateOpenFiles(std::set<std::string>* files) {
+static int EliminateOpenFiles(const std::string& dirname, std::set<std::string>* files) {
std::unique_ptr<DIR, decltype(&closedir)> d(opendir("/proc"), closedir);
if (!d) {
- printf("error opening /proc: %s\n", strerror(errno));
+ PLOG(ERROR) << "Failed to open /proc";
return -1;
}
struct dirent* de;
@@ -52,7 +56,7 @@
struct dirent* fdde;
std::unique_ptr<DIR, decltype(&closedir)> fdd(opendir(path.c_str()), closedir);
if (!fdd) {
- printf("error opening %s: %s\n", path.c_str(), strerror(errno));
+ PLOG(ERROR) << "Failed to open " << path;
continue;
}
while ((fdde = readdir(fdd.get())) != 0) {
@@ -62,9 +66,9 @@
int count = readlink(fd_path.c_str(), link, sizeof(link)-1);
if (count >= 0) {
link[count] = '\0';
- if (strncmp(link, "/cache/", 7) == 0) {
+ if (android::base::StartsWith(link, dirname)) {
if (files->erase(link) > 0) {
- printf("%s is open by %s\n", link, de->d_name);
+ LOG(INFO) << link << " is open by " << de->d_name;
}
}
}
@@ -73,77 +77,171 @@
return 0;
}
-static std::set<std::string> FindExpendableFiles() {
- std::set<std::string> files;
- // We're allowed to delete unopened regular files in any of these
- // directories.
- const char* dirs[2] = {"/cache", "/cache/recovery/otatest"};
+static std::vector<std::string> FindExpendableFiles(
+ const std::string& dirname, const std::function<bool(const std::string&)>& name_filter) {
+ std::unique_ptr<DIR, decltype(&closedir)> d(opendir(dirname.c_str()), closedir);
+ if (!d) {
+ PLOG(ERROR) << "Failed to open " << dirname;
+ return {};
+ }
- for (size_t i = 0; i < sizeof(dirs)/sizeof(dirs[0]); ++i) {
- std::unique_ptr<DIR, decltype(&closedir)> d(opendir(dirs[i]), closedir);
- if (!d) {
- printf("error opening %s: %s\n", dirs[i], strerror(errno));
+ // Look for regular files in the directory (not in any subdirectories).
+ std::set<std::string> files;
+ struct dirent* de;
+ while ((de = readdir(d.get())) != 0) {
+ std::string path = dirname + "/" + de->d_name;
+
+ // We can't delete cache_temp_source; if it's there we might have restarted during
+ // installation and could be depending on it to be there.
+ if (path == Paths::Get().cache_temp_source()) {
continue;
}
- // Look for regular files in the directory (not in any subdirectories).
- struct dirent* de;
- while ((de = readdir(d.get())) != 0) {
- std::string path = std::string(dirs[i]) + "/" + de->d_name;
+ // Do not delete the file if it doesn't have the expected format.
+ if (name_filter != nullptr && !name_filter(de->d_name)) {
+ continue;
+ }
- // We can't delete cache_temp_source; if it's there we might have restarted during
- // installation and could be depending on it to be there.
- if (path == CacheLocation::location().cache_temp_source()) {
- continue;
- }
-
- struct stat st;
- if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
- files.insert(path);
- }
+ struct stat st;
+ if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
+ files.insert(path);
}
}
- printf("%zu regular files in deletable directories\n", files.size());
- if (EliminateOpenFiles(&files) < 0) {
- return std::set<std::string>();
+ LOG(INFO) << files.size() << " regular files in deletable directory";
+ if (EliminateOpenFiles(dirname, &files) < 0) {
+ return {};
}
- return files;
+
+ return std::vector<std::string>(files.begin(), files.end());
}
-int MakeFreeSpaceOnCache(size_t bytes_needed) {
-#ifndef __ANDROID__
- // TODO (xunchang) implement a heuristic cache size check during host simulation.
- printf("Skip making (%zu) bytes free space on cache; program is running on host\n", bytes_needed);
- return 0;
-#endif
-
- size_t free_now = FreeSpaceForFile("/cache");
- printf("%zu bytes free on /cache (%zu needed)\n", free_now, bytes_needed);
-
- if (free_now >= bytes_needed) {
+// Parses the index of given log file, e.g. 3 for last_log.3; returns max number if the log name
+// doesn't have the expected format so that we'll delete these ones first.
+static unsigned int GetLogIndex(const std::string& log_name) {
+ if (log_name == "last_log" || log_name == "last_kmsg") {
return 0;
}
- std::set<std::string> files = FindExpendableFiles();
- if (files.empty()) {
- // nothing we can delete to free up space!
- printf("no files can be deleted to free space on /cache\n");
+
+ unsigned int index;
+ if (sscanf(log_name.c_str(), "last_log.%u", &index) == 1 ||
+ sscanf(log_name.c_str(), "last_kmsg.%u", &index) == 1) {
+ return index;
+ }
+
+ return std::numeric_limits<unsigned int>::max();
+}
+
+// Returns the amount of free space (in bytes) on the filesystem containing filename, or -1 on
+// error.
+static int64_t FreeSpaceForFile(const std::string& filename) {
+ struct statfs sf;
+ if (statfs(filename.c_str(), &sf) == -1) {
+ PLOG(ERROR) << "Failed to statfs " << filename;
return -1;
}
- // We could try to be smarter about which files to delete: the
- // biggest ones? the smallest ones that will free up enough space?
- // the oldest? the newest?
- //
- // Instead, we'll be dumb.
+ auto f_bsize = static_cast<int64_t>(sf.f_bsize);
+ auto free_space = sf.f_bsize * sf.f_bavail;
+ if (f_bsize == 0 || free_space / f_bsize != static_cast<int64_t>(sf.f_bavail)) {
+ LOG(ERROR) << "Invalid block size or overflow (sf.f_bsize " << sf.f_bsize << ", sf.f_bavail "
+ << sf.f_bavail << ")";
+ return -1;
+ }
+ return free_space;
+}
- for (const auto& file : files) {
- unlink(file.c_str());
- free_now = FreeSpaceForFile("/cache");
- printf("deleted %s; now %zu bytes free\n", file.c_str(), free_now);
- if (free_now < bytes_needed) {
- break;
+bool CheckAndFreeSpaceOnCache(size_t bytes) {
+#ifndef __ANDROID__
+ // TODO(xunchang): Implement a heuristic cache size check during host simulation.
+ LOG(WARNING) << "Skipped making (" << bytes
+ << ") bytes free space on /cache; program is running on host";
+ return true;
+#endif
+
+ std::vector<std::string> dirs{ "/cache", Paths::Get().cache_log_directory() };
+ for (const auto& dirname : dirs) {
+ if (RemoveFilesInDirectory(bytes, dirname, FreeSpaceForFile)) {
+ return true;
}
}
- return (free_now >= bytes_needed) ? 0 : -1;
+
+ return false;
+}
+
+bool RemoveFilesInDirectory(size_t bytes_needed, const std::string& dirname,
+ const std::function<int64_t(const std::string&)>& space_checker) {
+ // The requested size cannot exceed max int64_t.
+ if (static_cast<uint64_t>(bytes_needed) >
+ static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) {
+ LOG(ERROR) << "Invalid arg of bytes_needed: " << bytes_needed;
+ return false;
+ }
+
+ struct stat st;
+ if (stat(dirname.c_str(), &st) == -1) {
+ PLOG(ERROR) << "Failed to stat " << dirname;
+ return false;
+ }
+ if (!S_ISDIR(st.st_mode)) {
+ LOG(ERROR) << dirname << " is not a directory";
+ return false;
+ }
+
+ int64_t free_now = space_checker(dirname);
+ if (free_now == -1) {
+ LOG(ERROR) << "Failed to check free space for " << dirname;
+ return false;
+ }
+ LOG(INFO) << free_now << " bytes free on " << dirname << " (" << bytes_needed << " needed)";
+
+ if (free_now >= static_cast<int64_t>(bytes_needed)) {
+ return true;
+ }
+
+ std::vector<std::string> files;
+ if (dirname == Paths::Get().cache_log_directory()) {
+ // Deletes the log files only.
+ auto log_filter = [](const std::string& file_name) {
+ return android::base::StartsWith(file_name, "last_log") ||
+ android::base::StartsWith(file_name, "last_kmsg");
+ };
+
+ files = FindExpendableFiles(dirname, log_filter);
+
+ // Older logs will come to the top of the queue.
+ auto comparator = [](const std::string& name1, const std::string& name2) -> bool {
+ unsigned int index1 = GetLogIndex(android::base::Basename(name1));
+ unsigned int index2 = GetLogIndex(android::base::Basename(name2));
+ if (index1 == index2) {
+ return name1 < name2;
+ }
+
+ return index1 > index2;
+ };
+
+ std::sort(files.begin(), files.end(), comparator);
+ } else {
+ // We're allowed to delete unopened regular files in the directory.
+ files = FindExpendableFiles(dirname, nullptr);
+ }
+
+ for (const auto& file : files) {
+ if (unlink(file.c_str()) == -1) {
+ PLOG(ERROR) << "Failed to delete " << file;
+ continue;
+ }
+
+ free_now = space_checker(dirname);
+ if (free_now == -1) {
+ LOG(ERROR) << "Failed to check free space for " << dirname;
+ return false;
+ }
+ LOG(INFO) << "Deleted " << file << "; now " << free_now << " bytes free";
+ if (free_now >= static_cast<int64_t>(bytes_needed)) {
+ return true;
+ }
+ }
+
+ return false;
}
diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp
index 674cc2b..cb34d46 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -462,12 +462,12 @@
target_len_(tgt.GetRawDataLength()),
target_uncompressed_len_(tgt.DataLengthForPatch()),
target_compress_level_(tgt.GetCompressLevel()),
- data_(tgt.DataForPatch(), tgt.DataForPatch() + tgt.DataLengthForPatch()) {}
+ data_(tgt.GetRawData(), tgt.GetRawData() + tgt.GetRawDataLength()) {}
// Return true if raw data is smaller than the patch size.
bool PatchChunk::RawDataIsSmaller(const ImageChunk& tgt, size_t patch_size) {
size_t target_len = tgt.GetRawDataLength();
- return (tgt.GetType() == CHUNK_NORMAL && (target_len <= 160 || target_len < patch_size));
+ return target_len < patch_size || (tgt.GetType() == CHUNK_NORMAL && target_len <= 160);
}
void PatchChunk::UpdateSourceOffset(const SortedRangeSet& src_range) {
@@ -675,7 +675,7 @@
// 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;
diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp
index 3682d61..f4c33e5 100644
--- a/applypatch/imgpatch.cpp
+++ b/applypatch/imgpatch.cpp
@@ -38,6 +38,7 @@
#include <zlib.h>
#include "edify/expr.h"
+#include "otautil/print_sha1.h"
static inline int64_t Read8(const void *address) {
return android::base::get_unaligned<int64_t>(address);
@@ -51,8 +52,9 @@
// patched data and stream the deflated data to output.
static bool ApplyBSDiffPatchAndStreamOutput(const uint8_t* src_data, size_t src_len,
const Value& patch, size_t patch_offset,
- const char* deflate_header, SinkFn sink, SHA_CTX* ctx) {
+ const char* deflate_header, SinkFn sink) {
size_t expected_target_length = static_cast<size_t>(Read8(deflate_header + 32));
+ CHECK_GT(expected_target_length, static_cast<size_t>(0));
int level = Read4(deflate_header + 40);
int method = Read4(deflate_header + 44);
int window_bits = Read4(deflate_header + 48);
@@ -77,7 +79,7 @@
size_t total_written = 0;
static constexpr size_t buffer_size = 32768;
auto compression_sink = [&strm, &actual_target_length, &expected_target_length, &total_written,
- &ret, &ctx, &sink](const uint8_t* data, size_t len) -> size_t {
+ &ret, &sink](const uint8_t* data, size_t len) -> size_t {
// The input patch length for an update never exceeds INT_MAX.
strm.avail_in = len;
strm.next_in = data;
@@ -102,15 +104,13 @@
LOG(ERROR) << "Failed to write " << have << " compressed bytes to output.";
return 0;
}
- if (ctx) SHA1_Update(ctx, buffer.data(), have);
} while ((strm.avail_in != 0 || strm.avail_out == 0) && ret != Z_STREAM_END);
actual_target_length += len;
return len;
};
- int bspatch_result =
- ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink, nullptr);
+ int bspatch_result = ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink);
deflateEnd(&strm);
if (bspatch_result != 0) {
@@ -127,19 +127,20 @@
<< actual_target_length;
return false;
}
- LOG(DEBUG) << "bspatch writes " << total_written << " bytes in total to streaming output.";
+ LOG(DEBUG) << "bspatch wrote " << total_written << " bytes in total to streaming output.";
return true;
}
int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data,
size_t patch_size, SinkFn sink) {
- Value patch(VAL_BLOB, std::string(reinterpret_cast<const char*>(patch_data), patch_size));
- return ApplyImagePatch(old_data, old_size, patch, sink, nullptr, nullptr);
+ Value patch(Value::Type::BLOB,
+ std::string(reinterpret_cast<const char*>(patch_data), patch_size));
+ return ApplyImagePatch(old_data, old_size, patch, sink, nullptr);
}
int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& patch, SinkFn sink,
- SHA_CTX* ctx, const Value* bonus_data) {
+ const Value* bonus_data) {
if (patch.data.size() < 12) {
printf("patch too short to contain header\n");
return -1;
@@ -180,10 +181,12 @@
printf("source data too short\n");
return -1;
}
- if (ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, ctx) != 0) {
+ if (ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink) != 0) {
printf("Failed to apply bsdiff patch.\n");
return -1;
}
+
+ LOG(DEBUG) << "Processed chunk type normal";
} else if (type == CHUNK_RAW) {
const char* raw_header = patch_header + pos;
pos += 4;
@@ -198,14 +201,13 @@
printf("failed to read chunk %d raw data\n", i);
return -1;
}
- if (ctx) {
- SHA1_Update(ctx, patch_header + pos, data_len);
- }
if (sink(reinterpret_cast<const unsigned char*>(patch_header + pos), data_len) != data_len) {
printf("failed to write chunk %d raw data\n", i);
return -1;
}
pos += data_len;
+
+ LOG(DEBUG) << "Processed chunk type raw";
} else if (type == CHUNK_DEFLATE) {
// deflate chunks have an additional 60 bytes in their chunk header.
const char* deflate_header = patch_header + pos;
@@ -228,11 +230,10 @@
// Decompress the source data; the chunk header tells us exactly
// how big we expect it to be when decompressed.
- // Note: expanded_len will include the bonus data size if
- // the patch was constructed with bonus data. The
- // deflation will come up 'bonus_size' bytes short; these
- // must be appended from the bonus_data value.
- size_t bonus_size = (i == 1 && bonus_data != NULL) ? bonus_data->data.size() : 0;
+ // Note: expanded_len will include the bonus data size if the patch was constructed with
+ // bonus data. The deflation will come up 'bonus_size' bytes short; these must be appended
+ // from the bonus_data value.
+ size_t bonus_size = (i == 1 && bonus_data != nullptr) ? bonus_data->data.size() : 0;
std::vector<unsigned char> expanded_source(expanded_len);
@@ -270,17 +271,18 @@
inflateEnd(&strm);
if (bonus_size) {
- memcpy(expanded_source.data() + (expanded_len - bonus_size), &bonus_data->data[0],
+ memcpy(expanded_source.data() + (expanded_len - bonus_size), bonus_data->data.data(),
bonus_size);
}
}
if (!ApplyBSDiffPatchAndStreamOutput(expanded_source.data(), expanded_len, patch,
- patch_offset, deflate_header, sink, ctx)) {
+ patch_offset, deflate_header, sink)) {
LOG(ERROR) << "Fail to apply streaming bspatch.";
return -1;
}
+ LOG(DEBUG) << "Processed chunk type deflate";
} else {
printf("patch chunk %d is unknown type %d\n", i, type);
return -1;
diff --git a/applypatch/include/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h
index 912ead1..6fc6f0f 100644
--- a/applypatch/include/applypatch/applypatch.h
+++ b/applypatch/include/applypatch/applypatch.h
@@ -21,6 +21,7 @@
#include <functional>
#include <memory>
+#include <ostream>
#include <string>
#include <vector>
@@ -39,45 +40,91 @@
// applypatch.cpp
int ShowLicenses();
-size_t FreeSpaceForFile(const char* filename);
-int CacheSizeCheck(size_t bytes);
-int ParseSha1(const char* str, uint8_t* digest);
-int applypatch(const char* source_filename,
- const char* target_filename,
- const char* target_sha1_str,
- size_t target_size,
- const std::vector<std::string>& patch_sha1_str,
- const std::vector<std::unique_ptr<Value>>& patch_data,
- const Value* bonus_data);
-int applypatch_check(const char* filename,
- const std::vector<std::string>& patch_sha1_str);
-int applypatch_flash(const char* source_filename, const char* target_filename,
- const char* target_sha1_str, size_t target_size);
+// Parses a given string of 40 hex digits into 20-byte array 'digest'. 'str' may contain only the
+// digest or be of the form "<digest>:<anything>". Returns 0 on success, or -1 on any error.
+int ParseSha1(const std::string& str, uint8_t* digest);
-int LoadFileContents(const char* filename, FileContents* file);
-int SaveFileContents(const char* filename, const FileContents* file);
+struct Partition {
+ Partition() = default;
+
+ Partition(const std::string& name, size_t size, const std::string& hash)
+ : name(name), size(size), hash(hash) {}
+
+ // Parses and returns the given string into a Partition object. The input string is of the form
+ // "EMMC:<device>:<size>:<hash>". Returns the parsed Partition, or an empty object on error.
+ static Partition Parse(const std::string& partition, std::string* err);
+
+ std::string ToString() const;
+
+ // Returns whether the current Partition object is valid.
+ explicit operator bool() const {
+ return !name.empty();
+ }
+
+ std::string name;
+ size_t size;
+ std::string hash;
+};
+
+std::ostream& operator<<(std::ostream& os, const Partition& partition);
+
+// Applies the given 'patch' to the 'source' Partition, verifies then writes the patching result to
+// 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.
+bool PatchPartition(const Partition& target, const Partition& source, const Value& patch,
+ const Value* bonus);
+
+// 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.
+bool PatchPartitionCheck(const Partition& target, const Partition& source);
+
+// Checks whether the contents of the given partition has the desired hash. It will NOT look for
+// the backup on /cache if the given partition doesn't have the expected checksum.
+bool CheckPartition(const Partition& target);
+
+// Flashes a given image in 'source_filename' to the eMMC target partition. It verifies the target
+// checksum first, and will return if target already has the desired hash. Otherwise it checks the
+// checksum of the given source image, flashes, and verifies the target partition afterwards. The
+// function is idempotent. Returns the flashing result.
+bool FlashPartition(const Partition& target, const std::string& source_filename);
+
+// Reads a file into memory; stores the file contents and associated metadata in *file.
+bool LoadFileContents(const std::string& filename, FileContents* file);
+
+// Saves the given FileContents object to the given filename.
+bool SaveFileContents(const std::string& filename, const FileContents* file);
// bspatch.cpp
void ShowBSDiffLicense();
// Applies the bsdiff-patch given in 'patch' (from offset 'patch_offset' to the end) to the source
-// data given by (old_data, old_size). Writes the patched output through the given 'sink', and
-// updates the SHA-1 context with the output data. Returns 0 on success.
+// data given by (old_data, old_size). Writes the patched output through the given 'sink'. Returns
+// 0 on success.
int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value& patch,
- size_t patch_offset, SinkFn sink, SHA_CTX* ctx);
+ size_t patch_offset, SinkFn sink);
// imgpatch.cpp
// Applies the imgdiff-patch given in 'patch' to the source data given by (old_data, old_size), with
-// the optional bonus data. Writes the patched output through the given 'sink', and updates the
-// SHA-1 context with the output data. Returns 0 on success.
+// the optional bonus data. Writes the patched output through the given 'sink'. Returns 0 on
+// success.
int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& patch, SinkFn sink,
- SHA_CTX* ctx, const Value* bonus_data);
+ const Value* bonus_data);
// freecache.cpp
-int MakeFreeSpaceOnCache(size_t bytes_needed);
+// Checks whether /cache partition has at least 'bytes'-byte free space. Returns true immediately
+// if so. Otherwise, it will try to free some space by removing older logs, checks again and
+// returns the checking result.
+bool CheckAndFreeSpaceOnCache(size_t bytes);
+// Removes the files in |dirname| until we have at least |bytes_needed| bytes of free space on the
+// partition. |space_checker| should return the size of the free space, or -1 on error.
+bool RemoveFilesInDirectory(size_t bytes_needed, const std::string& dirname,
+ const std::function<int64_t(const std::string&)>& space_checker);
#endif
diff --git a/applypatch/include/applypatch/imgdiff_image.h b/applypatch/include/applypatch/imgdiff_image.h
index 0848072..6716051 100644
--- a/applypatch/include/applypatch/imgdiff_image.h
+++ b/applypatch/include/applypatch/imgdiff_image.h
@@ -44,6 +44,8 @@
int GetType() const {
return type_;
}
+
+ const uint8_t* GetRawData() const;
size_t GetRawDataLength() const {
return raw_data_len_;
}
@@ -99,7 +101,6 @@
bsdiff::SuffixArrayIndexInterface** bsdiff_cache);
private:
- const uint8_t* GetRawData() const;
bool TryReconstruction(int level);
int type_; // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW
diff --git a/boot_control/Android.bp b/boot_control/Android.bp
new file mode 100644
index 0000000..7720ead
--- /dev/null
+++ b/boot_control/Android.bp
@@ -0,0 +1,37 @@
+//
+// 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.
+//
+
+cc_library_shared {
+ name: "bootctrl.default",
+ recovery_available: true,
+ relative_install_path: "hw",
+
+ srcs: ["boot_control.cpp"],
+
+ cflags: [
+ "-D_FILE_OFFSET_BITS=64",
+ "-Werror",
+ "-Wall",
+ "-Wextra",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libbootloader_message",
+ "libfs_mgr",
+ "liblog",
+ ],
+}
diff --git a/boot_control/Android.mk b/boot_control/Android.mk
deleted file mode 100644
index 9814d71..0000000
--- a/boot_control/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# 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.
-#
-
-LOCAL_PATH := $(my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := bootctrl.bcb
-LOCAL_MODULE_RELATIVE_PATH := hw
-LOCAL_SRC_FILES := boot_control.cpp
-LOCAL_CFLAGS := \
- -D_FILE_OFFSET_BITS=64 \
- -Werror \
- -Wall \
- -Wextra
-LOCAL_SHARED_LIBRARIES := liblog
-LOCAL_STATIC_LIBRARIES := libbootloader_message libfs_mgr libbase
-LOCAL_POST_INSTALL_CMD := \
- $(hide) mkdir -p $(TARGET_OUT_SHARED_LIBRARIES)/hw && \
- ln -sf bootctrl.bcb.so $(TARGET_OUT_SHARED_LIBRARIES)/hw/bootctrl.default.so
-include $(BUILD_SHARED_LIBRARY)
diff --git a/bootloader_message/Android.bp b/bootloader_message/Android.bp
index c81c67b..e76305f 100644
--- a/bootloader_message/Android.bp
+++ b/bootloader_message/Android.bp
@@ -14,16 +14,36 @@
// limitations under the License.
//
-cc_library_static {
+cc_library {
name: "libbootloader_message",
+ recovery_available: true,
+ host_supported: true,
srcs: ["bootloader_message.cpp"],
cflags: [
"-Wall",
"-Werror",
],
- static_libs: [
+ shared_libs: [
"libbase",
- "libfs_mgr",
],
export_include_dirs: ["include"],
+
+ target: {
+ android: {
+ shared_libs: [
+ "libfs_mgr",
+ ],
+ },
+ host: {
+ shared_libs: [
+ "libcutils", // for strlcpy
+ ],
+ static_libs: [
+ "libfstab",
+ ],
+ },
+ darwin: {
+ enabled: false,
+ },
+ }
}
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index aaeffdc..331a42b 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -27,21 +27,29 @@
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
-#include <fs_mgr.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 get_misc_blk_device(std::string* err) {
- std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(),
- fs_mgr_free_fstab);
- if (!fstab) {
+ Fstab fstab;
+ if (!ReadDefaultFstab(&fstab)) {
*err = "failed to read default fstab";
return "";
}
- fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab.get(), "/misc");
- if (record == nullptr) {
- *err = "failed to find /misc partition";
- return "";
+ for (const auto& entry : fstab) {
+ if (entry.mount_point == "/misc") {
+ return entry.blk_device;
+ }
}
- return record->blk_device;
+
+ *err = "failed to find /misc partition";
+ return "";
}
// In recovery mode, recovery can get started and try to access the misc
@@ -164,6 +172,14 @@
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)) {
@@ -182,13 +198,15 @@
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 95c19ae..2207d4c 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -207,6 +207,11 @@
// 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/common.h b/common.h
index 8b336f8..a524a41 100644
--- a/common.h
+++ b/common.h
@@ -14,11 +14,7 @@
* limitations under the License.
*/
-#ifndef RECOVERY_COMMON_H
-#define RECOVERY_COMMON_H
-
-#include <stdio.h>
-#include <stdarg.h>
+#pragma once
#include <string>
@@ -27,9 +23,11 @@
static constexpr int kRecoveryApiVersion = 3;
class RecoveryUI;
+struct selabel_handle;
+extern struct selabel_handle* sehandle;
extern RecoveryUI* ui;
-extern bool modified_flash;
+extern bool has_cache;
// The current stage, e.g. "1/2".
extern std::string stage;
@@ -37,13 +35,4 @@
// The reason argument provided in "--reason=".
extern const char* reason;
-// fopen a file, mounting volumes and making parent dirs as necessary.
-FILE* fopen_path(const char *path, const char *mode);
-
-void ui_print(const char* format, ...);
-
bool is_ro_debuggable();
-
-bool reboot(const std::string& command);
-
-#endif // RECOVERY_COMMON_H
diff --git a/device.cpp b/device.cpp
deleted file mode 100644
index f881daf..0000000
--- a/device.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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 "device.h"
-
-static const char* MENU_ITEMS[] = {
- "Reboot system now",
- "Reboot to bootloader",
- "Apply update from ADB",
- "Apply update from SD card",
- "Wipe data/factory reset",
-#ifndef AB_OTA_UPDATER
- "Wipe cache partition",
-#endif // !AB_OTA_UPDATER
- "Mount /system",
- "View recovery logs",
- "Run graphics test",
- "Run locale test",
- "Power off",
- nullptr,
-};
-
-static const Device::BuiltinAction MENU_ACTIONS[] = {
- Device::REBOOT,
- Device::REBOOT_BOOTLOADER,
- Device::APPLY_ADB_SIDELOAD,
- Device::APPLY_SDCARD,
- Device::WIPE_DATA,
-#ifndef AB_OTA_UPDATER
- Device::WIPE_CACHE,
-#endif // !AB_OTA_UPDATER
- Device::MOUNT_SYSTEM,
- Device::VIEW_RECOVERY_LOGS,
- Device::RUN_GRAPHICS_TEST,
- Device::RUN_LOCALE_TEST,
- Device::SHUTDOWN,
-};
-
-static_assert(sizeof(MENU_ITEMS) / sizeof(MENU_ITEMS[0]) ==
- sizeof(MENU_ACTIONS) / sizeof(MENU_ACTIONS[0]) + 1,
- "MENU_ITEMS and MENU_ACTIONS should have the same length, "
- "except for the extra NULL entry in MENU_ITEMS.");
-
-const char* const* Device::GetMenuItems() {
- return MENU_ITEMS;
-}
-
-Device::BuiltinAction Device::InvokeMenuItem(int menu_position) {
- return menu_position < 0 ? NO_ACTION : MENU_ACTIONS[menu_position];
-}
-
-int Device::HandleMenuKey(int key, bool visible) {
- if (!visible) {
- return kNoAction;
- }
-
- switch (key) {
- case KEY_DOWN:
- case KEY_VOLUMEDOWN:
- return kHighlightDown;
-
- case KEY_UP:
- case KEY_VOLUMEUP:
- return kHighlightUp;
-
- case KEY_ENTER:
- case KEY_POWER:
- return kInvokeItem;
-
- default:
- // If you have all of the above buttons, any other buttons
- // are ignored. Otherwise, any button cycles the highlight.
- return ui_->HasThreeButtons() ? kNoAction : kHighlightDown;
- }
-}
diff --git a/device.h b/device.h
deleted file mode 100644
index 74745b3..0000000
--- a/device.h
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2011 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.
- */
-
-#ifndef _RECOVERY_DEVICE_H
-#define _RECOVERY_DEVICE_H
-
-#include "ui.h"
-
-class Device {
- public:
- explicit Device(RecoveryUI* ui) : ui_(ui) {}
- virtual ~Device() {}
-
- // Called to obtain the UI object that should be used to display the recovery user interface for
- // this device. You should not have called Init() on the UI object already, the caller will do
- // that after this method returns.
- virtual RecoveryUI* GetUI() {
- return ui_;
- }
-
- // Called when recovery starts up (after the UI has been obtained and initialized and after the
- // arguments have been parsed, but before anything else).
- virtual void StartRecovery() {};
-
- // Called from the main thread when recovery is at the main menu and waiting for input, and a key
- // is pressed. (Note that "at" the main menu does not necessarily mean the menu is visible;
- // recovery will be at the main menu with it invisible after an unsuccessful operation [ie OTA
- // package failure], or if recovery is started with no command.)
- //
- // 'key' is the code of the key just pressed. (You can call IsKeyPressed() on the RecoveryUI
- // object you returned from GetUI if you want to find out if other keys are held down.)
- //
- // 'visible' is true if the menu is visible.
- //
- // Returns one of the defined constants below in order to:
- //
- // - move the menu highlight (kHighlight{Up,Down})
- // - invoke the highlighted item (kInvokeItem)
- // - do nothing (kNoAction)
- // - invoke a specific action (a menu position: any non-negative number)
- virtual int HandleMenuKey(int key, bool visible);
-
- enum BuiltinAction {
- NO_ACTION = 0,
- REBOOT = 1,
- APPLY_SDCARD = 2,
- // APPLY_CACHE was 3.
- APPLY_ADB_SIDELOAD = 4,
- WIPE_DATA = 5,
- WIPE_CACHE = 6,
- REBOOT_BOOTLOADER = 7,
- SHUTDOWN = 8,
- VIEW_RECOVERY_LOGS = 9,
- MOUNT_SYSTEM = 10,
- RUN_GRAPHICS_TEST = 11,
- RUN_LOCALE_TEST = 12,
- };
-
- // Return the list of menu items (an array of strings, NULL-terminated). The menu_position passed
- // to InvokeMenuItem will correspond to the indexes into this array.
- virtual const char* const* GetMenuItems();
-
- // Perform a recovery action selected from the menu. 'menu_position' will be the item number of
- // the selected menu item, or a non-negative number returned from HandleMenuKey(). The menu will
- // be hidden when this is called; implementations can call ui_print() to print information to the
- // screen. If the menu position is one of the builtin actions, you can just return the
- // corresponding enum value. If it is an action specific to your device, you actually perform it
- // here and return NO_ACTION.
- virtual BuiltinAction InvokeMenuItem(int menu_position);
-
- static const int kNoAction = -1;
- static const int kHighlightUp = -2;
- static const int kHighlightDown = -3;
- static const int kInvokeItem = -4;
-
- // Called before and after we do a wipe data/factory reset operation, either via a reboot from the
- // main system with the --wipe_data flag, or when the user boots into recovery image manually and
- // selects the option from the menu, to perform whatever device-specific wiping actions as needed.
- // Returns true on success; returning false from PreWipeData will prevent the regular wipe, and
- // returning false from PostWipeData will cause the wipe to be considered a failure.
- virtual bool PreWipeData() {
- return true;
- }
-
- virtual bool PostWipeData() {
- return true;
- }
-
- private:
- RecoveryUI* ui_;
-};
-
-// The device-specific library must define this function (or the default one will be used, if there
-// is no device-specific library). It returns the Device object that recovery should use.
-Device* make_device();
-
-#endif // _DEVICE_H
diff --git a/edify/expr.cpp b/edify/expr.cpp
index 6823b73..c090eb2 100644
--- a/edify/expr.cpp
+++ b/edify/expr.cpp
@@ -51,9 +51,9 @@
if (!v) {
return false;
}
- if (v->type != VAL_STRING) {
- ErrorAbort(state, kArgsParsingFailure, "expecting string, got value type %d", v->type);
- return false;
+ if (v->type != Value::Type::STRING) {
+ ErrorAbort(state, kArgsParsingFailure, "expecting string, got value type %d", v->type);
+ return false;
}
*result = v->data;
@@ -68,7 +68,7 @@
if (str == nullptr) {
return nullptr;
}
- return new Value(VAL_STRING, str);
+ return new Value(Value::Type::STRING, str);
}
Value* StringValue(const std::string& str) {
diff --git a/edify/include/edify/expr.h b/edify/include/edify/expr.h
index 770d1cf..5cbd5e1 100644
--- a/edify/include/edify/expr.h
+++ b/edify/include/edify/expr.h
@@ -53,19 +53,16 @@
bool is_retry = false;
};
-enum ValueType {
- VAL_INVALID = -1,
- VAL_STRING = 1,
- VAL_BLOB = 2,
-};
-
struct Value {
- ValueType type;
- std::string data;
+ enum class Type {
+ STRING = 1,
+ BLOB = 2,
+ };
- Value(ValueType type, const std::string& str) :
- type(type),
- data(str) {}
+ Value(Type type, const std::string& str) : type(type), data(str) {}
+
+ Type type;
+ std::string data;
};
struct Expr;
@@ -156,6 +153,6 @@
Value* StringValue(const std::string& str);
-int parse_string(const char* str, std::unique_ptr<Expr>* root, int* error_count);
+int ParseString(const std::string& str, std::unique_ptr<Expr>* root, int* error_count);
#endif // _EXPRESSION_H
diff --git a/edify/parser.yy b/edify/parser.yy
index bd2e010..3a63c37 100644
--- a/edify/parser.yy
+++ b/edify/parser.yy
@@ -138,7 +138,7 @@
++*error_count;
}
-int parse_string(const char* str, std::unique_ptr<Expr>* root, int* error_count) {
- yy_switch_to_buffer(yy_scan_string(str));
- return yyparse(root, error_count);
+int ParseString(const std::string& str, std::unique_ptr<Expr>* root, int* error_count) {
+ yy_switch_to_buffer(yy_scan_string(str.c_str()));
+ return yyparse(root, error_count);
}
diff --git a/etc/init.rc b/etc/init.rc
index 0fc6c4c..0822aba 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -6,11 +6,17 @@
start ueventd
+ setprop sys.usb.configfs 0
+
on init
export ANDROID_ROOT /system
export ANDROID_DATA /data
export EXTERNAL_STORAGE /sdcard
+ symlink /proc/self/fd/0 /dev/stdin
+ symlink /proc/self/fd/1 /dev/stdout
+ symlink /proc/self/fd/2 /dev/stderr
+
symlink /system/bin /bin
symlink /system/etc /etc
@@ -22,6 +28,7 @@
mkdir /data
mkdir /cache
mkdir /sideload
+ mkdir /mnt/system
mount tmpfs tmpfs /tmp
chown root shell /tmp
@@ -30,20 +37,6 @@
write /proc/sys/kernel/panic_on_oops 1
write /proc/sys/vm/max_map_count 1000000
-on fs
- write /sys/class/android_usb/android0/f_ffs/aliases adb
- mkdir /dev/usb-ffs 0770 shell shell
- mkdir /dev/usb-ffs/adb 0770 shell shell
- mount functionfs adb /dev/usb-ffs/adb uid=2000,gid=2000
-
- write /sys/class/android_usb/android0/enable 0
- write /sys/class/android_usb/android0/idVendor 18D1
- write /sys/class/android_usb/android0/idProduct D001
- write /sys/class/android_usb/android0/functions adb
- write /sys/class/android_usb/android0/iManufacturer ${ro.product.manufacturer}
- write /sys/class/android_usb/android0/iProduct ${ro.product.model}
- write /sys/class/android_usb/android0/iSerial ${ro.serialno}
-
on boot
ifup lo
hostname localhost
@@ -76,29 +69,119 @@
trigger early-boot
trigger boot
-service ueventd /sbin/ueventd
+service ueventd /system/bin/ueventd
critical
seclabel u:r:ueventd:s0
-service charger /charger -r
+service charger /system/bin/charger
critical
seclabel u:r:charger:s0
-service recovery /sbin/recovery
+service recovery /system/bin/recovery
+ socket recovery stream 422 system system
seclabel u:r:recovery:s0
-service adbd /sbin/adbd --root_seclabel=u:r:su:s0 --device_banner=recovery
+service adbd /system/bin/adbd --root_seclabel=u:r:su:s0 --device_banner=recovery
disabled
socket adbd stream 660 system system
seclabel u:r:adbd:s0
-# Always start adbd on userdebug and eng builds
-on property:ro.debuggable=1
- write /sys/class/android_usb/android0/enable 1
- start adbd
+service fastbootd /system/bin/fastbootd
+ disabled
+ group system
+ seclabel u:r:fastbootd:s0
# Restart adbd so it can run as root
on property:service.adb.root=1
- write /sys/class/android_usb/android0/enable 0
restart adbd
+
+# Always start adbd on userdebug and eng builds
+on fs && property:ro.debuggable=1
+ setprop sys.usb.config adb
+
+on fs && property:sys.usb.configfs=1
+ mount configfs none /config
+ mkdir /config/usb_gadget/g1 0770 shell shell
+ write /config/usb_gadget/g1/idVendor 0x18D1
+ mkdir /config/usb_gadget/g1/strings/0x409 0770
+ write /config/usb_gadget/g1/strings/0x409/serialnumber ${ro.serialno}
+ write /config/usb_gadget/g1/strings/0x409/manufacturer ${ro.product.manufacturer}
+ write /config/usb_gadget/g1/strings/0x409/product ${ro.product.model}
+ mkdir /config/usb_gadget/g1/functions/ffs.adb
+ mkdir /config/usb_gadget/g1/functions/ffs.fastboot
+ mkdir /config/usb_gadget/g1/configs/b.1 0777 shell shell
+ mkdir /config/usb_gadget/g1/configs/b.1/strings/0x409 0770 shell shell
+
+on fs && property:sys.usb.configfs=0
+ write /sys/class/android_usb/android0/f_ffs/aliases adb,fastboot
+ write /sys/class/android_usb/android0/idVendor 18D1
+ write /sys/class/android_usb/android0/iManufacturer ${ro.product.manufacturer}
+ write /sys/class/android_usb/android0/iProduct ${ro.product.model}
+ write /sys/class/android_usb/android0/iSerial ${ro.serialno}
+
+on fs
+ mkdir /dev/usb-ffs 0775 shell shell
+ mkdir /dev/usb-ffs/adb 0770 shell shell
+ mount functionfs adb /dev/usb-ffs/adb uid=2000,gid=2000
+ mkdir /dev/usb-ffs/fastboot 0770 system system
+ mount functionfs fastboot /dev/usb-ffs/fastboot rmode=0770,fmode=0660,uid=1000,gid=1000
+
+on property:sys.usb.config=adb
+ start adbd
+
+on property:sys.usb.config=fastboot
+ start fastbootd
+
+on property:sys.usb.config=none && property:sys.usb.configfs=0
+ stop adbd
+ stop fastboot
+ write /sys/class/android_usb/android0/enable 0
+ setprop sys.usb.state ${sys.usb.config}
+
+on property:sys.usb.config=adb && property:sys.usb.configfs=0
+ write /sys/class/android_usb/android0/idProduct D001
+ write /sys/class/android_usb/android0/functions adb
write /sys/class/android_usb/android0/enable 1
+ setprop sys.usb.state ${sys.usb.config}
+
+on property:sys.usb.config=sideload && property:sys.usb.configfs=0
+ write /sys/class/android_usb/android0/idProduct D001
+ write /sys/class/android_usb/android0/functions adb
+ write /sys/class/android_usb/android0/enable 1
+ setprop sys.usb.state ${sys.usb.config}
+
+on property:sys.usb.config=fastboot && property:sys.usb.configfs=0
+ write /sys/class/android_usb/android0/idProduct 4EE0
+ write /sys/class/android_usb/android0/functions fastboot
+ write /sys/class/android_usb/android0/enable 1
+ setprop sys.usb.state ${sys.usb.config}
+
+# Configfs triggers
+on property:sys.usb.config=none && property:sys.usb.configfs=1
+ write /config/usb_gadget/g1/UDC "none"
+ stop adbd
+ stop fastbootd
+ setprop sys.usb.ffs.ready 0
+ rm /config/usb_gadget/g1/configs/b.1/f1
+ setprop sys.usb.state ${sys.usb.config}
+
+on property:sys.usb.config=sideload && property:sys.usb.ffs.ready=1 && property:sys.usb.configfs=1
+ write /config/usb_gadget/g1/idProduct 0xD001
+ write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "adb"
+ symlink /config/usb_gadget/g1/functions/ffs.adb /config/usb_gadget/g1/configs/b.1/f1
+ write /config/usb_gadget/g1/UDC ${sys.usb.controller}
+ setprop sys.usb.state ${sys.usb.config}
+
+on property:sys.usb.config=adb && property:sys.usb.ffs.ready=1 && property:sys.usb.configfs=1
+ write /config/usb_gadget/g1/idProduct 0xD001
+ write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "adb"
+ symlink /config/usb_gadget/g1/functions/ffs.adb /config/usb_gadget/g1/configs/b.1/f1
+ write /config/usb_gadget/g1/UDC ${sys.usb.controller}
+ setprop sys.usb.state ${sys.usb.config}
+
+on property:sys.usb.config=fastboot && property:sys.usb.ffs.ready=1 && property:sys.usb.configfs=1
+ write /config/usb_gadget/g1/idProduct 0x4EE0
+ write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "fastboot"
+ symlink /config/usb_gadget/g1/functions/ffs.fastboot /config/usb_gadget/g1/configs/b.1/f1
+ write /config/usb_gadget/g1/UDC ${sys.usb.controller}
+ setprop sys.usb.state ${sys.usb.config}
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
new file mode 100644
index 0000000..14f5e4b
--- /dev/null
+++ b/fastboot/fastboot.cpp
@@ -0,0 +1,81 @@
+/*
+ * 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 "fastboot.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <bootloader_message/bootloader_message.h>
+
+#include "recovery_ui/ui.h"
+
+static const std::vector<std::pair<std::string, Device::BuiltinAction>> kFastbootMenuActions{
+ { "Reboot system now", Device::REBOOT },
+ { "Enter recovery", Device::ENTER_RECOVERY },
+ { "Reboot to bootloader", Device::REBOOT_BOOTLOADER },
+ { "Power off", Device::SHUTDOWN },
+};
+
+Device::BuiltinAction StartFastboot(Device* device, const std::vector<std::string>& /* args */) {
+ RecoveryUI* ui = device->GetUI();
+
+ std::vector<std::string> title_lines = { "Android Fastboot" };
+ title_lines.push_back("Product name - " + android::base::GetProperty("ro.product.device", ""));
+ title_lines.push_back("Bootloader version - " + android::base::GetProperty("ro.bootloader", ""));
+ title_lines.push_back("Baseband version - " +
+ android::base::GetProperty("ro.build.expect.baseband", ""));
+ title_lines.push_back("Serial number - " + android::base::GetProperty("ro.serialno", ""));
+ title_lines.push_back(std::string("Secure boot - ") +
+ ((android::base::GetProperty("ro.secure", "") == "1") ? "yes" : "no"));
+ title_lines.push_back("HW version - " + android::base::GetProperty("ro.revision", ""));
+
+ ui->ResetKeyInterruptStatus();
+ ui->SetTitle(title_lines);
+ ui->ShowText(true);
+
+ // Reset to normal system boot so recovery won't cycle indefinitely.
+ // TODO(b/112277594) Clear only if 'recovery' field of BCB is empty. If not,
+ // set the 'command' field of BCB to 'boot-recovery' so the next boot is into recovery
+ // to finish any interrupted tasks.
+ std::string err;
+ if (!clear_bootloader_message(&err)) {
+ LOG(ERROR) << "Failed to clear BCB message: " << err;
+ }
+
+ std::vector<std::string> fastboot_menu_items;
+ std::transform(kFastbootMenuActions.cbegin(), kFastbootMenuActions.cend(),
+ std::back_inserter(fastboot_menu_items),
+ [](const auto& entry) { return entry.first; });
+
+ auto chosen_item = ui->ShowMenu(
+ {}, fastboot_menu_items, 0, false,
+ std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
+
+ if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
+ return Device::KEY_INTERRUPTED;
+ }
+ if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::TIMED_OUT)) {
+ return Device::BuiltinAction::NO_ACTION;
+ }
+ return kFastbootMenuActions[chosen_item].second;
+}
diff --git a/default_device.cpp b/fastboot/fastboot.h
similarity index 70%
copy from default_device.cpp
copy to fastboot/fastboot.h
index a971866..1aa7de6 100644
--- a/default_device.cpp
+++ b/fastboot/fastboot.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * 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.
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-#include "device.h"
-#include "screen_ui.h"
+#pragma once
-Device* make_device() {
- return new Device(new ScreenRecoveryUI);
-}
+#include <string>
+#include <vector>
+
+#include "recovery_ui/device.h"
+
+Device::BuiltinAction StartFastboot(Device* device, const std::vector<std::string>& args);
diff --git a/fsck_unshare_blocks.cpp b/fsck_unshare_blocks.cpp
new file mode 100644
index 0000000..9dc0fd8
--- /dev/null
+++ b/fsck_unshare_blocks.cpp
@@ -0,0 +1,151 @@
+/*
+ * 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 "fsck_unshare_blocks.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <spawn.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/unique_fd.h>
+#include <fs_mgr/roots.h>
+
+#include "otautil/roots.h"
+
+static constexpr const char* SYSTEM_E2FSCK_BIN = "/system/bin/e2fsck_static";
+static constexpr const char* TMP_E2FSCK_BIN = "/tmp/e2fsck.bin";
+
+static bool copy_file(const char* source, const char* dest) {
+ android::base::unique_fd source_fd(open(source, O_RDONLY));
+ if (source_fd < 0) {
+ PLOG(ERROR) << "open %s failed" << source;
+ return false;
+ }
+
+ android::base::unique_fd dest_fd(open(dest, O_CREAT | O_WRONLY, S_IRWXU));
+ if (dest_fd < 0) {
+ PLOG(ERROR) << "open %s failed" << dest;
+ return false;
+ }
+
+ for (;;) {
+ char buf[4096];
+ ssize_t rv = read(source_fd, buf, sizeof(buf));
+ if (rv < 0) {
+ PLOG(ERROR) << "read failed";
+ return false;
+ }
+ if (rv == 0) {
+ break;
+ }
+ if (write(dest_fd, buf, rv) != rv) {
+ PLOG(ERROR) << "write failed";
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool run_e2fsck(const std::string& partition) {
+ Volume* volume = volume_for_mount_point(partition);
+ if (!volume) {
+ LOG(INFO) << "No fstab entry for " << partition << ", skipping.";
+ return true;
+ }
+
+ LOG(INFO) << "Running e2fsck on device " << volume->blk_device;
+
+ std::vector<std::string> args = { TMP_E2FSCK_BIN, "-p", "-E", "unshare_blocks",
+ volume->blk_device };
+ std::vector<char*> argv(args.size());
+ std::transform(args.cbegin(), args.cend(), argv.begin(),
+ [](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
+ argv.push_back(nullptr);
+
+ pid_t child;
+ char* env[] = { nullptr };
+ if (posix_spawn(&child, argv[0], nullptr, nullptr, argv.data(), env)) {
+ PLOG(ERROR) << "posix_spawn failed";
+ return false;
+ }
+
+ int status = 0;
+ int ret = TEMP_FAILURE_RETRY(waitpid(child, &status, 0));
+ if (ret < 0) {
+ PLOG(ERROR) << "waitpid failed";
+ return false;
+ }
+ if (!WIFEXITED(status)) {
+ LOG(ERROR) << "e2fsck exited abnormally: " << status;
+ return false;
+ }
+ int return_code = WEXITSTATUS(status);
+ if (return_code >= 8) {
+ LOG(ERROR) << "e2fsck could not unshare blocks: " << return_code;
+ return false;
+ }
+
+ LOG(INFO) << "Successfully unshared blocks on " << partition;
+ return true;
+}
+
+bool do_fsck_unshare_blocks() {
+ // List of partitions we will try to e2fsck -E unshare_blocks.
+ std::vector<std::string> partitions = { "/odm", "/oem", "/product", "/vendor" };
+
+ // Temporarily mount system so we can copy e2fsck_static.
+ auto system_root = android::fs_mgr::GetSystemRoot();
+ bool mounted = ensure_path_mounted_at(system_root, "/mnt/system") != -1;
+ partitions.push_back(system_root);
+
+ if (!mounted) {
+ LOG(ERROR) << "Failed to mount system image.";
+ return false;
+ }
+ if (!copy_file(SYSTEM_E2FSCK_BIN, TMP_E2FSCK_BIN)) {
+ LOG(ERROR) << "Could not copy e2fsck to /tmp.";
+ return false;
+ }
+ if (umount("/mnt/system") < 0) {
+ PLOG(ERROR) << "umount failed";
+ return false;
+ }
+
+ bool ok = true;
+ for (const auto& partition : partitions) {
+ ok &= run_e2fsck(partition);
+ }
+
+ if (ok) {
+ LOG(INFO) << "Finished running e2fsck.";
+ } else {
+ LOG(ERROR) << "Finished running e2fsck, but not all partitions succceeded.";
+ }
+ return ok;
+}
diff --git a/default_device.cpp b/fsck_unshare_blocks.h
similarity index 76%
copy from default_device.cpp
copy to fsck_unshare_blocks.h
index a971866..9de8ef9 100644
--- a/default_device.cpp
+++ b/fsck_unshare_blocks.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * 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.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-#include "device.h"
-#include "screen_ui.h"
+#ifndef _FILESYSTEM_CMDS_H
+#define _FILESYSTEM_CMDS_H
-Device* make_device() {
- return new Device(new ScreenRecoveryUI);
-}
+bool do_fsck_unshare_blocks();
+
+#endif // _FILESYSTEM_CMDS_H
diff --git a/fuse_sdcard_provider.cpp b/fuse_sdcard_provider.cpp
deleted file mode 100644
index 46bdf17..0000000
--- a/fuse_sdcard_provider.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2014 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 "fuse_sdcard_provider.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mount.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include <functional>
-
-#include <android-base/file.h>
-
-#include "fuse_sideload.h"
-
-struct file_data {
- int fd; // the underlying sdcard file
-
- uint64_t file_size;
- uint32_t block_size;
-};
-
-static int read_block_file(const file_data& fd, uint32_t block, uint8_t* buffer,
- uint32_t fetch_size) {
- off64_t offset = static_cast<off64_t>(block) * fd.block_size;
- if (TEMP_FAILURE_RETRY(lseek64(fd.fd, offset, SEEK_SET)) == -1) {
- fprintf(stderr, "seek on sdcard failed: %s\n", strerror(errno));
- return -EIO;
- }
-
- if (!android::base::ReadFully(fd.fd, buffer, fetch_size)) {
- fprintf(stderr, "read on sdcard failed: %s\n", strerror(errno));
- return -EIO;
- }
-
- return 0;
-}
-
-bool start_sdcard_fuse(const char* path) {
- struct stat sb;
- if (stat(path, &sb) == -1) {
- fprintf(stderr, "failed to stat %s: %s\n", path, strerror(errno));
- return false;
- }
-
- file_data fd;
- fd.fd = open(path, O_RDONLY);
- if (fd.fd == -1) {
- fprintf(stderr, "failed to open %s: %s\n", path, strerror(errno));
- return false;
- }
- fd.file_size = sb.st_size;
- fd.block_size = 65536;
-
- provider_vtab vtab;
- vtab.read_block = std::bind(&read_block_file, fd, std::placeholders::_1, std::placeholders::_2,
- std::placeholders::_3);
- vtab.close = [&fd]() { close(fd.fd); };
-
- // 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);
-
- return run_fuse_sideload(vtab, fd.file_size, fd.block_size) == 0;
-}
diff --git a/fuse_sdcard_provider.h b/fuse_sdcard_provider.h
deleted file mode 100644
index bdc60f2..0000000
--- a/fuse_sdcard_provider.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2014 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.
- */
-
-#ifndef __FUSE_SDCARD_PROVIDER_H
-#define __FUSE_SDCARD_PROVIDER_H
-
-bool start_sdcard_fuse(const char* path);
-
-#endif
diff --git a/fuse_sideload/Android.bp b/fuse_sideload/Android.bp
new file mode 100644
index 0000000..9bf19eb
--- /dev/null
+++ b/fuse_sideload/Android.bp
@@ -0,0 +1,45 @@
+// 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.
+
+cc_library {
+ name: "libfusesideload",
+ recovery_available: true,
+
+ defaults: [
+ "recovery_defaults",
+ ],
+
+ cflags: [
+ "-D_XOPEN_SOURCE",
+ "-D_GNU_SOURCE",
+ ],
+
+ srcs: [
+ "fuse_provider.cpp",
+ "fuse_sideload.cpp",
+ ],
+
+ export_include_dirs: [
+ "include",
+ ],
+
+ static_libs: [
+ "libotautil",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libcrypto",
+ ],
+}
diff --git a/fuse_sideload/fuse_provider.cpp b/fuse_sideload/fuse_provider.cpp
new file mode 100644
index 0000000..5ee6e24
--- /dev/null
+++ b/fuse_sideload/fuse_provider.cpp
@@ -0,0 +1,150 @@
+/*
+ * 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 "fuse_provider.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#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;
+ if (stat(path.c_str(), &sb) == -1) {
+ fprintf(stderr, "failed to stat %s: %s\n", path.c_str(), strerror(errno));
+ return;
+ }
+
+ fd_.reset(open(path.c_str(), O_RDONLY));
+ if (fd_ == -1) {
+ fprintf(stderr, "failed to open %s: %s\n", path.c_str(), strerror(errno));
+ return;
+ }
+ file_size_ = sb.st_size;
+ fuse_block_size_ = 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_;
+ if (fetch_size > file_size_ || offset > file_size_ - fetch_size) {
+ fprintf(stderr,
+ "Out of bound read, start block: %" PRIu32 ", fetch size: %" PRIu32
+ ", file size %" PRIu64 "\n",
+ start_block, fetch_size, file_size_);
+ return false;
+ }
+
+ if (!android::base::ReadFullyAtOffset(fd_, buffer, fetch_size, offset)) {
+ fprintf(stderr, "Failed to read fetch size: %" PRIu32 " bytes data at offset %" PRIu64 ": %s\n",
+ fetch_size, offset, strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+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<FuseBlockDataProvider> 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.cpp b/fuse_sideload/fuse_sideload.cpp
similarity index 96%
rename from fuse_sideload.cpp
rename to fuse_sideload/fuse_sideload.cpp
index 1c7e98f..3d94803 100644
--- a/fuse_sideload.cpp
+++ b/fuse_sideload/fuse_sideload.cpp
@@ -76,7 +76,7 @@
struct fuse_data {
android::base::unique_fd ffd; // file descriptor for the fuse socket
- provider_vtab vtab;
+ FuseDataProvider* provider; // Provider of the source data.
uint64_t file_size; // bytes
@@ -236,7 +236,7 @@
return 0;
}
- size_t fetch_size = fd->block_size;
+ uint32_t fetch_size = fd->block_size;
if (block * fd->block_size + fetch_size > fd->file_size) {
// If we're reading the last (partial) block of the file, expect a shorter response from the
// host, and pad the rest of the block with zeroes.
@@ -244,8 +244,9 @@
memset(fd->block_data + fetch_size, 0, fd->block_size - fetch_size);
}
- int result = fd->vtab.read_block(block, fd->block_data, fetch_size);
- if (result < 0) return result;
+ if (!fd->provider->ReadBlockAlignedData(fd->block_data, fetch_size, block)) {
+ return -EIO;
+ }
fd->curr_block = block;
@@ -340,12 +341,14 @@
return NO_STATUS;
}
-int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t block_size,
- const char* mount_point) {
+int run_fuse_sideload(std::unique_ptr<FuseDataProvider>&& provider, const char* mount_point) {
// If something's already mounted on our mountpoint, try to remove it. (Mostly in case of a
// previous abnormal exit.)
umount2(mount_point, MNT_FORCE);
+ uint64_t file_size = provider->file_size();
+ uint32_t block_size = provider->fuse_block_size();
+
// fs/fuse/inode.c in kernel code uses the greater of 4096 and the passed-in max_read.
if (block_size < 4096) {
fprintf(stderr, "block size (%u) is too small\n", block_size);
@@ -357,7 +360,7 @@
}
fuse_data fd = {};
- fd.vtab = vtab;
+ fd.provider = provider.get();
fd.file_size = file_size;
fd.block_size = block_size;
fd.file_blocks = (file_size == 0) ? 0 : (((file_size - 1) / block_size) + 1);
@@ -389,7 +392,7 @@
}
fd.ffd.reset(open("/dev/fuse", O_RDWR));
- if (!fd.ffd) {
+ if (fd.ffd == -1) {
perror("open /dev/fuse");
result = -1;
goto done;
@@ -479,7 +482,7 @@
}
done:
- fd.vtab.close();
+ provider->Close();
if (umount2(mount_point, MNT_DETACH) == -1) {
fprintf(stderr, "fuse_sideload umount failed: %s\n", strerror(errno));
diff --git a/fuse_sideload/include/fuse_provider.h b/fuse_sideload/include/fuse_provider.h
new file mode 100644
index 0000000..8d4ea40
--- /dev/null
+++ b/fuse_sideload/include/fuse_provider.h
@@ -0,0 +1,100 @@
+/*
+ * 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 <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:
+ FuseDataProvider(uint64_t file_size, uint32_t block_size)
+ : file_size_(file_size), fuse_block_size_(block_size) {}
+
+ virtual ~FuseDataProvider() = default;
+
+ uint64_t file_size() const {
+ return file_size_;
+ }
+ uint32_t fuse_block_size() const {
+ return fuse_block_size_;
+ }
+
+ // Reads |fetch_size| bytes data starting from |start_block|. Puts the result in |buffer|.
+ virtual bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+ uint32_t start_block) const = 0;
+
+ virtual void Close() {}
+
+ protected:
+ FuseDataProvider() = default;
+
+ // Size in bytes of the file to read.
+ uint64_t file_size_ = 0;
+ // Block size passed to the fuse, this is different from the block size of the block device.
+ uint32_t fuse_block_size_ = 0;
+};
+
+// This class reads data from a file.
+class FuseFileDataProvider : public FuseDataProvider {
+ public:
+ FuseFileDataProvider(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 {
+ return fd_ != -1;
+ }
+
+ void Close() override;
+
+ private:
+ // 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<FuseBlockDataProvider> 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;
+ 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/fuse_sideload.h b/fuse_sideload/include/fuse_sideload.h
similarity index 80%
rename from fuse_sideload.h
rename to fuse_sideload/include/fuse_sideload.h
index 1b34cbd..1b7759a 100644
--- a/fuse_sideload.h
+++ b/fuse_sideload/include/fuse_sideload.h
@@ -17,7 +17,9 @@
#ifndef __FUSE_SIDELOAD_H
#define __FUSE_SIDELOAD_H
-#include <functional>
+#include <memory>
+
+#include "fuse_provider.h"
// Define the filenames created by the sideload FUSE filesystem.
static constexpr const char* FUSE_SIDELOAD_HOST_MOUNTPOINT = "/sideload";
@@ -26,15 +28,7 @@
static constexpr const char* FUSE_SIDELOAD_HOST_EXIT_FLAG = "exit";
static constexpr const char* FUSE_SIDELOAD_HOST_EXIT_PATHNAME = "/sideload/exit";
-struct provider_vtab {
- // read a block
- std::function<int(uint32_t block, uint8_t* buffer, uint32_t fetch_size)> read_block;
-
- // close down
- std::function<void(void)> close;
-};
-
-int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t block_size,
+int run_fuse_sideload(std::unique_ptr<FuseDataProvider>&& provider,
const char* mount_point = FUSE_SIDELOAD_HOST_MOUNTPOINT);
#endif
diff --git a/install.h b/install.h
deleted file mode 100644
index f3fda30..0000000
--- a/install.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2007 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.
- */
-
-#ifndef RECOVERY_INSTALL_H_
-#define RECOVERY_INSTALL_H_
-
-#include <string>
-#include <ziparchive/zip_archive.h>
-
-enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE, INSTALL_SKIPPED,
- INSTALL_RETRY };
-
-// Installs the given update package. If INSTALL_SUCCESS is returned and *wipe_cache is true on
-// exit, caller should wipe the cache partition.
-int install_package(const std::string& package, bool* wipe_cache, const std::string& install_file,
- bool needs_mount, int retry_count);
-
-// Verify the package by ota keys. Return true if the package is verified successfully,
-// otherwise return false.
-bool verify_package(const unsigned char* package_data, size_t package_size);
-
-// Read meta data file of the package, write its content in the string pointed by meta_data.
-// Return true if succeed, otherwise return false.
-bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata);
-
-// 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);
-
-#endif // RECOVERY_INSTALL_H_
diff --git a/install/Android.bp b/install/Android.bp
new file mode 100644
index 0000000..4696e50
--- /dev/null
+++ b/install/Android.bp
@@ -0,0 +1,83 @@
+// 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: "libinstall_defaults",
+
+ defaults: [
+ "recovery_defaults",
+ ],
+
+ header_libs: [
+ "libminadbd_headers",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libbootloader_message",
+ "libcrypto",
+ "libext4_utils",
+ "libfs_mgr",
+ "libfusesideload",
+ "libhidl-gen-utils",
+ "libhidlbase",
+ "libhidltransport",
+ "liblog",
+ "libselinux",
+ "libtinyxml2",
+ "libutils",
+ "libz",
+ "libziparchive",
+ ],
+
+ static_libs: [
+ "libotautil",
+
+ // external dependencies
+ "libvintf_recovery",
+ "libvintf",
+ ],
+}
+
+cc_library_static {
+ name: "libinstall",
+ recovery_available: true,
+
+ defaults: [
+ "libinstall_defaults",
+ ],
+
+ srcs: [
+ "adb_install.cpp",
+ "asn1_decoder.cpp",
+ "fuse_sdcard_install.cpp",
+ "install.cpp",
+ "package.cpp",
+ "verifier.cpp",
+ "wipe_data.cpp",
+ "wipe_device.cpp",
+ ],
+
+ shared_libs: [
+ "librecovery_ui",
+ ],
+
+ export_include_dirs: [
+ "include",
+ ],
+
+ export_shared_lib_headers: [
+ "librecovery_ui",
+ ],
+}
diff --git a/install/adb_install.cpp b/install/adb_install.cpp
new file mode 100644
index 0000000..2de1075
--- /dev/null
+++ b/install/adb_install.cpp
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2012 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/adb_install.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <atomic>
+#include <functional>
+#include <map>
+#include <utility>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/memory.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+
+#include "fuse_sideload.h"
+#include "install/install.h"
+#include "install/wipe_data.h"
+#include "minadbd_types.h"
+#include "otautil/sysutil.h"
+#include "recovery_ui/device.h"
+#include "recovery_ui/ui.h"
+
+// A CommandFunction returns a pair of (result, should_continue), which indicates the command
+// execution result and whether it should proceed to the next iteration. The execution result will
+// always be sent to the minadbd side.
+using CommandFunction = std::function<std::pair<bool, bool>()>;
+
+static bool SetUsbConfig(const std::string& state) {
+ android::base::SetProperty("sys.usb.config", state);
+ return android::base::WaitForProperty("sys.usb.state", state);
+}
+
+// Parses the minadbd command in |message|; returns MinadbdCommand::kError upon errors.
+static MinadbdCommand ParseMinadbdCommand(const std::string& message) {
+ if (!android::base::StartsWith(message, kMinadbdCommandPrefix)) {
+ LOG(ERROR) << "Failed to parse command in message " << message;
+ return MinadbdCommand::kError;
+ }
+
+ auto cmd_code_string = message.substr(strlen(kMinadbdCommandPrefix));
+ auto cmd_code = android::base::get_unaligned<uint32_t>(cmd_code_string.c_str());
+ if (cmd_code >= static_cast<uint32_t>(MinadbdCommand::kError)) {
+ LOG(ERROR) << "Unsupported command code: " << cmd_code;
+ return MinadbdCommand::kError;
+ }
+
+ return static_cast<MinadbdCommand>(cmd_code);
+}
+
+static bool WriteStatusToFd(MinadbdCommandStatus status, int fd) {
+ char message[kMinadbdMessageSize];
+ memcpy(message, kMinadbdStatusPrefix, strlen(kMinadbdStatusPrefix));
+ android::base::put_unaligned(message + strlen(kMinadbdStatusPrefix), status);
+
+ if (!android::base::WriteFully(fd, message, kMinadbdMessageSize)) {
+ PLOG(ERROR) << "Failed to write message " << message;
+ return false;
+ }
+ return true;
+}
+
+// Installs the package from FUSE. Returns the installation result and whether it should continue
+// waiting for new commands.
+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
+ // appearance. (Note that inotify doesn't work with FUSE.)
+ constexpr int ADB_INSTALL_TIMEOUT = 15;
+ bool should_continue = true;
+ *result = INSTALL_ERROR;
+ for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) {
+ struct stat st;
+ if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) {
+ if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT - 1) {
+ sleep(1);
+ continue;
+ } else {
+ should_continue = false;
+ ui->Print("\nTimed out waiting for fuse to be ready.\n\n");
+ break;
+ }
+ }
+ *result = InstallPackage(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0, ui);
+ break;
+ }
+
+ // Calling stat() on this magic filename signals the FUSE to exit.
+ struct stat st;
+ stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st);
+ return std::make_pair(*result == INSTALL_SUCCESS, should_continue);
+}
+
+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
+ // installed bootloader/recovery image.
+ switch (command) {
+ case MinadbdCommand::kRebootBootloader:
+ *reboot_action = Device::REBOOT_BOOTLOADER;
+ break;
+ case MinadbdCommand::kRebootFastboot:
+ *reboot_action = Device::REBOOT_FASTBOOT;
+ break;
+ case MinadbdCommand::kRebootRecovery:
+ *reboot_action = Device::REBOOT_RECOVERY;
+ break;
+ case MinadbdCommand::kRebootRescue:
+ *reboot_action = Device::REBOOT_RESCUE;
+ break;
+ case MinadbdCommand::kRebootAndroid:
+ default:
+ *reboot_action = Device::REBOOT;
+ break;
+ }
+ *result = INSTALL_REBOOT;
+ return std::make_pair(true, false);
+}
+
+// Parses and executes the command from minadbd. Returns whether the caller should keep waiting for
+// next command.
+static bool HandleMessageFromMinadbd(int socket_fd,
+ const std::map<MinadbdCommand, CommandFunction>& command_map) {
+ char buffer[kMinadbdMessageSize];
+ if (!android::base::ReadFully(socket_fd, buffer, kMinadbdMessageSize)) {
+ PLOG(ERROR) << "Failed to read message from minadbd";
+ return false;
+ }
+
+ std::string message(buffer, buffer + kMinadbdMessageSize);
+ auto command_type = ParseMinadbdCommand(message);
+ if (command_type == MinadbdCommand::kError) {
+ return false;
+ }
+ if (command_map.find(command_type) == command_map.end()) {
+ LOG(ERROR) << "Unsupported command: "
+ << android::base::get_unaligned<unsigned int>(
+ message.substr(strlen(kMinadbdCommandPrefix)).c_str());
+ return false;
+ }
+
+ // We have received a valid command, execute the corresponding function.
+ const auto& command_func = command_map.at(command_type);
+ const auto [result, should_continue] = command_func();
+ LOG(INFO) << "Command " << static_cast<uint32_t>(command_type) << " finished with " << result;
+ if (!WriteStatusToFd(result ? MinadbdCommandStatus::kSuccess : MinadbdCommandStatus::kFailure,
+ socket_fd)) {
+ return false;
+ }
+ return should_continue;
+}
+
+// TODO(xunchang) add a wrapper function and kill the minadbd service there.
+static void ListenAndExecuteMinadbdCommands(
+ RecoveryUI* ui, pid_t minadbd_pid, android::base::unique_fd&& socket_fd,
+ const std::map<MinadbdCommand, CommandFunction>& command_map) {
+ android::base::unique_fd epoll_fd(epoll_create1(O_CLOEXEC));
+ if (epoll_fd == -1) {
+ PLOG(ERROR) << "Failed to create epoll";
+ kill(minadbd_pid, SIGKILL);
+ return;
+ }
+
+ constexpr int EPOLL_MAX_EVENTS = 10;
+ struct epoll_event ev = {};
+ ev.events = EPOLLIN | EPOLLHUP;
+ ev.data.fd = socket_fd.get();
+ struct epoll_event events[EPOLL_MAX_EVENTS];
+ if (epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, socket_fd.get(), &ev) == -1) {
+ PLOG(ERROR) << "Failed to add socket fd to epoll";
+ kill(minadbd_pid, SIGKILL);
+ return;
+ }
+
+ // Set the timeout to be 300s when waiting for minadbd commands.
+ constexpr int TIMEOUT_MILLIS = 300 * 1000;
+ while (true) {
+ // Reset the progress bar and the background image before each command.
+ ui->SetProgressType(RecoveryUI::EMPTY);
+ ui->SetBackground(RecoveryUI::NO_COMMAND);
+
+ // Poll for the status change of the socket_fd, and handle the message if the fd is ready to
+ // read.
+ int event_count =
+ TEMP_FAILURE_RETRY(epoll_wait(epoll_fd.get(), events, EPOLL_MAX_EVENTS, TIMEOUT_MILLIS));
+ if (event_count == -1) {
+ PLOG(ERROR) << "Failed to wait for epoll events";
+ kill(minadbd_pid, SIGKILL);
+ return;
+ }
+ if (event_count == 0) {
+ LOG(ERROR) << "Timeout waiting for messages from minadbd";
+ kill(minadbd_pid, SIGKILL);
+ return;
+ }
+
+ for (int n = 0; n < event_count; n++) {
+ if (events[n].events & EPOLLHUP) {
+ LOG(INFO) << "Socket has been closed";
+ kill(minadbd_pid, SIGKILL);
+ return;
+ }
+ if (!HandleMessageFromMinadbd(socket_fd.get(), command_map)) {
+ kill(minadbd_pid, SIGKILL);
+ return;
+ }
+ }
+ }
+}
+
+// Recovery starts minadbd service as a child process, and spawns another thread to listen for the
+// message from minadbd through a socket pair. Here is an example to execute one command from adb
+// host.
+// a. recovery b. listener thread c. minadbd service
+//
+// a1. create socket pair
+// a2. fork minadbd service
+// c3. wait for the adb commands
+// from host
+// c4. after receiving host commands:
+// 1) set up pre-condition (i.e.
+// start fuse for adb sideload)
+// 2) issue command through
+// socket.
+// 3) wait for result
+// a5. start listener thread
+// b6. listen for message from
+// minadbd in a loop.
+// b7. After receiving a minadbd
+// command from socket
+// 1) execute the command function
+// 2) send the result back to
+// minadbd
+// ......
+// c8. exit upon receiving the
+// result
+// a9. wait for listener thread
+// to exit.
+//
+// a10. wait for minadbd to
+// exit
+// b11. exit the listening loop
+//
+static void CreateMinadbdServiceAndExecuteCommands(
+ RecoveryUI* ui, const std::map<MinadbdCommand, CommandFunction>& command_map,
+ bool rescue_mode) {
+ signal(SIGPIPE, SIG_IGN);
+
+ android::base::unique_fd recovery_socket;
+ android::base::unique_fd minadbd_socket;
+ if (!android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &recovery_socket, &minadbd_socket)) {
+ PLOG(ERROR) << "Failed to create socket";
+ return;
+ }
+
+ pid_t child = fork();
+ if (child == -1) {
+ PLOG(ERROR) << "Failed to fork child process";
+ return;
+ }
+ if (child == 0) {
+ recovery_socket.reset();
+ std::vector<std::string> minadbd_commands = {
+ "/system/bin/minadbd",
+ "--socket_fd",
+ std::to_string(minadbd_socket.release()),
+ };
+ if (rescue_mode) {
+ minadbd_commands.push_back("--rescue");
+ }
+ auto exec_args = StringVectorToNullTerminatedArray(minadbd_commands);
+ execv(exec_args[0], exec_args.data());
+ _exit(EXIT_FAILURE);
+ }
+
+ minadbd_socket.reset();
+
+ // We need to call SetUsbConfig() after forking minadbd service. Because the function waits for
+ // the usb state to be updated, which depends on sys.usb.ffs.ready=1 set in the adb daemon.
+ if (!SetUsbConfig("sideload")) {
+ LOG(ERROR) << "Failed to set usb config to sideload";
+ return;
+ }
+
+ std::thread listener_thread(ListenAndExecuteMinadbdCommands, ui, child,
+ std::move(recovery_socket), std::ref(command_map));
+ if (listener_thread.joinable()) {
+ listener_thread.join();
+ }
+
+ int status;
+ waitpid(child, &status, 0);
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ if (WEXITSTATUS(status) == MinadbdErrorCode::kMinadbdAdbVersionError) {
+ LOG(ERROR) << "\nYou need adb 1.0.32 or newer to sideload\nto this device.\n";
+ } else if (!WIFSIGNALED(status)) {
+ LOG(ERROR) << "\n(adbd status " << WEXITSTATUS(status) << ")";
+ }
+ }
+
+ signal(SIGPIPE, SIG_DFL);
+}
+
+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.
+ if (usb_state != "none" && !SetUsbConfig("none")) {
+ LOG(ERROR) << "Failed to clear USB config";
+ return INSTALL_ERROR;
+ }
+
+ RecoveryUI* ui = device->GetUI();
+
+ 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,
+ &install_result, reboot_action) },
+ { MinadbdCommand::kRebootBootloader,
+ std::bind(&AdbRebootHandler, MinadbdCommand::kRebootBootloader, &install_result,
+ reboot_action) },
+ { MinadbdCommand::kRebootFastboot, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootFastboot,
+ &install_result, reboot_action) },
+ { MinadbdCommand::kRebootRecovery, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRecovery,
+ &install_result, reboot_action) },
+ { MinadbdCommand::kRebootRescue,
+ std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRescue, &install_result, reboot_action) },
+ };
+
+ if (!rescue_mode) {
+ ui->Print(
+ "\n\nNow send the package you want to apply\n"
+ "to the device with \"adb sideload <filename>\"...\n");
+ } else {
+ ui->Print("\n\nWaiting for rescue commands...\n");
+ command_map.emplace(MinadbdCommand::kWipeData, [&device]() {
+ bool result = WipeData(device, false);
+ return std::make_pair(result, true);
+ });
+ }
+
+ CreateMinadbdServiceAndExecuteCommands(ui, command_map, rescue_mode);
+
+ // Clean up before switching to the older state, for example setting the state
+ // to none sets sys/class/android_usb/android0/enable to 0.
+ if (!SetUsbConfig("none")) {
+ LOG(ERROR) << "Failed to clear USB config";
+ }
+
+ if (usb_state != "none") {
+ if (!SetUsbConfig(usb_state)) {
+ LOG(ERROR) << "Failed to set USB config to " << usb_state;
+ }
+ }
+
+ return install_result;
+}
diff --git a/asn1_decoder.cpp b/install/asn1_decoder.cpp
similarity index 98%
rename from asn1_decoder.cpp
rename to install/asn1_decoder.cpp
index 285214f..2d81a6e 100644
--- a/asn1_decoder.cpp
+++ b/install/asn1_decoder.cpp
@@ -14,9 +14,7 @@
* limitations under the License.
*/
-#include "asn1_decoder.h"
-
-#include <stdint.h>
+#include "private/asn1_decoder.h"
int asn1_context::peek_byte() const {
if (length_ == 0) {
diff --git a/install/fuse_sdcard_install.cpp b/install/fuse_sdcard_install.cpp
new file mode 100644
index 0000000..a5caa6e
--- /dev/null
+++ b/install/fuse_sdcard_install.cpp
@@ -0,0 +1,205 @@
+/*
+ * 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/fuse_sdcard_install.h"
+
+#include <dirent.h>
+#include <signal.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include "bootloader_message/bootloader_message.h"
+#include "fuse_provider.h"
+#include "fuse_sideload.h"
+#include "install/install.h"
+#include "otautil/roots.h"
+
+static constexpr const char* SDCARD_ROOT = "/sdcard";
+// How long (in seconds) we wait for the fuse-provided package file to
+// appear, before timing out.
+static constexpr int SDCARD_INSTALL_TIMEOUT = 10;
+
+// Set the BCB to reboot back into recovery (it won't resume the install from
+// sdcard though).
+static void SetSdcardUpdateBootloaderMessage() {
+ std::vector<std::string> options;
+ std::string err;
+ if (!update_bootloader_message(options, &err)) {
+ LOG(ERROR) << "Failed to set BCB message: " << err;
+ }
+}
+
+// Returns the selected filename, or an empty string.
+static std::string BrowseDirectory(const std::string& path, Device* device, RecoveryUI* ui) {
+ ensure_path_mounted(path);
+
+ std::unique_ptr<DIR, decltype(&closedir)> d(opendir(path.c_str()), closedir);
+ if (!d) {
+ PLOG(ERROR) << "error opening " << path;
+ return "";
+ }
+
+ std::vector<std::string> dirs;
+ std::vector<std::string> entries{ "../" }; // "../" is always the first entry.
+
+ dirent* de;
+ while ((de = readdir(d.get())) != nullptr) {
+ std::string name(de->d_name);
+
+ if (de->d_type == DT_DIR) {
+ // Skip "." and ".." entries.
+ if (name == "." || name == "..") continue;
+ dirs.push_back(name + "/");
+ } else if (de->d_type == DT_REG && android::base::EndsWithIgnoreCase(name, ".zip")) {
+ entries.push_back(name);
+ }
+ }
+
+ std::sort(dirs.begin(), dirs.end());
+ std::sort(entries.begin(), entries.end());
+
+ // Append dirs to the entries list.
+ entries.insert(entries.end(), dirs.begin(), dirs.end());
+
+ std::vector<std::string> headers{ "Choose a package to install:", path };
+
+ size_t chosen_item = 0;
+ while (true) {
+ chosen_item = ui->ShowMenu(
+ headers, entries, chosen_item, true,
+ std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
+
+ // Return if WaitKey() was interrupted.
+ if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
+ return "";
+ }
+
+ const std::string& item = entries[chosen_item];
+ if (chosen_item == 0) {
+ // Go up but continue browsing (if the caller is BrowseDirectory).
+ return "";
+ }
+
+ std::string new_path = path + "/" + item;
+ if (new_path.back() == '/') {
+ // Recurse down into a subdirectory.
+ new_path.pop_back();
+ std::string result = BrowseDirectory(new_path, device, ui);
+ if (!result.empty()) return result;
+ } else {
+ // Selected a zip file: return the path to the caller.
+ return new_path;
+ }
+ }
+
+ // Unreachable.
+}
+
+static bool StartSdcardFuse(const std::string& path) {
+ auto file_data_reader = std::make_unique<FuseFileDataProvider>(path, 65536);
+
+ if (!file_data_reader->Valid()) {
+ 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);
+
+ return run_fuse_sideload(std::move(file_data_reader)) == 0;
+}
+
+InstallResult ApplyFromSdcard(Device* device, RecoveryUI* ui) {
+ 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;
+ }
+
+ ui->Print("\n-- Install %s ...\n", path.c_str());
+ SetSdcardUpdateBootloaderMessage();
+
+ // 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);
+
+ _exit(status ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
+
+ // 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) {
+ if (waitpid(child, &status, WNOHANG) == -1) {
+ result = INSTALL_ERROR;
+ waited = true;
+ break;
+ }
+
+ struct stat sb;
+ if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &sb) == -1) {
+ if (errno == ENOENT && i < SDCARD_INSTALL_TIMEOUT - 1) {
+ sleep(1);
+ continue;
+ } else {
+ LOG(ERROR) << "Timed out waiting for the fuse-provided package.";
+ result = INSTALL_ERROR;
+ kill(child, SIGKILL);
+ break;
+ }
+ }
+
+ result = InstallPackage(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0 /* retry_count */, ui);
+ break;
+ }
+
+ if (!waited) {
+ // Calling stat() on this magic filename signals the fuse
+ // filesystem to shut down.
+ struct stat sb;
+ stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &sb);
+
+ waitpid(child, &status, 0);
+ }
+
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ LOG(ERROR) << "Error exit from the fuse process: " << WEXITSTATUS(status);
+ }
+
+ ensure_path_unmounted(SDCARD_ROOT);
+ return result;
+}
diff --git a/adb_install.h b/install/include/install/adb_install.h
similarity index 61%
rename from adb_install.h
rename to install/include/install/adb_install.h
index e654c89..8800223 100644
--- a/adb_install.h
+++ b/install/include/install/adb_install.h
@@ -14,9 +14,12 @@
* limitations under the License.
*/
-#ifndef _ADB_INSTALL_H
-#define _ADB_INSTALL_H
+#pragma once
-int apply_from_adb(bool* wipe_cache, const char* install_file);
+#include "install/install.h"
+#include "recovery_ui/device.h"
-#endif
+// 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/default_device.cpp b/install/include/install/fuse_sdcard_install.h
similarity index 71%
copy from default_device.cpp
copy to install/include/install/fuse_sdcard_install.h
index a971866..e5bb01f 100644
--- a/default_device.cpp
+++ b/install/include/install/fuse_sdcard_install.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 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.
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-#include "device.h"
-#include "screen_ui.h"
+#pragma once
-Device* make_device() {
- return new Device(new ScreenRecoveryUI);
-}
+#include "install/install.h"
+#include "recovery_ui/device.h"
+#include "recovery_ui/ui.h"
+
+InstallResult ApplyFromSdcard(Device* device, RecoveryUI* ui);
diff --git a/install/include/install/install.h b/install/include/install/install.h
new file mode 100644
index 0000000..44a5cde
--- /dev/null
+++ b/install/include/install/install.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007 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 <stddef.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <ziparchive/zip_archive.h>
+
+#include "package.h"
+#include "recovery_ui/ui.h"
+
+enum InstallResult {
+ INSTALL_SUCCESS,
+ INSTALL_ERROR,
+ INSTALL_CORRUPT,
+ INSTALL_NONE,
+ INSTALL_SKIPPED,
+ INSTALL_RETRY,
+ INSTALL_KEY_INTERRUPTED,
+ INSTALL_REBOOT,
+};
+
+enum class OtaType {
+ AB,
+ BLOCK,
+ 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.
+InstallResult InstallPackage(const std::string& package, bool should_wipe_cache, bool needs_mount,
+ int retry_count, RecoveryUI* ui);
+
+// Verifies the package by ota keys. Returns true if the package is verified successfully,
+// otherwise returns false.
+bool verify_package(Package* package, RecoveryUI* ui);
+
+// Reads meta data file of the package; parses each line in the format "key=value"; and writes the
+// result to |metadata|. Return true if succeed, otherwise return false.
+bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata);
+
+// 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 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
new file mode 100644
index 0000000..cd44d10
--- /dev/null
+++ b/install/include/install/package.h
@@ -0,0 +1,53 @@
+/*
+ * 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 <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <ziparchive/zip_archive.h>
+
+#include "verifier.h"
+
+// 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 {
+ public:
+ static std::unique_ptr<Package> CreateMemoryPackage(
+ const std::string& path, const std::function<void(float)>& set_progress);
+ static std::unique_ptr<Package> CreateMemoryPackage(
+ std::vector<uint8_t> content, const std::function<void(float)>& set_progress);
+ static std::unique_ptr<Package> CreateFilePackage(const std::string& path,
+ const std::function<void(float)>& set_progress);
+
+ virtual ~Package() = default;
+
+ // Opens the package as a zip file and returns the ZipArchiveHandle.
+ virtual ZipArchiveHandle GetZipArchiveHandle() = 0;
+
+ // Updates the progress in fraction during package verification.
+ void SetProgress(float progress) override;
+
+ protected:
+ // An optional function to update the progress.
+ std::function<void(float)> set_progress_;
+};
diff --git a/install/include/install/verifier.h b/install/include/install/verifier.h
new file mode 100644
index 0000000..f9e9475
--- /dev/null
+++ b/install/include/install/verifier.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2008 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 <functional>
+#include <memory>
+#include <vector>
+
+#include <openssl/ec_key.h>
+#include <openssl/rsa.h>
+#include <openssl/sha.h>
+
+constexpr size_t MiB = 1024 * 1024;
+
+using HasherUpdateCallback = std::function<void(const uint8_t* addr, uint64_t size)>;
+
+struct RSADeleter {
+ void operator()(RSA* rsa) const {
+ RSA_free(rsa);
+ }
+};
+
+struct ECKEYDeleter {
+ void operator()(EC_KEY* ec_key) const {
+ EC_KEY_free(ec_key);
+ }
+};
+
+struct Certificate {
+ typedef enum {
+ KEY_TYPE_RSA,
+ KEY_TYPE_EC,
+ } KeyType;
+
+ Certificate(int hash_len_, KeyType key_type_, std::unique_ptr<RSA, RSADeleter>&& rsa_,
+ std::unique_ptr<EC_KEY, ECKEYDeleter>&& ec_)
+ : hash_len(hash_len_), key_type(key_type_), rsa(std::move(rsa_)), ec(std::move(ec_)) {}
+
+ // SHA_DIGEST_LENGTH (SHA-1) or SHA256_DIGEST_LENGTH (SHA-256)
+ int hash_len;
+ KeyType key_type;
+ std::unique_ptr<RSA, RSADeleter> rsa;
+ std::unique_ptr<EC_KEY, ECKEYDeleter> ec;
+};
+
+class VerifierInterface {
+ public:
+ virtual ~VerifierInterface() = default;
+
+ // Returns the package size in bytes.
+ virtual uint64_t GetPackageSize() const = 0;
+
+ // Reads |byte_count| data starting from |offset|, and puts the result in |buffer|.
+ virtual bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) = 0;
+
+ // Updates the hash contexts for |length| bytes data starting from |start|.
+ virtual bool UpdateHashAtOffset(const std::vector<HasherUpdateCallback>& hashers, uint64_t start,
+ uint64_t length) = 0;
+
+ // Updates the progress in fraction during package verification.
+ virtual void SetProgress(float progress) = 0;
+};
+
+// Looks for an RSA signature embedded in the .ZIP file comment given the path to the zip.
+// Verifies that it matches one of the given public keys. Returns VERIFY_SUCCESS or
+// VERIFY_FAILURE (if any error is encountered or no key matches the signature).
+int verify_file(VerifierInterface* package, const std::vector<Certificate>& keys);
+
+// Checks that the RSA key has a modulus of 2048 or 4096 bits long, and public exponent is 3 or
+// 65537.
+bool CheckRSAKey(const std::unique_ptr<RSA, RSADeleter>& rsa);
+
+// Checks that the field size of the curve for the EC key is 256 bits.
+bool CheckECKey(const std::unique_ptr<EC_KEY, ECKEYDeleter>& ec_key);
+
+// Parses a PEM-encoded x509 certificate from the given buffer and saves it into |cert|. Returns
+// false if there is a parsing failure or the signature's encryption algorithm is not supported.
+bool LoadCertificateFromBuffer(const std::vector<uint8_t>& pem_content, Certificate* cert);
+
+// Iterates over the zip entries with the suffix "x509.pem" and returns a list of recognized
+// certificates. Returns an empty list if we fail to parse any of the entries.
+std::vector<Certificate> LoadKeysFromZipfile(const std::string& zip_name);
+
+#define VERIFY_SUCCESS 0
+#define VERIFY_FAILURE 1
diff --git a/default_device.cpp b/install/include/install/wipe_data.h
similarity index 61%
copy from default_device.cpp
copy to install/include/install/wipe_data.h
index a971866..b34891f 100644
--- a/default_device.cpp
+++ b/install/include/install/wipe_data.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 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.
@@ -14,9 +14,17 @@
* limitations under the License.
*/
-#include "device.h"
-#include "screen_ui.h"
+#pragma once
-Device* make_device() {
- return new Device(new ScreenRecoveryUI);
-}
+#include <functional>
+
+#include "recovery_ui/device.h"
+#include "recovery_ui/ui.h"
+
+struct selabel_handle;
+
+// Returns true on success.
+bool WipeCache(RecoveryUI* ui, const std::function<bool()>& confirm);
+
+// Returns true on success.
+bool WipeData(Device* device, bool convert_fbe);
diff --git a/install/include/install/wipe_device.h b/install/include/install/wipe_device.h
new file mode 100644
index 0000000..c60b999
--- /dev/null
+++ b/install/include/install/wipe_device.h
@@ -0,0 +1,29 @@
+/*
+ * 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 <vector>
+
+#include "install/package.h"
+#include "recovery_ui/device.h"
+
+// 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/asn1_decoder.h b/install/include/private/asn1_decoder.h
similarity index 98%
rename from asn1_decoder.h
rename to install/include/private/asn1_decoder.h
index 3e99211..e5337d9 100644
--- a/asn1_decoder.h
+++ b/install/include/private/asn1_decoder.h
@@ -17,6 +17,7 @@
#ifndef ASN1_DECODER_H_
#define ASN1_DECODER_H_
+#include <stddef.h>
#include <stdint.h>
class asn1_context {
diff --git a/install/include/private/setup_commands.h b/install/include/private/setup_commands.h
new file mode 100644
index 0000000..dcff761
--- /dev/null
+++ b/install/include/private/setup_commands.h
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+// Private headers exposed for testing purpose only.
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <ziparchive/zip_archive.h>
+
+// Sets up the commands for a non-A/B update. Extracts the updater binary 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.
+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.
+bool SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
+ std::vector<std::string>* cmd);
diff --git a/install.cpp b/install/install.cpp
similarity index 60%
rename from install.cpp
rename to install/install.cpp
index d058931..1b51b42 100644
--- a/install.cpp
+++ b/install/install.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "install.h"
+#include "install/install.h"
#include <ctype.h>
#include <errno.h>
@@ -32,9 +32,7 @@
#include <condition_variable>
#include <functional>
#include <limits>
-#include <map>
#include <mutex>
-#include <string>
#include <thread>
#include <vector>
@@ -45,147 +43,117 @@
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
#include <vintf/VintfObjectRecovery.h>
-#include <ziparchive/zip_archive.h>
-#include "common.h"
-#include "otautil/SysUtil.h"
-#include "otautil/ThermalUtil.h"
+#include "install/package.h"
+#include "install/verifier.h"
+#include "install/wipe_data.h"
#include "otautil/error_code.h"
-#include "private/install.h"
-#include "roots.h"
-#include "ui.h"
-#include "verifier.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"
using namespace std::chrono_literals;
+static constexpr int kRecoveryApiVersion = 3;
+// 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
static constexpr int VERIFICATION_PROGRESS_TIME = 60;
static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25;
static std::condition_variable finish_log_temperature;
-// This function parses and returns the build.version.incremental
-static std::string parse_build_number(const std::string& str) {
- size_t pos = str.find('=');
- if (pos != std::string::npos) {
- return android::base::Trim(str.substr(pos+1));
- }
-
- LOG(ERROR) << "Failed to parse build number in " << str;
- return "";
-}
-
-bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata) {
+bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata) {
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;
}
uint32_t length = entry.uncompressed_length;
- metadata->resize(length, '\0');
- int32_t err = ExtractToMemory(zip, &entry, reinterpret_cast<uint8_t*>(&(*metadata)[0]), length);
+ std::string metadata_string(length, '\0');
+ int32_t err =
+ ExtractToMemory(zip, &entry, reinterpret_cast<uint8_t*>(&metadata_string[0]), length);
if (err != 0) {
LOG(ERROR) << "Failed to extract " << METADATA_PATH << ": " << ErrorCodeString(err);
return false;
}
+
+ for (const std::string& line : android::base::Split(metadata_string, "\n")) {
+ size_t eq = line.find('=');
+ if (eq != std::string::npos) {
+ metadata->emplace(android::base::Trim(line.substr(0, eq)),
+ android::base::Trim(line.substr(eq + 1)));
+ }
+ }
+
return true;
}
-// Read the build.version.incremental of src/tgt from the metadata and log it to last_install.
-static void read_source_target_build(ZipArchiveHandle zip, std::vector<std::string>* log_buffer) {
- std::string metadata;
- if (!read_metadata_from_package(zip, &metadata)) {
- return;
- }
- // Examples of the pre-build and post-build strings in metadata:
- // pre-build-incremental=2943039
- // post-build-incremental=2951741
- std::vector<std::string> lines = android::base::Split(metadata, "\n");
- for (const std::string& line : lines) {
- std::string str = android::base::Trim(line);
- if (android::base::StartsWith(str, "pre-build-incremental")) {
- std::string source_build = parse_build_number(str);
- if (!source_build.empty()) {
- log_buffer->push_back("source_build: " + source_build);
- }
- } else if (android::base::StartsWith(str, "post-build-incremental")) {
- std::string target_build = parse_build_number(str);
- if (!target_build.empty()) {
- log_buffer->push_back("target_build: " + target_build);
- }
- }
+// Gets the value for the given key in |metadata|. Returns an emtpy string if the key isn't
+// present.
+static std::string get_value(const std::map<std::string, std::string>& metadata,
+ const std::string& key) {
+ const auto& it = metadata.find(key);
+ return (it == metadata.end()) ? "" : it->second;
+}
+
+static std::string OtaTypeToString(OtaType type) {
+ switch (type) {
+ case OtaType::AB:
+ return "AB";
+ case OtaType::BLOCK:
+ return "BLOCK";
+ case OtaType::BRICK:
+ return "BRICK";
}
}
-#ifdef AB_OTA_UPDATER
-
-// Parses the metadata of the OTA package in |zip| and checks whether we are
-// allowed to accept this A/B package. Downgrading is not allowed unless
-// explicitly enabled in the package and only for incremental packages.
-static int check_newer_ab_build(ZipArchiveHandle zip) {
- std::string metadata_str;
- if (!read_metadata_from_package(zip, &metadata_str)) {
- return INSTALL_CORRUPT;
- }
- std::map<std::string, std::string> metadata;
- for (const std::string& line : android::base::Split(metadata_str, "\n")) {
- size_t eq = line.find('=');
- if (eq != std::string::npos) {
- metadata[line.substr(0, eq)] = line.substr(eq + 1);
- }
+// Read the build.version.incremental of src/tgt from the metadata and log it to last_install.
+static void ReadSourceTargetBuild(const std::map<std::string, std::string>& metadata,
+ std::vector<std::string>* log_buffer) {
+ // Examples of the pre-build and post-build strings in metadata:
+ // pre-build-incremental=2943039
+ // post-build-incremental=2951741
+ auto source_build = get_value(metadata, "pre-build-incremental");
+ if (!source_build.empty()) {
+ log_buffer->push_back("source_build: " + source_build);
}
- std::string value = android::base::GetProperty("ro.product.device", "");
- const std::string& pkg_device = metadata["pre-device"];
- if (pkg_device != value || pkg_device.empty()) {
- LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << value;
- return INSTALL_ERROR;
+ auto target_build = get_value(metadata, "post-build-incremental");
+ if (!target_build.empty()) {
+ log_buffer->push_back("target_build: " + target_build);
}
+}
- // We allow the package to not have any serialno; and we also allow it to carry multiple serial
- // numbers split by "|"; e.g. serialno=serialno1|serialno2|serialno3 ... We will fail the
- // verification if the device's serialno doesn't match any of these carried numbers.
- value = android::base::GetProperty("ro.serialno", "");
- const std::string& pkg_serial_no = metadata["serialno"];
- if (!pkg_serial_no.empty()) {
- bool match = false;
- for (const std::string& number : android::base::Split(pkg_serial_no, "|")) {
- if (value == android::base::Trim(number)) {
- match = true;
- break;
- }
- }
- if (!match) {
- LOG(ERROR) << "Package is for serial " << pkg_serial_no;
- return INSTALL_ERROR;
- }
- }
-
- if (metadata["ota-type"] != "AB") {
- LOG(ERROR) << "Package is not A/B";
- return INSTALL_ERROR;
- }
-
+// 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 bool CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata) {
// Incremental updates should match the current build.
- value = android::base::GetProperty("ro.build.version.incremental", "");
- const std::string& pkg_pre_build = metadata["pre-build-incremental"];
- if (!pkg_pre_build.empty() && pkg_pre_build != value) {
- LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected " << value;
- return INSTALL_ERROR;
+ 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 false;
}
- value = android::base::GetProperty("ro.build.fingerprint", "");
- const std::string& pkg_pre_build_fingerprint = metadata["pre-build"];
- if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != value) {
+ auto device_fingerprint = android::base::GetProperty("ro.build.fingerprint", "");
+ auto pkg_pre_build_fingerprint = get_value(metadata, "pre-build");
+ 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 "
- << value;
- return INSTALL_ERROR;
+ << device_fingerprint;
+ return false;
}
// Check for downgrade version.
@@ -194,42 +162,83 @@
int64_t pkg_post_timestamp = 0;
// We allow to full update to the same version we are running, in case there
// is a problem with the current copy of that version.
- if (metadata["post-timestamp"].empty() ||
- !android::base::ParseInt(metadata["post-timestamp"].c_str(), &pkg_post_timestamp) ||
+ auto pkg_post_timestamp_string = get_value(metadata, "post-timestamp");
+ if (pkg_post_timestamp_string.empty() ||
+ !android::base::ParseInt(pkg_post_timestamp_string, &pkg_post_timestamp) ||
pkg_post_timestamp < build_timestamp) {
- if (metadata["ota-downgrade"] != "yes") {
+ if (get_value(metadata, "ota-downgrade") != "yes") {
LOG(ERROR) << "Update package is older than the current build, expected a build "
"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 update_binary_command(const std::string& package, ZipArchiveHandle zip,
- const std::string& binary_path, int /* retry_count */, int status_fd,
- std::vector<std::string>* cmd) {
- CHECK(cmd != nullptr);
- int ret = check_newer_ab_build(zip);
- if (ret != 0) {
- return ret;
+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 true;
}
+ if (package_ota_type != expected_ota_type) {
+ LOG(ERROR) << "Unexpected ota package type, expects " << expected_ota_type << ", actual "
+ << package_ota_type;
+ 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 false;
+ }
+
+ // We allow the package to not have any serialno; and we also allow it to carry multiple serial
+ // numbers split by "|"; e.g. serialno=serialno1|serialno2|serialno3 ... We will fail the
+ // verification if the device's serialno doesn't match any of these carried numbers.
+ auto pkg_serial_no = get_value(metadata, "serialno");
+ if (!pkg_serial_no.empty()) {
+ auto device_serial_no = android::base::GetProperty("ro.serialno", "");
+ bool serial_number_match = false;
+ for (const auto& number : android::base::Split(pkg_serial_no, "|")) {
+ if (device_serial_no == android::base::Trim(number)) {
+ serial_number_match = true;
+ }
+ }
+ if (!serial_number_match) {
+ LOG(ERROR) << "Package is for serial " << pkg_serial_no;
+ return false;
+ }
+ }
+
+ if (ota_type == OtaType::AB) {
+ return CheckAbSpecificMetadata(metadata);
+ }
+
+ return true;
+}
+
+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);
@@ -237,57 +246,57 @@
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 = {
- binary_path,
+ "/system/bin/update_engine_sideload",
"--payload=file://" + package,
android::base::StringPrintf("--offset=%ld", payload_offset),
"--headers=" + std::string(payload_properties.begin(), payload_properties.end()),
android::base::StringPrintf("--status_fd=%d", status_fd),
};
- return 0;
+ return true;
}
-#else // !AB_OTA_UPDATER
-
-int update_binary_command(const std::string& package, ZipArchiveHandle zip,
- const std::string& binary_path, 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);
- // On traditional updates we extract the update binary from the package.
+ // 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();
unlink(binary_path.c_str());
- int fd = open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755);
+ android::base::unique_fd fd(
+ 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);
- close(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:
+ // - the version number for this interface
+ // - an FD to which the program can write in order to update the progress bar.
+ // - the name of the package zip file.
+ // - an optional argument "retry" if this update is a retry of a failed update attempt.
*cmd = {
binary_path,
std::to_string(kRecoveryApiVersion),
@@ -297,9 +306,8 @@
if (retry_count > 0) {
cmd->push_back("retry");
}
- return 0;
+ return true;
}
-#endif // !AB_OTA_UPDATER
static void log_max_temperature(int* max_temperature, const std::atomic<bool>& logger_finished) {
CHECK(max_temperature != nullptr);
@@ -312,89 +320,76 @@
}
// 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) {
- read_source_target_build(zip, log_buffer);
-
- int pipefd[2];
- pipe(pipefd);
-
- std::vector<std::string> args;
-#ifdef AB_OTA_UPDATER
- int ret = update_binary_command(package, zip, "/sbin/update_engine_sideload", retry_count,
- pipefd[1], &args);
-#else
- int ret = update_binary_command(package, zip, "/tmp/update-binary", retry_count, pipefd[1],
- &args);
-#endif
- if (ret) {
- close(pipefd[0]);
- close(pipefd[1]);
- log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
- return ret;
+static InstallResult TryUpdateBinary(const std::string& package, ZipArchiveHandle zip,
+ bool* wipe_cache, std::vector<std::string>* log_buffer,
+ int retry_count, int* max_temperature, RecoveryUI* ui) {
+ std::map<std::string, std::string> metadata;
+ if (!ReadMetadataFromPackage(zip, &metadata)) {
+ LOG(ERROR) << "Failed to parse metadata in the zip file";
+ return INSTALL_CORRUPT;
}
- // When executing the update binary contained in the package, the
- // arguments passed are:
+ bool is_ab = android::base::GetBoolProperty("ro.build.ab_update", false);
+ // 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 INSTALL_ERROR;
+ }
+
+ ReadSourceTargetBuild(metadata, log_buffer);
+
+ // The updater in child process writes to the pipe to communicate with recovery.
+ android::base::unique_fd pipe_read, pipe_write;
+ // Explicitly disable O_CLOEXEC using 0 as the flags (last) parameter to Pipe
+ // so that the child updater process will recieve a non-closed fd.
+ if (!android::base::Pipe(&pipe_read, &pipe_write, 0)) {
+ PLOG(ERROR) << "Failed to create pipe for updater-recovery communication";
+ return INSTALL_CORRUPT;
+ }
+
+ // The updater-recovery communication protocol.
//
- // - the version number for this interface
+ // progress <frac> <secs>
+ // fill up the next <frac> part of of the progress bar over <secs> seconds. If <secs> is
+ // zero, use `set_progress` commands to manually control the progress of this segment of the
+ // bar.
//
- // - an FD to which the program can write in order to update the
- // progress bar. The program can write single-line commands:
+ // set_progress <frac>
+ // <frac> should be between 0.0 and 1.0; sets the progress bar within the segment defined by
+ // the most recent progress command.
//
- // progress <frac> <secs>
- // fill up the next <frac> part of of the progress bar
- // over <secs> seconds. If <secs> is zero, use
- // set_progress commands to manually control the
- // progress of this segment of the bar.
+ // ui_print <string>
+ // display <string> on the screen.
//
- // set_progress <frac>
- // <frac> should be between 0.0 and 1.0; sets the
- // progress bar within the segment defined by the most
- // recent progress command.
+ // wipe_cache
+ // a wipe of cache will be performed following a successful installation.
//
- // ui_print <string>
- // display <string> on the screen.
+ // clear_display
+ // turn off the text display.
//
- // wipe_cache
- // a wipe of cache will be performed following a successful
- // installation.
+ // enable_reboot
+ // packages can explicitly request that they want the user to be able to reboot during
+ // installation (useful for debugging packages that don't exit).
//
- // clear_display
- // turn off the text display.
+ // retry_update
+ // updater encounters some issue during the update. It requests a reboot to retry the same
+ // package automatically.
//
- // enable_reboot
- // packages can explicitly request that they want the user
- // to be able to reboot during installation (useful for
- // debugging packages that don't exit).
- //
- // retry_update
- // updater encounters some issue during the update. It requests
- // a reboot to retry the same package automatically.
- //
- // log <string>
- // updater requests logging the string (e.g. cause of the
- // failure).
- //
- // - the name of the package zip file.
- //
- // - an optional argument "retry" if this update is a retry of a failed
- // update attempt.
+ // log <string>
+ // updater requests logging the string (e.g. cause of the failure).
//
- // Convert the vector to a NULL-terminated char* array suitable for execv.
- const char* chr_args[args.size() + 1];
- chr_args[args.size()] = nullptr;
- for (size_t i = 0; i < args.size(); i++) {
- chr_args[i] = args[i].c_str();
+ std::vector<std::string> args;
+ if (auto setup_result =
+ is_ab ? SetUpAbUpdateCommands(package, zip, pipe_write.get(), &args)
+ : SetUpNonAbUpdateCommands(package, zip, retry_count, pipe_write.get(), &args);
+ !setup_result) {
+ log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
+ return INSTALL_CORRUPT;
}
pid_t pid = fork();
-
if (pid == -1) {
- close(pipefd[0]);
- close(pipefd[1]);
PLOG(ERROR) << "Failed to fork update binary";
log_buffer->push_back(android::base::StringPrintf("error: %d", kForkUpdateBinaryFailure));
return INSTALL_ERROR;
@@ -402,16 +397,18 @@
if (pid == 0) {
umask(022);
- close(pipefd[0]);
- execv(chr_args[0], const_cast<char**>(chr_args));
- // Bug: 34769056
- // We shouldn't use LOG/PLOG in the forked process, since they may cause
- // the child process to hang. This deadlock results from an improperly
- // copied mutex in the ui functions.
+ pipe_read.reset();
+
+ // Convert the std::string vector to a NULL-terminated char* vector suitable for execv.
+ auto chr_args = StringVectorToNullTerminatedArray(args);
+ execv(chr_args[0], chr_args.data());
+ // We shouldn't use LOG/PLOG in the forked process, since they may cause the child process to
+ // hang. This deadlock results from an improperly copied mutex in the ui functions.
+ // (Bug: 34769056)
fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
_exit(EXIT_FAILURE);
}
- close(pipefd[1]);
+ pipe_write.reset();
std::atomic<bool> logger_finished(false);
std::thread temperature_logger(log_max_temperature, max_temperature, std::ref(logger_finished));
@@ -420,7 +417,7 @@
bool retry_update = false;
char buffer[1024];
- FILE* from_child = fdopen(pipefd[0], "r");
+ FILE* from_child = android::base::Fdopen(std::move(pipe_read), "r");
while (fgets(buffer, sizeof(buffer), from_child) != nullptr) {
std::string line(buffer);
size_t space = line.find_first_of(" \n");
@@ -485,9 +482,16 @@
if (retry_update) {
return INSTALL_RETRY;
}
- if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
- LOG(ERROR) << "Error in " << package << " (Status " << WEXITSTATUS(status) << ")";
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) != EXIT_SUCCESS) {
+ LOG(ERROR) << "Error in " << package << " (status " << WEXITSTATUS(status) << ")";
+ return INSTALL_ERROR;
+ }
+ } else if (WIFSIGNALED(status)) {
+ LOG(ERROR) << "Error in " << package << " (killed by signal " << WTERMSIG(status) << ")";
return INSTALL_ERROR;
+ } else {
+ LOG(FATAL) << "Invalid status code " << status;
}
return INSTALL_SUCCESS;
@@ -500,9 +504,8 @@
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;
}
@@ -526,7 +529,7 @@
// 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);
@@ -561,9 +564,10 @@
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) {
+static InstallResult VerifyAndInstallPackage(const std::string& path, bool* wipe_cache,
+ bool needs_mount, 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...
@@ -576,40 +580,35 @@
if (needs_mount) {
if (path[0] == '@') {
- ensure_path_mounted(path.substr(1).c_str());
+ ensure_path_mounted(path.substr(1));
} else {
- ensure_path_mounted(path.c_str());
+ ensure_path_mounted(path);
}
}
- MemMapping map;
- if (!map.MapFile(path)) {
- LOG(ERROR) << "failed to map file";
+ 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(map.addr, map.length)) {
+ if (!verify_package(package.get(), ui)) {
log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
return INSTALL_CORRUPT;
}
// Try to open the package.
- ZipArchiveHandle zip;
- int err = OpenArchiveFromMemory(map.addr, map.length, path.c_str(), &zip);
- if (err != 0) {
- LOG(ERROR) << "Can't open " << path << " : " << ErrorCodeString(err);
+ ZipArchiveHandle zip = package->GetZipArchiveHandle();
+ if (!zip) {
log_buffer->push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));
-
- CloseArchive(zip);
return INSTALL_CORRUPT;
}
- // Additionally verify the compatibility of the package.
- if (!verify_package_compatibility(zip)) {
+ // Additionally verify the compatibility of the package if it's a fresh install.
+ if (retry_count == 0 && !verify_package_compatibility(zip)) {
log_buffer->push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
- CloseArchive(zip);
return INSTALL_CORRUPT;
}
@@ -619,34 +618,33 @@
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);
+ auto result =
+ TryUpdateBinary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature, ui);
ui->SetEnableReboot(true);
ui->Print("\n");
- CloseArchive(zip);
return result;
}
-int install_package(const std::string& path, bool* wipe_cache, const std::string& install_file,
- bool needs_mount, int retry_count) {
+InstallResult InstallPackage(const std::string& path, bool should_wipe_cache, bool needs_mount,
+ int retry_count, RecoveryUI* ui) {
CHECK(!path.empty());
- CHECK(!install_file.empty());
- CHECK(wipe_cache != nullptr);
- modified_flash = true;
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) {
LOG(ERROR) << "failed to set up expected mounts for install; aborting";
result = INSTALL_ERROR;
} else {
- result = really_install_package(path, wipe_cache, needs_mount, &log_buffer, retry_count,
- &max_temperature);
+ bool updater_wipe_cache = false;
+ result = VerifyAndInstallPackage(path, &updater_wipe_cache, needs_mount, &log_buffer,
+ retry_count, &max_temperature, ui);
+ should_wipe_cache = should_wipe_cache || updater_wipe_cache;
}
// Measure the time spent to apply OTA update in seconds.
@@ -693,6 +691,7 @@
std::string log_content =
android::base::Join(log_header, "\n") + "\n" + android::base::Join(log_buffer, "\n") + "\n";
+ const std::string& install_file = Paths::Get().temporary_install_file();
if (!android::base::WriteStringToFile(log_content, install_file)) {
PLOG(ERROR) << "failed to write " << install_file;
}
@@ -700,23 +699,28 @@
// Write a copy into last_log.
LOG(INFO) << log_content;
+ if (result == INSTALL_SUCCESS && should_wipe_cache) {
+ if (!WipeCache(ui, nullptr)) {
+ result = INSTALL_ERROR;
+ }
+ }
+
return result;
}
-bool verify_package(const unsigned char* package_data, size_t package_size) {
- static constexpr const char* PUBLIC_KEYS_FILE = "/res/keys";
- std::vector<Certificate> loadedKeys;
- if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) {
+bool verify_package(Package* package, RecoveryUI* ui) {
+ static constexpr const char* CERTIFICATE_ZIP_FILE = "/system/etc/security/otacerts.zip";
+ std::vector<Certificate> loaded_keys = LoadKeysFromZipfile(CERTIFICATE_ZIP_FILE);
+ if (loaded_keys.empty()) {
LOG(ERROR) << "Failed to load keys";
return false;
}
- LOG(INFO) << loadedKeys.size() << " key(s) loaded from " << PUBLIC_KEYS_FILE;
+ LOG(INFO) << loaded_keys.size() << " key(s) loaded from " << CERTIFICATE_ZIP_FILE;
// Verify package.
ui->Print("Verifying update package...\n");
auto t0 = std::chrono::system_clock::now();
- int err = verify_file(package_data, package_size, loadedKeys,
- std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
+ int err = verify_file(package, loaded_keys);
std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0;
ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err);
if (err != VERIFY_SUCCESS) {
diff --git a/install/package.cpp b/install/package.cpp
new file mode 100644
index 0000000..4402f48
--- /dev/null
+++ b/install/package.cpp
@@ -0,0 +1,262 @@
+/*
+ * 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/package.h"
+
+#include <string.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+
+#include "otautil/error_code.h"
+#include "otautil/sysutil.h"
+
+// This class wraps the package in memory, i.e. a memory mapped package, or a package loaded
+// to a string/vector.
+class MemoryPackage : public Package {
+ public:
+ // Constructs the class from a file. We will memory maps the file later.
+ MemoryPackage(const std::string& path, std::unique_ptr<MemMapping> map,
+ const std::function<void(float)>& set_progress);
+
+ // Constructs the class from the package bytes in |content|.
+ MemoryPackage(std::vector<uint8_t> content, const std::function<void(float)>& set_progress);
+
+ ~MemoryPackage() override;
+
+ // Memory maps the package file if necessary. Initializes the start address and size of the
+ // package.
+ uint64_t GetPackageSize() const override {
+ return package_size_;
+ }
+
+ bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) override;
+
+ ZipArchiveHandle GetZipArchiveHandle() override;
+
+ bool UpdateHashAtOffset(const std::vector<HasherUpdateCallback>& hashers, uint64_t start,
+ uint64_t length) override;
+
+ private:
+ const uint8_t* addr_; // Start address of the package in memory.
+ uint64_t package_size_; // Package size in bytes.
+
+ // The memory mapped package.
+ std::unique_ptr<MemMapping> map_;
+ // A copy of the package content, valid only if we create the class with the exact bytes of
+ // the package.
+ std::vector<uint8_t> package_content_;
+ // The physical path to the package, empty if we create the class with the package content.
+ std::string path_;
+
+ // The ZipArchiveHandle of the package.
+ ZipArchiveHandle zip_handle_;
+};
+
+void Package::SetProgress(float progress) {
+ if (set_progress_) {
+ set_progress_(progress);
+ }
+}
+
+class FilePackage : public Package {
+ public:
+ FilePackage(android::base::unique_fd&& fd, uint64_t file_size, const std::string& path,
+ const std::function<void(float)>& set_progress);
+
+ ~FilePackage() override;
+
+ uint64_t GetPackageSize() const override {
+ return package_size_;
+ }
+
+ bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) override;
+
+ ZipArchiveHandle GetZipArchiveHandle() override;
+
+ bool UpdateHashAtOffset(const std::vector<HasherUpdateCallback>& hashers, uint64_t start,
+ uint64_t length) override;
+
+ private:
+ android::base::unique_fd fd_; // The underlying fd to the open package.
+ uint64_t package_size_;
+ std::string path_; // The physical path to the package.
+
+ ZipArchiveHandle zip_handle_;
+};
+
+std::unique_ptr<Package> Package::CreateMemoryPackage(
+ const std::string& path, const std::function<void(float)>& set_progress) {
+ std::unique_ptr<MemMapping> mmap = std::make_unique<MemMapping>();
+ if (!mmap->MapFile(path)) {
+ LOG(ERROR) << "failed to map file";
+ return nullptr;
+ }
+
+ return std::make_unique<MemoryPackage>(path, std::move(mmap), set_progress);
+}
+
+std::unique_ptr<Package> Package::CreateFilePackage(
+ const std::string& path, const std::function<void(float)>& set_progress) {
+ android::base::unique_fd fd(open(path.c_str(), O_RDONLY));
+ if (fd == -1) {
+ PLOG(ERROR) << "Failed to open " << path;
+ return nullptr;
+ }
+
+ off64_t file_size = lseek64(fd.get(), 0, SEEK_END);
+ if (file_size == -1) {
+ PLOG(ERROR) << "Failed to get the package size";
+ return nullptr;
+ }
+
+ return std::make_unique<FilePackage>(std::move(fd), file_size, path, set_progress);
+}
+
+std::unique_ptr<Package> Package::CreateMemoryPackage(
+ std::vector<uint8_t> content, const std::function<void(float)>& set_progress) {
+ return std::make_unique<MemoryPackage>(std::move(content), set_progress);
+}
+
+MemoryPackage::MemoryPackage(const std::string& path, std::unique_ptr<MemMapping> map,
+ const std::function<void(float)>& set_progress)
+ : map_(std::move(map)), path_(path), zip_handle_(nullptr) {
+ addr_ = map_->addr;
+ package_size_ = map_->length;
+ set_progress_ = set_progress;
+}
+
+MemoryPackage::MemoryPackage(std::vector<uint8_t> content,
+ const std::function<void(float)>& set_progress)
+ : package_content_(std::move(content)), zip_handle_(nullptr) {
+ CHECK(!package_content_.empty());
+ addr_ = package_content_.data();
+ package_size_ = package_content_.size();
+ set_progress_ = set_progress;
+}
+
+MemoryPackage::~MemoryPackage() {
+ if (zip_handle_) {
+ CloseArchive(zip_handle_);
+ }
+}
+
+bool MemoryPackage::ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) {
+ if (byte_count > package_size_ || offset > package_size_ - byte_count) {
+ LOG(ERROR) << "Out of bound read, offset: " << offset << ", size: " << byte_count
+ << ", total package_size: " << package_size_;
+ return false;
+ }
+ memcpy(buffer, addr_ + offset, byte_count);
+ return true;
+}
+
+bool MemoryPackage::UpdateHashAtOffset(const std::vector<HasherUpdateCallback>& hashers,
+ uint64_t start, uint64_t length) {
+ if (length > package_size_ || start > package_size_ - length) {
+ LOG(ERROR) << "Out of bound read, offset: " << start << ", size: " << length
+ << ", total package_size: " << package_size_;
+ return false;
+ }
+
+ for (const auto& hasher : hashers) {
+ hasher(addr_ + start, length);
+ }
+ return true;
+}
+
+ZipArchiveHandle MemoryPackage::GetZipArchiveHandle() {
+ if (zip_handle_) {
+ return zip_handle_;
+ }
+
+ if (auto err = OpenArchiveFromMemory(const_cast<uint8_t*>(addr_), package_size_, path_.c_str(),
+ &zip_handle_);
+ err != 0) {
+ LOG(ERROR) << "Can't open package" << path_ << " : " << ErrorCodeString(err);
+ return nullptr;
+ }
+
+ return zip_handle_;
+}
+
+FilePackage::FilePackage(android::base::unique_fd&& fd, uint64_t file_size, const std::string& path,
+ const std::function<void(float)>& set_progress)
+ : fd_(std::move(fd)), package_size_(file_size), path_(path), zip_handle_(nullptr) {
+ set_progress_ = set_progress;
+}
+
+FilePackage::~FilePackage() {
+ if (zip_handle_) {
+ CloseArchive(zip_handle_);
+ }
+}
+
+bool FilePackage::ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) {
+ if (byte_count > package_size_ || offset > package_size_ - byte_count) {
+ LOG(ERROR) << "Out of bound read, offset: " << offset << ", size: " << byte_count
+ << ", total package_size: " << package_size_;
+ return false;
+ }
+
+ if (!android::base::ReadFullyAtOffset(fd_.get(), buffer, byte_count, offset)) {
+ PLOG(ERROR) << "Failed to read " << byte_count << " bytes data at offset " << offset;
+ return false;
+ }
+
+ return true;
+}
+
+bool FilePackage::UpdateHashAtOffset(const std::vector<HasherUpdateCallback>& hashers,
+ uint64_t start, uint64_t length) {
+ if (length > package_size_ || start > package_size_ - length) {
+ LOG(ERROR) << "Out of bound read, offset: " << start << ", size: " << length
+ << ", total package_size: " << package_size_;
+ return false;
+ }
+
+ uint64_t so_far = 0;
+ while (so_far < length) {
+ uint64_t read_size = std::min<uint64_t>(length - so_far, 16 * MiB);
+ std::vector<uint8_t> buffer(read_size);
+ if (!ReadFullyAtOffset(buffer.data(), read_size, start + so_far)) {
+ return false;
+ }
+
+ for (const auto& hasher : hashers) {
+ hasher(buffer.data(), read_size);
+ }
+ so_far += read_size;
+ }
+
+ return true;
+}
+
+ZipArchiveHandle FilePackage::GetZipArchiveHandle() {
+ if (zip_handle_) {
+ return zip_handle_;
+ }
+
+ if (auto err = OpenArchiveFd(fd_.get(), path_.c_str(), &zip_handle_); err != 0) {
+ LOG(ERROR) << "Can't open package" << path_ << " : " << ErrorCodeString(err);
+ return nullptr;
+ }
+
+ return zip_handle_;
+}
diff --git a/install/verifier.cpp b/install/verifier.cpp
new file mode 100644
index 0000000..3de58e4
--- /dev/null
+++ b/install/verifier.cpp
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2008 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/verifier.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <openssl/bio.h>
+#include <openssl/bn.h>
+#include <openssl/ecdsa.h>
+#include <openssl/evp.h>
+#include <openssl/obj_mac.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <ziparchive/zip_archive.h>
+
+#include "otautil/print_sha1.h"
+#include "private/asn1_decoder.h"
+
+/*
+ * Simple version of PKCS#7 SignedData extraction. This extracts the
+ * signature OCTET STRING to be used for signature verification.
+ *
+ * For full details, see http://www.ietf.org/rfc/rfc3852.txt
+ *
+ * The PKCS#7 structure looks like:
+ *
+ * SEQUENCE (ContentInfo)
+ * OID (ContentType)
+ * [0] (content)
+ * SEQUENCE (SignedData)
+ * INTEGER (version CMSVersion)
+ * SET (DigestAlgorithmIdentifiers)
+ * SEQUENCE (EncapsulatedContentInfo)
+ * [0] (CertificateSet OPTIONAL)
+ * [1] (RevocationInfoChoices OPTIONAL)
+ * SET (SignerInfos)
+ * SEQUENCE (SignerInfo)
+ * INTEGER (CMSVersion)
+ * SEQUENCE (SignerIdentifier)
+ * SEQUENCE (DigestAlgorithmIdentifier)
+ * SEQUENCE (SignatureAlgorithmIdentifier)
+ * OCTET STRING (SignatureValue)
+ */
+static bool read_pkcs7(const uint8_t* pkcs7_der, size_t pkcs7_der_len,
+ std::vector<uint8_t>* sig_der) {
+ CHECK(sig_der != nullptr);
+ sig_der->clear();
+
+ asn1_context ctx(pkcs7_der, pkcs7_der_len);
+
+ std::unique_ptr<asn1_context> pkcs7_seq(ctx.asn1_sequence_get());
+ if (pkcs7_seq == nullptr || !pkcs7_seq->asn1_sequence_next()) {
+ return false;
+ }
+
+ std::unique_ptr<asn1_context> signed_data_app(pkcs7_seq->asn1_constructed_get());
+ if (signed_data_app == nullptr) {
+ return false;
+ }
+
+ std::unique_ptr<asn1_context> signed_data_seq(signed_data_app->asn1_sequence_get());
+ if (signed_data_seq == nullptr || !signed_data_seq->asn1_sequence_next() ||
+ !signed_data_seq->asn1_sequence_next() || !signed_data_seq->asn1_sequence_next() ||
+ !signed_data_seq->asn1_constructed_skip_all()) {
+ return false;
+ }
+
+ std::unique_ptr<asn1_context> sig_set(signed_data_seq->asn1_set_get());
+ if (sig_set == nullptr) {
+ return false;
+ }
+
+ std::unique_ptr<asn1_context> sig_seq(sig_set->asn1_sequence_get());
+ if (sig_seq == nullptr || !sig_seq->asn1_sequence_next() || !sig_seq->asn1_sequence_next() ||
+ !sig_seq->asn1_sequence_next() || !sig_seq->asn1_sequence_next()) {
+ return false;
+ }
+
+ const uint8_t* sig_der_ptr;
+ size_t sig_der_length;
+ if (!sig_seq->asn1_octet_string_get(&sig_der_ptr, &sig_der_length)) {
+ return false;
+ }
+
+ sig_der->resize(sig_der_length);
+ std::copy(sig_der_ptr, sig_der_ptr + sig_der_length, sig_der->begin());
+ return true;
+}
+
+int verify_file(VerifierInterface* package, const std::vector<Certificate>& keys) {
+ CHECK(package);
+ package->SetProgress(0.0);
+
+ // An archive with a whole-file signature will end in six bytes:
+ //
+ // (2-byte signature start) $ff $ff (2-byte comment size)
+ //
+ // (As far as the ZIP format is concerned, these are part of the archive comment.) We start by
+ // reading this footer, this tells us how far back from the end we have to start reading to find
+ // the whole comment.
+
+#define FOOTER_SIZE 6
+ uint64_t length = package->GetPackageSize();
+
+ if (length < FOOTER_SIZE) {
+ LOG(ERROR) << "not big enough to contain footer";
+ return VERIFY_FAILURE;
+ }
+
+ uint8_t footer[FOOTER_SIZE];
+ if (!package->ReadFullyAtOffset(footer, FOOTER_SIZE, length - FOOTER_SIZE)) {
+ LOG(ERROR) << "Failed to read footer";
+ return VERIFY_FAILURE;
+ }
+
+ if (footer[2] != 0xff || footer[3] != 0xff) {
+ LOG(ERROR) << "footer is wrong";
+ return VERIFY_FAILURE;
+ }
+
+ size_t comment_size = footer[4] + (footer[5] << 8);
+ size_t signature_start = footer[0] + (footer[1] << 8);
+ LOG(INFO) << "comment is " << comment_size << " bytes; signature is " << signature_start
+ << " bytes from end";
+
+ if (signature_start > comment_size) {
+ LOG(ERROR) << "signature start: " << signature_start
+ << " is larger than comment size: " << comment_size;
+ return VERIFY_FAILURE;
+ }
+
+ if (signature_start <= FOOTER_SIZE) {
+ LOG(ERROR) << "Signature start is in the footer";
+ return VERIFY_FAILURE;
+ }
+
+#define EOCD_HEADER_SIZE 22
+
+ // The end-of-central-directory record is 22 bytes plus any comment length.
+ size_t eocd_size = comment_size + EOCD_HEADER_SIZE;
+
+ if (length < eocd_size) {
+ LOG(ERROR) << "not big enough to contain EOCD";
+ return VERIFY_FAILURE;
+ }
+
+ // Determine how much of the file is covered by the signature. This is everything except the
+ // signature data and length, which includes all of the EOCD except for the comment length field
+ // (2 bytes) and the comment data.
+ uint64_t signed_len = length - eocd_size + EOCD_HEADER_SIZE - 2;
+
+ uint8_t eocd[eocd_size];
+ if (!package->ReadFullyAtOffset(eocd, eocd_size, length - eocd_size)) {
+ LOG(ERROR) << "Failed to read EOCD of " << eocd_size << " bytes";
+ return VERIFY_FAILURE;
+ }
+
+ // If this is really is the EOCD record, it will begin with the magic number $50 $4b $05 $06.
+ if (eocd[0] != 0x50 || eocd[1] != 0x4b || eocd[2] != 0x05 || eocd[3] != 0x06) {
+ LOG(ERROR) << "signature length doesn't match EOCD marker";
+ return VERIFY_FAILURE;
+ }
+
+ for (size_t i = 4; i < eocd_size - 3; ++i) {
+ if (eocd[i] == 0x50 && eocd[i + 1] == 0x4b && eocd[i + 2] == 0x05 && eocd[i + 3] == 0x06) {
+ // If the sequence $50 $4b $05 $06 appears anywhere after the real one, libziparchive will
+ // find the later (wrong) one, which could be exploitable. Fail the verification if this
+ // sequence occurs anywhere after the real one.
+ LOG(ERROR) << "EOCD marker occurs after start of EOCD";
+ return VERIFY_FAILURE;
+ }
+ }
+
+ bool need_sha1 = false;
+ bool need_sha256 = false;
+ for (const auto& key : keys) {
+ switch (key.hash_len) {
+ case SHA_DIGEST_LENGTH:
+ need_sha1 = true;
+ break;
+ case SHA256_DIGEST_LENGTH:
+ need_sha256 = true;
+ break;
+ }
+ }
+
+ SHA_CTX sha1_ctx;
+ SHA256_CTX sha256_ctx;
+ SHA1_Init(&sha1_ctx);
+ SHA256_Init(&sha256_ctx);
+
+ std::vector<HasherUpdateCallback> hashers;
+ if (need_sha1) {
+ hashers.emplace_back(
+ std::bind(&SHA1_Update, &sha1_ctx, std::placeholders::_1, std::placeholders::_2));
+ }
+ if (need_sha256) {
+ hashers.emplace_back(
+ std::bind(&SHA256_Update, &sha256_ctx, std::placeholders::_1, std::placeholders::_2));
+ }
+
+ double frac = -1.0;
+ uint64_t so_far = 0;
+ while (so_far < signed_len) {
+ // On a Nexus 5X, experiment showed 16MiB beat 1MiB by 6% faster for a 1196MiB full OTA and
+ // 60% for an 89MiB incremental OTA. http://b/28135231.
+ uint64_t read_size = std::min<uint64_t>(signed_len - so_far, 16 * MiB);
+ package->UpdateHashAtOffset(hashers, so_far, read_size);
+ so_far += read_size;
+
+ double f = so_far / static_cast<double>(signed_len);
+ if (f > frac + 0.02 || read_size == so_far) {
+ package->SetProgress(f);
+ frac = f;
+ }
+ }
+
+ uint8_t sha1[SHA_DIGEST_LENGTH];
+ SHA1_Final(sha1, &sha1_ctx);
+ uint8_t sha256[SHA256_DIGEST_LENGTH];
+ SHA256_Final(sha256, &sha256_ctx);
+
+ const uint8_t* signature = eocd + eocd_size - signature_start;
+ size_t signature_size = signature_start - FOOTER_SIZE;
+
+ LOG(INFO) << "signature (offset: " << std::hex << (length - signature_start)
+ << ", length: " << signature_size << "): " << print_hex(signature, signature_size);
+
+ std::vector<uint8_t> sig_der;
+ if (!read_pkcs7(signature, signature_size, &sig_der)) {
+ LOG(ERROR) << "Could not find signature DER block";
+ return VERIFY_FAILURE;
+ }
+
+ // Check to make sure at least one of the keys matches the signature. Since any key can match,
+ // we need to try each before determining a verification failure has happened.
+ size_t i = 0;
+ for (const auto& key : keys) {
+ const uint8_t* hash;
+ int hash_nid;
+ switch (key.hash_len) {
+ case SHA_DIGEST_LENGTH:
+ hash = sha1;
+ hash_nid = NID_sha1;
+ break;
+ case SHA256_DIGEST_LENGTH:
+ hash = sha256;
+ hash_nid = NID_sha256;
+ break;
+ default:
+ continue;
+ }
+
+ // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that the signing tool appends
+ // after the signature itself.
+ if (key.key_type == Certificate::KEY_TYPE_RSA) {
+ if (!RSA_verify(hash_nid, hash, key.hash_len, sig_der.data(), sig_der.size(),
+ key.rsa.get())) {
+ LOG(INFO) << "failed to verify against RSA key " << i;
+ continue;
+ }
+
+ LOG(INFO) << "whole-file signature verified against RSA key " << i;
+ return VERIFY_SUCCESS;
+ } else if (key.key_type == Certificate::KEY_TYPE_EC && key.hash_len == SHA256_DIGEST_LENGTH) {
+ if (!ECDSA_verify(0, hash, key.hash_len, sig_der.data(), sig_der.size(), key.ec.get())) {
+ LOG(INFO) << "failed to verify against EC key " << i;
+ continue;
+ }
+
+ LOG(INFO) << "whole-file signature verified against EC key " << i;
+ return VERIFY_SUCCESS;
+ } else {
+ LOG(INFO) << "Unknown key type " << key.key_type;
+ }
+ i++;
+ }
+
+ if (need_sha1) {
+ LOG(INFO) << "SHA-1 digest: " << print_hex(sha1, SHA_DIGEST_LENGTH);
+ }
+ if (need_sha256) {
+ LOG(INFO) << "SHA-256 digest: " << print_hex(sha256, SHA256_DIGEST_LENGTH);
+ }
+ LOG(ERROR) << "failed to verify whole-file signature";
+ return VERIFY_FAILURE;
+}
+
+static std::vector<Certificate> IterateZipEntriesAndSearchForKeys(const ZipArchiveHandle& handle) {
+ void* cookie;
+ 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);
+ return {};
+ }
+
+ std::vector<Certificate> result;
+
+ ZipString 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);
+ 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);
+ return {};
+ }
+
+ result.emplace_back(std::move(cert));
+ }
+
+ if (iter_status != -1) {
+ LOG(ERROR) << "Error while iterating over zip entries: " << ErrorCodeString(iter_status);
+ return {};
+ }
+
+ return result;
+}
+
+std::vector<Certificate> LoadKeysFromZipfile(const std::string& zip_name) {
+ ZipArchiveHandle handle;
+ if (int32_t open_status = OpenArchive(zip_name.c_str(), &handle); open_status != 0) {
+ LOG(ERROR) << "Failed to open " << zip_name << ": " << ErrorCodeString(open_status);
+ return {};
+ }
+
+ std::vector<Certificate> result = IterateZipEntriesAndSearchForKeys(handle);
+ CloseArchive(handle);
+ return result;
+}
+
+bool CheckRSAKey(const std::unique_ptr<RSA, RSADeleter>& rsa) {
+ if (!rsa) {
+ return false;
+ }
+
+ const BIGNUM* out_n;
+ const BIGNUM* out_e;
+ RSA_get0_key(rsa.get(), &out_n, &out_e, nullptr /* private exponent */);
+ auto modulus_bits = BN_num_bits(out_n);
+ if (modulus_bits != 2048 && modulus_bits != 4096) {
+ LOG(ERROR) << "Modulus should be 2048 or 4096 bits long, actual: " << modulus_bits;
+ return false;
+ }
+
+ BN_ULONG exponent = BN_get_word(out_e);
+ if (exponent != 3 && exponent != 65537) {
+ LOG(ERROR) << "Public exponent should be 3 or 65537, actual: " << exponent;
+ return false;
+ }
+
+ return true;
+}
+
+bool CheckECKey(const std::unique_ptr<EC_KEY, ECKEYDeleter>& ec_key) {
+ if (!ec_key) {
+ return false;
+ }
+
+ const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key.get());
+ if (!ec_group) {
+ LOG(ERROR) << "Failed to get the ec_group from the ec_key";
+ return false;
+ }
+ auto degree = EC_GROUP_get_degree(ec_group);
+ if (degree != 256) {
+ LOG(ERROR) << "Field size of the ec key should be 256 bits long, actual: " << degree;
+ return false;
+ }
+
+ return true;
+}
+
+bool LoadCertificateFromBuffer(const std::vector<uint8_t>& pem_content, Certificate* cert) {
+ std::unique_ptr<BIO, decltype(&BIO_free)> content(
+ BIO_new_mem_buf(pem_content.data(), pem_content.size()), BIO_free);
+
+ std::unique_ptr<X509, decltype(&X509_free)> x509(
+ PEM_read_bio_X509(content.get(), nullptr, nullptr, nullptr), X509_free);
+ if (!x509) {
+ LOG(ERROR) << "Failed to read x509 certificate";
+ return false;
+ }
+
+ int nid = X509_get_signature_nid(x509.get());
+ switch (nid) {
+ // SignApk has historically accepted md5WithRSA certificates, but treated them as
+ // sha1WithRSA anyway. Continue to do so for backwards compatibility.
+ case NID_md5WithRSA:
+ case NID_md5WithRSAEncryption:
+ case NID_sha1WithRSA:
+ case NID_sha1WithRSAEncryption:
+ cert->hash_len = SHA_DIGEST_LENGTH;
+ break;
+ case NID_sha256WithRSAEncryption:
+ case NID_ecdsa_with_SHA256:
+ cert->hash_len = SHA256_DIGEST_LENGTH;
+ break;
+ default:
+ LOG(ERROR) << "Unrecognized signature nid " << OBJ_nid2ln(nid);
+ return false;
+ }
+
+ std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> public_key(X509_get_pubkey(x509.get()),
+ EVP_PKEY_free);
+ if (!public_key) {
+ LOG(ERROR) << "Failed to extract the public key from x509 certificate";
+ return false;
+ }
+
+ int key_type = EVP_PKEY_id(public_key.get());
+ if (key_type == EVP_PKEY_RSA) {
+ cert->key_type = Certificate::KEY_TYPE_RSA;
+ cert->ec.reset();
+ cert->rsa.reset(EVP_PKEY_get1_RSA(public_key.get()));
+ if (!cert->rsa || !CheckRSAKey(cert->rsa)) {
+ LOG(ERROR) << "Failed to validate the rsa key info from public key";
+ return false;
+ }
+ } else if (key_type == EVP_PKEY_EC) {
+ cert->key_type = Certificate::KEY_TYPE_EC;
+ cert->rsa.reset();
+ cert->ec.reset(EVP_PKEY_get1_EC_KEY(public_key.get()));
+ if (!cert->ec || !CheckECKey(cert->ec)) {
+ LOG(ERROR) << "Failed to validate the ec key info from the public key";
+ return false;
+ }
+ } else {
+ LOG(ERROR) << "Unrecognized public key type " << OBJ_nid2ln(key_type);
+ return false;
+ }
+
+ return true;
+}
diff --git a/install/wipe_data.cpp b/install/wipe_data.cpp
new file mode 100644
index 0000000..765a815
--- /dev/null
+++ b/install/wipe_data.cpp
@@ -0,0 +1,123 @@
+/*
+ * 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_data.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <functional>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include "otautil/dirutil.h"
+#include "otautil/logging.h"
+#include "otautil/roots.h"
+#include "recovery_ui/ui.h"
+
+constexpr const char* CACHE_ROOT = "/cache";
+constexpr const char* DATA_ROOT = "/data";
+constexpr const char* METADATA_ROOT = "/metadata";
+
+static bool EraseVolume(const char* volume, RecoveryUI* ui, bool convert_fbe) {
+ bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
+ bool is_data = (strcmp(volume, DATA_ROOT) == 0);
+
+ ui->SetBackground(RecoveryUI::ERASING);
+ ui->SetProgressType(RecoveryUI::INDETERMINATE);
+
+ std::vector<saved_log_file> log_files;
+ if (is_cache) {
+ // If we're reformatting /cache, we load any past logs (i.e. "/cache/recovery/last_*") and the
+ // current log ("/cache/recovery/log") into memory, so we can restore them after the reformat.
+ log_files = ReadLogFilesToMemory();
+ }
+
+ ui->Print("Formatting %s...\n", volume);
+
+ ensure_path_unmounted(volume);
+
+ int result;
+ if (is_data && convert_fbe) {
+ constexpr const char* CONVERT_FBE_DIR = "/tmp/convert_fbe";
+ constexpr const char* CONVERT_FBE_FILE = "/tmp/convert_fbe/convert_fbe";
+ // Create convert_fbe breadcrumb file to signal init to convert to file based encryption, not
+ // full disk encryption.
+ if (mkdir(CONVERT_FBE_DIR, 0700) != 0) {
+ PLOG(ERROR) << "Failed to mkdir " << CONVERT_FBE_DIR;
+ return false;
+ }
+ FILE* f = fopen(CONVERT_FBE_FILE, "wbe");
+ if (!f) {
+ PLOG(ERROR) << "Failed to convert to file encryption";
+ return false;
+ }
+ fclose(f);
+ result = format_volume(volume, CONVERT_FBE_DIR);
+ remove(CONVERT_FBE_FILE);
+ rmdir(CONVERT_FBE_DIR);
+ } else {
+ result = format_volume(volume);
+ }
+
+ if (is_cache) {
+ RestoreLogFilesAfterFormat(log_files);
+ }
+
+ return (result == 0);
+}
+
+bool WipeCache(RecoveryUI* ui, const std::function<bool()>& confirm_func) {
+ bool has_cache = volume_for_mount_point("/cache") != nullptr;
+ if (!has_cache) {
+ ui->Print("No /cache partition found.\n");
+ return false;
+ }
+
+ if (confirm_func && !confirm_func()) {
+ return false;
+ }
+
+ ui->Print("\n-- Wiping cache...\n");
+ bool success = EraseVolume("/cache", ui, false);
+ ui->Print("Cache wipe %s.\n", success ? "complete" : "failed");
+ return success;
+}
+
+bool WipeData(Device* device, bool convert_fbe) {
+ RecoveryUI* ui = device->GetUI();
+ ui->Print("\n-- Wiping data...\n");
+ bool success = device->PreWipeData();
+ if (success) {
+ success &= EraseVolume(DATA_ROOT, ui, convert_fbe);
+ bool has_cache = volume_for_mount_point("/cache") != nullptr;
+ if (has_cache) {
+ success &= EraseVolume(CACHE_ROOT, ui, false);
+ }
+ if (volume_for_mount_point(METADATA_ROOT) != nullptr) {
+ success &= EraseVolume(METADATA_ROOT, ui, false);
+ }
+ }
+ if (success) {
+ success &= device->PostWipeData();
+ }
+ 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 0000000..89d5d31
--- /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
new file mode 100644
index 0000000..afd57ad
--- /dev/null
+++ b/minadbd/Android.bp
@@ -0,0 +1,117 @@
+// 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.
+
+cc_defaults {
+ name: "minadbd_defaults",
+
+ cflags: [
+ "-DADB_HOST=0",
+ "-Wall",
+ "-Werror",
+ ],
+
+ cpp_std: "experimental",
+
+ include_dirs: [
+ "system/core/adb",
+ ],
+}
+
+// `libminadbd_services` is analogous to the `libadbd_services` for regular `adbd`, but providing
+// the sideload service only.
+cc_library {
+ name: "libminadbd_services",
+ recovery_available: true,
+
+ defaults: [
+ "minadbd_defaults",
+ ],
+
+ srcs: [
+ "fuse_adb_provider.cpp",
+ "minadbd_services.cpp",
+ ],
+
+ static_libs: [
+ "libotautil",
+ ],
+
+ shared_libs: [
+ "libadbd",
+ "libbase",
+ "libcrypto",
+ "libfusesideload",
+ ],
+}
+
+cc_library_headers {
+ name: "libminadbd_headers",
+ recovery_available: true,
+ // TODO create a include dir
+ export_include_dirs: [
+ ".",
+ ],
+}
+
+cc_binary {
+ name: "minadbd",
+ recovery: true,
+
+ defaults: [
+ "minadbd_defaults",
+ ],
+
+ srcs: [
+ "minadbd.cpp",
+ ],
+
+ shared_libs: [
+ "libadbd",
+ "libbase",
+ "libcrypto",
+ "libminadbd_services",
+ ],
+}
+
+cc_test {
+ name: "minadbd_test",
+ isolated: true,
+
+ defaults: [
+ "minadbd_defaults",
+ ],
+
+ srcs: [
+ "fuse_adb_provider_test.cpp",
+ "minadbd_services_test.cpp",
+ ],
+
+ static_libs: [
+ "libminadbd_services",
+ "libfusesideload",
+ "libotautil",
+ "libadbd",
+ "libcrypto",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ "liblog",
+ ],
+
+ test_suites: [
+ "device-tests",
+ ],
+}
diff --git a/minadbd/Android.mk b/minadbd/Android.mk
deleted file mode 100644
index 50e3b34..0000000
--- a/minadbd/Android.mk
+++ /dev/null
@@ -1,55 +0,0 @@
-# Copyright 2005 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-minadbd_cflags := \
- -Wall -Werror \
- -DADB_HOST=0 \
-
-# libminadbd (static library)
-# ===============================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
- fuse_adb_provider.cpp \
- minadbd.cpp \
- minadbd_services.cpp \
-
-LOCAL_MODULE := libminadbd
-LOCAL_CFLAGS := $(minadbd_cflags)
-LOCAL_C_INCLUDES := bootable/recovery system/core/adb
-LOCAL_WHOLE_STATIC_LIBRARIES := libadbd
-LOCAL_STATIC_LIBRARIES := libcrypto libbase
-
-include $(BUILD_STATIC_LIBRARY)
-
-# minadbd_test (native test)
-# ===============================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := minadbd_test
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_SRC_FILES := fuse_adb_provider_test.cpp
-LOCAL_CFLAGS := $(minadbd_cflags)
-LOCAL_C_INCLUDES := $(LOCAL_PATH) system/core/adb
-LOCAL_STATIC_LIBRARIES := \
- libBionicGtestMain \
- libminadbd
-LOCAL_SHARED_LIBRARIES := \
- liblog \
- libbase \
- libcutils
-
-include $(BUILD_NATIVE_TEST)
diff --git a/minadbd/fuse_adb_provider.cpp b/minadbd/fuse_adb_provider.cpp
index 9bd3f23..47719b0 100644
--- a/minadbd/fuse_adb_provider.cpp
+++ b/minadbd/fuse_adb_provider.cpp
@@ -18,39 +18,22 @@
#include <errno.h>
#include <stdio.h>
-#include <stdlib.h>
#include <string.h>
-#include <functional>
-
#include "adb.h"
#include "adb_io.h"
-#include "fuse_sideload.h"
-int read_block_adb(const adb_data& ad, uint32_t block, uint8_t* buffer, uint32_t fetch_size) {
- if (!WriteFdFmt(ad.sfd, "%08u", block)) {
+bool FuseAdbDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+ uint32_t start_block) const {
+ if (!WriteFdFmt(fd_, "%08u", start_block)) {
fprintf(stderr, "failed to write to adb host: %s\n", strerror(errno));
- return -EIO;
+ return false;
}
- if (!ReadFdExactly(ad.sfd, buffer, fetch_size)) {
+ if (!ReadFdExactly(fd_, buffer, fetch_size)) {
fprintf(stderr, "failed to read from adb host: %s\n", strerror(errno));
- return -EIO;
+ return false;
}
- return 0;
-}
-
-int run_adb_fuse(int sfd, uint64_t file_size, uint32_t block_size) {
- adb_data ad;
- ad.sfd = sfd;
- ad.file_size = file_size;
- ad.block_size = block_size;
-
- provider_vtab vtab;
- vtab.read_block = std::bind(read_block_adb, ad, std::placeholders::_1, std::placeholders::_2,
- std::placeholders::_3);
- vtab.close = [&ad]() { WriteFdExactly(ad.sfd, "DONEDONE"); };
-
- return run_fuse_sideload(vtab, file_size, block_size);
+ return true;
}
diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h
index 36d86d5..c5561e5 100644
--- a/minadbd/fuse_adb_provider.h
+++ b/minadbd/fuse_adb_provider.h
@@ -14,19 +14,22 @@
* limitations under the License.
*/
-#ifndef __FUSE_ADB_PROVIDER_H
-#define __FUSE_ADB_PROVIDER_H
+#pragma once
#include <stdint.h>
-struct adb_data {
- int sfd; // file descriptor for the adb channel
+#include "fuse_provider.h"
- uint64_t file_size;
- uint32_t block_size;
+// This class reads data from adb server.
+class FuseAdbDataProvider : public FuseDataProvider {
+ public:
+ FuseAdbDataProvider(int fd, uint64_t file_size, uint32_t block_size)
+ : FuseDataProvider(file_size, block_size), fd_(fd) {}
+
+ bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+ uint32_t start_block) const override;
+
+ private:
+ // The underlying source to read data from (i.e. the one that talks to the host).
+ int fd_;
};
-
-int read_block_adb(const adb_data& ad, uint32_t block, uint8_t* buffer, uint32_t fetch_size);
-int run_adb_fuse(int sfd, uint64_t file_size, uint32_t block_size);
-
-#endif
diff --git a/minadbd/fuse_adb_provider_test.cpp b/minadbd/fuse_adb_provider_test.cpp
index 00250e5..0b09712 100644
--- a/minadbd/fuse_adb_provider_test.cpp
+++ b/minadbd/fuse_adb_provider_test.cpp
@@ -21,19 +21,19 @@
#include <string>
+#include <android-base/unique_fd.h>
#include <gtest/gtest.h>
#include "adb_io.h"
#include "fuse_adb_provider.h"
TEST(fuse_adb_provider, read_block_adb) {
- adb_data data = {};
- int sockets[2];
+ android::base::unique_fd device_socket;
+ android::base::unique_fd host_socket;
- ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sockets));
- data.sfd = sockets[0];
+ ASSERT_TRUE(android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &device_socket, &host_socket));
+ FuseAdbDataProvider data(std::move(device_socket), 0, 0);
- int host_socket = sockets[1];
fcntl(host_socket, F_SETFL, O_NONBLOCK);
const char expected_data[] = "foobar";
@@ -46,8 +46,8 @@
uint32_t block = 1234U;
const char expected_block[] = "00001234";
- ASSERT_EQ(0, read_block_adb(data, block, reinterpret_cast<uint8_t*>(block_data),
- sizeof(expected_data) - 1));
+ ASSERT_TRUE(data.ReadBlockAlignedData(reinterpret_cast<uint8_t*>(block_data),
+ sizeof(expected_data) - 1, block));
// Check that read_block_adb requested the right block.
char block_req[sizeof(expected_block)] = {};
@@ -65,26 +65,21 @@
errno = 0;
ASSERT_EQ(-1, read(host_socket, &tmp, 1));
ASSERT_EQ(EWOULDBLOCK, errno);
-
- close(sockets[0]);
- close(sockets[1]);
}
TEST(fuse_adb_provider, read_block_adb_fail_write) {
- adb_data data = {};
- int sockets[2];
+ android::base::unique_fd device_socket;
+ android::base::unique_fd host_socket;
- ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sockets));
- data.sfd = sockets[0];
+ ASSERT_TRUE(android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &device_socket, &host_socket));
+ FuseAdbDataProvider data(std::move(device_socket), 0, 0);
- ASSERT_EQ(0, close(sockets[1]));
+ host_socket.reset();
// write(2) raises SIGPIPE since the reading end has been closed. Ignore the signal to avoid
// failing the test.
signal(SIGPIPE, SIG_IGN);
char buf[1];
- ASSERT_EQ(-EIO, read_block_adb(data, 0, reinterpret_cast<uint8_t*>(buf), 1));
-
- close(sockets[0]);
+ ASSERT_FALSE(data.ReadBlockAlignedData(reinterpret_cast<uint8_t*>(buf), 1, 0));
}
diff --git a/minadbd/minadbd.cpp b/minadbd/minadbd.cpp
index 349189c..c80d549 100644
--- a/minadbd/minadbd.cpp
+++ b/minadbd/minadbd.cpp
@@ -14,30 +14,62 @@
* limitations under the License.
*/
-#include "minadbd.h"
-
#include <errno.h>
+#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
+#include <strings.h>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
#include "adb.h"
#include "adb_auth.h"
#include "transport.h"
-int minadbd_main() {
+#include "minadbd_services.h"
+#include "minadbd_types.h"
+
+using namespace std::string_literals;
+
+int main(int argc, char** argv) {
+ android::base::InitLogging(argv, &android::base::StderrLogger);
+ // TODO(xunchang) implement a command parser
+ if ((argc != 3 && argc != 4) || argv[1] != "--socket_fd"s ||
+ (argc == 4 && argv[3] != "--rescue"s)) {
+ LOG(ERROR) << "minadbd has invalid arguments, argc: " << argc;
+ exit(kMinadbdArgumentsParsingError);
+ }
+
+ int socket_fd;
+ if (!android::base::ParseInt(argv[2], &socket_fd)) {
+ LOG(ERROR) << "Failed to parse int in " << argv[2];
+ exit(kMinadbdArgumentsParsingError);
+ }
+ if (fcntl(socket_fd, F_GETFD, 0) == -1) {
+ PLOG(ERROR) << "Failed to get minadbd socket";
+ exit(kMinadbdSocketIOError);
+ }
+ SetMinadbdSocketFd(socket_fd);
+
+ if (argc == 4) {
+ SetMinadbdRescueMode(true);
+ adb_device_banner = "rescue";
+ } else {
adb_device_banner = "sideload";
+ }
- signal(SIGPIPE, SIG_IGN);
+ signal(SIGPIPE, SIG_IGN);
- // We can't require authentication for sideloading. http://b/22025550.
- auth_required = false;
+ // We can't require authentication for sideloading. http://b/22025550.
+ auth_required = false;
- init_transport_registration();
- usb_init();
+ init_transport_registration();
+ usb_init();
- VLOG(ADB) << "Event loop starting";
- fdevent_loop();
+ VLOG(ADB) << "Event loop starting";
+ fdevent_loop();
- return 0;
+ return 0;
}
diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp
index 043c51a..8f2b71a 100644
--- a/minadbd/minadbd_services.cpp
+++ b/minadbd/minadbd_services.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include "minadbd_services.h"
+
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
@@ -21,57 +23,256 @@
#include <string.h>
#include <unistd.h>
+#include <functional>
+#include <memory>
#include <string>
+#include <string_view>
#include <thread>
+#include <unordered_set>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/memory.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#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 "services.h"
#include "sysdeps.h"
-static void sideload_host_service(int sfd, const std::string& args) {
- int file_size;
- int block_size;
- if (sscanf(args.c_str(), "%d:%d", &file_size, &block_size) != 2) {
- printf("bad sideload-host arguments: %s\n", args.c_str());
- exit(1);
- }
+static int minadbd_socket = -1;
+static bool rescue_mode = false;
+static std::string sideload_mount_point = FUSE_SIDELOAD_HOST_MOUNTPOINT;
- printf("sideload-host file size %d block size %d\n", file_size, block_size);
-
- int result = run_adb_fuse(sfd, file_size, block_size);
-
- printf("sideload_host finished\n");
- exit(result == 0 ? 0 : 1);
+void SetMinadbdSocketFd(int socket_fd) {
+ minadbd_socket = socket_fd;
}
-static int create_service_thread(void (*func)(int, const std::string&), const std::string& args) {
- int s[2];
- if (adb_socketpair(s)) {
- printf("cannot create service socket pair\n");
- return -1;
- }
-
- std::thread([s, func, args]() { func(s[1], args); }).detach();
-
- VLOG(SERVICES) << "service thread started, " << s[0] << ":" << s[1];
- return s[0];
+void SetMinadbdRescueMode(bool rescue) {
+ rescue_mode = rescue;
}
-int service_to_fd(const char* name, atransport* /* transport */) {
- int ret = -1;
+void SetSideloadMountPoint(const std::string& path) {
+ sideload_mount_point = path;
+}
- if (!strncmp(name, "sideload:", 9)) {
- // this exit status causes recovery to print a special error
- // message saying to use a newer adb (that supports
- // sideload-host).
- exit(3);
- } else if (!strncmp(name, "sideload-host:", 14)) {
- std::string arg(name + 14);
- ret = create_service_thread(sideload_host_service, arg);
+static bool WriteCommandToFd(MinadbdCommand cmd, int fd) {
+ char message[kMinadbdMessageSize];
+ memcpy(message, kMinadbdCommandPrefix, strlen(kMinadbdStatusPrefix));
+ android::base::put_unaligned(message + strlen(kMinadbdStatusPrefix), cmd);
+
+ if (!android::base::WriteFully(fd, message, kMinadbdMessageSize)) {
+ PLOG(ERROR) << "Failed to write message " << message;
+ return false;
}
- if (ret >= 0) {
- close_on_exec(ret);
+ return true;
+}
+
+// Blocks and reads the command status from |fd|. Returns false if the received message has a
+// format error.
+static bool WaitForCommandStatus(int fd, MinadbdCommandStatus* status) {
+ char buffer[kMinadbdMessageSize];
+ if (!android::base::ReadFully(fd, buffer, kMinadbdMessageSize)) {
+ PLOG(ERROR) << "Failed to response status from socket";
+ exit(kMinadbdSocketIOError);
}
- return ret;
+
+ std::string message(buffer, buffer + kMinadbdMessageSize);
+ if (!android::base::StartsWith(message, kMinadbdStatusPrefix)) {
+ LOG(ERROR) << "Failed to parse status in " << message;
+ return false;
+ }
+
+ *status = android::base::get_unaligned<MinadbdCommandStatus>(
+ message.substr(strlen(kMinadbdStatusPrefix)).c_str());
+ return true;
+}
+
+static MinadbdErrorCode RunAdbFuseSideload(int sfd, const std::string& args,
+ MinadbdCommandStatus* status) {
+ auto pieces = android::base::Split(args, ":");
+ int64_t file_size;
+ int block_size;
+ if (pieces.size() != 2 || !android::base::ParseInt(pieces[0], &file_size) || file_size <= 0 ||
+ !android::base::ParseInt(pieces[1], &block_size) || block_size <= 0) {
+ LOG(ERROR) << "bad sideload-host arguments: " << args;
+ return kMinadbdHostCommandArgumentError;
+ }
+
+ LOG(INFO) << "sideload-host file size " << file_size << ", block size " << block_size;
+
+ if (!WriteCommandToFd(MinadbdCommand::kInstall, minadbd_socket)) {
+ return kMinadbdSocketIOError;
+ }
+
+ auto adb_data_reader = std::make_unique<FuseAdbDataProvider>(sfd, file_size, block_size);
+ if (int result = run_fuse_sideload(std::move(adb_data_reader), sideload_mount_point.c_str());
+ result != 0) {
+ LOG(ERROR) << "Failed to start fuse";
+ return kMinadbdFuseStartError;
+ }
+
+ if (!WaitForCommandStatus(minadbd_socket, status)) {
+ return kMinadbdMessageFormatError;
+ }
+
+ // Signal host-side adb to stop. For sideload mode, we always send kMinadbdServicesExitSuccess
+ // (i.e. "DONEDONE") regardless of the install result. For rescue mode, we send failure message on
+ // install error.
+ if (!rescue_mode || *status == MinadbdCommandStatus::kSuccess) {
+ if (!android::base::WriteFully(sfd, kMinadbdServicesExitSuccess,
+ strlen(kMinadbdServicesExitSuccess))) {
+ return kMinadbdHostSocketIOError;
+ }
+ } else {
+ if (!android::base::WriteFully(sfd, kMinadbdServicesExitFailure,
+ strlen(kMinadbdServicesExitFailure))) {
+ return kMinadbdHostSocketIOError;
+ }
+ }
+
+ return kMinadbdSuccess;
+}
+
+// Sideload service always exits after serving an install command.
+static void SideloadHostService(unique_fd sfd, const std::string& args) {
+ MinadbdCommandStatus status;
+ exit(RunAdbFuseSideload(sfd.get(), args, &status));
+}
+
+// Rescue service waits for the next command after an install command.
+static void RescueInstallHostService(unique_fd sfd, const std::string& args) {
+ MinadbdCommandStatus status;
+ if (auto result = RunAdbFuseSideload(sfd.get(), args, &status); result != kMinadbdSuccess) {
+ exit(result);
+ }
+}
+
+static void RescueGetpropHostService(unique_fd sfd, const std::string& prop) {
+ static const std::unordered_set<std::string> kGetpropAllowedProps = {
+ "ro.build.fingerprint",
+ "ro.build.date.utc",
+ };
+ auto allowed = kGetpropAllowedProps.find(prop) != kGetpropAllowedProps.end();
+ if (!allowed) {
+ return;
+ }
+
+ auto result = android::base::GetProperty(prop, "");
+ if (result.empty()) {
+ return;
+ }
+ if (!android::base::WriteFully(sfd, result.data(), result.size())) {
+ exit(kMinadbdHostSocketIOError);
+ }
+}
+
+// Reboots into the given target. We don't reboot directly from minadbd, but going through recovery
+// instead. This allows recovery to finish all the pending works (clear BCB, save logs etc) before
+// the reboot.
+static void RebootHostService(unique_fd /* sfd */, const std::string& target) {
+ MinadbdCommand command;
+ if (target == "bootloader") {
+ command = MinadbdCommand::kRebootBootloader;
+ } else if (target == "rescue") {
+ command = MinadbdCommand::kRebootRescue;
+ } else if (target == "recovery") {
+ command = MinadbdCommand::kRebootRecovery;
+ } else if (target == "fastboot") {
+ command = MinadbdCommand::kRebootFastboot;
+ } else {
+ command = MinadbdCommand::kRebootAndroid;
+ }
+ if (!WriteCommandToFd(command, minadbd_socket)) {
+ exit(kMinadbdSocketIOError);
+ }
+ MinadbdCommandStatus status;
+ if (!WaitForCommandStatus(minadbd_socket, &status)) {
+ exit(kMinadbdMessageFormatError);
+ }
+}
+
+static void WipeDeviceService(unique_fd fd, const std::string& args) {
+ auto pieces = android::base::Split(args, ":");
+ if (pieces.size() != 2 || pieces[0] != "userdata") {
+ LOG(ERROR) << "Failed to parse wipe device command arguments " << args;
+ exit(kMinadbdHostCommandArgumentError);
+ }
+
+ size_t message_size;
+ if (!android::base::ParseUint(pieces[1], &message_size) ||
+ message_size < strlen(kMinadbdServicesExitSuccess)) {
+ LOG(ERROR) << "Failed to parse wipe device message size in " << args;
+ exit(kMinadbdHostCommandArgumentError);
+ }
+
+ WriteCommandToFd(MinadbdCommand::kWipeData, minadbd_socket);
+ MinadbdCommandStatus status;
+ if (!WaitForCommandStatus(minadbd_socket, &status)) {
+ exit(kMinadbdMessageFormatError);
+ }
+
+ std::string response = (status == MinadbdCommandStatus::kSuccess) ? kMinadbdServicesExitSuccess
+ : kMinadbdServicesExitFailure;
+ response += std::string(message_size - response.size(), '\0');
+ if (!android::base::WriteFully(fd, response.c_str(), response.size())) {
+ exit(kMinadbdHostSocketIOError);
+ }
+}
+
+unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport */) {
+ // Common services that are supported both in sideload and rescue modes.
+ 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" ||
+ args == "fastboot") {
+ return create_service_thread("reboot",
+ std::bind(RebootHostService, std::placeholders::_1, args));
+ }
+ return unique_fd{};
+ }
+
+ // Rescue-specific services.
+ if (rescue_mode) {
+ 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 (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 (android::base::ConsumePrefix(&name, "rescue-wipe:")) {
+ // rescue-wipe:target:<message-size>
+ std::string args(name);
+ return create_service_thread("rescue-wipe",
+ std::bind(WipeDeviceService, std::placeholders::_1, args));
+ }
+
+ return unique_fd{};
+ }
+
+ // Sideload-specific services.
+ if (name.starts_with("sideload:")) {
+ // 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 (android::base::ConsumePrefix(&name, "sideload-host:")) {
+ // sideload-host:<file-size>:<block-size>
+ std::string args(name);
+ return create_service_thread("sideload-host",
+ std::bind(SideloadHostService, std::placeholders::_1, args));
+ }
+ return unique_fd{};
}
diff --git a/minadbd/minadbd.h b/minadbd/minadbd_services.h
similarity index 79%
copy from minadbd/minadbd.h
copy to minadbd/minadbd_services.h
index 3570a5d..5575c6b 100644
--- a/minadbd/minadbd.h
+++ b/minadbd/minadbd_services.h
@@ -14,9 +14,12 @@
* limitations under the License.
*/
-#ifndef MINADBD_H__
-#define MINADBD_H__
+#pragma once
-int minadbd_main();
+#include <string>
-#endif
+void SetMinadbdSocketFd(int socket_fd);
+
+void SetMinadbdRescueMode(bool);
+
+void SetSideloadMountPoint(const std::string& path);
diff --git a/minadbd/minadbd_services_test.cpp b/minadbd/minadbd_services_test.cpp
new file mode 100644
index 0000000..f878737
--- /dev/null
+++ b/minadbd/minadbd_services_test.cpp
@@ -0,0 +1,213 @@
+/*
+ * 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 <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <strings.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+
+#include "adb.h"
+#include "adb_io.h"
+#include "fuse_adb_provider.h"
+#include "fuse_sideload.h"
+#include "minadbd_services.h"
+#include "minadbd_types.h"
+#include "socket.h"
+
+class MinadbdServicesTest : public ::testing::Test {
+ protected:
+ static constexpr int EXIT_TIME_OUT = 10;
+
+ void SetUp() override {
+ ASSERT_TRUE(
+ android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &minadbd_socket_, &recovery_socket_));
+ SetMinadbdSocketFd(minadbd_socket_);
+ SetSideloadMountPoint(mount_point_.path);
+
+ package_path_ = std::string(mount_point_.path) + "/" + FUSE_SIDELOAD_HOST_FILENAME;
+ exit_flag_ = std::string(mount_point_.path) + "/" + FUSE_SIDELOAD_HOST_EXIT_FLAG;
+
+ signal(SIGPIPE, SIG_IGN);
+ }
+
+ void TearDown() override {
+ // Umount in case the test fails. Ignore the result.
+ umount(mount_point_.path);
+
+ signal(SIGPIPE, SIG_DFL);
+ }
+
+ void ReadAndCheckCommandMessage(int fd, MinadbdCommand expected_command) {
+ std::vector<uint8_t> received(kMinadbdMessageSize, '\0');
+ ASSERT_TRUE(android::base::ReadFully(fd, received.data(), kMinadbdMessageSize));
+
+ std::vector<uint8_t> expected(kMinadbdMessageSize, '\0');
+ memcpy(expected.data(), kMinadbdCommandPrefix, strlen(kMinadbdCommandPrefix));
+ memcpy(expected.data() + strlen(kMinadbdCommandPrefix), &expected_command,
+ sizeof(expected_command));
+ ASSERT_EQ(expected, received);
+ }
+
+ void WaitForFusePath() {
+ constexpr int TIME_OUT = 10;
+ for (int i = 0; i < TIME_OUT; ++i) {
+ struct stat sb;
+ if (stat(package_path_.c_str(), &sb) == 0) {
+ return;
+ }
+
+ if (errno == ENOENT) {
+ sleep(1);
+ continue;
+ }
+ FAIL() << "Timed out waiting for the fuse-provided package " << strerror(errno);
+ }
+ }
+
+ void StatExitFlagAndExitProcess(int exit_code) {
+ struct stat sb;
+ if (stat(exit_flag_.c_str(), &sb) != 0) {
+ PLOG(ERROR) << "Failed to stat " << exit_flag_;
+ }
+
+ exit(exit_code);
+ }
+
+ void WriteMinadbdCommandStatus(MinadbdCommandStatus status) {
+ std::string status_message(kMinadbdMessageSize, '\0');
+ memcpy(status_message.data(), kMinadbdStatusPrefix, strlen(kMinadbdStatusPrefix));
+ memcpy(status_message.data() + strlen(kMinadbdStatusPrefix), &status, sizeof(status));
+ ASSERT_TRUE(
+ android::base::WriteFully(recovery_socket_, status_message.data(), kMinadbdMessageSize));
+ }
+
+ void ExecuteCommandAndWaitForExit(const std::string& command) {
+ unique_fd fd = daemon_service_to_fd(command, nullptr);
+ ASSERT_NE(-1, fd);
+ sleep(EXIT_TIME_OUT);
+ }
+
+ android::base::unique_fd minadbd_socket_;
+ android::base::unique_fd recovery_socket_;
+
+ TemporaryDir mount_point_;
+ std::string package_path_;
+ std::string exit_flag_;
+};
+
+TEST_F(MinadbdServicesTest, SideloadHostService_wrong_size_argument) {
+ ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:abc:4096"),
+ ::testing::ExitedWithCode(kMinadbdHostCommandArgumentError), "");
+}
+
+TEST_F(MinadbdServicesTest, SideloadHostService_wrong_block_size) {
+ ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:10:20"),
+ ::testing::ExitedWithCode(kMinadbdFuseStartError), "");
+}
+
+TEST_F(MinadbdServicesTest, SideloadHostService_broken_minadbd_socket) {
+ SetMinadbdSocketFd(-1);
+ ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:4096:4096"),
+ ::testing::ExitedWithCode(kMinadbdSocketIOError), "");
+}
+
+TEST_F(MinadbdServicesTest, SideloadHostService_broken_recovery_socket) {
+ recovery_socket_.reset();
+ ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:4096:4096"),
+ ::testing::ExitedWithCode(kMinadbdSocketIOError), "");
+}
+
+TEST_F(MinadbdServicesTest, SideloadHostService_wrong_command_format) {
+ auto test_body = [&](const std::string& command) {
+ unique_fd fd = daemon_service_to_fd(command, nullptr);
+ ASSERT_NE(-1, fd);
+ WaitForFusePath();
+ ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommand::kInstall);
+
+ struct stat sb;
+ ASSERT_EQ(0, stat(exit_flag_.c_str(), &sb));
+ ASSERT_TRUE(android::base::WriteStringToFd("12345678", recovery_socket_));
+ sleep(EXIT_TIME_OUT);
+ };
+
+ ASSERT_EXIT(test_body("sideload-host:4096:4096"),
+ ::testing::ExitedWithCode(kMinadbdMessageFormatError), "");
+}
+
+TEST_F(MinadbdServicesTest, SideloadHostService_read_data_from_fuse) {
+ auto test_body = [&]() {
+ std::vector<uint8_t> content(4096, 'a');
+ // Start a new process instead of a thread to read from the package mounted by FUSE. Because
+ // the test may not exit and report failures correctly when the thread blocks by a syscall.
+ pid_t pid = fork();
+ if (pid == 0) {
+ WaitForFusePath();
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(package_path_.c_str(), O_RDONLY)));
+ // Do not use assertion here because we want to stat the exit flag and exit the process.
+ // Otherwise the test will wait for the time out instead of failing immediately.
+ if (fd == -1) {
+ PLOG(ERROR) << "Failed to open " << package_path_;
+ StatExitFlagAndExitProcess(1);
+ }
+ std::vector<uint8_t> content_from_fuse(4096);
+ if (!android::base::ReadFully(fd, content_from_fuse.data(), 4096)) {
+ PLOG(ERROR) << "Failed to read from " << package_path_;
+ StatExitFlagAndExitProcess(1);
+ }
+ if (content_from_fuse != content) {
+ LOG(ERROR) << "Content read from fuse doesn't match with the expected value";
+ StatExitFlagAndExitProcess(1);
+ }
+ StatExitFlagAndExitProcess(0);
+ }
+
+ unique_fd fd = daemon_service_to_fd("sideload-host:4096:4096", nullptr);
+ ASSERT_NE(-1, fd);
+ ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommand::kInstall);
+
+ // Mimic the response from adb host.
+ std::string adb_message(8, '\0');
+ ASSERT_TRUE(android::base::ReadFully(fd, adb_message.data(), 8));
+ ASSERT_EQ(android::base::StringPrintf("%08u", 0), adb_message);
+ ASSERT_TRUE(android::base::WriteFully(fd, content.data(), 4096));
+
+ // Check that we read the correct data from fuse.
+ int child_status;
+ waitpid(pid, &child_status, 0);
+ ASSERT_TRUE(WIFEXITED(child_status));
+ ASSERT_EQ(0, WEXITSTATUS(child_status));
+
+ WriteMinadbdCommandStatus(MinadbdCommandStatus::kSuccess);
+
+ // TODO(xunchang) check if adb host-side receives "DONEDONE", there's a race condition between
+ // receiving the message and exit of test body (by detached thread in minadbd service).
+ exit(kMinadbdSuccess);
+ };
+
+ ASSERT_EXIT(test_body(), ::testing::ExitedWithCode(kMinadbdSuccess), "");
+}
diff --git a/minadbd/minadbd_types.h b/minadbd/minadbd_types.h
new file mode 100644
index 0000000..99fd45e
--- /dev/null
+++ b/minadbd/minadbd_types.h
@@ -0,0 +1,63 @@
+/*
+ * 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>
+
+// The message between recovery and minadbd is 8 bytes in size unless the length is explicitly
+// specified. Both the command and status has the format |prefix(4 bytes) + encoded enum(4 bytes)|.
+constexpr size_t kMinadbdMessageSize = 8;
+constexpr char const kMinadbdCommandPrefix[] = "COMD";
+constexpr char const kMinadbdStatusPrefix[] = "STAT";
+
+enum MinadbdErrorCode : int {
+ kMinadbdSuccess = 0,
+ kMinadbdArgumentsParsingError = 1,
+ kMinadbdSocketIOError = 2,
+ kMinadbdMessageFormatError = 3,
+ kMinadbdAdbVersionError = 4,
+ kMinadbdHostCommandArgumentError = 5,
+ kMinadbdFuseStartError = 6,
+ kMinadbdUnsupportedCommandError = 7,
+ kMinadbdCommandExecutionError = 8,
+ kMinadbdErrorUnknown = 9,
+ kMinadbdHostSocketIOError = 10,
+};
+
+enum class MinadbdCommandStatus : uint32_t {
+ kSuccess = 0,
+ kFailure = 1,
+};
+
+enum class MinadbdCommand : uint32_t {
+ kInstall = 0,
+ kUiPrint = 1,
+ kRebootAndroid = 2,
+ kRebootBootloader = 3,
+ kRebootFastboot = 4,
+ kRebootRecovery = 5,
+ kRebootRescue = 6,
+ kWipeCache = 7,
+ kWipeData = 8,
+
+ // Last but invalid command.
+ kError,
+};
+
+static_assert(kMinadbdMessageSize == sizeof(kMinadbdCommandPrefix) - 1 + sizeof(MinadbdCommand));
+static_assert(kMinadbdMessageSize ==
+ sizeof(kMinadbdStatusPrefix) - 1 + sizeof(MinadbdCommandStatus));
diff --git a/minui/Android.bp b/minui/Android.bp
new file mode 100644
index 0000000..fff3a8e
--- /dev/null
+++ b/minui/Android.bp
@@ -0,0 +1,47 @@
+// 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.
+
+cc_library {
+ name: "libminui",
+ recovery_available: true,
+
+ defaults: [
+ "recovery_defaults",
+ ],
+
+ export_include_dirs: [
+ "include",
+ ],
+
+ srcs: [
+ "events.cpp",
+ "graphics.cpp",
+ "graphics_adf.cpp",
+ "graphics_drm.cpp",
+ "graphics_fbdev.cpp",
+ "resources.cpp",
+ ],
+
+ whole_static_libs: [
+ "libadf",
+ "libdrm",
+ "libsync",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libpng",
+ "libz",
+ ],
+}
diff --git a/minui/Android.mk b/minui/Android.mk
deleted file mode 100644
index ae1552b..0000000
--- a/minui/Android.mk
+++ /dev/null
@@ -1,85 +0,0 @@
-# Copyright (C) 2007 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.
-
-LOCAL_PATH := $(call my-dir)
-
-# libminui (static library)
-# ===============================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
- events.cpp \
- graphics.cpp \
- graphics_adf.cpp \
- graphics_drm.cpp \
- graphics_fbdev.cpp \
- resources.cpp \
-
-LOCAL_WHOLE_STATIC_LIBRARIES := \
- libadf \
- libdrm \
- libsync_recovery
-
-LOCAL_STATIC_LIBRARIES := \
- libpng \
- libbase
-
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-
-LOCAL_MODULE := libminui
-
-# This used to compare against values in double-quotes (which are just
-# ordinary characters in this context). Strip double-quotes from the
-# value so that either will work.
-
-ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),ABGR_8888)
- LOCAL_CFLAGS += -DRECOVERY_ABGR
-endif
-ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),RGBX_8888)
- LOCAL_CFLAGS += -DRECOVERY_RGBX
-endif
-ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),BGRA_8888)
- LOCAL_CFLAGS += -DRECOVERY_BGRA
-endif
-
-ifneq ($(TARGET_RECOVERY_OVERSCAN_PERCENT),)
- LOCAL_CFLAGS += -DOVERSCAN_PERCENT=$(TARGET_RECOVERY_OVERSCAN_PERCENT)
-else
- LOCAL_CFLAGS += -DOVERSCAN_PERCENT=0
-endif
-
-ifneq ($(TARGET_RECOVERY_DEFAULT_ROTATION),)
- LOCAL_CFLAGS += -DDEFAULT_ROTATION=$(TARGET_RECOVERY_DEFAULT_ROTATION)
-else
- LOCAL_CFLAGS += -DDEFAULT_ROTATION=ROTATION_NONE
-endif
-
-include $(BUILD_STATIC_LIBRARY)
-
-# libminui (shared library)
-# ===============================
-# Used by OEMs for factory test images.
-include $(CLEAR_VARS)
-LOCAL_MODULE := libminui
-LOCAL_WHOLE_STATIC_LIBRARIES += libminui
-LOCAL_SHARED_LIBRARIES := \
- libpng \
- libbase
-
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-include $(BUILD_SHARED_LIBRARY)
diff --git a/minui/events.cpp b/minui/events.cpp
index 2894c3b..7d0250e 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -23,144 +23,148 @@
#include <string.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
+#include <sys/types.h>
#include <unistd.h>
#include <functional>
+#include <memory>
+
+#include <android-base/unique_fd.h>
#include "minui/minui.h"
-#define MAX_DEVICES 16
-#define MAX_MISC_FDS 16
+constexpr size_t MAX_DEVICES = 16;
+constexpr size_t MAX_MISC_FDS = 16;
-#define BITS_PER_LONG (sizeof(unsigned long) * 8)
-#define BITS_TO_LONGS(x) (((x) + BITS_PER_LONG - 1) / BITS_PER_LONG)
+constexpr size_t BITS_PER_LONG = sizeof(unsigned long) * 8;
+constexpr size_t BITS_TO_LONGS(size_t bits) {
+ return ((bits + BITS_PER_LONG - 1) / BITS_PER_LONG);
+}
-struct fd_info {
- int fd;
+struct FdInfo {
+ android::base::unique_fd fd;
ev_callback cb;
};
-static int g_epoll_fd;
-static epoll_event polledevents[MAX_DEVICES + MAX_MISC_FDS];
-static int npolledevents;
+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;
-static fd_info ev_fdinfo[MAX_DEVICES + MAX_MISC_FDS];
+static FdInfo ev_fdinfo[MAX_DEVICES + MAX_MISC_FDS];
-static unsigned ev_count = 0;
-static unsigned ev_dev_count = 0;
-static unsigned ev_misc_count = 0;
+static size_t g_ev_count = 0;
+static size_t g_ev_dev_count = 0;
+static size_t g_ev_misc_count = 0;
static bool test_bit(size_t bit, unsigned long* array) { // NOLINT
- return (array[bit/BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0;
+ return (array[bit / BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0;
}
int ev_init(ev_callback input_cb, bool allow_touch_inputs) {
- g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS);
- if (g_epoll_fd == -1) {
+ g_epoll_fd.reset();
+
+ android::base::unique_fd epoll_fd(epoll_create1(EPOLL_CLOEXEC));
+ if (epoll_fd == -1) {
return -1;
}
- bool epollctlfail = false;
- DIR* dir = opendir("/dev/input");
- if (dir != nullptr) {
- dirent* de;
- while ((de = readdir(dir))) {
- if (strncmp(de->d_name, "event", 5)) continue;
- int fd = openat(dirfd(dir), de->d_name, O_RDONLY);
- if (fd == -1) continue;
+ std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/dev/input"), closedir);
+ if (!dir) {
+ return -1;
+ }
- // Use unsigned long to match ioctl's parameter type.
- unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
+ bool epoll_ctl_failed = false;
+ dirent* de;
+ while ((de = readdir(dir.get())) != nullptr) {
+ if (strncmp(de->d_name, "event", 5)) continue;
+ android::base::unique_fd fd(openat(dirfd(dir.get()), de->d_name, O_RDONLY | O_CLOEXEC));
+ if (fd == -1) continue;
- // Read the evbits of the input device.
- if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
- close(fd);
- continue;
- }
+ // Use unsigned long to match ioctl's parameter type.
+ unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
- // 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)) {
- close(fd);
- continue;
- }
- }
-
- epoll_event ev;
- ev.events = EPOLLIN | EPOLLWAKEUP;
- ev.data.ptr = &ev_fdinfo[ev_count];
- if (epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
- close(fd);
- epollctlfail = true;
- continue;
- }
-
- ev_fdinfo[ev_count].fd = fd;
- ev_fdinfo[ev_count].cb = std::move(input_cb);
- ev_count++;
- ev_dev_count++;
- if (ev_dev_count == MAX_DEVICES) break;
+ // Read the evbits of the input device.
+ if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+ continue;
}
- closedir(dir);
+ // 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];
+ if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
+ epoll_ctl_failed = true;
+ continue;
+ }
+
+ ev_fdinfo[g_ev_count].fd.reset(fd.release());
+ ev_fdinfo[g_ev_count].cb = input_cb;
+ g_ev_count++;
+ g_ev_dev_count++;
+ if (g_ev_dev_count == MAX_DEVICES) break;
}
- if (epollctlfail && !ev_count) {
- close(g_epoll_fd);
- g_epoll_fd = -1;
+ if (epoll_ctl_failed && !g_ev_count) {
return -1;
}
+ g_epoll_fd.reset(epoll_fd.release());
return 0;
}
int ev_get_epollfd(void) {
- return g_epoll_fd;
+ return g_epoll_fd.get();
}
-int ev_add_fd(int fd, ev_callback cb) {
- if (ev_misc_count == MAX_MISC_FDS || cb == NULL) {
+int ev_add_fd(android::base::unique_fd&& fd, ev_callback cb) {
+ if (g_ev_misc_count == MAX_MISC_FDS || cb == nullptr) {
return -1;
}
epoll_event ev;
ev.events = EPOLLIN | EPOLLWAKEUP;
- ev.data.ptr = static_cast<void*>(&ev_fdinfo[ev_count]);
+ ev.data.ptr = static_cast<void*>(&ev_fdinfo[g_ev_count]);
int ret = epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev);
if (!ret) {
- ev_fdinfo[ev_count].fd = fd;
- ev_fdinfo[ev_count].cb = std::move(cb);
- ev_count++;
- ev_misc_count++;
+ ev_fdinfo[g_ev_count].fd.reset(fd.release());
+ ev_fdinfo[g_ev_count].cb = std::move(cb);
+ g_ev_count++;
+ g_ev_misc_count++;
}
return ret;
}
void ev_exit(void) {
- while (ev_count > 0) {
- close(ev_fdinfo[--ev_count].fd);
- }
- ev_misc_count = 0;
- ev_dev_count = 0;
- close(g_epoll_fd);
+ while (g_ev_count > 0) {
+ ev_fdinfo[--g_ev_count].fd.reset();
+ }
+ g_ev_misc_count = 0;
+ g_ev_dev_count = 0;
+ g_epoll_fd.reset();
}
int ev_wait(int timeout) {
- npolledevents = epoll_wait(g_epoll_fd, polledevents, ev_count, timeout);
- if (npolledevents <= 0) {
- return -1;
- }
- return 0;
+ g_polled_events_count = epoll_wait(g_epoll_fd, g_polled_events, g_ev_count, timeout);
+ if (g_polled_events_count <= 0) {
+ return -1;
+ }
+ return 0;
}
void ev_dispatch(void) {
- for (int n = 0; n < npolledevents; n++) {
- fd_info* fdi = static_cast<fd_info*>(polledevents[n].data.ptr);
+ for (int n = 0; n < g_polled_events_count; n++) {
+ FdInfo* fdi = static_cast<FdInfo*>(g_polled_events[n].data.ptr);
const ev_callback& cb = fdi->cb;
if (cb) {
- cb(fdi->fd, polledevents[n].events);
+ cb(fdi->fd, g_polled_events[n].events);
}
}
}
@@ -180,7 +184,7 @@
unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINT
- for (size_t i = 0; i < ev_dev_count; ++i) {
+ for (size_t i = 0; i < g_ev_dev_count; ++i) {
memset(ev_bits, 0, sizeof(ev_bits));
memset(key_bits, 0, sizeof(key_bits));
@@ -205,37 +209,36 @@
}
void ev_iterate_available_keys(const std::function<void(int)>& f) {
- // Use unsigned long to match ioctl's parameter type.
- unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
- unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINT
+ // Use unsigned long to match ioctl's parameter type.
+ unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
+ unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINT
- for (size_t i = 0; i < ev_dev_count; ++i) {
- memset(ev_bits, 0, sizeof(ev_bits));
- memset(key_bits, 0, sizeof(key_bits));
+ for (size_t i = 0; i < g_ev_dev_count; ++i) {
+ memset(ev_bits, 0, sizeof(ev_bits));
+ memset(key_bits, 0, sizeof(key_bits));
- // Does this device even have keys?
- if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
- continue;
- }
- if (!test_bit(EV_KEY, ev_bits)) {
- continue;
- }
-
- int rc = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX), key_bits);
- if (rc == -1) {
- continue;
- }
-
- for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {
- if (test_bit(key_code, key_bits)) {
- f(key_code);
- }
- }
+ // Does this device even have keys?
+ if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+ continue;
}
+ if (!test_bit(EV_KEY, ev_bits)) {
+ continue;
+ }
+
+ if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX), key_bits) == -1) {
+ continue;
+ }
+
+ for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {
+ if (test_bit(key_code, key_bits)) {
+ f(key_code);
+ }
+ }
+ }
}
void ev_iterate_touch_inputs(const std::function<void(int)>& action) {
- for (size_t i = 0; i < ev_dev_count; ++i) {
+ for (size_t i = 0; i < g_ev_dev_count; ++i) {
// Use unsigned long to match ioctl's parameter type.
unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)] = {}; // NOLINT
if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
diff --git a/minui/font_10x18.h b/minui/font_10x18.h
deleted file mode 100644
index 30dfb9c..0000000
--- a/minui/font_10x18.h
+++ /dev/null
@@ -1,214 +0,0 @@
-struct {
- unsigned width;
- unsigned height;
- unsigned char_width;
- unsigned char_height;
- unsigned char rundata[2973];
-} font = {
- .width = 960,
- .height = 18,
- .char_width = 10,
- .char_height = 18,
- .rundata = {
-0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x55,0x82,0x06,0x82,0x02,0x82,0x10,0x82,
-0x11,0x83,0x08,0x82,0x0a,0x82,0x04,0x82,0x46,0x82,0x08,0x82,0x07,0x84,0x06,
-0x84,0x0a,0x81,0x03,0x88,0x04,0x84,0x04,0x88,0x04,0x84,0x06,0x84,0x1e,0x81,
-0x0e,0x81,0x0a,0x84,0x06,0x84,0x07,0x82,0x05,0x85,0x07,0x84,0x04,0x86,0x04,
-0x88,0x02,0x88,0x04,0x84,0x04,0x82,0x04,0x82,0x02,0x88,0x05,0x86,0x01,0x82,
-0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x84,0x04,
-0x86,0x06,0x84,0x04,0x86,0x06,0x84,0x04,0x88,0x02,0x82,0x04,0x82,0x02,0x82,
-0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,
-0x88,0x03,0x86,0x0e,0x86,0x06,0x82,0x11,0x82,0x10,0x82,0x18,0x82,0x0f,0x84,
-0x0d,0x82,0x1c,0x82,0x09,0x84,0x7f,0x16,0x84,0x05,0x82,0x05,0x84,0x07,0x83,
-0x02,0x82,0x19,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x82,0x03,0x86,0x04,
-0x83,0x02,0x82,0x03,0x82,0x01,0x82,0x07,0x82,0x09,0x82,0x06,0x82,0x3e,0x82,
-0x04,0x84,0x06,0x83,0x06,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x03,
-0x82,0x09,0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,
-0x1c,0x82,0x0e,0x82,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x05,0x84,0x04,
-0x82,0x02,0x82,0x05,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,
-0x09,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x04,
-0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x03,0x82,0x02,0x82,
-0x03,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x02,
-0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
-0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x03,0x82,0x08,0x82,0x0c,
-0x82,0x05,0x84,0x11,0x82,0x0f,0x82,0x18,0x82,0x0e,0x82,0x02,0x82,0x0c,0x82,
-0x1c,0x82,0x0b,0x82,0x7f,0x15,0x82,0x08,0x82,0x08,0x82,0x05,0x82,0x01,0x82,
-0x01,0x82,0x19,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x82,0x02,0x82,0x01,
-0x82,0x01,0x82,0x02,0x82,0x01,0x82,0x01,0x82,0x03,0x82,0x01,0x82,0x07,0x82,
-0x08,0x82,0x08,0x82,0x3d,0x82,0x03,0x82,0x02,0x82,0x04,0x84,0x05,0x82,0x04,
-0x82,0x02,0x82,0x04,0x82,0x06,0x83,0x03,0x82,0x08,0x82,0x04,0x81,0x09,0x82,
-0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x1a,0x82,0x10,0x82,0x06,0x82,0x04,
-0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,
-0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,
-0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x83,
-0x02,0x83,0x02,0x83,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,
-0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,
-0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x04,
-0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x08,0x82,0x0c,0x82,0x04,0x82,0x02,0x82,
-0x11,0x82,0x0e,0x82,0x18,0x82,0x0e,0x82,0x02,0x82,0x0c,0x82,0x0b,0x82,0x0b,
-0x82,0x02,0x82,0x0b,0x82,0x4d,0x82,0x45,0x82,0x08,0x82,0x08,0x82,0x05,0x82,
-0x02,0x83,0x1a,0x82,0x07,0x81,0x02,0x81,0x07,0x82,0x01,0x82,0x02,0x82,0x01,
-0x82,0x05,0x82,0x01,0x84,0x04,0x82,0x01,0x82,0x07,0x82,0x08,0x82,0x08,0x82,
-0x06,0x82,0x02,0x82,0x06,0x82,0x28,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x01,
-0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x84,0x03,0x82,0x08,0x82,
-0x0d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x19,0x82,0x12,0x82,0x05,
-0x82,0x04,0x82,0x02,0x82,0x02,0x84,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82,
-0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x04,
-0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x83,0x02,0x83,
-0x02,0x84,0x02,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
-0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x0b,0x82,0x05,0x82,0x04,0x82,0x02,0x82,
-0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,
-0x82,0x04,0x82,0x09,0x82,0x0b,0x82,0x03,0x82,0x04,0x82,0x20,0x82,0x18,0x82,
-0x0e,0x82,0x10,0x82,0x0b,0x82,0x0b,0x82,0x02,0x82,0x0b,0x82,0x4d,0x82,0x45,
-0x82,0x08,0x82,0x08,0x82,0x26,0x82,0x10,0x88,0x01,0x82,0x01,0x82,0x06,0x83,
-0x01,0x82,0x04,0x84,0x08,0x81,0x08,0x82,0x0a,0x82,0x05,0x82,0x02,0x82,0x06,
-0x82,0x28,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x08,0x82,0x04,0x82,
-0x01,0x82,0x03,0x82,0x08,0x82,0x0d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,
-0x82,0x18,0x82,0x06,0x88,0x06,0x82,0x04,0x82,0x04,0x82,0x02,0x82,0x01,0x85,
-0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x04,0x82,0x02,
-0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,
-0x02,0x82,0x04,0x82,0x08,0x88,0x02,0x84,0x02,0x82,0x02,0x82,0x04,0x82,0x02,
-0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x0b,0x82,
-0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x04,0x84,0x06,
-0x84,0x08,0x82,0x05,0x82,0x09,0x82,0x0b,0x82,0x2b,0x82,0x18,0x82,0x0e,0x82,
-0x10,0x82,0x1c,0x82,0x0b,0x82,0x4d,0x82,0x45,0x82,0x08,0x82,0x08,0x82,0x26,
-0x82,0x11,0x82,0x01,0x82,0x03,0x82,0x01,0x82,0x09,0x82,0x06,0x82,0x12,0x82,
-0x0a,0x82,0x06,0x84,0x07,0x82,0x27,0x82,0x04,0x82,0x04,0x82,0x05,0x82,0x0b,
-0x82,0x07,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x01,0x83,0x04,0x82,0x01,0x83,
-0x08,0x82,0x05,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x05,0x83,0x07,0x83,0x05,
-0x82,0x16,0x82,0x08,0x82,0x03,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,
-0x02,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,
-0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,
-0x08,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,
-0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,
-0x0a,0x82,0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,
-0x82,0x04,0x84,0x06,0x84,0x08,0x82,0x05,0x82,0x0a,0x82,0x0a,0x82,0x23,0x85,
-0x03,0x82,0x01,0x83,0x06,0x85,0x05,0x83,0x01,0x82,0x04,0x84,0x04,0x86,0x05,
-0x85,0x01,0x81,0x02,0x82,0x01,0x83,0x05,0x84,0x09,0x84,0x02,0x82,0x03,0x82,
-0x06,0x82,0x05,0x81,0x01,0x82,0x01,0x82,0x03,0x82,0x01,0x83,0x06,0x84,0x04,
-0x82,0x01,0x83,0x06,0x83,0x01,0x82,0x02,0x82,0x01,0x84,0x04,0x86,0x03,0x86,
-0x04,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
-0x82,0x02,0x82,0x04,0x82,0x03,0x87,0x05,0x82,0x08,0x82,0x08,0x82,0x26,0x82,
-0x11,0x82,0x01,0x82,0x04,0x86,0x07,0x82,0x05,0x83,0x12,0x82,0x0a,0x82,0x04,
-0x88,0x02,0x88,0x0c,0x88,0x10,0x82,0x04,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,
-0x06,0x83,0x04,0x82,0x03,0x82,0x03,0x83,0x02,0x82,0x03,0x83,0x02,0x82,0x07,
-0x82,0x06,0x84,0x05,0x82,0x02,0x83,0x05,0x83,0x07,0x83,0x04,0x82,0x18,0x82,
-0x06,0x82,0x04,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x86,0x04,
-0x82,0x08,0x82,0x04,0x82,0x02,0x86,0x04,0x86,0x04,0x82,0x02,0x84,0x02,0x88,
-0x05,0x82,0x0a,0x82,0x03,0x85,0x05,0x82,0x08,0x82,0x01,0x82,0x01,0x82,0x02,
-0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,
-0x04,0x82,0x02,0x82,0x03,0x82,0x05,0x84,0x07,0x82,0x05,0x82,0x04,0x82,0x03,
-0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,0x82,0x05,0x82,0x08,0x82,0x08,0x82,
-0x06,0x82,0x0a,0x82,0x0a,0x82,0x22,0x82,0x03,0x82,0x02,0x83,0x02,0x82,0x04,
-0x82,0x03,0x82,0x03,0x82,0x02,0x83,0x03,0x82,0x02,0x82,0x05,0x82,0x06,0x82,
-0x03,0x83,0x02,0x83,0x02,0x82,0x06,0x82,0x0b,0x82,0x02,0x82,0x02,0x82,0x07,
-0x82,0x05,0x88,0x02,0x83,0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x82,
-0x04,0x82,0x02,0x83,0x03,0x83,0x02,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06,
-0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,
-0x03,0x82,0x04,0x82,0x08,0x82,0x02,0x84,0x09,0x82,0x09,0x84,0x23,0x82,0x11,
-0x82,0x01,0x82,0x06,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x01,0x82,0x11,0x82,
-0x0a,0x82,0x06,0x84,0x07,0x82,0x26,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x08,
-0x83,0x09,0x82,0x03,0x82,0x03,0x82,0x09,0x82,0x02,0x82,0x04,0x82,0x05,0x82,
-0x06,0x82,0x02,0x82,0x05,0x83,0x01,0x82,0x17,0x82,0x16,0x82,0x06,0x82,0x05,
-0x82,0x01,0x82,0x01,0x82,0x02,0x88,0x02,0x82,0x03,0x82,0x03,0x82,0x08,0x82,
-0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,
-0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x01,0x82,0x01,0x82,
-0x02,0x82,0x02,0x84,0x02,0x82,0x04,0x82,0x02,0x86,0x04,0x82,0x04,0x82,0x02,
-0x86,0x09,0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x82,
-0x01,0x82,0x04,0x84,0x07,0x82,0x07,0x82,0x07,0x82,0x0b,0x82,0x09,0x82,0x27,
-0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
-0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,
-0x82,0x01,0x82,0x08,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,
-0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x07,
-0x82,0x0a,0x82,0x06,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x04,0x82,
-0x04,0x84,0x04,0x82,0x04,0x82,0x07,0x82,0x06,0x82,0x08,0x82,0x08,0x82,0x26,
-0x82,0x0f,0x88,0x05,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x02,0x82,0x01,0x82,
-0x0d,0x82,0x0a,0x82,0x05,0x82,0x02,0x82,0x06,0x82,0x26,0x82,0x05,0x82,0x04,
-0x82,0x05,0x82,0x07,0x82,0x0c,0x82,0x02,0x88,0x08,0x82,0x02,0x82,0x04,0x82,
-0x05,0x82,0x05,0x82,0x04,0x82,0x08,0x82,0x18,0x82,0x14,0x82,0x07,0x82,0x05,
-0x82,0x01,0x84,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,
-0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,
-0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x01,0x82,0x01,0x82,
-0x02,0x82,0x02,0x84,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,
-0x82,0x02,0x82,0x0a,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x82,
-0x01,0x82,0x01,0x82,0x04,0x84,0x07,0x82,0x07,0x82,0x07,0x82,0x0b,0x82,0x09,
-0x82,0x22,0x87,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x88,
-0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,
-0x84,0x09,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,
-0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x08,0x86,0x05,
-0x82,0x06,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,0x82,
-0x05,0x82,0x05,0x82,0x04,0x82,0x06,0x82,0x07,0x82,0x08,0x82,0x08,0x82,0x26,
-0x82,0x10,0x82,0x01,0x82,0x07,0x82,0x01,0x82,0x04,0x82,0x01,0x83,0x02,0x82,
-0x03,0x83,0x0f,0x82,0x08,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x25,0x82,0x07,
-0x82,0x02,0x82,0x06,0x82,0x06,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x09,0x82,
-0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x04,0x82,0x08,0x82,0x19,0x82,0x05,
-0x88,0x05,0x82,0x08,0x82,0x05,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x02,0x82,
-0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,
-0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x03,0x82,0x03,0x82,0x03,0x82,
-0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02,
-0x82,0x08,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x03,0x82,0x09,0x82,0x05,0x82,
-0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x83,0x02,0x83,0x03,0x82,0x02,0x82,0x06,
-0x82,0x06,0x82,0x08,0x82,0x0c,0x82,0x08,0x82,0x21,0x82,0x04,0x82,0x02,0x82,
-0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a,0x82,0x06,0x82,0x03,
-0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x85,0x08,0x82,0x05,0x82,
-0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
-0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0d,0x82,0x04,0x82,0x06,0x82,0x04,0x82,
-0x04,0x84,0x04,0x82,0x01,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x05,
-0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x38,0x82,0x01,0x82,0x04,0x82,0x01,0x82,
-0x01,0x82,0x04,0x84,0x01,0x82,0x01,0x82,0x03,0x82,0x10,0x82,0x08,0x82,0x30,
-0x83,0x06,0x82,0x07,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x08,0x82,0x04,0x82,
-0x07,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x04,
-0x82,0x03,0x81,0x04,0x82,0x1a,0x82,0x10,0x82,0x10,0x82,0x08,0x82,0x04,0x82,
-0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,
-0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x03,0x82,
-0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,
-0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x02,0x84,0x02,0x82,0x03,0x82,0x03,0x82,
-0x04,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x05,0x83,0x02,0x83,0x03,
-0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x09,0x82,0x0c,0x82,0x08,0x82,0x21,0x82,
-0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a,
-0x82,0x07,0x85,0x04,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x82,0x02,0x82,
-0x07,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
-0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0d,0x82,0x04,0x82,
-0x06,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x82,0x01,0x82,0x04,0x84,0x04,
-0x82,0x04,0x82,0x04,0x82,0x09,0x82,0x08,0x82,0x08,0x82,0x26,0x82,0x10,0x82,
-0x01,0x82,0x05,0x86,0x04,0x82,0x01,0x82,0x01,0x82,0x01,0x83,0x01,0x84,0x10,
-0x82,0x06,0x82,0x1d,0x83,0x11,0x83,0x05,0x82,0x09,0x84,0x07,0x82,0x05,0x82,
-0x09,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
-0x82,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x06,0x83,0x07,0x83,0x09,0x82,
-0x0e,0x82,0x0a,0x82,0x06,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x03,
-0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x09,0x82,
-0x02,0x83,0x02,0x82,0x04,0x82,0x05,0x82,0x06,0x82,0x01,0x82,0x04,0x82,0x04,
-0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,
-0x03,0x82,0x09,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x06,
-0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
-0x05,0x82,0x05,0x82,0x09,0x82,0x0d,0x82,0x07,0x82,0x21,0x82,0x04,0x82,0x02,
-0x83,0x02,0x82,0x04,0x82,0x03,0x82,0x03,0x82,0x02,0x83,0x03,0x82,0x03,0x82,
-0x04,0x82,0x06,0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x82,0x03,
-0x82,0x06,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x03,0x82,
-0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x07,0x82,0x04,
-0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x02,0x83,0x05,0x82,0x05,0x88,0x03,0x82,
-0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x0a,0x82,0x08,0x82,0x08,0x82,0x26,
-0x82,0x1c,0x82,0x06,0x82,0x02,0x83,0x03,0x84,0x02,0x82,0x10,0x82,0x04,0x82,
-0x1e,0x83,0x11,0x83,0x05,0x82,0x0a,0x82,0x05,0x88,0x02,0x88,0x04,0x84,0x09,
-0x82,0x05,0x84,0x06,0x84,0x05,0x82,0x09,0x84,0x06,0x84,0x07,0x83,0x07,0x83,
-0x0a,0x81,0x0e,0x81,0x0b,0x82,0x07,0x85,0x03,0x82,0x04,0x82,0x02,0x86,0x06,
-0x84,0x04,0x86,0x04,0x88,0x02,0x82,0x0a,0x84,0x01,0x81,0x02,0x82,0x04,0x82,
-0x02,0x88,0x04,0x83,0x05,0x82,0x04,0x82,0x02,0x88,0x02,0x82,0x04,0x82,0x02,
-0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x0a,0x85,0x03,0x82,0x04,0x82,0x04,0x84,
-0x07,0x82,0x07,0x84,0x07,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,
-0x82,0x05,0x88,0x03,0x86,0x09,0x82,0x03,0x86,0x22,0x85,0x01,0x81,0x02,0x82,
-0x01,0x83,0x06,0x85,0x05,0x83,0x01,0x82,0x04,0x85,0x05,0x82,0x07,0x86,0x03,
-0x82,0x04,0x82,0x02,0x88,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x88,0x02,0x82,
-0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x83,0x06,
-0x83,0x01,0x82,0x03,0x82,0x08,0x86,0x06,0x84,0x05,0x83,0x01,0x82,0x05,0x82,
-0x06,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x04,0x83,0x01,0x82,0x03,0x87,0x06,
-0x84,0x05,0x82,0x05,0x84,0x7f,0x15,0x83,0x7f,0x14,0x83,0x7f,0x5e,0x82,0x7f,
-0x05,0x89,0x47,0x82,0x04,0x82,0x17,0x82,0x03,0x82,0x34,0x82,0x0e,0x82,0x4e,
-0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0a,0x82,0x04,0x82,0x17,0x82,0x03,0x82,
-0x34,0x82,0x0e,0x82,0x48,0x82,0x04,0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0a,
-0x82,0x04,0x82,0x17,0x82,0x03,0x82,0x34,0x82,0x0e,0x82,0x49,0x82,0x02,0x82,
-0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0c,0x86,0x19,0x85,0x35,0x82,0x0e,0x82,0x4a,
-0x84,0x3f,
-0x00,
- }
-};
diff --git a/minui/graphics.cpp b/minui/graphics.cpp
index 56f471b..4d1f9b2 100644
--- a/minui/graphics.cpp
+++ b/minui/graphics.cpp
@@ -23,41 +23,57 @@
#include <memory>
-#include "font_10x18.h"
+#include <android-base/properties.h>
+
#include "graphics_adf.h"
#include "graphics_drm.h"
#include "graphics_fbdev.h"
#include "minui/minui.h"
-static GRFont* gr_font = NULL;
+static GRFont* gr_font = nullptr;
static MinuiBackend* gr_backend = nullptr;
-static int overscan_percent = OVERSCAN_PERCENT;
static int overscan_offset_x = 0;
static int overscan_offset_y = 0;
static uint32_t gr_current = ~0;
static constexpr uint32_t alpha_mask = 0xff000000;
-static GRSurface* gr_draw = NULL;
-static GRRotation rotation = ROTATION_NONE;
+// gr_draw is owned by backends.
+static GRSurface* gr_draw = nullptr;
+static GRRotation rotation = GRRotation::NONE;
+static PixelFormat pixel_format = PixelFormat::UNKNOWN;
static bool outside(int x, int y) {
- return x < 0 || x >= (rotation % 2 ? gr_draw->height : gr_draw->width) || y < 0 ||
- y >= (rotation % 2 ? gr_draw->width : gr_draw->height);
+ auto swapped = (rotation == GRRotation::LEFT || rotation == GRRotation::RIGHT);
+ return x < 0 || x >= (swapped ? gr_draw->height : gr_draw->width) || y < 0 ||
+ y >= (swapped ? gr_draw->width : gr_draw->height);
}
const GRFont* gr_sys_font() {
return gr_font;
}
+PixelFormat gr_pixel_format() {
+ return pixel_format;
+}
+
int gr_measure(const GRFont* font, const char* s) {
+ if (font == nullptr) {
+ return -1;
+ }
+
return font->char_width * strlen(s);
}
-void gr_font_size(const GRFont* font, int* x, int* y) {
+int gr_font_size(const GRFont* font, int* x, int* y) {
+ if (font == nullptr) {
+ return -1;
+ }
+
*x = font->char_width;
*y = font->char_height;
+ return 0;
}
// Blends gr_current onto pix value, assumes alpha as most significant byte.
@@ -78,47 +94,56 @@
return (out_r & 0xff) | (out_g & 0xff00) | (out_b & 0xff0000) | (gr_current & 0xff000000);
}
-// increments pixel pointer right, with current rotation.
+// Increments pixel pointer right, with current rotation.
static void incr_x(uint32_t** p, int row_pixels) {
- if (rotation % 2) {
- *p = *p + (rotation == 1 ? 1 : -1) * row_pixels;
- } else {
- *p = *p + (rotation ? -1 : 1);
+ if (rotation == GRRotation::LEFT) {
+ *p = *p - row_pixels;
+ } else if (rotation == GRRotation::RIGHT) {
+ *p = *p + row_pixels;
+ } else if (rotation == GRRotation::DOWN) {
+ *p = *p - 1;
+ } else { // GRRotation::NONE
+ *p = *p + 1;
}
}
-// increments pixel pointer down, with current rotation.
+// Increments pixel pointer down, with current rotation.
static void incr_y(uint32_t** p, int row_pixels) {
- if (rotation % 2) {
- *p = *p + (rotation == 1 ? -1 : 1);
- } else {
- *p = *p + (rotation ? -1 : 1) * row_pixels;
+ if (rotation == GRRotation::LEFT) {
+ *p = *p + 1;
+ } else if (rotation == GRRotation::RIGHT) {
+ *p = *p - 1;
+ } else if (rotation == GRRotation::DOWN) {
+ *p = *p - row_pixels;
+ } else { // GRRotation::NONE
+ *p = *p + row_pixels;
}
}
-// returns pixel pointer at given coordinates with rotation adjustment.
-static uint32_t* pixel_at(GRSurface* surf, int x, int y, int row_pixels) {
+// Returns pixel pointer at given coordinates with rotation adjustment.
+static uint32_t* PixelAt(GRSurface* surface, int x, int y, int row_pixels) {
switch (rotation) {
- case ROTATION_NONE:
- return reinterpret_cast<uint32_t*>(surf->data) + y * row_pixels + x;
- case ROTATION_RIGHT:
- return reinterpret_cast<uint32_t*>(surf->data) + x * row_pixels + (surf->width - y);
- case ROTATION_DOWN:
- return reinterpret_cast<uint32_t*>(surf->data) + (surf->height - 1 - y) * row_pixels +
- (surf->width - 1 - x);
- case ROTATION_LEFT:
- return reinterpret_cast<uint32_t*>(surf->data) + (surf->height - 1 - x) * row_pixels + y;
+ case GRRotation::NONE:
+ return reinterpret_cast<uint32_t*>(surface->data()) + y * row_pixels + x;
+ case GRRotation::RIGHT:
+ return reinterpret_cast<uint32_t*>(surface->data()) + x * row_pixels + (surface->width - y);
+ case GRRotation::DOWN:
+ return reinterpret_cast<uint32_t*>(surface->data()) + (surface->height - 1 - y) * row_pixels +
+ (surface->width - 1 - x);
+ case GRRotation::LEFT:
+ return reinterpret_cast<uint32_t*>(surface->data()) + (surface->height - 1 - x) * row_pixels +
+ y;
default:
- printf("invalid rotation %d", rotation);
+ printf("invalid rotation %d", static_cast<int>(rotation));
}
return nullptr;
}
-static void text_blend(uint8_t* src_p, int src_row_bytes, uint32_t* dst_p, int dst_row_pixels,
- int width, int height) {
+static void TextBlend(const uint8_t* src_p, int src_row_bytes, uint32_t* dst_p, int dst_row_pixels,
+ int width, int height) {
uint8_t alpha_current = static_cast<uint8_t>((alpha_mask & gr_current) >> 24);
for (int j = 0; j < height; ++j) {
- uint8_t* sx = src_p;
+ const uint8_t* sx = src_p;
uint32_t* px = dst_p;
for (int i = 0; i < width; ++i, incr_x(&px, dst_row_pixels)) {
uint8_t a = *sx++;
@@ -152,19 +177,19 @@
}
int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes;
- uint8_t* src_p = font->texture->data + ((ch - ' ') * font->char_width) +
- (bold ? font->char_height * font->texture->row_bytes : 0);
- uint32_t* dst_p = pixel_at(gr_draw, x, y, row_pixels);
+ const uint8_t* src_p = font->texture->data() + ((ch - ' ') * font->char_width) +
+ (bold ? font->char_height * font->texture->row_bytes : 0);
+ uint32_t* dst_p = PixelAt(gr_draw, x, y, row_pixels);
- text_blend(src_p, font->texture->row_bytes, dst_p, row_pixels, font->char_width,
- font->char_height);
+ TextBlend(src_p, font->texture->row_bytes, dst_p, row_pixels, font->char_width,
+ font->char_height);
x += font->char_width;
}
}
-void gr_texticon(int x, int y, GRSurface* icon) {
- if (icon == NULL) return;
+void gr_texticon(int x, int y, const GRSurface* icon) {
+ if (icon == nullptr) return;
if (icon->pixel_bytes != 1) {
printf("gr_texticon: source has wrong format\n");
@@ -177,19 +202,18 @@
if (outside(x, y) || outside(x + icon->width - 1, y + icon->height - 1)) return;
int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes;
- uint8_t* src_p = icon->data;
- uint32_t* dst_p = pixel_at(gr_draw, x, y, row_pixels);
-
- text_blend(src_p, icon->row_bytes, dst_p, row_pixels, icon->width, icon->height);
+ const uint8_t* src_p = icon->data();
+ uint32_t* dst_p = PixelAt(gr_draw, x, y, row_pixels);
+ TextBlend(src_p, icon->row_bytes, dst_p, row_pixels, icon->width, icon->height);
}
void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
uint32_t r32 = r, g32 = g, b32 = b, a32 = a;
-#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
- gr_current = (a32 << 24) | (r32 << 16) | (g32 << 8) | b32;
-#else
- gr_current = (a32 << 24) | (b32 << 16) | (g32 << 8) | r32;
-#endif
+ if (pixel_format == PixelFormat::ABGR || pixel_format == PixelFormat::BGRA) {
+ gr_current = (a32 << 24) | (r32 << 16) | (g32 << 8) | b32;
+ } else {
+ gr_current = (a32 << 24) | (b32 << 16) | (g32 << 8) | r32;
+ }
}
void gr_clear() {
@@ -197,9 +221,9 @@
(gr_current & 0xff) == ((gr_current >> 16) & 0xff) &&
(gr_current & 0xff) == ((gr_current >> 24) & 0xff) &&
gr_draw->row_bytes == gr_draw->width * gr_draw->pixel_bytes) {
- memset(gr_draw->data, gr_current & 0xff, gr_draw->height * gr_draw->row_bytes);
+ memset(gr_draw->data(), gr_current & 0xff, gr_draw->height * gr_draw->row_bytes);
} else {
- uint32_t* px = reinterpret_cast<uint32_t*>(gr_draw->data);
+ uint32_t* px = reinterpret_cast<uint32_t*>(gr_draw->data());
int row_diff = gr_draw->row_bytes / gr_draw->pixel_bytes - gr_draw->width;
for (int y = 0; y < gr_draw->height; ++y) {
for (int x = 0; x < gr_draw->width; ++x) {
@@ -220,7 +244,7 @@
if (outside(x1, y1) || outside(x2 - 1, y2 - 1)) return;
int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes;
- uint32_t* p = pixel_at(gr_draw, x1, y1, row_pixels);
+ uint32_t* p = PixelAt(gr_draw, x1, y1, row_pixels);
uint8_t alpha = static_cast<uint8_t>(((gr_current & alpha_mask) >> 24));
if (alpha > 0) {
for (int y = y1; y < y2; ++y) {
@@ -234,8 +258,8 @@
}
}
-void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) {
- if (source == NULL) return;
+void gr_blit(const GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) {
+ if (source == nullptr) return;
if (gr_draw->pixel_bytes != source->pixel_bytes) {
printf("gr_blit: source has wrong format\n");
@@ -247,14 +271,15 @@
if (outside(dx, dy) || outside(dx + w - 1, dy + h - 1)) return;
- if (rotation) {
+ if (rotation != GRRotation::NONE) {
int src_row_pixels = source->row_bytes / source->pixel_bytes;
int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes;
- uint32_t* src_py = reinterpret_cast<uint32_t*>(source->data) + sy * source->row_bytes / 4 + sx;
- uint32_t* dst_py = pixel_at(gr_draw, dx, dy, row_pixels);
+ const uint32_t* src_py =
+ reinterpret_cast<const uint32_t*>(source->data()) + sy * source->row_bytes / 4 + sx;
+ uint32_t* dst_py = PixelAt(gr_draw, dx, dy, row_pixels);
for (int y = 0; y < h; y += 1) {
- uint32_t* src_px = src_py;
+ const uint32_t* src_px = src_py;
uint32_t* dst_px = dst_py;
for (int x = 0; x < w; x += 1) {
*dst_px = *src_px++;
@@ -264,11 +289,10 @@
incr_y(&dst_py, row_pixels);
}
} else {
- unsigned char* src_p = source->data + sy * source->row_bytes + sx * source->pixel_bytes;
- unsigned char* dst_p = gr_draw->data + dy * gr_draw->row_bytes + dx * gr_draw->pixel_bytes;
+ const uint8_t* src_p = source->data() + sy * source->row_bytes + sx * source->pixel_bytes;
+ uint8_t* dst_p = gr_draw->data() + dy * gr_draw->row_bytes + dx * gr_draw->pixel_bytes;
- int i;
- for (i = 0; i < h; ++i) {
+ for (int i = 0; i < h; ++i) {
memcpy(dst_p, src_p, w * source->pixel_bytes);
src_p += source->row_bytes;
dst_p += gr_draw->row_bytes;
@@ -276,15 +300,15 @@
}
}
-unsigned int gr_get_width(GRSurface* surface) {
- if (surface == NULL) {
+unsigned int gr_get_width(const GRSurface* surface) {
+ if (surface == nullptr) {
return 0;
}
return surface->width;
}
-unsigned int gr_get_height(GRSurface* surface) {
- if (surface == NULL) {
+unsigned int gr_get_height(const GRSurface* surface) {
+ if (surface == nullptr) {
return 0;
}
return surface->height;
@@ -313,42 +337,28 @@
return 0;
}
-static void gr_init_font(void) {
- int res = gr_init_font("font", &gr_font);
- if (res == 0) {
- return;
- }
-
- printf("failed to read font: res=%d\n", res);
-
- // fall back to the compiled-in font.
- gr_font = static_cast<GRFont*>(calloc(1, sizeof(*gr_font)));
- gr_font->texture = static_cast<GRSurface*>(malloc(sizeof(*gr_font->texture)));
- gr_font->texture->width = font.width;
- gr_font->texture->height = font.height;
- gr_font->texture->row_bytes = font.width;
- gr_font->texture->pixel_bytes = 1;
-
- unsigned char* bits = static_cast<unsigned char*>(malloc(font.width * font.height));
- gr_font->texture->data = bits;
-
- unsigned char data;
- unsigned char* in = font.rundata;
- while ((data = *in++)) {
- memset(bits, (data & 0x80) ? 255 : 0, data & 0x7f);
- bits += (data & 0x7f);
- }
-
- gr_font->char_width = font.char_width;
- gr_font->char_height = font.char_height;
-}
-
void gr_flip() {
gr_draw = gr_backend->Flip();
}
int gr_init() {
- gr_init_font();
+ // pixel_format needs to be set before loading any resources or initializing backends.
+ std::string format = android::base::GetProperty("ro.minui.pixel_format", "");
+ if (format == "ABGR_8888") {
+ pixel_format = PixelFormat::ABGR;
+ } else if (format == "RGBX_8888") {
+ pixel_format = PixelFormat::RGBX;
+ } else if (format == "BGRA_8888") {
+ pixel_format = PixelFormat::BGRA;
+ } else {
+ pixel_format = PixelFormat::UNKNOWN;
+ }
+
+ int ret = gr_init_font("font", &gr_font);
+ if (ret != 0) {
+ printf("Failed to init font: %d, continuing graphic backend initialization without font file\n",
+ ret);
+ }
auto backend = std::unique_ptr<MinuiBackend>{ std::make_unique<MinuiBackendAdf>() };
gr_draw = backend->Init();
@@ -369,13 +379,28 @@
gr_backend = backend.release();
+ int overscan_percent = android::base::GetIntProperty("ro.minui.overscan_percent", 0);
overscan_offset_x = gr_draw->width * overscan_percent / 100;
overscan_offset_y = gr_draw->height * overscan_percent / 100;
gr_flip();
gr_flip();
+ if (!gr_draw) {
+ printf("gr_init: gr_draw becomes nullptr after gr_flip\n");
+ return -1;
+ }
- gr_rotate(DEFAULT_ROTATION);
+ std::string rotation_str =
+ android::base::GetProperty("ro.minui.default_rotation", "ROTATION_NONE");
+ if (rotation_str == "ROTATION_RIGHT") {
+ gr_rotate(GRRotation::RIGHT);
+ } else if (rotation_str == "ROTATION_DOWN") {
+ gr_rotate(GRRotation::DOWN);
+ } else if (rotation_str == "ROTATION_LEFT") {
+ gr_rotate(GRRotation::LEFT);
+ } else { // "ROTATION_NONE" or unknown string
+ gr_rotate(GRRotation::NONE);
+ }
if (gr_draw->pixel_bytes != 4) {
printf("gr_init: Only 4-byte pixel formats supported\n");
@@ -386,16 +411,22 @@
void gr_exit() {
delete gr_backend;
+ gr_backend = nullptr;
+
+ delete gr_font;
+ gr_font = nullptr;
}
int gr_fb_width() {
- return rotation % 2 ? gr_draw->height - 2 * overscan_offset_y
- : gr_draw->width - 2 * overscan_offset_x;
+ return (rotation == GRRotation::LEFT || rotation == GRRotation::RIGHT)
+ ? gr_draw->height - 2 * overscan_offset_y
+ : gr_draw->width - 2 * overscan_offset_x;
}
int gr_fb_height() {
- return rotation % 2 ? gr_draw->width - 2 * overscan_offset_x
- : gr_draw->height - 2 * overscan_offset_y;
+ return (rotation == GRRotation::LEFT || rotation == GRRotation::RIGHT)
+ ? gr_draw->width - 2 * overscan_offset_x
+ : gr_draw->height - 2 * overscan_offset_y;
}
void gr_fb_blank(bool blank) {
diff --git a/minui/graphics_adf.cpp b/minui/graphics_adf.cpp
index a59df00..10cd607 100644
--- a/minui/graphics_adf.cpp
+++ b/minui/graphics_adf.cpp
@@ -20,6 +20,7 @@
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
@@ -28,51 +29,60 @@
#include "minui/minui.h"
-MinuiBackendAdf::MinuiBackendAdf()
- : intf_fd(-1), dev(), current_surface(0), n_surfaces(0), surfaces() {}
-
-int MinuiBackendAdf::SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf) {
- *surf = {};
- surf->fence_fd = -1;
- surf->fd = adf_interface_simple_buffer_alloc(intf_fd, mode->hdisplay, mode->vdisplay, format,
- &surf->offset, &surf->pitch);
- if (surf->fd < 0) {
- return surf->fd;
+GRSurfaceAdf::~GRSurfaceAdf() {
+ if (mmapped_buffer_) {
+ munmap(mmapped_buffer_, pitch * height);
}
-
- surf->width = mode->hdisplay;
- surf->height = mode->vdisplay;
- surf->row_bytes = surf->pitch;
- surf->pixel_bytes = (format == DRM_FORMAT_RGB565) ? 2 : 4;
-
- surf->data = static_cast<uint8_t*>(
- mmap(nullptr, surf->pitch * surf->height, PROT_WRITE, MAP_SHARED, surf->fd, surf->offset));
- if (surf->data == MAP_FAILED) {
- int saved_errno = errno;
- close(surf->fd);
- return -saved_errno;
+ if (fence_fd != -1) {
+ close(fence_fd);
}
-
- return 0;
+ if (fd != -1) {
+ close(fd);
+ }
}
+std::unique_ptr<GRSurfaceAdf> GRSurfaceAdf::Create(int intf_fd, const drm_mode_modeinfo* mode,
+ __u32 format, int* err) {
+ __u32 offset;
+ __u32 pitch;
+ auto fd = adf_interface_simple_buffer_alloc(intf_fd, mode->hdisplay, mode->vdisplay, format,
+ &offset, &pitch);
+
+ if (fd < 0) {
+ *err = fd;
+ return nullptr;
+ }
+
+ std::unique_ptr<GRSurfaceAdf> surf = std::unique_ptr<GRSurfaceAdf>(
+ new GRSurfaceAdf(mode->hdisplay, mode->vdisplay, pitch, (format == DRM_FORMAT_RGB565 ? 2 : 4),
+ offset, pitch, fd));
+
+ auto mmapped =
+ mmap(nullptr, surf->pitch * surf->height, PROT_WRITE, MAP_SHARED, surf->fd, surf->offset);
+ if (mmapped == MAP_FAILED) {
+ *err = -errno;
+ return nullptr;
+ }
+ surf->mmapped_buffer_ = static_cast<uint8_t*>(mmapped);
+ return surf;
+}
+
+MinuiBackendAdf::MinuiBackendAdf() : intf_fd(-1), dev(), current_surface(0), n_surfaces(0) {}
+
int MinuiBackendAdf::InterfaceInit() {
adf_interface_data intf_data;
- int err = adf_get_interface_data(intf_fd, &intf_data);
- if (err < 0) return err;
+ if (int err = adf_get_interface_data(intf_fd, &intf_data); err < 0) return err;
- int ret = 0;
- err = SurfaceInit(&intf_data.current_mode, &surfaces[0]);
- if (err < 0) {
- fprintf(stderr, "allocating surface 0 failed: %s\n", strerror(-err));
- ret = err;
+ int result = 0;
+ surfaces[0] = GRSurfaceAdf::Create(intf_fd, &intf_data.current_mode, format, &result);
+ if (!surfaces[0]) {
+ fprintf(stderr, "Failed to allocate surface 0: %s\n", strerror(-result));
goto done;
}
- err = SurfaceInit(&intf_data.current_mode, &surfaces[1]);
- if (err < 0) {
- fprintf(stderr, "allocating surface 1 failed: %s\n", strerror(-err));
- surfaces[1] = {};
+ surfaces[1] = GRSurfaceAdf::Create(intf_fd, &intf_data.current_mode, format, &result);
+ if (!surfaces[1]) {
+ fprintf(stderr, "Failed to allocate surface 1: %s\n", strerror(-result));
n_surfaces = 1;
} else {
n_surfaces = 2;
@@ -80,7 +90,7 @@
done:
adf_free_interface_data(&intf_data);
- return ret;
+ return result;
}
int MinuiBackendAdf::DeviceInit(adf_device* dev) {
@@ -91,7 +101,7 @@
err = adf_device_attach(dev, eng_id, intf_id);
if (err < 0 && err != -EALREADY) return err;
- intf_fd = adf_interface_open(dev, intf_id, O_RDWR);
+ intf_fd = adf_interface_open(dev, intf_id, O_RDWR | O_CLOEXEC);
if (intf_fd < 0) return intf_fd;
err = InterfaceInit();
@@ -104,15 +114,16 @@
}
GRSurface* MinuiBackendAdf::Init() {
-#if defined(RECOVERY_ABGR)
- format = DRM_FORMAT_ABGR8888;
-#elif defined(RECOVERY_BGRA)
- format = DRM_FORMAT_BGRA8888;
-#elif defined(RECOVERY_RGBX)
- format = DRM_FORMAT_RGBX8888;
-#else
- format = DRM_FORMAT_RGB565;
-#endif
+ PixelFormat pixel_format = gr_pixel_format();
+ if (pixel_format == PixelFormat::ABGR) {
+ format = DRM_FORMAT_ABGR8888;
+ } else if (pixel_format == PixelFormat::BGRA) {
+ format = DRM_FORMAT_BGRA8888;
+ } else if (pixel_format == PixelFormat::RGBX) {
+ format = DRM_FORMAT_RGBX8888;
+ } else {
+ format = DRM_FORMAT_RGB565;
+ }
adf_id_t* dev_ids = nullptr;
ssize_t n_dev_ids = adf_devices(&dev_ids);
@@ -152,12 +163,12 @@
}
void MinuiBackendAdf::Sync(GRSurfaceAdf* surf) {
- static constexpr unsigned int warningTimeout = 3000;
+ static constexpr unsigned int kWarningTimeout = 3000;
if (surf == nullptr) return;
if (surf->fence_fd >= 0) {
- int err = sync_wait(surf->fence_fd, warningTimeout);
+ int err = sync_wait(surf->fence_fd, kWarningTimeout);
if (err < 0) {
perror("adf sync fence wait error\n");
}
@@ -168,31 +179,22 @@
}
GRSurface* MinuiBackendAdf::Flip() {
- GRSurfaceAdf* surf = &surfaces[current_surface];
+ const auto& surf = surfaces[current_surface];
int fence_fd = adf_interface_simple_post(intf_fd, eng_id, surf->width, surf->height, format,
surf->fd, surf->offset, surf->pitch, -1);
if (fence_fd >= 0) surf->fence_fd = fence_fd;
current_surface = (current_surface + 1) % n_surfaces;
- Sync(&surfaces[current_surface]);
- return &surfaces[current_surface];
+ Sync(surfaces[current_surface].get());
+ return surfaces[current_surface].get();
}
void MinuiBackendAdf::Blank(bool blank) {
adf_interface_blank(intf_fd, blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON);
}
-void MinuiBackendAdf::SurfaceDestroy(GRSurfaceAdf* surf) {
- munmap(surf->data, surf->pitch * surf->height);
- close(surf->fence_fd);
- close(surf->fd);
-}
-
MinuiBackendAdf::~MinuiBackendAdf() {
adf_device_close(&dev);
- for (unsigned int i = 0; i < n_surfaces; i++) {
- SurfaceDestroy(&surfaces[i]);
- }
if (intf_fd >= 0) close(intf_fd);
}
diff --git a/minui/graphics_adf.h b/minui/graphics_adf.h
index 2f019ed..79d8d2a 100644
--- a/minui/graphics_adf.h
+++ b/minui/graphics_adf.h
@@ -14,45 +14,63 @@
* limitations under the License.
*/
-#ifndef _GRAPHICS_ADF_H_
-#define _GRAPHICS_ADF_H_
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <memory>
#include <adf/adf.h>
#include "graphics.h"
+#include "minui/minui.h"
class GRSurfaceAdf : public GRSurface {
- private:
- int fence_fd;
- int fd;
- __u32 offset;
- __u32 pitch;
+ public:
+ ~GRSurfaceAdf() override;
+ static std::unique_ptr<GRSurfaceAdf> Create(int intf_fd, const drm_mode_modeinfo* mode,
+ __u32 format, int* err);
+
+ uint8_t* data() override {
+ return mmapped_buffer_;
+ }
+
+ private:
friend class MinuiBackendAdf;
+
+ GRSurfaceAdf(size_t width, size_t height, size_t row_bytes, size_t pixel_bytes, __u32 offset,
+ __u32 pitch, int fd)
+ : GRSurface(width, height, row_bytes, pixel_bytes), offset(offset), pitch(pitch), fd(fd) {}
+
+ const __u32 offset;
+ const __u32 pitch;
+
+ int fd;
+ int fence_fd{ -1 };
+ uint8_t* mmapped_buffer_{ nullptr };
};
class MinuiBackendAdf : public MinuiBackend {
public:
+ MinuiBackendAdf();
+ ~MinuiBackendAdf() override;
GRSurface* Init() override;
GRSurface* Flip() override;
void Blank(bool) override;
- ~MinuiBackendAdf() override;
- MinuiBackendAdf();
private:
- int SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf);
int InterfaceInit();
int DeviceInit(adf_device* dev);
- void SurfaceDestroy(GRSurfaceAdf* surf);
void Sync(GRSurfaceAdf* surf);
int intf_fd;
adf_id_t eng_id;
__u32 format;
adf_device dev;
- unsigned int current_surface;
- unsigned int n_surfaces;
- GRSurfaceAdf surfaces[2];
+ size_t current_surface;
+ size_t n_surfaces;
+ std::unique_ptr<GRSurfaceAdf> surfaces[2];
};
-
-#endif // _GRAPHICS_ADF_H_
diff --git a/minui/graphics_drm.cpp b/minui/graphics_drm.cpp
index e7d4b38..7b2eed1 100644
--- a/minui/graphics_drm.cpp
+++ b/minui/graphics_drm.cpp
@@ -17,78 +17,44 @@
#include "graphics_drm.h"
#include <fcntl.h>
+#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
+#include <memory>
+
+#include <android-base/macros.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
#include <drm_fourcc.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include "minui/minui.h"
-#define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A)))
-
-MinuiBackendDrm::MinuiBackendDrm()
- : GRSurfaceDrms(), main_monitor_crtc(nullptr), main_monitor_connector(nullptr), drm_fd(-1) {}
-
-void MinuiBackendDrm::DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc) {
- if (crtc) {
- drmModeSetCrtc(drm_fd, crtc->crtc_id,
- 0, // fb_id
- 0, 0, // x,y
- nullptr, // connectors
- 0, // connector_count
- nullptr); // mode
- }
-}
-
-void MinuiBackendDrm::DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, GRSurfaceDrm* surface) {
- int32_t ret = drmModeSetCrtc(drm_fd, crtc->crtc_id, surface->fb_id, 0, 0, // x,y
- &main_monitor_connector->connector_id,
- 1, // connector_count
- &main_monitor_crtc->mode);
-
- if (ret) {
- printf("drmModeSetCrtc failed ret=%d\n", ret);
- }
-}
-
-void MinuiBackendDrm::Blank(bool blank) {
- if (blank) {
- DrmDisableCrtc(drm_fd, main_monitor_crtc);
- } else {
- DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[current_buffer]);
- }
-}
-
-void MinuiBackendDrm::DrmDestroySurface(GRSurfaceDrm* surface) {
- if (!surface) return;
-
- if (surface->data) {
- munmap(surface->data, surface->row_bytes * surface->height);
+GRSurfaceDrm::~GRSurfaceDrm() {
+ if (mmapped_buffer_) {
+ munmap(mmapped_buffer_, row_bytes * height);
}
- if (surface->fb_id) {
- int ret = drmModeRmFB(drm_fd, surface->fb_id);
- if (ret) {
- printf("drmModeRmFB failed ret=%d\n", ret);
+ if (fb_id) {
+ if (drmModeRmFB(drm_fd_, fb_id) != 0) {
+ perror("Failed to drmModeRmFB");
+ // Falling through to free other resources.
}
}
- if (surface->handle) {
+ if (handle) {
drm_gem_close gem_close = {};
- gem_close.handle = surface->handle;
+ gem_close.handle = handle;
- int ret = drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
- if (ret) {
- printf("DRM_IOCTL_GEM_CLOSE failed ret=%d\n", ret);
+ if (drmIoctl(drm_fd_, DRM_IOCTL_GEM_CLOSE, &gem_close) != 0) {
+ perror("Failed to DRM_IOCTL_GEM_CLOSE");
}
}
-
- delete surface;
}
static int drm_format_to_bpp(uint32_t format) {
@@ -108,20 +74,22 @@
}
}
-GRSurfaceDrm* MinuiBackendDrm::DrmCreateSurface(int width, int height) {
- GRSurfaceDrm* surface = new GRSurfaceDrm;
- *surface = {};
-
+std::unique_ptr<GRSurfaceDrm> GRSurfaceDrm::Create(int drm_fd, int width, int height) {
uint32_t format;
-#if defined(RECOVERY_ABGR)
- format = DRM_FORMAT_RGBA8888;
-#elif defined(RECOVERY_BGRA)
- format = DRM_FORMAT_ARGB8888;
-#elif defined(RECOVERY_RGBX)
- format = DRM_FORMAT_XBGR8888;
-#else
- format = DRM_FORMAT_RGB565;
-#endif
+ PixelFormat pixel_format = gr_pixel_format();
+ // PixelFormat comes in byte order, whereas DRM_FORMAT_* uses little-endian
+ // (external/libdrm/include/drm/drm_fourcc.h). Note that although drm_fourcc.h also defines a
+ // macro of DRM_FORMAT_BIG_ENDIAN, it doesn't seem to be actually supported (see the discussion
+ // in https://lists.freedesktop.org/archives/amd-gfx/2017-May/008560.html).
+ if (pixel_format == PixelFormat::ABGR) {
+ format = DRM_FORMAT_RGBA8888;
+ } else if (pixel_format == PixelFormat::BGRA) {
+ format = DRM_FORMAT_ARGB8888;
+ } else if (pixel_format == PixelFormat::RGBX) {
+ format = DRM_FORMAT_XBGR8888;
+ } else {
+ format = DRM_FORMAT_RGB565;
+ }
drm_mode_create_dumb create_dumb = {};
create_dumb.height = height;
@@ -129,53 +97,74 @@
create_dumb.bpp = drm_format_to_bpp(format);
create_dumb.flags = 0;
- int ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
- if (ret) {
- printf("DRM_IOCTL_MODE_CREATE_DUMB failed ret=%d\n", ret);
- DrmDestroySurface(surface);
+ if (drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb) != 0) {
+ perror("Failed to DRM_IOCTL_MODE_CREATE_DUMB");
return nullptr;
}
- surface->handle = create_dumb.handle;
+
+ // Cannot use std::make_unique to access non-public ctor.
+ auto surface = std::unique_ptr<GRSurfaceDrm>(new GRSurfaceDrm(
+ width, height, create_dumb.pitch, create_dumb.bpp / 8, drm_fd, create_dumb.handle));
uint32_t handles[4], pitches[4], offsets[4];
handles[0] = surface->handle;
pitches[0] = create_dumb.pitch;
offsets[0] = 0;
-
- ret =
- drmModeAddFB2(drm_fd, width, height, format, handles, pitches, offsets, &(surface->fb_id), 0);
- if (ret) {
- printf("drmModeAddFB2 failed ret=%d\n", ret);
- DrmDestroySurface(surface);
+ if (drmModeAddFB2(drm_fd, width, height, format, handles, pitches, offsets, &surface->fb_id, 0) !=
+ 0) {
+ perror("Failed to drmModeAddFB2");
return nullptr;
}
drm_mode_map_dumb map_dumb = {};
map_dumb.handle = create_dumb.handle;
- ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb);
- if (ret) {
- printf("DRM_IOCTL_MODE_MAP_DUMB failed ret=%d\n", ret);
- DrmDestroySurface(surface);
+ if (drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb) != 0) {
+ perror("Failed to DRM_IOCTL_MODE_MAP_DUMB");
return nullptr;
}
- surface->height = height;
- surface->width = width;
- surface->row_bytes = create_dumb.pitch;
- surface->pixel_bytes = create_dumb.bpp / 8;
- surface->data = static_cast<unsigned char*>(mmap(nullptr, surface->height * surface->row_bytes,
- PROT_READ | PROT_WRITE, MAP_SHARED, drm_fd,
- map_dumb.offset));
- if (surface->data == MAP_FAILED) {
- perror("mmap() failed");
- DrmDestroySurface(surface);
+ auto mmapped = mmap(nullptr, surface->height * surface->row_bytes, PROT_READ | PROT_WRITE,
+ MAP_SHARED, drm_fd, map_dumb.offset);
+ if (mmapped == MAP_FAILED) {
+ perror("Failed to mmap()");
return nullptr;
}
-
+ surface->mmapped_buffer_ = static_cast<uint8_t*>(mmapped);
return surface;
}
+void MinuiBackendDrm::DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc) {
+ if (crtc) {
+ drmModeSetCrtc(drm_fd, crtc->crtc_id,
+ 0, // fb_id
+ 0, 0, // x,y
+ nullptr, // connectors
+ 0, // connector_count
+ nullptr); // mode
+ }
+}
+
+bool MinuiBackendDrm::DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc,
+ const std::unique_ptr<GRSurfaceDrm>& surface) {
+ if (drmModeSetCrtc(drm_fd, crtc->crtc_id, surface->fb_id, 0, 0, // x,y
+ &main_monitor_connector->connector_id,
+ 1, // connector_count
+ &main_monitor_crtc->mode) != 0) {
+ perror("Failed to drmModeSetCrtc");
+ return false;
+ }
+ return true;
+}
+
+void MinuiBackendDrm::Blank(bool blank) {
+ if (blank) {
+ DrmDisableCrtc(drm_fd, main_monitor_crtc);
+ } else {
+ DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[current_buffer]);
+ }
+}
+
static drmModeCrtc* find_crtc_for_connector(int fd, drmModeRes* resources,
drmModeConnector* connector) {
// Find the encoder. If we already have one, just use it.
@@ -257,7 +246,7 @@
do {
main_monitor_connector = find_used_connector_by_type(fd, resources, kConnectorPriority[i]);
i++;
- } while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority));
+ } while (!main_monitor_connector && i < arraysize(kConnectorPriority));
/* If we didn't find a connector, grab the first one that is connected. */
if (!main_monitor_connector) {
@@ -291,60 +280,53 @@
GRSurface* MinuiBackendDrm::Init() {
drmModeRes* res = nullptr;
+ drm_fd = -1;
/* Consider DRM devices in order. */
for (int i = 0; i < DRM_MAX_MINOR; i++) {
- char* dev_name;
- int ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i);
- if (ret < 0) continue;
+ auto dev_name = android::base::StringPrintf(DRM_DEV_NAME, DRM_DIR_NAME, i);
+ android::base::unique_fd fd(open(dev_name.c_str(), O_RDWR | O_CLOEXEC));
+ if (fd == -1) continue;
- drm_fd = open(dev_name, O_RDWR, 0);
- free(dev_name);
- if (drm_fd < 0) continue;
-
- uint64_t cap = 0;
/* We need dumb buffers. */
- ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &cap);
- if (ret || cap == 0) {
- close(drm_fd);
+ if (uint64_t cap = 0; drmGetCap(fd.get(), DRM_CAP_DUMB_BUFFER, &cap) != 0 || cap == 0) {
continue;
}
- res = drmModeGetResources(drm_fd);
+ res = drmModeGetResources(fd.get());
if (!res) {
- close(drm_fd);
continue;
}
/* Use this device if it has at least one connected monitor. */
if (res->count_crtcs > 0 && res->count_connectors > 0) {
- if (find_first_connected_connector(drm_fd, res)) break;
+ if (find_first_connected_connector(fd.get(), res)) {
+ drm_fd = fd.release();
+ break;
+ }
}
drmModeFreeResources(res);
- close(drm_fd);
res = nullptr;
}
- if (drm_fd < 0 || res == nullptr) {
- perror("cannot find/open a drm device");
+ if (drm_fd == -1 || res == nullptr) {
+ perror("Failed to find/open a drm device");
return nullptr;
}
uint32_t selected_mode;
main_monitor_connector = FindMainMonitor(drm_fd, res, &selected_mode);
-
if (!main_monitor_connector) {
- printf("main_monitor_connector not found\n");
+ fprintf(stderr, "Failed to find main_monitor_connector\n");
drmModeFreeResources(res);
close(drm_fd);
return nullptr;
}
main_monitor_crtc = find_crtc_for_connector(drm_fd, res, main_monitor_connector);
-
if (!main_monitor_crtc) {
- printf("main_monitor_crtc not found\n");
+ fprintf(stderr, "Failed to find main_monitor_crtc\n");
drmModeFreeResources(res);
close(drm_fd);
return nullptr;
@@ -359,35 +341,66 @@
drmModeFreeResources(res);
- GRSurfaceDrms[0] = DrmCreateSurface(width, height);
- GRSurfaceDrms[1] = DrmCreateSurface(width, height);
+ GRSurfaceDrms[0] = GRSurfaceDrm::Create(drm_fd, width, height);
+ GRSurfaceDrms[1] = GRSurfaceDrm::Create(drm_fd, width, height);
if (!GRSurfaceDrms[0] || !GRSurfaceDrms[1]) {
- // GRSurfaceDrms and drm_fd should be freed in d'tor.
return nullptr;
}
current_buffer = 0;
- DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[1]);
+ // We will likely encounter errors in the backend functions (i.e. Flip) if EnableCrtc fails.
+ if (!DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[1])) {
+ return nullptr;
+ }
- return GRSurfaceDrms[0];
+ return GRSurfaceDrms[0].get();
+}
+
+static void page_flip_complete(__unused int fd,
+ __unused unsigned int sequence,
+ __unused unsigned int tv_sec,
+ __unused unsigned int tv_usec,
+ void *user_data) {
+ *static_cast<bool*>(user_data) = false;
}
GRSurface* MinuiBackendDrm::Flip() {
- int ret = drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id,
- GRSurfaceDrms[current_buffer]->fb_id, 0, nullptr);
- if (ret < 0) {
- printf("drmModePageFlip failed ret=%d\n", ret);
+ bool ongoing_flip = true;
+ if (drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id, GRSurfaceDrms[current_buffer]->fb_id,
+ DRM_MODE_PAGE_FLIP_EVENT, &ongoing_flip) != 0) {
+ perror("Failed to drmModePageFlip");
return nullptr;
}
+
+ while (ongoing_flip) {
+ struct pollfd fds = {
+ .fd = drm_fd,
+ .events = POLLIN
+ };
+
+ if (poll(&fds, 1, -1) == -1 || !(fds.revents & POLLIN)) {
+ perror("Failed to poll() on drm fd");
+ break;
+ }
+
+ drmEventContext evctx = {
+ .version = DRM_EVENT_CONTEXT_VERSION,
+ .page_flip_handler = page_flip_complete
+ };
+
+ if (drmHandleEvent(drm_fd, &evctx) != 0) {
+ perror("Failed to drmHandleEvent");
+ break;
+ }
+ }
+
current_buffer = 1 - current_buffer;
- return GRSurfaceDrms[current_buffer];
+ return GRSurfaceDrms[current_buffer].get();
}
MinuiBackendDrm::~MinuiBackendDrm() {
DrmDisableCrtc(drm_fd, main_monitor_crtc);
- DrmDestroySurface(GRSurfaceDrms[0]);
- DrmDestroySurface(GRSurfaceDrms[1]);
drmModeFreeCrtc(main_monitor_crtc);
drmModeFreeConnector(main_monitor_connector);
close(drm_fd);
diff --git a/minui/graphics_drm.h b/minui/graphics_drm.h
index de96212..57ba39b 100644
--- a/minui/graphics_drm.h
+++ b/minui/graphics_drm.h
@@ -14,45 +14,61 @@
* limitations under the License.
*/
-#ifndef _GRAPHICS_DRM_H_
-#define _GRAPHICS_DRM_H_
+#pragma once
+#include <stddef.h>
#include <stdint.h>
+#include <memory>
+
#include <xf86drmMode.h>
#include "graphics.h"
#include "minui/minui.h"
class GRSurfaceDrm : public GRSurface {
- private:
- uint32_t fb_id;
- uint32_t handle;
+ public:
+ ~GRSurfaceDrm() override;
+ // Creates a GRSurfaceDrm instance.
+ static std::unique_ptr<GRSurfaceDrm> Create(int drm_fd, int width, int height);
+
+ uint8_t* data() override {
+ return mmapped_buffer_;
+ }
+
+ private:
friend class MinuiBackendDrm;
+
+ GRSurfaceDrm(size_t width, size_t height, size_t row_bytes, size_t pixel_bytes, int drm_fd,
+ uint32_t handle)
+ : GRSurface(width, height, row_bytes, pixel_bytes), drm_fd_(drm_fd), handle(handle) {}
+
+ const int drm_fd_;
+
+ uint32_t fb_id{ 0 };
+ uint32_t handle{ 0 };
+ uint8_t* mmapped_buffer_{ nullptr };
};
class MinuiBackendDrm : public MinuiBackend {
public:
+ MinuiBackendDrm() = default;
+ ~MinuiBackendDrm() override;
+
GRSurface* Init() override;
GRSurface* Flip() override;
void Blank(bool) override;
- ~MinuiBackendDrm() override;
- MinuiBackendDrm();
private:
void DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc);
- void DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, GRSurfaceDrm* surface);
- GRSurfaceDrm* DrmCreateSurface(int width, int height);
- void DrmDestroySurface(GRSurfaceDrm* surface);
+ bool DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, const std::unique_ptr<GRSurfaceDrm>& surface);
void DisableNonMainCrtcs(int fd, drmModeRes* resources, drmModeCrtc* main_crtc);
drmModeConnector* FindMainMonitor(int fd, drmModeRes* resources, uint32_t* mode_index);
- GRSurfaceDrm* GRSurfaceDrms[2];
- int current_buffer;
- drmModeCrtc* main_monitor_crtc;
- drmModeConnector* main_monitor_connector;
- int drm_fd;
+ std::unique_ptr<GRSurfaceDrm> GRSurfaceDrms[2];
+ int current_buffer{ 0 };
+ drmModeCrtc* main_monitor_crtc{ nullptr };
+ drmModeConnector* main_monitor_connector{ nullptr };
+ int drm_fd{ -1 };
};
-
-#endif // _GRAPHICS_DRM_H_
diff --git a/minui/graphics_fbdev.cpp b/minui/graphics_fbdev.cpp
index 746f42a..2584017 100644
--- a/minui/graphics_fbdev.cpp
+++ b/minui/graphics_fbdev.cpp
@@ -26,21 +26,29 @@
#include <sys/types.h>
#include <unistd.h>
+#include <memory>
+
+#include <android-base/unique_fd.h>
+
#include "minui/minui.h"
-MinuiBackendFbdev::MinuiBackendFbdev() : gr_draw(nullptr), fb_fd(-1) {}
+std::unique_ptr<GRSurfaceFbdev> GRSurfaceFbdev::Create(size_t width, size_t height,
+ size_t row_bytes, size_t pixel_bytes) {
+ // Cannot use std::make_unique to access non-public ctor.
+ return std::unique_ptr<GRSurfaceFbdev>(new GRSurfaceFbdev(width, height, row_bytes, pixel_bytes));
+}
void MinuiBackendFbdev::Blank(bool blank) {
int ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
if (ret < 0) perror("ioctl(): blank");
}
-void MinuiBackendFbdev::SetDisplayedFramebuffer(unsigned n) {
+void MinuiBackendFbdev::SetDisplayedFramebuffer(size_t n) {
if (n > 1 || !double_buffered) return;
- vi.yres_virtual = gr_framebuffer[0].height * 2;
- vi.yoffset = n * gr_framebuffer[0].height;
- vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8;
+ vi.yres_virtual = gr_framebuffer[0]->height * 2;
+ vi.yoffset = n * gr_framebuffer[0]->height;
+ vi.bits_per_pixel = gr_framebuffer[0]->pixel_bytes * 8;
if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
perror("active fb swap failed");
}
@@ -48,7 +56,7 @@
}
GRSurface* MinuiBackendFbdev::Init() {
- int fd = open("/dev/graphics/fb0", O_RDWR);
+ android::base::unique_fd fd(open("/dev/graphics/fb0", O_RDWR | O_CLOEXEC));
if (fd == -1) {
perror("cannot open fb0");
return nullptr;
@@ -57,13 +65,11 @@
fb_fix_screeninfo fi;
if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
perror("failed to get fb0 info");
- close(fd);
return nullptr;
}
if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
perror("failed to get fb0 info");
- close(fd);
return nullptr;
}
@@ -90,50 +96,41 @@
void* bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (bits == MAP_FAILED) {
perror("failed to mmap framebuffer");
- close(fd);
return nullptr;
}
memset(bits, 0, fi.smem_len);
- gr_framebuffer[0].width = vi.xres;
- gr_framebuffer[0].height = vi.yres;
- gr_framebuffer[0].row_bytes = fi.line_length;
- gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8;
- gr_framebuffer[0].data = static_cast<uint8_t*>(bits);
- memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes);
+ gr_framebuffer[0] =
+ GRSurfaceFbdev::Create(vi.xres, vi.yres, fi.line_length, vi.bits_per_pixel / 8);
+ gr_framebuffer[0]->buffer_ = static_cast<uint8_t*>(bits);
+ memset(gr_framebuffer[0]->buffer_, 0, gr_framebuffer[0]->height * gr_framebuffer[0]->row_bytes);
+
+ gr_framebuffer[1] =
+ GRSurfaceFbdev::Create(gr_framebuffer[0]->width, gr_framebuffer[0]->height,
+ gr_framebuffer[0]->row_bytes, gr_framebuffer[0]->pixel_bytes);
/* check if we can use double buffering */
if (vi.yres * fi.line_length * 2 <= fi.smem_len) {
double_buffered = true;
- memcpy(gr_framebuffer + 1, gr_framebuffer, sizeof(GRSurface));
- gr_framebuffer[1].data =
- gr_framebuffer[0].data + gr_framebuffer[0].height * gr_framebuffer[0].row_bytes;
-
- gr_draw = gr_framebuffer + 1;
-
+ gr_framebuffer[1]->buffer_ =
+ gr_framebuffer[0]->buffer_ + gr_framebuffer[0]->height * gr_framebuffer[0]->row_bytes;
} else {
double_buffered = false;
- // Without double-buffering, we allocate RAM for a buffer to
- // draw in, and then "flipping" the buffer consists of a
- // memcpy from the buffer we allocated to the framebuffer.
-
- gr_draw = static_cast<GRSurface*>(malloc(sizeof(GRSurface)));
- memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface));
- gr_draw->data = static_cast<unsigned char*>(malloc(gr_draw->height * gr_draw->row_bytes));
- if (!gr_draw->data) {
- perror("failed to allocate in-memory surface");
- return nullptr;
- }
+ // Without double-buffering, we allocate RAM for a buffer to draw in, and then "flipping" the
+ // buffer consists of a memcpy from the buffer we allocated to the framebuffer.
+ memory_buffer.resize(gr_framebuffer[1]->height * gr_framebuffer[1]->row_bytes);
+ gr_framebuffer[1]->buffer_ = memory_buffer.data();
}
- memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes);
- fb_fd = fd;
+ gr_draw = gr_framebuffer[1].get();
+ memset(gr_draw->buffer_, 0, gr_draw->height * gr_draw->row_bytes);
+ fb_fd = std::move(fd);
SetDisplayedFramebuffer(0);
- printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height);
+ printf("framebuffer: %d (%zu x %zu)\n", fb_fd.get(), gr_draw->width, gr_draw->height);
Blank(true);
Blank(false);
@@ -143,25 +140,13 @@
GRSurface* MinuiBackendFbdev::Flip() {
if (double_buffered) {
- // Change gr_draw to point to the buffer currently displayed,
- // then flip the driver so we're displaying the other buffer
- // instead.
- gr_draw = gr_framebuffer + displayed_buffer;
+ // Change gr_draw to point to the buffer currently displayed, then flip the driver so we're
+ // displaying the other buffer instead.
+ gr_draw = gr_framebuffer[displayed_buffer].get();
SetDisplayedFramebuffer(1 - displayed_buffer);
} else {
// Copy from the in-memory surface to the framebuffer.
- memcpy(gr_framebuffer[0].data, gr_draw->data, gr_draw->height * gr_draw->row_bytes);
+ memcpy(gr_framebuffer[0]->buffer_, gr_draw->buffer_, gr_draw->height * gr_draw->row_bytes);
}
return gr_draw;
}
-
-MinuiBackendFbdev::~MinuiBackendFbdev() {
- close(fb_fd);
- fb_fd = -1;
-
- if (!double_buffered && gr_draw) {
- free(gr_draw->data);
- free(gr_draw);
- }
- gr_draw = nullptr;
-}
diff --git a/minui/graphics_fbdev.h b/minui/graphics_fbdev.h
index 107e195..596ba74 100644
--- a/minui/graphics_fbdev.h
+++ b/minui/graphics_fbdev.h
@@ -14,31 +14,58 @@
* limitations under the License.
*/
-#ifndef _GRAPHICS_FBDEV_H_
-#define _GRAPHICS_FBDEV_H_
+#pragma once
#include <linux/fb.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <vector>
+
+#include <android-base/unique_fd.h>
#include "graphics.h"
#include "minui/minui.h"
+class GRSurfaceFbdev : public GRSurface {
+ public:
+ // Creates and returns a GRSurfaceFbdev instance, or nullptr on error.
+ static std::unique_ptr<GRSurfaceFbdev> Create(size_t width, size_t height, size_t row_bytes,
+ size_t pixel_bytes);
+
+ uint8_t* data() override {
+ return buffer_;
+ }
+
+ protected:
+ using GRSurface::GRSurface;
+
+ private:
+ friend class MinuiBackendFbdev;
+
+ // Points to the start of the buffer: either the mmap'd framebuffer or one allocated in-memory.
+ uint8_t* buffer_{ nullptr };
+};
+
class MinuiBackendFbdev : public MinuiBackend {
public:
+ MinuiBackendFbdev() = default;
+ ~MinuiBackendFbdev() override = default;
+
GRSurface* Init() override;
GRSurface* Flip() override;
void Blank(bool) override;
- ~MinuiBackendFbdev() override;
- MinuiBackendFbdev();
private:
- void SetDisplayedFramebuffer(unsigned n);
+ void SetDisplayedFramebuffer(size_t n);
- GRSurface gr_framebuffer[2];
+ std::unique_ptr<GRSurfaceFbdev> gr_framebuffer[2];
+ // Points to the current surface (i.e. one of the two gr_framebuffer's).
+ GRSurfaceFbdev* gr_draw{ nullptr };
bool double_buffered;
- GRSurface* gr_draw;
- int displayed_buffer;
+ std::vector<uint8_t> memory_buffer;
+ size_t displayed_buffer{ 0 };
fb_var_screeninfo vi;
- int fb_fd;
+ android::base::unique_fd fb_fd;
};
-
-#endif // _GRAPHICS_FBDEV_H_
diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h
index f9da199..36bdcf1 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -14,25 +14,73 @@
* limitations under the License.
*/
-#ifndef _MINUI_H_
-#define _MINUI_H_
+#pragma once
+#include <stdint.h>
+#include <stdlib.h>
#include <sys/types.h>
#include <functional>
+#include <memory>
#include <string>
#include <vector>
+#include <android-base/macros.h>
+#include <android-base/unique_fd.h>
+
//
// Graphics.
//
-struct GRSurface {
- int width;
- int height;
- int row_bytes;
- int pixel_bytes;
- unsigned char* data;
+class GRSurface {
+ public:
+ static constexpr size_t kSurfaceDataAlignment = 8;
+
+ virtual ~GRSurface() = default;
+
+ // Creates and returns a GRSurface instance that's sufficient for storing an image of the given
+ // size (i.e. row_bytes * height). The starting address of the surface data is aligned to
+ // kSurfaceDataAlignment. Returns the created GRSurface instance (in std::unique_ptr), or nullptr
+ // on error.
+ static std::unique_ptr<GRSurface> Create(size_t width, size_t height, size_t row_bytes,
+ size_t pixel_bytes);
+
+ // Clones the current GRSurface instance (i.e. an image).
+ std::unique_ptr<GRSurface> Clone() const;
+
+ virtual uint8_t* data() {
+ return data_.get();
+ }
+
+ const uint8_t* data() const {
+ return const_cast<const uint8_t*>(const_cast<GRSurface*>(this)->data());
+ }
+
+ size_t data_size() const {
+ return data_size_;
+ }
+
+ size_t width;
+ size_t height;
+ size_t row_bytes;
+ size_t pixel_bytes;
+
+ protected:
+ GRSurface(size_t width, size_t height, size_t row_bytes, size_t pixel_bytes)
+ : width(width), height(height), row_bytes(row_bytes), pixel_bytes(pixel_bytes) {}
+
+ private:
+ // The deleter for data_, whose data is allocated via aligned_alloc(3).
+ struct DataDeleter {
+ void operator()(uint8_t* data) {
+ free(data);
+ }
+ };
+
+ std::unique_ptr<uint8_t, DataDeleter> data_;
+ size_t data_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(GRSurface);
};
struct GRFont {
@@ -41,14 +89,27 @@
int char_height;
};
-enum GRRotation {
- ROTATION_NONE = 0,
- ROTATION_RIGHT = 1,
- ROTATION_DOWN = 2,
- ROTATION_LEFT = 3,
+enum class GRRotation : int {
+ NONE = 0,
+ RIGHT = 1,
+ DOWN = 2,
+ LEFT = 3,
};
+enum class PixelFormat : int {
+ UNKNOWN = 0,
+ ABGR = 1,
+ RGBX = 2,
+ BGRA = 3,
+};
+
+// Initializes the graphics backend and loads font file. Returns 0 on success, or -1 on error. Note
+// that the font initialization failure would be non-fatal, as caller may not need to draw any text
+// at all. Caller can check the font initialization result via gr_sys_font() as needed.
int gr_init();
+
+// Frees the allocated resources. The function is idempotent, and safe to be called if gr_init()
+// didn't finish successfully.
void gr_exit();
int gr_fb_width();
@@ -57,25 +118,31 @@
void gr_flip();
void gr_fb_blank(bool blank);
-void gr_clear(); // clear entire surface to current color
+// Clears entire surface to current color.
+void gr_clear();
void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
void gr_fill(int x1, int y1, int x2, int y2);
-void gr_texticon(int x, int y, GRSurface* icon);
+void gr_texticon(int x, int y, const GRSurface* icon);
const GRFont* gr_sys_font();
int gr_init_font(const char* name, GRFont** dest);
void gr_text(const GRFont* font, int x, int y, const char* s, bool bold);
+// Returns -1 if font is nullptr.
int gr_measure(const GRFont* font, const char* s);
-void gr_font_size(const GRFont* font, int* x, int* y);
+// Returns -1 if font is nullptr.
+int gr_font_size(const GRFont* font, int* x, int* y);
-void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy);
-unsigned int gr_get_width(GRSurface* surface);
-unsigned int gr_get_height(GRSurface* surface);
+void gr_blit(const GRSurface* source, int sx, int sy, int w, int h, int dx, int dy);
+unsigned int gr_get_width(const GRSurface* surface);
+unsigned int gr_get_height(const GRSurface* surface);
-// Set rotation, flips gr_fb_width/height if 90 degree rotation difference
+// Sets rotation, flips gr_fb_width/height if 90 degree rotation difference
void gr_rotate(GRRotation rotation);
+// Returns the current PixelFormat being used.
+PixelFormat gr_pixel_format();
+
//
// Input events.
//
@@ -87,7 +154,7 @@
int ev_init(ev_callback input_cb, bool allow_touch_inputs = false);
void ev_exit();
-int ev_add_fd(int fd, ev_callback cb);
+int ev_add_fd(android::base::unique_fd&& fd, ev_callback cb);
void ev_iterate_available_keys(const std::function<void(int)>& f);
void ev_iterate_touch_inputs(const std::function<void(int)>& action);
int ev_sync_key_state(const ev_set_key_callback& set_key_cb);
@@ -146,5 +213,3 @@
// Free a surface allocated by any of the res_create_*_surface()
// functions.
void res_free_surface(GRSurface* surface);
-
-#endif
diff --git a/minui/include/private/resources.h b/minui/include/private/resources.h
new file mode 100644
index 0000000..047ebe2
--- /dev/null
+++ b/minui/include/private/resources.h
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <stdio.h>
+
+#include <memory>
+#include <string>
+
+#include <png.h>
+
+// This class handles the PNG file parsing. It also holds the ownership of the PNG pointer and the
+// opened file pointer. Both will be destroyed / closed when this object goes out of scope.
+class PngHandler {
+ public:
+ // Constructs an instance by loading the PNG file from '/res/images/<name>.png', or '<name>'.
+ PngHandler(const std::string& name);
+
+ ~PngHandler();
+
+ png_uint_32 width() const {
+ return width_;
+ }
+
+ png_uint_32 height() const {
+ return height_;
+ }
+
+ png_byte channels() const {
+ return channels_;
+ }
+
+ int bit_depth() const {
+ return bit_depth_;
+ }
+
+ int color_type() const {
+ return color_type_;
+ }
+
+ png_structp png_ptr() const {
+ return png_ptr_;
+ }
+
+ png_infop info_ptr() const {
+ return info_ptr_;
+ }
+
+ int error_code() const {
+ return error_code_;
+ };
+
+ operator bool() const {
+ return error_code_ == 0;
+ }
+
+ private:
+ png_structp png_ptr_{ nullptr };
+ png_infop info_ptr_{ nullptr };
+ png_uint_32 width_;
+ png_uint_32 height_;
+ png_byte channels_;
+ int bit_depth_;
+ int color_type_;
+
+ // The |error_code_| is set to a negative value if an error occurs when opening the png file.
+ int error_code_{ 0 };
+ // After initialization, we'll keep the file pointer open before destruction of PngHandler.
+ std::unique_ptr<FILE, decltype(&fclose)> png_fp_{ nullptr, fclose };
+};
+
+// Overrides the default resource dir, for testing purpose.
+void res_set_resource_dir(const std::string&);
diff --git a/minui/mkfont.c b/minui/mkfont.c
deleted file mode 100644
index 61a5ede..0000000
--- a/minui/mkfont.c
+++ /dev/null
@@ -1,54 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-
-int main(int argc, char *argv)
-{
- unsigned n;
- unsigned char *x;
- unsigned m;
- unsigned run_val;
- unsigned run_count;
-
- n = gimp_image.width * gimp_image.height;
- m = 0;
- x = gimp_image.pixel_data;
-
- printf("struct {\n");
- printf(" unsigned width;\n");
- printf(" unsigned height;\n");
- printf(" unsigned cwidth;\n");
- printf(" unsigned cheight;\n");
- printf(" unsigned char rundata[];\n");
- printf("} font = {\n");
- printf(" .width = %d,\n .height = %d,\n .cwidth = %d,\n .cheight = %d,\n", gimp_image.width, gimp_image.height,
- gimp_image.width / 96, gimp_image.height);
- printf(" .rundata = {\n");
-
- run_val = (*x ? 0 : 255);
- run_count = 1;
- n--;
- x+=3;
-
- while(n-- > 0) {
- unsigned val = (*x ? 0 : 255);
- x+=3;
- if((val == run_val) && (run_count < 127)) {
- run_count++;
- } else {
-eject:
- printf("0x%02x,",run_count | (run_val ? 0x80 : 0x00));
- run_val = val;
- run_count = 1;
- m += 5;
- if(m >= 75) {
- printf("\n");
- m = 0;
- }
- }
- }
- printf("0x%02x,",run_count | (run_val ? 0x80 : 0x00));
- printf("\n0x00,");
- printf("\n");
- printf(" }\n};\n");
- return 0;
-}
diff --git a/minui/resources.cpp b/minui/resources.cpp
index 52ab60b..00d36d5 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include "private/resources.h"
+
#include <fcntl.h>
#include <linux/fb.h>
#include <linux/kd.h>
@@ -25,87 +27,55 @@
#include <sys/types.h>
#include <unistd.h>
+#include <limits>
#include <memory>
#include <regex>
#include <string>
#include <vector>
-#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <png.h>
#include "minui/minui.h"
-#define SURFACE_DATA_ALIGNMENT 8
+static std::string g_resource_dir{ "/res/images" };
-static GRSurface* malloc_surface(size_t data_size) {
- size_t size = sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT;
- unsigned char* temp = static_cast<unsigned char*>(malloc(size));
- if (temp == NULL) return NULL;
- GRSurface* surface = reinterpret_cast<GRSurface*>(temp);
- surface->data = temp + sizeof(GRSurface) +
- (SURFACE_DATA_ALIGNMENT - (sizeof(GRSurface) % SURFACE_DATA_ALIGNMENT));
- return surface;
+std::unique_ptr<GRSurface> GRSurface::Create(size_t width, size_t height, size_t row_bytes,
+ size_t pixel_bytes) {
+ if (width == 0 || row_bytes == 0 || height == 0 || pixel_bytes == 0) return nullptr;
+ if (std::numeric_limits<size_t>::max() / row_bytes < height) return nullptr;
+
+ // Cannot use std::make_unique to access non-public ctor.
+ auto result = std::unique_ptr<GRSurface>(new GRSurface(width, height, row_bytes, pixel_bytes));
+ size_t data_size = row_bytes * height;
+ result->data_size_ =
+ (data_size + kSurfaceDataAlignment - 1) / kSurfaceDataAlignment * kSurfaceDataAlignment;
+ result->data_.reset(
+ static_cast<uint8_t*>(aligned_alloc(kSurfaceDataAlignment, result->data_size_)));
+ if (!result->data_) return nullptr;
+ return result;
}
-// This class handles the png file parsing. It also holds the ownership of the png pointer and the
-// opened file pointer. Both will be destroyed/closed when this object goes out of scope.
-class PngHandler {
- public:
- PngHandler(const std::string& name);
+std::unique_ptr<GRSurface> GRSurface::Clone() const {
+ auto result = GRSurface::Create(width, height, row_bytes, pixel_bytes);
+ if (!result) return nullptr;
+ memcpy(result->data(), data(), data_size_);
+ return result;
+}
- ~PngHandler();
-
- png_uint_32 width() const {
- return width_;
- }
-
- png_uint_32 height() const {
- return height_;
- }
-
- png_byte channels() const {
- return channels_;
- }
-
- png_structp png_ptr() const {
- return png_ptr_;
- }
-
- png_infop info_ptr() const {
- return info_ptr_;
- }
-
- int error_code() const {
- return error_code_;
- };
-
- operator bool() const {
- return error_code_ == 0;
- }
-
- private:
- png_structp png_ptr_{ nullptr };
- png_infop info_ptr_{ nullptr };
- png_uint_32 width_;
- png_uint_32 height_;
- png_byte channels_;
-
- // The |error_code_| is set to a negative value if an error occurs when opening the png file.
- int error_code_;
- // After initialization, we'll keep the file pointer open before destruction of PngHandler.
- std::unique_ptr<FILE, decltype(&fclose)> png_fp_;
-};
-
-PngHandler::PngHandler(const std::string& name) : error_code_(0), png_fp_(nullptr, fclose) {
- std::string res_path = android::base::StringPrintf("/res/images/%s.png", name.c_str());
+PngHandler::PngHandler(const std::string& name) {
+ std::string res_path = g_resource_dir + "/" + name + ".png";
png_fp_.reset(fopen(res_path.c_str(), "rbe"));
+ // Try to read from |name| if the resource path does not work.
+ if (!png_fp_) {
+ png_fp_.reset(fopen(name.c_str(), "rbe"));
+ }
if (!png_fp_) {
error_code_ = -1;
return;
}
- unsigned char header[8];
+ uint8_t header[8];
size_t bytesRead = fread(header, 1, sizeof(header), png_fp_.get());
if (bytesRead != sizeof(header)) {
error_code_ = -2;
@@ -138,19 +108,17 @@
png_set_sig_bytes(png_ptr_, sizeof(header));
png_read_info(png_ptr_, info_ptr_);
- int color_type;
- int bit_depth;
- png_get_IHDR(png_ptr_, info_ptr_, &width_, &height_, &bit_depth, &color_type, nullptr, nullptr,
+ png_get_IHDR(png_ptr_, info_ptr_, &width_, &height_, &bit_depth_, &color_type_, nullptr, nullptr,
nullptr);
channels_ = png_get_channels(png_ptr_, info_ptr_);
- if (bit_depth == 8 && channels_ == 3 && color_type == PNG_COLOR_TYPE_RGB) {
+ if (bit_depth_ == 8 && channels_ == 3 && color_type_ == PNG_COLOR_TYPE_RGB) {
// 8-bit RGB images: great, nothing to do.
- } else if (bit_depth <= 8 && channels_ == 1 && color_type == PNG_COLOR_TYPE_GRAY) {
+ } else if (bit_depth_ <= 8 && channels_ == 1 && color_type_ == PNG_COLOR_TYPE_GRAY) {
// 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray.
png_set_expand_gray_1_2_4_to_8(png_ptr_);
- } else if (bit_depth <= 8 && channels_ == 1 && color_type == PNG_COLOR_TYPE_PALETTE) {
+ } else if (bit_depth_ <= 8 && channels_ == 1 && color_type_ == PNG_COLOR_TYPE_PALETTE) {
// paletted images: expand to 8-bit RGB. Note that we DON'T
// currently expand the tRNS chunk (if any) to an alpha
// channel, because minui doesn't support alpha channels in
@@ -158,8 +126,8 @@
png_set_palette_to_rgb(png_ptr_);
channels_ = 3;
} else {
- fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n", bit_depth,
- channels_, color_type);
+ fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n", bit_depth_,
+ channels_, color_type_);
error_code_ = -7;
}
}
@@ -170,70 +138,49 @@
}
}
-// "display" surfaces are transformed into the framebuffer's required
-// pixel format (currently only RGBX is supported) at load time, so
-// gr_blit() can be nothing more than a memcpy() for each row. The
-// next two functions are the only ones that know anything about the
-// framebuffer pixel format; they need to be modified if the
-// framebuffer format changes (but nothing else should).
+// "display" surfaces are transformed into the framebuffer's required pixel format (currently only
+// RGBX is supported) at load time, so gr_blit() can be nothing more than a memcpy() for each row.
-// Allocate and return a GRSurface* sufficient for storing an image of
-// the indicated size in the framebuffer pixel format.
-static GRSurface* init_display_surface(png_uint_32 width, png_uint_32 height) {
- GRSurface* surface = malloc_surface(width * height * 4);
- if (surface == NULL) return NULL;
-
- surface->width = width;
- surface->height = height;
- surface->row_bytes = width * 4;
- surface->pixel_bytes = 4;
-
- return surface;
-}
-
-// Copy 'input_row' to 'output_row', transforming it to the
-// framebuffer pixel format. The input format depends on the value of
-// 'channels':
+// Copies 'input_row' to 'output_row', transforming it to the framebuffer pixel format. The input
+// format depends on the value of 'channels':
//
// 1 - input is 8-bit grayscale
// 3 - input is 24-bit RGB
// 4 - input is 32-bit RGBA/RGBX
//
// 'width' is the number of pixels in the row.
-static void transform_rgb_to_draw(unsigned char* input_row,
- unsigned char* output_row,
- int channels, int width) {
- int x;
- unsigned char* ip = input_row;
- unsigned char* op = output_row;
+static void TransformRgbToDraw(const uint8_t* input_row, uint8_t* output_row, int channels,
+ int width) {
+ const uint8_t* ip = input_row;
+ uint8_t* op = output_row;
- switch (channels) {
- case 1:
- // expand gray level to RGBX
- for (x = 0; x < width; ++x) {
- *op++ = *ip;
- *op++ = *ip;
- *op++ = *ip;
- *op++ = 0xff;
- ip++;
- }
- break;
+ switch (channels) {
+ case 1:
+ // expand gray level to RGBX
+ for (int x = 0; x < width; ++x) {
+ *op++ = *ip;
+ *op++ = *ip;
+ *op++ = *ip;
+ *op++ = 0xff;
+ ip++;
+ }
+ break;
- case 3:
- // expand RGBA to RGBX
- for (x = 0; x < width; ++x) {
- *op++ = *ip++;
- *op++ = *ip++;
- *op++ = *ip++;
- *op++ = 0xff;
- }
- break;
+ case 3:
+ // expand RGBA to RGBX
+ for (int x = 0; x < width; ++x) {
+ *op++ = *ip++;
+ *op++ = *ip++;
+ *op++ = *ip++;
+ *op++ = 0xff;
+ }
+ break;
- case 4:
- // copy RGBA to RGBX
- memcpy(output_row, input_row, width*4);
- break;
- }
+ case 4:
+ // copy RGBA to RGBX
+ memcpy(output_row, input_row, width * 4);
+ break;
+ }
}
int res_create_display_surface(const char* name, GRSurface** pSurface) {
@@ -246,23 +193,24 @@
png_uint_32 width = png_handler.width();
png_uint_32 height = png_handler.height();
- GRSurface* surface = init_display_surface(width, height);
+ auto surface = GRSurface::Create(width, height, width * 4, 4);
if (!surface) {
return -8;
}
-#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
- png_set_bgr(png_ptr);
-#endif
-
- for (png_uint_32 y = 0; y < height; ++y) {
- std::vector<unsigned char> p_row(width * 4);
- png_read_row(png_ptr, p_row.data(), nullptr);
- transform_rgb_to_draw(p_row.data(), surface->data + y * surface->row_bytes,
- png_handler.channels(), width);
+ PixelFormat pixel_format = gr_pixel_format();
+ if (pixel_format == PixelFormat::ABGR || pixel_format == PixelFormat::BGRA) {
+ png_set_bgr(png_ptr);
}
- *pSurface = surface;
+ for (png_uint_32 y = 0; y < height; ++y) {
+ std::vector<uint8_t> p_row(width * 4);
+ png_read_row(png_ptr, p_row.data(), nullptr);
+ TransformRgbToDraw(p_row.data(), surface->data() + y * surface->row_bytes,
+ png_handler.channels(), width);
+ }
+
+ *pSurface = surface.release();
return 0;
}
@@ -315,23 +263,24 @@
goto exit;
}
for (int i = 0; i < *frames; ++i) {
- surface[i] = init_display_surface(width, height / *frames);
- if (!surface[i]) {
+ auto created_surface = GRSurface::Create(width, height / *frames, width * 4, 4);
+ if (!created_surface) {
result = -8;
goto exit;
}
+ surface[i] = created_surface.release();
}
-#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
- png_set_bgr(png_ptr);
-#endif
+ if (gr_pixel_format() == PixelFormat::ABGR || gr_pixel_format() == PixelFormat::BGRA) {
+ png_set_bgr(png_ptr);
+ }
for (png_uint_32 y = 0; y < height; ++y) {
- std::vector<unsigned char> p_row(width * 4);
+ std::vector<uint8_t> p_row(width * 4);
png_read_row(png_ptr, p_row.data(), nullptr);
int frame = y % *frames;
- unsigned char* out_row = surface[frame]->data + (y / *frames) * surface[frame]->row_bytes;
- transform_rgb_to_draw(p_row.data(), out_row, png_handler.channels(), width);
+ uint8_t* out_row = surface[frame]->data() + (y / *frames) * surface[frame]->row_bytes;
+ TransformRgbToDraw(p_row.data(), out_row, png_handler.channels(), width);
}
*pSurface = surface;
@@ -362,29 +311,30 @@
png_uint_32 width = png_handler.width();
png_uint_32 height = png_handler.height();
- GRSurface* surface = malloc_surface(width * height);
+ auto surface = GRSurface::Create(width, height, width, 1);
if (!surface) {
return -8;
}
- surface->width = width;
- surface->height = height;
- surface->row_bytes = width;
- surface->pixel_bytes = 1;
-#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
- png_set_bgr(png_ptr);
-#endif
+ PixelFormat pixel_format = gr_pixel_format();
+ if (pixel_format == PixelFormat::ABGR || pixel_format == PixelFormat::BGRA) {
+ png_set_bgr(png_ptr);
+ }
for (png_uint_32 y = 0; y < height; ++y) {
- unsigned char* p_row = surface->data + y * surface->row_bytes;
+ uint8_t* p_row = surface->data() + y * surface->row_bytes;
png_read_row(png_ptr, p_row, nullptr);
}
- *pSurface = surface;
+ *pSurface = surface.release();
return 0;
}
+void res_set_resource_dir(const std::string& dirname) {
+ g_resource_dir = dirname;
+}
+
// This function tests if a locale string stored in PNG (prefix) matches
// the locale string provided by the system (locale).
bool matches_locale(const std::string& prefix, const std::string& locale) {
@@ -397,6 +347,10 @@
// 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;
}
@@ -421,7 +375,7 @@
}
std::vector<std::string> result;
- std::vector<unsigned char> row(png_handler.width());
+ std::vector<uint8_t> row(png_handler.width());
for (png_uint_32 y = 0; y < png_handler.height(); ++y) {
png_read_row(png_handler.png_ptr(), row.data(), nullptr);
int h = (row[3] << 8) | row[2];
@@ -457,32 +411,34 @@
png_uint_32 height = png_handler.height();
for (png_uint_32 y = 0; y < height; ++y) {
- std::vector<unsigned char> row(width);
+ std::vector<uint8_t> row(width);
png_read_row(png_ptr, row.data(), nullptr);
int w = (row[1] << 8) | row[0];
int h = (row[3] << 8) | row[2];
__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);
- GRSurface* surface = malloc_surface(w * h);
+ auto surface = GRSurface::Create(w, h, w, 1);
if (!surface) {
- return -8;
+ return -9;
}
- surface->width = w;
- surface->height = h;
- surface->row_bytes = w;
- surface->pixel_bytes = 1;
for (int i = 0; i < h; ++i, ++y) {
png_read_row(png_ptr, row.data(), nullptr);
- memcpy(surface->data + i * w, row.data(), w);
+ memcpy(surface->data() + i * w, row.data(), w);
}
- *pSurface = surface;
- break;
+ *pSurface = surface.release();
+ return 0;
}
for (int i = 0; i < h; ++i, ++y) {
@@ -490,7 +446,7 @@
}
}
- return 0;
+ return -10;
}
void res_free_surface(GRSurface* surface) {
diff --git a/otafault/Android.bp b/otafault/Android.bp
deleted file mode 100644
index b39d5be..0000000
--- a/otafault/Android.bp
+++ /dev/null
@@ -1,68 +0,0 @@
-// 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.
-
-cc_library_static {
- name: "libotafault",
-
- host_supported: true,
-
- srcs: [
- "config.cpp",
- "ota_io.cpp",
- ],
-
- static_libs: [
- "libbase",
- "liblog",
- "libziparchive",
- ],
-
- export_include_dirs: [
- "include",
- ],
-
- cflags: [
- "-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS",
- "-Wall",
- "-Werror",
- "-Wthread-safety",
- "-Wthread-safety-negative",
- ],
-
- target: {
- darwin: {
- enabled: false,
- },
- },
-}
-
-cc_test {
- name: "otafault_test",
-
- srcs: ["test.cpp"],
-
- cflags: [
- "-Wall",
- "-Werror",
- ],
-
- static_executable: true,
-
- static_libs: [
- "libotafault",
- "libziparchive",
- "libbase",
- "liblog",
- ],
-}
diff --git a/otafault/config.cpp b/otafault/config.cpp
deleted file mode 100644
index 3993948..0000000
--- a/otafault/config.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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 "otafault/config.h"
-
-#include <map>
-#include <string>
-
-#include <android-base/stringprintf.h>
-#include <ziparchive/zip_archive.h>
-
-#include "otafault/ota_io.h"
-
-#define OTAIO_MAX_FNAME_SIZE 128
-
-static ZipArchiveHandle archive;
-static bool is_retry = false;
-static std::map<std::string, bool> should_inject_cache;
-
-static std::string get_type_path(const char* io_type) {
- return android::base::StringPrintf("%s/%s", OTAIO_BASE_DIR, io_type);
-}
-
-void ota_io_init(ZipArchiveHandle za, bool retry) {
- archive = za;
- is_retry = retry;
- ota_set_fault_files();
-}
-
-bool should_fault_inject(const char* io_type) {
- // archive will be NULL if we used an entry point other
- // than updater/updater.cpp:main
- if (archive == nullptr || is_retry) {
- return false;
- }
- const std::string type_path = get_type_path(io_type);
- if (should_inject_cache.find(type_path) != should_inject_cache.end()) {
- return should_inject_cache[type_path];
- }
- ZipString zip_type_path(type_path.c_str());
- ZipEntry entry;
- int status = FindEntry(archive, zip_type_path, &entry);
- should_inject_cache[type_path] = (status == 0);
- return (status == 0);
-}
-
-bool should_hit_cache() {
- return should_fault_inject(OTAIO_CACHE);
-}
-
-std::string fault_fname(const char* io_type) {
- std::string type_path = get_type_path(io_type);
- std::string fname;
- fname.resize(OTAIO_MAX_FNAME_SIZE);
- ZipString zip_type_path(type_path.c_str());
- ZipEntry entry;
- if (FindEntry(archive, zip_type_path, &entry) != 0) {
- return {};
- }
- ExtractToMemory(archive, &entry, reinterpret_cast<uint8_t*>(&fname[0]), OTAIO_MAX_FNAME_SIZE);
- return fname;
-}
diff --git a/otafault/include/otafault/config.h b/otafault/include/otafault/config.h
deleted file mode 100644
index cc4bfd2..0000000
--- a/otafault/include/otafault/config.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Read configuration files in the OTA package to determine which files, if any, will trigger
- * errors.
- *
- * OTA packages can be modified to trigger errors by adding a top-level directory called
- * .libotafault, which may optionally contain up to three files called READ, WRITE, and FSYNC.
- * Each one of these optional files contains the name of a single file on the device disk which
- * will cause an IO error on the first call of the appropriate I/O action to that file.
- *
- * Example:
- * ota.zip
- * <normal package contents>
- * .libotafault
- * WRITE
- *
- * If the contents of the file WRITE were /system/build.prop, the first write action to
- * /system/build.prop would fail with EIO. Note that READ and FSYNC files are absent, so these
- * actions will not cause an error.
- */
-
-#ifndef _UPDATER_OTA_IO_CFG_H_
-#define _UPDATER_OTA_IO_CFG_H_
-
-#include <string>
-
-#include <ziparchive/zip_archive.h>
-
-#define OTAIO_BASE_DIR ".libotafault"
-#define OTAIO_READ "READ"
-#define OTAIO_WRITE "WRITE"
-#define OTAIO_FSYNC "FSYNC"
-#define OTAIO_CACHE "CACHE"
-
-/*
- * Initialize libotafault by providing a reference to the OTA package.
- */
-void ota_io_init(ZipArchiveHandle zip, bool retry);
-
-/*
- * Return true if a config file is present for the given IO type.
- */
-bool should_fault_inject(const char* io_type);
-
-/*
- * Return true if an EIO should occur on the next hit to /cache/saved.file
- * instead of the next hit to the specified file.
- */
-bool should_hit_cache();
-
-/*
- * Return the name of the file that should cause an error for the
- * given IO type.
- */
-std::string fault_fname(const char* io_type);
-
-#endif
diff --git a/otafault/include/otafault/ota_io.h b/otafault/include/otafault/ota_io.h
deleted file mode 100644
index 45e481a..0000000
--- a/otafault/include/otafault/ota_io.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Provide a series of proxy functions for basic file accessors.
- * The behavior of these functions can be changed to return different
- * errors under a variety of conditions.
- */
-
-#ifndef _UPDATER_OTA_IO_H_
-#define _UPDATER_OTA_IO_H_
-
-#include <stddef.h>
-#include <stdio.h>
-#include <sys/stat.h> // mode_t
-
-#include <memory>
-
-#include <android-base/unique_fd.h>
-
-#define OTAIO_CACHE_FNAME "/cache/saved.file"
-
-void ota_set_fault_files();
-
-int ota_open(const char* path, int oflags);
-
-int ota_open(const char* path, int oflags, mode_t mode);
-
-FILE* ota_fopen(const char* filename, const char* mode);
-
-size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream);
-
-ssize_t ota_read(int fd, void* buf, size_t nbyte);
-
-size_t ota_fwrite(const void* ptr, size_t size, size_t count, FILE* stream);
-
-ssize_t ota_write(int fd, const void* buf, size_t nbyte);
-
-int ota_fsync(int fd);
-
-struct OtaCloser {
- static void Close(int);
-};
-
-using unique_fd = android::base::unique_fd_impl<OtaCloser>;
-
-int ota_close(unique_fd& fd);
-
-struct OtaFcloser {
- void operator()(FILE*) const;
-};
-
-using unique_file = std::unique_ptr<FILE, OtaFcloser>;
-
-int ota_fclose(unique_file& fh);
-
-#endif
diff --git a/otafault/ota_io.cpp b/otafault/ota_io.cpp
deleted file mode 100644
index 63ef18e..0000000
--- a/otafault/ota_io.cpp
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * 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 "otafault/ota_io.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <map>
-#include <mutex>
-#include <string>
-
-#include <android-base/thread_annotations.h>
-
-#include "otafault/config.h"
-
-static std::mutex filename_mutex;
-static std::map<intptr_t, const char*> filename_cache GUARDED_BY(filename_mutex);
-static std::string read_fault_file_name = "";
-static std::string write_fault_file_name = "";
-static std::string fsync_fault_file_name = "";
-
-static bool get_hit_file(const char* cached_path, const std::string& ffn) {
- return should_hit_cache()
- ? !strncmp(cached_path, OTAIO_CACHE_FNAME, strlen(cached_path))
- : !strncmp(cached_path, ffn.c_str(), strlen(cached_path));
-}
-
-void ota_set_fault_files() {
- if (should_fault_inject(OTAIO_READ)) {
- read_fault_file_name = fault_fname(OTAIO_READ);
- }
- if (should_fault_inject(OTAIO_WRITE)) {
- write_fault_file_name = fault_fname(OTAIO_WRITE);
- }
- if (should_fault_inject(OTAIO_FSYNC)) {
- fsync_fault_file_name = fault_fname(OTAIO_FSYNC);
- }
-}
-
-bool have_eio_error = false;
-
-int ota_open(const char* path, int oflags) {
- // Let the caller handle errors; we do not care if open succeeds or fails
- int fd = open(path, oflags);
- std::lock_guard<std::mutex> lock(filename_mutex);
- filename_cache[fd] = path;
- return fd;
-}
-
-int ota_open(const char* path, int oflags, mode_t mode) {
- int fd = open(path, oflags, mode);
- std::lock_guard<std::mutex> lock(filename_mutex);
- filename_cache[fd] = path;
- return fd;
-}
-
-FILE* ota_fopen(const char* path, const char* mode) {
- FILE* fh = fopen(path, mode);
- std::lock_guard<std::mutex> lock(filename_mutex);
- filename_cache[(intptr_t)fh] = path;
- return fh;
-}
-
-static int __ota_close(int fd) {
- // descriptors can be reused, so make sure not to leave them in the cache
- std::lock_guard<std::mutex> lock(filename_mutex);
- filename_cache.erase(fd);
- return close(fd);
-}
-
-void OtaCloser::Close(int fd) {
- __ota_close(fd);
-}
-
-int ota_close(unique_fd& fd) {
- return __ota_close(fd.release());
-}
-
-static int __ota_fclose(FILE* fh) {
- std::lock_guard<std::mutex> lock(filename_mutex);
- filename_cache.erase(reinterpret_cast<intptr_t>(fh));
- return fclose(fh);
-}
-
-void OtaFcloser::operator()(FILE* f) const {
- __ota_fclose(f);
-};
-
-int ota_fclose(unique_file& fh) {
- return __ota_fclose(fh.release());
-}
-
-size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream) {
- if (should_fault_inject(OTAIO_READ)) {
- std::lock_guard<std::mutex> lock(filename_mutex);
- auto cached = filename_cache.find((intptr_t)stream);
- const char* cached_path = cached->second;
- if (cached != filename_cache.end() &&
- get_hit_file(cached_path, read_fault_file_name)) {
- read_fault_file_name = "";
- errno = EIO;
- have_eio_error = true;
- return 0;
- }
- }
- size_t status = fread(ptr, size, nitems, stream);
- // If I/O error occurs, set the retry-update flag.
- if (status != nitems && errno == EIO) {
- have_eio_error = true;
- }
- return status;
-}
-
-ssize_t ota_read(int fd, void* buf, size_t nbyte) {
- if (should_fault_inject(OTAIO_READ)) {
- std::lock_guard<std::mutex> lock(filename_mutex);
- auto cached = filename_cache.find(fd);
- const char* cached_path = cached->second;
- if (cached != filename_cache.end()
- && get_hit_file(cached_path, read_fault_file_name)) {
- read_fault_file_name = "";
- errno = EIO;
- have_eio_error = true;
- return -1;
- }
- }
- ssize_t status = read(fd, buf, nbyte);
- if (status == -1 && errno == EIO) {
- have_eio_error = true;
- }
- return status;
-}
-
-size_t ota_fwrite(const void* ptr, size_t size, size_t count, FILE* stream) {
- if (should_fault_inject(OTAIO_WRITE)) {
- std::lock_guard<std::mutex> lock(filename_mutex);
- auto cached = filename_cache.find((intptr_t)stream);
- const char* cached_path = cached->second;
- if (cached != filename_cache.end() &&
- get_hit_file(cached_path, write_fault_file_name)) {
- write_fault_file_name = "";
- errno = EIO;
- have_eio_error = true;
- return 0;
- }
- }
- size_t status = fwrite(ptr, size, count, stream);
- if (status != count && errno == EIO) {
- have_eio_error = true;
- }
- return status;
-}
-
-ssize_t ota_write(int fd, const void* buf, size_t nbyte) {
- if (should_fault_inject(OTAIO_WRITE)) {
- std::lock_guard<std::mutex> lock(filename_mutex);
- auto cached = filename_cache.find(fd);
- const char* cached_path = cached->second;
- if (cached != filename_cache.end() &&
- get_hit_file(cached_path, write_fault_file_name)) {
- write_fault_file_name = "";
- errno = EIO;
- have_eio_error = true;
- return -1;
- }
- }
- ssize_t status = write(fd, buf, nbyte);
- if (status == -1 && errno == EIO) {
- have_eio_error = true;
- }
- return status;
-}
-
-int ota_fsync(int fd) {
- if (should_fault_inject(OTAIO_FSYNC)) {
- std::lock_guard<std::mutex> lock(filename_mutex);
- auto cached = filename_cache.find(fd);
- const char* cached_path = cached->second;
- if (cached != filename_cache.end() &&
- get_hit_file(cached_path, fsync_fault_file_name)) {
- fsync_fault_file_name = "";
- errno = EIO;
- have_eio_error = true;
- return -1;
- }
- }
- int status = fsync(fd);
- if (status == -1 && errno == EIO) {
- have_eio_error = true;
- }
- return status;
-}
-
diff --git a/otafault/test.cpp b/otafault/test.cpp
deleted file mode 100644
index 63e2445..0000000
--- a/otafault/test.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 <fcntl.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "otafault/ota_io.h"
-
-int main(int /* argc */, char** /* argv */) {
- int fd = open("testdata/test.file", O_RDWR);
- char buf[8];
- const char* out = "321";
- int readv = ota_read(fd, buf, 4);
- printf("Read returned %d\n", readv);
- int writev = ota_write(fd, out, 4);
- printf("Write returned %d\n", writev);
- close(fd);
- return 0;
-}
diff --git a/otautil/Android.bp b/otautil/Android.bp
index 75cf691..73398c3 100644
--- a/otautil/Android.bp
+++ b/otautil/Android.bp
@@ -16,27 +16,56 @@
name: "libotautil",
host_supported: true,
+ recovery_available: true,
+ defaults: [
+ "recovery_defaults",
+ ],
+
+ // Minimal set of files to support host build.
srcs: [
- "SysUtil.cpp",
- "DirUtil.cpp",
- "ThermalUtil.cpp",
- "cache_location.cpp",
+ "paths.cpp",
"rangeset.cpp",
],
- static_libs: [
- "libselinux",
+ shared_libs: [
"libbase",
],
- cflags: [
- "-D_FILE_OFFSET_BITS=64",
- "-Werror",
- "-Wall",
- ],
-
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",
+ ],
+
+ export_static_lib_headers: [
+ "libfstab",
+ ],
+ },
+ },
}
diff --git a/otautil/SysUtil.cpp b/otautil/SysUtil.cpp
deleted file mode 100644
index 48336ad..0000000
--- a/otautil/SysUtil.cpp
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright 2006 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 "otautil/SysUtil.h"
-
-#include <errno.h> // TEMP_FAILURE_RETRY
-#include <fcntl.h>
-#include <stdint.h> // SIZE_MAX
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#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>
-
-bool MemMapping::MapFD(int fd) {
- struct stat sb;
- if (fstat(fd, &sb) == -1) {
- PLOG(ERROR) << "fstat(" << fd << ") failed";
- return false;
- }
-
- void* memPtr = mmap(nullptr, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
- if (memPtr == MAP_FAILED) {
- PLOG(ERROR) << "mmap(" << sb.st_size << ", R, PRIVATE, " << fd << ", 0) failed";
- return false;
- }
-
- addr = static_cast<unsigned char*>(memPtr);
- length = sb.st_size;
- ranges_.clear();
- ranges_.emplace_back(MappedRange{ memPtr, static_cast<size_t>(sb.st_size) });
-
- return true;
-}
-
-// A "block map" which looks like this (from uncrypt/uncrypt.cpp):
-//
-// /dev/block/platform/msm_sdcc.1/by-name/userdata # block device
-// 49652 4096 # file size in bytes, block size
-// 3 # count of block ranges
-// 1000 1008 # block range 0
-// 2100 2102 # ... block range 1
-// 30 33 # ... block range 2
-//
-// Each block range represents a half-open interval; the line "30 33" reprents the blocks
-// [30, 31, 32].
-bool MemMapping::MapBlockFile(const std::string& filename) {
- std::string content;
- if (!android::base::ReadFileToString(filename, &content)) {
- PLOG(ERROR) << "Failed to read " << filename;
- return false;
- }
-
- std::vector<std::string> lines = android::base::Split(android::base::Trim(content), "\n");
- if (lines.size() < 4) {
- LOG(ERROR) << "Block map file is too short: " << lines.size();
- return false;
- }
-
- size_t size;
- size_t blksize;
- if (sscanf(lines[1].c_str(), "%zu %zu", &size, &blksize) != 2) {
- LOG(ERROR) << "Failed to parse file size and block size: " << lines[1];
- return false;
- }
-
- size_t range_count;
- if (sscanf(lines[2].c_str(), "%zu", &range_count) != 1) {
- LOG(ERROR) << "Failed to parse block map header: " << lines[2];
- return false;
- }
-
- size_t blocks;
- if (blksize != 0) {
- blocks = ((size - 1) / blksize) + 1;
- }
- if (size == 0 || blksize == 0 || blocks > SIZE_MAX / blksize || range_count == 0 ||
- lines.size() != 3 + range_count) {
- LOG(ERROR) << "Invalid data in block map file: size " << size << ", blksize " << blksize
- << ", range_count " << range_count << ", lines " << lines.size();
- return false;
- }
-
- // Reserve enough contiguous address space for the whole file.
- void* reserve = mmap(nullptr, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
- if (reserve == MAP_FAILED) {
- PLOG(ERROR) << "failed to reserve address space";
- return false;
- }
-
- const std::string& block_dev = lines[0];
- android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(block_dev.c_str(), O_RDONLY)));
- if (fd == -1) {
- PLOG(ERROR) << "failed to open block device " << block_dev;
- munmap(reserve, blocks * blksize);
- return false;
- }
-
- ranges_.clear();
-
- unsigned char* next = static_cast<unsigned char*>(reserve);
- size_t remaining_size = blocks * blksize;
- bool success = true;
- for (size_t i = 0; i < range_count; ++i) {
- const std::string& line = lines[i + 3];
-
- size_t start, end;
- if (sscanf(line.c_str(), "%zu %zu\n", &start, &end) != 2) {
- LOG(ERROR) << "failed to parse range " << i << ": " << line;
- success = false;
- break;
- }
- size_t range_size = (end - start) * blksize;
- if (end <= start || (end - start) > SIZE_MAX / blksize || range_size > remaining_size) {
- LOG(ERROR) << "Invalid range: " << start << " " << end;
- success = false;
- break;
- }
-
- void* range_start = mmap(next, range_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd,
- static_cast<off_t>(start) * blksize);
- if (range_start == MAP_FAILED) {
- PLOG(ERROR) << "failed to map range " << i << ": " << line;
- success = false;
- break;
- }
- ranges_.emplace_back(MappedRange{ range_start, range_size });
-
- next += range_size;
- remaining_size -= range_size;
- }
- if (success && remaining_size != 0) {
- LOG(ERROR) << "Invalid ranges: remaining_size " << remaining_size;
- success = false;
- }
- if (!success) {
- munmap(reserve, blocks * blksize);
- return false;
- }
-
- addr = static_cast<unsigned char*>(reserve);
- length = size;
-
- LOG(INFO) << "mmapped " << range_count << " ranges";
-
- return true;
-}
-
-bool MemMapping::MapFile(const std::string& fn) {
- if (fn.empty()) {
- LOG(ERROR) << "Empty filename";
- return false;
- }
-
- if (fn[0] == '@') {
- // Block map file "@/cache/recovery/block.map".
- if (!MapBlockFile(fn.substr(1))) {
- LOG(ERROR) << "Map of '" << fn << "' failed";
- return false;
- }
- } else {
- // This is a regular file.
- android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(fn.c_str(), O_RDONLY)));
- if (fd == -1) {
- PLOG(ERROR) << "Unable to open '" << fn << "'";
- return false;
- }
-
- if (!MapFD(fd)) {
- LOG(ERROR) << "Map of '" << fn << "' failed";
- return false;
- }
- }
- return true;
-}
-
-MemMapping::~MemMapping() {
- for (const auto& range : ranges_) {
- if (munmap(range.addr, range.length) == -1) {
- PLOG(ERROR) << "Failed to munmap(" << range.addr << ", " << range.length << ")";
- }
- };
- ranges_.clear();
-}
diff --git a/otautil/cache_location.cpp b/otautil/cache_location.cpp
deleted file mode 100644
index 8ddefec..0000000
--- a/otautil/cache_location.cpp
+++ /dev/null
@@ -1,31 +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 "otautil/cache_location.h"
-
-constexpr const char kDefaultCacheTempSource[] = "/cache/saved.file";
-constexpr const char kDefaultLastCommandFile[] = "/cache/recovery/last_command";
-constexpr const char kDefaultStashDirectoryBase[] = "/cache/recovery";
-
-CacheLocation& CacheLocation::location() {
- static CacheLocation cache_location;
- return cache_location;
-}
-
-CacheLocation::CacheLocation()
- : cache_temp_source_(kDefaultCacheTempSource),
- last_command_file_(kDefaultLastCommandFile),
- stash_directory_base_(kDefaultStashDirectoryBase) {}
diff --git a/otautil/DirUtil.cpp b/otautil/dirutil.cpp
similarity index 98%
rename from otautil/DirUtil.cpp
rename to otautil/dirutil.cpp
index 61c8328..ae1cd5c 100644
--- a/otautil/DirUtil.cpp
+++ b/otautil/dirutil.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "otautil/DirUtil.h"
+#include "otautil/dirutil.h"
#include <dirent.h>
#include <errno.h>
diff --git a/otautil/include/otautil/SysUtil.h b/otautil/include/otautil/SysUtil.h
deleted file mode 100644
index 52f6d20..0000000
--- a/otautil/include/otautil/SysUtil.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2006 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.
- */
-
-#ifndef _OTAUTIL_SYSUTIL
-#define _OTAUTIL_SYSUTIL
-
-#include <sys/types.h>
-
-#include <string>
-#include <vector>
-
-/*
- * Use this to keep track of mapped segments.
- */
-class MemMapping {
- public:
- ~MemMapping();
- // Map a file into a private, read-only memory segment. If 'filename' begins with an '@'
- // character, it is a map of blocks to be mapped, otherwise it is treated as an ordinary file.
- bool MapFile(const std::string& filename);
- size_t ranges() const {
- return ranges_.size();
- };
-
- unsigned char* addr; // start of data
- size_t length; // length of data
-
- private:
- struct MappedRange {
- void* addr;
- size_t length;
- };
-
- bool MapBlockFile(const std::string& filename);
- bool MapFD(int fd);
-
- std::vector<MappedRange> ranges_;
-};
-
-#endif // _OTAUTIL_SYSUTIL
diff --git a/otautil/include/otautil/cache_location.h b/otautil/include/otautil/cache_location.h
deleted file mode 100644
index f2f6638..0000000
--- a/otautil/include/otautil/cache_location.h
+++ /dev/null
@@ -1,69 +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.
- */
-
-#ifndef _OTAUTIL_OTAUTIL_CACHE_LOCATION_H_
-#define _OTAUTIL_OTAUTIL_CACHE_LOCATION_H_
-
-#include <string>
-
-#include "android-base/macros.h"
-
-// A singleton class to maintain the update related locations. The locations should be only set
-// once at the start of the program.
-class CacheLocation {
- public:
- static CacheLocation& location();
-
- // getter and setter functions.
- std::string cache_temp_source() const {
- return cache_temp_source_;
- }
- void set_cache_temp_source(const std::string& temp_source) {
- cache_temp_source_ = temp_source;
- }
-
- std::string last_command_file() const {
- return last_command_file_;
- }
- void set_last_command_file(const std::string& last_command) {
- last_command_file_ = last_command;
- }
-
- std::string stash_directory_base() const {
- return stash_directory_base_;
- }
- void set_stash_directory_base(const std::string& base) {
- stash_directory_base_ = base;
- }
-
- private:
- CacheLocation();
- DISALLOW_COPY_AND_ASSIGN(CacheLocation);
-
- // When there isn't enough room on the target filesystem to hold the patched version of the file,
- // we copy the original here and delete it to free up space. If the expected source file doesn't
- // exist, or is corrupted, we look to see if the cached file contains the bits we want and use it
- // as the source instead. The default location for the cached source is "/cache/saved.file".
- std::string cache_temp_source_;
-
- // Location to save the last command that stashes blocks.
- std::string last_command_file_;
-
- // The base directory to write stashes during update.
- std::string stash_directory_base_;
-};
-
-#endif // _OTAUTIL_OTAUTIL_CACHE_LOCATION_H_
diff --git a/otautil/include/otautil/DirUtil.h b/otautil/include/otautil/dirutil.h
similarity index 100%
rename from otautil/include/otautil/DirUtil.h
rename to otautil/include/otautil/dirutil.h
diff --git a/otautil/include/otautil/error_code.h b/otautil/include/otautil/error_code.h
index b0ff42d..2b73c13 100644
--- a/otautil/include/otautil/error_code.h
+++ b/otautil/include/otautil/error_code.h
@@ -48,6 +48,8 @@
kRebootFailure,
kPackageExtractFileFailure,
kPatchApplicationFailure,
+ kHashTreeComputationFailure,
+ kEioFailure,
kVendorFailure = 200
};
diff --git a/rotate_logs.h b/otautil/include/otautil/logging.h
similarity index 60%
rename from rotate_logs.h
rename to otautil/include/otautil/logging.h
index 007c33d..6083497 100644
--- a/rotate_logs.h
+++ b/otautil/include/otautil/logging.h
@@ -14,16 +14,30 @@
* limitations under the License.
*/
-#ifndef _ROTATE_LOGS_H
-#define _ROTATE_LOGS_H
+#ifndef _LOGGING_H
+#define _LOGGING_H
#include <stddef.h>
+#include <sys/stat.h>
#include <sys/types.h>
+#include <string>
+#include <vector>
+
#include <log/log_id.h>
static constexpr int KEEP_LOG_COUNT = 10;
+struct selabel_handle;
+
+struct saved_log_file {
+ std::string name;
+ struct stat sb;
+ std::string data;
+};
+
+void SetLoggingSehandle(selabel_handle* handle);
+
ssize_t logbasename(log_id_t id, char prio, const char* filename, const char* buf, size_t len,
void* arg);
@@ -35,4 +49,17 @@
// Overwrite any existing last_log.$max and last_kmsg.$max.
void rotate_logs(const char* last_log_file, const char* last_kmsg_file);
-#endif //_ROTATE_LOG_H
+// In turn fflush(3)'s, fsync(3)'s and fclose(3)'s the given stream.
+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 reset_tmplog_offset();
+
+void save_kernel_log(const char* destination);
+
+std::vector<saved_log_file> ReadLogFilesToMemory();
+
+bool RestoreLogFilesAfterFormat(const std::vector<saved_log_file>& log_files);
+
+#endif //_LOGGING_H
diff --git a/mounts.h b/otautil/include/otautil/mounts.h
similarity index 94%
rename from mounts.h
rename to otautil/include/otautil/mounts.h
index 0de1ebd..6786c8d 100644
--- a/mounts.h
+++ b/otautil/include/otautil/mounts.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef MOUNTS_H_
-#define MOUNTS_H_
+#pragma once
struct MountedVolume;
@@ -24,5 +23,3 @@
MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point);
int unmount_mounted_volume(MountedVolume* volume);
-
-#endif
diff --git a/otautil/include/otautil/parse_install_logs.h b/otautil/include/otautil/parse_install_logs.h
new file mode 100644
index 0000000..135d29c
--- /dev/null
+++ b/otautil/include/otautil/parse_install_logs.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+constexpr const char* LAST_INSTALL_FILE = "/data/misc/recovery/last_install";
+constexpr const char* LAST_INSTALL_FILE_IN_CACHE = "/cache/recovery/last_install";
+
+// Parses the metrics of update applied under recovery mode in |lines|, and returns a map with
+// "name: value".
+std::map<std::string, int64_t> ParseRecoveryUpdateMetrics(const std::vector<std::string>& lines);
+// Parses the sideload history and update metrics in the last_install file. Returns a map with
+// entries as "metrics_name: value". If no such file exists, returns an empty map.
+std::map<std::string, int64_t> ParseLastInstall(const std::string& file_name);
diff --git a/otautil/include/otautil/paths.h b/otautil/include/otautil/paths.h
new file mode 100644
index 0000000..f95741a
--- /dev/null
+++ b/otautil/include/otautil/paths.h
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+
+#ifndef _OTAUTIL_PATHS_H_
+#define _OTAUTIL_PATHS_H_
+
+#include <string>
+
+#include <android-base/macros.h>
+
+// A singleton class to maintain the update related paths. The paths should be only set once at the
+// start of the program.
+class Paths {
+ public:
+ static Paths& Get();
+
+ std::string cache_log_directory() const {
+ return cache_log_directory_;
+ }
+ void set_cache_log_directory(const std::string& log_dir) {
+ cache_log_directory_ = log_dir;
+ }
+
+ std::string cache_temp_source() const {
+ return cache_temp_source_;
+ }
+ void set_cache_temp_source(const std::string& temp_source) {
+ cache_temp_source_ = temp_source;
+ }
+
+ std::string last_command_file() const {
+ return last_command_file_;
+ }
+ void set_last_command_file(const std::string& last_command_file) {
+ last_command_file_ = last_command_file;
+ }
+
+ std::string resource_dir() const {
+ return resource_dir_;
+ }
+ void set_resource_dir(const std::string& resource_dir) {
+ resource_dir_ = resource_dir;
+ }
+
+ std::string stash_directory_base() const {
+ return stash_directory_base_;
+ }
+ void set_stash_directory_base(const std::string& base) {
+ stash_directory_base_ = base;
+ }
+
+ std::string temporary_install_file() const {
+ return temporary_install_file_;
+ }
+ void set_temporary_install_file(const std::string& install_file) {
+ temporary_install_file_ = install_file;
+ }
+
+ std::string temporary_log_file() const {
+ return temporary_log_file_;
+ }
+ void set_temporary_log_file(const std::string& log_file) {
+ temporary_log_file_ = log_file;
+ }
+
+ std::string temporary_update_binary() const {
+ return temporary_update_binary_;
+ }
+ void set_temporary_update_binary(const std::string& update_binary) {
+ temporary_update_binary_ = update_binary;
+ }
+
+ private:
+ Paths();
+ DISALLOW_COPY_AND_ASSIGN(Paths);
+
+ // Path to the directory that contains last_log and last_kmsg log files.
+ std::string cache_log_directory_;
+
+ // Path to the temporary source file on /cache. When there isn't enough room on the target
+ // filesystem to hold the patched version of the file, we copy the original here and delete it to
+ // free up space. If the expected source file doesn't exist, or is corrupted, we look to see if
+ // the cached file contains the bits we want and use it as the source instead.
+ std::string cache_temp_source_;
+
+ // Path to the last command file.
+ std::string last_command_file_;
+
+ // Path to the resource dir;
+ std::string resource_dir_;
+
+ // Path to the base directory to write stashes during update.
+ std::string stash_directory_base_;
+
+ // Path to the temporary file that contains the install result.
+ std::string temporary_install_file_;
+
+ // Path to the temporary log file while under recovery.
+ std::string temporary_log_file_;
+
+ // Path to the temporary update binary while installing a non-A/B package.
+ std::string temporary_update_binary_;
+};
+
+#endif // _OTAUTIL_PATHS_H_
diff --git a/otautil/include/otautil/rangeset.h b/otautil/include/otautil/rangeset.h
index e91d02c..a18c30e 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 @@
// 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/roots.h b/otautil/include/otautil/roots.h
similarity index 80%
rename from roots.h
rename to otautil/include/otautil/roots.h
index 46bb77e..2ab3f45 100644
--- a/roots.h
+++ b/otautil/include/otautil/roots.h
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-#ifndef RECOVERY_ROOTS_H_
-#define RECOVERY_ROOTS_H_
+#pragma once
#include <string>
-typedef struct fstab_rec Volume;
+#include <fstab/fstab.h>
+
+using Volume = android::fs_mgr::FstabEntry;
// Load and parse volume data from /etc/recovery.fstab.
void load_volume_table();
@@ -29,28 +30,26 @@
// Make sure that the volume 'path' is on is mounted. Returns 0 on
// success (volume is mounted).
-int ensure_path_mounted(const char* path);
+int ensure_path_mounted(const std::string& path);
// Similar to ensure_path_mounted, but allows one to specify the mount_point.
-int ensure_path_mounted_at(const char* path, const char* mount_point);
+int ensure_path_mounted_at(const std::string& path, const std::string& mount_point);
// Make sure that the volume 'path' is on is unmounted. Returns 0 on
// success (volume is unmounted);
-int ensure_path_unmounted(const char* path);
+int ensure_path_unmounted(const std::string& path);
// Reformat the given volume (must be the mount point only, eg
// "/cache"), no paths permitted. Attempts to unmount the volume if
// it is mounted.
-int format_volume(const char* volume);
+int format_volume(const std::string& volume);
// Reformat the given volume (must be the mount point only, eg
// "/cache"), no paths permitted. Attempts to unmount the volume if
// it is mounted.
// Copies 'directory' to root of the newly formatted volume
-int format_volume(const char* volume, const char* directory);
+int format_volume(const std::string& volume, const std::string& directory);
// Ensure that all and only the volumes that packages expect to find
// mounted (/tmp and /cache) are mounted. Returns 0 on success.
int setup_install_mounts();
-
-#endif // RECOVERY_ROOTS_H_
diff --git a/otautil/include/otautil/sysutil.h b/otautil/include/otautil/sysutil.h
new file mode 100644
index 0000000..48e9011
--- /dev/null
+++ b/otautil/include/otautil/sysutil.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2006 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 <sys/types.h>
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "rangeset.h"
+
+// This class holds the content of a block map file.
+class BlockMapData {
+ public:
+ // A "block map" which looks like this (from uncrypt/uncrypt.cpp):
+ //
+ // /dev/block/platform/msm_sdcc.1/by-name/userdata # block device
+ // 49652 4096 # file size in bytes, block size
+ // 3 # count of block ranges
+ // 1000 1008 # block range 0
+ // 2100 2102 # ... block range 1
+ // 30 33 # ... block range 2
+ //
+ // Each block range represents a half-open interval; the line "30 33" reprents the blocks
+ // [30, 31, 32].
+ static BlockMapData ParseBlockMapFile(const std::string& block_map_path);
+
+ explicit operator bool() const {
+ return !path_.empty();
+ }
+
+ std::string path() const {
+ return path_;
+ }
+ uint64_t file_size() const {
+ return file_size_;
+ }
+ uint32_t block_size() const {
+ return block_size_;
+ }
+ RangeSet block_ranges() const {
+ return block_ranges_;
+ }
+
+ private:
+ BlockMapData() = default;
+
+ BlockMapData(const std::string& path, uint64_t file_size, uint32_t block_size,
+ RangeSet block_ranges)
+ : path_(path),
+ file_size_(file_size),
+ block_size_(block_size),
+ block_ranges_(std::move(block_ranges)) {}
+
+ std::string path_;
+ uint64_t file_size_ = 0;
+ uint32_t block_size_ = 0;
+ RangeSet block_ranges_;
+};
+
+/*
+ * Use this to keep track of mapped segments.
+ */
+class MemMapping {
+ public:
+ ~MemMapping();
+ // Map a file into a private, read-only memory segment. If 'filename' begins with an '@'
+ // character, it is a map of blocks to be mapped, otherwise it is treated as an ordinary file.
+ bool MapFile(const std::string& filename);
+ size_t ranges() const {
+ return ranges_.size();
+ };
+
+ unsigned char* addr; // start of data
+ size_t length; // length of data
+
+ private:
+ struct MappedRange {
+ void* addr;
+ size_t length;
+ };
+
+ bool MapBlockFile(const std::string& filename);
+ bool MapFD(int fd);
+
+ std::vector<MappedRange> ranges_;
+};
+
+// Reboots the device into the specified target, by additionally handling quiescent reboot mode.
+// 'target' can be an empty string, which indicates booting into Android.
+bool Reboot(std::string_view target);
+
+// Triggers a shutdown.
+bool Shutdown();
+
+// 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);
diff --git a/otautil/include/otautil/ThermalUtil.h b/otautil/include/otautil/thermalutil.h
similarity index 100%
rename from otautil/include/otautil/ThermalUtil.h
rename to otautil/include/otautil/thermalutil.h
diff --git a/otautil/logging.cpp b/otautil/logging.cpp
new file mode 100644
index 0000000..484f115
--- /dev/null
+++ b/otautil/logging.cpp
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2016 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 "otautil/logging.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/klog.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <private/android_filesystem_config.h> /* for AID_SYSTEM */
+#include <private/android_logger.h> /* private pmsg functions */
+#include <selinux/label.h>
+
+#include "otautil/dirutil.h"
+#include "otautil/paths.h"
+#include "otautil/roots.h"
+
+constexpr const char* LOG_FILE = "/cache/recovery/log";
+constexpr const char* LAST_INSTALL_FILE = "/cache/recovery/last_install";
+constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg";
+constexpr const char* LAST_LOG_FILE = "/cache/recovery/last_log";
+
+constexpr const char* LAST_KMSG_FILTER = "recovery/last_kmsg";
+constexpr const char* LAST_LOG_FILTER = "recovery/last_log";
+
+constexpr const char* CACHE_LOG_DIR = "/cache/recovery";
+
+static struct selabel_handle* logging_sehandle;
+
+void SetLoggingSehandle(selabel_handle* handle) {
+ logging_sehandle = handle;
+}
+
+// fopen(3)'s the given file, by mounting volumes and making parent dirs as necessary. Returns the
+// file pointer, or nullptr on error.
+static FILE* fopen_path(const std::string& path, const char* mode, const selabel_handle* sehandle) {
+ if (ensure_path_mounted(path) != 0) {
+ LOG(ERROR) << "Can't mount " << path;
+ return nullptr;
+ }
+
+ // When writing, try to create the containing directory, if necessary. Use generous permissions,
+ // the system (init.rc) will reset them.
+ if (strchr("wa", mode[0])) {
+ mkdir_recursively(path, 0777, true, sehandle);
+ }
+ return fopen(path.c_str(), mode);
+}
+
+void check_and_fclose(FILE* fp, const std::string& name) {
+ fflush(fp);
+ if (fsync(fileno(fp)) == -1) {
+ PLOG(ERROR) << "Failed to fsync " << name;
+ }
+ if (ferror(fp)) {
+ PLOG(ERROR) << "Error in " << name;
+ }
+ fclose(fp);
+}
+
+// close a file, log an error if the error indicator is set
+ssize_t logbasename(log_id_t /* id */, char /* prio */, const char* filename, const char* /* buf */,
+ size_t len, void* arg) {
+ bool* do_rotate = static_cast<bool*>(arg);
+ if (std::string(LAST_KMSG_FILTER).find(filename) != std::string::npos ||
+ std::string(LAST_LOG_FILTER).find(filename) != std::string::npos) {
+ *do_rotate = true;
+ }
+ return len;
+}
+
+ssize_t logrotate(log_id_t id, char prio, const char* filename, const char* buf, size_t len,
+ void* arg) {
+ bool* do_rotate = static_cast<bool*>(arg);
+ if (!*do_rotate) {
+ return __android_log_pmsg_file_write(id, prio, filename, buf, len);
+ }
+
+ std::string name(filename);
+ size_t dot = name.find_last_of('.');
+ std::string sub = name.substr(0, dot);
+
+ if (std::string(LAST_KMSG_FILTER).find(sub) == std::string::npos &&
+ std::string(LAST_LOG_FILTER).find(sub) == std::string::npos) {
+ return __android_log_pmsg_file_write(id, prio, filename, buf, len);
+ }
+
+ // filename rotation
+ if (dot == std::string::npos) {
+ name += ".1";
+ } else {
+ std::string number = name.substr(dot + 1);
+ if (!isdigit(number[0])) {
+ name += ".1";
+ } else {
+ size_t i;
+ if (!android::base::ParseUint(number, &i)) {
+ LOG(ERROR) << "failed to parse uint in " << number;
+ return -1;
+ }
+ name = sub + "." + std::to_string(i + 1);
+ }
+ }
+
+ return __android_log_pmsg_file_write(id, prio, name.c_str(), buf, len);
+}
+
+// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max.
+// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max.
+// Overwrite any existing last_log.$max and last_kmsg.$max.
+void rotate_logs(const char* last_log_file, const char* last_kmsg_file) {
+ // Logs should only be rotated once.
+ static bool rotated = false;
+ if (rotated) {
+ return;
+ }
+ rotated = true;
+
+ for (int i = KEEP_LOG_COUNT - 1; i >= 0; --i) {
+ std::string old_log = android::base::StringPrintf("%s", last_log_file);
+ if (i > 0) {
+ old_log += "." + std::to_string(i);
+ }
+ std::string new_log = android::base::StringPrintf("%s.%d", last_log_file, i + 1);
+ // Ignore errors if old_log doesn't exist.
+ rename(old_log.c_str(), new_log.c_str());
+
+ std::string old_kmsg = android::base::StringPrintf("%s", last_kmsg_file);
+ if (i > 0) {
+ old_kmsg += "." + std::to_string(i);
+ }
+ std::string new_kmsg = android::base::StringPrintf("%s.%d", last_kmsg_file, i + 1);
+ rename(old_kmsg.c_str(), new_kmsg.c_str());
+ }
+}
+
+// Writes content to the current pmsg session.
+static ssize_t __pmsg_write(const std::string& filename, const std::string& buf) {
+ return __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filename.c_str(),
+ buf.data(), buf.size());
+}
+
+void copy_log_file_to_pmsg(const std::string& source, const std::string& destination) {
+ std::string content;
+ android::base::ReadFileToString(source, &content);
+ __pmsg_write(destination, content);
+}
+
+// How much of the temp log we have copied to the copy in cache.
+static off_t tmplog_offset = 0;
+
+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);
+ if (dest_fp == nullptr) {
+ PLOG(ERROR) << "Can't open " << destination;
+ } else {
+ FILE* source_fp = fopen(source.c_str(), "re");
+ if (source_fp != nullptr) {
+ if (append) {
+ fseeko(source_fp, tmplog_offset, SEEK_SET); // Since last write
+ }
+ char buf[4096];
+ size_t bytes;
+ while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
+ fwrite(buf, 1, bytes, dest_fp);
+ }
+ if (append) {
+ tmplog_offset = ftello(source_fp);
+ }
+ check_and_fclose(source_fp, source);
+ }
+ check_and_fclose(dest_fp, destination);
+ }
+}
+
+void copy_logs(bool save_current_log, bool has_cache, const selabel_handle* sehandle) {
+ // 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.
+ if (!save_current_log) {
+ return;
+ }
+
+ // Always write to pmsg, this allows the OTA logs to be caught in `logcat -L`.
+ copy_log_file_to_pmsg(Paths::Get().temporary_log_file(), LAST_LOG_FILE);
+ 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) {
+ return;
+ }
+
+ ensure_path_mounted(LAST_LOG_FILE);
+ ensure_path_mounted(LAST_KMSG_FILE);
+ 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);
+ save_kernel_log(LAST_KMSG_FILE);
+ chmod(LOG_FILE, 0600);
+ chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM);
+ chmod(LAST_KMSG_FILE, 0600);
+ chown(LAST_KMSG_FILE, AID_SYSTEM, AID_SYSTEM);
+ chmod(LAST_LOG_FILE, 0640);
+ chmod(LAST_INSTALL_FILE, 0644);
+ chown(LAST_INSTALL_FILE, AID_SYSTEM, AID_SYSTEM);
+ sync();
+}
+
+// Read from kernel log into buffer and write out to file.
+void save_kernel_log(const char* destination) {
+ int klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0);
+ if (klog_buf_len <= 0) {
+ PLOG(ERROR) << "Error getting klog size";
+ return;
+ }
+
+ std::string buffer(klog_buf_len, 0);
+ int n = klogctl(KLOG_READ_ALL, &buffer[0], klog_buf_len);
+ if (n == -1) {
+ PLOG(ERROR) << "Error in reading klog";
+ return;
+ }
+ buffer.resize(n);
+ android::base::WriteStringToFile(buffer, destination);
+}
+
+std::vector<saved_log_file> ReadLogFilesToMemory() {
+ ensure_path_mounted("/cache");
+
+ struct dirent* de;
+ std::unique_ptr<DIR, decltype(&closedir)> d(opendir(CACHE_LOG_DIR), closedir);
+ if (!d) {
+ if (errno != ENOENT) {
+ PLOG(ERROR) << "Failed to opendir " << CACHE_LOG_DIR;
+ }
+ return {};
+ }
+
+ std::vector<saved_log_file> log_files;
+ while ((de = readdir(d.get())) != nullptr) {
+ if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) {
+ std::string path = android::base::StringPrintf("%s/%s", CACHE_LOG_DIR, de->d_name);
+
+ struct stat sb;
+ if (stat(path.c_str(), &sb) != 0) {
+ PLOG(ERROR) << "Failed to stat " << path;
+ continue;
+ }
+ // Truncate files to 512kb
+ size_t read_size = std::min<size_t>(sb.st_size, 1 << 19);
+ std::string data(read_size, '\0');
+
+ android::base::unique_fd log_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY)));
+ if (log_fd == -1 || !android::base::ReadFully(log_fd, data.data(), read_size)) {
+ PLOG(ERROR) << "Failed to read log file " << path;
+ continue;
+ }
+
+ log_files.emplace_back(saved_log_file{ path, sb, data });
+ }
+ }
+
+ return log_files;
+}
+
+bool RestoreLogFilesAfterFormat(const std::vector<saved_log_file>& log_files) {
+ // Re-create the log dir and write back the log entries.
+ if (ensure_path_mounted(CACHE_LOG_DIR) != 0) {
+ PLOG(ERROR) << "Failed to mount " << CACHE_LOG_DIR;
+ return false;
+ }
+
+ if (mkdir_recursively(CACHE_LOG_DIR, 0777, false, logging_sehandle) != 0) {
+ PLOG(ERROR) << "Failed to create " << CACHE_LOG_DIR;
+ return false;
+ }
+
+ for (const auto& log : log_files) {
+ if (!android::base::WriteStringToFile(log.data, log.name, log.sb.st_mode, log.sb.st_uid,
+ log.sb.st_gid)) {
+ PLOG(ERROR) << "Failed to write to " << log.name;
+ }
+ }
+
+ // Any part of the log we'd copied to cache is now gone.
+ // 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);
+
+ return true;
+}
diff --git a/mounts.cpp b/otautil/mounts.cpp
similarity index 60%
rename from mounts.cpp
rename to otautil/mounts.cpp
index 76fa657..951311b 100644
--- a/mounts.cpp
+++ b/otautil/mounts.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "mounts.h"
+#include "otautil/mounts.h"
#include <errno.h>
#include <fcntl.h>
@@ -30,43 +30,43 @@
#include <android-base/logging.h>
struct MountedVolume {
- std::string device;
- std::string mount_point;
- std::string filesystem;
- std::string flags;
+ std::string device;
+ std::string mount_point;
+ std::string filesystem;
+ std::string flags;
};
-std::vector<MountedVolume*> g_mounts_state;
+static std::vector<MountedVolume*> g_mounts_state;
bool scan_mounted_volumes() {
- for (size_t i = 0; i < g_mounts_state.size(); ++i) {
- delete g_mounts_state[i];
- }
- g_mounts_state.clear();
+ for (size_t i = 0; i < g_mounts_state.size(); ++i) {
+ delete g_mounts_state[i];
+ }
+ g_mounts_state.clear();
- // Open and read mount table entries.
- FILE* fp = setmntent("/proc/mounts", "re");
- if (fp == NULL) {
- return false;
- }
- mntent* e;
- while ((e = getmntent(fp)) != NULL) {
- MountedVolume* v = new MountedVolume;
- v->device = e->mnt_fsname;
- v->mount_point = e->mnt_dir;
- v->filesystem = e->mnt_type;
- v->flags = e->mnt_opts;
- g_mounts_state.push_back(v);
- }
- endmntent(fp);
- return true;
+ // Open and read mount table entries.
+ FILE* fp = setmntent("/proc/mounts", "re");
+ if (fp == NULL) {
+ return false;
+ }
+ mntent* e;
+ while ((e = getmntent(fp)) != NULL) {
+ MountedVolume* v = new MountedVolume;
+ v->device = e->mnt_fsname;
+ v->mount_point = e->mnt_dir;
+ v->filesystem = e->mnt_type;
+ v->flags = e->mnt_opts;
+ g_mounts_state.push_back(v);
+ }
+ endmntent(fp);
+ return true;
}
MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point) {
- for (size_t i = 0; i < g_mounts_state.size(); ++i) {
- if (g_mounts_state[i]->mount_point == mount_point) return g_mounts_state[i];
- }
- return nullptr;
+ for (size_t i = 0; i < g_mounts_state.size(); ++i) {
+ if (g_mounts_state[i]->mount_point == mount_point) return g_mounts_state[i];
+ }
+ return nullptr;
}
int unmount_mounted_volume(MountedVolume* volume) {
diff --git a/otautil/parse_install_logs.cpp b/otautil/parse_install_logs.cpp
new file mode 100644
index 0000000..13a7299
--- /dev/null
+++ b/otautil/parse_install_logs.cpp
@@ -0,0 +1,114 @@
+/*
+ * 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 "otautil/parse_install_logs.h"
+
+#include <unistd.h>
+
+#include <optional>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+
+constexpr const char* OTA_SIDELOAD_METRICS = "ota_sideload";
+
+// Here is an example of lines in last_install:
+// ...
+// time_total: 101
+// bytes_written_vendor: 51074
+// bytes_stashed_vendor: 200
+std::map<std::string, int64_t> ParseRecoveryUpdateMetrics(const std::vector<std::string>& lines) {
+ constexpr unsigned int kMiB = 1024 * 1024;
+ std::optional<int64_t> bytes_written_in_mib;
+ std::optional<int64_t> bytes_stashed_in_mib;
+ std::map<std::string, int64_t> metrics;
+ for (const auto& line : lines) {
+ size_t num_index = line.find(':');
+ if (num_index == std::string::npos) {
+ LOG(WARNING) << "Skip parsing " << line;
+ continue;
+ }
+
+ std::string num_string = android::base::Trim(line.substr(num_index + 1));
+ int64_t parsed_num;
+ if (!android::base::ParseInt(num_string, &parsed_num)) {
+ LOG(ERROR) << "Failed to parse numbers in " << line;
+ continue;
+ }
+
+ if (android::base::StartsWith(line, "bytes_written")) {
+ bytes_written_in_mib = bytes_written_in_mib.value_or(0) + parsed_num / kMiB;
+ } else if (android::base::StartsWith(line, "bytes_stashed")) {
+ bytes_stashed_in_mib = bytes_stashed_in_mib.value_or(0) + parsed_num / kMiB;
+ } else if (android::base::StartsWith(line, "time")) {
+ metrics.emplace("ota_time_total", parsed_num);
+ } else if (android::base::StartsWith(line, "uncrypt_time")) {
+ metrics.emplace("ota_uncrypt_time", parsed_num);
+ } else if (android::base::StartsWith(line, "source_build")) {
+ metrics.emplace("ota_source_version", parsed_num);
+ } else if (android::base::StartsWith(line, "temperature_start")) {
+ metrics.emplace("ota_temperature_start", parsed_num);
+ } else if (android::base::StartsWith(line, "temperature_end")) {
+ metrics.emplace("ota_temperature_end", parsed_num);
+ } else if (android::base::StartsWith(line, "temperature_max")) {
+ metrics.emplace("ota_temperature_max", parsed_num);
+ } else if (android::base::StartsWith(line, "error")) {
+ metrics.emplace("ota_non_ab_error_code", parsed_num);
+ } else if (android::base::StartsWith(line, "cause")) {
+ metrics.emplace("ota_non_ab_cause_code", parsed_num);
+ }
+ }
+
+ if (bytes_written_in_mib) {
+ metrics.emplace("ota_written_in_MiBs", bytes_written_in_mib.value());
+ }
+ if (bytes_stashed_in_mib) {
+ metrics.emplace("ota_stashed_in_MiBs", bytes_stashed_in_mib.value());
+ }
+
+ return metrics;
+}
+
+std::map<std::string, int64_t> ParseLastInstall(const std::string& file_name) {
+ if (access(file_name.c_str(), F_OK) != 0) {
+ return {};
+ }
+
+ std::string content;
+ if (!android::base::ReadFileToString(file_name, &content)) {
+ PLOG(ERROR) << "Failed to read " << file_name;
+ return {};
+ }
+
+ if (content.empty()) {
+ LOG(INFO) << "Empty last_install file";
+ return {};
+ }
+
+ std::vector<std::string> lines = android::base::Split(content, "\n");
+ auto metrics = ParseRecoveryUpdateMetrics(lines);
+
+ // LAST_INSTALL starts with "/sideload/package.zip" after a sideload.
+ if (android::base::Trim(lines[0]) == "/sideload/package.zip") {
+ int type = (android::base::GetProperty("ro.build.type", "") == "user") ? 1 : 0;
+ metrics.emplace(OTA_SIDELOAD_METRICS, type);
+ }
+
+ return metrics;
+}
diff --git a/otautil/paths.cpp b/otautil/paths.cpp
new file mode 100644
index 0000000..33ab4a5
--- /dev/null
+++ b/otautil/paths.cpp
@@ -0,0 +1,41 @@
+/*
+ * 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 "otautil/paths.h"
+
+constexpr const char kDefaultCacheLogDirectory[] = "/cache/recovery";
+constexpr const char kDefaultCacheTempSource[] = "/cache/saved.file";
+constexpr const char kDefaultLastCommandFile[] = "/cache/recovery/last_command";
+constexpr const char kDefaultResourceDirectory[] = "/res/images";
+constexpr const char kDefaultStashDirectoryBase[] = "/cache/recovery";
+constexpr const char kDefaultTemporaryInstallFile[] = "/tmp/last_install";
+constexpr const char kDefaultTemporaryLogFile[] = "/tmp/recovery.log";
+constexpr const char kDefaultTemporaryUpdateBinary[] = "/tmp/update-binary";
+
+Paths& Paths::Get() {
+ static Paths paths;
+ return paths;
+}
+
+Paths::Paths()
+ : cache_log_directory_(kDefaultCacheLogDirectory),
+ cache_temp_source_(kDefaultCacheTempSource),
+ last_command_file_(kDefaultLastCommandFile),
+ resource_dir_(kDefaultResourceDirectory),
+ stash_directory_base_(kDefaultStashDirectoryBase),
+ temporary_install_file_(kDefaultTemporaryInstallFile),
+ temporary_log_file_(kDefaultTemporaryLogFile),
+ temporary_update_binary_(kDefaultTemporaryUpdateBinary) {}
diff --git a/otautil/rangeset.cpp b/otautil/rangeset.cpp
index 96955b9..8ee99dd 100644
--- a/otautil/rangeset.cpp
+++ b/otautil/rangeset.cpp
@@ -148,8 +148,8 @@
return "";
}
std::string result = std::to_string(ranges_.size() * 2);
- for (const auto& r : ranges_) {
- result += android::base::StringPrintf(",%zu,%zu", r.first, r.second);
+ for (const auto& [begin, end] : ranges_) {
+ result += android::base::StringPrintf(",%zu,%zu", begin, end);
}
return result;
@@ -159,11 +159,11 @@
size_t RangeSet::GetBlockNumber(size_t idx) const {
CHECK_LT(idx, blocks_) << "Out of bound index " << idx << " (total blocks: " << blocks_ << ")";
- for (const auto& range : ranges_) {
- if (idx < range.second - range.first) {
- return range.first + idx;
+ for (const auto& [begin, end] : ranges_) {
+ if (idx < end - begin) {
+ return begin + idx;
}
- idx -= (range.second - range.first);
+ idx -= (end - begin);
}
CHECK(false) << "Failed to find block number for index " << idx;
@@ -173,14 +173,10 @@
// RangeSet has half-closed half-open bounds. For example, "3,5" contains blocks 3 and 4. So "3,5"
// and "5,7" are not overlapped.
bool RangeSet::Overlaps(const RangeSet& other) const {
- for (const auto& range : ranges_) {
- size_t start = range.first;
- size_t end = range.second;
- for (const auto& other_range : other.ranges_) {
- size_t other_start = other_range.first;
- size_t other_end = other_range.second;
- // [start, end) vs [other_start, other_end)
- if (!(other_start >= end || start >= other_end)) {
+ for (const auto& [begin, end] : ranges_) {
+ for (const auto& [other_begin, other_end] : other.ranges_) {
+ // [begin, end) vs [other_begin, other_end)
+ if (!(other_begin >= end || begin >= other_end)) {
return true;
}
}
@@ -188,6 +184,58 @@
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());
@@ -248,20 +296,20 @@
size_t SortedRangeSet::GetOffsetInRangeSet(size_t old_offset) const {
size_t old_block_start = old_offset / kBlockSize;
size_t new_block_start = 0;
- for (const auto& range : ranges_) {
+ for (const auto& [start, end] : ranges_) {
// Find the index of old_block_start.
- if (old_block_start >= range.second) {
- new_block_start += (range.second - range.first);
- } else if (old_block_start >= range.first) {
- new_block_start += (old_block_start - range.first);
+ if (old_block_start >= end) {
+ new_block_start += (end - start);
+ } else if (old_block_start >= start) {
+ new_block_start += (old_block_start - start);
return (new_block_start * kBlockSize + old_offset % kBlockSize);
} else {
CHECK(false) << "block_start " << old_block_start
- << " is missing between two ranges: " << this->ToString();
+ << " is missing between two ranges: " << ToString();
return 0;
}
}
CHECK(false) << "block_start " << old_block_start
- << " exceeds the limit of current RangeSet: " << this->ToString();
+ << " exceeds the limit of current RangeSet: " << ToString();
return 0;
}
diff --git a/otautil/roots.cpp b/otautil/roots.cpp
new file mode 100644
index 0000000..a778e05
--- /dev/null
+++ b/otautil/roots.cpp
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2007 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 "otautil/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>
+#include <unistd.h>
+
+#include <iostream>
+#include <string>
+#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;
+using android::fs_mgr::FstabEntry;
+using android::fs_mgr::ReadDefaultFstab;
+
+static Fstab fstab;
+
+void load_volume_table() {
+ if (!ReadDefaultFstab(&fstab)) {
+ LOG(ERROR) << "Failed to read default fstab";
+ return;
+ }
+
+ fstab.emplace_back(FstabEntry{
+ .mount_point = "/tmp", .fs_type = "ramdisk", .blk_device = "ramdisk", .length = 0 });
+
+ std::cout << "recovery filesystem table" << std::endl << "=========================" << std::endl;
+ for (size_t i = 0; i < fstab.size(); ++i) {
+ const auto& entry = fstab[i];
+ std::cout << " " << i << " " << entry.mount_point << " "
+ << " " << entry.fs_type << " " << entry.blk_device << " " << entry.length
+ << std::endl;
+ }
+ std::cout << std::endl;
+}
+
+Volume* volume_for_mount_point(const std::string& mount_point) {
+ return android::fs_mgr::GetEntryForMountPoint(&fstab, mount_point);
+}
+
+// Mount the volume specified by path at the given mount_point.
+int ensure_path_mounted_at(const std::string& path, const std::string& mount_point) {
+ return android::fs_mgr::EnsurePathMounted(&fstab, path, mount_point) ? 0 : -1;
+}
+
+int ensure_path_mounted(const std::string& path) {
+ // Mount at the default mount point.
+ return android::fs_mgr::EnsurePathMounted(&fstab, path) ? 0 : -1;
+}
+
+int ensure_path_unmounted(const std::string& path) {
+ return android::fs_mgr::EnsurePathUnmounted(&fstab, path) ? 0 : -1;
+}
+
+static int exec_cmd(const std::vector<std::string>& args) {
+ CHECK(!args.empty());
+ auto argv = StringVectorToNullTerminatedArray(args);
+
+ pid_t child;
+ if ((child = fork()) == 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);
+}
+
+static int64_t get_file_size(int fd, uint64_t reserve_len) {
+ struct stat buf;
+ int ret = fstat(fd, &buf);
+ if (ret) return 0;
+
+ int64_t computed_size;
+ if (S_ISREG(buf.st_mode)) {
+ computed_size = buf.st_size - reserve_len;
+ } else if (S_ISBLK(buf.st_mode)) {
+ uint64_t block_device_size = get_block_device_size(fd);
+ if (block_device_size < reserve_len ||
+ block_device_size > std::numeric_limits<int64_t>::max()) {
+ computed_size = 0;
+ } else {
+ computed_size = block_device_size - reserve_len;
+ }
+ } else {
+ computed_size = 0;
+ }
+
+ return computed_size;
+}
+
+int format_volume(const std::string& volume, const std::string& directory) {
+ const FstabEntry* v = android::fs_mgr::GetEntryForPath(&fstab, volume);
+ if (v == nullptr) {
+ LOG(ERROR) << "unknown volume \"" << volume << "\"";
+ return -1;
+ }
+ if (v->fs_type == "ramdisk") {
+ LOG(ERROR) << "can't format_volume \"" << volume << "\"";
+ return -1;
+ }
+ if (v->mount_point != volume) {
+ LOG(ERROR) << "can't give path \"" << volume << "\" to format_volume";
+ return -1;
+ }
+ if (ensure_path_unmounted(volume) != 0) {
+ LOG(ERROR) << "format_volume: Failed to unmount \"" << v->mount_point << "\"";
+ return -1;
+ }
+ if (v->fs_type != "ext4" && v->fs_type != "f2fs") {
+ LOG(ERROR) << "format_volume: fs_type \"" << v->fs_type << "\" unsupported";
+ return -1;
+ }
+
+ // If there's a key_loc that looks like a path, it should be a block device for storing encryption
+ // metadata. Wipe it too.
+ if (!v->key_loc.empty() && v->key_loc[0] == '/') {
+ LOG(INFO) << "Wiping " << v->key_loc;
+ int fd = open(v->key_loc.c_str(), O_WRONLY | O_CREAT, 0644);
+ if (fd == -1) {
+ PLOG(ERROR) << "format_volume: Failed to open " << v->key_loc;
+ return -1;
+ }
+ wipe_block_device(fd, get_file_size(fd));
+ close(fd);
+ }
+
+ int64_t length = 0;
+ if (v->length > 0) {
+ length = v->length;
+ } else if (v->length < 0 || v->key_loc == "footer") {
+ android::base::unique_fd fd(open(v->blk_device.c_str(), O_RDONLY));
+ if (fd == -1) {
+ PLOG(ERROR) << "format_volume: failed to open " << v->blk_device;
+ return -1;
+ }
+ length = get_file_size(fd.get(), v->length ? -v->length : CRYPT_FOOTER_OFFSET);
+ if (length <= 0) {
+ LOG(ERROR) << "get_file_size: invalid size " << length << " for " << v->blk_device;
+ return -1;
+ }
+ }
+
+ if (v->fs_type == "ext4") {
+ static constexpr int kBlockSize = 4096;
+ std::vector<std::string> mke2fs_args = {
+ "/system/bin/mke2fs", "-F", "-t", "ext4", "-b", std::to_string(kBlockSize),
+ };
+
+ int raid_stride = v->logical_blk_size / kBlockSize;
+ int raid_stripe_width = v->erase_blk_size / kBlockSize;
+ // stride should be the max of 8KB and logical block size
+ if (v->logical_blk_size != 0 && v->logical_blk_size < 8192) {
+ raid_stride = 8192 / kBlockSize;
+ }
+ if (v->erase_blk_size != 0 && v->logical_blk_size != 0) {
+ mke2fs_args.push_back("-E");
+ mke2fs_args.push_back(
+ android::base::StringPrintf("stride=%d,stripe-width=%d", raid_stride, raid_stripe_width));
+ }
+ mke2fs_args.push_back(v->blk_device);
+ if (length != 0) {
+ mke2fs_args.push_back(std::to_string(length / kBlockSize));
+ }
+
+ int result = exec_cmd(mke2fs_args);
+ if (result == 0 && !directory.empty()) {
+ std::vector<std::string> e2fsdroid_args = {
+ "/system/bin/e2fsdroid", "-e", "-f", directory, "-a", volume, v->blk_device,
+ };
+ result = exec_cmd(e2fsdroid_args);
+ }
+
+ if (result != 0) {
+ PLOG(ERROR) << "format_volume: Failed to make ext4 on " << v->blk_device;
+ return -1;
+ }
+ return 0;
+ }
+
+ // Has to be f2fs because we checked earlier.
+ static constexpr int kSectorSize = 4096;
+ std::vector<std::string> make_f2fs_cmd = {
+ "/system/bin/make_f2fs",
+ "-g",
+ "android",
+ v->blk_device,
+ };
+ if (length >= kSectorSize) {
+ make_f2fs_cmd.push_back(std::to_string(length / kSectorSize));
+ }
+
+ if (exec_cmd(make_f2fs_cmd) != 0) {
+ PLOG(ERROR) << "format_volume: Failed to make_f2fs on " << v->blk_device;
+ return -1;
+ }
+ if (!directory.empty()) {
+ std::vector<std::string> sload_f2fs_cmd = {
+ "/system/bin/sload_f2fs", "-f", directory, "-t", volume, v->blk_device,
+ };
+ if (exec_cmd(sload_f2fs_cmd) != 0) {
+ PLOG(ERROR) << "format_volume: Failed to sload_f2fs on " << v->blk_device;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int format_volume(const std::string& volume) {
+ return format_volume(volume, "");
+}
+
+int setup_install_mounts() {
+ if (fstab.empty()) {
+ LOG(ERROR) << "can't set up install mounts: no fstab loaded";
+ return -1;
+ }
+ for (const FstabEntry& entry : fstab) {
+ // We don't want to do anything with "/".
+ if (entry.mount_point == "/") {
+ continue;
+ }
+
+ if (entry.mount_point == "/tmp" || entry.mount_point == "/cache") {
+ if (ensure_path_mounted(entry.mount_point) != 0) {
+ LOG(ERROR) << "Failed to mount " << entry.mount_point;
+ return -1;
+ }
+ } else {
+ if (ensure_path_unmounted(entry.mount_point) != 0) {
+ LOG(ERROR) << "Failed to unmount " << entry.mount_point;
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
diff --git a/otautil/sysutil.cpp b/otautil/sysutil.cpp
new file mode 100644
index 0000000..420db4c
--- /dev/null
+++ b/otautil/sysutil.cpp
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2006 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 "otautil/sysutil.h"
+
+#include <errno.h> // TEMP_FAILURE_RETRY
+#include <fcntl.h>
+#include <inttypes.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <limits>
+#include <string>
+#include <vector>
+
+#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 <cutils/android_reboot.h>
+
+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;
+ return {};
+ }
+
+ std::vector<std::string> lines = android::base::Split(android::base::Trim(content), "\n");
+ if (lines.size() < 4) {
+ LOG(ERROR) << "Block map file is too short: " << lines.size();
+ return {};
+ }
+
+ const std::string& block_dev = lines[0];
+
+ uint64_t file_size;
+ uint32_t blksize;
+ if (sscanf(lines[1].c_str(), "%" SCNu64 "%" SCNu32, &file_size, &blksize) != 2) {
+ LOG(ERROR) << "Failed to parse file size and block size: " << lines[1];
+ return {};
+ }
+
+ if (file_size == 0 || blksize == 0) {
+ LOG(ERROR) << "Invalid size in block map file: size " << file_size << ", blksize " << blksize;
+ return {};
+ }
+
+ size_t range_count;
+ if (sscanf(lines[2].c_str(), "%zu", &range_count) != 1) {
+ LOG(ERROR) << "Failed to parse block map header: " << lines[2];
+ return {};
+ }
+
+ uint64_t blocks = ((file_size - 1) / blksize) + 1;
+ if (blocks > std::numeric_limits<uint32_t>::max() || range_count == 0 ||
+ lines.size() != 3 + range_count) {
+ LOG(ERROR) << "Invalid data in block map file: size " << file_size << ", blksize " << blksize
+ << ", range_count " << range_count << ", lines " << lines.size();
+ return {};
+ }
+
+ RangeSet ranges;
+ uint64_t remaining_blocks = blocks;
+ for (size_t i = 0; i < range_count; ++i) {
+ const std::string& line = lines[i + 3];
+ uint64_t start, end;
+ if (sscanf(line.c_str(), "%" SCNu64 "%" SCNu64, &start, &end) != 2) {
+ LOG(ERROR) << "failed to parse range " << i << ": " << line;
+ return {};
+ }
+ uint64_t range_blocks = end - start;
+ if (end <= start || range_blocks > remaining_blocks) {
+ LOG(ERROR) << "Invalid range: " << start << " " << end;
+ return {};
+ }
+ ranges.PushBack({ start, end });
+ 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));
+}
+
+bool MemMapping::MapFD(int fd) {
+ struct stat sb;
+ if (fstat(fd, &sb) == -1) {
+ PLOG(ERROR) << "fstat(" << fd << ") failed";
+ return false;
+ }
+
+ void* memPtr = mmap(nullptr, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (memPtr == MAP_FAILED) {
+ PLOG(ERROR) << "mmap(" << sb.st_size << ", R, PRIVATE, " << fd << ", 0) failed";
+ return false;
+ }
+
+ addr = static_cast<unsigned char*>(memPtr);
+ length = sb.st_size;
+ ranges_.clear();
+ ranges_.emplace_back(MappedRange{ memPtr, static_cast<size_t>(sb.st_size) });
+
+ return true;
+}
+
+bool MemMapping::MapBlockFile(const std::string& filename) {
+ auto block_map_data = BlockMapData::ParseBlockMapFile(filename);
+ if (!block_map_data) {
+ return false;
+ }
+
+ if (block_map_data.file_size() > std::numeric_limits<size_t>::max()) {
+ LOG(ERROR) << "File size is too large for mmap " << block_map_data.file_size();
+ return false;
+ }
+
+ // Reserve enough contiguous address space for the whole file.
+ uint32_t blksize = block_map_data.block_size();
+ uint64_t blocks = ((block_map_data.file_size() - 1) / blksize) + 1;
+ void* reserve = mmap(nullptr, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
+ if (reserve == MAP_FAILED) {
+ PLOG(ERROR) << "failed to reserve address space";
+ return false;
+ }
+
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(block_map_data.path().c_str(), O_RDONLY)));
+ if (fd == -1) {
+ PLOG(ERROR) << "failed to open block device " << block_map_data.path();
+ munmap(reserve, blocks * blksize);
+ return false;
+ }
+
+ ranges_.clear();
+
+ auto next = static_cast<unsigned char*>(reserve);
+ size_t remaining_size = blocks * blksize;
+ for (const auto& [start, end] : block_map_data.block_ranges()) {
+ size_t range_size = (end - start) * blksize;
+ void* range_start = mmap(next, range_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd,
+ static_cast<off_t>(start) * blksize);
+ if (range_start == MAP_FAILED) {
+ PLOG(ERROR) << "failed to map range " << start << ": " << end;
+ munmap(reserve, blocks * blksize);
+ return false;
+ }
+ ranges_.emplace_back(MappedRange{ range_start, range_size });
+
+ next += range_size;
+ remaining_size -= range_size;
+ }
+ if (remaining_size != 0) {
+ LOG(ERROR) << "Invalid ranges: remaining_size " << remaining_size;
+ munmap(reserve, blocks * blksize);
+ return false;
+ }
+
+ addr = static_cast<unsigned char*>(reserve);
+ length = block_map_data.file_size();
+
+ LOG(INFO) << "mmapped " << block_map_data.block_ranges().size() << " ranges";
+
+ return true;
+}
+
+bool MemMapping::MapFile(const std::string& fn) {
+ if (fn.empty()) {
+ LOG(ERROR) << "Empty filename";
+ return false;
+ }
+
+ if (fn[0] == '@') {
+ // Block map file "@/cache/recovery/block.map".
+ if (!MapBlockFile(fn.substr(1))) {
+ LOG(ERROR) << "Map of '" << fn << "' failed";
+ return false;
+ }
+ } else {
+ // This is a regular file.
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(fn.c_str(), O_RDONLY)));
+ if (fd == -1) {
+ PLOG(ERROR) << "Unable to open '" << fn << "'";
+ return false;
+ }
+
+ if (!MapFD(fd)) {
+ LOG(ERROR) << "Map of '" << fn << "' failed";
+ return false;
+ }
+ }
+ return true;
+}
+
+MemMapping::~MemMapping() {
+ for (const auto& range : ranges_) {
+ if (munmap(range.addr, range.length) == -1) {
+ PLOG(ERROR) << "Failed to munmap(" << range.addr << ", " << range.length << ")";
+ }
+ };
+ ranges_.clear();
+}
+
+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() {
+ // "shutdown" doesn't need a "reason" arg nor a comma.
+ return android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown");
+}
+
+std::vector<char*> StringVectorToNullTerminatedArray(const std::vector<std::string>& args) {
+ std::vector<char*> result(args.size());
+ std::transform(args.cbegin(), args.cend(), result.begin(),
+ [](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
+ result.push_back(nullptr);
+ return result;
+}
diff --git a/otautil/ThermalUtil.cpp b/otautil/thermalutil.cpp
similarity index 98%
rename from otautil/ThermalUtil.cpp
rename to otautil/thermalutil.cpp
index 5d9bd45..4660e05 100644
--- a/otautil/ThermalUtil.cpp
+++ b/otautil/thermalutil.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "otautil/ThermalUtil.h"
+#include "otautil/thermalutil.h"
#include <dirent.h>
#include <stdio.h>
diff --git a/private/install.h b/private/install.h
deleted file mode 100644
index ef64bd4..0000000
--- a/private/install.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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.
- */
-
-// Private headers exposed for testing purpose only.
-
-#pragma once
-
-#include <string>
-#include <vector>
-
-#include <ziparchive/zip_archive.h>
-
-// Extract the update binary from the open zip archive |zip| located at |package| to |binary_path|.
-// Store 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 update_binary_command(const std::string& package, ZipArchiveHandle zip,
- const std::string& binary_path, int retry_count, int status_fd,
- std::vector<std::string>* cmd);
diff --git a/recovery-persist.cpp b/recovery-persist.cpp
index dbce7ff..294017a 100644
--- a/recovery-persist.cpp
+++ b/recovery-persist.cpp
@@ -35,19 +35,22 @@
#include <string.h>
#include <unistd.h>
+#include <limits>
#include <string>
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <metricslogger/metrics_logger.h>
#include <private/android_logger.h> /* private pmsg functions */
-#include "rotate_logs.h"
+#include "otautil/logging.h"
+#include "otautil/parse_install_logs.h"
-static const char *LAST_LOG_FILE = "/data/misc/recovery/last_log";
-static const char *LAST_PMSG_FILE = "/sys/fs/pstore/pmsg-ramoops-0";
-static const char *LAST_KMSG_FILE = "/data/misc/recovery/last_kmsg";
-static const char *LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops-0";
-static const char *ALT_LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops";
+constexpr const char* LAST_LOG_FILE = "/data/misc/recovery/last_log";
+constexpr const char* LAST_PMSG_FILE = "/sys/fs/pstore/pmsg-ramoops-0";
+constexpr const char* LAST_KMSG_FILE = "/data/misc/recovery/last_kmsg";
+constexpr const char* LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops-0";
+constexpr const char* ALT_LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops";
// close a file, log an error if the error indicator is set
static void check_and_fclose(FILE *fp, const char *name) {
@@ -109,6 +112,20 @@
return android::base::WriteStringToFile(buffer, destination.c_str());
}
+// Parses the LAST_INSTALL file and reports the update metrics saved under recovery mode.
+static void report_metrics_from_last_install(const std::string& file_name) {
+ auto metrics = ParseLastInstall(file_name);
+ // TODO(xunchang) report the installation result.
+ for (const auto& [event, value] : metrics) {
+ if (value > std::numeric_limits<int>::max()) {
+ LOG(WARNING) << event << " (" << value << ") exceeds integer max.";
+ } else {
+ LOG(INFO) << "Uploading " << value << " to " << event;
+ android::metricslogger::LogHistogram(event, value);
+ }
+ }
+}
+
int main(int argc, char **argv) {
/* Is /cache a mount?, we have been delivered where we are not wanted */
@@ -138,14 +155,18 @@
}
if (has_cache) {
- /*
- * TBD: Future location to move content from
- * /cache/recovery to /data/misc/recovery/
- */
- /* if --force-persist flag, then transfer pmsg data anyways */
- if ((argc <= 1) || !argv[1] || strcmp(argv[1], "--force-persist")) {
- return 0;
- }
+ // Collects and reports the non-a/b update metrics from last_install; and removes the file
+ // to avoid duplicate report.
+ report_metrics_from_last_install(LAST_INSTALL_FILE_IN_CACHE);
+ if (access(LAST_INSTALL_FILE_IN_CACHE, F_OK) && unlink(LAST_INSTALL_FILE_IN_CACHE) == -1) {
+ PLOG(ERROR) << "Failed to unlink " << LAST_INSTALL_FILE_IN_CACHE;
+ }
+
+ // TBD: Future location to move content from /cache/recovery to /data/misc/recovery/
+ // if --force-persist flag, then transfer pmsg data anyways
+ if ((argc <= 1) || !argv[1] || strcmp(argv[1], "--force-persist")) {
+ return 0;
+ }
}
/* Is there something in pmsg? */
@@ -157,6 +178,15 @@
__android_log_pmsg_file_read(
LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", logsave, NULL);
+ // For those device without /cache, the last_install file has been copied to
+ // /data/misc/recovery from pmsg. Looks for the sideload history only.
+ if (!has_cache) {
+ report_metrics_from_last_install(LAST_INSTALL_FILE);
+ if (access(LAST_INSTALL_FILE, F_OK) && unlink(LAST_INSTALL_FILE) == -1) {
+ PLOG(ERROR) << "Failed to unlink " << LAST_INSTALL_FILE;
+ }
+ }
+
/* Is there a last console log too? */
if (rotated) {
if (!access(LAST_CONSOLE_FILE, R_OK)) {
diff --git a/recovery-refresh.cpp b/recovery-refresh.cpp
index 14565d3..d41755d 100644
--- a/recovery-refresh.cpp
+++ b/recovery-refresh.cpp
@@ -42,7 +42,7 @@
#include <private/android_logger.h> /* private pmsg functions */
-#include "rotate_logs.h"
+#include "otautil/logging.h"
int main(int argc, char **argv) {
static const char filter[] = "recovery/";
diff --git a/recovery.cpp b/recovery.cpp
index 07ec5cf..20e5a1b 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -14,28 +14,22 @@
* limitations under the License.
*/
+#include "recovery.h"
+
#include <ctype.h>
-#include <dirent.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 <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/klog.h>
-#include <sys/stat.h>
#include <sys/types.h>
-#include <sys/wait.h>
-#include <time.h>
#include <unistd.h>
-#include <algorithm>
-#include <chrono>
+#include <functional>
+#include <iterator>
#include <memory>
#include <string>
#include <vector>
@@ -46,98 +40,42 @@
#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/android_reboot.h>
#include <cutils/properties.h> /* for property_list */
-#include <health2/Health.h>
-#include <private/android_filesystem_config.h> /* for AID_SYSTEM */
-#include <private/android_logger.h> /* private pmsg functions */
-#include <selinux/android.h>
-#include <selinux/label.h>
-#include <selinux/selinux.h>
+#include <fs_mgr/roots.h>
+#include <healthhalutils/HealthHalUtils.h>
#include <ziparchive/zip_archive.h>
-#include "adb_install.h"
+#include "bootloader_message/bootloader_message.h"
#include "common.h"
-#include "device.h"
-#include "fuse_sdcard_provider.h"
-#include "fuse_sideload.h"
-#include "install.h"
-#include "minadbd/minadbd.h"
-#include "minui/minui.h"
-#include "otautil/DirUtil.h"
+#include "fsck_unshare_blocks.h"
+#include "install/adb_install.h"
+#include "install/fuse_sdcard_install.h"
+#include "install/install.h"
+#include "install/package.h"
+#include "install/wipe_data.h"
+#include "install/wipe_device.h"
#include "otautil/error_code.h"
-#include "roots.h"
-#include "rotate_logs.h"
-#include "screen_ui.h"
-#include "stub_ui.h"
-#include "ui.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"
-static const struct option OPTIONS[] = {
- { "update_package", required_argument, NULL, 'u' },
- { "retry_count", required_argument, NULL, 'n' },
- { "wipe_data", no_argument, NULL, 'w' },
- { "wipe_cache", no_argument, NULL, 'c' },
- { "show_text", no_argument, NULL, 't' },
- { "sideload", no_argument, NULL, 's' },
- { "sideload_auto_reboot", no_argument, NULL, 'a' },
- { "just_exit", no_argument, NULL, 'x' },
- { "locale", required_argument, NULL, 'l' },
- { "shutdown_after", no_argument, NULL, 'p' },
- { "reason", required_argument, NULL, 'r' },
- { "security", no_argument, NULL, 'e'},
- { "wipe_ab", no_argument, NULL, 0 },
- { "wipe_package_size", required_argument, NULL, 0 },
- { "prompt_and_wipe_data", no_argument, NULL, 0 },
- { NULL, 0, NULL, 0 },
-};
+static constexpr const char* COMMAND_FILE = "/cache/recovery/command";
+static constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg";
+static constexpr const char* LAST_LOG_FILE = "/cache/recovery/last_log";
+static constexpr const char* LOCALE_FILE = "/cache/recovery/last_locale";
-// More bootreasons can be found in "system/core/bootstat/bootstat.cpp".
-static const std::vector<std::string> bootreason_blacklist {
- "kernel_panic",
- "Panic",
-};
-
-static const char *CACHE_LOG_DIR = "/cache/recovery";
-static const char *COMMAND_FILE = "/cache/recovery/command";
-static const char *LOG_FILE = "/cache/recovery/log";
-static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install";
-static const char *LOCALE_FILE = "/cache/recovery/last_locale";
-static const char *CONVERT_FBE_DIR = "/tmp/convert_fbe";
-static const char *CONVERT_FBE_FILE = "/tmp/convert_fbe/convert_fbe";
-static const char *CACHE_ROOT = "/cache";
-static const char *DATA_ROOT = "/data";
-static const char* METADATA_ROOT = "/metadata";
-static const char *SDCARD_ROOT = "/sdcard";
-static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log";
-static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install";
-static const char *LAST_KMSG_FILE = "/cache/recovery/last_kmsg";
-static const char *LAST_LOG_FILE = "/cache/recovery/last_log";
-// We will try to apply the update package 5 times at most in case of an I/O error or
-// bspatch | imgpatch error.
-static const int RETRY_LIMIT = 4;
-static const int BATTERY_READ_TIMEOUT_IN_SEC = 10;
-// 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 const int BATTERY_OK_PERCENTAGE = 20;
-static const int BATTERY_WITH_CHARGER_OK_PERCENTAGE = 15;
-static constexpr const char* RECOVERY_WIPE = "/etc/recovery.wipe";
-static constexpr const char* DEFAULT_LOCALE = "en-US";
+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 std::string locale;
-static bool has_cache = false;
-
-RecoveryUI* ui = nullptr;
-bool modified_flash = false;
+static bool save_current_log = false;
std::string stage;
const char* reason = nullptr;
-struct selabel_handle* sehandle;
/*
* The recovery tool communicates with the main system through /cache files.
@@ -147,9 +85,10 @@
* The arguments which may be supplied in the recovery.command file:
* --update_package=path - verify install an OTA package file
* --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
+ * --prompt_and_wipe_data - prompt the user that data is corrupt, with their consent erase user
+ * data (and cache), then reboot
* --wipe_cache - wipe cache (but not user data), then reboot
+ * --show_text - show the recovery text menu, used by some bootloader (e.g. http://b/36872519).
* --set_encrypted_filesystem=on|off - enables / diasables encrypted fs
* --just_exit - do nothing; exit and reboot
*
@@ -175,318 +114,24 @@
* 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
* -- 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
*/
-// Open a given path, mounting partitions as necessary.
-FILE* fopen_path(const char* path, const char* mode) {
- if (ensure_path_mounted(path) != 0) {
- LOG(ERROR) << "Can't mount " << path;
- return nullptr;
- }
-
- // When writing, try to create the containing directory, if necessary. Use generous permissions,
- // the system (init.rc) will reset them.
- if (strchr("wa", mode[0])) {
- mkdir_recursively(path, 0777, true, sehandle);
- }
- return fopen(path, mode);
-}
-
-// close a file, log an error if the error indicator is set
-static void check_and_fclose(FILE *fp, const char *name) {
- fflush(fp);
- if (fsync(fileno(fp)) == -1) {
- PLOG(ERROR) << "Failed to fsync " << name;
- }
- if (ferror(fp)) {
- PLOG(ERROR) << "Error in " << name;
- }
- fclose(fp);
-}
-
bool is_ro_debuggable() {
return android::base::GetBoolProperty("ro.debuggable", false);
}
-bool reboot(const std::string& command) {
- std::string cmd = command;
- if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
- cmd += ",quiescent";
- }
- return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd);
-}
-
-static void redirect_stdio(const char* filename) {
- int pipefd[2];
- if (pipe(pipefd) == -1) {
- PLOG(ERROR) << "pipe failed";
-
- // Fall back to traditional logging mode without timestamps.
- // If these fail, there's not really anywhere to complain...
- freopen(filename, "a", stdout); setbuf(stdout, NULL);
- freopen(filename, "a", stderr); setbuf(stderr, NULL);
-
- return;
- }
-
- pid_t pid = fork();
- if (pid == -1) {
- PLOG(ERROR) << "fork failed";
-
- // Fall back to traditional logging mode without timestamps.
- // If these fail, there's not really anywhere to complain...
- freopen(filename, "a", stdout); setbuf(stdout, NULL);
- freopen(filename, "a", stderr); setbuf(stderr, NULL);
-
- return;
- }
-
- if (pid == 0) {
- /// Close the unused write end.
- close(pipefd[1]);
-
- auto start = std::chrono::steady_clock::now();
-
- // Child logger to actually write to the log file.
- FILE* log_fp = fopen(filename, "ae");
- if (log_fp == nullptr) {
- PLOG(ERROR) << "fopen \"" << filename << "\" failed";
- close(pipefd[0]);
- _exit(EXIT_FAILURE);
- }
-
- FILE* pipe_fp = fdopen(pipefd[0], "r");
- if (pipe_fp == nullptr) {
- PLOG(ERROR) << "fdopen failed";
- check_and_fclose(log_fp, filename);
- close(pipefd[0]);
- _exit(EXIT_FAILURE);
- }
-
- char* line = nullptr;
- size_t len = 0;
- while (getline(&line, &len, pipe_fp) != -1) {
- auto now = std::chrono::steady_clock::now();
- double duration = std::chrono::duration_cast<std::chrono::duration<double>>(
- now - start).count();
- if (line[0] == '\n') {
- fprintf(log_fp, "[%12.6lf]\n", duration);
- } else {
- fprintf(log_fp, "[%12.6lf] %s", duration, line);
- }
- fflush(log_fp);
- }
-
- PLOG(ERROR) << "getline failed";
-
- free(line);
- check_and_fclose(log_fp, filename);
- close(pipefd[0]);
- _exit(EXIT_FAILURE);
- } else {
- // Redirect stdout/stderr to the logger process.
- // Close the unused read end.
- close(pipefd[0]);
-
- setbuf(stdout, nullptr);
- setbuf(stderr, nullptr);
-
- if (dup2(pipefd[1], STDOUT_FILENO) == -1) {
- PLOG(ERROR) << "dup2 stdout failed";
- }
- if (dup2(pipefd[1], STDERR_FILENO) == -1) {
- PLOG(ERROR) << "dup2 stderr failed";
- }
-
- close(pipefd[1]);
- }
-}
-
-// 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) {
- CHECK_GT(argc, 0);
-
- bootloader_message boot = {};
- std::string err;
- if (!read_bootloader_message(&boot, &err)) {
- LOG(ERROR) << err;
- // If fails, leave a zeroed bootloader_message.
- boot = {};
- }
- stage = std::string(boot.stage);
-
- if (boot.command[0] != 0) {
- std::string boot_command = std::string(boot.command, sizeof(boot.command));
- LOG(INFO) << "Boot command: " << boot_command;
- }
-
- if (boot.status[0] != 0) {
- std::string boot_status = std::string(boot.status, sizeof(boot.status));
- LOG(INFO) << "Boot status: " << boot_status;
- }
-
- std::vector<std::string> args(argv, argv + argc);
-
- // --- if arguments weren't supplied, look in the bootloader control block
- if (args.size() == 1) {
- boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination
- std::string boot_recovery(boot.recovery);
- std::vector<std::string> tokens = android::base::Split(boot_recovery, "\n");
- if (!tokens.empty() && tokens[0] == "recovery") {
- for (auto it = tokens.begin() + 1; it != tokens.end(); it++) {
- // Skip empty and '\0'-filled tokens.
- if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));
- }
- LOG(INFO) << "Got " << args.size() << " arguments from boot message";
- } else if (boot.recovery[0] != 0) {
- LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\"";
- }
- }
-
- // --- if that doesn't work, try the command file (if we have /cache).
- if (args.size() == 1 && has_cache) {
- std::string content;
- if (ensure_path_mounted(COMMAND_FILE) == 0 &&
- android::base::ReadFileToString(COMMAND_FILE, &content)) {
- std::vector<std::string> tokens = android::base::Split(content, "\n");
- // All the arguments in COMMAND_FILE are needed (unlike the BCB message,
- // COMMAND_FILE doesn't use filename as the first argument).
- for (auto it = tokens.begin(); it != tokens.end(); it++) {
- // Skip empty and '\0'-filled tokens.
- if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));
- }
- LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE;
- }
- }
-
- // 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.
- std::vector<std::string> options(args.cbegin() + 1, args.cend());
- if (!update_bootloader_message(options, &err)) {
- LOG(ERROR) << "Failed to set BCB message: " << err;
- }
-
- return args;
-}
-
-// Set the BCB to reboot back into recovery (it won't resume the install from
-// sdcard though).
-static void set_sdcard_update_bootloader_message() {
- std::vector<std::string> options;
- std::string err;
- if (!update_bootloader_message(options, &err)) {
- LOG(ERROR) << "Failed to set BCB message: " << err;
- }
-}
-
-// Read from kernel log into buffer and write out to file.
-static void save_kernel_log(const char* destination) {
- int klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0);
- if (klog_buf_len <= 0) {
- PLOG(ERROR) << "Error getting klog size";
- return;
- }
-
- std::string buffer(klog_buf_len, 0);
- int n = klogctl(KLOG_READ_ALL, &buffer[0], klog_buf_len);
- if (n == -1) {
- PLOG(ERROR) << "Error in reading klog";
- return;
- }
- buffer.resize(n);
- android::base::WriteStringToFile(buffer, destination);
-}
-
-// write content to the current pmsg session.
-static ssize_t __pmsg_write(const char *filename, const char *buf, size_t len) {
- return __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO,
- filename, buf, len);
-}
-
-static void copy_log_file_to_pmsg(const char* source, const char* destination) {
- std::string content;
- android::base::ReadFileToString(source, &content);
- __pmsg_write(destination, content.c_str(), content.length());
-}
-
-// How much of the temp log we have copied to the copy in cache.
-static off_t tmplog_offset = 0;
-
-static void copy_log_file(const char* source, const char* destination, bool append) {
- FILE* dest_fp = fopen_path(destination, append ? "ae" : "we");
- if (dest_fp == nullptr) {
- PLOG(ERROR) << "Can't open " << destination;
- } else {
- FILE* source_fp = fopen(source, "re");
- if (source_fp != nullptr) {
- if (append) {
- fseeko(source_fp, tmplog_offset, SEEK_SET); // Since last write
- }
- char buf[4096];
- size_t bytes;
- while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
- fwrite(buf, 1, bytes, dest_fp);
- }
- if (append) {
- tmplog_offset = ftello(source_fp);
- }
- check_and_fclose(source_fp, source);
- }
- check_and_fclose(dest_fp, destination);
- }