[automerger skipped] DO NOT MERGE: Initialize the ZipArchive to zero before parsing skipped: e830a51351 skipped: 244aba4412 skipped: 9216bccd16 skipped: 98acdb9fce skipped: bc42a6b0eb am: 35332d7c62 am: 6b13f89f15
am: 274945cb5b -s ours
Change-Id: Ic5569d0abf7674ca5bee1b3df01170e69b6a0c1d
diff --git a/.clang-format b/.clang-format
index 5322788..0e0f4d1 100644
--- a/.clang-format
+++ b/.clang-format
@@ -10,6 +10,5 @@
PointerAlignment: Left
TabWidth: 2
UseTab: Never
-PenaltyExcessCharacter: 32
Cpp11BracedListStyle: false
diff --git a/Android.bp b/Android.bp
index 49438ad..f8c6a4b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,4 +1,8 @@
subdirs = [
+ "applypatch",
"bootloader_message",
+ "edify",
+ "otafault",
"otautil",
+ "uncrypt",
]
diff --git a/Android.mk b/Android.mk
index 776e6ea..7e0ad12 100644
--- a/Android.mk
+++ b/Android.mk
@@ -56,6 +56,7 @@
LOCAL_MODULE := librecovery
LOCAL_STATIC_LIBRARIES := \
libminui \
+ libotautil \
libvintf_recovery \
libcrypto_utils \
libcrypto \
@@ -88,12 +89,12 @@
ifeq ($(TARGET_USERIMAGES_USE_F2FS),true)
ifeq ($(HOST_OS),linux)
-LOCAL_REQUIRED_MODULES += mkfs.f2fs
+LOCAL_REQUIRED_MODULES += sload.f2fs mkfs.f2fs
endif
endif
LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
-LOCAL_CFLAGS += -Wno-unused-parameter -Werror
+LOCAL_CFLAGS += -Wall -Werror
ifneq ($(TARGET_RECOVERY_UI_MARGIN_HEIGHT),)
LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_HEIGHT=$(TARGET_RECOVERY_UI_MARGIN_HEIGHT)
@@ -146,10 +147,22 @@
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 \
- libbatterymonitor \
libbootloader_message \
libfs_mgr \
libext4_utils \
@@ -157,8 +170,8 @@
libziparchive \
libotautil \
libmounts \
- libz \
libminadbd \
+ libasyncio \
libfusesideload \
libminui \
libpng \
@@ -166,14 +179,14 @@
libcrypto \
libvintf_recovery \
libvintf \
+ libhidl-gen-utils \
libtinyxml2 \
libbase \
- libcutils \
libutils \
+ libcutils \
liblog \
libselinux \
- libm \
- libc
+ libz
LOCAL_HAL_STATIC_LIBRARIES := libhealthd
@@ -203,7 +216,7 @@
rotate_logs.cpp
LOCAL_MODULE := recovery-persist
LOCAL_SHARED_LIBRARIES := liblog libbase
-LOCAL_CFLAGS := -Werror
+LOCAL_CFLAGS := -Wall -Werror
LOCAL_INIT_RC := recovery-persist.rc
include $(BUILD_EXECUTABLE)
@@ -215,7 +228,7 @@
rotate_logs.cpp
LOCAL_MODULE := recovery-refresh
LOCAL_SHARED_LIBRARIES := liblog libbase
-LOCAL_CFLAGS := -Werror
+LOCAL_CFLAGS := -Wall -Werror
LOCAL_INIT_RC := recovery-refresh.rc
include $(BUILD_EXECUTABLE)
@@ -227,16 +240,18 @@
asn1_decoder.cpp \
verifier.cpp
LOCAL_STATIC_LIBRARIES := \
+ libotautil \
libcrypto_utils \
libcrypto \
libbase
-LOCAL_CFLAGS := -Werror
+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
@@ -248,6 +263,7 @@
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
@@ -255,14 +271,10 @@
include $(BUILD_STATIC_LIBRARY)
include \
- $(LOCAL_PATH)/applypatch/Android.mk \
$(LOCAL_PATH)/boot_control/Android.mk \
- $(LOCAL_PATH)/edify/Android.mk \
$(LOCAL_PATH)/minadbd/Android.mk \
$(LOCAL_PATH)/minui/Android.mk \
- $(LOCAL_PATH)/otafault/Android.mk \
$(LOCAL_PATH)/tests/Android.mk \
$(LOCAL_PATH)/tools/Android.mk \
- $(LOCAL_PATH)/uncrypt/Android.mk \
$(LOCAL_PATH)/updater/Android.mk \
$(LOCAL_PATH)/update_verifier/Android.mk \
diff --git a/OWNERS b/OWNERS
index 09754c6..b3f11dc 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,3 @@
-enh+aosp-gerrit@google.com
+enh@google.com
tbao@google.com
xunchang@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..b5f5f03
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,6 @@
+[Builtin Hooks]
+clang_format = true
+
+[Builtin Hooks Options]
+# Handle native codes only.
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
diff --git a/README.md b/README.md
index 8e20b5a..0aeadae 100644
--- a/README.md
+++ b/README.md
@@ -47,3 +47,94 @@
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
+--------------------------
+
+When running recovery image from debuggable builds (i.e. `-eng` or `-userdebug` build variants, or
+`ro.debuggable=1` in `/prop.default`), `adbd` service is enabled and started by default, which
+allows `adb` communication. A device should be listed under `adb devices`, either in `recovery` or
+`sideload` state.
+
+ $ adb devices
+ 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).
+
+## Troubleshooting
+
+### `adb devices` doesn't show the device.
+
+ $ adb devices
+ List of devices attached
+
+ * 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
+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
+start log as below.
+
+ [ 18.961986] c1 1 init: starting service 'adbd'...
+
+ * Ensure USB gadget has been enabled.
+
+If `adbd` service has been started but device not shown under `adb devices`, use `lsusb(8)` (on
+host) to check if the device is visible to the host.
+
+`bootable/recovery/etc/init.rc` disables Android USB gadget (via sysfs) as part of the `fs` action
+trigger, and will only re-enable it in debuggable builds (the `on property` rule will always run
+_after_ `on fs`).
+
+ on fs
+ write /sys/class/android_usb/android0/enable 0
+
+ # Always start adbd on userdebug and eng builds
+ on property:ro.debuggable=1
+ write /sys/class/android_usb/android0/enable 1
+ start adbd
+
+If device is using [configfs](https://www.kernel.org/doc/Documentation/usb/gadget_configfs.txt),
+check if configfs has been properly set up in init rc scripts. See the [example
+configuration](https://android.googlesource.com/device/google/wahoo/+/master/init.recovery.hardware.rc)
+for Pixel 2 devices. Note that the flag set via sysfs (i.e. the one above) is no-op when using
+configfs.
+
+### `adb devices` shows the device, but in `unauthorized` state.
+
+ $ adb devices
+ List of devices attached
+ 1234567890abcdef unauthorized
+
+recovery image doesn't honor the USB debugging toggle and the authorizations added under normal boot
+(because such authorization data stays in /data, which recovery doesn't mount), nor does it support
+authorizing a host device under recovery. We can use one of the following options instead.
+
+ * **Option 1 (Recommended):** Authorize a host device with adb vendor keys.
+
+For debuggable builds, an RSA keypair can be used to authorize a host device that has the private
+key. The public key, defined via `PRODUCT_ADB_KEYS`, will be copied to `/adb_keys`. When starting
+the host-side `adbd`, make sure the filename (or the directory) of the matching private key has been
+added to `$ADB_VENDOR_KEYS`.
+
+ $ export ADB_VENDOR_KEYS=/path/to/adb/private/key
+ $ adb kill-server
+ $ adb devices
+
+`-user` builds filter out `PRODUCT_ADB_KEYS`, so no `/adb_keys` will be included there.
+
+Note that this mechanism applies to both of normal boot and recovery modes.
+
+ * **Option 2:** Allow `adbd` to connect without authentication.
+ * `adbd` is compiled with `ALLOW_ADBD_NO_AUTH` (only on debuggable builds).
+ * `ro.adb.secure` has a value of `0`.
+
+Both of the two conditions need to be satisfied. Although `ro.adb.secure` is a runtime property, its
+value is set at build time (written into `/prop.default`). It defaults to `1` on `-user` builds, and
+`0` for other build variants. The value is overridable via `PRODUCT_DEFAULT_PROPERTY_OVERRIDES`.
diff --git a/applypatch/Android.bp b/applypatch/Android.bp
new file mode 100644
index 0000000..cb0b367
--- /dev/null
+++ b/applypatch/Android.bp
@@ -0,0 +1,206 @@
+// 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_defaults {
+ name: "applypatch_defaults",
+
+ cflags: [
+ "-D_FILE_OFFSET_BITS=64",
+ "-DZLIB_CONST",
+ "-Wall",
+ "-Werror",
+ ],
+
+ local_include_dirs: [
+ "include",
+ ],
+}
+
+cc_library_static {
+ name: "libapplypatch",
+
+ host_supported: true,
+
+ defaults: [
+ "applypatch_defaults",
+ ],
+
+ srcs: [
+ "applypatch.cpp",
+ "bspatch.cpp",
+ "freecache.cpp",
+ "imgpatch.cpp",
+ ],
+
+ export_include_dirs: [
+ "include",
+ ],
+
+ static_libs: [
+ "libbase",
+ "libbspatch",
+ "libbz",
+ "libcrypto",
+ "libedify",
+ "libotafault",
+ "libotautil",
+ "libz",
+ ],
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+}
+
+cc_library_static {
+ name: "libapplypatch_modes",
+
+ defaults: [
+ "applypatch_defaults",
+ ],
+
+ srcs: [
+ "applypatch_modes.cpp",
+ ],
+
+ static_libs: [
+ "libapplypatch",
+ "libbase",
+ "libcrypto",
+ "libedify",
+ "libotautil",
+ ],
+}
+
+cc_binary {
+ name: "applypatch",
+
+ defaults: [
+ "applypatch_defaults",
+ ],
+
+ srcs: [
+ "applypatch_main.cpp",
+ ],
+
+ static_libs: [
+ "libapplypatch_modes",
+ "libapplypatch",
+ "libedify",
+ "libotafault",
+ "libotautil",
+ "libbspatch",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libbrotli",
+ "libbz",
+ "libcrypto",
+ "liblog",
+ "libz",
+ "libziparchive",
+ ],
+}
+
+cc_library_static {
+ name: "libimgdiff",
+
+ host_supported: true,
+
+ defaults: [
+ "applypatch_defaults",
+ ],
+
+ srcs: [
+ "imgdiff.cpp",
+ ],
+
+ export_include_dirs: [
+ "include",
+ ],
+
+ static_libs: [
+ "libbase",
+ "libbsdiff",
+ "libdivsufsort",
+ "libdivsufsort64",
+ "liblog",
+ "libotautil",
+ "libutils",
+ "libz",
+ "libziparchive",
+ ],
+}
+
+cc_binary_host {
+ name: "imgdiff",
+
+ srcs: [
+ "imgdiff_main.cpp",
+ ],
+
+ defaults: [
+ "applypatch_defaults",
+ ],
+
+ static_libs: [
+ "libimgdiff",
+ "libotautil",
+ "libbsdiff",
+ "libdivsufsort",
+ "libdivsufsort64",
+ "libziparchive",
+ "libbase",
+ "libutils",
+ "liblog",
+ "libbrotli",
+ "libbz",
+ "libz",
+ ],
+}
+
+cc_library_static {
+ name: "libimgpatch",
+
+ // The host module is for recovery_host_test (Linux only).
+ host_supported: true,
+
+ defaults: [
+ "applypatch_defaults",
+ ],
+
+ srcs: [
+ "bspatch.cpp",
+ "imgpatch.cpp",
+ ],
+
+ static_libs: [
+ "libbase",
+ "libbspatch",
+ "libbz",
+ "libcrypto",
+ "libedify",
+ "libotautil",
+ "libz",
+ ],
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+}
diff --git a/applypatch/Android.mk b/applypatch/Android.mk
deleted file mode 100644
index a7412d2..0000000
--- a/applypatch/Android.mk
+++ /dev/null
@@ -1,182 +0,0 @@
-# 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.
-
-LOCAL_PATH := $(call my-dir)
-
-# libapplypatch (static library)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- applypatch.cpp \
- bspatch.cpp \
- freecache.cpp \
- imgpatch.cpp
-LOCAL_MODULE := libapplypatch
-LOCAL_MODULE_TAGS := eng
-LOCAL_C_INCLUDES := \
- $(LOCAL_PATH)/include \
- bootable/recovery
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_STATIC_LIBRARIES := \
- libotafault \
- libbase \
- libcrypto \
- libbspatch \
- libbz \
- libz
-LOCAL_CFLAGS := \
- -DZLIB_CONST \
- -Werror
-include $(BUILD_STATIC_LIBRARY)
-
-# libimgpatch (static library)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- bspatch.cpp \
- imgpatch.cpp
-LOCAL_MODULE := libimgpatch
-LOCAL_C_INCLUDES := \
- $(LOCAL_PATH)/include \
- bootable/recovery
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_STATIC_LIBRARIES := \
- libcrypto \
- libbspatch \
- libbase \
- libbz \
- libz
-LOCAL_CFLAGS := \
- -DZLIB_CONST \
- -Werror
-include $(BUILD_STATIC_LIBRARY)
-
-# libimgpatch (host static library)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- bspatch.cpp \
- imgpatch.cpp
-LOCAL_MODULE := libimgpatch
-LOCAL_MODULE_HOST_OS := linux
-LOCAL_C_INCLUDES := \
- $(LOCAL_PATH)/include \
- bootable/recovery
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_STATIC_LIBRARIES := \
- libcrypto \
- libbspatch \
- libbase \
- libbz \
- libz
-LOCAL_CFLAGS := \
- -DZLIB_CONST \
- -Werror
-include $(BUILD_HOST_STATIC_LIBRARY)
-
-# libapplypatch_modes (static library)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- applypatch_modes.cpp
-LOCAL_MODULE := libapplypatch_modes
-LOCAL_C_INCLUDES := bootable/recovery
-LOCAL_STATIC_LIBRARIES := \
- libapplypatch \
- libbase \
- libedify \
- libcrypto
-LOCAL_CFLAGS := -Werror
-include $(BUILD_STATIC_LIBRARY)
-
-# applypatch (target executable)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := applypatch_main.cpp
-LOCAL_MODULE := applypatch
-LOCAL_C_INCLUDES := bootable/recovery
-LOCAL_STATIC_LIBRARIES := \
- libapplypatch_modes \
- libapplypatch \
- libbase \
- libedify \
- libotafault \
- libcrypto \
- libbspatch \
- libbz
-LOCAL_SHARED_LIBRARIES := \
- libbase \
- libz \
- libcutils
-LOCAL_CFLAGS := -Werror
-include $(BUILD_EXECUTABLE)
-
-libimgdiff_src_files := imgdiff.cpp
-
-# libbsdiff is compiled with -D_FILE_OFFSET_BITS=64.
-libimgdiff_cflags := \
- -Werror \
- -D_FILE_OFFSET_BITS=64
-
-libimgdiff_static_libraries := \
- libbsdiff \
- libdivsufsort \
- libdivsufsort64 \
- libziparchive \
- libutils \
- liblog \
- libbase \
- libz
-
-# libimgdiff (static library)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- $(libimgdiff_src_files)
-LOCAL_MODULE := libimgdiff
-LOCAL_CFLAGS := \
- $(libimgdiff_cflags)
-LOCAL_STATIC_LIBRARIES := \
- $(libimgdiff_static_libraries)
-LOCAL_C_INCLUDES := \
- $(LOCAL_PATH)/include
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-include $(BUILD_STATIC_LIBRARY)
-
-# libimgdiff (host static library)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- $(libimgdiff_src_files)
-LOCAL_MODULE := libimgdiff
-LOCAL_CFLAGS := \
- $(libimgdiff_cflags)
-LOCAL_STATIC_LIBRARIES := \
- $(libimgdiff_static_libraries)
-LOCAL_C_INCLUDES := \
- $(LOCAL_PATH)/include
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-include $(BUILD_HOST_STATIC_LIBRARY)
-
-# imgdiff (host static executable)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := imgdiff_main.cpp
-LOCAL_MODULE := imgdiff
-LOCAL_CFLAGS := -Werror
-LOCAL_STATIC_LIBRARIES := \
- libimgdiff \
- $(libimgdiff_static_libraries) \
- libbz
-include $(BUILD_HOST_EXECUTABLE)
diff --git a/applypatch/Makefile b/applypatch/Makefile
deleted file mode 100644
index fb49843..0000000
--- a/applypatch/Makefile
+++ /dev/null
@@ -1,33 +0,0 @@
-# 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.
-
-# This file is for building imgdiff in Chrome OS.
-
-CPPFLAGS += -iquote.. -Iinclude
-CXXFLAGS += -std=c++11 -O3 -Wall -Werror
-LDLIBS += -lbz2 -lz
-
-.PHONY: all clean
-
-all: imgdiff libimgpatch.a
-
-clean:
- rm -f *.o imgdiff libimgpatch.a
-
-imgdiff: imgdiff.o bsdiff.o utils.o
- $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDLIBS) -o $@ $^
-
-libimgpatch.a utils.o: CXXFLAGS += -fPIC
-libimgpatch.a: imgpatch.o bspatch.o utils.o
- ${AR} rcs $@ $^
diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp
index 51bf393..7645a40 100644
--- a/applypatch/applypatch.cpp
+++ b/applypatch/applypatch.cpp
@@ -39,8 +39,9 @@
#include <openssl/sha.h>
#include "edify/expr.h"
-#include "ota_io.h"
-#include "print_sha1.h"
+#include "otafault/ota_io.h"
+#include "otautil/cache_location.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);
@@ -56,12 +57,13 @@
return LoadPartitionContents(filename, file);
}
- if (stat(filename, &file->st) == -1) {
+ struct stat sb;
+ if (stat(filename, &sb) == -1) {
printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
return -1;
}
- std::vector<unsigned char> data(file->st.st_size);
+ 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));
@@ -178,10 +180,6 @@
buffer.resize(buffer_size);
file->data = std::move(buffer);
- // Fake some stat() info.
- file->st.st_mode = 0644;
- file->st.st_uid = 0;
- file->st.st_gid = 0;
return 0;
}
@@ -210,15 +208,6 @@
return -1;
}
- if (chmod(filename, file->st.st_mode) != 0) {
- printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno));
- return -1;
- }
- if (chown(filename, file->st.st_uid, file->st.st_gid) != 0) {
- printf("chown of \"%s\" failed: %s\n", filename, strerror(errno));
- return -1;
- }
-
return 0;
}
@@ -411,12 +400,10 @@
(!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(CACHE_TEMP_SOURCE, &file) != 0) {
+ // 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;
}
@@ -462,9 +449,8 @@
if (MakeFreeSpaceOnCache(bytes) < 0) {
printf("unable to make %zu bytes available on /cache\n", bytes);
return 1;
- } else {
- return 0;
}
+ return 0;
}
// This function applies binary patches to EMMC target files in a way that is safe (the original
@@ -489,7 +475,7 @@
// 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 __unused,
+ 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);
@@ -539,7 +525,7 @@
printf("source file is bad; trying copy\n");
FileContents copy_file;
- if (LoadFileContents(CACHE_TEMP_SOURCE, ©_file) < 0) {
+ if (LoadFileContents(CacheLocation::location().cache_temp_source().c_str(), ©_file) < 0) {
printf("failed to read copy file\n");
return 1;
}
@@ -634,7 +620,7 @@
printf("not enough free space on /cache\n");
return 1;
}
- if (SaveFileContents(CACHE_TEMP_SOURCE, &source_file) < 0) {
+ if (SaveFileContents(CacheLocation::location().cache_temp_source().c_str(), &source_file) < 0) {
printf("failed to back up source file\n");
return 1;
}
@@ -651,11 +637,11 @@
int result;
if (use_bsdiff) {
- result = ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), patch.get(), 0,
- sink, &ctx);
+ result =
+ ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), *patch, 0, sink, &ctx);
} else {
- result = ApplyImagePatch(source_file.data.data(), source_file.data.size(), patch.get(), sink,
- &ctx, bonus_data);
+ result = ApplyImagePatch(source_file.data.data(), source_file.data.size(), *patch, sink, &ctx,
+ bonus_data);
}
if (result != 0) {
@@ -680,7 +666,7 @@
}
// Delete the backup copy of the source.
- unlink(CACHE_TEMP_SOURCE);
+ unlink(CacheLocation::location().cache_temp_source().c_str());
// Success!
return 0;
diff --git a/applypatch/bspatch.cpp b/applypatch/bspatch.cpp
index 65ee614..912dbbd 100644
--- a/applypatch/bspatch.cpp
+++ b/applypatch/bspatch.cpp
@@ -26,11 +26,12 @@
#include <string>
#include <android-base/logging.h>
-#include <bspatch.h>
+#include <bsdiff/bspatch.h>
#include <openssl/sha.h>
#include "applypatch/applypatch.h"
-#include "print_sha1.h"
+#include "edify/expr.h"
+#include "otautil/print_sha1.h"
void ShowBSDiffLicense() {
puts("The bsdiff library used herein is:\n"
@@ -64,7 +65,7 @@
);
}
-int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value* patch,
+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);
@@ -72,23 +73,21 @@
return len;
};
- CHECK(patch != nullptr);
- CHECK_LE(patch_offset, patch->data.size());
+ 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);
+ reinterpret_cast<const uint8_t*>(&patch.data[patch_offset]),
+ patch.data.size() - patch_offset, sha_sink);
if (result != 0) {
LOG(ERROR) << "bspatch failed, result: " << result;
// print SHA1 of the patch in the case of a data error.
if (result == 2) {
uint8_t digest[SHA_DIGEST_LENGTH];
- SHA1(reinterpret_cast<const uint8_t*>(patch->data.data() + patch_offset),
- patch->data.size() - patch_offset, digest);
+ SHA1(reinterpret_cast<const uint8_t*>(patch.data.data() + patch_offset),
+ patch.data.size() - patch_offset, digest);
std::string patch_sha1 = print_sha1(digest);
- LOG(ERROR) << "Patch may be corrupted, offset: " << patch_offset << ", SHA1: "
- << patch_sha1;
+ LOG(ERROR) << "Patch may be corrupted, offset: " << patch_offset << ", SHA1: " << patch_sha1;
}
}
return result;
-}
\ No newline at end of file
+}
diff --git a/applypatch/freecache.cpp b/applypatch/freecache.cpp
index 331cae2..ea364d8 100644
--- a/applypatch/freecache.cpp
+++ b/applypatch/freecache.cpp
@@ -33,6 +33,7 @@
#include <android-base/stringprintf.h>
#include "applypatch/applypatch.h"
+#include "otautil/cache_location.h"
static int EliminateOpenFiles(std::set<std::string>* files) {
std::unique_ptr<DIR, decltype(&closedir)> d(opendir("/proc"), closedir);
@@ -90,10 +91,9 @@
while ((de = readdir(d.get())) != 0) {
std::string path = std::string(dirs[i]) + "/" + 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 == CACHE_TEMP_SOURCE) {
+ // 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;
}
@@ -112,6 +112,12 @@
}
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);
diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp
index fc24064..674cc2b 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -15,53 +15,44 @@
*/
/*
- * This program constructs binary patches for images -- such as boot.img
- * and recovery.img -- that consist primarily of large chunks of gzipped
- * data interspersed with uncompressed data. Doing a naive bsdiff of
- * these files is not useful because small changes in the data lead to
- * large changes in the compressed bitstream; bsdiff patches of gzipped
- * data are typically as large as the data itself.
+ * This program constructs binary patches for images -- such as boot.img and recovery.img -- that
+ * consist primarily of large chunks of gzipped data interspersed with uncompressed data. Doing a
+ * naive bsdiff of these files is not useful because small changes in the data lead to large
+ * changes in the compressed bitstream; bsdiff patches of gzipped data are typically as large as
+ * the data itself.
*
- * To patch these usefully, we break the source and target images up into
- * chunks of two types: "normal" and "gzip". Normal chunks are simply
- * patched using a plain bsdiff. Gzip chunks are first expanded, then a
- * bsdiff is applied to the uncompressed data, then the patched data is
- * gzipped using the same encoder parameters. Patched chunks are
- * concatenated together to create the output file; the output image
- * should be *exactly* the same series of bytes as the target image used
- * originally to generate the patch.
+ * To patch these usefully, we break the source and target images up into chunks of two types:
+ * "normal" and "gzip". Normal chunks are simply patched using a plain bsdiff. Gzip chunks are
+ * first expanded, then a bsdiff is applied to the uncompressed data, then the patched data is
+ * gzipped using the same encoder parameters. Patched chunks are concatenated together to create
+ * the output file; the output image should be *exactly* the same series of bytes as the target
+ * image used originally to generate the patch.
*
- * To work well with this tool, the gzipped sections of the target
- * image must have been generated using the same deflate encoder that
- * is available in applypatch, namely, the one in the zlib library.
- * In practice this means that images should be compressed using the
- * "minigzip" tool included in the zlib distribution, not the GNU gzip
- * program.
+ * To work well with this tool, the gzipped sections of the target image must have been generated
+ * using the same deflate encoder that is available in applypatch, namely, the one in the zlib
+ * library. In practice this means that images should be compressed using the "minigzip" tool
+ * included in the zlib distribution, not the GNU gzip program.
*
- * An "imgdiff" patch consists of a header describing the chunk structure
- * of the file and any encoding parameters needed for the gzipped
- * chunks, followed by N bsdiff patches, one per chunk.
+ * An "imgdiff" patch consists of a header describing the chunk structure of the file and any
+ * encoding parameters needed for the gzipped chunks, followed by N bsdiff patches, one per chunk.
*
- * For a diff to be generated, the source and target images must have the
- * same "chunk" structure: that is, the same number of gzipped and normal
- * chunks in the same order. Android boot and recovery images currently
- * consist of five chunks: a small normal header, a gzipped kernel, a
- * small normal section, a gzipped ramdisk, and finally a small normal
- * footer.
+ * For a diff to be generated, the source and target must be in well-formed zip archive format;
+ * or they are image files with the same "chunk" structure: that is, the same number of gzipped and
+ * normal chunks in the same order. Android boot and recovery images currently consist of five
+ * chunks: a small normal header, a gzipped kernel, a small normal section, a gzipped ramdisk, and
+ * finally a small normal footer.
*
- * Caveats: we locate gzipped sections within the source and target
- * images by searching for the byte sequence 1f8b0800: 1f8b is the gzip
- * magic number; 08 specifies the "deflate" encoding [the only encoding
- * supported by the gzip standard]; and 00 is the flags byte. We do not
- * currently support any extra header fields (which would be indicated by
- * a nonzero flags byte). We also don't handle the case when that byte
- * sequence appears spuriously in the file. (Note that it would have to
- * occur spuriously within a normal chunk to be a problem.)
+ * Caveats: we locate gzipped sections within the source and target images by searching for the
+ * byte sequence 1f8b0800: 1f8b is the gzip magic number; 08 specifies the "deflate" encoding
+ * [the only encoding supported by the gzip standard]; and 00 is the flags byte. We do not
+ * currently support any extra header fields (which would be indicated by a nonzero flags byte).
+ * We also don't handle the case when that byte sequence appears spuriously in the file. (Note
+ * that it would have to occur spuriously within a normal chunk to be a problem.)
*
*
* The imgdiff patch header looks like this:
*
- * "IMGDIFF1" (8) [magic number and version]
+ * "IMGDIFF2" (8) [magic number and version]
* chunk count (4)
* for each chunk:
* chunk type (4) [CHUNK_{NORMAL, GZIP, DEFLATE, RAW}]
@@ -98,33 +89,62 @@
* target len (4)
* data (target len)
*
- * All integers are little-endian. "source start" and "source len"
- * specify the section of the input image that comprises this chunk,
- * including the gzip header and footer for gzip chunks. "source
- * expanded len" is the size of the uncompressed source data. "target
- * expected len" is the size of the uncompressed data after applying
- * the bsdiff patch. The next five parameters specify the zlib
- * parameters to be used when compressing the patched data, and the
- * next three specify the header and footer to be wrapped around the
- * compressed data to create the output chunk (so that header contents
- * like the timestamp are recreated exactly).
+ * All integers are little-endian. "source start" and "source len" specify the section of the
+ * input image that comprises this chunk, including the gzip header and footer for gzip chunks.
+ * "source expanded len" is the size of the uncompressed source data. "target expected len" is the
+ * size of the uncompressed data after applying the bsdiff patch. The next five parameters
+ * specify the zlib parameters to be used when compressing the patched data, and the next three
+ * specify the header and footer to be wrapped around the compressed data to create the output
+ * chunk (so that header contents like the timestamp are recreated exactly).
*
- * After the header there are 'chunk count' bsdiff patches; the offset
- * of each from the beginning of the file is specified in the header.
+ * After the header there are 'chunk count' bsdiff patches; the offset of each from the beginning
+ * of the file is specified in the header.
*
- * This tool can take an optional file of "bonus data". This is an
- * extra file of data that is appended to chunk #1 after it is
- * compressed (it must be a CHUNK_DEFLATE chunk). The same file must
- * be available (and passed to applypatch with -b) when applying the
- * patch. This is used to reduce the size of recovery-from-boot
- * patches by combining the boot image with recovery ramdisk
+ * This tool can take an optional file of "bonus data". This is an extra file of data that is
+ * appended to chunk #1 after it is compressed (it must be a CHUNK_DEFLATE chunk). The same file
+ * must be available (and passed to applypatch with -b) when applying the patch. This is used to
+ * reduce the size of recovery-from-boot patches by combining the boot image with recovery ramdisk
* information that is stored on the system partition.
+ *
+ * When generating the patch between two zip files, this tool has an option "--block-limit" to
+ * split the large source/target files into several pair of pieces, with each piece has at most
+ * *limit* blocks. When this option is used, we also need to output the split info into the file
+ * path specified by "--split-info".
+ *
+ * Format of split info file:
+ * 2 [version of imgdiff]
+ * n [count of split pieces]
+ * <patch_size>, <tgt_size>, <src_range> [size and ranges for split piece#1]
+ * ...
+ * <patch_size>, <tgt_size>, <src_range> [size and ranges for split piece#n]
+ *
+ * To split a pair of large zip files, we walk through the chunks in target zip and search by its
+ * entry_name in the source zip. If the entry_name is non-empty and a matching entry in source
+ * is found, we'll add the source entry to the current split source image; otherwise we'll skip
+ * this chunk and later do bsdiff between all the skipped trunks and the whole split source image.
+ * We move on to the next pair of pieces if the size of the split source image reaches the block
+ * limit.
+ *
+ * After the split, the target pieces are continuous and block aligned, while the source pieces
+ * are mutually exclusive. Some of the source blocks may not be used if there's no matching
+ * entry_name in the target; as a result, they won't be included in any of these split source
+ * images. Then we will generate patches accordingly between each split image pairs; in particular,
+ * the unmatched trunks in the split target will diff against the entire split source image.
+ *
+ * For example:
+ * Input: [src_image, tgt_image]
+ * Split: [src-0, tgt-0; src-1, tgt-1, src-2, tgt-2]
+ * Diff: [ patch-0; patch-1; patch-2]
+ *
+ * Patch: [(src-0, patch-0) = tgt-0; (src-1, patch-1) = tgt-1; (src-2, patch-2) = tgt-2]
+ * Concatenate: [tgt-0 + tgt-1 + tgt-2 = tgt_image]
*/
#include "applypatch/imgdiff.h"
#include <errno.h>
#include <fcntl.h>
+#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -139,15 +159,26 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/memory.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include <android-base/unique_fd.h>
+#include <bsdiff/bsdiff.h>
#include <ziparchive/zip_archive.h>
-
-#include <bsdiff.h>
#include <zlib.h>
+#include "applypatch/imgdiff_image.h"
+#include "otautil/rangeset.h"
+
using android::base::get_unaligned;
-static constexpr auto BUFFER_SIZE = 0x8000;
+static constexpr size_t VERSION = 2;
+
+// We assume the header "IMGDIFF#" is 8 bytes.
+static_assert(VERSION <= 9, "VERSION occupies more than one byte");
+
+static constexpr size_t BLOCK_SIZE = 4096;
+static constexpr size_t BUFFER_SIZE = 0x8000;
// If we use this function to write the offset and length (type size_t), their values should not
// exceed 2^63; because the signed bit will be casted away.
@@ -161,99 +192,80 @@
return android::base::WriteFully(fd, &value, sizeof(int32_t));
}
-class ImageChunk {
- public:
- static constexpr auto WINDOWBITS = -15; // 32kb window; negative to indicate a raw stream.
- static constexpr auto MEMLEVEL = 8; // the default value.
- static constexpr auto METHOD = Z_DEFLATED;
- static constexpr auto STRATEGY = Z_DEFAULT_STRATEGY;
+// Trim the head or tail to align with the block size. Return false if the chunk has nothing left
+// after alignment.
+static bool AlignHead(size_t* start, size_t* length) {
+ size_t residual = (*start % BLOCK_SIZE == 0) ? 0 : BLOCK_SIZE - *start % BLOCK_SIZE;
- ImageChunk(int type, size_t start, const std::vector<uint8_t>* file_content, size_t raw_data_len)
- : type_(type),
- start_(start),
- input_file_ptr_(file_content),
- raw_data_len_(raw_data_len),
- compress_level_(6),
- source_start_(0),
- source_len_(0),
- source_uncompressed_len_(0) {
- CHECK(file_content != nullptr) << "input file container can't be nullptr";
+ if (*length <= residual) {
+ *length = 0;
+ return false;
}
- int GetType() const {
- return type_;
- }
- size_t GetRawDataLength() const {
- return raw_data_len_;
- }
- const std::string& GetEntryName() const {
- return entry_name_;
+ // Trim the data in the beginning.
+ *start += residual;
+ *length -= residual;
+ return true;
+}
+
+static bool AlignTail(size_t* start, size_t* length) {
+ size_t residual = (*start + *length) % BLOCK_SIZE;
+ if (*length <= residual) {
+ *length = 0;
+ return false;
}
- // CHUNK_DEFLATE will return the uncompressed data for diff, while other types will simply return
- // the raw data.
- const uint8_t * DataForPatch() const;
- size_t DataLengthForPatch() const;
+ // Trim the data in the end.
+ *length -= residual;
+ return true;
+}
- void Dump() const {
- printf("type %d start %zu len %zu\n", type_, start_, DataLengthForPatch());
+// Remove the used blocks from the source chunk to make sure the source ranges are mutually
+// exclusive after split. Return false if we fail to get the non-overlapped ranges. In such
+// a case, we'll skip the entire source chunk.
+static bool RemoveUsedBlocks(size_t* start, size_t* length, const SortedRangeSet& used_ranges) {
+ if (!used_ranges.Overlaps(*start, *length)) {
+ return true;
}
- void SetSourceInfo(const ImageChunk& other);
- void SetEntryName(std::string entryname);
- void SetUncompressedData(std::vector<uint8_t> data);
- bool SetBonusData(const std::vector<uint8_t>& bonus_data);
+ // TODO find the largest non-overlap chunk.
+ LOG(INFO) << "Removing block " << used_ranges.ToString() << " from " << *start << " - "
+ << *start + *length - 1;
- bool operator==(const ImageChunk& other) const;
- bool operator!=(const ImageChunk& other) const {
- return !(*this == other);
+ // If there's no duplicate entry name, we should only overlap in the head or tail block. Try to
+ // trim both blocks. Skip this source chunk in case it still overlaps with the used ranges.
+ if (AlignHead(start, length) && !used_ranges.Overlaps(*start, *length)) {
+ return true;
+ }
+ if (AlignTail(start, length) && !used_ranges.Overlaps(*start, *length)) {
+ return true;
}
- size_t GetHeaderSize(size_t patch_size) const;
- // Return the offset of the next patch into the patch data.
- size_t WriteHeaderToFd(int fd, const std::vector<uint8_t>& patch, size_t offset);
+ LOG(WARNING) << "Failed to remove the overlapped block ranges; skip the source";
+ return false;
+}
- /*
- * Cause a gzip chunk to be treated as a normal chunk (ie, as a blob
- * of uninterpreted data). The resulting patch will likely be about
- * as big as the target file, but it lets us handle the case of images
- * where some gzip chunks are reconstructible but others aren't (by
- * treating the ones that aren't as normal chunks).
- */
- void ChangeDeflateChunkToNormal();
- bool ChangeChunkToRaw(size_t patch_size);
-
- /*
- * Verify that we can reproduce exactly the same compressed data that
- * we started with. Sets the level, method, windowBits, memLevel, and
- * strategy fields in the chunk to the encoding parameters needed to
- * produce the right output.
- */
- bool ReconstructDeflateChunk();
- bool IsAdjacentNormal(const ImageChunk& other) const;
- void MergeAdjacentNormal(const ImageChunk& other);
-
- private:
- int type_; // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW
- size_t start_; // offset of chunk in the original input file
- const std::vector<uint8_t>* input_file_ptr_; // ptr to the full content of original input file
- size_t raw_data_len_;
-
- // --- for CHUNK_DEFLATE chunks only: ---
- std::vector<uint8_t> uncompressed_data_;
- std::string entry_name_; // used for zip entries
-
- // deflate encoder parameters
- int compress_level_;
-
- size_t source_start_;
- size_t source_len_;
- size_t source_uncompressed_len_;
-
- const uint8_t* GetRawData() const;
- bool TryReconstruction(int level);
+static const struct option OPTIONS[] = {
+ { "zip-mode", no_argument, nullptr, 'z' },
+ { "bonus-file", required_argument, nullptr, 'b' },
+ { "block-limit", required_argument, nullptr, 0 },
+ { "debug-dir", required_argument, nullptr, 0 },
+ { "split-info", required_argument, nullptr, 0 },
+ { "verbose", no_argument, nullptr, 'v' },
+ { nullptr, 0, nullptr, 0 },
};
+ImageChunk::ImageChunk(int type, size_t start, const std::vector<uint8_t>* file_content,
+ size_t raw_data_len, std::string entry_name)
+ : type_(type),
+ start_(start),
+ input_file_ptr_(file_content),
+ raw_data_len_(raw_data_len),
+ compress_level_(6),
+ entry_name_(std::move(entry_name)) {
+ CHECK(file_content != nullptr) << "input file container can't be nullptr";
+}
+
const uint8_t* ImageChunk::GetRawData() const {
CHECK_LE(start_ + raw_data_len_, input_file_ptr_->size());
return input_file_ptr_->data() + start_;
@@ -273,6 +285,11 @@
return raw_data_len_;
}
+void ImageChunk::Dump(size_t index) const {
+ LOG(INFO) << "chunk: " << index << ", type: " << type_ << ", start: " << start_
+ << ", len: " << DataLengthForPatch() << ", name: " << entry_name_;
+}
+
bool ImageChunk::operator==(const ImageChunk& other) const {
if (type_ != other.type_) {
return false;
@@ -281,20 +298,6 @@
memcmp(GetRawData(), other.GetRawData(), raw_data_len_) == 0);
}
-void ImageChunk::SetSourceInfo(const ImageChunk& src) {
- source_start_ = src.start_;
- if (type_ == CHUNK_NORMAL) {
- source_len_ = src.raw_data_len_;
- } else if (type_ == CHUNK_DEFLATE) {
- source_len_ = src.raw_data_len_;
- source_uncompressed_len_ = src.uncompressed_data_.size();
- }
-}
-
-void ImageChunk::SetEntryName(std::string entryname) {
- entry_name_ = std::move(entryname);
-}
-
void ImageChunk::SetUncompressedData(std::vector<uint8_t> data) {
uncompressed_data_ = std::move(data);
}
@@ -307,80 +310,13 @@
return true;
}
-// Convert CHUNK_NORMAL & CHUNK_DEFLATE to CHUNK_RAW if the target size is
-// smaller. Also take the header size into account during size comparison.
-bool ImageChunk::ChangeChunkToRaw(size_t patch_size) {
- if (type_ == CHUNK_RAW) {
- return true;
- } else if (type_ == CHUNK_NORMAL && (raw_data_len_ <= 160 || raw_data_len_ < patch_size)) {
- type_ = CHUNK_RAW;
- return true;
- }
- return false;
-}
-
void ImageChunk::ChangeDeflateChunkToNormal() {
if (type_ != CHUNK_DEFLATE) return;
type_ = CHUNK_NORMAL;
- entry_name_.clear();
+ // No need to clear the entry name.
uncompressed_data_.clear();
}
-// Header size:
-// header_type 4 bytes
-// CHUNK_NORMAL 8*3 = 24 bytes
-// CHUNK_DEFLATE 8*5 + 4*5 = 60 bytes
-// CHUNK_RAW 4 bytes + patch_size
-size_t ImageChunk::GetHeaderSize(size_t patch_size) const {
- switch (type_) {
- case CHUNK_NORMAL:
- return 4 + 8 * 3;
- case CHUNK_DEFLATE:
- return 4 + 8 * 5 + 4 * 5;
- case CHUNK_RAW:
- return 4 + 4 + patch_size;
- default:
- CHECK(false) << "unexpected chunk type: " << type_; // Should not reach here.
- return 0;
- }
-}
-
-size_t ImageChunk::WriteHeaderToFd(int fd, const std::vector<uint8_t>& patch, size_t offset) {
- Write4(fd, type_);
- switch (type_) {
- case CHUNK_NORMAL:
- printf("normal (%10zu, %10zu) %10zu\n", start_, raw_data_len_, patch.size());
- Write8(fd, static_cast<int64_t>(source_start_));
- Write8(fd, static_cast<int64_t>(source_len_));
- Write8(fd, static_cast<int64_t>(offset));
- return offset + patch.size();
- case CHUNK_DEFLATE:
- printf("deflate (%10zu, %10zu) %10zu %s\n", start_, raw_data_len_, patch.size(),
- entry_name_.c_str());
- Write8(fd, static_cast<int64_t>(source_start_));
- Write8(fd, static_cast<int64_t>(source_len_));
- Write8(fd, static_cast<int64_t>(offset));
- Write8(fd, static_cast<int64_t>(source_uncompressed_len_));
- Write8(fd, static_cast<int64_t>(uncompressed_data_.size()));
- Write4(fd, compress_level_);
- Write4(fd, METHOD);
- Write4(fd, WINDOWBITS);
- Write4(fd, MEMLEVEL);
- Write4(fd, STRATEGY);
- return offset + patch.size();
- case CHUNK_RAW:
- printf("raw (%10zu, %10zu)\n", start_, raw_data_len_);
- Write4(fd, static_cast<int32_t>(patch.size()));
- if (!android::base::WriteFully(fd, patch.data(), patch.size())) {
- CHECK(false) << "failed to write " << patch.size() <<" bytes patch";
- }
- return offset;
- default:
- CHECK(false) << "unexpected chunk type: " << type_;
- return offset;
- }
-}
-
bool ImageChunk::IsAdjacentNormal(const ImageChunk& other) const {
if (type_ != CHUNK_NORMAL || other.type_ != CHUNK_NORMAL) {
return false;
@@ -393,14 +329,62 @@
raw_data_len_ = raw_data_len_ + other.raw_data_len_;
}
-bool ImageChunk::ReconstructDeflateChunk() {
- if (type_ != CHUNK_DEFLATE) {
- printf("attempt to reconstruct non-deflate chunk\n");
+bool ImageChunk::MakePatch(const ImageChunk& tgt, const ImageChunk& src,
+ std::vector<uint8_t>* patch_data,
+ bsdiff::SuffixArrayIndexInterface** bsdiff_cache) {
+#if defined(__ANDROID__)
+ char ptemp[] = "/data/local/tmp/imgdiff-patch-XXXXXX";
+#else
+ char ptemp[] = "/tmp/imgdiff-patch-XXXXXX";
+#endif
+
+ int fd = mkstemp(ptemp);
+ if (fd == -1) {
+ PLOG(ERROR) << "MakePatch failed to create a temporary file";
+ return false;
+ }
+ close(fd);
+
+ int r = bsdiff::bsdiff(src.DataForPatch(), src.DataLengthForPatch(), tgt.DataForPatch(),
+ tgt.DataLengthForPatch(), ptemp, bsdiff_cache);
+ if (r != 0) {
+ LOG(ERROR) << "bsdiff() failed: " << r;
return false;
}
- // We only check two combinations of encoder parameters: level 6
- // (the default) and level 9 (the maximum).
+ android::base::unique_fd patch_fd(open(ptemp, O_RDONLY));
+ if (patch_fd == -1) {
+ PLOG(ERROR) << "Failed to open " << ptemp;
+ return false;
+ }
+ struct stat st;
+ if (fstat(patch_fd, &st) != 0) {
+ PLOG(ERROR) << "Failed to stat patch file " << ptemp;
+ return false;
+ }
+
+ size_t sz = static_cast<size_t>(st.st_size);
+
+ patch_data->resize(sz);
+ if (!android::base::ReadFully(patch_fd, patch_data->data(), sz)) {
+ PLOG(ERROR) << "Failed to read " << ptemp;
+ unlink(ptemp);
+ return false;
+ }
+
+ unlink(ptemp);
+
+ return true;
+}
+
+bool ImageChunk::ReconstructDeflateChunk() {
+ if (type_ != CHUNK_DEFLATE) {
+ LOG(ERROR) << "Attempted to reconstruct non-deflate chunk";
+ return false;
+ }
+
+ // We only check two combinations of encoder parameters: level 6 (the default) and level 9
+ // (the maximum).
for (int level = 6; level <= 9; level += 3) {
if (TryReconstruction(level)) {
compress_level_ = level;
@@ -412,10 +396,9 @@
}
/*
- * Takes the uncompressed data stored in the chunk, compresses it
- * using the zlib parameters stored in the chunk, and checks that it
- * matches exactly the compressed data we started with (also stored in
- * the chunk).
+ * Takes the uncompressed data stored in the chunk, compresses it using the zlib parameters stored
+ * in the chunk, and checks that it matches exactly the compressed data we started with (also
+ * stored in the chunk).
*/
bool ImageChunk::TryReconstruction(int level) {
z_stream strm;
@@ -426,7 +409,7 @@
strm.next_in = uncompressed_data_.data();
int ret = deflateInit2(&strm, level, METHOD, WINDOWBITS, MEMLEVEL, STRATEGY);
if (ret < 0) {
- printf("failed to initialize deflate: %d\n", ret);
+ LOG(ERROR) << "Failed to initialize deflate: " << ret;
return false;
}
@@ -437,7 +420,7 @@
strm.next_out = buffer.data();
ret = deflate(&strm, Z_FINISH);
if (ret < 0) {
- printf("failed to deflate: %d\n", ret);
+ LOG(ERROR) << "Failed to deflate: " << ret;
return false;
}
@@ -458,29 +441,381 @@
return true;
}
+PatchChunk::PatchChunk(const ImageChunk& tgt, const ImageChunk& src, std::vector<uint8_t> data)
+ : type_(tgt.GetType()),
+ source_start_(src.GetStartOffset()),
+ source_len_(src.GetRawDataLength()),
+ source_uncompressed_len_(src.DataLengthForPatch()),
+ target_start_(tgt.GetStartOffset()),
+ target_len_(tgt.GetRawDataLength()),
+ target_uncompressed_len_(tgt.DataLengthForPatch()),
+ target_compress_level_(tgt.GetCompressLevel()),
+ data_(std::move(data)) {}
+
+// Construct a CHUNK_RAW patch from the target data directly.
+PatchChunk::PatchChunk(const ImageChunk& tgt)
+ : type_(CHUNK_RAW),
+ source_start_(0),
+ source_len_(0),
+ source_uncompressed_len_(0),
+ target_start_(tgt.GetStartOffset()),
+ target_len_(tgt.GetRawDataLength()),
+ target_uncompressed_len_(tgt.DataLengthForPatch()),
+ target_compress_level_(tgt.GetCompressLevel()),
+ data_(tgt.DataForPatch(), tgt.DataForPatch() + tgt.DataLengthForPatch()) {}
+
+// 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));
+}
+
+void PatchChunk::UpdateSourceOffset(const SortedRangeSet& src_range) {
+ if (type_ == CHUNK_DEFLATE) {
+ source_start_ = src_range.GetOffsetInRangeSet(source_start_);
+ }
+}
+
+// Header size:
+// header_type 4 bytes
+// CHUNK_NORMAL 8*3 = 24 bytes
+// CHUNK_DEFLATE 8*5 + 4*5 = 60 bytes
+// CHUNK_RAW 4 bytes + patch_size
+size_t PatchChunk::GetHeaderSize() const {
+ switch (type_) {
+ case CHUNK_NORMAL:
+ return 4 + 8 * 3;
+ case CHUNK_DEFLATE:
+ return 4 + 8 * 5 + 4 * 5;
+ case CHUNK_RAW:
+ return 4 + 4 + data_.size();
+ default:
+ CHECK(false) << "unexpected chunk type: " << type_; // Should not reach here.
+ return 0;
+ }
+}
+
+// Return the offset of the next patch into the patch data.
+size_t PatchChunk::WriteHeaderToFd(int fd, size_t offset, size_t index) const {
+ Write4(fd, type_);
+ switch (type_) {
+ case CHUNK_NORMAL:
+ LOG(INFO) << android::base::StringPrintf("chunk %zu: normal (%10zu, %10zu) %10zu", index,
+ target_start_, target_len_, data_.size());
+ Write8(fd, static_cast<int64_t>(source_start_));
+ Write8(fd, static_cast<int64_t>(source_len_));
+ Write8(fd, static_cast<int64_t>(offset));
+ return offset + data_.size();
+ case CHUNK_DEFLATE:
+ LOG(INFO) << android::base::StringPrintf("chunk %zu: deflate (%10zu, %10zu) %10zu", index,
+ target_start_, target_len_, data_.size());
+ Write8(fd, static_cast<int64_t>(source_start_));
+ Write8(fd, static_cast<int64_t>(source_len_));
+ Write8(fd, static_cast<int64_t>(offset));
+ Write8(fd, static_cast<int64_t>(source_uncompressed_len_));
+ Write8(fd, static_cast<int64_t>(target_uncompressed_len_));
+ Write4(fd, target_compress_level_);
+ Write4(fd, ImageChunk::METHOD);
+ Write4(fd, ImageChunk::WINDOWBITS);
+ Write4(fd, ImageChunk::MEMLEVEL);
+ Write4(fd, ImageChunk::STRATEGY);
+ return offset + data_.size();
+ case CHUNK_RAW:
+ LOG(INFO) << android::base::StringPrintf("chunk %zu: raw (%10zu, %10zu)", index,
+ target_start_, target_len_);
+ Write4(fd, static_cast<int32_t>(data_.size()));
+ if (!android::base::WriteFully(fd, data_.data(), data_.size())) {
+ CHECK(false) << "Failed to write " << data_.size() << " bytes patch";
+ }
+ return offset;
+ default:
+ CHECK(false) << "unexpected chunk type: " << type_;
+ return offset;
+ }
+}
+
+size_t PatchChunk::PatchSize() const {
+ if (type_ == CHUNK_RAW) {
+ return GetHeaderSize();
+ }
+ return GetHeaderSize() + data_.size();
+}
+
+// Write the contents of |patch_chunks| to |patch_fd|.
+bool PatchChunk::WritePatchDataToFd(const std::vector<PatchChunk>& patch_chunks, int patch_fd) {
+ // Figure out how big the imgdiff file header is going to be, so that we can correctly compute
+ // the offset of each bsdiff patch within the file.
+ size_t total_header_size = 12;
+ for (const auto& patch : patch_chunks) {
+ total_header_size += patch.GetHeaderSize();
+ }
+
+ size_t offset = total_header_size;
+
+ // Write out the headers.
+ if (!android::base::WriteStringToFd("IMGDIFF" + std::to_string(VERSION), patch_fd)) {
+ PLOG(ERROR) << "Failed to write \"IMGDIFF" << VERSION << "\"";
+ return false;
+ }
+
+ Write4(patch_fd, static_cast<int32_t>(patch_chunks.size()));
+ LOG(INFO) << "Writing " << patch_chunks.size() << " patch headers...";
+ for (size_t i = 0; i < patch_chunks.size(); ++i) {
+ offset = patch_chunks[i].WriteHeaderToFd(patch_fd, offset, i);
+ }
+
+ // Append each chunk's bsdiff patch, in order.
+ for (const auto& patch : patch_chunks) {
+ if (patch.type_ == CHUNK_RAW) {
+ continue;
+ }
+ if (!android::base::WriteFully(patch_fd, patch.data_.data(), patch.data_.size())) {
+ PLOG(ERROR) << "Failed to write " << patch.data_.size() << " bytes patch to patch_fd";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+ImageChunk& Image::operator[](size_t i) {
+ CHECK_LT(i, chunks_.size());
+ return chunks_[i];
+}
+
+const ImageChunk& Image::operator[](size_t i) const {
+ CHECK_LT(i, chunks_.size());
+ return chunks_[i];
+}
+
+void Image::MergeAdjacentNormalChunks() {
+ size_t merged_last = 0, cur = 0;
+ while (cur < chunks_.size()) {
+ // Look for normal chunks adjacent to the current one. If such chunk exists, extend the
+ // length of the current normal chunk.
+ size_t to_check = cur + 1;
+ while (to_check < chunks_.size() && chunks_[cur].IsAdjacentNormal(chunks_[to_check])) {
+ chunks_[cur].MergeAdjacentNormal(chunks_[to_check]);
+ to_check++;
+ }
+
+ if (merged_last != cur) {
+ chunks_[merged_last] = std::move(chunks_[cur]);
+ }
+ merged_last++;
+ cur = to_check;
+ }
+ if (merged_last < chunks_.size()) {
+ chunks_.erase(chunks_.begin() + merged_last, chunks_.end());
+ }
+}
+
+void Image::DumpChunks() const {
+ std::string type = is_source_ ? "source" : "target";
+ LOG(INFO) << "Dumping chunks for " << type;
+ for (size_t i = 0; i < chunks_.size(); ++i) {
+ chunks_[i].Dump(i);
+ }
+}
+
+bool Image::ReadFile(const std::string& filename, std::vector<uint8_t>* file_content) {
+ CHECK(file_content != nullptr);
+
+ android::base::unique_fd fd(open(filename.c_str(), O_RDONLY));
+ if (fd == -1) {
+ PLOG(ERROR) << "Failed to open " << filename;
+ return false;
+ }
+ struct stat st;
+ if (fstat(fd, &st) != 0) {
+ PLOG(ERROR) << "Failed to stat " << filename;
+ return false;
+ }
+
+ size_t sz = static_cast<size_t>(st.st_size);
+ file_content->resize(sz);
+ if (!android::base::ReadFully(fd, file_content->data(), sz)) {
+ PLOG(ERROR) << "Failed to read " << filename;
+ return false;
+ }
+ fd.reset();
+
+ return true;
+}
+
+bool ZipModeImage::Initialize(const std::string& filename) {
+ if (!ReadFile(filename, &file_content_)) {
+ return false;
+ }
+
+ // Omit the trailing zeros before we pass the file to ziparchive handler.
+ size_t zipfile_size;
+ if (!GetZipFileSize(&zipfile_size)) {
+ LOG(ERROR) << "Failed to parse the actual size of " << filename;
+ return false;
+ }
+ ZipArchiveHandle handle;
+ int err = OpenArchiveFromMemory(const_cast<uint8_t*>(file_content_.data()), zipfile_size,
+ filename.c_str(), &handle);
+ if (err != 0) {
+ LOG(ERROR) << "Failed to open zip file " << filename << ": " << ErrorCodeString(err);
+ CloseArchive(handle);
+ return false;
+ }
+
+ if (!InitializeChunks(filename, handle)) {
+ CloseArchive(handle);
+ return false;
+ }
+
+ CloseArchive(handle);
+ return true;
+}
+
+// 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);
+ if (ret != 0) {
+ LOG(ERROR) << "Failed to iterate over entries in " << filename << ": " << ErrorCodeString(ret);
+ return false;
+ }
+
+ // Create a list of deflated zip entries, sorted by offset.
+ std::vector<std::pair<std::string, ZipEntry>> temp_entries;
+ ZipString name;
+ ZipEntry entry;
+ while ((ret = Next(cookie, &entry, &name)) == 0) {
+ if (entry.method == kCompressDeflated || limit_ > 0) {
+ std::string entry_name(name.name, name.name + name.name_length);
+ temp_entries.emplace_back(entry_name, entry);
+ }
+ }
+
+ if (ret != -1) {
+ LOG(ERROR) << "Error while iterating over zip entries: " << ErrorCodeString(ret);
+ return false;
+ }
+ std::sort(temp_entries.begin(), temp_entries.end(),
+ [](auto& entry1, auto& entry2) { return entry1.second.offset < entry2.second.offset; });
+
+ EndIteration(cookie);
+
+ // For source chunks, we don't need to compose chunks for the metadata.
+ if (is_source_) {
+ for (auto& entry : temp_entries) {
+ if (!AddZipEntryToChunks(handle, entry.first, &entry.second)) {
+ LOG(ERROR) << "Failed to add " << entry.first << " to source chunks";
+ return false;
+ }
+ }
+
+ // Add the end of zip file (mainly central directory) as a normal chunk.
+ size_t entries_end = 0;
+ if (!temp_entries.empty()) {
+ entries_end = static_cast<size_t>(temp_entries.back().second.offset +
+ temp_entries.back().second.compressed_length);
+ }
+ CHECK_LT(entries_end, file_content_.size());
+ chunks_.emplace_back(CHUNK_NORMAL, entries_end, &file_content_,
+ file_content_.size() - entries_end);
+
+ return true;
+ }
+
+ // For target chunks, add the deflate entries as CHUNK_DEFLATE and the contents between two
+ // deflate entries as CHUNK_NORMAL.
+ size_t pos = 0;
+ size_t nextentry = 0;
+ while (pos < file_content_.size()) {
+ if (nextentry < temp_entries.size() &&
+ static_cast<off64_t>(pos) == temp_entries[nextentry].second.offset) {
+ // Add the next zip entry.
+ std::string entry_name = temp_entries[nextentry].first;
+ if (!AddZipEntryToChunks(handle, entry_name, &temp_entries[nextentry].second)) {
+ LOG(ERROR) << "Failed to add " << entry_name << " to target chunks";
+ return false;
+ }
+
+ pos += temp_entries[nextentry].second.compressed_length;
+ ++nextentry;
+ continue;
+ }
+
+ // Use a normal chunk to take all the data up to the start of the next entry.
+ size_t raw_data_len;
+ if (nextentry < temp_entries.size()) {
+ raw_data_len = temp_entries[nextentry].second.offset - pos;
+ } else {
+ raw_data_len = file_content_.size() - pos;
+ }
+ chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, raw_data_len);
+
+ pos += raw_data_len;
+ }
+
+ return true;
+}
+
+bool ZipModeImage::AddZipEntryToChunks(ZipArchiveHandle handle, const std::string& entry_name,
+ ZipEntry* entry) {
+ size_t compressed_len = entry->compressed_length;
+ if (compressed_len == 0) return true;
+
+ // Split the entry into several normal chunks if it's too large.
+ if (limit_ > 0 && compressed_len > limit_) {
+ int count = 0;
+ while (compressed_len > 0) {
+ size_t length = std::min(limit_, compressed_len);
+ std::string name = entry_name + "-" + std::to_string(count);
+ chunks_.emplace_back(CHUNK_NORMAL, entry->offset + limit_ * count, &file_content_, length,
+ name);
+
+ count++;
+ compressed_len -= length;
+ }
+ } else if (entry->method == kCompressDeflated) {
+ size_t uncompressed_len = entry->uncompressed_length;
+ std::vector<uint8_t> uncompressed_data(uncompressed_len);
+ int ret = ExtractToMemory(handle, entry, uncompressed_data.data(), uncompressed_len);
+ if (ret != 0) {
+ LOG(ERROR) << "Failed to extract " << entry_name << " with size " << uncompressed_len << ": "
+ << ErrorCodeString(ret);
+ return false;
+ }
+ ImageChunk curr(CHUNK_DEFLATE, entry->offset, &file_content_, compressed_len, entry_name);
+ curr.SetUncompressedData(std::move(uncompressed_data));
+ chunks_.push_back(std::move(curr));
+ } else {
+ chunks_.emplace_back(CHUNK_NORMAL, entry->offset, &file_content_, compressed_len, entry_name);
+ }
+
+ return true;
+}
+
// EOCD record
// offset 0: signature 0x06054b50, 4 bytes
// offset 4: number of this disk, 2 bytes
// ...
// offset 20: comment length, 2 bytes
// offset 22: comment, n bytes
-static bool GetZipFileSize(const std::vector<uint8_t>& zip_file, size_t* input_file_size) {
- if (zip_file.size() < 22) {
- printf("file is too small to be a zip file\n");
+bool ZipModeImage::GetZipFileSize(size_t* input_file_size) {
+ if (file_content_.size() < 22) {
+ LOG(ERROR) << "File is too small to be a zip file";
return false;
}
// Look for End of central directory record of the zip file, and calculate the actual
// zip_file size.
- for (int i = zip_file.size() - 22; i >= 0; i--) {
- if (zip_file[i] == 0x50) {
- if (get_unaligned<uint32_t>(&zip_file[i]) == 0x06054b50) {
+ for (int i = file_content_.size() - 22; i >= 0; i--) {
+ if (file_content_[i] == 0x50) {
+ if (get_unaligned<uint32_t>(&file_content_[i]) == 0x06054b50) {
// double-check: this archive consists of a single "disk".
- CHECK_EQ(get_unaligned<uint16_t>(&zip_file[i + 4]), 0);
+ CHECK_EQ(get_unaligned<uint16_t>(&file_content_[i + 4]), 0);
- uint16_t comment_length = get_unaligned<uint16_t>(&zip_file[i + 20]);
+ uint16_t comment_length = get_unaligned<uint16_t>(&file_content_[i + 20]);
size_t file_size = i + 22 + comment_length;
- CHECK_LE(file_size, zip_file.size());
+ CHECK_LE(file_size, file_content_.size());
*input_file_size = file_size;
return true;
}
@@ -491,162 +826,445 @@
return false;
}
-static bool ReadZip(const char* filename, std::vector<ImageChunk>* chunks,
- std::vector<uint8_t>* zip_file, bool include_pseudo_chunk) {
- CHECK(chunks != nullptr && zip_file != nullptr);
+ImageChunk ZipModeImage::PseudoSource() const {
+ CHECK(is_source_);
+ return ImageChunk(CHUNK_NORMAL, 0, &file_content_, file_content_.size());
+}
- android::base::unique_fd fd(open(filename, O_RDONLY));
- if (fd == -1) {
- printf("failed to open \"%s\" %s\n", filename, strerror(errno));
- return false;
+const ImageChunk* ZipModeImage::FindChunkByName(const std::string& name, bool find_normal) const {
+ if (name.empty()) {
+ return nullptr;
}
- struct stat st;
- if (fstat(fd, &st) != 0) {
- printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
- return false;
- }
-
- size_t sz = static_cast<size_t>(st.st_size);
- zip_file->resize(sz);
- if (!android::base::ReadFully(fd, zip_file->data(), sz)) {
- printf("failed to read \"%s\" %s\n", filename, strerror(errno));
- return false;
- }
- fd.reset();
-
- // Trim the trailing zeros before we pass the file to ziparchive handler.
- size_t zipfile_size;
- if (!GetZipFileSize(*zip_file, &zipfile_size)) {
- printf("failed to parse the actual size of %s\n", filename);
- return false;
- }
- ZipArchiveHandle handle;
- int err = OpenArchiveFromMemory(zip_file->data(), zipfile_size, filename, &handle);
- if (err != 0) {
- printf("failed to open zip file %s: %s\n", filename, ErrorCodeString(err));
- CloseArchive(handle);
- return false;
- }
-
- // Create a list of deflated zip entries, sorted by offset.
- std::vector<std::pair<std::string, ZipEntry>> temp_entries;
- void* cookie;
- int ret = StartIteration(handle, &cookie, nullptr, nullptr);
- if (ret != 0) {
- printf("failed to iterate over entries in %s: %s\n", filename, ErrorCodeString(ret));
- CloseArchive(handle);
- return false;
- }
-
- ZipString name;
- ZipEntry entry;
- while ((ret = Next(cookie, &entry, &name)) == 0) {
- if (entry.method == kCompressDeflated) {
- std::string entryname(name.name, name.name + name.name_length);
- temp_entries.push_back(std::make_pair(entryname, entry));
- }
- }
-
- if (ret != -1) {
- printf("Error while iterating over zip entries: %s\n", ErrorCodeString(ret));
- CloseArchive(handle);
- return false;
- }
- std::sort(temp_entries.begin(), temp_entries.end(),
- [](auto& entry1, auto& entry2) {
- return entry1.second.offset < entry2.second.offset;
- });
-
- EndIteration(cookie);
-
- if (include_pseudo_chunk) {
- chunks->emplace_back(CHUNK_NORMAL, 0, zip_file, zip_file->size());
- }
-
- size_t pos = 0;
- size_t nextentry = 0;
- while (pos < zip_file->size()) {
- if (nextentry < temp_entries.size() &&
- static_cast<off64_t>(pos) == temp_entries[nextentry].second.offset) {
- // compose the next deflate chunk.
- std::string entryname = temp_entries[nextentry].first;
- size_t uncompressed_len = temp_entries[nextentry].second.uncompressed_length;
- std::vector<uint8_t> uncompressed_data(uncompressed_len);
- if ((ret = ExtractToMemory(handle, &temp_entries[nextentry].second, uncompressed_data.data(),
- uncompressed_len)) != 0) {
- printf("failed to extract %s with size %zu: %s\n", entryname.c_str(), uncompressed_len,
- ErrorCodeString(ret));
- CloseArchive(handle);
- return false;
- }
-
- size_t compressed_len = temp_entries[nextentry].second.compressed_length;
- ImageChunk curr(CHUNK_DEFLATE, pos, zip_file, compressed_len);
- curr.SetEntryName(std::move(entryname));
- curr.SetUncompressedData(std::move(uncompressed_data));
- chunks->push_back(curr);
-
- pos += compressed_len;
- ++nextentry;
+ for (auto& chunk : chunks_) {
+ if (chunk.GetType() != CHUNK_DEFLATE && !find_normal) {
continue;
}
- // Use a normal chunk to take all the data up to the start of the next deflate section.
- size_t raw_data_len;
- if (nextentry < temp_entries.size()) {
- raw_data_len = temp_entries[nextentry].second.offset - pos;
- } else {
- raw_data_len = zip_file->size() - pos;
+ if (chunk.GetEntryName() == name) {
+ return &chunk;
}
- chunks->emplace_back(CHUNK_NORMAL, pos, zip_file, raw_data_len);
- pos += raw_data_len;
+ // Edge case when target chunk is split due to size limit but source chunk isn't.
+ if (name == (chunk.GetEntryName() + "-0") || chunk.GetEntryName() == (name + "-0")) {
+ return &chunk;
+ }
+
+ // TODO handle the .so files with incremental version number.
+ // (e.g. lib/arm64-v8a/libcronet.59.0.3050.4.so)
}
- CloseArchive(handle);
+ return nullptr;
+}
+
+ImageChunk* ZipModeImage::FindChunkByName(const std::string& name, bool find_normal) {
+ return const_cast<ImageChunk*>(
+ static_cast<const ZipModeImage*>(this)->FindChunkByName(name, find_normal));
+}
+
+bool ZipModeImage::CheckAndProcessChunks(ZipModeImage* tgt_image, ZipModeImage* src_image) {
+ for (auto& tgt_chunk : *tgt_image) {
+ if (tgt_chunk.GetType() != CHUNK_DEFLATE) {
+ continue;
+ }
+
+ ImageChunk* src_chunk = src_image->FindChunkByName(tgt_chunk.GetEntryName());
+ if (src_chunk == nullptr) {
+ tgt_chunk.ChangeDeflateChunkToNormal();
+ } else if (tgt_chunk == *src_chunk) {
+ // If two deflate chunks are identical (eg, the kernel has not changed between two builds),
+ // treat them as normal chunks. This makes applypatch much faster -- it can apply a trivial
+ // patch to the compressed data, rather than uncompressing and recompressing to apply the
+ // trivial patch to the uncompressed data.
+ tgt_chunk.ChangeDeflateChunkToNormal();
+ src_chunk->ChangeDeflateChunkToNormal();
+ } else if (!tgt_chunk.ReconstructDeflateChunk()) {
+ // We cannot recompress the data and get exactly the same bits as are in the input target
+ // image. Treat the chunk as a normal non-deflated chunk.
+ LOG(WARNING) << "Failed to reconstruct target deflate chunk [" << tgt_chunk.GetEntryName()
+ << "]; treating as normal";
+
+ tgt_chunk.ChangeDeflateChunkToNormal();
+ src_chunk->ChangeDeflateChunkToNormal();
+ }
+ }
+
+ // For zips, we only need merge normal chunks for the target: deflated chunks are matched via
+ // filename, and normal chunks are patched using the entire source file as the source.
+ if (tgt_image->limit_ == 0) {
+ tgt_image->MergeAdjacentNormalChunks();
+ tgt_image->DumpChunks();
+ }
+
return true;
}
-// Read the given file and break it up into chunks, and putting the data in to a vector.
-static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks,
- std::vector<uint8_t>* img) {
- CHECK(chunks != nullptr && img != nullptr);
+// For each target chunk, look for the corresponding source chunk by the zip_entry name. If
+// found, add the range of this chunk in the original source file to the block aligned source
+// ranges. Construct the split src & tgt image once the size of source range reaches limit.
+bool ZipModeImage::SplitZipModeImageWithLimit(const ZipModeImage& tgt_image,
+ const ZipModeImage& src_image,
+ std::vector<ZipModeImage>* split_tgt_images,
+ std::vector<ZipModeImage>* split_src_images,
+ std::vector<SortedRangeSet>* split_src_ranges) {
+ CHECK_EQ(tgt_image.limit_, src_image.limit_);
+ size_t limit = tgt_image.limit_;
- android::base::unique_fd fd(open(filename, O_RDONLY));
- if (fd == -1) {
- printf("failed to open \"%s\" %s\n", filename, strerror(errno));
- return false;
+ src_image.DumpChunks();
+ LOG(INFO) << "Splitting " << tgt_image.NumOfChunks() << " tgt chunks...";
+
+ SortedRangeSet used_src_ranges; // ranges used for previous split source images.
+
+ // Reserve the central directory in advance for the last split image.
+ const auto& central_directory = src_image.cend() - 1;
+ CHECK_EQ(CHUNK_NORMAL, central_directory->GetType());
+ used_src_ranges.Insert(central_directory->GetStartOffset(),
+ central_directory->DataLengthForPatch());
+
+ SortedRangeSet src_ranges;
+ std::vector<ImageChunk> split_src_chunks;
+ std::vector<ImageChunk> split_tgt_chunks;
+ for (auto tgt = tgt_image.cbegin(); tgt != tgt_image.cend(); tgt++) {
+ const ImageChunk* src = src_image.FindChunkByName(tgt->GetEntryName(), true);
+ if (src == nullptr) {
+ split_tgt_chunks.emplace_back(CHUNK_NORMAL, tgt->GetStartOffset(), &tgt_image.file_content_,
+ tgt->GetRawDataLength());
+ continue;
+ }
+
+ size_t src_offset = src->GetStartOffset();
+ size_t src_length = src->GetRawDataLength();
+
+ CHECK(src_length > 0);
+ CHECK_LE(src_length, limit);
+
+ // Make sure this source range hasn't been used before so that the src_range pieces don't
+ // overlap with each other.
+ if (!RemoveUsedBlocks(&src_offset, &src_length, used_src_ranges)) {
+ split_tgt_chunks.emplace_back(CHUNK_NORMAL, tgt->GetStartOffset(), &tgt_image.file_content_,
+ tgt->GetRawDataLength());
+ } else if (src_ranges.blocks() * BLOCK_SIZE + src_length <= limit) {
+ src_ranges.Insert(src_offset, src_length);
+
+ // Add the deflate source chunk if it hasn't been aligned.
+ if (src->GetType() == CHUNK_DEFLATE && src_length == src->GetRawDataLength()) {
+ split_src_chunks.push_back(*src);
+ split_tgt_chunks.push_back(*tgt);
+ } else {
+ // TODO split smarter to avoid alignment of large deflate chunks
+ split_tgt_chunks.emplace_back(CHUNK_NORMAL, tgt->GetStartOffset(), &tgt_image.file_content_,
+ tgt->GetRawDataLength());
+ }
+ } else {
+ bool added_image = ZipModeImage::AddSplitImageFromChunkList(
+ tgt_image, src_image, src_ranges, split_tgt_chunks, split_src_chunks, split_tgt_images,
+ split_src_images);
+
+ split_tgt_chunks.clear();
+ split_src_chunks.clear();
+ // No need to update the split_src_ranges if we don't update the split source images.
+ if (added_image) {
+ used_src_ranges.Insert(src_ranges);
+ split_src_ranges->push_back(std::move(src_ranges));
+ }
+ src_ranges.Clear();
+
+ // We don't have enough space for the current chunk; start a new split image and handle
+ // this chunk there.
+ tgt--;
+ }
}
- struct stat st;
- if (fstat(fd, &st) != 0) {
- printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
+
+ // TODO Trim it in case the CD exceeds limit too much.
+ src_ranges.Insert(central_directory->GetStartOffset(), central_directory->DataLengthForPatch());
+ bool added_image = ZipModeImage::AddSplitImageFromChunkList(tgt_image, src_image, src_ranges,
+ split_tgt_chunks, split_src_chunks,
+ split_tgt_images, split_src_images);
+ if (added_image) {
+ split_src_ranges->push_back(std::move(src_ranges));
+ }
+
+ ValidateSplitImages(*split_tgt_images, *split_src_images, *split_src_ranges,
+ tgt_image.file_content_.size());
+
+ return true;
+}
+
+bool ZipModeImage::AddSplitImageFromChunkList(const ZipModeImage& tgt_image,
+ const ZipModeImage& src_image,
+ const SortedRangeSet& split_src_ranges,
+ const std::vector<ImageChunk>& split_tgt_chunks,
+ const std::vector<ImageChunk>& split_src_chunks,
+ std::vector<ZipModeImage>* split_tgt_images,
+ std::vector<ZipModeImage>* split_src_images) {
+ CHECK(!split_tgt_chunks.empty());
+
+ std::vector<ImageChunk> aligned_tgt_chunks;
+
+ // Align the target chunks in the beginning with BLOCK_SIZE.
+ size_t i = 0;
+ while (i < split_tgt_chunks.size()) {
+ size_t tgt_start = split_tgt_chunks[i].GetStartOffset();
+ size_t tgt_length = split_tgt_chunks[i].GetRawDataLength();
+
+ // Current ImageChunk is long enough to align.
+ if (AlignHead(&tgt_start, &tgt_length)) {
+ aligned_tgt_chunks.emplace_back(CHUNK_NORMAL, tgt_start, &tgt_image.file_content_,
+ tgt_length);
+ break;
+ }
+
+ i++;
+ }
+
+ // Nothing left after alignment in the current split tgt chunks; skip adding the split_tgt_image.
+ if (i == split_tgt_chunks.size()) {
return false;
}
- size_t sz = static_cast<size_t>(st.st_size);
- img->resize(sz);
- if (!android::base::ReadFully(fd, img->data(), sz)) {
- printf("failed to read \"%s\" %s\n", filename, strerror(errno));
+ aligned_tgt_chunks.insert(aligned_tgt_chunks.end(), split_tgt_chunks.begin() + i + 1,
+ split_tgt_chunks.end());
+ CHECK(!aligned_tgt_chunks.empty());
+
+ // Add a normal chunk to align the contents in the end.
+ size_t end_offset =
+ aligned_tgt_chunks.back().GetStartOffset() + aligned_tgt_chunks.back().GetRawDataLength();
+ if (end_offset % BLOCK_SIZE != 0 && end_offset < tgt_image.file_content_.size()) {
+ size_t tail_block_length = std::min<size_t>(tgt_image.file_content_.size() - end_offset,
+ BLOCK_SIZE - (end_offset % BLOCK_SIZE));
+ aligned_tgt_chunks.emplace_back(CHUNK_NORMAL, end_offset, &tgt_image.file_content_,
+ tail_block_length);
+ }
+
+ ZipModeImage split_tgt_image(false);
+ split_tgt_image.Initialize(std::move(aligned_tgt_chunks), {});
+ split_tgt_image.MergeAdjacentNormalChunks();
+
+ // Construct the dummy source file based on the src_ranges.
+ std::vector<uint8_t> src_content;
+ for (const auto& r : split_src_ranges) {
+ size_t end = std::min(src_image.file_content_.size(), r.second * BLOCK_SIZE);
+ src_content.insert(src_content.end(), src_image.file_content_.begin() + r.first * BLOCK_SIZE,
+ src_image.file_content_.begin() + end);
+ }
+
+ // We should not have an empty src in our design; otherwise we will encounter an error in
+ // bsdiff since src_content.data() == nullptr.
+ CHECK(!src_content.empty());
+
+ ZipModeImage split_src_image(true);
+ split_src_image.Initialize(split_src_chunks, std::move(src_content));
+
+ split_tgt_images->push_back(std::move(split_tgt_image));
+ split_src_images->push_back(std::move(split_src_image));
+
+ return true;
+}
+
+void ZipModeImage::ValidateSplitImages(const std::vector<ZipModeImage>& split_tgt_images,
+ const std::vector<ZipModeImage>& split_src_images,
+ std::vector<SortedRangeSet>& split_src_ranges,
+ size_t total_tgt_size) {
+ CHECK_EQ(split_tgt_images.size(), split_src_images.size());
+
+ LOG(INFO) << "Validating " << split_tgt_images.size() << " images";
+
+ // Verify that the target image pieces is continuous and can add up to the total size.
+ size_t last_offset = 0;
+ for (const auto& tgt_image : split_tgt_images) {
+ CHECK(!tgt_image.chunks_.empty());
+
+ CHECK_EQ(last_offset, tgt_image.chunks_.front().GetStartOffset());
+ CHECK(last_offset % BLOCK_SIZE == 0);
+
+ // Check the target chunks within the split image are continuous.
+ for (const auto& chunk : tgt_image.chunks_) {
+ CHECK_EQ(last_offset, chunk.GetStartOffset());
+ last_offset += chunk.GetRawDataLength();
+ }
+ }
+ CHECK_EQ(total_tgt_size, last_offset);
+
+ // Verify that the source ranges are mutually exclusive.
+ CHECK_EQ(split_src_images.size(), split_src_ranges.size());
+ SortedRangeSet used_src_ranges;
+ for (size_t i = 0; i < split_src_ranges.size(); i++) {
+ CHECK(!used_src_ranges.Overlaps(split_src_ranges[i]))
+ << "src range " << split_src_ranges[i].ToString() << " overlaps "
+ << used_src_ranges.ToString();
+ used_src_ranges.Insert(split_src_ranges[i]);
+ }
+}
+
+bool ZipModeImage::GeneratePatchesInternal(const ZipModeImage& tgt_image,
+ const ZipModeImage& src_image,
+ std::vector<PatchChunk>* patch_chunks) {
+ LOG(INFO) << "Constructing patches for " << tgt_image.NumOfChunks() << " chunks...";
+ patch_chunks->clear();
+
+ bsdiff::SuffixArrayIndexInterface* bsdiff_cache = nullptr;
+ for (size_t i = 0; i < tgt_image.NumOfChunks(); i++) {
+ const auto& tgt_chunk = tgt_image[i];
+
+ if (PatchChunk::RawDataIsSmaller(tgt_chunk, 0)) {
+ patch_chunks->emplace_back(tgt_chunk);
+ continue;
+ }
+
+ const ImageChunk* src_chunk = (tgt_chunk.GetType() != CHUNK_DEFLATE)
+ ? nullptr
+ : src_image.FindChunkByName(tgt_chunk.GetEntryName());
+
+ const auto& src_ref = (src_chunk == nullptr) ? src_image.PseudoSource() : *src_chunk;
+ bsdiff::SuffixArrayIndexInterface** bsdiff_cache_ptr =
+ (src_chunk == nullptr) ? &bsdiff_cache : nullptr;
+
+ std::vector<uint8_t> patch_data;
+ if (!ImageChunk::MakePatch(tgt_chunk, src_ref, &patch_data, bsdiff_cache_ptr)) {
+ LOG(ERROR) << "Failed to generate patch, name: " << tgt_chunk.GetEntryName();
+ return false;
+ }
+
+ LOG(INFO) << "patch " << i << " is " << patch_data.size() << " bytes (of "
+ << tgt_chunk.GetRawDataLength() << ")";
+
+ if (PatchChunk::RawDataIsSmaller(tgt_chunk, patch_data.size())) {
+ patch_chunks->emplace_back(tgt_chunk);
+ } else {
+ patch_chunks->emplace_back(tgt_chunk, src_ref, std::move(patch_data));
+ }
+ }
+ delete bsdiff_cache;
+
+ CHECK_EQ(patch_chunks->size(), tgt_image.NumOfChunks());
+ return true;
+}
+
+bool ZipModeImage::GeneratePatches(const ZipModeImage& tgt_image, const ZipModeImage& src_image,
+ const std::string& patch_name) {
+ std::vector<PatchChunk> patch_chunks;
+
+ ZipModeImage::GeneratePatchesInternal(tgt_image, src_image, &patch_chunks);
+
+ CHECK_EQ(tgt_image.NumOfChunks(), patch_chunks.size());
+
+ android::base::unique_fd patch_fd(
+ open(patch_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
+ if (patch_fd == -1) {
+ PLOG(ERROR) << "Failed to open " << patch_name;
return false;
}
+ return PatchChunk::WritePatchDataToFd(patch_chunks, patch_fd);
+}
+
+bool ZipModeImage::GeneratePatches(const std::vector<ZipModeImage>& split_tgt_images,
+ const std::vector<ZipModeImage>& split_src_images,
+ const std::vector<SortedRangeSet>& split_src_ranges,
+ const std::string& patch_name,
+ const std::string& split_info_file,
+ const std::string& debug_dir) {
+ LOG(INFO) << "Constructing patches for " << split_tgt_images.size() << " split images...";
+
+ android::base::unique_fd patch_fd(
+ open(patch_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
+ if (patch_fd == -1) {
+ PLOG(ERROR) << "Failed to open " << patch_name;
+ return false;
+ }
+
+ std::vector<std::string> split_info_list;
+ for (size_t i = 0; i < split_tgt_images.size(); i++) {
+ std::vector<PatchChunk> patch_chunks;
+ if (!ZipModeImage::GeneratePatchesInternal(split_tgt_images[i], split_src_images[i],
+ &patch_chunks)) {
+ LOG(ERROR) << "Failed to generate split patch";
+ return false;
+ }
+
+ size_t total_patch_size = 12;
+ for (auto& p : patch_chunks) {
+ p.UpdateSourceOffset(split_src_ranges[i]);
+ total_patch_size += p.PatchSize();
+ }
+
+ if (!PatchChunk::WritePatchDataToFd(patch_chunks, patch_fd)) {
+ return false;
+ }
+
+ size_t split_tgt_size = split_tgt_images[i].chunks_.back().GetStartOffset() +
+ split_tgt_images[i].chunks_.back().GetRawDataLength() -
+ split_tgt_images[i].chunks_.front().GetStartOffset();
+ std::string split_info = android::base::StringPrintf(
+ "%zu %zu %s", total_patch_size, split_tgt_size, split_src_ranges[i].ToString().c_str());
+ split_info_list.push_back(split_info);
+
+ // Write the split source & patch into the debug directory.
+ if (!debug_dir.empty()) {
+ std::string src_name = android::base::StringPrintf("%s/src-%zu", debug_dir.c_str(), i);
+ android::base::unique_fd fd(
+ open(src_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
+
+ if (fd == -1) {
+ PLOG(ERROR) << "Failed to open " << src_name;
+ return false;
+ }
+ if (!android::base::WriteFully(fd, split_src_images[i].PseudoSource().DataForPatch(),
+ split_src_images[i].PseudoSource().DataLengthForPatch())) {
+ PLOG(ERROR) << "Failed to write split source data into " << src_name;
+ return false;
+ }
+
+ std::string patch_name = android::base::StringPrintf("%s/patch-%zu", debug_dir.c_str(), i);
+ fd.reset(open(patch_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
+
+ if (fd == -1) {
+ PLOG(ERROR) << "Failed to open " << patch_name;
+ return false;
+ }
+ if (!PatchChunk::WritePatchDataToFd(patch_chunks, fd)) {
+ return false;
+ }
+ }
+ }
+
+ // Store the split in the following format:
+ // Line 0: imgdiff version#
+ // Line 1: number of pieces
+ // Line 2: patch_size_1 tgt_size_1 src_range_1
+ // ...
+ // Line n+1: patch_size_n tgt_size_n src_range_n
+ std::string split_info_string = android::base::StringPrintf(
+ "%zu\n%zu\n", VERSION, split_info_list.size()) + android::base::Join(split_info_list, '\n');
+ if (!android::base::WriteStringToFile(split_info_string, split_info_file)) {
+ PLOG(ERROR) << "Failed to write split info to " << split_info_file;
+ return false;
+ }
+
+ return true;
+}
+
+bool ImageModeImage::Initialize(const std::string& filename) {
+ if (!ReadFile(filename, &file_content_)) {
+ return false;
+ }
+
+ size_t sz = file_content_.size();
size_t pos = 0;
-
while (pos < sz) {
// 0x00 no header flags, 0x08 deflate compression, 0x1f8b gzip magic number
- if (sz - pos >= 4 && get_unaligned<uint32_t>(img->data() + pos) == 0x00088b1f) {
+ if (sz - pos >= 4 && get_unaligned<uint32_t>(file_content_.data() + pos) == 0x00088b1f) {
// 'pos' is the offset of the start of a gzip chunk.
size_t chunk_offset = pos;
// The remaining data is too small to be a gzip chunk; treat them as a normal chunk.
if (sz - pos < GZIP_HEADER_LEN + GZIP_FOOTER_LEN) {
- chunks->emplace_back(CHUNK_NORMAL, pos, img, sz - pos);
+ chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, sz - pos);
break;
}
// We need three chunks for the deflated image in total, one normal chunk for the header,
// one deflated chunk for the body, and another normal chunk for the footer.
- chunks->emplace_back(CHUNK_NORMAL, pos, img, GZIP_HEADER_LEN);
+ chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, GZIP_HEADER_LEN);
pos += GZIP_HEADER_LEN;
// We must decompress this chunk in order to discover where it ends, and so we can update
@@ -657,13 +1275,13 @@
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = sz - pos;
- strm.next_in = img->data() + pos;
+ strm.next_in = file_content_.data() + pos;
// -15 means we are decoding a 'raw' deflate stream; zlib will
// not expect zlib headers.
int ret = inflateInit2(&strm, -15);
if (ret < 0) {
- printf("failed to initialize inflate: %d\n", ret);
+ LOG(ERROR) << "Failed to initialize inflate: " << ret;
return false;
}
@@ -675,8 +1293,8 @@
strm.next_out = uncompressed_data.data() + uncompressed_len;
ret = inflate(&strm, Z_NO_FLUSH);
if (ret < 0) {
- printf("Warning: inflate failed [%s] at offset [%zu], treating as a normal chunk\n",
- strm.msg, chunk_offset);
+ LOG(WARNING) << "Inflate failed [" << strm.msg << "] at offset [" << chunk_offset
+ << "]; treating as a normal chunk";
break;
}
uncompressed_len = allocated - strm.avail_out;
@@ -697,25 +1315,25 @@
// matches the size of the data we got when we actually did the decompression.
size_t footer_index = pos + raw_data_len + GZIP_FOOTER_LEN - 4;
if (sz - footer_index < 4) {
- printf("Warning: invalid footer position; treating as a nomal chunk\n");
+ LOG(WARNING) << "invalid footer position; treating as a normal chunk";
continue;
}
- size_t footer_size = get_unaligned<uint32_t>(img->data() + footer_index);
+ size_t footer_size = get_unaligned<uint32_t>(file_content_.data() + footer_index);
if (footer_size != uncompressed_len) {
- printf("Warning: footer size %zu != decompressed size %zu; treating as a nomal chunk\n",
- footer_size, uncompressed_len);
+ LOG(WARNING) << "footer size " << footer_size << " != " << uncompressed_len
+ << "; treating as a normal chunk";
continue;
}
- ImageChunk body(CHUNK_DEFLATE, pos, img, raw_data_len);
+ ImageChunk body(CHUNK_DEFLATE, pos, &file_content_, raw_data_len);
uncompressed_data.resize(uncompressed_len);
body.SetUncompressedData(std::move(uncompressed_data));
- chunks->push_back(body);
+ chunks_.push_back(std::move(body));
pos += raw_data_len;
// create a normal chunk for the footer
- chunks->emplace_back(CHUNK_NORMAL, pos, img, GZIP_FOOTER_LEN);
+ chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, GZIP_FOOTER_LEN);
pos += GZIP_FOOTER_LEN;
} else {
@@ -726,12 +1344,12 @@
size_t data_len = 0;
while (data_len + pos < sz) {
if (data_len + pos + 4 <= sz &&
- get_unaligned<uint32_t>(img->data() + pos + data_len) == 0x00088b1f) {
+ get_unaligned<uint32_t>(file_content_.data() + pos + data_len) == 0x00088b1f) {
break;
}
data_len++;
}
- chunks->emplace_back(CHUNK_NORMAL, pos, img, data_len);
+ chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, data_len);
pos += data_len;
}
@@ -740,346 +1358,256 @@
return true;
}
-/*
- * Given source and target chunks, compute a bsdiff patch between them.
- * Store the result in the patch_data.
- * |bsdiff_cache| can be used to cache the suffix array if the same |src| chunk
- * is used repeatedly, pass nullptr if not needed.
- */
-static bool MakePatch(const ImageChunk* src, ImageChunk* tgt, std::vector<uint8_t>* patch_data,
- saidx_t** bsdiff_cache) {
- if (tgt->ChangeChunkToRaw(0)) {
- size_t patch_size = tgt->DataLengthForPatch();
- patch_data->resize(patch_size);
- std::copy(tgt->DataForPatch(), tgt->DataForPatch() + patch_size, patch_data->begin());
- return true;
- }
-
-#if defined(__ANDROID__)
- char ptemp[] = "/data/local/tmp/imgdiff-patch-XXXXXX";
-#else
- char ptemp[] = "/tmp/imgdiff-patch-XXXXXX";
-#endif
-
- int fd = mkstemp(ptemp);
- if (fd == -1) {
- printf("MakePatch failed to create a temporary file: %s\n", strerror(errno));
- return false;
- }
- close(fd);
-
- int r = bsdiff::bsdiff(src->DataForPatch(), src->DataLengthForPatch(), tgt->DataForPatch(),
- tgt->DataLengthForPatch(), ptemp, bsdiff_cache);
- if (r != 0) {
- printf("bsdiff() failed: %d\n", r);
+bool ImageModeImage::SetBonusData(const std::vector<uint8_t>& bonus_data) {
+ CHECK(is_source_);
+ if (chunks_.size() < 2 || !chunks_[1].SetBonusData(bonus_data)) {
+ LOG(ERROR) << "Failed to set bonus data";
+ DumpChunks();
return false;
}
- android::base::unique_fd patch_fd(open(ptemp, O_RDONLY));
- if (patch_fd == -1) {
- printf("failed to open %s: %s\n", ptemp, strerror(errno));
+ LOG(INFO) << " using " << bonus_data.size() << " bytes of bonus data";
+ return true;
+}
+
+// In Image Mode, verify that the source and target images have the same chunk structure (ie, the
+// same sequence of deflate and normal chunks).
+bool ImageModeImage::CheckAndProcessChunks(ImageModeImage* tgt_image, ImageModeImage* src_image) {
+ // In image mode, merge the gzip header and footer in with any adjacent normal chunks.
+ tgt_image->MergeAdjacentNormalChunks();
+ src_image->MergeAdjacentNormalChunks();
+
+ if (tgt_image->NumOfChunks() != src_image->NumOfChunks()) {
+ LOG(ERROR) << "Source and target don't have same number of chunks!";
+ tgt_image->DumpChunks();
+ src_image->DumpChunks();
return false;
}
- struct stat st;
- if (fstat(patch_fd, &st) != 0) {
- printf("failed to stat patch file %s: %s\n", ptemp, strerror(errno));
- return false;
+ for (size_t i = 0; i < tgt_image->NumOfChunks(); ++i) {
+ if ((*tgt_image)[i].GetType() != (*src_image)[i].GetType()) {
+ LOG(ERROR) << "Source and target don't have same chunk structure! (chunk " << i << ")";
+ tgt_image->DumpChunks();
+ src_image->DumpChunks();
+ return false;
+ }
}
- size_t sz = static_cast<size_t>(st.st_size);
- // Change the chunk type to raw if the patch takes less space that way.
- if (tgt->ChangeChunkToRaw(sz)) {
- unlink(ptemp);
- size_t patch_size = tgt->DataLengthForPatch();
- patch_data->resize(patch_size);
- std::copy(tgt->DataForPatch(), tgt->DataForPatch() + patch_size, patch_data->begin());
- return true;
- }
- patch_data->resize(sz);
- if (!android::base::ReadFully(patch_fd, patch_data->data(), sz)) {
- printf("failed to read \"%s\" %s\n", ptemp, strerror(errno));
- return false;
+ for (size_t i = 0; i < tgt_image->NumOfChunks(); ++i) {
+ auto& tgt_chunk = (*tgt_image)[i];
+ auto& src_chunk = (*src_image)[i];
+ if (tgt_chunk.GetType() != CHUNK_DEFLATE) {
+ continue;
+ }
+
+ // If two deflate chunks are identical treat them as normal chunks.
+ if (tgt_chunk == src_chunk) {
+ tgt_chunk.ChangeDeflateChunkToNormal();
+ src_chunk.ChangeDeflateChunkToNormal();
+ } else if (!tgt_chunk.ReconstructDeflateChunk()) {
+ // We cannot recompress the data and get exactly the same bits as are in the input target
+ // image, fall back to normal
+ LOG(WARNING) << "Failed to reconstruct target deflate chunk " << i << " ["
+ << tgt_chunk.GetEntryName() << "]; treating as normal";
+ tgt_chunk.ChangeDeflateChunkToNormal();
+ src_chunk.ChangeDeflateChunkToNormal();
+ }
}
- unlink(ptemp);
- tgt->SetSourceInfo(*src);
+ // For images, we need to maintain the parallel structure of the chunk lists, so do the merging
+ // in both the source and target lists.
+ tgt_image->MergeAdjacentNormalChunks();
+ src_image->MergeAdjacentNormalChunks();
+ if (tgt_image->NumOfChunks() != src_image->NumOfChunks()) {
+ // This shouldn't happen.
+ LOG(ERROR) << "Merging normal chunks went awry";
+ return false;
+ }
return true;
}
-/*
- * Look for runs of adjacent normal chunks and compress them down into
- * a single chunk. (Such runs can be produced when deflate chunks are
- * changed to normal chunks.)
- */
-static void MergeAdjacentNormalChunks(std::vector<ImageChunk>* chunks) {
- size_t merged_last = 0, cur = 0;
- while (cur < chunks->size()) {
- // Look for normal chunks adjacent to the current one. If such chunk exists, extend the
- // length of the current normal chunk.
- size_t to_check = cur + 1;
- while (to_check < chunks->size() && chunks->at(cur).IsAdjacentNormal(chunks->at(to_check))) {
- chunks->at(cur).MergeAdjacentNormal(chunks->at(to_check));
- to_check++;
+// In image mode, generate patches against the given source chunks and bonus_data; write the
+// result to |patch_name|.
+bool ImageModeImage::GeneratePatches(const ImageModeImage& tgt_image,
+ const ImageModeImage& src_image,
+ const std::string& patch_name) {
+ LOG(INFO) << "Constructing patches for " << tgt_image.NumOfChunks() << " chunks...";
+ std::vector<PatchChunk> patch_chunks;
+ patch_chunks.reserve(tgt_image.NumOfChunks());
+
+ for (size_t i = 0; i < tgt_image.NumOfChunks(); i++) {
+ const auto& tgt_chunk = tgt_image[i];
+ const auto& src_chunk = src_image[i];
+
+ if (PatchChunk::RawDataIsSmaller(tgt_chunk, 0)) {
+ patch_chunks.emplace_back(tgt_chunk);
+ continue;
}
- if (merged_last != cur) {
- chunks->at(merged_last) = std::move(chunks->at(cur));
+ std::vector<uint8_t> patch_data;
+ if (!ImageChunk::MakePatch(tgt_chunk, src_chunk, &patch_data, nullptr)) {
+ LOG(ERROR) << "Failed to generate patch for target chunk " << i;
+ return false;
}
- merged_last++;
- cur = to_check;
- }
- if (merged_last < chunks->size()) {
- chunks->erase(chunks->begin() + merged_last, chunks->end());
- }
-}
+ LOG(INFO) << "patch " << i << " is " << patch_data.size() << " bytes (of "
+ << tgt_chunk.GetRawDataLength() << ")";
-static ImageChunk* FindChunkByName(const std::string& name, std::vector<ImageChunk>& chunks) {
- for (size_t i = 0; i < chunks.size(); ++i) {
- if (chunks[i].GetType() == CHUNK_DEFLATE && chunks[i].GetEntryName() == name) {
- return &chunks[i];
+ if (PatchChunk::RawDataIsSmaller(tgt_chunk, patch_data.size())) {
+ patch_chunks.emplace_back(tgt_chunk);
+ } else {
+ patch_chunks.emplace_back(tgt_chunk, src_chunk, std::move(patch_data));
}
}
- return nullptr;
-}
-static void DumpChunks(const std::vector<ImageChunk>& chunks) {
- for (size_t i = 0; i < chunks.size(); ++i) {
- printf("chunk %zu: ", i);
- chunks[i].Dump();
+ CHECK_EQ(tgt_image.NumOfChunks(), patch_chunks.size());
+
+ android::base::unique_fd patch_fd(
+ open(patch_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
+ if (patch_fd == -1) {
+ PLOG(ERROR) << "Failed to open " << patch_name;
+ return false;
}
+
+ return PatchChunk::WritePatchDataToFd(patch_chunks, patch_fd);
}
int imgdiff(int argc, const char** argv) {
+ bool verbose = false;
bool zip_mode = false;
-
- if (argc >= 2 && strcmp(argv[1], "-z") == 0) {
- zip_mode = true;
- --argc;
- ++argv;
- }
-
std::vector<uint8_t> bonus_data;
- if (argc >= 3 && strcmp(argv[1], "-b") == 0) {
- android::base::unique_fd fd(open(argv[2], O_RDONLY));
- if (fd == -1) {
- printf("failed to open bonus file %s: %s\n", argv[2], strerror(errno));
- return 1;
- }
- struct stat st;
- if (fstat(fd, &st) != 0) {
- printf("failed to stat bonus file %s: %s\n", argv[2], strerror(errno));
- return 1;
- }
+ size_t blocks_limit = 0;
+ std::string split_info_file;
+ std::string debug_dir;
- size_t bonus_size = st.st_size;
- bonus_data.resize(bonus_size);
- if (!android::base::ReadFully(fd, bonus_data.data(), bonus_size)) {
- printf("failed to read bonus file %s: %s\n", argv[2], strerror(errno));
- return 1;
- }
+ int opt;
+ int option_index;
+ optind = 0; // Reset the getopt state so that we can call it multiple times for test.
- argc -= 2;
- argv += 2;
+ while ((opt = getopt_long(argc, const_cast<char**>(argv), "zb:v", OPTIONS, &option_index)) !=
+ -1) {
+ switch (opt) {
+ case 'z':
+ zip_mode = true;
+ break;
+ case 'b': {
+ android::base::unique_fd fd(open(optarg, O_RDONLY));
+ if (fd == -1) {
+ PLOG(ERROR) << "Failed to open bonus file " << optarg;
+ return 1;
+ }
+ struct stat st;
+ if (fstat(fd, &st) != 0) {
+ PLOG(ERROR) << "Failed to stat bonus file " << optarg;
+ return 1;
+ }
+
+ size_t bonus_size = st.st_size;
+ bonus_data.resize(bonus_size);
+ if (!android::base::ReadFully(fd, bonus_data.data(), bonus_size)) {
+ PLOG(ERROR) << "Failed to read bonus file " << optarg;
+ return 1;
+ }
+ break;
+ }
+ case 'v':
+ verbose = true;
+ break;
+ case 0: {
+ std::string name = OPTIONS[option_index].name;
+ if (name == "block-limit" && !android::base::ParseUint(optarg, &blocks_limit)) {
+ LOG(ERROR) << "Failed to parse size blocks_limit: " << optarg;
+ return 1;
+ } else if (name == "split-info") {
+ split_info_file = optarg;
+ } else if (name == "debug-dir") {
+ debug_dir = optarg;
+ }
+ break;
+ }
+ default:
+ LOG(ERROR) << "unexpected opt: " << static_cast<char>(opt);
+ return 2;
+ }
}
- if (argc != 4) {
- printf("usage: %s [-z] [-b <bonus-file>] <src-img> <tgt-img> <patch-file>\n",
- argv[0]);
+ if (!verbose) {
+ android::base::SetMinimumLogSeverity(android::base::WARNING);
+ }
+
+ if (argc - optind != 3) {
+ LOG(ERROR) << "usage: " << argv[0] << " [options] <src-img> <tgt-img> <patch-file>";
+ LOG(ERROR)
+ << " -z <zip-mode>, Generate patches in zip mode, src and tgt should be zip files.\n"
+ " -b <bonus-file>, Bonus file in addition to src, image mode only.\n"
+ " --block-limit, For large zips, split the src and tgt based on the block limit;\n"
+ " and generate patches between each pair of pieces. Concatenate "
+ "these\n"
+ " patches together and output them into <patch-file>.\n"
+ " --split-info, Output the split information (patch_size, tgt_size, src_ranges);\n"
+ " zip mode with block-limit only.\n"
+ " --debug-dir, Debug directory to put the split srcs and patches, zip mode only.\n"
+ " -v, --verbose, Enable verbose logging.";
return 2;
}
- std::vector<ImageChunk> src_chunks;
- std::vector<ImageChunk> tgt_chunks;
- std::vector<uint8_t> src_file;
- std::vector<uint8_t> tgt_file;
-
if (zip_mode) {
- if (!ReadZip(argv[1], &src_chunks, &src_file, true)) {
- printf("failed to break apart source zip file\n");
+ ZipModeImage src_image(true, blocks_limit * BLOCK_SIZE);
+ ZipModeImage tgt_image(false, blocks_limit * BLOCK_SIZE);
+
+ if (!src_image.Initialize(argv[optind])) {
return 1;
}
- if (!ReadZip(argv[2], &tgt_chunks, &tgt_file, false)) {
- printf("failed to break apart target zip file\n");
+ if (!tgt_image.Initialize(argv[optind + 1])) {
+ return 1;
+ }
+
+ if (!ZipModeImage::CheckAndProcessChunks(&tgt_image, &src_image)) {
+ return 1;
+ }
+
+ // Compute bsdiff patches for each chunk's data (the uncompressed data, in the case of
+ // deflate chunks).
+ if (blocks_limit > 0) {
+ if (split_info_file.empty()) {
+ LOG(ERROR) << "split-info path cannot be empty when generating patches with a block-limit";
+ return 1;
+ }
+
+ std::vector<ZipModeImage> split_tgt_images;
+ std::vector<ZipModeImage> split_src_images;
+ std::vector<SortedRangeSet> split_src_ranges;
+ ZipModeImage::SplitZipModeImageWithLimit(tgt_image, src_image, &split_tgt_images,
+ &split_src_images, &split_src_ranges);
+
+ if (!ZipModeImage::GeneratePatches(split_tgt_images, split_src_images, split_src_ranges,
+ argv[optind + 2], split_info_file, debug_dir)) {
+ return 1;
+ }
+
+ } else if (!ZipModeImage::GeneratePatches(tgt_image, src_image, argv[optind + 2])) {
return 1;
}
} else {
- if (!ReadImage(argv[1], &src_chunks, &src_file)) {
- printf("failed to break apart source image\n");
+ ImageModeImage src_image(true);
+ ImageModeImage tgt_image(false);
+
+ if (!src_image.Initialize(argv[optind])) {
return 1;
}
- if (!ReadImage(argv[2], &tgt_chunks, &tgt_file)) {
- printf("failed to break apart target image\n");
+ if (!tgt_image.Initialize(argv[optind + 1])) {
return 1;
}
- // Verify that the source and target images have the same chunk
- // structure (ie, the same sequence of deflate and normal chunks).
-
- // Merge the gzip header and footer in with any adjacent normal chunks.
- MergeAdjacentNormalChunks(&tgt_chunks);
- MergeAdjacentNormalChunks(&src_chunks);
-
- if (src_chunks.size() != tgt_chunks.size()) {
- printf("source and target don't have same number of chunks!\n");
- printf("source chunks:\n");
- DumpChunks(src_chunks);
- printf("target chunks:\n");
- DumpChunks(tgt_chunks);
+ if (!ImageModeImage::CheckAndProcessChunks(&tgt_image, &src_image)) {
return 1;
}
- for (size_t i = 0; i < src_chunks.size(); ++i) {
- if (src_chunks[i].GetType() != tgt_chunks[i].GetType()) {
- printf("source and target don't have same chunk structure! (chunk %zu)\n", i);
- printf("source chunks:\n");
- DumpChunks(src_chunks);
- printf("target chunks:\n");
- DumpChunks(tgt_chunks);
- return 1;
- }
- }
- }
- for (size_t i = 0; i < tgt_chunks.size(); ++i) {
- if (tgt_chunks[i].GetType() == CHUNK_DEFLATE) {
- // Confirm that given the uncompressed chunk data in the target, we
- // can recompress it and get exactly the same bits as are in the
- // input target image. If this fails, treat the chunk as a normal
- // non-deflated chunk.
- if (!tgt_chunks[i].ReconstructDeflateChunk()) {
- printf("failed to reconstruct target deflate chunk %zu [%s]; treating as normal\n", i,
- tgt_chunks[i].GetEntryName().c_str());
- tgt_chunks[i].ChangeDeflateChunkToNormal();
- if (zip_mode) {
- ImageChunk* src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks);
- if (src != nullptr) {
- src->ChangeDeflateChunkToNormal();
- }
- } else {
- src_chunks[i].ChangeDeflateChunkToNormal();
- }
- continue;
- }
-
- // If two deflate chunks are identical (eg, the kernel has not
- // changed between two builds), treat them as normal chunks.
- // This makes applypatch much faster -- it can apply a trivial
- // patch to the compressed data, rather than uncompressing and
- // recompressing to apply the trivial patch to the uncompressed
- // data.
- ImageChunk* src;
- if (zip_mode) {
- src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks);
- } else {
- src = &src_chunks[i];
- }
-
- if (src == nullptr) {
- tgt_chunks[i].ChangeDeflateChunkToNormal();
- } else if (tgt_chunks[i] == *src) {
- tgt_chunks[i].ChangeDeflateChunkToNormal();
- src->ChangeDeflateChunkToNormal();
- }
- }
- }
-
- // Merging neighboring normal chunks.
- if (zip_mode) {
- // For zips, we only need to do this to the target: deflated
- // chunks are matched via filename, and normal chunks are patched
- // using the entire source file as the source.
- MergeAdjacentNormalChunks(&tgt_chunks);
-
- } else {
- // For images, we need to maintain the parallel structure of the
- // chunk lists, so do the merging in both the source and target
- // lists.
- MergeAdjacentNormalChunks(&tgt_chunks);
- MergeAdjacentNormalChunks(&src_chunks);
- if (src_chunks.size() != tgt_chunks.size()) {
- // This shouldn't happen.
- printf("merging normal chunks went awry\n");
+ if (!bonus_data.empty() && !src_image.SetBonusData(bonus_data)) {
return 1;
}
- }
- // Compute bsdiff patches for each chunk's data (the uncompressed
- // data, in the case of deflate chunks).
-
- DumpChunks(src_chunks);
-
- printf("Construct patches for %zu chunks...\n", tgt_chunks.size());
- std::vector<std::vector<uint8_t>> patch_data(tgt_chunks.size());
- saidx_t* bsdiff_cache = nullptr;
- for (size_t i = 0; i < tgt_chunks.size(); ++i) {
- if (zip_mode) {
- ImageChunk* src;
- if (tgt_chunks[i].GetType() == CHUNK_DEFLATE &&
- (src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks))) {
- if (!MakePatch(src, &tgt_chunks[i], &patch_data[i], nullptr)) {
- printf("Failed to generate patch for target chunk %zu: ", i);
- return 1;
- }
- } else {
- if (!MakePatch(&src_chunks[0], &tgt_chunks[i], &patch_data[i], &bsdiff_cache)) {
- printf("Failed to generate patch for target chunk %zu: ", i);
- return 1;
- }
- }
- } else {
- if (i == 1 && !bonus_data.empty()) {
- printf(" using %zu bytes of bonus data for chunk %zu\n", bonus_data.size(), i);
- src_chunks[i].SetBonusData(bonus_data);
- }
-
- if (!MakePatch(&src_chunks[i], &tgt_chunks[i], &patch_data[i], nullptr)) {
- printf("Failed to generate patch for target chunk %zu: ", i);
- return 1;
- }
- }
- printf("patch %3zu is %zu bytes (of %zu)\n", i, patch_data[i].size(),
- src_chunks[i].GetRawDataLength());
- }
-
- if (bsdiff_cache != nullptr) {
- free(bsdiff_cache);
- }
-
- // Figure out how big the imgdiff file header is going to be, so
- // that we can correctly compute the offset of each bsdiff patch
- // within the file.
-
- size_t total_header_size = 12;
- for (size_t i = 0; i < tgt_chunks.size(); ++i) {
- total_header_size += tgt_chunks[i].GetHeaderSize(patch_data[i].size());
- }
-
- size_t offset = total_header_size;
-
- android::base::unique_fd patch_fd(open(argv[3], O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
- if (patch_fd == -1) {
- printf("failed to open \"%s\": %s\n", argv[3], strerror(errno));
- return 1;
- }
-
- // Write out the headers.
- if (!android::base::WriteStringToFd("IMGDIFF2", patch_fd)) {
- printf("failed to write \"IMGDIFF2\" to \"%s\": %s\n", argv[3], strerror(errno));
- return 1;
- }
- Write4(patch_fd, static_cast<int32_t>(tgt_chunks.size()));
- for (size_t i = 0; i < tgt_chunks.size(); ++i) {
- printf("chunk %zu: ", i);
- offset = tgt_chunks[i].WriteHeaderToFd(patch_fd, patch_data[i], offset);
- }
-
- // Append each chunk's bsdiff patch, in order.
- for (size_t i = 0; i < tgt_chunks.size(); ++i) {
- if (tgt_chunks[i].GetType() != CHUNK_RAW) {
- if (!android::base::WriteFully(patch_fd, patch_data[i].data(), patch_data[i].size())) {
- CHECK(false) << "failed to write " << patch_data[i].size() << " bytes patch for chunk "
- << i;
- }
+ if (!ImageModeImage::GeneratePatches(tgt_image, src_image, argv[optind + 2])) {
+ return 1;
}
}
diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp
index df75f98..3682d61 100644
--- a/applypatch/imgpatch.cpp
+++ b/applypatch/imgpatch.cpp
@@ -37,6 +37,8 @@
#include <openssl/sha.h>
#include <zlib.h>
+#include "edify/expr.h"
+
static inline int64_t Read8(const void *address) {
return android::base::get_unaligned<int64_t>(address);
}
@@ -48,7 +50,7 @@
// This function is a wrapper of ApplyBSDiffPatch(). It has a custom sink function to deflate the
// 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 Value& patch, size_t patch_offset,
const char* deflate_header, SinkFn sink, SHA_CTX* ctx) {
size_t expected_target_length = static_cast<size_t>(Read8(deflate_header + 32));
int level = Read4(deflate_header + 40);
@@ -57,13 +59,13 @@
int mem_level = Read4(deflate_header + 52);
int strategy = Read4(deflate_header + 56);
- std::unique_ptr<z_stream, decltype(&deflateEnd)> strm(new z_stream(), deflateEnd);
- strm->zalloc = Z_NULL;
- strm->zfree = Z_NULL;
- strm->opaque = Z_NULL;
- strm->avail_in = 0;
- strm->next_in = nullptr;
- int ret = deflateInit2(strm.get(), level, method, window_bits, mem_level, strategy);
+ z_stream strm;
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.avail_in = 0;
+ strm.next_in = nullptr;
+ int ret = deflateInit2(&strm, level, method, window_bits, mem_level, strategy);
if (ret != Z_OK) {
LOG(ERROR) << "Failed to init uncompressed data deflation: " << ret;
return false;
@@ -74,18 +76,19 @@
size_t actual_target_length = 0;
size_t total_written = 0;
static constexpr size_t buffer_size = 32768;
- auto compression_sink = [&](const uint8_t* data, size_t len) -> size_t {
+ auto compression_sink = [&strm, &actual_target_length, &expected_target_length, &total_written,
+ &ret, &ctx, &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;
+ strm.avail_in = len;
+ strm.next_in = data;
do {
std::vector<uint8_t> buffer(buffer_size);
- strm->avail_out = buffer_size;
- strm->next_out = buffer.data();
+ strm.avail_out = buffer_size;
+ strm.next_out = buffer.data();
if (actual_target_length + len < expected_target_length) {
- ret = deflate(strm.get(), Z_NO_FLUSH);
+ ret = deflate(&strm, Z_NO_FLUSH);
} else {
- ret = deflate(strm.get(), Z_FINISH);
+ ret = deflate(&strm, Z_FINISH);
}
if (ret != Z_OK && ret != Z_STREAM_END) {
LOG(ERROR) << "Failed to deflate stream: " << ret;
@@ -93,20 +96,24 @@
return 0;
}
- size_t have = buffer_size - strm->avail_out;
+ size_t have = buffer_size - strm.avail_out;
total_written += have;
if (sink(buffer.data(), have) != have) {
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);
+ } while ((strm.avail_in != 0 || strm.avail_out == 0) && ret != Z_STREAM_END);
actual_target_length += len;
return len;
};
- if (ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink, nullptr) != 0) {
+ int bspatch_result =
+ ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink, nullptr);
+ deflateEnd(&strm);
+
+ if (bspatch_result != 0) {
return false;
}
@@ -128,48 +135,39 @@
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);
+ return ApplyImagePatch(old_data, old_size, patch, sink, nullptr, nullptr);
}
-/*
- * Apply the patch given in 'patch_filename' to the source data given
- * by (old_data, old_size). Write the patched output to the 'output'
- * file, and update the SHA context with the output data as well.
- * Return 0 on success.
- */
-int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value* patch, SinkFn sink,
+int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& patch, SinkFn sink,
SHA_CTX* ctx, const Value* bonus_data) {
- if (patch->data.size() < 12) {
+ if (patch.data.size() < 12) {
printf("patch too short to contain header\n");
return -1;
}
- // IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW.
- // (IMGDIFF1, which is no longer supported, used CHUNK_NORMAL and
- // CHUNK_GZIP.)
- size_t pos = 12;
- const char* header = &patch->data[0];
- if (memcmp(header, "IMGDIFF2", 8) != 0) {
+ // IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW. (IMGDIFF1, which is no longer
+ // supported, used CHUNK_NORMAL and CHUNK_GZIP.)
+ const char* const patch_header = patch.data.data();
+ if (memcmp(patch_header, "IMGDIFF2", 8) != 0) {
printf("corrupt patch file header (magic number)\n");
return -1;
}
- int num_chunks = Read4(header + 8);
-
+ int num_chunks = Read4(patch_header + 8);
+ size_t pos = 12;
for (int i = 0; i < num_chunks; ++i) {
// each chunk's header record starts with 4 bytes.
- if (pos + 4 > patch->data.size()) {
+ if (pos + 4 > patch.data.size()) {
printf("failed to read chunk %d record\n", i);
return -1;
}
- int type = Read4(&patch->data[pos]);
+ int type = Read4(patch_header + pos);
pos += 4;
if (type == CHUNK_NORMAL) {
- const char* normal_header = &patch->data[pos];
+ const char* normal_header = patch_header + pos;
pos += 24;
- if (pos > patch->data.size()) {
+ if (pos > patch.data.size()) {
printf("failed to read chunk %d normal header data\n", i);
return -1;
}
@@ -187,30 +185,32 @@
return -1;
}
} else if (type == CHUNK_RAW) {
- const char* raw_header = &patch->data[pos];
+ const char* raw_header = patch_header + pos;
pos += 4;
- if (pos > patch->data.size()) {
+ if (pos > patch.data.size()) {
printf("failed to read chunk %d raw header data\n", i);
return -1;
}
size_t data_len = static_cast<size_t>(Read4(raw_header));
- if (pos + data_len > patch->data.size()) {
+ if (pos + data_len > patch.data.size()) {
printf("failed to read chunk %d raw data\n", i);
return -1;
}
- if (ctx) SHA1_Update(ctx, &patch->data[pos], data_len);
- if (sink(reinterpret_cast<const unsigned char*>(&patch->data[pos]), data_len) != data_len) {
+ 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;
} else if (type == CHUNK_DEFLATE) {
// deflate chunks have an additional 60 bytes in their chunk header.
- const char* deflate_header = &patch->data[pos];
+ const char* deflate_header = patch_header + pos;
pos += 60;
- if (pos > patch->data.size()) {
+ if (pos > patch.data.size()) {
printf("failed to read chunk %d deflate header data\n", i);
return -1;
}
diff --git a/applypatch/include/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h
index 581360e..912ead1 100644
--- a/applypatch/include/applypatch/applypatch.h
+++ b/applypatch/include/applypatch/applypatch.h
@@ -18,7 +18,6 @@
#define _APPLYPATCH_H
#include <stdint.h>
-#include <sys/stat.h>
#include <functional>
#include <memory>
@@ -27,24 +26,18 @@
#include <openssl/sha.h>
-#include "edify/expr.h"
+// Forward declaration to avoid including "edify/expr.h" in the header.
+struct Value;
struct FileContents {
uint8_t sha1[SHA_DIGEST_LENGTH];
std::vector<unsigned char> data;
- struct stat st;
};
-// 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 this file contains the bits we want
-// and use it as the source instead.
-#define CACHE_TEMP_SOURCE "/cache/saved.file"
-
using SinkFn = std::function<size_t(const unsigned char*, size_t)>;
// applypatch.cpp
+
int ShowLicenses();
size_t FreeSpaceForFile(const char* filename);
int CacheSizeCheck(size_t bytes);
@@ -66,15 +59,25 @@
int SaveFileContents(const char* filename, const FileContents* file);
// bspatch.cpp
+
void ShowBSDiffLicense();
-int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value* patch,
+
+// 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.
+int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value& patch,
size_t patch_offset, SinkFn sink, SHA_CTX* ctx);
// imgpatch.cpp
-int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value* patch, SinkFn sink,
+
+// 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.
+int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& patch, SinkFn sink,
SHA_CTX* ctx, const Value* bonus_data);
// freecache.cpp
+
int MakeFreeSpaceOnCache(size_t bytes_needed);
#endif
diff --git a/applypatch/include/applypatch/imgdiff_image.h b/applypatch/include/applypatch/imgdiff_image.h
new file mode 100644
index 0000000..0848072
--- /dev/null
+++ b/applypatch/include/applypatch/imgdiff_image.h
@@ -0,0 +1,306 @@
+/*
+ * 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.
+ */
+
+#ifndef _APPLYPATCH_IMGDIFF_IMAGE_H
+#define _APPLYPATCH_IMGDIFF_IMAGE_H
+
+#include <stddef.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#include <bsdiff/bsdiff.h>
+#include <ziparchive/zip_archive.h>
+#include <zlib.h>
+
+#include "imgdiff.h"
+#include "otautil/rangeset.h"
+
+class ImageChunk {
+ public:
+ static constexpr auto WINDOWBITS = -15; // 32kb window; negative to indicate a raw stream.
+ static constexpr auto MEMLEVEL = 8; // the default value.
+ static constexpr auto METHOD = Z_DEFLATED;
+ static constexpr auto STRATEGY = Z_DEFAULT_STRATEGY;
+
+ ImageChunk(int type, size_t start, const std::vector<uint8_t>* file_content, size_t raw_data_len,
+ std::string entry_name = {});
+
+ int GetType() const {
+ return type_;
+ }
+ size_t GetRawDataLength() const {
+ return raw_data_len_;
+ }
+ const std::string& GetEntryName() const {
+ return entry_name_;
+ }
+ size_t GetStartOffset() const {
+ return start_;
+ }
+ int GetCompressLevel() const {
+ return compress_level_;
+ }
+
+ // CHUNK_DEFLATE will return the uncompressed data for diff, while other types will simply return
+ // the raw data.
+ const uint8_t* DataForPatch() const;
+ size_t DataLengthForPatch() const;
+
+ void Dump(size_t index) const;
+
+ void SetUncompressedData(std::vector<uint8_t> data);
+ bool SetBonusData(const std::vector<uint8_t>& bonus_data);
+
+ bool operator==(const ImageChunk& other) const;
+ bool operator!=(const ImageChunk& other) const {
+ return !(*this == other);
+ }
+
+ /*
+ * Cause a gzip chunk to be treated as a normal chunk (ie, as a blob of uninterpreted data).
+ * The resulting patch will likely be about as big as the target file, but it lets us handle
+ * the case of images where some gzip chunks are reconstructible but others aren't (by treating
+ * the ones that aren't as normal chunks).
+ */
+ void ChangeDeflateChunkToNormal();
+
+ /*
+ * Verify that we can reproduce exactly the same compressed data that we started with. Sets the
+ * level, method, windowBits, memLevel, and strategy fields in the chunk to the encoding
+ * parameters needed to produce the right output.
+ */
+ bool ReconstructDeflateChunk();
+ bool IsAdjacentNormal(const ImageChunk& other) const;
+ void MergeAdjacentNormal(const ImageChunk& other);
+
+ /*
+ * Compute a bsdiff patch between |src| and |tgt|; Store the result in the patch_data.
+ * |bsdiff_cache| can be used to cache the suffix array if the same |src| chunk is used
+ * repeatedly, pass nullptr if not needed.
+ */
+ static bool MakePatch(const ImageChunk& tgt, const ImageChunk& src,
+ std::vector<uint8_t>* patch_data,
+ bsdiff::SuffixArrayIndexInterface** bsdiff_cache);
+
+ private:
+ const uint8_t* GetRawData() const;
+ bool TryReconstruction(int level);
+
+ int type_; // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW
+ size_t start_; // offset of chunk in the original input file
+ const std::vector<uint8_t>* input_file_ptr_; // ptr to the full content of original input file
+ size_t raw_data_len_;
+
+ // deflate encoder parameters
+ int compress_level_;
+
+ // --- for CHUNK_DEFLATE chunks only: ---
+ std::vector<uint8_t> uncompressed_data_;
+ std::string entry_name_; // used for zip entries
+};
+
+// PatchChunk stores the patch data between a source chunk and a target chunk. It also keeps track
+// of the metadata of src&tgt chunks (e.g. offset, raw data length, uncompressed data length).
+class PatchChunk {
+ public:
+ PatchChunk(const ImageChunk& tgt, const ImageChunk& src, std::vector<uint8_t> data);
+
+ // Construct a CHUNK_RAW patch from the target data directly.
+ explicit PatchChunk(const ImageChunk& tgt);
+
+ // Return true if raw data size is smaller than the patch size.
+ static bool RawDataIsSmaller(const ImageChunk& tgt, size_t patch_size);
+
+ // Update the source start with the new offset within the source range.
+ void UpdateSourceOffset(const SortedRangeSet& src_range);
+
+ // Return the total size (header + data) of the patch.
+ size_t PatchSize() const;
+
+ static bool WritePatchDataToFd(const std::vector<PatchChunk>& patch_chunks, int patch_fd);
+
+ private:
+ size_t GetHeaderSize() const;
+ size_t WriteHeaderToFd(int fd, size_t offset, size_t index) const;
+
+ // The patch chunk type is the same as the target chunk type. The only exception is we change
+ // the |type_| to CHUNK_RAW if target length is smaller than the patch size.
+ int type_;
+
+ size_t source_start_;
+ size_t source_len_;
+ size_t source_uncompressed_len_;
+
+ size_t target_start_; // offset of the target chunk within the target file
+ size_t target_len_;
+ size_t target_uncompressed_len_;
+ size_t target_compress_level_; // the deflate compression level of the target chunk.
+
+ std::vector<uint8_t> data_; // storage for the patch data
+};
+
+// Interface for zip_mode and image_mode images. We initialize the image from an input file and
+// split the file content into a list of image chunks.
+class Image {
+ public:
+ explicit Image(bool is_source) : is_source_(is_source) {}
+
+ virtual ~Image() {}
+
+ // Create a list of image chunks from input file.
+ virtual bool Initialize(const std::string& filename) = 0;
+
+ // Look for runs of adjacent normal chunks and compress them down into a single chunk. (Such
+ // runs can be produced when deflate chunks are changed to normal chunks.)
+ void MergeAdjacentNormalChunks();
+
+ void DumpChunks() const;
+
+ // Non const iterators to access the stored ImageChunks.
+ std::vector<ImageChunk>::iterator begin() {
+ return chunks_.begin();
+ }
+
+ std::vector<ImageChunk>::iterator end() {
+ return chunks_.end();
+ }
+
+ std::vector<ImageChunk>::const_iterator cbegin() const {
+ return chunks_.cbegin();
+ }
+
+ std::vector<ImageChunk>::const_iterator cend() const {
+ return chunks_.cend();
+ }
+
+ ImageChunk& operator[](size_t i);
+ const ImageChunk& operator[](size_t i) const;
+
+ size_t NumOfChunks() const {
+ return chunks_.size();
+ }
+
+ protected:
+ bool ReadFile(const std::string& filename, std::vector<uint8_t>* file_content);
+
+ bool is_source_; // True if it's for source chunks.
+ std::vector<ImageChunk> chunks_; // Internal storage of ImageChunk.
+ std::vector<uint8_t> file_content_; // Store the whole input file in memory.
+};
+
+class ZipModeImage : public Image {
+ public:
+ explicit ZipModeImage(bool is_source, size_t limit = 0) : Image(is_source), limit_(limit) {}
+
+ bool Initialize(const std::string& filename) override;
+
+ // Initialize a dummy ZipModeImage from an existing ImageChunk vector. For src img pieces, we
+ // reconstruct a new file_content based on the source ranges; but it's not needed for the tgt img
+ // pieces; because for each chunk both the data and their offset within the file are unchanged.
+ void Initialize(const std::vector<ImageChunk>& chunks, const std::vector<uint8_t>& file_content) {
+ chunks_ = chunks;
+ file_content_ = file_content;
+ }
+
+ // The pesudo source chunk for bsdiff if there's no match for the given target chunk. It's in
+ // fact the whole source file.
+ ImageChunk PseudoSource() const;
+
+ // Find the matching deflate source chunk by entry name. Search for normal chunks also if
+ // |find_normal| is true.
+ ImageChunk* FindChunkByName(const std::string& name, bool find_normal = false);
+
+ const ImageChunk* FindChunkByName(const std::string& name, bool find_normal = false) const;
+
+ // Verify that we can reconstruct the deflate chunks; also change the type to CHUNK_NORMAL if
+ // src and tgt are identical.
+ static bool CheckAndProcessChunks(ZipModeImage* tgt_image, ZipModeImage* src_image);
+
+ // Compute the patch between tgt & src images, and write the data into |patch_name|.
+ static bool GeneratePatches(const ZipModeImage& tgt_image, const ZipModeImage& src_image,
+ const std::string& patch_name);
+
+ // Compute the patch based on the lists of split src and tgt images. Generate patches for each
+ // pair of split pieces and write the data to |patch_name|. If |debug_dir| is specified, write
+ // each split src data and patch data into that directory.
+ static bool GeneratePatches(const std::vector<ZipModeImage>& split_tgt_images,
+ const std::vector<ZipModeImage>& split_src_images,
+ const std::vector<SortedRangeSet>& split_src_ranges,
+ const std::string& patch_name, const std::string& split_info_file,
+ const std::string& debug_dir);
+
+ // Split the tgt chunks and src chunks based on the size limit.
+ static bool SplitZipModeImageWithLimit(const ZipModeImage& tgt_image,
+ const ZipModeImage& src_image,
+ std::vector<ZipModeImage>* split_tgt_images,
+ std::vector<ZipModeImage>* split_src_images,
+ std::vector<SortedRangeSet>* split_src_ranges);
+
+ private:
+ // Initialize image chunks based on the zip entries.
+ bool InitializeChunks(const std::string& filename, ZipArchiveHandle handle);
+ // Add the a zip entry to the list.
+ bool AddZipEntryToChunks(ZipArchiveHandle handle, const std::string& entry_name, ZipEntry* entry);
+ // Return the real size of the zip file. (omit the trailing zeros that used for alignment)
+ bool GetZipFileSize(size_t* input_file_size);
+
+ static void ValidateSplitImages(const std::vector<ZipModeImage>& split_tgt_images,
+ const std::vector<ZipModeImage>& split_src_images,
+ std::vector<SortedRangeSet>& split_src_ranges,
+ size_t total_tgt_size);
+ // Construct the dummy split images based on the chunks info and source ranges; and move them into
+ // the given vectors. Return true if we add a new split image into |split_tgt_images|, and
+ // false otherwise.
+ static bool AddSplitImageFromChunkList(const ZipModeImage& tgt_image,
+ const ZipModeImage& src_image,
+ const SortedRangeSet& split_src_ranges,
+ const std::vector<ImageChunk>& split_tgt_chunks,
+ const std::vector<ImageChunk>& split_src_chunks,
+ std::vector<ZipModeImage>* split_tgt_images,
+ std::vector<ZipModeImage>* split_src_images);
+
+ // Function that actually iterates the tgt_chunks and makes patches.
+ static bool GeneratePatchesInternal(const ZipModeImage& tgt_image, const ZipModeImage& src_image,
+ std::vector<PatchChunk>* patch_chunks);
+
+ // size limit in bytes of each chunk. Also, if the length of one zip_entry exceeds the limit,
+ // we'll split that entry into several smaller chunks in advance.
+ size_t limit_;
+};
+
+class ImageModeImage : public Image {
+ public:
+ explicit ImageModeImage(bool is_source) : Image(is_source) {}
+
+ // Initialize the image chunks list by searching the magic numbers in an image file.
+ bool Initialize(const std::string& filename) override;
+
+ bool SetBonusData(const std::vector<uint8_t>& bonus_data);
+
+ // In Image Mode, verify that the source and target images have the same chunk structure (ie, the
+ // same sequence of deflate and normal chunks).
+ static bool CheckAndProcessChunks(ImageModeImage* tgt_image, ImageModeImage* src_image);
+
+ // In image mode, generate patches against the given source chunks and bonus_data; write the
+ // result to |patch_name|.
+ static bool GeneratePatches(const ImageModeImage& tgt_image, const ImageModeImage& src_image,
+ const std::string& patch_name);
+};
+
+#endif // _APPLYPATCH_IMGDIFF_IMAGE_H
diff --git a/applypatch/libimgpatch.pc b/applypatch/libimgpatch.pc
deleted file mode 100644
index e500293..0000000
--- a/applypatch/libimgpatch.pc
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file is for libimgpatch in Chrome OS.
-
-Name: libimgpatch
-Description: Apply imgdiff patch
-Version: 0.0.1
-Libs: -limgpatch -lbz2 -lz
diff --git a/boot_control/Android.mk b/boot_control/Android.mk
index 27e3d97..9814d71 100644
--- a/boot_control/Android.mk
+++ b/boot_control/Android.mk
@@ -24,8 +24,7 @@
-D_FILE_OFFSET_BITS=64 \
-Werror \
-Wall \
- -Wextra \
- -Wno-unused-parameter
+ -Wextra
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_STATIC_LIBRARIES := libbootloader_message libfs_mgr libbase
LOCAL_POST_INSTALL_CMD := \
diff --git a/bootloader_message/Android.bp b/bootloader_message/Android.bp
index f0d76e7..c81c67b 100644
--- a/bootloader_message/Android.bp
+++ b/bootloader_message/Android.bp
@@ -17,7 +17,10 @@
cc_library_static {
name: "libbootloader_message",
srcs: ["bootloader_message.cpp"],
- cppflags: ["-Werror"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
static_libs: [
"libbase",
"libfs_mgr",
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index f91446b..aaeffdc 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -159,14 +159,8 @@
bool write_bootloader_message(const std::vector<std::string>& options, std::string* err) {
bootloader_message boot = {};
- strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
- strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
- for (const auto& s : options) {
- strlcat(boot.recovery, s.c_str(), sizeof(boot.recovery));
- if (s.back() != '\n') {
- strlcat(boot.recovery, "\n", sizeof(boot.recovery));
- }
- }
+ update_bootloader_message_in_struct(&boot, options);
+
return write_bootloader_message(boot, err);
}
@@ -175,20 +169,27 @@
if (!read_bootloader_message(&boot, err)) {
return false;
}
+ update_bootloader_message_in_struct(&boot, options);
- // Zero out the entire fields.
- memset(boot.command, 0, sizeof(boot.command));
- memset(boot.recovery, 0, sizeof(boot.recovery));
+ return write_bootloader_message(boot, err);
+}
- strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
- strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
+bool update_bootloader_message_in_struct(bootloader_message* boot,
+ const std::vector<std::string>& options) {
+ if (!boot) return false;
+ // Replace the command & recovery fields.
+ memset(boot->command, 0, sizeof(boot->command));
+ memset(boot->recovery, 0, sizeof(boot->recovery));
+
+ strlcpy(boot->command, "boot-recovery", sizeof(boot->command));
+ strlcpy(boot->recovery, "recovery\n", sizeof(boot->recovery));
for (const auto& s : options) {
- strlcat(boot.recovery, s.c_str(), sizeof(boot.recovery));
+ strlcat(boot->recovery, s.c_str(), sizeof(boot->recovery));
if (s.back() != '\n') {
- strlcat(boot.recovery, "\n", sizeof(boot.recovery));
+ strlcat(boot->recovery, "\n", sizeof(boot->recovery));
}
}
- return write_bootloader_message(boot, err);
+ return true;
}
bool write_reboot_bootloader(std::string* err) {
diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h
index 2ffbfc9..95c19ae 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -103,13 +103,17 @@
* implementations are free to use all 32 bytes and may store private
* data past the first NUL-byte in this field. It is encouraged, but
* not mandatory, to use 'struct bootloader_control' described below.
+ *
+ * The update_channel field is used to store the Omaha update channel
+ * if update_engine is compiled with Omaha support.
*/
struct bootloader_message_ab {
struct bootloader_message message;
char slot_suffix[32];
+ char update_channel[128];
// Round up the entire struct to 4096-byte.
- char reserved[2016];
+ char reserved[1888];
};
/**
@@ -207,6 +211,11 @@
// only update the command and recovery fields.
bool update_bootloader_message(const std::vector<std::string>& options, std::string* err);
+// Update bootloader message (boots into recovery with the |options|) in |boot|. Will only update
+// the command and recovery fields.
+bool update_bootloader_message_in_struct(bootloader_message* boot,
+ const std::vector<std::string>& options);
+
// Clear BCB.
bool clear_bootloader_message(std::string* err);
diff --git a/common.h b/common.h
index 62fb132..8b336f8 100644
--- a/common.h
+++ b/common.h
@@ -22,8 +22,9 @@
#include <string>
-#define STRINGIFY(x) #x
-#define EXPAND(x) STRINGIFY(x)
+// Not using the command-line defined macro here because this header could be included by
+// device-specific recovery libraries. We static assert the value consistency in recovery.cpp.
+static constexpr int kRecoveryApiVersion = 3;
class RecoveryUI;
diff --git a/device.cpp b/device.cpp
index 6150186..f881daf 100644
--- a/device.cpp
+++ b/device.cpp
@@ -17,34 +17,36 @@
#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",
+ "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",
+ "Wipe cache partition",
#endif // !AB_OTA_UPDATER
- "Mount /system",
- "View recovery logs",
- "Run graphics test",
- "Power off",
- NULL,
+ "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,
+ Device::REBOOT,
+ Device::REBOOT_BOOTLOADER,
+ Device::APPLY_ADB_SIDELOAD,
+ Device::APPLY_SDCARD,
+ Device::WIPE_DATA,
#ifndef AB_OTA_UPDATER
- Device::WIPE_CACHE,
+ Device::WIPE_CACHE,
#endif // !AB_OTA_UPDATER
- Device::MOUNT_SYSTEM,
- Device::VIEW_RECOVERY_LOGS,
- Device::RUN_GRAPHICS_TEST,
- Device::SHUTDOWN,
+ 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]) ==
diff --git a/device.h b/device.h
index 639e2bf..74745b3 100644
--- a/device.h
+++ b/device.h
@@ -66,6 +66,7 @@
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
diff --git a/edify/Android.bp b/edify/Android.bp
new file mode 100644
index 0000000..42947eb
--- /dev/null
+++ b/edify/Android.bp
@@ -0,0 +1,45 @@
+// 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: "libedify",
+
+ host_supported: true,
+
+ srcs: [
+ "expr.cpp",
+ "lexer.ll",
+ "parser.yy",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-deprecated-register",
+ "-Wno-unused-parameter",
+ ],
+
+ export_include_dirs: [
+ "include",
+ ],
+
+ local_include_dirs: [
+ "include",
+ ],
+
+ static_libs: [
+ "libbase",
+ "libotautil",
+ ],
+}
diff --git a/edify/Android.mk b/edify/Android.mk
deleted file mode 100644
index d8058c1..0000000
--- a/edify/Android.mk
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright 2009 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)
-
-edify_src_files := \
- lexer.ll \
- parser.yy \
- expr.cpp
-
-#
-# Build the host-side command line tool (host executable)
-#
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
- $(edify_src_files) \
- edify_parser.cpp
-
-LOCAL_CFLAGS := -Werror
-LOCAL_CPPFLAGS := -g -O0
-LOCAL_MODULE := edify_parser
-LOCAL_YACCFLAGS := -v
-LOCAL_CPPFLAGS += -Wno-unused-parameter
-LOCAL_CPPFLAGS += -Wno-deprecated-register
-LOCAL_CLANG := true
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
-LOCAL_STATIC_LIBRARIES += libbase
-
-include $(BUILD_HOST_EXECUTABLE)
-
-#
-# Build the device-side library (static library)
-#
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(edify_src_files)
-
-LOCAL_CFLAGS := -Werror
-LOCAL_CPPFLAGS := -Wno-unused-parameter
-LOCAL_CPPFLAGS += -Wno-deprecated-register
-LOCAL_MODULE := libedify
-LOCAL_CLANG := true
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
-LOCAL_STATIC_LIBRARIES += libbase
-
-include $(BUILD_STATIC_LIBRARY)
diff --git a/edify/edify_parser.cpp b/edify/edify_parser.cpp
deleted file mode 100644
index f1b5628..0000000
--- a/edify/edify_parser.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2009 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.
- */
-
-/**
- * This is a host-side tool for validating a given edify script file.
- *
- * We used to have edify test cases here, which have been moved to
- * tests/component/edify_test.cpp.
- *
- * Caveat: It doesn't recognize functions defined through updater, which
- * makes the tool less useful. We should either extend the tool or remove it.
- */
-
-#include <errno.h>
-#include <stdio.h>
-
-#include <memory>
-#include <string>
-
-#include <android-base/file.h>
-
-#include "expr.h"
-
-static void ExprDump(int depth, const std::unique_ptr<Expr>& n, const std::string& script) {
- printf("%*s", depth*2, "");
- printf("%s %p (%d-%d) \"%s\"\n",
- n->name.c_str(), n->fn, n->start, n->end,
- script.substr(n->start, n->end - n->start).c_str());
- for (size_t i = 0; i < n->argv.size(); ++i) {
- ExprDump(depth+1, n->argv[i], script);
- }
-}
-
-int main(int argc, char** argv) {
- RegisterBuiltins();
-
- if (argc != 2) {
- printf("Usage: %s <edify script>\n", argv[0]);
- return 1;
- }
-
- std::string buffer;
- if (!android::base::ReadFileToString(argv[1], &buffer)) {
- printf("%s: failed to read %s: %s\n", argv[0], argv[1], strerror(errno));
- return 1;
- }
-
- std::unique_ptr<Expr> root;
- int error_count = 0;
- int error = parse_string(buffer.data(), &root, &error_count);
- printf("parse returned %d; %d errors encountered\n", error, error_count);
- if (error == 0 || error_count > 0) {
-
- ExprDump(0, root, buffer);
-
- State state(buffer, nullptr);
- std::string result;
- if (!Evaluate(&state, root, &result)) {
- printf("result was NULL, message is: %s\n",
- (state.errmsg.empty() ? "(NULL)" : state.errmsg.c_str()));
- } else {
- printf("result is [%s]\n", result.c_str());
- }
- }
- return 0;
-}
diff --git a/edify/expr.cpp b/edify/expr.cpp
index 54ab332..6823b73 100644
--- a/edify/expr.cpp
+++ b/edify/expr.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "expr.h"
+#include "edify/expr.h"
#include <stdarg.h>
#include <stdio.h>
@@ -31,6 +31,8 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include "otautil/error_code.h"
+
// Functions should:
//
// - return a malloc()'d string
@@ -112,9 +114,9 @@
Value* AbortFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
std::string msg;
if (!argv.empty() && Evaluate(state, argv[0], &msg)) {
- state->errmsg = msg;
+ state->errmsg += msg;
} else {
- state->errmsg = "called abort()";
+ state->errmsg += "called abort()";
}
return nullptr;
}
@@ -408,16 +410,16 @@
}
Value* ErrorAbort(State* state, CauseCode cause_code, const char* format, ...) {
- va_list ap;
- va_start(ap, format);
- android::base::StringAppendV(&state->errmsg, format, ap);
- va_end(ap);
- state->cause_code = cause_code;
- return nullptr;
+ std::string err_message;
+ va_list ap;
+ va_start(ap, format);
+ android::base::StringAppendV(&err_message, format, ap);
+ va_end(ap);
+ // Ensure that there's exactly one line break at the end of the error message.
+ state->errmsg = android::base::Trim(err_message) + "\n";
+ state->cause_code = cause_code;
+ return nullptr;
}
-State::State(const std::string& script, void* cookie) :
- script(script),
- cookie(cookie) {
-}
-
+State::State(const std::string& script, void* cookie)
+ : script(script), cookie(cookie), error_code(kNoError), cause_code(kNoCause) {}
diff --git a/edify/expr.h b/edify/include/edify/expr.h
similarity index 86%
rename from edify/expr.h
rename to edify/include/edify/expr.h
index 4838d20..770d1cf 100644
--- a/edify/expr.h
+++ b/edify/include/edify/expr.h
@@ -23,32 +23,34 @@
#include <string>
#include <vector>
-#include "error_code.h"
+// Forward declaration to avoid including "otautil/error_code.h".
+enum ErrorCode : int;
+enum CauseCode : int;
struct State {
- State(const std::string& script, void* cookie);
+ State(const std::string& script, void* cookie);
- // The source of the original script.
- const std::string& script;
+ // The source of the original script.
+ const std::string& script;
- // Optional pointer to app-specific data; the core of edify never
- // uses this value.
- void* cookie;
+ // Optional pointer to app-specific data; the core of edify never
+ // uses this value.
+ void* cookie;
- // The error message (if any) returned if the evaluation aborts.
- // Should be empty initially, will be either empty or a string that
- // Evaluate() returns.
- std::string errmsg;
+ // The error message (if any) returned if the evaluation aborts.
+ // Should be empty initially, will be either empty or a string that
+ // Evaluate() returns.
+ std::string errmsg;
- // error code indicates the type of failure (e.g. failure to update system image)
- // during the OTA process.
- ErrorCode error_code = kNoError;
+ // error code indicates the type of failure (e.g. failure to update system image)
+ // during the OTA process.
+ ErrorCode error_code;
- // cause code provides more detailed reason of an OTA failure (e.g. fsync error)
- // in addition to the error code.
- CauseCode cause_code = kNoCause;
+ // cause code provides more detailed reason of an OTA failure (e.g. fsync error)
+ // in addition to the error code.
+ CauseCode cause_code;
- bool is_retry = false;
+ bool is_retry = false;
};
enum ValueType {
diff --git a/edify/lexer.ll b/edify/lexer.ll
index b764d16..4e04003 100644
--- a/edify/lexer.ll
+++ b/edify/lexer.ll
@@ -18,7 +18,7 @@
#include <string.h>
#include <string>
-#include "expr.h"
+#include "edify/expr.h"
#include "yydefs.h"
#include "parser.h"
@@ -35,6 +35,8 @@
%x STR
+%option noinput
+%option nounput
%option noyywrap
%%
diff --git a/edify/parser.yy b/edify/parser.yy
index b1685eb..bd2e010 100644
--- a/edify/parser.yy
+++ b/edify/parser.yy
@@ -25,7 +25,7 @@
#include <android-base/macros.h>
-#include "expr.h"
+#include "edify/expr.h"
#include "yydefs.h"
#include "parser.h"
diff --git a/etc/init.rc b/etc/init.rc
index d8121cc..0fc6c4c 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -11,6 +11,7 @@
export ANDROID_DATA /data
export EXTERNAL_STORAGE /sdcard
+ symlink /system/bin /bin
symlink /system/etc /etc
mount cgroup none /acct cpuacct
diff --git a/fuse_sdcard_provider.cpp b/fuse_sdcard_provider.cpp
index b0ecf96..46bdf17 100644
--- a/fuse_sdcard_provider.cpp
+++ b/fuse_sdcard_provider.cpp
@@ -14,72 +14,70 @@
* limitations under the License.
*/
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
+#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 <fcntl.h>
+
+#include <functional>
#include <android-base/file.h>
#include "fuse_sideload.h"
struct file_data {
- int fd; // the underlying sdcard file
+ int fd; // the underlying sdcard file
- uint64_t file_size;
- uint32_t block_size;
+ uint64_t file_size;
+ uint32_t block_size;
};
-static int read_block_file(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size) {
- file_data* fd = reinterpret_cast<file_data*>(cookie);
+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;
+ }
- off64_t offset = ((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;
+ }
- if (!android::base::ReadFully(fd->fd, buffer, fetch_size)) {
- fprintf(stderr, "read on sdcard failed: %s\n", strerror(errno));
- return -EIO;
- }
-
- return 0;
-}
-
-static void close_file(void* cookie) {
- file_data* fd = reinterpret_cast<file_data*>(cookie);
- close(fd->fd);
+ 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;
- }
+ 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;
+ 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 = read_block_file;
- vtab.close = close_file;
+ 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);
+ // 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, fd.file_size, fd.block_size) == 0;
+ return run_fuse_sideload(vtab, fd.file_size, fd.block_size) == 0;
}
diff --git a/fuse_sideload.cpp b/fuse_sideload.cpp
index 219374f..1c7e98f 100644
--- a/fuse_sideload.cpp
+++ b/fuse_sideload.cpp
@@ -41,337 +41,310 @@
// two files is implemented. In particular, you can't opendir() or
// readdir() on the "/sideload" directory; ls on it won't work.
-#include <ctype.h>
-#include <dirent.h>
+#include "fuse_sideload.h"
+
#include <errno.h>
#include <fcntl.h>
-#include <limits.h>
+#include <limits.h> // PATH_MAX
#include <linux/fuse.h>
-#include <pthread.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/inotify.h>
#include <sys/mount.h>
-#include <sys/param.h>
-#include <sys/resource.h>
+#include <sys/param.h> // MIN
#include <sys/stat.h>
-#include <sys/statfs.h>
-#include <sys/time.h>
#include <sys/uio.h>
#include <unistd.h>
+#include <array>
#include <string>
+#include <vector>
#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
#include <openssl/sha.h>
-#include "fuse_sideload.h"
+static constexpr uint64_t PACKAGE_FILE_ID = FUSE_ROOT_ID + 1;
+static constexpr uint64_t EXIT_FLAG_ID = FUSE_ROOT_ID + 2;
-#define PACKAGE_FILE_ID (FUSE_ROOT_ID+1)
-#define EXIT_FLAG_ID (FUSE_ROOT_ID+2)
+static constexpr int NO_STATUS = 1;
+static constexpr int NO_STATUS_EXIT = 2;
-#define NO_STATUS 1
-#define NO_STATUS_EXIT 2
+using SHA256Digest = std::array<uint8_t, SHA256_DIGEST_LENGTH>;
struct fuse_data {
- int ffd; // file descriptor for the fuse socket
+ android::base::unique_fd ffd; // file descriptor for the fuse socket
- struct provider_vtab* vtab;
- void* cookie;
+ provider_vtab vtab;
- uint64_t file_size; // bytes
+ uint64_t file_size; // bytes
- uint32_t block_size; // block size that the adb host is using to send the file to us
- uint32_t file_blocks; // file size in block_size blocks
+ uint32_t block_size; // block size that the adb host is using to send the file to us
+ uint32_t file_blocks; // file size in block_size blocks
- uid_t uid;
- gid_t gid;
+ uid_t uid;
+ gid_t gid;
- uint32_t curr_block; // cache the block most recently read from the host
- uint8_t* block_data;
+ uint32_t curr_block; // cache the block most recently read from the host
+ uint8_t* block_data;
- uint8_t* extra_block; // another block of storage for reads that
- // span two blocks
+ uint8_t* extra_block; // another block of storage for reads that span two blocks
- uint8_t* hashes; // SHA-256 hash of each block (all zeros
- // if block hasn't been read yet)
+ std::vector<SHA256Digest>
+ hashes; // SHA-256 hash of each block (all zeros if block hasn't been read yet)
};
-static void fuse_reply(struct fuse_data* fd, __u64 unique, const void *data, size_t len)
-{
- struct fuse_out_header hdr;
- struct iovec vec[2];
- int res;
+static void fuse_reply(const fuse_data* fd, uint64_t unique, const void* data, size_t len) {
+ fuse_out_header hdr;
+ hdr.len = len + sizeof(hdr);
+ hdr.error = 0;
+ hdr.unique = unique;
- hdr.len = len + sizeof(hdr);
- hdr.error = 0;
- hdr.unique = unique;
+ struct iovec vec[2];
+ vec[0].iov_base = &hdr;
+ vec[0].iov_len = sizeof(hdr);
+ vec[1].iov_base = const_cast<void*>(data);
+ vec[1].iov_len = len;
- vec[0].iov_base = &hdr;
- vec[0].iov_len = sizeof(hdr);
- vec[1].iov_base = /* const_cast */(void*)(data);
- vec[1].iov_len = len;
-
- res = writev(fd->ffd, vec, 2);
- if (res < 0) {
- printf("*** REPLY FAILED *** %s\n", strerror(errno));
- }
+ int res = writev(fd->ffd, vec, 2);
+ if (res == -1) {
+ printf("*** REPLY FAILED *** %s\n", strerror(errno));
+ }
}
-static int handle_init(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
- const struct fuse_init_in* req = reinterpret_cast<const struct fuse_init_in*>(data);
- struct fuse_init_out out;
- size_t fuse_struct_size;
+static int handle_init(void* data, fuse_data* fd, const fuse_in_header* hdr) {
+ const fuse_init_in* req = static_cast<const fuse_init_in*>(data);
+ // Kernel 2.6.16 is the first stable kernel with struct fuse_init_out defined (fuse version 7.6).
+ // The structure is the same from 7.6 through 7.22. Beginning with 7.23, the structure increased
+ // in size and added new parameters.
+ if (req->major != FUSE_KERNEL_VERSION || req->minor < 6) {
+ printf("Fuse kernel version mismatch: Kernel version %d.%d, Expected at least %d.6", req->major,
+ req->minor, FUSE_KERNEL_VERSION);
+ return -1;
+ }
- /* Kernel 2.6.16 is the first stable kernel with struct fuse_init_out
- * defined (fuse version 7.6). The structure is the same from 7.6 through
- * 7.22. Beginning with 7.23, the structure increased in size and added
- * new parameters.
- */
- if (req->major != FUSE_KERNEL_VERSION || req->minor < 6) {
- printf("Fuse kernel version mismatch: Kernel version %d.%d, Expected at least %d.6",
- req->major, req->minor, FUSE_KERNEL_VERSION);
- return -1;
- }
-
- out.minor = MIN(req->minor, FUSE_KERNEL_MINOR_VERSION);
- fuse_struct_size = sizeof(out);
+ fuse_init_out out;
+ out.minor = MIN(req->minor, FUSE_KERNEL_MINOR_VERSION);
+ size_t fuse_struct_size = sizeof(out);
#if defined(FUSE_COMPAT_22_INIT_OUT_SIZE)
- /* FUSE_KERNEL_VERSION >= 23. */
+ /* FUSE_KERNEL_VERSION >= 23. */
- /* If the kernel only works on minor revs older than or equal to 22,
- * then use the older structure size since this code only uses the 7.22
- * version of the structure. */
- if (req->minor <= 22) {
- fuse_struct_size = FUSE_COMPAT_22_INIT_OUT_SIZE;
- }
+ // If the kernel only works on minor revs older than or equal to 22, then use the older structure
+ // size since this code only uses the 7.22 version of the structure.
+ if (req->minor <= 22) {
+ fuse_struct_size = FUSE_COMPAT_22_INIT_OUT_SIZE;
+ }
#endif
- out.major = FUSE_KERNEL_VERSION;
- out.max_readahead = req->max_readahead;
- out.flags = 0;
- out.max_background = 32;
- out.congestion_threshold = 32;
- out.max_write = 4096;
- fuse_reply(fd, hdr->unique, &out, fuse_struct_size);
+ out.major = FUSE_KERNEL_VERSION;
+ out.max_readahead = req->max_readahead;
+ out.flags = 0;
+ out.max_background = 32;
+ out.congestion_threshold = 32;
+ out.max_write = 4096;
+ fuse_reply(fd, hdr->unique, &out, fuse_struct_size);
- return NO_STATUS;
+ return NO_STATUS;
}
-static void fill_attr(struct fuse_attr* attr, struct fuse_data* fd,
- uint64_t nodeid, uint64_t size, uint32_t mode) {
- memset(attr, 0, sizeof(*attr));
- attr->nlink = 1;
- attr->uid = fd->uid;
- attr->gid = fd->gid;
- attr->blksize = 4096;
+static void fill_attr(fuse_attr* attr, const fuse_data* fd, uint64_t nodeid, uint64_t size,
+ uint32_t mode) {
+ *attr = {};
+ attr->nlink = 1;
+ attr->uid = fd->uid;
+ attr->gid = fd->gid;
+ attr->blksize = 4096;
- attr->ino = nodeid;
- attr->size = size;
- attr->blocks = (size == 0) ? 0 : (((size-1) / attr->blksize) + 1);
- attr->mode = mode;
+ attr->ino = nodeid;
+ attr->size = size;
+ attr->blocks = (size == 0) ? 0 : (((size - 1) / attr->blksize) + 1);
+ attr->mode = mode;
}
-static int handle_getattr(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) {
- struct fuse_attr_out out;
- memset(&out, 0, sizeof(out));
- out.attr_valid = 10;
+static int handle_getattr(void* /* data */, const fuse_data* fd, const fuse_in_header* hdr) {
+ fuse_attr_out out = {};
+ out.attr_valid = 10;
- if (hdr->nodeid == FUSE_ROOT_ID) {
- fill_attr(&(out.attr), fd, hdr->nodeid, 4096, S_IFDIR | 0555);
- } else if (hdr->nodeid == PACKAGE_FILE_ID) {
- fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444);
- } else if (hdr->nodeid == EXIT_FLAG_ID) {
- fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0);
- } else {
- return -ENOENT;
- }
+ if (hdr->nodeid == FUSE_ROOT_ID) {
+ fill_attr(&(out.attr), fd, hdr->nodeid, 4096, S_IFDIR | 0555);
+ } else if (hdr->nodeid == PACKAGE_FILE_ID) {
+ fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444);
+ } else if (hdr->nodeid == EXIT_FLAG_ID) {
+ fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0);
+ } else {
+ return -ENOENT;
+ }
- fuse_reply(fd, hdr->unique, &out, sizeof(out));
- return (hdr->nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS;
+ fuse_reply(fd, hdr->unique, &out, sizeof(out));
+ return (hdr->nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS;
}
-static int handle_lookup(void* data, struct fuse_data* fd,
- const struct fuse_in_header* hdr) {
- struct fuse_entry_out out;
- memset(&out, 0, sizeof(out));
- out.entry_valid = 10;
- out.attr_valid = 10;
+static int handle_lookup(void* data, const fuse_data* fd, const fuse_in_header* hdr) {
+ if (data == nullptr) return -ENOENT;
- if (strncmp(FUSE_SIDELOAD_HOST_FILENAME, reinterpret_cast<const char*>(data),
- sizeof(FUSE_SIDELOAD_HOST_FILENAME)) == 0) {
- out.nodeid = PACKAGE_FILE_ID;
- out.generation = PACKAGE_FILE_ID;
- fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444);
- } else if (strncmp(FUSE_SIDELOAD_HOST_EXIT_FLAG, reinterpret_cast<const char*>(data),
- sizeof(FUSE_SIDELOAD_HOST_EXIT_FLAG)) == 0) {
- out.nodeid = EXIT_FLAG_ID;
- out.generation = EXIT_FLAG_ID;
- fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0);
- } else {
- return -ENOENT;
- }
+ fuse_entry_out out = {};
+ out.entry_valid = 10;
+ out.attr_valid = 10;
- fuse_reply(fd, hdr->unique, &out, sizeof(out));
- return (out.nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS;
+ std::string filename(static_cast<const char*>(data));
+ if (filename == FUSE_SIDELOAD_HOST_FILENAME) {
+ out.nodeid = PACKAGE_FILE_ID;
+ out.generation = PACKAGE_FILE_ID;
+ fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444);
+ } else if (filename == FUSE_SIDELOAD_HOST_EXIT_FLAG) {
+ out.nodeid = EXIT_FLAG_ID;
+ out.generation = EXIT_FLAG_ID;
+ fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0);
+ } else {
+ return -ENOENT;
+ }
+
+ fuse_reply(fd, hdr->unique, &out, sizeof(out));
+ return (out.nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS;
}
-static int handle_open(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) {
- if (hdr->nodeid == EXIT_FLAG_ID) return -EPERM;
- if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT;
+static int handle_open(void* /* data */, const fuse_data* fd, const fuse_in_header* hdr) {
+ if (hdr->nodeid == EXIT_FLAG_ID) return -EPERM;
+ if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT;
- struct fuse_open_out out;
- memset(&out, 0, sizeof(out));
- out.fh = 10; // an arbitrary number; we always use the same handle
- fuse_reply(fd, hdr->unique, &out, sizeof(out));
- return NO_STATUS;
+ fuse_open_out out = {};
+ out.fh = 10; // an arbitrary number; we always use the same handle
+ fuse_reply(fd, hdr->unique, &out, sizeof(out));
+ return NO_STATUS;
}
-static int handle_flush(void* /* data */, struct fuse_data* /* fd */,
- const struct fuse_in_header* /* hdr */) {
- return 0;
+static int handle_flush(void* /* data */, fuse_data* /* fd */, const fuse_in_header* /* hdr */) {
+ return 0;
}
-static int handle_release(void* /* data */, struct fuse_data* /* fd */,
- const struct fuse_in_header* /* hdr */) {
- return 0;
+static int handle_release(void* /* data */, fuse_data* /* fd */, const fuse_in_header* /* hdr */) {
+ return 0;
}
// Fetch a block from the host into fd->curr_block and fd->block_data.
// Returns 0 on successful fetch, negative otherwise.
-static int fetch_block(struct fuse_data* fd, uint32_t block) {
- if (block == fd->curr_block) {
- return 0;
- }
-
- if (block >= fd->file_blocks) {
- memset(fd->block_data, 0, fd->block_size);
- fd->curr_block = block;
- return 0;
- }
-
- size_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.
- fetch_size = fd->file_size - (block * fd->block_size);
- memset(fd->block_data + fetch_size, 0, fd->block_size - fetch_size);
- }
-
- int result = fd->vtab->read_block(fd->cookie, block, fd->block_data, fetch_size);
- if (result < 0) return result;
-
- fd->curr_block = block;
-
- // Verify the hash of the block we just got from the host.
- //
- // - If the hash of the just-received data matches the stored hash
- // for the block, accept it.
- // - If the stored hash is all zeroes, store the new hash and
- // accept the block (this is the first time we've read this
- // block).
- // - Otherwise, return -EINVAL for the read.
-
- uint8_t hash[SHA256_DIGEST_LENGTH];
- SHA256(fd->block_data, fd->block_size, hash);
- uint8_t* blockhash = fd->hashes + block * SHA256_DIGEST_LENGTH;
- if (memcmp(hash, blockhash, SHA256_DIGEST_LENGTH) == 0) {
- return 0;
- }
-
- int i;
- for (i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
- if (blockhash[i] != 0) {
- fd->curr_block = -1;
- return -EIO;
- }
- }
-
- memcpy(blockhash, hash, SHA256_DIGEST_LENGTH);
+static int fetch_block(fuse_data* fd, uint32_t block) {
+ if (block == fd->curr_block) {
return 0;
+ }
+
+ if (block >= fd->file_blocks) {
+ memset(fd->block_data, 0, fd->block_size);
+ fd->curr_block = block;
+ return 0;
+ }
+
+ size_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.
+ fetch_size = fd->file_size - (block * fd->block_size);
+ 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;
+
+ fd->curr_block = block;
+
+ // Verify the hash of the block we just got from the host.
+ //
+ // - If the hash of the just-received data matches the stored hash for the block, accept it.
+ // - If the stored hash is all zeroes, store the new hash and accept the block (this is the first
+ // time we've read this block).
+ // - Otherwise, return -EINVAL for the read.
+
+ SHA256Digest hash;
+ SHA256(fd->block_data, fd->block_size, hash.data());
+
+ const SHA256Digest& blockhash = fd->hashes[block];
+ if (hash == blockhash) {
+ return 0;
+ }
+
+ for (uint8_t i : blockhash) {
+ if (i != 0) {
+ fd->curr_block = -1;
+ return -EIO;
+ }
+ }
+
+ fd->hashes[block] = hash;
+ return 0;
}
-static int handle_read(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
- const struct fuse_read_in* req = reinterpret_cast<const struct fuse_read_in*>(data);
- struct fuse_out_header outhdr;
- struct iovec vec[3];
- int vec_used;
- int result;
+static int handle_read(void* data, fuse_data* fd, const fuse_in_header* hdr) {
+ if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT;
- if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT;
+ const fuse_read_in* req = static_cast<const fuse_read_in*>(data);
+ uint64_t offset = req->offset;
+ uint32_t size = req->size;
- uint64_t offset = req->offset;
- uint32_t size = req->size;
+ // The docs on the fuse kernel interface are vague about what to do when a read request extends
+ // past the end of the file. We can return a short read -- the return structure does include a
+ // length field -- but in testing that caused the program using the file to segfault. (I
+ // speculate that this is due to the reading program accessing it via mmap; maybe mmap dislikes
+ // when you return something short of a whole page?) To fix this we zero-pad reads that extend
+ // past the end of the file so we're always returning exactly as many bytes as were requested.
+ // (Users of the mapped file have to know its real length anyway.)
- // The docs on the fuse kernel interface are vague about what to
- // do when a read request extends past the end of the file. We
- // can return a short read -- the return structure does include a
- // length field -- but in testing that caused the program using
- // the file to segfault. (I speculate that this is due to the
- // reading program accessing it via mmap; maybe mmap dislikes when
- // you return something short of a whole page?) To fix this we
- // zero-pad reads that extend past the end of the file so we're
- // always returning exactly as many bytes as were requested.
- // (Users of the mapped file have to know its real length anyway.)
+ fuse_out_header outhdr;
+ outhdr.len = sizeof(outhdr) + size;
+ outhdr.error = 0;
+ outhdr.unique = hdr->unique;
- outhdr.len = sizeof(outhdr) + size;
- outhdr.error = 0;
- outhdr.unique = hdr->unique;
- vec[0].iov_base = &outhdr;
- vec[0].iov_len = sizeof(outhdr);
+ struct iovec vec[3];
+ vec[0].iov_base = &outhdr;
+ vec[0].iov_len = sizeof(outhdr);
- uint32_t block = offset / fd->block_size;
- result = fetch_block(fd, block);
+ uint32_t block = offset / fd->block_size;
+ int result = fetch_block(fd, block);
+ if (result != 0) return result;
+
+ // Two cases:
+ //
+ // - the read request is entirely within this block. In this case we can reply immediately.
+ //
+ // - the read request goes over into the next block. Note that since we mount the filesystem
+ // with max_read=block_size, a read can never span more than two blocks. In this case we copy
+ // the block to extra_block and issue a fetch for the following block.
+
+ uint32_t block_offset = offset - (block * fd->block_size);
+
+ int vec_used;
+ if (size + block_offset <= fd->block_size) {
+ // First case: the read fits entirely in the first block.
+
+ vec[1].iov_base = fd->block_data + block_offset;
+ vec[1].iov_len = size;
+ vec_used = 2;
+ } else {
+ // Second case: the read spills over into the next block.
+
+ memcpy(fd->extra_block, fd->block_data + block_offset, fd->block_size - block_offset);
+ vec[1].iov_base = fd->extra_block;
+ vec[1].iov_len = fd->block_size - block_offset;
+
+ result = fetch_block(fd, block + 1);
if (result != 0) return result;
+ vec[2].iov_base = fd->block_data;
+ vec[2].iov_len = size - vec[1].iov_len;
+ vec_used = 3;
+ }
- // Two cases:
- //
- // - the read request is entirely within this block. In this
- // case we can reply immediately.
- //
- // - the read request goes over into the next block. Note that
- // since we mount the filesystem with max_read=block_size, a
- // read can never span more than two blocks. In this case we
- // copy the block to extra_block and issue a fetch for the
- // following block.
-
- uint32_t block_offset = offset - (block * fd->block_size);
-
- if (size + block_offset <= fd->block_size) {
- // First case: the read fits entirely in the first block.
-
- vec[1].iov_base = fd->block_data + block_offset;
- vec[1].iov_len = size;
- vec_used = 2;
- } else {
- // Second case: the read spills over into the next block.
-
- memcpy(fd->extra_block, fd->block_data + block_offset,
- fd->block_size - block_offset);
- vec[1].iov_base = fd->extra_block;
- vec[1].iov_len = fd->block_size - block_offset;
-
- result = fetch_block(fd, block+1);
- if (result != 0) return result;
- vec[2].iov_base = fd->block_data;
- vec[2].iov_len = size - vec[1].iov_len;
- vec_used = 3;
- }
-
- if (writev(fd->ffd, vec, vec_used) < 0) {
- printf("*** READ REPLY FAILED: %s ***\n", strerror(errno));
- }
- return NO_STATUS;
+ if (writev(fd->ffd, vec, vec_used) == -1) {
+ printf("*** READ REPLY FAILED: %s ***\n", strerror(errno));
+ }
+ return NO_STATUS;
}
-int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_size,
- uint32_t block_size) {
+int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t block_size,
+ 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(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_FORCE);
+ umount2(mount_point, MNT_FORCE);
// fs/fuse/inode.c in kernel code uses the greater of 4096 and the passed-in max_read.
if (block_size < 4096) {
@@ -383,9 +356,8 @@
return -1;
}
- struct fuse_data fd = {};
+ fuse_data fd = {};
fd.vtab = vtab;
- fd.cookie = cookie;
fd.file_size = file_size;
fd.block_size = block_size;
fd.file_blocks = (file_size == 0) ? 0 : (((file_size - 1) / block_size) + 1);
@@ -397,33 +369,27 @@
goto done;
}
- fd.hashes = (uint8_t*)calloc(fd.file_blocks, SHA256_DIGEST_LENGTH);
- if (fd.hashes == NULL) {
- fprintf(stderr, "failed to allocate %d bites for hashes\n",
- fd.file_blocks * SHA256_DIGEST_LENGTH);
- result = -1;
- goto done;
- }
-
+ // All hashes will be zero-initialized.
+ fd.hashes.resize(fd.file_blocks);
fd.uid = getuid();
fd.gid = getgid();
fd.curr_block = -1;
- fd.block_data = (uint8_t*)malloc(block_size);
- if (fd.block_data == NULL) {
+ fd.block_data = static_cast<uint8_t*>(malloc(block_size));
+ if (fd.block_data == nullptr) {
fprintf(stderr, "failed to allocate %d bites for block_data\n", block_size);
result = -1;
goto done;
}
- fd.extra_block = (uint8_t*)malloc(block_size);
- if (fd.extra_block == NULL) {
+ fd.extra_block = static_cast<uint8_t*>(malloc(block_size));
+ if (fd.extra_block == nullptr) {
fprintf(stderr, "failed to allocate %d bites for extra_block\n", block_size);
result = -1;
goto done;
}
- fd.ffd = open("/dev/fuse", O_RDWR);
- if (fd.ffd < 0) {
+ fd.ffd.reset(open("/dev/fuse", O_RDWR));
+ if (!fd.ffd) {
perror("open /dev/fuse");
result = -1;
goto done;
@@ -431,18 +397,18 @@
{
std::string opts = android::base::StringPrintf(
- "fd=%d,user_id=%d,group_id=%d,max_read=%u,allow_other,rootmode=040000", fd.ffd, fd.uid,
- fd.gid, block_size);
+ "fd=%d,user_id=%d,group_id=%d,max_read=%u,allow_other,rootmode=040000", fd.ffd.get(),
+ fd.uid, fd.gid, block_size);
- result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT, "fuse",
- MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts.c_str());
- if (result < 0) {
+ result = mount("/dev/fuse", mount_point, "fuse", MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC,
+ opts.c_str());
+ if (result == -1) {
perror("mount");
goto done;
}
}
- uint8_t request_buffer[sizeof(struct fuse_in_header) + PATH_MAX * 8];
+ uint8_t request_buffer[sizeof(fuse_in_header) + PATH_MAX * 8];
for (;;) {
ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer)));
if (len == -1) {
@@ -454,13 +420,13 @@
continue;
}
- if (static_cast<size_t>(len) < sizeof(struct fuse_in_header)) {
+ if (static_cast<size_t>(len) < sizeof(fuse_in_header)) {
fprintf(stderr, "request too short: len=%zd\n", len);
continue;
}
- struct fuse_in_header* hdr = reinterpret_cast<struct fuse_in_header*>(request_buffer);
- void* data = request_buffer + sizeof(struct fuse_in_header);
+ fuse_in_header* hdr = reinterpret_cast<fuse_in_header*>(request_buffer);
+ void* data = request_buffer + sizeof(fuse_in_header);
result = -ENOSYS;
@@ -504,7 +470,7 @@
}
if (result != NO_STATUS) {
- struct fuse_out_header outhdr;
+ fuse_out_header outhdr;
outhdr.len = sizeof(outhdr);
outhdr.error = result;
outhdr.unique = hdr->unique;
@@ -513,15 +479,12 @@
}
done:
- fd.vtab->close(fd.cookie);
+ fd.vtab.close();
- result = umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_DETACH);
- if (result < 0) {
- printf("fuse_sideload umount failed: %s\n", strerror(errno));
+ if (umount2(mount_point, MNT_DETACH) == -1) {
+ fprintf(stderr, "fuse_sideload umount failed: %s\n", strerror(errno));
}
- if (fd.ffd) close(fd.ffd);
- free(fd.hashes);
free(fd.block_data);
free(fd.extra_block);
diff --git a/fuse_sideload.h b/fuse_sideload.h
index c0b16ef..1b34cbd 100644
--- a/fuse_sideload.h
+++ b/fuse_sideload.h
@@ -17,22 +17,24 @@
#ifndef __FUSE_SIDELOAD_H
#define __FUSE_SIDELOAD_H
-// define the filenames created by the sideload FUSE filesystem
-#define FUSE_SIDELOAD_HOST_MOUNTPOINT "/sideload"
-#define FUSE_SIDELOAD_HOST_FILENAME "package.zip"
-#define FUSE_SIDELOAD_HOST_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_FILENAME)
-#define FUSE_SIDELOAD_HOST_EXIT_FLAG "exit"
-#define FUSE_SIDELOAD_HOST_EXIT_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_EXIT_FLAG)
+#include <functional>
+
+// Define the filenames created by the sideload FUSE filesystem.
+static constexpr const char* FUSE_SIDELOAD_HOST_MOUNTPOINT = "/sideload";
+static constexpr const char* FUSE_SIDELOAD_HOST_FILENAME = "package.zip";
+static constexpr const char* FUSE_SIDELOAD_HOST_PATHNAME = "/sideload/package.zip";
+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
- int (*read_block)(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size);
+ // read a block
+ std::function<int(uint32_t block, uint8_t* buffer, uint32_t fetch_size)> read_block;
- // close down
- void (*close)(void* cookie);
+ // close down
+ std::function<void(void)> close;
};
-int run_fuse_sideload(struct provider_vtab* vtab, void* cookie,
- uint64_t file_size, uint32_t block_size);
+int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t block_size,
+ const char* mount_point = FUSE_SIDELOAD_HOST_MOUNTPOINT);
#endif
diff --git a/install.cpp b/install.cpp
index 7fbf5c0..d058931 100644
--- a/install.cpp
+++ b/install.cpp
@@ -49,9 +49,9 @@
#include <ziparchive/zip_archive.h>
#include "common.h"
-#include "error_code.h"
#include "otautil/SysUtil.h"
#include "otautil/ThermalUtil.h"
+#include "otautil/error_code.h"
#include "private/install.h"
#include "roots.h"
#include "ui.h"
@@ -148,13 +148,23 @@
return INSTALL_ERROR;
}
- // We allow the package to not have any serialno, but if it has a non-empty
- // value it should match.
+ // 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() && pkg_serial_no != value) {
- LOG(ERROR) << "Package is for serial " << pkg_serial_no;
- return INSTALL_ERROR;
+ 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") {
@@ -280,7 +290,7 @@
*cmd = {
binary_path,
- EXPAND(RECOVERY_API_VERSION), // defined in Android.mk
+ std::to_string(kRecoveryApiVersion),
std::to_string(status_fd),
package,
};
@@ -321,6 +331,7 @@
if (ret) {
close(pipefd[0]);
close(pipefd[1]);
+ log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
return ret;
}
@@ -385,6 +396,7 @@
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;
}
@@ -573,6 +585,7 @@
MemMapping map;
if (!map.MapFile(path)) {
LOG(ERROR) << "failed to map file";
+ log_buffer->push_back(android::base::StringPrintf("error: %d", kMapFileFailure));
return INSTALL_CORRUPT;
}
@@ -640,7 +653,7 @@
std::chrono::duration<double> duration = std::chrono::system_clock::now() - start;
int time_total = static_cast<int>(duration.count());
- bool has_cache = volume_for_path("/cache") != nullptr;
+ bool has_cache = volume_for_mount_point("/cache") != nullptr;
// Skip logging the uncrypt_status on devices without /cache.
if (has_cache) {
static constexpr const char* UNCRYPT_STATUS = "/cache/recovery/uncrypt_status";
diff --git a/minadbd/Android.mk b/minadbd/Android.mk
index de0b0c8..50e3b34 100644
--- a/minadbd/Android.mk
+++ b/minadbd/Android.mk
@@ -1,13 +1,25 @@
# 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 \
- -Wno-unused-parameter \
- -Wno-missing-field-initializers \
-DADB_HOST=0 \
+# libminadbd (static library)
+# ===============================
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
@@ -15,25 +27,29 @@
minadbd.cpp \
minadbd_services.cpp \
-LOCAL_CLANG := true
LOCAL_MODULE := libminadbd
LOCAL_CFLAGS := $(minadbd_cflags)
-LOCAL_CONLY_FLAGS := -Wimplicit-function-declaration
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_CLANG := true
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 := libminadbd
-LOCAL_SHARED_LIBRARIES := liblog libbase libcutils
+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 0f4c256..9bd3f23 100644
--- a/minadbd/fuse_adb_provider.cpp
+++ b/minadbd/fuse_adb_provider.cpp
@@ -14,46 +14,43 @@
* limitations under the License.
*/
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
+#include "fuse_adb_provider.h"
+
#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <functional>
#include "adb.h"
#include "adb_io.h"
-#include "fuse_adb_provider.h"
#include "fuse_sideload.h"
-int read_block_adb(void* data, uint32_t block, uint8_t* buffer, uint32_t fetch_size) {
- adb_data* ad = reinterpret_cast<adb_data*>(data);
+int read_block_adb(const adb_data& ad, uint32_t block, uint8_t* buffer, uint32_t fetch_size) {
+ if (!WriteFdFmt(ad.sfd, "%08u", block)) {
+ fprintf(stderr, "failed to write to adb host: %s\n", strerror(errno));
+ return -EIO;
+ }
- if (!WriteFdFmt(ad->sfd, "%08u", block)) {
- fprintf(stderr, "failed to write to adb host: %s\n", strerror(errno));
- return -EIO;
- }
+ if (!ReadFdExactly(ad.sfd, buffer, fetch_size)) {
+ fprintf(stderr, "failed to read from adb host: %s\n", strerror(errno));
+ return -EIO;
+ }
- if (!ReadFdExactly(ad->sfd, buffer, fetch_size)) {
- fprintf(stderr, "failed to read from adb host: %s\n", strerror(errno));
- return -EIO;
- }
-
- return 0;
-}
-
-static void close_adb(void* data) {
- adb_data* ad = reinterpret_cast<adb_data*>(data);
- WriteFdExactly(ad->sfd, "DONEDONE");
+ 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;
+ adb_data ad;
+ ad.sfd = sfd;
+ ad.file_size = file_size;
+ ad.block_size = block_size;
- provider_vtab vtab;
- vtab.read_block = read_block_adb;
- vtab.close = close_adb;
+ 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, &ad, file_size, block_size);
+ return run_fuse_sideload(vtab, file_size, block_size);
}
diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h
index 9941709..36d86d5 100644
--- a/minadbd/fuse_adb_provider.h
+++ b/minadbd/fuse_adb_provider.h
@@ -20,13 +20,13 @@
#include <stdint.h>
struct adb_data {
- int sfd; // file descriptor for the adb channel
+ int sfd; // file descriptor for the adb channel
- uint64_t file_size;
- uint32_t block_size;
+ uint64_t file_size;
+ uint32_t block_size;
};
-int read_block_adb(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size);
+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 31be2a6..00250e5 100644
--- a/minadbd/fuse_adb_provider_test.cpp
+++ b/minadbd/fuse_adb_provider_test.cpp
@@ -46,8 +46,8 @@
uint32_t block = 1234U;
const char expected_block[] = "00001234";
- ASSERT_EQ(0, read_block_adb(static_cast<void*>(&data), block,
- reinterpret_cast<uint8_t*>(block_data), sizeof(expected_data) - 1));
+ ASSERT_EQ(0, read_block_adb(data, block, reinterpret_cast<uint8_t*>(block_data),
+ sizeof(expected_data) - 1));
// Check that read_block_adb requested the right block.
char block_req[sizeof(expected_block)] = {};
@@ -84,7 +84,7 @@
signal(SIGPIPE, SIG_IGN);
char buf[1];
- ASSERT_EQ(-EIO, read_block_adb(static_cast<void*>(&data), 0, reinterpret_cast<uint8_t*>(buf), 1));
+ ASSERT_EQ(-EIO, read_block_adb(data, 0, reinterpret_cast<uint8_t*>(buf), 1));
close(sockets[0]);
}
diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp
index 61c06cc..043c51a 100644
--- a/minadbd/minadbd_services.cpp
+++ b/minadbd/minadbd_services.cpp
@@ -58,20 +58,20 @@
return s[0];
}
-int service_to_fd(const char* name, const atransport* transport) {
- int ret = -1;
+int service_to_fd(const char* name, atransport* /* transport */) {
+ int ret = -1;
- 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);
- }
- if (ret >= 0) {
- close_on_exec(ret);
- }
- return ret;
+ 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);
+ }
+ if (ret >= 0) {
+ close_on_exec(ret);
+ }
+ return ret;
}
diff --git a/minui/Android.mk b/minui/Android.mk
index 6522fcf..ae1552b 100644
--- a/minui/Android.mk
+++ b/minui/Android.mk
@@ -13,6 +13,9 @@
# limitations under the License.
LOCAL_PATH := $(call my-dir)
+
+# libminui (static library)
+# ===============================
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
@@ -25,14 +28,14 @@
LOCAL_WHOLE_STATIC_LIBRARIES := \
libadf \
- libdrm_platform \
+ libdrm \
libsync_recovery
LOCAL_STATIC_LIBRARIES := \
libpng \
libbase
-LOCAL_CFLAGS := -Werror
+LOCAL_CFLAGS := -Wall -Werror
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
@@ -58,8 +61,16 @@
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
@@ -68,7 +79,7 @@
libpng \
libbase
-LOCAL_CFLAGS := -Werror
+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 24c2a82..2894c3b 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -15,6 +15,7 @@
*/
#include <dirent.h>
+#include <errno.h>
#include <fcntl.h>
#include <linux/input.h>
#include <stdio.h>
diff --git a/minui/graphics.cpp b/minui/graphics.cpp
index 3bfce11..56f471b 100644
--- a/minui/graphics.cpp
+++ b/minui/graphics.cpp
@@ -16,6 +16,7 @@
#include "graphics.h"
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -35,281 +36,311 @@
static int overscan_offset_x = 0;
static int overscan_offset_y = 0;
-static unsigned char gr_current_r = 255;
-static unsigned char gr_current_g = 255;
-static unsigned char gr_current_b = 255;
-static unsigned char gr_current_a = 255;
+static uint32_t gr_current = ~0;
+static constexpr uint32_t alpha_mask = 0xff000000;
static GRSurface* gr_draw = NULL;
+static GRRotation rotation = ROTATION_NONE;
-static bool outside(int x, int y)
-{
- return x < 0 || x >= gr_draw->width || y < 0 || y >= gr_draw->height;
+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);
}
-const GRFont* gr_sys_font()
-{
- return gr_font;
+const GRFont* gr_sys_font() {
+ return gr_font;
}
-int gr_measure(const GRFont* font, const char *s)
-{
- return font->char_width * strlen(s);
+int gr_measure(const GRFont* font, const char* s) {
+ return font->char_width * strlen(s);
}
-void gr_font_size(const GRFont* font, int *x, int *y)
-{
- *x = font->char_width;
- *y = font->char_height;
+void gr_font_size(const GRFont* font, int* x, int* y) {
+ *x = font->char_width;
+ *y = font->char_height;
}
-static void text_blend(unsigned char* src_p, int src_row_bytes,
- unsigned char* dst_p, int dst_row_bytes,
- int width, int height)
-{
- for (int j = 0; j < height; ++j) {
- unsigned char* sx = src_p;
- unsigned char* px = dst_p;
- for (int i = 0; i < width; ++i) {
- unsigned char a = *sx++;
- if (gr_current_a < 255) a = ((int)a * gr_current_a) / 255;
- if (a == 255) {
- *px++ = gr_current_r;
- *px++ = gr_current_g;
- *px++ = gr_current_b;
- px++;
- } else if (a > 0) {
- *px = (*px * (255-a) + gr_current_r * a) / 255;
- ++px;
- *px = (*px * (255-a) + gr_current_g * a) / 255;
- ++px;
- *px = (*px * (255-a) + gr_current_b * a) / 255;
- ++px;
- ++px;
- } else {
- px += 4;
- }
- }
- src_p += src_row_bytes;
- dst_p += dst_row_bytes;
+// Blends gr_current onto pix value, assumes alpha as most significant byte.
+static inline uint32_t pixel_blend(uint8_t alpha, uint32_t pix) {
+ if (alpha == 255) return gr_current;
+ if (alpha == 0) return pix;
+ uint32_t pix_r = pix & 0xff;
+ uint32_t pix_g = pix & 0xff00;
+ uint32_t pix_b = pix & 0xff0000;
+ uint32_t cur_r = gr_current & 0xff;
+ uint32_t cur_g = gr_current & 0xff00;
+ uint32_t cur_b = gr_current & 0xff0000;
+
+ uint32_t out_r = (pix_r * (255 - alpha) + cur_r * alpha) / 255;
+ uint32_t out_g = (pix_g * (255 - alpha) + cur_g * alpha) / 255;
+ uint32_t out_b = (pix_b * (255 - alpha) + cur_b * alpha) / 255;
+
+ return (out_r & 0xff) | (out_g & 0xff00) | (out_b & 0xff0000) | (gr_current & 0xff000000);
+}
+
+// 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);
+ }
+}
+
+// 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;
+ }
+}
+
+// returns pixel pointer at given coordinates with rotation adjustment.
+static uint32_t* pixel_at(GRSurface* surf, 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;
+ default:
+ printf("invalid rotation %d", 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) {
+ 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;
+ uint32_t* px = dst_p;
+ for (int i = 0; i < width; ++i, incr_x(&px, dst_row_pixels)) {
+ uint8_t a = *sx++;
+ if (alpha_current < 255) a = (static_cast<uint32_t>(a) * alpha_current) / 255;
+ *px = pixel_blend(a, *px);
}
+ src_p += src_row_bytes;
+ incr_y(&dst_p, dst_row_pixels);
+ }
}
-void gr_text(const GRFont* font, int x, int y, const char *s, bool bold)
-{
- if (!font->texture || gr_current_a == 0) return;
+void gr_text(const GRFont* font, int x, int y, const char* s, bool bold) {
+ if (!font || !font->texture || (gr_current & alpha_mask) == 0) return;
- bold = bold && (font->texture->height != font->char_height);
+ if (font->texture->pixel_bytes != 1) {
+ printf("gr_text: font has wrong format\n");
+ return;
+ }
- x += overscan_offset_x;
- y += overscan_offset_y;
+ bold = bold && (font->texture->height != font->char_height);
- unsigned char ch;
- while ((ch = *s++)) {
- if (outside(x, y) || outside(x+font->char_width-1, y+font->char_height-1)) break;
+ x += overscan_offset_x;
+ y += overscan_offset_y;
- if (ch < ' ' || ch > '~') {
- ch = '?';
- }
+ unsigned char ch;
+ while ((ch = *s++)) {
+ if (outside(x, y) || outside(x + font->char_width - 1, y + font->char_height - 1)) break;
- unsigned char* src_p = font->texture->data + ((ch - ' ') * font->char_width) +
- (bold ? font->char_height * font->texture->row_bytes : 0);
- unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes;
-
- text_blend(src_p, font->texture->row_bytes,
- dst_p, gr_draw->row_bytes,
- font->char_width, font->char_height);
-
- x += font->char_width;
+ if (ch < ' ' || ch > '~') {
+ ch = '?';
}
+
+ 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);
+
+ text_blend(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;
+ if (icon == NULL) return;
- if (icon->pixel_bytes != 1) {
- printf("gr_texticon: source has wrong format\n");
- return;
- }
+ if (icon->pixel_bytes != 1) {
+ printf("gr_texticon: source has wrong format\n");
+ return;
+ }
- x += overscan_offset_x;
- y += overscan_offset_y;
+ x += overscan_offset_x;
+ y += overscan_offset_y;
- if (outside(x, y) || outside(x+icon->width-1, y+icon->height-1)) return;
+ if (outside(x, y) || outside(x + icon->width - 1, y + icon->height - 1)) return;
- unsigned char* src_p = icon->data;
- unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes;
+ 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, gr_draw->row_bytes,
- icon->width, icon->height);
+ text_blend(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)
-{
+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_r = b;
- gr_current_g = g;
- gr_current_b = r;
- gr_current_a = a;
+ gr_current = (a32 << 24) | (r32 << 16) | (g32 << 8) | b32;
#else
- gr_current_r = r;
- gr_current_g = g;
- gr_current_b = b;
- gr_current_a = a;
+ gr_current = (a32 << 24) | (b32 << 16) | (g32 << 8) | r32;
#endif
}
-void gr_clear()
-{
- if (gr_current_r == gr_current_g && gr_current_r == gr_current_b) {
- memset(gr_draw->data, gr_current_r, gr_draw->height * gr_draw->row_bytes);
- } else {
- unsigned char* px = gr_draw->data;
- for (int y = 0; y < gr_draw->height; ++y) {
- for (int x = 0; x < gr_draw->width; ++x) {
- *px++ = gr_current_r;
- *px++ = gr_current_g;
- *px++ = gr_current_b;
- px++;
- }
- px += gr_draw->row_bytes - (gr_draw->width * gr_draw->pixel_bytes);
- }
+void gr_clear() {
+ if ((gr_current & 0xff) == ((gr_current >> 8) & 0xff) &&
+ (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);
+ } else {
+ 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) {
+ *px++ = gr_current;
+ }
+ px += row_diff;
}
+ }
}
-void gr_fill(int x1, int y1, int x2, int y2)
-{
- x1 += overscan_offset_x;
- y1 += overscan_offset_y;
+void gr_fill(int x1, int y1, int x2, int y2) {
+ x1 += overscan_offset_x;
+ y1 += overscan_offset_y;
- x2 += overscan_offset_x;
- y2 += overscan_offset_y;
+ x2 += overscan_offset_x;
+ y2 += overscan_offset_y;
- if (outside(x1, y1) || outside(x2-1, y2-1)) return;
+ if (outside(x1, y1) || outside(x2 - 1, y2 - 1)) return;
- unsigned char* p = gr_draw->data + y1 * gr_draw->row_bytes + x1 * gr_draw->pixel_bytes;
- if (gr_current_a == 255) {
- int x, y;
- for (y = y1; y < y2; ++y) {
- unsigned char* px = p;
- for (x = x1; x < x2; ++x) {
- *px++ = gr_current_r;
- *px++ = gr_current_g;
- *px++ = gr_current_b;
- px++;
- }
- p += gr_draw->row_bytes;
- }
- } else if (gr_current_a > 0) {
- int x, y;
- for (y = y1; y < y2; ++y) {
- unsigned char* px = p;
- for (x = x1; x < x2; ++x) {
- *px = (*px * (255-gr_current_a) + gr_current_r * gr_current_a) / 255;
- ++px;
- *px = (*px * (255-gr_current_a) + gr_current_g * gr_current_a) / 255;
- ++px;
- *px = (*px * (255-gr_current_a) + gr_current_b * gr_current_a) / 255;
- ++px;
- ++px;
- }
- p += gr_draw->row_bytes;
- }
+ int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes;
+ uint32_t* p = pixel_at(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) {
+ uint32_t* px = p;
+ for (int x = x1; x < x2; ++x) {
+ *px = pixel_blend(alpha, *px);
+ incr_x(&px, row_pixels);
+ }
+ incr_y(&p, row_pixels);
}
+ }
}
void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) {
- if (source == NULL) return;
+ if (source == NULL) return;
- if (gr_draw->pixel_bytes != source->pixel_bytes) {
- printf("gr_blit: source has wrong format\n");
- return;
+ if (gr_draw->pixel_bytes != source->pixel_bytes) {
+ printf("gr_blit: source has wrong format\n");
+ return;
+ }
+
+ dx += overscan_offset_x;
+ dy += overscan_offset_y;
+
+ if (outside(dx, dy) || outside(dx + w - 1, dy + h - 1)) return;
+
+ if (rotation) {
+ 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);
+
+ for (int y = 0; y < h; y += 1) {
+ uint32_t* src_px = src_py;
+ uint32_t* dst_px = dst_py;
+ for (int x = 0; x < w; x += 1) {
+ *dst_px = *src_px++;
+ incr_x(&dst_px, row_pixels);
+ }
+ src_py += src_row_pixels;
+ incr_y(&dst_py, row_pixels);
}
-
- dx += overscan_offset_x;
- dy += overscan_offset_y;
-
- if (outside(dx, dy) || outside(dx+w-1, dy+h-1)) return;
-
- 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;
+ } 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;
int i;
for (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;
+ memcpy(dst_p, src_p, w * source->pixel_bytes);
+ src_p += source->row_bytes;
+ dst_p += gr_draw->row_bytes;
}
+ }
}
unsigned int gr_get_width(GRSurface* surface) {
- if (surface == NULL) {
- return 0;
- }
- return surface->width;
+ if (surface == NULL) {
+ return 0;
+ }
+ return surface->width;
}
unsigned int gr_get_height(GRSurface* surface) {
- if (surface == NULL) {
- return 0;
- }
- return surface->height;
+ if (surface == NULL) {
+ return 0;
+ }
+ return surface->height;
}
int gr_init_font(const char* name, GRFont** dest) {
- GRFont* font = static_cast<GRFont*>(calloc(1, sizeof(*gr_font)));
- if (font == nullptr) {
- return -1;
- }
+ GRFont* font = static_cast<GRFont*>(calloc(1, sizeof(*gr_font)));
+ if (font == nullptr) {
+ return -1;
+ }
- int res = res_create_alpha_surface(name, &(font->texture));
- if (res < 0) {
- free(font);
- return res;
- }
+ int res = res_create_alpha_surface(name, &(font->texture));
+ if (res < 0) {
+ free(font);
+ return res;
+ }
- // The font image should be a 96x2 array of character images. The
- // columns are the printable ASCII characters 0x20 - 0x7f. The
- // top row is regular text; the bottom row is bold.
- font->char_width = font->texture->width / 96;
- font->char_height = font->texture->height / 2;
+ // The font image should be a 96x2 array of character images. The
+ // columns are the printable ASCII characters 0x20 - 0x7f. The
+ // top row is regular text; the bottom row is bold.
+ font->char_width = font->texture->width / 96;
+ font->char_height = font->texture->height / 2;
- *dest = font;
+ *dest = font;
- return 0;
+ return 0;
}
-static void gr_init_font(void)
-{
- int res = gr_init_font("font", &gr_font);
- if (res == 0) {
- return;
- }
+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);
+ 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;
- // 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* 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);
+ }
- 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;
+ gr_font->char_width = font.char_width;
+ gr_font->char_height = font.char_height;
}
void gr_flip() {
@@ -344,6 +375,12 @@
gr_flip();
gr_flip();
+ gr_rotate(DEFAULT_ROTATION);
+
+ if (gr_draw->pixel_bytes != 4) {
+ printf("gr_init: Only 4-byte pixel formats supported\n");
+ }
+
return 0;
}
@@ -352,13 +389,19 @@
}
int gr_fb_width() {
- return gr_draw->width - 2 * overscan_offset_x;
+ return rotation % 2 ? gr_draw->height - 2 * overscan_offset_y
+ : gr_draw->width - 2 * overscan_offset_x;
}
int gr_fb_height() {
- return gr_draw->height - 2 * overscan_offset_y;
+ return rotation % 2 ? gr_draw->width - 2 * overscan_offset_x
+ : gr_draw->height - 2 * overscan_offset_y;
}
void gr_fb_blank(bool blank) {
gr_backend->Blank(blank);
}
+
+void gr_rotate(GRRotation rot) {
+ rotation = rot;
+}
diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h
index 017ddde..f9da199 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -21,23 +21,31 @@
#include <functional>
#include <string>
+#include <vector>
//
// Graphics.
//
struct GRSurface {
- int width;
- int height;
- int row_bytes;
- int pixel_bytes;
- unsigned char* data;
+ int width;
+ int height;
+ int row_bytes;
+ int pixel_bytes;
+ unsigned char* data;
};
struct GRFont {
- GRSurface* texture;
- int char_width;
- int char_height;
+ GRSurface* texture;
+ int char_width;
+ int char_height;
+};
+
+enum GRRotation {
+ ROTATION_NONE = 0,
+ ROTATION_RIGHT = 1,
+ ROTATION_DOWN = 2,
+ ROTATION_LEFT = 3,
};
int gr_init();
@@ -57,14 +65,17 @@
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);
-int gr_measure(const GRFont* font, const char *s);
-void gr_font_size(const GRFont* font, int *x, int *y);
+void gr_text(const GRFont* font, int x, int y, const char* s, bool bold);
+int gr_measure(const GRFont* font, const char* s);
+void 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);
+// Set rotation, flips gr_fb_width/height if 90 degree rotation difference
+void gr_rotate(GRRotation rotation);
+
//
// Input events.
//
@@ -114,8 +125,8 @@
// should have a 'Frames' text chunk whose value is the number of
// frames this image represents. The pixel data itself is interlaced
// by row.
-int res_create_multi_display_surface(const char* name, int* frames,
- int* fps, GRSurface*** pSurface);
+int res_create_multi_display_surface(const char* name, int* frames, int* fps,
+ GRSurface*** pSurface);
// Load a single alpha surface from a grayscale PNG image.
int res_create_alpha_surface(const char* name, GRSurface** pSurface);
@@ -129,6 +140,9 @@
int res_create_localized_alpha_surface(const char* name, const char* locale,
GRSurface** pSurface);
+// Return a list of locale strings embedded in |png_name|. Return a empty list in case of failure.
+std::vector<std::string> get_locales_in_png(const std::string& png_name);
+
// Free a surface allocated by any of the res_create_*_surface()
// functions.
void res_free_surface(GRSurface* surface);
diff --git a/minui/resources.cpp b/minui/resources.cpp
index 8f8d36d..52ab60b 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -25,10 +25,12 @@
#include <sys/types.h>
#include <unistd.h>
+#include <memory>
#include <regex>
#include <string>
#include <vector>
+#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <png.h>
@@ -46,89 +48,126 @@
return surface;
}
-static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr,
- png_uint_32* width, png_uint_32* height, png_byte* channels) {
- char resPath[256];
- unsigned char header[8];
- int result = 0;
- int color_type, bit_depth;
- size_t bytesRead;
+// 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);
- snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name);
- resPath[sizeof(resPath)-1] = '\0';
- FILE* fp = fopen(resPath, "rbe");
- if (fp == NULL) {
- result = -1;
- goto exit;
- }
+ ~PngHandler();
- bytesRead = fread(header, 1, sizeof(header), fp);
- if (bytesRead != sizeof(header)) {
- result = -2;
- goto exit;
- }
+ png_uint_32 width() const {
+ return width_;
+ }
- if (png_sig_cmp(header, 0, sizeof(header))) {
- result = -3;
- goto exit;
- }
+ png_uint_32 height() const {
+ return height_;
+ }
- *png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
- if (!*png_ptr) {
- result = -4;
- goto exit;
- }
+ png_byte channels() const {
+ return channels_;
+ }
- *info_ptr = png_create_info_struct(*png_ptr);
- if (!*info_ptr) {
- result = -5;
- goto exit;
- }
+ png_structp png_ptr() const {
+ return png_ptr_;
+ }
- if (setjmp(png_jmpbuf(*png_ptr))) {
- result = -6;
- goto exit;
- }
+ png_infop info_ptr() const {
+ return info_ptr_;
+ }
- png_init_io(*png_ptr, fp);
- png_set_sig_bytes(*png_ptr, sizeof(header));
- png_read_info(*png_ptr, *info_ptr);
+ int error_code() const {
+ return error_code_;
+ };
- png_get_IHDR(*png_ptr, *info_ptr, width, height, &bit_depth,
- &color_type, NULL, NULL, NULL);
+ operator bool() const {
+ return error_code_ == 0;
+ }
- *channels = png_get_channels(*png_ptr, *info_ptr);
+ private:
+ png_structp png_ptr_{ nullptr };
+ png_infop info_ptr_{ nullptr };
+ png_uint_32 width_;
+ png_uint_32 height_;
+ png_byte channels_;
- 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) {
- // 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) {
- // 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
- // general.
- 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);
- result = -7;
- goto exit;
- }
+ // 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_;
+};
- return result;
+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());
+ png_fp_.reset(fopen(res_path.c_str(), "rbe"));
+ if (!png_fp_) {
+ error_code_ = -1;
+ return;
+ }
- exit:
- if (result < 0) {
- png_destroy_read_struct(png_ptr, info_ptr, NULL);
- }
- if (fp != NULL) {
- fclose(fp);
- }
+ unsigned char header[8];
+ size_t bytesRead = fread(header, 1, sizeof(header), png_fp_.get());
+ if (bytesRead != sizeof(header)) {
+ error_code_ = -2;
+ return;
+ }
- return result;
+ if (png_sig_cmp(header, 0, sizeof(header))) {
+ error_code_ = -3;
+ return;
+ }
+
+ png_ptr_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (!png_ptr_) {
+ error_code_ = -4;
+ return;
+ }
+
+ info_ptr_ = png_create_info_struct(png_ptr_);
+ if (!info_ptr_) {
+ error_code_ = -5;
+ return;
+ }
+
+ if (setjmp(png_jmpbuf(png_ptr_))) {
+ error_code_ = -6;
+ return;
+ }
+
+ png_init_io(png_ptr_, png_fp_.get());
+ 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,
+ nullptr);
+
+ channels_ = png_get_channels(png_ptr_, info_ptr_);
+
+ 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) {
+ // 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) {
+ // 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
+ // general.
+ 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);
+ error_code_ = -7;
+ }
+}
+
+PngHandler::~PngHandler() {
+ if (png_ptr_) {
+ png_destroy_read_struct(&png_ptr_, &info_ptr_, nullptr);
+ }
}
// "display" surfaces are transformed into the framebuffer's required
@@ -198,178 +237,152 @@
}
int res_create_display_surface(const char* name, GRSurface** pSurface) {
- GRSurface* surface = NULL;
- int result = 0;
- png_structp png_ptr = NULL;
- png_infop info_ptr = NULL;
- png_uint_32 width, height;
- png_byte channels;
- unsigned char* p_row;
- unsigned int y;
+ *pSurface = nullptr;
- *pSurface = NULL;
+ PngHandler png_handler(name);
+ if (!png_handler) return png_handler.error_code();
- result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
- if (result < 0) return result;
+ png_structp png_ptr = png_handler.png_ptr();
+ png_uint_32 width = png_handler.width();
+ png_uint_32 height = png_handler.height();
- surface = init_display_surface(width, height);
- if (surface == NULL) {
- result = -8;
- goto exit;
- }
+ GRSurface* surface = init_display_surface(width, height);
+ if (!surface) {
+ return -8;
+ }
#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
- png_set_bgr(png_ptr);
+ png_set_bgr(png_ptr);
#endif
- p_row = static_cast<unsigned char*>(malloc(width * 4));
- for (y = 0; y < height; ++y) {
- png_read_row(png_ptr, p_row, NULL);
- transform_rgb_to_draw(p_row, surface->data + y * surface->row_bytes, channels, width);
- }
- free(p_row);
+ 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);
+ }
- *pSurface = surface;
+ *pSurface = surface;
- exit:
- png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
- if (result < 0 && surface != NULL) free(surface);
- return result;
+ return 0;
}
int res_create_multi_display_surface(const char* name, int* frames, int* fps,
- GRSurface*** pSurface) {
- GRSurface** surface = NULL;
- int result = 0;
- png_structp png_ptr = NULL;
- png_infop info_ptr = NULL;
- png_uint_32 width, height;
- png_byte channels;
- png_textp text;
- int num_text;
- unsigned char* p_row;
- unsigned int y;
+ GRSurface*** pSurface) {
+ *pSurface = nullptr;
+ *frames = -1;
- *pSurface = NULL;
- *frames = -1;
+ PngHandler png_handler(name);
+ if (!png_handler) return png_handler.error_code();
- result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
- if (result < 0) return result;
+ png_structp png_ptr = png_handler.png_ptr();
+ png_uint_32 width = png_handler.width();
+ png_uint_32 height = png_handler.height();
- *frames = 1;
- *fps = 20;
- if (png_get_text(png_ptr, info_ptr, &text, &num_text)) {
- for (int i = 0; i < num_text; ++i) {
- if (text[i].key && strcmp(text[i].key, "Frames") == 0 && text[i].text) {
- *frames = atoi(text[i].text);
- } else if (text[i].key && strcmp(text[i].key, "FPS") == 0 && text[i].text) {
- *fps = atoi(text[i].text);
- }
- }
- printf(" found frames = %d\n", *frames);
- printf(" found fps = %d\n", *fps);
+ *frames = 1;
+ *fps = 20;
+ png_textp text;
+ int num_text;
+ if (png_get_text(png_ptr, png_handler.info_ptr(), &text, &num_text)) {
+ for (int i = 0; i < num_text; ++i) {
+ if (text[i].key && strcmp(text[i].key, "Frames") == 0 && text[i].text) {
+ *frames = atoi(text[i].text);
+ } else if (text[i].key && strcmp(text[i].key, "FPS") == 0 && text[i].text) {
+ *fps = atoi(text[i].text);
+ }
}
+ printf(" found frames = %d\n", *frames);
+ printf(" found fps = %d\n", *fps);
+ }
- if (*frames <= 0 || *fps <= 0) {
- printf("bad number of frames (%d) and/or FPS (%d)\n", *frames, *fps);
- result = -10;
- goto exit;
- }
+ int result = 0;
+ GRSurface** surface = nullptr;
+ if (*frames <= 0 || *fps <= 0) {
+ printf("bad number of frames (%d) and/or FPS (%d)\n", *frames, *fps);
+ result = -10;
+ goto exit;
+ }
- if (height % *frames != 0) {
- printf("bad height (%d) for frame count (%d)\n", height, *frames);
- result = -9;
- goto exit;
- }
+ if (height % *frames != 0) {
+ printf("bad height (%d) for frame count (%d)\n", height, *frames);
+ result = -9;
+ goto exit;
+ }
- surface = static_cast<GRSurface**>(calloc(*frames, sizeof(GRSurface*)));
- if (surface == NULL) {
- result = -8;
- goto exit;
+ surface = static_cast<GRSurface**>(calloc(*frames, sizeof(GRSurface*)));
+ if (!surface) {
+ result = -8;
+ goto exit;
+ }
+ for (int i = 0; i < *frames; ++i) {
+ surface[i] = init_display_surface(width, height / *frames);
+ if (!surface[i]) {
+ result = -8;
+ goto exit;
}
- for (int i = 0; i < *frames; ++i) {
- surface[i] = init_display_surface(width, height / *frames);
- if (surface[i] == NULL) {
- result = -8;
- goto exit;
- }
- }
+ }
#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
- png_set_bgr(png_ptr);
+ png_set_bgr(png_ptr);
#endif
- p_row = static_cast<unsigned char*>(malloc(width * 4));
- for (y = 0; y < height; ++y) {
- png_read_row(png_ptr, p_row, NULL);
- int frame = y % *frames;
- unsigned char* out_row = surface[frame]->data +
- (y / *frames) * surface[frame]->row_bytes;
- transform_rgb_to_draw(p_row, out_row, channels, width);
- }
- free(p_row);
+ 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);
+ 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);
+ }
- *pSurface = surface;
+ *pSurface = surface;
exit:
- png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
-
- if (result < 0) {
- if (surface) {
- for (int i = 0; i < *frames; ++i) {
- free(surface[i]);
- }
- free(surface);
- }
+ if (result < 0) {
+ if (surface) {
+ for (int i = 0; i < *frames; ++i) {
+ free(surface[i]);
+ }
+ free(surface);
}
- return result;
+ }
+ return result;
}
int res_create_alpha_surface(const char* name, GRSurface** pSurface) {
- GRSurface* surface = NULL;
- int result = 0;
- png_structp png_ptr = NULL;
- png_infop info_ptr = NULL;
- png_uint_32 width, height;
- png_byte channels;
+ *pSurface = nullptr;
- *pSurface = NULL;
+ PngHandler png_handler(name);
+ if (!png_handler) return png_handler.error_code();
- result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
- if (result < 0) return result;
+ if (png_handler.channels() != 1) {
+ return -7;
+ }
- if (channels != 1) {
- result = -7;
- goto exit;
- }
+ png_structp png_ptr = png_handler.png_ptr();
+ png_uint_32 width = png_handler.width();
+ png_uint_32 height = png_handler.height();
- surface = malloc_surface(width * height);
- if (surface == NULL) {
- result = -8;
- goto exit;
- }
- surface->width = width;
- surface->height = height;
- surface->row_bytes = width;
- surface->pixel_bytes = 1;
+ GRSurface* surface = malloc_surface(width * height);
+ 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);
+ png_set_bgr(png_ptr);
#endif
- unsigned char* p_row;
- unsigned int y;
- for (y = 0; y < height; ++y) {
- p_row = surface->data + y * surface->row_bytes;
- png_read_row(png_ptr, p_row, NULL);
- }
+ for (png_uint_32 y = 0; y < height; ++y) {
+ unsigned char* p_row = surface->data + y * surface->row_bytes;
+ png_read_row(png_ptr, p_row, nullptr);
+ }
- *pSurface = surface;
+ *pSurface = surface;
- exit:
- png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
- if (result < 0 && surface != NULL) free(surface);
- return result;
+ return 0;
}
// This function tests if a locale string stored in PNG (prefix) matches
@@ -384,7 +397,7 @@
// 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 (android::base::StartsWith(locale, prefix.c_str())) {
+ if (android::base::StartsWith(locale, prefix)) {
return true;
}
@@ -396,75 +409,90 @@
return std::regex_match(locale, loc_regex);
}
+std::vector<std::string> get_locales_in_png(const std::string& png_name) {
+ PngHandler png_handler(png_name);
+ if (!png_handler) {
+ printf("Failed to open %s, error: %d\n", png_name.c_str(), png_handler.error_code());
+ return {};
+ }
+ if (png_handler.channels() != 1) {
+ printf("Expect input png to have 1 data channel, this file has %d\n", png_handler.channels());
+ return {};
+ }
+
+ std::vector<std::string> result;
+ std::vector<unsigned char> 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];
+ std::string loc(reinterpret_cast<char*>(&row[5]));
+ if (!loc.empty()) {
+ result.push_back(loc);
+ }
+ for (int i = 0; i < h; ++i, ++y) {
+ png_read_row(png_handler.png_ptr(), row.data(), nullptr);
+ }
+ }
+
+ return result;
+}
+
int res_create_localized_alpha_surface(const char* name,
const char* locale,
GRSurface** pSurface) {
- GRSurface* surface = NULL;
- int result = 0;
- png_structp png_ptr = NULL;
- png_infop info_ptr = NULL;
- png_uint_32 width, height;
- png_byte channels;
- png_uint_32 y;
- std::vector<unsigned char> row;
+ *pSurface = nullptr;
+ if (locale == nullptr) {
+ return 0;
+ }
- *pSurface = NULL;
+ PngHandler png_handler(name);
+ if (!png_handler) return png_handler.error_code();
- if (locale == NULL) {
- return result;
+ if (png_handler.channels() != 1) {
+ return -7;
+ }
+
+ png_structp png_ptr = png_handler.png_ptr();
+ png_uint_32 width = png_handler.width();
+ png_uint_32 height = png_handler.height();
+
+ for (png_uint_32 y = 0; y < height; ++y) {
+ std::vector<unsigned char> 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)) {
+ printf(" %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);
+
+ GRSurface* surface = malloc_surface(w * h);
+ if (!surface) {
+ return -8;
+ }
+ 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);
+ }
+
+ *pSurface = surface;
+ break;
}
- result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
- if (result < 0) return result;
-
- if (channels != 1) {
- result = -7;
- goto exit;
+ for (int i = 0; i < h; ++i, ++y) {
+ png_read_row(png_ptr, row.data(), nullptr);
}
+ }
- row.resize(width);
- for (y = 0; y < height; ++y) {
- png_read_row(png_ptr, row.data(), NULL);
- 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)) {
- printf(" %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);
-
- surface = malloc_surface(w*h);
- if (surface == NULL) {
- result = -8;
- goto exit;
- }
- surface->width = w;
- surface->height = h;
- surface->row_bytes = w;
- surface->pixel_bytes = 1;
-
- int i;
- for (i = 0; i < h; ++i, ++y) {
- png_read_row(png_ptr, row.data(), NULL);
- memcpy(surface->data + i*w, row.data(), w);
- }
-
- *pSurface = surface;
- break;
- } else {
- int i;
- for (i = 0; i < h; ++i, ++y) {
- png_read_row(png_ptr, row.data(), NULL);
- }
- }
- }
-
-exit:
- png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
- if (result < 0 && surface != NULL) free(surface);
- return result;
+ return 0;
}
void res_free_surface(GRSurface* surface) {
- free(surface);
+ free(surface);
}
diff --git a/otafault/Android.bp b/otafault/Android.bp
new file mode 100644
index 0000000..b39d5be
--- /dev/null
+++ b/otafault/Android.bp
@@ -0,0 +1,68 @@
+// 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/Android.mk b/otafault/Android.mk
deleted file mode 100644
index ec4cdb3..0000000
--- a/otafault/Android.mk
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright 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 languae governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-otafault_static_libs := \
- libziparchive \
- libz \
- libselinux \
- libbase \
- liblog
-
-LOCAL_CFLAGS := \
- -Werror \
- -Wthread-safety \
- -Wthread-safety-negative \
- -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS
-
-LOCAL_SRC_FILES := config.cpp ota_io.cpp
-LOCAL_MODULE_TAGS := eng
-LOCAL_MODULE := libotafault
-LOCAL_CLANG := true
-LOCAL_C_INCLUDES := bootable/recovery
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
-LOCAL_WHOLE_STATIC_LIBRARIES := $(otafault_static_libs)
-
-include $(BUILD_STATIC_LIBRARY)
-
-# otafault_test (static executable)
-# ===============================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := config.cpp ota_io.cpp test.cpp
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE := otafault_test
-LOCAL_STATIC_LIBRARIES := $(otafault_static_libs)
-LOCAL_CFLAGS := -Werror
-LOCAL_C_INCLUDES := bootable/recovery
-LOCAL_FORCE_STATIC_EXECUTABLE := true
-
-include $(BUILD_EXECUTABLE)
diff --git a/otafault/config.cpp b/otafault/config.cpp
index 8590833..3993948 100644
--- a/otafault/config.cpp
+++ b/otafault/config.cpp
@@ -14,17 +14,15 @@
* limitations under the License.
*/
+#include "otafault/config.h"
+
#include <map>
#include <string>
-#include <stdio.h>
-#include <unistd.h>
-
#include <android-base/stringprintf.h>
#include <ziparchive/zip_archive.h>
-#include "config.h"
-#include "ota_io.h"
+#include "otafault/ota_io.h"
#define OTAIO_MAX_FNAME_SIZE 128
@@ -69,7 +67,9 @@
fname.resize(OTAIO_MAX_FNAME_SIZE);
ZipString zip_type_path(type_path.c_str());
ZipEntry entry;
- int status = FindEntry(archive, zip_type_path, &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/config.h b/otafault/include/otafault/config.h
similarity index 76%
rename from otafault/config.h
rename to otafault/include/otafault/config.h
index 4adbdd1..cc4bfd2 100644
--- a/otafault/config.h
+++ b/otafault/include/otafault/config.h
@@ -15,13 +15,13 @@
*/
/*
- * Read configuration files in the OTA package to determine which files, if any, will trigger errors.
+ * 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.
+ * 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
@@ -29,9 +29,9 @@
* .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.
+ * 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_
@@ -39,8 +39,6 @@
#include <string>
-#include <stdbool.h>
-
#include <ziparchive/zip_archive.h>
#define OTAIO_BASE_DIR ".libotafault"
diff --git a/otafault/ota_io.h b/otafault/include/otafault/ota_io.h
similarity index 96%
rename from otafault/ota_io.h
rename to otafault/include/otafault/ota_io.h
index 9428f1b..45e481a 100644
--- a/otafault/ota_io.h
+++ b/otafault/include/otafault/ota_io.h
@@ -23,8 +23,9 @@
#ifndef _UPDATER_OTA_IO_H_
#define _UPDATER_OTA_IO_H_
+#include <stddef.h>
#include <stdio.h>
-#include <sys/stat.h>
+#include <sys/stat.h> // mode_t
#include <memory>
diff --git a/otafault/ota_io.cpp b/otafault/ota_io.cpp
index faae527..63ef18e 100644
--- a/otafault/ota_io.cpp
+++ b/otafault/ota_io.cpp
@@ -14,20 +14,23 @@
* limitations under the License.
*/
-#include "ota_io.h"
+#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 <memory>
#include <mutex>
+#include <string>
#include <android-base/thread_annotations.h>
-#include "config.h"
+
+#include "otafault/config.h"
static std::mutex filename_mutex;
static std::map<intptr_t, const char*> filename_cache GUARDED_BY(filename_mutex);
diff --git a/otafault/test.cpp b/otafault/test.cpp
index 6514782..63e2445 100644
--- a/otafault/test.cpp
+++ b/otafault/test.cpp
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
-#include "ota_io.h"
+#include "otafault/ota_io.h"
int main(int /* argc */, char** /* argv */) {
int fd = open("testdata/test.file", O_RDWR);
diff --git a/otautil/Android.bp b/otautil/Android.bp
index a2eaa04..75cf691 100644
--- a/otautil/Android.bp
+++ b/otautil/Android.bp
@@ -15,10 +15,14 @@
cc_library_static {
name: "libotautil",
+ host_supported: true,
+
srcs: [
"SysUtil.cpp",
"DirUtil.cpp",
"ThermalUtil.cpp",
+ "cache_location.cpp",
+ "rangeset.cpp",
],
static_libs: [
@@ -27,7 +31,12 @@
],
cflags: [
+ "-D_FILE_OFFSET_BITS=64",
"-Werror",
"-Wall",
],
+
+ export_include_dirs: [
+ "include",
+ ],
}
diff --git a/otautil/DirUtil.cpp b/otautil/DirUtil.cpp
index e08e360..61c8328 100644
--- a/otautil/DirUtil.cpp
+++ b/otautil/DirUtil.cpp
@@ -14,205 +14,103 @@
* limitations under the License.
*/
-#include "DirUtil.h"
+#include "otautil/DirUtil.h"
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
#include <dirent.h>
-#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
#include <string>
#include <selinux/label.h>
#include <selinux/selinux.h>
-typedef enum { DMISSING, DDIR, DILLEGAL } DirStatus;
+enum class DirStatus { DMISSING, DDIR, DILLEGAL };
-static DirStatus
-getPathDirStatus(const char *path)
-{
- struct stat st;
- int err;
-
- err = stat(path, &st);
- if (err == 0) {
- /* Something's there; make sure it's a directory.
- */
- if (S_ISDIR(st.st_mode)) {
- return DDIR;
- }
- errno = ENOTDIR;
- return DILLEGAL;
- } else if (errno != ENOENT) {
- /* Something went wrong, or something in the path
- * is bad. Can't do anything in this situation.
- */
- return DILLEGAL;
+static DirStatus dir_status(const std::string& path) {
+ struct stat sb;
+ if (stat(path.c_str(), &sb) == 0) {
+ // Something's there; make sure it's a directory.
+ if (S_ISDIR(sb.st_mode)) {
+ return DirStatus::DDIR;
}
- return DMISSING;
+ errno = ENOTDIR;
+ return DirStatus::DILLEGAL;
+ } else if (errno != ENOENT) {
+ // Something went wrong, or something in the path is bad. Can't do anything in this situation.
+ return DirStatus::DILLEGAL;
+ }
+ return DirStatus::DMISSING;
}
-int
-dirCreateHierarchy(const char *path, int mode,
- const struct utimbuf *timestamp, bool stripFileName,
- struct selabel_handle *sehnd)
-{
- DirStatus ds;
+int mkdir_recursively(const std::string& input_path, mode_t mode, bool strip_filename,
+ const selabel_handle* sehnd) {
+ // Check for an empty string before we bother making any syscalls.
+ if (input_path.empty()) {
+ errno = ENOENT;
+ return -1;
+ }
- /* Check for an empty string before we bother
- * making any syscalls.
- */
- if (path[0] == '\0') {
- errno = ENOENT;
- return -1;
+ // Allocate a path that we can modify; stick a slash on the end to make things easier.
+ std::string path = input_path;
+ if (strip_filename) {
+ // Strip everything after the last slash.
+ size_t pos = path.rfind('/');
+ if (pos == std::string::npos) {
+ errno = ENOENT;
+ return -1;
}
- // Allocate a path that we can modify; stick a slash on
- // the end to make things easier.
- std::string cpath = path;
- if (stripFileName) {
- // Strip everything after the last slash.
- size_t pos = cpath.rfind('/');
- if (pos == std::string::npos) {
- errno = ENOENT;
- return -1;
- }
- cpath.resize(pos + 1);
- } else {
- // Make sure that the path ends in a slash.
- cpath.push_back('/');
- }
+ path.resize(pos + 1);
+ } else {
+ // Make sure that the path ends in a slash.
+ path.push_back('/');
+ }
- /* See if it already exists.
- */
- ds = getPathDirStatus(cpath.c_str());
- if (ds == DDIR) {
- return 0;
- } else if (ds == DILLEGAL) {
- return -1;
- }
-
- /* Walk up the path from the root and make each level.
- * If a directory already exists, no big deal.
- */
- const char *path_start = &cpath[0];
- char *p = &cpath[0];
- while (*p != '\0') {
- /* Skip any slashes, watching out for the end of the string.
- */
- while (*p != '\0' && *p == '/') {
- p++;
- }
- if (*p == '\0') {
- break;
- }
-
- /* Find the end of the next path component.
- * We know that we'll see a slash before the NUL,
- * because we added it, above.
- */
- while (*p != '/') {
- p++;
- }
- *p = '\0';
-
- /* Check this part of the path and make a new directory
- * if necessary.
- */
- ds = getPathDirStatus(path_start);
- if (ds == DILLEGAL) {
- /* Could happen if some other process/thread is
- * messing with the filesystem.
- */
- return -1;
- } else if (ds == DMISSING) {
- int err;
-
- char *secontext = NULL;
-
- if (sehnd) {
- selabel_lookup(sehnd, &secontext, path_start, mode);
- setfscreatecon(secontext);
- }
-
- err = mkdir(path_start, mode);
-
- if (secontext) {
- freecon(secontext);
- setfscreatecon(NULL);
- }
-
- if (err != 0) {
- return -1;
- }
- if (timestamp != NULL && utime(path_start, timestamp)) {
- return -1;
- }
- }
- // else, this directory already exists.
-
- // Repair the path and continue.
- *p = '/';
- }
+ // See if it already exists.
+ DirStatus ds = dir_status(path);
+ if (ds == DirStatus::DDIR) {
return 0;
-}
+ } else if (ds == DirStatus::DILLEGAL) {
+ return -1;
+ }
-int
-dirUnlinkHierarchy(const char *path)
-{
- struct stat st;
- DIR *dir;
- struct dirent *de;
- int fail = 0;
-
- /* is it a file or directory? */
- if (lstat(path, &st) < 0) {
+ // Walk up the path from the root and make each level.
+ size_t prev_end = 0;
+ while (prev_end < path.size()) {
+ size_t next_end = path.find('/', prev_end + 1);
+ if (next_end == std::string::npos) {
+ break;
+ }
+ std::string dir_path = path.substr(0, next_end);
+ // Check this part of the path and make a new directory if necessary.
+ switch (dir_status(dir_path)) {
+ case DirStatus::DILLEGAL:
+ // Could happen if some other process/thread is messing with the filesystem.
return -1;
- }
-
- /* a file, so unlink it */
- if (!S_ISDIR(st.st_mode)) {
- return unlink(path);
- }
-
- /* a directory, so open handle */
- dir = opendir(path);
- if (dir == NULL) {
- return -1;
- }
-
- /* recurse over components */
- errno = 0;
- while ((de = readdir(dir)) != NULL) {
- //TODO: don't blow the stack
- char dn[PATH_MAX];
- if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) {
- continue;
+ case DirStatus::DMISSING: {
+ char* secontext = nullptr;
+ if (sehnd) {
+ selabel_lookup(const_cast<selabel_handle*>(sehnd), &secontext, dir_path.c_str(), mode);
+ setfscreatecon(secontext);
}
- snprintf(dn, sizeof(dn), "%s/%s", path, de->d_name);
- if (dirUnlinkHierarchy(dn) < 0) {
- fail = 1;
- break;
+ int err = mkdir(dir_path.c_str(), mode);
+ if (secontext) {
+ freecon(secontext);
+ setfscreatecon(nullptr);
}
- errno = 0;
+ if (err != 0) {
+ return -1;
+ }
+ break;
+ }
+ default:
+ // Already exists.
+ break;
}
- /* in case readdir or unlink_recursive failed */
- if (fail || errno < 0) {
- int save = errno;
- closedir(dir);
- errno = save;
- return -1;
- }
-
- /* close directory handle */
- if (closedir(dir) < 0) {
- return -1;
- }
-
- /* delete target directory */
- return rmdir(path);
+ prev_end = next_end;
+ }
+ return 0;
}
diff --git a/otautil/DirUtil.h b/otautil/DirUtil.h
deleted file mode 100644
index 85b83c3..0000000
--- a/otautil/DirUtil.h
+++ /dev/null
@@ -1,54 +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 MINZIP_DIRUTIL_H_
-#define MINZIP_DIRUTIL_H_
-
-#include <stdbool.h>
-#include <utime.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct selabel_handle;
-
-/* Like "mkdir -p", try to guarantee that all directories
- * specified in path are present, creating as many directories
- * as necessary. The specified mode is passed to all mkdir
- * calls; no modifications are made to umask.
- *
- * If stripFileName is set, everything after the final '/'
- * is stripped before creating the directory hierarchy.
- *
- * If timestamp is non-NULL, new directories will be timestamped accordingly.
- *
- * Returns 0 on success; returns -1 (and sets errno) on failure
- * (usually if some element of path is not a directory).
- */
-int dirCreateHierarchy(const char *path, int mode,
- const struct utimbuf *timestamp, bool stripFileName,
- struct selabel_handle* sehnd);
-
-/* rm -rf <path>
- */
-int dirUnlinkHierarchy(const char *path);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // MINZIP_DIRUTIL_H_
diff --git a/otautil/SysUtil.cpp b/otautil/SysUtil.cpp
index dfa2150..48336ad 100644
--- a/otautil/SysUtil.cpp
+++ b/otautil/SysUtil.cpp
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-#include "SysUtil.h"
+#include "otautil/SysUtil.h"
+#include <errno.h> // TEMP_FAILURE_RETRY
#include <fcntl.h>
#include <stdint.h> // SIZE_MAX
#include <sys/mman.h>
@@ -100,7 +101,7 @@
}
// Reserve enough contiguous address space for the whole file.
- void* reserve = mmap64(nullptr, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
+ 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;
@@ -135,8 +136,8 @@
break;
}
- void* range_start = mmap64(next, range_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd,
- static_cast<off64_t>(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 " << i << ": " << line;
success = false;
diff --git a/otautil/ThermalUtil.cpp b/otautil/ThermalUtil.cpp
index 13d3643..5d9bd45 100644
--- a/otautil/ThermalUtil.cpp
+++ b/otautil/ThermalUtil.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "ThermalUtil.h"
+#include "otautil/ThermalUtil.h"
#include <dirent.h>
#include <stdio.h>
@@ -77,4 +77,4 @@
}
LOG(INFO) << "current maximum temperature: " << max_temperature;
return max_temperature;
-}
\ No newline at end of file
+}
diff --git a/otautil/cache_location.cpp b/otautil/cache_location.cpp
new file mode 100644
index 0000000..8ddefec
--- /dev/null
+++ b/otautil/cache_location.cpp
@@ -0,0 +1,31 @@
+/*
+ * 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/include/otautil/DirUtil.h b/otautil/include/otautil/DirUtil.h
new file mode 100644
index 0000000..85d6c16
--- /dev/null
+++ b/otautil/include/otautil/DirUtil.h
@@ -0,0 +1,39 @@
+/*
+ * 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 OTAUTIL_DIRUTIL_H_
+#define OTAUTIL_DIRUTIL_H_
+
+#include <sys/stat.h> // mode_t
+
+#include <string>
+
+struct selabel_handle;
+
+// Like "mkdir -p", try to guarantee that all directories specified in path are present, creating as
+// many directories as necessary. The specified mode is passed to all mkdir calls; no modifications
+// are made to umask.
+//
+// If strip_filename is set, everything after the final '/' is stripped before creating the
+// directory
+// hierarchy.
+//
+// Returns 0 on success; returns -1 (and sets errno) on failure (usually if some element of path is
+// not a directory).
+int mkdir_recursively(const std::string& path, mode_t mode, bool strip_filename,
+ const struct selabel_handle* sehnd);
+
+#endif // OTAUTIL_DIRUTIL_H_
diff --git a/otautil/SysUtil.h b/otautil/include/otautil/SysUtil.h
similarity index 100%
rename from otautil/SysUtil.h
rename to otautil/include/otautil/SysUtil.h
diff --git a/otautil/ThermalUtil.h b/otautil/include/otautil/ThermalUtil.h
similarity index 100%
rename from otautil/ThermalUtil.h
rename to otautil/include/otautil/ThermalUtil.h
diff --git a/otautil/include/otautil/cache_location.h b/otautil/include/otautil/cache_location.h
new file mode 100644
index 0000000..f2f6638
--- /dev/null
+++ b/otautil/include/otautil/cache_location.h
@@ -0,0 +1,69 @@
+/*
+ * 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/error_code.h b/otautil/include/otautil/error_code.h
similarity index 87%
rename from error_code.h
rename to otautil/include/otautil/error_code.h
index 9fe047c..b0ff42d 100644
--- a/error_code.h
+++ b/otautil/include/otautil/error_code.h
@@ -17,7 +17,7 @@
#ifndef _ERROR_CODE_H_
#define _ERROR_CODE_H_
-enum ErrorCode {
+enum ErrorCode : int {
kNoError = -1,
kLowBattery = 20,
kZipVerificationFailure,
@@ -25,9 +25,12 @@
kBootreasonInBlacklist,
kPackageCompatibilityFailure,
kScriptExecutionFailure,
+ kMapFileFailure,
+ kForkUpdateBinaryFailure,
+ kUpdateBinaryCommandFailure,
};
-enum CauseCode {
+enum CauseCode : int {
kNoCause = -1,
kArgsParsingFailure = 100,
kStashCreationFailure,
@@ -48,7 +51,7 @@
kVendorFailure = 200
};
-enum UncryptErrorCode {
+enum UncryptErrorCode : int {
kUncryptNoError = -1,
kUncryptErrorPlaceholder = 50,
kUncryptTimeoutError = 100,
@@ -68,6 +71,8 @@
kUncryptFileCloseError,
kUncryptFileRenameError,
kUncryptPackageMissingError,
+ kUncryptRealpathFindError,
+ kUncryptBlockDeviceFindError,
};
-#endif // _ERROR_CODE_H_
+#endif // _ERROR_CODE_H_
diff --git a/otautil/include/otautil/print_sha1.h b/otautil/include/otautil/print_sha1.h
new file mode 100644
index 0000000..03a8d29
--- /dev/null
+++ b/otautil/include/otautil/print_sha1.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#ifndef RECOVERY_PRINT_SHA1_H
+#define RECOVERY_PRINT_SHA1_H
+
+#include <stdint.h>
+#include <string>
+
+#include <openssl/sha.h>
+
+static std::string print_sha1(const uint8_t* sha1, size_t len) {
+ const char* hex = "0123456789abcdef";
+ std::string result = "";
+ for (size_t i = 0; i < len; ++i) {
+ result.push_back(hex[(sha1[i] >> 4) & 0xf]);
+ result.push_back(hex[sha1[i] & 0xf]);
+ }
+ return result;
+}
+
+[[maybe_unused]] static std::string print_sha1(const uint8_t sha1[SHA_DIGEST_LENGTH]) {
+ return print_sha1(sha1, SHA_DIGEST_LENGTH);
+}
+
+[[maybe_unused]] static std::string short_sha1(const uint8_t sha1[SHA_DIGEST_LENGTH]) {
+ return print_sha1(sha1, 4);
+}
+
+[[maybe_unused]] static std::string print_hex(const uint8_t* bytes, size_t len) {
+ return print_sha1(bytes, len);
+}
+
+#endif // RECOVERY_PRINT_SHA1_H
diff --git a/otautil/include/otautil/rangeset.h b/otautil/include/otautil/rangeset.h
new file mode 100644
index 0000000..e91d02c
--- /dev/null
+++ b/otautil/include/otautil/rangeset.h
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <stddef.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+using Range = std::pair<size_t, size_t>;
+
+class RangeSet {
+ public:
+ RangeSet() : blocks_(0) {}
+
+ explicit RangeSet(std::vector<Range>&& pairs);
+
+ // Parses the given string into a RangeSet. Returns the parsed RangeSet, or an empty RangeSet on
+ // errors.
+ static RangeSet Parse(const std::string& range_text);
+
+ // Appends the given Range to the current RangeSet.
+ bool PushBack(Range range);
+
+ // Clears all the ranges from the RangeSet.
+ void Clear();
+
+ std::string ToString() const;
+
+ // Gets the block number for the i-th (starting from 0) block in the RangeSet.
+ size_t GetBlockNumber(size_t idx) const;
+
+ // Returns whether the current RangeSet overlaps with other. 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 Overlaps(const RangeSet& other) 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,
+ // as opposed to 4 + 4 + 4 + 2. If the total number of blocks (T) is less than groups, it
+ // returns a vector of T 1-block RangeSets. Otherwise the number of the returned RangeSets must
+ // equal to groups. The current RangeSet remains intact after the split.
+ std::vector<RangeSet> Split(size_t groups) const;
+
+ // Returns the number of Range's in this RangeSet.
+ size_t size() const {
+ return ranges_.size();
+ }
+
+ // Returns the total number of blocks in this RangeSet.
+ size_t blocks() const {
+ return blocks_;
+ }
+
+ std::vector<Range>::const_iterator cbegin() const {
+ return ranges_.cbegin();
+ }
+
+ std::vector<Range>::const_iterator cend() const {
+ return ranges_.cend();
+ }
+
+ std::vector<Range>::iterator begin() {
+ return ranges_.begin();
+ }
+
+ std::vector<Range>::iterator end() {
+ return ranges_.end();
+ }
+
+ std::vector<Range>::const_iterator begin() const {
+ return ranges_.begin();
+ }
+
+ std::vector<Range>::const_iterator end() const {
+ return ranges_.end();
+ }
+
+ // Reverse const iterators for MoveRange().
+ std::vector<Range>::const_reverse_iterator crbegin() const {
+ return ranges_.crbegin();
+ }
+
+ std::vector<Range>::const_reverse_iterator crend() const {
+ return ranges_.crend();
+ }
+
+ // Returns whether the RangeSet is valid (i.e. non-empty).
+ explicit operator bool() const {
+ return !ranges_.empty();
+ }
+
+ const Range& operator[](size_t i) const {
+ return ranges_[i];
+ }
+
+ bool operator==(const RangeSet& other) const {
+ // The orders of Range's matter. "4,1,5,8,10" != "4,8,10,1,5".
+ return (ranges_ == other.ranges_);
+ }
+
+ bool operator!=(const RangeSet& other) const {
+ return ranges_ != other.ranges_;
+ }
+
+ protected:
+ // Actual limit for each value and the total number are both INT_MAX.
+ std::vector<Range> ranges_;
+ size_t blocks_;
+};
+
+// The class is a sorted version of a RangeSet; and it's useful in imgdiff to split the input
+// files when we're handling large zip files. Specifically, we can treat the input file as a
+// continuous RangeSet (i.e. RangeSet("0-99") for a 100 blocks file); and break it down into
+// several smaller chunks based on the zip entries.
+
+// For example, [source: 0-99] can be split into
+// [split_src1: 10-29]; [split_src2: 40-49, 60-69]; [split_src3: 70-89]
+// Here "10-29" simply means block 10th to block 29th with respect to the original input file.
+// Also, note that the split sources should be mutual exclusive, but they don't need to cover
+// every block in the original source.
+class SortedRangeSet : public RangeSet {
+ public:
+ // The block size when working with offset and file length.
+ static constexpr size_t kBlockSize = 4096;
+
+ SortedRangeSet() {}
+
+ // Ranges in the the set should be mutually exclusive; and they're sorted by the start block.
+ explicit SortedRangeSet(std::vector<Range>&& pairs);
+
+ void Insert(const Range& to_insert);
+
+ // Insert the input SortedRangeSet; keep the ranges sorted and merge the overlap ranges.
+ void Insert(const SortedRangeSet& rs);
+
+ // Compute the block range the file occupies, and insert that range.
+ void Insert(size_t start, size_t len);
+
+ using RangeSet::Overlaps;
+
+ bool Overlaps(size_t start, size_t len) const;
+
+ // Given an offset of the file, checks if the corresponding block (by considering the file as
+ // 0-based continuous block ranges) is covered by the SortedRangeSet. If so, returns the offset
+ // within this SortedRangeSet.
+ //
+ // For example, the 4106-th byte of a file is from block 1, assuming a block size of 4096-byte.
+ // The mapped offset within a SortedRangeSet("1-9 15-19") is 10.
+ //
+ // An offset of 65546 falls into the 16-th block in a file. Block 16 is contained as the 10-th
+ // item in SortedRangeSet("1-9 15-19"). So its data can be found at offset 40970 (i.e. 4096 * 10
+ // + 10) in a range represented by this SortedRangeSet.
+ size_t GetOffsetInRangeSet(size_t old_offset) const;
+};
diff --git a/otautil/rangeset.cpp b/otautil/rangeset.cpp
new file mode 100644
index 0000000..96955b9
--- /dev/null
+++ b/otautil/rangeset.cpp
@@ -0,0 +1,267 @@
+/*
+ * 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.
+ */
+
+#include "otautil/rangeset.h"
+
+#include <limits.h>
+#include <stddef.h>
+
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+RangeSet::RangeSet(std::vector<Range>&& pairs) {
+ blocks_ = 0;
+ if (pairs.empty()) {
+ LOG(ERROR) << "Invalid number of tokens";
+ return;
+ }
+
+ for (const auto& range : pairs) {
+ if (!PushBack(range)) {
+ Clear();
+ return;
+ }
+ }
+}
+
+RangeSet RangeSet::Parse(const std::string& range_text) {
+ std::vector<std::string> pieces = android::base::Split(range_text, ",");
+ if (pieces.size() < 3) {
+ LOG(ERROR) << "Invalid range text: " << range_text;
+ return {};
+ }
+
+ size_t num;
+ if (!android::base::ParseUint(pieces[0], &num, static_cast<size_t>(INT_MAX))) {
+ LOG(ERROR) << "Failed to parse the number of tokens: " << range_text;
+ return {};
+ }
+ if (num == 0) {
+ LOG(ERROR) << "Invalid number of tokens: " << range_text;
+ return {};
+ }
+ if (num % 2 != 0) {
+ LOG(ERROR) << "Number of tokens must be even: " << range_text;
+ return {};
+ }
+ if (num != pieces.size() - 1) {
+ LOG(ERROR) << "Mismatching number of tokens: " << range_text;
+ return {};
+ }
+
+ std::vector<Range> pairs;
+ for (size_t i = 0; i < num; i += 2) {
+ size_t first;
+ size_t second;
+ if (!android::base::ParseUint(pieces[i + 1], &first, static_cast<size_t>(INT_MAX)) ||
+ !android::base::ParseUint(pieces[i + 2], &second, static_cast<size_t>(INT_MAX))) {
+ return {};
+ }
+ pairs.emplace_back(first, second);
+ }
+ return RangeSet(std::move(pairs));
+}
+
+bool RangeSet::PushBack(Range range) {
+ if (range.first >= range.second) {
+ LOG(ERROR) << "Empty or negative range: " << range.first << ", " << range.second;
+ return false;
+ }
+ size_t sz = range.second - range.first;
+ if (blocks_ >= SIZE_MAX - sz) {
+ LOG(ERROR) << "RangeSet size overflow";
+ return false;
+ }
+
+ ranges_.push_back(std::move(range));
+ blocks_ += sz;
+ return true;
+}
+
+void RangeSet::Clear() {
+ ranges_.clear();
+ blocks_ = 0;
+}
+
+std::vector<RangeSet> RangeSet::Split(size_t groups) const {
+ if (ranges_.empty() || groups == 0) return {};
+
+ if (blocks_ < groups) {
+ groups = blocks_;
+ }
+
+ // Evenly distribute blocks, with the first few groups possibly containing one more.
+ size_t mean = blocks_ / groups;
+ std::vector<size_t> blocks_per_group(groups, mean);
+ std::fill_n(blocks_per_group.begin(), blocks_ % groups, mean + 1);
+
+ std::vector<RangeSet> result;
+
+ // Forward iterate Ranges and fill up each group with the desired number of blocks.
+ auto it = ranges_.cbegin();
+ Range range = *it;
+ for (const auto& blocks : blocks_per_group) {
+ RangeSet buffer;
+ size_t needed = blocks;
+ while (needed > 0) {
+ size_t range_blocks = range.second - range.first;
+ if (range_blocks > needed) {
+ // Split the current range and don't advance the iterator.
+ buffer.PushBack({ range.first, range.first + needed });
+ range.first = range.first + needed;
+ break;
+ }
+ buffer.PushBack(range);
+ it++;
+ if (it != ranges_.cend()) {
+ range = *it;
+ }
+ needed -= range_blocks;
+ }
+ result.push_back(std::move(buffer));
+ }
+ return result;
+}
+
+std::string RangeSet::ToString() const {
+ if (ranges_.empty()) {
+ 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);
+ }
+
+ return result;
+}
+
+// Get the block number for the i-th (starting from 0) block in the RangeSet.
+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;
+ }
+ idx -= (range.second - range.first);
+ }
+
+ CHECK(false) << "Failed to find block number for index " << idx;
+ return 0; // Unreachable, but to make compiler happy.
+}
+
+// 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)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// 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());
+}
+
+void SortedRangeSet::Insert(const Range& to_insert) {
+ SortedRangeSet rs({ to_insert });
+ Insert(rs);
+}
+
+// Insert the input SortedRangeSet; keep the ranges sorted and merge the overlap ranges.
+void SortedRangeSet::Insert(const SortedRangeSet& rs) {
+ if (rs.size() == 0) {
+ return;
+ }
+ // Merge and sort the two RangeSets.
+ std::vector<Range> temp = std::move(ranges_);
+ std::copy(rs.begin(), rs.end(), std::back_inserter(temp));
+ std::sort(temp.begin(), temp.end());
+
+ Clear();
+ // Trim overlaps and insert the result back to ranges_.
+ Range to_insert = temp.front();
+ for (auto it = temp.cbegin() + 1; it != temp.cend(); it++) {
+ if (it->first <= to_insert.second) {
+ to_insert.second = std::max(to_insert.second, it->second);
+ } else {
+ ranges_.push_back(to_insert);
+ blocks_ += (to_insert.second - to_insert.first);
+ to_insert = *it;
+ }
+ }
+ ranges_.push_back(to_insert);
+ blocks_ += (to_insert.second - to_insert.first);
+}
+
+// Compute the block range the file occupies, and insert that range.
+void SortedRangeSet::Insert(size_t start, size_t len) {
+ Range to_insert{ start / kBlockSize, (start + len - 1) / kBlockSize + 1 };
+ Insert(to_insert);
+}
+
+bool SortedRangeSet::Overlaps(size_t start, size_t len) const {
+ RangeSet rs({ { start / kBlockSize, (start + len - 1) / kBlockSize + 1 } });
+ return Overlaps(rs);
+}
+
+// Given an offset of the file, checks if the corresponding block (by considering the file as
+// 0-based continuous block ranges) is covered by the SortedRangeSet. If so, returns the offset
+// within this SortedRangeSet.
+//
+// For example, the 4106-th byte of a file is from block 1, assuming a block size of 4096-byte.
+// The mapped offset within a SortedRangeSet("1-9 15-19") is 10.
+//
+// An offset of 65546 falls into the 16-th block in a file. Block 16 is contained as the 10-th
+// item in SortedRangeSet("1-9 15-19"). So its data can be found at offset 40970 (i.e. 4096 * 10
+// + 10) in a range represented by this SortedRangeSet.
+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_) {
+ // 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);
+ return (new_block_start * kBlockSize + old_offset % kBlockSize);
+ } else {
+ CHECK(false) << "block_start " << old_block_start
+ << " is missing between two ranges: " << this->ToString();
+ return 0;
+ }
+ }
+ CHECK(false) << "block_start " << old_block_start
+ << " exceeds the limit of current RangeSet: " << this->ToString();
+ return 0;
+}
diff --git a/print_sha1.h b/print_sha1.h
deleted file mode 100644
index 1f85895..0000000
--- a/print_sha1.h
+++ /dev/null
@@ -1,47 +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.
- */
-
-#ifndef RECOVERY_PRINT_SHA1_H
-#define RECOVERY_PRINT_SHA1_H
-
-#include <stdint.h>
-#include <string>
-
-#include <openssl/sha.h>
-
-static std::string print_sha1(const uint8_t* sha1, size_t len) {
- const char* hex = "0123456789abcdef";
- std::string result = "";
- for (size_t i = 0; i < len; ++i) {
- result.push_back(hex[(sha1[i]>>4) & 0xf]);
- result.push_back(hex[sha1[i] & 0xf]);
- }
- return result;
-}
-
-static std::string print_sha1(const uint8_t sha1[SHA_DIGEST_LENGTH]) {
- return print_sha1(sha1, SHA_DIGEST_LENGTH);
-}
-
-static std::string short_sha1(const uint8_t sha1[SHA_DIGEST_LENGTH]) {
- return print_sha1(sha1, 4);
-}
-
-static std::string print_hex(const uint8_t* bytes, size_t len) {
- return print_sha1(bytes, len);
-}
-
-#endif // RECOVERY_PRINT_SHA1_H
diff --git a/recovery-persist.rc b/recovery-persist.rc
index 6761627..135a3c3 100644
--- a/recovery-persist.rc
+++ b/recovery-persist.rc
@@ -1,3 +1,3 @@
on post-fs-data
mkdir /data/misc/recovery 0770 system log
- exec - system log -- /system/bin/recovery-persist
+ exec_background - system log -- /system/bin/recovery-persist
diff --git a/recovery-refresh.rc b/recovery-refresh.rc
index 14b05cc..9fefc81 100644
--- a/recovery-refresh.rc
+++ b/recovery-refresh.rc
@@ -1,2 +1,2 @@
on post-fs
- exec - system log -- /system/bin/recovery-refresh
+ exec_background - system log -- /system/bin/recovery-refresh
diff --git a/recovery.cpp b/recovery.cpp
index 07bd7b9..07ec5cf 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -50,9 +50,9 @@
#include <bootloader_message/bootloader_message.h>
#include <cutils/android_reboot.h>
#include <cutils/properties.h> /* for property_list */
-#include <healthd/BatteryMonitor.h>
-#include <private/android_logger.h> /* private pmsg functions */
-#include <private/android_filesystem_config.h> /* for AID_SYSTEM */
+#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>
@@ -61,13 +61,13 @@
#include "adb_install.h"
#include "common.h"
#include "device.h"
-#include "error_code.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 "otautil/error_code.h"
#include "roots.h"
#include "rotate_logs.h"
#include "screen_ui.h"
@@ -108,6 +108,7 @@
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";
@@ -125,6 +126,10 @@
static constexpr const char* RECOVERY_WIPE = "/etc/recovery.wipe";
static constexpr const char* DEFAULT_LOCALE = "en-US";
+// 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;
@@ -179,19 +184,19 @@
* 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 NULL;
- }
+// 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])) dirCreateHierarchy(path, 0777, NULL, 1, sehandle);
-
- FILE *fp = fopen(path, mode);
- return fp;
+ // 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
@@ -594,7 +599,7 @@
if (is_cache) {
// Re-create the log dir and write back the log entries.
if (ensure_path_mounted(CACHE_LOG_DIR) == 0 &&
- dirCreateHierarchy(CACHE_LOG_DIR, 0777, nullptr, false, sehandle) == 0) {
+ mkdir_recursively(CACHE_LOG_DIR, 0777, false, sehandle) == 0) {
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)) {
@@ -748,11 +753,19 @@
modified_flash = true;
ui->Print("\n-- Wiping data...\n");
- bool success =
- device->PreWipeData() &&
- erase_volume("/data") &&
- (has_cache ? erase_volume("/cache") : true) &&
- device->PostWipeData();
+ bool success = device->PreWipeData();
+ if (success) {
+ success &= erase_volume(DATA_ROOT);
+ if (has_cache) {
+ success &= erase_volume(CACHE_ROOT);
+ }
+ if (volume_for_mount_point(METADATA_ROOT) != nullptr) {
+ success &= erase_volume(METADATA_ROOT);
+ }
+ }
+ if (success) {
+ success &= device->PostWipeData();
+ }
ui->Print("Data wipe %s.\n", success ? "complete" : "failed");
return success;
}
@@ -1187,6 +1200,11 @@
run_graphics_test();
break;
+ case Device::RUN_LOCALE_TEST: {
+ ScreenRecoveryUI* screen_ui = static_cast<ScreenRecoveryUI*>(ui);
+ screen_ui->CheckBackgroundTextImages(locale);
+ break;
+ }
case Device::MOUNT_SYSTEM:
// For a system image built with the root directory (i.e. system_root_image == "true"), we
// mount it to /system_root, and symlink /system to /system_root/system to make adb shell
@@ -1205,9 +1223,8 @@
}
}
-static void
-print_property(const char *key, const char *name, void *cookie) {
- printf("%s=%s\n", key, name);
+static void print_property(const char* key, const char* name, void* /* cookie */) {
+ printf("%s=%s\n", key, name);
}
static std::string load_locale_from_cache() {
@@ -1241,70 +1258,91 @@
static constexpr char log_characters[] = "VDIWEF";
-void UiLogger(android::base::LogId id, android::base::LogSeverity severity,
- const char* tag, const char* file, unsigned int line,
- const char* message) {
- if (severity >= android::base::ERROR && ui != nullptr) {
- ui->Print("E:%s\n", message);
- } else {
- fprintf(stdout, "%c:%s\n", log_characters[severity], message);
- }
+void UiLogger(android::base::LogId /* id */, android::base::LogSeverity severity,
+ const char* /* tag */, const char* /* file */, unsigned int /* line */,
+ const char* message) {
+ if (severity >= android::base::ERROR && ui != nullptr) {
+ ui->Print("E:%s\n", message);
+ } else {
+ fprintf(stdout, "%c:%s\n", log_characters[severity], message);
+ }
}
static bool is_battery_ok() {
- struct healthd_config healthd_config = {
- .batteryStatusPath = android::String8(android::String8::kEmptyString),
- .batteryHealthPath = android::String8(android::String8::kEmptyString),
- .batteryPresentPath = android::String8(android::String8::kEmptyString),
- .batteryCapacityPath = android::String8(android::String8::kEmptyString),
- .batteryVoltagePath = android::String8(android::String8::kEmptyString),
- .batteryTemperaturePath = android::String8(android::String8::kEmptyString),
- .batteryTechnologyPath = android::String8(android::String8::kEmptyString),
- .batteryCurrentNowPath = android::String8(android::String8::kEmptyString),
- .batteryCurrentAvgPath = android::String8(android::String8::kEmptyString),
- .batteryChargeCounterPath = android::String8(android::String8::kEmptyString),
- .batteryFullChargePath = android::String8(android::String8::kEmptyString),
- .batteryCycleCountPath = android::String8(android::String8::kEmptyString),
- .energyCounter = NULL,
- .boot_min_cap = 0,
- .screen_on = NULL
- };
- healthd_board_init(&healthd_config);
+ using android::hardware::health::V1_0::BatteryStatus;
+ using android::hardware::health::V2_0::Result;
+ using android::hardware::health::V2_0::toString;
+ using android::hardware::health::V2_0::implementation::Health;
- android::BatteryMonitor monitor;
- monitor.init(&healthd_config);
+ struct healthd_config healthd_config = {
+ .batteryStatusPath = android::String8(android::String8::kEmptyString),
+ .batteryHealthPath = android::String8(android::String8::kEmptyString),
+ .batteryPresentPath = android::String8(android::String8::kEmptyString),
+ .batteryCapacityPath = android::String8(android::String8::kEmptyString),
+ .batteryVoltagePath = android::String8(android::String8::kEmptyString),
+ .batteryTemperaturePath = android::String8(android::String8::kEmptyString),
+ .batteryTechnologyPath = android::String8(android::String8::kEmptyString),
+ .batteryCurrentNowPath = android::String8(android::String8::kEmptyString),
+ .batteryCurrentAvgPath = android::String8(android::String8::kEmptyString),
+ .batteryChargeCounterPath = android::String8(android::String8::kEmptyString),
+ .batteryFullChargePath = android::String8(android::String8::kEmptyString),
+ .batteryCycleCountPath = android::String8(android::String8::kEmptyString),
+ .energyCounter = NULL,
+ .boot_min_cap = 0,
+ .screen_on = NULL
+ };
- int wait_second = 0;
- while (true) {
- int charge_status = monitor.getChargeStatus();
- // Treat unknown status as charged.
- bool charged = (charge_status != android::BATTERY_STATUS_DISCHARGING &&
- charge_status != android::BATTERY_STATUS_NOT_CHARGING);
- android::BatteryProperty capacity;
- android::status_t status = monitor.getProperty(android::BATTERY_PROP_CAPACITY, &capacity);
- ui_print("charge_status %d, charged %d, status %d, capacity %lld\n", charge_status,
- charged, status, capacity.valueInt64);
- // At startup, the battery drivers in devices like N5X/N6P take some time to load
- // the battery profile. Before the load finishes, it reports value 50 as a fake
- // capacity. BATTERY_READ_TIMEOUT_IN_SEC is set that the battery drivers are expected
- // to finish loading the battery profile earlier than 10 seconds after kernel startup.
- if (status == 0 && capacity.valueInt64 == 50) {
- if (wait_second < BATTERY_READ_TIMEOUT_IN_SEC) {
- sleep(1);
- wait_second++;
- continue;
- }
- }
- // If we can't read battery percentage, it may be a device without battery. In this
- // situation, use 100 as a fake battery percentage.
- if (status != 0) {
- capacity.valueInt64 = 100;
- }
- return (charged && capacity.valueInt64 >= BATTERY_WITH_CHARGER_OK_PERCENTAGE) ||
- (!charged && capacity.valueInt64 >= BATTERY_OK_PERCENTAGE);
+ auto health =
+ android::hardware::health::V2_0::implementation::Health::initInstance(&healthd_config);
+
+ int wait_second = 0;
+ while (true) {
+ auto charge_status = BatteryStatus::UNKNOWN;
+ health
+ ->getChargeStatus([&charge_status](auto res, auto out_status) {
+ if (res == Result::SUCCESS) {
+ charge_status = out_status;
+ }
+ })
+ .isOk(); // should not have transport error
+
+ // Treat unknown status as charged.
+ bool charged = (charge_status != BatteryStatus::DISCHARGING &&
+ charge_status != BatteryStatus::NOT_CHARGING);
+
+ Result res = Result::UNKNOWN;
+ int32_t capacity = INT32_MIN;
+ health
+ ->getCapacity([&res, &capacity](auto out_res, auto out_capacity) {
+ res = out_res;
+ capacity = out_capacity;
+ })
+ .isOk(); // should not have transport error
+
+ ui_print("charge_status %d, charged %d, status %s, capacity %" PRId32 "\n", charge_status,
+ charged, toString(res).c_str(), capacity);
+ // At startup, the battery drivers in devices like N5X/N6P take some time to load
+ // the battery profile. Before the load finishes, it reports value 50 as a fake
+ // capacity. BATTERY_READ_TIMEOUT_IN_SEC is set that the battery drivers are expected
+ // to finish loading the battery profile earlier than 10 seconds after kernel startup.
+ if (res == Result::SUCCESS && capacity == 50) {
+ if (wait_second < BATTERY_READ_TIMEOUT_IN_SEC) {
+ sleep(1);
+ wait_second++;
+ continue;
+ }
+ }
+ // If we can't read battery percentage, it may be a device without battery. In this
+ // situation, use 100 as a fake battery percentage.
+ if (res != Result::SUCCESS) {
+ capacity = 100;
+ }
+ return (charged && capacity >= BATTERY_WITH_CHARGER_OK_PERCENTAGE) ||
+ (!charged && capacity >= BATTERY_OK_PERCENTAGE);
}
}
+// Set the retry count to |retry_count| in BCB.
static void set_retry_bootloader_message(int retry_count, const std::vector<std::string>& args) {
std::vector<std::string> options;
for (const auto& arg : args) {
@@ -1313,8 +1351,8 @@
}
}
- // Increment the retry counter by 1.
- options.push_back(android::base::StringPrintf("--retry_count=%d", retry_count + 1));
+ // Update the retry counter in BCB.
+ options.push_back(android::base::StringPrintf("--retry_count=%d", retry_count));
std::string err;
if (!update_bootloader_message(options, &err)) {
LOG(ERROR) << err;
@@ -1349,303 +1387,331 @@
}
int main(int argc, char **argv) {
- // We don't have logcat yet under recovery; so we'll print error on screen and
- // log to stdout (which is redirected to recovery.log) as we used to do.
- android::base::InitLogging(argv, &UiLogger);
+ // We don't have logcat yet under recovery; so we'll print error on screen and
+ // log to stdout (which is redirected to recovery.log) as we used to do.
+ android::base::InitLogging(argv, &UiLogger);
- // Take last pmsg contents and rewrite it to the current pmsg session.
- static const char filter[] = "recovery/";
- // Do we need to rotate?
- bool doRotate = false;
+ // Take last pmsg contents and rewrite it to the current pmsg session.
+ static const char filter[] = "recovery/";
+ // Do we need to rotate?
+ bool doRotate = false;
- __android_log_pmsg_file_read(
- LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter,
- logbasename, &doRotate);
- // Take action to refresh pmsg contents
- __android_log_pmsg_file_read(
- LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter,
- logrotate, &doRotate);
+ __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &doRotate);
+ // Take action to refresh pmsg contents
+ __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &doRotate);
- // If this binary is started with the single argument "--adbd",
- // instead of being the normal recovery binary, it turns into kind
- // of a stripped-down version of adbd that only supports the
- // 'sideload' command. Note this must be a real argument, not
- // anything in the command file or bootloader control block; the
- // only way recovery should be run with this argument is when it
- // starts a copy of itself from the apply_from_adb() function.
- if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
- minadbd_main();
- return 0;
+ // If this binary is started with the single argument "--adbd",
+ // instead of being the normal recovery binary, it turns into kind
+ // of a stripped-down version of adbd that only supports the
+ // 'sideload' command. Note this must be a real argument, not
+ // anything in the command file or bootloader control block; the
+ // only way recovery should be run with this argument is when it
+ // starts a copy of itself from the apply_from_adb() function.
+ if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
+ minadbd_main();
+ return 0;
+ }
+
+ time_t start = time(nullptr);
+
+ // redirect_stdio should be called only in non-sideload mode. Otherwise
+ // we may have two logger instances with different timestamps.
+ redirect_stdio(TEMPORARY_LOG_FILE);
+
+ printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start));
+
+ load_volume_table();
+ has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;
+
+ std::vector<std::string> args = get_args(argc, argv);
+ std::vector<char*> args_to_parse(args.size());
+ std::transform(args.cbegin(), args.cend(), args_to_parse.begin(),
+ [](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
+
+ const char* update_package = nullptr;
+ bool should_wipe_data = false;
+ bool should_prompt_and_wipe_data = false;
+ bool should_wipe_cache = false;
+ bool should_wipe_ab = false;
+ size_t wipe_package_size = 0;
+ bool show_text = false;
+ bool sideload = false;
+ bool sideload_auto_reboot = false;
+ bool just_exit = false;
+ bool shutdown_after = false;
+ int retry_count = 0;
+ bool security_update = false;
+
+ int arg;
+ int option_index;
+ while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
+ &option_index)) != -1) {
+ switch (arg) {
+ case 'n':
+ android::base::ParseInt(optarg, &retry_count, 0);
+ break;
+ case 'u':
+ update_package = optarg;
+ break;
+ case 'w':
+ should_wipe_data = true;
+ break;
+ case 'c':
+ should_wipe_cache = true;
+ break;
+ case 't':
+ show_text = true;
+ break;
+ case 's':
+ sideload = true;
+ break;
+ case 'a':
+ sideload = true;
+ sideload_auto_reboot = true;
+ break;
+ case 'x':
+ just_exit = true;
+ break;
+ case 'l':
+ locale = optarg;
+ break;
+ case 'p':
+ shutdown_after = true;
+ break;
+ case 'r':
+ reason = optarg;
+ break;
+ case 'e':
+ security_update = true;
+ break;
+ case 0: {
+ std::string option = OPTIONS[option_index].name;
+ if (option == "wipe_ab") {
+ should_wipe_ab = true;
+ } else if (option == "wipe_package_size") {
+ android::base::ParseUint(optarg, &wipe_package_size);
+ } else if (option == "prompt_and_wipe_data") {
+ should_prompt_and_wipe_data = true;
+ }
+ break;
+ }
+ case '?':
+ LOG(ERROR) << "Invalid command argument";
+ continue;
}
+ }
- time_t start = time(NULL);
-
- // redirect_stdio should be called only in non-sideload mode. Otherwise
- // we may have two logger instances with different timestamps.
- redirect_stdio(TEMPORARY_LOG_FILE);
-
- printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start));
-
- load_volume_table();
- has_cache = volume_for_path(CACHE_ROOT) != nullptr;
-
- std::vector<std::string> args = get_args(argc, argv);
- std::vector<char*> args_to_parse(args.size());
- std::transform(args.cbegin(), args.cend(), args_to_parse.begin(),
- [](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
-
- const char *update_package = NULL;
- bool should_wipe_data = false;
- bool should_prompt_and_wipe_data = false;
- bool should_wipe_cache = false;
- bool should_wipe_ab = false;
- size_t wipe_package_size = 0;
- bool show_text = false;
- bool sideload = false;
- bool sideload_auto_reboot = false;
- bool just_exit = false;
- bool shutdown_after = false;
- int retry_count = 0;
- bool security_update = false;
-
- int arg;
- int option_index;
- while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
- &option_index)) != -1) {
- switch (arg) {
- case 'n': android::base::ParseInt(optarg, &retry_count, 0); break;
- case 'u': update_package = optarg; break;
- case 'w': should_wipe_data = true; break;
- case 'c': should_wipe_cache = true; break;
- case 't': show_text = true; break;
- case 's': sideload = true; break;
- case 'a': sideload = true; sideload_auto_reboot = true; break;
- case 'x': just_exit = true; break;
- case 'l': locale = optarg; break;
- case 'p': shutdown_after = true; break;
- case 'r': reason = optarg; break;
- case 'e': security_update = true; break;
- case 0: {
- std::string option = OPTIONS[option_index].name;
- if (option == "wipe_ab") {
- should_wipe_ab = true;
- } else if (option == "wipe_package_size") {
- android::base::ParseUint(optarg, &wipe_package_size);
- } else if (option == "prompt_and_wipe_data") {
- should_prompt_and_wipe_data = true;
- }
- break;
- }
- case '?':
- LOG(ERROR) << "Invalid command argument";
- continue;
- }
+ if (locale.empty()) {
+ if (has_cache) {
+ locale = load_locale_from_cache();
}
if (locale.empty()) {
- if (has_cache) {
- locale = load_locale_from_cache();
- }
-
- if (locale.empty()) {
- locale = DEFAULT_LOCALE;
- }
+ locale = DEFAULT_LOCALE;
}
+ }
- printf("locale is [%s]\n", locale.c_str());
- printf("stage is [%s]\n", stage.c_str());
- printf("reason is [%s]\n", reason);
+ printf("locale is [%s]\n", locale.c_str());
+ printf("stage is [%s]\n", stage.c_str());
+ printf("reason is [%s]\n", reason);
- Device* device = make_device();
- if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
- printf("Quiescent recovery mode.\n");
- ui = new StubRecoveryUI();
+ Device* device = make_device();
+ if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
+ printf("Quiescent recovery mode.\n");
+ ui = new StubRecoveryUI();
+ } else {
+ ui = device->GetUI();
+
+ if (!ui->Init(locale)) {
+ printf("Failed to initialize UI, use stub UI instead.\n");
+ ui = new StubRecoveryUI();
+ }
+ }
+
+ // Set background string to "installing security update" for security update,
+ // otherwise set it to "installing system update".
+ ui->SetSystemUpdateText(security_update);
+
+ int st_cur, st_max;
+ if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) {
+ ui->SetStage(st_cur, st_max);
+ }
+
+ ui->SetBackground(RecoveryUI::NONE);
+ if (show_text) ui->ShowText(true);
+
+ sehandle = selinux_android_file_context_handle();
+ selinux_android_set_sehandle(sehandle);
+ if (!sehandle) {
+ ui->Print("Warning: No file_contexts\n");
+ }
+
+ device->StartRecovery();
+
+ printf("Command:");
+ for (const auto& arg : args) {
+ printf(" \"%s\"", arg.c_str());
+ }
+ printf("\n\n");
+
+ property_list(print_property, nullptr);
+ printf("\n");
+
+ ui->Print("Supported API: %d\n", kRecoveryApiVersion);
+
+ int status = INSTALL_SUCCESS;
+
+ if (update_package != nullptr) {
+ // It's not entirely true that we will modify the flash. But we want
+ // to log the update attempt since update_package is non-NULL.
+ modified_flash = true;
+
+ if (!is_battery_ok()) {
+ ui->Print("battery capacity is not enough for installing package, needed is %d%%\n",
+ BATTERY_OK_PERCENTAGE);
+ // Log the error code to last_install when installation skips due to
+ // low battery.
+ log_failure_code(kLowBattery, update_package);
+ status = INSTALL_SKIPPED;
+ } else if (bootreason_in_blacklist()) {
+ // Skip update-on-reboot when bootreason is kernel_panic or similar
+ ui->Print("bootreason is in the blacklist; skip OTA installation\n");
+ log_failure_code(kBootreasonInBlacklist, update_package);
+ status = INSTALL_SKIPPED;
} else {
- ui = device->GetUI();
-
- if (!ui->Init(locale)) {
- printf("Failed to initialize UI, use stub UI instead.\n");
- ui = new StubRecoveryUI();
- }
- }
-
- // Set background string to "installing security update" for security update,
- // otherwise set it to "installing system update".
- ui->SetSystemUpdateText(security_update);
-
- int st_cur, st_max;
- if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) {
- ui->SetStage(st_cur, st_max);
- }
-
- ui->SetBackground(RecoveryUI::NONE);
- if (show_text) ui->ShowText(true);
-
- sehandle = selinux_android_file_context_handle();
- selinux_android_set_sehandle(sehandle);
- if (!sehandle) {
- ui->Print("Warning: No file_contexts\n");
- }
-
- device->StartRecovery();
-
- printf("Command:");
- for (const auto& arg : args) {
- printf(" \"%s\"", arg.c_str());
- }
- printf("\n\n");
-
- property_list(print_property, NULL);
- printf("\n");
-
- ui->Print("Supported API: %d\n", RECOVERY_API_VERSION);
-
- int status = INSTALL_SUCCESS;
-
- if (update_package != NULL) {
- // It's not entirely true that we will modify the flash. But we want
- // to log the update attempt since update_package is non-NULL.
- modified_flash = true;
-
- if (!is_battery_ok()) {
- ui->Print("battery capacity is not enough for installing package, needed is %d%%\n",
- BATTERY_OK_PERCENTAGE);
- // Log the error code to last_install when installation skips due to
- // low battery.
- log_failure_code(kLowBattery, update_package);
- status = INSTALL_SKIPPED;
- } else if (bootreason_in_blacklist()) {
- // Skip update-on-reboot when bootreason is kernel_panic or similar
- ui->Print("bootreason is in the blacklist; skip OTA installation\n");
- log_failure_code(kBootreasonInBlacklist, update_package);
- status = INSTALL_SKIPPED;
- } else {
- status = install_package(update_package, &should_wipe_cache,
- TEMPORARY_INSTALL_FILE, true, retry_count);
- if (status == INSTALL_SUCCESS && should_wipe_cache) {
- wipe_cache(false, device);
- }
- if (status != INSTALL_SUCCESS) {
- ui->Print("Installation aborted.\n");
- // When I/O error happens, reboot and retry installation RETRY_LIMIT
- // times before we abandon this OTA update.
- if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) {
- copy_logs();
- set_retry_bootloader_message(retry_count, args);
- // Print retry count on screen.
- ui->Print("Retry attempt %d\n", retry_count);
-
- // Reboot and retry the update
- if (!reboot("reboot,recovery")) {
- ui->Print("Reboot failed\n");
- } else {
- while (true) {
- pause();
- }
- }
- }
- // If this is an eng or userdebug build, then automatically
- // turn the text display on if the script fails so the error
- // message is visible.
- if (is_ro_debuggable()) {
- ui->ShowText(true);
- }
- }
- }
- } else if (should_wipe_data) {
- if (!wipe_data(device)) {
- status = INSTALL_ERROR;
- }
- } else if (should_prompt_and_wipe_data) {
- ui->ShowText(true);
- ui->SetBackground(RecoveryUI::ERROR);
- if (!prompt_and_wipe_data(device)) {
- status = INSTALL_ERROR;
- }
- ui->ShowText(false);
- } else if (should_wipe_cache) {
- if (!wipe_cache(false, device)) {
- status = INSTALL_ERROR;
- }
- } else if (should_wipe_ab) {
- if (!wipe_ab_device(wipe_package_size)) {
- status = INSTALL_ERROR;
- }
- } else if (sideload) {
- // 'adb reboot sideload' acts the same as user presses key combinations
- // to enter the sideload mode. When 'sideload-auto-reboot' is used, text
- // display will NOT be turned on by default. And it will reboot after
- // sideload finishes even if there are errors. Unless one turns on the
- // text display during the installation. This is to enable automated
- // testing.
- if (!sideload_auto_reboot) {
- ui->ShowText(true);
- }
- status = apply_from_adb(&should_wipe_cache, TEMPORARY_INSTALL_FILE);
- if (status == INSTALL_SUCCESS && should_wipe_cache) {
- if (!wipe_cache(false, device)) {
- status = INSTALL_ERROR;
- }
- }
- ui->Print("\nInstall from ADB complete (status: %d).\n", status);
- if (sideload_auto_reboot) {
- ui->Print("Rebooting automatically.\n");
- }
- } else if (!just_exit) {
- // If this is an eng or userdebug build, automatically turn on the text display if no command
- // is specified. Note that this should be called before setting the background to avoid
- // flickering the background image.
- if (is_ro_debuggable()) {
- ui->ShowText(true);
+ // It's a fresh update. Initialize the retry_count in the BCB to 1; therefore we can later
+ // identify the interrupted update due to unexpected reboots.
+ if (retry_count == 0) {
+ set_retry_bootloader_message(retry_count + 1, args);
}
- status = INSTALL_NONE; // No command specified
- ui->SetBackground(RecoveryUI::NO_COMMAND);
- }
- if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
- ui->SetBackground(RecoveryUI::ERROR);
- if (!ui->IsTextVisible()) {
- sleep(5);
+ status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, true,
+ retry_count);
+ if (status == INSTALL_SUCCESS && should_wipe_cache) {
+ wipe_cache(false, device);
+ }
+ if (status != INSTALL_SUCCESS) {
+ ui->Print("Installation aborted.\n");
+ // When I/O error happens, reboot and retry installation RETRY_LIMIT
+ // times before we abandon this OTA update.
+ if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) {
+ copy_logs();
+ retry_count += 1;
+ set_retry_bootloader_message(retry_count, args);
+ // Print retry count on screen.
+ ui->Print("Retry attempt %d\n", retry_count);
+
+ // Reboot and retry the update
+ if (!reboot("reboot,recovery")) {
+ ui->Print("Reboot failed\n");
+ } else {
+ while (true) {
+ pause();
+ }
+ }
}
- }
-
- Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
- // 1. If the recovery menu is visible, prompt and wait for commands.
- // 2. If the state is INSTALL_NONE, wait for commands. (i.e. In user build, manually reboot into
- // recovery to sideload a package.)
- // 3. sideload_auto_reboot is an option only available in user-debug build, reboot the device
- // without waiting.
- // 4. In all other cases, reboot the device. Therefore, normal users will observe the device
- // reboot after it shows the "error" screen for 5s.
- if ((status == INSTALL_NONE && !sideload_auto_reboot) || ui->IsTextVisible()) {
- Device::BuiltinAction temp = prompt_and_wait(device, status);
- if (temp != Device::NO_ACTION) {
- after = temp;
+ // If this is an eng or userdebug build, then automatically
+ // turn the text display on if the script fails so the error
+ // message is visible.
+ if (is_ro_debuggable()) {
+ ui->ShowText(true);
}
+ }
}
-
- // Save logs and clean up before rebooting or shutting down.
- finish_recovery();
-
- switch (after) {
- case Device::SHUTDOWN:
- ui->Print("Shutting down...\n");
- android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,");
- break;
-
- case Device::REBOOT_BOOTLOADER:
- ui->Print("Rebooting to bootloader...\n");
- android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader");
- break;
-
- default:
- ui->Print("Rebooting...\n");
- reboot("reboot,");
- break;
+ } else if (should_wipe_data) {
+ if (!wipe_data(device)) {
+ status = INSTALL_ERROR;
}
- while (true) {
- pause();
+ } else if (should_prompt_and_wipe_data) {
+ ui->ShowText(true);
+ ui->SetBackground(RecoveryUI::ERROR);
+ if (!prompt_and_wipe_data(device)) {
+ status = INSTALL_ERROR;
}
- // Should be unreachable.
- return EXIT_SUCCESS;
+ ui->ShowText(false);
+ } else if (should_wipe_cache) {
+ if (!wipe_cache(false, device)) {
+ status = INSTALL_ERROR;
+ }
+ } else if (should_wipe_ab) {
+ if (!wipe_ab_device(wipe_package_size)) {
+ status = INSTALL_ERROR;
+ }
+ } else if (sideload) {
+ // 'adb reboot sideload' acts the same as user presses key combinations
+ // to enter the sideload mode. When 'sideload-auto-reboot' is used, text
+ // display will NOT be turned on by default. And it will reboot after
+ // sideload finishes even if there are errors. Unless one turns on the
+ // text display during the installation. This is to enable automated
+ // testing.
+ if (!sideload_auto_reboot) {
+ ui->ShowText(true);
+ }
+ status = apply_from_adb(&should_wipe_cache, TEMPORARY_INSTALL_FILE);
+ if (status == INSTALL_SUCCESS && should_wipe_cache) {
+ if (!wipe_cache(false, device)) {
+ status = INSTALL_ERROR;
+ }
+ }
+ ui->Print("\nInstall from ADB complete (status: %d).\n", status);
+ if (sideload_auto_reboot) {
+ ui->Print("Rebooting automatically.\n");
+ }
+ } else if (!just_exit) {
+ // If this is an eng or userdebug build, automatically turn on the text display if no command
+ // is specified. Note that this should be called before setting the background to avoid
+ // flickering the background image.
+ if (is_ro_debuggable()) {
+ ui->ShowText(true);
+ }
+ status = INSTALL_NONE; // No command specified
+ ui->SetBackground(RecoveryUI::NO_COMMAND);
+ }
+
+ if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
+ ui->SetBackground(RecoveryUI::ERROR);
+ if (!ui->IsTextVisible()) {
+ sleep(5);
+ }
+ }
+
+ Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
+ // 1. If the recovery menu is visible, prompt and wait for commands.
+ // 2. If the state is INSTALL_NONE, wait for commands. (i.e. In user build, manually reboot into
+ // recovery to sideload a package.)
+ // 3. sideload_auto_reboot is an option only available in user-debug build, reboot the device
+ // without waiting.
+ // 4. In all other cases, reboot the device. Therefore, normal users will observe the device
+ // reboot after it shows the "error" screen for 5s.
+ if ((status == INSTALL_NONE && !sideload_auto_reboot) || ui->IsTextVisible()) {
+ Device::BuiltinAction temp = prompt_and_wait(device, status);
+ if (temp != Device::NO_ACTION) {
+ after = temp;
+ }
+ }
+
+ // Save logs and clean up before rebooting or shutting down.
+ finish_recovery();
+
+ switch (after) {
+ case Device::SHUTDOWN:
+ ui->Print("Shutting down...\n");
+ android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,");
+ break;
+
+ case Device::REBOOT_BOOTLOADER:
+ ui->Print("Rebooting to bootloader...\n");
+ android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader");
+ break;
+
+ default:
+ ui->Print("Rebooting...\n");
+ reboot("reboot,");
+ break;
+ }
+ while (true) {
+ pause();
+ }
+ // Should be unreachable.
+ return EXIT_SUCCESS;
}
diff --git a/res-hdpi/images/erasing_text.png b/res-hdpi/images/erasing_text.png
index 0982544..34c56a9 100644
--- a/res-hdpi/images/erasing_text.png
+++ b/res-hdpi/images/erasing_text.png
Binary files differ
diff --git a/res-hdpi/images/error_text.png b/res-hdpi/images/error_text.png
index 3a06f6e..2a96053 100644
--- a/res-hdpi/images/error_text.png
+++ b/res-hdpi/images/error_text.png
Binary files differ
diff --git a/res-hdpi/images/installing_security_text.png b/res-hdpi/images/installing_security_text.png
index b1acd23..97e1f11 100644
--- a/res-hdpi/images/installing_security_text.png
+++ b/res-hdpi/images/installing_security_text.png
Binary files differ
diff --git a/res-hdpi/images/installing_text.png b/res-hdpi/images/installing_text.png
index f0f5d8b..1d591eb 100644
--- a/res-hdpi/images/installing_text.png
+++ b/res-hdpi/images/installing_text.png
Binary files differ
diff --git a/res-hdpi/images/no_command_text.png b/res-hdpi/images/no_command_text.png
index def5036..977fcfa 100644
--- a/res-hdpi/images/no_command_text.png
+++ b/res-hdpi/images/no_command_text.png
Binary files differ
diff --git a/res-mdpi/images/erasing_text.png b/res-mdpi/images/erasing_text.png
index 82b4461..dcd0ea6 100644
--- a/res-mdpi/images/erasing_text.png
+++ b/res-mdpi/images/erasing_text.png
Binary files differ
diff --git a/res-mdpi/images/error_text.png b/res-mdpi/images/error_text.png
index adb4513..2152dad 100644
--- a/res-mdpi/images/error_text.png
+++ b/res-mdpi/images/error_text.png
Binary files differ
diff --git a/res-mdpi/images/installing_security_text.png b/res-mdpi/images/installing_security_text.png
index 54e5564..d1ac4ca 100644
--- a/res-mdpi/images/installing_security_text.png
+++ b/res-mdpi/images/installing_security_text.png
Binary files differ
diff --git a/res-mdpi/images/installing_text.png b/res-mdpi/images/installing_text.png
index d423318..c9b6d71 100644
--- a/res-mdpi/images/installing_text.png
+++ b/res-mdpi/images/installing_text.png
Binary files differ
diff --git a/res-mdpi/images/no_command_text.png b/res-mdpi/images/no_command_text.png
index cd77ff4..f77ad13 100644
--- a/res-mdpi/images/no_command_text.png
+++ b/res-mdpi/images/no_command_text.png
Binary files differ
diff --git a/res-xhdpi/images/erasing_text.png b/res-xhdpi/images/erasing_text.png
index 333edbe..e22b274 100644
--- a/res-xhdpi/images/erasing_text.png
+++ b/res-xhdpi/images/erasing_text.png
Binary files differ
diff --git a/res-xhdpi/images/error_text.png b/res-xhdpi/images/error_text.png
index e262584..e4c27e1 100644
--- a/res-xhdpi/images/error_text.png
+++ b/res-xhdpi/images/error_text.png
Binary files differ
diff --git a/res-xhdpi/images/installing_security_text.png b/res-xhdpi/images/installing_security_text.png
index e0f0f3e..7ba12b6 100644
--- a/res-xhdpi/images/installing_security_text.png
+++ b/res-xhdpi/images/installing_security_text.png
Binary files differ
diff --git a/res-xhdpi/images/installing_text.png b/res-xhdpi/images/installing_text.png
index a7e67f5..567988e 100644
--- a/res-xhdpi/images/installing_text.png
+++ b/res-xhdpi/images/installing_text.png
Binary files differ
diff --git a/res-xhdpi/images/no_command_text.png b/res-xhdpi/images/no_command_text.png
index 13aef7b..a682abb 100644
--- a/res-xhdpi/images/no_command_text.png
+++ b/res-xhdpi/images/no_command_text.png
Binary files differ
diff --git a/res-xxhdpi/images/erasing_text.png b/res-xxhdpi/images/erasing_text.png
index 80e7c47..6cc953b 100644
--- a/res-xxhdpi/images/erasing_text.png
+++ b/res-xxhdpi/images/erasing_text.png
Binary files differ
diff --git a/res-xxhdpi/images/error_text.png b/res-xxhdpi/images/error_text.png
index 32a1965..0d5cea8 100644
--- a/res-xxhdpi/images/error_text.png
+++ b/res-xxhdpi/images/error_text.png
Binary files differ
diff --git a/res-xxhdpi/images/installing_security_text.png b/res-xxhdpi/images/installing_security_text.png
index c53c9ac..5d10598 100644
--- a/res-xxhdpi/images/installing_security_text.png
+++ b/res-xxhdpi/images/installing_security_text.png
Binary files differ
diff --git a/res-xxhdpi/images/installing_text.png b/res-xxhdpi/images/installing_text.png
index 38b18d2..6e94fa2 100644
--- a/res-xxhdpi/images/installing_text.png
+++ b/res-xxhdpi/images/installing_text.png
Binary files differ
diff --git a/res-xxhdpi/images/no_command_text.png b/res-xxhdpi/images/no_command_text.png
index a0666d8..40ab484 100644
--- a/res-xxhdpi/images/no_command_text.png
+++ b/res-xxhdpi/images/no_command_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/erasing_text.png b/res-xxxhdpi/images/erasing_text.png
index 4f7b37b..cc73099 100644
--- a/res-xxxhdpi/images/erasing_text.png
+++ b/res-xxxhdpi/images/erasing_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/error_text.png b/res-xxxhdpi/images/error_text.png
index 052bf21..fea3cfc 100644
--- a/res-xxxhdpi/images/error_text.png
+++ b/res-xxxhdpi/images/error_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/installing_security_text.png b/res-xxxhdpi/images/installing_security_text.png
index a9e739b..ed77d08 100644
--- a/res-xxxhdpi/images/installing_security_text.png
+++ b/res-xxxhdpi/images/installing_security_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/installing_text.png b/res-xxxhdpi/images/installing_text.png
index 2d19486..9659106 100644
--- a/res-xxxhdpi/images/installing_text.png
+++ b/res-xxxhdpi/images/installing_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/no_command_text.png b/res-xxxhdpi/images/no_command_text.png
index ee0c238..4e6f363 100644
--- a/res-xxxhdpi/images/no_command_text.png
+++ b/res-xxxhdpi/images/no_command_text.png
Binary files differ
diff --git a/roots.cpp b/roots.cpp
index 29f55b9..184e799 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -16,174 +16,208 @@
#include "roots.h"
-#include <errno.h>
+#include <ctype.h>
+#include <fcntl.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 <ctype.h>
-#include <fcntl.h>
+
+#include <algorithm>
+#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 "common.h"
#include "mounts.h"
-#include "cryptfs.h"
-static struct fstab *fstab = NULL;
+static struct fstab* fstab = nullptr;
-extern struct selabel_handle *sehandle;
+extern struct selabel_handle* sehandle;
-void load_volume_table()
-{
- int i;
- int ret;
+void load_volume_table() {
+ fstab = fs_mgr_read_fstab_default();
+ if (!fstab) {
+ LOG(ERROR) << "Failed to read default fstab";
+ return;
+ }
- fstab = fs_mgr_read_fstab_default();
- if (!fstab) {
- LOG(ERROR) << "failed to read default fstab";
- return;
- }
+ int ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
+ if (ret == -1) {
+ LOG(ERROR) << "Failed to add /tmp entry to fstab";
+ fs_mgr_free_fstab(fstab);
+ fstab = nullptr;
+ return;
+ }
- ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
- if (ret < 0 ) {
- LOG(ERROR) << "failed to add /tmp entry to fstab";
- fs_mgr_free_fstab(fstab);
- fstab = NULL;
- return;
- }
-
- printf("recovery filesystem table\n");
- printf("=========================\n");
- for (i = 0; i < fstab->num_entries; ++i) {
- Volume* v = &fstab->recs[i];
- printf(" %d %s %s %s %lld\n", i, v->mount_point, v->fs_type,
- v->blk_device, v->length);
- }
- printf("\n");
+ printf("recovery filesystem table\n");
+ printf("=========================\n");
+ for (int i = 0; i < fstab->num_entries; ++i) {
+ const Volume* v = &fstab->recs[i];
+ printf(" %d %s %s %s %lld\n", i, v->mount_point, v->fs_type, v->blk_device, v->length);
+ }
+ printf("\n");
}
-Volume* volume_for_path(const char* path) {
- return fs_mgr_get_entry_for_mount_point(fstab, path);
+Volume* volume_for_mount_point(const std::string& mount_point) {
+ return fs_mgr_get_entry_for_mount_point(fstab, mount_point);
+}
+
+// Finds the volume specified by the given path. fs_mgr_get_entry_for_mount_point() does exact match
+// only, so it attempts the prefixes recursively (e.g. "/cache/recovery/last_log",
+// "/cache/recovery", "/cache", "/" for a given path of "/cache/recovery/last_log") and returns the
+// first match or nullptr.
+static Volume* volume_for_path(const char* path) {
+ if (path == nullptr || path[0] == '\0') return nullptr;
+ std::string str(path);
+ while (true) {
+ Volume* result = fs_mgr_get_entry_for_mount_point(fstab, str);
+ if (result != nullptr || str == "/") {
+ return result;
+ }
+ size_t slash = str.find_last_of('/');
+ if (slash == std::string::npos) return nullptr;
+ if (slash == 0) {
+ str = "/";
+ } else {
+ str = str.substr(0, slash);
+ }
+ }
+ return nullptr;
}
// Mount the volume specified by path at the given mount_point.
int ensure_path_mounted_at(const char* path, const char* mount_point) {
- Volume* v = volume_for_path(path);
- if (v == NULL) {
- LOG(ERROR) << "unknown volume for path [" << path << "]";
- return -1;
- }
- if (strcmp(v->fs_type, "ramdisk") == 0) {
- // the ramdisk is always mounted.
- return 0;
- }
-
- if (!scan_mounted_volumes()) {
- LOG(ERROR) << "failed to scan mounted volumes";
- return -1;
- }
-
- if (!mount_point) {
- mount_point = v->mount_point;
- }
-
- MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point);
- if (mv) {
- // volume is already mounted
- return 0;
- }
-
- mkdir(mount_point, 0755); // in case it doesn't already exist
-
- if (strcmp(v->fs_type, "ext4") == 0 ||
- strcmp(v->fs_type, "squashfs") == 0 ||
- strcmp(v->fs_type, "vfat") == 0) {
- int result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options);
- if (result == -1 && fs_mgr_is_formattable(v)) {
- LOG(ERROR) << "failed to mount " << mount_point << " (" << strerror(errno)
- << ") , formatting.....";
- bool crypt_footer = fs_mgr_is_encryptable(v) && !strcmp(v->key_loc, "footer");
- if (fs_mgr_do_format(v, crypt_footer) == 0) {
- result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options);
- } else {
- PLOG(ERROR) << "failed to format " << mount_point;
- return -1;
- }
- }
-
- if (result == -1) {
- PLOG(ERROR) << "failed to mount " << mount_point;
- return -1;
- }
- return 0;
- }
-
- LOG(ERROR) << "unknown fs_type \"" << v->fs_type << "\" for " << mount_point;
+ Volume* v = volume_for_path(path);
+ if (v == nullptr) {
+ LOG(ERROR) << "unknown volume for path [" << path << "]";
return -1;
+ }
+ if (strcmp(v->fs_type, "ramdisk") == 0) {
+ // The ramdisk is always mounted.
+ return 0;
+ }
+
+ if (!scan_mounted_volumes()) {
+ LOG(ERROR) << "Failed to scan mounted volumes";
+ return -1;
+ }
+
+ if (!mount_point) {
+ mount_point = v->mount_point;
+ }
+
+ const MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point);
+ if (mv != nullptr) {
+ // Volume is already mounted.
+ return 0;
+ }
+
+ mkdir(mount_point, 0755); // in case it doesn't already exist
+
+ if (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "squashfs") == 0 ||
+ strcmp(v->fs_type, "vfat") == 0) {
+ int result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options);
+ if (result == -1 && fs_mgr_is_formattable(v)) {
+ PLOG(ERROR) << "Failed to mount " << mount_point << "; formatting";
+ bool crypt_footer = fs_mgr_is_encryptable(v) && !strcmp(v->key_loc, "footer");
+ if (fs_mgr_do_format(v, crypt_footer) == 0) {
+ result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options);
+ } else {
+ PLOG(ERROR) << "Failed to format " << mount_point;
+ return -1;
+ }
+ }
+
+ if (result == -1) {
+ PLOG(ERROR) << "Failed to mount " << mount_point;
+ return -1;
+ }
+ return 0;
+ }
+
+ LOG(ERROR) << "unknown fs_type \"" << v->fs_type << "\" for " << mount_point;
+ return -1;
}
int ensure_path_mounted(const char* path) {
- // Mount at the default mount point.
- return ensure_path_mounted_at(path, nullptr);
+ // Mount at the default mount point.
+ return ensure_path_mounted_at(path, nullptr);
}
int ensure_path_unmounted(const char* path) {
- Volume* v = volume_for_path(path);
- if (v == NULL) {
- LOG(ERROR) << "unknown volume for path [" << path << "]";
- return -1;
- }
- if (strcmp(v->fs_type, "ramdisk") == 0) {
- // the ramdisk is always mounted; you can't unmount it.
- return -1;
- }
+ const Volume* v = volume_for_path(path);
+ if (v == nullptr) {
+ LOG(ERROR) << "unknown volume for path [" << path << "]";
+ return -1;
+ }
+ if (strcmp(v->fs_type, "ramdisk") == 0) {
+ // The ramdisk is always mounted; you can't unmount it.
+ return -1;
+ }
- if (!scan_mounted_volumes()) {
- LOG(ERROR) << "failed to scan mounted volumes";
- return -1;
- }
+ if (!scan_mounted_volumes()) {
+ LOG(ERROR) << "Failed to scan mounted volumes";
+ return -1;
+ }
- MountedVolume* mv = find_mounted_volume_by_mount_point(v->mount_point);
- if (mv == NULL) {
- // volume is already unmounted
- return 0;
- }
+ MountedVolume* mv = find_mounted_volume_by_mount_point(v->mount_point);
+ if (mv == nullptr) {
+ // Volume is already unmounted.
+ return 0;
+ }
- return unmount_mounted_volume(mv);
+ return unmount_mounted_volume(mv);
}
-static int exec_cmd(const char* path, char* const argv[]) {
- int status;
- pid_t child;
- if ((child = vfork()) == 0) {
- execv(path, argv);
- _exit(EXIT_FAILURE);
- }
- waitpid(child, &status, 0);
- if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
- LOG(ERROR) << path << " failed with status " << WEXITSTATUS(status);
- }
- return WEXITSTATUS(status);
+static int exec_cmd(const std::vector<std::string>& args) {
+ CHECK_NE(static_cast<size_t>(0), args.size());
+
+ 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;
+ 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 ssize_t get_file_size(int fd, uint64_t reserve_len) {
+static int64_t get_file_size(int fd, uint64_t reserve_len) {
struct stat buf;
int ret = fstat(fd, &buf);
if (ret) return 0;
- ssize_t computed_size;
+ int64_t computed_size;
if (S_ISREG(buf.st_mode)) {
computed_size = buf.st_size - reserve_len;
} else if (S_ISBLK(buf.st_mode)) {
- computed_size = get_block_device_size(fd) - reserve_len;
+ 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;
}
@@ -192,136 +226,144 @@
}
int format_volume(const char* volume, const char* directory) {
- Volume* v = volume_for_path(volume);
- if (v == NULL) {
- LOG(ERROR) << "unknown volume \"" << volume << "\"";
- return -1;
- }
- if (strcmp(v->fs_type, "ramdisk") == 0) {
- // you can't format the ramdisk.
- LOG(ERROR) << "can't format_volume \"" << volume << "\"";
- return -1;
- }
- if (strcmp(v->mount_point, volume) != 0) {
- 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 (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "f2fs") == 0) {
- // 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 != NULL && v->key_loc[0] == '/') {
- LOG(INFO) << "wiping " << v->key_loc;
- int fd = open(v->key_loc, O_WRONLY | O_CREAT, 0644);
- if (fd < 0) {
- LOG(ERROR) << "format_volume: failed to open " << v->key_loc;
- return -1;
- }
- wipe_block_device(fd, get_file_size(fd));
- close(fd);
- }
-
- ssize_t length = 0;
- if (v->length != 0) {
- length = v->length;
- } else if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0) {
- android::base::unique_fd fd(open(v->blk_device, O_RDONLY));
- if (fd < 0) {
- PLOG(ERROR) << "get_file_size: failed to open " << v->blk_device;
- return -1;
- }
- length = get_file_size(fd.get(), CRYPT_FOOTER_OFFSET);
- if (length <= 0) {
- LOG(ERROR) << "get_file_size: invalid size " << length << " for " << v->blk_device;
- return -1;
- }
- }
- int result;
- if (strcmp(v->fs_type, "ext4") == 0) {
- static constexpr int block_size = 4096;
- int raid_stride = v->logical_blk_size / block_size;
- int raid_stripe_width = v->erase_blk_size / block_size;
-
- // 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 / block_size;
- }
-
- const char* mke2fs_argv[] = { "/sbin/mke2fs_static",
- "-F",
- "-t",
- "ext4",
- "-b",
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr };
-
- int i = 5;
- std::string block_size_str = std::to_string(block_size);
- mke2fs_argv[i++] = block_size_str.c_str();
-
- std::string ext_args;
- if (v->erase_blk_size != 0 && v->logical_blk_size != 0) {
- ext_args = android::base::StringPrintf("stride=%d,stripe-width=%d", raid_stride,
- raid_stripe_width);
- mke2fs_argv[i++] = "-E";
- mke2fs_argv[i++] = ext_args.c_str();
- }
-
- mke2fs_argv[i++] = v->blk_device;
-
- std::string size_str = std::to_string(length / block_size);
- if (length != 0) {
- mke2fs_argv[i++] = size_str.c_str();
- }
-
- result = exec_cmd(mke2fs_argv[0], const_cast<char**>(mke2fs_argv));
- if (result == 0 && directory != nullptr) {
- const char* e2fsdroid_argv[] = { "/sbin/e2fsdroid_static",
- "-e",
- "-f",
- directory,
- "-a",
- volume,
- v->blk_device,
- nullptr };
-
- result = exec_cmd(e2fsdroid_argv[0], const_cast<char**>(e2fsdroid_argv));
- }
- } else { /* Has to be f2fs because we checked earlier. */
- char *num_sectors = nullptr;
- if (length >= 512 && asprintf(&num_sectors, "%zd", length / 512) <= 0) {
- LOG(ERROR) << "format_volume: failed to create " << v->fs_type
- << " command for " << v->blk_device;
- return -1;
- }
- const char *f2fs_path = "/sbin/mkfs.f2fs";
- const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", v->blk_device, num_sectors, nullptr};
-
- result = exec_cmd(f2fs_path, (char* const*)f2fs_argv);
- free(num_sectors);
- }
- if (result != 0) {
- PLOG(ERROR) << "format_volume: make " << v->fs_type << " failed on " << v->blk_device;
- return -1;
- }
- return 0;
- }
-
+ const Volume* v = volume_for_path(volume);
+ if (v == nullptr) {
+ LOG(ERROR) << "unknown volume \"" << volume << "\"";
+ return -1;
+ }
+ if (strcmp(v->fs_type, "ramdisk") == 0) {
+ LOG(ERROR) << "can't format_volume \"" << volume << "\"";
+ return -1;
+ }
+ if (strcmp(v->mount_point, volume) != 0) {
+ 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 (strcmp(v->fs_type, "ext4") != 0 && strcmp(v->fs_type, "f2fs") != 0) {
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 != nullptr && v->key_loc[0] == '/') {
+ LOG(INFO) << "Wiping " << v->key_loc;
+ int fd = open(v->key_loc, 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 != nullptr && strcmp(v->key_loc, "footer") == 0)) {
+ android::base::unique_fd fd(open(v->blk_device, 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 (strcmp(v->fs_type, "ext4") == 0) {
+ static constexpr int kBlockSize = 4096;
+ std::vector<std::string> mke2fs_args = {
+ "/sbin/mke2fs_static", "-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 != nullptr) {
+ std::vector<std::string> e2fsdroid_args = {
+ "/sbin/e2fsdroid_static",
+ "-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::string cmd("/sbin/mkfs.f2fs");
+ // clang-format off
+ std::vector<std::string> make_f2fs_cmd = {
+ cmd,
+ "-d1",
+ "-f",
+ "-O", "encrypt",
+ "-O", "quota",
+ "-O", "verity",
+ "-w", std::to_string(kSectorSize),
+ v->blk_device,
+ };
+ // clang-format on
+ if (length >= kSectorSize) {
+ make_f2fs_cmd.push_back(std::to_string(length / kSectorSize));
+ }
+
+ int result = exec_cmd(make_f2fs_cmd);
+ if (result == 0 && directory != nullptr) {
+ cmd = "/sbin/sload.f2fs";
+ // clang-format off
+ std::vector<std::string> sload_f2fs_cmd = {
+ cmd,
+ "-f", directory,
+ "-t", volume,
+ v->blk_device,
+ };
+ // clang-format on
+ result = exec_cmd(sload_f2fs_cmd);
+ }
+ if (result != 0) {
+ PLOG(ERROR) << "format_volume: Failed " << cmd << " on " << v->blk_device;
+ return -1;
+ }
+ return 0;
}
int format_volume(const char* volume) {
- return format_volume(volume, NULL);
+ return format_volume(volume, nullptr);
}
int setup_install_mounts() {
@@ -339,12 +381,12 @@
if (strcmp(v->mount_point, "/tmp") == 0 || strcmp(v->mount_point, "/cache") == 0) {
if (ensure_path_mounted(v->mount_point) != 0) {
- LOG(ERROR) << "failed to mount " << v->mount_point;
+ LOG(ERROR) << "Failed to mount " << v->mount_point;
return -1;
}
} else {
if (ensure_path_unmounted(v->mount_point) != 0) {
- LOG(ERROR) << "failed to unmount " << v->mount_point;
+ LOG(ERROR) << "Failed to unmount " << v->mount_point;
return -1;
}
}
diff --git a/roots.h b/roots.h
index 542f03b..46bb77e 100644
--- a/roots.h
+++ b/roots.h
@@ -17,13 +17,15 @@
#ifndef RECOVERY_ROOTS_H_
#define RECOVERY_ROOTS_H_
+#include <string>
+
typedef struct fstab_rec Volume;
// Load and parse volume data from /etc/recovery.fstab.
void load_volume_table();
-// Return the Volume* record for this path (or NULL).
-Volume* volume_for_path(const char* path);
+// Return the Volume* record for this mount point (or nullptr).
+Volume* volume_for_mount_point(const std::string& mount_point);
// Make sure that the volume 'path' is on is mounted. Returns 0 on
// success (volume is mounted).
diff --git a/rotate_logs.cpp b/rotate_logs.cpp
index fc22021..da00879 100644
--- a/rotate_logs.cpp
+++ b/rotate_logs.cpp
@@ -31,85 +31,77 @@
static const std::string LAST_KMSG_FILTER = "recovery/last_kmsg";
static const std::string LAST_LOG_FILTER = "recovery/last_log";
-ssize_t logbasename(
- log_id_t /* logId */,
- char /* prio */,
- const char *filename,
- const char * /* buf */, size_t len,
- void *arg) {
- bool* doRotate = static_cast<bool*>(arg);
- if (LAST_KMSG_FILTER.find(filename) != std::string::npos ||
- LAST_LOG_FILTER.find(filename) != std::string::npos) {
- *doRotate = true;
- }
- return len;
+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 (LAST_KMSG_FILTER.find(filename) != std::string::npos ||
+ LAST_LOG_FILTER.find(filename) != std::string::npos) {
+ *do_rotate = true;
+ }
+ return len;
}
-ssize_t logrotate(
- log_id_t logId,
- char prio,
- const char *filename,
- const char *buf, size_t len,
- void *arg) {
- bool* doRotate = static_cast<bool*>(arg);
- if (!*doRotate) {
- return __android_log_pmsg_file_write(logId, prio, filename, buf, 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);
+ std::string name(filename);
+ size_t dot = name.find_last_of('.');
+ std::string sub = name.substr(0, dot);
- if (LAST_KMSG_FILTER.find(sub) == std::string::npos &&
- LAST_LOG_FILTER.find(sub) == std::string::npos) {
- return __android_log_pmsg_file_write(logId, prio, filename, buf, len);
- }
+ if (LAST_KMSG_FILTER.find(sub) == std::string::npos &&
+ 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";
+ // filename rotation
+ if (dot == std::string::npos) {
+ name += ".1";
+ } else {
+ std::string number = name.substr(dot + 1);
+ if (!isdigit(number[0])) {
+ 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);
- }
+ 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(logId, prio, name.c_str(), buf, len);
+ 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;
+ // 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());
+ 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());
+ }
}
diff --git a/rotate_logs.h b/rotate_logs.h
index 809c213..007c33d 100644
--- a/rotate_logs.h
+++ b/rotate_logs.h
@@ -17,24 +17,18 @@
#ifndef _ROTATE_LOGS_H
#define _ROTATE_LOGS_H
-#include <string>
+#include <stddef.h>
+#include <sys/types.h>
-#include <private/android_logger.h> /* private pmsg functions */
+#include <log/log_id.h>
-constexpr int KEEP_LOG_COUNT = 10;
+static constexpr int KEEP_LOG_COUNT = 10;
-ssize_t logbasename(log_id_t /* logId */,
- char /* prio */,
- const char *filename,
- const char * /* buf */, size_t len,
- void *arg);
+ssize_t logbasename(log_id_t id, char prio, const char* filename, const char* buf, size_t len,
+ void* arg);
-ssize_t logrotate(
- log_id_t logId,
- char prio,
- const char *filename,
- const char *buf, size_t len,
- void *arg);
+ssize_t logrotate(log_id_t id, char prio, const char* filename, const char* buf, size_t len,
+ void* arg);
// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max.
// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max.
diff --git a/screen_ui.cpp b/screen_ui.cpp
index b8f6ea2..c8fb5aa 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include "screen_ui.h"
+
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
@@ -29,18 +31,19 @@
#include <time.h>
#include <unistd.h>
+#include <memory>
#include <string>
+#include <unordered_map>
#include <vector>
#include <android-base/logging.h>
#include <android-base/properties.h>
-#include <android-base/strings.h>
#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <minui/minui.h>
#include "common.h"
#include "device.h"
-#include "minui/minui.h"
-#include "screen_ui.h"
#include "ui.h"
// Return the current time as a double (including fractions of a second).
@@ -54,7 +57,7 @@
: kMarginWidth(RECOVERY_UI_MARGIN_WIDTH),
kMarginHeight(RECOVERY_UI_MARGIN_HEIGHT),
kAnimationFps(RECOVERY_UI_ANIMATION_FPS),
- density_(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f),
+ kDensity(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f),
currentIcon(NONE),
progressBarType(EMPTY),
progressScopeStart(0),
@@ -66,7 +69,6 @@
text_(nullptr),
text_col_(0),
text_row_(0),
- text_top_(0),
show_text(false),
show_text_ever(false),
menu_headers_(nullptr),
@@ -80,6 +82,8 @@
intro_done(false),
stage(-1),
max_stage(-1),
+ locale_(""),
+ rtl_locale_(false),
updateMutex(PTHREAD_MUTEX_INITIALIZER) {}
GRSurface* ScreenRecoveryUI::GetCurrentFrame() const {
@@ -105,7 +109,7 @@
}
int ScreenRecoveryUI::PixelsFromDp(int dp) const {
- return dp * density_;
+ return dp * kDensity;
}
// Here's the intended layout:
@@ -145,8 +149,8 @@
int elements_sum = gr_get_height(loopFrames[0]) + PixelsFromDp(kLayouts[layout_][ICON]) +
gr_get_height(installing_text) + PixelsFromDp(kLayouts[layout_][TEXT]) +
gr_get_height(progressBarFill);
- int bottom_gap = (gr_fb_height() - elements_sum) / 2;
- return gr_fb_height() - bottom_gap - gr_get_height(progressBarFill);
+ int bottom_gap = (ScreenHeight() - elements_sum) / 2;
+ return ScreenHeight() - bottom_gap - gr_get_height(progressBarFill);
}
// Clear the screen and draw the currently selected background icon (if any).
@@ -155,25 +159,24 @@
pagesIdentical = false;
gr_color(0, 0, 0, 255);
gr_clear();
-
if (currentIcon != NONE) {
if (max_stage != -1) {
int stage_height = gr_get_height(stageMarkerEmpty);
int stage_width = gr_get_width(stageMarkerEmpty);
- int x = (gr_fb_width() - max_stage * gr_get_width(stageMarkerEmpty)) / 2;
- int y = gr_fb_height() - stage_height;
+ int x = (ScreenWidth() - max_stage * gr_get_width(stageMarkerEmpty)) / 2;
+ int y = ScreenHeight() - stage_height - kMarginHeight;
for (int i = 0; i < max_stage; ++i) {
GRSurface* stage_surface = (i < stage) ? stageMarkerFill : stageMarkerEmpty;
- gr_blit(stage_surface, 0, 0, stage_width, stage_height, x, y);
+ DrawSurface(stage_surface, 0, 0, stage_width, stage_height, x, y);
x += stage_width;
}
}
GRSurface* text_surface = GetCurrentText();
- int text_x = (gr_fb_width() - gr_get_width(text_surface)) / 2;
+ int text_x = (ScreenWidth() - gr_get_width(text_surface)) / 2;
int text_y = GetTextBaseline();
gr_color(255, 255, 255, 255);
- gr_texticon(text_x, text_y, text_surface);
+ DrawTextIcon(text_x, text_y, text_surface);
}
}
@@ -184,21 +187,21 @@
GRSurface* frame = GetCurrentFrame();
int frame_width = gr_get_width(frame);
int frame_height = gr_get_height(frame);
- int frame_x = (gr_fb_width() - frame_width) / 2;
+ int frame_x = (ScreenWidth() - frame_width) / 2;
int frame_y = GetAnimationBaseline();
- gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
+ DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
}
if (progressBarType != EMPTY) {
int width = gr_get_width(progressBarEmpty);
int height = gr_get_height(progressBarEmpty);
- int progress_x = (gr_fb_width() - width) / 2;
+ int progress_x = (ScreenWidth() - width) / 2;
int progress_y = GetProgressBaseline();
// Erase behind the progress bar (in case this was a progress-only update)
gr_color(0, 0, 0, 255);
- gr_fill(progress_x, progress_y, width, height);
+ DrawFill(progress_x, progress_y, width, height);
if (progressBarType == DETERMINATE) {
float p = progressScopeStart + progress * progressScopeSize;
@@ -207,19 +210,19 @@
if (rtl_locale_) {
// Fill the progress bar from right to left.
if (pos > 0) {
- gr_blit(progressBarFill, width - pos, 0, pos, height, progress_x + width - pos,
- progress_y);
+ DrawSurface(progressBarFill, width - pos, 0, pos, height, progress_x + width - pos,
+ progress_y);
}
if (pos < width - 1) {
- gr_blit(progressBarEmpty, 0, 0, width - pos, height, progress_x, progress_y);
+ DrawSurface(progressBarEmpty, 0, 0, width - pos, height, progress_x, progress_y);
}
} else {
// Fill the progress bar from left to right.
if (pos > 0) {
- gr_blit(progressBarFill, 0, 0, pos, height, progress_x, progress_y);
+ DrawSurface(progressBarFill, 0, 0, pos, height, progress_x, progress_y);
}
if (pos < width - 1) {
- gr_blit(progressBarEmpty, pos, 0, width - pos, height, progress_x + pos, progress_y);
+ DrawSurface(progressBarEmpty, pos, 0, width - pos, height, progress_x + pos, progress_y);
}
}
}
@@ -256,8 +259,96 @@
}
}
+void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector<std::string>& locales_entries,
+ size_t sel) {
+ SetLocale(locales_entries[sel]);
+ std::vector<std::string> text_name = { "erasing_text", "error_text", "installing_text",
+ "installing_security_text", "no_command_text" };
+ std::unordered_map<std::string, std::unique_ptr<GRSurface, decltype(&free)>> surfaces;
+ for (const auto& name : text_name) {
+ GRSurface* text_image = nullptr;
+ LoadLocalizedBitmap(name.c_str(), &text_image);
+ if (!text_image) {
+ Print("Failed to load %s\n", name.c_str());
+ return;
+ }
+ surfaces.emplace(name, std::unique_ptr<GRSurface, decltype(&free)>(text_image, &free));
+ }
+
+ pthread_mutex_lock(&updateMutex);
+ gr_color(0, 0, 0, 255);
+ gr_clear();
+
+ int text_y = kMarginHeight;
+ int text_x = kMarginWidth;
+ int line_spacing = gr_sys_font()->char_height; // Put some extra space between images.
+ // Write the header and descriptive texts.
+ SetColor(INFO);
+ std::string header = "Show background text image";
+ text_y += DrawTextLine(text_x, text_y, header.c_str(), true);
+ std::string locale_selection = android::base::StringPrintf(
+ "Current locale: %s, %zu/%zu", locales_entries[sel].c_str(), sel, locales_entries.size());
+ const char* instruction[] = { locale_selection.c_str(),
+ "Use volume up/down to switch locales and power to exit.",
+ nullptr };
+ text_y += DrawWrappedTextLines(text_x, text_y, instruction);
+
+ // Iterate through the text images and display them in order for the current locale.
+ for (const auto& p : surfaces) {
+ text_y += line_spacing;
+ SetColor(LOG);
+ text_y += DrawTextLine(text_x, text_y, p.first.c_str(), false);
+ gr_color(255, 255, 255, 255);
+ gr_texticon(text_x, text_y, p.second.get());
+ text_y += gr_get_height(p.second.get());
+ }
+ // Update the whole screen.
+ gr_flip();
+ pthread_mutex_unlock(&updateMutex);
+}
+
+void ScreenRecoveryUI::CheckBackgroundTextImages(const std::string& saved_locale) {
+ // Load a list of locales embedded in one of the resource files.
+ std::vector<std::string> locales_entries = get_locales_in_png("installing_text");
+ if (locales_entries.empty()) {
+ Print("Failed to load locales from the resource files\n");
+ return;
+ }
+ size_t selected = 0;
+ SelectAndShowBackgroundText(locales_entries, selected);
+
+ FlushKeys();
+ while (true) {
+ int key = WaitKey();
+ if (key == KEY_POWER || key == KEY_ENTER) {
+ break;
+ } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
+ selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1;
+ SelectAndShowBackgroundText(locales_entries, selected);
+ } else if (key == KEY_DOWN || key == KEY_VOLUMEDOWN) {
+ selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1;
+ SelectAndShowBackgroundText(locales_entries, selected);
+ }
+ }
+
+ SetLocale(saved_locale);
+}
+
+int ScreenRecoveryUI::ScreenWidth() const {
+ return gr_fb_width();
+}
+
+int ScreenRecoveryUI::ScreenHeight() const {
+ return gr_fb_height();
+}
+
+void ScreenRecoveryUI::DrawSurface(GRSurface* surface, int sx, int sy, int w, int h, int dx,
+ int dy) const {
+ gr_blit(surface, sx, sy, w, h, dx, dy);
+}
+
int ScreenRecoveryUI::DrawHorizontalRule(int y) const {
- gr_fill(0, y + 4, gr_fb_width(), y + 6);
+ gr_fill(0, y + 4, ScreenWidth(), y + 6);
return 8;
}
@@ -265,6 +356,14 @@
gr_fill(x, y, x + width, y + height);
}
+void ScreenRecoveryUI::DrawFill(int x, int y, int w, int h) const {
+ gr_fill(x, y, w, h);
+}
+
+void ScreenRecoveryUI::DrawTextIcon(int x, int y, GRSurface* surface) const {
+ gr_texticon(x, y, surface);
+}
+
int ScreenRecoveryUI::DrawTextLine(int x, int y, const char* line, bool bold) const {
gr_text(gr_sys_font(), x, y, line, bold);
return char_height_ + 4;
@@ -353,7 +452,7 @@
if (i == menu_sel) {
// Draw the highlight bar.
SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG);
- DrawHighlightBar(0, y - 2, gr_fb_width(), char_height_ + 4);
+ DrawHighlightBar(0, y - 2, ScreenWidth(), char_height_ + 4);
// Bold white text for the selected item.
SetColor(MENU_SEL_FG);
y += DrawTextLine(x, y, menu_[i].c_str(), true);
@@ -368,9 +467,9 @@
// Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or
// we've displayed the entire text buffer.
SetColor(LOG);
- int row = (text_top_ + text_rows_ - 1) % text_rows_;
+ int row = text_row_;
size_t count = 0;
- for (int ty = gr_fb_height() - kMarginHeight - char_height_; ty >= y && count < text_rows_;
+ for (int ty = ScreenHeight() - kMarginHeight - char_height_; ty >= y && count < text_rows_;
ty -= char_height_, ++count) {
DrawTextLine(kMarginWidth, ty, text_[row], false);
--row;
@@ -490,13 +589,14 @@
}
gr_font_size(gr_sys_font(), &char_width_, &char_height_);
- text_rows_ = (gr_fb_height() - kMarginHeight * 2) / char_height_;
- text_cols_ = (gr_fb_width() - kMarginWidth * 2) / char_width_;
+ text_rows_ = (ScreenHeight() - kMarginHeight * 2) / char_height_;
+ text_cols_ = (ScreenWidth() - kMarginWidth * 2) / char_width_;
return true;
}
bool ScreenRecoveryUI::Init(const std::string& locale) {
RecoveryUI::Init(locale);
+
if (!InitTextParams()) {
return false;
}
@@ -510,7 +610,9 @@
file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1);
text_col_ = text_row_ = 0;
- text_top_ = 1;
+
+ // Set up the locale info.
+ SetLocale(locale);
LoadBitmap("icon_error", &error_icon);
@@ -643,7 +745,6 @@
text_[text_row_][text_col_] = '\0';
text_col_ = 0;
text_row_ = (text_row_ + 1) % text_rows_;
- if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
}
if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
}
@@ -673,8 +774,6 @@
if (ch == '\n' || text_col_ >= text_cols_) {
text_col_ = 0;
++text_row_;
-
- if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
}
pthread_mutex_unlock(&updateMutex);
}
@@ -683,7 +782,6 @@
pthread_mutex_lock(&updateMutex);
text_col_ = 0;
text_row_ = 0;
- text_top_ = 1;
for (size_t i = 0; i < text_rows_; ++i) {
memset(text_[i], 0, text_cols_ + 1);
}
@@ -750,7 +848,6 @@
char** old_text = text_;
size_t old_text_col = text_col_;
size_t old_text_row = text_row_;
- size_t old_text_top = text_top_;
// Swap in the alternate screen and clear it.
text_ = file_viewer_text_;
@@ -762,7 +859,6 @@
text_ = old_text;
text_col_ = old_text_col;
text_row_ = old_text_row;
- text_top_ = old_text_top;
}
void ScreenRecoveryUI::StartMenu(const char* const* headers, const char* const* items,
@@ -841,3 +937,23 @@
// will change color to indicate a successful long press.
Redraw();
}
+
+void ScreenRecoveryUI::SetLocale(const std::string& new_locale) {
+ locale_ = new_locale;
+ rtl_locale_ = false;
+
+ if (!new_locale.empty()) {
+ size_t underscore = new_locale.find('_');
+ // lang has the language prefix prior to '_', or full string if '_' doesn't exist.
+ std::string lang = new_locale.substr(0, underscore);
+
+ // A bit cheesy: keep an explicit list of supported RTL languages.
+ if (lang == "ar" || // Arabic
+ lang == "fa" || // Persian (Farsi)
+ lang == "he" || // Hebrew (new language code)
+ lang == "iw" || // Hebrew (old language code)
+ lang == "ur") { // Urdu
+ rtl_locale_ = true;
+ }
+ }
+}
diff --git a/screen_ui.h b/screen_ui.h
index 8231a2b..f05761c 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -32,6 +32,17 @@
// (shows an icon + a progress bar, text logging, menu, etc.)
class ScreenRecoveryUI : public RecoveryUI {
public:
+ enum UIElement {
+ HEADER,
+ MENU,
+ MENU_SEL_BG,
+ MENU_SEL_BG_ACTIVE,
+ MENU_SEL_FG,
+ LOG,
+ TEXT_FILL,
+ INFO
+ };
+
ScreenRecoveryUI();
bool Init(const std::string& locale) override;
@@ -67,18 +78,12 @@
void Redraw();
- enum UIElement {
- HEADER,
- MENU,
- MENU_SEL_BG,
- MENU_SEL_BG_ACTIVE,
- MENU_SEL_FG,
- LOG,
- TEXT_FILL,
- INFO
- };
void SetColor(UIElement e) const;
+ // Check the background text image. Use volume up/down button to cycle through the locales
+ // embedded in the png file, and power button to go back to recovery main menu.
+ void CheckBackgroundTextImages(const std::string& saved_locale);
+
protected:
// The margin that we don't want to use for showing texts (e.g. round screen, or screen with
// rounded corners).
@@ -89,7 +94,58 @@
const int kAnimationFps;
// The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi.
- const float density_;
+ const float kDensity;
+
+ virtual bool InitTextParams();
+
+ virtual void draw_background_locked();
+ virtual void draw_foreground_locked();
+ virtual void draw_screen_locked();
+ virtual void update_screen_locked();
+ virtual void update_progress_locked();
+
+ GRSurface* GetCurrentFrame() const;
+ GRSurface* GetCurrentText() const;
+
+ static void* ProgressThreadStartRoutine(void* data);
+ void ProgressThreadLoop();
+
+ virtual void ShowFile(FILE*);
+ virtual void PrintV(const char*, bool, va_list);
+ void PutChar(char);
+ void ClearText();
+
+ void LoadAnimation();
+ void LoadBitmap(const char* filename, GRSurface** surface);
+ void LoadLocalizedBitmap(const char* filename, GRSurface** surface);
+
+ int PixelsFromDp(int dp) const;
+ virtual int GetAnimationBaseline() const;
+ virtual int GetProgressBaseline() const;
+ virtual int GetTextBaseline() const;
+
+ // Returns pixel width of draw buffer.
+ virtual int ScreenWidth() const;
+ // Returns pixel height of draw buffer.
+ virtual int ScreenHeight() const;
+
+ // Draws a highlight bar at (x, y) - (x + width, y + height).
+ virtual void DrawHighlightBar(int x, int y, int width, int height) const;
+ // Draws a horizontal rule at Y. Returns the offset it should be moving along Y-axis.
+ virtual int DrawHorizontalRule(int y) const;
+ // Draws a line of text. Returns the offset it should be moving along Y-axis.
+ virtual int DrawTextLine(int x, int y, const char* line, bool bold) const;
+ // Draws surface portion (sx, sy, w, h) at screen location (dx, dy).
+ virtual void DrawSurface(GRSurface* surface, int sx, int sy, int w, int h, int dx, int dy) const;
+ // Draws rectangle at (x, y) - (x + w, y + h).
+ virtual void DrawFill(int x, int y, int w, int h) const;
+ // Draws given surface (surface->pixel_bytes = 1) as text at (x, y).
+ virtual void DrawTextIcon(int x, int y, GRSurface* surface) const;
+ // Draws multiple text lines. Returns the offset it should be moving along Y-axis.
+ int DrawTextLines(int x, int y, const char* const* lines) const;
+ // Similar to DrawTextLines() to draw multiple text lines, but additionally wraps long lines.
+ // Returns the offset it should be moving along Y-axis.
+ int DrawWrappedTextLines(int x, int y, const char* const* lines) const;
Icon currentIcon;
@@ -123,7 +179,7 @@
// Log text overlay, displayed when a magic key is pressed.
char** text_;
- size_t text_col_, text_row_, text_top_;
+ size_t text_col_, text_row_;
bool show_text;
bool show_text_ever; // has show_text ever been true?
@@ -150,47 +206,18 @@
int char_width_;
int char_height_;
+ // The locale that's used to show the rendered texts.
+ std::string locale_;
+ bool rtl_locale_;
+
pthread_mutex_t updateMutex;
- virtual bool InitTextParams();
+ private:
+ void SetLocale(const std::string&);
- virtual void draw_background_locked();
- virtual void draw_foreground_locked();
- virtual void draw_screen_locked();
- virtual void update_screen_locked();
- virtual void update_progress_locked();
-
- GRSurface* GetCurrentFrame() const;
- GRSurface* GetCurrentText() const;
-
- static void* ProgressThreadStartRoutine(void* data);
- void ProgressThreadLoop();
-
- virtual void ShowFile(FILE*);
- virtual void PrintV(const char*, bool, va_list);
- void PutChar(char);
- void ClearText();
-
- void LoadAnimation();
- void LoadBitmap(const char* filename, GRSurface** surface);
- void LoadLocalizedBitmap(const char* filename, GRSurface** surface);
-
- int PixelsFromDp(int dp) const;
- virtual int GetAnimationBaseline() const;
- virtual int GetProgressBaseline() const;
- virtual int GetTextBaseline() const;
-
- // Draws a highlight bar at (x, y) - (x + width, y + height).
- virtual void DrawHighlightBar(int x, int y, int width, int height) const;
- // Draws a horizontal rule at Y. Returns the offset it should be moving along Y-axis.
- virtual int DrawHorizontalRule(int y) const;
- // Draws a line of text. Returns the offset it should be moving along Y-axis.
- virtual int DrawTextLine(int x, int y, const char* line, bool bold) const;
- // Draws multiple text lines. Returns the offset it should be moving along Y-axis.
- int DrawTextLines(int x, int y, const char* const* lines) const;
- // Similar to DrawTextLines() to draw multiple text lines, but additionally wraps long lines.
- // Returns the offset it should be moving along Y-axis.
- int DrawWrappedTextLines(int x, int y, const char* const* lines) const;
+ // Display the background texts for "erasing", "error", "no_command" and "installing" for the
+ // selected locale.
+ void SelectAndShowBackgroundText(const std::vector<std::string>& locales_entries, size_t sel);
};
#endif // RECOVERY_UI_H
diff --git a/stub_ui.h b/stub_ui.h
index 85dbcfd..1f6b29a 100644
--- a/stub_ui.h
+++ b/stub_ui.h
@@ -24,18 +24,18 @@
public:
StubRecoveryUI() = default;
- void SetBackground(Icon icon) override {}
- void SetSystemUpdateText(bool security_update) override {}
+ void SetBackground(Icon /* icon */) override {}
+ void SetSystemUpdateText(bool /* security_update */) override {}
// progress indicator
- void SetProgressType(ProgressType type) override {}
- void ShowProgress(float portion, float seconds) override {}
- void SetProgress(float fraction) override {}
+ void SetProgressType(ProgressType /* type */) override {}
+ void ShowProgress(float /* portion */, float /* seconds */) override {}
+ void SetProgress(float /* fraction */) override {}
- void SetStage(int current, int max) override {}
+ void SetStage(int /* current */, int /* max */) override {}
// text log
- void ShowText(bool visible) override {}
+ void ShowText(bool /* visible */) override {}
bool IsTextVisible() override {
return false;
}
@@ -50,12 +50,12 @@
vprintf(fmt, ap);
va_end(ap);
}
- void PrintOnScreenOnly(const char* fmt, ...) override {}
- void ShowFile(const char* filename) override {}
+ void PrintOnScreenOnly(const char* /* fmt */, ...) override {}
+ void ShowFile(const char* /* filename */) override {}
// menu display
- void StartMenu(const char* const* headers, const char* const* items,
- int initial_selection) override {}
+ void StartMenu(const char* const* /* headers */, const char* const* /* items */,
+ int /* initial_selection */) override {}
int SelectMenu(int sel) override {
return sel;
}
diff --git a/tests/Android.mk b/tests/Android.mk
index f2497b8..b3584fe 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -18,7 +18,7 @@
# Unit tests
include $(CLEAR_VARS)
-LOCAL_CFLAGS := -Werror
+LOCAL_CFLAGS := -Wall -Werror
LOCAL_MODULE := recovery_unit_test
LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_STATIC_LIBRARIES := \
@@ -30,7 +30,8 @@
libutils \
libz \
libselinux \
- libbase
+ libbase \
+ libBionicGtestMain
LOCAL_SRC_FILES := \
unit/asn1_decoder_test.cpp \
@@ -46,11 +47,12 @@
# Manual tests
include $(CLEAR_VARS)
-LOCAL_CFLAGS := -Werror
+LOCAL_CFLAGS := -Wall -Werror
LOCAL_MODULE := recovery_manual_test
LOCAL_STATIC_LIBRARIES := \
libminui \
- libbase
+ libbase \
+ libBionicGtestMain
LOCAL_SRC_FILES := manual/recovery_test.cpp
LOCAL_SHARED_LIBRARIES := \
@@ -81,6 +83,7 @@
# Component tests
include $(CLEAR_VARS)
LOCAL_CFLAGS := \
+ -Wall \
-Werror \
-D_FILE_OFFSET_BITS=64
@@ -142,9 +145,9 @@
libdivsufsort \
libdivsufsort64 \
libfs_mgr \
- liblog \
libvintf_recovery \
libvintf \
+ libhidl-gen-utils \
libtinyxml2 \
libselinux \
libext4_utils \
@@ -153,6 +156,7 @@
libcrypto \
libbz \
libziparchive \
+ liblog \
libutils \
libz \
libbase \
@@ -162,6 +166,7 @@
libsquashfs_utils \
libcutils \
libbrotli \
+ libBionicGtestMain \
$(tune2fs_static_libraries)
testdata_files := $(call find-subdir-files, testdata/*)
@@ -191,7 +196,7 @@
# Host tests
include $(CLEAR_VARS)
-LOCAL_CFLAGS := -Werror
+LOCAL_CFLAGS := -Wall -Werror
LOCAL_MODULE := recovery_host_test
LOCAL_MODULE_HOST_OS := linux
LOCAL_C_INCLUDES := bootable/recovery
@@ -200,16 +205,19 @@
LOCAL_STATIC_LIBRARIES := \
libimgdiff \
libimgpatch \
+ libotautil \
libbsdiff \
libbspatch \
libziparchive \
libutils \
libbase \
libcrypto \
+ libbrotli \
libbz \
libdivsufsort64 \
libdivsufsort \
- libz
+ libz \
+ libBionicGtestMain
LOCAL_SHARED_LIBRARIES := \
liblog
include $(BUILD_HOST_NATIVE_TEST)
diff --git a/tests/common/test_constants.h b/tests/common/test_constants.h
index f6b6922..514818e 100644
--- a/tests/common/test_constants.h
+++ b/tests/common/test_constants.h
@@ -19,6 +19,8 @@
#include <stdlib.h>
+#include <string>
+
// Zip entries in ziptest_valid.zip.
static const std::string kATxtContents("abcdefghabcdefgh\n");
static const std::string kBTxtContents("abcdefgh\n");
@@ -30,10 +32,14 @@
// echo -n -e "abcdefgh\n" | sha1sum
static const std::string kBTxtSha1Sum("e414af7161c9554089f4106d6f1797ef14a73666");
-static const char* data_root = getenv("ANDROID_DATA");
-
static std::string from_testdata_base(const std::string& fname) {
- return std::string(data_root) + "/nativetest/recovery/testdata/" + fname;
+#ifdef __ANDROID__
+ static std::string data_root = getenv("ANDROID_DATA");
+#else
+ static std::string data_root = std::string(getenv("ANDROID_PRODUCT_OUT")) + "/data";
+#endif
+
+ return data_root + "/nativetest/recovery/testdata/" + fname;
}
#endif // _OTA_TEST_CONSTANTS_H
diff --git a/tests/component/applypatch_test.cpp b/tests/component/applypatch_test.cpp
index 016fed9..61e06ad 100644
--- a/tests/component/applypatch_test.cpp
+++ b/tests/component/applypatch_test.cpp
@@ -30,12 +30,16 @@
#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <android-base/test_utils.h>
+#include <bsdiff/bsdiff.h>
#include <openssl/sha.h>
#include "applypatch/applypatch.h"
#include "applypatch/applypatch_modes.h"
#include "common/test_constants.h"
-#include "print_sha1.h"
+#include "otautil/cache_location.h"
+#include "otautil/print_sha1.h"
+
+using namespace std::string_literals;
static void sha1sum(const std::string& fname, std::string* sha1, size_t* fsize = nullptr) {
ASSERT_NE(nullptr, sha1);
@@ -53,34 +57,20 @@
}
static void mangle_file(const std::string& fname) {
- std::string content;
- content.reserve(1024);
+ std::string content(1024, '\0');
for (size_t i = 0; i < 1024; i++) {
content[i] = rand() % 256;
}
ASSERT_TRUE(android::base::WriteStringToFile(content, fname));
}
-static bool file_cmp(const std::string& f1, const std::string& f2) {
- std::string c1;
- android::base::ReadFileToString(f1, &c1);
- std::string c2;
- android::base::ReadFileToString(f2, &c2);
- return c1 == c2;
-}
-
class ApplyPatchTest : public ::testing::Test {
public:
- static void SetUpTestCase() {
+ virtual void SetUp() override {
// set up files
old_file = from_testdata_base("old.file");
new_file = from_testdata_base("new.file");
- patch_file = from_testdata_base("patch.bsdiff");
- rand_file = "/cache/applypatch_test_rand.file";
- cache_file = "/cache/saved.file";
-
- // write stuff to rand_file
- ASSERT_TRUE(android::base::WriteStringToFile("hello", rand_file));
+ nonexistent_file = from_testdata_base("nonexistent.file");
// set up SHA constants
sha1sum(old_file, &old_sha1, &old_size);
@@ -90,56 +80,35 @@
bad_sha1_b = android::base::StringPrintf("%040x", rand());
}
- static std::string old_file;
- static std::string new_file;
- static std::string rand_file;
- static std::string cache_file;
- static std::string patch_file;
+ std::string old_file;
+ std::string new_file;
+ std::string nonexistent_file;
- static std::string old_sha1;
- static std::string new_sha1;
- static std::string bad_sha1_a;
- static std::string bad_sha1_b;
+ std::string old_sha1;
+ std::string new_sha1;
+ std::string bad_sha1_a;
+ std::string bad_sha1_b;
- static size_t old_size;
- static size_t new_size;
+ size_t old_size;
+ size_t new_size;
};
-static void cp(const std::string& src, const std::string& tgt) {
- std::string cmd = "cp " + src + " " + tgt;
- system(cmd.c_str());
-}
-
-static void backup_old() {
- cp(ApplyPatchTest::old_file, ApplyPatchTest::cache_file);
-}
-
-static void restore_old() {
- cp(ApplyPatchTest::cache_file, ApplyPatchTest::old_file);
-}
-
class ApplyPatchCacheTest : public ApplyPatchTest {
- public:
- virtual void SetUp() {
- backup_old();
- }
-
- virtual void TearDown() {
- restore_old();
+ protected:
+ void SetUp() override {
+ ApplyPatchTest::SetUp();
+ CacheLocation::location().set_cache_temp_source(old_file);
}
};
-std::string ApplyPatchTest::old_file;
-std::string ApplyPatchTest::new_file;
-std::string ApplyPatchTest::rand_file;
-std::string ApplyPatchTest::patch_file;
-std::string ApplyPatchTest::cache_file;
-std::string ApplyPatchTest::old_sha1;
-std::string ApplyPatchTest::new_sha1;
-std::string ApplyPatchTest::bad_sha1_a;
-std::string ApplyPatchTest::bad_sha1_b;
-size_t ApplyPatchTest::old_size;
-size_t ApplyPatchTest::new_size;
+class ApplyPatchModesTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ CacheLocation::location().set_cache_temp_source(cache_source.path);
+ }
+
+ TemporaryFile cache_source;
+};
TEST_F(ApplyPatchTest, CheckModeSkip) {
std::vector<std::string> sha1s;
@@ -197,43 +166,31 @@
ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
}
-TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSingle) {
- mangle_file(old_file);
- std::vector<std::string> sha1s = { old_sha1 };
- ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
+TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSourceSingle) {
+ TemporaryFile temp_file;
+ mangle_file(temp_file.path);
+ std::vector<std::string> sha1s_single = { old_sha1 };
+ ASSERT_EQ(0, applypatch_check(temp_file.path, sha1s_single));
+ ASSERT_EQ(0, applypatch_check(nonexistent_file.c_str(), sha1s_single));
}
-TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedMultiple) {
- mangle_file(old_file);
- std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b };
- ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
+TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSourceMultiple) {
+ TemporaryFile temp_file;
+ mangle_file(temp_file.path);
+ std::vector<std::string> sha1s_multiple = { bad_sha1_a, old_sha1, bad_sha1_b };
+ ASSERT_EQ(0, applypatch_check(temp_file.path, sha1s_multiple));
+ ASSERT_EQ(0, applypatch_check(nonexistent_file.c_str(), sha1s_multiple));
}
-TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedFailure) {
- mangle_file(old_file);
- std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b };
- ASSERT_NE(0, applypatch_check(&old_file[0], sha1s));
+TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSourceFailure) {
+ TemporaryFile temp_file;
+ mangle_file(temp_file.path);
+ std::vector<std::string> sha1s_failure = { bad_sha1_a, bad_sha1_b };
+ ASSERT_NE(0, applypatch_check(temp_file.path, sha1s_failure));
+ ASSERT_NE(0, applypatch_check(nonexistent_file.c_str(), sha1s_failure));
}
-TEST_F(ApplyPatchCacheTest, CheckCacheMissingSingle) {
- unlink(&old_file[0]);
- std::vector<std::string> sha1s = { old_sha1 };
- ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
-}
-
-TEST_F(ApplyPatchCacheTest, CheckCacheMissingMultiple) {
- unlink(&old_file[0]);
- std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b };
- ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
-}
-
-TEST_F(ApplyPatchCacheTest, CheckCacheMissingFailure) {
- unlink(&old_file[0]);
- std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b };
- ASSERT_NE(0, applypatch_check(&old_file[0], sha1s));
-}
-
-TEST(ApplyPatchModesTest, InvalidArgs) {
+TEST_F(ApplyPatchModesTest, InvalidArgs) {
// At least two args (including the filename).
ASSERT_EQ(2, applypatch_modes(1, (const char* []){ "applypatch" }));
@@ -241,7 +198,7 @@
ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-x" }));
}
-TEST(ApplyPatchModesTest, PatchModeEmmcTarget) {
+TEST_F(ApplyPatchModesTest, PatchModeEmmcTarget) {
std::string boot_img = from_testdata_base("boot.img");
size_t boot_img_size;
std::string boot_img_sha1;
@@ -311,7 +268,55 @@
ASSERT_EQ(0, applypatch_modes(args3.size(), args3.data()));
}
-TEST(ApplyPatchModesTest, PatchModeInvalidArgs) {
+// Ensures that applypatch works with a bsdiff based recovery-from-boot.p.
+TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithBsdiffPatch) {
+ std::string boot_img_file = from_testdata_base("boot.img");
+ std::string boot_img_sha1;
+ size_t boot_img_size;
+ sha1sum(boot_img_file, &boot_img_sha1, &boot_img_size);
+
+ std::string recovery_img_file = from_testdata_base("recovery.img");
+ std::string recovery_img_sha1;
+ size_t recovery_img_size;
+ sha1sum(recovery_img_file, &recovery_img_sha1, &recovery_img_size);
+
+ // Generate the bsdiff patch of recovery-from-boot.p.
+ std::string src_content;
+ ASSERT_TRUE(android::base::ReadFileToString(boot_img_file, &src_content));
+
+ std::string tgt_content;
+ ASSERT_TRUE(android::base::ReadFileToString(recovery_img_file, &tgt_content));
+
+ TemporaryFile patch_file;
+ ASSERT_EQ(0,
+ bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()), src_content.size(),
+ reinterpret_cast<const uint8_t*>(tgt_content.data()), tgt_content.size(),
+ patch_file.path, nullptr));
+
+ // applypatch <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch>
+ std::string src_file_arg =
+ "EMMC:" + boot_img_file + ":" + std::to_string(boot_img_size) + ":" + boot_img_sha1;
+ TemporaryFile tgt_file;
+ std::string tgt_file_arg = "EMMC:"s + tgt_file.path;
+ std::string recovery_img_size_arg = std::to_string(recovery_img_size);
+ std::string patch_arg = boot_img_sha1 + ":" + patch_file.path;
+ std::vector<const char*> args = { "applypatch",
+ src_file_arg.c_str(),
+ tgt_file_arg.c_str(),
+ recovery_img_sha1.c_str(),
+ recovery_img_size_arg.c_str(),
+ patch_arg.c_str() };
+ ASSERT_EQ(0, applypatch_modes(args.size(), args.data()));
+
+ // Double check the patched recovery image.
+ std::string tgt_file_sha1;
+ size_t tgt_file_size;
+ sha1sum(tgt_file.path, &tgt_file_sha1, &tgt_file_size);
+ ASSERT_EQ(recovery_img_size, tgt_file_size);
+ ASSERT_EQ(recovery_img_sha1, tgt_file_sha1);
+}
+
+TEST_F(ApplyPatchModesTest, PatchModeInvalidArgs) {
// Invalid bonus file.
ASSERT_NE(0, applypatch_modes(3, (const char* []){ "applypatch", "-b", "/doesntexist" }));
@@ -372,11 +377,11 @@
ASSERT_NE(0, applypatch_modes(args6.size(), args6.data()));
}
-TEST(ApplyPatchModesTest, CheckModeInvalidArgs) {
+TEST_F(ApplyPatchModesTest, CheckModeInvalidArgs) {
// Insufficient args.
ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-c" }));
}
-TEST(ApplyPatchModesTest, ShowLicenses) {
+TEST_F(ApplyPatchModesTest, ShowLicenses) {
ASSERT_EQ(0, applypatch_modes(2, (const char* []){ "applypatch", "-l" }));
}
diff --git a/tests/component/bootloader_message_test.cpp b/tests/component/bootloader_message_test.cpp
index b38bc71..6cc59a4 100644
--- a/tests/component/bootloader_message_test.cpp
+++ b/tests/component/bootloader_message_test.cpp
@@ -18,53 +18,12 @@
#include <vector>
#include <android-base/strings.h>
+#include <android-base/test_utils.h>
#include <bootloader_message/bootloader_message.h>
#include <gtest/gtest.h>
-class BootloaderMessageTest : public ::testing::Test {
- protected:
- BootloaderMessageTest() : has_misc(true) {}
-
- virtual void SetUp() override {
- std::string err;
- has_misc = !get_bootloader_message_blk_device(&err).empty();
- }
-
- virtual void TearDown() override {
- // Clear the BCB.
- if (has_misc) {
- std::string err;
- ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
- }
- }
-
- bool has_misc;
-};
-
-TEST_F(BootloaderMessageTest, clear_bootloader_message) {
- if (!has_misc) {
- GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
- return;
- }
-
- // Clear the BCB.
- std::string err;
- ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
-
- // Verify the content.
- bootloader_message boot;
- ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
-
- // All the bytes should be cleared.
- ASSERT_EQ(std::string(sizeof(boot), '\0'),
- std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)));
-}
-
-TEST_F(BootloaderMessageTest, read_and_write_bootloader_message) {
- if (!has_misc) {
- GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
- return;
- }
+TEST(BootloaderMessageTest, read_and_write_bootloader_message) {
+ TemporaryFile temp_misc;
// Write the BCB.
bootloader_message boot = {};
@@ -73,90 +32,71 @@
strlcpy(boot.status, "status1", sizeof(boot.status));
std::string err;
- ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err;
+ ASSERT_TRUE(write_bootloader_message_to(boot, temp_misc.path, &err))
+ << "Failed to write BCB: " << err;
// Read and verify.
bootloader_message boot_verify;
- ASSERT_TRUE(read_bootloader_message(&boot_verify, &err)) << "Failed to read BCB: " << err;
+ ASSERT_TRUE(read_bootloader_message_from(&boot_verify, temp_misc.path, &err))
+ << "Failed to read BCB: " << err;
ASSERT_EQ(std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)),
std::string(reinterpret_cast<const char*>(&boot_verify), sizeof(boot_verify)));
}
-TEST_F(BootloaderMessageTest, write_bootloader_message_options) {
- if (!has_misc) {
- GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
- return;
- }
-
+TEST(BootloaderMessageTest, update_bootloader_message_in_struct) {
// Write the options to BCB.
std::vector<std::string> options = { "option1", "option2" };
- std::string err;
- ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
- // Inject some bytes into boot, which should be overwritten while reading.
- bootloader_message boot;
+ bootloader_message boot = {};
+ // Inject some bytes into boot.
strlcpy(boot.recovery, "random message", sizeof(boot.recovery));
+ strlcpy(boot.status, "status bytes", sizeof(boot.status));
+ strlcpy(boot.stage, "stage bytes", sizeof(boot.stage));
strlcpy(boot.reserved, "reserved bytes", sizeof(boot.reserved));
- ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+ ASSERT_TRUE(update_bootloader_message_in_struct(&boot, options));
// Verify that command and recovery fields should be set.
ASSERT_EQ("boot-recovery", std::string(boot.command));
std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
ASSERT_EQ(expected, std::string(boot.recovery));
- // The rest should be cleared.
- ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
- ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
- ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
- std::string(boot.reserved, sizeof(boot.reserved)));
+ // The rest should be intact.
+ ASSERT_EQ("status bytes", std::string(boot.status));
+ ASSERT_EQ("stage bytes", std::string(boot.stage));
+ ASSERT_EQ("reserved bytes", std::string(boot.reserved));
}
-TEST_F(BootloaderMessageTest, write_bootloader_message_options_empty) {
- if (!has_misc) {
- GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
- return;
- }
-
+TEST(BootloaderMessageTest, update_bootloader_message_recovery_options_empty) {
// Write empty vector.
std::vector<std::string> options;
- std::string err;
- ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
// Read and verify.
- bootloader_message boot;
- ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+ bootloader_message boot = {};
+ ASSERT_TRUE(update_bootloader_message_in_struct(&boot, options));
// command and recovery fields should be set.
ASSERT_EQ("boot-recovery", std::string(boot.command));
ASSERT_EQ("recovery\n", std::string(boot.recovery));
- // The rest should be cleared.
+ // The rest should be empty.
ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
std::string(boot.reserved, sizeof(boot.reserved)));
}
-TEST_F(BootloaderMessageTest, write_bootloader_message_options_long) {
- if (!has_misc) {
- GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
- return;
- }
-
+TEST(BootloaderMessageTest, update_bootloader_message_recovery_options_long) {
// Write super long message.
std::vector<std::string> options;
for (int i = 0; i < 100; i++) {
options.push_back("option: " + std::to_string(i));
}
- std::string err;
- ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
-
// Read and verify.
- bootloader_message boot;
- ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+ bootloader_message boot = {};
+ ASSERT_TRUE(update_bootloader_message_in_struct(&boot, options));
// Make sure it's long enough.
std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
@@ -167,40 +107,10 @@
ASSERT_EQ(expected.substr(0, sizeof(boot.recovery) - 1), std::string(boot.recovery));
ASSERT_EQ('\0', boot.recovery[sizeof(boot.recovery) - 1]);
- // The rest should be cleared.
+ // The rest should be empty.
ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
std::string(boot.reserved, sizeof(boot.reserved)));
}
-TEST_F(BootloaderMessageTest, update_bootloader_message) {
- if (!has_misc) {
- GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
- return;
- }
-
- // Inject some bytes into boot, which should be not overwritten later.
- bootloader_message boot;
- strlcpy(boot.recovery, "random message", sizeof(boot.recovery));
- strlcpy(boot.reserved, "reserved bytes", sizeof(boot.reserved));
- std::string err;
- ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err;
-
- // Update the BCB message.
- std::vector<std::string> options = { "option1", "option2" };
- ASSERT_TRUE(update_bootloader_message(options, &err)) << "Failed to update BCB: " << err;
-
- bootloader_message boot_verify;
- ASSERT_TRUE(read_bootloader_message(&boot_verify, &err)) << "Failed to read BCB: " << err;
-
- // Verify that command and recovery fields should be set.
- ASSERT_EQ("boot-recovery", std::string(boot_verify.command));
- std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
- ASSERT_EQ(expected, std::string(boot_verify.recovery));
-
- // The rest should be intact.
- ASSERT_EQ(std::string(boot.status), std::string(boot_verify.status));
- ASSERT_EQ(std::string(boot.stage), std::string(boot_verify.stage));
- ASSERT_EQ(std::string(boot.reserved), std::string(boot_verify.reserved));
-}
diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp
index bf25aeb..6c23def 100644
--- a/tests/component/imgdiff_test.cpp
+++ b/tests/component/imgdiff_test.cpp
@@ -16,17 +16,24 @@
#include <stdio.h>
+#include <algorithm>
#include <string>
+#include <tuple>
#include <vector>
#include <android-base/file.h>
#include <android-base/memory.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include <android-base/test_utils.h>
#include <applypatch/imgdiff.h>
+#include <applypatch/imgdiff_image.h>
#include <applypatch/imgpatch.h>
#include <gtest/gtest.h>
#include <ziparchive/zip_writer.h>
+#include "common/test_constants.h"
+
using android::base::get_unaligned;
// Sanity check for the given imgdiff patch header.
@@ -75,15 +82,20 @@
if (num_deflate != nullptr) *num_deflate = deflate;
}
+static void GenerateTarget(const std::string& src, const std::string& patch, std::string* patched) {
+ patched->clear();
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ [&](const unsigned char* data, size_t len) {
+ patched->append(reinterpret_cast<const char*>(data), len);
+ return len;
+ }));
+}
+
static void verify_patched_image(const std::string& src, const std::string& patch,
const std::string& tgt) {
std::string patched;
- ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
- reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
- [&patched](const unsigned char* data, size_t len) {
- patched.append(reinterpret_cast<const char*>(data), len);
- return len;
- }));
+ GenerateTarget(src, patch, &patched);
ASSERT_EQ(tgt, patched);
}
@@ -138,7 +150,7 @@
TEST(ImgdiffTest, zip_mode_smoke_store) {
// Construct src and tgt zip files.
TemporaryFile src_file;
- FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+ FILE* src_file_ptr = fdopen(src_file.release(), "wb");
ZipWriter src_writer(src_file_ptr);
ASSERT_EQ(0, src_writer.StartEntry("file1.txt", 0)); // Store mode.
const std::string src_content("abcdefg");
@@ -148,7 +160,7 @@
ASSERT_EQ(0, fclose(src_file_ptr));
TemporaryFile tgt_file;
- FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+ FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
ZipWriter tgt_writer(tgt_file_ptr);
ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", 0)); // Store mode.
const std::string tgt_content("abcdefgxyz");
@@ -187,7 +199,7 @@
TEST(ImgdiffTest, zip_mode_smoke_compressed) {
// Construct src and tgt zip files.
TemporaryFile src_file;
- FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+ FILE* src_file_ptr = fdopen(src_file.release(), "wb");
ZipWriter src_writer(src_file_ptr);
ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
const std::string src_content("abcdefg");
@@ -197,7 +209,7 @@
ASSERT_EQ(0, fclose(src_file_ptr));
TemporaryFile tgt_file;
- FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+ FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
ZipWriter tgt_writer(tgt_file_ptr);
ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
const std::string tgt_content("abcdefgxyz");
@@ -236,7 +248,7 @@
TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
// Construct src and tgt zip files.
TemporaryFile src_file;
- FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+ FILE* src_file_ptr = fdopen(src_file.release(), "wb");
ZipWriter src_writer(src_file_ptr);
ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
const std::string src_content("abcdefg");
@@ -246,7 +258,7 @@
ASSERT_EQ(0, fclose(src_file_ptr));
TemporaryFile tgt_file;
- FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+ FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
ZipWriter tgt_writer(tgt_file_ptr);
ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
const std::string tgt_content("abcdefgxyz");
@@ -623,3 +635,438 @@
reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
[](const unsigned char* /*data*/, size_t len) { return len; }));
}
+
+static void construct_store_entry(const std::vector<std::tuple<std::string, size_t, char>>& info,
+ ZipWriter* writer) {
+ for (auto& t : info) {
+ // Create t(1) blocks of t(2), and write the data to t(0)
+ ASSERT_EQ(0, writer->StartEntry(std::get<0>(t).c_str(), 0));
+ const std::string content(std::get<1>(t) * 4096, std::get<2>(t));
+ ASSERT_EQ(0, writer->WriteBytes(content.data(), content.size()));
+ ASSERT_EQ(0, writer->FinishEntry());
+ }
+}
+
+static void construct_deflate_entry(const std::vector<std::tuple<std::string, size_t, size_t>>& info,
+ ZipWriter* writer, const std::string& data) {
+ for (auto& t : info) {
+ // t(0): entry_name; t(1): block offset; t(2) length in blocks.
+ ASSERT_EQ(0, writer->StartEntry(std::get<0>(t).c_str(), ZipWriter::kCompress));
+ ASSERT_EQ(0, writer->WriteBytes(data.data() + std::get<1>(t) * 4096, std::get<2>(t) * 4096));
+ ASSERT_EQ(0, writer->FinishEntry());
+ }
+}
+
+// Look for the source and patch pieces in debug_dir. Generate a target piece from each pair.
+// Concatenate all the target pieces and match against the orignal one. Used pieces in debug_dir
+// will be cleaned up.
+static void GenerateAndCheckSplitTarget(const std::string& debug_dir, size_t count,
+ const std::string& tgt) {
+ std::string patched;
+ for (size_t i = 0; i < count; i++) {
+ std::string split_src_path = android::base::StringPrintf("%s/src-%zu", debug_dir.c_str(), i);
+ std::string split_src;
+ ASSERT_TRUE(android::base::ReadFileToString(split_src_path, &split_src));
+ ASSERT_EQ(0, unlink(split_src_path.c_str()));
+
+ std::string split_patch_path =
+ android::base::StringPrintf("%s/patch-%zu", debug_dir.c_str(), i);
+ std::string split_patch;
+ ASSERT_TRUE(android::base::ReadFileToString(split_patch_path, &split_patch));
+ ASSERT_EQ(0, unlink(split_patch_path.c_str()));
+
+ std::string split_tgt;
+ GenerateTarget(split_src, split_patch, &split_tgt);
+ patched += split_tgt;
+ }
+
+ // Verify we can get back the original target image.
+ ASSERT_EQ(tgt, patched);
+}
+
+std::vector<ImageChunk> ConstructImageChunks(
+ const std::vector<uint8_t>& content, const std::vector<std::tuple<std::string, size_t>>& info) {
+ std::vector<ImageChunk> chunks;
+ size_t start = 0;
+ for (const auto& t : info) {
+ size_t length = std::get<1>(t);
+ chunks.emplace_back(CHUNK_NORMAL, start, &content, length, std::get<0>(t));
+ start += length;
+ }
+
+ return chunks;
+}
+
+TEST(ImgdiffTest, zip_mode_split_image_smoke) {
+ std::vector<uint8_t> content;
+ content.reserve(4096 * 50);
+ uint8_t n = 0;
+ generate_n(back_inserter(content), 4096 * 50, [&n]() { return n++ / 4096; });
+
+ ZipModeImage tgt_image(false, 4096 * 10);
+ std::vector<ImageChunk> tgt_chunks = ConstructImageChunks(content, { { "a", 100 },
+ { "b", 4096 * 2 },
+ { "c", 4096 * 3 },
+ { "d", 300 },
+ { "e-0", 4096 * 10 },
+ { "e-1", 4096 * 5 },
+ { "CD", 200 } });
+ tgt_image.Initialize(std::move(tgt_chunks),
+ std::vector<uint8_t>(content.begin(), content.begin() + 82520));
+
+ tgt_image.DumpChunks();
+
+ ZipModeImage src_image(true, 4096 * 10);
+ std::vector<ImageChunk> src_chunks = ConstructImageChunks(content, { { "b", 4096 * 3 },
+ { "c-0", 4096 * 10 },
+ { "c-1", 4096 * 2 },
+ { "a", 4096 * 5 },
+ { "e-0", 4096 * 10 },
+ { "e-1", 10000 },
+ { "CD", 5000 } });
+ src_image.Initialize(std::move(src_chunks),
+ std::vector<uint8_t>(content.begin(), content.begin() + 137880));
+
+ std::vector<ZipModeImage> split_tgt_images;
+ std::vector<ZipModeImage> split_src_images;
+ std::vector<SortedRangeSet> split_src_ranges;
+
+ ZipModeImage::SplitZipModeImageWithLimit(tgt_image, src_image, &split_tgt_images,
+ &split_src_images, &split_src_ranges);
+
+ // src_piece 1: a 5 blocks, b 3 blocks
+ // src_piece 2: c-0 10 blocks
+ // src_piece 3: d 0 block, e-0 10 blocks
+ // src_piece 4: e-1 2 blocks; CD 2 blocks
+ ASSERT_EQ(split_tgt_images.size(), split_src_images.size());
+ ASSERT_EQ(static_cast<size_t>(4), split_tgt_images.size());
+
+ ASSERT_EQ(static_cast<size_t>(1), split_tgt_images[0].NumOfChunks());
+ ASSERT_EQ(static_cast<size_t>(12288), split_tgt_images[0][0].DataLengthForPatch());
+ ASSERT_EQ("4,0,3,15,20", split_src_ranges[0].ToString());
+
+ ASSERT_EQ(static_cast<size_t>(1), split_tgt_images[1].NumOfChunks());
+ ASSERT_EQ(static_cast<size_t>(12288), split_tgt_images[1][0].DataLengthForPatch());
+ ASSERT_EQ("2,3,13", split_src_ranges[1].ToString());
+
+ ASSERT_EQ(static_cast<size_t>(1), split_tgt_images[2].NumOfChunks());
+ ASSERT_EQ(static_cast<size_t>(40960), split_tgt_images[2][0].DataLengthForPatch());
+ ASSERT_EQ("2,20,30", split_src_ranges[2].ToString());
+
+ ASSERT_EQ(static_cast<size_t>(1), split_tgt_images[3].NumOfChunks());
+ ASSERT_EQ(static_cast<size_t>(16984), split_tgt_images[3][0].DataLengthForPatch());
+ ASSERT_EQ("2,30,34", split_src_ranges[3].ToString());
+}
+
+TEST(ImgdiffTest, zip_mode_store_large_apk) {
+ // Construct src and tgt zip files with limit = 10 blocks.
+ // src tgt
+ // 12 blocks 'd' 3 blocks 'a'
+ // 8 blocks 'c' 3 blocks 'b'
+ // 3 blocks 'b' 8 blocks 'c' (exceeds limit)
+ // 3 blocks 'a' 12 blocks 'd' (exceeds limit)
+ // 3 blocks 'e'
+ TemporaryFile tgt_file;
+ FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
+ ZipWriter tgt_writer(tgt_file_ptr);
+ construct_store_entry(
+ { { "a", 3, 'a' }, { "b", 3, 'b' }, { "c", 8, 'c' }, { "d", 12, 'd' }, { "e", 3, 'e' } },
+ &tgt_writer);
+ ASSERT_EQ(0, tgt_writer.Finish());
+ ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+ TemporaryFile src_file;
+ FILE* src_file_ptr = fdopen(src_file.release(), "wb");
+ ZipWriter src_writer(src_file_ptr);
+ construct_store_entry({ { "d", 12, 'd' }, { "c", 8, 'c' }, { "b", 3, 'b' }, { "a", 3, 'a' } },
+ &src_writer);
+ ASSERT_EQ(0, src_writer.Finish());
+ ASSERT_EQ(0, fclose(src_file_ptr));
+
+ // Compute patch.
+ TemporaryFile patch_file;
+ TemporaryFile split_info_file;
+ TemporaryDir debug_dir;
+ std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path);
+ std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path);
+ std::vector<const char*> args = {
+ "imgdiff", "-z", "--block-limit=10", split_info_arg.c_str(), debug_dir_arg.c_str(),
+ src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ std::string tgt;
+ ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+
+ // Expect 4 pieces of patch. (Roughly 3'a',3'b'; 8'c'; 10'd'; 2'd'3'e')
+ GenerateAndCheckSplitTarget(debug_dir.path, 4, tgt);
+}
+
+TEST(ImgdiffTest, zip_mode_deflate_large_apk) {
+ // Src and tgt zip files are constructed as follows.
+ // src tgt
+ // 22 blocks, "d" 4 blocks, "a"
+ // 5 blocks, "b" 4 blocks, "b"
+ // 3 blocks, "a" 8 blocks, "c" (exceeds limit)
+ // 1 block, "g" 20 blocks, "d" (exceeds limit)
+ // 8 blocks, "c" 2 blocks, "e"
+ // 1 block, "f" 1 block , "f"
+ std::string tgt_path = from_testdata_base("deflate_tgt.zip");
+ std::string src_path = from_testdata_base("deflate_src.zip");
+
+ ZipModeImage src_image(true, 10 * 4096);
+ ZipModeImage tgt_image(false, 10 * 4096);
+ ASSERT_TRUE(src_image.Initialize(src_path));
+ ASSERT_TRUE(tgt_image.Initialize(tgt_path));
+ ASSERT_TRUE(ZipModeImage::CheckAndProcessChunks(&tgt_image, &src_image));
+
+ src_image.DumpChunks();
+ tgt_image.DumpChunks();
+
+ std::vector<ZipModeImage> split_tgt_images;
+ std::vector<ZipModeImage> split_src_images;
+ std::vector<SortedRangeSet> split_src_ranges;
+ ZipModeImage::SplitZipModeImageWithLimit(tgt_image, src_image, &split_tgt_images,
+ &split_src_images, &split_src_ranges);
+
+ // Expected split images with limit = 10 blocks.
+ // src_piece 0: a 3 blocks, b 5 blocks
+ // src_piece 1: c 8 blocks
+ // src_piece 2: d-0 10 block
+ // src_piece 3: d-1 10 blocks
+ // src_piece 4: e 1 block, CD
+ ASSERT_EQ(split_tgt_images.size(), split_src_images.size());
+ ASSERT_EQ(static_cast<size_t>(5), split_tgt_images.size());
+
+ ASSERT_EQ(static_cast<size_t>(2), split_src_images[0].NumOfChunks());
+ ASSERT_EQ("a", split_src_images[0][0].GetEntryName());
+ ASSERT_EQ("b", split_src_images[0][1].GetEntryName());
+
+ ASSERT_EQ(static_cast<size_t>(1), split_src_images[1].NumOfChunks());
+ ASSERT_EQ("c", split_src_images[1][0].GetEntryName());
+
+ ASSERT_EQ(static_cast<size_t>(0), split_src_images[2].NumOfChunks());
+ ASSERT_EQ(static_cast<size_t>(0), split_src_images[3].NumOfChunks());
+ ASSERT_EQ(static_cast<size_t>(0), split_src_images[4].NumOfChunks());
+
+ // Compute patch.
+ TemporaryFile patch_file;
+ TemporaryFile split_info_file;
+ TemporaryDir debug_dir;
+ ASSERT_TRUE(ZipModeImage::GeneratePatches(split_tgt_images, split_src_images, split_src_ranges,
+ patch_file.path, split_info_file.path, debug_dir.path));
+
+ // Verify the content of split info.
+ // Expect 5 pieces of patch. ["a","b"; "c"; "d-0"; "d-1"; "e"]
+ std::string split_info_string;
+ android::base::ReadFileToString(split_info_file.path, &split_info_string);
+ std::vector<std::string> info_list =
+ android::base::Split(android::base::Trim(split_info_string), "\n");
+
+ ASSERT_EQ(static_cast<size_t>(7), info_list.size());
+ ASSERT_EQ("2", android::base::Trim(info_list[0]));
+ ASSERT_EQ("5", android::base::Trim(info_list[1]));
+
+ std::string tgt;
+ ASSERT_TRUE(android::base::ReadFileToString(tgt_path, &tgt));
+ ASSERT_EQ(static_cast<size_t>(160385), tgt.size());
+ std::vector<std::string> tgt_file_ranges = {
+ "36864 2,22,31", "32768 2,31,40", "40960 2,0,11", "40960 2,11,21", "8833 4,21,22,40,41",
+ };
+
+ for (size_t i = 0; i < 5; i++) {
+ struct stat st;
+ std::string path = android::base::StringPrintf("%s/patch-%zu", debug_dir.path, i);
+ ASSERT_EQ(0, stat(path.c_str(), &st));
+ ASSERT_EQ(std::to_string(st.st_size) + " " + tgt_file_ranges[i],
+ android::base::Trim(info_list[i + 2]));
+ }
+
+ GenerateAndCheckSplitTarget(debug_dir.path, 5, tgt);
+}
+
+TEST(ImgdiffTest, zip_mode_no_match_source) {
+ // Generate 20 blocks of random data.
+ std::string random_data;
+ random_data.reserve(4096 * 20);
+ generate_n(back_inserter(random_data), 4096 * 20, []() { return rand() % 256; });
+
+ TemporaryFile tgt_file;
+ FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
+ ZipWriter tgt_writer(tgt_file_ptr);
+
+ construct_deflate_entry({ { "a", 0, 4 }, { "b", 5, 5 }, { "c", 11, 5 } }, &tgt_writer,
+ random_data);
+
+ ASSERT_EQ(0, tgt_writer.Finish());
+ ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+ // We don't have a matching source entry.
+ TemporaryFile src_file;
+ FILE* src_file_ptr = fdopen(src_file.release(), "wb");
+ ZipWriter src_writer(src_file_ptr);
+ construct_store_entry({ { "d", 1, 'd' } }, &src_writer);
+ ASSERT_EQ(0, src_writer.Finish());
+ ASSERT_EQ(0, fclose(src_file_ptr));
+
+ // Compute patch.
+ TemporaryFile patch_file;
+ TemporaryFile split_info_file;
+ TemporaryDir debug_dir;
+ std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path);
+ std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path);
+ std::vector<const char*> args = {
+ "imgdiff", "-z", "--block-limit=10", debug_dir_arg.c_str(), split_info_arg.c_str(),
+ src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ std::string tgt;
+ ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+
+ // Expect 1 pieces of patch due to no matching source entry.
+ GenerateAndCheckSplitTarget(debug_dir.path, 1, tgt);
+}
+
+TEST(ImgdiffTest, zip_mode_large_enough_limit) {
+ // Generate 20 blocks of random data.
+ std::string random_data;
+ random_data.reserve(4096 * 20);
+ generate_n(back_inserter(random_data), 4096 * 20, []() { return rand() % 256; });
+
+ TemporaryFile tgt_file;
+ FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
+ ZipWriter tgt_writer(tgt_file_ptr);
+
+ construct_deflate_entry({ { "a", 0, 10 }, { "b", 10, 5 } }, &tgt_writer, random_data);
+
+ ASSERT_EQ(0, tgt_writer.Finish());
+ ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+ // Construct 10 blocks of source.
+ TemporaryFile src_file;
+ FILE* src_file_ptr = fdopen(src_file.release(), "wb");
+ ZipWriter src_writer(src_file_ptr);
+ construct_deflate_entry({ { "a", 1, 10 } }, &src_writer, random_data);
+ ASSERT_EQ(0, src_writer.Finish());
+ ASSERT_EQ(0, fclose(src_file_ptr));
+
+ // Compute patch with a limit of 20 blocks.
+ TemporaryFile patch_file;
+ TemporaryFile split_info_file;
+ TemporaryDir debug_dir;
+ std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path);
+ std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path);
+ std::vector<const char*> args = {
+ "imgdiff", "-z", "--block-limit=20", split_info_arg.c_str(), debug_dir_arg.c_str(),
+ src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ std::string tgt;
+ ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+
+ // Expect 1 piece of patch since limit is larger than the zip file size.
+ GenerateAndCheckSplitTarget(debug_dir.path, 1, tgt);
+}
+
+TEST(ImgdiffTest, zip_mode_large_apk_small_target_chunk) {
+ TemporaryFile tgt_file;
+ FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
+ ZipWriter tgt_writer(tgt_file_ptr);
+
+ // The first entry is less than 4096 bytes, followed immediately by an entry that has a very
+ // large counterpart in the source file. Therefore the first entry will be patched separately.
+ std::string small_chunk("a", 2000);
+ ASSERT_EQ(0, tgt_writer.StartEntry("a", 0));
+ ASSERT_EQ(0, tgt_writer.WriteBytes(small_chunk.data(), small_chunk.size()));
+ ASSERT_EQ(0, tgt_writer.FinishEntry());
+ construct_store_entry(
+ {
+ { "b", 12, 'b' }, { "c", 3, 'c' },
+ },
+ &tgt_writer);
+ ASSERT_EQ(0, tgt_writer.Finish());
+ ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+ TemporaryFile src_file;
+ FILE* src_file_ptr = fdopen(src_file.release(), "wb");
+ ZipWriter src_writer(src_file_ptr);
+ construct_store_entry({ { "a", 1, 'a' }, { "b", 13, 'b' }, { "c", 1, 'c' } }, &src_writer);
+ ASSERT_EQ(0, src_writer.Finish());
+ ASSERT_EQ(0, fclose(src_file_ptr));
+
+ // Compute patch.
+ TemporaryFile patch_file;
+ TemporaryFile split_info_file;
+ TemporaryDir debug_dir;
+ std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path);
+ std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path);
+ std::vector<const char*> args = {
+ "imgdiff", "-z", "--block-limit=10", split_info_arg.c_str(), debug_dir_arg.c_str(),
+ src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ std::string tgt;
+ ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+
+ // Expect three split src images:
+ // src_piece 0: a 1 blocks
+ // src_piece 1: b-0 10 blocks
+ // src_piece 2: b-1 3 blocks, c 1 blocks, CD
+ GenerateAndCheckSplitTarget(debug_dir.path, 3, tgt);
+}
+
+TEST(ImgdiffTest, zip_mode_large_apk_skipped_small_target_chunk) {
+ TemporaryFile tgt_file;
+ FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
+ ZipWriter tgt_writer(tgt_file_ptr);
+
+ construct_store_entry(
+ {
+ { "a", 11, 'a' },
+ },
+ &tgt_writer);
+
+ // Construct a tiny target entry of 1 byte, which will be skipped due to the tail alignment of
+ // the previous entry.
+ std::string small_chunk("b", 1);
+ ASSERT_EQ(0, tgt_writer.StartEntry("b", 0));
+ ASSERT_EQ(0, tgt_writer.WriteBytes(small_chunk.data(), small_chunk.size()));
+ ASSERT_EQ(0, tgt_writer.FinishEntry());
+
+ ASSERT_EQ(0, tgt_writer.Finish());
+ ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+ TemporaryFile src_file;
+ FILE* src_file_ptr = fdopen(src_file.release(), "wb");
+ ZipWriter src_writer(src_file_ptr);
+ construct_store_entry(
+ {
+ { "a", 11, 'a' }, { "b", 11, 'b' },
+ },
+ &src_writer);
+ ASSERT_EQ(0, src_writer.Finish());
+ ASSERT_EQ(0, fclose(src_file_ptr));
+
+ // Compute patch.
+ TemporaryFile patch_file;
+ TemporaryFile split_info_file;
+ TemporaryDir debug_dir;
+ std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path);
+ std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path);
+ std::vector<const char*> args = {
+ "imgdiff", "-z", "--block-limit=10", split_info_arg.c_str(), debug_dir_arg.c_str(),
+ src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ std::string tgt;
+ ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+
+ // Expect two split src images:
+ // src_piece 0: a-0 10 blocks
+ // src_piece 1: a-0 1 block, CD
+ GenerateAndCheckSplitTarget(debug_dir.path, 2, tgt);
+}
diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp
index 968196f..d19d788 100644
--- a/tests/component/install_test.cpp
+++ b/tests/component/install_test.cpp
@@ -19,6 +19,7 @@
#include <sys/types.h>
#include <unistd.h>
+#include <algorithm>
#include <string>
#include <vector>
@@ -36,7 +37,7 @@
TEST(InstallTest, verify_package_compatibility_no_entry) {
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.fd, "w");
+ FILE* zip_file = fdopen(temp_file.release(), "w");
ZipWriter writer(zip_file);
// The archive must have something to be opened correctly.
ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0));
@@ -53,7 +54,7 @@
TEST(InstallTest, verify_package_compatibility_invalid_entry) {
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.fd, "w");
+ FILE* zip_file = fdopen(temp_file.release(), "w");
ZipWriter writer(zip_file);
ASSERT_EQ(0, writer.StartEntry("compatibility.zip", 0));
ASSERT_EQ(0, writer.FinishEntry());
@@ -69,7 +70,7 @@
TEST(InstallTest, read_metadata_from_package_smoke) {
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.fd, "w");
+ FILE* zip_file = fdopen(temp_file.release(), "w");
ZipWriter writer(zip_file);
ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
const std::string content("abcdefg");
@@ -86,7 +87,7 @@
CloseArchive(zip);
TemporaryFile temp_file2;
- FILE* zip_file2 = fdopen(temp_file2.fd, "w");
+ FILE* zip_file2 = fdopen(temp_file2.release(), "w");
ZipWriter writer2(zip_file2);
ASSERT_EQ(0, writer2.StartEntry("META-INF/com/android/metadata", kCompressDeflated));
ASSERT_EQ(0, writer2.WriteBytes(content.data(), content.size()));
@@ -103,7 +104,7 @@
TEST(InstallTest, read_metadata_from_package_no_entry) {
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.fd, "w");
+ FILE* zip_file = fdopen(temp_file.release(), "w");
ZipWriter writer(zip_file);
ASSERT_EQ(0, writer.StartEntry("dummy_entry", kCompressStored));
ASSERT_EQ(0, writer.FinishEntry());
@@ -119,7 +120,7 @@
TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
TemporaryFile compatibility_zip_file;
- FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
+ FILE* compatibility_zip = fdopen(compatibility_zip_file.release(), "w");
ZipWriter compatibility_zip_writer(compatibility_zip);
ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
std::string malformed_xml = "malformed";
@@ -129,7 +130,7 @@
ASSERT_EQ(0, fclose(compatibility_zip));
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.fd, "w");
+ FILE* zip_file = fdopen(temp_file.release(), "w");
ZipWriter writer(zip_file);
ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
std::string compatibility_zip_content;
@@ -164,7 +165,7 @@
ASSERT_TRUE(
android::base::ReadFileToString(system_manifest_xml_path, &system_manifest_xml_content));
TemporaryFile compatibility_zip_file;
- FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
+ FILE* compatibility_zip = fdopen(compatibility_zip_file.release(), "w");
ZipWriter compatibility_zip_writer(compatibility_zip);
ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(system_manifest_xml_content.data(),
@@ -174,7 +175,7 @@
ASSERT_EQ(0, fclose(compatibility_zip));
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.fd, "w");
+ FILE* zip_file = fdopen(temp_file.release(), "w");
ZipWriter writer(zip_file);
ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
std::string compatibility_zip_content;
@@ -198,10 +199,10 @@
CloseArchive(zip);
}
-TEST(InstallTest, update_binary_command_smoke) {
#ifdef AB_OTA_UPDATER
+static void VerifyAbUpdateBinaryCommand(const std::string& serialno, bool success = true) {
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.fd, "w");
+ FILE* zip_file = fdopen(temp_file.release(), "w");
ZipWriter writer(zip_file);
ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
ASSERT_EQ(0, writer.FinishEntry());
@@ -215,11 +216,13 @@
ASSERT_NE("", device);
std::string timestamp = android::base::GetProperty("ro.build.date.utc", "");
ASSERT_NE("", timestamp);
- std::string metadata = android::base::Join(
- std::vector<std::string>{
- "ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp,
- },
- "\n");
+
+ std::vector<std::string> meta{ "ota-type=AB", "pre-device=" + device,
+ "post-timestamp=" + timestamp };
+ if (!serialno.empty()) {
+ meta.push_back("serialno=" + serialno);
+ }
+ std::string metadata = android::base::Join(meta, "\n");
ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size()));
ASSERT_EQ(0, writer.FinishEntry());
ASSERT_EQ(0, writer.Finish());
@@ -234,17 +237,28 @@
std::string package = "/path/to/update.zip";
std::string binary_path = "/sbin/update_engine_sideload";
std::vector<std::string> cmd;
- ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
- ASSERT_EQ(5U, cmd.size());
- ASSERT_EQ(binary_path, cmd[0]);
- ASSERT_EQ("--payload=file://" + package, cmd[1]);
- ASSERT_EQ("--offset=" + std::to_string(payload_entry.offset), cmd[2]);
- ASSERT_EQ("--headers=" + properties, cmd[3]);
- ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
+ if (success) {
+ ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
+ ASSERT_EQ(5U, cmd.size());
+ ASSERT_EQ(binary_path, cmd[0]);
+ ASSERT_EQ("--payload=file://" + package, cmd[1]);
+ ASSERT_EQ("--offset=" + std::to_string(payload_entry.offset), cmd[2]);
+ ASSERT_EQ("--headers=" + properties, cmd[3]);
+ ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
+ } else {
+ ASSERT_EQ(INSTALL_ERROR, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
+ }
CloseArchive(zip);
+}
+#endif // AB_OTA_UPDATER
+
+TEST(InstallTest, update_binary_command_smoke) {
+#ifdef AB_OTA_UPDATER
+ // Empty serialno will pass the verification.
+ VerifyAbUpdateBinaryCommand({});
#else
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.fd, "w");
+ FILE* zip_file = fdopen(temp_file.release(), "w");
ZipWriter writer(zip_file);
static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
ASSERT_EQ(0, writer.StartEntry(UPDATE_BINARY_NAME, kCompressStored));
@@ -289,7 +303,7 @@
TEST(InstallTest, update_binary_command_invalid) {
#ifdef AB_OTA_UPDATER
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.fd, "w");
+ FILE* zip_file = fdopen(temp_file.release(), "w");
ZipWriter writer(zip_file);
// Missing payload_properties.txt.
ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
@@ -320,7 +334,7 @@
CloseArchive(zip);
#else
TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.fd, "w");
+ FILE* zip_file = fdopen(temp_file.release(), "w");
ZipWriter writer(zip_file);
// The archive must have something to be opened correctly.
ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0));
@@ -340,3 +354,34 @@
CloseArchive(zip);
#endif // AB_OTA_UPDATER
}
+
+#ifdef AB_OTA_UPDATER
+TEST(InstallTest, update_binary_command_multiple_serialno) {
+ std::string serialno = android::base::GetProperty("ro.serialno", "");
+ ASSERT_NE("", serialno);
+
+ // Single matching serialno will pass the verification.
+ VerifyAbUpdateBinaryCommand(serialno);
+
+ static constexpr char alphabet[] =
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ auto generator = []() { return alphabet[rand() % (sizeof(alphabet) - 1)]; };
+
+ // Generate 900 random serial numbers.
+ std::string random_serial;
+ for (size_t i = 0; i < 900; i++) {
+ generate_n(back_inserter(random_serial), serialno.size(), generator);
+ random_serial.append("|");
+ }
+ // Random serialnos should fail the verification.
+ VerifyAbUpdateBinaryCommand(random_serial, false);
+
+ std::string long_serial = random_serial + serialno + "|";
+ for (size_t i = 0; i < 99; i++) {
+ generate_n(back_inserter(long_serial), serialno.size(), generator);
+ long_serial.append("|");
+ }
+ // String with the matching serialno should pass the verification.
+ VerifyAbUpdateBinaryCommand(long_serial);
+}
+#endif // AB_OTA_UPDATER
diff --git a/tests/component/sideload_test.cpp b/tests/component/sideload_test.cpp
index 40cfc69..b7109fc 100644
--- a/tests/component/sideload_test.cpp
+++ b/tests/component/sideload_test.cpp
@@ -16,6 +16,12 @@
#include <unistd.h>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
#include <gtest/gtest.h>
#include "fuse_sideload.h"
@@ -26,11 +32,67 @@
TEST(SideloadTest, run_fuse_sideload_wrong_parameters) {
provider_vtab vtab;
- vtab.close = [](void*) {};
+ vtab.close = [](void) {};
- ASSERT_EQ(-1, run_fuse_sideload(&vtab, nullptr, 4096, 4095));
- ASSERT_EQ(-1, run_fuse_sideload(&vtab, nullptr, 4096, (1 << 22) + 1));
+ ASSERT_EQ(-1, run_fuse_sideload(vtab, 4096, 4095));
+ ASSERT_EQ(-1, run_fuse_sideload(vtab, 4096, (1 << 22) + 1));
// Too many blocks.
- ASSERT_EQ(-1, run_fuse_sideload(&vtab, nullptr, ((1 << 18) + 1) * 4096, 4096));
+ ASSERT_EQ(-1, run_fuse_sideload(vtab, ((1 << 18) + 1) * 4096, 4096));
+}
+
+TEST(SideloadTest, run_fuse_sideload) {
+ const std::vector<std::string> blocks = {
+ std::string(2048, 'a') + std::string(2048, 'b'),
+ std::string(2048, 'c') + std::string(2048, 'd'),
+ std::string(2048, 'e') + std::string(2048, 'f'),
+ std::string(2048, 'g') + std::string(2048, 'h'),
+ };
+ const std::string content = android::base::Join(blocks, "");
+ ASSERT_EQ(16384U, content.size());
+
+ provider_vtab vtab;
+ vtab.close = [](void) {};
+ vtab.read_block = [&blocks](uint32_t block, uint8_t* buffer, uint32_t fetch_size) {
+ if (block >= 4) return -1;
+ blocks[block].copy(reinterpret_cast<char*>(buffer), fetch_size);
+ return 0;
+ };
+
+ TemporaryDir mount_point;
+ pid_t pid = fork();
+ if (pid == 0) {
+ ASSERT_EQ(0, run_fuse_sideload(vtab, 16384, 4096, mount_point.path));
+ _exit(EXIT_SUCCESS);
+ }
+
+ std::string package = std::string(mount_point.path) + "/" + FUSE_SIDELOAD_HOST_FILENAME;
+ int status;
+ static constexpr int kSideloadInstallTimeout = 10;
+ for (int i = 0; i < kSideloadInstallTimeout; ++i) {
+ ASSERT_NE(-1, waitpid(pid, &status, WNOHANG));
+
+ struct stat sb;
+ if (stat(package.c_str(), &sb) == 0) {
+ break;
+ }
+
+ if (errno == ENOENT && i < kSideloadInstallTimeout - 1) {
+ sleep(1);
+ continue;
+ }
+ FAIL() << "Timed out waiting for the fuse-provided package.";
+ }
+
+ std::string content_via_fuse;
+ ASSERT_TRUE(android::base::ReadFileToString(package, &content_via_fuse));
+ ASSERT_EQ(content, content_via_fuse);
+
+ std::string exit_flag = std::string(mount_point.path) + "/" + FUSE_SIDELOAD_HOST_EXIT_FLAG;
+ struct stat sb;
+ ASSERT_EQ(0, stat(exit_flag.c_str(), &sb));
+
+ waitpid(pid, &status, 0);
+ ASSERT_EQ(0, WEXITSTATUS(status));
+ ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
}
diff --git a/tests/component/uncrypt_test.cpp b/tests/component/uncrypt_test.cpp
index 3925236..55baca2 100644
--- a/tests/component/uncrypt_test.cpp
+++ b/tests/component/uncrypt_test.cpp
@@ -20,6 +20,7 @@
#include <sys/un.h>
#include <unistd.h>
+#include <algorithm>
#include <string>
#include <android-base/file.h>
@@ -38,43 +39,49 @@
static const std::string INIT_SVC_UNCRYPT = "init.svc.uncrypt";
static constexpr int SOCKET_CONNECTION_MAX_RETRY = 30;
+static void StopService() {
+ ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
+ ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
+ ASSERT_TRUE(android::base::SetProperty("ctl.stop", "uncrypt"));
+
+ bool success = false;
+ for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+ std::string setup_bcb = android::base::GetProperty(INIT_SVC_SETUP_BCB, "");
+ std::string clear_bcb = android::base::GetProperty(INIT_SVC_CLEAR_BCB, "");
+ std::string uncrypt = android::base::GetProperty(INIT_SVC_UNCRYPT, "");
+ GTEST_LOG_(INFO) << "setup-bcb: [" << setup_bcb << "] clear-bcb: [" << clear_bcb
+ << "] uncrypt: [" << uncrypt << "]";
+ if (setup_bcb != "running" && clear_bcb != "running" && uncrypt != "running") {
+ success = true;
+ break;
+ }
+ sleep(1);
+ }
+
+ ASSERT_TRUE(success) << "uncrypt service is not available.";
+}
+
class UncryptTest : public ::testing::Test {
protected:
UncryptTest() : has_misc(true) {}
- virtual void SetUp() override {
- ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
- ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
- ASSERT_TRUE(android::base::SetProperty("ctl.stop", "uncrypt"));
-
- bool success = false;
- for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
- std::string setup_bcb = android::base::GetProperty(INIT_SVC_SETUP_BCB, "");
- std::string clear_bcb = android::base::GetProperty(INIT_SVC_CLEAR_BCB, "");
- std::string uncrypt = android::base::GetProperty(INIT_SVC_UNCRYPT, "");
- LOG(INFO) << "setup-bcb: [" << setup_bcb << "] clear-bcb: [" << clear_bcb << "] uncrypt: ["
- << uncrypt << "]";
- if (setup_bcb != "running" && clear_bcb != "running" && uncrypt != "running") {
- success = true;
- break;
- }
- sleep(1);
- }
-
- ASSERT_TRUE(success) << "uncrypt service is not available.";
-
+ void SetUp() override {
std::string err;
has_misc = !get_bootloader_message_blk_device(&err).empty();
}
+ void TearDown() override {
+ // Clear the BCB.
+ if (has_misc) {
+ std::string err;
+ ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+ }
+ }
+
void SetupOrClearBcb(bool isSetup, const std::string& message,
const std::string& message_in_bcb) const {
- if (!has_misc) {
- GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
- return;
- }
-
- // Trigger the setup-bcb service.
+ // Restart the setup-bcb service.
+ StopService();
ASSERT_TRUE(android::base::SetProperty("ctl.start", isSetup ? "setup-bcb" : "clear-bcb"));
// Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
@@ -144,27 +151,49 @@
}
}
+ void VerifyBootloaderMessage(const std::string& expected) {
+ std::string err;
+ bootloader_message boot;
+ ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+ // Check that we have all the expected bytes.
+ ASSERT_EQ(expected, std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)));
+ }
+
bool has_misc;
};
TEST_F(UncryptTest, setup_bcb) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ std::string random_data;
+ random_data.reserve(sizeof(bootloader_message));
+ generate_n(back_inserter(random_data), sizeof(bootloader_message), []() { return rand() % 128; });
+
+ bootloader_message boot;
+ memcpy(&boot, random_data.c_str(), random_data.size());
+
+ std::string err;
+ ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err;
+ VerifyBootloaderMessage(random_data);
+
+ ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+ VerifyBootloaderMessage(std::string(sizeof(bootloader_message), '\0'));
+
std::string message = "--update_message=abc value";
std::string message_in_bcb = "recovery\n--update_message=abc value\n";
SetupOrClearBcb(true, message, message_in_bcb);
-}
-TEST_F(UncryptTest, clear_bcb) {
SetupOrClearBcb(false, "", "");
-}
-TEST_F(UncryptTest, setup_bcb_wipe_ab) {
TemporaryFile wipe_package;
ASSERT_TRUE(android::base::WriteStringToFile(std::string(345, 'a'), wipe_package.path));
// It's expected to store a wipe package in /misc, with the package size passed to recovery.
- std::string message =
- "--wipe_ab\n--wipe_package="s + wipe_package.path + "\n--reason=wipePackage"s;
- std::string message_in_bcb =
- "recovery\n--wipe_ab\n--wipe_package_size=345\n--reason=wipePackage\n";
+ message = "--wipe_ab\n--wipe_package="s + wipe_package.path + "\n--reason=wipePackage"s;
+ message_in_bcb = "recovery\n--wipe_ab\n--wipe_package_size=345\n--reason=wipePackage\n";
SetupOrClearBcb(true, message, message_in_bcb);
}
diff --git a/tests/component/update_verifier_test.cpp b/tests/component/update_verifier_test.cpp
index b04e118..1544bb2 100644
--- a/tests/component/update_verifier_test.cpp
+++ b/tests/component/update_verifier_test.cpp
@@ -46,7 +46,6 @@
return;
}
- // The care map file can have only two or four lines.
TemporaryFile temp_file;
std::string content = "system\n2,0,1";
ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
@@ -58,7 +57,7 @@
}
TEST_F(UpdateVerifierTest, verify_image_wrong_lines) {
- // The care map file can have only two or four lines.
+ // The care map file can have only 2 / 4 / 6 lines.
TemporaryFile temp_file;
ASSERT_FALSE(verify_image(temp_file.path));
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp
index 6c341c1..5bfd7cb 100644
--- a/tests/component/updater_test.cpp
+++ b/tests/component/updater_test.cpp
@@ -23,6 +23,7 @@
#include <algorithm>
#include <memory>
#include <string>
+#include <unordered_map>
#include <vector>
#include <android-base/file.h>
@@ -32,16 +33,17 @@
#include <android-base/test_utils.h>
#include <bootloader_message/bootloader_message.h>
#include <brotli/encode.h>
-#include <bsdiff.h>
+#include <bsdiff/bsdiff.h>
#include <gtest/gtest.h>
#include <ziparchive/zip_archive.h>
#include <ziparchive/zip_writer.h>
#include "common/test_constants.h"
#include "edify/expr.h"
-#include "error_code.h"
#include "otautil/SysUtil.h"
-#include "print_sha1.h"
+#include "otautil/cache_location.h"
+#include "otautil/error_code.h"
+#include "otautil/print_sha1.h"
#include "updater/blockimg.h"
#include "updater/install.h"
#include "updater/updater.h"
@@ -74,6 +76,23 @@
ASSERT_EQ(cause_code, state.cause_code);
}
+static void BuildUpdatePackage(const std::unordered_map<std::string, std::string>& entries,
+ int fd) {
+ FILE* zip_file_ptr = fdopen(fd, "wb");
+ ZipWriter zip_writer(zip_file_ptr);
+
+ for (const auto& entry : entries) {
+ ASSERT_EQ(0, zip_writer.StartEntry(entry.first.c_str(), 0));
+ if (!entry.second.empty()) {
+ ASSERT_EQ(0, zip_writer.WriteBytes(entry.second.data(), entry.second.size()));
+ }
+ ASSERT_EQ(0, zip_writer.FinishEntry());
+ }
+
+ ASSERT_EQ(0, zip_writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file_ptr));
+}
+
static std::string get_sha1(const std::string& content) {
uint8_t digest[SHA_DIGEST_LENGTH];
SHA1(reinterpret_cast<const uint8_t*>(content.c_str()), content.size(), digest);
@@ -86,7 +105,16 @@
RegisterBuiltins();
RegisterInstallFunctions();
RegisterBlockImageFunctions();
+
+ // Mock the location of last_command_file.
+ CacheLocation::location().set_cache_temp_source(temp_saved_source_.path);
+ CacheLocation::location().set_last_command_file(temp_last_command_.path);
+ CacheLocation::location().set_stash_directory_base(temp_stash_base_.path);
}
+
+ TemporaryFile temp_saved_source_;
+ TemporaryFile temp_last_command_;
+ TemporaryDir temp_stash_base_;
};
TEST_F(UpdaterTest, getprop) {
@@ -383,7 +411,7 @@
TemporaryFile tf;
UpdaterInfo updater_info;
- updater_info.cmd_pipe = fdopen(tf.fd, "w");
+ updater_info.cmd_pipe = fdopen(tf.release(), "w");
expect(".52", "set_progress(\".52\")", kNoCause, &updater_info);
fflush(updater_info.cmd_pipe);
@@ -392,6 +420,7 @@
ASSERT_EQ(android::base::StringPrintf("set_progress %f\n", .52), cmd);
// recovery-updater protocol expects 2 tokens ("set_progress <frac>").
ASSERT_EQ(2U, android::base::Split(cmd, " ").size());
+ ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
}
TEST_F(UpdaterTest, show_progress) {
@@ -407,7 +436,7 @@
TemporaryFile tf;
UpdaterInfo updater_info;
- updater_info.cmd_pipe = fdopen(tf.fd, "w");
+ updater_info.cmd_pipe = fdopen(tf.release(), "w");
expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_info);
fflush(updater_info.cmd_pipe);
@@ -416,32 +445,22 @@
ASSERT_EQ(android::base::StringPrintf("progress %f %d\n", .52, 10), cmd);
// recovery-updater protocol expects 3 tokens ("progress <frac> <secs>").
ASSERT_EQ(3U, android::base::Split(cmd, " ").size());
+ ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
}
-TEST_F(UpdaterTest, block_image_update) {
- // Create a zip file with new_data and patch_data.
- TemporaryFile zip_file;
- FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
- ZipWriter zip_writer(zip_file_ptr);
-
- // Add a dummy new data.
- ASSERT_EQ(0, zip_writer.StartEntry("new_data", 0));
- ASSERT_EQ(0, zip_writer.FinishEntry());
-
- // Generate and add the patch data.
+TEST_F(UpdaterTest, block_image_update_patch_data) {
std::string src_content = std::string(4096, 'a') + std::string(4096, 'c');
std::string tgt_content = std::string(4096, 'b') + std::string(4096, 'd');
+
+ // Generate the patch data.
TemporaryFile patch_file;
ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()),
src_content.size(), reinterpret_cast<const uint8_t*>(tgt_content.data()),
tgt_content.size(), patch_file.path, nullptr));
std::string patch_content;
ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content));
- ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
- ASSERT_EQ(0, zip_writer.WriteBytes(patch_content.data(), patch_content.size()));
- ASSERT_EQ(0, zip_writer.FinishEntry());
- // Add two transfer lists. The first one contains a bsdiff; and we expect the update to succeed.
+ // Create the transfer list that contains a bsdiff.
std::string src_hash = get_sha1(src_content);
std::string tgt_hash = get_sha1(tgt_content);
std::vector<std::string> transfer_list = {
@@ -454,27 +473,16 @@
src_hash.c_str(), tgt_hash.c_str(), src_hash.c_str()),
"free " + src_hash,
};
- ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
- std::string commands = android::base::Join(transfer_list, '\n');
- ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
- ASSERT_EQ(0, zip_writer.FinishEntry());
- // Stash and free some blocks, then fail the 2nd update intentionally.
- std::vector<std::string> fail_transfer_list = {
- "4",
- "2",
- "0",
- "2",
- "stash " + tgt_hash + " 2,0,2",
- "free " + tgt_hash,
- "fail",
+ std::unordered_map<std::string, std::string> entries = {
+ { "new_data", "" },
+ { "patch_data", patch_content },
+ { "transfer_list", android::base::Join(transfer_list, '\n') },
};
- ASSERT_EQ(0, zip_writer.StartEntry("fail_transfer_list", 0));
- std::string fail_commands = android::base::Join(fail_transfer_list, '\n');
- ASSERT_EQ(0, zip_writer.WriteBytes(fail_commands.data(), fail_commands.size()));
- ASSERT_EQ(0, zip_writer.FinishEntry());
- ASSERT_EQ(0, zip_writer.Finish());
- ASSERT_EQ(0, fclose(zip_file_ptr));
+
+ // Build the update package.
+ TemporaryFile zip_file;
+ BuildUpdatePackage(entries, zip_file.release());
MemMapping map;
ASSERT_TRUE(map.MapFile(zip_file.path));
@@ -485,11 +493,11 @@
UpdaterInfo updater_info;
updater_info.package_zip = handle;
TemporaryFile temp_pipe;
- updater_info.cmd_pipe = fopen(temp_pipe.path, "wbe");
+ updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
updater_info.package_zip_addr = map.addr;
updater_info.package_zip_len = map.length;
- // Execute the commands in the 1st transfer list.
+ // Execute the commands in the transfer list.
TemporaryFile update_file;
ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
std::string script = "block_image_update(\"" + std::string(update_file.path) +
@@ -500,57 +508,28 @@
ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content));
ASSERT_EQ(tgt_hash, get_sha1(updated_content));
- // Expect the 2nd update to fail, but expect the stashed blocks to be freed.
- script = "block_image_update(\"" + std::string(update_file.path) +
- R"(", package_extract_file("fail_transfer_list"), "new_data", "patch_data"))";
- expect("", script.c_str(), kNoCause, &updater_info);
- // Updater generates the stash name based on the input file name.
- std::string name_digest = get_sha1(update_file.path);
- std::string stash_base = "/cache/recovery/" + name_digest;
- ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
- ASSERT_EQ(-1, access((stash_base + tgt_hash).c_str(), F_OK));
- ASSERT_EQ(0, rmdir(stash_base.c_str()));
-
ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
CloseArchive(handle);
}
-TEST_F(UpdaterTest, new_data_short_write) {
- // Create a zip file with new_data.
- TemporaryFile zip_file;
- FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
- ZipWriter zip_writer(zip_file_ptr);
-
- // Add the empty new data.
- ASSERT_EQ(0, zip_writer.StartEntry("empty_new_data", 0));
- ASSERT_EQ(0, zip_writer.FinishEntry());
- // Add the short written new data.
- ASSERT_EQ(0, zip_writer.StartEntry("short_new_data", 0));
- std::string new_data_short = std::string(10, 'a');
- ASSERT_EQ(0, zip_writer.WriteBytes(new_data_short.data(), new_data_short.size()));
- ASSERT_EQ(0, zip_writer.FinishEntry());
- // Add the data of exactly one block.
- ASSERT_EQ(0, zip_writer.StartEntry("exact_new_data", 0));
- std::string new_data_exact = std::string(4096, 'a');
- ASSERT_EQ(0, zip_writer.WriteBytes(new_data_exact.data(), new_data_exact.size()));
- ASSERT_EQ(0, zip_writer.FinishEntry());
- // Add a dummy patch data.
- ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
- ASSERT_EQ(0, zip_writer.FinishEntry());
-
+TEST_F(UpdaterTest, block_image_update_fail) {
+ std::string src_content(4096 * 2, 'e');
+ std::string src_hash = get_sha1(src_content);
+ // Stash and free some blocks, then fail the update intentionally.
std::vector<std::string> transfer_list = {
- "4",
- "1",
- "0",
- "0",
- "new 2,0,1",
+ "4", "2", "0", "2", "stash " + src_hash + " 2,0,2", "free " + src_hash, "fail",
};
- ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
- std::string commands = android::base::Join(transfer_list, '\n');
- ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
- ASSERT_EQ(0, zip_writer.FinishEntry());
- ASSERT_EQ(0, zip_writer.Finish());
- ASSERT_EQ(0, fclose(zip_file_ptr));
+
+ // Add a new data of 10 bytes to test the deadlock.
+ std::unordered_map<std::string, std::string> entries = {
+ { "new_data", std::string(10, 0) },
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list, '\n') },
+ };
+
+ // Build the update package.
+ TemporaryFile zip_file;
+ BuildUpdatePackage(entries, zip_file.release());
MemMapping map;
ASSERT_TRUE(map.MapFile(zip_file.path));
@@ -561,7 +540,95 @@
UpdaterInfo updater_info;
updater_info.package_zip = handle;
TemporaryFile temp_pipe;
- updater_info.cmd_pipe = fopen(temp_pipe.path, "wbe");
+ updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
+ updater_info.package_zip_addr = map.addr;
+ updater_info.package_zip_len = map.length;
+
+ TemporaryFile update_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+ // Expect the stashed blocks to be freed.
+ std::string script = "block_image_update(\"" + std::string(update_file.path) +
+ R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))";
+ expect("", script.c_str(), kNoCause, &updater_info);
+ // Updater generates the stash name based on the input file name.
+ std::string name_digest = get_sha1(update_file.path);
+ std::string stash_base = std::string(temp_stash_base_.path) + "/" + name_digest;
+ ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
+ ASSERT_EQ(-1, access((stash_base + src_hash).c_str(), F_OK));
+ ASSERT_EQ(0, rmdir(stash_base.c_str()));
+
+ ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+ CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, new_data_over_write) {
+ std::vector<std::string> transfer_list = {
+ "4", "1", "0", "0", "new 2,0,1",
+ };
+
+ // Write 4096 + 100 bytes of new data.
+ std::unordered_map<std::string, std::string> entries = {
+ { "new_data", std::string(4196, 0) },
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list, '\n') },
+ };
+
+ // Build the update package.
+ TemporaryFile zip_file;
+ BuildUpdatePackage(entries, zip_file.release());
+
+ MemMapping map;
+ ASSERT_TRUE(map.MapFile(zip_file.path));
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+
+ // Set up the handler, command_pipe, patch offset & length.
+ UpdaterInfo updater_info;
+ updater_info.package_zip = handle;
+ TemporaryFile temp_pipe;
+ updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
+ updater_info.package_zip_addr = map.addr;
+ updater_info.package_zip_len = map.length;
+
+ TemporaryFile update_file;
+ std::string script = "block_image_update(\"" + std::string(update_file.path) +
+ R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))";
+ expect("t", script.c_str(), kNoCause, &updater_info);
+
+ ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+ CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, new_data_short_write) {
+ std::vector<std::string> transfer_list = {
+ "4",
+ "1",
+ "0",
+ "0",
+ "new 2,0,1",
+ };
+
+ std::unordered_map<std::string, std::string> entries = {
+ { "empty_new_data", "" },
+ { "short_new_data", std::string(10, 'a') },
+ { "exact_new_data", std::string(4096, 'a') },
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list, '\n') },
+ };
+
+ TemporaryFile zip_file;
+ BuildUpdatePackage(entries, zip_file.release());
+
+ MemMapping map;
+ ASSERT_TRUE(map.MapFile(zip_file.path));
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+
+ // Set up the handler, command_pipe, patch offset & length.
+ UpdaterInfo updater_info;
+ updater_info.package_zip = handle;
+ TemporaryFile temp_pipe;
+ updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
updater_info.package_zip_addr = map.addr;
updater_info.package_zip_len = map.length;
@@ -579,18 +646,12 @@
std::string script_exact_data = "block_image_update(\"" + std::string(update_file.path) +
R"(", package_extract_file("transfer_list"), "exact_new_data", "patch_data"))";
expect("t", script_exact_data.c_str(), kNoCause, &updater_info);
+
+ ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
CloseArchive(handle);
}
TEST_F(UpdaterTest, brotli_new_data) {
- // Create a zip file with new_data.
- TemporaryFile zip_file;
- FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
- ZipWriter zip_writer(zip_file_ptr);
-
- // Add a brotli compressed new data entry.
- ASSERT_EQ(0, zip_writer.StartEntry("new.dat.br", 0));
-
auto generator = []() { return rand() % 128; };
// Generate 100 blocks of random data.
std::string brotli_new_data;
@@ -598,16 +659,12 @@
generate_n(back_inserter(brotli_new_data), 4096 * 100, generator);
size_t encoded_size = BrotliEncoderMaxCompressedSize(brotli_new_data.size());
- std::vector<uint8_t> encoded_data(encoded_size);
+ std::string encoded_data(encoded_size, 0);
ASSERT_TRUE(BrotliEncoderCompress(
BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, brotli_new_data.size(),
- reinterpret_cast<const uint8_t*>(brotli_new_data.data()), &encoded_size, encoded_data.data()));
-
- ASSERT_EQ(0, zip_writer.WriteBytes(encoded_data.data(), encoded_size));
- ASSERT_EQ(0, zip_writer.FinishEntry());
- // Add a dummy patch data.
- ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
- ASSERT_EQ(0, zip_writer.FinishEntry());
+ reinterpret_cast<const uint8_t*>(brotli_new_data.data()), &encoded_size,
+ reinterpret_cast<uint8_t*>(const_cast<char*>(encoded_data.data()))));
+ encoded_data.resize(encoded_size);
// Write a few small chunks of new data, then a large chunk, and finally a few small chunks.
// This helps us to catch potential short writes.
@@ -623,12 +680,15 @@
"new 2,98,99",
"new 2,99,100",
};
- ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
- std::string commands = android::base::Join(transfer_list, '\n');
- ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
- ASSERT_EQ(0, zip_writer.FinishEntry());
- ASSERT_EQ(0, zip_writer.Finish());
- ASSERT_EQ(0, fclose(zip_file_ptr));
+
+ std::unordered_map<std::string, std::string> entries = {
+ { "new.dat.br", std::move(encoded_data) },
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list, '\n') },
+ };
+
+ TemporaryFile zip_file;
+ BuildUpdatePackage(entries, zip_file.release());
MemMapping map;
ASSERT_TRUE(map.MapFile(zip_file.path));
@@ -639,7 +699,7 @@
UpdaterInfo updater_info;
updater_info.package_zip = handle;
TemporaryFile temp_pipe;
- updater_info.cmd_pipe = fopen(temp_pipe.path, "wb");
+ updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wb");
updater_info.package_zip_addr = map.addr;
updater_info.package_zip_len = map.length;
@@ -653,5 +713,221 @@
std::string updated_content;
ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content));
ASSERT_EQ(brotli_new_data, updated_content);
+
+ ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+ CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, last_command_update) {
+ std::string last_command_file = CacheLocation::location().last_command_file();
+
+ std::string block1 = std::string(4096, '1');
+ std::string block2 = std::string(4096, '2');
+ std::string block3 = std::string(4096, '3');
+ std::string block1_hash = get_sha1(block1);
+ std::string block2_hash = get_sha1(block2);
+ std::string block3_hash = get_sha1(block3);
+
+ // Compose the transfer list to fail the first update.
+ std::vector<std::string> transfer_list_fail = {
+ "4",
+ "2",
+ "0",
+ "2",
+ "stash " + block1_hash + " 2,0,1",
+ "move " + block1_hash + " 2,1,2 1 2,0,1",
+ "stash " + block3_hash + " 2,2,3",
+ "fail",
+ };
+
+ // Mimic a resumed update with the same transfer commands.
+ std::vector<std::string> transfer_list_continue = {
+ "4",
+ "2",
+ "0",
+ "2",
+ "stash " + block1_hash + " 2,0,1",
+ "move " + block1_hash + " 2,1,2 1 2,0,1",
+ "stash " + block3_hash + " 2,2,3",
+ "move " + block1_hash + " 2,2,3 1 2,0,1",
+ };
+
+ std::unordered_map<std::string, std::string> entries = {
+ { "new_data", "" },
+ { "patch_data", "" },
+ { "transfer_list_fail", android::base::Join(transfer_list_fail, '\n') },
+ { "transfer_list_continue", android::base::Join(transfer_list_continue, '\n') },
+ };
+
+ // Build the update package.
+ TemporaryFile zip_file;
+ BuildUpdatePackage(entries, zip_file.release());
+
+ MemMapping map;
+ ASSERT_TRUE(map.MapFile(zip_file.path));
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+
+ // Set up the handler, command_pipe, patch offset & length.
+ UpdaterInfo updater_info;
+ updater_info.package_zip = handle;
+ TemporaryFile temp_pipe;
+ updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
+ updater_info.package_zip_addr = map.addr;
+ updater_info.package_zip_len = map.length;
+
+ std::string src_content = block1 + block2 + block3;
+ TemporaryFile update_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+ std::string script =
+ "block_image_update(\"" + std::string(update_file.path) +
+ R"(", package_extract_file("transfer_list_fail"), "new_data", "patch_data"))";
+ expect("", script.c_str(), kNoCause, &updater_info);
+
+ // Expect last_command to contain the last stash command.
+ std::string last_command_content;
+ ASSERT_TRUE(android::base::ReadFileToString(last_command_file.c_str(), &last_command_content));
+ EXPECT_EQ("2\nstash " + block3_hash + " 2,2,3", last_command_content);
+ std::string updated_contents;
+ ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_contents));
+ ASSERT_EQ(block1 + block1 + block3, updated_contents);
+
+ // Resume the update, expect the first 'move' to be skipped but the second 'move' to be executed.
+ ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+ std::string script_second_update =
+ "block_image_update(\"" + std::string(update_file.path) +
+ R"(", package_extract_file("transfer_list_continue"), "new_data", "patch_data"))";
+ expect("t", script_second_update.c_str(), kNoCause, &updater_info);
+ ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_contents));
+ ASSERT_EQ(block1 + block2 + block1, updated_contents);
+
+ ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+ CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, last_command_update_unresumable) {
+ std::string last_command_file = CacheLocation::location().last_command_file();
+
+ std::string block1 = std::string(4096, '1');
+ std::string block2 = std::string(4096, '2');
+ std::string block1_hash = get_sha1(block1);
+ std::string block2_hash = get_sha1(block2);
+
+ // Construct an unresumable update with source blocks mismatch.
+ std::vector<std::string> transfer_list_unresumable = {
+ "4", "2", "0", "2", "stash " + block1_hash + " 2,0,1", "move " + block2_hash + " 2,1,2 1 2,0,1",
+ };
+
+ std::unordered_map<std::string, std::string> entries = {
+ { "new_data", "" },
+ { "patch_data", "" },
+ { "transfer_list_unresumable", android::base::Join(transfer_list_unresumable, '\n') },
+ };
+
+ // Build the update package.
+ TemporaryFile zip_file;
+ BuildUpdatePackage(entries, zip_file.release());
+
+ MemMapping map;
+ ASSERT_TRUE(map.MapFile(zip_file.path));
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+
+ // Set up the handler, command_pipe, patch offset & length.
+ UpdaterInfo updater_info;
+ updater_info.package_zip = handle;
+ TemporaryFile temp_pipe;
+ updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
+ updater_info.package_zip_addr = map.addr;
+ updater_info.package_zip_len = map.length;
+
+ // Set up the last_command_file
+ ASSERT_TRUE(
+ android::base::WriteStringToFile("0\nstash " + block1_hash + " 2,0,1", last_command_file));
+
+ // The last_command_file will be deleted if the update encounters an unresumable failure
+ // later.
+ std::string src_content = block1 + block1;
+ TemporaryFile update_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+ std::string script =
+ "block_image_update(\"" + std::string(update_file.path) +
+ R"(", package_extract_file("transfer_list_unresumable"), "new_data", "patch_data"))";
+ expect("", script.c_str(), kNoCause, &updater_info);
+ ASSERT_EQ(-1, access(last_command_file.c_str(), R_OK));
+
+ ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+ CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, last_command_verify) {
+ std::string last_command_file = CacheLocation::location().last_command_file();
+
+ std::string block1 = std::string(4096, '1');
+ std::string block2 = std::string(4096, '2');
+ std::string block3 = std::string(4096, '3');
+ std::string block1_hash = get_sha1(block1);
+ std::string block2_hash = get_sha1(block2);
+ std::string block3_hash = get_sha1(block3);
+
+ std::vector<std::string> transfer_list_verify = {
+ "4",
+ "2",
+ "0",
+ "2",
+ "stash " + block1_hash + " 2,0,1",
+ "move " + block1_hash + " 2,0,1 1 2,0,1",
+ "move " + block1_hash + " 2,1,2 1 2,0,1",
+ "stash " + block3_hash + " 2,2,3",
+ };
+
+ std::unordered_map<std::string, std::string> entries = {
+ { "new_data", "" },
+ { "patch_data", "" },
+ { "transfer_list_verify", android::base::Join(transfer_list_verify, '\n') },
+ };
+
+ // Build the update package.
+ TemporaryFile zip_file;
+ BuildUpdatePackage(entries, zip_file.release());
+
+ MemMapping map;
+ ASSERT_TRUE(map.MapFile(zip_file.path));
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+
+ // Set up the handler, command_pipe, patch offset & length.
+ UpdaterInfo updater_info;
+ updater_info.package_zip = handle;
+ TemporaryFile temp_pipe;
+ updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
+ updater_info.package_zip_addr = map.addr;
+ updater_info.package_zip_len = map.length;
+
+ std::string src_content = block1 + block1 + block3;
+ TemporaryFile update_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+
+ ASSERT_TRUE(
+ android::base::WriteStringToFile("2\nstash " + block3_hash + " 2,2,3", last_command_file));
+
+ // Expect the verification to succeed and the last_command_file is intact.
+ std::string script_verify =
+ "block_image_verify(\"" + std::string(update_file.path) +
+ R"(", package_extract_file("transfer_list_verify"), "new_data","patch_data"))";
+ expect("t", script_verify.c_str(), kNoCause, &updater_info);
+
+ std::string last_command_content;
+ ASSERT_TRUE(android::base::ReadFileToString(last_command_file.c_str(), &last_command_content));
+ EXPECT_EQ("2\nstash " + block3_hash + " 2,2,3", last_command_content);
+
+ // Expect the verification to succeed but last_command_file to be deleted; because the target
+ // blocks don't have the expected contents for the second move command.
+ src_content = block1 + block2 + block3;
+ ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+ expect("t", script_verify.c_str(), kNoCause, &updater_info);
+ ASSERT_EQ(-1, access(last_command_file.c_str(), R_OK));
+
+ ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
CloseArchive(handle);
}
diff --git a/tests/manual/recovery_test.cpp b/tests/manual/recovery_test.cpp
index 92c6ef2..64e3b59 100644
--- a/tests/manual/recovery_test.cpp
+++ b/tests/manual/recovery_test.cpp
@@ -209,7 +209,7 @@
ASSERT_GT(height, y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
char* loc = reinterpret_cast<char*>(&row[5]);
if (matches_locale(loc, kLocale.c_str())) {
- EXPECT_TRUE(android::base::StartsWith(loc, kLocale.c_str()));
+ EXPECT_TRUE(android::base::StartsWith(loc, kLocale));
break;
} else {
for (int i = 0; i < h; ++i, ++y) {
diff --git a/tests/testdata/deflate_src.zip b/tests/testdata/deflate_src.zip
new file mode 100644
index 0000000..bdb2b32
--- /dev/null
+++ b/tests/testdata/deflate_src.zip
Binary files differ
diff --git a/tests/testdata/deflate_tgt.zip b/tests/testdata/deflate_tgt.zip
new file mode 100644
index 0000000..2a21760
--- /dev/null
+++ b/tests/testdata/deflate_tgt.zip
Binary files differ
diff --git a/tests/unit/dirutil_test.cpp b/tests/unit/dirutil_test.cpp
index 5e2ae4f..7f85d13 100644
--- a/tests/unit/dirutil_test.cpp
+++ b/tests/unit/dirutil_test.cpp
@@ -26,23 +26,23 @@
TEST(DirUtilTest, create_invalid) {
// Requesting to create an empty dir is invalid.
- ASSERT_EQ(-1, dirCreateHierarchy("", 0755, nullptr, false, nullptr));
+ ASSERT_EQ(-1, mkdir_recursively("", 0755, false, nullptr));
ASSERT_EQ(ENOENT, errno);
// Requesting to strip the name with no slash present.
- ASSERT_EQ(-1, dirCreateHierarchy("abc", 0755, nullptr, true, nullptr));
+ ASSERT_EQ(-1, mkdir_recursively("abc", 0755, true, nullptr));
ASSERT_EQ(ENOENT, errno);
// Creating a dir that already exists.
TemporaryDir td;
- ASSERT_EQ(0, dirCreateHierarchy(td.path, 0755, nullptr, false, nullptr));
+ ASSERT_EQ(0, mkdir_recursively(td.path, 0755, false, nullptr));
// "///" is a valid dir.
- ASSERT_EQ(0, dirCreateHierarchy("///", 0755, nullptr, false, nullptr));
+ ASSERT_EQ(0, mkdir_recursively("///", 0755, false, nullptr));
// Request to create a dir, but a file with the same name already exists.
TemporaryFile tf;
- ASSERT_EQ(-1, dirCreateHierarchy(tf.path, 0755, nullptr, false, nullptr));
+ ASSERT_EQ(-1, mkdir_recursively(tf.path, 0755, false, nullptr));
ASSERT_EQ(ENOTDIR, errno);
}
@@ -51,7 +51,7 @@
std::string prefix(td.path);
std::string path = prefix + "/a/b";
constexpr mode_t mode = 0755;
- ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), mode, nullptr, false, nullptr));
+ ASSERT_EQ(0, mkdir_recursively(path, mode, false, nullptr));
// Verify.
struct stat sb;
@@ -69,7 +69,7 @@
TemporaryDir td;
std::string prefix(td.path);
std::string path = prefix + "/a/b";
- ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), 0755, nullptr, true, nullptr));
+ ASSERT_EQ(0, mkdir_recursively(path, 0755, true, nullptr));
// Verify that "../a" exists but not "../a/b".
struct stat sb;
@@ -83,31 +83,21 @@
ASSERT_EQ(0, rmdir((prefix + "/a").c_str()));
}
-TEST(DirUtilTest, create_mode_and_timestamp) {
+TEST(DirUtilTest, create_mode) {
TemporaryDir td;
std::string prefix(td.path);
std::string path = prefix + "/a/b";
- // Set the timestamp to 8/1/2008.
- constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };
constexpr mode_t mode = 0751;
- ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), mode, ×tamp, false, nullptr));
+ ASSERT_EQ(0, mkdir_recursively(path, mode, false, nullptr));
- // Verify the mode and timestamp for "../a/b".
+ // Verify the mode for "../a/b".
struct stat sb;
ASSERT_EQ(0, stat(path.c_str(), &sb)) << strerror(errno);
ASSERT_TRUE(S_ISDIR(sb.st_mode));
constexpr mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO;
ASSERT_EQ(mode, sb.st_mode & mask);
- timespec time;
- time.tv_sec = 1217592000;
- time.tv_nsec = 0;
-
- ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
- ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
-
- // Verify the mode for "../a". Note that the timestamp for intermediate directories (e.g. "../a")
- // may not be 'timestamp' according to the current implementation.
+ // Verify the mode for "../a".
ASSERT_EQ(0, stat((prefix + "/a").c_str(), &sb)) << strerror(errno);
ASSERT_TRUE(S_ISDIR(sb.st_mode));
ASSERT_EQ(mode, sb.st_mode & mask);
@@ -116,35 +106,3 @@
ASSERT_EQ(0, rmdir((prefix + "/a/b").c_str()));
ASSERT_EQ(0, rmdir((prefix + "/a").c_str()));
}
-
-TEST(DirUtilTest, unlink_invalid) {
- // File doesn't exist.
- ASSERT_EQ(-1, dirUnlinkHierarchy("doesntexist"));
-
- // Nonexistent directory.
- TemporaryDir td;
- std::string path(td.path);
- ASSERT_EQ(-1, dirUnlinkHierarchy((path + "/a").c_str()));
- ASSERT_EQ(ENOENT, errno);
-}
-
-TEST(DirUtilTest, unlink_smoke) {
- // Unlink a file.
- TemporaryFile tf;
- ASSERT_EQ(0, dirUnlinkHierarchy(tf.path));
- ASSERT_EQ(-1, access(tf.path, F_OK));
-
- TemporaryDir td;
- std::string path(td.path);
- constexpr mode_t mode = 0700;
- ASSERT_EQ(0, mkdir((path + "/a").c_str(), mode));
- ASSERT_EQ(0, mkdir((path + "/a/b").c_str(), mode));
- ASSERT_EQ(0, mkdir((path + "/a/b/c").c_str(), mode));
- ASSERT_EQ(0, mkdir((path + "/a/d").c_str(), mode));
-
- // Remove "../a" recursively.
- ASSERT_EQ(0, dirUnlinkHierarchy((path + "/a").c_str()));
-
- // Verify it's gone.
- ASSERT_EQ(-1, access((path + "/a").c_str(), F_OK));
-}
diff --git a/tests/unit/rangeset_test.cpp b/tests/unit/rangeset_test.cpp
index 3c6d77e..7ae193e 100644
--- a/tests/unit/rangeset_test.cpp
+++ b/tests/unit/rangeset_test.cpp
@@ -17,11 +17,23 @@
#include <signal.h>
#include <sys/types.h>
+#include <limits>
#include <vector>
#include <gtest/gtest.h>
-#include "updater/rangeset.h"
+#include "otautil/rangeset.h"
+
+TEST(RangeSetTest, ctor) {
+ RangeSet rs(std::vector<Range>{ Range{ 8, 10 }, Range{ 1, 5 } });
+ ASSERT_TRUE(rs);
+
+ RangeSet rs2(std::vector<Range>{});
+ ASSERT_FALSE(rs2);
+
+ RangeSet rs3(std::vector<Range>{ Range{ 8, 10 }, Range{ 5, 1 } });
+ ASSERT_FALSE(rs3);
+}
TEST(RangeSetTest, Parse_smoke) {
RangeSet rs = RangeSet::Parse("2,1,10");
@@ -37,27 +49,64 @@
// Leading zeros are fine. But android::base::ParseUint() doesn't like trailing zeros like "10 ".
ASSERT_EQ(rs, RangeSet::Parse(" 2, 1, 10"));
- ASSERT_EXIT(RangeSet::Parse("2,1,10 "), ::testing::KilledBySignal(SIGABRT), "");
+ ASSERT_FALSE(RangeSet::Parse("2,1,10 "));
}
TEST(RangeSetTest, Parse_InvalidCases) {
// Insufficient number of tokens.
- ASSERT_EXIT(RangeSet::Parse(""), ::testing::KilledBySignal(SIGABRT), "");
- ASSERT_EXIT(RangeSet::Parse("2,1"), ::testing::KilledBySignal(SIGABRT), "");
+ ASSERT_FALSE(RangeSet::Parse(""));
+ ASSERT_FALSE(RangeSet::Parse("2,1"));
// The first token (i.e. the number of following tokens) is invalid.
- ASSERT_EXIT(RangeSet::Parse("a,1,1"), ::testing::KilledBySignal(SIGABRT), "");
- ASSERT_EXIT(RangeSet::Parse("3,1,1"), ::testing::KilledBySignal(SIGABRT), "");
- ASSERT_EXIT(RangeSet::Parse("-3,1,1"), ::testing::KilledBySignal(SIGABRT), "");
- ASSERT_EXIT(RangeSet::Parse("2,1,2,3"), ::testing::KilledBySignal(SIGABRT), "");
+ ASSERT_FALSE(RangeSet::Parse("a,1,1"));
+ ASSERT_FALSE(RangeSet::Parse("3,1,1"));
+ ASSERT_FALSE(RangeSet::Parse("-3,1,1"));
+ ASSERT_FALSE(RangeSet::Parse("2,1,2,3"));
// Invalid tokens.
- ASSERT_EXIT(RangeSet::Parse("2,1,10a"), ::testing::KilledBySignal(SIGABRT), "");
- ASSERT_EXIT(RangeSet::Parse("2,,10"), ::testing::KilledBySignal(SIGABRT), "");
+ ASSERT_FALSE(RangeSet::Parse("2,1,10a"));
+ ASSERT_FALSE(RangeSet::Parse("2,,10"));
// Empty or negative range.
- ASSERT_EXIT(RangeSet::Parse("2,2,2"), ::testing::KilledBySignal(SIGABRT), "");
- ASSERT_EXIT(RangeSet::Parse("2,2,1"), ::testing::KilledBySignal(SIGABRT), "");
+ ASSERT_FALSE(RangeSet::Parse("2,2,2"));
+ ASSERT_FALSE(RangeSet::Parse("2,2,1"));
+}
+
+TEST(RangeSetTest, Clear) {
+ RangeSet rs = RangeSet::Parse("2,1,6");
+ ASSERT_TRUE(rs);
+ rs.Clear();
+ ASSERT_FALSE(rs);
+
+ // No-op to clear an empty RangeSet.
+ rs.Clear();
+ ASSERT_FALSE(rs);
+}
+
+TEST(RangeSetTest, PushBack) {
+ RangeSet rs;
+ ASSERT_FALSE(rs);
+
+ ASSERT_TRUE(rs.PushBack({ 3, 5 }));
+ ASSERT_EQ(RangeSet::Parse("2,3,5"), rs);
+
+ ASSERT_TRUE(rs.PushBack({ 5, 15 }));
+ ASSERT_EQ(RangeSet::Parse("4,3,5,5,15"), rs);
+ ASSERT_EQ(static_cast<size_t>(2), rs.size());
+ ASSERT_EQ(static_cast<size_t>(12), rs.blocks());
+}
+
+TEST(RangeSetTest, PushBack_InvalidInput) {
+ RangeSet rs;
+ ASSERT_FALSE(rs);
+ ASSERT_FALSE(rs.PushBack({ 5, 3 }));
+ ASSERT_FALSE(rs);
+ ASSERT_FALSE(rs.PushBack({ 15, 15 }));
+ ASSERT_FALSE(rs);
+
+ ASSERT_TRUE(rs.PushBack({ 5, 15 }));
+ ASSERT_FALSE(rs.PushBack({ 5, std::numeric_limits<size_t>::max() - 2 }));
+ ASSERT_EQ(RangeSet::Parse("2,5,15"), rs);
}
TEST(RangeSetTest, Overlaps) {
@@ -74,6 +123,86 @@
ASSERT_FALSE(RangeSet::Parse("2,5,7").Overlaps(RangeSet::Parse("2,3,5")));
}
+TEST(RangeSetTest, Split) {
+ RangeSet rs1 = RangeSet::Parse("2,1,2");
+ ASSERT_TRUE(rs1);
+ ASSERT_EQ((std::vector<RangeSet>{ RangeSet::Parse("2,1,2") }), rs1.Split(1));
+
+ RangeSet rs2 = RangeSet::Parse("2,5,10");
+ ASSERT_TRUE(rs2);
+ ASSERT_EQ((std::vector<RangeSet>{ RangeSet::Parse("2,5,8"), RangeSet::Parse("2,8,10") }),
+ rs2.Split(2));
+
+ RangeSet rs3 = RangeSet::Parse("4,0,1,5,10");
+ ASSERT_TRUE(rs3);
+ ASSERT_EQ((std::vector<RangeSet>{ RangeSet::Parse("4,0,1,5,7"), RangeSet::Parse("2,7,10") }),
+ rs3.Split(2));
+
+ RangeSet rs4 = RangeSet::Parse("6,1,3,3,4,4,5");
+ ASSERT_TRUE(rs4);
+ ASSERT_EQ((std::vector<RangeSet>{ RangeSet::Parse("2,1,3"), RangeSet::Parse("2,3,4"),
+ RangeSet::Parse("2,4,5") }),
+ rs4.Split(3));
+
+ RangeSet rs5 = RangeSet::Parse("2,0,10");
+ ASSERT_TRUE(rs5);
+ ASSERT_EQ((std::vector<RangeSet>{ RangeSet::Parse("2,0,3"), RangeSet::Parse("2,3,6"),
+ RangeSet::Parse("2,6,8"), RangeSet::Parse("2,8,10") }),
+ rs5.Split(4));
+
+ RangeSet rs6 = RangeSet::Parse(
+ "20,0,268,269,271,286,447,8350,32770,33022,98306,98558,163842,164094,196609,204800,229378,"
+ "229630,294914,295166,457564");
+ ASSERT_TRUE(rs6);
+ size_t rs6_blocks = rs6.blocks();
+ auto splits = rs6.Split(4);
+ ASSERT_EQ(
+ (std::vector<RangeSet>{
+ RangeSet::Parse("12,0,268,269,271,286,447,8350,32770,33022,98306,98558,118472"),
+ RangeSet::Parse("8,118472,163842,164094,196609,204800,229378,229630,237216"),
+ RangeSet::Parse("4,237216,294914,295166,347516"), RangeSet::Parse("2,347516,457564") }),
+ splits);
+ size_t sum = 0;
+ for (const auto& element : splits) {
+ sum += element.blocks();
+ }
+ ASSERT_EQ(rs6_blocks, sum);
+}
+
+TEST(RangeSetTest, Split_EdgeCases) {
+ // Empty RangeSet.
+ RangeSet rs1;
+ ASSERT_FALSE(rs1);
+ ASSERT_EQ((std::vector<RangeSet>{}), rs1.Split(2));
+ ASSERT_FALSE(rs1);
+
+ // Zero group.
+ RangeSet rs2 = RangeSet::Parse("2,1,5");
+ ASSERT_TRUE(rs2);
+ ASSERT_EQ((std::vector<RangeSet>{}), rs2.Split(0));
+
+ // The number of blocks equals to the number of groups.
+ RangeSet rs3 = RangeSet::Parse("2,1,5");
+ ASSERT_TRUE(rs3);
+ ASSERT_EQ((std::vector<RangeSet>{ RangeSet::Parse("2,1,2"), RangeSet::Parse("2,2,3"),
+ RangeSet::Parse("2,3,4"), RangeSet::Parse("2,4,5") }),
+ rs3.Split(4));
+
+ // Less blocks than the number of groups.
+ RangeSet rs4 = RangeSet::Parse("2,1,5");
+ ASSERT_TRUE(rs4);
+ ASSERT_EQ((std::vector<RangeSet>{ RangeSet::Parse("2,1,2"), RangeSet::Parse("2,2,3"),
+ RangeSet::Parse("2,3,4"), RangeSet::Parse("2,4,5") }),
+ rs4.Split(8));
+
+ // Less blocks than the number of groups.
+ RangeSet rs5 = RangeSet::Parse("2,0,3");
+ ASSERT_TRUE(rs5);
+ ASSERT_EQ((std::vector<RangeSet>{ RangeSet::Parse("2,0,1"), RangeSet::Parse("2,1,2"),
+ RangeSet::Parse("2,2,3") }),
+ rs5.Split(4));
+}
+
TEST(RangeSetTest, GetBlockNumber) {
RangeSet rs = RangeSet::Parse("2,1,10");
ASSERT_EQ(static_cast<size_t>(1), rs.GetBlockNumber(0));
@@ -90,7 +219,7 @@
ASSERT_NE(RangeSet::Parse("2,1,6"), RangeSet::Parse("2,1,7"));
ASSERT_NE(RangeSet::Parse("2,1,6"), RangeSet::Parse("2,2,7"));
- // The orders of Range's matter. "4,1,5,8,10" != "4,8,10,1,5".
+ // The orders of Range's matter, e.g. "4,1,5,8,10" != "4,8,10,1,5".
ASSERT_NE(RangeSet::Parse("4,1,5,8,10"), RangeSet::Parse("4,8,10,1,5"));
}
@@ -110,3 +239,51 @@
}
ASSERT_EQ((std::vector<Range>{ Range{ 8, 10 }, Range{ 1, 5 } }), ranges);
}
+
+TEST(RangeSetTest, ToString) {
+ ASSERT_EQ("", RangeSet::Parse("").ToString());
+ ASSERT_EQ("2,1,6", RangeSet::Parse("2,1,6").ToString());
+ ASSERT_EQ("4,1,5,8,10", RangeSet::Parse("4,1,5,8,10").ToString());
+ ASSERT_EQ("6,1,3,4,6,15,22", RangeSet::Parse("6,1,3,4,6,15,22").ToString());
+}
+
+TEST(SortedRangeSetTest, Insert) {
+ SortedRangeSet rs({ { 2, 3 }, { 4, 6 }, { 8, 14 } });
+ rs.Insert({ 1, 2 });
+ ASSERT_EQ(SortedRangeSet({ { 1, 3 }, { 4, 6 }, { 8, 14 } }), rs);
+ ASSERT_EQ(static_cast<size_t>(10), rs.blocks());
+ rs.Insert({ 3, 5 });
+ ASSERT_EQ(SortedRangeSet({ { 1, 6 }, { 8, 14 } }), rs);
+ ASSERT_EQ(static_cast<size_t>(11), rs.blocks());
+
+ SortedRangeSet r1({ { 20, 22 }, { 15, 18 } });
+ rs.Insert(r1);
+ ASSERT_EQ(SortedRangeSet({ { 1, 6 }, { 8, 14 }, { 15, 18 }, { 20, 22 } }), rs);
+ ASSERT_EQ(static_cast<size_t>(16), rs.blocks());
+
+ SortedRangeSet r2({ { 2, 7 }, { 15, 21 }, { 20, 25 } });
+ rs.Insert(r2);
+ ASSERT_EQ(SortedRangeSet({ { 1, 7 }, { 8, 14 }, { 15, 25 } }), rs);
+ ASSERT_EQ(static_cast<size_t>(22), rs.blocks());
+}
+
+TEST(SortedRangeSetTest, file_range) {
+ SortedRangeSet rs;
+ rs.Insert(4096, 4096);
+ ASSERT_EQ(SortedRangeSet({ { 1, 2 } }), rs);
+ // insert block 2-9
+ rs.Insert(4096 * 3 - 1, 4096 * 7);
+ ASSERT_EQ(SortedRangeSet({ { 1, 10 } }), rs);
+ // insert block 15-19
+ rs.Insert(4096 * 15 + 1, 4096 * 4);
+ ASSERT_EQ(SortedRangeSet({ { 1, 10 }, { 15, 20 } }), rs);
+
+ // rs overlaps block 2-2
+ ASSERT_TRUE(rs.Overlaps(4096 * 2 - 1, 10));
+ ASSERT_FALSE(rs.Overlaps(4096 * 10, 4096 * 5));
+
+ ASSERT_EQ(static_cast<size_t>(10), rs.GetOffsetInRangeSet(4106));
+ ASSERT_EQ(static_cast<size_t>(40970), rs.GetOffsetInRangeSet(4096 * 16 + 10));
+ // block#10 not in range.
+ ASSERT_EXIT(rs.GetOffsetInRangeSet(40970), ::testing::KilledBySignal(SIGABRT), "");
+}
diff --git a/tools/recovery_l10n/Android.mk b/tools/recovery_l10n/Android.mk
index 937abd1..7197c5c 100644
--- a/tools/recovery_l10n/Android.mk
+++ b/tools/recovery_l10n/Android.mk
@@ -5,6 +5,7 @@
include $(CLEAR_VARS)
LOCAL_PACKAGE_NAME := RecoveryLocalizer
+LOCAL_SDK_VERSION := current
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tools/recovery_l10n/res/values-as/strings.xml b/tools/recovery_l10n/res/values-as/strings.xml
new file mode 100644
index 0000000..2624ceb
--- /dev/null
+++ b/tools/recovery_l10n/res/values-as/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="recovery_installing" msgid="2013591905463558223">"আপডেইট ইনষ্টল কৰি থকা হৈছে"</string>
+ <string name="recovery_erasing" msgid="7334826894904037088">"মচি থকা হৈছে"</string>
+ <string name="recovery_no_command" msgid="4465476568623024327">"কোনো আদেশ নাই"</string>
+ <string name="recovery_error" msgid="5748178989622716736">"ত্ৰুটি!"</string>
+ <string name="recovery_installing_security" msgid="9184031299717114342">"সুৰক্ষা আপডেইট ইনষ্টল কৰি থকা হৈছে"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-or/strings.xml b/tools/recovery_l10n/res/values-or/strings.xml
new file mode 100644
index 0000000..2b0851c
--- /dev/null
+++ b/tools/recovery_l10n/res/values-or/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="recovery_installing" msgid="2013591905463558223">"ସିଷ୍ଟମ ଅପଡେଟ ଇନଷ୍ଟଲ କରୁଛି"</string>
+ <string name="recovery_erasing" msgid="7334826894904037088">"ଲିଭାଉଛି"</string>
+ <string name="recovery_no_command" msgid="4465476568623024327">"କୌଣସି କମାଣ୍ଡ ନାହିଁ"</string>
+ <string name="recovery_error" msgid="5748178989622716736">"ତ୍ରୁଟି!"</string>
+ <string name="recovery_installing_security" msgid="9184031299717114342">"ସୁରକ୍ଷା ଅପ୍ଡେଟ୍ ଇନ୍ଷ୍ଟଲ୍ କରୁଛି"</string>
+</resources>
diff --git a/ui.cpp b/ui.cpp
index e80d7ed..3c9ded7 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -48,12 +48,16 @@
static constexpr int UI_WAIT_KEY_TIMEOUT_SEC = 120;
static constexpr const char* BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/brightness";
static constexpr const char* MAX_BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/max_brightness";
+static constexpr const char* BRIGHTNESS_FILE_SDM =
+ "/sys/class/backlight/panel0-backlight/brightness";
+static constexpr const char* MAX_BRIGHTNESS_FILE_SDM =
+ "/sys/class/backlight/panel0-backlight/max_brightness";
RecoveryUI::RecoveryUI()
- : locale_(""),
- rtl_locale_(false),
- brightness_normal_(50),
+ : brightness_normal_(50),
brightness_dimmed_(25),
+ brightness_file_(BRIGHTNESS_FILE),
+ max_brightness_file_(MAX_BRIGHTNESS_FILE),
touch_screen_allowed_(false),
kTouchLowThreshold(RECOVERY_UI_TOUCH_LOW_THRESHOLD),
kTouchHighThreshold(RECOVERY_UI_TOUCH_HIGH_THRESHOLD),
@@ -103,12 +107,17 @@
if (brightness_normal_ == 0 || brightness_dimmed_ > brightness_normal_) {
return false;
}
-
+ if (access(brightness_file_.c_str(), R_OK | W_OK)) {
+ brightness_file_ = BRIGHTNESS_FILE_SDM;
+ }
+ if (access(max_brightness_file_.c_str(), R_OK)) {
+ max_brightness_file_ = MAX_BRIGHTNESS_FILE_SDM;
+ }
// Set the initial brightness level based on the max brightness. Note that reading the initial
// value from BRIGHTNESS_FILE doesn't give the actual brightness value (bullhead, sailfish), so
// we don't have a good way to query the default value.
std::string content;
- if (!android::base::ReadFileToString(MAX_BRIGHTNESS_FILE, &content)) {
+ if (!android::base::ReadFileToString(max_brightness_file_, &content)) {
PLOG(WARNING) << "Failed to read max brightness";
return false;
}
@@ -122,7 +131,7 @@
brightness_normal_value_ = max_value * brightness_normal_ / 100.0;
brightness_dimmed_value_ = max_value * brightness_dimmed_ / 100.0;
if (!android::base::WriteStringToFile(std::to_string(brightness_normal_value_),
- BRIGHTNESS_FILE)) {
+ brightness_file_)) {
PLOG(WARNING) << "Failed to set brightness";
return false;
}
@@ -132,10 +141,7 @@
return true;
}
-bool RecoveryUI::Init(const std::string& locale) {
- // Set up the locale info.
- SetLocale(locale);
-
+bool RecoveryUI::Init(const std::string& /* locale */) {
ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2),
touch_screen_allowed_);
@@ -435,13 +441,13 @@
// Lower the brightness level: NORMAL -> DIMMED; DIMMED -> OFF.
if (screensaver_state_ == ScreensaverState::NORMAL) {
if (android::base::WriteStringToFile(std::to_string(brightness_dimmed_value_),
- BRIGHTNESS_FILE)) {
+ brightness_file_)) {
LOG(INFO) << "Brightness: " << brightness_dimmed_value_ << " (" << brightness_dimmed_
<< "%)";
screensaver_state_ = ScreensaverState::DIMMED;
}
} else if (screensaver_state_ == ScreensaverState::DIMMED) {
- if (android::base::WriteStringToFile("0", BRIGHTNESS_FILE)) {
+ if (android::base::WriteStringToFile("0", brightness_file_)) {
LOG(INFO) << "Brightness: 0 (off)";
screensaver_state_ = ScreensaverState::OFF;
}
@@ -456,7 +462,7 @@
// Reset the brightness to normal.
if (android::base::WriteStringToFile(std::to_string(brightness_normal_value_),
- BRIGHTNESS_FILE)) {
+ brightness_file_)) {
screensaver_state_ = ScreensaverState::NORMAL;
LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_
<< "%)";
@@ -574,23 +580,3 @@
enable_reboot = enabled;
pthread_mutex_unlock(&key_queue_mutex);
}
-
-void RecoveryUI::SetLocale(const std::string& new_locale) {
- this->locale_ = new_locale;
- this->rtl_locale_ = false;
-
- if (!new_locale.empty()) {
- size_t underscore = new_locale.find('_');
- // lang has the language prefix prior to '_', or full string if '_' doesn't exist.
- std::string lang = new_locale.substr(0, underscore);
-
- // A bit cheesy: keep an explicit list of supported RTL languages.
- if (lang == "ar" || // Arabic
- lang == "fa" || // Persian (Farsi)
- lang == "he" || // Hebrew (new language code)
- lang == "iw" || // Hebrew (old language code)
- lang == "ur") { // Urdu
- rtl_locale_ = true;
- }
- }
-}
diff --git a/ui.h b/ui.h
index 3d9afec..4c54d69 100644
--- a/ui.h
+++ b/ui.h
@@ -26,6 +26,27 @@
// Abstract class for controlling the user interface during recovery.
class RecoveryUI {
public:
+ enum Icon {
+ NONE,
+ INSTALLING_UPDATE,
+ ERASING,
+ NO_COMMAND,
+ ERROR
+ };
+
+ enum ProgressType {
+ EMPTY,
+ INDETERMINATE,
+ DETERMINATE
+ };
+
+ enum KeyAction {
+ ENQUEUE,
+ TOGGLE,
+ REBOOT,
+ IGNORE
+ };
+
RecoveryUI();
virtual ~RecoveryUI() {}
@@ -38,12 +59,10 @@
virtual void SetStage(int current, int max) = 0;
// Sets the overall recovery state ("background image").
- enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR };
virtual void SetBackground(Icon icon) = 0;
virtual void SetSystemUpdateText(bool security_update) = 0;
// --- progress indicator ---
- enum ProgressType { EMPTY, INDETERMINATE, DETERMINATE };
virtual void SetProgressType(ProgressType determinate) = 0;
// Shows a progress bar and define the scope of the next operation:
@@ -94,7 +113,6 @@
// Called on each key press, even while operations are in progress. Return value indicates whether
// an immediate operation should be triggered (toggling the display, rebooting the device), or if
// the key should be enqueued for use by the main thread.
- enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE };
virtual KeyAction CheckKey(int key, bool is_long_press);
// Called when a key is held down long enough to have been a long-press (but before the key is
@@ -125,24 +143,47 @@
protected:
void EnqueueKey(int key_code);
- // The locale that's used to show the rendered texts.
- std::string locale_;
- bool rtl_locale_;
-
// The normal and dimmed brightness percentages (default: 50 and 25, which means 50% and 25% of
// the max_brightness). Because the absolute values may vary across devices. These two values can
// be configured via subclassing. Setting brightness_normal_ to 0 to disable screensaver.
unsigned int brightness_normal_;
unsigned int brightness_dimmed_;
+ std::string brightness_file_;
+ std::string max_brightness_file_;
// Whether we should listen for touch inputs (default: false).
bool touch_screen_allowed_;
private:
+ enum class ScreensaverState {
+ DISABLED,
+ NORMAL,
+ DIMMED,
+ OFF
+ };
+
+ struct key_timer_t {
+ RecoveryUI* ui;
+ int key_code;
+ int count;
+ };
+
// The sensitivity when detecting a swipe.
const int kTouchLowThreshold;
const int kTouchHighThreshold;
+ void OnKeyDetected(int key_code);
+ void OnTouchDetected(int dx, int dy);
+ int OnInputEvent(int fd, uint32_t epevents);
+ void ProcessKey(int key_code, int updown);
+
+ bool IsUsbConnected();
+
+ static void* time_key_helper(void* cookie);
+ void time_key(int key_code, int count);
+
+ bool InitScreensaver();
+
// Key event input queue
pthread_mutex_t key_queue_mutex;
pthread_cond_t key_queue_cond;
@@ -172,33 +213,14 @@
bool touch_swiping_;
bool is_bootreason_recovery_ui_;
- struct key_timer_t {
- RecoveryUI* ui;
- int key_code;
- int count;
- };
-
pthread_t input_thread_;
- void OnKeyDetected(int key_code);
- void OnTouchDetected(int dx, int dy);
- int OnInputEvent(int fd, uint32_t epevents);
- void ProcessKey(int key_code, int updown);
-
- bool IsUsbConnected();
-
- static void* time_key_helper(void* cookie);
- void time_key(int key_code, int count);
-
- void SetLocale(const std::string&);
-
- enum class ScreensaverState { DISABLED, NORMAL, DIMMED, OFF };
ScreensaverState screensaver_state_;
+
// The following two contain the absolute values computed from brightness_normal_ and
// brightness_dimmed_ respectively.
unsigned int brightness_normal_value_;
unsigned int brightness_dimmed_value_;
- bool InitScreensaver();
};
#endif // RECOVERY_UI_H
diff --git a/uncrypt/Android.bp b/uncrypt/Android.bp
new file mode 100644
index 0000000..aa56d2f
--- /dev/null
+++ b/uncrypt/Android.bp
@@ -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.
+
+cc_binary {
+ name: "uncrypt",
+
+ srcs: [
+ "uncrypt.cpp",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ static_libs: [
+ "libbootloader_message",
+ "libotautil",
+ "libfs_mgr",
+ "libbase",
+ "libcutils",
+ "liblog",
+ ],
+
+ init_rc: [
+ "uncrypt.rc",
+ ],
+}
diff --git a/uncrypt/Android.mk b/uncrypt/Android.mk
deleted file mode 100644
index 59084b0..0000000
--- a/uncrypt/Android.mk
+++ /dev/null
@@ -1,32 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_CLANG := true
-LOCAL_SRC_FILES := uncrypt.cpp
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/..
-LOCAL_MODULE := uncrypt
-LOCAL_STATIC_LIBRARIES := \
- libbootloader_message \
- libbase \
- liblog \
- libfs_mgr \
- libcutils
-LOCAL_CFLAGS := -Werror
-LOCAL_INIT_RC := uncrypt.rc
-
-include $(BUILD_EXECUTABLE)
diff --git a/uncrypt/uncrypt.cpp b/uncrypt/uncrypt.cpp
index ad3bdce..bb43c2c 100644
--- a/uncrypt/uncrypt.cpp
+++ b/uncrypt/uncrypt.cpp
@@ -116,7 +116,7 @@
#include <cutils/sockets.h>
#include <fs_mgr.h>
-#include "error_code.h"
+#include "otautil/error_code.h"
static constexpr int WINDOW_SIZE = 5;
static constexpr int FIBMAP_RETRY_LIMIT = 3;
@@ -172,10 +172,14 @@
return fstab;
}
-static const char* find_block_device(const char* path, bool* encryptable, bool* encrypted) {
+static const char* find_block_device(const char* path, bool* encryptable, bool* encrypted, bool *f2fs_fs) {
// Look for a volume whose mount point is the prefix of path and
// return its block device. Set encrypted if it's currently
// encrypted.
+
+ // ensure f2fs_fs is set to 0 first.
+ if (f2fs_fs)
+ *f2fs_fs = false;
for (int i = 0; i < fstab->num_entries; ++i) {
struct fstab_rec* v = &fstab->recs[i];
if (!v->mount_point) {
@@ -192,6 +196,8 @@
*encrypted = true;
}
}
+ if (f2fs_fs && strcmp(v->fs_type, "f2fs") == 0)
+ *f2fs_fs = true;
return v->blk_device;
}
}
@@ -244,7 +250,7 @@
}
static int produce_block_map(const char* path, const char* map_file, const char* blk_dev,
- bool encrypted, int socket) {
+ bool encrypted, bool f2fs_fs, int socket) {
std::string err;
if (!android::base::RemoveFileIfExists(map_file, &err)) {
LOG(ERROR) << "failed to remove the existing map file " << map_file << ": " << err;
@@ -307,6 +313,17 @@
}
}
+#ifndef F2FS_IOC_SET_DONTMOVE
+#ifndef F2FS_IOCTL_MAGIC
+#define F2FS_IOCTL_MAGIC 0xf5
+#endif
+#define F2FS_IOC_SET_DONTMOVE _IO(F2FS_IOCTL_MAGIC, 13)
+#endif
+ if (f2fs_fs && ioctl(fd, F2FS_IOC_SET_DONTMOVE) < 0) {
+ PLOG(ERROR) << "Failed to set non-movable file for f2fs: " << path << " on " << blk_dev;
+ return kUncryptIoctlError;
+ }
+
off64_t pos = 0;
int last_progress = 0;
while (pos < sb.st_size) {
@@ -448,20 +465,21 @@
static int uncrypt(const char* input_path, const char* map_file, const int socket) {
LOG(INFO) << "update package is \"" << input_path << "\"";
- // Turn the name of the file we're supposed to convert into an
- // absolute path, so we can find what filesystem it's on.
+ // Turn the name of the file we're supposed to convert into an absolute path, so we can find
+ // what filesystem it's on.
char path[PATH_MAX+1];
- if (realpath(input_path, path) == NULL) {
+ if (realpath(input_path, path) == nullptr) {
PLOG(ERROR) << "failed to convert \"" << input_path << "\" to absolute path";
- return 1;
+ return kUncryptRealpathFindError;
}
bool encryptable;
bool encrypted;
- const char* blk_dev = find_block_device(path, &encryptable, &encrypted);
- if (blk_dev == NULL) {
+ bool f2fs_fs;
+ const char* blk_dev = find_block_device(path, &encryptable, &encrypted, &f2fs_fs);
+ if (blk_dev == nullptr) {
LOG(ERROR) << "failed to find block device for " << path;
- return 1;
+ return kUncryptBlockDeviceFindError;
}
// If the filesystem it's on isn't encrypted, we only produce the
@@ -479,7 +497,7 @@
// and /sdcard we leave the file alone.
if (strncmp(path, "/data/", 6) == 0) {
LOG(INFO) << "writing block map " << map_file;
- return produce_block_map(path, map_file, blk_dev, encrypted, socket);
+ return produce_block_map(path, map_file, blk_dev, encrypted, f2fs_fs, socket);
}
return 0;
diff --git a/update_verifier/Android.mk b/update_verifier/Android.mk
index 33c5fe9..0ff8854 100644
--- a/update_verifier/Android.mk
+++ b/update_verifier/Android.mk
@@ -22,6 +22,10 @@
update_verifier.cpp
LOCAL_MODULE := libupdate_verifier
+
+LOCAL_STATIC_LIBRARIES := \
+ libotautil
+
LOCAL_SHARED_LIBRARIES := \
libbase \
libcutils \
@@ -54,7 +58,9 @@
LOCAL_MODULE := update_verifier
LOCAL_STATIC_LIBRARIES := \
- libupdate_verifier
+ libupdate_verifier \
+ libotautil
+
LOCAL_SHARED_LIBRARIES := \
libbase \
libcutils \
diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp
index ba7b7ae..92d9313 100644
--- a/update_verifier/update_verifier.cpp
+++ b/update_verifier/update_verifier.cpp
@@ -58,6 +58,8 @@
#include <android/hardware/boot/1.0/IBootControl.h>
#include <cutils/android_reboot.h>
+#include "otautil/rangeset.h"
+
using android::sp;
using android::hardware::boot::V1_0::IBootControl;
using android::hardware::boot::V1_0::BoolResult;
@@ -72,14 +74,13 @@
}
static bool read_blocks(const std::string& partition, const std::string& range_str) {
- if (partition != "system" && partition != "vendor") {
- LOG(ERROR) << "partition name must be system or vendor: " << partition;
+ if (partition != "system" && partition != "vendor" && partition != "product") {
+ LOG(ERROR) << "Invalid partition name \"" << partition << "\"";
return false;
}
- // Iterate the content of "/sys/block/dm-X/dm/name". If it matches "system"
- // (or "vendor"), then dm-X is a dm-wrapped system/vendor partition.
- // Afterwards, update_verifier will read every block on the care_map_file of
- // "/dev/block/dm-X" to ensure the partition's integrity.
+ // Iterate the content of "/sys/block/dm-X/dm/name". If it matches one of "system", "vendor" or
+ // "product", then dm-X is a dm-wrapped device for that target. We will later read all the
+ // ("cared") blocks from "/dev/block/dm-X" to ensure the target partition's integrity.
static constexpr auto DM_PATH_PREFIX = "/sys/block/";
dirent** namelist;
int n = scandir(DM_PATH_PREFIX, &namelist, dm_name_filter, alphasort);
@@ -129,42 +130,33 @@
// followed by 'count' number comma separated integers. Every two integers reprensent a
// block range with the first number included in range but second number not included.
// For example '4,64536,65343,74149,74150' represents: [64536,65343) and [74149,74150).
- std::vector<std::string> ranges = android::base::Split(range_str, ",");
- size_t range_count;
- bool status = android::base::ParseUint(ranges[0], &range_count);
- if (!status || (range_count == 0) || (range_count % 2 != 0) ||
- (range_count != ranges.size() - 1)) {
- LOG(ERROR) << "Error in parsing range string.";
+ RangeSet ranges = RangeSet::Parse(range_str);
+ if (!ranges) {
+ LOG(ERROR) << "Error parsing RangeSet string " << range_str;
return false;
}
- range_count /= 2;
+
+ // RangeSet::Split() splits the ranges into multiple groups with same number of blocks (except for
+ // the last group).
+ size_t thread_num = std::thread::hardware_concurrency() ?: 4;
+ std::vector<RangeSet> groups = ranges.Split(thread_num);
std::vector<std::future<bool>> threads;
- size_t thread_num = std::thread::hardware_concurrency() ?: 4;
- thread_num = std::min(thread_num, range_count);
- size_t group_range_count = (range_count + thread_num - 1) / thread_num;
-
- for (size_t t = 0; t < thread_num; t++) {
- auto thread_func = [t, group_range_count, &dm_block_device, &ranges, &partition]() {
- size_t blk_count = 0;
- static constexpr size_t kBlockSize = 4096;
- std::vector<uint8_t> buf(1024 * kBlockSize);
+ for (const auto& group : groups) {
+ auto thread_func = [&group, &dm_block_device, &partition]() {
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
if (fd.get() == -1) {
PLOG(ERROR) << "Error reading " << dm_block_device << " for partition " << partition;
return false;
}
- for (size_t i = group_range_count * 2 * t + 1;
- i < std::min(group_range_count * 2 * (t + 1) + 1, ranges.size()); i += 2) {
- unsigned int range_start, range_end;
- bool parse_status = android::base::ParseUint(ranges[i], &range_start);
- parse_status = parse_status && android::base::ParseUint(ranges[i + 1], &range_end);
- if (!parse_status || range_start >= range_end) {
- LOG(ERROR) << "Invalid range pair " << ranges[i] << ", " << ranges[i + 1];
- return false;
- }
+ static constexpr size_t kBlockSize = 4096;
+ std::vector<uint8_t> buf(1024 * kBlockSize);
+ size_t block_count = 0;
+ for (const auto& range : group) {
+ size_t range_start = range.first;
+ size_t range_end = range.second;
if (lseek64(fd.get(), static_cast<off64_t>(range_start) * kBlockSize, SEEK_SET) == -1) {
PLOG(ERROR) << "lseek to " << range_start << " failed";
return false;
@@ -179,9 +171,9 @@
}
remain -= to_read;
}
- blk_count += (range_end - range_start);
+ block_count += (range_end - range_start);
}
- LOG(INFO) << "Finished reading " << blk_count << " blocks on " << dm_block_device;
+ LOG(INFO) << "Finished reading " << block_count << " blocks on " << dm_block_device;
return true;
};
@@ -213,10 +205,9 @@
PLOG(WARNING) << "Failed to open " << care_map_name;
return true;
}
- // Care map file has four lines (two lines if vendor partition is not present):
- // First line has the block partition name (system/vendor).
- // Second line holds all ranges of blocks to verify.
- // The next two lines have the same format but for vendor partition.
+ // care_map file has up to six lines, where every two lines make a pair. Within each pair, the
+ // first line has the partition name (e.g. "system"), while the second line holds the ranges of
+ // all the blocks to verify.
std::string file_content;
if (!android::base::ReadFdToString(care_map_fd.get(), &file_content)) {
LOG(ERROR) << "Error reading care map contents to string.";
@@ -225,9 +216,9 @@
std::vector<std::string> lines;
lines = android::base::Split(android::base::Trim(file_content), "\n");
- if (lines.size() != 2 && lines.size() != 4) {
+ if (lines.size() != 2 && lines.size() != 4 && lines.size() != 6) {
LOG(ERROR) << "Invalid lines in care_map: found " << lines.size()
- << " lines, expecting 2 or 4 lines.";
+ << " lines, expecting 2 or 4 or 6 lines.";
return false;
}
diff --git a/update_verifier/update_verifier_main.cpp b/update_verifier/update_verifier_main.cpp
index 46e8bbb..a86203b 100644
--- a/update_verifier/update_verifier_main.cpp
+++ b/update_verifier/update_verifier_main.cpp
@@ -16,8 +16,23 @@
// See the comments in update_verifier.cpp.
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+
#include "update_verifier/update_verifier.h"
int main(int argc, char** argv) {
+ std::string s = android::base::GetProperty("ro.boot.slot_suffix", "");
+
+ if (s.empty()) {
+ return 0; // non-A/B update device, so we quit
+ }
+
+ // Set up update_verifier logging to be written to kmsg; because we may not have Logd during
+ // boot time.
+ android::base::InitLogging(argv, &android::base::KernelLogger);
+
return update_verifier(argc, argv);
}
diff --git a/updater/Android.mk b/updater/Android.mk
index 86dc48e..6f334ee 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -66,7 +66,7 @@
external/e2fsprogs/misc
LOCAL_CFLAGS := \
- -Wno-unused-parameter \
+ -Wall \
-Werror
LOCAL_EXPORT_C_INCLUDE_DIRS := \
@@ -91,7 +91,7 @@
$(LOCAL_PATH)/include
LOCAL_CFLAGS := \
- -Wno-unused-parameter \
+ -Wall \
-Werror
LOCAL_STATIC_LIBRARIES := \
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index a0b9ad2..e93196b 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -34,11 +34,13 @@
#include <fec/io.h>
#include <functional>
+#include <limits>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
+#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
@@ -50,11 +52,12 @@
#include <ziparchive/zip_archive.h>
#include "edify/expr.h"
-#include "error_code.h"
-#include "ota_io.h"
-#include "print_sha1.h"
+#include "otafault/ota_io.h"
+#include "otautil/cache_location.h"
+#include "otautil/error_code.h"
+#include "otautil/print_sha1.h"
+#include "otautil/rangeset.h"
#include "updater/install.h"
-#include "updater/rangeset.h"
#include "updater/updater.h"
// Set this to 0 to interpret 'erase' transfers to mean do a
@@ -63,7 +66,6 @@
#define DEBUG_ERASE 0
static constexpr size_t BLOCKSIZE = 4096;
-static constexpr const char* STASH_DIRECTORY_BASE = "/cache/recovery";
static constexpr mode_t STASH_DIRECTORY_MODE = 0700;
static constexpr mode_t STASH_FILE_MODE = 0600;
@@ -71,6 +73,93 @@
static bool is_retry = false;
static std::unordered_map<std::string, RangeSet> stash_map;
+static void DeleteLastCommandFile() {
+ std::string last_command_file = CacheLocation::location().last_command_file();
+ if (unlink(last_command_file.c_str()) == -1 && errno != ENOENT) {
+ PLOG(ERROR) << "Failed to unlink: " << last_command_file;
+ }
+}
+
+// Parse the last command index of the last update and save the result to |last_command_index|.
+// Return true if we successfully read the index.
+static bool ParseLastCommandFile(int* last_command_index) {
+ std::string last_command_file = CacheLocation::location().last_command_file();
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(last_command_file.c_str(), O_RDONLY)));
+ if (fd == -1) {
+ if (errno != ENOENT) {
+ PLOG(ERROR) << "Failed to open " << last_command_file;
+ return false;
+ }
+
+ LOG(INFO) << last_command_file << " doesn't exist.";
+ return false;
+ }
+
+ // Now that the last_command file exists, parse the last command index of previous update.
+ std::string content;
+ if (!android::base::ReadFdToString(fd.get(), &content)) {
+ LOG(ERROR) << "Failed to read: " << last_command_file;
+ return false;
+ }
+
+ std::vector<std::string> lines = android::base::Split(android::base::Trim(content), "\n");
+ if (lines.size() != 2) {
+ LOG(ERROR) << "Unexpected line counts in last command file: " << content;
+ return false;
+ }
+
+ if (!android::base::ParseInt(lines[0], last_command_index)) {
+ LOG(ERROR) << "Failed to parse integer in: " << lines[0];
+ return false;
+ }
+
+ return true;
+}
+
+// Update the last command index in the last_command_file if the current command writes to the
+// stash either explicitly or implicitly.
+static bool UpdateLastCommandIndex(int command_index, const std::string& command_string) {
+ std::string last_command_file = CacheLocation::location().last_command_file();
+ std::string last_command_tmp = last_command_file + ".tmp";
+ std::string content = std::to_string(command_index) + "\n" + command_string;
+ android::base::unique_fd wfd(
+ TEMP_FAILURE_RETRY(open(last_command_tmp.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0660)));
+ if (wfd == -1 || !android::base::WriteStringToFd(content, wfd)) {
+ PLOG(ERROR) << "Failed to update last command";
+ return false;
+ }
+
+ if (fsync(wfd) == -1) {
+ PLOG(ERROR) << "Failed to fsync " << last_command_tmp;
+ return false;
+ }
+
+ if (chown(last_command_tmp.c_str(), AID_SYSTEM, AID_SYSTEM) == -1) {
+ PLOG(ERROR) << "Failed to change owner for " << last_command_tmp;
+ return false;
+ }
+
+ if (rename(last_command_tmp.c_str(), last_command_file.c_str()) == -1) {
+ PLOG(ERROR) << "Failed to rename" << last_command_tmp;
+ return false;
+ }
+
+ std::string last_command_dir = android::base::Dirname(last_command_file);
+ android::base::unique_fd dfd(
+ TEMP_FAILURE_RETRY(ota_open(last_command_dir.c_str(), O_RDONLY | O_DIRECTORY)));
+ if (dfd == -1) {
+ PLOG(ERROR) << "Failed to open " << last_command_dir;
+ return false;
+ }
+
+ if (fsync(dfd) == -1) {
+ PLOG(ERROR) << "Failed to fsync " << last_command_dir;
+ return false;
+ }
+
+ return true;
+}
+
static int read_all(int fd, uint8_t* data, size_t size) {
size_t so_far = 0;
while (so_far < size) {
@@ -281,6 +370,11 @@
// Wait for nti->writer to be non-null, indicating some of this data is wanted.
pthread_mutex_lock(&nti->mu);
while (nti->writer == nullptr) {
+ // End the new data receiver if we encounter an error when performing block image update.
+ if (!nti->receiver_available) {
+ pthread_mutex_unlock(&nti->mu);
+ return false;
+ }
pthread_cond_wait(&nti->cv, &nti->mu);
}
pthread_mutex_unlock(&nti->mu);
@@ -316,6 +410,11 @@
// Wait for nti->writer to be non-null, indicating some of this data is wanted.
pthread_mutex_lock(&nti->mu);
while (nti->writer == nullptr) {
+ // End the receiver if we encounter an error when performing block image update.
+ if (!nti->receiver_available) {
+ pthread_mutex_unlock(&nti->mu);
+ return false;
+ }
pthread_cond_wait(&nti->cv, &nti->mu);
}
pthread_mutex_unlock(&nti->mu);
@@ -429,6 +528,7 @@
struct CommandParameters {
std::vector<std::string> tokens;
size_t cpos;
+ int cmdindex;
const char* cmdname;
const char* cmdline;
std::string freestash;
@@ -445,6 +545,7 @@
pthread_t thread;
std::vector<uint8_t> buffer;
uint8_t* patch_start;
+ bool target_verified; // The target blocks have expected contents already.
};
// Print the hash in hex for corrupted source blocks (excluding the stashed blocks which is
@@ -482,6 +583,10 @@
}
RangeSet src = RangeSet::Parse(params.tokens[pos++]);
+ if (!src) {
+ LOG(ERROR) << "Failed to parse range in " << params.cmdline;
+ return;
+ }
RangeSet locs;
// If there's no stashed blocks, content in the buffer is consecutive and has the same
@@ -572,7 +677,7 @@
return "";
}
- std::string fn(STASH_DIRECTORY_BASE);
+ std::string fn(CacheLocation::location().stash_directory_base());
fn += "/" + base + "/" + id + postfix;
return fn;
@@ -803,7 +908,7 @@
size_t max_stash_size = maxblocks * BLOCKSIZE;
if (res == -1 && errno != ENOENT) {
- ErrorAbort(state, kStashCreationFailure, "stat \"%s\" failed: %s\n", dirname.c_str(),
+ ErrorAbort(state, kStashCreationFailure, "stat \"%s\" failed: %s", dirname.c_str(),
strerror(errno));
return -1;
} else if (res != 0) {
@@ -811,19 +916,19 @@
res = mkdir(dirname.c_str(), STASH_DIRECTORY_MODE);
if (res != 0) {
- ErrorAbort(state, kStashCreationFailure, "mkdir \"%s\" failed: %s\n", dirname.c_str(),
+ ErrorAbort(state, kStashCreationFailure, "mkdir \"%s\" failed: %s", dirname.c_str(),
strerror(errno));
return -1;
}
if (chown(dirname.c_str(), AID_SYSTEM, AID_SYSTEM) != 0) { // system user
- ErrorAbort(state, kStashCreationFailure, "chown \"%s\" failed: %s\n", dirname.c_str(),
+ ErrorAbort(state, kStashCreationFailure, "chown \"%s\" failed: %s", dirname.c_str(),
strerror(errno));
return -1;
}
if (CacheSizeCheck(max_stash_size) != 0) {
- ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu needed)\n",
+ ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu needed)",
max_stash_size);
return -1;
}
@@ -855,7 +960,7 @@
if (max_stash_size > existing) {
size_t needed = max_stash_size - existing;
if (CacheSizeCheck(needed) != 0) {
- ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu more needed)\n",
+ ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu more needed)",
needed);
return -1;
}
@@ -926,6 +1031,7 @@
params.cpos++;
} else {
RangeSet src = RangeSet::Parse(params.tokens[params.cpos++]);
+ CHECK(static_cast<bool>(src));
*overlap = src.Overlaps(tgt);
if (ReadBlocks(src, params.buffer, params.fd) == -1) {
@@ -938,6 +1044,7 @@
}
RangeSet locs = RangeSet::Parse(params.tokens[params.cpos++]);
+ CHECK(static_cast<bool>(locs));
MoveRange(params.buffer, locs, params.buffer);
}
@@ -960,6 +1067,7 @@
}
RangeSet locs = RangeSet::Parse(tokens[1]);
+ CHECK(static_cast<bool>(locs));
MoveRange(params.buffer, locs, stash);
}
@@ -1024,6 +1132,7 @@
// <tgt_range>
tgt = RangeSet::Parse(params.tokens[params.cpos++]);
+ CHECK(static_cast<bool>(tgt));
std::vector<uint8_t> tgtbuffer(tgt.blocks() * BLOCKSIZE);
if (ReadBlocks(tgt, tgtbuffer, params.fd) == -1) {
@@ -1054,6 +1163,10 @@
return -1;
}
+ if (!UpdateLastCommandIndex(params.cmdindex, params.cmdline)) {
+ LOG(WARNING) << "Failed to update the last command file.";
+ }
+
params.stashed += *src_blocks;
// Can be deleted when the write has completed.
if (!stash_exists) {
@@ -1094,8 +1207,11 @@
if (status == 0) {
params.foundwrites = true;
- } else if (params.foundwrites) {
- LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]";
+ } else {
+ params.target_verified = true;
+ if (params.foundwrites) {
+ LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]";
+ }
}
if (params.canwrite) {
@@ -1136,6 +1252,7 @@
}
RangeSet src = RangeSet::Parse(params.tokens[params.cpos++]);
+ CHECK(static_cast<bool>(src));
allocate(src.blocks() * BLOCKSIZE, params.buffer);
if (ReadBlocks(src, params.buffer, params.fd) == -1) {
@@ -1158,8 +1275,15 @@
}
LOG(INFO) << "stashing " << blocks << " blocks to " << id;
- params.stashed += blocks;
- return WriteStash(params.stashbase, id, blocks, params.buffer, false, nullptr);
+ int result = WriteStash(params.stashbase, id, blocks, params.buffer, false, nullptr);
+ if (result == 0) {
+ if (!UpdateLastCommandIndex(params.cmdindex, params.cmdline)) {
+ LOG(WARNING) << "Failed to update the last command file.";
+ }
+
+ params.stashed += blocks;
+ }
+ return result;
}
static int PerformCommandFree(CommandParameters& params) {
@@ -1186,6 +1310,7 @@
}
RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]);
+ CHECK(static_cast<bool>(tgt));
LOG(INFO) << " zeroing " << tgt.blocks() << " blocks";
@@ -1228,6 +1353,7 @@
}
RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]);
+ CHECK(static_cast<bool>(tgt));
if (params.canwrite) {
LOG(INFO) << " writing " << tgt.blocks() << " blocks of new data";
@@ -1285,8 +1411,11 @@
if (status == 0) {
params.foundwrites = true;
- } else if (params.foundwrites) {
- LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]";
+ } else {
+ params.target_verified = true;
+ if (params.foundwrites) {
+ LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]";
+ }
}
if (params.canwrite) {
@@ -1297,7 +1426,7 @@
RangeSinkWriter writer(params.fd, tgt);
if (params.cmdname[0] == 'i') { // imgdiff
- if (ApplyImagePatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value,
+ if (ApplyImagePatch(params.buffer.data(), blocks * BLOCKSIZE, patch_value,
std::bind(&RangeSinkWriter::Write, &writer, std::placeholders::_1,
std::placeholders::_2),
nullptr, nullptr) != 0) {
@@ -1306,7 +1435,7 @@
return -1;
}
} else {
- if (ApplyBSDiffPatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value, 0,
+ if (ApplyBSDiffPatch(params.buffer.data(), blocks * BLOCKSIZE, patch_value, 0,
std::bind(&RangeSinkWriter::Write, &writer, std::placeholders::_1,
std::placeholders::_2),
nullptr) != 0) {
@@ -1358,6 +1487,7 @@
}
RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]);
+ CHECK(static_cast<bool>(tgt));
if (params.canwrite) {
LOG(INFO) << " erasing " << tgt.blocks() << " blocks";
@@ -1495,7 +1625,7 @@
std::vector<std::string> lines = android::base::Split(transfer_list_value->data, "\n");
if (lines.size() < 2) {
- ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zd]\n",
+ ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zd]",
lines.size());
return StringValue("");
}
@@ -1511,7 +1641,7 @@
// Second line in transfer list is the total number of blocks we expect to write.
size_t total_blocks;
if (!android::base::ParseUint(lines[1], &total_blocks)) {
- ErrorAbort(state, kArgsParsingFailure, "unexpected block count [%s]\n", lines[1].c_str());
+ ErrorAbort(state, kArgsParsingFailure, "unexpected block count [%s]", lines[1].c_str());
return StringValue("");
}
@@ -1521,7 +1651,7 @@
size_t start = 2;
if (lines.size() < 4) {
- ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]\n",
+ ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]",
lines.size());
return StringValue("");
}
@@ -1532,7 +1662,7 @@
// Fourth line is the maximum number of blocks that will be stashed simultaneously
size_t stash_max_blocks;
if (!android::base::ParseUint(lines[3], &stash_max_blocks)) {
- ErrorAbort(state, kArgsParsingFailure, "unexpected maximum stash blocks [%s]\n",
+ ErrorAbort(state, kArgsParsingFailure, "unexpected maximum stash blocks [%s]",
lines[3].c_str());
return StringValue("");
}
@@ -1544,6 +1674,23 @@
params.createdstash = res;
+ // When performing an update, save the index and cmdline of the current command into
+ // the last_command_file if this command writes to the stash either explicitly of implicitly.
+ // Upon resuming an update, read the saved index first; then
+ // 1. In verification mode, check if the 'move' or 'diff' commands before the saved index has
+ // the expected target blocks already. If not, these commands cannot be skipped and we need
+ // to attempt to execute them again. Therefore, we will delete the last_command_file so that
+ // the update will resume from the start of the transfer list.
+ // 2. In update mode, skip all commands before the saved index. Therefore, we can avoid deleting
+ // stashes with duplicate id unintentionally (b/69858743); and also speed up the update.
+ // If an update succeeds or is unresumable, delete the last_command_file.
+ int saved_last_command_index;
+ if (!ParseLastCommandFile(&saved_last_command_index)) {
+ DeleteLastCommandFile();
+ // We failed to parse the last command, set it explicitly to -1.
+ saved_last_command_index = -1;
+ }
+
start += 2;
// Build a map of the available commands
@@ -1559,14 +1706,20 @@
int rc = -1;
// Subsequent lines are all individual transfer commands
- for (auto it = lines.cbegin() + start; it != lines.cend(); it++) {
- const std::string& line(*it);
+ for (size_t i = start; i < lines.size(); i++) {
+ const std::string& line = lines[i];
if (line.empty()) continue;
params.tokens = android::base::Split(line, " ");
params.cpos = 0;
+ if (i - start > std::numeric_limits<int>::max()) {
+ params.cmdindex = -1;
+ } else {
+ params.cmdindex = i - start;
+ }
params.cmdname = params.tokens[params.cpos++].c_str();
params.cmdline = line.c_str();
+ params.target_verified = false;
if (cmd_map.find(params.cmdname) == cmd_map.end()) {
LOG(ERROR) << "unexpected command [" << params.cmdname << "]";
@@ -1575,11 +1728,40 @@
const Command* cmd = cmd_map[params.cmdname];
- if (cmd->f != nullptr && cmd->f(params) == -1) {
+ // Skip the command if we explicitly set the corresponding function pointer to nullptr, e.g.
+ // "erase" during block_image_verify.
+ if (cmd->f == nullptr) {
+ LOG(DEBUG) << "skip executing command [" << line << "]";
+ continue;
+ }
+
+ // Skip all commands before the saved last command index when resuming an update.
+ if (params.canwrite && params.cmdindex != -1 && params.cmdindex <= saved_last_command_index) {
+ LOG(INFO) << "Skipping already executed command: " << params.cmdindex
+ << ", last executed command for previous update: " << saved_last_command_index;
+ continue;
+ }
+
+ if (cmd->f(params) == -1) {
LOG(ERROR) << "failed to execute command [" << line << "]";
goto pbiudone;
}
+ // In verify mode, check if the commands before the saved last_command_index have been
+ // executed correctly. If some target blocks have unexpected contents, delete the last command
+ // file so that we will resume the update from the first command in the transfer list.
+ if (!params.canwrite && saved_last_command_index != -1 && params.cmdindex != -1 &&
+ params.cmdindex <= saved_last_command_index) {
+ // TODO(xunchang) check that the cmdline of the saved index is correct.
+ std::string cmdname = std::string(params.cmdname);
+ if ((cmdname == "move" || cmdname == "bsdiff" || cmdname == "imgdiff") &&
+ !params.target_verified) {
+ LOG(WARNING) << "Previously executed command " << saved_last_command_index << ": "
+ << params.cmdline << " doesn't produce expected target blocks.";
+ saved_last_command_index = -1;
+ DeleteLastCommandFile();
+ }
+ }
if (params.canwrite) {
if (ota_fsync(params.fd) == -1) {
failure_type = kFsyncFailure;
@@ -1591,29 +1773,45 @@
}
}
- if (params.canwrite) {
- pthread_join(params.thread, nullptr);
-
- LOG(INFO) << "wrote " << params.written << " blocks; expected " << total_blocks;
- LOG(INFO) << "stashed " << params.stashed << " blocks";
- LOG(INFO) << "max alloc needed was " << params.buffer.size();
-
- const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
- if (partition != nullptr && *(partition + 1) != 0) {
- fprintf(cmd_pipe, "log bytes_written_%s: %zu\n", partition + 1, params.written * BLOCKSIZE);
- fprintf(cmd_pipe, "log bytes_stashed_%s: %zu\n", partition + 1, params.stashed * BLOCKSIZE);
- fflush(cmd_pipe);
- }
- // Delete stash only after successfully completing the update, as it may contain blocks needed
- // to complete the update later.
- DeleteStash(params.stashbase);
- } else {
- LOG(INFO) << "verified partition contents; update may be resumed";
- }
-
rc = 0;
pbiudone:
+ if (params.canwrite) {
+ pthread_mutex_lock(¶ms.nti.mu);
+ if (params.nti.receiver_available) {
+ LOG(WARNING) << "new data receiver is still available after executing all commands.";
+ }
+ params.nti.receiver_available = false;
+ pthread_cond_broadcast(¶ms.nti.cv);
+ pthread_mutex_unlock(¶ms.nti.mu);
+ int ret = pthread_join(params.thread, nullptr);
+ if (ret != 0) {
+ LOG(WARNING) << "pthread join returned with " << strerror(ret);
+ }
+
+ if (rc == 0) {
+ LOG(INFO) << "wrote " << params.written << " blocks; expected " << total_blocks;
+ LOG(INFO) << "stashed " << params.stashed << " blocks";
+ LOG(INFO) << "max alloc needed was " << params.buffer.size();
+
+ const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
+ if (partition != nullptr && *(partition + 1) != 0) {
+ fprintf(cmd_pipe, "log bytes_written_%s: %zu\n", partition + 1, params.written * BLOCKSIZE);
+ fprintf(cmd_pipe, "log bytes_stashed_%s: %zu\n", partition + 1, params.stashed * BLOCKSIZE);
+ fflush(cmd_pipe);
+ }
+ // Delete stash only after successfully completing the update, as it may contain blocks needed
+ // to complete the update later.
+ DeleteStash(params.stashbase);
+ DeleteLastCommandFile();
+ }
+
+ pthread_mutex_destroy(¶ms.nti.mu);
+ pthread_cond_destroy(¶ms.nti.cv);
+ } else if (rc == 0) {
+ LOG(INFO) << "verified partition contents; update may be resumed";
+ }
+
if (ota_fsync(params.fd) == -1) {
failure_type = kFsyncFailure;
PLOG(ERROR) << "fsync failed";
@@ -1624,6 +1822,11 @@
BrotliDecoderDestroyInstance(params.nti.brotli_decoder_state);
}
+ // Delete the last command file if the update cannot be resumed.
+ if (params.isunresumable) {
+ DeleteLastCommandFile();
+ }
+
// Only delete the stash if the update cannot be resumed, or it's a verification run and we
// created the stash.
if (params.isunresumable || (!params.canwrite && params.createdstash)) {
@@ -1748,6 +1951,7 @@
}
RangeSet rs = RangeSet::Parse(ranges->data);
+ CHECK(static_cast<bool>(rs));
SHA_CTX ctx;
SHA1_Init(&ctx);
@@ -1859,6 +2063,11 @@
ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name);
return StringValue("");
}
+ RangeSet rs = RangeSet::Parse(ranges->data);
+ if (!rs) {
+ ErrorAbort(state, kArgsParsingFailure, "failed to parse ranges: %s", ranges->data.c_str());
+ return StringValue("");
+ }
// Output notice to log when recover is attempted
LOG(INFO) << filename->data << " image corrupted, attempting to recover...";
@@ -1884,7 +2093,7 @@
}
uint8_t buffer[BLOCKSIZE];
- for (const auto& range : RangeSet::Parse(ranges->data)) {
+ for (const auto& range : rs) {
for (size_t j = range.first; j < range.second; ++j) {
// Stay within the data area, libfec validates and corrects metadata
if (status.data_size <= static_cast<uint64_t>(j) * BLOCKSIZE) {
diff --git a/updater/include/updater/blockimg.h b/updater/include/updater/blockimg.h
index 2f4ad3c..71733b3 100644
--- a/updater/include/updater/blockimg.h
+++ b/updater/include/updater/blockimg.h
@@ -17,6 +17,8 @@
#ifndef _UPDATER_BLOCKIMG_H_
#define _UPDATER_BLOCKIMG_H_
+#include <string>
+
void RegisterBlockImageFunctions();
#endif
diff --git a/updater/include/updater/rangeset.h b/updater/include/updater/rangeset.h
deleted file mode 100644
index fad0380..0000000
--- a/updater/include/updater/rangeset.h
+++ /dev/null
@@ -1,164 +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.
- */
-
-#pragma once
-
-#include <stddef.h>
-
-#include <string>
-#include <utility>
-#include <vector>
-
-#include <android-base/logging.h>
-#include <android-base/parseint.h>
-#include <android-base/strings.h>
-
-using Range = std::pair<size_t, size_t>;
-
-class RangeSet {
- public:
- RangeSet() : blocks_(0) {}
-
- explicit RangeSet(std::vector<Range>&& pairs) {
- CHECK_NE(pairs.size(), static_cast<size_t>(0)) << "Invalid number of tokens";
-
- // Sanity check the input.
- size_t result = 0;
- for (const auto& range : pairs) {
- CHECK_LT(range.first, range.second)
- << "Empty or negative range: " << range.first << ", " << range.second;
- size_t sz = range.second - range.first;
- CHECK_LE(result, SIZE_MAX - sz) << "RangeSet size overflow";
- result += sz;
- }
-
- ranges_ = pairs;
- blocks_ = result;
- }
-
- static RangeSet Parse(const std::string& range_text) {
- std::vector<std::string> pieces = android::base::Split(range_text, ",");
- CHECK_GE(pieces.size(), static_cast<size_t>(3)) << "Invalid range text: " << range_text;
-
- size_t num;
- CHECK(android::base::ParseUint(pieces[0], &num, static_cast<size_t>(INT_MAX)))
- << "Failed to parse the number of tokens: " << range_text;
-
- CHECK_NE(num, static_cast<size_t>(0)) << "Invalid number of tokens: " << range_text;
- CHECK_EQ(num % 2, static_cast<size_t>(0)) << "Number of tokens must be even: " << range_text;
- CHECK_EQ(num, pieces.size() - 1) << "Mismatching number of tokens: " << range_text;
-
- std::vector<Range> pairs;
- for (size_t i = 0; i < num; i += 2) {
- size_t first;
- CHECK(android::base::ParseUint(pieces[i + 1], &first, static_cast<size_t>(INT_MAX)));
- size_t second;
- CHECK(android::base::ParseUint(pieces[i + 2], &second, static_cast<size_t>(INT_MAX)));
-
- pairs.emplace_back(first, second);
- }
-
- return RangeSet(std::move(pairs));
- }
-
- // Get the block number for the i-th (starting from 0) block in the RangeSet.
- size_t 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;
- }
- idx -= (range.second - range.first);
- }
-
- CHECK(false) << "Failed to find block number for index " << idx;
- return 0; // Unreachable, but to make compiler happy.
- }
-
- // 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 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)) {
- return true;
- }
- }
- }
- return false;
- }
-
- // size() gives the number of Range's in this RangeSet.
- size_t size() const {
- return ranges_.size();
- }
-
- // blocks() gives the number of all blocks in this RangeSet.
- size_t blocks() const {
- return blocks_;
- }
-
- // We provide const iterators only.
- std::vector<Range>::const_iterator cbegin() const {
- return ranges_.cbegin();
- }
-
- std::vector<Range>::const_iterator cend() const {
- return ranges_.cend();
- }
-
- // Need to provide begin()/end() since range-based loop expects begin()/end().
- std::vector<Range>::const_iterator begin() const {
- return ranges_.cbegin();
- }
-
- std::vector<Range>::const_iterator end() const {
- return ranges_.cend();
- }
-
- // Reverse const iterators for MoveRange().
- std::vector<Range>::const_reverse_iterator crbegin() const {
- return ranges_.crbegin();
- }
-
- std::vector<Range>::const_reverse_iterator crend() const {
- return ranges_.crend();
- }
-
- const Range& operator[](size_t i) const {
- return ranges_[i];
- }
-
- bool operator==(const RangeSet& other) const {
- // The orders of Range's matter. "4,1,5,8,10" != "4,8,10,1,5".
- return (ranges_ == other.ranges_);
- }
-
- bool operator!=(const RangeSet& other) const {
- return ranges_ != other.ranges_;
- }
-
- private:
- // Actual limit for each value and the total number are both INT_MAX.
- std::vector<Range> ranges_;
- size_t blocks_;
-};
diff --git a/updater/install.cpp b/updater/install.cpp
index bfe91e7..9be7645 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -49,20 +49,19 @@
#include <applypatch/applypatch.h>
#include <bootloader_message/bootloader_message.h>
#include <cutils/android_reboot.h>
-#include <ext4_utils/make_ext4fs.h>
#include <ext4_utils/wipe.h>
#include <openssl/sha.h>
#include <selinux/label.h>
#include <selinux/selinux.h>
+#include <tune2fs.h>
#include <ziparchive/zip_archive.h>
#include "edify/expr.h"
-#include "error_code.h"
#include "mounts.h"
-#include "ota_io.h"
+#include "otafault/ota_io.h"
#include "otautil/DirUtil.h"
-#include "print_sha1.h"
-#include "tune2fs.h"
+#include "otautil/error_code.h"
+#include "otautil/print_sha1.h"
#include "updater/updater.h"
// Send over the buffer to recovery though the command pipe.
@@ -95,32 +94,242 @@
uiPrint(state, error_msg);
}
-static bool is_dir(const std::string& dirpath) {
- struct stat st;
- return stat(dirpath.c_str(), &st) == 0 && S_ISDIR(st.st_mode);
+// This is the updater side handler for ui_print() in edify script. Contents will be sent over to
+// the recovery side for on-screen display.
+Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+ std::vector<std::string> args;
+ if (!ReadArgs(state, argv, &args)) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
+ }
+
+ std::string buffer = android::base::Join(args, "");
+ uiPrint(state, buffer);
+ return StringValue(buffer);
}
-// Create all parent directories of name, if necessary.
-static bool make_parents(const std::string& name) {
- size_t prev_end = 0;
- while (prev_end < name.size()) {
- size_t next_end = name.find('/', prev_end + 1);
- if (next_end == std::string::npos) {
- break;
- }
- std::string dir_path = name.substr(0, next_end);
- if (!is_dir(dir_path)) {
- int result = mkdir(dir_path.c_str(), 0700);
- if (result != 0) {
- PLOG(ERROR) << "failed to mkdir " << dir_path << " when make parents for " << name;
- return false;
- }
-
- LOG(INFO) << "created [" << dir_path << "]";
- }
- prev_end = next_end;
+// package_extract_file(package_file[, dest_file])
+// Extracts a single package_file from the update package and writes it to dest_file,
+// overwriting existing files if necessary. Without the dest_file argument, returns the
+// contents of the package file as a binary blob.
+Value* PackageExtractFileFn(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
+ if (argv.size() < 1 || argv.size() > 2) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 or 2 args, got %zu", name,
+ argv.size());
}
- return true;
+
+ if (argv.size() == 2) {
+ // The two-argument version extracts to a file.
+
+ std::vector<std::string> args;
+ if (!ReadArgs(state, argv, &args)) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name,
+ argv.size());
+ }
+ const std::string& zip_path = args[0];
+ const std::string& dest_path = args[1];
+
+ ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
+ ZipString zip_string_path(zip_path.c_str());
+ ZipEntry entry;
+ if (FindEntry(za, zip_string_path, &entry) != 0) {
+ LOG(ERROR) << name << ": no " << zip_path << " in package";
+ return StringValue("");
+ }
+
+ unique_fd fd(TEMP_FAILURE_RETRY(
+ ota_open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)));
+ if (fd == -1) {
+ PLOG(ERROR) << name << ": can't open " << dest_path << " for write";
+ return StringValue("");
+ }
+
+ bool success = true;
+ int32_t ret = ExtractEntryToFile(za, &entry, fd);
+ if (ret != 0) {
+ LOG(ERROR) << name << ": Failed to extract entry \"" << zip_path << "\" ("
+ << entry.uncompressed_length << " bytes) to \"" << dest_path
+ << "\": " << ErrorCodeString(ret);
+ success = false;
+ }
+ if (ota_fsync(fd) == -1) {
+ PLOG(ERROR) << "fsync of \"" << dest_path << "\" failed";
+ success = false;
+ }
+ if (ota_close(fd) == -1) {
+ PLOG(ERROR) << "close of \"" << dest_path << "\" failed";
+ success = false;
+ }
+
+ return StringValue(success ? "t" : "");
+ } else {
+ // The one-argument version returns the contents of the file as the result.
+
+ std::vector<std::string> args;
+ if (!ReadArgs(state, argv, &args)) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name,
+ argv.size());
+ }
+ const std::string& zip_path = args[0];
+
+ ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
+ ZipString zip_string_path(zip_path.c_str());
+ ZipEntry entry;
+ if (FindEntry(za, zip_string_path, &entry) != 0) {
+ return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name,
+ zip_path.c_str());
+ }
+
+ std::string buffer;
+ buffer.resize(entry.uncompressed_length);
+
+ int32_t ret =
+ ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&buffer[0]), buffer.size());
+ if (ret != 0) {
+ return ErrorAbort(state, kPackageExtractFileFailure,
+ "%s: Failed to extract entry \"%s\" (%zu bytes) to memory: %s", name,
+ zip_path.c_str(), buffer.size(), ErrorCodeString(ret));
+ }
+
+ return new Value(VAL_BLOB, buffer);
+ }
+}
+
+// apply_patch(src_file, tgt_file, tgt_sha1, tgt_size, patch1_sha1, patch1_blob, [...])
+// Applies a binary patch to the src_file to produce the tgt_file. If the desired target is the
+// same as the source, pass "-" for tgt_file. tgt_sha1 and tgt_size are the expected final SHA1
+// hash and size of the target file. The remaining arguments must come in pairs: a SHA1 hash (a
+// 40-character hex string) and a blob. The blob is the patch to be applied when the source
+// file's current contents have the given SHA1.
+//
+// The patching is done in a safe manner that guarantees the target file either has the desired
+// SHA1 hash and size, or it is untouched -- it will not be left in an unrecoverable intermediate
+// state. If the process is interrupted during patching, the target file may be in an intermediate
+// state; a copy exists in the cache partition so restarting the update can successfully update
+// the file.
+Value* ApplyPatchFn(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
+ if (argv.size() < 6 || (argv.size() % 2) == 1) {
+ return ErrorAbort(state, kArgsParsingFailure,
+ "%s(): expected at least 6 args and an "
+ "even number, got %zu",
+ name, argv.size());
+ }
+
+ std::vector<std::string> args;
+ if (!ReadArgs(state, argv, &args, 0, 4)) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+ }
+ const std::string& source_filename = args[0];
+ const std::string& target_filename = args[1];
+ const std::string& target_sha1 = args[2];
+ const std::string& target_size_str = args[3];
+
+ size_t target_size;
+ if (!android::base::ParseUint(target_size_str.c_str(), &target_size)) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count", name,
+ target_size_str.c_str());
+ }
+
+ int patchcount = (argv.size() - 4) / 2;
+ std::vector<std::unique_ptr<Value>> arg_values;
+ if (!ReadValueArgs(state, argv, &arg_values, 4, argv.size() - 4)) {
+ return nullptr;
+ }
+
+ for (int i = 0; i < patchcount; ++i) {
+ if (arg_values[i * 2]->type != VAL_STRING) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s(): sha-1 #%d is not string", name, i * 2);
+ }
+ if (arg_values[i * 2 + 1]->type != VAL_BLOB) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s(): patch #%d is not blob", name, i * 2 + 1);
+ }
+ }
+
+ std::vector<std::string> patch_sha_str;
+ std::vector<std::unique_ptr<Value>> patches;
+ for (int i = 0; i < patchcount; ++i) {
+ patch_sha_str.push_back(arg_values[i * 2]->data);
+ patches.push_back(std::move(arg_values[i * 2 + 1]));
+ }
+
+ int result = applypatch(source_filename.c_str(), target_filename.c_str(), target_sha1.c_str(),
+ target_size, patch_sha_str, patches, nullptr);
+
+ return StringValue(result == 0 ? "t" : "");
+}
+
+// apply_patch_check(filename, [sha1, ...])
+// Returns true if the contents of filename or the temporary copy in the cache partition (if
+// present) have a SHA-1 checksum equal to one of the given sha1 values. sha1 values are
+// specified as 40 hex digits. This function differs from sha1_check(read_file(filename),
+// sha1 [, ...]) in that it knows to check the cache partition copy, so apply_patch_check() will
+// succeed even if the file was corrupted by an interrupted apply_patch() update.
+Value* ApplyPatchCheckFn(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
+ if (argv.size() < 1) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 1 arg, got %zu", name,
+ argv.size());
+ }
+
+ std::vector<std::string> args;
+ if (!ReadArgs(state, argv, &args, 0, 1)) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+ }
+ const std::string& filename = args[0];
+
+ std::vector<std::string> sha1s;
+ if (argv.size() > 1 && !ReadArgs(state, argv, &sha1s, 1, argv.size() - 1)) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+ }
+ int result = applypatch_check(filename.c_str(), sha1s);
+
+ return StringValue(result == 0 ? "t" : "");
+}
+
+// sha1_check(data)
+// to return the sha1 of the data (given in the format returned by
+// read_file).
+//
+// sha1_check(data, sha1_hex, [sha1_hex, ...])
+// returns the sha1 of the file if it matches any of the hex
+// strings passed, or "" if it does not equal any of them.
+//
+Value* Sha1CheckFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+ if (argv.size() < 1) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name);
+ }
+
+ std::vector<std::unique_ptr<Value>> args;
+ if (!ReadValueArgs(state, argv, &args)) {
+ return nullptr;
+ }
+
+ if (args[0]->type == VAL_INVALID) {
+ return StringValue("");
+ }
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1(reinterpret_cast<const uint8_t*>(args[0]->data.c_str()), args[0]->data.size(), digest);
+
+ if (argv.size() == 1) {
+ return StringValue(print_sha1(digest));
+ }
+
+ for (size_t i = 1; i < argv.size(); ++i) {
+ uint8_t arg_digest[SHA_DIGEST_LENGTH];
+ if (args[i]->type != VAL_STRING) {
+ LOG(ERROR) << name << "(): arg " << i << " is not a string; skipping";
+ } else if (ParseSha1(args[i]->data.c_str(), arg_digest) != 0) {
+ // Warn about bad args and skip them.
+ LOG(ERROR) << name << "(): error parsing \"" << args[i]->data << "\" as sha-1; skipping";
+ } else if (memcmp(digest, arg_digest, SHA_DIGEST_LENGTH) == 0) {
+ // Found a match.
+ return args[i].release();
+ }
+ }
+
+ // Didn't match any of the hex strings; return false.
+ return StringValue("");
}
// mount(fs_type, partition_type, location, mount_point)
@@ -297,7 +506,7 @@
int64_t size;
if (!android::base::ParseInt(fs_size, &size)) {
- return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s\n", name,
+ return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s", name,
fs_size.c_str());
}
@@ -312,14 +521,8 @@
int status = exec_cmd(mke2fs_argv[0], const_cast<char**>(mke2fs_argv));
if (status != 0) {
- LOG(WARNING) << name << ": mke2fs failed (" << status << ") on " << location
- << ", falling back to make_ext4fs";
- status = make_ext4fs(location.c_str(), size, mount_point.c_str(), sehandle);
- if (status != 0) {
- LOG(ERROR) << name << ": make_ext4fs failed (" << status << ") on " << location;
- return StringValue("");
- }
- return StringValue(location);
+ LOG(ERROR) << name << ": mke2fs failed (" << status << ") on " << location;
+ return StringValue("");
}
const char* e2fsdroid_argv[] = { "/sbin/e2fsdroid_static", "-e", "-a", mount_point.c_str(),
@@ -338,15 +541,30 @@
std::string num_sectors = std::to_string(size / 512);
const char* f2fs_path = "/sbin/mkfs.f2fs";
- const char* f2fs_argv[] = {
- "mkfs.f2fs", "-t", "-d1", location.c_str(), (size < 512) ? nullptr : num_sectors.c_str(),
- nullptr
- };
+ const char* f2fs_argv[] = { "mkfs.f2fs",
+ "-d1",
+ "-f",
+ "-O", "encrypt",
+ "-O", "quota",
+ "-O", "verity",
+ "-w", "512",
+ location.c_str(),
+ (size < 512) ? nullptr : num_sectors.c_str(),
+ nullptr };
int status = exec_cmd(f2fs_path, const_cast<char**>(f2fs_argv));
if (status != 0) {
LOG(ERROR) << name << ": mkfs.f2fs failed (" << status << ") on " << location;
return StringValue("");
}
+
+ const char* sload_argv[] = { "/sbin/sload.f2fs", "-t", mount_point.c_str(), location.c_str(),
+ nullptr };
+ status = exec_cmd(sload_argv[0], const_cast<char**>(sload_argv));
+ if (status != 0) {
+ LOG(ERROR) << name << ": sload.f2fs failed (" << status << ") on " << location;
+ return StringValue("");
+ }
+
return StringValue(location);
} else {
LOG(ERROR) << name << ": unsupported fs_type \"" << fs_type << "\" partition_type \""
@@ -372,12 +590,12 @@
double frac;
if (!android::base::ParseDouble(frac_str.c_str(), &frac)) {
- return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s\n", name,
+ return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s", name,
frac_str.c_str());
}
int sec;
if (!android::base::ParseInt(sec_str.c_str(), &sec)) {
- return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s\n", name,
+ return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s", name,
sec_str.c_str());
}
@@ -387,7 +605,8 @@
return StringValue(frac_str);
}
-Value* SetProgressFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+Value* SetProgressFn(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 1) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
}
@@ -400,7 +619,7 @@
double frac;
if (!android::base::ParseDouble(frac_str.c_str(), &frac)) {
- return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s\n", name,
+ return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s", name,
frac_str.c_str());
}
@@ -410,93 +629,6 @@
return StringValue(frac_str);
}
-// package_extract_file(package_file[, dest_file])
-// Extracts a single package_file from the update package and writes it to dest_file,
-// overwriting existing files if necessary. Without the dest_file argument, returns the
-// contents of the package file as a binary blob.
-Value* PackageExtractFileFn(const char* name, State* state,
- const std::vector<std::unique_ptr<Expr>>& argv) {
- if (argv.size() < 1 || argv.size() > 2) {
- return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 or 2 args, got %zu", name,
- argv.size());
- }
-
- if (argv.size() == 2) {
- // The two-argument version extracts to a file.
-
- std::vector<std::string> args;
- if (!ReadArgs(state, argv, &args)) {
- return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name,
- argv.size());
- }
- const std::string& zip_path = args[0];
- const std::string& dest_path = args[1];
-
- ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
- ZipString zip_string_path(zip_path.c_str());
- ZipEntry entry;
- if (FindEntry(za, zip_string_path, &entry) != 0) {
- LOG(ERROR) << name << ": no " << zip_path << " in package";
- return StringValue("");
- }
-
- unique_fd fd(TEMP_FAILURE_RETRY(
- ota_open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)));
- if (fd == -1) {
- PLOG(ERROR) << name << ": can't open " << dest_path << " for write";
- return StringValue("");
- }
-
- bool success = true;
- int32_t ret = ExtractEntryToFile(za, &entry, fd);
- if (ret != 0) {
- LOG(ERROR) << name << ": Failed to extract entry \"" << zip_path << "\" ("
- << entry.uncompressed_length << " bytes) to \"" << dest_path
- << "\": " << ErrorCodeString(ret);
- success = false;
- }
- if (ota_fsync(fd) == -1) {
- PLOG(ERROR) << "fsync of \"" << dest_path << "\" failed";
- success = false;
- }
- if (ota_close(fd) == -1) {
- PLOG(ERROR) << "close of \"" << dest_path << "\" failed";
- success = false;
- }
-
- return StringValue(success ? "t" : "");
- } else {
- // The one-argument version returns the contents of the file as the result.
-
- std::vector<std::string> args;
- if (!ReadArgs(state, argv, &args)) {
- return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name,
- argv.size());
- }
- const std::string& zip_path = args[0];
-
- ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
- ZipString zip_string_path(zip_path.c_str());
- ZipEntry entry;
- if (FindEntry(za, zip_string_path, &entry) != 0) {
- return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name,
- zip_path.c_str());
- }
-
- std::string buffer;
- buffer.resize(entry.uncompressed_length);
-
- int32_t ret = ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&buffer[0]), buffer.size());
- if (ret != 0) {
- return ErrorAbort(state, kPackageExtractFileFailure,
- "%s: Failed to extract entry \"%s\" (%zu bytes) to memory: %s", name,
- zip_path.c_str(), buffer.size(), ErrorCodeString(ret));
- }
-
- return new Value(VAL_BLOB, buffer);
- }
-}
-
Value* GetPropFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 1) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
@@ -515,7 +647,8 @@
// interprets 'file' as a getprop-style file (key=value pairs, one
// per line. # comment lines, blank lines, lines without '=' ignored),
// and returns the value for 'key' (or "" if it isn't defined).
-Value* FileGetPropFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+Value* FileGetPropFn(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 2) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
argv.size());
@@ -581,7 +714,8 @@
}
// apply_patch_space(bytes)
-Value* ApplyPatchSpaceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+Value* ApplyPatchSpaceFn(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 1) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 args, got %zu", name,
argv.size());
@@ -594,115 +728,15 @@
size_t bytes;
if (!android::base::ParseUint(bytes_str.c_str(), &bytes)) {
- return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count\n\n",
- name, bytes_str.c_str());
+ return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count", name,
+ bytes_str.c_str());
}
- return StringValue(CacheSizeCheck(bytes) ? "" : "t");
-}
-
-// apply_patch(src_file, tgt_file, tgt_sha1, tgt_size, patch1_sha1, patch1_blob, [...])
-// Applies a binary patch to the src_file to produce the tgt_file. If the desired target is the
-// same as the source, pass "-" for tgt_file. tgt_sha1 and tgt_size are the expected final SHA1
-// hash and size of the target file. The remaining arguments must come in pairs: a SHA1 hash (a
-// 40-character hex string) and a blob. The blob is the patch to be applied when the source
-// file's current contents have the given SHA1.
-//
-// The patching is done in a safe manner that guarantees the target file either has the desired
-// SHA1 hash and size, or it is untouched -- it will not be left in an unrecoverable intermediate
-// state. If the process is interrupted during patching, the target file may be in an intermediate
-// state; a copy exists in the cache partition so restarting the update can successfully update
-// the file.
-Value* ApplyPatchFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
- if (argv.size() < 6 || (argv.size() % 2) == 1) {
- return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 6 args and an "
- "even number, got %zu", name, argv.size());
- }
-
- std::vector<std::string> args;
- if (!ReadArgs(state, argv, &args, 0, 4)) {
- return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
- }
- const std::string& source_filename = args[0];
- const std::string& target_filename = args[1];
- const std::string& target_sha1 = args[2];
- const std::string& target_size_str = args[3];
-
- size_t target_size;
- if (!android::base::ParseUint(target_size_str.c_str(), &target_size)) {
- return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count",
- name, target_size_str.c_str());
- }
-
- int patchcount = (argv.size()-4) / 2;
- std::vector<std::unique_ptr<Value>> arg_values;
- if (!ReadValueArgs(state, argv, &arg_values, 4, argv.size() - 4)) {
- return nullptr;
- }
-
- for (int i = 0; i < patchcount; ++i) {
- if (arg_values[i * 2]->type != VAL_STRING) {
- return ErrorAbort(state, kArgsParsingFailure, "%s(): sha-1 #%d is not string", name,
- i * 2);
- }
- if (arg_values[i * 2 + 1]->type != VAL_BLOB) {
- return ErrorAbort(state, kArgsParsingFailure, "%s(): patch #%d is not blob", name,
- i * 2 + 1);
- }
- }
-
- std::vector<std::string> patch_sha_str;
- std::vector<std::unique_ptr<Value>> patches;
- for (int i = 0; i < patchcount; ++i) {
- patch_sha_str.push_back(arg_values[i * 2]->data);
- patches.push_back(std::move(arg_values[i * 2 + 1]));
- }
-
- int result = applypatch(source_filename.c_str(), target_filename.c_str(),
- target_sha1.c_str(), target_size,
- patch_sha_str, patches, nullptr);
-
- return StringValue(result == 0 ? "t" : "");
-}
-
-// apply_patch_check(filename, [sha1, ...])
-// Returns true if the contents of filename or the temporary copy in the cache partition (if
-// present) have a SHA-1 checksum equal to one of the given sha1 values. sha1 values are
-// specified as 40 hex digits. This function differs from sha1_check(read_file(filename),
-// sha1 [, ...]) in that it knows to check the cache partition copy, so apply_patch_check() will
-// succeed even if the file was corrupted by an interrupted apply_patch() update.
-Value* ApplyPatchCheckFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
- if (argv.size() < 1) {
- return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 1 arg, got %zu", name,
- argv.size());
+ // Skip the cache size check if the update is a retry.
+ if (state->is_retry || CacheSizeCheck(bytes) == 0) {
+ return StringValue("t");
}
-
- std::vector<std::string> args;
- if (!ReadArgs(state, argv, &args, 0, 1)) {
- return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
- }
- const std::string& filename = args[0];
-
- std::vector<std::string> sha1s;
- if (argv.size() > 1 && !ReadArgs(state, argv, &sha1s, 1, argv.size() - 1)) {
- return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
- }
- int result = applypatch_check(filename.c_str(), sha1s);
-
- return StringValue(result == 0 ? "t" : "");
-}
-
-// This is the updater side handler for ui_print() in edify script. Contents will be sent over to
-// the recovery side for on-screen display.
-Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
- std::vector<std::string> args;
- if (!ReadArgs(state, argv, &args)) {
- return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
- }
-
- std::string buffer = android::base::Join(args, "");
- uiPrint(state, buffer);
- return StringValue(buffer);
+ return StringValue("");
}
Value* WipeCacheFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
@@ -752,51 +786,6 @@
return StringValue(std::to_string(status));
}
-// sha1_check(data)
-// to return the sha1 of the data (given in the format returned by
-// read_file).
-//
-// sha1_check(data, sha1_hex, [sha1_hex, ...])
-// returns the sha1 of the file if it matches any of the hex
-// strings passed, or "" if it does not equal any of them.
-//
-Value* Sha1CheckFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
- if (argv.size() < 1) {
- return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name);
- }
-
- std::vector<std::unique_ptr<Value>> args;
- if (!ReadValueArgs(state, argv, &args)) {
- return nullptr;
- }
-
- if (args[0]->type == VAL_INVALID) {
- return StringValue("");
- }
- uint8_t digest[SHA_DIGEST_LENGTH];
- SHA1(reinterpret_cast<const uint8_t*>(args[0]->data.c_str()), args[0]->data.size(), digest);
-
- if (argv.size() == 1) {
- return StringValue(print_sha1(digest));
- }
-
- for (size_t i = 1; i < argv.size(); ++i) {
- uint8_t arg_digest[SHA_DIGEST_LENGTH];
- if (args[i]->type != VAL_STRING) {
- LOG(ERROR) << name << "(): arg " << i << " is not a string; skipping";
- } else if (ParseSha1(args[i]->data.c_str(), arg_digest) != 0) {
- // Warn about bad args and skip them.
- LOG(ERROR) << name << "(): error parsing \"" << args[i]->data << "\" as sha-1; skipping";
- } else if (memcmp(digest, arg_digest, SHA_DIGEST_LENGTH) == 0) {
- // Found a match.
- return args[i].release();
- }
- }
-
- // Didn't match any of the hex strings; return false.
- return StringValue("");
-}
-
// Read a local file and return its contents (the Value* returned
// is actually a FileContents*).
Value* ReadFileFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
diff --git a/updater/updater.cpp b/updater/updater.cpp
index 1d8fa8e..1d6b172 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -30,10 +30,12 @@
#include <selinux/selinux.h>
#include <ziparchive/zip_archive.h>
-#include "config.h"
#include "edify/expr.h"
+#include "otafault/config.h"
#include "otautil/DirUtil.h"
#include "otautil/SysUtil.h"
+#include "otautil/cache_location.h"
+#include "otautil/error_code.h"
#include "updater/blockimg.h"
#include "updater/install.h"
diff --git a/verifier.cpp b/verifier.cpp
index 18437fb..283e043 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -32,7 +32,7 @@
#include <openssl/obj_mac.h>
#include "asn1_decoder.h"
-#include "print_sha1.h"
+#include "otautil/print_sha1.h"
static constexpr size_t MiB = 1024 * 1024;
diff --git a/vr_ui.cpp b/vr_ui.cpp
index 1251672..a58c99e 100644
--- a/vr_ui.cpp
+++ b/vr_ui.cpp
@@ -20,16 +20,46 @@
VrRecoveryUI::VrRecoveryUI() : kStereoOffset(RECOVERY_UI_VR_STEREO_OFFSET) {}
-bool VrRecoveryUI::InitTextParams() {
- if (!ScreenRecoveryUI::InitTextParams()) return false;
- int mid_divide = gr_fb_width() / 2;
- text_cols_ = (mid_divide - kMarginWidth - kStereoOffset) / char_width_;
- return true;
+int VrRecoveryUI::ScreenWidth() const {
+ return gr_fb_width() / 2;
+}
+
+int VrRecoveryUI::ScreenHeight() const {
+ return gr_fb_height();
+}
+
+void VrRecoveryUI::DrawSurface(GRSurface* surface, int sx, int sy, int w, int h, int dx,
+ int dy) const {
+ gr_blit(surface, sx, sy, w, h, dx + kStereoOffset, dy);
+ gr_blit(surface, sx, sy, w, h, dx - kStereoOffset + ScreenWidth(), dy);
+}
+
+void VrRecoveryUI::DrawTextIcon(int x, int y, GRSurface* surface) const {
+ gr_texticon(x + kStereoOffset, y, surface);
+ gr_texticon(x - kStereoOffset + ScreenWidth(), y, surface);
}
int VrRecoveryUI::DrawTextLine(int x, int y, const char* line, bool bold) const {
- int mid_divide = gr_fb_width() / 2;
gr_text(gr_sys_font(), x + kStereoOffset, y, line, bold);
- gr_text(gr_sys_font(), x - kStereoOffset + mid_divide, y, line, bold);
+ gr_text(gr_sys_font(), x - kStereoOffset + ScreenWidth(), y, line, bold);
return char_height_ + 4;
}
+
+int VrRecoveryUI::DrawHorizontalRule(int y) const {
+ y += 4;
+ gr_fill(kMarginWidth + kStereoOffset, y, ScreenWidth() - kMarginWidth + kStereoOffset, y + 2);
+ gr_fill(ScreenWidth() + kMarginWidth - kStereoOffset, y,
+ gr_fb_width() - kMarginWidth - kStereoOffset, y + 2);
+ return y + 4;
+}
+
+void VrRecoveryUI::DrawHighlightBar(int /* x */, int y, int /* width */, int height) const {
+ gr_fill(kMarginWidth + kStereoOffset, y, ScreenWidth() - kMarginWidth + kStereoOffset, y + height);
+ gr_fill(ScreenWidth() + kMarginWidth - kStereoOffset, y,
+ gr_fb_width() - kMarginWidth - kStereoOffset, y + height);
+}
+
+void VrRecoveryUI::DrawFill(int x, int y, int w, int h) const {
+ gr_fill(x + kStereoOffset, y, w, h);
+ gr_fill(x - kStereoOffset + ScreenWidth(), y, w, h);
+}
diff --git a/vr_ui.h b/vr_ui.h
index d996c14..eeb4589 100644
--- a/vr_ui.h
+++ b/vr_ui.h
@@ -28,8 +28,14 @@
// Can vary per device depending on screen size and lens distortion.
const int kStereoOffset;
- bool InitTextParams() override;
+ int ScreenWidth() const override;
+ int ScreenHeight() const override;
+ void DrawSurface(GRSurface* surface, int sx, int sy, int w, int h, int dx, int dy) const override;
+ int DrawHorizontalRule(int y) const override;
+ void DrawHighlightBar(int x, int y, int width, int height) const override;
+ void DrawFill(int x, int y, int w, int h) const override;
+ void DrawTextIcon(int x, int y, GRSurface* surface) const override;
int DrawTextLine(int x, int y, const char* line, bool bold) const override;
};
diff --git a/wear_ui.cpp b/wear_ui.cpp
index 624116c..ca6b1b1 100644
--- a/wear_ui.cpp
+++ b/wear_ui.cpp
@@ -16,40 +16,16 @@
#include "wear_ui.h"
-#include <errno.h>
-#include <fcntl.h>
-#include <stdarg.h>
-#include <stdlib.h>
+#include <pthread.h>
+#include <stdio.h> // TODO: Remove after killing the call to sprintf().
#include <string.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <time.h>
-#include <unistd.h>
#include <string>
-#include <vector>
#include <android-base/properties.h>
#include <android-base/strings.h>
-#include <android-base/stringprintf.h>
#include <minui/minui.h>
-#include "common.h"
-#include "device.h"
-
-// There's only (at most) one of these objects, and global callbacks
-// (for pthread_create, and the input event system) need to find it,
-// so use a global variable.
-static WearRecoveryUI* self = NULL;
-
-// Return the current time as a double (including fractions of a second).
-static double now() {
- struct timeval tv;
- gettimeofday(&tv, NULL);
- return tv.tv_sec + tv.tv_usec / 1000000.0;
-}
-
WearRecoveryUI::WearRecoveryUI()
: kProgressBarBaseline(RECOVERY_UI_PROGRESS_BAR_BASELINE),
kMenuUnusableRows(RECOVERY_UI_MENU_UNUSABLE_ROWS) {
@@ -61,10 +37,6 @@
loop_frames = 60;
touch_screen_allowed_ = true;
-
- for (size_t i = 0; i < 5; i++) backgroundIcon[i] = NULL;
-
- self = this;
}
int WearRecoveryUI::GetProgressBaseline() const {
@@ -80,24 +52,12 @@
gr_fill(0, 0, gr_fb_width(), gr_fb_height());
if (currentIcon != NONE) {
- GRSurface* surface;
- if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
- if (!intro_done) {
- surface = introFrames[current_frame];
- } else {
- surface = loopFrames[current_frame];
- }
- } else {
- surface = backgroundIcon[currentIcon];
- }
-
- int width = gr_get_width(surface);
- int height = gr_get_height(surface);
-
- int x = (gr_fb_width() - width) / 2;
- int y = (gr_fb_height() - height) / 2;
-
- gr_blit(surface, 0, 0, width, height, x, y);
+ GRSurface* frame = GetCurrentFrame();
+ int frame_width = gr_get_width(frame);
+ int frame_height = gr_get_height(frame);
+ int frame_x = (gr_fb_width() - frame_width) / 2;
+ int frame_y = (gr_fb_height() - frame_height) / 2;
+ gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
}
}
@@ -173,8 +133,7 @@
// display from the bottom up, until we hit the top of the
// screen, the bottom of the menu, or we've displayed the
// entire text buffer.
- int ty;
- int row = (text_top_ + text_rows_ - 1) % text_rows_;
+ int row = text_row_;
size_t count = 0;
for (int ty = gr_fb_height() - char_height_ - kMarginHeight; ty > y + 2 && count < text_rows_;
ty -= char_height_, ++count) {
@@ -191,64 +150,7 @@
gr_flip();
}
-bool WearRecoveryUI::InitTextParams() {
- if (!ScreenRecoveryUI::InitTextParams()) {
- return false;
- }
-
- text_cols_ = (gr_fb_width() - (kMarginWidth * 2)) / char_width_;
-
- if (text_rows_ > kMaxRows) text_rows_ = kMaxRows;
- if (text_cols_ > kMaxCols) text_cols_ = kMaxCols;
-
- visible_text_rows = (gr_fb_height() - (kMarginHeight * 2)) / char_height_;
- return true;
-}
-
-bool WearRecoveryUI::Init(const std::string& locale) {
- if (!ScreenRecoveryUI::Init(locale)) {
- return false;
- }
-
- LoadBitmap("icon_error", &backgroundIcon[ERROR]);
- backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];
-
- // This leaves backgroundIcon[INSTALLING_UPDATE] and backgroundIcon[ERASING]
- // as NULL which is fine since draw_background_locked() doesn't use them.
-
- return true;
-}
-
-void WearRecoveryUI::SetStage(int current, int max) {
-}
-
-void WearRecoveryUI::Print(const char* fmt, ...) {
- char buf[256];
- va_list ap;
- va_start(ap, fmt);
- vsnprintf(buf, 256, fmt, ap);
- va_end(ap);
-
- fputs(buf, stdout);
-
- // This can get called before ui_init(), so be careful.
- pthread_mutex_lock(&updateMutex);
- if (text_rows_ > 0 && text_cols_ > 0) {
- char* ptr;
- for (ptr = buf; *ptr != '\0'; ++ptr) {
- if (*ptr == '\n' || text_col_ >= text_cols_) {
- text_[text_row_][text_col_] = '\0';
- text_col_ = 0;
- text_row_ = (text_row_ + 1) % text_rows_;
- if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
- }
- if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
- }
- text_[text_row_][text_col_] = '\0';
- update_screen_locked();
- }
- pthread_mutex_unlock(&updateMutex);
-}
+void WearRecoveryUI::SetStage(int /* current */, int /* max */) {}
void WearRecoveryUI::StartMenu(const char* const* headers, const char* const* items,
int initial_selection) {
@@ -268,7 +170,7 @@
show_menu = true;
menu_sel = initial_selection;
menu_start = 0;
- menu_end = visible_text_rows - 1 - kMenuUnusableRows;
+ menu_end = text_rows_ - 1 - kMenuUnusableRows;
if (menu_items <= menu_end) menu_end = menu_items;
update_screen_locked();
}
@@ -296,106 +198,3 @@
pthread_mutex_unlock(&updateMutex);
return sel;
}
-
-void WearRecoveryUI::ShowFile(FILE* fp) {
- std::vector<off_t> offsets;
- offsets.push_back(ftello(fp));
- ClearText();
-
- struct stat sb;
- fstat(fileno(fp), &sb);
-
- bool show_prompt = false;
- while (true) {
- if (show_prompt) {
- Print("--(%d%% of %d bytes)--",
- static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))),
- static_cast<int>(sb.st_size));
- Redraw();
- while (show_prompt) {
- show_prompt = false;
- int key = WaitKey();
- if (key == KEY_POWER || key == KEY_ENTER) {
- return;
- } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
- if (offsets.size() <= 1) {
- show_prompt = true;
- } else {
- offsets.pop_back();
- fseek(fp, offsets.back(), SEEK_SET);
- }
- } else {
- if (feof(fp)) {
- return;
- }
- offsets.push_back(ftello(fp));
- }
- }
- ClearText();
- }
-
- int ch = getc(fp);
- if (ch == EOF) {
- text_row_ = text_top_ = text_rows_ - 2;
- show_prompt = true;
- } else {
- PutChar(ch);
- if (text_col_ == 0 && text_row_ >= text_rows_ - 2) {
- text_top_ = text_row_;
- show_prompt = true;
- }
- }
- }
-}
-
-void WearRecoveryUI::PutChar(char ch) {
- pthread_mutex_lock(&updateMutex);
- if (ch != '\n') text_[text_row_][text_col_++] = ch;
- if (ch == '\n' || text_col_ >= text_cols_) {
- text_col_ = 0;
- ++text_row_;
- }
- pthread_mutex_unlock(&updateMutex);
-}
-
-void WearRecoveryUI::ShowFile(const char* filename) {
- FILE* fp = fopen_path(filename, "re");
- if (fp == nullptr) {
- Print(" Unable to open %s: %s\n", filename, strerror(errno));
- return;
- }
- ShowFile(fp);
- fclose(fp);
-}
-
-void WearRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) {
- va_list ap;
- va_start(ap, fmt);
- PrintV(fmt, false, ap);
- va_end(ap);
-}
-
-void WearRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) {
- std::string str;
- android::base::StringAppendV(&str, fmt, ap);
-
- if (copy_to_stdout) {
- fputs(str.c_str(), stdout);
- }
-
- pthread_mutex_lock(&updateMutex);
- if (text_rows_ > 0 && text_cols_ > 0) {
- for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) {
- if (*ptr == '\n' || text_col_ >= text_cols_) {
- text_[text_row_][text_col_] = '\0';
- text_col_ = 0;
- text_row_ = (text_row_ + 1) % text_rows_;
- if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
- }
- if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
- }
- text_[text_row_][text_col_] = '\0';
- update_screen_locked();
- }
- pthread_mutex_unlock(&updateMutex);
-}
diff --git a/wear_ui.h b/wear_ui.h
index 3bd90b6..739b4cb 100644
--- a/wear_ui.h
+++ b/wear_ui.h
@@ -19,22 +19,12 @@
#include "screen_ui.h"
-#include <string>
-
class WearRecoveryUI : public ScreenRecoveryUI {
public:
WearRecoveryUI();
- bool Init(const std::string& locale) override;
-
void SetStage(int current, int max) override;
- // printing messages
- void Print(const char* fmt, ...) override;
- void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3);
- void ShowFile(const char* filename) override;
- void ShowFile(FILE* fp) override;
-
// menu display
void StartMenu(const char* const* headers, const char* const* items,
int initial_selection) override;
@@ -50,30 +40,13 @@
int GetProgressBaseline() const override;
- bool InitTextParams() override;
-
void update_progress_locked() override;
- void PrintV(const char*, bool, va_list) override;
-
private:
- GRSurface* backgroundIcon[5];
-
- static const int kMaxCols = 96;
- static const int kMaxRows = 96;
-
- // Number of text rows seen on screen
- int visible_text_rows;
-
- const char* const* menu_headers_;
- int menu_start, menu_end;
-
- pthread_t progress_t;
-
void draw_background_locked() override;
void draw_screen_locked() override;
- void PutChar(char);
+ int menu_start, menu_end;
};
#endif // RECOVERY_WEAR_UI_H