diff options
139 files changed, 7001 insertions, 4536 deletions
diff --git a/.clang-format b/.clang-format index 532278864..0e0f4d143 100644 --- a/.clang-format +++ b/.clang-format @@ -10,6 +10,5 @@ IndentWidth: 2 PointerAlignment: Left TabWidth: 2 UseTab: Never -PenaltyExcessCharacter: 32 Cpp11BracedListStyle: false diff --git a/Android.bp b/Android.bp index 49438ad9e..f8c6a4b71 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 776e6ea19..7e0ad122e 100644 --- a/Android.mk +++ b/Android.mk @@ -56,6 +56,7 @@ endif LOCAL_MODULE := librecovery LOCAL_STATIC_LIBRARIES := \ libminui \ + libotautil \ libvintf_recovery \ libcrypto_utils \ libcrypto \ @@ -88,12 +89,12 @@ LOCAL_REQUIRED_MODULES := e2fsdroid_static mke2fs_static mke2fs.conf 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 @@ endif LOCAL_C_INCLUDES += \ system/vold \ +# Health HAL dependency LOCAL_STATIC_LIBRARIES := \ + android.hardware.health@2.0-impl \ + android.hardware.health@2.0 \ + android.hardware.health@1.0 \ + android.hardware.health@1.0-convert \ + libhealthstoragedefault \ + libhidltransport \ + libhidlbase \ + libhwbinder_noltopgo \ + libvndksupport \ + libbatterymonitor + +LOCAL_STATIC_LIBRARIES += \ librecovery \ libverifier \ - libbatterymonitor \ libbootloader_message \ libfs_mgr \ libext4_utils \ @@ -157,8 +170,8 @@ LOCAL_STATIC_LIBRARIES := \ libziparchive \ libotautil \ libmounts \ - libz \ libminadbd \ + libasyncio \ libfusesideload \ libminui \ libpng \ @@ -166,14 +179,14 @@ LOCAL_STATIC_LIBRARIES := \ 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 @@ LOCAL_SRC_FILES := \ 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 @@ LOCAL_SRC_FILES := \ 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 @@ LOCAL_SRC_FILES := \ 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 $(BUILD_STATIC_LIBRARY) 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 @@ LOCAL_MODULE := librecovery_ui_vr 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 \ @@ -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 000000000..b5f5f0362 --- /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 @@ -47,3 +47,94 @@ image under recovery. 1. `adb sync data` to make sure the test-dir has the images to test. 2. The test will automatically pickup and verify all `_text.png` files in the test dir. + +Using `adb` under recovery +-------------------------- + +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 000000000..cb0b36746 --- /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 a7412d238..000000000 --- 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 fb4984303..000000000 --- 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 51bf3932a..7645a4005 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 @@ int LoadFileContents(const char* filename, FileContents* file) { 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 @@ static int LoadPartitionContents(const std::string& filename, FileContents* file 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 @@ int SaveFileContents(const char* filename, const FileContents* file) { 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 @@ int applypatch_check(const char* filename, const std::vector<std::string>& patch (!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 @@ int CacheSizeCheck(size_t bytes) { 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 @@ int CacheSizeCheck(size_t bytes) { // 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 @@ int applypatch(const char* source_filename, const char* target_filename, 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 @@ static int GenerateTarget(const FileContents& source_file, const std::unique_ptr 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 @@ static int GenerateTarget(const FileContents& source_file, const std::unique_ptr 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 @@ static int GenerateTarget(const FileContents& source_file, const std::unique_ptr } // 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 65ee614ef..912dbbdd8 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 @@ void ShowBSDiffLicense() { ); } -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 @@ int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value 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 331cae265..ea364d8e6 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 @@ static std::set<std::string> FindExpendableFiles() { 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 @@ static std::set<std::string> FindExpendableFiles() { } 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 fc240644f..674cc2b16 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 @@ static inline bool Write4(int fd, int32_t value) { 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; - - 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"; - } - - int GetType() const { - return type_; - } - size_t GetRawDataLength() const { - return raw_data_len_; - } - const std::string& GetEntryName() const { - return entry_name_; - } - - // 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() const { - printf("type %d start %zu len %zu\n", type_, start_, DataLengthForPatch()); - } - - 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); - - bool operator==(const ImageChunk& other) const; - bool operator!=(const ImageChunk& other) const { - return !(*this == other); - } - - 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); - - /* - * 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); +// 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; + + if (*length <= residual) { + *length = 0; + return false; + } + + // 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; + } + + // Trim the data in the end. + *length -= residual; + return true; +} + +// 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; + } + + // TODO find the largest non-overlap chunk. + LOG(INFO) << "Removing block " << used_ranges.ToString() << " from " << *start << " - " + << *start + *length - 1; + + // 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; + } + + LOG(WARNING) << "Failed to remove the overlapped block ranges; skip the source"; + return false; +} + +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 @@ size_t ImageChunk::DataLengthForPatch() const { 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 @@ bool ImageChunk::operator==(const ImageChunk& other) const { 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 @@ bool ImageChunk::SetBonusData(const std::vector<uint8_t>& bonus_data) { 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 @@ void ImageChunk::MergeAdjacentNormal(const ImageChunk& other) { raw_data_len_ = raw_data_len_ + other.raw_data_len_; } +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; + } + + 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) { - printf("attempt to reconstruct non-deflate chunk\n"); + 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). + // 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 @@ bool ImageChunk::ReconstructDeflateChunk() { } /* - * 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 @@ bool ImageChunk::TryReconstruction(int level) { 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 @@ bool ImageChunk::TryReconstruction(int level) { 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,195 +441,830 @@ bool ImageChunk::TryReconstruction(int level) { 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"); - return false; +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_); } +} - // 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) { - // double-check: this archive consists of a single "disk". - CHECK_EQ(get_unaligned<uint16_t>(&zip_file[i + 4]), 0); +// 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; + } +} - uint16_t comment_length = get_unaligned<uint16_t>(&zip_file[i + 20]); - size_t file_size = i + 22 + comment_length; - CHECK_LE(file_size, zip_file.size()); - *input_file_size = file_size; - return true; +// 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; } } - // EOCD not found, this file is likely not a valid zip file. - 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); + } } -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); +bool Image::ReadFile(const std::string& filename, std::vector<uint8_t>* file_content) { + CHECK(file_content != nullptr); - android::base::unique_fd fd(open(filename, O_RDONLY)); + android::base::unique_fd fd(open(filename.c_str(), O_RDONLY)); if (fd == -1) { - printf("failed to open \"%s\" %s\n", filename, strerror(errno)); + PLOG(ERROR) << "Failed to open " << filename; return false; } struct stat st; if (fstat(fd, &st) != 0) { - printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); + PLOG(ERROR) << "Failed to stat " << filename; 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)); + file_content->resize(sz); + if (!android::base::ReadFully(fd, file_content->data(), sz)) { + PLOG(ERROR) << "Failed to read " << filename; return false; } fd.reset(); - // Trim the trailing zeros before we pass the file to ziparchive handler. + 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(*zip_file, &zipfile_size)) { - printf("failed to parse the actual size of %s\n", filename); + if (!GetZipFileSize(&zipfile_size)) { + LOG(ERROR) << "Failed to parse the actual size of " << filename; return false; } ZipArchiveHandle handle; - int err = OpenArchiveFromMemory(zip_file->data(), zipfile_size, filename, &handle); + int err = OpenArchiveFromMemory(const_cast<uint8_t*>(file_content_.data()), zipfile_size, + filename.c_str(), &handle); if (err != 0) { - printf("failed to open zip file %s: %s\n", filename, ErrorCodeString(err)); + LOG(ERROR) << "Failed to open zip file " << 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; + 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) { - printf("failed to iterate over entries in %s: %s\n", filename, ErrorCodeString(ret)); - CloseArchive(handle); + 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) { - std::string entryname(name.name, name.name + name.name_length); - temp_entries.push_back(std::make_pair(entryname, entry)); + 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) { - printf("Error while iterating over zip entries: %s\n", ErrorCodeString(ret)); - CloseArchive(handle); + 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; - }); + [](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()); + // 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 < zip_file->size()) { + while (pos < file_content_.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); + // 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; } - 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; + 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 deflate section. + // 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 = zip_file->size() - pos; + raw_data_len = file_content_.size() - pos; } - chunks->emplace_back(CHUNK_NORMAL, pos, zip_file, raw_data_len); + chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, raw_data_len); pos += raw_data_len; } - CloseArchive(handle); 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); +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; +} - android::base::unique_fd fd(open(filename, O_RDONLY)); - if (fd == -1) { - printf("failed to open \"%s\" %s\n", filename, strerror(errno)); +// 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 +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; } - struct stat st; - if (fstat(fd, &st) != 0) { - printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); + + // Look for End of central directory record of the zip file, and calculate the actual + // zip_file size. + 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>(&file_content_[i + 4]), 0); + + uint16_t comment_length = get_unaligned<uint16_t>(&file_content_[i + 20]); + size_t file_size = i + 22 + comment_length; + CHECK_LE(file_size, file_content_.size()); + *input_file_size = file_size; + return true; + } + } + } + + // EOCD not found, this file is likely not a valid zip file. + return false; +} + +ImageChunk ZipModeImage::PseudoSource() const { + CHECK(is_source_); + return ImageChunk(CHUNK_NORMAL, 0, &file_content_, file_content_.size()); +} + +const ImageChunk* ZipModeImage::FindChunkByName(const std::string& name, bool find_normal) const { + if (name.empty()) { + return nullptr; + } + for (auto& chunk : chunks_) { + if (chunk.GetType() != CHUNK_DEFLATE && !find_normal) { + continue; + } + + if (chunk.GetEntryName() == name) { + return &chunk; + } + + // 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) + } + + 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; +} + +// 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_; + + 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--; + } + } + + // 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; } - size_t pos = 0; + 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 @@ static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks, 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 @@ static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks, 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 @@ static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks, // 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 @@ static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks, 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 @@ static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks, 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)); +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; } - 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); - return false; - } + LOG(INFO) << " using " << bonus_data.size() << " bytes of bonus data"; + return true; +} - android::base::unique_fd patch_fd(open(ptemp, O_RDONLY)); - if (patch_fd == -1) { - printf("failed to open %s: %s\n", ptemp, strerror(errno)); +// 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; + 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(); + } } - patch_data->resize(sz); - if (!android::base::ReadFully(patch_fd, patch_data->data(), sz)) { - printf("failed to read \"%s\" %s\n", ptemp, strerror(errno)); + + // 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; } - unlink(ptemp); - tgt->SetSourceInfo(*src); - 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; + + int opt; + int option_index; + optind = 0; // Reset the getopt state so that we can call it multiple times for test. + + 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)) { - printf("failed to read bonus file %s: %s\n", argv[2], strerror(errno)); - 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; } + } - argc -= 2; - argv += 2; + if (!verbose) { + android::base::SetMinimumLogSeverity(android::base::WARNING); } - if (argc != 4) { - printf("usage: %s [-z] [-b <bonus-file>] <src-img> <tgt-img> <patch-file>\n", - argv[0]); + 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"); - return 1; - } - if (!ReadZip(argv[2], &tgt_chunks, &tgt_file, false)) { - printf("failed to break apart target zip file\n"); - return 1; - } - } else { - if (!ReadImage(argv[1], &src_chunks, &src_file)) { - printf("failed to break apart source image\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 (!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 (!ZipModeImage::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); + + // 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; } - } - } - 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; - } + 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 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 (!ZipModeImage::GeneratePatches(split_tgt_images, split_src_images, split_src_ranges, + argv[optind + 2], split_info_file, debug_dir)) { + return 1; } - if (src == nullptr) { - tgt_chunks[i].ChangeDeflateChunkToNormal(); - } else if (tgt_chunks[i] == *src) { - tgt_chunks[i].ChangeDeflateChunkToNormal(); - src->ChangeDeflateChunkToNormal(); - } + } else if (!ZipModeImage::GeneratePatches(tgt_image, src_image, argv[optind + 2])) { + return 1; } - } - - // 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"); + ImageModeImage src_image(true); + ImageModeImage tgt_image(false); + + if (!src_image.Initialize(argv[optind])) { 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; - } + if (!tgt_image.Initialize(argv[optind + 1])) { + 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; - } + if (!ImageModeImage::CheckAndProcessChunks(&tgt_image, &src_image)) { + 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); - } + if (!bonus_data.empty() && !src_image.SetBonusData(bonus_data)) { + return 1; + } - // 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 df75f98d4..3682d6115 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 @@ static inline int32_t Read4(const void *address) { // 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 @@ static bool ApplyBSDiffPatchAndStreamOutput(const uint8_t* src_data, size_t src_ 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 @@ static bool ApplyBSDiffPatchAndStreamOutput(const uint8_t* src_data, size_t src_ 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 @@ static bool ApplyBSDiffPatchAndStreamOutput(const uint8_t* src_data, size_t src_ 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 @@ static bool ApplyBSDiffPatchAndStreamOutput(const uint8_t* src_data, size_t src_ 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 @@ int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value* 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 581360ef1..912ead1fa 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 LoadFileContents(const char* filename, FileContents* file); 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 000000000..084807237 --- /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 e5002934f..000000000 --- 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 27e3d9765..9814d7122 100644 --- a/boot_control/Android.mk +++ b/boot_control/Android.mk @@ -24,8 +24,7 @@ LOCAL_CFLAGS := \ -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 f0d76e718..c81c67bdb 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 f91446b43..aaeffdc5c 100644 --- a/bootloader_message/bootloader_message.cpp +++ b/bootloader_message/bootloader_message.cpp @@ -159,14 +159,8 @@ bool clear_bootloader_message(std::string* err) { 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 @@ bool update_bootloader_message(const std::vector<std::string>& options, std::str 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); +} + +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)); + 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 2ffbfc9e3..95c19ae54 100644 --- a/bootloader_message/include/bootloader_message/bootloader_message.h +++ b/bootloader_message/include/bootloader_message/bootloader_message.h @@ -103,13 +103,17 @@ static_assert(sizeof(struct bootloader_message) == 2048, * 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 @@ bool write_bootloader_message(const std::vector<std::string>& options, std::stri // 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); @@ -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 61501869e..f881daff6 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]) == @@ -66,6 +66,7 @@ class Device { 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 000000000..42947eb4e --- /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 d8058c16f..000000000 --- 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 f1b56284c..000000000 --- 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 54ab3325c..6823b7339 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* IfElseFn(const char* name, State* state, const std::vector<std::unique_pt 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, const char* format, ...) { } 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; -} - -State::State(const std::string& script, void* cookie) : - script(script), - cookie(cookie) { + 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), error_code(kNoError), cause_code(kNoCause) {} diff --git a/edify/expr.h b/edify/include/edify/expr.h index 4838d20c0..770d1cf0d 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 b764d1699..4e04003b1 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 @@ std::string string_buffer; %x STR +%option noinput +%option nounput %option noyywrap %% diff --git a/edify/parser.yy b/edify/parser.yy index b1685eb1f..bd2e0105f 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 d8121cc4e..0fc6c4c13 100644 --- a/etc/init.rc +++ b/etc/init.rc @@ -11,6 +11,7 @@ on init 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 b0ecf96be..46bdf1774 100644 --- a/fuse_sdcard_provider.cpp +++ b/fuse_sdcard_provider.cpp @@ -14,72 +14,70 @@ * limitations under the License. */ -#include <stdlib.h> +#include "fuse_sdcard_provider.h" + +#include <errno.h> +#include <fcntl.h> #include <stdio.h> +#include <stdlib.h> #include <string.h> -#include <errno.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); - - 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; - } +static int read_block_file(const file_data& fd, uint32_t block, uint8_t* buffer, + uint32_t fetch_size) { + off64_t offset = static_cast<off64_t>(block) * fd.block_size; + if (TEMP_FAILURE_RETRY(lseek64(fd.fd, offset, SEEK_SET)) == -1) { + fprintf(stderr, "seek on sdcard failed: %s\n", strerror(errno)); + return -EIO; + } - if (!android::base::ReadFully(fd->fd, buffer, fetch_size)) { - fprintf(stderr, "read on sdcard failed: %s\n", strerror(errno)); - return -EIO; - } - - return 0; -} + if (!android::base::ReadFully(fd.fd, buffer, fetch_size)) { + fprintf(stderr, "read on sdcard failed: %s\n", strerror(errno)); + return -EIO; + } -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 219374fdb..1c7e98f01 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; - - hdr.len = len + sizeof(hdr); - hdr.error = 0; - hdr.unique = unique; - - 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)); - } +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; + + 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; + + 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; - - - /* 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; - } +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; + } - 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; - - attr->ino = nodeid; - attr->size = size; - attr->blocks = (size == 0) ? 0 : (((size-1) / attr->blksize) + 1); - attr->mode = mode; +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; } -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; - - 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; - } +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; + } - 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; - - 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; - } +static int handle_lookup(void* data, const fuse_data* fd, const fuse_in_header* hdr) { + if (data == nullptr) return -ENOENT; + + fuse_entry_out out = {}; + out.entry_valid = 10; + out.attr_valid = 10; + + 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; + 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; - } +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; - } + 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); - } + 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; + int result = fd->vtab.read_block(block, fd->block_data, fetch_size); + if (result < 0) return result; - fd->curr_block = block; + 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; - } + // 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. - int i; - for (i = 0; i < SHA256_DIGEST_LENGTH; ++i) { - if (blockhash[i] != 0) { - fd->curr_block = -1; - return -EIO; - } - } + SHA256Digest hash; + SHA256(fd->block_data, fd->block_size, hash.data()); - memcpy(blockhash, hash, SHA256_DIGEST_LENGTH); + 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; - - if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; - - 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.) - - outhdr.len = sizeof(outhdr) + size; - outhdr.error = 0; - outhdr.unique = hdr->unique; - vec[0].iov_base = &outhdr; - vec[0].iov_len = sizeof(outhdr); - - uint32_t block = offset / fd->block_size; - result = fetch_block(fd, block); +static int handle_read(void* data, fuse_data* fd, const fuse_in_header* hdr) { + 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; + + // 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; + + struct iovec vec[3]; + vec[0].iov_base = &outhdr; + vec[0].iov_len = sizeof(outhdr); + + 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 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_si 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 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_si 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 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_si { 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 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_si 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 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_si } 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 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_si } 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 c0b16efbe..1b34cbdb0 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 7fbf5c01f..d05893171 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 @@ static int check_newer_ab_build(ZipArchiveHandle zip) { 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 @@ int update_binary_command(const std::string& package, ZipArchiveHandle zip, *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 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b if (ret) { close(pipefd[0]); close(pipefd[1]); + log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure)); return ret; } @@ -385,6 +396,7 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b 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 @@ static int really_install_package(const std::string& path, bool* wipe_cache, boo 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 @@ int install_package(const std::string& path, bool* wipe_cache, const std::string 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 de0b0c890..50e3b34ef 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 @@ LOCAL_SRC_FILES := \ 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 0f4c2563d..9bd3f2392 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 "fuse_adb_provider.h" + +#include <errno.h> #include <stdio.h> +#include <stdlib.h> #include <string.h> -#include <errno.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); - - if (!WriteFdFmt(ad->sfd, "%08u", block)) { - fprintf(stderr, "failed to write to adb host: %s\n", strerror(errno)); - return -EIO; - } +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 (!ReadFdExactly(ad->sfd, buffer, fetch_size)) { - fprintf(stderr, "failed to read from adb host: %s\n", strerror(errno)); - return -EIO; - } - - return 0; -} + if (!ReadFdExactly(ad.sfd, buffer, fetch_size)) { + fprintf(stderr, "failed to read from adb host: %s\n", strerror(errno)); + return -EIO; + } -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 9941709b9..36d86d539 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 31be2a64e..00250e505 100644 --- a/minadbd/fuse_adb_provider_test.cpp +++ b/minadbd/fuse_adb_provider_test.cpp @@ -46,8 +46,8 @@ TEST(fuse_adb_provider, read_block_adb) { 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 @@ TEST(fuse_adb_provider, read_block_adb_fail_write) { 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 61c06cc0a..043c51a6a 100644 --- a/minadbd/minadbd_services.cpp +++ b/minadbd/minadbd_services.cpp @@ -58,20 +58,20 @@ static int create_service_thread(void (*func)(int, const std::string&), const st 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 6522fcfd2..ae1552b1b 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_SRC_FILES := \ 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 @@ else LOCAL_CFLAGS += -DOVERSCAN_PERCENT=0 endif +ifneq ($(TARGET_RECOVERY_DEFAULT_ROTATION),) + LOCAL_CFLAGS += -DDEFAULT_ROTATION=$(TARGET_RECOVERY_DEFAULT_ROTATION) +else + LOCAL_CFLAGS += -DDEFAULT_ROTATION=ROTATION_NONE +endif + include $(BUILD_STATIC_LIBRARY) +# libminui (shared library) +# =============================== # Used by OEMs for factory test images. include $(CLEAR_VARS) LOCAL_MODULE := libminui @@ -68,7 +79,7 @@ LOCAL_SHARED_LIBRARIES := \ 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 24c2a8277..2894c3b6b 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 3bfce11d8..56f471bce 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_percent = OVERSCAN_PERCENT; 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); } -void gr_text(const GRFont* font, int x, int y, const char *s, bool bold) -{ - if (!font->texture || gr_current_a == 0) return; +// 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); + } +} - bold = bold && (font->texture->height != font->char_height); +// 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; + } +} - x += overscan_offset_x; - y += overscan_offset_y; +// 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; +} - unsigned char ch; - while ((ch = *s++)) { - if (outside(x, y) || outside(x+font->char_width-1, y+font->char_height-1)) break; +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 || !font->texture || (gr_current & alpha_mask) == 0) return; + + if (font->texture->pixel_bytes != 1) { + printf("gr_text: font has wrong format\n"); + return; + } - if (ch < ' ' || ch > '~') { - ch = '?'; - } + bold = bold && (font->texture->height != font->char_height); - 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; + x += overscan_offset_x; + y += overscan_offset_y; - text_blend(src_p, font->texture->row_bytes, - dst_p, gr_draw->row_bytes, - font->char_width, 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 += 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; - - x2 += overscan_offset_x; - y2 += overscan_offset_y; - - 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; - } +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; + + if (outside(x1, y1) || outside(x2 - 1, y2 - 1)) return; + + int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes; + uint32_t* p = pixel_at(gr_draw, x1, y1, row_pixels); + 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 (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 (source == NULL) return; - if (outside(dx, dy) || outside(dx+w-1, dy+h-1)) return; + if (gr_draw->pixel_bytes != source->pixel_bytes) { + printf("gr_blit: source has wrong format\n"); + 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; + 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); + } + } 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; - } - - printf("failed to read font: res=%d\n", res); +static void gr_init_font(void) { + int res = gr_init_font("font", &gr_font); + if (res == 0) { + return; + } + printf("failed to read font: res=%d\n", res); - // fall back to the compiled-in font. - gr_font = static_cast<GRFont*>(calloc(1, sizeof(*gr_font))); - gr_font->texture = static_cast<GRSurface*>(malloc(sizeof(*gr_font->texture))); - gr_font->texture->width = font.width; - gr_font->texture->height = font.height; - gr_font->texture->row_bytes = font.width; - gr_font->texture->pixel_bytes = 1; + // 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 @@ int gr_init() { 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 @@ void gr_exit() { } 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 017ddde75..f9da19999 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 @@ void gr_texticon(int x, int y, GRSurface* icon); const GRFont* gr_sys_font(); int gr_init_font(const char* name, GRFont** dest); -void gr_text(const GRFont* font, int x, int y, const char *s, bool bold); -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 @@ int res_create_display_surface(const char* name, GRSurface** pSurface); // 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_alpha_surface(const char* name, GRSurface** pSurface); 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 8f8d36d27..52ab60b1b 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 @@ static GRSurface* malloc_surface(size_t data_size) { 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; - - 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; - } +// 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); - bytesRead = fread(header, 1, sizeof(header), fp); - if (bytesRead != sizeof(header)) { - result = -2; - goto exit; - } + ~PngHandler(); - if (png_sig_cmp(header, 0, sizeof(header))) { - result = -3; - goto exit; - } + png_uint_32 width() const { + return width_; + } - *png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (!*png_ptr) { - result = -4; - goto exit; - } + png_uint_32 height() const { + return height_; + } - *info_ptr = png_create_info_struct(*png_ptr); - if (!*info_ptr) { - result = -5; - goto exit; - } + png_byte channels() const { + return channels_; + } - if (setjmp(png_jmpbuf(*png_ptr))) { - result = -6; - goto exit; - } + png_structp png_ptr() const { + return png_ptr_; + } - png_init_io(*png_ptr, fp); - png_set_sig_bytes(*png_ptr, sizeof(header)); - png_read_info(*png_ptr, *info_ptr); - - png_get_IHDR(*png_ptr, *info_ptr, width, height, &bit_depth, - &color_type, NULL, NULL, NULL); - - *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); - result = -7; - goto exit; - } + png_infop info_ptr() const { + return info_ptr_; + } - return result; + int error_code() const { + return error_code_; + }; - exit: - if (result < 0) { - png_destroy_read_struct(png_ptr, info_ptr, NULL); - } - if (fp != NULL) { - fclose(fp); - } + operator bool() const { + return error_code_ == 0; + } + + private: + png_structp png_ptr_{ nullptr }; + png_infop info_ptr_{ nullptr }; + png_uint_32 width_; + png_uint_32 height_; + png_byte channels_; + + // The |error_code_| is set to a negative value if an error occurs when opening the png file. + int error_code_; + // After initialization, we'll keep the file pointer open before destruction of PngHandler. + std::unique_ptr<FILE, decltype(&fclose)> png_fp_; +}; + +PngHandler::PngHandler(const std::string& name) : error_code_(0), png_fp_(nullptr, fclose) { + std::string res_path = android::base::StringPrintf("/res/images/%s.png", name.c_str()); + png_fp_.reset(fopen(res_path.c_str(), "rbe")); + if (!png_fp_) { + error_code_ = -1; + return; + } + + unsigned char header[8]; + size_t bytesRead = fread(header, 1, sizeof(header), png_fp_.get()); + if (bytesRead != sizeof(header)) { + error_code_ = -2; + return; + } + + 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; + } +} - return result; +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 @@ static void transform_rgb_to_draw(unsigned char* input_row, } 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 = NULL; - - result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels); - if (result < 0) return result; - - surface = init_display_surface(width, height); - if (surface == NULL) { - result = -8; - goto exit; - } + *pSurface = nullptr; + + PngHandler png_handler(name); + if (!png_handler) return png_handler.error_code(); + + png_structp png_ptr = png_handler.png_ptr(); + png_uint_32 width = png_handler.width(); + png_uint_32 height = png_handler.height(); + + 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; - - *pSurface = NULL; - *frames = -1; - - result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels); - if (result < 0) return result; - - *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); + GRSurface*** pSurface) { + *pSurface = nullptr; + *frames = -1; + + PngHandler png_handler(name); + if (!png_handler) return png_handler.error_code(); + + 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; + 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; - } - for (int i = 0; i < *frames; ++i) { - surface[i] = init_display_surface(width, height / *frames); - if (surface[i] == 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; } + } #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 @@ bool matches_locale(const std::string& prefix, const std::string& locale) { // match the locale string without the {script} section. // For instance, prefix == "en" matches locale == "en-US", prefix == "sr-Latn" matches locale // == "sr-Latn-BA", and prefix == "zh-CN" matches locale == "zh-Hans-CN". - if (android::base::StartsWith(locale, prefix.c_str())) { + if (android::base::StartsWith(locale, prefix)) { return true; } @@ -396,75 +409,90 @@ bool matches_locale(const std::string& prefix, const std::string& locale) { 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 = NULL; - - if (locale == NULL) { - return result; - } + *pSurface = nullptr; + if (locale == nullptr) { + return 0; + } - result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels); - if (result < 0) return result; + PngHandler png_handler(name); + if (!png_handler) return png_handler.error_code(); - if (channels != 1) { - result = -7; - goto exit; - } + if (png_handler.channels() != 1) { + return -7; + } - 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); - } + 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; + } - *pSurface = surface; - break; - } else { - int i; - for (i = 0; i < h; ++i, ++y) { - png_read_row(png_ptr, row.data(), NULL); - } - } + for (int i = 0; i < h; ++i, ++y) { + png_read_row(png_ptr, row.data(), nullptr); } + } -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 000000000..b39d5bee2 --- /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 ec4cdb365..000000000 --- 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 8590833ee..3993948ff 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 @@ std::string fault_fname(const char* io_type) { 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 index 4adbdd121..cc4bfd2ad 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 index 9428f1b1f..45e481a62 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 faae5275d..63ef18e26 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 6514782bf..63e2445af 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 a2eaa0402..75cf69148 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 @@ cc_library_static { ], cflags: [ + "-D_FILE_OFFSET_BITS=64", "-Werror", "-Wall", ], + + export_include_dirs: [ + "include", + ], } diff --git a/otautil/DirUtil.cpp b/otautil/DirUtil.cpp index e08e360c0..61c832813 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 <dirent.h> +#include <errno.h> #include <stdlib.h> -#include <string.h> -#include <stdio.h> -#include <sys/types.h> #include <sys/stat.h> +#include <sys/types.h> #include <unistd.h> -#include <errno.h> -#include <dirent.h> -#include <limits.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; - - /* 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 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('/'); - } - - /* 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 = '/'; +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; + } + + // 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; } + path.resize(pos + 1); + } else { + // Make sure that the path ends in a slash. + path.push_back('/'); + } + + // See if it already exists. + DirStatus ds = dir_status(path); + if (ds == DirStatus::DDIR) { return 0; -} - -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) { - return -1; - } - - /* a file, so unlink it */ - if (!S_ISDIR(st.st_mode)) { - return unlink(path); + } else if (ds == DirStatus::DILLEGAL) { + return -1; + } + + // 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; } - - /* a directory, so open handle */ - dir = opendir(path); - if (dir == NULL) { + 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; - } - - /* 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; - } - /* 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; + if (err != 0) { + return -1; + } + break; + } + default: + // Already exists. + break; } - - /* 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 85b83c387..000000000 --- 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 dfa215073..48336ad07 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 @@ bool MemMapping::MapBlockFile(const std::string& filename) { } // 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 @@ bool MemMapping::MapBlockFile(const std::string& filename) { 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 13d36432a..5d9bd45c0 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 @@ int GetMaxValueFromThermalZone() { } 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 000000000..8ddefec5e --- /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 000000000..85d6c16d1 --- /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 index 52f6d20a7..52f6d20a7 100644 --- a/otautil/SysUtil.h +++ b/otautil/include/otautil/SysUtil.h diff --git a/otautil/ThermalUtil.h b/otautil/include/otautil/ThermalUtil.h index 43ab55940..43ab55940 100644 --- a/otautil/ThermalUtil.h +++ b/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 000000000..f2f663816 --- /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 index 9fe047c91..b0ff42d8d 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 @@ enum ErrorCode { kBootreasonInBlacklist, kPackageCompatibilityFailure, kScriptExecutionFailure, + kMapFileFailure, + kForkUpdateBinaryFailure, + kUpdateBinaryCommandFailure, }; -enum CauseCode { +enum CauseCode : int { kNoCause = -1, kArgsParsingFailure = 100, kStashCreationFailure, @@ -48,7 +51,7 @@ enum CauseCode { kVendorFailure = 200 }; -enum UncryptErrorCode { +enum UncryptErrorCode : int { kUncryptNoError = -1, kUncryptErrorPlaceholder = 50, kUncryptTimeoutError = 100, @@ -68,6 +71,8 @@ enum UncryptErrorCode { kUncryptFileCloseError, kUncryptFileRenameError, kUncryptPackageMissingError, + kUncryptRealpathFindError, + kUncryptBlockDeviceFindError, }; -#endif // _ERROR_CODE_H_ +#endif // _ERROR_CODE_H_ diff --git a/print_sha1.h b/otautil/include/otautil/print_sha1.h index 1f8589519..03a8d292a 100644 --- a/print_sha1.h +++ b/otautil/include/otautil/print_sha1.h @@ -23,25 +23,25 @@ #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; + 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); +[[maybe_unused]] 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); +[[maybe_unused]] 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); +[[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 000000000..e91d02ca6 --- /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 000000000..96955b9d0 --- /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/recovery-persist.rc b/recovery-persist.rc index 6761627d5..135a3c33d 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 14b05cca4..9fefc819b 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 07bd7b9d4..07ec5cfb6 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_DIR = "/tmp/convert_fbe"; static const char *CONVERT_FBE_FILE = "/tmp/convert_fbe/convert_fbe"; static const char *CACHE_ROOT = "/cache"; static const char *DATA_ROOT = "/data"; +static const char* METADATA_ROOT = "/metadata"; static const char *SDCARD_ROOT = "/sdcard"; static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install"; @@ -125,6 +126,10 @@ static const int BATTERY_WITH_CHARGER_OK_PERCENTAGE = 15; static constexpr const char* RECOVERY_WIPE = "/etc/recovery.wipe"; static constexpr const char* DEFAULT_LOCALE = "en-US"; +// 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 @@ struct selabel_handle* sehandle; * 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; - } - - // 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); +// 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; + } - 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 @@ static bool erase_volume(const char* volume) { 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 @@ static bool wipe_data(Device* device) { 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 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { 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 Device::BuiltinAction prompt_and_wait(Device* device, int status) { } } -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 @@ void ui_print(const char* format, ...) { 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); - - android::BatteryMonitor monitor; - monitor.init(&healthd_config); - - 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); + 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; + + 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 + }; + + 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 @@ static void set_retry_bootloader_message(int retry_count, const std::vector<std: } } - // 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 @@ static void log_failure_code(ErrorCode code, const char *update_package) { } 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); - - // 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); - - // 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(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; + // 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; + + __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; + } + + 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; } + } - 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; - } + if (locale.empty()) { + 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(); - } else { - ui = device->GetUI(); + 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(); - } + 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); + // 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); - } + 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); + 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"); - } + sehandle = selinux_android_file_context_handle(); + selinux_android_set_sehandle(sehandle); + if (!sehandle) { + ui->Print("Warning: No file_contexts\n"); + } - device->StartRecovery(); + device->StartRecovery(); - printf("Command:"); - for (const auto& arg : args) { - printf(" \"%s\"", arg.c_str()); - } - printf("\n\n"); + printf("Command:"); + for (const auto& arg : args) { + printf(" \"%s\"", arg.c_str()); + } + printf("\n\n"); - property_list(print_property, NULL); - printf("\n"); + property_list(print_property, nullptr); + printf("\n"); - ui->Print("Supported API: %d\n", RECOVERY_API_VERSION); + ui->Print("Supported API: %d\n", kRecoveryApiVersion); - int status = INSTALL_SUCCESS; + 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 (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 { - 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; + 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 { + // 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_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(); } + } } - ui->Print("\nInstall from ADB complete (status: %d).\n", status); - if (sideload_auto_reboot) { - ui->Print("Rebooting automatically.\n"); + // 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 (!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); } + } 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); + } + 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); - } + 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; - } + 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(); + // 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; + 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; + 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; + 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 Binary files differindex 0982544d2..34c56a966 100644 --- a/res-hdpi/images/erasing_text.png +++ b/res-hdpi/images/erasing_text.png diff --git a/res-hdpi/images/error_text.png b/res-hdpi/images/error_text.png Binary files differindex 3a06f6eb1..2a96053da 100644 --- a/res-hdpi/images/error_text.png +++ b/res-hdpi/images/error_text.png diff --git a/res-hdpi/images/installing_security_text.png b/res-hdpi/images/installing_security_text.png Binary files differindex b1acd2336..97e1f11b3 100644 --- a/res-hdpi/images/installing_security_text.png +++ b/res-hdpi/images/installing_security_text.png diff --git a/res-hdpi/images/installing_text.png b/res-hdpi/images/installing_text.png Binary files differindex f0f5d8b6c..1d591eb8b 100644 --- a/res-hdpi/images/installing_text.png +++ b/res-hdpi/images/installing_text.png diff --git a/res-hdpi/images/no_command_text.png b/res-hdpi/images/no_command_text.png Binary files differindex def503678..977fcfaff 100644 --- a/res-hdpi/images/no_command_text.png +++ b/res-hdpi/images/no_command_text.png diff --git a/res-mdpi/images/erasing_text.png b/res-mdpi/images/erasing_text.png Binary files differindex 82b4461ba..dcd0ea656 100644 --- a/res-mdpi/images/erasing_text.png +++ b/res-mdpi/images/erasing_text.png diff --git a/res-mdpi/images/error_text.png b/res-mdpi/images/error_text.png Binary files differindex adb45131f..2152dad83 100644 --- a/res-mdpi/images/error_text.png +++ b/res-mdpi/images/error_text.png diff --git a/res-mdpi/images/installing_security_text.png b/res-mdpi/images/installing_security_text.png Binary files differindex 54e556448..d1ac4cad6 100644 --- a/res-mdpi/images/installing_security_text.png +++ b/res-mdpi/images/installing_security_text.png diff --git a/res-mdpi/images/installing_text.png b/res-mdpi/images/installing_text.png Binary files differindex d42331820..c9b6d7185 100644 --- a/res-mdpi/images/installing_text.png +++ b/res-mdpi/images/installing_text.png diff --git a/res-mdpi/images/no_command_text.png b/res-mdpi/images/no_command_text.png Binary files differindex cd77ff457..f77ad1325 100644 --- a/res-mdpi/images/no_command_text.png +++ b/res-mdpi/images/no_command_text.png diff --git a/res-xhdpi/images/erasing_text.png b/res-xhdpi/images/erasing_text.png Binary files differindex 333edbe27..e22b27479 100644 --- a/res-xhdpi/images/erasing_text.png +++ b/res-xhdpi/images/erasing_text.png diff --git a/res-xhdpi/images/error_text.png b/res-xhdpi/images/error_text.png Binary files differindex e26258438..e4c27e1fc 100644 --- a/res-xhdpi/images/error_text.png +++ b/res-xhdpi/images/error_text.png diff --git a/res-xhdpi/images/installing_security_text.png b/res-xhdpi/images/installing_security_text.png Binary files differindex e0f0f3ea7..7ba12b667 100644 --- a/res-xhdpi/images/installing_security_text.png +++ b/res-xhdpi/images/installing_security_text.png diff --git a/res-xhdpi/images/installing_text.png b/res-xhdpi/images/installing_text.png Binary files differindex a7e67f512..567988e7f 100644 --- a/res-xhdpi/images/installing_text.png +++ b/res-xhdpi/images/installing_text.png diff --git a/res-xhdpi/images/no_command_text.png b/res-xhdpi/images/no_command_text.png Binary files differindex 13aef7b71..a682abbef 100644 --- a/res-xhdpi/images/no_command_text.png +++ b/res-xhdpi/images/no_command_text.png diff --git a/res-xxhdpi/images/erasing_text.png b/res-xxhdpi/images/erasing_text.png Binary files differindex 80e7c475e..6cc953b6d 100644 --- a/res-xxhdpi/images/erasing_text.png +++ b/res-xxhdpi/images/erasing_text.png diff --git a/res-xxhdpi/images/error_text.png b/res-xxhdpi/images/error_text.png Binary files differindex 32a1965b8..0d5cea843 100644 --- a/res-xxhdpi/images/error_text.png +++ b/res-xxhdpi/images/error_text.png diff --git a/res-xxhdpi/images/installing_security_text.png b/res-xxhdpi/images/installing_security_text.png Binary files differindex c53c9ac21..5d105986a 100644 --- a/res-xxhdpi/images/installing_security_text.png +++ b/res-xxhdpi/images/installing_security_text.png diff --git a/res-xxhdpi/images/installing_text.png b/res-xxhdpi/images/installing_text.png Binary files differindex 38b18d20d..6e94fa28b 100644 --- a/res-xxhdpi/images/installing_text.png +++ b/res-xxhdpi/images/installing_text.png diff --git a/res-xxhdpi/images/no_command_text.png b/res-xxhdpi/images/no_command_text.png Binary files differindex a0666d8dc..40ab484d9 100644 --- a/res-xxhdpi/images/no_command_text.png +++ b/res-xxhdpi/images/no_command_text.png diff --git a/res-xxxhdpi/images/erasing_text.png b/res-xxxhdpi/images/erasing_text.png Binary files differindex 4f7b37b51..cc730992b 100644 --- a/res-xxxhdpi/images/erasing_text.png +++ b/res-xxxhdpi/images/erasing_text.png diff --git a/res-xxxhdpi/images/error_text.png b/res-xxxhdpi/images/error_text.png Binary files differindex 052bf2142..fea3cfc95 100644 --- a/res-xxxhdpi/images/error_text.png +++ b/res-xxxhdpi/images/error_text.png diff --git a/res-xxxhdpi/images/installing_security_text.png b/res-xxxhdpi/images/installing_security_text.png Binary files differindex a9e739b17..ed77d0889 100644 --- a/res-xxxhdpi/images/installing_security_text.png +++ b/res-xxxhdpi/images/installing_security_text.png diff --git a/res-xxxhdpi/images/installing_text.png b/res-xxxhdpi/images/installing_text.png Binary files differindex 2d1948677..965910648 100644 --- a/res-xxxhdpi/images/installing_text.png +++ b/res-xxxhdpi/images/installing_text.png diff --git a/res-xxxhdpi/images/no_command_text.png b/res-xxxhdpi/images/no_command_text.png Binary files differindex ee0c23865..4e6f3639d 100644 --- a/res-xxxhdpi/images/no_command_text.png +++ b/res-xxxhdpi/images/no_command_text.png @@ -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 (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"); +} - 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"); +Volume* volume_for_mount_point(const std::string& mount_point) { + return fs_mgr_get_entry_for_mount_point(fstab, mount_point); } -Volume* volume_for_path(const char* path) { - return fs_mgr_get_entry_for_mount_point(fstab, path); +// 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; - } + 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 (!scan_mounted_volumes()) { + LOG(ERROR) << "Failed to scan mounted volumes"; + return -1; + } - if (!mount_point) { - mount_point = v->mount_point; - } + 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; + } - 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)) { + 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; + } } - 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; + 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; + 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 @@ static ssize_t get_file_size(int fd, uint64_t reserve_len) { } 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; + 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; } - if (strcmp(v->fs_type, "ramdisk") == 0) { - // you can't format the ramdisk. - LOG(ERROR) << "can't format_volume \"" << volume << "\""; - 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; } - if (strcmp(v->mount_point, volume) != 0) { - LOG(ERROR) << "can't give path \"" << volume << "\" to format_volume"; - 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 (ensure_path_unmounted(volume) != 0) { - LOG(ERROR) << "format_volume failed to unmount \"" << v->mount_point << "\""; - 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)); } - 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; + 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); } - LOG(ERROR) << "format_volume: fs_type \"" << v->fs_type << "\" unsupported"; + 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 @@ int setup_install_mounts() { 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; } } @@ -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 fc220215e..da008792c 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()); + 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()); + 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 809c213b6..007c33d44 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 b8f6ea28b..c8fb5aa75 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 @@ ScreenRecoveryUI::ScreenRecoveryUI() : 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 @@ ScreenRecoveryUI::ScreenRecoveryUI() 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 @@ ScreenRecoveryUI::ScreenRecoveryUI() intro_done(false), stage(-1), max_stage(-1), + locale_(""), + rtl_locale_(false), updateMutex(PTHREAD_MUTEX_INITIALIZER) {} GRSurface* ScreenRecoveryUI::GetCurrentFrame() const { @@ -105,7 +109,7 @@ GRSurface* ScreenRecoveryUI::GetCurrentText() const { } int ScreenRecoveryUI::PixelsFromDp(int dp) const { - return dp * density_; + return dp * kDensity; } // Here's the intended layout: @@ -145,8 +149,8 @@ int ScreenRecoveryUI::GetProgressBaseline() const { 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 @@ void ScreenRecoveryUI::draw_background_locked() { 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 @@ void ScreenRecoveryUI::draw_foreground_locked() { 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 @@ void ScreenRecoveryUI::draw_foreground_locked() { 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::SetColor(UIElement e) const { } } +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 @@ void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) con 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 @@ void ScreenRecoveryUI::draw_screen_locked() { 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 @@ void ScreenRecoveryUI::draw_screen_locked() { // 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 @@ bool ScreenRecoveryUI::InitTextParams() { } 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 @@ bool ScreenRecoveryUI::Init(const std::string& locale) { 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 @@ void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) 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 @@ void ScreenRecoveryUI::PutChar(char ch) { 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 @@ void ScreenRecoveryUI::ClearText() { 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 @@ void ScreenRecoveryUI::ShowFile(const char* filename) { 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 @@ void ScreenRecoveryUI::ShowFile(const char* filename) { 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 @@ void ScreenRecoveryUI::KeyLongPress(int) { // 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 8231a2ba0..f05761c42 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -32,6 +32,17 @@ struct GRSurface; // (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 @@ class ScreenRecoveryUI : public RecoveryUI { 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 @@ class ScreenRecoveryUI : public RecoveryUI { 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 @@ class ScreenRecoveryUI : public RecoveryUI { // 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 @@ class ScreenRecoveryUI : public RecoveryUI { int char_width_; int char_height_; - pthread_mutex_t updateMutex; - - virtual bool InitTextParams(); + // The locale that's used to show the rendered texts. + std::string locale_; + bool rtl_locale_; - 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(); + pthread_mutex_t updateMutex; - virtual void ShowFile(FILE*); - virtual void PrintV(const char*, bool, va_list); - void PutChar(char); - void ClearText(); + private: + void SetLocale(const std::string&); - 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 @@ -24,18 +24,18 @@ class StubRecoveryUI : public RecoveryUI { 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 @@ class StubRecoveryUI : public RecoveryUI { 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 f2497b8b3..b3584fe87 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -18,7 +18,7 @@ LOCAL_PATH := $(call my-dir) # 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 @@ LOCAL_STATIC_LIBRARIES := \ libutils \ libz \ libselinux \ - libbase + libbase \ + libBionicGtestMain LOCAL_SRC_FILES := \ unit/asn1_decoder_test.cpp \ @@ -46,11 +47,12 @@ include $(BUILD_NATIVE_TEST) # 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 @@ include $(BUILD_NATIVE_TEST) # Component tests include $(CLEAR_VARS) LOCAL_CFLAGS := \ + -Wall \ -Werror \ -D_FILE_OFFSET_BITS=64 @@ -142,9 +145,9 @@ LOCAL_STATIC_LIBRARIES := \ libdivsufsort \ libdivsufsort64 \ libfs_mgr \ - liblog \ libvintf_recovery \ libvintf \ + libhidl-gen-utils \ libtinyxml2 \ libselinux \ libext4_utils \ @@ -153,6 +156,7 @@ LOCAL_STATIC_LIBRARIES := \ libcrypto \ libbz \ libziparchive \ + liblog \ libutils \ libz \ libbase \ @@ -162,6 +166,7 @@ LOCAL_STATIC_LIBRARIES := \ libsquashfs_utils \ libcutils \ libbrotli \ + libBionicGtestMain \ $(tune2fs_static_libraries) testdata_files := $(call find-subdir-files, testdata/*) @@ -191,7 +196,7 @@ include $(BUILD_NATIVE_TEST) # 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_SRC_FILES := \ 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 f6b6922a4..514818e0a 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 @@ static const std::string kATxtSha1Sum("32c96a03dc8cd20097940f351bca6261ee5a1643" // 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 016fed9b1..61e06adb6 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 sha1sum(const std::string& fname, std::string* sha1, size_t* fsize = } 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 @@ class ApplyPatchTest : public ::testing::Test { 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(); + protected: + void SetUp() override { + ApplyPatchTest::SetUp(); + CacheLocation::location().set_cache_temp_source(old_file); } +}; - virtual void TearDown() { - restore_old(); +class ApplyPatchModesTest : public ::testing::Test { + protected: + void SetUp() override { + CacheLocation::location().set_cache_temp_source(cache_source.path); } -}; -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; + TemporaryFile cache_source; +}; TEST_F(ApplyPatchTest, CheckModeSkip) { std::vector<std::string> sha1s; @@ -197,43 +166,31 @@ TEST_F(ApplyPatchTest, CheckModeEmmcTarget) { 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 @@ TEST(ApplyPatchModesTest, InvalidArgs) { 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 @@ TEST(ApplyPatchModesTest, PatchModeEmmcTarget) { 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 @@ TEST(ApplyPatchModesTest, PatchModeInvalidArgs) { 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 b38bc7134..6cc59a495 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 @@ TEST_F(BootloaderMessageTest, read_and_write_bootloader_message) { 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 @@ TEST_F(BootloaderMessageTest, write_bootloader_message_options_long) { 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 bf25aebb0..6c23def01 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 @@ static void verify_patch_header(const std::string& patch, size_t* num_normal, si if (num_deflate != nullptr) *num_deflate = deflate; } -static void verify_patched_image(const std::string& src, const std::string& patch, - const std::string& tgt) { - std::string patched; +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(), - [&patched](const unsigned char* data, size_t len) { - patched.append(reinterpret_cast<const char*>(data), len); + [&](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; + GenerateTarget(src, patch, &patched); ASSERT_EQ(tgt, patched); } @@ -138,7 +150,7 @@ TEST(ImgdiffTest, image_mode_smoke) { 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 @@ TEST(ImgdiffTest, zip_mode_smoke_store) { 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_store) { 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 @@ TEST(ImgdiffTest, zip_mode_smoke_compressed) { 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_compressed) { 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 @@ TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) { 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 @@ TEST(ImgpatchTest, image_mode_patch_corruption) { 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 968196fc0..d19d788e4 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_no_entry) { 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, verify_package_compatibility_invalid_entry) { 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 @@ TEST(InstallTest, read_metadata_from_package_smoke) { 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_smoke) { 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, read_metadata_from_package_no_entry) { 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 @@ TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) { 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 @@ TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml 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 @@ TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml 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 @@ TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml 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 @@ TEST(InstallTest, update_binary_command_smoke) { 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 @@ TEST(InstallTest, update_binary_command_smoke) { 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_smoke) { 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 @@ TEST(InstallTest, update_binary_command_invalid) { 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 @@ TEST(InstallTest, update_binary_command_invalid) { 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 40cfc6975..b7109fcc2 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, fuse_device) { 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 3925236a5..55baca2e3 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_CLEAR_BCB = "init.svc.clear-bcb"; 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 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; + void TearDown() override { + // Clear the BCB. + if (has_misc) { + std::string err; + ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err; } + } - // Trigger the setup-bcb service. + void SetupOrClearBcb(bool isSetup, const std::string& message, + const std::string& message_in_bcb) const { + // 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 @@ class UncryptTest : public ::testing::Test { } } + 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 b04e1185e..1544bb2a4 100644 --- a/tests/component/update_verifier_test.cpp +++ b/tests/component/update_verifier_test.cpp @@ -46,7 +46,6 @@ TEST_F(UpdateVerifierTest, verify_image_smoke) { 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_smoke) { } 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 6c341c111..5bfd7cb40 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 @@ static void expect(const char* expected, const char* expr_str, CauseCode cause_c 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 @@ class UpdaterTest : public ::testing::Test { 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 @@ TEST_F(UpdaterTest, set_progress) { 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 @@ TEST_F(UpdaterTest, set_progress) { 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 @@ TEST_F(UpdaterTest, show_progress) { 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 @@ TEST_F(UpdaterTest, show_progress) { 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 @@ TEST_F(UpdaterTest, block_image_update) { 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 @@ TEST_F(UpdaterTest, block_image_update) { 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,44 +508,98 @@ TEST_F(UpdaterTest, block_image_update) { 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"))"; + ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); + CloseArchive(handle); +} + +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", "2", "0", "2", "stash " + src_hash + " 2,0,2", "free " + src_hash, "fail", + }; + + // 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)); + 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; + 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 = "/cache/recovery/" + name_digest; + 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 + tgt_hash).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_short_write) { - // Create a zip file with new_data. +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; - FILE* zip_file_ptr = fdopen(zip_file.fd, "wb"); - ZipWriter zip_writer(zip_file_ptr); + BuildUpdatePackage(entries, zip_file.release()); - // 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()); + 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", @@ -545,12 +607,17 @@ TEST_F(UpdaterTest, new_data_short_write) { "0", "new 2,0,1", }; - 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 = { + { "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)); @@ -561,7 +628,7 @@ TEST_F(UpdaterTest, new_data_short_write) { 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; @@ -579,18 +646,12 @@ TEST_F(UpdaterTest, new_data_short_write) { 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 @@ TEST_F(UpdaterTest, brotli_new_data) { 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 @@ TEST_F(UpdaterTest, brotli_new_data) { "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 @@ TEST_F(UpdaterTest, brotli_new_data) { 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 @@ TEST_F(UpdaterTest, brotli_new_data) { 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 92c6ef2d4..64e3b59e6 100644 --- a/tests/manual/recovery_test.cpp +++ b/tests/manual/recovery_test.cpp @@ -209,7 +209,7 @@ TEST_P(ResourceTest, ValidateLocale) { 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 Binary files differnew file mode 100644 index 000000000..bdb2b3216 --- /dev/null +++ b/tests/testdata/deflate_src.zip diff --git a/tests/testdata/deflate_tgt.zip b/tests/testdata/deflate_tgt.zip Binary files differnew file mode 100644 index 000000000..2a21760ec --- /dev/null +++ b/tests/testdata/deflate_tgt.zip diff --git a/tests/unit/dirutil_test.cpp b/tests/unit/dirutil_test.cpp index 5e2ae4fb5..7f85d13ea 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 @@ TEST(DirUtilTest, create_smoke) { 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 @@ TEST(DirUtilTest, create_strip_filename) { 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 @@ TEST(DirUtilTest, create_strip_filename) { 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 @@ TEST(DirUtilTest, create_mode_and_timestamp) { 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 3c6d77ef5..7ae193e18 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 @@ TEST(RangeSetTest, Parse_smoke) { // 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 @@ TEST(RangeSetTest, Overlaps) { 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 @@ TEST(RangeSetTest, equality) { 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 @@ TEST(RangeSetTest, iterators) { } 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 937abd1e1..7197c5c78 100644 --- a/tools/recovery_l10n/Android.mk +++ b/tools/recovery_l10n/Android.mk @@ -5,6 +5,7 @@ LOCAL_PATH := $(call my-dir) 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 000000000..2624cebe4 --- /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 000000000..2b0851cdd --- /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> @@ -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 @@ bool RecoveryUI::InitScreensaver() { 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 @@ bool RecoveryUI::InitScreensaver() { 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 @@ bool RecoveryUI::InitScreensaver() { 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 @@ int RecoveryUI::WaitKey() { // 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 @@ int RecoveryUI::WaitKey() { // 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 @@ void RecoveryUI::SetEnableReboot(bool enabled) { 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; - } - } -} @@ -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 @@ class RecoveryUI { 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 @@ class RecoveryUI { // 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 @@ class RecoveryUI { 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 @@ class RecoveryUI { 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 000000000..aa56d2f74 --- /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 59084b0bb..000000000 --- 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 ad3bdce7a..bb43c2c4a 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 @@ static struct fstab* read_fstab() { 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 @@ static const char* find_block_device(const char* path, bool* encryptable, bool* *encrypted = true; } } + if (f2fs_fs && strcmp(v->fs_type, "f2fs") == 0) + *f2fs_fs = true; return v->blk_device; } } @@ -244,7 +250,7 @@ static int retry_fibmap(const int fd, const char* name, int* block, const int he } 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 @@ static int produce_block_map(const char* path, const char* map_file, const char* } } +#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 produce_block_map(const char* path, const char* map_file, const char* 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 @@ static int uncrypt(const char* input_path, const char* map_file, const int socke // 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 33c5fe9e7..0ff88546f 100644 --- a/update_verifier/Android.mk +++ b/update_verifier/Android.mk @@ -22,6 +22,10 @@ LOCAL_SRC_FILES := \ update_verifier.cpp LOCAL_MODULE := libupdate_verifier + +LOCAL_STATIC_LIBRARIES := \ + libotautil + LOCAL_SHARED_LIBRARIES := \ libbase \ libcutils \ @@ -54,7 +58,9 @@ LOCAL_SRC_FILES := \ 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 ba7b7aec4..92d931371 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 int dm_name_filter(const dirent* de) { } 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 @@ static bool read_blocks(const std::string& partition, const std::string& range_s // 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; - std::vector<std::future<bool>> threads; + // 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; - thread_num = std::min(thread_num, range_count); - size_t group_range_count = (range_count + thread_num - 1) / thread_num; + std::vector<RangeSet> groups = ranges.Split(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); + std::vector<std::future<bool>> threads; + 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 @@ static bool read_blocks(const std::string& partition, const std::string& range_s } 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 @@ bool verify_image(const std::string& care_map_name) { 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 @@ bool verify_image(const std::string& care_map_name) { 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 46e8bbb59..a86203bfb 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 86dc48e30..6f334ee18 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -66,7 +66,7 @@ LOCAL_C_INCLUDES := \ external/e2fsprogs/misc LOCAL_CFLAGS := \ - -Wno-unused-parameter \ + -Wall \ -Werror LOCAL_EXPORT_C_INCLUDE_DIRS := \ @@ -91,7 +91,7 @@ LOCAL_C_INCLUDES := \ $(LOCAL_PATH)/include LOCAL_CFLAGS := \ - -Wno-unused-parameter \ + -Wall \ -Werror LOCAL_STATIC_LIBRARIES := \ diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index a0b9ad233..e93196b27 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 CauseCode failure_type = kNoCause; 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 @@ static bool receive_new_data(const uint8_t* data, size_t size, void* cookie) { // 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 @@ static bool receive_brotli_new_data(const uint8_t* data, size_t size, void* cook // 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 @@ static int WriteBlocks(const RangeSet& tgt, const std::vector<uint8_t>& buffer, 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 @@ struct CommandParameters { 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 @@ static void PrintHashForCorruptedSourceBlocks(const CommandParameters& params, } 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 @@ static std::string GetStashFileName(const std::string& base, const std::string& 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 @@ static int CreateStash(State* state, size_t maxblocks, const std::string& blockd 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 @@ static int CreateStash(State* state, size_t maxblocks, const std::string& blockd 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 @@ static int CreateStash(State* state, size_t maxblocks, const std::string& blockd 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 @@ static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size 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 @@ static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size } RangeSet locs = RangeSet::Parse(params.tokens[params.cpos++]); + CHECK(static_cast<bool>(locs)); MoveRange(params.buffer, locs, params.buffer); } @@ -960,6 +1067,7 @@ static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size } RangeSet locs = RangeSet::Parse(tokens[1]); + CHECK(static_cast<bool>(locs)); MoveRange(params.buffer, locs, stash); } @@ -1024,6 +1132,7 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t* // <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 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t* 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 @@ static int PerformCommandMove(CommandParameters& params) { 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 @@ static int PerformCommandStash(CommandParameters& params) { } 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 @@ static int PerformCommandStash(CommandParameters& params) { } 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 @@ static int PerformCommandZero(CommandParameters& params) { } RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]); + CHECK(static_cast<bool>(tgt)); LOG(INFO) << " zeroing " << tgt.blocks() << " blocks"; @@ -1228,6 +1353,7 @@ static int PerformCommandNew(CommandParameters& params) { } 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 @@ static int PerformCommandDiff(CommandParameters& params) { 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 @@ static int PerformCommandDiff(CommandParameters& params) { 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 @@ static int PerformCommandDiff(CommandParameters& params) { 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 @@ static int PerformCommandErase(CommandParameters& params) { } 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 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, 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 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, // 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 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, 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 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, // 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 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, 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 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, 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 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, 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 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, } } + rc = 0; + +pbiudone: if (params.canwrite) { - pthread_join(params.thread, nullptr); + 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); + } - LOG(INFO) << "wrote " << params.written << " blocks; expected " << total_blocks; - LOG(INFO) << "stashed " << params.stashed << " blocks"; - LOG(INFO) << "max alloc needed was " << params.buffer.size(); + 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); + 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(); } - // Delete stash only after successfully completing the update, as it may contain blocks needed - // to complete the update later. - DeleteStash(params.stashbase); - } else { + + 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"; } - rc = 0; - -pbiudone: if (ota_fsync(params.fd) == -1) { failure_type = kFsyncFailure; PLOG(ERROR) << "fsync failed"; @@ -1624,6 +1822,11 @@ pbiudone: 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 @@ Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique } RangeSet rs = RangeSet::Parse(ranges->data); + CHECK(static_cast<bool>(rs)); SHA_CTX ctx; SHA1_Init(&ctx); @@ -1859,6 +2063,11 @@ Value* BlockImageRecoverFn(const char* name, State* state, 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 @@ Value* BlockImageRecoverFn(const char* name, State* state, } 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 2f4ad3c04..71733b303 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 fad038043..000000000 --- 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 bfe91e7f9..9be7645f3 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 @@ void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...) { 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; +// 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()); } - 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 << "]"; + 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(""); } - prev_end = next_end; + + 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); } - return true; +} + +// 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 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt 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 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt 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 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt 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 @@ Value* ShowProgressFn(const char* name, State* state, 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 @@ Value* ShowProgressFn(const char* name, State* state, 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 @@ Value* SetProgressFn(const char* name, State* state, const std::vector<std::uniq 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 @@ Value* SetProgressFn(const char* name, State* state, const std::vector<std::uniq 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 @@ Value* GetPropFn(const char* name, State* state, const std::vector<std::unique_p // 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 @@ Value* FileGetPropFn(const char* name, State* state, const std::vector<std::uniq } // 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 @@ Value* ApplyPatchSpaceFn(const char* name, State* state, const std::vector<std:: 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()); - } - - 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); + // Skip the cache size check if the update is a retry. + if (state->is_retry || CacheSizeCheck(bytes) == 0) { + return StringValue("t"); } - - 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 @@ Value* RunProgramFn(const char* name, State* state, const std::vector<std::uniqu 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 1d8fa8e92..1d6b172bb 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 18437fb7a..283e04300 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; @@ -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); +} @@ -28,8 +28,14 @@ class VrRecoveryUI : public ScreenRecoveryUI { // 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 624116c0c..ca6b1b102 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 @@ WearRecoveryUI::WearRecoveryUI() 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 @@ void WearRecoveryUI::draw_background_locked() { 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 @@ void WearRecoveryUI::draw_screen_locked() { // 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 @@ void WearRecoveryUI::update_progress_locked() { 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 @@ void WearRecoveryUI::StartMenu(const char* const* headers, const char* const* it 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 @@ int WearRecoveryUI::SelectMenu(int sel) { 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); -} @@ -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 @@ class WearRecoveryUI : public ScreenRecoveryUI { 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 |