Project import
diff --git a/recovery/Android.mk b/recovery/Android.mk new file mode 100644 index 0000000..b1d84cd --- /dev/null +++ b/recovery/Android.mk
@@ -0,0 +1,160 @@ +# Copyright (C) 2007 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +# libfusesideload (static library) +# =============================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := fuse_sideload.cpp +LOCAL_CLANG := true +LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter -Werror +LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE +LOCAL_MODULE := libfusesideload +LOCAL_STATIC_LIBRARIES := libcutils libc libcrypto +include $(BUILD_STATIC_LIBRARY) + +# libmounts (static library) +# =============================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := mounts.cpp +LOCAL_CLANG := true +LOCAL_CFLAGS := -Wall -Wno-unused-parameter -Werror +LOCAL_MODULE := libmounts +include $(BUILD_STATIC_LIBRARY) + +# recovery (static executable) +# =============================== +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + adb_install.cpp \ + asn1_decoder.cpp \ + bootloader.cpp \ + device.cpp \ + fuse_sdcard_provider.cpp \ + install.cpp \ + recovery.cpp \ + roots.cpp \ + screen_ui.cpp \ + ui.cpp \ + verifier.cpp \ + wear_ui.cpp \ + +LOCAL_MODULE := recovery + +LOCAL_FORCE_STATIC_EXECUTABLE := true + +ifeq ($(TARGET_USERIMAGES_USE_F2FS),true) +ifeq ($(HOST_OS),linux) +LOCAL_REQUIRED_MODULES := mkfs.f2fs +endif +endif + +RECOVERY_API_VERSION := 3 +RECOVERY_FSTAB_VERSION := 2 +LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) +LOCAL_CFLAGS += -Wno-unused-parameter +LOCAL_CLANG := true + +LOCAL_C_INCLUDES += \ + system/vold \ + system/extras/ext4_utils \ + system/core/adb \ + +LOCAL_STATIC_LIBRARIES := \ + libbatterymonitor \ + libext4_utils_static \ + libsparse_static \ + libminzip \ + libmounts \ + libz \ + libminadbd \ + libfusesideload \ + libminui \ + libpng \ + libfs_mgr \ + libcrypto_utils \ + libcrypto \ + libbase \ + libcutils \ + libutils \ + liblog \ + libselinux \ + libm \ + libc + +LOCAL_HAL_STATIC_LIBRARIES := libhealthd + +LOCAL_C_INCLUDES += system/extras/ext4_utils +LOCAL_STATIC_LIBRARIES += libext4_utils_static libz + +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin + +ifeq ($(TARGET_RECOVERY_UI_LIB),) + LOCAL_SRC_FILES += default_device.cpp +else + LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB) +endif + +ifeq ($(BOARD_CACHEIMAGE_PARTITION_SIZE),) +LOCAL_REQUIRED_MODULES := recovery-persist recovery-refresh +endif + +include $(BUILD_EXECUTABLE) + +# recovery-persist (system partition dynamic executable run after /data mounts) +# =============================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := recovery-persist.cpp +LOCAL_MODULE := recovery-persist +LOCAL_SHARED_LIBRARIES := liblog libbase +LOCAL_CFLAGS := -Werror +LOCAL_INIT_RC := recovery-persist.rc +include $(BUILD_EXECUTABLE) + +# recovery-refresh (system partition dynamic executable run at init) +# =============================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := recovery-refresh.cpp +LOCAL_MODULE := recovery-refresh +LOCAL_SHARED_LIBRARIES := liblog +LOCAL_CFLAGS := -Werror +LOCAL_INIT_RC := recovery-refresh.rc +include $(BUILD_EXECUTABLE) + +# libverifier (static library) +# =============================== +include $(CLEAR_VARS) +LOCAL_CLANG := true +LOCAL_MODULE := libverifier +LOCAL_MODULE_TAGS := tests +LOCAL_SRC_FILES := \ + asn1_decoder.cpp \ + verifier.cpp \ + ui.cpp +LOCAL_STATIC_LIBRARIES := libcrypto_utils libcrypto +include $(BUILD_STATIC_LIBRARY) + +include $(LOCAL_PATH)/minui/Android.mk \ + $(LOCAL_PATH)/minzip/Android.mk \ + $(LOCAL_PATH)/minadbd/Android.mk \ + $(LOCAL_PATH)/tests/Android.mk \ + $(LOCAL_PATH)/tools/Android.mk \ + $(LOCAL_PATH)/edify/Android.mk \ + $(LOCAL_PATH)/uncrypt/Android.mk \ + $(LOCAL_PATH)/otafault/Android.mk \ + $(LOCAL_PATH)/updater/Android.mk \ + $(LOCAL_PATH)/update_verifier/Android.mk \ + $(LOCAL_PATH)/applypatch/Android.mk
diff --git a/recovery/CleanSpec.mk b/recovery/CleanSpec.mk new file mode 100644 index 0000000..e2d97d4 --- /dev/null +++ b/recovery/CleanSpec.mk
@@ -0,0 +1,51 @@ +# 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. +# + +# If you don't need to do a full clean build but would like to touch +# a file or delete some intermediate files, add a clean step to the end +# of the list. These steps will only be run once, if they haven't been +# run before. +# +# E.g.: +# $(call add-clean-step, touch -c external/sqlite/sqlite3.h) +# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates) +# +# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with +# files that are missing or have been moved. +# +# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory. +# Use $(OUT_DIR) to refer to the "out" directory. +# +# If you need to re-do something that's already mentioned, just copy +# the command and add it to the bottom of the list. E.g., if a change +# that you made last week required touching a file and a change you +# made today requires touching the same file, just copy the old +# touch step and add it to the end of the list. +# +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ + +# For example: +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates) +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates) +#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f) +#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*) + +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/recovery_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminui_intermediates/import_includes)
diff --git a/recovery/NOTICE b/recovery/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/recovery/NOTICE
@@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS +
diff --git a/recovery/README.md b/recovery/README.md new file mode 100644 index 0000000..01fab94 --- /dev/null +++ b/recovery/README.md
@@ -0,0 +1,29 @@ +The Recovery Image +================== + +Quick turn-around testing +------------------------- + + mm -j && m ramdisk-nodeps && m recoveryimage-nodeps + + # To boot into the new recovery image + # without flashing the recovery partition: + adb reboot bootloader + fastboot boot $ANDROID_PRODUCT_OUT/recovery.img + +Running the tests +----------------- + # After setting up environment and lunch. + mmma -j bootable/recovery + + # Running the tests on device. + adb root + adb sync data + + # 32-bit device + adb shell /data/nativetest/recovery_unit_test/recovery_unit_test + adb shell /data/nativetest/recovery_component_test/recovery_component_test + + # Or 64-bit device + adb shell /data/nativetest64/recovery_unit_test/recovery_unit_test + adb shell /data/nativetest64/recovery_component_test/recovery_component_test
diff --git a/recovery/adb_install.cpp b/recovery/adb_install.cpp new file mode 100644 index 0000000..60616ca --- /dev/null +++ b/recovery/adb_install.cpp
@@ -0,0 +1,136 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <unistd.h> +#include <dirent.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <signal.h> +#include <fcntl.h> + +#include "ui.h" +#include "cutils/properties.h" +#include "install.h" +#include "common.h" +#include "adb_install.h" +#include "minadbd/fuse_adb_provider.h" +#include "fuse_sideload.h" + +static void set_usb_driver(RecoveryUI* ui, bool enabled) { + int fd = open("/sys/class/android_usb/android0/enable", O_WRONLY); + if (fd < 0) { + ui->Print("failed to open driver control: %s\n", strerror(errno)); + return; + } + if (TEMP_FAILURE_RETRY(write(fd, enabled ? "1" : "0", 1)) == -1) { + ui->Print("failed to set driver control: %s\n", strerror(errno)); + } + if (close(fd) < 0) { + ui->Print("failed to close driver control: %s\n", strerror(errno)); + } +} + +static void stop_adbd(RecoveryUI* ui) { + ui->Print("Stopping adbd...\n"); + property_set("ctl.stop", "adbd"); + set_usb_driver(ui, false); +} + +static void maybe_restart_adbd(RecoveryUI* ui) { + if (is_ro_debuggable()) { + ui->Print("Restarting adbd...\n"); + set_usb_driver(ui, true); + property_set("ctl.start", "adbd"); + } +} + +// How long (in seconds) we wait for the host to start sending us a +// package, before timing out. +#define ADB_INSTALL_TIMEOUT 300 + +int apply_from_adb(RecoveryUI* ui, bool* wipe_cache, const char* install_file) { + modified_flash = true; + + stop_adbd(ui); + set_usb_driver(ui, true); + + ui->Print("\n\nNow send the package you want to apply\n" + "to the device with \"adb sideload <filename>\"...\n"); + + pid_t child; + if ((child = fork()) == 0) { + execl("/sbin/recovery", "recovery", "--adbd", NULL); + _exit(-1); + } + + // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the host + // connects and starts serving a package. Poll for its + // appearance. (Note that inotify doesn't work with FUSE.) + int result = INSTALL_ERROR; + int status; + bool waited = false; + struct stat st; + for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) { + if (waitpid(child, &status, WNOHANG) != 0) { + result = INSTALL_ERROR; + waited = true; + break; + } + + if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) { + if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT-1) { + sleep(1); + continue; + } else { + ui->Print("\nTimed out waiting for package.\n\n"); + result = INSTALL_ERROR; + kill(child, SIGKILL); + break; + } + } + result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, install_file, false); + break; + } + + if (!waited) { + // Calling stat() on this magic filename signals the minadbd + // subprocess to shut down. + stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); + + // TODO(dougz): there should be a way to cancel waiting for a + // package (by pushing some button combo on the device). For now + // you just have to 'adb sideload' a file that's not a valid + // package, like "/dev/null". + waitpid(child, &status, 0); + } + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + if (WEXITSTATUS(status) == 3) { + ui->Print("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n"); + } else if (!WIFSIGNALED(status)) { + ui->Print("\n(adbd status %d)\n", WEXITSTATUS(status)); + } + } + + set_usb_driver(ui, false); + maybe_restart_adbd(ui); + + return result; +}
diff --git a/recovery/adb_install.h b/recovery/adb_install.h new file mode 100644 index 0000000..efad436 --- /dev/null +++ b/recovery/adb_install.h
@@ -0,0 +1,24 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ADB_INSTALL_H +#define _ADB_INSTALL_H + +class RecoveryUI; + +int apply_from_adb(RecoveryUI* h, bool* wipe_cache, const char* install_file); + +#endif
diff --git a/recovery/applypatch/Android.mk b/recovery/applypatch/Android.mk new file mode 100644 index 0000000..604787e --- /dev/null +++ b/recovery/applypatch/Android.mk
@@ -0,0 +1,99 @@ +# 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_CLANG := true +LOCAL_SRC_FILES := \ + applypatch.cpp \ + bspatch.cpp \ + freecache.cpp \ + imgpatch.cpp \ + utils.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 \ + libbz \ + libz +include $(BUILD_STATIC_LIBRARY) + +# libimgpatch (static library) +# =============================== +include $(CLEAR_VARS) +LOCAL_CLANG := true +LOCAL_SRC_FILES := bspatch.cpp imgpatch.cpp utils.cpp +LOCAL_MODULE := libimgpatch +LOCAL_C_INCLUDES += \ + $(LOCAL_PATH)/include \ + bootable/recovery +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +LOCAL_STATIC_LIBRARIES += libcrypto libbz libz +include $(BUILD_STATIC_LIBRARY) + +# libimgpatch (host static library) +# =============================== +include $(CLEAR_VARS) +LOCAL_CLANG := true +LOCAL_SRC_FILES := bspatch.cpp imgpatch.cpp utils.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 libbz libz +include $(BUILD_HOST_STATIC_LIBRARY) + +# applypatch (executable) +# =============================== +include $(CLEAR_VARS) +LOCAL_CLANG := true +LOCAL_SRC_FILES := main.cpp +LOCAL_MODULE := applypatch +LOCAL_C_INCLUDES += bootable/recovery +LOCAL_STATIC_LIBRARIES += \ + libapplypatch \ + libbase \ + libedify \ + libotafault \ + libminzip \ + libcrypto \ + libbz +LOCAL_SHARED_LIBRARIES += libz libcutils libc +include $(BUILD_EXECUTABLE) + +# imgdiff (host static executable) +# =============================== +include $(CLEAR_VARS) +LOCAL_CLANG := true +LOCAL_SRC_FILES := imgdiff.cpp utils.cpp +LOCAL_MODULE := imgdiff +LOCAL_STATIC_LIBRARIES += \ + libbsdiff \ + libbz \ + libdivsufsort64 \ + libdivsufsort \ + libz +LOCAL_FORCE_STATIC_EXECUTABLE := true +include $(BUILD_HOST_EXECUTABLE)
diff --git a/recovery/applypatch/Makefile b/recovery/applypatch/Makefile new file mode 100644 index 0000000..fb49843 --- /dev/null +++ b/recovery/applypatch/Makefile
@@ -0,0 +1,33 @@ +# 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/recovery/applypatch/NOTICE b/recovery/applypatch/NOTICE new file mode 100644 index 0000000..6156a0c --- /dev/null +++ b/recovery/applypatch/NOTICE
@@ -0,0 +1,41 @@ +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. + + +bsdiff.c +bspatch.c + +Copyright 2003-2005 Colin Percival +All rights reserved + +Redistribution and use in source and binary forms, with or without +modification, are permitted providing that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE.
diff --git a/recovery/applypatch/applypatch.cpp b/recovery/applypatch/applypatch.cpp new file mode 100644 index 0000000..02a3c6e --- /dev/null +++ b/recovery/applypatch/applypatch.cpp
@@ -0,0 +1,888 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <sys/types.h> +#include <unistd.h> + +#include <memory> +#include <string> + +#include <android-base/strings.h> + +#include "openssl/sha.h" +#include "applypatch/applypatch.h" +#include "edify/expr.h" +#include "ota_io.h" +#include "print_sha1.h" + +static int LoadPartitionContents(const char* filename, FileContents* file); +static ssize_t FileSink(const unsigned char* data, ssize_t len, void* token); +static int GenerateTarget(FileContents* source_file, + const Value* source_patch_value, + FileContents* copy_file, + const Value* copy_patch_value, + const char* source_filename, + const char* target_filename, + const uint8_t target_sha1[SHA_DIGEST_LENGTH], + size_t target_size, + const Value* bonus_data); + +// Read a file into memory; store the file contents and associated +// metadata in *file. +// +// Return 0 on success. +int LoadFileContents(const char* filename, FileContents* file) { + // A special 'filename' beginning with "EMMC:" means to + // load the contents of a partition. + if (strncmp(filename, "EMMC:", 5) == 0) { + return LoadPartitionContents(filename, file); + } + + if (stat(filename, &file->st) != 0) { + printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); + return -1; + } + + std::vector<unsigned char> data(file->st.st_size); + FILE* f = ota_fopen(filename, "rb"); + if (f == NULL) { + printf("failed to open \"%s\": %s\n", filename, strerror(errno)); + return -1; + } + + size_t bytes_read = ota_fread(data.data(), 1, data.size(), f); + if (bytes_read != data.size()) { + printf("short read of \"%s\" (%zu bytes of %zu)\n", filename, bytes_read, data.size()); + ota_fclose(f); + return -1; + } + ota_fclose(f); + file->data = std::move(data); + SHA1(file->data.data(), file->data.size(), file->sha1); + return 0; +} + +// Load the contents of an EMMC partition into the provided +// FileContents. filename should be a string of the form +// "EMMC:<partition_device>:...". The smallest size_n bytes for +// which that prefix of the partition contents has the corresponding +// sha1 hash will be loaded. It is acceptable for a size value to be +// repeated with different sha1s. Will return 0 on success. +// +// This complexity is needed because if an OTA installation is +// interrupted, the partition might contain either the source or the +// target data, which might be of different lengths. We need to know +// the length in order to read from a partition (there is no +// "end-of-file" marker), so the caller must specify the possible +// lengths and the hash of the data, and we'll do the load expecting +// to find one of those hashes. +static int LoadPartitionContents(const char* filename, FileContents* file) { + std::string copy(filename); + std::vector<std::string> pieces = android::base::Split(copy, ":"); + if (pieces.size() < 4 || pieces.size() % 2 != 0) { + printf("LoadPartitionContents called with bad filename (%s)\n", filename); + return -1; + } + + if (pieces[0] != "EMMC") { + printf("LoadPartitionContents called with bad filename (%s)\n", filename); + return -1; + } + const char* partition = pieces[1].c_str(); + + size_t pairs = (pieces.size() - 2) / 2; // # of (size, sha1) pairs in filename + std::vector<size_t> index(pairs); + std::vector<size_t> size(pairs); + std::vector<std::string> sha1sum(pairs); + + for (size_t i = 0; i < pairs; ++i) { + size[i] = strtol(pieces[i*2+2].c_str(), NULL, 10); + if (size[i] == 0) { + printf("LoadPartitionContents called with bad size (%s)\n", filename); + return -1; + } + sha1sum[i] = pieces[i*2+3].c_str(); + index[i] = i; + } + + // Sort the index[] array so it indexes the pairs in order of increasing size. + sort(index.begin(), index.end(), + [&](const size_t& i, const size_t& j) { + return (size[i] < size[j]); + } + ); + + FILE* dev = ota_fopen(partition, "rb"); + if (dev == NULL) { + printf("failed to open emmc partition \"%s\": %s\n", partition, strerror(errno)); + return -1; + } + + SHA_CTX sha_ctx; + SHA1_Init(&sha_ctx); + uint8_t parsed_sha[SHA_DIGEST_LENGTH]; + + // Allocate enough memory to hold the largest size. + std::vector<unsigned char> data(size[index[pairs-1]]); + char* p = reinterpret_cast<char*>(data.data()); + size_t data_size = 0; // # bytes read so far + bool found = false; + + for (size_t i = 0; i < pairs; ++i) { + // Read enough additional bytes to get us up to the next size. (Again, + // we're trying the possibilities in order of increasing size). + size_t next = size[index[i]] - data_size; + if (next > 0) { + size_t read = ota_fread(p, 1, next, dev); + if (next != read) { + printf("short read (%zu bytes of %zu) for partition \"%s\"\n", + read, next, partition); + return -1; + } + SHA1_Update(&sha_ctx, p, read); + data_size += read; + p += read; + } + + // Duplicate the SHA context and finalize the duplicate so we can + // check it against this pair's expected hash. + SHA_CTX temp_ctx; + memcpy(&temp_ctx, &sha_ctx, sizeof(SHA_CTX)); + uint8_t sha_so_far[SHA_DIGEST_LENGTH]; + SHA1_Final(sha_so_far, &temp_ctx); + + if (ParseSha1(sha1sum[index[i]].c_str(), parsed_sha) != 0) { + printf("failed to parse sha1 %s in %s\n", sha1sum[index[i]].c_str(), filename); + return -1; + } + + if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_LENGTH) == 0) { + // we have a match. stop reading the partition; we'll return + // the data we've read so far. + printf("partition read matched size %zu sha %s\n", + size[index[i]], sha1sum[index[i]].c_str()); + found = true; + break; + } + } + + ota_fclose(dev); + + if (!found) { + // Ran off the end of the list of (size,sha1) pairs without finding a match. + printf("contents of partition \"%s\" didn't match %s\n", partition, filename); + return -1; + } + + SHA1_Final(file->sha1, &sha_ctx); + + data.resize(data_size); + file->data = std::move(data); + // Fake some stat() info. + file->st.st_mode = 0644; + file->st.st_uid = 0; + file->st.st_gid = 0; + + return 0; +} + + +// Save the contents of the given FileContents object under the given +// filename. Return 0 on success. +int SaveFileContents(const char* filename, const FileContents* file) { + int fd = ota_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR); + if (fd < 0) { + printf("failed to open \"%s\" for write: %s\n", filename, strerror(errno)); + return -1; + } + + ssize_t bytes_written = FileSink(file->data.data(), file->data.size(), &fd); + if (bytes_written != static_cast<ssize_t>(file->data.size())) { + printf("short write of \"%s\" (%zd bytes of %zu) (%s)\n", + filename, bytes_written, file->data.size(), strerror(errno)); + ota_close(fd); + return -1; + } + if (ota_fsync(fd) != 0) { + printf("fsync of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } + if (ota_close(fd) != 0) { + printf("close of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } + + if (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; +} + +// Write a memory buffer to 'target' partition, a string of the form +// "EMMC:<partition_device>[:...]". The target name +// might contain multiple colons, but WriteToPartition() only uses the first +// two and ignores the rest. Return 0 on success. +int WriteToPartition(const unsigned char* data, size_t len, const char* target) { + std::string copy(target); + std::vector<std::string> pieces = android::base::Split(copy, ":"); + + if (pieces.size() < 2) { + printf("WriteToPartition called with bad target (%s)\n", target); + return -1; + } + + if (pieces[0] != "EMMC") { + printf("WriteToPartition called with bad target (%s)\n", target); + return -1; + } + const char* partition = pieces[1].c_str(); + + size_t start = 0; + bool success = false; + int fd = ota_open(partition, O_RDWR | O_SYNC); + if (fd < 0) { + printf("failed to open %s: %s\n", partition, strerror(errno)); + return -1; + } + + for (size_t attempt = 0; attempt < 2; ++attempt) { + if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) { + printf("failed seek on %s: %s\n", partition, strerror(errno)); + return -1; + } + while (start < len) { + size_t to_write = len - start; + if (to_write > 1<<20) to_write = 1<<20; + + ssize_t written = TEMP_FAILURE_RETRY(ota_write(fd, data+start, to_write)); + if (written == -1) { + printf("failed write writing to %s: %s\n", partition, strerror(errno)); + return -1; + } + start += written; + } + if (ota_fsync(fd) != 0) { + printf("failed to sync to %s (%s)\n", partition, strerror(errno)); + return -1; + } + if (ota_close(fd) != 0) { + printf("failed to close %s (%s)\n", partition, strerror(errno)); + return -1; + } + fd = ota_open(partition, O_RDONLY); + if (fd < 0) { + printf("failed to reopen %s for verify (%s)\n", partition, strerror(errno)); + return -1; + } + + // Drop caches so our subsequent verification read + // won't just be reading the cache. + sync(); + int dc = ota_open("/proc/sys/vm/drop_caches", O_WRONLY); + if (TEMP_FAILURE_RETRY(ota_write(dc, "3\n", 2)) == -1) { + printf("write to /proc/sys/vm/drop_caches failed: %s\n", strerror(errno)); + } else { + printf(" caches dropped\n"); + } + ota_close(dc); + sleep(1); + + // verify + if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) { + printf("failed to seek back to beginning of %s: %s\n", + partition, strerror(errno)); + return -1; + } + unsigned char buffer[4096]; + start = len; + for (size_t p = 0; p < len; p += sizeof(buffer)) { + size_t to_read = len - p; + if (to_read > sizeof(buffer)) { + to_read = sizeof(buffer); + } + + size_t so_far = 0; + while (so_far < to_read) { + ssize_t read_count = + TEMP_FAILURE_RETRY(ota_read(fd, buffer+so_far, to_read-so_far)); + if (read_count == -1) { + printf("verify read error %s at %zu: %s\n", + partition, p, strerror(errno)); + return -1; + } + if (static_cast<size_t>(read_count) < to_read) { + printf("short verify read %s at %zu: %zd %zu %s\n", + partition, p, read_count, to_read, strerror(errno)); + } + so_far += read_count; + } + + if (memcmp(buffer, data+p, to_read) != 0) { + printf("verification failed starting at %zu\n", p); + start = p; + break; + } + } + + if (start == len) { + printf("verification read succeeded (attempt %zu)\n", attempt+1); + success = true; + break; + } + } + + if (!success) { + printf("failed to verify after all attempts\n"); + return -1; + } + + if (ota_close(fd) != 0) { + printf("error closing %s (%s)\n", partition, strerror(errno)); + return -1; + } + sync(); + + return 0; +} + + +// Take a string 'str' of 40 hex digits and parse it into the 20 +// byte array 'digest'. 'str' may contain only the digest or be of +// the form "<digest>:<anything>". Return 0 on success, -1 on any +// error. +int ParseSha1(const char* str, uint8_t* digest) { + const char* ps = str; + uint8_t* pd = digest; + for (int i = 0; i < SHA_DIGEST_LENGTH * 2; ++i, ++ps) { + int digit; + if (*ps >= '0' && *ps <= '9') { + digit = *ps - '0'; + } else if (*ps >= 'a' && *ps <= 'f') { + digit = *ps - 'a' + 10; + } else if (*ps >= 'A' && *ps <= 'F') { + digit = *ps - 'A' + 10; + } else { + return -1; + } + if (i % 2 == 0) { + *pd = digit << 4; + } else { + *pd |= digit; + ++pd; + } + } + if (*ps != '\0') return -1; + return 0; +} + +// Search an array of sha1 strings for one matching the given sha1. +// Return the index of the match on success, or -1 if no match is +// found. +int FindMatchingPatch(uint8_t* sha1, char* const * const patch_sha1_str, + int num_patches) { + uint8_t patch_sha1[SHA_DIGEST_LENGTH]; + for (int i = 0; i < num_patches; ++i) { + if (ParseSha1(patch_sha1_str[i], patch_sha1) == 0 && + memcmp(patch_sha1, sha1, SHA_DIGEST_LENGTH) == 0) { + return i; + } + } + return -1; +} + +// Returns 0 if the contents of the file (argv[2]) or the cached file +// match any of the sha1's on the command line (argv[3:]). Returns +// nonzero otherwise. +int applypatch_check(const char* filename, int num_patches, + char** const patch_sha1_str) { + FileContents file; + + // It's okay to specify no sha1s; the check will pass if the + // LoadFileContents is successful. (Useful for reading + // partitions, where the filename encodes the sha1s; no need to + // check them twice.) + if (LoadFileContents(filename, &file) != 0 || + (num_patches > 0 && + FindMatchingPatch(file.sha1, patch_sha1_str, num_patches) < 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) { + printf("failed to load cache file\n"); + return 1; + } + + if (FindMatchingPatch(file.sha1, patch_sha1_str, num_patches) < 0) { + printf("cache bits don't match any sha1 for \"%s\"\n", filename); + return 1; + } + } + return 0; +} + +int ShowLicenses() { + ShowBSDiffLicense(); + return 0; +} + +ssize_t FileSink(const unsigned char* data, ssize_t len, void* token) { + int fd = *static_cast<int*>(token); + ssize_t done = 0; + ssize_t wrote; + while (done < len) { + wrote = TEMP_FAILURE_RETRY(ota_write(fd, data+done, len-done)); + if (wrote == -1) { + printf("error writing %zd bytes: %s\n", (len-done), strerror(errno)); + return done; + } + done += wrote; + } + return done; +} + +ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) { + std::string* s = static_cast<std::string*>(token); + s->append(reinterpret_cast<const char*>(data), len); + return len; +} + +// Return the amount of free space (in bytes) on the filesystem +// containing filename. filename must exist. Return -1 on error. +size_t FreeSpaceForFile(const char* filename) { + struct statfs sf; + if (statfs(filename, &sf) != 0) { + printf("failed to statfs %s: %s\n", filename, strerror(errno)); + return -1; + } + return sf.f_bsize * sf.f_bavail; +} + +int CacheSizeCheck(size_t bytes) { + if (MakeFreeSpaceOnCache(bytes) < 0) { + printf("unable to make %zu bytes available on /cache\n", bytes); + return 1; + } else { + return 0; + } +} + +// This function applies binary patches to files in a way that is safe +// (the original file is not touched until we have the desired +// replacement for it) and idempotent (it's okay to run this program +// multiple times). +// +// - if the sha1 hash of <target_filename> is <target_sha1_string>, +// does nothing and exits successfully. +// +// - otherwise, if the sha1 hash of <source_filename> is one of the +// entries in <patch_sha1_str>, the corresponding patch from +// <patch_data> (which must be a VAL_BLOB) is applied to produce a +// new file (the type of patch is automatically detected from the +// blob data). If that new file has sha1 hash <target_sha1_str>, +// moves it to replace <target_filename>, and exits successfully. +// Note that if <source_filename> and <target_filename> are not the +// same, <source_filename> is NOT deleted on success. +// <target_filename> may be the string "-" to mean "the same as +// source_filename". +// +// - otherwise, or if any error is encountered, exits with non-zero +// status. +// +// <source_filename> may refer to a partition to read the source data. +// See the comments for the LoadPartitionContents() function above +// for the format of such a filename. + +int applypatch(const char* source_filename, + const char* target_filename, + const char* target_sha1_str, + size_t target_size, + int num_patches, + char** const patch_sha1_str, + Value** patch_data, + Value* bonus_data) { + printf("patch %s: ", source_filename); + + if (target_filename[0] == '-' && target_filename[1] == '\0') { + target_filename = source_filename; + } + + uint8_t target_sha1[SHA_DIGEST_LENGTH]; + if (ParseSha1(target_sha1_str, target_sha1) != 0) { + printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str); + return 1; + } + + FileContents copy_file; + FileContents source_file; + const Value* source_patch_value = NULL; + const Value* copy_patch_value = NULL; + + // We try to load the target file into the source_file object. + if (LoadFileContents(target_filename, &source_file) == 0) { + if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) { + // The early-exit case: the patch was already applied, this file + // has the desired hash, nothing for us to do. + printf("already %s\n", short_sha1(target_sha1).c_str()); + return 0; + } + } + + if (source_file.data.empty() || + (target_filename != source_filename && + strcmp(target_filename, source_filename) != 0)) { + // Need to load the source file: either we failed to load the + // target file, or we did but it's different from the source file. + source_file.data.clear(); + LoadFileContents(source_filename, &source_file); + } + + if (!source_file.data.empty()) { + int to_use = FindMatchingPatch(source_file.sha1, patch_sha1_str, num_patches); + if (to_use >= 0) { + source_patch_value = patch_data[to_use]; + } + } + + if (source_patch_value == NULL) { + source_file.data.clear(); + printf("source file is bad; trying copy\n"); + + if (LoadFileContents(CACHE_TEMP_SOURCE, ©_file) < 0) { + // fail. + printf("failed to read copy file\n"); + return 1; + } + + int to_use = FindMatchingPatch(copy_file.sha1, patch_sha1_str, num_patches); + if (to_use >= 0) { + copy_patch_value = patch_data[to_use]; + } + + if (copy_patch_value == NULL) { + // fail. + printf("copy file doesn't match source SHA-1s either\n"); + return 1; + } + } + + return GenerateTarget(&source_file, source_patch_value, + ©_file, copy_patch_value, + source_filename, target_filename, + target_sha1, target_size, bonus_data); +} + +/* + * This function flashes a given image to the target partition. It verifies + * the target cheksum first, and will return if target has the desired hash. + * It checks the checksum of the given source image before flashing, and + * verifies the target partition afterwards. The function is idempotent. + * Returns zero on success. + */ +int applypatch_flash(const char* source_filename, const char* target_filename, + const char* target_sha1_str, size_t target_size) { + printf("flash %s: ", target_filename); + + uint8_t target_sha1[SHA_DIGEST_LENGTH]; + if (ParseSha1(target_sha1_str, target_sha1) != 0) { + printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str); + return 1; + } + + FileContents source_file; + std::string target_str(target_filename); + + std::vector<std::string> pieces = android::base::Split(target_str, ":"); + if (pieces.size() != 2 || pieces[0] != "EMMC") { + printf("invalid target name \"%s\"", target_filename); + return 1; + } + + // Load the target into the source_file object to see if already applied. + pieces.push_back(std::to_string(target_size)); + pieces.push_back(target_sha1_str); + std::string fullname = android::base::Join(pieces, ':'); + if (LoadPartitionContents(fullname.c_str(), &source_file) == 0 && + memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) { + // The early-exit case: the image was already applied, this partition + // has the desired hash, nothing for us to do. + printf("already %s\n", short_sha1(target_sha1).c_str()); + return 0; + } + + if (LoadFileContents(source_filename, &source_file) == 0) { + if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) { + // The source doesn't have desired checksum. + printf("source \"%s\" doesn't have expected sha1 sum\n", source_filename); + printf("expected: %s, found: %s\n", short_sha1(target_sha1).c_str(), + short_sha1(source_file.sha1).c_str()); + return 1; + } + } + + if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) { + printf("write of copied data to %s failed\n", target_filename); + return 1; + } + return 0; +} + +static int GenerateTarget(FileContents* source_file, + const Value* source_patch_value, + FileContents* copy_file, + const Value* copy_patch_value, + const char* source_filename, + const char* target_filename, + const uint8_t target_sha1[SHA_DIGEST_LENGTH], + size_t target_size, + const Value* bonus_data) { + int retry = 1; + SHA_CTX ctx; + std::string memory_sink_str; + FileContents* source_to_use; + int made_copy = 0; + + bool target_is_partition = (strncmp(target_filename, "EMMC:", 5) == 0); + const std::string tmp_target_filename = std::string(target_filename) + ".patch"; + + // assume that target_filename (eg "/system/app/Foo.apk") is located + // on the same filesystem as its top-level directory ("/system"). + // We need something that exists for calling statfs(). + std::string target_fs = target_filename; + auto slash_pos = target_fs.find('/', 1); + if (slash_pos != std::string::npos) { + target_fs.resize(slash_pos); + } + + const Value* patch; + if (source_patch_value != NULL) { + source_to_use = source_file; + patch = source_patch_value; + } else { + source_to_use = copy_file; + patch = copy_patch_value; + } + if (patch->type != VAL_BLOB) { + printf("patch is not a blob\n"); + return 1; + } + char* header = patch->data; + ssize_t header_bytes_read = patch->size; + bool use_bsdiff = false; + if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) { + use_bsdiff = true; + } else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) { + use_bsdiff = false; + } else { + printf("Unknown patch file format\n"); + return 1; + } + + do { + // Is there enough room in the target filesystem to hold the patched + // file? + + if (target_is_partition) { + // If the target is a partition, we're actually going to + // write the output to /tmp and then copy it to the + // partition. statfs() always returns 0 blocks free for + // /tmp, so instead we'll just assume that /tmp has enough + // space to hold the file. + + // We still write the original source to cache, in case + // the partition write is interrupted. + if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) { + printf("not enough free space on /cache\n"); + return 1; + } + if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) { + printf("failed to back up source file\n"); + return 1; + } + made_copy = 1; + retry = 0; + } else { + int enough_space = 0; + if (retry > 0) { + size_t free_space = FreeSpaceForFile(target_fs.c_str()); + enough_space = + (free_space > (256 << 10)) && // 256k (two-block) minimum + (free_space > (target_size * 3 / 2)); // 50% margin of error + if (!enough_space) { + printf("target %zu bytes; free space %zu bytes; retry %d; enough %d\n", + target_size, free_space, retry, enough_space); + } + } + + if (!enough_space) { + retry = 0; + } + + if (!enough_space && source_patch_value != NULL) { + // Using the original source, but not enough free space. First + // copy the source file to cache, then delete it from the original + // location. + + if (strncmp(source_filename, "EMMC:", 5) == 0) { + // It's impossible to free space on the target filesystem by + // deleting the source if the source is a partition. If + // we're ever in a state where we need to do this, fail. + printf("not enough free space for target but source is partition\n"); + return 1; + } + + if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) { + printf("not enough free space on /cache\n"); + return 1; + } + + if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) { + printf("failed to back up source file\n"); + return 1; + } + made_copy = 1; + unlink(source_filename); + + size_t free_space = FreeSpaceForFile(target_fs.c_str()); + printf("(now %zu bytes free for target) ", free_space); + } + } + + + SinkFn sink = NULL; + void* token = NULL; + int output_fd = -1; + if (target_is_partition) { + // We store the decoded output in memory. + sink = MemorySink; + token = &memory_sink_str; + } else { + // We write the decoded output to "<tgt-file>.patch". + output_fd = ota_open(tmp_target_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, + S_IRUSR | S_IWUSR); + if (output_fd < 0) { + printf("failed to open output file %s: %s\n", tmp_target_filename.c_str(), + strerror(errno)); + return 1; + } + sink = FileSink; + token = &output_fd; + } + + + SHA1_Init(&ctx); + + int result; + if (use_bsdiff) { + result = ApplyBSDiffPatch(source_to_use->data.data(), source_to_use->data.size(), + patch, 0, sink, token, &ctx); + } else { + result = ApplyImagePatch(source_to_use->data.data(), source_to_use->data.size(), + patch, sink, token, &ctx, bonus_data); + } + + if (!target_is_partition) { + if (ota_fsync(output_fd) != 0) { + printf("failed to fsync file \"%s\" (%s)\n", tmp_target_filename.c_str(), + strerror(errno)); + result = 1; + } + if (ota_close(output_fd) != 0) { + printf("failed to close file \"%s\" (%s)\n", tmp_target_filename.c_str(), + strerror(errno)); + result = 1; + } + } + + if (result != 0) { + if (retry == 0) { + printf("applying patch failed\n"); + return result != 0; + } else { + printf("applying patch failed; retrying\n"); + } + if (!target_is_partition) { + unlink(tmp_target_filename.c_str()); + } + } else { + // succeeded; no need to retry + break; + } + } while (retry-- > 0); + + uint8_t current_target_sha1[SHA_DIGEST_LENGTH]; + SHA1_Final(current_target_sha1, &ctx); + if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) { + printf("patch did not produce expected sha1\n"); + return 1; + } else { + printf("now %s\n", short_sha1(target_sha1).c_str()); + } + + if (target_is_partition) { + // Copy the temp file to the partition. + if (WriteToPartition(reinterpret_cast<const unsigned char*>(memory_sink_str.c_str()), + memory_sink_str.size(), target_filename) != 0) { + printf("write of patched data to %s failed\n", target_filename); + return 1; + } + } else { + // Give the .patch file the same owner, group, and mode of the + // original source file. + if (chmod(tmp_target_filename.c_str(), source_to_use->st.st_mode) != 0) { + printf("chmod of \"%s\" failed: %s\n", tmp_target_filename.c_str(), strerror(errno)); + return 1; + } + if (chown(tmp_target_filename.c_str(), source_to_use->st.st_uid, source_to_use->st.st_gid) != 0) { + printf("chown of \"%s\" failed: %s\n", tmp_target_filename.c_str(), strerror(errno)); + return 1; + } + + // Finally, rename the .patch file to replace the target file. + if (rename(tmp_target_filename.c_str(), target_filename) != 0) { + printf("rename of .patch to \"%s\" failed: %s\n", target_filename, strerror(errno)); + return 1; + } + } + + // If this run of applypatch created the copy, and we're here, we + // can delete it. + if (made_copy) { + unlink(CACHE_TEMP_SOURCE); + } + + // Success! + return 0; +}
diff --git a/recovery/applypatch/applypatch.sh b/recovery/applypatch/applypatch.sh new file mode 100755 index 0000000..8ea68a1 --- /dev/null +++ b/recovery/applypatch/applypatch.sh
@@ -0,0 +1,350 @@ +#!/bin/bash +# +# A test suite for applypatch. Run in a client where you have done +# envsetup, choosecombo, etc. +# +# DO NOT RUN THIS ON A DEVICE YOU CARE ABOUT. It will mess up your +# system partition. +# +# +# TODO: find some way to get this run regularly along with the rest of +# the tests. + +EMULATOR_PORT=5580 +DATA_DIR=$ANDROID_BUILD_TOP/bootable/recovery/applypatch/testdata + +# This must be the filename that applypatch uses for its copies. +CACHE_TEMP_SOURCE=/cache/saved.file + +# Put all binaries and files here. We use /cache because it's a +# temporary filesystem in the emulator; it's created fresh each time +# the emulator starts. +WORK_DIR=/system + +# partition that WORK_DIR is located on, without the leading slash +WORK_FS=system + +# set to 0 to use a device instead +USE_EMULATOR=1 + +# ------------------------ + +tmpdir=$(mktemp -d) + +if [ "$USE_EMULATOR" == 1 ]; then + emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT & + pid_emulator=$! + ADB="adb -s emulator-$EMULATOR_PORT " +else + ADB="adb -d " +fi + +echo "waiting to connect to device" +$ADB wait-for-device +echo "device is available" +$ADB remount +# free up enough space on the system partition for the test to run. +$ADB shell rm -r /system/media + +# run a command on the device; exit with the exit status of the device +# command. +run_command() { + $ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}' +} + +testname() { + echo + echo "$1"... + testname="$1" +} + +fail() { + echo + echo FAIL: $testname + echo + [ "$open_pid" == "" ] || kill $open_pid + [ "$pid_emulator" == "" ] || kill $pid_emulator + exit 1 +} + +sha1() { + sha1sum $1 | awk '{print $1}' +} + +free_space() { + run_command df | awk "/$1/ {print gensub(/K/, \"\", \"g\", \$6)}" +} + +cleanup() { + # not necessary if we're about to kill the emulator, but nice for + # running on real devices or already-running emulators. + testname "removing test files" + run_command rm $WORK_DIR/bloat.dat + run_command rm $WORK_DIR/old.file + run_command rm $WORK_DIR/foo + run_command rm $WORK_DIR/patch.bsdiff + run_command rm $WORK_DIR/applypatch + run_command rm $CACHE_TEMP_SOURCE + run_command rm /cache/bloat*.dat + + [ "$pid_emulator" == "" ] || kill $pid_emulator + + if [ $# == 0 ]; then + rm -rf $tmpdir + fi +} + +cleanup leave_tmp + +$ADB push $ANDROID_PRODUCT_OUT/system/bin/applypatch $WORK_DIR/applypatch + +BAD1_SHA1=$(printf "%040x" $RANDOM) +BAD2_SHA1=$(printf "%040x" $RANDOM) +OLD_SHA1=$(sha1 $DATA_DIR/old.file) +NEW_SHA1=$(sha1 $DATA_DIR/new.file) +NEW_SIZE=$(stat -c %s $DATA_DIR/new.file) + +# --------------- basic execution ---------------------- + +testname "usage message" +run_command $WORK_DIR/applypatch && fail + +testname "display license" +run_command $WORK_DIR/applypatch -l | grep -q -i copyright || fail + + +# --------------- check mode ---------------------- + +$ADB push $DATA_DIR/old.file $WORK_DIR + +testname "check mode single" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail + +testname "check mode multiple" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail + +testname "check mode failure" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail + +$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE +# put some junk in the old file +run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail + +testname "check mode cache (corrupted) single" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail + +testname "check mode cache (corrupted) multiple" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail + +testname "check mode cache (corrupted) failure" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail + +# remove the old file entirely +run_command rm $WORK_DIR/old.file + +testname "check mode cache (missing) single" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail + +testname "check mode cache (missing) multiple" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail + +testname "check mode cache (missing) failure" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail + + +# --------------- apply patch ---------------------- + +$ADB push $DATA_DIR/old.file $WORK_DIR +$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR +echo hello > $tmpdir/foo +$ADB push $tmpdir/foo $WORK_DIR + +# Check that the partition has enough space to apply the patch without +# copying. If it doesn't, we'll be testing the low-space condition +# when we intend to test the not-low-space condition. +testname "apply patches (with enough space)" +free_kb=$(free_space $WORK_FS) +echo "${free_kb}kb free on /$WORK_FS." +if (( free_kb * 1024 < NEW_SIZE * 3 / 2 )); then + echo "Not enough space on /$WORK_FS to patch test file." + echo + echo "This doesn't mean that applypatch is necessarily broken;" + echo "just that /$WORK_FS doesn't have enough free space to" + echo "properly run this test." + exit 1 +fi + +testname "apply bsdiff patch" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/old.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + +testname "reapply bsdiff patch" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/old.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + + +# --------------- apply patch in new location ---------------------- + +$ADB push $DATA_DIR/old.file $WORK_DIR +$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR + +# Check that the partition has enough space to apply the patch without +# copying. If it doesn't, we'll be testing the low-space condition +# when we intend to test the not-low-space condition. +testname "apply patch to new location (with enough space)" +free_kb=$(free_space $WORK_FS) +echo "${free_kb}kb free on /$WORK_FS." +if (( free_kb * 1024 < NEW_SIZE * 3 / 2 )); then + echo "Not enough space on /$WORK_FS to patch test file." + echo + echo "This doesn't mean that applypatch is necessarily broken;" + echo "just that /$WORK_FS doesn't have enough free space to" + echo "properly run this test." + exit 1 +fi + +run_command rm $WORK_DIR/new.file +run_command rm $CACHE_TEMP_SOURCE + +testname "apply bsdiff patch to new location" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/new.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + +testname "reapply bsdiff patch to new location" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/new.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + +$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE +# put some junk in the old file +run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail + +testname "apply bsdiff patch to new location with corrupted source" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo || fail +$ADB pull $WORK_DIR/new.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + +# put some junk in the cache copy, too +run_command dd if=/dev/urandom of=$CACHE_TEMP_SOURCE count=100 bs=1024 || fail + +run_command rm $WORK_DIR/new.file +testname "apply bsdiff patch to new location with corrupted source and copy (no new file)" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo && fail + +# put some junk in the new file +run_command dd if=/dev/urandom of=$WORK_DIR/new.file count=100 bs=1024 || fail + +testname "apply bsdiff patch to new location with corrupted source and copy (bad new file)" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo && fail + +# --------------- apply patch with low space on /system ---------------------- + +$ADB push $DATA_DIR/old.file $WORK_DIR +$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR + +free_kb=$(free_space $WORK_FS) +echo "${free_kb}kb free on /$WORK_FS; we'll soon fix that." +echo run_command dd if=/dev/zero of=$WORK_DIR/bloat.dat count=$((free_kb-512)) bs=1024 || fail +run_command dd if=/dev/zero of=$WORK_DIR/bloat.dat count=$((free_kb-512)) bs=1024 || fail +free_kb=$(free_space $WORK_FS) +echo "${free_kb}kb free on /$WORK_FS now." + +testname "apply bsdiff patch with low space" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/old.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + +testname "reapply bsdiff patch with low space" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/old.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + +# --------------- apply patch with low space on /system and /cache ---------------------- + +$ADB push $DATA_DIR/old.file $WORK_DIR +$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR + +free_kb=$(free_space $WORK_FS) +echo "${free_kb}kb free on /$WORK_FS" + +run_command mkdir /cache/subdir +run_command 'echo > /cache/subdir/a.file' +run_command 'echo > /cache/a.file' +run_command mkdir /cache/recovery /cache/recovery/otatest +run_command 'echo > /cache/recovery/otatest/b.file' +run_command "echo > $CACHE_TEMP_SOURCE" +free_kb=$(free_space cache) +echo "${free_kb}kb free on /cache; we'll soon fix that." +run_command dd if=/dev/zero of=/cache/bloat_small.dat count=128 bs=1024 || fail +run_command dd if=/dev/zero of=/cache/bloat_large.dat count=$((free_kb-640)) bs=1024 || fail +free_kb=$(free_space cache) +echo "${free_kb}kb free on /cache now." + +testname "apply bsdiff patch with low space, full cache, can't delete enough" +$ADB shell 'cat >> /cache/bloat_large.dat' & open_pid=$! +echo "open_pid is $open_pid" + +# size check should fail even though it deletes some stuff +run_command $WORK_DIR/applypatch -s $NEW_SIZE && fail +run_command ls /cache/bloat_small.dat && fail # was deleted +run_command ls /cache/a.file && fail # was deleted +run_command ls /cache/recovery/otatest/b.file && fail # was deleted +run_command ls /cache/bloat_large.dat || fail # wasn't deleted because it was open +run_command ls /cache/subdir/a.file || fail # wasn't deleted because it's in a subdir +run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy + +# should fail; not enough files can be deleted +run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff && fail +run_command ls /cache/bloat_large.dat || fail # wasn't deleted because it was open +run_command ls /cache/subdir/a.file || fail # wasn't deleted because it's in a subdir +run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy + +kill $open_pid # /cache/bloat_large.dat is no longer open + +testname "apply bsdiff patch with low space, full cache, can delete enough" + +# should succeed after deleting /cache/bloat_large.dat +run_command $WORK_DIR/applypatch -s $NEW_SIZE || fail +run_command ls /cache/bloat_large.dat && fail # was deleted +run_command ls /cache/subdir/a.file || fail # still wasn't deleted because it's in a subdir +run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy + +# should succeed +run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/old.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail +run_command ls /cache/subdir/a.file || fail # still wasn't deleted because it's in a subdir +run_command ls $CACHE_TEMP_SOURCE && fail # was deleted because patching overwrote it, then deleted it + +# --------------- apply patch from cache ---------------------- + +$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE +# put some junk in the old file +run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail + +testname "apply bsdiff patch from cache (corrupted source) with low space" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/old.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + +$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE +# remove the old file entirely +run_command rm $WORK_DIR/old.file + +testname "apply bsdiff patch from cache (missing source) with low space" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/old.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + + +# --------------- cleanup ---------------------- + +cleanup + +echo +echo PASS +echo +
diff --git a/recovery/applypatch/bspatch.cpp b/recovery/applypatch/bspatch.cpp new file mode 100644 index 0000000..a4945da --- /dev/null +++ b/recovery/applypatch/bspatch.cpp
@@ -0,0 +1,246 @@ +/* + * 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. + */ + +// This file is a nearly line-for-line copy of bspatch.c from the +// bsdiff-4.3 distribution; the primary differences being how the +// input and output data are read and the error handling. Running +// applypatch with the -l option will display the bsdiff license +// notice. + +#include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> + +#include <bzlib.h> + +#include "openssl/sha.h" +#include "applypatch/applypatch.h" + +void ShowBSDiffLicense() { + puts("The bsdiff library used herein is:\n" + "\n" + "Copyright 2003-2005 Colin Percival\n" + "All rights reserved\n" + "\n" + "Redistribution and use in source and binary forms, with or without\n" + "modification, are permitted providing that the following conditions\n" + "are met:\n" + "1. Redistributions of source code must retain the above copyright\n" + " notice, this list of conditions and the following disclaimer.\n" + "2. Redistributions in binary form must reproduce the above copyright\n" + " notice, this list of conditions and the following disclaimer in the\n" + " documentation and/or other materials provided with the distribution.\n" + "\n" + "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n" + "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n" + "WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n" + "ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\n" + "DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n" + "DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n" + "OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n" + "HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n" + "STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\n" + "IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n" + "POSSIBILITY OF SUCH DAMAGE.\n" + "\n------------------\n\n" + "This program uses Julian R Seward's \"libbzip2\" library, available\n" + "from http://www.bzip.org/.\n" + ); +} + +static off_t offtin(u_char *buf) +{ + off_t y; + + y=buf[7]&0x7F; + y=y*256;y+=buf[6]; + y=y*256;y+=buf[5]; + y=y*256;y+=buf[4]; + y=y*256;y+=buf[3]; + y=y*256;y+=buf[2]; + y=y*256;y+=buf[1]; + y=y*256;y+=buf[0]; + + if(buf[7]&0x80) y=-y; + + return y; +} + +int FillBuffer(unsigned char* buffer, int size, bz_stream* stream) { + stream->next_out = (char*)buffer; + stream->avail_out = size; + while (stream->avail_out > 0) { + int bzerr = BZ2_bzDecompress(stream); + if (bzerr != BZ_OK && bzerr != BZ_STREAM_END) { + printf("bz error %d decompressing\n", bzerr); + return -1; + } + if (stream->avail_out > 0) { + printf("need %d more bytes\n", stream->avail_out); + } + } + return 0; +} + +int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size, + const Value* patch, ssize_t patch_offset, + SinkFn sink, void* token, SHA_CTX* ctx) { + + std::vector<unsigned char> new_data; + if (ApplyBSDiffPatchMem(old_data, old_size, patch, patch_offset, &new_data) != 0) { + return -1; + } + + if (sink(new_data.data(), new_data.size(), token) < static_cast<ssize_t>(new_data.size())) { + printf("short write of output: %d (%s)\n", errno, strerror(errno)); + return 1; + } + if (ctx) SHA1_Update(ctx, new_data.data(), new_data.size()); + return 0; +} + +int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, + const Value* patch, ssize_t patch_offset, + std::vector<unsigned char>* new_data) { + // Patch data format: + // 0 8 "BSDIFF40" + // 8 8 X + // 16 8 Y + // 24 8 sizeof(newfile) + // 32 X bzip2(control block) + // 32+X Y bzip2(diff block) + // 32+X+Y ??? bzip2(extra block) + // with control block a set of triples (x,y,z) meaning "add x bytes + // from oldfile to x bytes from the diff block; copy y bytes from the + // extra block; seek forwards in oldfile by z bytes". + + unsigned char* header = (unsigned char*) patch->data + patch_offset; + if (memcmp(header, "BSDIFF40", 8) != 0) { + printf("corrupt bsdiff patch file header (magic number)\n"); + return 1; + } + + ssize_t ctrl_len, data_len, new_size; + ctrl_len = offtin(header+8); + data_len = offtin(header+16); + new_size = offtin(header+24); + + if (ctrl_len < 0 || data_len < 0 || new_size < 0) { + printf("corrupt patch file header (data lengths)\n"); + return 1; + } + + int bzerr; + + bz_stream cstream; + cstream.next_in = patch->data + patch_offset + 32; + cstream.avail_in = ctrl_len; + cstream.bzalloc = NULL; + cstream.bzfree = NULL; + cstream.opaque = NULL; + if ((bzerr = BZ2_bzDecompressInit(&cstream, 0, 0)) != BZ_OK) { + printf("failed to bzinit control stream (%d)\n", bzerr); + } + + bz_stream dstream; + dstream.next_in = patch->data + patch_offset + 32 + ctrl_len; + dstream.avail_in = data_len; + dstream.bzalloc = NULL; + dstream.bzfree = NULL; + dstream.opaque = NULL; + if ((bzerr = BZ2_bzDecompressInit(&dstream, 0, 0)) != BZ_OK) { + printf("failed to bzinit diff stream (%d)\n", bzerr); + } + + bz_stream estream; + estream.next_in = patch->data + patch_offset + 32 + ctrl_len + data_len; + estream.avail_in = patch->size - (patch_offset + 32 + ctrl_len + data_len); + estream.bzalloc = NULL; + estream.bzfree = NULL; + estream.opaque = NULL; + if ((bzerr = BZ2_bzDecompressInit(&estream, 0, 0)) != BZ_OK) { + printf("failed to bzinit extra stream (%d)\n", bzerr); + } + + new_data->resize(new_size); + + off_t oldpos = 0, newpos = 0; + off_t ctrl[3]; + int i; + unsigned char buf[24]; + while (newpos < new_size) { + // Read control data + if (FillBuffer(buf, 24, &cstream) != 0) { + printf("error while reading control stream\n"); + return 1; + } + ctrl[0] = offtin(buf); + ctrl[1] = offtin(buf+8); + ctrl[2] = offtin(buf+16); + + if (ctrl[0] < 0 || ctrl[1] < 0) { + printf("corrupt patch (negative byte counts)\n"); + return 1; + } + + // Sanity check + if (newpos + ctrl[0] > new_size) { + printf("corrupt patch (new file overrun)\n"); + return 1; + } + + // Read diff string + if (FillBuffer(new_data->data() + newpos, ctrl[0], &dstream) != 0) { + printf("error while reading diff stream\n"); + return 1; + } + + // Add old data to diff string + for (i = 0; i < ctrl[0]; ++i) { + if ((oldpos+i >= 0) && (oldpos+i < old_size)) { + (*new_data)[newpos+i] += old_data[oldpos+i]; + } + } + + // Adjust pointers + newpos += ctrl[0]; + oldpos += ctrl[0]; + + // Sanity check + if (newpos + ctrl[1] > new_size) { + printf("corrupt patch (new file overrun)\n"); + return 1; + } + + // Read extra string + if (FillBuffer(new_data->data() + newpos, ctrl[1], &estream) != 0) { + printf("error while reading extra stream\n"); + return 1; + } + + // Adjust pointers + newpos += ctrl[1]; + oldpos += ctrl[2]; + } + + BZ2_bzDecompressEnd(&cstream); + BZ2_bzDecompressEnd(&dstream); + BZ2_bzDecompressEnd(&estream); + return 0; +}
diff --git a/recovery/applypatch/freecache.cpp b/recovery/applypatch/freecache.cpp new file mode 100644 index 0000000..331cae2 --- /dev/null +++ b/recovery/applypatch/freecache.cpp
@@ -0,0 +1,143 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <unistd.h> +#include <dirent.h> +#include <ctype.h> + +#include <memory> +#include <set> +#include <string> + +#include <android-base/parseint.h> +#include <android-base/stringprintf.h> + +#include "applypatch/applypatch.h" + +static int EliminateOpenFiles(std::set<std::string>* files) { + std::unique_ptr<DIR, decltype(&closedir)> d(opendir("/proc"), closedir); + if (!d) { + printf("error opening /proc: %s\n", strerror(errno)); + return -1; + } + struct dirent* de; + while ((de = readdir(d.get())) != 0) { + unsigned int pid; + if (!android::base::ParseUint(de->d_name, &pid)) { + continue; + } + std::string path = android::base::StringPrintf("/proc/%s/fd/", de->d_name); + + struct dirent* fdde; + std::unique_ptr<DIR, decltype(&closedir)> fdd(opendir(path.c_str()), closedir); + if (!fdd) { + printf("error opening %s: %s\n", path.c_str(), strerror(errno)); + continue; + } + while ((fdde = readdir(fdd.get())) != 0) { + std::string fd_path = path + fdde->d_name; + char link[FILENAME_MAX]; + + int count = readlink(fd_path.c_str(), link, sizeof(link)-1); + if (count >= 0) { + link[count] = '\0'; + if (strncmp(link, "/cache/", 7) == 0) { + if (files->erase(link) > 0) { + printf("%s is open by %s\n", link, de->d_name); + } + } + } + } + } + return 0; +} + +static std::set<std::string> FindExpendableFiles() { + std::set<std::string> files; + // We're allowed to delete unopened regular files in any of these + // directories. + const char* dirs[2] = {"/cache", "/cache/recovery/otatest"}; + + for (size_t i = 0; i < sizeof(dirs)/sizeof(dirs[0]); ++i) { + std::unique_ptr<DIR, decltype(&closedir)> d(opendir(dirs[i]), closedir); + if (!d) { + printf("error opening %s: %s\n", dirs[i], strerror(errno)); + continue; + } + + // Look for regular files in the directory (not in any subdirectories). + struct dirent* de; + while ((de = readdir(d.get())) != 0) { + std::string path = std::string(dirs[i]) + "/" + de->d_name; + + // 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) { + continue; + } + + struct stat st; + if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) { + files.insert(path); + } + } + } + + printf("%zu regular files in deletable directories\n", files.size()); + if (EliminateOpenFiles(&files) < 0) { + return std::set<std::string>(); + } + return files; +} + +int MakeFreeSpaceOnCache(size_t bytes_needed) { + size_t free_now = FreeSpaceForFile("/cache"); + printf("%zu bytes free on /cache (%zu needed)\n", free_now, bytes_needed); + + if (free_now >= bytes_needed) { + return 0; + } + std::set<std::string> files = FindExpendableFiles(); + if (files.empty()) { + // nothing we can delete to free up space! + printf("no files can be deleted to free space on /cache\n"); + return -1; + } + + // We could try to be smarter about which files to delete: the + // biggest ones? the smallest ones that will free up enough space? + // the oldest? the newest? + // + // Instead, we'll be dumb. + + for (const auto& file : files) { + unlink(file.c_str()); + free_now = FreeSpaceForFile("/cache"); + printf("deleted %s; now %zu bytes free\n", file.c_str(), free_now); + if (free_now < bytes_needed) { + break; + } + } + return (free_now >= bytes_needed) ? 0 : -1; +}
diff --git a/recovery/applypatch/imgdiff.cpp b/recovery/applypatch/imgdiff.cpp new file mode 100644 index 0000000..7c5bb86 --- /dev/null +++ b/recovery/applypatch/imgdiff.cpp
@@ -0,0 +1,1068 @@ +/* + * 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 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 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. + * + * 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. + * + * 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] + * chunk count (4) + * for each chunk: + * chunk type (4) [CHUNK_{NORMAL, GZIP, DEFLATE, RAW}] + * if chunk type == CHUNK_NORMAL: + * source start (8) + * source len (8) + * bsdiff patch offset (8) [from start of patch file] + * if chunk type == CHUNK_GZIP: (version 1 only) + * source start (8) + * source len (8) + * bsdiff patch offset (8) [from start of patch file] + * source expanded len (8) [size of uncompressed source] + * target expected len (8) [size of uncompressed target] + * gzip level (4) + * method (4) + * windowBits (4) + * memLevel (4) + * strategy (4) + * gzip header len (4) + * gzip header (gzip header len) + * gzip footer (8) + * if chunk type == CHUNK_DEFLATE: (version 2 only) + * source start (8) + * source len (8) + * bsdiff patch offset (8) [from start of patch file] + * source expanded len (8) [size of uncompressed source] + * target expected len (8) [size of uncompressed target] + * gzip level (4) + * method (4) + * windowBits (4) + * memLevel (4) + * strategy (4) + * if chunk type == RAW: (version 2 only) + * 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). + * + * 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 + * information that is stored on the system partition. + */ + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#include <sys/types.h> + +#include <bsdiff.h> + +#include "zlib.h" +#include "imgdiff.h" +#include "utils.h" + +typedef struct { + int type; // CHUNK_NORMAL, CHUNK_DEFLATE + size_t start; // offset of chunk in original image file + + size_t len; + unsigned char* data; // data to be patched (uncompressed, for deflate chunks) + + size_t source_start; + size_t source_len; + + // --- for CHUNK_DEFLATE chunks only: --- + + // original (compressed) deflate data + size_t deflate_len; + unsigned char* deflate_data; + + char* filename; // used for zip entries + + // deflate encoder parameters + int level, method, windowBits, memLevel, strategy; + + size_t source_uncompressed_len; +} ImageChunk; + +typedef struct { + int data_offset; + int deflate_len; + int uncomp_len; + char* filename; +} ZipFileEntry; + +static int fileentry_compare(const void* a, const void* b) { + int ao = ((ZipFileEntry*)a)->data_offset; + int bo = ((ZipFileEntry*)b)->data_offset; + if (ao < bo) { + return -1; + } else if (ao > bo) { + return 1; + } else { + return 0; + } +} + +unsigned char* ReadZip(const char* filename, + int* num_chunks, ImageChunk** chunks, + int include_pseudo_chunk) { + struct stat st; + if (stat(filename, &st) != 0) { + printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); + return NULL; + } + + size_t sz = static_cast<size_t>(st.st_size); + unsigned char* img = reinterpret_cast<unsigned char*>(malloc(sz)); + FILE* f = fopen(filename, "rb"); + if (fread(img, 1, sz, f) != sz) { + printf("failed to read \"%s\" %s\n", filename, strerror(errno)); + fclose(f); + return NULL; + } + fclose(f); + + // look for the end-of-central-directory record. + + int i; + for (i = st.st_size-20; i >= 0 && i > st.st_size - 65600; --i) { + if (img[i] == 0x50 && img[i+1] == 0x4b && + img[i+2] == 0x05 && img[i+3] == 0x06) { + break; + } + } + // double-check: this archive consists of a single "disk" + if (!(img[i+4] == 0 && img[i+5] == 0 && img[i+6] == 0 && img[i+7] == 0)) { + printf("can't process multi-disk archive\n"); + return NULL; + } + + int cdcount = Read2(img+i+8); + int cdoffset = Read4(img+i+16); + + ZipFileEntry* temp_entries = reinterpret_cast<ZipFileEntry*>(malloc( + cdcount * sizeof(ZipFileEntry))); + int entrycount = 0; + + unsigned char* cd = img+cdoffset; + for (i = 0; i < cdcount; ++i) { + if (!(cd[0] == 0x50 && cd[1] == 0x4b && cd[2] == 0x01 && cd[3] == 0x02)) { + printf("bad central directory entry %d\n", i); + return NULL; + } + + int clen = Read4(cd+20); // compressed len + int ulen = Read4(cd+24); // uncompressed len + int nlen = Read2(cd+28); // filename len + int xlen = Read2(cd+30); // extra field len + int mlen = Read2(cd+32); // file comment len + int hoffset = Read4(cd+42); // local header offset + + char* filename = reinterpret_cast<char*>(malloc(nlen+1)); + memcpy(filename, cd+46, nlen); + filename[nlen] = '\0'; + + int method = Read2(cd+10); + + cd += 46 + nlen + xlen + mlen; + + if (method != 8) { // 8 == deflate + free(filename); + continue; + } + + unsigned char* lh = img + hoffset; + + if (!(lh[0] == 0x50 && lh[1] == 0x4b && lh[2] == 0x03 && lh[3] == 0x04)) { + printf("bad local file header entry %d\n", i); + return NULL; + } + + if (Read2(lh+26) != nlen || memcmp(lh+30, filename, nlen) != 0) { + printf("central dir filename doesn't match local header\n"); + return NULL; + } + + xlen = Read2(lh+28); // extra field len; might be different from CD entry? + + temp_entries[entrycount].data_offset = hoffset+30+nlen+xlen; + temp_entries[entrycount].deflate_len = clen; + temp_entries[entrycount].uncomp_len = ulen; + temp_entries[entrycount].filename = filename; + ++entrycount; + } + + qsort(temp_entries, entrycount, sizeof(ZipFileEntry), fileentry_compare); + +#if 0 + printf("found %d deflated entries\n", entrycount); + for (i = 0; i < entrycount; ++i) { + printf("off %10d len %10d unlen %10d %p %s\n", + temp_entries[i].data_offset, + temp_entries[i].deflate_len, + temp_entries[i].uncomp_len, + temp_entries[i].filename, + temp_entries[i].filename); + } +#endif + + *num_chunks = 0; + *chunks = reinterpret_cast<ImageChunk*>(malloc((entrycount*2+2) * sizeof(ImageChunk))); + ImageChunk* curr = *chunks; + + if (include_pseudo_chunk) { + curr->type = CHUNK_NORMAL; + curr->start = 0; + curr->len = st.st_size; + curr->data = img; + curr->filename = NULL; + ++curr; + ++*num_chunks; + } + + int pos = 0; + int nextentry = 0; + + while (pos < st.st_size) { + if (nextentry < entrycount && pos == temp_entries[nextentry].data_offset) { + curr->type = CHUNK_DEFLATE; + curr->start = pos; + curr->deflate_len = temp_entries[nextentry].deflate_len; + curr->deflate_data = img + pos; + curr->filename = temp_entries[nextentry].filename; + + curr->len = temp_entries[nextentry].uncomp_len; + curr->data = reinterpret_cast<unsigned char*>(malloc(curr->len)); + + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = curr->deflate_len; + strm.next_in = curr->deflate_data; + + // -15 means we are decoding a 'raw' deflate stream; zlib will + // not expect zlib headers. + int ret = inflateInit2(&strm, -15); + + strm.avail_out = curr->len; + strm.next_out = curr->data; + ret = inflate(&strm, Z_NO_FLUSH); + if (ret != Z_STREAM_END) { + printf("failed to inflate \"%s\"; %d\n", curr->filename, ret); + return NULL; + } + + inflateEnd(&strm); + + pos += curr->deflate_len; + ++nextentry; + ++*num_chunks; + ++curr; + continue; + } + + // use a normal chunk to take all the data up to the start of the + // next deflate section. + + curr->type = CHUNK_NORMAL; + curr->start = pos; + if (nextentry < entrycount) { + curr->len = temp_entries[nextentry].data_offset - pos; + } else { + curr->len = st.st_size - pos; + } + curr->data = img + pos; + curr->filename = NULL; + pos += curr->len; + + ++*num_chunks; + ++curr; + } + + free(temp_entries); + return img; +} + +/* + * Read the given file and break it up into chunks, putting the number + * of chunks and their info in *num_chunks and **chunks, + * respectively. Returns a malloc'd block of memory containing the + * contents of the file; various pointers in the output chunk array + * will point into this block of memory. The caller should free the + * return value when done with all the chunks. Returns NULL on + * failure. + */ +unsigned char* ReadImage(const char* filename, + int* num_chunks, ImageChunk** chunks) { + struct stat st; + if (stat(filename, &st) != 0) { + printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); + return NULL; + } + + size_t sz = static_cast<size_t>(st.st_size); + unsigned char* img = reinterpret_cast<unsigned char*>(malloc(sz + 4)); + FILE* f = fopen(filename, "rb"); + if (fread(img, 1, sz, f) != sz) { + printf("failed to read \"%s\" %s\n", filename, strerror(errno)); + fclose(f); + return NULL; + } + fclose(f); + + // append 4 zero bytes to the data so we can always search for the + // four-byte string 1f8b0800 starting at any point in the actual + // file data, without special-casing the end of the data. + memset(img+sz, 0, 4); + + size_t pos = 0; + + *num_chunks = 0; + *chunks = NULL; + + while (pos < sz) { + unsigned char* p = img+pos; + + if (sz - pos >= 4 && + p[0] == 0x1f && p[1] == 0x8b && + p[2] == 0x08 && // deflate compression + p[3] == 0x00) { // no header flags + // 'pos' is the offset of the start of a gzip chunk. + size_t chunk_offset = pos; + + *num_chunks += 3; + *chunks = reinterpret_cast<ImageChunk*>(realloc(*chunks, + *num_chunks * sizeof(ImageChunk))); + ImageChunk* curr = *chunks + (*num_chunks-3); + + // create a normal chunk for the header. + curr->start = pos; + curr->type = CHUNK_NORMAL; + curr->len = GZIP_HEADER_LEN; + curr->data = p; + + pos += curr->len; + p += curr->len; + ++curr; + + curr->type = CHUNK_DEFLATE; + curr->filename = NULL; + + // We must decompress this chunk in order to discover where it + // ends, and so we can put the uncompressed data and its length + // into curr->data and curr->len. + + size_t allocated = 32768; + curr->len = 0; + curr->data = reinterpret_cast<unsigned char*>(malloc(allocated)); + curr->start = pos; + curr->deflate_data = p; + + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = sz - pos; + strm.next_in = p; + + // -15 means we are decoding a 'raw' deflate stream; zlib will + // not expect zlib headers. + int ret = inflateInit2(&strm, -15); + + do { + strm.avail_out = allocated - curr->len; + strm.next_out = curr->data + curr->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); + break; + } + curr->len = allocated - strm.avail_out; + if (strm.avail_out == 0) { + allocated *= 2; + curr->data = reinterpret_cast<unsigned char*>(realloc(curr->data, allocated)); + } + } while (ret != Z_STREAM_END); + + curr->deflate_len = sz - strm.avail_in - pos; + inflateEnd(&strm); + + if (ret < 0) { + free(curr->data); + *num_chunks -= 2; + continue; + } + + pos += curr->deflate_len; + p += curr->deflate_len; + ++curr; + + // create a normal chunk for the footer + + curr->type = CHUNK_NORMAL; + curr->start = pos; + curr->len = GZIP_FOOTER_LEN; + curr->data = img+pos; + + pos += curr->len; + p += curr->len; + ++curr; + + // The footer (that we just skipped over) contains the size of + // the uncompressed data. Double-check to make sure that it + // matches the size of the data we got when we actually did + // the decompression. + size_t footer_size = Read4(p-4); + if (footer_size != curr[-2].len) { + printf("Error: footer size %zu != decompressed size %zu\n", + footer_size, curr[-2].len); + free(img); + return NULL; + } + } else { + // Reallocate the list for every chunk; we expect the number of + // chunks to be small (5 for typical boot and recovery images). + ++*num_chunks; + *chunks = reinterpret_cast<ImageChunk*>(realloc(*chunks, *num_chunks * sizeof(ImageChunk))); + ImageChunk* curr = *chunks + (*num_chunks-1); + curr->start = pos; + + // 'pos' is not the offset of the start of a gzip chunk, so scan + // forward until we find a gzip header. + curr->type = CHUNK_NORMAL; + curr->data = p; + + for (curr->len = 0; curr->len < (sz - pos); ++curr->len) { + if (p[curr->len] == 0x1f && + p[curr->len+1] == 0x8b && + p[curr->len+2] == 0x08 && + p[curr->len+3] == 0x00) { + break; + } + } + pos += curr->len; + } + } + + return img; +} + +#define BUFFER_SIZE 32768 + +/* + * 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). Return 0 on success. + */ +int TryReconstruction(ImageChunk* chunk, unsigned char* out) { + size_t p = 0; + +#if 0 + printf("trying %d %d %d %d %d\n", + chunk->level, chunk->method, chunk->windowBits, + chunk->memLevel, chunk->strategy); +#endif + + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = chunk->len; + strm.next_in = chunk->data; + int ret; + ret = deflateInit2(&strm, chunk->level, chunk->method, chunk->windowBits, + chunk->memLevel, chunk->strategy); + do { + strm.avail_out = BUFFER_SIZE; + strm.next_out = out; + ret = deflate(&strm, Z_FINISH); + size_t have = BUFFER_SIZE - strm.avail_out; + + if (memcmp(out, chunk->deflate_data+p, have) != 0) { + // mismatch; data isn't the same. + deflateEnd(&strm); + return -1; + } + p += have; + } while (ret != Z_STREAM_END); + deflateEnd(&strm); + if (p != chunk->deflate_len) { + // mismatch; ran out of data before we should have. + return -1; + } + return 0; +} + +/* + * 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. Returns 0 on success. + */ +int ReconstructDeflateChunk(ImageChunk* chunk) { + if (chunk->type != CHUNK_DEFLATE) { + printf("attempt to reconstruct non-deflate chunk\n"); + return -1; + } + + unsigned char* out = reinterpret_cast<unsigned char*>(malloc(BUFFER_SIZE)); + + // We only check two combinations of encoder parameters: level 6 + // (the default) and level 9 (the maximum). + for (chunk->level = 6; chunk->level <= 9; chunk->level += 3) { + chunk->windowBits = -15; // 32kb window; negative to indicate a raw stream. + chunk->memLevel = 8; // the default value. + chunk->method = Z_DEFLATED; + chunk->strategy = Z_DEFAULT_STRATEGY; + + if (TryReconstruction(chunk, out) == 0) { + free(out); + return 0; + } + } + + free(out); + return -1; +} + +/* + * Given source and target chunks, compute a bsdiff patch between them + * by running bsdiff in a subprocess. Return the patch data, placing + * its length in *size. Return NULL on failure. We expect the bsdiff + * program to be in the path. + */ +unsigned char* MakePatch(ImageChunk* src, ImageChunk* tgt, size_t* size) { + if (tgt->type == CHUNK_NORMAL) { + if (tgt->len <= 160) { + tgt->type = CHUNK_RAW; + *size = tgt->len; + return tgt->data; + } + } + + char ptemp[] = "/tmp/imgdiff-patch-XXXXXX"; + int fd = mkstemp(ptemp); + + if (fd == -1) { + printf("MakePatch failed to create a temporary file: %s\n", + strerror(errno)); + return NULL; + } + close(fd); // temporary file is created and we don't need its file + // descriptor + + int r = bsdiff::bsdiff(src->data, src->len, tgt->data, tgt->len, ptemp); + if (r != 0) { + printf("bsdiff() failed: %d\n", r); + return NULL; + } + + struct stat st; + if (stat(ptemp, &st) != 0) { + printf("failed to stat patch file %s: %s\n", + ptemp, strerror(errno)); + return NULL; + } + + size_t sz = static_cast<size_t>(st.st_size); + // TODO: Memory leak on error return. + unsigned char* data = reinterpret_cast<unsigned char*>(malloc(sz)); + + if (tgt->type == CHUNK_NORMAL && tgt->len <= sz) { + unlink(ptemp); + + tgt->type = CHUNK_RAW; + *size = tgt->len; + return tgt->data; + } + + *size = sz; + + FILE* f = fopen(ptemp, "rb"); + if (f == NULL) { + printf("failed to open patch %s: %s\n", ptemp, strerror(errno)); + return NULL; + } + if (fread(data, 1, sz, f) != sz) { + printf("failed to read patch %s: %s\n", ptemp, strerror(errno)); + return NULL; + } + fclose(f); + + unlink(ptemp); + + tgt->source_start = src->start; + switch (tgt->type) { + case CHUNK_NORMAL: + tgt->source_len = src->len; + break; + case CHUNK_DEFLATE: + tgt->source_len = src->deflate_len; + tgt->source_uncompressed_len = src->len; + break; + } + + return data; +} + +/* + * 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(ImageChunk* ch) { + if (ch->type != CHUNK_DEFLATE) return; + ch->type = CHUNK_NORMAL; + free(ch->data); + ch->data = ch->deflate_data; + ch->len = ch->deflate_len; +} + +/* + * Return true if the data in the chunk is identical (including the + * compressed representation, for gzip chunks). + */ +int AreChunksEqual(ImageChunk* a, ImageChunk* b) { + if (a->type != b->type) return 0; + + switch (a->type) { + case CHUNK_NORMAL: + return a->len == b->len && memcmp(a->data, b->data, a->len) == 0; + + case CHUNK_DEFLATE: + return a->deflate_len == b->deflate_len && + memcmp(a->deflate_data, b->deflate_data, a->deflate_len) == 0; + + default: + printf("unknown chunk type %d\n", a->type); + return 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(ImageChunk* chunks, int* num_chunks) { + int out = 0; + int in_start = 0, in_end; + while (in_start < *num_chunks) { + if (chunks[in_start].type != CHUNK_NORMAL) { + in_end = in_start+1; + } else { + // in_start is a normal chunk. Look for a run of normal chunks + // that constitute a solid block of data (ie, each chunk begins + // where the previous one ended). + for (in_end = in_start+1; + in_end < *num_chunks && chunks[in_end].type == CHUNK_NORMAL && + (chunks[in_end].start == + chunks[in_end-1].start + chunks[in_end-1].len && + chunks[in_end].data == + chunks[in_end-1].data + chunks[in_end-1].len); + ++in_end); + } + + if (in_end == in_start+1) { +#if 0 + printf("chunk %d is now %d\n", in_start, out); +#endif + if (out != in_start) { + memcpy(chunks+out, chunks+in_start, sizeof(ImageChunk)); + } + } else { +#if 0 + printf("collapse normal chunks %d-%d into %d\n", in_start, in_end-1, out); +#endif + + // Merge chunks [in_start, in_end-1] into one chunk. Since the + // data member of each chunk is just a pointer into an in-memory + // copy of the file, this can be done without recopying (the + // output chunk has the first chunk's start location and data + // pointer, and length equal to the sum of the input chunk + // lengths). + chunks[out].type = CHUNK_NORMAL; + chunks[out].start = chunks[in_start].start; + chunks[out].data = chunks[in_start].data; + chunks[out].len = chunks[in_end-1].len + + (chunks[in_end-1].start - chunks[in_start].start); + } + + ++out; + in_start = in_end; + } + *num_chunks = out; +} + +ImageChunk* FindChunkByName(const char* name, + ImageChunk* chunks, int num_chunks) { + int i; + for (i = 0; i < num_chunks; ++i) { + if (chunks[i].type == CHUNK_DEFLATE && chunks[i].filename && + strcmp(name, chunks[i].filename) == 0) { + return chunks+i; + } + } + return NULL; +} + +void DumpChunks(ImageChunk* chunks, int num_chunks) { + for (int i = 0; i < num_chunks; ++i) { + printf("chunk %d: type %d start %zu len %zu\n", + i, chunks[i].type, chunks[i].start, chunks[i].len); + } +} + +int main(int argc, char** argv) { + int zip_mode = 0; + + if (argc >= 2 && strcmp(argv[1], "-z") == 0) { + zip_mode = 1; + --argc; + ++argv; + } + + size_t bonus_size = 0; + unsigned char* bonus_data = NULL; + if (argc >= 3 && strcmp(argv[1], "-b") == 0) { + struct stat st; + if (stat(argv[2], &st) != 0) { + printf("failed to stat bonus file %s: %s\n", argv[2], strerror(errno)); + return 1; + } + bonus_size = st.st_size; + bonus_data = reinterpret_cast<unsigned char*>(malloc(bonus_size)); + FILE* f = fopen(argv[2], "rb"); + if (f == NULL) { + printf("failed to open bonus file %s: %s\n", argv[2], strerror(errno)); + return 1; + } + if (fread(bonus_data, 1, bonus_size, f) != bonus_size) { + printf("failed to read bonus file %s: %s\n", argv[2], strerror(errno)); + return 1; + } + fclose(f); + + argc -= 2; + argv += 2; + } + + if (argc != 4) { + printf("usage: %s [-z] [-b <bonus-file>] <src-img> <tgt-img> <patch-file>\n", + argv[0]); + return 2; + } + + int num_src_chunks; + ImageChunk* src_chunks; + int num_tgt_chunks; + ImageChunk* tgt_chunks; + int i; + + if (zip_mode) { + if (ReadZip(argv[1], &num_src_chunks, &src_chunks, 1) == NULL) { + printf("failed to break apart source zip file\n"); + return 1; + } + if (ReadZip(argv[2], &num_tgt_chunks, &tgt_chunks, 0) == NULL) { + printf("failed to break apart target zip file\n"); + return 1; + } + } else { + if (ReadImage(argv[1], &num_src_chunks, &src_chunks) == NULL) { + printf("failed to break apart source image\n"); + return 1; + } + if (ReadImage(argv[2], &num_tgt_chunks, &tgt_chunks) == NULL) { + printf("failed to break apart target image\n"); + return 1; + } + + // Verify that the source and target images have the same chunk + // structure (ie, the same sequence of deflate and normal chunks). + + if (!zip_mode) { + // Merge the gzip header and footer in with any adjacent + // normal chunks. + MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks); + MergeAdjacentNormalChunks(src_chunks, &num_src_chunks); + } + + if (num_src_chunks != num_tgt_chunks) { + printf("source and target don't have same number of chunks!\n"); + printf("source chunks:\n"); + DumpChunks(src_chunks, num_src_chunks); + printf("target chunks:\n"); + DumpChunks(tgt_chunks, num_tgt_chunks); + return 1; + } + for (i = 0; i < num_src_chunks; ++i) { + if (src_chunks[i].type != tgt_chunks[i].type) { + printf("source and target don't have same chunk " + "structure! (chunk %d)\n", i); + printf("source chunks:\n"); + DumpChunks(src_chunks, num_src_chunks); + printf("target chunks:\n"); + DumpChunks(tgt_chunks, num_tgt_chunks); + return 1; + } + } + } + + for (i = 0; i < num_tgt_chunks; ++i) { + if (tgt_chunks[i].type == 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 (ReconstructDeflateChunk(tgt_chunks+i) < 0) { + printf("failed to reconstruct target deflate chunk %d [%s]; " + "treating as normal\n", i, tgt_chunks[i].filename); + ChangeDeflateChunkToNormal(tgt_chunks+i); + if (zip_mode) { + ImageChunk* src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks); + if (src) { + ChangeDeflateChunkToNormal(src); + } + } else { + ChangeDeflateChunkToNormal(src_chunks+i); + } + continue; + } + + // If two deflate chunks are identical (eg, the kernel has not + // changed between two builds), treat them as normal chunks. + // This makes applypatch much faster -- it can apply a trivial + // patch to the compressed data, rather than uncompressing and + // recompressing to apply the trivial patch to the uncompressed + // data. + ImageChunk* src; + if (zip_mode) { + src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks); + } else { + src = src_chunks+i; + } + + if (src == NULL || AreChunksEqual(tgt_chunks+i, src)) { + ChangeDeflateChunkToNormal(tgt_chunks+i); + if (src) { + ChangeDeflateChunkToNormal(src); + } + } + } + } + + // 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, &num_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, &num_tgt_chunks); + MergeAdjacentNormalChunks(src_chunks, &num_src_chunks); + if (num_src_chunks != num_tgt_chunks) { + // This shouldn't happen. + printf("merging normal chunks went awry\n"); + return 1; + } + } + + // Compute bsdiff patches for each chunk's data (the uncompressed + // data, in the case of deflate chunks). + + DumpChunks(src_chunks, num_src_chunks); + + printf("Construct patches for %d chunks...\n", num_tgt_chunks); + unsigned char** patch_data = reinterpret_cast<unsigned char**>(malloc( + num_tgt_chunks * sizeof(unsigned char*))); + size_t* patch_size = reinterpret_cast<size_t*>(malloc(num_tgt_chunks * sizeof(size_t))); + for (i = 0; i < num_tgt_chunks; ++i) { + if (zip_mode) { + ImageChunk* src; + if (tgt_chunks[i].type == CHUNK_DEFLATE && + (src = FindChunkByName(tgt_chunks[i].filename, src_chunks, + num_src_chunks))) { + patch_data[i] = MakePatch(src, tgt_chunks+i, patch_size+i); + } else { + patch_data[i] = MakePatch(src_chunks, tgt_chunks+i, patch_size+i); + } + } else { + if (i == 1 && bonus_data) { + printf(" using %zu bytes of bonus data for chunk %d\n", bonus_size, i); + src_chunks[i].data = reinterpret_cast<unsigned char*>(realloc(src_chunks[i].data, + src_chunks[i].len + bonus_size)); + memcpy(src_chunks[i].data+src_chunks[i].len, bonus_data, bonus_size); + src_chunks[i].len += bonus_size; + } + + patch_data[i] = MakePatch(src_chunks+i, tgt_chunks+i, patch_size+i); + } + printf("patch %3d is %zu bytes (of %zu)\n", + i, patch_size[i], tgt_chunks[i].source_len); + } + + // 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 (i = 0; i < num_tgt_chunks; ++i) { + total_header_size += 4; + switch (tgt_chunks[i].type) { + case CHUNK_NORMAL: + total_header_size += 8*3; + break; + case CHUNK_DEFLATE: + total_header_size += 8*5 + 4*5; + break; + case CHUNK_RAW: + total_header_size += 4 + patch_size[i]; + break; + } + } + + size_t offset = total_header_size; + + FILE* f = fopen(argv[3], "wb"); + + // Write out the headers. + + fwrite("IMGDIFF2", 1, 8, f); + Write4(num_tgt_chunks, f); + for (i = 0; i < num_tgt_chunks; ++i) { + Write4(tgt_chunks[i].type, f); + + switch (tgt_chunks[i].type) { + case CHUNK_NORMAL: + printf("chunk %3d: normal (%10zu, %10zu) %10zu\n", i, + tgt_chunks[i].start, tgt_chunks[i].len, patch_size[i]); + Write8(tgt_chunks[i].source_start, f); + Write8(tgt_chunks[i].source_len, f); + Write8(offset, f); + offset += patch_size[i]; + break; + + case CHUNK_DEFLATE: + printf("chunk %3d: deflate (%10zu, %10zu) %10zu %s\n", i, + tgt_chunks[i].start, tgt_chunks[i].deflate_len, patch_size[i], + tgt_chunks[i].filename); + Write8(tgt_chunks[i].source_start, f); + Write8(tgt_chunks[i].source_len, f); + Write8(offset, f); + Write8(tgt_chunks[i].source_uncompressed_len, f); + Write8(tgt_chunks[i].len, f); + Write4(tgt_chunks[i].level, f); + Write4(tgt_chunks[i].method, f); + Write4(tgt_chunks[i].windowBits, f); + Write4(tgt_chunks[i].memLevel, f); + Write4(tgt_chunks[i].strategy, f); + offset += patch_size[i]; + break; + + case CHUNK_RAW: + printf("chunk %3d: raw (%10zu, %10zu)\n", i, + tgt_chunks[i].start, tgt_chunks[i].len); + Write4(patch_size[i], f); + fwrite(patch_data[i], 1, patch_size[i], f); + break; + } + } + + // Append each chunk's bsdiff patch, in order. + + for (i = 0; i < num_tgt_chunks; ++i) { + if (tgt_chunks[i].type != CHUNK_RAW) { + fwrite(patch_data[i], 1, patch_size[i], f); + } + } + + fclose(f); + + return 0; +}
diff --git a/recovery/applypatch/imgdiff.h b/recovery/applypatch/imgdiff.h new file mode 100644 index 0000000..f2069b4 --- /dev/null +++ b/recovery/applypatch/imgdiff.h
@@ -0,0 +1,30 @@ +/* + * 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. + */ + +// Image patch chunk types +#define CHUNK_NORMAL 0 +#define CHUNK_GZIP 1 // version 1 only +#define CHUNK_DEFLATE 2 // version 2 only +#define CHUNK_RAW 3 // version 2 only + +// The gzip header size is actually variable, but we currently don't +// support gzipped data with any of the optional fields, so for now it +// will always be ten bytes. See RFC 1952 for the definition of the +// gzip format. +#define GZIP_HEADER_LEN 10 + +// The gzip footer size really is fixed. +#define GZIP_FOOTER_LEN 8
diff --git a/recovery/applypatch/imgdiff_test.sh b/recovery/applypatch/imgdiff_test.sh new file mode 100755 index 0000000..dcdb922 --- /dev/null +++ b/recovery/applypatch/imgdiff_test.sh
@@ -0,0 +1,118 @@ +#!/bin/bash +# +# A script for testing imgdiff/applypatch. It takes two full OTA +# packages as arguments. It generates (on the host) patches for all +# the zip/jar/apk files they have in common, as well as boot and +# recovery images. It then applies the patches on the device (or +# emulator) and checks that the resulting file is correct. + +EMULATOR_PORT=5580 + +# set to 0 to use a device instead +USE_EMULATOR=0 + +# where on the device to do all the patching. +WORK_DIR=/data/local/tmp + +START_OTA_PACKAGE=$1 +END_OTA_PACKAGE=$2 + +# ------------------------ + +tmpdir=$(mktemp -d) + +if [ "$USE_EMULATOR" == 1 ]; then + emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT & + pid_emulator=$! + ADB="adb -s emulator-$EMULATOR_PORT " +else + ADB="adb -d " +fi + +echo "waiting to connect to device" +$ADB wait-for-device + +# run a command on the device; exit with the exit status of the device +# command. +run_command() { + $ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}' +} + +testname() { + echo + echo "$1"... + testname="$1" +} + +fail() { + echo + echo FAIL: $testname + echo + [ "$open_pid" == "" ] || kill $open_pid + [ "$pid_emulator" == "" ] || kill $pid_emulator + exit 1 +} + +sha1() { + sha1sum $1 | awk '{print $1}' +} + +size() { + stat -c %s $1 | tr -d '\n' +} + +cleanup() { + # not necessary if we're about to kill the emulator, but nice for + # running on real devices or already-running emulators. + testname "removing test files" + run_command rm $WORK_DIR/applypatch + run_command rm $WORK_DIR/source + run_command rm $WORK_DIR/target + run_command rm $WORK_DIR/patch + + [ "$pid_emulator" == "" ] || kill $pid_emulator + + rm -rf $tmpdir +} + +$ADB push $ANDROID_PRODUCT_OUT/system/bin/applypatch $WORK_DIR/applypatch + +patch_and_apply() { + local fn=$1 + shift + + unzip -p $START_OTA_PACKAGE $fn > $tmpdir/source + unzip -p $END_OTA_PACKAGE $fn > $tmpdir/target + imgdiff "$@" $tmpdir/source $tmpdir/target $tmpdir/patch + bsdiff $tmpdir/source $tmpdir/target $tmpdir/patch.bs + echo "patch for $fn is $(size $tmpdir/patch) [of $(size $tmpdir/target)] ($(size $tmpdir/patch.bs) with bsdiff)" + echo "$fn $(size $tmpdir/patch) of $(size $tmpdir/target) bsdiff $(size $tmpdir/patch.bs)" >> /tmp/stats.txt + $ADB push $tmpdir/source $WORK_DIR/source || fail "source push failed" + run_command rm /data/local/tmp/target + $ADB push $tmpdir/patch $WORK_DIR/patch || fail "patch push failed" + run_command /data/local/tmp/applypatch /data/local/tmp/source \ + /data/local/tmp/target $(sha1 $tmpdir/target) $(size $tmpdir/target) \ + $(sha1 $tmpdir/source):/data/local/tmp/patch \ + || fail "applypatch of $fn failed" + $ADB pull /data/local/tmp/target $tmpdir/result + diff -q $tmpdir/target $tmpdir/result || fail "patch output not correct!" +} + +# --------------- basic execution ---------------------- + +for i in $((zipinfo -1 $START_OTA_PACKAGE; zipinfo -1 $END_OTA_PACKAGE) | \ + sort | uniq -d | egrep -e '[.](apk|jar|zip)$'); do + patch_and_apply $i -z +done +patch_and_apply boot.img +patch_and_apply system/recovery.img + + +# --------------- cleanup ---------------------- + +cleanup + +echo +echo PASS +echo +
diff --git a/recovery/applypatch/imgpatch.cpp b/recovery/applypatch/imgpatch.cpp new file mode 100644 index 0000000..f5aed76 --- /dev/null +++ b/recovery/applypatch/imgpatch.cpp
@@ -0,0 +1,246 @@ +/* + * 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. + */ + +// See imgdiff.c in this directory for a description of the patch file +// format. + +#include <stdio.h> +#include <sys/cdefs.h> +#include <sys/stat.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> + +#include <vector> + +#include "zlib.h" +#include "openssl/sha.h" +#include "applypatch/applypatch.h" +#include "imgdiff.h" +#include "utils.h" + +int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, + const unsigned char* patch_data, ssize_t patch_size, + SinkFn sink, void* token) { + Value patch = {VAL_BLOB, patch_size, + reinterpret_cast<char*>(const_cast<unsigned char*>(patch_data))}; + return ApplyImagePatch( + old_data, old_size, &patch, sink, token, 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, ssize_t old_size, + const Value* patch, + SinkFn sink, void* token, SHA_CTX* ctx, + const Value* bonus_data) { + ssize_t pos = 12; + char* header = patch->data; + if (patch->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.) + if (memcmp(header, "IMGDIFF2", 8) != 0) { + printf("corrupt patch file header (magic number)\n"); + return -1; + } + + int num_chunks = Read4(header+8); + + int i; + for (i = 0; i < num_chunks; ++i) { + // each chunk's header record starts with 4 bytes. + if (pos + 4 > patch->size) { + printf("failed to read chunk %d record\n", i); + return -1; + } + int type = Read4(patch->data + pos); + pos += 4; + + if (type == CHUNK_NORMAL) { + char* normal_header = patch->data + pos; + pos += 24; + if (pos > patch->size) { + printf("failed to read chunk %d normal header data\n", i); + return -1; + } + + size_t src_start = Read8(normal_header); + size_t src_len = Read8(normal_header+8); + size_t patch_offset = Read8(normal_header+16); + + if (src_start + src_len > static_cast<size_t>(old_size)) { + printf("source data too short\n"); + return -1; + } + ApplyBSDiffPatch(old_data + src_start, src_len, + patch, patch_offset, sink, token, ctx); + } else if (type == CHUNK_RAW) { + char* raw_header = patch->data + pos; + pos += 4; + if (pos > patch->size) { + printf("failed to read chunk %d raw header data\n", i); + return -1; + } + + ssize_t data_len = Read4(raw_header); + + if (pos + data_len > patch->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((unsigned char*)patch->data + pos, + data_len, token) != 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. + char* deflate_header = patch->data + pos; + pos += 60; + if (pos > patch->size) { + printf("failed to read chunk %d deflate header data\n", i); + return -1; + } + + size_t src_start = Read8(deflate_header); + size_t src_len = Read8(deflate_header+8); + size_t patch_offset = Read8(deflate_header+16); + size_t expanded_len = Read8(deflate_header+24); + size_t target_len = Read8(deflate_header+32); + int level = Read4(deflate_header+40); + int method = Read4(deflate_header+44); + int windowBits = Read4(deflate_header+48); + int memLevel = Read4(deflate_header+52); + int strategy = Read4(deflate_header+56); + + if (src_start + src_len > static_cast<size_t>(old_size)) { + printf("source data too short\n"); + return -1; + } + + // Decompress the source data; the chunk header tells us exactly + // how big we expect it to be when decompressed. + + // Note: expanded_len will include the bonus data size if + // the patch was constructed with bonus data. The + // deflation will come up 'bonus_size' bytes short; these + // must be appended from the bonus_data value. + size_t bonus_size = (i == 1 && bonus_data != NULL) ? bonus_data->size : 0; + + std::vector<unsigned char> expanded_source(expanded_len); + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = src_len; + strm.next_in = (unsigned char*)(old_data + src_start); + strm.avail_out = expanded_len; + strm.next_out = expanded_source.data(); + + int ret; + ret = inflateInit2(&strm, -15); + if (ret != Z_OK) { + printf("failed to init source inflation: %d\n", ret); + return -1; + } + + // Because we've provided enough room to accommodate the output + // data, we expect one call to inflate() to suffice. + ret = inflate(&strm, Z_SYNC_FLUSH); + if (ret != Z_STREAM_END) { + printf("source inflation returned %d\n", ret); + return -1; + } + // We should have filled the output buffer exactly, except + // for the bonus_size. + if (strm.avail_out != bonus_size) { + printf("source inflation short by %zu bytes\n", strm.avail_out-bonus_size); + return -1; + } + inflateEnd(&strm); + + if (bonus_size) { + memcpy(expanded_source.data() + (expanded_len - bonus_size), + bonus_data->data, bonus_size); + } + + // Next, apply the bsdiff patch (in memory) to the uncompressed + // data. + std::vector<unsigned char> uncompressed_target_data; + if (ApplyBSDiffPatchMem(expanded_source.data(), expanded_len, + patch, patch_offset, + &uncompressed_target_data) != 0) { + return -1; + } + if (uncompressed_target_data.size() != target_len) { + printf("expected target len to be %zu, but it's %zu\n", + target_len, uncompressed_target_data.size()); + return -1; + } + + // Now compress the target data and append it to the output. + + // we're done with the expanded_source data buffer, so we'll + // reuse that memory to receive the output of deflate. + if (expanded_source.size() < 32768U) { + expanded_source.resize(32768U); + } + std::vector<unsigned char>& temp_data = expanded_source; + + // now the deflate stream + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = uncompressed_target_data.size(); + strm.next_in = uncompressed_target_data.data(); + ret = deflateInit2(&strm, level, method, windowBits, memLevel, strategy); + if (ret != Z_OK) { + printf("failed to init uncompressed data deflation: %d\n", ret); + return -1; + } + do { + strm.avail_out = temp_data.size(); + strm.next_out = temp_data.data(); + ret = deflate(&strm, Z_FINISH); + ssize_t have = temp_data.size() - strm.avail_out; + + if (sink(temp_data.data(), have, token) != have) { + printf("failed to write %zd compressed bytes to output\n", + have); + return -1; + } + if (ctx) SHA1_Update(ctx, temp_data.data(), have); + } while (ret != Z_STREAM_END); + deflateEnd(&strm); + } else { + printf("patch chunk %d is unknown type %d\n", i, type); + return -1; + } + } + + return 0; +}
diff --git a/recovery/applypatch/include/applypatch/applypatch.h b/recovery/applypatch/include/applypatch/applypatch.h new file mode 100644 index 0000000..9ee39d2 --- /dev/null +++ b/recovery/applypatch/include/applypatch/applypatch.h
@@ -0,0 +1,87 @@ +/* + * 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. + */ + +#ifndef _APPLYPATCH_H +#define _APPLYPATCH_H + +#include <stdint.h> +#include <sys/stat.h> + +#include <vector> + +#include "openssl/sha.h" +#include "edify/expr.h" + +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" + +typedef ssize_t (*SinkFn)(const unsigned char*, ssize_t, void*); + +// applypatch.c +int ShowLicenses(); +size_t FreeSpaceForFile(const char* filename); +int CacheSizeCheck(size_t bytes); +int ParseSha1(const char* str, uint8_t* digest); + +int applypatch_flash(const char* source_filename, const char* target_filename, + const char* target_sha1_str, size_t target_size); +int applypatch(const char* source_filename, + const char* target_filename, + const char* target_sha1_str, + size_t target_size, + int num_patches, + char** const patch_sha1_str, + Value** patch_data, + Value* bonus_data); +int applypatch_check(const char* filename, + int num_patches, + char** const patch_sha1_str); + +int LoadFileContents(const char* filename, FileContents* file); +int SaveFileContents(const char* filename, const FileContents* file); +void FreeFileContents(FileContents* file); +int FindMatchingPatch(uint8_t* sha1, char* const * const patch_sha1_str, + int num_patches); + +// bsdiff.cpp +void ShowBSDiffLicense(); +int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size, + const Value* patch, ssize_t patch_offset, + SinkFn sink, void* token, SHA_CTX* ctx); +int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, + const Value* patch, ssize_t patch_offset, + std::vector<unsigned char>* new_data); + +// imgpatch.cpp +int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, + const Value* patch, + SinkFn sink, void* token, SHA_CTX* ctx, + const Value* bonus_data); + +// freecache.cpp +int MakeFreeSpaceOnCache(size_t bytes_needed); + +#endif
diff --git a/recovery/applypatch/include/applypatch/imgpatch.h b/recovery/applypatch/include/applypatch/imgpatch.h new file mode 100644 index 0000000..64d9aa9 --- /dev/null +++ b/recovery/applypatch/include/applypatch/imgpatch.h
@@ -0,0 +1,26 @@ +/* + * 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. + */ + +#ifndef _IMGPATCH_H +#define _IMGPATCH_H + +typedef ssize_t (*SinkFn)(const unsigned char*, ssize_t, void*); + +int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, + const unsigned char* patch_data, ssize_t patch_size, + SinkFn sink, void* token); + +#endif //_IMGPATCH_H
diff --git a/recovery/applypatch/libimgpatch.pc b/recovery/applypatch/libimgpatch.pc new file mode 100644 index 0000000..e500293 --- /dev/null +++ b/recovery/applypatch/libimgpatch.pc
@@ -0,0 +1,6 @@ +# 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/recovery/applypatch/main.cpp b/recovery/applypatch/main.cpp new file mode 100644 index 0000000..1968ae4 --- /dev/null +++ b/recovery/applypatch/main.cpp
@@ -0,0 +1,200 @@ +/* + * 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <memory> +#include <vector> + +#include "applypatch/applypatch.h" +#include "edify/expr.h" +#include "openssl/sha.h" + +static int CheckMode(int argc, char** argv) { + if (argc < 3) { + return 2; + } + return applypatch_check(argv[2], argc-3, argv+3); +} + +static int SpaceMode(int argc, char** argv) { + if (argc != 3) { + return 2; + } + char* endptr; + size_t bytes = strtol(argv[2], &endptr, 10); + if (bytes == 0 && endptr == argv[2]) { + printf("can't parse \"%s\" as byte count\n\n", argv[2]); + return 1; + } + return CacheSizeCheck(bytes); +} + +// Parse arguments (which should be of the form "<sha1>:<filename>" +// into the new parallel arrays *sha1s and *files.Returns true on +// success. +static bool ParsePatchArgs(int argc, char** argv, std::vector<char*>* sha1s, + std::vector<FileContents>* files) { + uint8_t digest[SHA_DIGEST_LENGTH]; + + for (int i = 0; i < argc; ++i) { + char* colon = strchr(argv[i], ':'); + if (colon == nullptr) { + printf("no ':' in patch argument \"%s\"\n", argv[i]); + return false; + } + *colon = '\0'; + ++colon; + if (ParseSha1(argv[i], digest) != 0) { + printf("failed to parse sha1 \"%s\"\n", argv[i]); + return false; + } + + sha1s->push_back(argv[i]); + FileContents fc; + if (LoadFileContents(colon, &fc) != 0) { + return false; + } + files->push_back(std::move(fc)); + } + return true; +} + +static int FlashMode(const char* src_filename, const char* tgt_filename, + const char* tgt_sha1, size_t tgt_size) { + return applypatch_flash(src_filename, tgt_filename, tgt_sha1, tgt_size); +} + +static int PatchMode(int argc, char** argv) { + FileContents bonusFc; + Value bonusValue; + Value* bonus = nullptr; + + if (argc >= 3 && strcmp(argv[1], "-b") == 0) { + if (LoadFileContents(argv[2], &bonusFc) != 0) { + printf("failed to load bonus file %s\n", argv[2]); + return 1; + } + bonus = &bonusValue; + bonus->type = VAL_BLOB; + bonus->size = bonusFc.data.size(); + bonus->data = reinterpret_cast<char*>(bonusFc.data.data()); + argc -= 2; + argv += 2; + } + + if (argc < 4) { + return 2; + } + + char* endptr; + size_t target_size = strtol(argv[4], &endptr, 10); + if (target_size == 0 && endptr == argv[4]) { + printf("can't parse \"%s\" as byte count\n\n", argv[4]); + return 1; + } + + // If no <src-sha1>:<patch> is provided, it is in flash mode. + if (argc == 5) { + if (bonus != nullptr) { + printf("bonus file not supported in flash mode\n"); + return 1; + } + return FlashMode(argv[1], argv[2], argv[3], target_size); + } + std::vector<char*> sha1s; + std::vector<FileContents> files; + if (!ParsePatchArgs(argc-5, argv+5, &sha1s, &files)) { + printf("failed to parse patch args\n"); + return 1; + } + std::vector<Value> patches(files.size()); + std::vector<Value*> patch_ptrs(files.size()); + for (size_t i = 0; i < files.size(); ++i) { + patches[i].type = VAL_BLOB; + patches[i].size = files[i].data.size(); + patches[i].data = reinterpret_cast<char*>(files[i].data.data()); + patch_ptrs[i] = &patches[i]; + } + return applypatch(argv[1], argv[2], argv[3], target_size, + patch_ptrs.size(), sha1s.data(), + patch_ptrs.data(), bonus); +} + +// This program applies binary patches to files in a way that is safe +// (the original file is not touched until we have the desired +// replacement for it) and idempotent (it's okay to run this program +// multiple times). +// +// - if the sha1 hash of <tgt-file> is <tgt-sha1>, does nothing and exits +// successfully. +// +// - otherwise, if no <src-sha1>:<patch> is provided, flashes <tgt-file> with +// <src-file>. <tgt-file> must be a partition name, while <src-file> must +// be a regular image file. <src-file> will not be deleted on success. +// +// - otherwise, if the sha1 hash of <src-file> is <src-sha1>, applies the +// bsdiff <patch> to <src-file> to produce a new file (the type of patch +// is automatically detected from the file header). If that new +// file has sha1 hash <tgt-sha1>, moves it to replace <tgt-file>, and +// exits successfully. Note that if <src-file> and <tgt-file> are +// not the same, <src-file> is NOT deleted on success. <tgt-file> +// may be the string "-" to mean "the same as src-file". +// +// - otherwise, or if any error is encountered, exits with non-zero +// status. +// +// <src-file> (or <file> in check mode) may refer to an EMMC partition +// to read the source data. See the comments for the +// LoadPartitionContents() function for the format of such a filename. + +int main(int argc, char** argv) { + if (argc < 2) { + usage: + printf( + "usage: %s [-b <bonus-file>] <src-file> <tgt-file> <tgt-sha1> <tgt-size> " + "[<src-sha1>:<patch> ...]\n" + " or %s -c <file> [<sha1> ...]\n" + " or %s -s <bytes>\n" + " or %s -l\n" + "\n" + "Filenames may be of the form\n" + " EMMC:<partition>:<len_1>:<sha1_1>:<len_2>:<sha1_2>:...\n" + "to specify reading from or writing to an EMMC partition.\n\n", + argv[0], argv[0], argv[0], argv[0]); + return 2; + } + + int result; + + if (strncmp(argv[1], "-l", 3) == 0) { + result = ShowLicenses(); + } else if (strncmp(argv[1], "-c", 3) == 0) { + result = CheckMode(argc, argv); + } else if (strncmp(argv[1], "-s", 3) == 0) { + result = SpaceMode(argc, argv); + } else { + result = PatchMode(argc, argv); + } + + if (result == 2) { + goto usage; + } + return result; +}
diff --git a/recovery/applypatch/testdata/new.file b/recovery/applypatch/testdata/new.file new file mode 100644 index 0000000..cdeb8fd --- /dev/null +++ b/recovery/applypatch/testdata/new.file Binary files differ
diff --git a/recovery/applypatch/testdata/old.file b/recovery/applypatch/testdata/old.file new file mode 100644 index 0000000..166c873 --- /dev/null +++ b/recovery/applypatch/testdata/old.file Binary files differ
diff --git a/recovery/applypatch/testdata/patch.bsdiff b/recovery/applypatch/testdata/patch.bsdiff new file mode 100644 index 0000000..b78d385 --- /dev/null +++ b/recovery/applypatch/testdata/patch.bsdiff Binary files differ
diff --git a/recovery/applypatch/utils.cpp b/recovery/applypatch/utils.cpp new file mode 100644 index 0000000..fef250f --- /dev/null +++ b/recovery/applypatch/utils.cpp
@@ -0,0 +1,65 @@ +/* + * 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. + */ + +#include <stdio.h> + +#include "utils.h" + +/** Write a 4-byte value to f in little-endian order. */ +void Write4(int value, FILE* f) { + fputc(value & 0xff, f); + fputc((value >> 8) & 0xff, f); + fputc((value >> 16) & 0xff, f); + fputc((value >> 24) & 0xff, f); +} + +/** Write an 8-byte value to f in little-endian order. */ +void Write8(int64_t value, FILE* f) { + fputc(value & 0xff, f); + fputc((value >> 8) & 0xff, f); + fputc((value >> 16) & 0xff, f); + fputc((value >> 24) & 0xff, f); + fputc((value >> 32) & 0xff, f); + fputc((value >> 40) & 0xff, f); + fputc((value >> 48) & 0xff, f); + fputc((value >> 56) & 0xff, f); +} + +int Read2(void* pv) { + unsigned char* p = reinterpret_cast<unsigned char*>(pv); + return (int)(((unsigned int)p[1] << 8) | + (unsigned int)p[0]); +} + +int Read4(void* pv) { + unsigned char* p = reinterpret_cast<unsigned char*>(pv); + return (int)(((unsigned int)p[3] << 24) | + ((unsigned int)p[2] << 16) | + ((unsigned int)p[1] << 8) | + (unsigned int)p[0]); +} + +int64_t Read8(void* pv) { + unsigned char* p = reinterpret_cast<unsigned char*>(pv); + return (int64_t)(((uint64_t)p[7] << 56) | + ((uint64_t)p[6] << 48) | + ((uint64_t)p[5] << 40) | + ((uint64_t)p[4] << 32) | + ((uint64_t)p[3] << 24) | + ((uint64_t)p[2] << 16) | + ((uint64_t)p[1] << 8) | + (uint64_t)p[0]); +}
diff --git a/recovery/applypatch/utils.h b/recovery/applypatch/utils.h new file mode 100644 index 0000000..1c34edd --- /dev/null +++ b/recovery/applypatch/utils.h
@@ -0,0 +1,31 @@ +/* + * 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. + */ + +#ifndef _BUILD_TOOLS_APPLYPATCH_UTILS_H +#define _BUILD_TOOLS_APPLYPATCH_UTILS_H + +#include <inttypes.h> +#include <stdio.h> + +// Read and write little-endian values of various sizes. + +void Write4(int value, FILE* f); +void Write8(int64_t value, FILE* f); +int Read2(void* p); +int Read4(void* p); +int64_t Read8(void* p); + +#endif // _BUILD_TOOLS_APPLYPATCH_UTILS_H
diff --git a/recovery/asn1_decoder.cpp b/recovery/asn1_decoder.cpp new file mode 100644 index 0000000..e7aef78 --- /dev/null +++ b/recovery/asn1_decoder.cpp
@@ -0,0 +1,191 @@ +/* + * Copyright (C) 2013 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 <malloc.h> +#include <stdint.h> +#include <string.h> + +#include "asn1_decoder.h" + + +typedef struct asn1_context { + size_t length; + uint8_t* p; + int app_type; +} asn1_context_t; + + +static const int kMaskConstructed = 0xE0; +static const int kMaskTag = 0x7F; +static const int kMaskAppType = 0x1F; + +static const int kTagOctetString = 0x04; +static const int kTagOid = 0x06; +static const int kTagSequence = 0x30; +static const int kTagSet = 0x31; +static const int kTagConstructed = 0xA0; + +asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length) { + asn1_context_t* ctx = (asn1_context_t*) calloc(1, sizeof(asn1_context_t)); + if (ctx == NULL) { + return NULL; + } + ctx->p = buffer; + ctx->length = length; + return ctx; +} + +void asn1_context_free(asn1_context_t* ctx) { + free(ctx); +} + +static inline int peek_byte(asn1_context_t* ctx) { + if (ctx->length <= 0) { + return -1; + } + return *ctx->p; +} + +static inline int get_byte(asn1_context_t* ctx) { + if (ctx->length <= 0) { + return -1; + } + int byte = *ctx->p; + ctx->p++; + ctx->length--; + return byte; +} + +static inline bool skip_bytes(asn1_context_t* ctx, size_t num_skip) { + if (ctx->length < num_skip) { + return false; + } + ctx->p += num_skip; + ctx->length -= num_skip; + return true; +} + +static bool decode_length(asn1_context_t* ctx, size_t* out_len) { + int num_octets = get_byte(ctx); + if (num_octets == -1) { + return false; + } + if ((num_octets & 0x80) == 0x00) { + *out_len = num_octets; + return 1; + } + num_octets &= kMaskTag; + if ((size_t)num_octets >= sizeof(size_t)) { + return false; + } + size_t length = 0; + for (int i = 0; i < num_octets; ++i) { + int byte = get_byte(ctx); + if (byte == -1) { + return false; + } + length <<= 8; + length += byte; + } + *out_len = length; + return true; +} + +/** + * Returns the constructed type and advances the pointer. E.g. A0 -> 0 + */ +asn1_context_t* asn1_constructed_get(asn1_context_t* ctx) { + int type = get_byte(ctx); + if (type == -1 || (type & kMaskConstructed) != kTagConstructed) { + return NULL; + } + size_t length; + if (!decode_length(ctx, &length) || length > ctx->length) { + return NULL; + } + asn1_context_t* app_ctx = asn1_context_new(ctx->p, length); + app_ctx->app_type = type & kMaskAppType; + return app_ctx; +} + +bool asn1_constructed_skip_all(asn1_context_t* ctx) { + int byte = peek_byte(ctx); + while (byte != -1 && (byte & kMaskConstructed) == kTagConstructed) { + skip_bytes(ctx, 1); + size_t length; + if (!decode_length(ctx, &length) || !skip_bytes(ctx, length)) { + return false; + } + byte = peek_byte(ctx); + } + return byte != -1; +} + +int asn1_constructed_type(asn1_context_t* ctx) { + return ctx->app_type; +} + +asn1_context_t* asn1_sequence_get(asn1_context_t* ctx) { + if ((get_byte(ctx) & kMaskTag) != kTagSequence) { + return NULL; + } + size_t length; + if (!decode_length(ctx, &length) || length > ctx->length) { + return NULL; + } + return asn1_context_new(ctx->p, length); +} + +asn1_context_t* asn1_set_get(asn1_context_t* ctx) { + if ((get_byte(ctx) & kMaskTag) != kTagSet) { + return NULL; + } + size_t length; + if (!decode_length(ctx, &length) || length > ctx->length) { + return NULL; + } + return asn1_context_new(ctx->p, length); +} + +bool asn1_sequence_next(asn1_context_t* ctx) { + size_t length; + if (get_byte(ctx) == -1 || !decode_length(ctx, &length) || !skip_bytes(ctx, length)) { + return false; + } + return true; +} + +bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length) { + if (get_byte(ctx) != kTagOid) { + return false; + } + if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) { + return false; + } + *oid = ctx->p; + return true; +} + +bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length) { + if (get_byte(ctx) != kTagOctetString) { + return false; + } + if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) { + return false; + } + *octet_string = ctx->p; + return true; +}
diff --git a/recovery/asn1_decoder.h b/recovery/asn1_decoder.h new file mode 100644 index 0000000..b17141c --- /dev/null +++ b/recovery/asn1_decoder.h
@@ -0,0 +1,36 @@ +/* + * Copyright (C) 2013 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 ASN1_DECODER_H_ +#define ASN1_DECODER_H_ + +#include <stdint.h> + +typedef struct asn1_context asn1_context_t; + +asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length); +void asn1_context_free(asn1_context_t* ctx); +asn1_context_t* asn1_constructed_get(asn1_context_t* ctx); +bool asn1_constructed_skip_all(asn1_context_t* ctx); +int asn1_constructed_type(asn1_context_t* ctx); +asn1_context_t* asn1_sequence_get(asn1_context_t* ctx); +asn1_context_t* asn1_set_get(asn1_context_t* ctx); +bool asn1_sequence_next(asn1_context_t* seq); +bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length); +bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length); + +#endif /* ASN1_DECODER_H_ */
diff --git a/recovery/bootloader.cpp b/recovery/bootloader.cpp new file mode 100644 index 0000000..783f56e --- /dev/null +++ b/recovery/bootloader.cpp
@@ -0,0 +1,133 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <fs_mgr.h> + +#include "bootloader.h" +#include "common.h" +#include "roots.h" +#include <android-base/unique_fd.h> + +static int get_bootloader_message_block(bootloader_message* out, const Volume* v); +static int set_bootloader_message_block(const bootloader_message* in, const Volume* v); + +int get_bootloader_message(bootloader_message* out) { + Volume* v = volume_for_path("/misc"); + if (v == nullptr) { + LOGE("Cannot load volume /misc!\n"); + return -1; + } + if (strcmp(v->fs_type, "emmc") == 0) { + return get_bootloader_message_block(out, v); + } + LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type); + return -1; +} + +int set_bootloader_message(const bootloader_message* in) { + Volume* v = volume_for_path("/misc"); + if (v == nullptr) { + LOGE("Cannot load volume /misc!\n"); + return -1; + } + if (strcmp(v->fs_type, "emmc") == 0) { + return set_bootloader_message_block(in, v); + } + LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type); + return -1; +} + +// ------------------------------------ +// for misc partitions on block devices +// ------------------------------------ + +static void wait_for_device(const char* fn) { + int tries = 0; + int ret; + do { + ++tries; + struct stat buf; + ret = stat(fn, &buf); + if (ret == -1) { + printf("failed to stat \"%s\" try %d: %s\n", fn, tries, strerror(errno)); + sleep(1); + } + } while (ret && tries < 10); + + if (ret) { + printf("failed to stat \"%s\"\n", fn); + } +} + +static int get_bootloader_message_block(bootloader_message* out, + const Volume* v) { + wait_for_device(v->blk_device); + FILE* f = fopen(v->blk_device, "rb"); + if (f == nullptr) { + LOGE("failed to open \"%s\": %s\n", v->blk_device, strerror(errno)); + return -1; + } + bootloader_message temp; + int count = fread(&temp, sizeof(temp), 1, f); + if (count != 1) { + LOGE("failed to read \"%s\": %s\n", v->blk_device, strerror(errno)); + return -1; + } + if (fclose(f) != 0) { + LOGE("failed to close \"%s\": %s\n", v->blk_device, strerror(errno)); + return -1; + } + memcpy(out, &temp, sizeof(temp)); + return 0; +} + +static int set_bootloader_message_block(const bootloader_message* in, + const Volume* v) { + wait_for_device(v->blk_device); + android::base::unique_fd fd(open(v->blk_device, O_WRONLY | O_SYNC)); + if (fd == -1) { + LOGE("failed to open \"%s\": %s\n", v->blk_device, strerror(errno)); + return -1; + } + + size_t written = 0; + const uint8_t* start = reinterpret_cast<const uint8_t*>(in); + size_t total = sizeof(*in); + while (written < total) { + ssize_t wrote = TEMP_FAILURE_RETRY(write(fd, start + written, total - written)); + if (wrote == -1) { + LOGE("failed to write %" PRId64 " bytes: %s\n", + static_cast<off64_t>(written), strerror(errno)); + return -1; + } + written += wrote; + } + + if (fsync(fd) == -1) { + LOGE("failed to fsync \"%s\": %s\n", v->blk_device, strerror(errno)); + return -1; + } + return 0; +}
diff --git a/recovery/bootloader.h b/recovery/bootloader.h new file mode 100644 index 0000000..1801705 --- /dev/null +++ b/recovery/bootloader.h
@@ -0,0 +1,171 @@ +/* + * 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. + */ + +#ifndef _RECOVERY_BOOTLOADER_H +#define _RECOVERY_BOOTLOADER_H + +#include <assert.h> + +/* Bootloader Message (2-KiB) + * + * This structure describes the content of a block in flash + * that is used for recovery and the bootloader to talk to + * each other. + * + * The command field is updated by linux when it wants to + * reboot into recovery or to update radio or bootloader firmware. + * It is also updated by the bootloader when firmware update + * is complete (to boot into recovery for any final cleanup) + * + * The status field is written by the bootloader after the + * completion of an "update-radio" or "update-hboot" command. + * + * The recovery field is only written by linux and used + * for the system to send a message to recovery or the + * other way around. + * + * The stage field is written by packages which restart themselves + * multiple times, so that the UI can reflect which invocation of the + * package it is. If the value is of the format "#/#" (eg, "1/3"), + * the UI will add a simple indicator of that status. + * + * We used to have slot_suffix field for A/B boot control metadata in + * this struct, which gets unintentionally cleared by recovery or + * uncrypt. Move it into struct bootloader_message_ab to avoid the + * issue. + */ +struct bootloader_message { + char command[32]; + char status[32]; + char recovery[768]; + + // The 'recovery' field used to be 1024 bytes. It has only ever + // been used to store the recovery command line, so 768 bytes + // should be plenty. We carve off the last 256 bytes to store the + // stage string (for multistage packages) and possible future + // expansion. + char stage[32]; + + // The 'reserved' field used to be 224 bytes when it was initially + // carved off from the 1024-byte recovery field. Bump it up to + // 1184-byte so that the entire bootloader_message struct rounds up + // to 2048-byte. + char reserved[1184]; +}; + +/** + * We must be cautious when changing the bootloader_message struct size, + * because A/B-specific fields may end up with different offsets. + */ +#if (__STDC_VERSION__ >= 201112L) || defined(__cplusplus) +static_assert(sizeof(struct bootloader_message) == 2048, + "struct bootloader_message size changes, which may break A/B devices"); +#endif + +/** + * The A/B-specific bootloader message structure (4-KiB). + * + * We separate A/B boot control metadata from the regular bootloader + * message struct and keep it here. Everything that's A/B-specific + * stays after struct bootloader_message, which should be managed by + * the A/B-bootloader or boot control HAL. + * + * The slot_suffix field is used for A/B implementations where the + * bootloader does not set the androidboot.ro.boot.slot_suffix kernel + * commandline parameter. This is used by fs_mgr to mount /system and + * other partitions with the slotselect flag set in fstab. A/B + * 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. + */ +struct bootloader_message_ab { + struct bootloader_message message; + char slot_suffix[32]; + + // Round up the entire struct to 4096-byte. + char reserved[2016]; +}; + +/** + * Be cautious about the struct size change, in case we put anything post + * bootloader_message_ab struct (b/29159185). + */ +#if (__STDC_VERSION__ >= 201112L) || defined(__cplusplus) +static_assert(sizeof(struct bootloader_message_ab) == 4096, + "struct bootloader_message_ab size changes"); +#endif + +#define BOOT_CTRL_MAGIC 0x42414342 /* Bootloader Control AB */ +#define BOOT_CTRL_VERSION 1 + +struct slot_metadata { + // Slot priority with 15 meaning highest priority, 1 lowest + // priority and 0 the slot is unbootable. + uint8_t priority : 4; + // Number of times left attempting to boot this slot. + uint8_t tries_remaining : 3; + // 1 if this slot has booted successfully, 0 otherwise. + uint8_t successful_boot : 1; + // 1 if this slot is corrupted from a dm-verity corruption, 0 + // otherwise. + uint8_t verity_corrupted : 1; + // Reserved for further use. + uint8_t reserved : 7; +} __attribute__((packed)); + +/* Bootloader Control AB + * + * This struct can be used to manage A/B metadata. It is designed to + * be put in the 'slot_suffix' field of the 'bootloader_message' + * structure described above. It is encouraged to use the + * 'bootloader_control' structure to store the A/B metadata, but not + * mandatory. + */ +struct bootloader_control { + // NUL terminated active slot suffix. + char slot_suffix[4]; + // Bootloader Control AB magic number (see BOOT_CTRL_MAGIC). + uint32_t magic; + // Version of struct being used (see BOOT_CTRL_VERSION). + uint8_t version; + // Number of slots being managed. + uint8_t nb_slot : 3; + // Number of times left attempting to boot recovery. + uint8_t recovery_tries_remaining : 3; + // Ensure 4-bytes alignment for slot_info field. + uint8_t reserved0[2]; + // Per-slot information. Up to 4 slots. + struct slot_metadata slot_info[4]; + // Reserved for further use. + uint8_t reserved1[8]; + // CRC32 of all 28 bytes preceding this field (little endian + // format). + uint32_t crc32_le; +} __attribute__((packed)); + +#if (__STDC_VERSION__ >= 201112L) || defined(__cplusplus) +static_assert(sizeof(struct bootloader_control) == + sizeof(((struct bootloader_message_ab *)0)->slot_suffix), + "struct bootloader_control has wrong size"); +#endif + +/* Read and write the bootloader command from the "misc" partition. + * These return zero on success. + */ +int get_bootloader_message(struct bootloader_message *out); +int set_bootloader_message(const struct bootloader_message *in); + +#endif
diff --git a/recovery/common.h b/recovery/common.h new file mode 100644 index 0000000..de8b409 --- /dev/null +++ b/recovery/common.h
@@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RECOVERY_COMMON_H +#define RECOVERY_COMMON_H + +#include <stdbool.h> +#include <stdio.h> +#include <stdarg.h> + +#define LOGE(...) ui_print("E:" __VA_ARGS__) +#define LOGW(...) fprintf(stdout, "W:" __VA_ARGS__) +#define LOGI(...) fprintf(stdout, "I:" __VA_ARGS__) + +#if 0 +#define LOGV(...) fprintf(stdout, "V:" __VA_ARGS__) +#define LOGD(...) fprintf(stdout, "D:" __VA_ARGS__) +#else +#define LOGV(...) do {} while (0) +#define LOGD(...) do {} while (0) +#endif + +#define STRINGIFY(x) #x +#define EXPAND(x) STRINGIFY(x) + +extern bool modified_flash; +typedef struct fstab_rec Volume; + +// fopen a file, mounting volumes and making parent dirs as necessary. +FILE* fopen_path(const char *path, const char *mode); + +void ui_print(const char* format, ...); + +bool is_ro_debuggable(); + +#endif // RECOVERY_COMMON_H
diff --git a/recovery/default_device.cpp b/recovery/default_device.cpp new file mode 100644 index 0000000..a971866 --- /dev/null +++ b/recovery/default_device.cpp
@@ -0,0 +1,22 @@ +/* + * 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. + */ + +#include "device.h" +#include "screen_ui.h" + +Device* make_device() { + return new Device(new ScreenRecoveryUI); +}
diff --git a/recovery/device.cpp b/recovery/device.cpp new file mode 100644 index 0000000..fd1a987 --- /dev/null +++ b/recovery/device.cpp
@@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "device.h" + +static const char* MENU_ITEMS[] = { + "Reboot system now", + "Reboot to bootloader", + "Apply update from ADB", + "Apply update from SD card", + "Wipe data/factory reset", + "Wipe cache partition", + "Mount /system", + "View recovery logs", + "Power off", + NULL +}; + +const char* const* Device::GetMenuItems() { + return MENU_ITEMS; +} + +Device::BuiltinAction Device::InvokeMenuItem(int menu_position) { + switch (menu_position) { + case 0: return REBOOT; + case 1: return REBOOT_BOOTLOADER; + case 2: return APPLY_ADB_SIDELOAD; + case 3: return APPLY_SDCARD; + case 4: return WIPE_DATA; + case 5: return WIPE_CACHE; + case 6: return MOUNT_SYSTEM; + case 7: return VIEW_RECOVERY_LOGS; + case 8: return SHUTDOWN; + default: return NO_ACTION; + } +} + +int Device::HandleMenuKey(int key, int visible) { + if (!visible) { + return kNoAction; + } + + switch (key) { + case KEY_DOWN: + case KEY_VOLUMEDOWN: + return kHighlightDown; + + case KEY_UP: + case KEY_VOLUMEUP: + return kHighlightUp; + + case KEY_ENTER: + case KEY_POWER: + return kInvokeItem; + + default: + // If you have all of the above buttons, any other buttons + // are ignored. Otherwise, any button cycles the highlight. + return ui_->HasThreeButtons() ? kNoAction : kHighlightDown; + } +}
diff --git a/recovery/device.h b/recovery/device.h new file mode 100644 index 0000000..e0a3ff7 --- /dev/null +++ b/recovery/device.h
@@ -0,0 +1,114 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _RECOVERY_DEVICE_H +#define _RECOVERY_DEVICE_H + +#include "ui.h" + +class Device { + public: + explicit Device(RecoveryUI* ui) : ui_(ui) { } + virtual ~Device() { } + + // Called to obtain the UI object that should be used to display + // the recovery user interface for this device. You should not + // have called Init() on the UI object already, the caller will do + // that after this method returns. + virtual RecoveryUI* GetUI() { return ui_; } + + // Called when recovery starts up (after the UI has been obtained + // and initialized and after the arguments have been parsed, but + // before anything else). + virtual void StartRecovery() { }; + + // Called from the main thread when recovery is at the main menu + // and waiting for input, and a key is pressed. (Note that "at" + // the main menu does not necessarily mean the menu is visible; + // recovery will be at the main menu with it invisible after an + // unsuccessful operation [ie OTA package failure], or if recovery + // is started with no command.) + // + // key is the code of the key just pressed. (You can call + // IsKeyPressed() on the RecoveryUI object you returned from GetUI + // if you want to find out if other keys are held down.) + // + // visible is true if the menu is visible. + // + // Return one of the defined constants below in order to: + // + // - move the menu highlight (kHighlight{Up,Down}) + // - invoke the highlighted item (kInvokeItem) + // - do nothing (kNoAction) + // - invoke a specific action (a menu position: any non-negative number) + virtual int HandleMenuKey(int key, int visible); + + enum BuiltinAction { + NO_ACTION = 0, + REBOOT = 1, + APPLY_SDCARD = 2, + // APPLY_CACHE was 3. + APPLY_ADB_SIDELOAD = 4, + WIPE_DATA = 5, + WIPE_CACHE = 6, + REBOOT_BOOTLOADER = 7, + SHUTDOWN = 8, + VIEW_RECOVERY_LOGS = 9, + MOUNT_SYSTEM = 10, + }; + + // Return the list of menu items (an array of strings, + // NULL-terminated). The menu_position passed to InvokeMenuItem + // will correspond to the indexes into this array. + virtual const char* const* GetMenuItems(); + + // Perform a recovery action selected from the menu. + // 'menu_position' will be the item number of the selected menu + // item, or a non-negative number returned from + // device_handle_key(). The menu will be hidden when this is + // called; implementations can call ui_print() to print + // information to the screen. If the menu position is one of the + // builtin actions, you can just return the corresponding enum + // value. If it is an action specific to your device, you + // actually perform it here and return NO_ACTION. + virtual BuiltinAction InvokeMenuItem(int menu_position); + + static const int kNoAction = -1; + static const int kHighlightUp = -2; + static const int kHighlightDown = -3; + static const int kInvokeItem = -4; + + // Called before and after we do a wipe data/factory reset operation, + // either via a reboot from the main system with the --wipe_data flag, + // or when the user boots into recovery image manually and selects the + // option from the menu, to perform whatever device-specific wiping + // actions are needed. + // Return true on success; returning false from PreWipeData will prevent + // the regular wipe, and returning false from PostWipeData will cause + // the wipe to be considered a failure. + virtual bool PreWipeData() { return true; } + virtual bool PostWipeData() { return true; } + + private: + RecoveryUI* ui_; +}; + +// The device-specific library must define this function (or the +// default one will be used, if there is no device-specific library). +// It returns the Device object that recovery should use. +Device* make_device(); + +#endif // _DEVICE_H
diff --git a/recovery/edify/Android.mk b/recovery/edify/Android.mk new file mode 100644 index 0000000..038dec0 --- /dev/null +++ b/recovery/edify/Android.mk
@@ -0,0 +1,40 @@ +# Copyright 2009 The Android Open Source Project + +LOCAL_PATH := $(call my-dir) + +edify_src_files := \ + lexer.ll \ + parser.yy \ + expr.cpp + +# +# Build the host-side command line tool +# +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + $(edify_src_files) \ + main.cpp + +LOCAL_CPPFLAGS := -g -O0 +LOCAL_MODULE := edify +LOCAL_YACCFLAGS := -v +LOCAL_CPPFLAGS += -Wno-unused-parameter +LOCAL_CPPFLAGS += -Wno-deprecated-register +LOCAL_CLANG := true + +include $(BUILD_HOST_EXECUTABLE) + +# +# Build the device-side library +# +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(edify_src_files) + +LOCAL_CPPFLAGS := -Wno-unused-parameter +LOCAL_CPPFLAGS += -Wno-deprecated-register +LOCAL_MODULE := libedify +LOCAL_CLANG := true + +include $(BUILD_STATIC_LIBRARY)
diff --git a/recovery/edify/README b/recovery/edify/README new file mode 100644 index 0000000..810455c --- /dev/null +++ b/recovery/edify/README
@@ -0,0 +1,108 @@ +Update scripts (from donut onwards) are written in a new little +scripting language ("edify") that is superficially somewhat similar to +the old one ("amend"). This is a brief overview of the new language. + +- The entire script is a single expression. + +- All expressions are string-valued. + +- String literals appear in double quotes. \n, \t, \", and \\ are + understood, as are hexadecimal escapes like \x4a. + +- String literals consisting of only letters, numbers, colons, + underscores, slashes, and periods don't need to be in double quotes. + +- The following words are reserved: + + if then else endif + + They have special meaning when unquoted. (In quotes, they are just + string literals.) + +- When used as a boolean, the empty string is "false" and all other + strings are "true". + +- All functions are actually macros (in the Lisp sense); the body of + the function can control which (if any) of the arguments are + evaluated. This means that functions can act as control + structures. + +- Operators (like "&&" and "||") are just syntactic sugar for builtin + functions, so they can act as control structures as well. + +- ";" is a binary operator; evaluating it just means to first evaluate + the left side, then the right. It can also appear after any + expression. + +- Comments start with "#" and run to the end of the line. + + + +Some examples: + +- There's no distinction between quoted and unquoted strings; the + quotes are only needed if you want characters like whitespace to + appear in the string. The following expressions all evaluate to the + same string. + + "a b" + a + " " + b + "a" + " " + "b" + "a\x20b" + a + "\x20b" + concat(a, " ", "b") + "concat"(a, " ", "b") + + As shown in the last example, function names are just strings, + too. They must be string *literals*, however. This is not legal: + + ("con" + "cat")(a, " ", b) # syntax error! + + +- The ifelse() builtin takes three arguments: it evaluates exactly + one of the second and third, depending on whether the first one is + true. There is also some syntactic sugar to make expressions that + look like if/else statements: + + # these are all equivalent + ifelse(something(), "yes", "no") + if something() then yes else no endif + if something() then "yes" else "no" endif + + The else part is optional. + + if something() then "yes" endif # if something() is false, + # evaluates to false + + ifelse(condition(), "", abort()) # abort() only called if + # condition() is false + + The last example is equivalent to: + + assert(condition()) + + +- The && and || operators can be used similarly; they evaluate their + second argument only if it's needed to determine the truth of the + expression. Their value is the value of the last-evaluated + argument: + + file_exists("/data/system/bad") && delete("/data/system/bad") + + file_exists("/data/system/missing") || create("/data/system/missing") + + get_it() || "xxx" # returns value of get_it() if that value is + # true, otherwise returns "xxx" + + +- The purpose of ";" is to simulate imperative statements, of course, + but the operator can be used anywhere. Its value is the value of + its right side: + + concat(a;b;c, d, e;f) # evaluates to "cdf" + + A more useful example might be something like: + + ifelse(condition(), + (first_step(); second_step();), # second ; is optional + alternative_procedure())
diff --git a/recovery/edify/expr.cpp b/recovery/edify/expr.cpp new file mode 100644 index 0000000..c34342f --- /dev/null +++ b/recovery/edify/expr.cpp
@@ -0,0 +1,508 @@ +/* + * 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. + */ + +#include <string.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> + +#include "expr.h" + +// Functions should: +// +// - return a malloc()'d string +// - if Evaluate() on any argument returns NULL, return NULL. + +int BooleanString(const char* s) { + return s[0] != '\0'; +} + +char* Evaluate(State* state, Expr* expr) { + Value* v = expr->fn(expr->name, state, expr->argc, expr->argv); + if (v == NULL) return NULL; + if (v->type != VAL_STRING) { + ErrorAbort(state, "expecting string, got value type %d", v->type); + FreeValue(v); + return NULL; + } + char* result = v->data; + free(v); + return result; +} + +Value* EvaluateValue(State* state, Expr* expr) { + return expr->fn(expr->name, state, expr->argc, expr->argv); +} + +Value* StringValue(char* str) { + if (str == NULL) return NULL; + Value* v = reinterpret_cast<Value*>(malloc(sizeof(Value))); + v->type = VAL_STRING; + v->size = strlen(str); + v->data = str; + return v; +} + +void FreeValue(Value* v) { + if (v == NULL) return; + free(v->data); + free(v); +} + +Value* ConcatFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc == 0) { + return StringValue(strdup("")); + } + char** strings = reinterpret_cast<char**>(malloc(argc * sizeof(char*))); + int i; + for (i = 0; i < argc; ++i) { + strings[i] = NULL; + } + char* result = NULL; + int length = 0; + for (i = 0; i < argc; ++i) { + strings[i] = Evaluate(state, argv[i]); + if (strings[i] == NULL) { + goto done; + } + length += strlen(strings[i]); + } + + result = reinterpret_cast<char*>(malloc(length+1)); + int p; + p = 0; + for (i = 0; i < argc; ++i) { + strcpy(result+p, strings[i]); + p += strlen(strings[i]); + } + result[p] = '\0'; + + done: + for (i = 0; i < argc; ++i) { + free(strings[i]); + } + free(strings); + return StringValue(result); +} + +Value* IfElseFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2 && argc != 3) { + free(state->errmsg); + state->errmsg = strdup("ifelse expects 2 or 3 arguments"); + return NULL; + } + char* cond = Evaluate(state, argv[0]); + if (cond == NULL) { + return NULL; + } + + if (BooleanString(cond) == true) { + free(cond); + return EvaluateValue(state, argv[1]); + } else { + if (argc == 3) { + free(cond); + return EvaluateValue(state, argv[2]); + } else { + return StringValue(cond); + } + } +} + +Value* AbortFn(const char* name, State* state, int argc, Expr* argv[]) { + char* msg = NULL; + if (argc > 0) { + msg = Evaluate(state, argv[0]); + } + free(state->errmsg); + if (msg) { + state->errmsg = msg; + } else { + state->errmsg = strdup("called abort()"); + } + return NULL; +} + +Value* AssertFn(const char* name, State* state, int argc, Expr* argv[]) { + int i; + for (i = 0; i < argc; ++i) { + char* v = Evaluate(state, argv[i]); + if (v == NULL) { + return NULL; + } + int b = BooleanString(v); + free(v); + if (!b) { + int prefix_len; + int len = argv[i]->end - argv[i]->start; + char* err_src = reinterpret_cast<char*>(malloc(len + 20)); + strcpy(err_src, "assert failed: "); + prefix_len = strlen(err_src); + memcpy(err_src + prefix_len, state->script + argv[i]->start, len); + err_src[prefix_len + len] = '\0'; + free(state->errmsg); + state->errmsg = err_src; + return NULL; + } + } + return StringValue(strdup("")); +} + +Value* SleepFn(const char* name, State* state, int argc, Expr* argv[]) { + char* val = Evaluate(state, argv[0]); + if (val == NULL) { + return NULL; + } + int v = strtol(val, NULL, 10); + sleep(v); + return StringValue(val); +} + +Value* StdoutFn(const char* name, State* state, int argc, Expr* argv[]) { + int i; + for (i = 0; i < argc; ++i) { + char* v = Evaluate(state, argv[i]); + if (v == NULL) { + return NULL; + } + fputs(v, stdout); + free(v); + } + return StringValue(strdup("")); +} + +Value* LogicalAndFn(const char* name, State* state, + int argc, Expr* argv[]) { + char* left = Evaluate(state, argv[0]); + if (left == NULL) return NULL; + if (BooleanString(left) == true) { + free(left); + return EvaluateValue(state, argv[1]); + } else { + return StringValue(left); + } +} + +Value* LogicalOrFn(const char* name, State* state, + int argc, Expr* argv[]) { + char* left = Evaluate(state, argv[0]); + if (left == NULL) return NULL; + if (BooleanString(left) == false) { + free(left); + return EvaluateValue(state, argv[1]); + } else { + return StringValue(left); + } +} + +Value* LogicalNotFn(const char* name, State* state, + int argc, Expr* argv[]) { + char* val = Evaluate(state, argv[0]); + if (val == NULL) return NULL; + bool bv = BooleanString(val); + free(val); + return StringValue(strdup(bv ? "" : "t")); +} + +Value* SubstringFn(const char* name, State* state, + int argc, Expr* argv[]) { + char* needle = Evaluate(state, argv[0]); + if (needle == NULL) return NULL; + char* haystack = Evaluate(state, argv[1]); + if (haystack == NULL) { + free(needle); + return NULL; + } + + char* result = strdup(strstr(haystack, needle) ? "t" : ""); + free(needle); + free(haystack); + return StringValue(result); +} + +Value* EqualityFn(const char* name, State* state, int argc, Expr* argv[]) { + char* left = Evaluate(state, argv[0]); + if (left == NULL) return NULL; + char* right = Evaluate(state, argv[1]); + if (right == NULL) { + free(left); + return NULL; + } + + char* result = strdup(strcmp(left, right) == 0 ? "t" : ""); + free(left); + free(right); + return StringValue(result); +} + +Value* InequalityFn(const char* name, State* state, int argc, Expr* argv[]) { + char* left = Evaluate(state, argv[0]); + if (left == NULL) return NULL; + char* right = Evaluate(state, argv[1]); + if (right == NULL) { + free(left); + return NULL; + } + + char* result = strdup(strcmp(left, right) != 0 ? "t" : ""); + free(left); + free(right); + return StringValue(result); +} + +Value* SequenceFn(const char* name, State* state, int argc, Expr* argv[]) { + Value* left = EvaluateValue(state, argv[0]); + if (left == NULL) return NULL; + FreeValue(left); + return EvaluateValue(state, argv[1]); +} + +Value* LessThanIntFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + free(state->errmsg); + state->errmsg = strdup("less_than_int expects 2 arguments"); + return NULL; + } + + char* left; + char* right; + if (ReadArgs(state, argv, 2, &left, &right) < 0) return NULL; + + bool result = false; + char* end; + + // Parse up to at least long long or 64-bit integers. + int64_t l_int = static_cast<int64_t>(strtoll(left, &end, 10)); + if (left[0] == '\0' || *end != '\0') { + goto done; + } + + int64_t r_int; + r_int = static_cast<int64_t>(strtoll(right, &end, 10)); + if (right[0] == '\0' || *end != '\0') { + goto done; + } + + result = l_int < r_int; + + done: + free(left); + free(right); + return StringValue(strdup(result ? "t" : "")); +} + +Value* GreaterThanIntFn(const char* name, State* state, + int argc, Expr* argv[]) { + if (argc != 2) { + free(state->errmsg); + state->errmsg = strdup("greater_than_int expects 2 arguments"); + return NULL; + } + + Expr* temp[2]; + temp[0] = argv[1]; + temp[1] = argv[0]; + + return LessThanIntFn(name, state, 2, temp); +} + +Value* Literal(const char* name, State* state, int argc, Expr* argv[]) { + return StringValue(strdup(name)); +} + +Expr* Build(Function fn, YYLTYPE loc, int count, ...) { + va_list v; + va_start(v, count); + Expr* e = reinterpret_cast<Expr*>(malloc(sizeof(Expr))); + e->fn = fn; + e->name = "(operator)"; + e->argc = count; + e->argv = reinterpret_cast<Expr**>(malloc(count * sizeof(Expr*))); + int i; + for (i = 0; i < count; ++i) { + e->argv[i] = va_arg(v, Expr*); + } + va_end(v); + e->start = loc.start; + e->end = loc.end; + return e; +} + +// ----------------------------------------------------------------- +// the function table +// ----------------------------------------------------------------- + +static int fn_entries = 0; +static int fn_size = 0; +NamedFunction* fn_table = NULL; + +void RegisterFunction(const char* name, Function fn) { + if (fn_entries >= fn_size) { + fn_size = fn_size*2 + 1; + fn_table = reinterpret_cast<NamedFunction*>(realloc(fn_table, fn_size * sizeof(NamedFunction))); + } + fn_table[fn_entries].name = name; + fn_table[fn_entries].fn = fn; + ++fn_entries; +} + +static int fn_entry_compare(const void* a, const void* b) { + const char* na = ((const NamedFunction*)a)->name; + const char* nb = ((const NamedFunction*)b)->name; + return strcmp(na, nb); +} + +void FinishRegistration() { + qsort(fn_table, fn_entries, sizeof(NamedFunction), fn_entry_compare); +} + +Function FindFunction(const char* name) { + NamedFunction key; + key.name = name; + NamedFunction* nf = reinterpret_cast<NamedFunction*>(bsearch(&key, fn_table, fn_entries, + sizeof(NamedFunction), fn_entry_compare)); + if (nf == NULL) { + return NULL; + } + return nf->fn; +} + +void RegisterBuiltins() { + RegisterFunction("ifelse", IfElseFn); + RegisterFunction("abort", AbortFn); + RegisterFunction("assert", AssertFn); + RegisterFunction("concat", ConcatFn); + RegisterFunction("is_substring", SubstringFn); + RegisterFunction("stdout", StdoutFn); + RegisterFunction("sleep", SleepFn); + + RegisterFunction("less_than_int", LessThanIntFn); + RegisterFunction("greater_than_int", GreaterThanIntFn); +} + + +// ----------------------------------------------------------------- +// convenience methods for functions +// ----------------------------------------------------------------- + +// Evaluate the expressions in argv, giving 'count' char* (the ... is +// zero or more char** to put them in). If any expression evaluates +// to NULL, free the rest and return -1. Return 0 on success. +int ReadArgs(State* state, Expr* argv[], int count, ...) { + char** args = reinterpret_cast<char**>(malloc(count * sizeof(char*))); + va_list v; + va_start(v, count); + int i; + for (i = 0; i < count; ++i) { + args[i] = Evaluate(state, argv[i]); + if (args[i] == NULL) { + va_end(v); + int j; + for (j = 0; j < i; ++j) { + free(args[j]); + } + free(args); + return -1; + } + *(va_arg(v, char**)) = args[i]; + } + va_end(v); + free(args); + return 0; +} + +// Evaluate the expressions in argv, giving 'count' Value* (the ... is +// zero or more Value** to put them in). If any expression evaluates +// to NULL, free the rest and return -1. Return 0 on success. +int ReadValueArgs(State* state, Expr* argv[], int count, ...) { + Value** args = reinterpret_cast<Value**>(malloc(count * sizeof(Value*))); + va_list v; + va_start(v, count); + int i; + for (i = 0; i < count; ++i) { + args[i] = EvaluateValue(state, argv[i]); + if (args[i] == NULL) { + va_end(v); + int j; + for (j = 0; j < i; ++j) { + FreeValue(args[j]); + } + free(args); + return -1; + } + *(va_arg(v, Value**)) = args[i]; + } + va_end(v); + free(args); + return 0; +} + +// Evaluate the expressions in argv, returning an array of char* +// results. If any evaluate to NULL, free the rest and return NULL. +// The caller is responsible for freeing the returned array and the +// strings it contains. +char** ReadVarArgs(State* state, int argc, Expr* argv[]) { + char** args = (char**)malloc(argc * sizeof(char*)); + int i = 0; + for (i = 0; i < argc; ++i) { + args[i] = Evaluate(state, argv[i]); + if (args[i] == NULL) { + int j; + for (j = 0; j < i; ++j) { + free(args[j]); + } + free(args); + return NULL; + } + } + return args; +} + +// Evaluate the expressions in argv, returning an array of Value* +// results. If any evaluate to NULL, free the rest and return NULL. +// The caller is responsible for freeing the returned array and the +// Values it contains. +Value** ReadValueVarArgs(State* state, int argc, Expr* argv[]) { + Value** args = (Value**)malloc(argc * sizeof(Value*)); + int i = 0; + for (i = 0; i < argc; ++i) { + args[i] = EvaluateValue(state, argv[i]); + if (args[i] == NULL) { + int j; + for (j = 0; j < i; ++j) { + FreeValue(args[j]); + } + free(args); + return NULL; + } + } + return args; +} + +// Use printf-style arguments to compose an error message to put into +// *state. Returns NULL. +Value* ErrorAbort(State* state, const char* format, ...) { + char* buffer = reinterpret_cast<char*>(malloc(4096)); + va_list v; + va_start(v, format); + vsnprintf(buffer, 4096, format, v); + va_end(v); + free(state->errmsg); + state->errmsg = buffer; + return NULL; +}
diff --git a/recovery/edify/expr.h b/recovery/edify/expr.h new file mode 100644 index 0000000..36f8e96 --- /dev/null +++ b/recovery/edify/expr.h
@@ -0,0 +1,165 @@ +/* + * 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. + */ + +#ifndef _EXPRESSION_H +#define _EXPRESSION_H + +#include <unistd.h> + +#include "yydefs.h" + +#define MAX_STRING_LEN 1024 + +typedef struct Expr Expr; + +typedef struct { + // Optional pointer to app-specific data; the core of edify never + // uses this value. + void* cookie; + + // The source of the original script. Must be NULL-terminated, + // and in writable memory (Evaluate may make temporary changes to + // it but will restore it when done). + char* script; + + // The error message (if any) returned if the evaluation aborts. + // Should be NULL initially, will be either NULL or a malloc'd + // pointer after Evaluate() returns. + char* errmsg; +} State; + +#define VAL_STRING 1 // data will be NULL-terminated; size doesn't count null +#define VAL_BLOB 2 + +typedef struct { + int type; + ssize_t size; + char* data; +} Value; + +typedef Value* (*Function)(const char* name, State* state, + int argc, Expr* argv[]); + +struct Expr { + Function fn; + const char* name; + int argc; + Expr** argv; + int start, end; +}; + +// Take one of the Expr*s passed to the function as an argument, +// evaluate it, return the resulting Value. The caller takes +// ownership of the returned Value. +Value* EvaluateValue(State* state, Expr* expr); + +// Take one of the Expr*s passed to the function as an argument, +// evaluate it, assert that it is a string, and return the resulting +// char*. The caller takes ownership of the returned char*. This is +// a convenience function for older functions that want to deal only +// with strings. +char* Evaluate(State* state, Expr* expr); + +// Glue to make an Expr out of a literal. +Value* Literal(const char* name, State* state, int argc, Expr* argv[]); + +// Functions corresponding to various syntactic sugar operators. +// ("concat" is also available as a builtin function, to concatenate +// more than two strings.) +Value* ConcatFn(const char* name, State* state, int argc, Expr* argv[]); +Value* LogicalAndFn(const char* name, State* state, int argc, Expr* argv[]); +Value* LogicalOrFn(const char* name, State* state, int argc, Expr* argv[]); +Value* LogicalNotFn(const char* name, State* state, int argc, Expr* argv[]); +Value* SubstringFn(const char* name, State* state, int argc, Expr* argv[]); +Value* EqualityFn(const char* name, State* state, int argc, Expr* argv[]); +Value* InequalityFn(const char* name, State* state, int argc, Expr* argv[]); +Value* SequenceFn(const char* name, State* state, int argc, Expr* argv[]); + +// Convenience function for building expressions with a fixed number +// of arguments. +Expr* Build(Function fn, YYLTYPE loc, int count, ...); + +// Global builtins, registered by RegisterBuiltins(). +Value* IfElseFn(const char* name, State* state, int argc, Expr* argv[]); +Value* AssertFn(const char* name, State* state, int argc, Expr* argv[]); +Value* AbortFn(const char* name, State* state, int argc, Expr* argv[]); + + +// For setting and getting the global error string (when returning +// NULL from a function). +void SetError(const char* message); // makes a copy +const char* GetError(); // retains ownership +void ClearError(); + + +typedef struct { + const char* name; + Function fn; +} NamedFunction; + +// Register a new function. The same Function may be registered under +// multiple names, but a given name should only be used once. +void RegisterFunction(const char* name, Function fn); + +// Register all the builtins. +void RegisterBuiltins(); + +// Call this after all calls to RegisterFunction() but before parsing +// any scripts to finish building the function table. +void FinishRegistration(); + +// Find the Function for a given name; return NULL if no such function +// exists. +Function FindFunction(const char* name); + + +// --- convenience functions for use in functions --- + +// Evaluate the expressions in argv, giving 'count' char* (the ... is +// zero or more char** to put them in). If any expression evaluates +// to NULL, free the rest and return -1. Return 0 on success. +int ReadArgs(State* state, Expr* argv[], int count, ...); + +// Evaluate the expressions in argv, giving 'count' Value* (the ... is +// zero or more Value** to put them in). If any expression evaluates +// to NULL, free the rest and return -1. Return 0 on success. +int ReadValueArgs(State* state, Expr* argv[], int count, ...); + +// Evaluate the expressions in argv, returning an array of char* +// results. If any evaluate to NULL, free the rest and return NULL. +// The caller is responsible for freeing the returned array and the +// strings it contains. +char** ReadVarArgs(State* state, int argc, Expr* argv[]); + +// Evaluate the expressions in argv, returning an array of Value* +// results. If any evaluate to NULL, free the rest and return NULL. +// The caller is responsible for freeing the returned array and the +// Values it contains. +Value** ReadValueVarArgs(State* state, int argc, Expr* argv[]); + +// Use printf-style arguments to compose an error message to put into +// *state. Returns NULL. +Value* ErrorAbort(State* state, const char* format, ...) __attribute__((format(printf, 2, 3))); + +// Wrap a string into a Value, taking ownership of the string. +Value* StringValue(char* str); + +// Free a Value object. +void FreeValue(Value* v); + +int parse_string(const char* str, Expr** root, int* error_count); + +#endif // _EXPRESSION_H
diff --git a/recovery/edify/lexer.ll b/recovery/edify/lexer.ll new file mode 100644 index 0000000..b764d16 --- /dev/null +++ b/recovery/edify/lexer.ll
@@ -0,0 +1,110 @@ +%{ +/* + * 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. + */ + +#include <string.h> +#include <string> + +#include "expr.h" +#include "yydefs.h" +#include "parser.h" + +int gLine = 1; +int gColumn = 1; +int gPos = 0; + +std::string string_buffer; + +#define ADVANCE do {yylloc.start=gPos; yylloc.end=gPos+yyleng; \ + gColumn+=yyleng; gPos+=yyleng;} while(0) + +%} + +%x STR + +%option noyywrap + +%% + + +\" { + BEGIN(STR); + string_buffer.clear(); + yylloc.start = gPos; + ++gColumn; + ++gPos; +} + +<STR>{ + \" { + ++gColumn; + ++gPos; + BEGIN(INITIAL); + yylval.str = strdup(string_buffer.c_str()); + yylloc.end = gPos; + return STRING; + } + + \\n { gColumn += yyleng; gPos += yyleng; string_buffer.push_back('\n'); } + \\t { gColumn += yyleng; gPos += yyleng; string_buffer.push_back('\t'); } + \\\" { gColumn += yyleng; gPos += yyleng; string_buffer.push_back('\"'); } + \\\\ { gColumn += yyleng; gPos += yyleng; string_buffer.push_back('\\'); } + + \\x[0-9a-fA-F]{2} { + gColumn += yyleng; + gPos += yyleng; + int val; + sscanf(yytext+2, "%x", &val); + string_buffer.push_back(static_cast<char>(val)); + } + + \n { + ++gLine; + ++gPos; + gColumn = 1; + string_buffer.push_back(yytext[0]); + } + + . { + ++gColumn; + ++gPos; + string_buffer.push_back(yytext[0]); + } +} + +if ADVANCE; return IF; +then ADVANCE; return THEN; +else ADVANCE; return ELSE; +endif ADVANCE; return ENDIF; + +[a-zA-Z0-9_:/.]+ { + ADVANCE; + yylval.str = strdup(yytext); + return STRING; +} + +\&\& ADVANCE; return AND; +\|\| ADVANCE; return OR; +== ADVANCE; return EQ; +!= ADVANCE; return NE; + +[+(),!;] ADVANCE; return yytext[0]; + +[ \t]+ ADVANCE; + +(#.*)?\n gPos += yyleng; ++gLine; gColumn = 1; + +. return BAD;
diff --git a/recovery/edify/main.cpp b/recovery/edify/main.cpp new file mode 100644 index 0000000..6007a3d --- /dev/null +++ b/recovery/edify/main.cpp
@@ -0,0 +1,219 @@ +/* + * 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <string> + +#include "expr.h" +#include "parser.h" + +extern int yyparse(Expr** root, int* error_count); + +int expect(const char* expr_str, const char* expected, int* errors) { + Expr* e; + char* result; + + printf("."); + + int error_count = parse_string(expr_str, &e, &error_count); + if (error_count > 0) { + printf("error parsing \"%s\" (%d errors)\n", + expr_str, error_count); + ++*errors; + return 0; + } + + State state; + state.cookie = NULL; + state.script = strdup(expr_str); + state.errmsg = NULL; + + result = Evaluate(&state, e); + free(state.errmsg); + free(state.script); + if (result == NULL && expected != NULL) { + printf("error evaluating \"%s\"\n", expr_str); + ++*errors; + return 0; + } + + if (result == NULL && expected == NULL) { + return 1; + } + + if (strcmp(result, expected) != 0) { + printf("evaluating \"%s\": expected \"%s\", got \"%s\"\n", + expr_str, expected, result); + ++*errors; + free(result); + return 0; + } + + free(result); + return 1; +} + +int test() { + int errors = 0; + + expect("a", "a", &errors); + expect("\"a\"", "a", &errors); + expect("\"\\x61\"", "a", &errors); + expect("# this is a comment\n" + " a\n" + " \n", + "a", &errors); + + + // sequence operator + expect("a; b; c", "c", &errors); + + // string concat operator + expect("a + b", "ab", &errors); + expect("a + \n \"b\"", "ab", &errors); + expect("a + b +\nc\n", "abc", &errors); + + // string concat function + expect("concat(a, b)", "ab", &errors); + expect("concat(a,\n \"b\")", "ab", &errors); + expect("concat(a + b,\nc,\"d\")", "abcd", &errors); + expect("\"concat\"(a + b,\nc,\"d\")", "abcd", &errors); + + // logical and + expect("a && b", "b", &errors); + expect("a && \"\"", "", &errors); + expect("\"\" && b", "", &errors); + expect("\"\" && \"\"", "", &errors); + expect("\"\" && abort()", "", &errors); // test short-circuiting + expect("t && abort()", NULL, &errors); + + // logical or + expect("a || b", "a", &errors); + expect("a || \"\"", "a", &errors); + expect("\"\" || b", "b", &errors); + expect("\"\" || \"\"", "", &errors); + expect("a || abort()", "a", &errors); // test short-circuiting + expect("\"\" || abort()", NULL, &errors); + + // logical not + expect("!a", "", &errors); + expect("! \"\"", "t", &errors); + expect("!!a", "t", &errors); + + // precedence + expect("\"\" == \"\" && b", "b", &errors); + expect("a + b == ab", "t", &errors); + expect("ab == a + b", "t", &errors); + expect("a + (b == ab)", "a", &errors); + expect("(ab == a) + b", "b", &errors); + + // substring function + expect("is_substring(cad, abracadabra)", "t", &errors); + expect("is_substring(abrac, abracadabra)", "t", &errors); + expect("is_substring(dabra, abracadabra)", "t", &errors); + expect("is_substring(cad, abracxadabra)", "", &errors); + expect("is_substring(abrac, axbracadabra)", "", &errors); + expect("is_substring(dabra, abracadabrxa)", "", &errors); + + // ifelse function + expect("ifelse(t, yes, no)", "yes", &errors); + expect("ifelse(!t, yes, no)", "no", &errors); + expect("ifelse(t, yes, abort())", "yes", &errors); + expect("ifelse(!t, abort(), no)", "no", &errors); + + // if "statements" + expect("if t then yes else no endif", "yes", &errors); + expect("if \"\" then yes else no endif", "no", &errors); + expect("if \"\" then yes endif", "", &errors); + expect("if \"\"; t then yes endif", "yes", &errors); + + // numeric comparisons + expect("less_than_int(3, 14)", "t", &errors); + expect("less_than_int(14, 3)", "", &errors); + expect("less_than_int(x, 3)", "", &errors); + expect("less_than_int(3, x)", "", &errors); + expect("greater_than_int(3, 14)", "", &errors); + expect("greater_than_int(14, 3)", "t", &errors); + expect("greater_than_int(x, 3)", "", &errors); + expect("greater_than_int(3, x)", "", &errors); + + // big string + expect(std::string(8192, 's').c_str(), std::string(8192, 's').c_str(), &errors); + + printf("\n"); + + return errors; +} + +void ExprDump(int depth, Expr* n, char* script) { + printf("%*s", depth*2, ""); + char temp = script[n->end]; + script[n->end] = '\0'; + printf("%s %p (%d-%d) \"%s\"\n", + n->name == NULL ? "(NULL)" : n->name, n->fn, n->start, n->end, + script+n->start); + script[n->end] = temp; + int i; + for (i = 0; i < n->argc; ++i) { + ExprDump(depth+1, n->argv[i], script); + } +} + +int main(int argc, char** argv) { + RegisterBuiltins(); + FinishRegistration(); + + if (argc == 1) { + return test() != 0; + } + + FILE* f = fopen(argv[1], "r"); + if (f == NULL) { + printf("%s: %s: No such file or directory\n", argv[0], argv[1]); + return 1; + } + char buffer[8192]; + int size = fread(buffer, 1, 8191, f); + fclose(f); + buffer[size] = '\0'; + + Expr* root; + int error_count = 0; + int error = parse_string(buffer, &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; + state.cookie = NULL; + state.script = buffer; + state.errmsg = NULL; + + char* result = Evaluate(&state, root); + if (result == NULL) { + printf("result was NULL, message is: %s\n", + (state.errmsg == NULL ? "(NULL)" : state.errmsg)); + free(state.errmsg); + } else { + printf("result is [%s]\n", result); + } + } + return 0; +}
diff --git a/recovery/edify/parser.yy b/recovery/edify/parser.yy new file mode 100644 index 0000000..098a637 --- /dev/null +++ b/recovery/edify/parser.yy
@@ -0,0 +1,139 @@ +%{ +/* + * 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "expr.h" +#include "yydefs.h" +#include "parser.h" + +extern int gLine; +extern int gColumn; + +void yyerror(Expr** root, int* error_count, const char* s); +int yyparse(Expr** root, int* error_count); + +struct yy_buffer_state; +void yy_switch_to_buffer(struct yy_buffer_state* new_buffer); +struct yy_buffer_state* yy_scan_string(const char* yystr); + +%} + +%locations + +%union { + char* str; + Expr* expr; + struct { + int argc; + Expr** argv; + } args; +} + +%token AND OR SUBSTR SUPERSTR EQ NE IF THEN ELSE ENDIF +%token <str> STRING BAD +%type <expr> expr +%type <args> arglist + +%parse-param {Expr** root} +%parse-param {int* error_count} +%error-verbose + +/* declarations in increasing order of precedence */ +%left ';' +%left ',' +%left OR +%left AND +%left EQ NE +%left '+' +%right '!' + +%% + +input: expr { *root = $1; } +; + +expr: STRING { + $$ = reinterpret_cast<Expr*>(malloc(sizeof(Expr))); + $$->fn = Literal; + $$->name = $1; + $$->argc = 0; + $$->argv = NULL; + $$->start = @$.start; + $$->end = @$.end; +} +| '(' expr ')' { $$ = $2; $$->start=@$.start; $$->end=@$.end; } +| expr ';' { $$ = $1; $$->start=@1.start; $$->end=@1.end; } +| expr ';' expr { $$ = Build(SequenceFn, @$, 2, $1, $3); } +| error ';' expr { $$ = $3; $$->start=@$.start; $$->end=@$.end; } +| expr '+' expr { $$ = Build(ConcatFn, @$, 2, $1, $3); } +| expr EQ expr { $$ = Build(EqualityFn, @$, 2, $1, $3); } +| expr NE expr { $$ = Build(InequalityFn, @$, 2, $1, $3); } +| expr AND expr { $$ = Build(LogicalAndFn, @$, 2, $1, $3); } +| expr OR expr { $$ = Build(LogicalOrFn, @$, 2, $1, $3); } +| '!' expr { $$ = Build(LogicalNotFn, @$, 1, $2); } +| IF expr THEN expr ENDIF { $$ = Build(IfElseFn, @$, 2, $2, $4); } +| IF expr THEN expr ELSE expr ENDIF { $$ = Build(IfElseFn, @$, 3, $2, $4, $6); } +| STRING '(' arglist ')' { + $$ = reinterpret_cast<Expr*>(malloc(sizeof(Expr))); + $$->fn = FindFunction($1); + if ($$->fn == NULL) { + char buffer[256]; + snprintf(buffer, sizeof(buffer), "unknown function \"%s\"", $1); + yyerror(root, error_count, buffer); + YYERROR; + } + $$->name = $1; + $$->argc = $3.argc; + $$->argv = $3.argv; + $$->start = @$.start; + $$->end = @$.end; +} +; + +arglist: /* empty */ { + $$.argc = 0; + $$.argv = NULL; +} +| expr { + $$.argc = 1; + $$.argv = reinterpret_cast<Expr**>(malloc(sizeof(Expr*))); + $$.argv[0] = $1; +} +| arglist ',' expr { + $$.argc = $1.argc + 1; + $$.argv = reinterpret_cast<Expr**>(realloc($$.argv, $$.argc * sizeof(Expr*))); + $$.argv[$$.argc-1] = $3; +} +; + +%% + +void yyerror(Expr** root, int* error_count, const char* s) { + if (strlen(s) == 0) { + s = "syntax error"; + } + printf("line %d col %d: %s\n", gLine, gColumn, s); + ++*error_count; +} + +int parse_string(const char* str, Expr** root, int* error_count) { + yy_switch_to_buffer(yy_scan_string(str)); + return yyparse(root, error_count); +}
diff --git a/recovery/edify/yydefs.h b/recovery/edify/yydefs.h new file mode 100644 index 0000000..aca398f --- /dev/null +++ b/recovery/edify/yydefs.h
@@ -0,0 +1,38 @@ +/* + * 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. + */ + +#ifndef _YYDEFS_H_ +#define _YYDEFS_H_ + +#define YYLTYPE YYLTYPE +typedef struct { + int start, end; +} YYLTYPE; + +#define YYLLOC_DEFAULT(Current, Rhs, N) \ + do { \ + if (N) { \ + (Current).start = YYRHSLOC(Rhs, 1).start; \ + (Current).end = YYRHSLOC(Rhs, N).end; \ + } else { \ + (Current).start = YYRHSLOC(Rhs, 0).start; \ + (Current).end = YYRHSLOC(Rhs, 0).end; \ + } \ + } while (0) + +int yylex(); + +#endif
diff --git a/recovery/etc/init.rc b/recovery/etc/init.rc new file mode 100644 index 0000000..29b088d --- /dev/null +++ b/recovery/etc/init.rc
@@ -0,0 +1,107 @@ +import /init.recovery.${ro.hardware}.rc + +on early-init + # Set the security context of /postinstall if present. + restorecon /postinstall + + start ueventd + start healthd + +on init + export ANDROID_ROOT /system + export ANDROID_DATA /data + export EXTERNAL_STORAGE /sdcard + + symlink /system/etc /etc + + mount cgroup none /acct cpuacct + mkdir /acct/uid + + mkdir /sdcard + mkdir /system + mkdir /data + mkdir /cache + mkdir /sideload + mount tmpfs tmpfs /tmp + + chown root shell /tmp + chmod 0775 /tmp + + write /proc/sys/kernel/panic_on_oops 1 + write /proc/sys/vm/max_map_count 1000000 + +on fs + mkdir /dev/usb-ffs 0770 shell shell + mkdir /dev/usb-ffs/adb 0770 shell shell + mount functionfs adb /dev/usb-ffs/adb uid=2000,gid=2000 + + write /sys/class/android_usb/android0/enable 0 + write /sys/class/android_usb/android0/idVendor 18D1 + write /sys/class/android_usb/android0/idProduct D001 + write /sys/class/android_usb/android0/f_ffs/aliases adb + write /sys/class/android_usb/android0/functions adb + write /sys/class/android_usb/android0/iManufacturer ${ro.product.manufacturer} + write /sys/class/android_usb/android0/iProduct ${ro.product.model} + write /sys/class/android_usb/android0/iSerial ${ro.serialno} + +on boot + ifup lo + hostname localhost + domainname localdomain + + class_start default + +# Load properties from /system/ + /factory after fs mount. +on load_system_props_action + load_system_props + +on firmware_mounts_complete + rm /dev/.booting + +# Mount filesystems and start core system services. +on late-init + trigger early-fs + trigger fs + trigger post-fs + trigger post-fs-data + + # Load properties from /system/ + /factory after fs mount. Place + # this in another action so that the load will be scheduled after the prior + # issued fs triggers have completed. + trigger load_system_props_action + + # Remove a file to wake up anything waiting for firmware + trigger firmware_mounts_complete + + trigger early-boot + trigger boot + +on property:sys.powerctl=* + powerctl ${sys.powerctl} + +service ueventd /sbin/ueventd + critical + seclabel u:r:ueventd:s0 + +service healthd /sbin/healthd -r + critical + seclabel u:r:healthd:s0 + +service recovery /sbin/recovery + seclabel u:r:recovery:s0 + +service adbd /sbin/adbd --root_seclabel=u:r:su:s0 --device_banner=recovery + disabled + socket adbd stream 660 system system + seclabel u:r:adbd:s0 + +# Always start adbd on userdebug and eng builds +on property:ro.debuggable=1 + write /sys/class/android_usb/android0/enable 1 + start adbd + +# Restart adbd so it can run as root +on property:service.adb.root=1 + write /sys/class/android_usb/android0/enable 0 + restart adbd + write /sys/class/android_usb/android0/enable 1
diff --git a/recovery/fonts/12x22.png b/recovery/fonts/12x22.png new file mode 100644 index 0000000..ae826be --- /dev/null +++ b/recovery/fonts/12x22.png Binary files differ
diff --git a/recovery/fonts/18x32.png b/recovery/fonts/18x32.png new file mode 100644 index 0000000..d95408a --- /dev/null +++ b/recovery/fonts/18x32.png Binary files differ
diff --git a/recovery/fonts/OFL.txt b/recovery/fonts/OFL.txt new file mode 100644 index 0000000..b14edde --- /dev/null +++ b/recovery/fonts/OFL.txt
@@ -0,0 +1,93 @@ +Copyright (c) 2011, Raph Levien (firstname.lastname@gmail.com), Copyright (c) 2012, Cyreal (cyreal.org) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/recovery/fonts/README b/recovery/fonts/README new file mode 100644 index 0000000..d0748d2 --- /dev/null +++ b/recovery/fonts/README
@@ -0,0 +1,6 @@ +The images in this directory were generated using the font +Inconsolata, which is released under the OFL license and was obtained +from: + + https://code.google.com/p/googlefontdirectory/source/browse/ofl/inconsolata/ +
diff --git a/recovery/fuse_sdcard_provider.cpp b/recovery/fuse_sdcard_provider.cpp new file mode 100644 index 0000000..df96312 --- /dev/null +++ b/recovery/fuse_sdcard_provider.cpp
@@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> + +#include "fuse_sideload.h" + +struct file_data { + int fd; // the underlying sdcard file + + uint64_t file_size; + uint32_t block_size; +}; + +static int read_block_file(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; + } + + while (fetch_size > 0) { + ssize_t r = TEMP_FAILURE_RETRY(read(fd->fd, buffer, fetch_size)); + if (r == -1) { + fprintf(stderr, "read on sdcard failed: %s\n", strerror(errno)); + return -EIO; + } + fetch_size -= r; + buffer += r; + } + + return 0; +} + +static void close_file(void* cookie) { + file_data* fd = reinterpret_cast<file_data*>(cookie); + close(fd->fd); +} + +bool start_sdcard_fuse(const char* path) { + struct stat sb; + if (stat(path, &sb) == -1) { + fprintf(stderr, "failed to stat %s: %s\n", path, strerror(errno)); + return false; + } + + file_data fd; + fd.fd = open(path, O_RDONLY); + if (fd.fd == -1) { + fprintf(stderr, "failed to open %s: %s\n", path, strerror(errno)); + return false; + } + fd.file_size = sb.st_size; + fd.block_size = 65536; + + provider_vtab vtab; + vtab.read_block = read_block_file; + vtab.close = close_file; + + // 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; +}
diff --git a/recovery/fuse_sdcard_provider.h b/recovery/fuse_sdcard_provider.h new file mode 100644 index 0000000..bdc60f2 --- /dev/null +++ b/recovery/fuse_sdcard_provider.h
@@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FUSE_SDCARD_PROVIDER_H +#define __FUSE_SDCARD_PROVIDER_H + +bool start_sdcard_fuse(const char* path); + +#endif
diff --git a/recovery/fuse_sideload.cpp b/recovery/fuse_sideload.cpp new file mode 100644 index 0000000..1725e88 --- /dev/null +++ b/recovery/fuse_sideload.cpp
@@ -0,0 +1,525 @@ +/* + * 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. + */ + +// This module creates a special filesystem containing two files. +// +// "/sideload/package.zip" appears to be a normal file, but reading +// from it causes data to be fetched from the adb host. We can use +// this to sideload packages over an adb connection without having to +// store the entire package in RAM on the device. +// +// Because we may not trust the adb host, this filesystem maintains +// the following invariant: each read of a given position returns the +// same data as the first read at that position. That is, once a +// section of the file is read, future reads of that section return +// the same data. (Otherwise, a malicious adb host process could +// return one set of bits when the package is read for signature +// verification, and then different bits for when the package is +// accessed by the installer.) If the adb host returns something +// different than it did on the first read, the reader of the file +// will see their read fail with EINVAL. +// +// The other file, "/sideload/exit", is used to control the subprocess +// that creates this filesystem. Calling stat() on the exit file +// causes the filesystem to be unmounted and the adb process on the +// device shut down. +// +// Note that only the minimal set of file operations needed for these +// 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 <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <linux/fuse.h> +#include <pthread.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/stat.h> +#include <sys/statfs.h> +#include <sys/time.h> +#include <sys/uio.h> +#include <unistd.h> + +#include <openssl/sha.h> + +#include "fuse_sideload.h" + +#define PACKAGE_FILE_ID (FUSE_ROOT_ID+1) +#define EXIT_FLAG_ID (FUSE_ROOT_ID+2) + +#define NO_STATUS 1 +#define NO_STATUS_EXIT 2 + +struct fuse_data { + int ffd; // file descriptor for the fuse socket + + struct provider_vtab* vtab; + void* cookie; + + 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 + + uid_t uid; + gid_t gid; + + 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* 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 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; + } + + out.minor = MIN(req->minor, FUSE_KERNEL_MINOR_VERSION); + fuse_struct_size = sizeof(out); +#if defined(FUSE_COMPAT_22_INIT_OUT_SIZE) + /* 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; + } +#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); + + 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 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; + } + + 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; + } + + 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; + + 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; +} + +static int handle_flush(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { + return 0; +} + +static int handle_release(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { + return 0; +} + +// Fetch a block from the host into fd->curr_block and fd->block_data. +// Returns 0 on successful fetch, negative otherwise. +static int fetch_block(struct fuse_data* fd, uint32_t block) { + if (block == fd->curr_block) { + return 0; + } + + if (block >= fd->file_blocks) { + memset(fd->block_data, 0, fd->block_size); + fd->curr_block = block; + return 0; + } + + size_t fetch_size = fd->block_size; + if (block * fd->block_size + fetch_size > fd->file_size) { + // If we're reading the last (partial) block of the file, + // expect a shorter response from the host, and pad the rest + // of the block with zeroes. + fetch_size = fd->file_size - (block * fd->block_size); + memset(fd->block_data + fetch_size, 0, fd->block_size - fetch_size); + } + + int result = fd->vtab->read_block(fd->cookie, block, fd->block_data, fetch_size); + if (result < 0) return result; + + fd->curr_block = block; + + // Verify the hash of the block we just got from the host. + // + // - If the hash of the just-received data matches the stored hash + // for the block, accept it. + // - If the stored hash is all zeroes, store the new hash and + // accept the block (this is the first time we've read this + // block). + // - Otherwise, return -EINVAL for the read. + + uint8_t hash[SHA256_DIGEST_LENGTH]; + SHA256(fd->block_data, fd->block_size, hash); + uint8_t* blockhash = fd->hashes + block * SHA256_DIGEST_LENGTH; + if (memcmp(hash, blockhash, SHA256_DIGEST_LENGTH) == 0) { + return 0; + } + + int i; + for (i = 0; i < SHA256_DIGEST_LENGTH; ++i) { + if (blockhash[i] != 0) { + fd->curr_block = -1; + return -EIO; + } + } + + memcpy(blockhash, hash, SHA256_DIGEST_LENGTH); + 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); + 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); + + 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; +} + +int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, + uint64_t file_size, uint32_t block_size) +{ + int result; + + // 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); + + if (block_size < 1024) { + fprintf(stderr, "block size (%u) is too small\n", block_size); + return -1; + } + if (block_size > (1<<22)) { // 4 MiB + fprintf(stderr, "block size (%u) is too large\n", block_size); + return -1; + } + + struct fuse_data fd; + memset(&fd, 0, sizeof(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); + + if (fd.file_blocks > (1<<18)) { + fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks); + result = -1; + 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; + } + + fd.uid = getuid(); + fd.gid = getgid(); + + fd.curr_block = -1; + fd.block_data = (uint8_t*)malloc(block_size); + if (fd.block_data == NULL) { + 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) { + 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) { + perror("open /dev/fuse"); + result = -1; + goto done; + } + + char opts[256]; + snprintf(opts, sizeof(opts), + ("fd=%d,user_id=%d,group_id=%d,max_read=%u," + "allow_other,rootmode=040000"), + fd.ffd, fd.uid, fd.gid, block_size); + + result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT, + "fuse", MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts); + if (result < 0) { + perror("mount"); + goto done; + } + uint8_t request_buffer[sizeof(struct fuse_in_header) + PATH_MAX*8]; + for (;;) { + ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer))); + if (len == -1) { + perror("read request"); + if (errno == ENODEV) { + result = -1; + break; + } + continue; + } + + if ((size_t)len < sizeof(struct fuse_in_header)) { + fprintf(stderr, "request too short: len=%zu\n", (size_t)len); + continue; + } + + struct fuse_in_header* hdr = (struct fuse_in_header*) request_buffer; + void* data = request_buffer + sizeof(struct fuse_in_header); + + result = -ENOSYS; + + switch (hdr->opcode) { + case FUSE_INIT: + result = handle_init(data, &fd, hdr); + break; + + case FUSE_LOOKUP: + result = handle_lookup(data, &fd, hdr); + break; + + case FUSE_GETATTR: + result = handle_getattr(data, &fd, hdr); + break; + + case FUSE_OPEN: + result = handle_open(data, &fd, hdr); + break; + + case FUSE_READ: + result = handle_read(data, &fd, hdr); + break; + + case FUSE_FLUSH: + result = handle_flush(data, &fd, hdr); + break; + + case FUSE_RELEASE: + result = handle_release(data, &fd, hdr); + break; + + default: + fprintf(stderr, "unknown fuse request opcode %d\n", hdr->opcode); + break; + } + + if (result == NO_STATUS_EXIT) { + result = 0; + break; + } + + if (result != NO_STATUS) { + struct fuse_out_header outhdr; + outhdr.len = sizeof(outhdr); + outhdr.error = result; + outhdr.unique = hdr->unique; + TEMP_FAILURE_RETRY(write(fd.ffd, &outhdr, sizeof(outhdr))); + } + } + + done: + fd.vtab->close(fd.cookie); + + result = umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_DETACH); + if (result < 0) { + printf("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); + + return result; +}
diff --git a/recovery/fuse_sideload.h b/recovery/fuse_sideload.h new file mode 100644 index 0000000..c0b16ef --- /dev/null +++ b/recovery/fuse_sideload.h
@@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FUSE_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) + +struct provider_vtab { + // read a block + int (*read_block)(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size); + + // close down + void (*close)(void* cookie); +}; + +int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, + uint64_t file_size, uint32_t block_size); + +#endif
diff --git a/recovery/install.cpp b/recovery/install.cpp new file mode 100644 index 0000000..fa7b73e --- /dev/null +++ b/recovery/install.cpp
@@ -0,0 +1,296 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <vector> + +#include "common.h" +#include "install.h" +#include "minui/minui.h" +#include "minzip/SysUtil.h" +#include "minzip/Zip.h" +#include "roots.h" +#include "ui.h" +#include "verifier.h" + +extern RecoveryUI* ui; + +#define ASSUMED_UPDATE_BINARY_NAME "META-INF/com/google/android/update-binary" +#define PUBLIC_KEYS_FILE "/res/keys" + +// Default allocation of progress bar segments to operations +static const int VERIFICATION_PROGRESS_TIME = 60; +static const float VERIFICATION_PROGRESS_FRACTION = 0.25; +static const float DEFAULT_FILES_PROGRESS_FRACTION = 0.4; +static const float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1; + +// If the package contains an update binary, extract it and run it. +static int +try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) { + const ZipEntry* binary_entry = + mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME); + if (binary_entry == NULL) { + mzCloseZipArchive(zip); + return INSTALL_CORRUPT; + } + + const char* binary = "/tmp/update_binary"; + unlink(binary); + int fd = creat(binary, 0755); + if (fd < 0) { + mzCloseZipArchive(zip); + LOGE("Can't make %s\n", binary); + return INSTALL_ERROR; + } + bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd); + close(fd); + mzCloseZipArchive(zip); + + if (!ok) { + LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME); + return INSTALL_ERROR; + } + + int pipefd[2]; + pipe(pipefd); + + // When executing the update binary contained in the package, the + // arguments passed are: + // + // - the version number for this interface + // + // - an fd to which the program can write in order to update the + // progress bar. The program can write single-line commands: + // + // progress <frac> <secs> + // fill up the next <frac> part of of the progress bar + // over <secs> seconds. If <secs> is zero, use + // set_progress commands to manually control the + // progress of this segment of the bar. + // + // set_progress <frac> + // <frac> should be between 0.0 and 1.0; sets the + // progress bar within the segment defined by the most + // recent progress command. + // + // firmware <"hboot"|"radio"> <filename> + // arrange to install the contents of <filename> in the + // given partition on reboot. + // + // (API v2: <filename> may start with "PACKAGE:" to + // indicate taking a file from the OTA package.) + // + // (API v3: this command no longer exists.) + // + // ui_print <string> + // display <string> on the screen. + // + // wipe_cache + // a wipe of cache will be performed following a successful + // installation. + // + // clear_display + // turn off the text display. + // + // enable_reboot + // packages can explicitly request that they want the user + // to be able to reboot during installation (useful for + // debugging packages that don't exit). + // + // - the name of the package zip file. + // + + const char* args[5]; + args[0] = binary; + args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mk + char temp[16]; + snprintf(temp, sizeof(temp), "%d", pipefd[1]); + args[2] = temp; + args[3] = path; + args[4] = NULL; + + pid_t pid = fork(); + if (pid == 0) { + umask(022); + close(pipefd[0]); + execv(binary, const_cast<char**>(args)); + fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno)); + _exit(-1); + } + close(pipefd[1]); + + *wipe_cache = false; + bool retry_update = false; + + char buffer[1024]; + FILE* from_child = fdopen(pipefd[0], "r"); + while (fgets(buffer, sizeof(buffer), from_child) != NULL) { + char* command = strtok(buffer, " \n"); + if (command == NULL) { + continue; + } else if (strcmp(command, "progress") == 0) { + char* fraction_s = strtok(NULL, " \n"); + char* seconds_s = strtok(NULL, " \n"); + + float fraction = strtof(fraction_s, NULL); + int seconds = strtol(seconds_s, NULL, 10); + + ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds); + } else if (strcmp(command, "set_progress") == 0) { + char* fraction_s = strtok(NULL, " \n"); + float fraction = strtof(fraction_s, NULL); + ui->SetProgress(fraction); + } else if (strcmp(command, "ui_print") == 0) { + char* str = strtok(NULL, "\n"); + if (str) { + ui->PrintOnScreenOnly("%s", str); + } else { + ui->PrintOnScreenOnly("\n"); + } + fflush(stdout); + } else if (strcmp(command, "wipe_cache") == 0) { + *wipe_cache = true; + } else if (strcmp(command, "clear_display") == 0) { + ui->SetBackground(RecoveryUI::NONE); + } else if (strcmp(command, "enable_reboot") == 0) { + // packages can explicitly request that they want the user + // to be able to reboot during installation (useful for + // debugging packages that don't exit). + ui->SetEnableReboot(true); + } else if (strcmp(command, "retry_update") == 0) { + retry_update = true; + } else { + LOGE("unknown command [%s]\n", command); + } + } + fclose(from_child); + + int status; + waitpid(pid, &status, 0); + if (retry_update) { + return INSTALL_RETRY; + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status)); + return INSTALL_ERROR; + } + + return INSTALL_SUCCESS; +} + +static int +really_install_package(const char *path, bool* wipe_cache, bool needs_mount) +{ + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + ui->Print("Finding update package...\n"); + // Give verification half the progress bar... + ui->SetProgressType(RecoveryUI::DETERMINATE); + ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME); + LOGI("Update location: %s\n", path); + + // Map the update package into memory. + ui->Print("Opening update package...\n"); + + if (path && needs_mount) { + if (path[0] == '@') { + ensure_path_mounted(path+1); + } else { + ensure_path_mounted(path); + } + } + + MemMapping map; + if (sysMapFile(path, &map) != 0) { + LOGE("failed to map file\n"); + return INSTALL_CORRUPT; + } + + std::vector<Certificate> loadedKeys; + if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) { + LOGE("Failed to load keys\n"); + sysReleaseMap(&map); + return INSTALL_CORRUPT; + } + LOGI("%zu key(s) loaded from %s\n", loadedKeys.size(), PUBLIC_KEYS_FILE); + + ui->Print("Verifying update package...\n"); + + int err = verify_file(map.addr, map.length, loadedKeys); + LOGI("verify_file returned %d\n", err); + if (err != VERIFY_SUCCESS) { + LOGE("signature verification failed\n"); + sysReleaseMap(&map); + return INSTALL_CORRUPT; + } + + /* Try to open the package. + */ + ZipArchive zip; + err = mzOpenZipArchive(map.addr, map.length, &zip); + if (err != 0) { + LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad"); + sysReleaseMap(&map); + return INSTALL_CORRUPT; + } + + /* Verify and install the contents of the package. + */ + ui->Print("Installing update...\n"); + ui->SetEnableReboot(false); + int result = try_update_binary(path, &zip, wipe_cache); + ui->SetEnableReboot(true); + ui->Print("\n"); + + sysReleaseMap(&map); + + return result; +} + +int +install_package(const char* path, bool* wipe_cache, const char* install_file, + bool needs_mount) +{ + modified_flash = true; + + FILE* install_log = fopen_path(install_file, "w"); + if (install_log) { + fputs(path, install_log); + fputc('\n', install_log); + } else { + LOGE("failed to open last_install: %s\n", strerror(errno)); + } + int result; + if (setup_install_mounts() != 0) { + LOGE("failed to set up expected mounts for install; aborting\n"); + result = INSTALL_ERROR; + } else { + result = really_install_package(path, wipe_cache, needs_mount); + } + if (install_log) { + fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log); + fputc('\n', install_log); + fclose(install_log); + } + return result; +}
diff --git a/recovery/install.h b/recovery/install.h new file mode 100644 index 0000000..fd08e3c --- /dev/null +++ b/recovery/install.h
@@ -0,0 +1,38 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RECOVERY_INSTALL_H_ +#define RECOVERY_INSTALL_H_ + +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE, INSTALL_SKIPPED, + INSTALL_RETRY }; +// Install the package specified by root_path. If INSTALL_SUCCESS is +// returned and *wipe_cache is true on exit, caller should wipe the +// cache partition. +int install_package(const char* root_path, bool* wipe_cache, + const char* install_file, bool needs_mount); + +#ifdef __cplusplus +} +#endif + +#endif // RECOVERY_INSTALL_H_
diff --git a/recovery/interlace-frames.py b/recovery/interlace-frames.py new file mode 100644 index 0000000..3e777b4 --- /dev/null +++ b/recovery/interlace-frames.py
@@ -0,0 +1,80 @@ +# 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. + +""" +Script to take a set of frames (PNG files) for a recovery animation and turn +it into a single output image which contains the input frames interlaced by +row. Run with the names of all the input frames on the command line. Specify +the name of the output file with -o (or --output), and optionally specify the +number of frames per second (FPS) with --fps (default: 20). + +e.g. +interlace-frames.py --fps 20 --output output.png frame0.png frame1.png frame3.png +""" + +from __future__ import print_function + +import argparse +import sys +try: + import Image + import PngImagePlugin +except ImportError: + print("This script requires the Python Imaging Library to be installed.") + sys.exit(1) + + +def interlace(output, fps, inputs): + frames = [Image.open(fn).convert("RGB") for fn in inputs] + assert len(frames) > 0, "Must have at least one input frame." + sizes = set() + for fr in frames: + sizes.add(fr.size) + + assert len(sizes) == 1, "All input images must have the same size." + w, h = sizes.pop() + N = len(frames) + + out = Image.new("RGB", (w, h*N)) + for j in range(h): + for i in range(w): + for fn, f in enumerate(frames): + out.putpixel((i, j*N+fn), f.getpixel((i, j))) + + # When loading this image, the graphics library expects to find a text + # chunk that specifies how many frames this animation represents. If + # you post-process the output of this script with some kind of + # optimizer tool (eg pngcrush or zopflipng) make sure that your + # optimizer preserves this text chunk. + + meta = PngImagePlugin.PngInfo() + meta.add_text("Frames", str(N)) + meta.add_text("FPS", str(fps)) + + out.save(output, pnginfo=meta) + + +def main(argv): + parser = argparse.ArgumentParser() + parser.add_argument('--fps', default=20) + parser.add_argument('--output', '-o', required=True) + parser.add_argument('input', nargs='+') + args = parser.parse_args(argv) + + interlace(args.output, args.fps, args.input) + + +if __name__ == '__main__': + main(sys.argv[1:]) +
diff --git a/recovery/minadbd/Android.mk b/recovery/minadbd/Android.mk new file mode 100644 index 0000000..34631a9 --- /dev/null +++ b/recovery/minadbd/Android.mk
@@ -0,0 +1,38 @@ +# Copyright 2005 The Android Open Source Project + +LOCAL_PATH:= $(call my-dir) + +minadbd_cflags := \ + -Wall -Werror \ + -Wno-unused-parameter \ + -Wno-missing-field-initializers \ + -DADB_HOST=0 \ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + adb_main.cpp \ + fuse_adb_provider.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) + +include $(CLEAR_VARS) + +LOCAL_CLANG := true +LOCAL_MODULE := minadbd_test +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 + +include $(BUILD_NATIVE_TEST)
diff --git a/recovery/minadbd/README.txt b/recovery/minadbd/README.txt new file mode 100644 index 0000000..e69dc87 --- /dev/null +++ b/recovery/minadbd/README.txt
@@ -0,0 +1,8 @@ +minadbd is now mostly built from libadbd. The fuse features are unique to +minadbd, and services.c has been modified as follows: + + - all services removed + - all host mode support removed + - sideload_service() added; this is the only service supported. It + receives a single blob of data, writes it to a fixed filename, and + makes the process exit.
diff --git a/recovery/minadbd/adb_main.cpp b/recovery/minadbd/adb_main.cpp new file mode 100644 index 0000000..8e581c2 --- /dev/null +++ b/recovery/minadbd/adb_main.cpp
@@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> + +#include "adb.h" +#include "adb_auth.h" +#include "transport.h" + +int adb_server_main(int is_daemon, int server_port, int /* reply_fd */) { + adb_device_banner = "sideload"; + + signal(SIGPIPE, SIG_IGN); + + // We can't require authentication for sideloading. http://b/22025550. + auth_required = false; + + init_transport_registration(); + usb_init(); + + VLOG(ADB) << "Event loop starting"; + fdevent_loop(); + + return 0; +}
diff --git a/recovery/minadbd/fuse_adb_provider.cpp b/recovery/minadbd/fuse_adb_provider.cpp new file mode 100644 index 0000000..0f4c256 --- /dev/null +++ b/recovery/minadbd/fuse_adb_provider.cpp
@@ -0,0 +1,59 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#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; + } + + if (!ReadFdExactly(ad->sfd, buffer, fetch_size)) { + fprintf(stderr, "failed to read from adb host: %s\n", strerror(errno)); + return -EIO; + } + + return 0; +} + +static void close_adb(void* data) { + adb_data* ad = reinterpret_cast<adb_data*>(data); + WriteFdExactly(ad->sfd, "DONEDONE"); +} + +int run_adb_fuse(int sfd, uint64_t file_size, uint32_t block_size) { + adb_data ad; + ad.sfd = sfd; + ad.file_size = file_size; + ad.block_size = block_size; + + provider_vtab vtab; + vtab.read_block = read_block_adb; + vtab.close = close_adb; + + return run_fuse_sideload(&vtab, &ad, file_size, block_size); +}
diff --git a/recovery/minadbd/fuse_adb_provider.h b/recovery/minadbd/fuse_adb_provider.h new file mode 100644 index 0000000..9941709 --- /dev/null +++ b/recovery/minadbd/fuse_adb_provider.h
@@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FUSE_ADB_PROVIDER_H +#define __FUSE_ADB_PROVIDER_H + +#include <stdint.h> + +struct adb_data { + int sfd; // file descriptor for the adb channel + + 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 run_adb_fuse(int sfd, uint64_t file_size, uint32_t block_size); + +#endif
diff --git a/recovery/minadbd/fuse_adb_provider_test.cpp b/recovery/minadbd/fuse_adb_provider_test.cpp new file mode 100644 index 0000000..0f2e881 --- /dev/null +++ b/recovery/minadbd/fuse_adb_provider_test.cpp
@@ -0,0 +1,88 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "fuse_adb_provider.h" + +#include <gtest/gtest.h> + +#include <errno.h> +#include <fcntl.h> +#include <sys/socket.h> + +#include <string> + +#include "adb_io.h" + +TEST(fuse_adb_provider, read_block_adb) { + adb_data data = {}; + int sockets[2]; + + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sockets)); + data.sfd = sockets[0]; + + int host_socket = sockets[1]; + fcntl(host_socket, F_SETFL, O_NONBLOCK); + + const char expected_data[] = "foobar"; + char block_data[sizeof(expected_data)] = {}; + + // If we write the result of read_block_adb's request before the request is + // actually made we can avoid needing an extra thread for this test. + ASSERT_TRUE(WriteFdExactly(host_socket, expected_data, + strlen(expected_data))); + + uint32_t block = 1234U; + const char expected_block[] = "00001234"; + ASSERT_EQ(0, read_block_adb(reinterpret_cast<void*>(&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)] = {}; + ASSERT_TRUE(ReadFdExactly(host_socket, block_req, 8)); + ASSERT_EQ(0, block_req[8]); + ASSERT_EQ(8U, strlen(block_req)); + ASSERT_STREQ(expected_block, block_req); + + // Check that read_block_adb returned the right data. + ASSERT_EQ(0, block_req[8]); + ASSERT_STREQ(expected_data, block_data); + + // Check that nothing else was written to the socket. + char tmp; + errno = 0; + ASSERT_EQ(-1, read(host_socket, &tmp, 1)); + ASSERT_EQ(EWOULDBLOCK, errno); + + close(sockets[0]); + close(sockets[1]); +} + +TEST(fuse_adb_provider, read_block_adb_fail_write) { + adb_data data = {}; + int sockets[2]; + + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sockets)); + data.sfd = sockets[0]; + + ASSERT_EQ(0, close(sockets[1])); + + char buf[1]; + ASSERT_EQ(-EIO, read_block_adb(reinterpret_cast<void*>(&data), 0, + reinterpret_cast<uint8_t*>(buf), 1)); + + close(sockets[0]); +}
diff --git a/recovery/minadbd/minadbd_services.cpp b/recovery/minadbd/minadbd_services.cpp new file mode 100644 index 0000000..003b519 --- /dev/null +++ b/recovery/minadbd/minadbd_services.cpp
@@ -0,0 +1,103 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "adb.h" +#include "fdevent.h" +#include "fuse_adb_provider.h" +#include "sysdeps.h" + +typedef struct stinfo stinfo; + +struct stinfo { + void (*func)(int fd, void *cookie); + int fd; + void *cookie; +}; + +void service_bootstrap_func(void* x) { + stinfo* sti = reinterpret_cast<stinfo*>(x); + sti->func(sti->fd, sti->cookie); + free(sti); +} + +static void sideload_host_service(int sfd, void* data) { + char* args = reinterpret_cast<char*>(data); + int file_size; + int block_size; + if (sscanf(args, "%d:%d", &file_size, &block_size) != 2) { + printf("bad sideload-host arguments: %s\n", args); + exit(1); + } + free(args); + + printf("sideload-host file size %d block size %d\n", file_size, block_size); + + int result = run_adb_fuse(sfd, file_size, block_size); + + printf("sideload_host finished\n"); + sleep(1); + exit(result == 0 ? 0 : 1); +} + +static int create_service_thread(void (*func)(int, void *), void *cookie) { + int s[2]; + if (adb_socketpair(s)) { + printf("cannot create service socket pair\n"); + return -1; + } + + stinfo* sti = reinterpret_cast<stinfo*>(malloc(sizeof(stinfo))); + if(sti == 0) fatal("cannot allocate stinfo"); + sti->func = func; + sti->cookie = cookie; + sti->fd = s[1]; + + if (!adb_thread_create(service_bootstrap_func, sti)) { + free(sti); + adb_close(s[0]); + adb_close(s[1]); + printf("cannot create service thread\n"); + return -1; + } + + VLOG(SERVICES) << "service thread started, " << s[0] << ":" << s[1]; + return s[0]; +} + +int service_to_fd(const char* name, const 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)) { + char* arg = strdup(name + 14); + ret = create_service_thread(sideload_host_service, arg); + } + if (ret >= 0) { + close_on_exec(ret); + } + return ret; +}
diff --git a/recovery/minui/Android.mk b/recovery/minui/Android.mk new file mode 100644 index 0000000..380ec2b --- /dev/null +++ b/recovery/minui/Android.mk
@@ -0,0 +1,49 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + events.cpp \ + graphics.cpp \ + graphics_adf.cpp \ + graphics_drm.cpp \ + graphics_fbdev.cpp \ + resources.cpp \ + +LOCAL_WHOLE_STATIC_LIBRARIES += libadf +LOCAL_WHOLE_STATIC_LIBRARIES += libdrm +LOCAL_WHOLE_STATIC_LIBRARIES += libsync_recovery +LOCAL_STATIC_LIBRARIES += libpng + +LOCAL_MODULE := libminui + +LOCAL_CLANG := true + +# This used to compare against values in double-quotes (which are just +# ordinary characters in this context). Strip double-quotes from the +# value so that either will work. + +ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),ABGR_8888) + LOCAL_CFLAGS += -DRECOVERY_ABGR +endif +ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),RGBX_8888) + LOCAL_CFLAGS += -DRECOVERY_RGBX +endif +ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),BGRA_8888) + LOCAL_CFLAGS += -DRECOVERY_BGRA +endif + +ifneq ($(TARGET_RECOVERY_OVERSCAN_PERCENT),) + LOCAL_CFLAGS += -DOVERSCAN_PERCENT=$(TARGET_RECOVERY_OVERSCAN_PERCENT) +else + LOCAL_CFLAGS += -DOVERSCAN_PERCENT=0 +endif + +include $(BUILD_STATIC_LIBRARY) + +# Used by OEMs for factory test images. +include $(CLEAR_VARS) +LOCAL_CLANG := true +LOCAL_MODULE := libminui +LOCAL_WHOLE_STATIC_LIBRARIES += libminui +LOCAL_SHARED_LIBRARIES := libpng +include $(BUILD_SHARED_LIBRARY)
diff --git a/recovery/minui/events.cpp b/recovery/minui/events.cpp new file mode 100644 index 0000000..e6e7bd2 --- /dev/null +++ b/recovery/minui/events.cpp
@@ -0,0 +1,235 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/epoll.h> +#include <unistd.h> + +#include <linux/input.h> + +#include "minui.h" + +#define MAX_DEVICES 16 +#define MAX_MISC_FDS 16 + +#define BITS_PER_LONG (sizeof(unsigned long) * 8) +#define BITS_TO_LONGS(x) (((x) + BITS_PER_LONG - 1) / BITS_PER_LONG) + +struct fd_info { + int fd; + ev_callback cb; + void* data; +}; + +static int g_epoll_fd; +static epoll_event polledevents[MAX_DEVICES + MAX_MISC_FDS]; +static int npolledevents; + +static fd_info ev_fdinfo[MAX_DEVICES + MAX_MISC_FDS]; + +static unsigned ev_count = 0; +static unsigned ev_dev_count = 0; +static unsigned ev_misc_count = 0; + +static bool test_bit(size_t bit, unsigned long* array) { // NOLINT + return (array[bit/BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0; +} + +int ev_init(ev_callback input_cb, void* data) { + bool epollctlfail = false; + + g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS); + if (g_epoll_fd == -1) { + return -1; + } + + DIR* dir = opendir("/dev/input"); + if (dir != NULL) { + dirent* de; + while ((de = readdir(dir))) { + // Use unsigned long to match ioctl's parameter type. + unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT + +// fprintf(stderr,"/dev/input/%s\n", de->d_name); + if (strncmp(de->d_name, "event", 5)) continue; + int fd = openat(dirfd(dir), de->d_name, O_RDONLY); + if (fd == -1) continue; + + // Read the evbits of the input device. + if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { + close(fd); + continue; + } + + // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. + if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) { + close(fd); + continue; + } + + epoll_event ev; + ev.events = EPOLLIN | EPOLLWAKEUP; + ev.data.ptr = &ev_fdinfo[ev_count]; + if (epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { + close(fd); + epollctlfail = true; + continue; + } + + ev_fdinfo[ev_count].fd = fd; + ev_fdinfo[ev_count].cb = input_cb; + ev_fdinfo[ev_count].data = data; + ev_count++; + ev_dev_count++; + if (ev_dev_count == MAX_DEVICES) break; + } + + closedir(dir); + } + + if (epollctlfail && !ev_count) { + close(g_epoll_fd); + g_epoll_fd = -1; + return -1; + } + + return 0; +} + +int ev_get_epollfd(void) { + return g_epoll_fd; +} + +int ev_add_fd(int fd, ev_callback cb, void* data) { + if (ev_misc_count == MAX_MISC_FDS || cb == NULL) { + return -1; + } + + epoll_event ev; + ev.events = EPOLLIN | EPOLLWAKEUP; + ev.data.ptr = (void *)&ev_fdinfo[ev_count]; + int ret = epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev); + if (!ret) { + ev_fdinfo[ev_count].fd = fd; + ev_fdinfo[ev_count].cb = cb; + ev_fdinfo[ev_count].data = data; + ev_count++; + ev_misc_count++; + } + + return ret; +} + +void ev_exit(void) { + while (ev_count > 0) { + close(ev_fdinfo[--ev_count].fd); + } + ev_misc_count = 0; + ev_dev_count = 0; + close(g_epoll_fd); +} + +int ev_wait(int timeout) { + npolledevents = epoll_wait(g_epoll_fd, polledevents, ev_count, timeout); + if (npolledevents <= 0) { + return -1; + } + return 0; +} + +void ev_dispatch(void) { + for (int n = 0; n < npolledevents; n++) { + fd_info* fdi = reinterpret_cast<fd_info*>(polledevents[n].data.ptr); + ev_callback cb = fdi->cb; + if (cb) { + cb(fdi->fd, polledevents[n].events, fdi->data); + } + } +} + +int ev_get_input(int fd, uint32_t epevents, input_event* ev) { + if (epevents & EPOLLIN) { + ssize_t r = TEMP_FAILURE_RETRY(read(fd, ev, sizeof(*ev))); + if (r == sizeof(*ev)) { + return 0; + } + } + return -1; +} + +int ev_sync_key_state(ev_set_key_callback set_key_cb, void* data) { + // Use unsigned long to match ioctl's parameter type. + unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT + unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINT + + for (size_t i = 0; i < ev_dev_count; ++i) { + memset(ev_bits, 0, sizeof(ev_bits)); + memset(key_bits, 0, sizeof(key_bits)); + + if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { + continue; + } + if (!test_bit(EV_KEY, ev_bits)) { + continue; + } + if (ioctl(ev_fdinfo[i].fd, EVIOCGKEY(sizeof(key_bits)), key_bits) == -1) { + continue; + } + + for (int code = 0; code <= KEY_MAX; code++) { + if (test_bit(code, key_bits)) { + set_key_cb(code, 1, data); + } + } + } + + return 0; +} + +void ev_iterate_available_keys(const std::function<void(int)>& f) { + // Use unsigned long to match ioctl's parameter type. + unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT + unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINT + + for (size_t i = 0; i < ev_dev_count; ++i) { + memset(ev_bits, 0, sizeof(ev_bits)); + memset(key_bits, 0, sizeof(key_bits)); + + // Does this device even have keys? + if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { + continue; + } + if (!test_bit(EV_KEY, ev_bits)) { + continue; + } + + int rc = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX), key_bits); + if (rc == -1) { + continue; + } + + for (int key_code = 0; key_code <= KEY_MAX; ++key_code) { + if (test_bit(key_code, key_bits)) { + f(key_code); + } + } + } +}
diff --git a/recovery/minui/font_10x18.h b/recovery/minui/font_10x18.h new file mode 100644 index 0000000..29d7053 --- /dev/null +++ b/recovery/minui/font_10x18.h
@@ -0,0 +1,214 @@ +struct { + unsigned width; + unsigned height; + unsigned cwidth; + unsigned cheight; + unsigned char rundata[2973]; +} font = { + .width = 960, + .height = 18, + .cwidth = 10, + .cheight = 18, + .rundata = { +0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x55,0x82,0x06,0x82,0x02,0x82,0x10,0x82, +0x11,0x83,0x08,0x82,0x0a,0x82,0x04,0x82,0x46,0x82,0x08,0x82,0x07,0x84,0x06, +0x84,0x0a,0x81,0x03,0x88,0x04,0x84,0x04,0x88,0x04,0x84,0x06,0x84,0x1e,0x81, +0x0e,0x81,0x0a,0x84,0x06,0x84,0x07,0x82,0x05,0x85,0x07,0x84,0x04,0x86,0x04, +0x88,0x02,0x88,0x04,0x84,0x04,0x82,0x04,0x82,0x02,0x88,0x05,0x86,0x01,0x82, +0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x84,0x04, +0x86,0x06,0x84,0x04,0x86,0x06,0x84,0x04,0x88,0x02,0x82,0x04,0x82,0x02,0x82, +0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02, +0x88,0x03,0x86,0x0e,0x86,0x06,0x82,0x11,0x82,0x10,0x82,0x18,0x82,0x0f,0x84, +0x0d,0x82,0x1c,0x82,0x09,0x84,0x7f,0x16,0x84,0x05,0x82,0x05,0x84,0x07,0x83, +0x02,0x82,0x19,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x82,0x03,0x86,0x04, +0x83,0x02,0x82,0x03,0x82,0x01,0x82,0x07,0x82,0x09,0x82,0x06,0x82,0x3e,0x82, +0x04,0x84,0x06,0x83,0x06,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x03, +0x82,0x09,0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82, +0x1c,0x82,0x0e,0x82,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x05,0x84,0x04, +0x82,0x02,0x82,0x05,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82, +0x09,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x04, +0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x03,0x82,0x02,0x82, +0x03,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x02, +0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82, +0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x03,0x82,0x08,0x82,0x0c, +0x82,0x05,0x84,0x11,0x82,0x0f,0x82,0x18,0x82,0x0e,0x82,0x02,0x82,0x0c,0x82, +0x1c,0x82,0x0b,0x82,0x7f,0x15,0x82,0x08,0x82,0x08,0x82,0x05,0x82,0x01,0x82, +0x01,0x82,0x19,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x82,0x02,0x82,0x01, +0x82,0x01,0x82,0x02,0x82,0x01,0x82,0x01,0x82,0x03,0x82,0x01,0x82,0x07,0x82, +0x08,0x82,0x08,0x82,0x3d,0x82,0x03,0x82,0x02,0x82,0x04,0x84,0x05,0x82,0x04, +0x82,0x02,0x82,0x04,0x82,0x06,0x83,0x03,0x82,0x08,0x82,0x04,0x81,0x09,0x82, +0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x1a,0x82,0x10,0x82,0x06,0x82,0x04, +0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82, +0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02, +0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x83, +0x02,0x83,0x02,0x83,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02, +0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82, +0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x04, +0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x08,0x82,0x0c,0x82,0x04,0x82,0x02,0x82, +0x11,0x82,0x0e,0x82,0x18,0x82,0x0e,0x82,0x02,0x82,0x0c,0x82,0x0b,0x82,0x0b, +0x82,0x02,0x82,0x0b,0x82,0x4d,0x82,0x45,0x82,0x08,0x82,0x08,0x82,0x05,0x82, +0x02,0x83,0x1a,0x82,0x07,0x81,0x02,0x81,0x07,0x82,0x01,0x82,0x02,0x82,0x01, +0x82,0x05,0x82,0x01,0x84,0x04,0x82,0x01,0x82,0x07,0x82,0x08,0x82,0x08,0x82, +0x06,0x82,0x02,0x82,0x06,0x82,0x28,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x01, +0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x84,0x03,0x82,0x08,0x82, +0x0d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x19,0x82,0x12,0x82,0x05, +0x82,0x04,0x82,0x02,0x82,0x02,0x84,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82, +0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x04, +0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x83,0x02,0x83, +0x02,0x84,0x02,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04, +0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x0b,0x82,0x05,0x82,0x04,0x82,0x02,0x82, +0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08, +0x82,0x04,0x82,0x09,0x82,0x0b,0x82,0x03,0x82,0x04,0x82,0x20,0x82,0x18,0x82, +0x0e,0x82,0x10,0x82,0x0b,0x82,0x0b,0x82,0x02,0x82,0x0b,0x82,0x4d,0x82,0x45, +0x82,0x08,0x82,0x08,0x82,0x26,0x82,0x10,0x88,0x01,0x82,0x01,0x82,0x06,0x83, +0x01,0x82,0x04,0x84,0x08,0x81,0x08,0x82,0x0a,0x82,0x05,0x82,0x02,0x82,0x06, +0x82,0x28,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x08,0x82,0x04,0x82, +0x01,0x82,0x03,0x82,0x08,0x82,0x0d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04, +0x82,0x18,0x82,0x06,0x88,0x06,0x82,0x04,0x82,0x04,0x82,0x02,0x82,0x01,0x85, +0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x04,0x82,0x02, +0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82, +0x02,0x82,0x04,0x82,0x08,0x88,0x02,0x84,0x02,0x82,0x02,0x82,0x04,0x82,0x02, +0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x0b,0x82, +0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x04,0x84,0x06, +0x84,0x08,0x82,0x05,0x82,0x09,0x82,0x0b,0x82,0x2b,0x82,0x18,0x82,0x0e,0x82, +0x10,0x82,0x1c,0x82,0x0b,0x82,0x4d,0x82,0x45,0x82,0x08,0x82,0x08,0x82,0x26, +0x82,0x11,0x82,0x01,0x82,0x03,0x82,0x01,0x82,0x09,0x82,0x06,0x82,0x12,0x82, +0x0a,0x82,0x06,0x84,0x07,0x82,0x27,0x82,0x04,0x82,0x04,0x82,0x05,0x82,0x0b, +0x82,0x07,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x01,0x83,0x04,0x82,0x01,0x83, +0x08,0x82,0x05,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x05,0x83,0x07,0x83,0x05, +0x82,0x16,0x82,0x08,0x82,0x03,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82, +0x02,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08, +0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82, +0x08,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04, +0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82, +0x0a,0x82,0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01, +0x82,0x04,0x84,0x06,0x84,0x08,0x82,0x05,0x82,0x0a,0x82,0x0a,0x82,0x23,0x85, +0x03,0x82,0x01,0x83,0x06,0x85,0x05,0x83,0x01,0x82,0x04,0x84,0x04,0x86,0x05, +0x85,0x01,0x81,0x02,0x82,0x01,0x83,0x05,0x84,0x09,0x84,0x02,0x82,0x03,0x82, +0x06,0x82,0x05,0x81,0x01,0x82,0x01,0x82,0x03,0x82,0x01,0x83,0x06,0x84,0x04, +0x82,0x01,0x83,0x06,0x83,0x01,0x82,0x02,0x82,0x01,0x84,0x04,0x86,0x03,0x86, +0x04,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04, +0x82,0x02,0x82,0x04,0x82,0x03,0x87,0x05,0x82,0x08,0x82,0x08,0x82,0x26,0x82, +0x11,0x82,0x01,0x82,0x04,0x86,0x07,0x82,0x05,0x83,0x12,0x82,0x0a,0x82,0x04, +0x88,0x02,0x88,0x0c,0x88,0x10,0x82,0x04,0x82,0x04,0x82,0x05,0x82,0x0a,0x82, +0x06,0x83,0x04,0x82,0x03,0x82,0x03,0x83,0x02,0x82,0x03,0x83,0x02,0x82,0x07, +0x82,0x06,0x84,0x05,0x82,0x02,0x83,0x05,0x83,0x07,0x83,0x04,0x82,0x18,0x82, +0x06,0x82,0x04,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x86,0x04, +0x82,0x08,0x82,0x04,0x82,0x02,0x86,0x04,0x86,0x04,0x82,0x02,0x84,0x02,0x88, +0x05,0x82,0x0a,0x82,0x03,0x85,0x05,0x82,0x08,0x82,0x01,0x82,0x01,0x82,0x02, +0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82, +0x04,0x82,0x02,0x82,0x03,0x82,0x05,0x84,0x07,0x82,0x05,0x82,0x04,0x82,0x03, +0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,0x82,0x05,0x82,0x08,0x82,0x08,0x82, +0x06,0x82,0x0a,0x82,0x0a,0x82,0x22,0x82,0x03,0x82,0x02,0x83,0x02,0x82,0x04, +0x82,0x03,0x82,0x03,0x82,0x02,0x83,0x03,0x82,0x02,0x82,0x05,0x82,0x06,0x82, +0x03,0x83,0x02,0x83,0x02,0x82,0x06,0x82,0x0b,0x82,0x02,0x82,0x02,0x82,0x07, +0x82,0x05,0x88,0x02,0x83,0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x82, +0x04,0x82,0x02,0x83,0x03,0x83,0x02,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06, +0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82, +0x03,0x82,0x04,0x82,0x08,0x82,0x02,0x84,0x09,0x82,0x09,0x84,0x23,0x82,0x11, +0x82,0x01,0x82,0x06,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x01,0x82,0x11,0x82, +0x0a,0x82,0x06,0x84,0x07,0x82,0x26,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x08, +0x83,0x09,0x82,0x03,0x82,0x03,0x82,0x09,0x82,0x02,0x82,0x04,0x82,0x05,0x82, +0x06,0x82,0x02,0x82,0x05,0x83,0x01,0x82,0x17,0x82,0x16,0x82,0x06,0x82,0x05, +0x82,0x01,0x82,0x01,0x82,0x02,0x88,0x02,0x82,0x03,0x82,0x03,0x82,0x08,0x82, +0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05, +0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x01,0x82,0x01,0x82, +0x02,0x82,0x02,0x84,0x02,0x82,0x04,0x82,0x02,0x86,0x04,0x82,0x04,0x82,0x02, +0x86,0x09,0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x82, +0x01,0x82,0x04,0x84,0x07,0x82,0x07,0x82,0x07,0x82,0x0b,0x82,0x09,0x82,0x27, +0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82, +0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02, +0x82,0x01,0x82,0x08,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82, +0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x07, +0x82,0x0a,0x82,0x06,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x04,0x82, +0x04,0x84,0x04,0x82,0x04,0x82,0x07,0x82,0x06,0x82,0x08,0x82,0x08,0x82,0x26, +0x82,0x0f,0x88,0x05,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x02,0x82,0x01,0x82, +0x0d,0x82,0x0a,0x82,0x05,0x82,0x02,0x82,0x06,0x82,0x26,0x82,0x05,0x82,0x04, +0x82,0x05,0x82,0x07,0x82,0x0c,0x82,0x02,0x88,0x08,0x82,0x02,0x82,0x04,0x82, +0x05,0x82,0x05,0x82,0x04,0x82,0x08,0x82,0x18,0x82,0x14,0x82,0x07,0x82,0x05, +0x82,0x01,0x84,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82, +0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05, +0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x01,0x82,0x01,0x82, +0x02,0x82,0x02,0x84,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02, +0x82,0x02,0x82,0x0a,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x82, +0x01,0x82,0x01,0x82,0x04,0x84,0x07,0x82,0x07,0x82,0x07,0x82,0x0b,0x82,0x09, +0x82,0x22,0x87,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x88, +0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02, +0x84,0x09,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82, +0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x08,0x86,0x05, +0x82,0x06,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,0x82, +0x05,0x82,0x05,0x82,0x04,0x82,0x06,0x82,0x07,0x82,0x08,0x82,0x08,0x82,0x26, +0x82,0x10,0x82,0x01,0x82,0x07,0x82,0x01,0x82,0x04,0x82,0x01,0x83,0x02,0x82, +0x03,0x83,0x0f,0x82,0x08,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x25,0x82,0x07, +0x82,0x02,0x82,0x06,0x82,0x06,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x09,0x82, +0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x04,0x82,0x08,0x82,0x19,0x82,0x05, +0x88,0x05,0x82,0x08,0x82,0x05,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x02,0x82, +0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04, +0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x03,0x82,0x03,0x82,0x03,0x82, +0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02, +0x82,0x08,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x03,0x82,0x09,0x82,0x05,0x82, +0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x83,0x02,0x83,0x03,0x82,0x02,0x82,0x06, +0x82,0x06,0x82,0x08,0x82,0x0c,0x82,0x08,0x82,0x21,0x82,0x04,0x82,0x02,0x82, +0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a,0x82,0x06,0x82,0x03, +0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x85,0x08,0x82,0x05,0x82, +0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04, +0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0d,0x82,0x04,0x82,0x06,0x82,0x04,0x82, +0x04,0x84,0x04,0x82,0x01,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x05, +0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x38,0x82,0x01,0x82,0x04,0x82,0x01,0x82, +0x01,0x82,0x04,0x84,0x01,0x82,0x01,0x82,0x03,0x82,0x10,0x82,0x08,0x82,0x30, +0x83,0x06,0x82,0x07,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x08,0x82,0x04,0x82, +0x07,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x04, +0x82,0x03,0x81,0x04,0x82,0x1a,0x82,0x10,0x82,0x10,0x82,0x08,0x82,0x04,0x82, +0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08, +0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x03,0x82, +0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02, +0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x02,0x84,0x02,0x82,0x03,0x82,0x03,0x82, +0x04,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x05,0x83,0x02,0x83,0x03, +0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x09,0x82,0x0c,0x82,0x08,0x82,0x21,0x82, +0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a, +0x82,0x07,0x85,0x04,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x82,0x02,0x82, +0x07,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04, +0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0d,0x82,0x04,0x82, +0x06,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x82,0x01,0x82,0x04,0x84,0x04, +0x82,0x04,0x82,0x04,0x82,0x09,0x82,0x08,0x82,0x08,0x82,0x26,0x82,0x10,0x82, +0x01,0x82,0x05,0x86,0x04,0x82,0x01,0x82,0x01,0x82,0x01,0x83,0x01,0x84,0x10, +0x82,0x06,0x82,0x1d,0x83,0x11,0x83,0x05,0x82,0x09,0x84,0x07,0x82,0x05,0x82, +0x09,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04, +0x82,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x06,0x83,0x07,0x83,0x09,0x82, +0x0e,0x82,0x0a,0x82,0x06,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x03, +0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x09,0x82, +0x02,0x83,0x02,0x82,0x04,0x82,0x05,0x82,0x06,0x82,0x01,0x82,0x04,0x82,0x04, +0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82, +0x03,0x82,0x09,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x06, +0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82, +0x05,0x82,0x05,0x82,0x09,0x82,0x0d,0x82,0x07,0x82,0x21,0x82,0x04,0x82,0x02, +0x83,0x02,0x82,0x04,0x82,0x03,0x82,0x03,0x82,0x02,0x83,0x03,0x82,0x03,0x82, +0x04,0x82,0x06,0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x82,0x03, +0x82,0x06,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x03,0x82, +0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x07,0x82,0x04, +0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x02,0x83,0x05,0x82,0x05,0x88,0x03,0x82, +0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x0a,0x82,0x08,0x82,0x08,0x82,0x26, +0x82,0x1c,0x82,0x06,0x82,0x02,0x83,0x03,0x84,0x02,0x82,0x10,0x82,0x04,0x82, +0x1e,0x83,0x11,0x83,0x05,0x82,0x0a,0x82,0x05,0x88,0x02,0x88,0x04,0x84,0x09, +0x82,0x05,0x84,0x06,0x84,0x05,0x82,0x09,0x84,0x06,0x84,0x07,0x83,0x07,0x83, +0x0a,0x81,0x0e,0x81,0x0b,0x82,0x07,0x85,0x03,0x82,0x04,0x82,0x02,0x86,0x06, +0x84,0x04,0x86,0x04,0x88,0x02,0x82,0x0a,0x84,0x01,0x81,0x02,0x82,0x04,0x82, +0x02,0x88,0x04,0x83,0x05,0x82,0x04,0x82,0x02,0x88,0x02,0x82,0x04,0x82,0x02, +0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x0a,0x85,0x03,0x82,0x04,0x82,0x04,0x84, +0x07,0x82,0x07,0x84,0x07,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05, +0x82,0x05,0x88,0x03,0x86,0x09,0x82,0x03,0x86,0x22,0x85,0x01,0x81,0x02,0x82, +0x01,0x83,0x06,0x85,0x05,0x83,0x01,0x82,0x04,0x85,0x05,0x82,0x07,0x86,0x03, +0x82,0x04,0x82,0x02,0x88,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x88,0x02,0x82, +0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x83,0x06, +0x83,0x01,0x82,0x03,0x82,0x08,0x86,0x06,0x84,0x05,0x83,0x01,0x82,0x05,0x82, +0x06,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x04,0x83,0x01,0x82,0x03,0x87,0x06, +0x84,0x05,0x82,0x05,0x84,0x7f,0x15,0x83,0x7f,0x14,0x83,0x7f,0x5e,0x82,0x7f, +0x05,0x89,0x47,0x82,0x04,0x82,0x17,0x82,0x03,0x82,0x34,0x82,0x0e,0x82,0x4e, +0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0a,0x82,0x04,0x82,0x17,0x82,0x03,0x82, +0x34,0x82,0x0e,0x82,0x48,0x82,0x04,0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0a, +0x82,0x04,0x82,0x17,0x82,0x03,0x82,0x34,0x82,0x0e,0x82,0x49,0x82,0x02,0x82, +0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0c,0x86,0x19,0x85,0x35,0x82,0x0e,0x82,0x4a, +0x84,0x3f, +0x00, + } +};
diff --git a/recovery/minui/graphics.cpp b/recovery/minui/graphics.cpp new file mode 100644 index 0000000..c0eea9e --- /dev/null +++ b/recovery/minui/graphics.cpp
@@ -0,0 +1,411 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <fcntl.h> +#include <stdio.h> + +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/types.h> + +#include <linux/fb.h> +#include <linux/kd.h> + +#include <time.h> + +#include "font_10x18.h" +#include "minui.h" +#include "graphics.h" + +struct GRFont { + GRSurface* texture; + int cwidth; + int cheight; +}; + +static GRFont* gr_font = NULL; +static minui_backend* gr_backend = NULL; + +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 GRSurface* gr_draw = NULL; + +static bool outside(int x, int y) +{ + return x < 0 || x >= gr_draw->width || y < 0 || y >= gr_draw->height; +} + +int gr_measure(const char *s) +{ + return gr_font->cwidth * strlen(s); +} + +void gr_font_size(int *x, int *y) +{ + *x = gr_font->cwidth; + *y = gr_font->cheight; +} + +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; + } +} + +void gr_text(int x, int y, const char *s, bool bold) +{ + GRFont* font = gr_font; + + if (!font->texture || gr_current_a == 0) return; + + bold = bold && (font->texture->height != font->cheight); + + x += overscan_offset_x; + y += overscan_offset_y; + + unsigned char ch; + while ((ch = *s++)) { + if (outside(x, y) || outside(x+font->cwidth-1, y+font->cheight-1)) break; + + if (ch < ' ' || ch > '~') { + ch = '?'; + } + + unsigned char* src_p = font->texture->data + ((ch - ' ') * font->cwidth) + + (bold ? font->cheight * font->texture->row_bytes : 0); + unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes; + + text_blend(src_p, font->texture->row_bytes, + dst_p, gr_draw->row_bytes, + font->cwidth, font->cheight); + + x += font->cwidth; + } +} + +void gr_texticon(int x, int y, GRSurface* icon) { + if (icon == NULL) return; + + if (icon->pixel_bytes != 1) { + printf("gr_texticon: source has wrong format\n"); + return; + } + + x += overscan_offset_x; + y += overscan_offset_y; + + 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; + + text_blend(src_p, icon->row_bytes, + dst_p, gr_draw->row_bytes, + icon->width, icon->height); +} + +void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ +#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) + gr_current_r = b; + gr_current_g = g; + gr_current_b = r; + gr_current_a = a; +#else + gr_current_r = r; + gr_current_g = g; + gr_current_b = b; + gr_current_a = a; +#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_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_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 (outside(dx, dy) || outside(dx+w-1, dy+h-1)) return; + + unsigned char* src_p = source->data + sy*source->row_bytes + sx*source->pixel_bytes; + unsigned char* dst_p = gr_draw->data + dy*gr_draw->row_bytes + dx*gr_draw->pixel_bytes; + + 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; + } +} + +unsigned int gr_get_width(GRSurface* surface) { + if (surface == NULL) { + return 0; + } + return surface->width; +} + +unsigned int gr_get_height(GRSurface* surface) { + if (surface == NULL) { + return 0; + } + return surface->height; +} + +static void gr_init_font(void) +{ + gr_font = reinterpret_cast<GRFont*>(calloc(sizeof(*gr_font), 1)); + + int res = res_create_alpha_surface("font", &(gr_font->texture)); + if (res == 0) { + // 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. + gr_font->cwidth = gr_font->texture->width / 96; + gr_font->cheight = gr_font->texture->height / 2; + } else { + printf("failed to read font: res=%d\n", res); + + // fall back to the compiled-in font. + gr_font->texture = reinterpret_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 = reinterpret_cast<unsigned char*>(malloc(font.width * font.height)); + gr_font->texture->data = reinterpret_cast<unsigned char*>(bits); + + unsigned char data; + unsigned char* in = font.rundata; + while((data = *in++)) { + memset(bits, (data & 0x80) ? 255 : 0, data & 0x7f); + bits += (data & 0x7f); + } + + gr_font->cwidth = font.cwidth; + gr_font->cheight = font.cheight; + } +} + +#if 0 +// Exercises many of the gr_*() functions; useful for testing. +static void gr_test() { + GRSurface** images; + int frames; + int result = res_create_multi_surface("icon_installing", &frames, &images); + if (result < 0) { + printf("create surface %d\n", result); + gr_exit(); + return; + } + + time_t start = time(NULL); + int x; + for (x = 0; x <= 1200; ++x) { + if (x < 400) { + gr_color(0, 0, 0, 255); + } else { + gr_color(0, (x-400)%128, 0, 255); + } + gr_clear(); + + gr_color(255, 0, 0, 255); + GRSurface* frame = images[x%frames]; + gr_blit(frame, 0, 0, frame->width, frame->height, x, 0); + + gr_color(255, 0, 0, 128); + gr_fill(400, 150, 600, 350); + + gr_color(255, 255, 255, 255); + gr_text(500, 225, "hello, world!", 0); + gr_color(255, 255, 0, 128); + gr_text(300+x, 275, "pack my box with five dozen liquor jugs", 1); + + gr_color(0, 0, 255, 128); + gr_fill(gr_draw->width - 200 - x, 300, gr_draw->width - x, 500); + + gr_draw = gr_backend->flip(gr_backend); + } + printf("getting end time\n"); + time_t end = time(NULL); + printf("got end time\n"); + printf("start %ld end %ld\n", (long)start, (long)end); + if (end > start) { + printf("%.2f fps\n", ((double)x) / (end-start)); + } +} +#endif + +void gr_flip() { + gr_draw = gr_backend->flip(gr_backend); +} + +int gr_init(void) +{ + gr_init_font(); + + gr_backend = open_adf(); + if (gr_backend) { + gr_draw = gr_backend->init(gr_backend); + if (!gr_draw) { + gr_backend->exit(gr_backend); + } + } + + if (!gr_draw) { + gr_backend = open_drm(); + gr_draw = gr_backend->init(gr_backend); + } + + if (!gr_draw) { + gr_backend = open_fbdev(); + gr_draw = gr_backend->init(gr_backend); + if (gr_draw == NULL) { + return -1; + } + } + + overscan_offset_x = gr_draw->width * overscan_percent / 100; + overscan_offset_y = gr_draw->height * overscan_percent / 100; + + gr_flip(); + gr_flip(); + + return 0; +} + +void gr_exit(void) +{ + gr_backend->exit(gr_backend); +} + +int gr_fb_width(void) +{ + return gr_draw->width - 2*overscan_offset_x; +} + +int gr_fb_height(void) +{ + return gr_draw->height - 2*overscan_offset_y; +} + +void gr_fb_blank(bool blank) +{ + gr_backend->blank(gr_backend, blank); +}
diff --git a/recovery/minui/graphics.h b/recovery/minui/graphics.h new file mode 100644 index 0000000..52968eb --- /dev/null +++ b/recovery/minui/graphics.h
@@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _GRAPHICS_H_ +#define _GRAPHICS_H_ + +#include "minui.h" + +// TODO: lose the function pointers. +struct minui_backend { + // Initializes the backend and returns a GRSurface* to draw into. + GRSurface* (*init)(minui_backend*); + + // Causes the current drawing surface (returned by the most recent + // call to flip() or init()) to be displayed, and returns a new + // drawing surface. + GRSurface* (*flip)(minui_backend*); + + // Blank (or unblank) the screen. + void (*blank)(minui_backend*, bool); + + // Device cleanup when drawing is done. + void (*exit)(minui_backend*); +}; + +minui_backend* open_fbdev(); +minui_backend* open_adf(); +minui_backend* open_drm(); + +#endif
diff --git a/recovery/minui/graphics_adf.cpp b/recovery/minui/graphics_adf.cpp new file mode 100644 index 0000000..3c35410 --- /dev/null +++ b/recovery/minui/graphics_adf.cpp
@@ -0,0 +1,273 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <sys/cdefs.h> +#include <sys/mman.h> + +#include <adf/adf.h> +#include <sync/sync.h> + +#include "graphics.h" + +struct adf_surface_pdata { + GRSurface base; + int fence_fd; + int fd; + __u32 offset; + __u32 pitch; +}; + +struct adf_pdata { + minui_backend base; + int intf_fd; + adf_id_t eng_id; + __u32 format; + + adf_device dev; + + unsigned int current_surface; + unsigned int n_surfaces; + adf_surface_pdata surfaces[2]; +}; + +static GRSurface* adf_flip(minui_backend *backend); +static void adf_blank(minui_backend *backend, bool blank); + +static int adf_surface_init(adf_pdata *pdata, drm_mode_modeinfo *mode, adf_surface_pdata *surf) { + memset(surf, 0, sizeof(*surf)); + + surf->fence_fd = -1; + surf->fd = adf_interface_simple_buffer_alloc(pdata->intf_fd, mode->hdisplay, + mode->vdisplay, pdata->format, &surf->offset, &surf->pitch); + if (surf->fd < 0) + return surf->fd; + + surf->base.width = mode->hdisplay; + surf->base.height = mode->vdisplay; + surf->base.row_bytes = surf->pitch; + surf->base.pixel_bytes = (pdata->format == DRM_FORMAT_RGB565) ? 2 : 4; + + surf->base.data = reinterpret_cast<uint8_t*>(mmap(NULL, + surf->pitch * surf->base.height, PROT_WRITE, + MAP_SHARED, surf->fd, surf->offset)); + if (surf->base.data == MAP_FAILED) { + close(surf->fd); + return -errno; + } + + return 0; +} + +static int adf_interface_init(adf_pdata *pdata) +{ + adf_interface_data intf_data; + int ret = 0; + int err; + + err = adf_get_interface_data(pdata->intf_fd, &intf_data); + if (err < 0) + return err; + + err = adf_surface_init(pdata, &intf_data.current_mode, &pdata->surfaces[0]); + if (err < 0) { + fprintf(stderr, "allocating surface 0 failed: %s\n", strerror(-err)); + ret = err; + goto done; + } + + err = adf_surface_init(pdata, &intf_data.current_mode, + &pdata->surfaces[1]); + if (err < 0) { + fprintf(stderr, "allocating surface 1 failed: %s\n", strerror(-err)); + memset(&pdata->surfaces[1], 0, sizeof(pdata->surfaces[1])); + pdata->n_surfaces = 1; + } else { + pdata->n_surfaces = 2; + } + +done: + adf_free_interface_data(&intf_data); + return ret; +} + +static int adf_device_init(adf_pdata *pdata, adf_device *dev) +{ + adf_id_t intf_id; + int intf_fd; + int err; + + err = adf_find_simple_post_configuration(dev, &pdata->format, 1, &intf_id, + &pdata->eng_id); + if (err < 0) + return err; + + err = adf_device_attach(dev, pdata->eng_id, intf_id); + if (err < 0 && err != -EALREADY) + return err; + + pdata->intf_fd = adf_interface_open(dev, intf_id, O_RDWR); + if (pdata->intf_fd < 0) + return pdata->intf_fd; + + err = adf_interface_init(pdata); + if (err < 0) { + close(pdata->intf_fd); + pdata->intf_fd = -1; + } + + return err; +} + +static GRSurface* adf_init(minui_backend *backend) +{ + adf_pdata *pdata = (adf_pdata *)backend; + adf_id_t *dev_ids = NULL; + ssize_t n_dev_ids, i; + GRSurface* ret; + +#if defined(RECOVERY_ABGR) + pdata->format = DRM_FORMAT_ABGR8888; +#elif defined(RECOVERY_BGRA) + pdata->format = DRM_FORMAT_BGRA8888; +#elif defined(RECOVERY_RGBX) + pdata->format = DRM_FORMAT_RGBX8888; +#else + pdata->format = DRM_FORMAT_RGB565; +#endif + + n_dev_ids = adf_devices(&dev_ids); + if (n_dev_ids == 0) { + return NULL; + } else if (n_dev_ids < 0) { + fprintf(stderr, "enumerating adf devices failed: %s\n", + strerror(-n_dev_ids)); + return NULL; + } + + pdata->intf_fd = -1; + + for (i = 0; i < n_dev_ids && pdata->intf_fd < 0; i++) { + + int err = adf_device_open(dev_ids[i], O_RDWR, &pdata->dev); + if (err < 0) { + fprintf(stderr, "opening adf device %u failed: %s\n", dev_ids[i], + strerror(-err)); + continue; + } + + err = adf_device_init(pdata, &pdata->dev); + if (err < 0) { + fprintf(stderr, "initializing adf device %u failed: %s\n", + dev_ids[i], strerror(-err)); + adf_device_close(&pdata->dev); + } + } + + free(dev_ids); + + if (pdata->intf_fd < 0) + return NULL; + + ret = adf_flip(backend); + + adf_blank(backend, true); + adf_blank(backend, false); + + return ret; +} + +static void adf_sync(adf_surface_pdata *surf) +{ + unsigned int warningTimeout = 3000; + + if (surf == NULL) + return; + + if (surf->fence_fd >= 0){ + int err = sync_wait(surf->fence_fd, warningTimeout); + if (err < 0) + perror("adf sync fence wait error\n"); + + close(surf->fence_fd); + surf->fence_fd = -1; + } +} + +static GRSurface* adf_flip(minui_backend *backend) +{ + adf_pdata *pdata = (adf_pdata *)backend; + adf_surface_pdata *surf = &pdata->surfaces[pdata->current_surface]; + + int fence_fd = adf_interface_simple_post(pdata->intf_fd, pdata->eng_id, + surf->base.width, surf->base.height, pdata->format, surf->fd, + surf->offset, surf->pitch, -1); + if (fence_fd >= 0) + surf->fence_fd = fence_fd; + + pdata->current_surface = (pdata->current_surface + 1) % pdata->n_surfaces; + adf_sync(&pdata->surfaces[pdata->current_surface]); + return &pdata->surfaces[pdata->current_surface].base; +} + +static void adf_blank(minui_backend *backend, bool blank) +{ + adf_pdata *pdata = (adf_pdata *)backend; + adf_interface_blank(pdata->intf_fd, + blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON); +} + +static void adf_surface_destroy(adf_surface_pdata *surf) +{ + munmap(surf->base.data, surf->pitch * surf->base.height); + close(surf->fence_fd); + close(surf->fd); +} + +static void adf_exit(minui_backend *backend) +{ + adf_pdata *pdata = (adf_pdata *)backend; + unsigned int i; + + adf_device_close(&pdata->dev); + for (i = 0; i < pdata->n_surfaces; i++) + adf_surface_destroy(&pdata->surfaces[i]); + if (pdata->intf_fd >= 0) + close(pdata->intf_fd); + free(pdata); +} + +minui_backend *open_adf() +{ + adf_pdata* pdata = reinterpret_cast<adf_pdata*>(calloc(1, sizeof(*pdata))); + if (!pdata) { + perror("allocating adf backend failed"); + return NULL; + } + + pdata->base.init = adf_init; + pdata->base.flip = adf_flip; + pdata->base.blank = adf_blank; + pdata->base.exit = adf_exit; + return &pdata->base; +}
diff --git a/recovery/minui/graphics_drm.cpp b/recovery/minui/graphics_drm.cpp new file mode 100644 index 0000000..03e33b7 --- /dev/null +++ b/recovery/minui/graphics_drm.cpp
@@ -0,0 +1,476 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <drm_fourcc.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/cdefs.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <unistd.h> +#include <xf86drm.h> +#include <xf86drmMode.h> + +#include "minui.h" +#include "graphics.h" + +#define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A))) + +struct drm_surface { + GRSurface base; + uint32_t fb_id; + uint32_t handle; +}; + +static drm_surface *drm_surfaces[2]; +static int current_buffer; + +static drmModeCrtc *main_monitor_crtc; +static drmModeConnector *main_monitor_connector; + +static int drm_fd = -1; + +static void drm_disable_crtc(int drm_fd, drmModeCrtc *crtc) { + if (crtc) { + drmModeSetCrtc(drm_fd, crtc->crtc_id, + 0, // fb_id + 0, 0, // x,y + NULL, // connectors + 0, // connector_count + NULL); // mode + } +} + +static void drm_enable_crtc(int drm_fd, drmModeCrtc *crtc, + struct drm_surface *surface) { + int32_t ret; + + ret = drmModeSetCrtc(drm_fd, crtc->crtc_id, + surface->fb_id, + 0, 0, // x,y + &main_monitor_connector->connector_id, + 1, // connector_count + &main_monitor_crtc->mode); + + if (ret) + printf("drmModeSetCrtc failed ret=%d\n", ret); +} + +static void drm_blank(minui_backend* backend __unused, bool blank) { + if (blank) + drm_disable_crtc(drm_fd, main_monitor_crtc); + else + drm_enable_crtc(drm_fd, main_monitor_crtc, + drm_surfaces[current_buffer]); +} + +static void drm_destroy_surface(struct drm_surface *surface) { + struct drm_gem_close gem_close; + int ret; + + if(!surface) + return; + + if (surface->base.data) + munmap(surface->base.data, + surface->base.row_bytes * surface->base.height); + + if (surface->fb_id) { + ret = drmModeRmFB(drm_fd, surface->fb_id); + if (ret) + printf("drmModeRmFB failed ret=%d\n", ret); + } + + if (surface->handle) { + memset(&gem_close, 0, sizeof(gem_close)); + gem_close.handle = surface->handle; + + ret = drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close); + if (ret) + printf("DRM_IOCTL_GEM_CLOSE failed ret=%d\n", ret); + } + + free(surface); +} + +static int drm_format_to_bpp(uint32_t format) { + switch(format) { + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_XRGB8888: + return 32; + case DRM_FORMAT_RGB565: + return 16; + default: + printf("Unknown format %d\n", format); + return 32; + } +} + +static drm_surface *drm_create_surface(int width, int height) { + struct drm_surface *surface; + struct drm_mode_create_dumb create_dumb; + uint32_t format; + int ret; + + surface = (struct drm_surface*)calloc(1, sizeof(*surface)); + if (!surface) { + printf("Can't allocate memory\n"); + return NULL; + } + +#if defined(RECOVERY_ABGR) + format = DRM_FORMAT_RGBA8888; +#elif defined(RECOVERY_BGRA) + format = DRM_FORMAT_ARGB8888; +#elif defined(RECOVERY_RGBX) + format = DRM_FORMAT_XBGR8888; +#else + format = DRM_FORMAT_RGB565; +#endif + + memset(&create_dumb, 0, sizeof(create_dumb)); + create_dumb.height = height; + create_dumb.width = width; + create_dumb.bpp = drm_format_to_bpp(format); + create_dumb.flags = 0; + + ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb); + if (ret) { + printf("DRM_IOCTL_MODE_CREATE_DUMB failed ret=%d\n",ret); + drm_destroy_surface(surface); + return NULL; + } + surface->handle = create_dumb.handle; + + uint32_t handles[4], pitches[4], offsets[4]; + + handles[0] = surface->handle; + pitches[0] = create_dumb.pitch; + offsets[0] = 0; + + ret = drmModeAddFB2(drm_fd, width, height, + format, handles, pitches, offsets, + &(surface->fb_id), 0); + if (ret) { + printf("drmModeAddFB2 failed ret=%d\n", ret); + drm_destroy_surface(surface); + return NULL; + } + + struct drm_mode_map_dumb map_dumb; + memset(&map_dumb, 0, sizeof(map_dumb)); + map_dumb.handle = create_dumb.handle; + ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb); + if (ret) { + printf("DRM_IOCTL_MODE_MAP_DUMB failed ret=%d\n",ret); + drm_destroy_surface(surface); + return NULL;; + } + + surface->base.height = height; + surface->base.width = width; + surface->base.row_bytes = create_dumb.pitch; + surface->base.pixel_bytes = create_dumb.bpp / 8; + surface->base.data = (unsigned char*) + mmap(NULL, + surface->base.height * surface->base.row_bytes, + PROT_READ | PROT_WRITE, MAP_SHARED, + drm_fd, map_dumb.offset); + if (surface->base.data == MAP_FAILED) { + perror("mmap() failed"); + drm_destroy_surface(surface); + return NULL; + } + + return surface; +} + +static drmModeCrtc *find_crtc_for_connector(int fd, + drmModeRes *resources, + drmModeConnector *connector) { + int i, j; + drmModeEncoder *encoder; + int32_t crtc; + + /* + * Find the encoder. If we already have one, just use it. + */ + if (connector->encoder_id) + encoder = drmModeGetEncoder(fd, connector->encoder_id); + else + encoder = NULL; + + if (encoder && encoder->crtc_id) { + crtc = encoder->crtc_id; + drmModeFreeEncoder(encoder); + return drmModeGetCrtc(fd, crtc); + } + + /* + * Didn't find anything, try to find a crtc and encoder combo. + */ + crtc = -1; + for (i = 0; i < connector->count_encoders; i++) { + encoder = drmModeGetEncoder(fd, connector->encoders[i]); + + if (encoder) { + for (j = 0; j < resources->count_crtcs; j++) { + if (!(encoder->possible_crtcs & (1 << j))) + continue; + crtc = resources->crtcs[j]; + break; + } + if (crtc >= 0) { + drmModeFreeEncoder(encoder); + return drmModeGetCrtc(fd, crtc); + } + } + } + + return NULL; +} + +static drmModeConnector *find_used_connector_by_type(int fd, + drmModeRes *resources, + unsigned type) { + int i; + for (i = 0; i < resources->count_connectors; i++) { + drmModeConnector *connector; + + connector = drmModeGetConnector(fd, resources->connectors[i]); + if (connector) { + if ((connector->connector_type == type) && + (connector->connection == DRM_MODE_CONNECTED) && + (connector->count_modes > 0)) + return connector; + + drmModeFreeConnector(connector); + } + } + return NULL; +} + +static drmModeConnector *find_first_connected_connector(int fd, + drmModeRes *resources) { + int i; + for (i = 0; i < resources->count_connectors; i++) { + drmModeConnector *connector; + + connector = drmModeGetConnector(fd, resources->connectors[i]); + if (connector) { + if ((connector->count_modes > 0) && + (connector->connection == DRM_MODE_CONNECTED)) + return connector; + + drmModeFreeConnector(connector); + } + } + return NULL; +} + +static drmModeConnector *find_main_monitor(int fd, drmModeRes *resources, + uint32_t *mode_index) { + unsigned i = 0; + int modes; + /* Look for LVDS/eDP/DSI connectors. Those are the main screens. */ + unsigned kConnectorPriority[] = { + DRM_MODE_CONNECTOR_LVDS, + DRM_MODE_CONNECTOR_eDP, + DRM_MODE_CONNECTOR_DSI, + }; + + drmModeConnector *main_monitor_connector = NULL; + do { + main_monitor_connector = find_used_connector_by_type(fd, + resources, + kConnectorPriority[i]); + i++; + } while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority)); + + /* If we didn't find a connector, grab the first one that is connected. */ + if (!main_monitor_connector) + main_monitor_connector = + find_first_connected_connector(fd, resources); + + /* If we still didn't find a connector, give up and return. */ + if (!main_monitor_connector) + return NULL; + + *mode_index = 0; + for (modes = 0; modes < main_monitor_connector->count_modes; modes++) { + if (main_monitor_connector->modes[modes].type & + DRM_MODE_TYPE_PREFERRED) { + *mode_index = modes; + break; + } + } + + return main_monitor_connector; +} + +static void disable_non_main_crtcs(int fd, + drmModeRes *resources, + drmModeCrtc* main_crtc) { + int i; + drmModeCrtc* crtc; + + for (i = 0; i < resources->count_connectors; i++) { + drmModeConnector *connector; + + connector = drmModeGetConnector(fd, resources->connectors[i]); + crtc = find_crtc_for_connector(fd, resources, connector); + if (crtc->crtc_id != main_crtc->crtc_id) + drm_disable_crtc(fd, crtc); + drmModeFreeCrtc(crtc); + } +} + +static GRSurface* drm_init(minui_backend* backend __unused) { + drmModeRes *res = NULL; + uint32_t selected_mode; + char *dev_name; + int width, height; + int ret, i; + + /* Consider DRM devices in order. */ + for (i = 0; i < DRM_MAX_MINOR; i++) { + uint64_t cap = 0; + + ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i); + if (ret < 0) + continue; + + drm_fd = open(dev_name, O_RDWR, 0); + free(dev_name); + if (drm_fd < 0) + continue; + + /* We need dumb buffers. */ + ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &cap); + if (ret || cap == 0) { + close(drm_fd); + continue; + } + + res = drmModeGetResources(drm_fd); + if (!res) { + close(drm_fd); + continue; + } + + /* Use this device if it has at least one connected monitor. */ + if (res->count_crtcs > 0 && res->count_connectors > 0) + if (find_first_connected_connector(drm_fd, res)) + break; + + drmModeFreeResources(res); + close(drm_fd); + res = NULL; + } + + if (drm_fd < 0 || res == NULL) { + perror("cannot find/open a drm device"); + return NULL; + } + + main_monitor_connector = find_main_monitor(drm_fd, + res, &selected_mode); + + if (!main_monitor_connector) { + printf("main_monitor_connector not found\n"); + drmModeFreeResources(res); + close(drm_fd); + return NULL; + } + + main_monitor_crtc = find_crtc_for_connector(drm_fd, res, + main_monitor_connector); + + if (!main_monitor_crtc) { + printf("main_monitor_crtc not found\n"); + drmModeFreeResources(res); + close(drm_fd); + return NULL; + } + + disable_non_main_crtcs(drm_fd, + res, main_monitor_crtc); + + main_monitor_crtc->mode = main_monitor_connector->modes[selected_mode]; + + width = main_monitor_crtc->mode.hdisplay; + height = main_monitor_crtc->mode.vdisplay; + + drmModeFreeResources(res); + + drm_surfaces[0] = drm_create_surface(width, height); + drm_surfaces[1] = drm_create_surface(width, height); + if (!drm_surfaces[0] || !drm_surfaces[1]) { + drm_destroy_surface(drm_surfaces[0]); + drm_destroy_surface(drm_surfaces[1]); + drmModeFreeResources(res); + close(drm_fd); + return NULL; + } + + current_buffer = 0; + + drm_enable_crtc(drm_fd, main_monitor_crtc, drm_surfaces[1]); + + return &(drm_surfaces[0]->base); +} + +static GRSurface* drm_flip(minui_backend* backend __unused) { + int ret; + + ret = drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id, + drm_surfaces[current_buffer]->fb_id, 0, NULL); + if (ret < 0) { + printf("drmModePageFlip failed ret=%d\n", ret); + return NULL; + } + current_buffer = 1 - current_buffer; + return &(drm_surfaces[current_buffer]->base); +} + +static void drm_exit(minui_backend* backend __unused) { + drm_disable_crtc(drm_fd, main_monitor_crtc); + drm_destroy_surface(drm_surfaces[0]); + drm_destroy_surface(drm_surfaces[1]); + drmModeFreeCrtc(main_monitor_crtc); + drmModeFreeConnector(main_monitor_connector); + close(drm_fd); + drm_fd = -1; +} + +static minui_backend drm_backend = { + .init = drm_init, + .flip = drm_flip, + .blank = drm_blank, + .exit = drm_exit, +}; + +minui_backend* open_drm() { + return &drm_backend; +}
diff --git a/recovery/minui/graphics_fbdev.cpp b/recovery/minui/graphics_fbdev.cpp new file mode 100644 index 0000000..0788f75 --- /dev/null +++ b/recovery/minui/graphics_fbdev.cpp
@@ -0,0 +1,201 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <fcntl.h> +#include <stdio.h> + +#include <sys/cdefs.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/types.h> + +#include <linux/fb.h> +#include <linux/kd.h> + +#include "minui.h" +#include "graphics.h" + +static GRSurface* fbdev_init(minui_backend*); +static GRSurface* fbdev_flip(minui_backend*); +static void fbdev_blank(minui_backend*, bool); +static void fbdev_exit(minui_backend*); + +static GRSurface gr_framebuffer[2]; +static bool double_buffered; +static GRSurface* gr_draw = NULL; +static int displayed_buffer; + +static fb_var_screeninfo vi; +static int fb_fd = -1; + +static minui_backend my_backend = { + .init = fbdev_init, + .flip = fbdev_flip, + .blank = fbdev_blank, + .exit = fbdev_exit, +}; + +minui_backend* open_fbdev() { + return &my_backend; +} + +static void fbdev_blank(minui_backend* backend __unused, bool blank) +{ + int ret; + + ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK); + if (ret < 0) + perror("ioctl(): blank"); +} + +static void set_displayed_framebuffer(unsigned n) +{ + if (n > 1 || !double_buffered) return; + + vi.yres_virtual = gr_framebuffer[0].height * 2; + vi.yoffset = n * gr_framebuffer[0].height; + vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8; + if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) { + perror("active fb swap failed"); + } + displayed_buffer = n; +} + +static GRSurface* fbdev_init(minui_backend* backend) { + int fd = open("/dev/graphics/fb0", O_RDWR); + if (fd == -1) { + perror("cannot open fb0"); + return NULL; + } + + fb_fix_screeninfo fi; + if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) { + perror("failed to get fb0 info"); + close(fd); + return NULL; + } + + if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) { + perror("failed to get fb0 info"); + close(fd); + return NULL; + } + + // We print this out for informational purposes only, but + // throughout we assume that the framebuffer device uses an RGBX + // pixel format. This is the case for every development device I + // have access to. For some of those devices (eg, hammerhead aka + // Nexus 5), FBIOGET_VSCREENINFO *reports* that it wants a + // different format (XBGR) but actually produces the correct + // results on the display when you write RGBX. + // + // If you have a device that actually *needs* another pixel format + // (ie, BGRX, or 565), patches welcome... + + printf("fb0 reports (possibly inaccurate):\n" + " vi.bits_per_pixel = %d\n" + " vi.red.offset = %3d .length = %3d\n" + " vi.green.offset = %3d .length = %3d\n" + " vi.blue.offset = %3d .length = %3d\n", + vi.bits_per_pixel, + vi.red.offset, vi.red.length, + vi.green.offset, vi.green.length, + vi.blue.offset, vi.blue.length); + + void* bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (bits == MAP_FAILED) { + perror("failed to mmap framebuffer"); + close(fd); + return NULL; + } + + memset(bits, 0, fi.smem_len); + + gr_framebuffer[0].width = vi.xres; + gr_framebuffer[0].height = vi.yres; + gr_framebuffer[0].row_bytes = fi.line_length; + gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8; + gr_framebuffer[0].data = reinterpret_cast<uint8_t*>(bits); + memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes); + + /* check if we can use double buffering */ + if (vi.yres * fi.line_length * 2 <= fi.smem_len) { + double_buffered = true; + + memcpy(gr_framebuffer+1, gr_framebuffer, sizeof(GRSurface)); + gr_framebuffer[1].data = gr_framebuffer[0].data + + gr_framebuffer[0].height * gr_framebuffer[0].row_bytes; + + gr_draw = gr_framebuffer+1; + + } else { + double_buffered = false; + + // Without double-buffering, we allocate RAM for a buffer to + // draw in, and then "flipping" the buffer consists of a + // memcpy from the buffer we allocated to the framebuffer. + + gr_draw = (GRSurface*) malloc(sizeof(GRSurface)); + memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface)); + gr_draw->data = (unsigned char*) malloc(gr_draw->height * gr_draw->row_bytes); + if (!gr_draw->data) { + perror("failed to allocate in-memory surface"); + return NULL; + } + } + + memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes); + fb_fd = fd; + set_displayed_framebuffer(0); + + printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height); + + fbdev_blank(backend, true); + fbdev_blank(backend, false); + + return gr_draw; +} + +static GRSurface* fbdev_flip(minui_backend* backend __unused) { + if (double_buffered) { + // Change gr_draw to point to the buffer currently displayed, + // then flip the driver so we're displaying the other buffer + // instead. + gr_draw = gr_framebuffer + displayed_buffer; + set_displayed_framebuffer(1-displayed_buffer); + } else { + // Copy from the in-memory surface to the framebuffer. + memcpy(gr_framebuffer[0].data, gr_draw->data, + gr_draw->height * gr_draw->row_bytes); + } + return gr_draw; +} + +static void fbdev_exit(minui_backend* backend __unused) { + close(fb_fd); + fb_fd = -1; + + if (!double_buffered && gr_draw) { + free(gr_draw->data); + free(gr_draw); + } + gr_draw = NULL; +}
diff --git a/recovery/minui/minui.h b/recovery/minui/minui.h new file mode 100644 index 0000000..d572205 --- /dev/null +++ b/recovery/minui/minui.h
@@ -0,0 +1,123 @@ +/* + * 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 _MINUI_H_ +#define _MINUI_H_ + +#include <sys/types.h> + +#include <functional> + +// +// Graphics. +// + +struct GRSurface { + int width; + int height; + int row_bytes; + int pixel_bytes; + unsigned char* data; +}; + +int gr_init(); +void gr_exit(); + +int gr_fb_width(); +int gr_fb_height(); + +void gr_flip(); +void gr_fb_blank(bool blank); + +void gr_clear(); // clear entire surface to current color +void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a); +void gr_fill(int x1, int y1, int x2, int y2); +void gr_text(int x, int y, const char *s, bool bold); +void gr_texticon(int x, int y, GRSurface* icon); +int gr_measure(const char *s); +void gr_font_size(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); + +// +// Input events. +// + +struct input_event; + +// TODO: move these over to std::function. +typedef int (*ev_callback)(int fd, uint32_t epevents, void* data); +typedef int (*ev_set_key_callback)(int code, int value, void* data); + +int ev_init(ev_callback input_cb, void* data); +void ev_exit(); +int ev_add_fd(int fd, ev_callback cb, void* data); +void ev_iterate_available_keys(const std::function<void(int)>& f); +int ev_sync_key_state(ev_set_key_callback set_key_cb, void* data); + +// 'timeout' has the same semantics as poll(2). +// 0 : don't block +// < 0 : block forever +// > 0 : block for 'timeout' milliseconds +int ev_wait(int timeout); + +int ev_get_input(int fd, uint32_t epevents, input_event* ev); +void ev_dispatch(); +int ev_get_epollfd(); + +// +// Resources +// + +// res_create_*_surface() functions return 0 if no error, else +// negative. +// +// A "display" surface is one that is intended to be drawn to the +// screen with gr_blit(). An "alpha" surface is a grayscale image +// interpreted as an alpha mask used to render text in the current +// color (with gr_text() or gr_texticon()). +// +// All these functions load PNG images from "/res/images/${name}.png". + +// Load a single display surface from a PNG image. +int res_create_display_surface(const char* name, GRSurface** pSurface); + +// Load an array of display surfaces from a single PNG image. The PNG +// 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); + +// Load a single alpha surface from a grayscale PNG image. +int res_create_alpha_surface(const char* name, GRSurface** pSurface); + +// Load part of a grayscale PNG image that is the first match for the +// given locale. The image is expected to be a composite of multiple +// translations of the same text, with special added rows that encode +// the subimages' size and intended locale in the pixel data. See +// bootable/recovery/tools/recovery_l10n for an app that will generate +// these specialized images from Android resources. +int res_create_localized_alpha_surface(const char* name, const char* locale, + GRSurface** pSurface); + +// Free a surface allocated by any of the res_create_*_surface() +// functions. +void res_free_surface(GRSurface* surface); + +#endif
diff --git a/recovery/minui/mkfont.c b/recovery/minui/mkfont.c new file mode 100644 index 0000000..61a5ede --- /dev/null +++ b/recovery/minui/mkfont.c
@@ -0,0 +1,54 @@ +#include <stdio.h> +#include <stdlib.h> + +int main(int argc, char *argv) +{ + unsigned n; + unsigned char *x; + unsigned m; + unsigned run_val; + unsigned run_count; + + n = gimp_image.width * gimp_image.height; + m = 0; + x = gimp_image.pixel_data; + + printf("struct {\n"); + printf(" unsigned width;\n"); + printf(" unsigned height;\n"); + printf(" unsigned cwidth;\n"); + printf(" unsigned cheight;\n"); + printf(" unsigned char rundata[];\n"); + printf("} font = {\n"); + printf(" .width = %d,\n .height = %d,\n .cwidth = %d,\n .cheight = %d,\n", gimp_image.width, gimp_image.height, + gimp_image.width / 96, gimp_image.height); + printf(" .rundata = {\n"); + + run_val = (*x ? 0 : 255); + run_count = 1; + n--; + x+=3; + + while(n-- > 0) { + unsigned val = (*x ? 0 : 255); + x+=3; + if((val == run_val) && (run_count < 127)) { + run_count++; + } else { +eject: + printf("0x%02x,",run_count | (run_val ? 0x80 : 0x00)); + run_val = val; + run_count = 1; + m += 5; + if(m >= 75) { + printf("\n"); + m = 0; + } + } + } + printf("0x%02x,",run_count | (run_val ? 0x80 : 0x00)); + printf("\n0x00,"); + printf("\n"); + printf(" }\n};\n"); + return 0; +}
diff --git a/recovery/minui/resources.cpp b/recovery/minui/resources.cpp new file mode 100644 index 0000000..8489d60 --- /dev/null +++ b/recovery/minui/resources.cpp
@@ -0,0 +1,464 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <fcntl.h> +#include <stdio.h> + +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/types.h> + +#include <linux/fb.h> +#include <linux/kd.h> + +#include <vector> +#include <png.h> + +#include "minui.h" + +extern char* locale; + +#define SURFACE_DATA_ALIGNMENT 8 + +static GRSurface* malloc_surface(size_t data_size) { + size_t size = sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT; + unsigned char* temp = reinterpret_cast<unsigned char*>(malloc(size)); + if (temp == NULL) return NULL; + GRSurface* surface = reinterpret_cast<GRSurface*>(temp); + surface->data = temp + sizeof(GRSurface) + + (SURFACE_DATA_ALIGNMENT - (sizeof(GRSurface) % SURFACE_DATA_ALIGNMENT)); + return surface; +} + +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, "rb"); + if (fp == NULL) { + result = -1; + goto exit; + } + + bytesRead = fread(header, 1, sizeof(header), fp); + if (bytesRead != sizeof(header)) { + result = -2; + goto exit; + } + + if (png_sig_cmp(header, 0, sizeof(header))) { + result = -3; + goto exit; + } + + *png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!*png_ptr) { + result = -4; + goto exit; + } + + *info_ptr = png_create_info_struct(*png_ptr); + if (!*info_ptr) { + result = -5; + goto exit; + } + + if (setjmp(png_jmpbuf(*png_ptr))) { + result = -6; + goto exit; + } + + 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; + } + + return result; + + exit: + if (result < 0) { + png_destroy_read_struct(png_ptr, info_ptr, NULL); + } + if (fp != NULL) { + fclose(fp); + } + + return result; +} + +// "display" surfaces are transformed into the framebuffer's required +// pixel format (currently only RGBX is supported) at load time, so +// gr_blit() can be nothing more than a memcpy() for each row. The +// next two functions are the only ones that know anything about the +// framebuffer pixel format; they need to be modified if the +// framebuffer format changes (but nothing else should). + +// Allocate and return a GRSurface* sufficient for storing an image of +// the indicated size in the framebuffer pixel format. +static GRSurface* init_display_surface(png_uint_32 width, png_uint_32 height) { + GRSurface* surface = malloc_surface(width * height * 4); + if (surface == NULL) return NULL; + + surface->width = width; + surface->height = height; + surface->row_bytes = width * 4; + surface->pixel_bytes = 4; + + return surface; +} + +// Copy 'input_row' to 'output_row', transforming it to the +// framebuffer pixel format. The input format depends on the value of +// 'channels': +// +// 1 - input is 8-bit grayscale +// 3 - input is 24-bit RGB +// 4 - input is 32-bit RGBA/RGBX +// +// 'width' is the number of pixels in the row. +static void transform_rgb_to_draw(unsigned char* input_row, + unsigned char* output_row, + int channels, int width) { + int x; + unsigned char* ip = input_row; + unsigned char* op = output_row; + + switch (channels) { + case 1: + // expand gray level to RGBX + for (x = 0; x < width; ++x) { + *op++ = *ip; + *op++ = *ip; + *op++ = *ip; + *op++ = 0xff; + ip++; + } + break; + + case 3: + // expand RGBA to RGBX + for (x = 0; x < width; ++x) { + *op++ = *ip++; + *op++ = *ip++; + *op++ = *ip++; + *op++ = 0xff; + } + break; + + case 4: + // copy RGBA to RGBX + memcpy(output_row, input_row, width*4); + break; + } +} + +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; + } + +#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) + png_set_bgr(png_ptr); +#endif + + p_row = reinterpret_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); + + *pSurface = surface; + + exit: + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + if (result < 0 && surface != NULL) free(surface); + return result; +} + +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); + } + + 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; + } + + surface = reinterpret_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; + } + } + +#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) + png_set_bgr(png_ptr); +#endif + + p_row = reinterpret_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); + + *pSurface = reinterpret_cast<GRSurface**>(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); + } + } + 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 = NULL; + + result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels); + if (result < 0) return result; + + if (channels != 1) { + result = -7; + goto exit; + } + + 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; + +#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) + 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); + } + + *pSurface = surface; + + exit: + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + if (result < 0 && surface != NULL) free(surface); + return result; +} + +static int matches_locale(const char* loc, const char* locale) { + if (locale == NULL) return 0; + + if (strcmp(loc, locale) == 0) return 1; + + // if loc does *not* have an underscore, and it matches the start + // of locale, and the next character in locale *is* an underscore, + // that's a match. For instance, loc == "en" matches locale == + // "en_US". + + int i; + for (i = 0; loc[i] != 0 && loc[i] != '_'; ++i); + if (loc[i] == '_') return 0; + + return (strncmp(locale, loc, i) == 0 && locale[i] == '_'); +} + +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; + } + + result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels); + if (result < 0) return result; + + if (channels != 1) { + result = -7; + goto exit; + } + + row.resize(width); + for (y = 0; y < height; ++y) { + png_read_row(png_ptr, row.data(), NULL); + int w = (row[1] << 8) | row[0]; + int h = (row[3] << 8) | row[2]; + __unused int len = row[4]; + char* loc = reinterpret_cast<char*>(&row[5]); + + if (y+1+h >= height || matches_locale(loc, locale)) { + printf(" %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y); + + surface = malloc_surface(w*h); + if (surface == NULL) { + result = -8; + goto exit; + } + surface->width = w; + surface->height = h; + surface->row_bytes = w; + surface->pixel_bytes = 1; + + int i; + for (i = 0; i < h; ++i, ++y) { + png_read_row(png_ptr, row.data(), NULL); + memcpy(surface->data + i*w, row.data(), w); + } + + *pSurface = reinterpret_cast<GRSurface*>(surface); + break; + } else { + int i; + for (i = 0; i < h; ++i, ++y) { + png_read_row(png_ptr, row.data(), NULL); + } + } + } + +exit: + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + if (result < 0 && surface != NULL) free(surface); + return result; +} + +void res_free_surface(GRSurface* surface) { + free(surface); +}
diff --git a/recovery/minzip/Android.mk b/recovery/minzip/Android.mk new file mode 100644 index 0000000..3d36fd6 --- /dev/null +++ b/recovery/minzip/Android.mk
@@ -0,0 +1,23 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + Hash.c \ + SysUtil.c \ + DirUtil.cpp \ + Inlines.c \ + Zip.c + +LOCAL_C_INCLUDES := \ + external/zlib \ + external/safe-iop/include + +LOCAL_STATIC_LIBRARIES := libselinux + +LOCAL_MODULE := libminzip + +LOCAL_CLANG := true + +LOCAL_CFLAGS += -Werror -Wall + +include $(BUILD_STATIC_LIBRARY)
diff --git a/recovery/minzip/Bits.h b/recovery/minzip/Bits.h new file mode 100644 index 0000000..f96e6c4 --- /dev/null +++ b/recovery/minzip/Bits.h
@@ -0,0 +1,357 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Some handy functions for manipulating bits and bytes. + */ +#ifndef _MINZIP_BITS +#define _MINZIP_BITS + +#include "inline_magic.h" + +#include <stdlib.h> +#include <string.h> + +/* + * Get 1 byte. (Included to make the code more legible.) + */ +INLINE unsigned char get1(unsigned const char* pSrc) +{ + return *pSrc; +} + +/* + * Get 2 big-endian bytes. + */ +INLINE unsigned short get2BE(unsigned char const* pSrc) +{ + unsigned short result; + + result = *pSrc++ << 8; + result |= *pSrc++; + + return result; +} + +/* + * Get 4 big-endian bytes. + */ +INLINE unsigned int get4BE(unsigned char const* pSrc) +{ + unsigned int result; + + result = *pSrc++ << 24; + result |= *pSrc++ << 16; + result |= *pSrc++ << 8; + result |= *pSrc++; + + return result; +} + +/* + * Get 8 big-endian bytes. + */ +INLINE unsigned long long get8BE(unsigned char const* pSrc) +{ + unsigned long long result; + + result = (unsigned long long) *pSrc++ << 56; + result |= (unsigned long long) *pSrc++ << 48; + result |= (unsigned long long) *pSrc++ << 40; + result |= (unsigned long long) *pSrc++ << 32; + result |= (unsigned long long) *pSrc++ << 24; + result |= (unsigned long long) *pSrc++ << 16; + result |= (unsigned long long) *pSrc++ << 8; + result |= (unsigned long long) *pSrc++; + + return result; +} + +/* + * Get 2 little-endian bytes. + */ +INLINE unsigned short get2LE(unsigned char const* pSrc) +{ + unsigned short result; + + result = *pSrc++; + result |= *pSrc++ << 8; + + return result; +} + +/* + * Get 4 little-endian bytes. + */ +INLINE unsigned int get4LE(unsigned char const* pSrc) +{ + unsigned int result; + + result = *pSrc++; + result |= *pSrc++ << 8; + result |= *pSrc++ << 16; + result |= *pSrc++ << 24; + + return result; +} + +/* + * Get 8 little-endian bytes. + */ +INLINE unsigned long long get8LE(unsigned char const* pSrc) +{ + unsigned long long result; + + result = (unsigned long long) *pSrc++; + result |= (unsigned long long) *pSrc++ << 8; + result |= (unsigned long long) *pSrc++ << 16; + result |= (unsigned long long) *pSrc++ << 24; + result |= (unsigned long long) *pSrc++ << 32; + result |= (unsigned long long) *pSrc++ << 40; + result |= (unsigned long long) *pSrc++ << 48; + result |= (unsigned long long) *pSrc++ << 56; + + return result; +} + +/* + * Grab 1 byte and advance the data pointer. + */ +INLINE unsigned char read1(unsigned const char** ppSrc) +{ + return *(*ppSrc)++; +} + +/* + * Grab 2 big-endian bytes and advance the data pointer. + */ +INLINE unsigned short read2BE(unsigned char const** ppSrc) +{ + unsigned short result; + + result = *(*ppSrc)++ << 8; + result |= *(*ppSrc)++; + + return result; +} + +/* + * Grab 4 big-endian bytes and advance the data pointer. + */ +INLINE unsigned int read4BE(unsigned char const** ppSrc) +{ + unsigned int result; + + result = *(*ppSrc)++ << 24; + result |= *(*ppSrc)++ << 16; + result |= *(*ppSrc)++ << 8; + result |= *(*ppSrc)++; + + return result; +} + +/* + * Get 8 big-endian bytes. + */ +INLINE unsigned long long read8BE(unsigned char const** ppSrc) +{ + unsigned long long result; + + result = (unsigned long long) *(*ppSrc)++ << 56; + result |= (unsigned long long) *(*ppSrc)++ << 48; + result |= (unsigned long long) *(*ppSrc)++ << 40; + result |= (unsigned long long) *(*ppSrc)++ << 32; + result |= (unsigned long long) *(*ppSrc)++ << 24; + result |= (unsigned long long) *(*ppSrc)++ << 16; + result |= (unsigned long long) *(*ppSrc)++ << 8; + result |= (unsigned long long) *(*ppSrc)++; + + return result; +} + +/* + * Grab 2 little-endian bytes and advance the data pointer. + */ +INLINE unsigned short read2LE(unsigned char const** ppSrc) +{ + unsigned short result; + + result = *(*ppSrc)++; + result |= *(*ppSrc)++ << 8; + + return result; +} + +/* + * Grab 4 little-endian bytes and advance the data pointer. + */ +INLINE unsigned int read4LE(unsigned char const** ppSrc) +{ + unsigned int result; + + result = *(*ppSrc)++; + result |= *(*ppSrc)++ << 8; + result |= *(*ppSrc)++ << 16; + result |= *(*ppSrc)++ << 24; + + return result; +} + +/* + * Get 8 little-endian bytes. + */ +INLINE unsigned long long read8LE(unsigned char const** ppSrc) +{ + unsigned long long result; + + result = (unsigned long long) *(*ppSrc)++; + result |= (unsigned long long) *(*ppSrc)++ << 8; + result |= (unsigned long long) *(*ppSrc)++ << 16; + result |= (unsigned long long) *(*ppSrc)++ << 24; + result |= (unsigned long long) *(*ppSrc)++ << 32; + result |= (unsigned long long) *(*ppSrc)++ << 40; + result |= (unsigned long long) *(*ppSrc)++ << 48; + result |= (unsigned long long) *(*ppSrc)++ << 56; + + return result; +} + +/* + * Skip over a UTF-8 string. + */ +INLINE void skipUtf8String(unsigned char const** ppSrc) +{ + unsigned int length = read4BE(ppSrc); + + (*ppSrc) += length; +} + +/* + * Read a UTF-8 string into a fixed-size buffer, and null-terminate it. + * + * Returns the length of the original string. + */ +INLINE int readUtf8String(unsigned char const** ppSrc, char* buf, size_t bufLen) +{ + unsigned int length = read4BE(ppSrc); + size_t copyLen = (length < bufLen) ? length : bufLen-1; + + memcpy(buf, *ppSrc, copyLen); + buf[copyLen] = '\0'; + + (*ppSrc) += length; + return length; +} + +/* + * Read a UTF-8 string into newly-allocated storage, and null-terminate it. + * + * Returns the string and its length. (The latter is probably unnecessary + * for the way we're using UTF8.) + */ +INLINE char* readNewUtf8String(unsigned char const** ppSrc, size_t* pLength) +{ + unsigned int length = read4BE(ppSrc); + char* buf; + + buf = (char*) malloc(length+1); + + memcpy(buf, *ppSrc, length); + buf[length] = '\0'; + + (*ppSrc) += length; + + *pLength = length; + return buf; +} + + +/* + * Set 1 byte. (Included to make the code more legible.) + */ +INLINE void set1(unsigned char* buf, unsigned char val) +{ + *buf = (unsigned char)(val); +} + +/* + * Set 2 big-endian bytes. + */ +INLINE void set2BE(unsigned char* buf, unsigned short val) +{ + *buf++ = (unsigned char)(val >> 8); + *buf = (unsigned char)(val); +} + +/* + * Set 4 big-endian bytes. + */ +INLINE void set4BE(unsigned char* buf, unsigned int val) +{ + *buf++ = (unsigned char)(val >> 24); + *buf++ = (unsigned char)(val >> 16); + *buf++ = (unsigned char)(val >> 8); + *buf = (unsigned char)(val); +} + +/* + * Set 8 big-endian bytes. + */ +INLINE void set8BE(unsigned char* buf, unsigned long long val) +{ + *buf++ = (unsigned char)(val >> 56); + *buf++ = (unsigned char)(val >> 48); + *buf++ = (unsigned char)(val >> 40); + *buf++ = (unsigned char)(val >> 32); + *buf++ = (unsigned char)(val >> 24); + *buf++ = (unsigned char)(val >> 16); + *buf++ = (unsigned char)(val >> 8); + *buf = (unsigned char)(val); +} + +/* + * Set 2 little-endian bytes. + */ +INLINE void set2LE(unsigned char* buf, unsigned short val) +{ + *buf++ = (unsigned char)(val); + *buf = (unsigned char)(val >> 8); +} + +/* + * Set 4 little-endian bytes. + */ +INLINE void set4LE(unsigned char* buf, unsigned int val) +{ + *buf++ = (unsigned char)(val); + *buf++ = (unsigned char)(val >> 8); + *buf++ = (unsigned char)(val >> 16); + *buf = (unsigned char)(val >> 24); +} + +/* + * Set 8 little-endian bytes. + */ +INLINE void set8LE(unsigned char* buf, unsigned long long val) +{ + *buf++ = (unsigned char)(val); + *buf++ = (unsigned char)(val >> 8); + *buf++ = (unsigned char)(val >> 16); + *buf++ = (unsigned char)(val >> 24); + *buf++ = (unsigned char)(val >> 32); + *buf++ = (unsigned char)(val >> 40); + *buf++ = (unsigned char)(val >> 48); + *buf = (unsigned char)(val >> 56); +} + +/* + * Stuff a UTF-8 string into the buffer. + */ +INLINE void setUtf8String(unsigned char* buf, const unsigned char* str) +{ + unsigned int strLen = strlen((const char*)str); + + set4BE(buf, strLen); + memcpy(buf + sizeof(unsigned int), str, strLen); +} + +#endif /*_MINZIP_BITS*/
diff --git a/recovery/minzip/DirUtil.cpp b/recovery/minzip/DirUtil.cpp new file mode 100644 index 0000000..e08e360 --- /dev/null +++ b/recovery/minzip/DirUtil.cpp
@@ -0,0 +1,218 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DirUtil.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <dirent.h> +#include <limits.h> + +#include <string> + +#include <selinux/label.h> +#include <selinux/selinux.h> + +typedef enum { DMISSING, DDIR, DILLEGAL } DirStatus; + +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; + } + return 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 = '/'; + } + 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); + } + + /* a directory, so open handle */ + dir = opendir(path); + if (dir == NULL) { + return -1; + } + + /* recurse over components */ + errno = 0; + while ((de = readdir(dir)) != NULL) { + //TODO: don't blow the stack + char dn[PATH_MAX]; + if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) { + continue; + } + snprintf(dn, sizeof(dn), "%s/%s", path, de->d_name); + if (dirUnlinkHierarchy(dn) < 0) { + fail = 1; + break; + } + 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; + } + + /* delete target directory */ + return rmdir(path); +}
diff --git a/recovery/minzip/DirUtil.h b/recovery/minzip/DirUtil.h new file mode 100644 index 0000000..85b83c3 --- /dev/null +++ b/recovery/minzip/DirUtil.h
@@ -0,0 +1,54 @@ +/* + * 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/recovery/minzip/Hash.c b/recovery/minzip/Hash.c new file mode 100644 index 0000000..49bcb31 --- /dev/null +++ b/recovery/minzip/Hash.c
@@ -0,0 +1,379 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Hash table. The dominant calls are add and lookup, with removals + * happening very infrequently. We use probing, and don't worry much + * about tombstone removal. + */ +#include <stdlib.h> +#include <assert.h> + +#define LOG_TAG "minzip" +#include "Log.h" +#include "Hash.h" + +/* table load factor, i.e. how full can it get before we resize */ +//#define LOAD_NUMER 3 // 75% +//#define LOAD_DENOM 4 +#define LOAD_NUMER 5 // 62.5% +#define LOAD_DENOM 8 +//#define LOAD_NUMER 1 // 50% +//#define LOAD_DENOM 2 + +/* + * Compute the capacity needed for a table to hold "size" elements. + */ +size_t mzHashSize(size_t size) { + return (size * LOAD_DENOM) / LOAD_NUMER +1; +} + +/* + * Round up to the next highest power of 2. + * + * Found on http://graphics.stanford.edu/~seander/bithacks.html. + */ +unsigned int roundUpPower2(unsigned int val) +{ + val--; + val |= val >> 1; + val |= val >> 2; + val |= val >> 4; + val |= val >> 8; + val |= val >> 16; + val++; + + return val; +} + +/* + * Create and initialize a hash table. + */ +HashTable* mzHashTableCreate(size_t initialSize, HashFreeFunc freeFunc) +{ + HashTable* pHashTable; + + assert(initialSize > 0); + + pHashTable = (HashTable*) malloc(sizeof(*pHashTable)); + if (pHashTable == NULL) + return NULL; + + pHashTable->tableSize = roundUpPower2(initialSize); + pHashTable->numEntries = pHashTable->numDeadEntries = 0; + pHashTable->freeFunc = freeFunc; + pHashTable->pEntries = + (HashEntry*) calloc((size_t)pHashTable->tableSize, sizeof(HashTable)); + if (pHashTable->pEntries == NULL) { + free(pHashTable); + return NULL; + } + + return pHashTable; +} + +/* + * Clear out all entries. + */ +void mzHashTableClear(HashTable* pHashTable) +{ + HashEntry* pEnt; + int i; + + pEnt = pHashTable->pEntries; + for (i = 0; i < pHashTable->tableSize; i++, pEnt++) { + if (pEnt->data == HASH_TOMBSTONE) { + // nuke entry + pEnt->data = NULL; + } else if (pEnt->data != NULL) { + // call free func then nuke entry + if (pHashTable->freeFunc != NULL) + (*pHashTable->freeFunc)(pEnt->data); + pEnt->data = NULL; + } + } + + pHashTable->numEntries = 0; + pHashTable->numDeadEntries = 0; +} + +/* + * Free the table. + */ +void mzHashTableFree(HashTable* pHashTable) +{ + if (pHashTable == NULL) + return; + mzHashTableClear(pHashTable); + free(pHashTable->pEntries); + free(pHashTable); +} + +#ifndef NDEBUG +/* + * Count up the number of tombstone entries in the hash table. + */ +static int countTombStones(HashTable* pHashTable) +{ + int i, count; + + for (count = i = 0; i < pHashTable->tableSize; i++) { + if (pHashTable->pEntries[i].data == HASH_TOMBSTONE) + count++; + } + return count; +} +#endif + +/* + * Resize a hash table. We do this when adding an entry increased the + * size of the table beyond its comfy limit. + * + * This essentially requires re-inserting all elements into the new storage. + * + * If multiple threads can access the hash table, the table's lock should + * have been grabbed before issuing the "lookup+add" call that led to the + * resize, so we don't have a synchronization problem here. + */ +static bool resizeHash(HashTable* pHashTable, int newSize) +{ + HashEntry* pNewEntries; + int i; + + assert(countTombStones(pHashTable) == pHashTable->numDeadEntries); + + pNewEntries = (HashEntry*) calloc(newSize, sizeof(HashTable)); + if (pNewEntries == NULL) + return false; + + for (i = 0; i < pHashTable->tableSize; i++) { + void* data = pHashTable->pEntries[i].data; + if (data != NULL && data != HASH_TOMBSTONE) { + int hashValue = pHashTable->pEntries[i].hashValue; + int newIdx; + + /* probe for new spot, wrapping around */ + newIdx = hashValue & (newSize-1); + while (pNewEntries[newIdx].data != NULL) + newIdx = (newIdx + 1) & (newSize-1); + + pNewEntries[newIdx].hashValue = hashValue; + pNewEntries[newIdx].data = data; + } + } + + free(pHashTable->pEntries); + pHashTable->pEntries = pNewEntries; + pHashTable->tableSize = newSize; + pHashTable->numDeadEntries = 0; + + assert(countTombStones(pHashTable) == 0); + return true; +} + +/* + * Look up an entry. + * + * We probe on collisions, wrapping around the table. + */ +void* mzHashTableLookup(HashTable* pHashTable, unsigned int itemHash, void* item, + HashCompareFunc cmpFunc, bool doAdd) +{ + HashEntry* pEntry; + HashEntry* pEnd; + void* result = NULL; + + assert(pHashTable->tableSize > 0); + assert(item != HASH_TOMBSTONE); + assert(item != NULL); + + /* jump to the first entry and probe for a match */ + pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)]; + pEnd = &pHashTable->pEntries[pHashTable->tableSize]; + while (pEntry->data != NULL) { + if (pEntry->data != HASH_TOMBSTONE && + pEntry->hashValue == itemHash && + (*cmpFunc)(pEntry->data, item) == 0) + { + /* match */ + break; + } + + pEntry++; + if (pEntry == pEnd) { /* wrap around to start */ + if (pHashTable->tableSize == 1) + break; /* edge case - single-entry table */ + pEntry = pHashTable->pEntries; + } + } + + if (pEntry->data == NULL) { + if (doAdd) { + pEntry->hashValue = itemHash; + pEntry->data = item; + pHashTable->numEntries++; + + /* + * We've added an entry. See if this brings us too close to full. + */ + if ((pHashTable->numEntries+pHashTable->numDeadEntries) * LOAD_DENOM + > pHashTable->tableSize * LOAD_NUMER) + { + if (!resizeHash(pHashTable, pHashTable->tableSize * 2)) { + /* don't really have a way to indicate failure */ + LOGE("Dalvik hash resize failure\n"); + abort(); + } + /* note "pEntry" is now invalid */ + } + + /* full table is bad -- search for nonexistent never halts */ + assert(pHashTable->numEntries < pHashTable->tableSize); + result = item; + } else { + assert(result == NULL); + } + } else { + result = pEntry->data; + } + + return result; +} + +/* + * Remove an entry from the table. + * + * Does NOT invoke the "free" function on the item. + */ +bool mzHashTableRemove(HashTable* pHashTable, unsigned int itemHash, void* item) +{ + HashEntry* pEntry; + HashEntry* pEnd; + + assert(pHashTable->tableSize > 0); + + /* jump to the first entry and probe for a match */ + pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)]; + pEnd = &pHashTable->pEntries[pHashTable->tableSize]; + while (pEntry->data != NULL) { + if (pEntry->data == item) { + pEntry->data = HASH_TOMBSTONE; + pHashTable->numEntries--; + pHashTable->numDeadEntries++; + return true; + } + + pEntry++; + if (pEntry == pEnd) { /* wrap around to start */ + if (pHashTable->tableSize == 1) + break; /* edge case - single-entry table */ + pEntry = pHashTable->pEntries; + } + } + + return false; +} + +/* + * Execute a function on every entry in the hash table. + * + * If "func" returns a nonzero value, terminate early and return the value. + */ +int mzHashForeach(HashTable* pHashTable, HashForeachFunc func, void* arg) +{ + int i, val; + + for (i = 0; i < pHashTable->tableSize; i++) { + HashEntry* pEnt = &pHashTable->pEntries[i]; + + if (pEnt->data != NULL && pEnt->data != HASH_TOMBSTONE) { + val = (*func)(pEnt->data, arg); + if (val != 0) + return val; + } + } + + return 0; +} + + +/* + * Look up an entry, counting the number of times we have to probe. + * + * Returns -1 if the entry wasn't found. + */ +int countProbes(HashTable* pHashTable, unsigned int itemHash, const void* item, + HashCompareFunc cmpFunc) +{ + HashEntry* pEntry; + HashEntry* pEnd; + int count = 0; + + assert(pHashTable->tableSize > 0); + assert(item != HASH_TOMBSTONE); + assert(item != NULL); + + /* jump to the first entry and probe for a match */ + pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)]; + pEnd = &pHashTable->pEntries[pHashTable->tableSize]; + while (pEntry->data != NULL) { + if (pEntry->data != HASH_TOMBSTONE && + pEntry->hashValue == itemHash && + (*cmpFunc)(pEntry->data, item) == 0) + { + /* match */ + break; + } + + pEntry++; + if (pEntry == pEnd) { /* wrap around to start */ + if (pHashTable->tableSize == 1) + break; /* edge case - single-entry table */ + pEntry = pHashTable->pEntries; + } + + count++; + } + if (pEntry->data == NULL) + return -1; + + return count; +} + +/* + * Evaluate the amount of probing required for the specified hash table. + * + * We do this by running through all entries in the hash table, computing + * the hash value and then doing a lookup. + * + * The caller should lock the table before calling here. + */ +void mzHashTableProbeCount(HashTable* pHashTable, HashCalcFunc calcFunc, + HashCompareFunc cmpFunc) +{ + int numEntries, minProbe, maxProbe, totalProbe; + HashIter iter; + + numEntries = maxProbe = totalProbe = 0; + minProbe = 65536*32767; + + for (mzHashIterBegin(pHashTable, &iter); !mzHashIterDone(&iter); + mzHashIterNext(&iter)) + { + const void* data = (const void*)mzHashIterData(&iter); + int count; + + count = countProbes(pHashTable, (*calcFunc)(data), data, cmpFunc); + + numEntries++; + + if (count < minProbe) + minProbe = count; + if (count > maxProbe) + maxProbe = count; + totalProbe += count; + } + + LOGV("Probe: min=%d max=%d, total=%d in %d (%d), avg=%.3f\n", + minProbe, maxProbe, totalProbe, numEntries, pHashTable->tableSize, + (float) totalProbe / (float) numEntries); +}
diff --git a/recovery/minzip/Hash.h b/recovery/minzip/Hash.h new file mode 100644 index 0000000..e83eac4 --- /dev/null +++ b/recovery/minzip/Hash.h
@@ -0,0 +1,194 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * General purpose hash table, used for finding classes, methods, etc. + * + * When the number of elements reaches 3/4 of the table's capacity, the + * table will be resized. + */ +#ifndef _MINZIP_HASH +#define _MINZIP_HASH + +#include "inline_magic.h" + +#include <stdlib.h> +#include <stdbool.h> +#include <assert.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* compute the hash of an item with a specific type */ +typedef unsigned int (*HashCompute)(const void* item); + +/* + * Compare a hash entry with a "loose" item after their hash values match. + * Returns { <0, 0, >0 } depending on ordering of items (same semantics + * as strcmp()). + */ +typedef int (*HashCompareFunc)(const void* tableItem, const void* looseItem); + +/* + * This function will be used to free entries in the table. This can be + * NULL if no free is required, free(), or a custom function. + */ +typedef void (*HashFreeFunc)(void* ptr); + +/* + * Used by mzHashForeach(). + */ +typedef int (*HashForeachFunc)(void* data, void* arg); + +/* + * One entry in the hash table. "data" values are expected to be (or have + * the same characteristics as) valid pointers. In particular, a NULL + * value for "data" indicates an empty slot, and HASH_TOMBSTONE indicates + * a no-longer-used slot that must be stepped over during probing. + * + * Attempting to add a NULL or tombstone value is an error. + * + * When an entry is released, we will call (HashFreeFunc)(entry->data). + */ +typedef struct HashEntry { + unsigned int hashValue; + void* data; +} HashEntry; + +#define HASH_TOMBSTONE ((void*) 0xcbcacccd) // invalid ptr value + +/* + * Expandable hash table. + * + * This structure should be considered opaque. + */ +typedef struct HashTable { + int tableSize; /* must be power of 2 */ + int numEntries; /* current #of "live" entries */ + int numDeadEntries; /* current #of tombstone entries */ + HashEntry* pEntries; /* array on heap */ + HashFreeFunc freeFunc; +} HashTable; + +/* + * Create and initialize a HashTable structure, using "initialSize" as + * a basis for the initial capacity of the table. (The actual initial + * table size may be adjusted upward.) If you know exactly how many + * elements the table will hold, pass the result from mzHashSize() in.) + * + * Returns "false" if unable to allocate the table. + */ +HashTable* mzHashTableCreate(size_t initialSize, HashFreeFunc freeFunc); + +/* + * Compute the capacity needed for a table to hold "size" elements. Use + * this when you know ahead of time how many elements the table will hold. + * Pass this value into mzHashTableCreate() to ensure that you can add + * all elements without needing to reallocate the table. + */ +size_t mzHashSize(size_t size); + +/* + * Clear out a hash table, freeing the contents of any used entries. + */ +void mzHashTableClear(HashTable* pHashTable); + +/* + * Free a hash table. + */ +void mzHashTableFree(HashTable* pHashTable); + +/* + * Get #of entries in hash table. + */ +INLINE int mzHashTableNumEntries(HashTable* pHashTable) { + return pHashTable->numEntries; +} + +/* + * Get total size of hash table (for memory usage calculations). + */ +INLINE int mzHashTableMemUsage(HashTable* pHashTable) { + return sizeof(HashTable) + pHashTable->tableSize * sizeof(HashEntry); +} + +/* + * Look up an entry in the table, possibly adding it if it's not there. + * + * If "item" is not found, and "doAdd" is false, NULL is returned. + * Otherwise, a pointer to the found or added item is returned. (You can + * tell the difference by seeing if return value == item.) + * + * An "add" operation may cause the entire table to be reallocated. + */ +void* mzHashTableLookup(HashTable* pHashTable, unsigned int itemHash, void* item, + HashCompareFunc cmpFunc, bool doAdd); + +/* + * Remove an item from the hash table, given its "data" pointer. Does not + * invoke the "free" function; just detaches it from the table. + */ +bool mzHashTableRemove(HashTable* pHashTable, unsigned int hash, void* item); + +/* + * Execute "func" on every entry in the hash table. + * + * If "func" returns a nonzero value, terminate early and return the value. + */ +int mzHashForeach(HashTable* pHashTable, HashForeachFunc func, void* arg); + +/* + * An alternative to mzHashForeach(), using an iterator. + * + * Use like this: + * HashIter iter; + * for (mzHashIterBegin(hashTable, &iter); !mzHashIterDone(&iter); + * mzHashIterNext(&iter)) + * { + * MyData* data = (MyData*)mzHashIterData(&iter); + * } + */ +typedef struct HashIter { + void* data; + HashTable* pHashTable; + int idx; +} HashIter; +INLINE void mzHashIterNext(HashIter* pIter) { + int i = pIter->idx +1; + int lim = pIter->pHashTable->tableSize; + for ( ; i < lim; i++) { + void* data = pIter->pHashTable->pEntries[i].data; + if (data != NULL && data != HASH_TOMBSTONE) + break; + } + pIter->idx = i; +} +INLINE void mzHashIterBegin(HashTable* pHashTable, HashIter* pIter) { + pIter->pHashTable = pHashTable; + pIter->idx = -1; + mzHashIterNext(pIter); +} +INLINE bool mzHashIterDone(HashIter* pIter) { + return (pIter->idx >= pIter->pHashTable->tableSize); +} +INLINE void* mzHashIterData(HashIter* pIter) { + assert(pIter->idx >= 0 && pIter->idx < pIter->pHashTable->tableSize); + return pIter->pHashTable->pEntries[pIter->idx].data; +} + + +/* + * Evaluate hash table performance by examining the number of times we + * have to probe for an entry. + * + * The caller should lock the table beforehand. + */ +typedef unsigned int (*HashCalcFunc)(const void* item); +void mzHashTableProbeCount(HashTable* pHashTable, HashCalcFunc calcFunc, + HashCompareFunc cmpFunc); + +#ifdef __cplusplus +} +#endif + +#endif /*_MINZIP_HASH*/
diff --git a/recovery/minzip/Inlines.c b/recovery/minzip/Inlines.c new file mode 100644 index 0000000..91f8775 --- /dev/null +++ b/recovery/minzip/Inlines.c
@@ -0,0 +1,25 @@ +/* + * 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. + */ + +/* Make sure that non-inlined versions of INLINED-marked functions + * exist so that debug builds (which don't generally do inlining) + * don't break. + */ +#define MINZIP_GENERATE_INLINES 1 +#include "Bits.h" +#include "Hash.h" +#include "SysUtil.h" +#include "Zip.h"
diff --git a/recovery/minzip/Log.h b/recovery/minzip/Log.h new file mode 100644 index 0000000..36e62f5 --- /dev/null +++ b/recovery/minzip/Log.h
@@ -0,0 +1,207 @@ +// +// Copyright 2005 The Android Open Source Project +// +// C/C++ logging functions. See the logging documentation for API details. +// +// We'd like these to be available from C code (in case we import some from +// somewhere), so this has a C interface. +// +// The output will be correct when the log file is shared between multiple +// threads and/or multiple processes so long as the operating system +// supports O_APPEND. These calls have mutex-protected data structures +// and so are NOT reentrant. Do not use LOG in a signal handler. +// +#ifndef _MINZIP_LOG_H +#define _MINZIP_LOG_H + +#include <stdio.h> + +// --------------------------------------------------------------------- + +/* + * Normally we strip LOGV (VERBOSE messages) from release builds. + * You can modify this (for example with "#define LOG_NDEBUG 0" + * at the top of your source file) to change that behavior. + */ +#ifndef LOG_NDEBUG +#ifdef NDEBUG +#define LOG_NDEBUG 1 +#else +#define LOG_NDEBUG 0 +#endif +#endif + +/* + * This is the local tag used for the following simplified + * logging macros. You can change this preprocessor definition + * before using the other macros to change the tag. + */ +#ifndef LOG_TAG +#define LOG_TAG NULL +#endif + +// --------------------------------------------------------------------- + +/* + * Simplified macro to send a verbose log message using the current LOG_TAG. + */ +#ifndef LOGV +#if LOG_NDEBUG +#define LOGV(...) ((void)0) +#else +#define LOGV(...) ((void)LOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) +#endif +#endif + +#define CONDITION(cond) (__builtin_expect((cond)!=0, 0)) + +#ifndef LOGV_IF +#if LOG_NDEBUG +#define LOGV_IF(cond, ...) ((void)0) +#else +#define LOGV_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)LOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif +#endif + +#define LOGVV LOGV +#define LOGVV_IF LOGV_IF + +/* + * Simplified macro to send a debug log message using the current LOG_TAG. + */ +#ifndef LOGD +#define LOGD(...) ((void)LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef LOGD_IF +#define LOGD_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send an info log message using the current LOG_TAG. + */ +#ifndef LOGI +#define LOGI(...) ((void)LOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef LOGI_IF +#define LOGI_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)LOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send a warning log message using the current LOG_TAG. + */ +#ifndef LOGW +#define LOGW(...) ((void)LOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef LOGW_IF +#define LOGW_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)LOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send an error log message using the current LOG_TAG. + */ +#ifndef LOGE +#define LOGE(...) ((void)LOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef LOGE_IF +#define LOGE_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)LOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * verbose priority. + */ +#ifndef IF_LOGV +#if LOG_NDEBUG +#define IF_LOGV() if (false) +#else +#define IF_LOGV() IF_LOG(LOG_VERBOSE, LOG_TAG) +#endif +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * debug priority. + */ +#ifndef IF_LOGD +#define IF_LOGD() IF_LOG(LOG_DEBUG, LOG_TAG) +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * info priority. + */ +#ifndef IF_LOGI +#define IF_LOGI() IF_LOG(LOG_INFO, LOG_TAG) +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * warn priority. + */ +#ifndef IF_LOGW +#define IF_LOGW() IF_LOG(LOG_WARN, LOG_TAG) +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * error priority. + */ +#ifndef IF_LOGE +#define IF_LOGE() IF_LOG(LOG_ERROR, LOG_TAG) +#endif + +// --------------------------------------------------------------------- + +/* + * Basic log message macro. + * + * Example: + * LOG(LOG_WARN, NULL, "Failed with error %d", errno); + * + * The second argument may be NULL or "" to indicate the "global" tag. + * + * Non-gcc probably won't have __FUNCTION__. It's not vital. gcc also + * offers __PRETTY_FUNCTION__, which is rather more than we need. + */ +#ifndef LOG +#define LOG(priority, tag, ...) \ + LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__) +#endif + +/* + * Log macro that allows you to specify a number for the priority. + */ +#ifndef LOG_PRI +#define LOG_PRI(priority, tag, ...) \ + printf(tag ": " __VA_ARGS__) +#endif + +/* + * Conditional given a desired logging priority and tag. + */ +#ifndef IF_LOG +#define IF_LOG(priority, tag) \ + if (1) +#endif + +#endif // _MINZIP_LOG_H
diff --git a/recovery/minzip/SysUtil.c b/recovery/minzip/SysUtil.c new file mode 100644 index 0000000..3f1fce1 --- /dev/null +++ b/recovery/minzip/SysUtil.c
@@ -0,0 +1,213 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * System utilities. + */ +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#define LOG_TAG "sysutil" +#include "Log.h" +#include "SysUtil.h" + +static bool sysMapFD(int fd, MemMapping* pMap) { + assert(pMap != NULL); + + struct stat sb; + if (fstat(fd, &sb) == -1) { + LOGE("fstat(%d) failed: %s\n", fd, strerror(errno)); + return false; + } + + void* memPtr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (memPtr == MAP_FAILED) { + LOGE("mmap(%d, R, PRIVATE, %d, 0) failed: %s\n", (int) sb.st_size, fd, strerror(errno)); + return false; + } + + pMap->addr = memPtr; + pMap->length = sb.st_size; + pMap->range_count = 1; + pMap->ranges = malloc(sizeof(MappedRange)); + if (pMap->ranges == NULL) { + LOGE("malloc failed: %s\n", strerror(errno)); + munmap(memPtr, length); + return -1; + } + pMap->ranges[0].addr = memPtr; + pMap->ranges[0].length = sb.st_size; + + return true; +} + +static int sysMapBlockFile(FILE* mapf, MemMapping* pMap) +{ + char block_dev[PATH_MAX+1]; + size_t size; + unsigned int blksize; + size_t blocks; + unsigned int range_count; + unsigned int i; + + if (fgets(block_dev, sizeof(block_dev), mapf) == NULL) { + LOGE("failed to read block device from header\n"); + return -1; + } + for (i = 0; i < sizeof(block_dev); ++i) { + if (block_dev[i] == '\n') { + block_dev[i] = 0; + break; + } + } + + if (fscanf(mapf, "%zu %u\n%u\n", &size, &blksize, &range_count) != 3) { + LOGE("failed to parse block map header\n"); + return -1; + } + if (blksize != 0) { + blocks = ((size-1) / blksize) + 1; + } + if (size == 0 || blksize == 0 || blocks > SIZE_MAX / blksize || range_count == 0) { + LOGE("invalid data in block map file: size %zu, blksize %u, range_count %u\n", + size, blksize, range_count); + return -1; + } + + pMap->range_count = range_count; + pMap->ranges = calloc(range_count, sizeof(MappedRange)); + if (pMap->ranges == NULL) { + LOGE("calloc(%u, %zu) failed: %s\n", range_count, sizeof(MappedRange), strerror(errno)); + return -1; + } + + // Reserve enough contiguous address space for the whole file. + unsigned char* reserve; + reserve = mmap64(NULL, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (reserve == MAP_FAILED) { + LOGW("failed to reserve address space: %s\n", strerror(errno)); + free(pMap->ranges); + return -1; + } + + int fd = open(block_dev, O_RDONLY); + if (fd < 0) { + LOGW("failed to open block device %s: %s\n", block_dev, strerror(errno)); + munmap(reserve, blocks * blksize); + free(pMap->ranges); + return -1; + } + + unsigned char* next = reserve; + size_t remaining_size = blocks * blksize; + bool success = true; + for (i = 0; i < range_count; ++i) { + size_t start, end; + if (fscanf(mapf, "%zu %zu\n", &start, &end) != 2) { + LOGW("failed to parse range %d in block map\n", i); + success = false; + break; + } + size_t length = (end - start) * blksize; + if (end <= start || (end - start) > SIZE_MAX / blksize || length > remaining_size) { + LOGE("unexpected range in block map: %zu %zu\n", start, end); + success = false; + break; + } + + void* addr = mmap64(next, length, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, ((off64_t)start)*blksize); + if (addr == MAP_FAILED) { + LOGW("failed to map block %d: %s\n", i, strerror(errno)); + success = false; + break; + } + pMap->ranges[i].addr = addr; + pMap->ranges[i].length = length; + + next += length; + remaining_size -= length; + } + if (success && remaining_size != 0) { + LOGE("ranges in block map are invalid: remaining_size = %zu\n", remaining_size); + success = false; + } + if (!success) { + close(fd); + munmap(reserve, blocks * blksize); + free(pMap->ranges); + return -1; + } + + close(fd); + pMap->addr = reserve; + pMap->length = size; + + LOGI("mmapped %d ranges\n", range_count); + + return 0; +} + +int sysMapFile(const char* fn, MemMapping* pMap) +{ + memset(pMap, 0, sizeof(*pMap)); + + if (fn && fn[0] == '@') { + // A map of blocks + FILE* mapf = fopen(fn+1, "r"); + if (mapf == NULL) { + LOGE("Unable to open '%s': %s\n", fn+1, strerror(errno)); + return -1; + } + + if (sysMapBlockFile(mapf, pMap) != 0) { + LOGW("Map of '%s' failed\n", fn); + fclose(mapf); + return -1; + } + + fclose(mapf); + } else { + // This is a regular file. + int fd = open(fn, O_RDONLY); + if (fd == -1) { + LOGE("Unable to open '%s': %s\n", fn, strerror(errno)); + return -1; + } + + if (!sysMapFD(fd, pMap)) { + LOGE("Map of '%s' failed\n", fn); + close(fd); + return -1; + } + + close(fd); + } + return 0; +} + +/* + * Release a memory mapping. + */ +void sysReleaseMap(MemMapping* pMap) +{ + int i; + for (i = 0; i < pMap->range_count; ++i) { + if (munmap(pMap->ranges[i].addr, pMap->ranges[i].length) < 0) { + LOGE("munmap(%p, %d) failed: %s\n", + pMap->ranges[i].addr, (int)pMap->ranges[i].length, strerror(errno)); + } + } + free(pMap->ranges); + pMap->ranges = NULL; + pMap->range_count = 0; +}
diff --git a/recovery/minzip/SysUtil.h b/recovery/minzip/SysUtil.h new file mode 100644 index 0000000..7adff1e --- /dev/null +++ b/recovery/minzip/SysUtil.h
@@ -0,0 +1,52 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * System utilities. + */ +#ifndef _MINZIP_SYSUTIL +#define _MINZIP_SYSUTIL + +#include <stdio.h> +#include <sys/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MappedRange { + void* addr; + size_t length; +} MappedRange; + +/* + * Use this to keep track of mapped segments. + */ +typedef struct MemMapping { + unsigned char* addr; /* start of data */ + size_t length; /* length of data */ + + int range_count; + MappedRange* ranges; +} MemMapping; + +/* + * Map a file into a private, read-only memory segment. If 'fn' + * begins with an '@' character, it is a map of blocks to be mapped, + * otherwise it is treated as an ordinary file. + * + * On success, "pMap" is filled in, and zero is returned. + */ +int sysMapFile(const char* fn, MemMapping* pMap); + +/* + * Release the pages associated with a shared memory segment. + * + * This does not free "pMap"; it just releases the memory. + */ +void sysReleaseMap(MemMapping* pMap); + +#ifdef __cplusplus +} +#endif + +#endif /*_MINZIP_SYSUTIL*/
diff --git a/recovery/minzip/Zip.c b/recovery/minzip/Zip.c new file mode 100644 index 0000000..d557daa --- /dev/null +++ b/recovery/minzip/Zip.c
@@ -0,0 +1,1018 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Simple Zip file support. + */ +#include "safe_iop.h" +#include "zlib.h" + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdint.h> // for uintptr_t +#include <stdlib.h> +#include <sys/stat.h> // for S_ISLNK() +#include <unistd.h> + +#define LOG_TAG "minzip" +#include "Zip.h" +#include "Bits.h" +#include "Log.h" +#include "DirUtil.h" + +#undef NDEBUG // do this after including Log.h +#include <assert.h> + +#include <selinux/label.h> +#include <selinux/selinux.h> + +#define SORT_ENTRIES 1 + +/* + * Offset and length constants (java.util.zip naming convention). + */ +enum { + CENSIG = 0x02014b50, // PK12 + CENHDR = 46, + + CENVEM = 4, + CENVER = 6, + CENFLG = 8, + CENHOW = 10, + CENTIM = 12, + CENCRC = 16, + CENSIZ = 20, + CENLEN = 24, + CENNAM = 28, + CENEXT = 30, + CENCOM = 32, + CENDSK = 34, + CENATT = 36, + CENATX = 38, + CENOFF = 42, + + ENDSIG = 0x06054b50, // PK56 + ENDHDR = 22, + + ENDSUB = 8, + ENDTOT = 10, + ENDSIZ = 12, + ENDOFF = 16, + ENDCOM = 20, + + EXTSIG = 0x08074b50, // PK78 + EXTHDR = 16, + + EXTCRC = 4, + EXTSIZ = 8, + EXTLEN = 12, + + LOCSIG = 0x04034b50, // PK34 + LOCHDR = 30, + + LOCVER = 4, + LOCFLG = 6, + LOCHOW = 8, + LOCTIM = 10, + LOCCRC = 14, + LOCSIZ = 18, + LOCLEN = 22, + LOCNAM = 26, + LOCEXT = 28, + + STORED = 0, + DEFLATED = 8, + + CENVEM_UNIX = 3 << 8, // the high byte of CENVEM +}; + + +/* + * For debugging, dump the contents of a ZipEntry. + */ +#if 0 +static void dumpEntry(const ZipEntry* pEntry) +{ + LOGI(" %p '%.*s'\n", pEntry->fileName,pEntry->fileNameLen,pEntry->fileName); + LOGI(" off=%u comp=%u uncomp=%u how=%d\n", pEntry->offset, + pEntry->compLen, pEntry->uncompLen, pEntry->compression); +} +#endif + +/* + * (This is a mzHashTableLookup callback.) + * + * Compare two ZipEntry structs, by name. + */ +static int hashcmpZipEntry(const void* ventry1, const void* ventry2) +{ + const ZipEntry* entry1 = (const ZipEntry*) ventry1; + const ZipEntry* entry2 = (const ZipEntry*) ventry2; + + if (entry1->fileNameLen != entry2->fileNameLen) + return entry1->fileNameLen - entry2->fileNameLen; + return memcmp(entry1->fileName, entry2->fileName, entry1->fileNameLen); +} + +/* + * (This is a mzHashTableLookup callback.) + * + * find a ZipEntry struct by name. + */ +static int hashcmpZipName(const void* ventry, const void* vname) +{ + const ZipEntry* entry = (const ZipEntry*) ventry; + const char* name = (const char*) vname; + unsigned int nameLen = strlen(name); + + if (entry->fileNameLen != nameLen) + return entry->fileNameLen - nameLen; + return memcmp(entry->fileName, name, nameLen); +} + +/* + * Compute the hash code for a ZipEntry filename. + * + * Not expected to be compatible with any other hash function, so we init + * to 2 to ensure it doesn't happen to match. + */ +static unsigned int computeHash(const char* name, int nameLen) +{ + unsigned int hash = 2; + + while (nameLen--) + hash = hash * 31 + *name++; + + return hash; +} + +static void addEntryToHashTable(HashTable* pHash, ZipEntry* pEntry) +{ + unsigned int itemHash = computeHash(pEntry->fileName, pEntry->fileNameLen); + const ZipEntry* found; + + found = (const ZipEntry*)mzHashTableLookup(pHash, + itemHash, pEntry, hashcmpZipEntry, true); + if (found != pEntry) { + LOGW("WARNING: duplicate entry '%.*s' in Zip\n", + found->fileNameLen, found->fileName); + /* keep going */ + } +} + +static int validFilename(const char *fileName, unsigned int fileNameLen) +{ + // Forbid super long filenames. + if (fileNameLen >= PATH_MAX) { + LOGW("Filename too long (%d chatacters)\n", fileNameLen); + return 0; + } + + // Require all characters to be printable ASCII (no NUL, no UTF-8, etc). + unsigned int i; + for (i = 0; i < fileNameLen; ++i) { + if (fileName[i] < 32 || fileName[i] >= 127) { + LOGW("Filename contains invalid character '\%03o'\n", fileName[i]); + return 0; + } + } + + return 1; +} + +/* + * Parse the contents of a Zip archive. After confirming that the file + * is in fact a Zip, we scan out the contents of the central directory and + * store it in a hash table. + * + * Returns "true" on success. + */ +static bool parseZipArchive(ZipArchive* pArchive) +{ + bool result = false; + const unsigned char* ptr; + unsigned int i, numEntries, cdOffset; + unsigned int val; + + /* + * The first 4 bytes of the file will either be the local header + * signature for the first file (LOCSIG) or, if the archive doesn't + * have any files in it, the end-of-central-directory signature (ENDSIG). + */ + val = get4LE(pArchive->addr); + if (val == ENDSIG) { + LOGW("Found Zip archive, but it looks empty\n"); + goto bail; + } else if (val != LOCSIG) { + LOGW("Not a Zip archive (found 0x%08x)\n", val); + goto bail; + } + + /* + * Find the EOCD. We'll find it immediately unless they have a file + * comment. + */ + ptr = pArchive->addr + pArchive->length - ENDHDR; + + while (ptr >= (const unsigned char*) pArchive->addr) { + if (*ptr == (ENDSIG & 0xff) && get4LE(ptr) == ENDSIG) + break; + ptr--; + } + if (ptr < (const unsigned char*) pArchive->addr) { + LOGW("Could not find end-of-central-directory in Zip\n"); + goto bail; + } + + /* + * There are two interesting items in the EOCD block: the number of + * entries in the file, and the file offset of the start of the + * central directory. + */ + numEntries = get2LE(ptr + ENDSUB); + cdOffset = get4LE(ptr + ENDOFF); + + LOGVV("numEntries=%d cdOffset=%d\n", numEntries, cdOffset); + if (numEntries == 0 || cdOffset >= pArchive->length) { + LOGW("Invalid entries=%d offset=%d (len=%zd)\n", + numEntries, cdOffset, pArchive->length); + goto bail; + } + + /* + * Create data structures to hold entries. + */ + pArchive->numEntries = numEntries; + pArchive->pEntries = (ZipEntry*) calloc(numEntries, sizeof(ZipEntry)); + pArchive->pHash = mzHashTableCreate(mzHashSize(numEntries), NULL); + if (pArchive->pEntries == NULL || pArchive->pHash == NULL) + goto bail; + + ptr = pArchive->addr + cdOffset; + for (i = 0; i < numEntries; i++) { + ZipEntry* pEntry; + unsigned int fileNameLen, extraLen, commentLen, localHdrOffset; + const unsigned char* localHdr; + const char *fileName; + + if (ptr + CENHDR > (const unsigned char*)pArchive->addr + pArchive->length) { + LOGW("Ran off the end (at %d)\n", i); + goto bail; + } + if (get4LE(ptr) != CENSIG) { + LOGW("Missed a central dir sig (at %d)\n", i); + goto bail; + } + + localHdrOffset = get4LE(ptr + CENOFF); + fileNameLen = get2LE(ptr + CENNAM); + extraLen = get2LE(ptr + CENEXT); + commentLen = get2LE(ptr + CENCOM); + fileName = (const char*)ptr + CENHDR; + if (fileName + fileNameLen > (const char*)pArchive->addr + pArchive->length) { + LOGW("Filename ran off the end (at %d)\n", i); + goto bail; + } + if (!validFilename(fileName, fileNameLen)) { + LOGW("Invalid filename (at %d)\n", i); + goto bail; + } + +#if SORT_ENTRIES + /* Figure out where this entry should go (binary search). + */ + if (i > 0) { + int low, high; + + low = 0; + high = i - 1; + while (low <= high) { + int mid; + int diff; + int diffLen; + + mid = low + ((high - low) / 2); // avoid overflow + + if (pArchive->pEntries[mid].fileNameLen < fileNameLen) { + diffLen = pArchive->pEntries[mid].fileNameLen; + } else { + diffLen = fileNameLen; + } + diff = strncmp(pArchive->pEntries[mid].fileName, fileName, + diffLen); + if (diff == 0) { + diff = pArchive->pEntries[mid].fileNameLen - fileNameLen; + } + if (diff < 0) { + low = mid + 1; + } else if (diff > 0) { + high = mid - 1; + } else { + high = mid; + break; + } + } + + unsigned int target = high + 1; + assert(target <= i); + if (target != i) { + /* It belongs somewhere other than at the end of + * the list. Make some room at [target]. + */ + memmove(pArchive->pEntries + target + 1, + pArchive->pEntries + target, + (i - target) * sizeof(ZipEntry)); + } + pEntry = &pArchive->pEntries[target]; + } else { + pEntry = &pArchive->pEntries[0]; + } +#else + pEntry = &pArchive->pEntries[i]; +#endif + pEntry->fileNameLen = fileNameLen; + pEntry->fileName = fileName; + + pEntry->compLen = get4LE(ptr + CENSIZ); + pEntry->uncompLen = get4LE(ptr + CENLEN); + pEntry->compression = get2LE(ptr + CENHOW); + pEntry->modTime = get4LE(ptr + CENTIM); + pEntry->crc32 = get4LE(ptr + CENCRC); + + /* These two are necessary for finding the mode of the file. + */ + pEntry->versionMadeBy = get2LE(ptr + CENVEM); + if ((pEntry->versionMadeBy & 0xff00) != 0 && + (pEntry->versionMadeBy & 0xff00) != CENVEM_UNIX) + { + LOGW("Incompatible \"version made by\": 0x%02x (at %d)\n", + pEntry->versionMadeBy >> 8, i); + goto bail; + } + pEntry->externalFileAttributes = get4LE(ptr + CENATX); + + // Perform pArchive->addr + localHdrOffset, ensuring that it won't + // overflow. This is needed because localHdrOffset is untrusted. + if (!safe_add((uintptr_t *)&localHdr, (uintptr_t)pArchive->addr, + (uintptr_t)localHdrOffset)) { + LOGW("Integer overflow adding in parseZipArchive\n"); + goto bail; + } + if ((uintptr_t)localHdr + LOCHDR > + (uintptr_t)pArchive->addr + pArchive->length) { + LOGW("Bad offset to local header: %d (at %d)\n", localHdrOffset, i); + goto bail; + } + if (get4LE(localHdr) != LOCSIG) { + LOGW("Missed a local header sig (at %d)\n", i); + goto bail; + } + pEntry->offset = localHdrOffset + LOCHDR + + get2LE(localHdr + LOCNAM) + get2LE(localHdr + LOCEXT); + if (!safe_add(NULL, pEntry->offset, pEntry->compLen)) { + LOGW("Integer overflow adding in parseZipArchive\n"); + goto bail; + } + if ((size_t)pEntry->offset + pEntry->compLen > pArchive->length) { + LOGW("Data ran off the end (at %d)\n", i); + goto bail; + } + +#if !SORT_ENTRIES + /* Add to hash table; no need to lock here. + * Can't do this now if we're sorting, because entries + * will move around. + */ + addEntryToHashTable(pArchive->pHash, pEntry); +#endif + + //dumpEntry(pEntry); + ptr += CENHDR + fileNameLen + extraLen + commentLen; + } + +#if SORT_ENTRIES + /* If we're sorting, we have to wait until all entries + * are in their final places, otherwise the pointers will + * probably point to the wrong things. + */ + for (i = 0; i < numEntries; i++) { + /* Add to hash table; no need to lock here. + */ + addEntryToHashTable(pArchive->pHash, &pArchive->pEntries[i]); + } +#endif + + result = true; + +bail: + if (!result) { + mzHashTableFree(pArchive->pHash); + pArchive->pHash = NULL; + } + return result; +} + +/* + * Open a Zip archive and scan out the contents. + * + * The easiest way to do this is to mmap() the whole thing and do the + * traditional backward scan for central directory. Since the EOCD is + * a relatively small bit at the end, we should end up only touching a + * small set of pages. + * + * This will be called on non-Zip files, especially during startup, so + * we don't want to be too noisy about failures. (Do we want a "quiet" + * flag?) + * + * On success, we fill out the contents of "pArchive". + */ +int mzOpenZipArchive(unsigned char* addr, size_t length, ZipArchive* pArchive) +{ + int err; + + if (length < ENDHDR) { + err = -1; + LOGW("Archive %p is too small to be zip (%zd)\n", pArchive, length); + goto bail; + } + + pArchive->addr = addr; + pArchive->length = length; + + if (!parseZipArchive(pArchive)) { + err = -1; + LOGW("Parsing archive %p failed\n", pArchive); + goto bail; + } + + err = 0; + +bail: + if (err != 0) + mzCloseZipArchive(pArchive); + return err; +} + +/* + * Close a ZipArchive, closing the file and freeing the contents. + * + * NOTE: the ZipArchive may not have been fully created. + */ +void mzCloseZipArchive(ZipArchive* pArchive) +{ + LOGV("Closing archive %p\n", pArchive); + + free(pArchive->pEntries); + + mzHashTableFree(pArchive->pHash); + + pArchive->pHash = NULL; + pArchive->pEntries = NULL; +} + +/* + * Find a matching entry. + * + * Returns NULL if no matching entry found. + */ +const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive, + const char* entryName) +{ + unsigned int itemHash = computeHash(entryName, strlen(entryName)); + + return (const ZipEntry*)mzHashTableLookup(pArchive->pHash, + itemHash, (char*) entryName, hashcmpZipName, false); +} + +/* + * Return true if the entry is a symbolic link. + */ +static bool mzIsZipEntrySymlink(const ZipEntry* pEntry) +{ + if ((pEntry->versionMadeBy & 0xff00) == CENVEM_UNIX) { + return S_ISLNK(pEntry->externalFileAttributes >> 16); + } + return false; +} + +/* Call processFunction on the uncompressed data of a STORED entry. + */ +static bool processStoredEntry(const ZipArchive *pArchive, + const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, + void *cookie) +{ + return processFunction(pArchive->addr + pEntry->offset, pEntry->uncompLen, cookie); +} + +static bool processDeflatedEntry(const ZipArchive *pArchive, + const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, + void *cookie) +{ + bool success = false; + unsigned long totalOut = 0; + unsigned char procBuf[32 * 1024]; + z_stream zstream; + int zerr; + + /* + * Initialize the zlib stream. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = pArchive->addr + pEntry->offset; + zstream.avail_in = pEntry->compLen; + zstream.next_out = (Bytef*) procBuf; + zstream.avail_out = sizeof(procBuf); + zstream.data_type = Z_UNKNOWN; + + /* + * Use the undocumented "negative window bits" feature to tell zlib + * that there's no zlib header waiting for it. + */ + zerr = inflateInit2(&zstream, -MAX_WBITS); + if (zerr != Z_OK) { + if (zerr == Z_VERSION_ERROR) { + LOGE("Installed zlib is not compatible with linked version (%s)\n", + ZLIB_VERSION); + } else { + LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr); + } + goto bail; + } + + /* + * Loop while we have data. + */ + do { + /* uncompress the data */ + zerr = inflate(&zstream, Z_NO_FLUSH); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + LOGW("zlib inflate call failed (zerr=%d)\n", zerr); + goto z_bail; + } + + /* write when we're full or when we're done */ + if (zstream.avail_out == 0 || + (zerr == Z_STREAM_END && zstream.avail_out != sizeof(procBuf))) + { + long procSize = zstream.next_out - procBuf; + LOGVV("+++ processing %d bytes\n", (int) procSize); + bool ret = processFunction(procBuf, procSize, cookie); + if (!ret) { + LOGW("Process function elected to fail (in inflate)\n"); + goto z_bail; + } + + zstream.next_out = procBuf; + zstream.avail_out = sizeof(procBuf); + } + } while (zerr == Z_OK); + + assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + // success! + totalOut = zstream.total_out; + success = true; + +z_bail: + inflateEnd(&zstream); /* free up any allocated structures */ + +bail: + if (totalOut != pEntry->uncompLen) { + if (success) { // error already shown? + LOGW("Size mismatch on inflated file (%lu vs %u)\n", totalOut, pEntry->uncompLen); + } + return false; + } + return true; +} + +/* + * Stream the uncompressed data through the supplied function, + * passing cookie to it each time it gets called. processFunction + * may be called more than once. + * + * If processFunction returns false, the operation is abandoned and + * mzProcessZipEntryContents() immediately returns false. + * + * This is useful for calculating the hash of an entry's uncompressed contents. + */ +bool mzProcessZipEntryContents(const ZipArchive *pArchive, + const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, + void *cookie) +{ + bool ret = false; + + switch (pEntry->compression) { + case STORED: + ret = processStoredEntry(pArchive, pEntry, processFunction, cookie); + break; + case DEFLATED: + ret = processDeflatedEntry(pArchive, pEntry, processFunction, cookie); + break; + default: + LOGE("Unsupported compression type %d for entry '%s'\n", + pEntry->compression, pEntry->fileName); + break; + } + + return ret; +} + +typedef struct { + char *buf; + int bufLen; +} CopyProcessArgs; + +static bool copyProcessFunction(const unsigned char *data, int dataLen, + void *cookie) +{ + CopyProcessArgs *args = (CopyProcessArgs *)cookie; + if (dataLen <= args->bufLen) { + memcpy(args->buf, data, dataLen); + args->buf += dataLen; + args->bufLen -= dataLen; + return true; + } + return false; +} + +/* + * Read an entry into a buffer allocated by the caller. + */ +bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry, + char *buf, int bufLen) +{ + CopyProcessArgs args; + bool ret; + + args.buf = buf; + args.bufLen = bufLen; + ret = mzProcessZipEntryContents(pArchive, pEntry, copyProcessFunction, + (void *)&args); + if (!ret) { + LOGE("Can't extract entry to buffer.\n"); + return false; + } + return true; +} + +static bool writeProcessFunction(const unsigned char *data, int dataLen, + void *cookie) +{ + int fd = (int)(intptr_t)cookie; + if (dataLen == 0) { + return true; + } + ssize_t soFar = 0; + while (true) { + ssize_t n = TEMP_FAILURE_RETRY(write(fd, data+soFar, dataLen-soFar)); + if (n <= 0) { + LOGE("Error writing %zd bytes from zip file from %p: %s\n", + dataLen-soFar, data+soFar, strerror(errno)); + return false; + } else if (n > 0) { + soFar += n; + if (soFar == dataLen) return true; + if (soFar > dataLen) { + LOGE("write overrun? (%zd bytes instead of %d)\n", + soFar, dataLen); + return false; + } + } + } +} + +/* + * Uncompress "pEntry" in "pArchive" to "fd" at the current offset. + */ +bool mzExtractZipEntryToFile(const ZipArchive *pArchive, + const ZipEntry *pEntry, int fd) +{ + bool ret = mzProcessZipEntryContents(pArchive, pEntry, writeProcessFunction, + (void*)(intptr_t)fd); + if (!ret) { + LOGE("Can't extract entry to file.\n"); + return false; + } + return true; +} + +typedef struct { + unsigned char* buffer; + long len; +} BufferExtractCookie; + +static bool bufferProcessFunction(const unsigned char *data, int dataLen, + void *cookie) { + BufferExtractCookie *bec = (BufferExtractCookie*)cookie; + + memmove(bec->buffer, data, dataLen); + bec->buffer += dataLen; + bec->len -= dataLen; + + return true; +} + +/* + * Uncompress "pEntry" in "pArchive" to buffer, which must be large + * enough to hold mzGetZipEntryUncomplen(pEntry) bytes. + */ +bool mzExtractZipEntryToBuffer(const ZipArchive *pArchive, + const ZipEntry *pEntry, unsigned char *buffer) +{ + BufferExtractCookie bec; + bec.buffer = buffer; + bec.len = mzGetZipEntryUncompLen(pEntry); + + bool ret = mzProcessZipEntryContents(pArchive, pEntry, + bufferProcessFunction, (void*)&bec); + if (!ret || bec.len != 0) { + LOGE("Can't extract entry to memory buffer.\n"); + return false; + } + return true; +} + + +/* Helper state to make path translation easier and less malloc-happy. + */ +typedef struct { + const char *targetDir; + const char *zipDir; + char *buf; + int targetDirLen; + int zipDirLen; + int bufLen; +} MzPathHelper; + +/* Given the values of targetDir and zipDir in the helper, + * return the target filename of the provided entry. + * The helper must be initialized first. + */ +static const char *targetEntryPath(MzPathHelper *helper, ZipEntry *pEntry) +{ + int needLen; + bool firstTime = (helper->buf == NULL); + + /* target file <-- targetDir + / + entry[zipDirLen:] + */ + needLen = helper->targetDirLen + 1 + + pEntry->fileNameLen - helper->zipDirLen + 1; + if (firstTime || needLen > helper->bufLen) { + char *newBuf; + + needLen *= 2; + newBuf = (char *)realloc(helper->buf, needLen); + if (newBuf == NULL) { + return NULL; + } + helper->buf = newBuf; + helper->bufLen = needLen; + } + + /* Every path will start with the target path and a slash. + */ + if (firstTime) { + char *p = helper->buf; + memcpy(p, helper->targetDir, helper->targetDirLen); + p += helper->targetDirLen; + if (p == helper->buf || p[-1] != '/') { + helper->targetDirLen += 1; + *p++ = '/'; + } + } + + /* Replace the custom part of the path with the appropriate + * part of the entry's path. + */ + char *epath = helper->buf + helper->targetDirLen; + memcpy(epath, pEntry->fileName + helper->zipDirLen, + pEntry->fileNameLen - helper->zipDirLen); + epath += pEntry->fileNameLen - helper->zipDirLen; + *epath = '\0'; + + return helper->buf; +} + +/* + * Inflate all entries under zipDir to the directory specified by + * targetDir, which must exist and be a writable directory. + * + * The immediate children of zipDir will become the immediate + * children of targetDir; e.g., if the archive contains the entries + * + * a/b/c/one + * a/b/c/two + * a/b/c/d/three + * + * and mzExtractRecursive(a, "a/b/c", "/tmp") is called, the resulting + * files will be + * + * /tmp/one + * /tmp/two + * /tmp/d/three + * + * Returns true on success, false on failure. + */ +bool mzExtractRecursive(const ZipArchive *pArchive, + const char *zipDir, const char *targetDir, + const struct utimbuf *timestamp, + void (*callback)(const char *fn, void *), void *cookie, + struct selabel_handle *sehnd) +{ + if (zipDir[0] == '/') { + LOGE("mzExtractRecursive(): zipDir must be a relative path.\n"); + return false; + } + if (targetDir[0] != '/') { + LOGE("mzExtractRecursive(): targetDir must be an absolute path.\n"); + return false; + } + + unsigned int zipDirLen; + char *zpath; + + zipDirLen = strlen(zipDir); + zpath = (char *)malloc(zipDirLen + 2); + if (zpath == NULL) { + LOGE("Can't allocate %d bytes for zip path\n", zipDirLen + 2); + return false; + } + /* If zipDir is empty, we'll extract the entire zip file. + * Otherwise, canonicalize the path. + */ + if (zipDirLen > 0) { + /* Make sure there's (hopefully, exactly one) slash at the + * end of the path. This way we don't need to worry about + * accidentally extracting "one/twothree" when a path like + * "one/two" is specified. + */ + memcpy(zpath, zipDir, zipDirLen); + if (zpath[zipDirLen-1] != '/') { + zpath[zipDirLen++] = '/'; + } + } + zpath[zipDirLen] = '\0'; + + /* Set up the helper structure that we'll use to assemble paths. + */ + MzPathHelper helper; + helper.targetDir = targetDir; + helper.targetDirLen = strlen(helper.targetDir); + helper.zipDir = zpath; + helper.zipDirLen = strlen(helper.zipDir); + helper.buf = NULL; + helper.bufLen = 0; + + /* Walk through the entries and extract anything whose path begins + * with zpath. + //TODO: since the entries are sorted, binary search for the first match + // and stop after the first non-match. + */ + unsigned int i; + bool seenMatch = false; + int ok = true; + int extractCount = 0; + for (i = 0; i < pArchive->numEntries; i++) { + ZipEntry *pEntry = pArchive->pEntries + i; + if (pEntry->fileNameLen < zipDirLen) { + //TODO: look out for a single empty directory entry that matches zpath, but + // missing the trailing slash. Most zip files seem to include + // the trailing slash, but I think it's legal to leave it off. + // e.g., zpath "a/b/", entry "a/b", with no children of the entry. + /* No chance of matching. + */ +#if SORT_ENTRIES + if (seenMatch) { + /* Since the entries are sorted, we can give up + * on the first mismatch after the first match. + */ + break; + } +#endif + continue; + } + /* If zpath is empty, this strncmp() will match everything, + * which is what we want. + */ + if (strncmp(pEntry->fileName, zpath, zipDirLen) != 0) { +#if SORT_ENTRIES + if (seenMatch) { + /* Since the entries are sorted, we can give up + * on the first mismatch after the first match. + */ + break; + } +#endif + continue; + } + /* This entry begins with zipDir, so we'll extract it. + */ + seenMatch = true; + + /* Find the target location of the entry. + */ + const char *targetFile = targetEntryPath(&helper, pEntry); + if (targetFile == NULL) { + LOGE("Can't assemble target path for \"%.*s\"\n", + pEntry->fileNameLen, pEntry->fileName); + ok = false; + break; + } + +#define UNZIP_DIRMODE 0755 +#define UNZIP_FILEMODE 0644 + /* + * Create the file or directory. We ignore directory entries + * because we recursively create paths to each file entry we encounter + * in the zip archive anyway. + * + * NOTE: A "directory entry" in a zip archive is just a zero length + * entry that ends in a "/". They're not mandatory and many tools get + * rid of them. We need to process them only if we want to preserve + * empty directories from the archive. + */ + if (pEntry->fileName[pEntry->fileNameLen-1] != '/') { + /* This is not a directory. First, make sure that + * the containing directory exists. + */ + int ret = dirCreateHierarchy( + targetFile, UNZIP_DIRMODE, timestamp, true, sehnd); + if (ret != 0) { + LOGE("Can't create containing directory for \"%s\": %s\n", + targetFile, strerror(errno)); + ok = false; + break; + } + + /* + * The entry is a regular file or a symlink. Open the target for writing. + * + * TODO: This behavior for symlinks seems rather bizarre. For a + * symlink foo/bar/baz -> foo/tar/taz, we will create a file called + * "foo/bar/baz" whose contents are the literal "foo/tar/taz". We + * warn about this for now and preserve older behavior. + */ + if (mzIsZipEntrySymlink(pEntry)) { + LOGE("Symlink entry \"%.*s\" will be output as a regular file.", + pEntry->fileNameLen, pEntry->fileName); + } + + char *secontext = NULL; + + if (sehnd) { + selabel_lookup(sehnd, &secontext, targetFile, UNZIP_FILEMODE); + setfscreatecon(secontext); + } + + int fd = open(targetFile, O_CREAT|O_WRONLY|O_TRUNC, + UNZIP_FILEMODE); + + if (secontext) { + freecon(secontext); + setfscreatecon(NULL); + } + + if (fd < 0) { + LOGE("Can't create target file \"%s\": %s\n", + targetFile, strerror(errno)); + ok = false; + break; + } + + bool ok = mzExtractZipEntryToFile(pArchive, pEntry, fd); + if (ok) { + ok = (fsync(fd) == 0); + } + if (close(fd) != 0) { + ok = false; + } + if (!ok) { + LOGE("Error extracting \"%s\"\n", targetFile); + ok = false; + break; + } + + if (timestamp != NULL && utime(targetFile, timestamp)) { + LOGE("Error touching \"%s\"\n", targetFile); + ok = false; + break; + } + + LOGV("Extracted file \"%s\"\n", targetFile); + ++extractCount; + } + + if (callback != NULL) callback(targetFile, cookie); + } + + LOGV("Extracted %d file(s)\n", extractCount); + + free(helper.buf); + free(zpath); + + return ok; +}
diff --git a/recovery/minzip/Zip.h b/recovery/minzip/Zip.h new file mode 100644 index 0000000..c932c11 --- /dev/null +++ b/recovery/minzip/Zip.h
@@ -0,0 +1,171 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Simple Zip archive support. + */ +#ifndef _MINZIP_ZIP +#define _MINZIP_ZIP + +#include "inline_magic.h" + +#include <stdlib.h> +#include <utime.h> + +#include "Hash.h" +#include "SysUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct selabel_handle; + +/* + * One entry in the Zip archive. Treat this as opaque -- use accessors below. + * + * TODO: we're now keeping the pages mapped so we don't have to copy the + * filename. We can change the accessors to retrieve the various pieces + * directly from the source file instead of copying them out, for a very + * slight speed hit and a modest reduction in memory usage. + */ +typedef struct ZipEntry { + unsigned int fileNameLen; + const char* fileName; // not null-terminated + uint32_t offset; + uint32_t compLen; + uint32_t uncompLen; + int compression; + long modTime; + long crc32; + int versionMadeBy; + long externalFileAttributes; +} ZipEntry; + +/* + * One Zip archive. Treat as opaque. + */ +typedef struct ZipArchive { + unsigned int numEntries; + ZipEntry* pEntries; + HashTable* pHash; // maps file name to ZipEntry + unsigned char* addr; + size_t length; +} ZipArchive; + +/* + * Represents a non-NUL-terminated string, + * which is how entry names are stored. + */ +typedef struct { + const char *str; + size_t len; +} UnterminatedString; + +/* + * Open a Zip archive. + * + * On success, returns 0 and populates "pArchive". Returns nonzero errno + * value on failure. + */ +int mzOpenZipArchive(unsigned char* addr, size_t length, ZipArchive* pArchive); + +/* + * Close archive, releasing resources associated with it. + * + * Depending on the implementation this could unmap pages used by classes + * stored in a Jar. This should only be done after unloading classes. + */ +void mzCloseZipArchive(ZipArchive* pArchive); + + +/* + * Find an entry in the Zip archive, by name. + */ +const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive, + const char* entryName); + +INLINE uint32_t mzGetZipEntryOffset(const ZipEntry* pEntry) { + return pEntry->offset; +} +INLINE uint32_t mzGetZipEntryUncompLen(const ZipEntry* pEntry) { + return pEntry->uncompLen; +} + +/* + * Type definition for the callback function used by + * mzProcessZipEntryContents(). + */ +typedef bool (*ProcessZipEntryContentsFunction)(const unsigned char *data, + int dataLen, void *cookie); + +/* + * Stream the uncompressed data through the supplied function, + * passing cookie to it each time it gets called. processFunction + * may be called more than once. + * + * If processFunction returns false, the operation is abandoned and + * mzProcessZipEntryContents() immediately returns false. + * + * This is useful for calculating the hash of an entry's uncompressed contents. + */ +bool mzProcessZipEntryContents(const ZipArchive *pArchive, + const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, + void *cookie); + +/* + * Read an entry into a buffer allocated by the caller. + */ +bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry, + char* buf, int bufLen); + +/* + * Inflate and write an entry to a file. + */ +bool mzExtractZipEntryToFile(const ZipArchive *pArchive, + const ZipEntry *pEntry, int fd); + +/* + * Inflate and write an entry to a memory buffer, which must be long + * enough to hold mzGetZipEntryUncomplen(pEntry) bytes. + */ +bool mzExtractZipEntryToBuffer(const ZipArchive *pArchive, + const ZipEntry *pEntry, unsigned char* buffer); + +/* + * Inflate all files under zipDir to the directory specified by + * targetDir, which must exist and be a writable directory. + * + * Directory entries and symlinks are not extracted. + * + * + * The immediate children of zipDir will become the immediate + * children of targetDir; e.g., if the archive contains the entries + * + * a/b/c/one + * a/b/c/two + * a/b/c/d/three + * + * and mzExtractRecursive(a, "a/b/c", "/tmp", ...) is called, the resulting + * files will be + * + * /tmp/one + * /tmp/two + * /tmp/d/three + * + * If timestamp is non-NULL, file timestamps will be set accordingly. + * + * If callback is non-NULL, it will be invoked with each unpacked file. + * + * Returns true on success, false on failure. + */ +bool mzExtractRecursive(const ZipArchive *pArchive, + const char *zipDir, const char *targetDir, + const struct utimbuf *timestamp, + void (*callback)(const char *fn, void*), void *cookie, + struct selabel_handle *sehnd); + +#ifdef __cplusplus +} +#endif + +#endif /*_MINZIP_ZIP*/
diff --git a/recovery/minzip/inline_magic.h b/recovery/minzip/inline_magic.h new file mode 100644 index 0000000..59c659f --- /dev/null +++ b/recovery/minzip/inline_magic.h
@@ -0,0 +1,26 @@ +/* + * 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_INLINE_MAGIC_H_ +#define MINZIP_INLINE_MAGIC_H_ + +#ifndef MINZIP_GENERATE_INLINES +#define INLINE extern inline __attribute((__gnu_inline__)) +#else +#define INLINE +#endif + +#endif // MINZIP_INLINE_MAGIC_H_
diff --git a/recovery/mounts.cpp b/recovery/mounts.cpp new file mode 100644 index 0000000..f23376b --- /dev/null +++ b/recovery/mounts.cpp
@@ -0,0 +1,89 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mounts.h" + +#include <errno.h> +#include <fcntl.h> +#include <mntent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mount.h> + +#include <string> +#include <vector> + +struct MountedVolume { + std::string device; + std::string mount_point; + std::string filesystem; + std::string flags; +}; + +std::vector<MountedVolume*> g_mounts_state; + +bool scan_mounted_volumes() { + for (size_t i = 0; i < g_mounts_state.size(); ++i) { + delete g_mounts_state[i]; + } + g_mounts_state.clear(); + + // Open and read mount table entries. + FILE* fp = setmntent("/proc/mounts", "re"); + if (fp == NULL) { + return false; + } + mntent* e; + while ((e = getmntent(fp)) != NULL) { + MountedVolume* v = new MountedVolume; + v->device = e->mnt_fsname; + v->mount_point = e->mnt_dir; + v->filesystem = e->mnt_type; + v->flags = e->mnt_opts; + g_mounts_state.push_back(v); + } + endmntent(fp); + return true; +} + +MountedVolume* find_mounted_volume_by_device(const char* device) { + for (size_t i = 0; i < g_mounts_state.size(); ++i) { + if (g_mounts_state[i]->device == device) return g_mounts_state[i]; + } + return nullptr; +} + +MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point) { + for (size_t i = 0; i < g_mounts_state.size(); ++i) { + if (g_mounts_state[i]->mount_point == mount_point) return g_mounts_state[i]; + } + return nullptr; +} + +int unmount_mounted_volume(MountedVolume* volume) { + // Intentionally pass the empty string to umount if the caller tries + // to unmount a volume they already unmounted using this + // function. + std::string mount_point = volume->mount_point; + volume->mount_point.clear(); + return umount(mount_point.c_str()); +} + +int remount_read_only(MountedVolume* volume) { + return mount(volume->device.c_str(), volume->mount_point.c_str(), volume->filesystem.c_str(), + MS_NOATIME | MS_NODEV | MS_NODIRATIME | MS_RDONLY | MS_REMOUNT, 0); +}
diff --git a/recovery/mounts.h b/recovery/mounts.h new file mode 100644 index 0000000..1b76703 --- /dev/null +++ b/recovery/mounts.h
@@ -0,0 +1,32 @@ +/* + * 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 MOUNTS_H_ +#define MOUNTS_H_ + +struct MountedVolume; + +bool scan_mounted_volumes(); + +MountedVolume* find_mounted_volume_by_device(const char* device); + +MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point); + +int unmount_mounted_volume(MountedVolume* volume); + +int remount_read_only(MountedVolume* volume); + +#endif
diff --git a/recovery/otafault/Android.mk b/recovery/otafault/Android.mk new file mode 100644 index 0000000..50e385e --- /dev/null +++ b/recovery/otafault/Android.mk
@@ -0,0 +1,49 @@ +# 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) + +# otafault (static library) +# =============================== +include $(CLEAR_VARS) + +otafault_static_libs := \ + libbase \ + libminzip \ + libz \ + libselinux + +LOCAL_SRC_FILES := config.cpp ota_io.cpp +LOCAL_MODULE := libotafault +LOCAL_CLANG := true +LOCAL_C_INCLUDES := bootable/recovery +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH) +LOCAL_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 := \ + libotafault \ + $(otafault_static_libs) +LOCAL_C_INCLUDES := bootable/recovery +LOCAL_FORCE_STATIC_EXECUTABLE := true + +include $(BUILD_EXECUTABLE)
diff --git a/recovery/otafault/config.cpp b/recovery/otafault/config.cpp new file mode 100644 index 0000000..b456739 --- /dev/null +++ b/recovery/otafault/config.cpp
@@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <map> +#include <string> + +#include <stdio.h> +#include <unistd.h> + +#include <android-base/stringprintf.h> + +#include "minzip/Zip.h" +#include "config.h" +#include "ota_io.h" + +#define OTAIO_MAX_FNAME_SIZE 128 + +static ZipArchive* archive; +static std::map<std::string, bool> should_inject_cache; + +static std::string get_type_path(const char* io_type) { + return android::base::StringPrintf("%s/%s", OTAIO_BASE_DIR, io_type); +} + +void ota_io_init(ZipArchive* za) { + archive = za; + ota_set_fault_files(); +} + +bool should_fault_inject(const char* io_type) { + // archive will be NULL if we used an entry point other + // than updater/updater.cpp:main + if (archive == NULL) { + return false; + } + const std::string type_path = get_type_path(io_type); + if (should_inject_cache.find(type_path) != should_inject_cache.end()) { + return should_inject_cache[type_path]; + } + const ZipEntry* entry = mzFindZipEntry(archive, type_path.c_str()); + should_inject_cache[type_path] = entry != nullptr; + return entry != NULL; +} + +bool should_hit_cache() { + return should_fault_inject(OTAIO_CACHE); +} + +std::string fault_fname(const char* io_type) { + std::string type_path = get_type_path(io_type); + std::string fname; + fname.resize(OTAIO_MAX_FNAME_SIZE); + const ZipEntry* entry = mzFindZipEntry(archive, type_path.c_str()); + mzReadZipEntry(archive, entry, &fname[0], OTAIO_MAX_FNAME_SIZE); + return fname; +}
diff --git a/recovery/otafault/config.h b/recovery/otafault/config.h new file mode 100644 index 0000000..4430be3 --- /dev/null +++ b/recovery/otafault/config.h
@@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Read configuration files in the OTA package to determine which files, if any, will trigger errors. + * + * OTA packages can be modified to trigger errors by adding a top-level + * directory called .libotafault, which may optionally contain up to three + * files called READ, WRITE, and FSYNC. Each one of these optional files + * contains the name of a single file on the device disk which will cause + * an IO error on the first call of the appropriate I/O action to that file. + * + * Example: + * ota.zip + * <normal package contents> + * .libotafault + * WRITE + * + * If the contents of the file WRITE were /system/build.prop, the first write + * action to /system/build.prop would fail with EIO. Note that READ and + * FSYNC files are absent, so these actions will not cause an error. + */ + +#ifndef _UPDATER_OTA_IO_CFG_H_ +#define _UPDATER_OTA_IO_CFG_H_ + +#include <string> + +#include <stdbool.h> + +#include "minzip/Zip.h" + +#define OTAIO_BASE_DIR ".libotafault" +#define OTAIO_READ "READ" +#define OTAIO_WRITE "WRITE" +#define OTAIO_FSYNC "FSYNC" +#define OTAIO_CACHE "CACHE" + +/* + * Initialize libotafault by providing a reference to the OTA package. + */ +void ota_io_init(ZipArchive* za); + +/* + * Return true if a config file is present for the given IO type. + */ +bool should_fault_inject(const char* io_type); + +/* + * Return true if an EIO should occur on the next hit to /cache/saved.file + * instead of the next hit to the specified file. + */ +bool should_hit_cache(); + +/* + * Return the name of the file that should cause an error for the + * given IO type. + */ +std::string fault_fname(const char* io_type); + +#endif
diff --git a/recovery/otafault/ota_io.cpp b/recovery/otafault/ota_io.cpp new file mode 100644 index 0000000..cb37d51 --- /dev/null +++ b/recovery/otafault/ota_io.cpp
@@ -0,0 +1,176 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <map> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "config.h" +#include "ota_io.h" + +static std::map<intptr_t, const char*> filename_cache; +static std::string read_fault_file_name = ""; +static std::string write_fault_file_name = ""; +static std::string fsync_fault_file_name = ""; +bool have_eio_error = false; + +static bool get_hit_file(const char* cached_path, const std::string& ffn) { + return should_hit_cache() + ? !strncmp(cached_path, OTAIO_CACHE_FNAME, strlen(cached_path)) + : !strncmp(cached_path, ffn.c_str(), strlen(cached_path)); +} + +void ota_set_fault_files() { + if (should_fault_inject(OTAIO_READ)) { + read_fault_file_name = fault_fname(OTAIO_READ); + } + if (should_fault_inject(OTAIO_WRITE)) { + write_fault_file_name = fault_fname(OTAIO_WRITE); + } + if (should_fault_inject(OTAIO_FSYNC)) { + fsync_fault_file_name = fault_fname(OTAIO_FSYNC); + } +} + +int ota_open(const char* path, int oflags) { + // Let the caller handle errors; we do not care if open succeeds or fails + int fd = open(path, oflags); + filename_cache[fd] = path; + return fd; +} + +int ota_open(const char* path, int oflags, mode_t mode) { + int fd = open(path, oflags, mode); + filename_cache[fd] = path; + return fd; } + +FILE* ota_fopen(const char* path, const char* mode) { + FILE* fh = fopen(path, mode); + filename_cache[(intptr_t)fh] = path; + return fh; +} + +int ota_close(int fd) { + // descriptors can be reused, so make sure not to leave them in the cache + filename_cache.erase(fd); + return close(fd); +} + +int ota_fclose(FILE* fh) { + filename_cache.erase((intptr_t)fh); + return fclose(fh); +} + +size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream) { + if (should_fault_inject(OTAIO_READ)) { + auto cached = filename_cache.find((intptr_t)stream); + const char* cached_path = cached->second; + if (cached != filename_cache.end() && + get_hit_file(cached_path, read_fault_file_name)) { + read_fault_file_name = ""; + errno = EIO; + have_eio_error = true; + return 0; + } + } + size_t status = fread(ptr, size, nitems, stream); + // If I/O error occurs, set the retry-update flag. + if (status != nitems && errno == EIO) { + have_eio_error = true; + } + return status; +} + +ssize_t ota_read(int fd, void* buf, size_t nbyte) { + if (should_fault_inject(OTAIO_READ)) { + auto cached = filename_cache.find(fd); + const char* cached_path = cached->second; + if (cached != filename_cache.end() + && get_hit_file(cached_path, read_fault_file_name)) { + read_fault_file_name = ""; + errno = EIO; + have_eio_error = true; + return -1; + } + } + ssize_t status = read(fd, buf, nbyte); + if (status == -1 && errno == EIO) { + have_eio_error = true; + } + return status; +} + +size_t ota_fwrite(const void* ptr, size_t size, size_t count, FILE* stream) { + if (should_fault_inject(OTAIO_WRITE)) { + auto cached = filename_cache.find((intptr_t)stream); + const char* cached_path = cached->second; + if (cached != filename_cache.end() && + get_hit_file(cached_path, write_fault_file_name)) { + write_fault_file_name = ""; + errno = EIO; + have_eio_error = true; + return 0; + } + } + size_t status = fwrite(ptr, size, count, stream); + if (status != count && errno == EIO) { + have_eio_error = true; + } + return status; +} + +ssize_t ota_write(int fd, const void* buf, size_t nbyte) { + if (should_fault_inject(OTAIO_WRITE)) { + auto cached = filename_cache.find(fd); + const char* cached_path = cached->second; + if (cached != filename_cache.end() && + get_hit_file(cached_path, write_fault_file_name)) { + write_fault_file_name = ""; + errno = EIO; + have_eio_error = true; + return -1; + } + } + ssize_t status = write(fd, buf, nbyte); + if (status == -1 && errno == EIO) { + have_eio_error = true; + } + return status; +} + +int ota_fsync(int fd) { + if (should_fault_inject(OTAIO_FSYNC)) { + auto cached = filename_cache.find(fd); + const char* cached_path = cached->second; + if (cached != filename_cache.end() && + get_hit_file(cached_path, fsync_fault_file_name)) { + fsync_fault_file_name = ""; + errno = EIO; + have_eio_error = true; + return -1; + } + } + int status = fsync(fd); + if (status == -1 && errno == EIO) { + have_eio_error = true; + } + return status; +} +
diff --git a/recovery/otafault/ota_io.h b/recovery/otafault/ota_io.h new file mode 100644 index 0000000..84187a7 --- /dev/null +++ b/recovery/otafault/ota_io.h
@@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Provide a series of proxy functions for basic file accessors. + * The behavior of these functions can be changed to return different + * errors under a variety of conditions. + */ + +#ifndef _UPDATER_OTA_IO_H_ +#define _UPDATER_OTA_IO_H_ + +#include <stdio.h> +#include <sys/stat.h> + +#define OTAIO_CACHE_FNAME "/cache/saved.file" + +void ota_set_fault_files(); + +int ota_open(const char* path, int oflags); + +int ota_open(const char* path, int oflags, mode_t mode); + +FILE* ota_fopen(const char* filename, const char* mode); + +int ota_close(int fd); + +int ota_fclose(FILE* fh); + +size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream); + +ssize_t ota_read(int fd, void* buf, size_t nbyte); + +size_t ota_fwrite(const void* ptr, size_t size, size_t count, FILE* stream); + +ssize_t ota_write(int fd, const void* buf, size_t nbyte); + +int ota_fsync(int fd); + +#endif
diff --git a/recovery/otafault/test.cpp b/recovery/otafault/test.cpp new file mode 100644 index 0000000..6514782 --- /dev/null +++ b/recovery/otafault/test.cpp
@@ -0,0 +1,34 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <unistd.h> + +#include "ota_io.h" + +int main(int /* argc */, char** /* argv */) { + int fd = open("testdata/test.file", O_RDWR); + char buf[8]; + const char* out = "321"; + int readv = ota_read(fd, buf, 4); + printf("Read returned %d\n", readv); + int writev = ota_write(fd, out, 4); + printf("Write returned %d\n", writev); + close(fd); + return 0; +}
diff --git a/recovery/print_sha1.h b/recovery/print_sha1.h new file mode 100644 index 0000000..fa3d7e0 --- /dev/null +++ b/recovery/print_sha1.h
@@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RECOVERY_PRINT_SHA1_H +#define RECOVERY_PRINT_SHA1_H + +#include <stdint.h> +#include <string> + +#include "openssl/sha.h" + +static std::string print_sha1(const uint8_t sha1[SHA_DIGEST_LENGTH], size_t len) { + const char* hex = "0123456789abcdef"; + std::string result = ""; + for (size_t i = 0; i < len; ++i) { + result.push_back(hex[(sha1[i]>>4) & 0xf]); + result.push_back(hex[sha1[i] & 0xf]); + } + return result; +} + +static std::string print_sha1(const uint8_t sha1[SHA_DIGEST_LENGTH]) { + return print_sha1(sha1, SHA_DIGEST_LENGTH); +} + +static std::string short_sha1(const uint8_t sha1[SHA_DIGEST_LENGTH]) { + return print_sha1(sha1, 4); +} + +#endif // RECOVERY_PRINT_SHA1_H
diff --git a/recovery/recovery-persist.cpp b/recovery/recovery-persist.cpp new file mode 100644 index 0000000..25df03f --- /dev/null +++ b/recovery/recovery-persist.cpp
@@ -0,0 +1,205 @@ +/* + * 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. + */ + +#define LOG_TAG "recovery-persist" + +// +// Strictly to deal with reboot into system after OTA after /data +// mounts to pull the last pmsg file data and place it +// into /data/misc/recovery/ directory, rotating it in. +// +// Usage: recovery-persist [--force-persist] +// +// On systems without /cache mount, all file content representing in the +// recovery/ directory stored in /sys/fs/pstore/pmsg-ramoops-0 in logger +// format that reside in the LOG_ID_SYSTEM buffer at ANDROID_LOG_INFO +// priority or higher is transfered to the /data/misc/recovery/ directory. +// The content is matched and rotated in as need be. +// +// --force-persist ignore /cache mount, always rotate in the contents. +// + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <string> + +#include <android/log.h> /* Android Log Priority Tags */ +#include <android-base/file.h> +#include <log/log.h> +#include <log/logger.h> /* Android Log packet format */ +#include <private/android_logger.h> /* private pmsg functions */ + +static const char *LAST_LOG_FILE = "/data/misc/recovery/last_log"; +static const char *LAST_PMSG_FILE = "/sys/fs/pstore/pmsg-ramoops-0"; +static const char *LAST_KMSG_FILE = "/data/misc/recovery/last_kmsg"; +static const char *LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops-0"; +static const char *ALT_LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops"; + +static const int KEEP_LOG_COUNT = 10; + +// close a file, log an error if the error indicator is set +static void check_and_fclose(FILE *fp, const char *name) { + fflush(fp); + if (ferror(fp)) SLOGE("%s %s", name, strerror(errno)); + fclose(fp); +} + +static void copy_file(const char* source, const char* destination) { + FILE* dest_fp = fopen(destination, "w"); + if (dest_fp == nullptr) { + SLOGE("%s %s", destination, strerror(errno)); + } else { + FILE* source_fp = fopen(source, "r"); + if (source_fp != nullptr) { + char buf[4096]; + size_t bytes; + while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) { + fwrite(buf, 1, bytes, dest_fp); + } + check_and_fclose(source_fp, source); + } + check_and_fclose(dest_fp, destination); + } +} + +static bool rotated = false; + +// 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. +static void rotate_logs(int max) { + // Logs should only be rotated once. + + if (rotated) { + return; + } + rotated = true; + + for (int i = max-1; i >= 0; --i) { + std::string old_log(LAST_LOG_FILE); + if (i > 0) { + old_log += "." + std::to_string(i); + } + std::string new_log(LAST_LOG_FILE); + new_log += "." + std::to_string(i+1); + + // Ignore errors if old_log doesn't exist. + rename(old_log.c_str(), new_log.c_str()); + + std::string old_kmsg(LAST_KMSG_FILE); + if (i > 0) { + old_kmsg += "." + std::to_string(i); + } + std::string new_kmsg(LAST_KMSG_FILE); + new_kmsg += "." + std::to_string(i+1); + + rename(old_kmsg.c_str(), new_kmsg.c_str()); + } +} + +ssize_t logsave( + log_id_t /* logId */, + char /* prio */, + const char *filename, + const char *buf, size_t len, + void * /* arg */) { + + std::string destination("/data/misc/"); + destination += filename; + + std::string buffer(buf, len); + + { + std::string content; + android::base::ReadFileToString(destination, &content); + + if (buffer.compare(content) == 0) { + return len; + } + } + + // ToDo: Any others that match? Are we pulling in multiple + // already-rotated files? Algorithm thus far is KISS: one file, + // one rotation allowed. + + rotate_logs(KEEP_LOG_COUNT); + + return android::base::WriteStringToFile(buffer, destination.c_str()); +} + +int main(int argc, char **argv) { + + /* Is /cache a mount?, we have been delivered where we are not wanted */ + /* + * Following code halves the size of the executable as compared to: + * + * load_volume_table(); + * has_cache = volume_for_path(CACHE_ROOT) != nullptr; + */ + bool has_cache = false; + static const char mounts_file[] = "/proc/mounts"; + FILE *fp = fopen(mounts_file, "r"); + if (!fp) { + SLOGV("%s %s", mounts_file, strerror(errno)); + } else { + char *line = NULL; + size_t len = 0; + ssize_t read; + while ((read = getline(&line, &len, fp)) != -1) { + if (strstr(line, " /cache ")) { + has_cache = true; + break; + } + } + free(line); + fclose(fp); + } + + if (has_cache) { + /* + * TBD: Future location to move content from + * /cache/recovery to /data/misc/recovery/ + */ + /* if --force-persist flag, then transfer pmsg data anyways */ + if ((argc <= 1) || !argv[1] || strcmp(argv[1], "--force-persist")) { + return 0; + } + } + + /* Is there something in pmsg? */ + if (access(LAST_PMSG_FILE, R_OK)) { + return 0; + } + + // Take last pmsg file contents and send it off to the logsave + __android_log_pmsg_file_read( + LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", logsave, NULL); + + /* Is there a last console log too? */ + if (rotated) { + if (!access(LAST_CONSOLE_FILE, R_OK)) { + copy_file(LAST_CONSOLE_FILE, LAST_KMSG_FILE); + } else if (!access(ALT_LAST_CONSOLE_FILE, R_OK)) { + copy_file(ALT_LAST_CONSOLE_FILE, LAST_KMSG_FILE); + } + } + + return 0; +}
diff --git a/recovery/recovery-persist.rc b/recovery/recovery-persist.rc new file mode 100644 index 0000000..6761627 --- /dev/null +++ b/recovery/recovery-persist.rc
@@ -0,0 +1,3 @@ +on post-fs-data + mkdir /data/misc/recovery 0770 system log + exec - system log -- /system/bin/recovery-persist
diff --git a/recovery/recovery-refresh.cpp b/recovery/recovery-refresh.cpp new file mode 100644 index 0000000..b75c915 --- /dev/null +++ b/recovery/recovery-refresh.cpp
@@ -0,0 +1,128 @@ +/* + * 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. + */ + +#define LOG_TAG "recovery-refresh" + +// +// Strictly to deal with reboot into system after OTA, then +// reboot while in system before boot complete landing us back +// into recovery to continue with any mitigations with retained +// log history. This simply refreshes the pmsg files from +// the last pmsg file contents. +// +// Usage: +// recovery-refresh [--force-rotate|--rotate] +// +// All file content representing in the recovery/ directory stored in +// /sys/fs/pstore/pmsg-ramoops-0 in logger format that reside in the +// LOG_ID_SYSTEM buffer at ANDROID_LOG_INFO priority or higher is +// refreshed into /dev/pmsg0. This ensures that an unexpected reboot +// before recovery-persist is run will still contain the associated +// pmsg Android Logger content. +// +// --force-rotate recovery/last_kmsg and recovery.last_log files are +// rotated with .<number> suffixes upwards. +// --rotate rotated only if rocovery/last_msg or recovery/last_log +// exist, otherwise perform 1:1 refresh. +// + +#include <string.h> + +#include <string> + +#include <android/log.h> /* Android Log Priority Tags */ +#include <log/logger.h> /* Android Log packet format */ +#include <private/android_logger.h> /* private pmsg functions */ + +static const char LAST_KMSG_FILE[] = "recovery/last_kmsg"; +static const char LAST_LOG_FILE[] = "recovery/last_log"; + +static ssize_t logbasename( + log_id_t /* logId */, + char /* prio */, + const char *filename, + const char * /* buf */, size_t len, + void *arg) { + if (strstr(LAST_KMSG_FILE, filename) || + strstr(LAST_LOG_FILE, filename)) { + bool *doRotate = reinterpret_cast<bool *>(arg); + *doRotate = true; + } + return len; +} + +static ssize_t logrotate( + log_id_t logId, + char prio, + const char *filename, + const char *buf, size_t len, + void *arg) { + bool *doRotate = reinterpret_cast<bool *>(arg); + if (!*doRotate) { + return __android_log_pmsg_file_write(logId, prio, filename, buf, len); + } + + std::string name(filename); + size_t dot = name.find_last_of('.'); + std::string sub = name.substr(0, dot); + + if (!strstr(LAST_KMSG_FILE, sub.c_str()) && + !strstr(LAST_LOG_FILE, sub.c_str())) { + return __android_log_pmsg_file_write(logId, prio, filename, buf, len); + } + + // filename rotation + if (dot == std::string::npos) { + name += ".1"; + } else { + std::string number = name.substr(dot + 1); + if (!isdigit(number.data()[0])) { + name += ".1"; + } else { + auto i = std::stoull(number); + name = sub + "." + std::to_string(i + 1); + } + } + + return __android_log_pmsg_file_write(logId, prio, name.c_str(), buf, len); +} + +int main(int argc, char **argv) { + static const char filter[] = "recovery/"; + static const char force_rotate_flag[] = "--force-rotate"; + static const char rotate_flag[] = "--rotate"; + ssize_t ret; + bool doRotate = false; + + // Take last pmsg contents and rewrite it to the current pmsg session. + if ((argc <= 1) || !argv[1] || + (((doRotate = strcmp(argv[1], rotate_flag))) && + strcmp(argv[1], force_rotate_flag))) { + doRotate = false; + } else if (!doRotate) { + // Do we need to rotate? + __android_log_pmsg_file_read( + LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, + logbasename, &doRotate); + } + + // Take action to refresh pmsg contents + ret = __android_log_pmsg_file_read( + LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, + logrotate, &doRotate); + + return (ret < 0) ? ret : 0; +}
diff --git a/recovery/recovery-refresh.rc b/recovery/recovery-refresh.rc new file mode 100644 index 0000000..14b05cc --- /dev/null +++ b/recovery/recovery-refresh.rc
@@ -0,0 +1,2 @@ +on post-fs + exec - system log -- /system/bin/recovery-refresh
diff --git a/recovery/recovery.cpp b/recovery/recovery.cpp new file mode 100644 index 0000000..0dfcce0 --- /dev/null +++ b/recovery/recovery.cpp
@@ -0,0 +1,1585 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <inttypes.h> +#include <limits.h> +#include <linux/fs.h> +#include <linux/input.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/klog.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +#include <chrono> +#include <string> +#include <vector> + +#include <adb.h> +#include <android/log.h> /* Android Log Priority Tags */ +#include <android-base/file.h> +#include <android-base/parseint.h> +#include <android-base/stringprintf.h> +#include <android-base/strings.h> +#include <android-base/unique_fd.h> +#include <cutils/android_reboot.h> +#include <cutils/properties.h> +#include <healthd/BatteryMonitor.h> +#include <log/logger.h> /* Android Log packet format */ +#include <private/android_logger.h> /* private pmsg functions */ +#include <selinux/label.h> +#include <selinux/selinux.h> + +#include "adb_install.h" +#include "bootloader.h" +#include "common.h" +#include "device.h" +#include "fuse_sdcard_provider.h" +#include "fuse_sideload.h" +#include "install.h" +#include "minui/minui.h" +#include "minzip/DirUtil.h" +#include "roots.h" +#include "ui.h" +#include "screen_ui.h" + +struct selabel_handle *sehandle; + +static const struct option OPTIONS[] = { + { "update_package", required_argument, NULL, 'u' }, + { "retry_count", required_argument, NULL, 'n' }, + { "wipe_data", no_argument, NULL, 'w' }, + { "wipe_cache", no_argument, NULL, 'c' }, + { "show_text", no_argument, NULL, 't' }, + { "sideload", no_argument, NULL, 's' }, + { "sideload_auto_reboot", no_argument, NULL, 'a' }, + { "just_exit", no_argument, NULL, 'x' }, + { "locale", required_argument, NULL, 'l' }, + { "stages", required_argument, NULL, 'g' }, + { "shutdown_after", no_argument, NULL, 'p' }, + { "reason", required_argument, NULL, 'r' }, + { "brick", no_argument, NULL, 0 }, + { NULL, 0, NULL, 0 }, +}; + +static const char *CACHE_LOG_DIR = "/cache/recovery"; +static const char *COMMAND_FILE = "/cache/recovery/command"; +static const char *LOG_FILE = "/cache/recovery/log"; +static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install"; +static const char *LOCALE_FILE = "/cache/recovery/last_locale"; +static const char *CACHE_ROOT = "/cache"; +static const char *SDCARD_ROOT = "/sdcard"; +static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; +static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install"; +static const char *LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; +static const char *LAST_LOG_FILE = "/cache/recovery/last_log"; +static const int KEEP_LOG_COUNT = 10; +static const int EIO_RETRY_COUNT = 2; +static const int BATTERY_READ_TIMEOUT_IN_SEC = 10; +// GmsCore enters recovery mode to install package when having enough battery +// percentage. Normally, the threshold is 40% without charger and 20% with charger. +// So we should check battery with a slightly lower limitation. +static const int BATTERY_OK_PERCENTAGE = 20; +static const int BATTERY_WITH_CHARGER_OK_PERCENTAGE = 15; +constexpr const char* RECOVERY_BRICK = "/etc/recovery.brick"; + +RecoveryUI* ui = NULL; +char* locale = NULL; +char* stage = NULL; +char* reason = NULL; +bool modified_flash = false; +static bool has_cache = false; + +/* + * The recovery tool communicates with the main system through /cache files. + * /cache/recovery/command - INPUT - command line for tool, one arg per line + * /cache/recovery/log - OUTPUT - combined log file from recovery run(s) + * + * The arguments which may be supplied in the recovery.command file: + * --update_package=path - verify install an OTA package file + * --wipe_data - erase user data (and cache), then reboot + * --wipe_cache - wipe cache (but not user data), then reboot + * --set_encrypted_filesystem=on|off - enables / diasables encrypted fs + * --just_exit - do nothing; exit and reboot + * + * After completing, we remove /cache/recovery/command and reboot. + * Arguments may also be supplied in the bootloader control block (BCB). + * These important scenarios must be safely restartable at any point: + * + * FACTORY RESET + * 1. user selects "factory reset" + * 2. main system writes "--wipe_data" to /cache/recovery/command + * 3. main system reboots into recovery + * 4. get_args() writes BCB with "boot-recovery" and "--wipe_data" + * -- after this, rebooting will restart the erase -- + * 5. erase_volume() reformats /data + * 6. erase_volume() reformats /cache + * 7. finish_recovery() erases BCB + * -- after this, rebooting will restart the main system -- + * 8. main() calls reboot() to boot main system + * + * OTA INSTALL + * 1. main system downloads OTA package to /cache/some-filename.zip + * 2. main system writes "--update_package=/cache/some-filename.zip" + * 3. main system reboots into recovery + * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..." + * -- after this, rebooting will attempt to reinstall the update -- + * 5. install_package() attempts to install the update + * NOTE: the package install must itself be restartable from any point + * 6. finish_recovery() erases BCB + * -- after this, rebooting will (try to) restart the main system -- + * 7. ** if install failed ** + * 7a. prompt_and_wait() shows an error icon and waits for the user + * 7b; the user reboots (pulling the battery, etc) into the main system + * 8. main() calls maybe_install_firmware_update() + * ** if the update contained radio/hboot firmware **: + * 8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache" + * -- after this, rebooting will reformat cache & restart main system -- + * 8b. m_i_f_u() writes firmware image into raw cache partition + * 8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache" + * -- after this, rebooting will attempt to reinstall firmware -- + * 8d. bootloader tries to flash firmware + * 8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache") + * -- after this, rebooting will reformat cache & restart main system -- + * 8f. erase_volume() reformats /cache + * 8g. finish_recovery() erases BCB + * -- after this, rebooting will (try to) restart the main system -- + * 9. main() calls reboot() to boot main system + */ + +static const int MAX_ARG_LENGTH = 4096; +static const int MAX_ARGS = 100; + +// open a given path, mounting partitions as necessary +FILE* fopen_path(const char *path, const char *mode) { + if (ensure_path_mounted(path) != 0) { + LOGE("Can't mount %s\n", 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); + + FILE *fp = fopen(path, mode); + return fp; +} + +// close a file, log an error if the error indicator is set +static void check_and_fclose(FILE *fp, const char *name) { + fflush(fp); + if (ferror(fp)) LOGE("Error in %s\n(%s)\n", name, strerror(errno)); + fclose(fp); +} + +bool is_ro_debuggable() { + char value[PROPERTY_VALUE_MAX+1]; + return (property_get("ro.debuggable", value, NULL) == 1 && value[0] == '1'); +} + +static void redirect_stdio(const char* filename) { + int pipefd[2]; + if (pipe(pipefd) == -1) { + LOGE("pipe failed: %s\n", strerror(errno)); + + // Fall back to traditional logging mode without timestamps. + // If these fail, there's not really anywhere to complain... + freopen(filename, "a", stdout); setbuf(stdout, NULL); + freopen(filename, "a", stderr); setbuf(stderr, NULL); + + return; + } + + pid_t pid = fork(); + if (pid == -1) { + LOGE("fork failed: %s\n", strerror(errno)); + + // Fall back to traditional logging mode without timestamps. + // If these fail, there's not really anywhere to complain... + freopen(filename, "a", stdout); setbuf(stdout, NULL); + freopen(filename, "a", stderr); setbuf(stderr, NULL); + + return; + } + + if (pid == 0) { + /// Close the unused write end. + close(pipefd[1]); + + auto start = std::chrono::steady_clock::now(); + + // Child logger to actually write to the log file. + FILE* log_fp = fopen(filename, "a"); + if (log_fp == nullptr) { + LOGE("fopen \"%s\" failed: %s\n", filename, strerror(errno)); + close(pipefd[0]); + _exit(1); + } + + FILE* pipe_fp = fdopen(pipefd[0], "r"); + if (pipe_fp == nullptr) { + LOGE("fdopen failed: %s\n", strerror(errno)); + check_and_fclose(log_fp, filename); + close(pipefd[0]); + _exit(1); + } + + char* line = nullptr; + size_t len = 0; + while (getline(&line, &len, pipe_fp) != -1) { + auto now = std::chrono::steady_clock::now(); + double duration = std::chrono::duration_cast<std::chrono::duration<double>>( + now - start).count(); + if (line[0] == '\n') { + fprintf(log_fp, "[%12.6lf]\n", duration); + } else { + fprintf(log_fp, "[%12.6lf] %s", duration, line); + } + fflush(log_fp); + } + + LOGE("getline failed: %s\n", strerror(errno)); + + free(line); + check_and_fclose(log_fp, filename); + close(pipefd[0]); + _exit(1); + } else { + // Redirect stdout/stderr to the logger process. + // Close the unused read end. + close(pipefd[0]); + + setbuf(stdout, nullptr); + setbuf(stderr, nullptr); + + if (dup2(pipefd[1], STDOUT_FILENO) == -1) { + LOGE("dup2 stdout failed: %s\n", strerror(errno)); + } + if (dup2(pipefd[1], STDERR_FILENO) == -1) { + LOGE("dup2 stderr failed: %s\n", strerror(errno)); + } + + close(pipefd[1]); + } +} + +// command line args come from, in decreasing precedence: +// - the actual command line +// - the bootloader control block (one per line, after "recovery") +// - the contents of COMMAND_FILE (one per line) +static void +get_args(int *argc, char ***argv) { + struct bootloader_message boot; + memset(&boot, 0, sizeof(boot)); + get_bootloader_message(&boot); // this may fail, leaving a zeroed structure + stage = strndup(boot.stage, sizeof(boot.stage)); + + if (boot.command[0] != 0 && boot.command[0] != 255) { + LOGI("Boot command: %.*s\n", (int)sizeof(boot.command), boot.command); + } + + if (boot.status[0] != 0 && boot.status[0] != 255) { + LOGI("Boot status: %.*s\n", (int)sizeof(boot.status), boot.status); + } + + // --- if arguments weren't supplied, look in the bootloader control block + if (*argc <= 1) { + boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination + const char *arg = strtok(boot.recovery, "\n"); + if (arg != NULL && !strcmp(arg, "recovery")) { + *argv = (char **) malloc(sizeof(char *) * MAX_ARGS); + (*argv)[0] = strdup(arg); + for (*argc = 1; *argc < MAX_ARGS; ++*argc) { + if ((arg = strtok(NULL, "\n")) == NULL) break; + (*argv)[*argc] = strdup(arg); + } + LOGI("Got arguments from boot message\n"); + } else if (boot.recovery[0] != 0 && boot.recovery[0] != 255) { + LOGE("Bad boot message\n\"%.20s\"\n", boot.recovery); + } + } + + // --- if that doesn't work, try the command file (if we have /cache). + if (*argc <= 1 && has_cache) { + FILE *fp = fopen_path(COMMAND_FILE, "r"); + if (fp != NULL) { + char *token; + char *argv0 = (*argv)[0]; + *argv = (char **) malloc(sizeof(char *) * MAX_ARGS); + (*argv)[0] = argv0; // use the same program name + + char buf[MAX_ARG_LENGTH]; + for (*argc = 1; *argc < MAX_ARGS; ++*argc) { + if (!fgets(buf, sizeof(buf), fp)) break; + token = strtok(buf, "\r\n"); + if (token != NULL) { + (*argv)[*argc] = strdup(token); // Strip newline. + } else { + --*argc; + } + } + + check_and_fclose(fp, COMMAND_FILE); + LOGI("Got arguments from %s\n", COMMAND_FILE); + } + } + + // --> write the arguments we have back into the bootloader control block + // always boot into recovery after this (until finish_recovery() is called) + strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); + strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); + int i; + for (i = 1; i < *argc; ++i) { + strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery)); + strlcat(boot.recovery, "\n", sizeof(boot.recovery)); + } + set_bootloader_message(&boot); +} + +static void +set_sdcard_update_bootloader_message() { + struct bootloader_message boot; + memset(&boot, 0, sizeof(boot)); + strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); + strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); + set_bootloader_message(&boot); +} + +// Read from kernel log into buffer and write out to file. +static void save_kernel_log(const char* destination) { + int klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0); + if (klog_buf_len <= 0) { + LOGE("Error getting klog size: %s\n", strerror(errno)); + return; + } + + std::string buffer(klog_buf_len, 0); + int n = klogctl(KLOG_READ_ALL, &buffer[0], klog_buf_len); + if (n == -1) { + LOGE("Error in reading klog: %s\n", strerror(errno)); + return; + } + buffer.resize(n); + android::base::WriteStringToFile(buffer, destination); +} + +// write content to the current pmsg session. +static ssize_t __pmsg_write(const char *filename, const char *buf, size_t len) { + return __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO, + filename, buf, len); +} + +static void copy_log_file_to_pmsg(const char* source, const char* destination) { + std::string content; + android::base::ReadFileToString(source, &content); + __pmsg_write(destination, content.c_str(), content.length()); +} + +// How much of the temp log we have copied to the copy in cache. +static off_t tmplog_offset = 0; + +static void copy_log_file(const char* source, const char* destination, bool append) { + FILE* dest_fp = fopen_path(destination, append ? "a" : "w"); + if (dest_fp == nullptr) { + LOGE("Can't open %s\n", destination); + } else { + FILE* source_fp = fopen(source, "r"); + if (source_fp != nullptr) { + if (append) { + fseeko(source_fp, tmplog_offset, SEEK_SET); // Since last write + } + char buf[4096]; + size_t bytes; + while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) { + fwrite(buf, 1, bytes, dest_fp); + } + if (append) { + tmplog_offset = ftello(source_fp); + } + check_and_fclose(source_fp, source); + } + check_and_fclose(dest_fp, destination); + } +} + +// 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. +static void rotate_logs(int max) { + // Logs should only be rotated once. + static bool rotated = false; + if (rotated) { + return; + } + rotated = true; + ensure_path_mounted(LAST_LOG_FILE); + ensure_path_mounted(LAST_KMSG_FILE); + + for (int i = max-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()); + } +} + +static void copy_logs() { + // We only rotate and record the log of the current session if there are + // actual attempts to modify the flash, such as wipes, installs from BCB + // or menu selections. This is to avoid unnecessary rotation (and + // possible deletion) of log files, if it does not do anything loggable. + if (!modified_flash) { + return; + } + + // Always write to pmsg, this allows the OTA logs to be caught in logcat -L + copy_log_file_to_pmsg(TEMPORARY_LOG_FILE, LAST_LOG_FILE); + copy_log_file_to_pmsg(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE); + + // We can do nothing for now if there's no /cache partition. + if (!has_cache) { + return; + } + + rotate_logs(KEEP_LOG_COUNT); + + // Copy logs to cache so the system can find out what happened. + copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true); + copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false); + copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false); + save_kernel_log(LAST_KMSG_FILE); + chmod(LOG_FILE, 0600); + chown(LOG_FILE, 1000, 1000); // system user + chmod(LAST_KMSG_FILE, 0600); + chown(LAST_KMSG_FILE, 1000, 1000); // system user + chmod(LAST_LOG_FILE, 0640); + chmod(LAST_INSTALL_FILE, 0644); + sync(); +} + +// clear the recovery command and prepare to boot a (hopefully working) system, +// copy our log file to cache as well (for the system to read). This function is +// idempotent: call it as many times as you like. +static void +finish_recovery() { + // Save the locale to cache, so if recovery is next started up + // without a --locale argument (eg, directly from the bootloader) + // it will use the last-known locale. + if (locale != NULL) { + size_t len = strlen(locale); + __pmsg_write(LOCALE_FILE, locale, len); + if (has_cache) { + LOGI("Saving locale \"%s\"\n", locale); + FILE* fp = fopen_path(LOCALE_FILE, "w"); + if (fp != NULL) { + fwrite(locale, 1, len, fp); + fflush(fp); + fsync(fileno(fp)); + check_and_fclose(fp, LOCALE_FILE); + } + } + } + + copy_logs(); + + // Reset to normal system boot so recovery won't cycle indefinitely. + struct bootloader_message boot; + memset(&boot, 0, sizeof(boot)); + set_bootloader_message(&boot); + + // Remove the command file, so recovery won't repeat indefinitely. + if (has_cache) { + if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) { + LOGW("Can't unlink %s\n", COMMAND_FILE); + } + ensure_path_unmounted(CACHE_ROOT); + } + + sync(); // For good measure. +} + +typedef struct _saved_log_file { + char* name; + struct stat st; + unsigned char* data; + struct _saved_log_file* next; +} saved_log_file; + +static bool erase_volume(const char* volume) { + bool is_cache = (strcmp(volume, CACHE_ROOT) == 0); + + ui->SetBackground(RecoveryUI::ERASING); + ui->SetProgressType(RecoveryUI::INDETERMINATE); + + saved_log_file* head = NULL; + + if (is_cache) { + // If we're reformatting /cache, we load any past logs + // (i.e. "/cache/recovery/last_*") and the current log + // ("/cache/recovery/log") into memory, so we can restore them after + // the reformat. + + ensure_path_mounted(volume); + + DIR* d; + struct dirent* de; + d = opendir(CACHE_LOG_DIR); + if (d) { + char path[PATH_MAX]; + strcpy(path, CACHE_LOG_DIR); + strcat(path, "/"); + int path_len = strlen(path); + while ((de = readdir(d)) != NULL) { + if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) { + saved_log_file* p = (saved_log_file*) malloc(sizeof(saved_log_file)); + strcpy(path+path_len, de->d_name); + p->name = strdup(path); + if (stat(path, &(p->st)) == 0) { + // truncate files to 512kb + if (p->st.st_size > (1 << 19)) { + p->st.st_size = 1 << 19; + } + p->data = (unsigned char*) malloc(p->st.st_size); + FILE* f = fopen(path, "rb"); + fread(p->data, 1, p->st.st_size, f); + fclose(f); + p->next = head; + head = p; + } else { + free(p); + } + } + } + closedir(d); + } else { + if (errno != ENOENT) { + printf("opendir failed: %s\n", strerror(errno)); + } + } + } + + ui->Print("Formatting %s...\n", volume); + + ensure_path_unmounted(volume); + int result = format_volume(volume); + + if (is_cache) { + while (head) { + FILE* f = fopen_path(head->name, "wb"); + if (f) { + fwrite(head->data, 1, head->st.st_size, f); + fclose(f); + chmod(head->name, head->st.st_mode); + chown(head->name, head->st.st_uid, head->st.st_gid); + } + free(head->name); + free(head->data); + saved_log_file* temp = head->next; + free(head); + head = temp; + } + + // Any part of the log we'd copied to cache is now gone. + // Reset the pointer so we copy from the beginning of the temp + // log. + tmplog_offset = 0; + copy_logs(); + } + + return (result == 0); +} + +static int +get_menu_selection(const char* const * headers, const char* const * items, + int menu_only, int initial_selection, Device* device) { + // throw away keys pressed previously, so user doesn't + // accidentally trigger menu items. + ui->FlushKeys(); + + ui->StartMenu(headers, items, initial_selection); + int selected = initial_selection; + int chosen_item = -1; + + while (chosen_item < 0) { + int key = ui->WaitKey(); + int visible = ui->IsTextVisible(); + + if (key == -1) { // ui_wait_key() timed out + if (ui->WasTextEverVisible()) { + continue; + } else { + LOGI("timed out waiting for key input; rebooting.\n"); + ui->EndMenu(); + return 0; // XXX fixme + } + } + + int action = device->HandleMenuKey(key, visible); + + if (action < 0) { + switch (action) { + case Device::kHighlightUp: + selected = ui->SelectMenu(--selected); + break; + case Device::kHighlightDown: + selected = ui->SelectMenu(++selected); + break; + case Device::kInvokeItem: + chosen_item = selected; + break; + case Device::kNoAction: + break; + } + } else if (!menu_only) { + chosen_item = action; + } + } + + ui->EndMenu(); + return chosen_item; +} + +static int compare_string(const void* a, const void* b) { + return strcmp(*(const char**)a, *(const char**)b); +} + +// Returns a malloc'd path, or NULL. +static char* browse_directory(const char* path, Device* device) { + ensure_path_mounted(path); + + DIR* d = opendir(path); + if (d == NULL) { + LOGE("error opening %s: %s\n", path, strerror(errno)); + return NULL; + } + + int d_size = 0; + int d_alloc = 10; + char** dirs = (char**)malloc(d_alloc * sizeof(char*)); + int z_size = 1; + int z_alloc = 10; + char** zips = (char**)malloc(z_alloc * sizeof(char*)); + zips[0] = strdup("../"); + + struct dirent* de; + while ((de = readdir(d)) != NULL) { + int name_len = strlen(de->d_name); + + if (de->d_type == DT_DIR) { + // skip "." and ".." entries + if (name_len == 1 && de->d_name[0] == '.') continue; + if (name_len == 2 && de->d_name[0] == '.' && + de->d_name[1] == '.') continue; + + if (d_size >= d_alloc) { + d_alloc *= 2; + dirs = (char**)realloc(dirs, d_alloc * sizeof(char*)); + } + dirs[d_size] = (char*)malloc(name_len + 2); + strcpy(dirs[d_size], de->d_name); + dirs[d_size][name_len] = '/'; + dirs[d_size][name_len+1] = '\0'; + ++d_size; + } else if (de->d_type == DT_REG && + name_len >= 4 && + strncasecmp(de->d_name + (name_len-4), ".zip", 4) == 0) { + if (z_size >= z_alloc) { + z_alloc *= 2; + zips = (char**)realloc(zips, z_alloc * sizeof(char*)); + } + zips[z_size++] = strdup(de->d_name); + } + } + closedir(d); + + qsort(dirs, d_size, sizeof(char*), compare_string); + qsort(zips, z_size, sizeof(char*), compare_string); + + // append dirs to the zips list + if (d_size + z_size + 1 > z_alloc) { + z_alloc = d_size + z_size + 1; + zips = (char**)realloc(zips, z_alloc * sizeof(char*)); + } + memcpy(zips + z_size, dirs, d_size * sizeof(char*)); + free(dirs); + z_size += d_size; + zips[z_size] = NULL; + + const char* headers[] = { "Choose a package to install:", path, NULL }; + + char* result; + int chosen_item = 0; + while (true) { + chosen_item = get_menu_selection(headers, zips, 1, chosen_item, device); + + char* item = zips[chosen_item]; + int item_len = strlen(item); + if (chosen_item == 0) { // item 0 is always "../" + // go up but continue browsing (if the caller is update_directory) + result = NULL; + break; + } + + char new_path[PATH_MAX]; + strlcpy(new_path, path, PATH_MAX); + strlcat(new_path, "/", PATH_MAX); + strlcat(new_path, item, PATH_MAX); + + if (item[item_len-1] == '/') { + // recurse down into a subdirectory + new_path[strlen(new_path)-1] = '\0'; // truncate the trailing '/' + result = browse_directory(new_path, device); + if (result) break; + } else { + // selected a zip file: return the malloc'd path to the caller. + result = strdup(new_path); + break; + } + } + + for (int i = 0; i < z_size; ++i) free(zips[i]); + free(zips); + + return result; +} + +static bool yes_no(Device* device, const char* question1, const char* question2) { + const char* headers[] = { question1, question2, NULL }; + const char* items[] = { " No", " Yes", NULL }; + + int chosen_item = get_menu_selection(headers, items, 1, 0, device); + return (chosen_item == 1); +} + +// Return true on success. +static bool wipe_data(int should_confirm, Device* device) { + if (should_confirm && !yes_no(device, "Wipe all user data?", " THIS CAN NOT BE UNDONE!")) { + return false; + } + + modified_flash = true; + + ui->Print("\n-- Wiping data...\n"); + bool success = + device->PreWipeData() && + erase_volume("/data") && + (has_cache ? erase_volume("/cache") : true) && + device->PostWipeData(); + ui->Print("Data wipe %s.\n", success ? "complete" : "failed"); + return success; +} + +// Return true on success. +static bool wipe_cache(bool should_confirm, Device* device) { + if (!has_cache) { + ui->Print("No /cache partition found.\n"); + return false; + } + + if (should_confirm && !yes_no(device, "Wipe cache?", " THIS CAN NOT BE UNDONE!")) { + return false; + } + + modified_flash = true; + + ui->Print("\n-- Wiping cache...\n"); + bool success = erase_volume("/cache"); + ui->Print("Cache wipe %s.\n", success ? "complete" : "failed"); + return success; +} + +// Secure-wipe a given partition. It uses BLKSECDISCARD, if supported. +// Otherwise, it goes with BLKDISCARD (if device supports BLKDISCARDZEROES) or +// BLKZEROOUT. +static bool secure_wipe_partition(const std::string& partition) { + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY))); + if (fd == -1) { + LOGE("failed to open \"%s\": %s\n", partition.c_str(), strerror(errno)); + return false; + } + + uint64_t range[2] = {0, 0}; + if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) { + LOGE("failed to get partition size: %s\n", strerror(errno)); + return false; + } + printf("Secure-wiping \"%s\" from %" PRIu64 " to %" PRIu64 ".\n", + partition.c_str(), range[0], range[1]); + + printf("Trying BLKSECDISCARD...\t"); + if (ioctl(fd, BLKSECDISCARD, &range) == -1) { + printf("failed: %s\n", strerror(errno)); + + // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT. + unsigned int zeroes; + if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) { + printf("Trying BLKDISCARD...\t"); + if (ioctl(fd, BLKDISCARD, &range) == -1) { + printf("failed: %s\n", strerror(errno)); + return false; + } + } else { + printf("Trying BLKZEROOUT...\t"); + if (ioctl(fd, BLKZEROOUT, &range) == -1) { + printf("failed: %s\n", strerror(errno)); + return false; + } + } + } + + printf("done\n"); + return true; +} + +// Brick the current device, with a secure wipe of all the partitions in +// RECOVERY_BRICK. +static bool brick_device() { + ui->SetBackground(RecoveryUI::ERASING); + ui->SetProgressType(RecoveryUI::INDETERMINATE); + + std::string partition_list; + if (!android::base::ReadFileToString(RECOVERY_BRICK, &partition_list)) { + LOGE("failed to read \"%s\".\n", RECOVERY_BRICK); + return false; + } + + std::vector<std::string> lines = android::base::Split(partition_list, "\n"); + for (const std::string& line : lines) { + std::string partition = android::base::Trim(line); + // Ignore '#' comment or empty lines. + if (android::base::StartsWith(partition, "#") || partition.empty()) { + continue; + } + + // Proceed anyway even if it fails to wipe some partition. + secure_wipe_partition(partition); + } + return true; +} + +static void choose_recovery_file(Device* device) { + // "Back" + KEEP_LOG_COUNT * 2 + terminating nullptr entry + char* entries[1 + KEEP_LOG_COUNT * 2 + 1]; + memset(entries, 0, sizeof(entries)); + + unsigned int n = 0; + + if (has_cache) { + // Add LAST_LOG_FILE + LAST_LOG_FILE.x + // Add LAST_KMSG_FILE + LAST_KMSG_FILE.x + for (int i = 0; i < KEEP_LOG_COUNT; i++) { + char* log_file; + int ret; + ret = (i == 0) ? asprintf(&log_file, "%s", LAST_LOG_FILE) : + asprintf(&log_file, "%s.%d", LAST_LOG_FILE, i); + if (ret == -1) { + // memory allocation failure - return early. Should never happen. + return; + } + if ((ensure_path_mounted(log_file) != 0) || (access(log_file, R_OK) == -1)) { + free(log_file); + } else { + entries[n++] = log_file; + } + + char* kmsg_file; + ret = (i == 0) ? asprintf(&kmsg_file, "%s", LAST_KMSG_FILE) : + asprintf(&kmsg_file, "%s.%d", LAST_KMSG_FILE, i); + if (ret == -1) { + // memory allocation failure - return early. Should never happen. + return; + } + if ((ensure_path_mounted(kmsg_file) != 0) || (access(kmsg_file, R_OK) == -1)) { + free(kmsg_file); + } else { + entries[n++] = kmsg_file; + } + } + } else { + // If cache partition is not found, view /tmp/recovery.log instead. + ui->Print("No /cache partition found.\n"); + if (access(TEMPORARY_LOG_FILE, R_OK) == -1) { + return; + } else{ + entries[n++] = strdup(TEMPORARY_LOG_FILE); + } + } + + entries[n++] = strdup("Back"); + + const char* headers[] = { "Select file to view", nullptr }; + + while (true) { + int chosen_item = get_menu_selection(headers, entries, 1, 0, device); + if (strcmp(entries[chosen_item], "Back") == 0) break; + + ui->ShowFile(entries[chosen_item]); + } + + for (size_t i = 0; i < (sizeof(entries) / sizeof(*entries)); i++) { + free(entries[i]); + } +} + +// How long (in seconds) we wait for the fuse-provided package file to +// appear, before timing out. +#define SDCARD_INSTALL_TIMEOUT 10 + +static int apply_from_sdcard(Device* device, bool* wipe_cache) { + modified_flash = true; + + if (ensure_path_mounted(SDCARD_ROOT) != 0) { + ui->Print("\n-- Couldn't mount %s.\n", SDCARD_ROOT); + return INSTALL_ERROR; + } + + char* path = browse_directory(SDCARD_ROOT, device); + if (path == NULL) { + ui->Print("\n-- No package file selected.\n"); + ensure_path_unmounted(SDCARD_ROOT); + return INSTALL_ERROR; + } + + ui->Print("\n-- Install %s ...\n", path); + set_sdcard_update_bootloader_message(); + + // We used to use fuse in a thread as opposed to a process. Since accessing + // through fuse involves going from kernel to userspace to kernel, it leads + // to deadlock when a page fault occurs. (Bug: 26313124) + pid_t child; + if ((child = fork()) == 0) { + bool status = start_sdcard_fuse(path); + + _exit(status ? EXIT_SUCCESS : EXIT_FAILURE); + } + + // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the fuse in child + // process is ready. + int result = INSTALL_ERROR; + int status; + bool waited = false; + for (int i = 0; i < SDCARD_INSTALL_TIMEOUT; ++i) { + if (waitpid(child, &status, WNOHANG) == -1) { + result = INSTALL_ERROR; + waited = true; + break; + } + + struct stat sb; + if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &sb) == -1) { + if (errno == ENOENT && i < SDCARD_INSTALL_TIMEOUT-1) { + sleep(1); + continue; + } else { + LOGE("Timed out waiting for the fuse-provided package.\n"); + result = INSTALL_ERROR; + kill(child, SIGKILL); + break; + } + } + + result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, + TEMPORARY_INSTALL_FILE, false); + break; + } + + if (!waited) { + // Calling stat() on this magic filename signals the fuse + // filesystem to shut down. + struct stat sb; + stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &sb); + + waitpid(child, &status, 0); + } + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + LOGE("Error exit from the fuse process: %d\n", WEXITSTATUS(status)); + } + + ensure_path_unmounted(SDCARD_ROOT); + return result; +} + +// Return REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION +// means to take the default, which is to reboot or shutdown depending +// on if the --shutdown_after flag was passed to recovery. +static Device::BuiltinAction +prompt_and_wait(Device* device, int status) { + for (;;) { + finish_recovery(); + switch (status) { + case INSTALL_SUCCESS: + case INSTALL_NONE: + ui->SetBackground(RecoveryUI::NO_COMMAND); + break; + + case INSTALL_ERROR: + case INSTALL_CORRUPT: + ui->SetBackground(RecoveryUI::ERROR); + break; + } + ui->SetProgressType(RecoveryUI::EMPTY); + + int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), 0, 0, device); + + // device-specific code may take some action here. It may + // return one of the core actions handled in the switch + // statement below. + Device::BuiltinAction chosen_action = device->InvokeMenuItem(chosen_item); + + bool should_wipe_cache = false; + switch (chosen_action) { + case Device::NO_ACTION: + break; + + case Device::REBOOT: + case Device::SHUTDOWN: + case Device::REBOOT_BOOTLOADER: + return chosen_action; + + case Device::WIPE_DATA: + wipe_data(ui->IsTextVisible(), device); + if (!ui->IsTextVisible()) return Device::NO_ACTION; + break; + + case Device::WIPE_CACHE: + wipe_cache(ui->IsTextVisible(), device); + if (!ui->IsTextVisible()) return Device::NO_ACTION; + break; + + case Device::APPLY_ADB_SIDELOAD: + case Device::APPLY_SDCARD: + { + bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD); + if (adb) { + status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); + } else { + status = apply_from_sdcard(device, &should_wipe_cache); + } + + if (status == INSTALL_SUCCESS && should_wipe_cache) { + if (!wipe_cache(false, device)) { + status = INSTALL_ERROR; + } + } + + if (status != INSTALL_SUCCESS) { + ui->SetBackground(RecoveryUI::ERROR); + ui->Print("Installation aborted.\n"); + copy_logs(); + } else if (!ui->IsTextVisible()) { + return Device::NO_ACTION; // reboot if logs aren't visible + } else { + ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card"); + } + } + break; + + case Device::VIEW_RECOVERY_LOGS: + choose_recovery_file(device); + break; + + case Device::MOUNT_SYSTEM: + char system_root_image[PROPERTY_VALUE_MAX]; + property_get("ro.build.system_root_image", system_root_image, ""); + + // 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 work (the symlink is created through + // the build system). + // Bug: 22855115 + if (strcmp(system_root_image, "true") == 0) { + if (ensure_path_mounted_at("/", "/system_root") != -1) { + ui->Print("Mounted /system.\n"); + } + } else { + if (ensure_path_mounted("/system") != -1) { + ui->Print("Mounted /system.\n"); + } + } + + break; + } + } +} + +static void +print_property(const char *key, const char *name, void *cookie) { + printf("%s=%s\n", key, name); +} + +static void +load_locale_from_cache() { + FILE* fp = fopen_path(LOCALE_FILE, "r"); + char buffer[80]; + if (fp != NULL) { + fgets(buffer, sizeof(buffer), fp); + int j = 0; + unsigned int i; + for (i = 0; i < sizeof(buffer) && buffer[i]; ++i) { + if (!isspace(buffer[i])) { + buffer[j++] = buffer[i]; + } + } + buffer[j] = 0; + locale = strdup(buffer); + check_and_fclose(fp, LOCALE_FILE); + } +} + +static RecoveryUI* gCurrentUI = NULL; + +void +ui_print(const char* format, ...) { + char buffer[256]; + + va_list ap; + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + if (gCurrentUI != NULL) { + gCurrentUI->Print("%s", buffer); + } else { + fputs(buffer, stdout); + } +} + +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); + } +} + +static void set_retry_bootloader_message(int retry_count, int argc, char** argv) { + struct bootloader_message boot {}; + strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); + strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); + + for (int i = 1; i < argc; ++i) { + if (strstr(argv[i], "retry_count") == nullptr) { + strlcat(boot.recovery, argv[i], sizeof(boot.recovery)); + strlcat(boot.recovery, "\n", sizeof(boot.recovery)); + } + } + + // Initialize counter to 1 if it's not in BCB, otherwise increment it by 1. + if (retry_count == 0) { + strlcat(boot.recovery, "--retry_count=1\n", sizeof(boot.recovery)); + } else { + char buffer[20]; + snprintf(buffer, sizeof(buffer), "--retry_count=%d\n", retry_count+1); + strlcat(boot.recovery, buffer, sizeof(boot.recovery)); + } + set_bootloader_message(&boot); +} + +static ssize_t logbasename( + log_id_t /* logId */, + char /* prio */, + const char *filename, + const char * /* buf */, size_t len, + void *arg) { + if (strstr(LAST_KMSG_FILE, filename) || + strstr(LAST_LOG_FILE, filename)) { + bool *doRotate = reinterpret_cast<bool *>(arg); + *doRotate = true; + } + return len; +} + +static ssize_t logrotate( + log_id_t logId, + char prio, + const char *filename, + const char *buf, size_t len, + void *arg) { + bool *doRotate = reinterpret_cast<bool *>(arg); + if (!*doRotate) { + return __android_log_pmsg_file_write(logId, prio, filename, buf, len); + } + + std::string name(filename); + size_t dot = name.find_last_of('.'); + std::string sub = name.substr(0, dot); + + if (!strstr(LAST_KMSG_FILE, sub.c_str()) && + !strstr(LAST_LOG_FILE, sub.c_str())) { + return __android_log_pmsg_file_write(logId, prio, filename, buf, len); + } + + // filename rotation + if (dot == std::string::npos) { + name += ".1"; + } else { + std::string number = name.substr(dot + 1); + if (!isdigit(number.data()[0])) { + name += ".1"; + } else { + auto i = std::stoull(number); + name = sub + "." + std::to_string(i + 1); + } + } + + return __android_log_pmsg_file_write(logId, prio, name.c_str(), buf, len); +} + +int main(int argc, char **argv) { + // 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) { + adb_server_main(0, DEFAULT_ADB_PORT, -1); + 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; + + get_args(&argc, &argv); + + const char *update_package = NULL; + bool should_wipe_data = false; + bool should_wipe_cache = false; + bool should_brick = false; + bool show_text = false; + bool sideload = false; + bool sideload_auto_reboot = false; + bool just_exit = false; + bool shutdown_after = false; + int retry_count = 0; + + int arg; + int option_index; + while ((arg = getopt_long(argc, argv, "", 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 'g': { + if (stage == NULL || *stage == '\0') { + char buffer[20] = "1/"; + strncat(buffer, optarg, sizeof(buffer)-3); + stage = strdup(buffer); + } + break; + } + case 'p': shutdown_after = true; break; + case 'r': reason = optarg; break; + case 0: { + if (strcmp(OPTIONS[option_index].name, "brick") == 0) { + should_brick = true; + break; + } + break; + } + case '?': + LOGE("Invalid command argument\n"); + continue; + } + } + + if (locale == nullptr && has_cache) { + load_locale_from_cache(); + } + printf("locale is [%s]\n", locale); + printf("stage is [%s]\n", stage); + printf("reason is [%s]\n", reason); + + Device* device = make_device(); + ui = device->GetUI(); + gCurrentUI = ui; + + ui->SetLocale(locale); + ui->Init(); + + int st_cur, st_max; + if (stage != NULL && sscanf(stage, "%d/%d", &st_cur, &st_max) == 2) { + ui->SetStage(st_cur, st_max); + } + + ui->SetBackground(RecoveryUI::NONE); + if (show_text) ui->ShowText(true); + + struct selinux_opt seopts[] = { + { SELABEL_OPT_PATH, "/file_contexts" } + }; + + sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1); + + if (!sehandle) { + ui->Print("Warning: No file_contexts\n"); + } + + device->StartRecovery(); + + printf("Command:"); + for (arg = 0; arg < argc; arg++) { + printf(" \"%s\"", argv[arg]); + } + printf("\n"); + + if (update_package) { + // For backwards compatibility on the cache partition only, if + // we're given an old 'root' path "CACHE:foo", change it to + // "/cache/foo". + if (strncmp(update_package, "CACHE:", 6) == 0) { + int len = strlen(update_package) + 10; + char* modified_path = (char*)malloc(len); + if (modified_path) { + strlcpy(modified_path, "/cache/", len); + strlcat(modified_path, update_package+6, len); + printf("(replacing path \"%s\" with \"%s\")\n", + update_package, modified_path); + update_package = modified_path; + } + else + printf("modified_path allocation failed\n"); + } + } + printf("\n"); + + property_list(print_property, NULL); + printf("\n"); + + ui->Print("Supported API: %d\n", RECOVERY_API_VERSION); + + int status = INSTALL_SUCCESS; + + if (update_package != NULL) { + // It's not entirely true that we will modify the flash. But we want + // to log the update attempt since update_package is non-NULL. + modified_flash = true; + + if (!is_battery_ok()) { + ui->Print("battery capacity is not enough for installing package, needed is %d%%\n", + BATTERY_OK_PERCENTAGE); + status = INSTALL_SKIPPED; + } else { + status = install_package(update_package, &should_wipe_cache, + TEMPORARY_INSTALL_FILE, true); + 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 EIO_RETRY_COUNT + // times before we abandon this OTA update. + if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) { + copy_logs(); + set_retry_bootloader_message(retry_count, argc, argv); + // Print retry count on screen. + ui->Print("Retry attempt %d\n", retry_count); + + // Reboot and retry the update + int ret = property_set(ANDROID_RB_PROPERTY, "reboot,recovery"); + if (ret < 0) { + 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(false, device)) { + status = INSTALL_ERROR; + } + } else if (should_wipe_cache) { + if (!wipe_cache(false, device)) { + status = INSTALL_ERROR; + } + } else if (should_brick) { + if (!brick_device()) { + 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(ui, &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) { + status = INSTALL_NONE; // No command specified + ui->SetBackground(RecoveryUI::NO_COMMAND); + + // http://b/17489952 + // If this is an eng or userdebug build, automatically turn on the + // text display if no command is specified. + if (is_ro_debuggable()) { + ui->ShowText(true); + } + } + + if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) { + copy_logs(); + ui->SetBackground(RecoveryUI::ERROR); + } + + Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; + if ((status != INSTALL_SUCCESS && status != INSTALL_SKIPPED && !sideload_auto_reboot) || + ui->IsTextVisible()) { + Device::BuiltinAction temp = prompt_and_wait(device, status); + if (temp != Device::NO_ACTION) { + after = temp; + } + } + + // Save logs and clean up before rebooting or shutting down. + finish_recovery(); + + switch (after) { + case Device::SHUTDOWN: + ui->Print("Shutting down...\n"); + property_set(ANDROID_RB_PROPERTY, "shutdown,"); + break; + + case Device::REBOOT_BOOTLOADER: + ui->Print("Rebooting to bootloader...\n"); + property_set(ANDROID_RB_PROPERTY, "reboot,bootloader"); + break; + + default: + ui->Print("Rebooting...\n"); + property_set(ANDROID_RB_PROPERTY, "reboot,"); + break; + } + while (true) { + pause(); + } + // Should be unreachable. + return EXIT_SUCCESS; +}
diff --git a/recovery/res-560dpi b/recovery/res-560dpi new file mode 120000 index 0000000..8576a9b --- /dev/null +++ b/recovery/res-560dpi
@@ -0,0 +1 @@ +res-xxhdpi \ No newline at end of file
diff --git a/recovery/res-hdpi/images/erasing_text.png b/recovery/res-hdpi/images/erasing_text.png new file mode 100644 index 0000000..2186c19 --- /dev/null +++ b/recovery/res-hdpi/images/erasing_text.png Binary files differ
diff --git a/recovery/res-hdpi/images/error_text.png b/recovery/res-hdpi/images/error_text.png new file mode 100644 index 0000000..9700f45 --- /dev/null +++ b/recovery/res-hdpi/images/error_text.png Binary files differ
diff --git a/recovery/res-hdpi/images/icon_error.png b/recovery/res-hdpi/images/icon_error.png new file mode 100644 index 0000000..cb3d1ab --- /dev/null +++ b/recovery/res-hdpi/images/icon_error.png Binary files differ
diff --git a/recovery/res-hdpi/images/icon_installing.png b/recovery/res-hdpi/images/icon_installing.png new file mode 100644 index 0000000..0fcfbc2 --- /dev/null +++ b/recovery/res-hdpi/images/icon_installing.png Binary files differ
diff --git a/recovery/res-hdpi/images/installing_security_text.png b/recovery/res-hdpi/images/installing_security_text.png new file mode 100644 index 0000000..0f60595 --- /dev/null +++ b/recovery/res-hdpi/images/installing_security_text.png Binary files differ
diff --git a/recovery/res-hdpi/images/installing_text.png b/recovery/res-hdpi/images/installing_text.png new file mode 100644 index 0000000..ec8b875 --- /dev/null +++ b/recovery/res-hdpi/images/installing_text.png Binary files differ
diff --git a/recovery/res-hdpi/images/no_command_text.png b/recovery/res-hdpi/images/no_command_text.png new file mode 100644 index 0000000..3eddcbb --- /dev/null +++ b/recovery/res-hdpi/images/no_command_text.png Binary files differ
diff --git a/recovery/res-hdpi/images/progress_empty.png b/recovery/res-hdpi/images/progress_empty.png new file mode 100644 index 0000000..7258183 --- /dev/null +++ b/recovery/res-hdpi/images/progress_empty.png Binary files differ
diff --git a/recovery/res-hdpi/images/progress_fill.png b/recovery/res-hdpi/images/progress_fill.png new file mode 100644 index 0000000..becf87b --- /dev/null +++ b/recovery/res-hdpi/images/progress_fill.png Binary files differ
diff --git a/recovery/res-hdpi/images/stage_empty.png b/recovery/res-hdpi/images/stage_empty.png new file mode 100644 index 0000000..251ec19 --- /dev/null +++ b/recovery/res-hdpi/images/stage_empty.png Binary files differ
diff --git a/recovery/res-hdpi/images/stage_fill.png b/recovery/res-hdpi/images/stage_fill.png new file mode 100644 index 0000000..1ab79e8 --- /dev/null +++ b/recovery/res-hdpi/images/stage_fill.png Binary files differ
diff --git a/recovery/res-mdpi/images/erasing_text.png b/recovery/res-mdpi/images/erasing_text.png new file mode 100644 index 0000000..b0dd3c6 --- /dev/null +++ b/recovery/res-mdpi/images/erasing_text.png Binary files differ
diff --git a/recovery/res-mdpi/images/error_text.png b/recovery/res-mdpi/images/error_text.png new file mode 100644 index 0000000..6a47a59 --- /dev/null +++ b/recovery/res-mdpi/images/error_text.png Binary files differ
diff --git a/recovery/res-mdpi/images/icon_error.png b/recovery/res-mdpi/images/icon_error.png new file mode 100644 index 0000000..cb3d1ab --- /dev/null +++ b/recovery/res-mdpi/images/icon_error.png Binary files differ
diff --git a/recovery/res-mdpi/images/icon_installing.png b/recovery/res-mdpi/images/icon_installing.png new file mode 100644 index 0000000..0fcfbc2 --- /dev/null +++ b/recovery/res-mdpi/images/icon_installing.png Binary files differ
diff --git a/recovery/res-mdpi/images/installing_security_text.png b/recovery/res-mdpi/images/installing_security_text.png new file mode 100644 index 0000000..1499398 --- /dev/null +++ b/recovery/res-mdpi/images/installing_security_text.png Binary files differ
diff --git a/recovery/res-mdpi/images/installing_text.png b/recovery/res-mdpi/images/installing_text.png new file mode 100644 index 0000000..01e9bfe --- /dev/null +++ b/recovery/res-mdpi/images/installing_text.png Binary files differ
diff --git a/recovery/res-mdpi/images/no_command_text.png b/recovery/res-mdpi/images/no_command_text.png new file mode 100644 index 0000000..d340df5 --- /dev/null +++ b/recovery/res-mdpi/images/no_command_text.png Binary files differ
diff --git a/recovery/res-mdpi/images/progress_empty.png b/recovery/res-mdpi/images/progress_empty.png new file mode 100644 index 0000000..7258183 --- /dev/null +++ b/recovery/res-mdpi/images/progress_empty.png Binary files differ
diff --git a/recovery/res-mdpi/images/progress_fill.png b/recovery/res-mdpi/images/progress_fill.png new file mode 100644 index 0000000..becf87b --- /dev/null +++ b/recovery/res-mdpi/images/progress_fill.png Binary files differ
diff --git a/recovery/res-mdpi/images/stage_empty.png b/recovery/res-mdpi/images/stage_empty.png new file mode 100644 index 0000000..251ec19 --- /dev/null +++ b/recovery/res-mdpi/images/stage_empty.png Binary files differ
diff --git a/recovery/res-mdpi/images/stage_fill.png b/recovery/res-mdpi/images/stage_fill.png new file mode 100644 index 0000000..1ab79e8 --- /dev/null +++ b/recovery/res-mdpi/images/stage_fill.png Binary files differ
diff --git a/recovery/res-xhdpi/images/erasing_text.png b/recovery/res-xhdpi/images/erasing_text.png new file mode 100644 index 0000000..2f8b469 --- /dev/null +++ b/recovery/res-xhdpi/images/erasing_text.png Binary files differ
diff --git a/recovery/res-xhdpi/images/error_text.png b/recovery/res-xhdpi/images/error_text.png new file mode 100644 index 0000000..ad18851 --- /dev/null +++ b/recovery/res-xhdpi/images/error_text.png Binary files differ
diff --git a/recovery/res-xhdpi/images/icon_error.png b/recovery/res-xhdpi/images/icon_error.png new file mode 100644 index 0000000..cb3d1ab --- /dev/null +++ b/recovery/res-xhdpi/images/icon_error.png Binary files differ
diff --git a/recovery/res-xhdpi/images/icon_installing.png b/recovery/res-xhdpi/images/icon_installing.png new file mode 100644 index 0000000..0fcfbc2 --- /dev/null +++ b/recovery/res-xhdpi/images/icon_installing.png Binary files differ
diff --git a/recovery/res-xhdpi/images/installing_security_text.png b/recovery/res-xhdpi/images/installing_security_text.png new file mode 100644 index 0000000..acc6a7c --- /dev/null +++ b/recovery/res-xhdpi/images/installing_security_text.png Binary files differ
diff --git a/recovery/res-xhdpi/images/installing_text.png b/recovery/res-xhdpi/images/installing_text.png new file mode 100644 index 0000000..32897d0 --- /dev/null +++ b/recovery/res-xhdpi/images/installing_text.png Binary files differ
diff --git a/recovery/res-xhdpi/images/no_command_text.png b/recovery/res-xhdpi/images/no_command_text.png new file mode 100644 index 0000000..eb43c59 --- /dev/null +++ b/recovery/res-xhdpi/images/no_command_text.png Binary files differ
diff --git a/recovery/res-xhdpi/images/progress_empty.png b/recovery/res-xhdpi/images/progress_empty.png new file mode 100644 index 0000000..7258183 --- /dev/null +++ b/recovery/res-xhdpi/images/progress_empty.png Binary files differ
diff --git a/recovery/res-xhdpi/images/progress_fill.png b/recovery/res-xhdpi/images/progress_fill.png new file mode 100644 index 0000000..becf87b --- /dev/null +++ b/recovery/res-xhdpi/images/progress_fill.png Binary files differ
diff --git a/recovery/res-xhdpi/images/stage_empty.png b/recovery/res-xhdpi/images/stage_empty.png new file mode 100644 index 0000000..251ec19 --- /dev/null +++ b/recovery/res-xhdpi/images/stage_empty.png Binary files differ
diff --git a/recovery/res-xhdpi/images/stage_fill.png b/recovery/res-xhdpi/images/stage_fill.png new file mode 100644 index 0000000..1ab79e8 --- /dev/null +++ b/recovery/res-xhdpi/images/stage_fill.png Binary files differ
diff --git a/recovery/res-xxhdpi/images/erasing_text.png b/recovery/res-xxhdpi/images/erasing_text.png new file mode 100644 index 0000000..8ff2b2f --- /dev/null +++ b/recovery/res-xxhdpi/images/erasing_text.png Binary files differ
diff --git a/recovery/res-xxhdpi/images/error_text.png b/recovery/res-xxhdpi/images/error_text.png new file mode 100644 index 0000000..658d4ea --- /dev/null +++ b/recovery/res-xxhdpi/images/error_text.png Binary files differ
diff --git a/recovery/res-xxhdpi/images/icon_error.png b/recovery/res-xxhdpi/images/icon_error.png new file mode 100644 index 0000000..cb3d1ab --- /dev/null +++ b/recovery/res-xxhdpi/images/icon_error.png Binary files differ
diff --git a/recovery/res-xxhdpi/images/icon_installing.png b/recovery/res-xxhdpi/images/icon_installing.png new file mode 100644 index 0000000..0fcfbc2 --- /dev/null +++ b/recovery/res-xxhdpi/images/icon_installing.png Binary files differ
diff --git a/recovery/res-xxhdpi/images/installing_security_text.png b/recovery/res-xxhdpi/images/installing_security_text.png new file mode 100644 index 0000000..23fcaa4 --- /dev/null +++ b/recovery/res-xxhdpi/images/installing_security_text.png Binary files differ
diff --git a/recovery/res-xxhdpi/images/installing_text.png b/recovery/res-xxhdpi/images/installing_text.png new file mode 100644 index 0000000..fd8e584 --- /dev/null +++ b/recovery/res-xxhdpi/images/installing_text.png Binary files differ
diff --git a/recovery/res-xxhdpi/images/no_command_text.png b/recovery/res-xxhdpi/images/no_command_text.png new file mode 100644 index 0000000..23932d6 --- /dev/null +++ b/recovery/res-xxhdpi/images/no_command_text.png Binary files differ
diff --git a/recovery/res-xxhdpi/images/progress_empty.png b/recovery/res-xxhdpi/images/progress_empty.png new file mode 100644 index 0000000..7258183 --- /dev/null +++ b/recovery/res-xxhdpi/images/progress_empty.png Binary files differ
diff --git a/recovery/res-xxhdpi/images/progress_fill.png b/recovery/res-xxhdpi/images/progress_fill.png new file mode 100644 index 0000000..becf87b --- /dev/null +++ b/recovery/res-xxhdpi/images/progress_fill.png Binary files differ
diff --git a/recovery/res-xxhdpi/images/stage_empty.png b/recovery/res-xxhdpi/images/stage_empty.png new file mode 100644 index 0000000..251ec19 --- /dev/null +++ b/recovery/res-xxhdpi/images/stage_empty.png Binary files differ
diff --git a/recovery/res-xxhdpi/images/stage_fill.png b/recovery/res-xxhdpi/images/stage_fill.png new file mode 100644 index 0000000..1ab79e8 --- /dev/null +++ b/recovery/res-xxhdpi/images/stage_fill.png Binary files differ
diff --git a/recovery/res-xxxhdpi/images/erasing_text.png b/recovery/res-xxxhdpi/images/erasing_text.png new file mode 100644 index 0000000..0315293 --- /dev/null +++ b/recovery/res-xxxhdpi/images/erasing_text.png Binary files differ
diff --git a/recovery/res-xxxhdpi/images/error_text.png b/recovery/res-xxxhdpi/images/error_text.png new file mode 100644 index 0000000..dba127f --- /dev/null +++ b/recovery/res-xxxhdpi/images/error_text.png Binary files differ
diff --git a/recovery/res-xxxhdpi/images/icon_error.png b/recovery/res-xxxhdpi/images/icon_error.png new file mode 100644 index 0000000..cb3d1ab --- /dev/null +++ b/recovery/res-xxxhdpi/images/icon_error.png Binary files differ
diff --git a/recovery/res-xxxhdpi/images/icon_installing.png b/recovery/res-xxxhdpi/images/icon_installing.png new file mode 100644 index 0000000..0fcfbc2 --- /dev/null +++ b/recovery/res-xxxhdpi/images/icon_installing.png Binary files differ
diff --git a/recovery/res-xxxhdpi/images/installing_security_text.png b/recovery/res-xxxhdpi/images/installing_security_text.png new file mode 100644 index 0000000..6cdbef4 --- /dev/null +++ b/recovery/res-xxxhdpi/images/installing_security_text.png Binary files differ
diff --git a/recovery/res-xxxhdpi/images/installing_text.png b/recovery/res-xxxhdpi/images/installing_text.png new file mode 100644 index 0000000..32511a9 --- /dev/null +++ b/recovery/res-xxxhdpi/images/installing_text.png Binary files differ
diff --git a/recovery/res-xxxhdpi/images/no_command_text.png b/recovery/res-xxxhdpi/images/no_command_text.png new file mode 100644 index 0000000..b6cdd77 --- /dev/null +++ b/recovery/res-xxxhdpi/images/no_command_text.png Binary files differ
diff --git a/recovery/res-xxxhdpi/images/progress_empty.png b/recovery/res-xxxhdpi/images/progress_empty.png new file mode 100644 index 0000000..7258183 --- /dev/null +++ b/recovery/res-xxxhdpi/images/progress_empty.png Binary files differ
diff --git a/recovery/res-xxxhdpi/images/progress_fill.png b/recovery/res-xxxhdpi/images/progress_fill.png new file mode 100644 index 0000000..becf87b --- /dev/null +++ b/recovery/res-xxxhdpi/images/progress_fill.png Binary files differ
diff --git a/recovery/res-xxxhdpi/images/stage_empty.png b/recovery/res-xxxhdpi/images/stage_empty.png new file mode 100644 index 0000000..251ec19 --- /dev/null +++ b/recovery/res-xxxhdpi/images/stage_empty.png Binary files differ
diff --git a/recovery/res-xxxhdpi/images/stage_fill.png b/recovery/res-xxxhdpi/images/stage_fill.png new file mode 100644 index 0000000..1ab79e8 --- /dev/null +++ b/recovery/res-xxxhdpi/images/stage_fill.png Binary files differ
diff --git a/recovery/roots.cpp b/recovery/roots.cpp new file mode 100644 index 0000000..b113d9a --- /dev/null +++ b/recovery/roots.cpp
@@ -0,0 +1,271 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "roots.h" + +#include <errno.h> +#include <stdlib.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 <fs_mgr.h> +#include "common.h" +#include "make_ext4fs.h" +#include "mounts.h" +#include "wipe.h" +#include "cryptfs.h" + +static struct fstab *fstab = NULL; + +extern struct selabel_handle *sehandle; + +void load_volume_table() +{ + int i; + int ret; + + fstab = fs_mgr_read_fstab("/etc/recovery.fstab"); + if (!fstab) { + LOGE("failed to read /etc/recovery.fstab\n"); + return; + } + + ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk"); + if (ret < 0 ) { + LOGE("failed to add /tmp entry to fstab\n"); + fs_mgr_free_fstab(fstab); + fstab = NULL; + return; + } + + printf("recovery filesystem table\n"); + printf("=========================\n"); + for (i = 0; i < fstab->num_entries; ++i) { + Volume* v = &fstab->recs[i]; + printf(" %d %s %s %s %lld\n", i, v->mount_point, v->fs_type, + v->blk_device, v->length); + } + printf("\n"); +} + +Volume* volume_for_path(const char* path) { + return fs_mgr_get_entry_for_mount_point(fstab, path); +} + +// 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) { + LOGE("unknown volume for path [%s]\n", path); + return -1; + } + if (strcmp(v->fs_type, "ramdisk") == 0) { + // the ramdisk is always mounted. + return 0; + } + + if (!scan_mounted_volumes()) { + LOGE("failed to scan mounted volumes\n"); + return -1; + } + + if (!mount_point) { + mount_point = v->mount_point; + } + + MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point); + if (mv) { + // volume is already mounted + return 0; + } + + mkdir(mount_point, 0755); // in case it doesn't already exist + + if (strcmp(v->fs_type, "ext4") == 0 || + strcmp(v->fs_type, "squashfs") == 0 || + strcmp(v->fs_type, "vfat") == 0) { + int result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options); + if (result == -1 && fs_mgr_is_formattable(v)) { + LOGE("failed to mount %s (%s), formatting ...\n", + mount_point, strerror(errno)); + 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 { + LOGE("failed to format %s (%s)\n", mount_point, strerror(errno)); + return -1; + } + } + + if (result == -1) { + LOGE("failed to mount %s (%s)\n", mount_point, strerror(errno)); + return -1; + } + return 0; + } + + LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, mount_point); + return -1; +} + +int ensure_path_mounted(const char* path) { + // 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) { + LOGE("unknown volume for path [%s]\n", 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()) { + LOGE("failed to scan mounted volumes\n"); + return -1; + } + + MountedVolume* mv = find_mounted_volume_by_mount_point(v->mount_point); + if (mv == NULL) { + // volume is already unmounted + return 0; + } + + 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(-1); + } + waitpid(child, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + LOGE("%s failed with status %d\n", path, WEXITSTATUS(status)); + } + return WEXITSTATUS(status); +} + +int format_volume(const char* volume) { + Volume* v = volume_for_path(volume); + if (v == NULL) { + LOGE("unknown volume \"%s\"\n", volume); + return -1; + } + if (strcmp(v->fs_type, "ramdisk") == 0) { + // you can't format the ramdisk. + LOGE("can't format_volume \"%s\"", volume); + return -1; + } + if (strcmp(v->mount_point, volume) != 0) { + LOGE("can't give path \"%s\" to format_volume\n", volume); + return -1; + } + + if (ensure_path_unmounted(volume) != 0) { + LOGE("format_volume failed to unmount \"%s\"\n", v->mount_point); + return -1; + } + + if (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "f2fs") == 0) { + // if there's a key_loc that looks like a path, it should be a + // block device for storing encryption metadata. wipe it too. + if (v->key_loc != NULL && v->key_loc[0] == '/') { + LOGI("wiping %s\n", v->key_loc); + int fd = open(v->key_loc, O_WRONLY | O_CREAT, 0644); + if (fd < 0) { + LOGE("format_volume: failed to open %s\n", 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) { + length = -CRYPT_FOOTER_OFFSET; + } + int result; + if (strcmp(v->fs_type, "ext4") == 0) { + result = make_ext4fs(v->blk_device, length, volume, sehandle); + } else { /* Has to be f2fs because we checked earlier. */ + if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0 && length < 0) { + LOGE("format_volume: crypt footer + negative length (%zd) not supported on %s\n", length, v->fs_type); + return -1; + } + if (length < 0) { + LOGE("format_volume: negative length (%zd) not supported on %s\n", length, v->fs_type); + return -1; + } + char *num_sectors; + if (asprintf(&num_sectors, "%zd", length / 512) <= 0) { + LOGE("format_volume: failed to create %s command for %s\n", v->fs_type, 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, NULL}; + + result = exec_cmd(f2fs_path, (char* const*)f2fs_argv); + free(num_sectors); + } + if (result != 0) { + LOGE("format_volume: make %s failed on %s with %d(%s)\n", v->fs_type, v->blk_device, result, strerror(errno)); + return -1; + } + return 0; + } + + LOGE("format_volume: fs_type \"%s\" unsupported\n", v->fs_type); + return -1; +} + +int setup_install_mounts() { + if (fstab == NULL) { + LOGE("can't set up install mounts: no fstab loaded\n"); + return -1; + } + for (int i = 0; i < fstab->num_entries; ++i) { + Volume* v = fstab->recs + i; + + if (strcmp(v->mount_point, "/tmp") == 0 || + strcmp(v->mount_point, "/cache") == 0) { + if (ensure_path_mounted(v->mount_point) != 0) { + LOGE("failed to mount %s\n", v->mount_point); + return -1; + } + + } else { + if (ensure_path_unmounted(v->mount_point) != 0) { + LOGE("failed to unmount %s\n", v->mount_point); + return -1; + } + } + } + return 0; +}
diff --git a/recovery/roots.h b/recovery/roots.h new file mode 100644 index 0000000..6e3b243 --- /dev/null +++ b/recovery/roots.h
@@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RECOVERY_ROOTS_H_ +#define RECOVERY_ROOTS_H_ + +#include "common.h" + +// 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); + +// Make sure that the volume 'path' is on is mounted. Returns 0 on +// success (volume is mounted). +int ensure_path_mounted(const char* path); + +// Similar to ensure_path_mounted, but allows one to specify the mount_point. +int ensure_path_mounted_at(const char* path, const char* mount_point); + +// Make sure that the volume 'path' is on is unmounted. Returns 0 on +// success (volume is unmounted); +int ensure_path_unmounted(const char* path); + +// Reformat the given volume (must be the mount point only, eg +// "/cache"), no paths permitted. Attempts to unmount the volume if +// it is mounted. +int format_volume(const char* volume); + +// Ensure that all and only the volumes that packages expect to find +// mounted (/tmp and /cache) are mounted. Returns 0 on success. +int setup_install_mounts(); + +#endif // RECOVERY_ROOTS_H_
diff --git a/recovery/screen_ui.cpp b/recovery/screen_ui.cpp new file mode 100644 index 0000000..1d33269 --- /dev/null +++ b/recovery/screen_ui.cpp
@@ -0,0 +1,725 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <linux/input.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include <vector> + +#include <android-base/strings.h> +#include <android-base/stringprintf.h> +#include <cutils/properties.h> + +#include "common.h" +#include "device.h" +#include "minui/minui.h" +#include "screen_ui.h" +#include "ui.h" + +static int char_width; +static int char_height; + +// Return the current time as a double (including fractions of a second). +static double now() { + struct timeval tv; + gettimeofday(&tv, nullptr); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +ScreenRecoveryUI::ScreenRecoveryUI() : + currentIcon(NONE), + installingFrame(0), + locale(nullptr), + rtl_locale(false), + progressBarType(EMPTY), + progressScopeStart(0), + progressScopeSize(0), + progress(0), + pagesIdentical(false), + text_cols_(0), + text_rows_(0), + text_(nullptr), + text_col_(0), + text_row_(0), + text_top_(0), + show_text(false), + show_text_ever(false), + menu_(nullptr), + show_menu(false), + menu_items(0), + menu_sel(0), + file_viewer_text_(nullptr), + animation_fps(-1), + installing_frames(-1), + stage(-1), + max_stage(-1) { + + for (int i = 0; i < 5; i++) { + backgroundIcon[i] = nullptr; + } + pthread_mutex_init(&updateMutex, nullptr); +} + +// Clear the screen and draw the currently selected background icon (if any). +// Should only be called with updateMutex locked. +void ScreenRecoveryUI::draw_background_locked(Icon icon) { + pagesIdentical = false; + gr_color(0, 0, 0, 255); + gr_clear(); + + if (icon) { + GRSurface* surface = backgroundIcon[icon]; + if (icon == INSTALLING_UPDATE || icon == ERASING) { + surface = installation[installingFrame]; + } + GRSurface* text_surface = backgroundText[icon]; + + int iconWidth = gr_get_width(surface); + int iconHeight = gr_get_height(surface); + int textWidth = gr_get_width(text_surface); + int textHeight = gr_get_height(text_surface); + int stageHeight = gr_get_height(stageMarkerEmpty); + + int sh = (max_stage >= 0) ? stageHeight : 0; + + iconX = (gr_fb_width() - iconWidth) / 2; + iconY = (gr_fb_height() - (iconHeight+textHeight+40+sh)) / 2; + + int textX = (gr_fb_width() - textWidth) / 2; + int textY = ((gr_fb_height() - (iconHeight+textHeight+40+sh)) / 2) + iconHeight + 40; + + gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY); + if (stageHeight > 0) { + int sw = gr_get_width(stageMarkerEmpty); + int x = (gr_fb_width() - max_stage * gr_get_width(stageMarkerEmpty)) / 2; + int y = iconY + iconHeight + 20; + for (int i = 0; i < max_stage; ++i) { + gr_blit((i < stage) ? stageMarkerFill : stageMarkerEmpty, + 0, 0, sw, stageHeight, x, y); + x += sw; + } + } + + gr_color(255, 255, 255, 255); + gr_texticon(textX, textY, text_surface); + } +} + +// Draw the progress bar (if any) on the screen. Does not flip pages. +// Should only be called with updateMutex locked. +void ScreenRecoveryUI::draw_progress_locked() { + if (currentIcon == ERROR) return; + + if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { + GRSurface* icon = installation[installingFrame]; + gr_blit(icon, 0, 0, gr_get_width(icon), gr_get_height(icon), iconX, iconY); + } + + if (progressBarType != EMPTY) { + int iconHeight = gr_get_height(backgroundIcon[INSTALLING_UPDATE]); + int width = gr_get_width(progressBarEmpty); + int height = gr_get_height(progressBarEmpty); + + int dx = (gr_fb_width() - width)/2; + int dy = (3*gr_fb_height() + iconHeight - 2*height)/4; + + // Erase behind the progress bar (in case this was a progress-only update) + gr_color(0, 0, 0, 255); + gr_fill(dx, dy, width, height); + + if (progressBarType == DETERMINATE) { + float p = progressScopeStart + progress * progressScopeSize; + int pos = (int) (p * width); + + if (rtl_locale) { + // Fill the progress bar from right to left. + if (pos > 0) { + gr_blit(progressBarFill, width-pos, 0, pos, height, dx+width-pos, dy); + } + if (pos < width-1) { + gr_blit(progressBarEmpty, 0, 0, width-pos, height, dx, dy); + } + } else { + // Fill the progress bar from left to right. + if (pos > 0) { + gr_blit(progressBarFill, 0, 0, pos, height, dx, dy); + } + if (pos < width-1) { + gr_blit(progressBarEmpty, pos, 0, width-pos, height, dx+pos, dy); + } + } + } + } +} + +void ScreenRecoveryUI::SetColor(UIElement e) { + switch (e) { + case INFO: + gr_color(249, 194, 0, 255); + break; + case HEADER: + gr_color(247, 0, 6, 255); + break; + case MENU: + case MENU_SEL_BG: + gr_color(0, 106, 157, 255); + break; + case MENU_SEL_BG_ACTIVE: + gr_color(0, 156, 100, 255); + break; + case MENU_SEL_FG: + gr_color(255, 255, 255, 255); + break; + case LOG: + gr_color(196, 196, 196, 255); + break; + case TEXT_FILL: + gr_color(0, 0, 0, 160); + break; + default: + gr_color(255, 255, 255, 255); + break; + } +} + +void ScreenRecoveryUI::DrawHorizontalRule(int* y) { + SetColor(MENU); + *y += 4; + gr_fill(0, *y, gr_fb_width(), *y + 2); + *y += 4; +} + +void ScreenRecoveryUI::DrawTextLine(int* y, const char* line, bool bold) { + gr_text(4, *y, line, bold); + *y += char_height + 4; +} + +void ScreenRecoveryUI::DrawTextLines(int* y, const char* const* lines) { + for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { + DrawTextLine(y, lines[i], false); + } +} + +static const char* REGULAR_HELP[] = { + "Use volume up/down and power.", + NULL +}; + +static const char* LONG_PRESS_HELP[] = { + "Any button cycles highlight.", + "Long-press activates.", + NULL +}; + +// Redraw everything on the screen. Does not flip pages. +// Should only be called with updateMutex locked. +void ScreenRecoveryUI::draw_screen_locked() { + if (!show_text) { + draw_background_locked(currentIcon); + draw_progress_locked(); + } else { + gr_color(0, 0, 0, 255); + gr_clear(); + + int y = 0; + if (show_menu) { + char recovery_fingerprint[PROPERTY_VALUE_MAX]; + property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, ""); + + SetColor(INFO); + DrawTextLine(&y, "Android Recovery", true); + for (auto& chunk : android::base::Split(recovery_fingerprint, ":")) { + DrawTextLine(&y, chunk.c_str(), false); + } + DrawTextLines(&y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); + + SetColor(HEADER); + DrawTextLines(&y, menu_headers_); + + SetColor(MENU); + DrawHorizontalRule(&y); + y += 4; + for (int i = 0; i < menu_items; ++i) { + if (i == menu_sel) { + // Draw the highlight bar. + SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG); + gr_fill(0, y - 2, gr_fb_width(), y + char_height + 2); + // Bold white text for the selected item. + SetColor(MENU_SEL_FG); + gr_text(4, y, menu_[i], true); + SetColor(MENU); + } else { + gr_text(4, y, menu_[i], false); + } + y += char_height + 4; + } + DrawHorizontalRule(&y); + } + + // 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_; + size_t count = 0; + for (int ty = gr_fb_height() - char_height; + ty >= y && count < text_rows_; + ty -= char_height, ++count) { + gr_text(0, ty, text_[row], false); + --row; + if (row < 0) row = text_rows_ - 1; + } + } +} + +// Redraw everything on the screen and flip the screen (make it visible). +// Should only be called with updateMutex locked. +void ScreenRecoveryUI::update_screen_locked() { + draw_screen_locked(); + gr_flip(); +} + +// Updates only the progress bar, if possible, otherwise redraws the screen. +// Should only be called with updateMutex locked. +void ScreenRecoveryUI::update_progress_locked() { + if (show_text || !pagesIdentical) { + draw_screen_locked(); // Must redraw the whole screen + pagesIdentical = true; + } else { + draw_progress_locked(); // Draw only the progress bar and overlays + } + gr_flip(); +} + +// Keeps the progress bar updated, even when the process is otherwise busy. +void* ScreenRecoveryUI::ProgressThreadStartRoutine(void* data) { + reinterpret_cast<ScreenRecoveryUI*>(data)->ProgressThreadLoop(); + return nullptr; +} + +void ScreenRecoveryUI::ProgressThreadLoop() { + double interval = 1.0 / animation_fps; + while (true) { + double start = now(); + pthread_mutex_lock(&updateMutex); + + int redraw = 0; + + // update the installation animation, if active + // skip this if we have a text overlay (too expensive to update) + if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && + installing_frames > 0 && !show_text) { + installingFrame = (installingFrame + 1) % installing_frames; + redraw = 1; + } + + // move the progress bar forward on timed intervals, if configured + int duration = progressScopeDuration; + if (progressBarType == DETERMINATE && duration > 0) { + double elapsed = now() - progressScopeTime; + float p = 1.0 * elapsed / duration; + if (p > 1.0) p = 1.0; + if (p > progress) { + progress = p; + redraw = 1; + } + } + + if (redraw) update_progress_locked(); + + pthread_mutex_unlock(&updateMutex); + double end = now(); + // minimum of 20ms delay between frames + double delay = interval - (end-start); + if (delay < 0.02) delay = 0.02; + usleep(static_cast<useconds_t>(delay * 1000000)); + } +} + +void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) { + int result = res_create_display_surface(filename, surface); + if (result < 0) { + LOGE("missing bitmap %s\n(Code %d)\n", filename, result); + } +} + +void ScreenRecoveryUI::LoadBitmapArray(const char* filename, int* frames, int* fps, + GRSurface*** surface) { + int result = res_create_multi_display_surface(filename, frames, fps, surface); + if (result < 0) { + LOGE("missing bitmap %s\n(Code %d)\n", filename, result); + } +} + +void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) { + int result = res_create_localized_alpha_surface(filename, locale, surface); + if (result < 0) { + LOGE("missing bitmap %s\n(Code %d)\n", filename, result); + } +} + +static char** Alloc2d(size_t rows, size_t cols) { + char** result = new char*[rows]; + for (size_t i = 0; i < rows; ++i) { + result[i] = new char[cols]; + memset(result[i], 0, cols); + } + return result; +} + +void ScreenRecoveryUI::Init() { + gr_init(); + + gr_font_size(&char_width, &char_height); + text_rows_ = gr_fb_height() / char_height; + text_cols_ = gr_fb_width() / char_width; + + text_ = Alloc2d(text_rows_, text_cols_ + 1); + file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); + menu_ = Alloc2d(text_rows_, text_cols_ + 1); + + text_col_ = text_row_ = 0; + text_top_ = 1; + + backgroundIcon[NONE] = nullptr; + LoadBitmapArray("icon_installing", &installing_frames, &animation_fps, &installation); + backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : nullptr; + backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE]; + LoadBitmap("icon_error", &backgroundIcon[ERROR]); + backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; + + LoadBitmap("progress_empty", &progressBarEmpty); + LoadBitmap("progress_fill", &progressBarFill); + LoadBitmap("stage_empty", &stageMarkerEmpty); + LoadBitmap("stage_fill", &stageMarkerFill); + + LoadLocalizedBitmap("installing_text", &backgroundText[INSTALLING_UPDATE]); + LoadLocalizedBitmap("erasing_text", &backgroundText[ERASING]); + LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]); + LoadLocalizedBitmap("error_text", &backgroundText[ERROR]); + + pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this); + + RecoveryUI::Init(); +} + +void ScreenRecoveryUI::SetLocale(const char* new_locale) { + if (new_locale) { + this->locale = new_locale; + char* lang = strdup(locale); + for (char* p = lang; *p; ++p) { + if (*p == '_') { + *p = '\0'; + break; + } + } + + // A bit cheesy: keep an explicit list of supported languages + // that are RTL. + if (strcmp(lang, "ar") == 0 || // Arabic + strcmp(lang, "fa") == 0 || // Persian (Farsi) + strcmp(lang, "he") == 0 || // Hebrew (new language code) + strcmp(lang, "iw") == 0 || // Hebrew (old language code) + strcmp(lang, "ur") == 0) { // Urdu + rtl_locale = true; + } + free(lang); + } else { + new_locale = nullptr; + } +} + +void ScreenRecoveryUI::SetBackground(Icon icon) { + pthread_mutex_lock(&updateMutex); + + currentIcon = icon; + update_screen_locked(); + + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::SetProgressType(ProgressType type) { + pthread_mutex_lock(&updateMutex); + if (progressBarType != type) { + progressBarType = type; + } + progressScopeStart = 0; + progressScopeSize = 0; + progress = 0; + update_progress_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::ShowProgress(float portion, float seconds) { + pthread_mutex_lock(&updateMutex); + progressBarType = DETERMINATE; + progressScopeStart += progressScopeSize; + progressScopeSize = portion; + progressScopeTime = now(); + progressScopeDuration = seconds; + progress = 0; + update_progress_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::SetProgress(float fraction) { + pthread_mutex_lock(&updateMutex); + if (fraction < 0.0) fraction = 0.0; + if (fraction > 1.0) fraction = 1.0; + if (progressBarType == DETERMINATE && fraction > progress) { + // Skip updates that aren't visibly different. + int width = gr_get_width(progressBarEmpty); + float scale = width * progressScopeSize; + if ((int) (progress * scale) != (int) (fraction * scale)) { + progress = fraction; + update_progress_locked(); + } + } + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::SetStage(int current, int max) { + pthread_mutex_lock(&updateMutex); + stage = current; + max_stage = max; + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::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); +} + +void ScreenRecoveryUI::Print(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + PrintV(fmt, true, ap); + va_end(ap); +} + +void ScreenRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + PrintV(fmt, false, ap); + va_end(ap); +} + +void ScreenRecoveryUI::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_; + + if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; + } + pthread_mutex_unlock(&updateMutex); +} + +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); + } + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::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) { + PrintOnScreenOnly("--(%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) { + while (text_row_ < text_rows_ - 1) PutChar('\n'); + show_prompt = true; + } else { + PutChar(ch); + if (text_col_ == 0 && text_row_ >= text_rows_ - 1) { + show_prompt = true; + } + } + } +} + +void ScreenRecoveryUI::ShowFile(const char* filename) { + FILE* fp = fopen_path(filename, "re"); + if (fp == nullptr) { + Print(" Unable to open %s: %s\n", filename, strerror(errno)); + return; + } + + 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_; + ClearText(); + + ShowFile(fp); + fclose(fp); + + 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, + int initial_selection) { + pthread_mutex_lock(&updateMutex); + if (text_rows_ > 0 && text_cols_ > 0) { + menu_headers_ = headers; + size_t i = 0; + for (; i < text_rows_ && items[i] != nullptr; ++i) { + strncpy(menu_[i], items[i], text_cols_ - 1); + menu_[i][text_cols_ - 1] = '\0'; + } + menu_items = i; + show_menu = true; + menu_sel = initial_selection; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +int ScreenRecoveryUI::SelectMenu(int sel) { + pthread_mutex_lock(&updateMutex); + if (show_menu) { + int old_sel = menu_sel; + menu_sel = sel; + + // Wrap at top and bottom. + if (menu_sel < 0) menu_sel = menu_items - 1; + if (menu_sel >= menu_items) menu_sel = 0; + + sel = menu_sel; + if (menu_sel != old_sel) update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); + return sel; +} + +void ScreenRecoveryUI::EndMenu() { + pthread_mutex_lock(&updateMutex); + if (show_menu && text_rows_ > 0 && text_cols_ > 0) { + show_menu = false; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +bool ScreenRecoveryUI::IsTextVisible() { + pthread_mutex_lock(&updateMutex); + int visible = show_text; + pthread_mutex_unlock(&updateMutex); + return visible; +} + +bool ScreenRecoveryUI::WasTextEverVisible() { + pthread_mutex_lock(&updateMutex); + int ever_visible = show_text_ever; + pthread_mutex_unlock(&updateMutex); + return ever_visible; +} + +void ScreenRecoveryUI::ShowText(bool visible) { + pthread_mutex_lock(&updateMutex); + show_text = visible; + if (show_text) show_text_ever = true; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::Redraw() { + pthread_mutex_lock(&updateMutex); + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::KeyLongPress(int) { + // Redraw so that if we're in the menu, the highlight + // will change color to indicate a successful long press. + Redraw(); +}
diff --git a/recovery/screen_ui.h b/recovery/screen_ui.h new file mode 100644 index 0000000..08a5f44 --- /dev/null +++ b/recovery/screen_ui.h
@@ -0,0 +1,144 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RECOVERY_SCREEN_UI_H +#define RECOVERY_SCREEN_UI_H + +#include <pthread.h> +#include <stdio.h> + +#include "ui.h" +#include "minui/minui.h" + +// Implementation of RecoveryUI appropriate for devices with a screen +// (shows an icon + a progress bar, text logging, menu, etc.) +class ScreenRecoveryUI : public RecoveryUI { + public: + ScreenRecoveryUI(); + + void Init(); + void SetLocale(const char* locale); + + // overall recovery state ("background image") + void SetBackground(Icon icon); + + // progress indicator + void SetProgressType(ProgressType type); + void ShowProgress(float portion, float seconds); + void SetProgress(float fraction); + + void SetStage(int current, int max); + + // text log + void ShowText(bool visible); + bool IsTextVisible(); + bool WasTextEverVisible(); + + // printing messages + void Print(const char* fmt, ...) __printflike(2, 3); + void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3); + void ShowFile(const char* filename); + + // menu display + void StartMenu(const char* const * headers, const char* const * items, + int initial_selection); + int SelectMenu(int sel); + void EndMenu(); + + void KeyLongPress(int); + + void Redraw(); + + enum UIElement { + HEADER, MENU, MENU_SEL_BG, MENU_SEL_BG_ACTIVE, MENU_SEL_FG, LOG, TEXT_FILL, INFO + }; + void SetColor(UIElement e); + + private: + Icon currentIcon; + int installingFrame; + const char* locale; + bool rtl_locale; + + pthread_mutex_t updateMutex; + GRSurface* backgroundIcon[5]; + GRSurface* backgroundText[5]; + GRSurface** installation; + GRSurface* progressBarEmpty; + GRSurface* progressBarFill; + GRSurface* stageMarkerEmpty; + GRSurface* stageMarkerFill; + + ProgressType progressBarType; + + float progressScopeStart, progressScopeSize, progress; + double progressScopeTime, progressScopeDuration; + + // true when both graphics pages are the same (except for the progress bar). + bool pagesIdentical; + + size_t text_cols_, text_rows_; + + // Log text overlay, displayed when a magic key is pressed. + char** text_; + size_t text_col_, text_row_, text_top_; + + bool show_text; + bool show_text_ever; // has show_text ever been true? + + char** menu_; + const char* const* menu_headers_; + bool show_menu; + int menu_items, menu_sel; + + // An alternate text screen, swapped with 'text_' when we're viewing a log file. + char** file_viewer_text_; + + pthread_t progress_thread_; + + // The following two are parsed from the image file + // (e.g. '/res/images/icon_installing.png'). + int animation_fps; + int installing_frames; + + int iconX, iconY; + + int stage, max_stage; + + void draw_background_locked(Icon icon); + void draw_progress_locked(); + void draw_screen_locked(); + void update_screen_locked(); + void update_progress_locked(); + + static void* ProgressThreadStartRoutine(void* data); + void ProgressThreadLoop(); + + void ShowFile(FILE*); + void PrintV(const char*, bool, va_list); + void PutChar(char); + void ClearText(); + + void DrawHorizontalRule(int* y); + void DrawTextLine(int* y, const char* line, bool bold); + void DrawTextLines(int* y, const char* const* lines); + + void LoadBitmap(const char* filename, GRSurface** surface); + void LoadBitmapArray(const char* filename, int* frames, int* fps, GRSurface*** surface); + void LoadLocalizedBitmap(const char* filename, GRSurface** surface); +}; + +#endif // RECOVERY_UI_H
diff --git a/recovery/tests/Android.mk b/recovery/tests/Android.mk new file mode 100644 index 0000000..a683395 --- /dev/null +++ b/recovery/tests/Android.mk
@@ -0,0 +1,58 @@ +# +# 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) + +# Unit tests +include $(CLEAR_VARS) +LOCAL_CLANG := true +LOCAL_MODULE := recovery_unit_test +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk +LOCAL_STATIC_LIBRARIES := libverifier +LOCAL_SRC_FILES := unit/asn1_decoder_test.cpp +LOCAL_SRC_FILES += unit/recovery_test.cpp +LOCAL_C_INCLUDES := bootable/recovery +LOCAL_SHARED_LIBRARIES := liblog +include $(BUILD_NATIVE_TEST) + +# Component tests +include $(CLEAR_VARS) +LOCAL_CLANG := true +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk +LOCAL_MODULE := recovery_component_test +LOCAL_C_INCLUDES := bootable/recovery +LOCAL_SRC_FILES := component/verifier_test.cpp +LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_STATIC_LIBRARIES := \ + libbase \ + libverifier \ + libcrypto_utils \ + libcrypto \ + libminui \ + libminzip \ + libcutils \ + libc + +testdata_out_path := $(TARGET_OUT_DATA_NATIVE_TESTS)/recovery +testdata_files := $(call find-subdir-files, testdata/*) + +GEN := $(addprefix $(testdata_out_path)/, $(testdata_files)) +$(GEN): PRIVATE_PATH := $(LOCAL_PATH) +$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@ +$(GEN): $(testdata_out_path)/% : $(LOCAL_PATH)/% + $(transform-generated-source) +LOCAL_GENERATED_SOURCES += $(GEN) +include $(BUILD_NATIVE_TEST)
diff --git a/recovery/tests/component/verifier_test.cpp b/recovery/tests/component/verifier_test.cpp new file mode 100644 index 0000000..2a78173 --- /dev/null +++ b/recovery/tests/component/verifier_test.cpp
@@ -0,0 +1,168 @@ +/* + * 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 agree to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <gtest/gtest.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <memory> +#include <string> +#include <vector> + +#include <openssl/sha.h> + +#include <android-base/stringprintf.h> + +#include "common.h" +#include "minzip/SysUtil.h" +#include "ui.h" +#include "verifier.h" + +#if defined(__LP64__) +#define NATIVE_TEST_PATH "/nativetest64" +#else +#define NATIVE_TEST_PATH "/nativetest" +#endif + +static const char* DATA_PATH = getenv("ANDROID_DATA"); +static const char* TESTDATA_PATH = "/recovery/testdata/"; + +RecoveryUI* ui = NULL; + +class MockUI : public RecoveryUI { + void Init() { } + void SetStage(int, int) { } + void SetLocale(const char*) { } + void SetBackground(Icon /*icon*/) { } + + void SetProgressType(ProgressType /*determinate*/) { } + void ShowProgress(float /*portion*/, float /*seconds*/) { } + void SetProgress(float /*fraction*/) { } + + void ShowText(bool /*visible*/) { } + bool IsTextVisible() { return false; } + bool WasTextEverVisible() { return false; } + void Print(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + void PrintOnScreenOnly(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + void ShowFile(const char*) { } + + void StartMenu(const char* const* /*headers*/, + const char* const* /*items*/, + int /*initial_selection*/) { } + int SelectMenu(int /*sel*/) { return 0; } + void EndMenu() { } +}; + +void +ui_print(const char* format, ...) { + va_list ap; + va_start(ap, format); + vfprintf(stdout, format, ap); + va_end(ap); +} + +class VerifierTest : public testing::TestWithParam<std::vector<std::string>> { + public: + MemMapping memmap; + std::vector<Certificate> certs; + + virtual void SetUp() { + std::vector<std::string> args = GetParam(); + std::string package = + android::base::StringPrintf("%s%s%s%s", DATA_PATH, NATIVE_TEST_PATH, + TESTDATA_PATH, args[0].c_str()); + if (sysMapFile(package.c_str(), &memmap) != 0) { + FAIL() << "Failed to mmap " << package << ": " << strerror(errno) << "\n"; + } + + for (auto it = ++(args.cbegin()); it != args.cend(); ++it) { + std::string public_key_file = android::base::StringPrintf( + "%s%s%stestkey_%s.txt", DATA_PATH, NATIVE_TEST_PATH, + TESTDATA_PATH, it->c_str()); + ASSERT_TRUE(load_keys(public_key_file.c_str(), certs)); + } + } + + static void SetUpTestCase() { + ui = new MockUI(); + } +}; + +class VerifierSuccessTest : public VerifierTest { +}; + +class VerifierFailureTest : public VerifierTest { +}; + +TEST_P(VerifierSuccessTest, VerifySucceed) { + ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs), VERIFY_SUCCESS); +} + +TEST_P(VerifierFailureTest, VerifyFailure) { + ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs), VERIFY_FAILURE); +} + +INSTANTIATE_TEST_CASE_P(SingleKeySuccess, VerifierSuccessTest, + ::testing::Values( + std::vector<std::string>({"otasigned_v1.zip", "v1"}), + std::vector<std::string>({"otasigned_v2.zip", "v2"}), + std::vector<std::string>({"otasigned_v3.zip", "v3"}), + std::vector<std::string>({"otasigned_v4.zip", "v4"}), + std::vector<std::string>({"otasigned_v5.zip", "v5"}))); + +INSTANTIATE_TEST_CASE_P(MultiKeySuccess, VerifierSuccessTest, + ::testing::Values( + std::vector<std::string>({"otasigned_v1.zip", "v1", "v2"}), + std::vector<std::string>({"otasigned_v2.zip", "v5", "v2"}), + std::vector<std::string>({"otasigned_v3.zip", "v5", "v1", "v3"}), + std::vector<std::string>({"otasigned_v4.zip", "v5", "v1", "v4"}), + std::vector<std::string>({"otasigned_v5.zip", "v4", "v1", "v5"}))); + +INSTANTIATE_TEST_CASE_P(WrongKey, VerifierFailureTest, + ::testing::Values( + std::vector<std::string>({"otasigned_v1.zip", "v2"}), + std::vector<std::string>({"otasigned_v2.zip", "v1"}), + std::vector<std::string>({"otasigned_v3.zip", "v5"}), + std::vector<std::string>({"otasigned_v4.zip", "v5"}), + std::vector<std::string>({"otasigned_v5.zip", "v3"}))); + +INSTANTIATE_TEST_CASE_P(WrongHash, VerifierFailureTest, + ::testing::Values( + std::vector<std::string>({"otasigned_v1.zip", "v3"}), + std::vector<std::string>({"otasigned_v2.zip", "v4"}), + std::vector<std::string>({"otasigned_v3.zip", "v1"}), + std::vector<std::string>({"otasigned_v4.zip", "v2"}))); + +INSTANTIATE_TEST_CASE_P(BadPackage, VerifierFailureTest, + ::testing::Values( + std::vector<std::string>({"random.zip", "v1"}), + std::vector<std::string>({"fake-eocd.zip", "v1"}), + std::vector<std::string>({"alter-metadata.zip", "v1"}), + std::vector<std::string>({"alter-footer.zip", "v1"})));
diff --git a/recovery/tests/testdata/alter-footer.zip b/recovery/tests/testdata/alter-footer.zip new file mode 100644 index 0000000..f497ec0 --- /dev/null +++ b/recovery/tests/testdata/alter-footer.zip Binary files differ
diff --git a/recovery/tests/testdata/alter-metadata.zip b/recovery/tests/testdata/alter-metadata.zip new file mode 100644 index 0000000..1c71fbc --- /dev/null +++ b/recovery/tests/testdata/alter-metadata.zip Binary files differ
diff --git a/recovery/tests/testdata/fake-eocd.zip b/recovery/tests/testdata/fake-eocd.zip new file mode 100644 index 0000000..15dc0a9 --- /dev/null +++ b/recovery/tests/testdata/fake-eocd.zip Binary files differ
diff --git a/recovery/tests/testdata/jarsigned.zip b/recovery/tests/testdata/jarsigned.zip new file mode 100644 index 0000000..8b1ef8b --- /dev/null +++ b/recovery/tests/testdata/jarsigned.zip Binary files differ
diff --git a/recovery/tests/testdata/otasigned_v1.zip b/recovery/tests/testdata/otasigned_v1.zip new file mode 100644 index 0000000..a6bc53e --- /dev/null +++ b/recovery/tests/testdata/otasigned_v1.zip Binary files differ
diff --git a/recovery/tests/testdata/otasigned_v2.zip b/recovery/tests/testdata/otasigned_v2.zip new file mode 100644 index 0000000..dd1e4dd --- /dev/null +++ b/recovery/tests/testdata/otasigned_v2.zip Binary files differ
diff --git a/recovery/tests/testdata/otasigned_v3.zip b/recovery/tests/testdata/otasigned_v3.zip new file mode 100644 index 0000000..0ed4409 --- /dev/null +++ b/recovery/tests/testdata/otasigned_v3.zip Binary files differ
diff --git a/recovery/tests/testdata/otasigned_v4.zip b/recovery/tests/testdata/otasigned_v4.zip new file mode 100644 index 0000000..3af408c --- /dev/null +++ b/recovery/tests/testdata/otasigned_v4.zip Binary files differ
diff --git a/recovery/tests/testdata/otasigned_v5.zip b/recovery/tests/testdata/otasigned_v5.zip new file mode 100644 index 0000000..999fcdd --- /dev/null +++ b/recovery/tests/testdata/otasigned_v5.zip Binary files differ
diff --git a/recovery/tests/testdata/random.zip b/recovery/tests/testdata/random.zip new file mode 100644 index 0000000..18c0b3b --- /dev/null +++ b/recovery/tests/testdata/random.zip Binary files differ
diff --git a/recovery/tests/testdata/testkey_v1.pk8 b/recovery/tests/testdata/testkey_v1.pk8 new file mode 100644 index 0000000..586c1bd --- /dev/null +++ b/recovery/tests/testdata/testkey_v1.pk8 Binary files differ
diff --git a/recovery/tests/testdata/testkey_v1.txt b/recovery/tests/testdata/testkey_v1.txt new file mode 100644 index 0000000..53f5297 --- /dev/null +++ b/recovery/tests/testdata/testkey_v1.txt
@@ -0,0 +1 @@ +{64,0xc926ad21,{1795090719,2141396315,950055447,2581568430,4268923165,1920809988,546586521,3498997798,1776797858,3740060814,1805317999,1429410244,129622599,1422441418,1783893377,1222374759,2563319927,323993566,28517732,609753416,1826472888,215237850,4261642700,4049082591,3228462402,774857746,154822455,2497198897,2758199418,3019015328,2794777644,87251430,2534927978,120774784,571297800,3695899472,2479925187,3811625450,3401832990,2394869647,3267246207,950095497,555058928,414729973,1136544882,3044590084,465547824,4058146728,2731796054,1689838846,3890756939,1048029507,895090649,247140249,178744550,3547885223,3165179243,109881576,3944604415,1044303212,3772373029,2985150306,3737520932,3599964420},{3437017481,3784475129,2800224972,3086222688,251333580,2131931323,512774938,325948880,2657486437,2102694287,3820568226,792812816,1026422502,2053275343,2800889200,3113586810,165549746,4273519969,4065247892,1902789247,772932719,3941848426,3652744109,216871947,3164400649,1942378755,3996765851,1055777370,964047799,629391717,2232744317,3910558992,191868569,2758883837,3682816752,2997714732,2702529250,3570700455,3776873832,3924067546,3555689545,2758825434,1323144535,61311905,1997411085,376844204,213777604,4077323584,9135381,1625809335,2804742137,2952293945,1117190829,4237312782,1825108855,3013147971,1111251351,2568837572,1684324211,2520978805,367251975,810756730,2353784344,1175080310}}
diff --git a/recovery/tests/testdata/testkey_v1.x509.pem b/recovery/tests/testdata/testkey_v1.x509.pem new file mode 100644 index 0000000..e242d83 --- /dev/null +++ b/recovery/tests/testdata/testkey_v1.x509.pem
@@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g +VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE +AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe +Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G +A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p +ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI +hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM +qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4 +wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy +4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU +RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s +zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw +HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ +AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE +CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH +QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG +CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud +EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa +J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y +LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe ++ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX +31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr +sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0= +-----END CERTIFICATE-----
diff --git a/recovery/tests/testdata/testkey_v2.pk8 b/recovery/tests/testdata/testkey_v2.pk8 new file mode 100644 index 0000000..3052613 --- /dev/null +++ b/recovery/tests/testdata/testkey_v2.pk8 Binary files differ
diff --git a/recovery/tests/testdata/testkey_v2.txt b/recovery/tests/testdata/testkey_v2.txt new file mode 100644 index 0000000..54ddbba --- /dev/null +++ b/recovery/tests/testdata/testkey_v2.txt
@@ -0,0 +1 @@ +v2 {64,0xc9bd1f21,{293133087,3210546773,865313125,250921607,3158780490,943703457,1242806226,2986289859,2942743769,2457906415,2719374299,1783459420,149579627,3081531591,3440738617,2788543742,2758457512,1146764939,3699497403,2446203424,1744968926,1159130537,2370028300,3978231572,3392699980,1487782451,1180150567,2841334302,3753960204,961373345,3333628321,748825784,2978557276,1566596926,1613056060,2600292737,1847226629,50398611,1890374404,2878700735,2286201787,1401186359,619285059,731930817,2340993166,1156490245,2992241729,151498140,318782170,3480838990,2100383433,4223552555,3628927011,4247846280,1759029513,4215632601,2719154626,3490334597,1751299340,3487864726,3668753795,4217506054,3748782284,3150295088},{1772626313,445326068,3477676155,1758201194,2986784722,491035581,3922936562,702212696,2979856666,3324974564,2488428922,3056318590,1626954946,664714029,398585816,3964097931,3356701905,2298377729,2040082097,3025491477,539143308,3348777868,2995302452,3602465520,212480763,2691021393,1307177300,704008044,2031136606,1054106474,3838318865,2441343869,1477566916,700949900,2534790355,3353533667,336163563,4106790558,2701448228,1571536379,1103842411,3623110423,1635278839,1577828979,910322800,715583630,138128831,1017877531,2289162787,447994798,1897243165,4121561445,4150719842,2131821093,2262395396,3305771534,980753571,3256525190,3128121808,1072869975,3507939515,4229109952,118381341,2209831334}}
diff --git a/recovery/tests/testdata/testkey_v2.x509.pem b/recovery/tests/testdata/testkey_v2.x509.pem new file mode 100644 index 0000000..814abcf --- /dev/null +++ b/recovery/tests/testdata/testkey_v2.x509.pem
@@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIJAKhkCO1dDYMaMA0GCSqGSIb3DQEBBQUAMG8xCzAJBgNV +BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBW +aWV3MQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMT +B1Rlc3QxMjMwHhcNMTIwNzI1MTg1NzAzWhcNMzkxMjExMTg1NzAzWjBvMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g +VmlldzEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRAwDgYDVQQD +EwdUZXN0MTIzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu8WwMN9x +4Mz7YgkG2qy9g8/kl5ZoYrUM0ApHhaITAcL7RXLZaNipCf0w/YjYTQgj+75MK30x +TsnPeWNOEwA62gkHrZyyWfxBRO6kBYuIuI4roGDBJOmKQ1OEaDeIRKu7q5V8v3Cs +0wQDAQWTbhpxBZr9UYFgJUg8XWBfPrGJLVwsoiy4xrMhoTlNZKHfwOMMqVtSHkZX +qydYrcIzyjh+TO0e/xSNQ8MMRRbtqWgCHN6Rzhog3IHZu0RaPoukariopjXM/s0V +gTm3rHDHCOpna2pNblyiFlvbkoCs769mtNmx/yrDShO30jg/xaG8RypKDvTChzOT +oWW/XQ5VEXjbHwIDAQABo4HUMIHRMB0GA1UdDgQWBBRlT2dEZJY1tmUM8mZ0xnhS +GdD9TTCBoQYDVR0jBIGZMIGWgBRlT2dEZJY1tmUM8mZ0xnhSGdD9TaFzpHEwbzEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50 +YWluIFZpZXcxDzANBgNVBAoTBkdvb2dsZTEQMA4GA1UECxMHQW5kcm9pZDEQMA4G +A1UEAxMHVGVzdDEyM4IJAKhkCO1dDYMaMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN +AQEFBQADggEBAHqnXHtE+h3hvGmHh24GT51vGAYLc68WUUtCVlMIU85zQ757wlxZ +BmRypZ1i9hSqnXj5n+mETV5rFX3g2gvdAPVHkRycuDa2aUdZSE8cW4Z6qYFx6SaD +e+3SyXokpUquW64RuHJrf/yd/FnGjneBe3Qpm2reuzGWNH90qZGdbsfNaCm5kx2L +X+ZNHM3CcGMLaphY5++sM0JxSEcju5EK33ZYgLf4YdlbyMp8LDFVNd7ff0SFi9fF +0ZlAsJWoS3QmVCj2744BFdsCu7UHpnYpG6X3MT4SHAawdOaT5zSuaCl2xx6H0O7t +w/Fvbl/KVD1ZmLHgBKjDMNSh0OB9mSsDWpw= +-----END CERTIFICATE-----
diff --git a/recovery/tests/testdata/testkey_v3.pk8 b/recovery/tests/testdata/testkey_v3.pk8 new file mode 120000 index 0000000..18ecf98 --- /dev/null +++ b/recovery/tests/testdata/testkey_v3.pk8
@@ -0,0 +1 @@ +testkey_v1.pk8 \ No newline at end of file
diff --git a/recovery/tests/testdata/testkey_v3.txt b/recovery/tests/testdata/testkey_v3.txt new file mode 100644 index 0000000..3208571 --- /dev/null +++ b/recovery/tests/testdata/testkey_v3.txt
@@ -0,0 +1 @@ +v3 {64,0xc926ad21,{1795090719,2141396315,950055447,2581568430,4268923165,1920809988,546586521,3498997798,1776797858,3740060814,1805317999,1429410244,129622599,1422441418,1783893377,1222374759,2563319927,323993566,28517732,609753416,1826472888,215237850,4261642700,4049082591,3228462402,774857746,154822455,2497198897,2758199418,3019015328,2794777644,87251430,2534927978,120774784,571297800,3695899472,2479925187,3811625450,3401832990,2394869647,3267246207,950095497,555058928,414729973,1136544882,3044590084,465547824,4058146728,2731796054,1689838846,3890756939,1048029507,895090649,247140249,178744550,3547885223,3165179243,109881576,3944604415,1044303212,3772373029,2985150306,3737520932,3599964420},{3437017481,3784475129,2800224972,3086222688,251333580,2131931323,512774938,325948880,2657486437,2102694287,3820568226,792812816,1026422502,2053275343,2800889200,3113586810,165549746,4273519969,4065247892,1902789247,772932719,3941848426,3652744109,216871947,3164400649,1942378755,3996765851,1055777370,964047799,629391717,2232744317,3910558992,191868569,2758883837,3682816752,2997714732,2702529250,3570700455,3776873832,3924067546,3555689545,2758825434,1323144535,61311905,1997411085,376844204,213777604,4077323584,9135381,1625809335,2804742137,2952293945,1117190829,4237312782,1825108855,3013147971,1111251351,2568837572,1684324211,2520978805,367251975,810756730,2353784344,1175080310}}
diff --git a/recovery/tests/testdata/testkey_v3.x509.pem b/recovery/tests/testdata/testkey_v3.x509.pem new file mode 100644 index 0000000..002ce89 --- /dev/null +++ b/recovery/tests/testdata/testkey_v3.x509.pem
@@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g +VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE +AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe +Fw0xMzA0MTAxODA1MzZaFw0xMzA1MTAxODA1MzZaMIGUMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G +A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p +ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI +hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM +qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4 +wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy +4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU +RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s +zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw +HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ +AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE +CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH +QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG +CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud +EwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKRVj9hOaozH1W8Wb4CNj7sCWixh +UMMZJXkxUtvUVHZGefp6MdtYiD/ZM7YRwZphm9aNhkykbHJdZ3lPzeL2csCa+sDQ +8sIzGu0/aD6p4zgIKQZmz0mZHqPGbHoLWOmA9EexRCFZ7vO/kO56ZbyhfFz2DI3S +Yez65CabErOFhNX6WukSPbV3zfsHRDD5JUStb/ko6t99HXsvIO0Ax9poj60PpCC1 +SiFzHZUY9mOnUfJFs+3NWCwKtP9nho3mZ3pJ1i+SeF6JiqbE3KHl4CDBeVGcu3CK +fiUZ8e8iXVN471Cgc5GD6Ud1pS7ifNZJsKhbETQ63KmvHCLRPi4NmP67uDE= +-----END CERTIFICATE-----
diff --git a/recovery/tests/testdata/testkey_v4.pk8 b/recovery/tests/testdata/testkey_v4.pk8 new file mode 120000 index 0000000..683b9a3 --- /dev/null +++ b/recovery/tests/testdata/testkey_v4.pk8
@@ -0,0 +1 @@ +testkey_v2.pk8 \ No newline at end of file
diff --git a/recovery/tests/testdata/testkey_v4.txt b/recovery/tests/testdata/testkey_v4.txt new file mode 100644 index 0000000..532cbd5 --- /dev/null +++ b/recovery/tests/testdata/testkey_v4.txt
@@ -0,0 +1 @@ +v4 {64,0xc9bd1f21,{293133087,3210546773,865313125,250921607,3158780490,943703457,1242806226,2986289859,2942743769,2457906415,2719374299,1783459420,149579627,3081531591,3440738617,2788543742,2758457512,1146764939,3699497403,2446203424,1744968926,1159130537,2370028300,3978231572,3392699980,1487782451,1180150567,2841334302,3753960204,961373345,3333628321,748825784,2978557276,1566596926,1613056060,2600292737,1847226629,50398611,1890374404,2878700735,2286201787,1401186359,619285059,731930817,2340993166,1156490245,2992241729,151498140,318782170,3480838990,2100383433,4223552555,3628927011,4247846280,1759029513,4215632601,2719154626,3490334597,1751299340,3487864726,3668753795,4217506054,3748782284,3150295088},{1772626313,445326068,3477676155,1758201194,2986784722,491035581,3922936562,702212696,2979856666,3324974564,2488428922,3056318590,1626954946,664714029,398585816,3964097931,3356701905,2298377729,2040082097,3025491477,539143308,3348777868,2995302452,3602465520,212480763,2691021393,1307177300,704008044,2031136606,1054106474,3838318865,2441343869,1477566916,700949900,2534790355,3353533667,336163563,4106790558,2701448228,1571536379,1103842411,3623110423,1635278839,1577828979,910322800,715583630,138128831,1017877531,2289162787,447994798,1897243165,4121561445,4150719842,2131821093,2262395396,3305771534,980753571,3256525190,3128121808,1072869975,3507939515,4229109952,118381341,2209831334}}
diff --git a/recovery/tests/testdata/testkey_v4.x509.pem b/recovery/tests/testdata/testkey_v4.x509.pem new file mode 100644 index 0000000..9d5376b --- /dev/null +++ b/recovery/tests/testdata/testkey_v4.x509.pem
@@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIJAKhkCO1dDYMaMA0GCSqGSIb3DQEBCwUAMG8xCzAJBgNV +BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBW +aWV3MQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMT +B1Rlc3QxMjMwHhcNMTMwNDEwMTcyMzUyWhcNMTMwNTEwMTcyMzUyWjBvMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g +VmlldzEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRAwDgYDVQQD +EwdUZXN0MTIzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu8WwMN9x +4Mz7YgkG2qy9g8/kl5ZoYrUM0ApHhaITAcL7RXLZaNipCf0w/YjYTQgj+75MK30x +TsnPeWNOEwA62gkHrZyyWfxBRO6kBYuIuI4roGDBJOmKQ1OEaDeIRKu7q5V8v3Cs +0wQDAQWTbhpxBZr9UYFgJUg8XWBfPrGJLVwsoiy4xrMhoTlNZKHfwOMMqVtSHkZX +qydYrcIzyjh+TO0e/xSNQ8MMRRbtqWgCHN6Rzhog3IHZu0RaPoukariopjXM/s0V +gTm3rHDHCOpna2pNblyiFlvbkoCs769mtNmx/yrDShO30jg/xaG8RypKDvTChzOT +oWW/XQ5VEXjbHwIDAQABo4HUMIHRMB0GA1UdDgQWBBRlT2dEZJY1tmUM8mZ0xnhS +GdD9TTCBoQYDVR0jBIGZMIGWgBRlT2dEZJY1tmUM8mZ0xnhSGdD9TaFzpHEwbzEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50 +YWluIFZpZXcxDzANBgNVBAoTBkdvb2dsZTEQMA4GA1UECxMHQW5kcm9pZDEQMA4G +A1UEAxMHVGVzdDEyM4IJAKhkCO1dDYMaMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN +AQELBQADggEBAKWWQ9S0V9wWjrMJe8exj1gklwD1Ysi0vi+h2tfixahelrpsNkWi +EFjoUSHEkW9ThLmtui646uAlwSiWtSn1XkGGmIJ3s+gmAFUcMc0CaK0dgoq/M9zn +fQ0Vkzc1tK4MLsf+CbPDywPycb6+T3dBkerbWn9GUpjGl1ANWlciXZZ3657m61sL +HhwUOBxbZZ6sYP4ed2SVCf45GgMyJ0VoUg5yI2JzPAgOkGfeEIPVXE1M94edJY4G +8eHYvXovJZwXvKFI+ZyS0KBPx8cpfw89RB9qmkxqNBIm8qWb3qBiuBEIPj+NF/7w +sC/Fv8NNXkVquy0xa0qdyJBABzWE18zGcXs= +-----END CERTIFICATE-----
diff --git a/recovery/tests/testdata/testkey_v5.pk8 b/recovery/tests/testdata/testkey_v5.pk8 new file mode 100644 index 0000000..9a521c8 --- /dev/null +++ b/recovery/tests/testdata/testkey_v5.pk8 Binary files differ
diff --git a/recovery/tests/testdata/testkey_v5.txt b/recovery/tests/testdata/testkey_v5.txt new file mode 100644 index 0000000..72b4395 --- /dev/null +++ b/recovery/tests/testdata/testkey_v5.txt
@@ -0,0 +1 @@ +v5 {32,{36,250,86,214,202,22,20,147,198,120,2,28,76,190,78,23,106,35,24,96,86,22,186,69,132,93,192,232,0,213,14,103},{222,154,23,13,125,130,22,76,146,185,140,159,138,255,105,143,32,16,27,72,175,145,141,121,233,184,77,24,217,141,132,181}}
diff --git a/recovery/tests/testdata/testkey_v5.x509.pem b/recovery/tests/testdata/testkey_v5.x509.pem new file mode 100644 index 0000000..b122836 --- /dev/null +++ b/recovery/tests/testdata/testkey_v5.x509.pem
@@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBezCCASACCQC4g5wurPSmtzAKBggqhkjOPQQDAjBFMQswCQYDVQQGEwJBVTET +MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMB4XDTEzMTAwODIxMTAxM1oXDTE0MTAwODIxMTAxM1owRTELMAkGA1UE +BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp +ZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGcO1QDowF2E +RboWVmAYI2oXTr5MHAJ4xpMUFsrWVvoktYSN2RhNuOl5jZGvSBsQII9p/4qfjLmS +TBaCfQ0Xmt4wCgYIKoZIzj0EAwIDSQAwRgIhAIJjWmZAwngc2VcHUhYp2oSLoCQ+ +P+7AtbAn5242AqfOAiEAghO0t6jTKs0LUhLJrQwbOkHyZMVdZaG2vcwV9y9H5Qc= +-----END CERTIFICATE-----
diff --git a/recovery/tests/testdata/unsigned.zip b/recovery/tests/testdata/unsigned.zip new file mode 100644 index 0000000..24e3ead --- /dev/null +++ b/recovery/tests/testdata/unsigned.zip Binary files differ
diff --git a/recovery/tests/unit/asn1_decoder_test.cpp b/recovery/tests/unit/asn1_decoder_test.cpp new file mode 100644 index 0000000..af96d87 --- /dev/null +++ b/recovery/tests/unit/asn1_decoder_test.cpp
@@ -0,0 +1,238 @@ +/* + * Copyright (C) 2013 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. + */ + +#define LOG_TAG "asn1_decoder_test" + +#include <cutils/log.h> +#include <gtest/gtest.h> +#include <stdint.h> +#include <unistd.h> + +#include "asn1_decoder.h" + +namespace android { + +class Asn1DecoderTest : public testing::Test { +}; + +TEST_F(Asn1DecoderTest, Empty_Failure) { + uint8_t empty[] = { }; + asn1_context_t* ctx = asn1_context_new(empty, sizeof(empty)); + + EXPECT_EQ(NULL, asn1_constructed_get(ctx)); + EXPECT_FALSE(asn1_constructed_skip_all(ctx)); + EXPECT_EQ(0, asn1_constructed_type(ctx)); + EXPECT_EQ(NULL, asn1_sequence_get(ctx)); + EXPECT_EQ(NULL, asn1_set_get(ctx)); + EXPECT_FALSE(asn1_sequence_next(ctx)); + + uint8_t* junk; + size_t length; + EXPECT_FALSE(asn1_oid_get(ctx, &junk, &length)); + EXPECT_FALSE(asn1_octet_string_get(ctx, &junk, &length)); + + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, ConstructedGet_TruncatedLength_Failure) { + uint8_t truncated[] = { 0xA0, 0x82, }; + asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated)); + EXPECT_EQ(NULL, asn1_constructed_get(ctx)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, ConstructedGet_LengthTooBig_Failure) { + uint8_t truncated[] = { 0xA0, 0x8a, 0xA5, 0x5A, 0xA5, 0x5A, + 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, }; + asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated)); + EXPECT_EQ(NULL, asn1_constructed_get(ctx)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, ConstructedGet_TooSmallForChild_Failure) { + uint8_t data[] = { 0xA5, 0x02, 0x06, 0x01, 0x01, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + asn1_context_t* ptr = asn1_constructed_get(ctx); + ASSERT_NE((asn1_context_t*)NULL, ptr); + EXPECT_EQ(5, asn1_constructed_type(ptr)); + uint8_t* oid; + size_t length; + EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length)); + asn1_context_free(ptr); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, ConstructedGet_Success) { + uint8_t data[] = { 0xA5, 0x03, 0x06, 0x01, 0x01, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + asn1_context_t* ptr = asn1_constructed_get(ctx); + ASSERT_NE((asn1_context_t*)NULL, ptr); + EXPECT_EQ(5, asn1_constructed_type(ptr)); + uint8_t* oid; + size_t length; + ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length)); + EXPECT_EQ(1U, length); + EXPECT_EQ(0x01U, *oid); + asn1_context_free(ptr); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, ConstructedSkipAll_TruncatedLength_Failure) { + uint8_t truncated[] = { 0xA2, 0x82, }; + asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated)); + EXPECT_FALSE(asn1_constructed_skip_all(ctx)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, ConstructedSkipAll_Success) { + uint8_t data[] = { 0xA0, 0x03, 0x02, 0x01, 0x01, + 0xA1, 0x03, 0x02, 0x01, 0x01, + 0x06, 0x01, 0xA5, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + ASSERT_TRUE(asn1_constructed_skip_all(ctx)); + uint8_t* oid; + size_t length; + ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length)); + EXPECT_EQ(1U, length); + EXPECT_EQ(0xA5U, *oid); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, SequenceGet_TruncatedLength_Failure) { + uint8_t truncated[] = { 0x30, 0x82, }; + asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated)); + EXPECT_EQ(NULL, asn1_sequence_get(ctx)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, SequenceGet_TooSmallForChild_Failure) { + uint8_t data[] = { 0x30, 0x02, 0x06, 0x01, 0x01, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + asn1_context_t* ptr = asn1_sequence_get(ctx); + ASSERT_NE((asn1_context_t*)NULL, ptr); + uint8_t* oid; + size_t length; + EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length)); + asn1_context_free(ptr); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, SequenceGet_Success) { + uint8_t data[] = { 0x30, 0x03, 0x06, 0x01, 0x01, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + asn1_context_t* ptr = asn1_sequence_get(ctx); + ASSERT_NE((asn1_context_t*)NULL, ptr); + uint8_t* oid; + size_t length; + ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length)); + EXPECT_EQ(1U, length); + EXPECT_EQ(0x01U, *oid); + asn1_context_free(ptr); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, SetGet_TruncatedLength_Failure) { + uint8_t truncated[] = { 0x31, 0x82, }; + asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated)); + EXPECT_EQ(NULL, asn1_set_get(ctx)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, SetGet_TooSmallForChild_Failure) { + uint8_t data[] = { 0x31, 0x02, 0x06, 0x01, 0x01, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + asn1_context_t* ptr = asn1_set_get(ctx); + ASSERT_NE((asn1_context_t*)NULL, ptr); + uint8_t* oid; + size_t length; + EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length)); + asn1_context_free(ptr); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, SetGet_Success) { + uint8_t data[] = { 0x31, 0x03, 0x06, 0x01, 0xBA, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + asn1_context_t* ptr = asn1_set_get(ctx); + ASSERT_NE((asn1_context_t*)NULL, ptr); + uint8_t* oid; + size_t length; + ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length)); + EXPECT_EQ(1U, length); + EXPECT_EQ(0xBAU, *oid); + asn1_context_free(ptr); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, OidGet_LengthZero_Failure) { + uint8_t data[] = { 0x06, 0x00, 0x01, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + uint8_t* oid; + size_t length; + EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, OidGet_TooSmall_Failure) { + uint8_t data[] = { 0x06, 0x01, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + uint8_t* oid; + size_t length; + EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, OidGet_Success) { + uint8_t data[] = { 0x06, 0x01, 0x99, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + uint8_t* oid; + size_t length; + ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length)); + EXPECT_EQ(1U, length); + EXPECT_EQ(0x99U, *oid); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, OctetStringGet_LengthZero_Failure) { + uint8_t data[] = { 0x04, 0x00, 0x55, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + uint8_t* string; + size_t length; + ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, OctetStringGet_TooSmall_Failure) { + uint8_t data[] = { 0x04, 0x01, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + uint8_t* string; + size_t length; + ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, OctetStringGet_Success) { + uint8_t data[] = { 0x04, 0x01, 0xAA, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + uint8_t* string; + size_t length; + ASSERT_TRUE(asn1_octet_string_get(ctx, &string, &length)); + EXPECT_EQ(1U, length); + EXPECT_EQ(0xAAU, *string); + asn1_context_free(ctx); +} + +} // namespace android
diff --git a/recovery/tests/unit/recovery_test.cpp b/recovery/tests/unit/recovery_test.cpp new file mode 100644 index 0000000..f397f25 --- /dev/null +++ b/recovery/tests/unit/recovery_test.cpp
@@ -0,0 +1,92 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fcntl.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> + +#include <android/log.h> +#include <gtest/gtest.h> +#include <log/logger.h> +#include <private/android_logger.h> + +static const char myFilename[] = "/data/misc/recovery/inject.txt"; +static const char myContent[] = "Hello World\nWelcome to my recovery\n"; + +// Failure is expected on systems that do not deliver either the +// recovery-persist or recovery-refresh executables. Tests also require +// a reboot sequence of test to truly verify. + +static ssize_t __pmsg_fn(log_id_t logId, char prio, const char *filename, + const char *buf, size_t len, void *arg) { + EXPECT_EQ(LOG_ID_SYSTEM, logId); + EXPECT_EQ(ANDROID_LOG_INFO, prio); + EXPECT_EQ(0, NULL == strstr(myFilename,filename)); + EXPECT_EQ(0, strcmp(myContent, buf)); + EXPECT_EQ(sizeof(myContent), len); + EXPECT_EQ(0, NULL != arg); + return len; +} + +// recovery.refresh - May fail. Requires recovery.inject, two reboots, +// then expect success after second reboot. +TEST(recovery, refresh) { + EXPECT_EQ(0, access("/system/bin/recovery-refresh", F_OK)); + + ssize_t ret = __android_log_pmsg_file_read( + LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, NULL); + if (ret == -ENOENT) { + EXPECT_LT(0, __android_log_pmsg_file_write( + LOG_ID_SYSTEM, ANDROID_LOG_INFO, + myFilename, myContent, sizeof(myContent))); + fprintf(stderr, "injected test data, " + "requires two intervening reboots " + "to check for replication\n"); + } + EXPECT_EQ((ssize_t)sizeof(myContent), ret); +} + +// recovery.persist - Requires recovery.inject, then a reboot, then +// expect success after for this test on that boot. +TEST(recovery, persist) { + EXPECT_EQ(0, access("/system/bin/recovery-persist", F_OK)); + + ssize_t ret = __android_log_pmsg_file_read( + LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, NULL); + if (ret == -ENOENT) { + EXPECT_LT(0, __android_log_pmsg_file_write( + LOG_ID_SYSTEM, ANDROID_LOG_INFO, + myFilename, myContent, sizeof(myContent))); + fprintf(stderr, "injected test data, " + "requires intervening reboot " + "to check for storage\n"); + } + + int fd = open(myFilename, O_RDONLY); + EXPECT_LE(0, fd); + + char buf[sizeof(myContent) + 32]; + ret = read(fd, buf, sizeof(buf)); + close(fd); + EXPECT_EQ(ret, (ssize_t)sizeof(myContent)); + EXPECT_EQ(0, strcmp(myContent, buf)); + if (fd >= 0) { + fprintf(stderr, "Removing persistent test data, " + "check if reconstructed on reboot\n"); + } + EXPECT_EQ(0, unlink(myFilename)); +}
diff --git a/recovery/tools/Android.mk b/recovery/tools/Android.mk new file mode 100644 index 0000000..6571161 --- /dev/null +++ b/recovery/tools/Android.mk
@@ -0,0 +1 @@ +include $(all-subdir-makefiles)
diff --git a/recovery/tools/dumpkey/Android.mk b/recovery/tools/dumpkey/Android.mk new file mode 100644 index 0000000..3154914 --- /dev/null +++ b/recovery/tools/dumpkey/Android.mk
@@ -0,0 +1,22 @@ +# 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) + +include $(CLEAR_VARS) +LOCAL_MODULE := dumpkey +LOCAL_SRC_FILES := DumpPublicKey.java +LOCAL_JAR_MANIFEST := DumpPublicKey.mf +LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host +include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/recovery/tools/dumpkey/DumpPublicKey.java b/recovery/tools/dumpkey/DumpPublicKey.java new file mode 100644 index 0000000..3eb1398 --- /dev/null +++ b/recovery/tools/dumpkey/DumpPublicKey.java
@@ -0,0 +1,270 @@ +/* + * 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. + */ + +package com.android.dumpkey; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.io.FileInputStream; +import java.math.BigInteger; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.KeyStore; +import java.security.Key; +import java.security.PublicKey; +import java.security.Security; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.ECPoint; + +/** + * Command line tool to extract RSA public keys from X.509 certificates + * and output source code with data initializers for the keys. + * @hide + */ +class DumpPublicKey { + /** + * @param key to perform sanity checks on + * @return version number of key. Supported versions are: + * 1: 2048-bit RSA key with e=3 and SHA-1 hash + * 2: 2048-bit RSA key with e=65537 and SHA-1 hash + * 3: 2048-bit RSA key with e=3 and SHA-256 hash + * 4: 2048-bit RSA key with e=65537 and SHA-256 hash + * @throws Exception if the key has the wrong size or public exponent + */ + static int checkRSA(RSAPublicKey key, boolean useSHA256) throws Exception { + BigInteger pubexp = key.getPublicExponent(); + BigInteger modulus = key.getModulus(); + int version; + + if (pubexp.equals(BigInteger.valueOf(3))) { + version = useSHA256 ? 3 : 1; + } else if (pubexp.equals(BigInteger.valueOf(65537))) { + version = useSHA256 ? 4 : 2; + } else { + throw new Exception("Public exponent should be 3 or 65537 but is " + + pubexp.toString(10) + "."); + } + + if (modulus.bitLength() != 2048) { + throw new Exception("Modulus should be 2048 bits long but is " + + modulus.bitLength() + " bits."); + } + + return version; + } + + /** + * @param key to perform sanity checks on + * @return version number of key. Supported versions are: + * 5: 256-bit EC key with curve NIST P-256 + * @throws Exception if the key has the wrong size or public exponent + */ + static int checkEC(ECPublicKey key) throws Exception { + if (key.getParams().getCurve().getField().getFieldSize() != 256) { + throw new Exception("Curve must be NIST P-256"); + } + + return 5; + } + + /** + * Perform sanity check on public key. + */ + static int check(PublicKey key, boolean useSHA256) throws Exception { + if (key instanceof RSAPublicKey) { + return checkRSA((RSAPublicKey) key, useSHA256); + } else if (key instanceof ECPublicKey) { + if (!useSHA256) { + throw new Exception("Must use SHA-256 with EC keys!"); + } + return checkEC((ECPublicKey) key); + } else { + throw new Exception("Unsupported key class: " + key.getClass().getName()); + } + } + + /** + * @param key to output + * @return a String representing this public key. If the key is a + * version 1 key, the string will be a C initializer; this is + * not true for newer key versions. + */ + static String printRSA(RSAPublicKey key, boolean useSHA256) throws Exception { + int version = check(key, useSHA256); + + BigInteger N = key.getModulus(); + + StringBuilder result = new StringBuilder(); + + int nwords = N.bitLength() / 32; // # of 32 bit integers in modulus + + if (version > 1) { + result.append("v"); + result.append(Integer.toString(version)); + result.append(" "); + } + + result.append("{"); + result.append(nwords); + + BigInteger B = BigInteger.valueOf(0x100000000L); // 2^32 + BigInteger N0inv = B.subtract(N.modInverse(B)); // -1 / N[0] mod 2^32 + + result.append(",0x"); + result.append(N0inv.toString(16)); + + BigInteger R = BigInteger.valueOf(2).pow(N.bitLength()); + BigInteger RR = R.multiply(R).mod(N); // 2^4096 mod N + + // Write out modulus as little endian array of integers. + result.append(",{"); + for (int i = 0; i < nwords; ++i) { + long n = N.mod(B).longValue(); + result.append(n); + + if (i != nwords - 1) { + result.append(","); + } + + N = N.divide(B); + } + result.append("}"); + + // Write R^2 as little endian array of integers. + result.append(",{"); + for (int i = 0; i < nwords; ++i) { + long rr = RR.mod(B).longValue(); + result.append(rr); + + if (i != nwords - 1) { + result.append(","); + } + + RR = RR.divide(B); + } + result.append("}"); + + result.append("}"); + return result.toString(); + } + + /** + * @param key to output + * @return a String representing this public key. If the key is a + * version 1 key, the string will be a C initializer; this is + * not true for newer key versions. + */ + static String printEC(ECPublicKey key) throws Exception { + int version = checkEC(key); + + StringBuilder result = new StringBuilder(); + + result.append("v"); + result.append(Integer.toString(version)); + result.append(" "); + + BigInteger X = key.getW().getAffineX(); + BigInteger Y = key.getW().getAffineY(); + int nbytes = key.getParams().getCurve().getField().getFieldSize() / 8; // # of 32 bit integers in X coordinate + + result.append("{"); + result.append(nbytes); + + BigInteger B = BigInteger.valueOf(0x100L); // 2^8 + + // Write out Y coordinate as array of characters. + result.append(",{"); + for (int i = 0; i < nbytes; ++i) { + long n = X.mod(B).longValue(); + result.append(n); + + if (i != nbytes - 1) { + result.append(","); + } + + X = X.divide(B); + } + result.append("}"); + + // Write out Y coordinate as array of characters. + result.append(",{"); + for (int i = 0; i < nbytes; ++i) { + long n = Y.mod(B).longValue(); + result.append(n); + + if (i != nbytes - 1) { + result.append(","); + } + + Y = Y.divide(B); + } + result.append("}"); + + result.append("}"); + return result.toString(); + } + + static String print(PublicKey key, boolean useSHA256) throws Exception { + if (key instanceof RSAPublicKey) { + return printRSA((RSAPublicKey) key, useSHA256); + } else if (key instanceof ECPublicKey) { + return printEC((ECPublicKey) key); + } else { + throw new Exception("Unsupported key class: " + key.getClass().getName()); + } + } + + public static void main(String[] args) { + if (args.length < 1) { + System.err.println("Usage: DumpPublicKey certfile ... > source.c"); + System.exit(1); + } + Security.addProvider(new BouncyCastleProvider()); + try { + for (int i = 0; i < args.length; i++) { + FileInputStream input = new FileInputStream(args[i]); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(input); + + boolean useSHA256 = false; + String sigAlg = cert.getSigAlgName(); + if ("SHA1withRSA".equals(sigAlg) || "MD5withRSA".equals(sigAlg)) { + // SignApk has historically accepted "MD5withRSA" + // certificates, but treated them as "SHA1withRSA" + // anyway. Continue to do so for backwards + // compatibility. + useSHA256 = false; + } else if ("SHA256withRSA".equals(sigAlg) || "SHA256withECDSA".equals(sigAlg)) { + useSHA256 = true; + } else { + System.err.println(args[i] + ": unsupported signature algorithm \"" + + sigAlg + "\""); + System.exit(1); + } + + PublicKey key = cert.getPublicKey(); + check(key, useSHA256); + System.out.print(print(key, useSHA256)); + System.out.println(i < args.length - 1 ? "," : ""); + } + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + System.exit(0); + } +}
diff --git a/recovery/tools/dumpkey/DumpPublicKey.mf b/recovery/tools/dumpkey/DumpPublicKey.mf new file mode 100644 index 0000000..7bb3bc8 --- /dev/null +++ b/recovery/tools/dumpkey/DumpPublicKey.mf
@@ -0,0 +1 @@ +Main-Class: com.android.dumpkey.DumpPublicKey
diff --git a/recovery/tools/recovery_l10n/Android.mk b/recovery/tools/recovery_l10n/Android.mk new file mode 100644 index 0000000..937abd1 --- /dev/null +++ b/recovery/tools/recovery_l10n/Android.mk
@@ -0,0 +1,12 @@ +# Copyright 2012 Google Inc. All Rights Reserved. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_PACKAGE_NAME := RecoveryLocalizer +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +include $(BUILD_PACKAGE)
diff --git a/recovery/tools/recovery_l10n/AndroidManifest.xml b/recovery/tools/recovery_l10n/AndroidManifest.xml new file mode 100644 index 0000000..8c51a4e --- /dev/null +++ b/recovery/tools/recovery_l10n/AndroidManifest.xml
@@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.recovery_l10n"> + + <application android:label="Recovery Localizer"> + <activity android:name="Main" + android:label="Recovery Localizer"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + +</manifest> + +
diff --git a/recovery/tools/recovery_l10n/README.md b/recovery/tools/recovery_l10n/README.md new file mode 100644 index 0000000..1554f66 --- /dev/null +++ b/recovery/tools/recovery_l10n/README.md
@@ -0,0 +1,33 @@ +# Steps to regenerate background text images under res-*dpi/images/ + +1. Build the recovery_l10n app: + + cd bootable/recovery && mma -j32 + +2. Install the app on the device (or emulator) with the intended dpi. + + * For example, we can use Nexus 5 to generate the text images under + res-xxhdpi. + * When using the emulator, make sure the NDK version matches the current + repository. Otherwise, the app may not work properly. + + adb install $PATH_TO_APP + +3. Run the app, select the string to translate and press the 'go' button. + +4. After the app goes through the strings for all locales, pull the output png + file from the device. + + adb root && adb pull /data/data/com.android.recovery_l10n/files/text-out.png + +5. Compress the output file put it under the corresponding directory. + + * "pngcrush -c 0 ..." converts "text-out.png" into a 1-channel image, + which is accepted by Recovery. This also compresses the image file by + ~60%. + * zopflipng could further compress the png files by ~10%, more details + in https://github.com/google/zopfli/blob/master/README.zopflipng + * If you're using other png compression tools, make sure the final text + image works by running graphic tests under the recovery mode. + + pngcrush -c 0 text-out.png $OUTPUT_PNG
diff --git a/recovery/tools/recovery_l10n/res/layout/main.xml b/recovery/tools/recovery_l10n/res/layout/main.xml new file mode 100644 index 0000000..0900b11 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/layout/main.xml
@@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + > + + <Spinner android:id="@+id/which" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + /> + + <Button android:id="@+id/go" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/go" + /> + + <TextView android:id="@+id/text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="#ffffffff" + android:background="#ff000000" + android:maxWidth="480px" + android:gravity="center" + /> + + +</LinearLayout> + +
diff --git a/recovery/tools/recovery_l10n/res/values-af/strings.xml b/recovery/tools/recovery_l10n/res/values-af/strings.xml new file mode 100644 index 0000000..d526418 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-af/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Installeer tans stelselopdatering..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Vee tans uit..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Geen bevel."</string> + <string name="recovery_error" msgid="4550265746256727080">"Fout!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-am/strings.xml b/recovery/tools/recovery_l10n/res/values-am/strings.xml new file mode 100644 index 0000000..cddb099 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-am/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"የስርዓት ዝማኔ በመጫን ላይ…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"በመደምሰስ ላይ…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"ምንም ትዕዛዝ የለም።"</string> + <string name="recovery_error" msgid="4550265746256727080">"ስህተት!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-ar/strings.xml b/recovery/tools/recovery_l10n/res/values-ar/strings.xml new file mode 100644 index 0000000..d06b966 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-ar/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"جارٍ تثبيت تحديث النظام…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"جارٍ المسح…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"ليس هناك أي أمر."</string> + <string name="recovery_error" msgid="4550265746256727080">"خطأ!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-az-rAZ/strings.xml b/recovery/tools/recovery_l10n/res/values-az-rAZ/strings.xml new file mode 100644 index 0000000..3435573 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-az-rAZ/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Sistem güncəlləməsi quraşdırılır..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Silinir..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Əmr yoxdur."</string> + <string name="recovery_error" msgid="4550265746256727080">"Xəta!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-bg/strings.xml b/recovery/tools/recovery_l10n/res/values-bg/strings.xml new file mode 100644 index 0000000..004f3b9 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-bg/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Системната актуализация се инсталира…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Изтрива се…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Без команда."</string> + <string name="recovery_error" msgid="4550265746256727080">"Грешка!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-bn-rBD/strings.xml b/recovery/tools/recovery_l10n/res/values-bn-rBD/strings.xml new file mode 100644 index 0000000..4d2e590 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-bn-rBD/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"সিস্টেম আপডেট ইনস্টল করা হচ্ছে…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"মোছা হচ্ছে…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"কোনো নির্দেশ নেই।"</string> + <string name="recovery_error" msgid="4550265746256727080">"ত্রুটি!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-ca/strings.xml b/recovery/tools/recovery_l10n/res/values-ca/strings.xml new file mode 100644 index 0000000..5d7b652 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-ca/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"S\'està instal·lant l\'actualització del sistema..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"S\'està esborrant..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Cap ordre."</string> + <string name="recovery_error" msgid="4550265746256727080">"Error!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-cs/strings.xml b/recovery/tools/recovery_l10n/res/values-cs/strings.xml new file mode 100644 index 0000000..771235d --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-cs/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Instalace aktualizace systému..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Mazání…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Žádný příkaz."</string> + <string name="recovery_error" msgid="4550265746256727080">"Chyba!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-da/strings.xml b/recovery/tools/recovery_l10n/res/values-da/strings.xml new file mode 100644 index 0000000..c28a76f --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-da/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Systemopdateringen installeres…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Sletter…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Ingen kommando."</string> + <string name="recovery_error" msgid="4550265746256727080">"Fejl!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-de/strings.xml b/recovery/tools/recovery_l10n/res/values-de/strings.xml new file mode 100644 index 0000000..02d2590 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-de/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Systemupdate wird installiert…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Wird gelöscht…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Kein Befehl"</string> + <string name="recovery_error" msgid="4550265746256727080">"Fehler"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-el/strings.xml b/recovery/tools/recovery_l10n/res/values-el/strings.xml new file mode 100644 index 0000000..aa2626b --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-el/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Εγκατάσταση ενημέρωσης συστήματος…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Διαγραφή…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Καμία εντολή."</string> + <string name="recovery_error" msgid="4550265746256727080">"Σφάλμα!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-en-rAU/strings.xml b/recovery/tools/recovery_l10n/res/values-en-rAU/strings.xml new file mode 100644 index 0000000..b70d678 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-en-rAU/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Installing system update…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Erasing…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"No command."</string> + <string name="recovery_error" msgid="4550265746256727080">"Error!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-en-rGB/strings.xml b/recovery/tools/recovery_l10n/res/values-en-rGB/strings.xml new file mode 100644 index 0000000..b70d678 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-en-rGB/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Installing system update…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Erasing…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"No command."</string> + <string name="recovery_error" msgid="4550265746256727080">"Error!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-en-rIN/strings.xml b/recovery/tools/recovery_l10n/res/values-en-rIN/strings.xml new file mode 100644 index 0000000..b70d678 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-en-rIN/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Installing system update…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Erasing…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"No command."</string> + <string name="recovery_error" msgid="4550265746256727080">"Error!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-es-rUS/strings.xml b/recovery/tools/recovery_l10n/res/values-es-rUS/strings.xml new file mode 100644 index 0000000..256272a --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-es-rUS/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Instalando actualización del sistema…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Borrando…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Ningún comando"</string> + <string name="recovery_error" msgid="4550265746256727080">"Error"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-es/strings.xml b/recovery/tools/recovery_l10n/res/values-es/strings.xml new file mode 100644 index 0000000..323f055 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-es/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Instalando actualización del sistema…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Borrando…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Sin comandos"</string> + <string name="recovery_error" msgid="4550265746256727080">"Error"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-et-rEE/strings.xml b/recovery/tools/recovery_l10n/res/values-et-rEE/strings.xml new file mode 100644 index 0000000..407a53d --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-et-rEE/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Süsteemivärskenduste installimine ..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Kustutamine ..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Käsk puudub."</string> + <string name="recovery_error" msgid="4550265746256727080">"Viga!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-eu-rES/strings.xml b/recovery/tools/recovery_l10n/res/values-eu-rES/strings.xml new file mode 100644 index 0000000..08d9c06 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-eu-rES/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Sistemaren eguneratzea instalatzen…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Ezabatzen…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Ez dago agindurik."</string> + <string name="recovery_error" msgid="4550265746256727080">"Errorea!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-fa/strings.xml b/recovery/tools/recovery_l10n/res/values-fa/strings.xml new file mode 100644 index 0000000..dd002fa --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-fa/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"در حال نصب بهروزرسانی سیستم ..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"پاک کردن..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"فرمانی موجود نیست."</string> + <string name="recovery_error" msgid="4550265746256727080">"خطا!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-fi/strings.xml b/recovery/tools/recovery_l10n/res/values-fi/strings.xml new file mode 100644 index 0000000..b77417a --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-fi/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Asennetaan järjestelmäpäivitystä..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Tyhjennetään..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Ei komentoa."</string> + <string name="recovery_error" msgid="4550265746256727080">"Virhe!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-fr-rCA/strings.xml b/recovery/tools/recovery_l10n/res/values-fr-rCA/strings.xml new file mode 100644 index 0000000..f2a85d8 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-fr-rCA/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Installation de la mise à jour du système en cours…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Effacement en cours…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Aucune commande."</string> + <string name="recovery_error" msgid="4550265746256727080">"Erreur!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-fr/strings.xml b/recovery/tools/recovery_l10n/res/values-fr/strings.xml new file mode 100644 index 0000000..cdb4a26 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-fr/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Installation de la mise à jour du système en cours…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Effacement en cours…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Aucune commande."</string> + <string name="recovery_error" msgid="4550265746256727080">"Erreur !"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-gl-rES/strings.xml b/recovery/tools/recovery_l10n/res/values-gl-rES/strings.xml new file mode 100644 index 0000000..7546fbd --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-gl-rES/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Instalando actualización do sistema..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Borrando..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Ningún comando"</string> + <string name="recovery_error" msgid="4550265746256727080">"Erro"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-gu-rIN/strings.xml b/recovery/tools/recovery_l10n/res/values-gu-rIN/strings.xml new file mode 100644 index 0000000..a364b52 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-gu-rIN/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"સિસ્ટમ અપડેટ ઇન્સ્ટોલ કરી રહ્યાં છે…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"કાઢી નાખી રહ્યાં છે…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"કોઈ આદેશ નથી."</string> + <string name="recovery_error" msgid="4550265746256727080">"ભૂલ!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-hi/strings.xml b/recovery/tools/recovery_l10n/res/values-hi/strings.xml new file mode 100644 index 0000000..a470d12 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-hi/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"सिस्टम के बारे में नई जानकारी मिल रही है…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"मिटा रहा है…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"कोई आदेश नहीं."</string> + <string name="recovery_error" msgid="4550265746256727080">"त्रुटि!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-hr/strings.xml b/recovery/tools/recovery_l10n/res/values-hr/strings.xml new file mode 100644 index 0000000..56225c0 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-hr/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Instaliranje ažuriranja sustava…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Brisanje…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Nema naredbe."</string> + <string name="recovery_error" msgid="4550265746256727080">"Pogreška!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-hu/strings.xml b/recovery/tools/recovery_l10n/res/values-hu/strings.xml new file mode 100644 index 0000000..a64f501 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-hu/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Rendszerfrissítés telepítése..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Törlés..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Nincs parancs."</string> + <string name="recovery_error" msgid="4550265746256727080">"Hiba!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-hy-rAM/strings.xml b/recovery/tools/recovery_l10n/res/values-hy-rAM/strings.xml new file mode 100644 index 0000000..7babe80 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-hy-rAM/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Համակարգի թարմացման տեղադրում…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Ջնջում…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Հրամանը տրված չէ:"</string> + <string name="recovery_error" msgid="4550265746256727080">"Սխալ"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-in/strings.xml b/recovery/tools/recovery_l10n/res/values-in/strings.xml new file mode 100644 index 0000000..93f9c28 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-in/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Memasang pembaruan sistem…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Menghapus..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Tidak ada perintah."</string> + <string name="recovery_error" msgid="4550265746256727080">"Kesalahan!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-is-rIS/strings.xml b/recovery/tools/recovery_l10n/res/values-is-rIS/strings.xml new file mode 100644 index 0000000..926e851 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-is-rIS/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Setur upp kerfisuppfærslu…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Þurrkar út…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Engin skipun."</string> + <string name="recovery_error" msgid="4550265746256727080">"Villa!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-it/strings.xml b/recovery/tools/recovery_l10n/res/values-it/strings.xml new file mode 100644 index 0000000..9defe36 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-it/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Installazione aggiornamento di sistema…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Cancellazione…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Nessun comando."</string> + <string name="recovery_error" msgid="4550265746256727080">"Errore!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-iw/strings.xml b/recovery/tools/recovery_l10n/res/values-iw/strings.xml new file mode 100644 index 0000000..e43bb20 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-iw/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"מתקין עדכון מערכת…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"מוחק…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"אין פקודה."</string> + <string name="recovery_error" msgid="4550265746256727080">"שגיאה!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-ja/strings.xml b/recovery/tools/recovery_l10n/res/values-ja/strings.xml new file mode 100644 index 0000000..da0fa62 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-ja/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"システムアップデートをインストールしています…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"消去しています…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"コマンドが指定されていません。"</string> + <string name="recovery_error" msgid="4550265746256727080">"エラーです"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-ka-rGE/strings.xml b/recovery/tools/recovery_l10n/res/values-ka-rGE/strings.xml new file mode 100644 index 0000000..2d27c17 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-ka-rGE/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"სისტემის განახლების დაყენება…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"მიმდინარეობს წაშლა…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"ბრძანება არ არის."</string> + <string name="recovery_error" msgid="4550265746256727080">"შეცდომა!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-kk-rKZ/strings.xml b/recovery/tools/recovery_l10n/res/values-kk-rKZ/strings.xml new file mode 100644 index 0000000..3ca05b9 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-kk-rKZ/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Жүйе жаңартуларын орнатуда…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Өшіруде..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Пәрмен берілген жоқ."</string> + <string name="recovery_error" msgid="4550265746256727080">"Қате!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-km-rKH/strings.xml b/recovery/tools/recovery_l10n/res/values-km-rKH/strings.xml new file mode 100644 index 0000000..0c1c272 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-km-rKH/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"កំពុងដំឡើងបច្ចុប្បន្នភាពប្រព័ន្ធ…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"កំពុងលុប…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"គ្មានពាក្យបញ្ជា។"</string> + <string name="recovery_error" msgid="4550265746256727080">"កំហុស!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-kn-rIN/strings.xml b/recovery/tools/recovery_l10n/res/values-kn-rIN/strings.xml new file mode 100644 index 0000000..be25d7a --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-kn-rIN/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"ಸಿಸ್ಟಂ ನವೀಕರಣವನ್ನು ಸ್ಥಾಪಿಸಲಾಗುತ್ತಿದೆ…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"ಅಳಿಸಲಾಗುತ್ತಿದೆ…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"ಯಾವುದೇ ಆದೇಶವಿಲ್ಲ."</string> + <string name="recovery_error" msgid="4550265746256727080">"ದೋಷ!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-ko/strings.xml b/recovery/tools/recovery_l10n/res/values-ko/strings.xml new file mode 100644 index 0000000..e46a876 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-ko/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"시스템 업데이트 설치 중…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"지우는 중…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"명령어가 없습니다."</string> + <string name="recovery_error" msgid="4550265746256727080">"오류!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-ky-rKG/strings.xml b/recovery/tools/recovery_l10n/res/values-ky-rKG/strings.xml new file mode 100644 index 0000000..e2ced27 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-ky-rKG/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Системдик жаңыртууларды орнотуу…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Өчүрүлүүдө…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Буйрук берилген жок."</string> + <string name="recovery_error" msgid="4550265746256727080">"Ката!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-lo-rLA/strings.xml b/recovery/tools/recovery_l10n/res/values-lo-rLA/strings.xml new file mode 100644 index 0000000..5880cca --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-lo-rLA/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"ກຳລັງຕິດຕັ້ງການອັບເດດລະບົບ..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"ກຳລັງລຶບ..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"ບໍ່ມີຄຳສັ່ງ."</string> + <string name="recovery_error" msgid="4550265746256727080">"ຜິດພາດ!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-lt/strings.xml b/recovery/tools/recovery_l10n/res/values-lt/strings.xml new file mode 100644 index 0000000..957ac75 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-lt/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Diegiamas sistemos naujinys…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Ištrinama…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Nėra komandos."</string> + <string name="recovery_error" msgid="4550265746256727080">"Klaida!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-lv/strings.xml b/recovery/tools/recovery_l10n/res/values-lv/strings.xml new file mode 100644 index 0000000..c5d5b93 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-lv/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Notiek sistēmas atjauninājuma instalēšana..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Notiek dzēšana..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Nav nevienas komandas."</string> + <string name="recovery_error" msgid="4550265746256727080">"Kļūda!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-mk-rMK/strings.xml b/recovery/tools/recovery_l10n/res/values-mk-rMK/strings.xml new file mode 100644 index 0000000..d91a67c --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-mk-rMK/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Се инсталира ажурирање на системот..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Се брише..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Нема наредба."</string> + <string name="recovery_error" msgid="4550265746256727080">"Грешка!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-ml-rIN/strings.xml b/recovery/tools/recovery_l10n/res/values-ml-rIN/strings.xml new file mode 100644 index 0000000..38ebcd1 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-ml-rIN/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"സിസ്റ്റം അപ്ഡേറ്റ് ഇൻസ്റ്റാളുചെയ്യുന്നു…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"മായ്ക്കുന്നു…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"കമാൻഡ് ഒന്നുമില്ല."</string> + <string name="recovery_error" msgid="4550265746256727080">"പിശക്!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-mn-rMN/strings.xml b/recovery/tools/recovery_l10n/res/values-mn-rMN/strings.xml new file mode 100644 index 0000000..463cafe --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-mn-rMN/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Системийн шинэчлэлтийг суулгаж байна…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Арилгаж байна…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Команд байхгүй."</string> + <string name="recovery_error" msgid="4550265746256727080">"Алдаа!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-mr-rIN/strings.xml b/recovery/tools/recovery_l10n/res/values-mr-rIN/strings.xml new file mode 100644 index 0000000..25c5d0c --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-mr-rIN/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"सिस्टम अद्यतन स्थापित करीत आहे..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"मिटवित आहे…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"कोणताही आदेश नाही."</string> + <string name="recovery_error" msgid="4550265746256727080">"त्रुटी!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-ms-rMY/strings.xml b/recovery/tools/recovery_l10n/res/values-ms-rMY/strings.xml new file mode 100644 index 0000000..f563591 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-ms-rMY/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Memasang kemas kini sistem..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Memadam..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Tiada arahan."</string> + <string name="recovery_error" msgid="4550265746256727080">"Ralat!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-my-rMM/strings.xml b/recovery/tools/recovery_l10n/res/values-my-rMM/strings.xml new file mode 100644 index 0000000..4091b19 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-my-rMM/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"စနစ်အား အဆင့်မြှင့်ခြင်း လုပ်ဆောင်နေသည်…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"ဖျက်နေသည် ..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"ညွှန်ကြားချက်မပေးထားပါ"</string> + <string name="recovery_error" msgid="4550265746256727080">"မှားနေပါသည်!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-nb/strings.xml b/recovery/tools/recovery_l10n/res/values-nb/strings.xml new file mode 100644 index 0000000..4e89ad7 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-nb/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Installerer systemoppdateringen ..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Sletter ..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Ingen kommando."</string> + <string name="recovery_error" msgid="4550265746256727080">"Feil!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-ne-rNP/strings.xml b/recovery/tools/recovery_l10n/res/values-ne-rNP/strings.xml new file mode 100644 index 0000000..835f275 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-ne-rNP/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"प्रणाली अद्यावधिक स्थापना गर्दै..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"मेटाइदै..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"कुनै आदेश छैन।"</string> + <string name="recovery_error" msgid="4550265746256727080">"त्रुटि!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-nl/strings.xml b/recovery/tools/recovery_l10n/res/values-nl/strings.xml new file mode 100644 index 0000000..be80a6b --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-nl/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Systeemupdate installeren…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Wissen…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Geen opdracht."</string> + <string name="recovery_error" msgid="4550265746256727080">"Fout!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-pa-rIN/strings.xml b/recovery/tools/recovery_l10n/res/values-pa-rIN/strings.xml new file mode 100644 index 0000000..39ef32f --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-pa-rIN/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"ਸਿਸਟਮ ਅਪਡੇਟ ਇੰਸਟੌਲ ਕਰ ਰਿਹਾ ਹੈ…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"ਹਟਾ ਰਿਹਾ ਹੈ…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"ਕੋਈ ਕਮਾਂਡ ਨਹੀਂ।"</string> + <string name="recovery_error" msgid="4550265746256727080">"ਅਸ਼ੁੱਧੀ!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-pl/strings.xml b/recovery/tools/recovery_l10n/res/values-pl/strings.xml new file mode 100644 index 0000000..b1e5b7b --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-pl/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Instaluję aktualizację systemu…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Usuwam…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Brak polecenia."</string> + <string name="recovery_error" msgid="4550265746256727080">"Błąd"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-pt-rBR/strings.xml b/recovery/tools/recovery_l10n/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000..3cc5723 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-pt-rBR/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Instalando atualização do sistema..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Apagando..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Nenhum comando."</string> + <string name="recovery_error" msgid="4550265746256727080">"Erro!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-pt-rPT/strings.xml b/recovery/tools/recovery_l10n/res/values-pt-rPT/strings.xml new file mode 100644 index 0000000..7d6bc18 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-pt-rPT/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"A instalar a atualização do sistema..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"A apagar…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Nenhum comando."</string> + <string name="recovery_error" msgid="4550265746256727080">"Erro!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-pt/strings.xml b/recovery/tools/recovery_l10n/res/values-pt/strings.xml new file mode 100644 index 0000000..3cc5723 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-pt/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Instalando atualização do sistema..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Apagando..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Nenhum comando."</string> + <string name="recovery_error" msgid="4550265746256727080">"Erro!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-ro/strings.xml b/recovery/tools/recovery_l10n/res/values-ro/strings.xml new file mode 100644 index 0000000..ad924da --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-ro/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Se instalează actualizarea de sistem…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Se efectuează ștergerea…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Nicio comandă."</string> + <string name="recovery_error" msgid="4550265746256727080">"Eroare!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-ru/strings.xml b/recovery/tools/recovery_l10n/res/values-ru/strings.xml new file mode 100644 index 0000000..de0da40 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-ru/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Установка обновления системы…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Удаление…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Команды нет"</string> + <string name="recovery_error" msgid="4550265746256727080">"Ошибка"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-si-rLK/strings.xml b/recovery/tools/recovery_l10n/res/values-si-rLK/strings.xml new file mode 100644 index 0000000..e717a97 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-si-rLK/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"පද්ධති යාවත්කාල ස්ථාපනය කරමින්…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"මකමින්...."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"විධානයක් නොමැත."</string> + <string name="recovery_error" msgid="4550265746256727080">"දෝෂය!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-sk/strings.xml b/recovery/tools/recovery_l10n/res/values-sk/strings.xml new file mode 100644 index 0000000..cae6bce --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-sk/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Inštalácia aktualizácie systému..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Prebieha mazanie..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Žiadny príkaz."</string> + <string name="recovery_error" msgid="4550265746256727080">"Chyba!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-sl/strings.xml b/recovery/tools/recovery_l10n/res/values-sl/strings.xml new file mode 100644 index 0000000..3f8d46f --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-sl/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Namestitev posodobitve sistema ..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Brisanje ..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Ni ukaza"</string> + <string name="recovery_error" msgid="4550265746256727080">"Napaka"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-sq-rAL/strings.xml b/recovery/tools/recovery_l10n/res/values-sq-rAL/strings.xml new file mode 100644 index 0000000..29f8ef5 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-sq-rAL/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Po instalon përditësimin e sistemit..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Po spastron..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Nuk ka komanda."</string> + <string name="recovery_error" msgid="4550265746256727080">"Gabim!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-sr/strings.xml b/recovery/tools/recovery_l10n/res/values-sr/strings.xml new file mode 100644 index 0000000..9553260 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-sr/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Инсталирање ажурирања система..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Брисање..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Нема команде."</string> + <string name="recovery_error" msgid="4550265746256727080">"Грешка!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-sv/strings.xml b/recovery/tools/recovery_l10n/res/values-sv/strings.xml new file mode 100644 index 0000000..f875d30 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-sv/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Installerar systemuppdatering ..."</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Tar bort ..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Inget kommando."</string> + <string name="recovery_error" msgid="4550265746256727080">"Fel!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-sw/strings.xml b/recovery/tools/recovery_l10n/res/values-sw/strings.xml new file mode 100644 index 0000000..1a53046 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-sw/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Inasakinisha sasisho la mfumo…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Inafuta…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Hakuna amri."</string> + <string name="recovery_error" msgid="4550265746256727080">"Hitilafu!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-ta-rIN/strings.xml b/recovery/tools/recovery_l10n/res/values-ta-rIN/strings.xml new file mode 100644 index 0000000..f6f3e0e --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-ta-rIN/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"முறைமை புதுப்பிப்பை நிறுவுகிறது…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"அழிக்கிறது…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"கட்டளை இல்லை."</string> + <string name="recovery_error" msgid="4550265746256727080">"பிழை!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-te-rIN/strings.xml b/recovery/tools/recovery_l10n/res/values-te-rIN/strings.xml new file mode 100644 index 0000000..6d0d17a --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-te-rIN/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"సిస్టమ్ నవీకరణను ఇన్స్టాల్ చేస్తోంది…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"ఎరేజ్ చేస్తోంది…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"ఆదేశం లేదు."</string> + <string name="recovery_error" msgid="4550265746256727080">"లోపం!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-th/strings.xml b/recovery/tools/recovery_l10n/res/values-th/strings.xml new file mode 100644 index 0000000..bcdfa2b --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-th/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"กำลังติดตั้งการอัปเดตระบบ…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"กำลังลบ…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"ไม่มีคำสั่ง"</string> + <string name="recovery_error" msgid="4550265746256727080">"ข้อผิดพลาด!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-tl/strings.xml b/recovery/tools/recovery_l10n/res/values-tl/strings.xml new file mode 100644 index 0000000..be2ba26 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-tl/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Ini-install ang update sa system…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Binubura…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Walang command."</string> + <string name="recovery_error" msgid="4550265746256727080">"Error!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-tr/strings.xml b/recovery/tools/recovery_l10n/res/values-tr/strings.xml new file mode 100644 index 0000000..8629029 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-tr/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Sistem güncellemesi yükleniyor…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Siliniyor…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Komut yok."</string> + <string name="recovery_error" msgid="4550265746256727080">"Hata!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-uk/strings.xml b/recovery/tools/recovery_l10n/res/values-uk/strings.xml new file mode 100644 index 0000000..762c06f --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-uk/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Встановлення оновлення системи…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Стирання…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Немає команди."</string> + <string name="recovery_error" msgid="4550265746256727080">"Помилка!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-ur-rPK/strings.xml b/recovery/tools/recovery_l10n/res/values-ur-rPK/strings.xml new file mode 100644 index 0000000..dc6eb6a --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-ur-rPK/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"سسٹم اپ ڈیٹ انسٹال ہو رہا ہے…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"صاف کر رہا ہے…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"کوئی کمانڈ نہیں ہے۔"</string> + <string name="recovery_error" msgid="4550265746256727080">"خرابی!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-uz-rUZ/strings.xml b/recovery/tools/recovery_l10n/res/values-uz-rUZ/strings.xml new file mode 100644 index 0000000..2874484 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-uz-rUZ/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Tizim yangilanishi o‘rnatilmoqda…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Tozalanmoqda…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Buyruq yo‘q."</string> + <string name="recovery_error" msgid="4550265746256727080">"Xato!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-vi/strings.xml b/recovery/tools/recovery_l10n/res/values-vi/strings.xml new file mode 100644 index 0000000..ab4005b --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-vi/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Đang cài đặt bản cập nhật hệ thống…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Đang xóa…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Không có lệnh nào."</string> + <string name="recovery_error" msgid="4550265746256727080">"Lỗi!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-zh-rCN/strings.xml b/recovery/tools/recovery_l10n/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000..2e1a6f5 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-zh-rCN/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"正在安装系统更新…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"正在清除…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"无命令。"</string> + <string name="recovery_error" msgid="4550265746256727080">"出错了!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-zh-rHK/strings.xml b/recovery/tools/recovery_l10n/res/values-zh-rHK/strings.xml new file mode 100644 index 0000000..f615c7a --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-zh-rHK/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"正在安裝系統更新…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"正在清除…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"沒有指令。"</string> + <string name="recovery_error" msgid="4550265746256727080">"錯誤!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-zh-rTW/strings.xml b/recovery/tools/recovery_l10n/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000..f3f6a2c --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-zh-rTW/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"正在安裝系統更新…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"清除中..."</string> + <string name="recovery_no_command" msgid="1915703879031023455">"沒有指令。"</string> + <string name="recovery_error" msgid="4550265746256727080">"錯誤!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values-zu/strings.xml b/recovery/tools/recovery_l10n/res/values-zu/strings.xml new file mode 100644 index 0000000..1f904a2 --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values-zu/strings.xml
@@ -0,0 +1,8 @@ +<?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="7864047928003865598">"Ifaka isibuyekezo sesistimu…"</string> + <string name="recovery_erasing" msgid="4612809744968710197">"Iyasula…"</string> + <string name="recovery_no_command" msgid="1915703879031023455">"Awukho umyalo."</string> + <string name="recovery_error" msgid="4550265746256727080">"Iphutha!"</string> +</resources>
diff --git a/recovery/tools/recovery_l10n/res/values/strings.xml b/recovery/tools/recovery_l10n/res/values/strings.xml new file mode 100644 index 0000000..48d18da --- /dev/null +++ b/recovery/tools/recovery_l10n/res/values/strings.xml
@@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- Do not translate. --> + <string translatable="false" name="go">Go</string> + + <!-- Do not translate. --> + <string-array translatable="false" name="string_options"> + <item>installing</item> + <item>erasing</item> + <item>no_command</item> + <item>error</item> + <item>installing_security</item> + </string-array> + + <!-- Displayed on the screen beneath the animated android while the + system is installing an update. [CHAR LIMIT=60] --> + <string name="recovery_installing">Installing system update\u2026</string> + + <!-- Displayed on the screen beneath the animated android while the + system is erasing a partition (either a data wipe aka "factory + reset", or a cache wipe). [CHAR LIMIT=60] --> + <string name="recovery_erasing">Erasing\u2026</string> + + <!-- Displayed on the screen when the user has gotten into recovery + mode without a command to run. Will not normally happen, but + users (especially developers) may boot into recovery mode + manually via special key combinations. [CHAR LIMIT=60] --> + <string name="recovery_no_command">No command.</string> + + <!-- Displayed on the triangle-! screen when a system update + installation or data wipe procedure encounters an error. [CHAR + LIMIT=60] --> + <string name="recovery_error">Error!</string> + + <!-- Displayed on the screen beneath the animation while the + system is installing a security update. [CHAR LIMIT=60] --> + <string name="recovery_installing_security">Installing security update\u2026</string> + +</resources>
diff --git a/recovery/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java b/recovery/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java new file mode 100644 index 0000000..a974fa3 --- /dev/null +++ b/recovery/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
@@ -0,0 +1,320 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.recovery_l10n; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Spinner; +import android.widget.ArrayAdapter; +import android.widget.AdapterView; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; + +/** + * This activity assists in generating the specially-formatted bitmaps + * of text needed for recovery's localized text display. Each image + * contains all the translations of a single string; above each + * translation is a "header row" that encodes that subimage's width, + * height, and locale using pixel values. + * + * To use this app to generate new translations: + * + * - Update the string resources in res/values-* + * + * - Build and run the app. Select the string you want to + * translate, and press the "Go" button. + * + * - Wait for it to finish cycling through all the strings, then + * pull /data/data/com.android.recovery_l10n/files/text-out.png + * from the device. + * + * - "pngcrush -c 0 text-out.png output.png" + * + * - Put output.png in bootable/recovery/res/images/ (renamed + * appropriately). + * + * Recovery expects 8-bit 1-channel images (white text on black + * background). pngcrush -c 0 will convert the output of this program + * to such an image. If you use any other image handling tools, + * remember that they must be lossless to preserve the exact values of + * pixels in the header rows; don't convert them to jpeg or anything. + */ + +public class Main extends Activity { + private static final String TAG = "RecoveryL10N"; + + HashMap<Locale, Bitmap> savedBitmaps; + TextView mText; + int mStringId = R.string.recovery_installing; + + public class TextCapture implements Runnable { + private Locale nextLocale; + private Locale thisLocale; + private Runnable next; + + TextCapture(Locale thisLocale, Locale nextLocale, Runnable next) { + this.nextLocale = nextLocale; + this.thisLocale = thisLocale; + this.next = next; + } + + public void run() { + Bitmap b = mText.getDrawingCache(); + savedBitmaps.put(thisLocale, b.copy(Bitmap.Config.ARGB_8888, false)); + + if (nextLocale != null) { + switchTo(nextLocale); + } + + if (next != null) { + mText.postDelayed(next, 200); + } + } + } + + private void switchTo(Locale locale) { + Resources standardResources = getResources(); + AssetManager assets = standardResources.getAssets(); + DisplayMetrics metrics = standardResources.getDisplayMetrics(); + Configuration config = new Configuration(standardResources.getConfiguration()); + config.locale = locale; + Resources defaultResources = new Resources(assets, metrics, config); + + mText.setText(mStringId); + + mText.setDrawingCacheEnabled(false); + mText.setDrawingCacheEnabled(true); + mText.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH); + } + + @Override + public void onCreate(Bundle savedInstance) { + super.onCreate(savedInstance); + setContentView(R.layout.main); + + savedBitmaps = new HashMap<Locale, Bitmap>(); + + Spinner spinner = (Spinner) findViewById(R.id.which); + ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource( + this, R.array.string_options, android.R.layout.simple_spinner_item); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, + int pos, long id) { + switch (pos) { + case 0: mStringId = R.string.recovery_installing; break; + case 1: mStringId = R.string.recovery_erasing; break; + case 2: mStringId = R.string.recovery_no_command; break; + case 3: mStringId = R.string.recovery_error; break; + case 4: mStringId = R.string.recovery_installing_security; break; + } + } + @Override public void onNothingSelected(AdapterView parent) { } + }); + + mText = (TextView) findViewById(R.id.text); + + String[] localeNames = getAssets().getLocales(); + Arrays.sort(localeNames); + ArrayList<Locale> locales = new ArrayList<Locale>(); + for (String ln : localeNames) { + int u = ln.indexOf('_'); + if (u >= 0) { + Log.i(TAG, "locale = " + ln); + locales.add(new Locale(ln.substring(0, u), ln.substring(u+1))); + } + } + + final Runnable seq = buildSequence(locales.toArray(new Locale[0])); + + Button b = (Button) findViewById(R.id.go); + b.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View ignore) { + mText.post(seq); + } + }); + } + + private Runnable buildSequence(final Locale[] locales) { + Runnable head = new Runnable() { public void run() { mergeBitmaps(locales); } }; + Locale prev = null; + for (Locale loc : locales) { + head = new TextCapture(loc, prev, head); + prev = loc; + } + final Runnable fhead = head; + final Locale floc = prev; + return new Runnable() { public void run() { startSequence(fhead, floc); } }; + } + + private void startSequence(Runnable firstRun, Locale firstLocale) { + savedBitmaps.clear(); + switchTo(firstLocale); + mText.postDelayed(firstRun, 200); + } + + private void saveBitmap(Bitmap b, String filename) { + try { + FileOutputStream fos = openFileOutput(filename, 0); + b.compress(Bitmap.CompressFormat.PNG, 100, fos); + fos.close(); + } catch (IOException e) { + Log.i(TAG, "failed to write PNG", e); + } + } + + private int colorFor(byte b) { + return 0xff000000 | (b<<16) | (b<<8) | b; + } + + private int colorFor(int b) { + return 0xff000000 | (b<<16) | (b<<8) | b; + } + + private void mergeBitmaps(final Locale[] locales) { + HashMap<String, Integer> countByLanguage = new HashMap<String, Integer>(); + + int height = 2; + int width = 10; + int maxHeight = 0; + for (Locale loc : locales) { + Bitmap b = savedBitmaps.get(loc); + int h = b.getHeight(); + int w = b.getWidth(); + height += h+1; + if (h > maxHeight) maxHeight = h; + if (w > width) width = w; + + String lang = loc.getLanguage(); + if (countByLanguage.containsKey(lang)) { + countByLanguage.put(lang, countByLanguage.get(lang)+1); + } else { + countByLanguage.put(lang, 1); + } + } + + Log.i(TAG, "output bitmap is " + width + " x " + height); + Bitmap out = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + out.eraseColor(0xff000000); + int[] pixels = new int[maxHeight * width]; + + int p = 0; + for (Locale loc : locales) { + Bitmap bm = savedBitmaps.get(loc); + int h = bm.getHeight(); + int w = bm.getWidth(); + + bm.getPixels(pixels, 0, w, 0, 0, w, h); + + // Find the rightmost and leftmost columns with any + // nonblack pixels; we'll copy just that region to the + // output image. + + int right = w; + while (right > 1) { + boolean all_black = true; + for (int j = 0; j < h; ++j) { + if (pixels[j*w+right-1] != 0xff000000) { + all_black = false; + break; + } + } + if (all_black) { + --right; + } else { + break; + } + } + + int left = 0; + while (left < right-1) { + boolean all_black = true; + for (int j = 0; j < h; ++j) { + if (pixels[j*w+left] != 0xff000000) { + all_black = false; + break; + } + } + if (all_black) { + ++left; + } else { + break; + } + } + + // Make the last country variant for a given language be + // the catch-all for that language (because recovery will + // take the first one that matches). + String lang = loc.getLanguage(); + if (countByLanguage.get(lang) > 1) { + countByLanguage.put(lang, countByLanguage.get(lang)-1); + lang = loc.toString(); + } + int tw = right - left; + Log.i(TAG, "encoding \"" + loc + "\" as \"" + lang + "\": " + tw + " x " + h); + byte[] langBytes = lang.getBytes(); + out.setPixel(0, p, colorFor(tw & 0xff)); + out.setPixel(1, p, colorFor(tw >>> 8)); + out.setPixel(2, p, colorFor(h & 0xff)); + out.setPixel(3, p, colorFor(h >>> 8)); + out.setPixel(4, p, colorFor(langBytes.length)); + int x = 5; + for (byte b : langBytes) { + out.setPixel(x, p, colorFor(b)); + x++; + } + out.setPixel(x, p, colorFor(0)); + + p++; + + out.setPixels(pixels, left, w, 0, p, tw, h); + p += h; + } + + // if no languages match, suppress text display by using a + // single black pixel as the image. + out.setPixel(0, p, colorFor(1)); + out.setPixel(1, p, colorFor(0)); + out.setPixel(2, p, colorFor(1)); + out.setPixel(3, p, colorFor(0)); + out.setPixel(4, p, colorFor(0)); + p++; + + saveBitmap(out, "text-out.png"); + Log.i(TAG, "wrote text-out.png"); + } +}
diff --git a/recovery/ui.cpp b/recovery/ui.cpp new file mode 100644 index 0000000..2efb759 --- /dev/null +++ b/recovery/ui.cpp
@@ -0,0 +1,339 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <linux/input.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include <cutils/properties.h> +#include <cutils/android_reboot.h> + +#include "common.h" +#include "roots.h" +#include "device.h" +#include "minui/minui.h" +#include "screen_ui.h" +#include "ui.h" + +#define UI_WAIT_KEY_TIMEOUT_SEC 120 + +RecoveryUI::RecoveryUI() + : key_queue_len(0), + key_last_down(-1), + key_long_press(false), + key_down_count(0), + enable_reboot(true), + consecutive_power_keys(0), + last_key(-1), + has_power_key(false), + has_up_key(false), + has_down_key(false) { + pthread_mutex_init(&key_queue_mutex, nullptr); + pthread_cond_init(&key_queue_cond, nullptr); + memset(key_pressed, 0, sizeof(key_pressed)); +} + +void RecoveryUI::OnKeyDetected(int key_code) { + if (key_code == KEY_POWER) { + has_power_key = true; + } else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) { + has_down_key = true; + } else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) { + has_up_key = true; + } +} + +int RecoveryUI::InputCallback(int fd, uint32_t epevents, void* data) { + return reinterpret_cast<RecoveryUI*>(data)->OnInputEvent(fd, epevents); +} + +// Reads input events, handles special hot keys, and adds to the key queue. +static void* InputThreadLoop(void*) { + while (true) { + if (!ev_wait(-1)) { + ev_dispatch(); + } + } + return nullptr; +} + +void RecoveryUI::Init() { + ev_init(InputCallback, this); + + ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); + + pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr); +} + +int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { + struct input_event ev; + if (ev_get_input(fd, epevents, &ev) == -1) { + return -1; + } + + if (ev.type == EV_SYN) { + return 0; + } else if (ev.type == EV_REL) { + if (ev.code == REL_Y) { + // accumulate the up or down motion reported by + // the trackball. When it exceeds a threshold + // (positive or negative), fake an up/down + // key event. + rel_sum += ev.value; + if (rel_sum > 3) { + ProcessKey(KEY_DOWN, 1); // press down key + ProcessKey(KEY_DOWN, 0); // and release it + rel_sum = 0; + } else if (rel_sum < -3) { + ProcessKey(KEY_UP, 1); // press up key + ProcessKey(KEY_UP, 0); // and release it + rel_sum = 0; + } + } + } else { + rel_sum = 0; + } + + if (ev.type == EV_KEY && ev.code <= KEY_MAX) { + ProcessKey(ev.code, ev.value); + } + + return 0; +} + +// Process a key-up or -down event. A key is "registered" when it is +// pressed and then released, with no other keypresses or releases in +// between. Registered keys are passed to CheckKey() to see if it +// should trigger a visibility toggle, an immediate reboot, or be +// queued to be processed next time the foreground thread wants a key +// (eg, for the menu). +// +// We also keep track of which keys are currently down so that +// CheckKey can call IsKeyPressed to see what other keys are held when +// a key is registered. +// +// updown == 1 for key down events; 0 for key up events +void RecoveryUI::ProcessKey(int key_code, int updown) { + bool register_key = false; + bool long_press = false; + bool reboot_enabled; + + pthread_mutex_lock(&key_queue_mutex); + key_pressed[key_code] = updown; + if (updown) { + ++key_down_count; + key_last_down = key_code; + key_long_press = false; + key_timer_t* info = new key_timer_t; + info->ui = this; + info->key_code = key_code; + info->count = key_down_count; + pthread_t thread; + pthread_create(&thread, nullptr, &RecoveryUI::time_key_helper, info); + pthread_detach(thread); + } else { + if (key_last_down == key_code) { + long_press = key_long_press; + register_key = true; + } + key_last_down = -1; + } + reboot_enabled = enable_reboot; + pthread_mutex_unlock(&key_queue_mutex); + + if (register_key) { + switch (CheckKey(key_code, long_press)) { + case RecoveryUI::IGNORE: + break; + + case RecoveryUI::TOGGLE: + ShowText(!IsTextVisible()); + break; + + case RecoveryUI::REBOOT: + if (reboot_enabled) { + property_set(ANDROID_RB_PROPERTY, "reboot,"); + while (true) { pause(); } + } + break; + + case RecoveryUI::ENQUEUE: + EnqueueKey(key_code); + break; + } + } +} + +void* RecoveryUI::time_key_helper(void* cookie) { + key_timer_t* info = (key_timer_t*) cookie; + info->ui->time_key(info->key_code, info->count); + delete info; + return nullptr; +} + +void RecoveryUI::time_key(int key_code, int count) { + usleep(750000); // 750 ms == "long" + bool long_press = false; + pthread_mutex_lock(&key_queue_mutex); + if (key_last_down == key_code && key_down_count == count) { + long_press = key_long_press = true; + } + pthread_mutex_unlock(&key_queue_mutex); + if (long_press) KeyLongPress(key_code); +} + +void RecoveryUI::EnqueueKey(int key_code) { + pthread_mutex_lock(&key_queue_mutex); + const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]); + if (key_queue_len < queue_max) { + key_queue[key_queue_len++] = key_code; + pthread_cond_signal(&key_queue_cond); + } + pthread_mutex_unlock(&key_queue_mutex); +} + +int RecoveryUI::WaitKey() { + pthread_mutex_lock(&key_queue_mutex); + + // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is + // plugged in. + do { + struct timeval now; + struct timespec timeout; + gettimeofday(&now, nullptr); + timeout.tv_sec = now.tv_sec; + timeout.tv_nsec = now.tv_usec * 1000; + timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC; + + int rc = 0; + while (key_queue_len == 0 && rc != ETIMEDOUT) { + rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout); + } + } while (IsUsbConnected() && key_queue_len == 0); + + int key = -1; + if (key_queue_len > 0) { + key = key_queue[0]; + memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); + } + pthread_mutex_unlock(&key_queue_mutex); + return key; +} + +bool RecoveryUI::IsUsbConnected() { + int fd = open("/sys/class/android_usb/android0/state", O_RDONLY); + if (fd < 0) { + printf("failed to open /sys/class/android_usb/android0/state: %s\n", + strerror(errno)); + return 0; + } + + char buf; + // USB is connected if android_usb state is CONNECTED or CONFIGURED. + int connected = (TEMP_FAILURE_RETRY(read(fd, &buf, 1)) == 1) && (buf == 'C'); + if (close(fd) < 0) { + printf("failed to close /sys/class/android_usb/android0/state: %s\n", + strerror(errno)); + } + return connected; +} + +bool RecoveryUI::IsKeyPressed(int key) { + pthread_mutex_lock(&key_queue_mutex); + int pressed = key_pressed[key]; + pthread_mutex_unlock(&key_queue_mutex); + return pressed; +} + +bool RecoveryUI::IsLongPress() { + pthread_mutex_lock(&key_queue_mutex); + bool result = key_long_press; + pthread_mutex_unlock(&key_queue_mutex); + return result; +} + +bool RecoveryUI::HasThreeButtons() { + return has_power_key && has_up_key && has_down_key; +} + +void RecoveryUI::FlushKeys() { + pthread_mutex_lock(&key_queue_mutex); + key_queue_len = 0; + pthread_mutex_unlock(&key_queue_mutex); +} + +RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { + pthread_mutex_lock(&key_queue_mutex); + key_long_press = false; + pthread_mutex_unlock(&key_queue_mutex); + + // If we have power and volume up keys, that chord is the signal to toggle the text display. + if (HasThreeButtons()) { + if (key == KEY_VOLUMEUP && IsKeyPressed(KEY_POWER)) { + return TOGGLE; + } + } else { + // Otherwise long press of any button toggles to the text display, + // and there's no way to toggle back (but that's pretty useless anyway). + if (is_long_press && !IsTextVisible()) { + return TOGGLE; + } + + // Also, for button-limited devices, a long press is translated to KEY_ENTER. + if (is_long_press && IsTextVisible()) { + EnqueueKey(KEY_ENTER); + return IGNORE; + } + } + + // Press power seven times in a row to reboot. + if (key == KEY_POWER) { + pthread_mutex_lock(&key_queue_mutex); + bool reboot_enabled = enable_reboot; + pthread_mutex_unlock(&key_queue_mutex); + + if (reboot_enabled) { + ++consecutive_power_keys; + if (consecutive_power_keys >= 7) { + return REBOOT; + } + } + } else { + consecutive_power_keys = 0; + } + + last_key = key; + return IsTextVisible() ? ENQUEUE : IGNORE; +} + +void RecoveryUI::KeyLongPress(int) { +} + +void RecoveryUI::SetEnableReboot(bool enabled) { + pthread_mutex_lock(&key_queue_mutex); + enable_reboot = enabled; + pthread_mutex_unlock(&key_queue_mutex); +}
diff --git a/recovery/ui.h b/recovery/ui.h new file mode 100644 index 0000000..ca72911 --- /dev/null +++ b/recovery/ui.h
@@ -0,0 +1,166 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RECOVERY_UI_H +#define RECOVERY_UI_H + +#include <linux/input.h> +#include <pthread.h> +#include <time.h> + +// Abstract class for controlling the user interface during recovery. +class RecoveryUI { + public: + RecoveryUI(); + + virtual ~RecoveryUI() { } + + // Initialize the object; called before anything else. + virtual void Init(); + // Show a stage indicator. Call immediately after Init(). + virtual void SetStage(int current, int max) = 0; + + // After calling Init(), you can tell the UI what locale it is operating in. + virtual void SetLocale(const char* locale) = 0; + + // Set the overall recovery state ("background image"). + enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR }; + virtual void SetBackground(Icon icon) = 0; + + // --- progress indicator --- + enum ProgressType { EMPTY, INDETERMINATE, DETERMINATE }; + virtual void SetProgressType(ProgressType determinate) = 0; + + // Show a progress bar and define the scope of the next operation: + // portion - fraction of the progress bar the next operation will use + // seconds - expected time interval (progress bar moves at this minimum rate) + virtual void ShowProgress(float portion, float seconds) = 0; + + // Set progress bar position (0.0 - 1.0 within the scope defined + // by the last call to ShowProgress). + virtual void SetProgress(float fraction) = 0; + + // --- text log --- + + virtual void ShowText(bool visible) = 0; + + virtual bool IsTextVisible() = 0; + + virtual bool WasTextEverVisible() = 0; + + // Write a message to the on-screen log (shown if the user has + // toggled on the text display). Print() will also dump the message + // to stdout / log file, while PrintOnScreenOnly() not. + virtual void Print(const char* fmt, ...) __printflike(2, 3) = 0; + virtual void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3) = 0; + + virtual void ShowFile(const char* filename) = 0; + + // --- key handling --- + + // Wait for a key and return it. May return -1 after timeout. + virtual int WaitKey(); + + virtual bool IsKeyPressed(int key); + virtual bool IsLongPress(); + + // Returns true if you have the volume up/down and power trio typical + // of phones and tablets, false otherwise. + virtual bool HasThreeButtons(); + + // Erase any queued-up keys. + virtual void FlushKeys(); + + // 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 released). This means that + // if the key is eventually registered (released without any other + // keys being pressed in the meantime), CheckKey will be called with + // 'is_long_press' true. + virtual void KeyLongPress(int key); + + // Normally in recovery there's a key sequence that triggers + // immediate reboot of the device, regardless of what recovery is + // doing (with the default CheckKey implementation, it's pressing + // the power button 7 times in row). Call this to enable or + // disable that feature. It is enabled by default. + virtual void SetEnableReboot(bool enabled); + + // --- menu display --- + + // Display some header text followed by a menu of items, which appears + // at the top of the screen (in place of any scrolling ui_print() + // output, if necessary). + virtual void StartMenu(const char* const * headers, const char* const * items, + int initial_selection) = 0; + + // Set the menu highlight to the given index, wrapping if necessary. + // Returns the actual item selected. + virtual int SelectMenu(int sel) = 0; + + // End menu mode, resetting the text overlay so that ui_print() + // statements will be displayed. + virtual void EndMenu() = 0; + +protected: + void EnqueueKey(int key_code); + +private: + // Key event input queue + pthread_mutex_t key_queue_mutex; + pthread_cond_t key_queue_cond; + int key_queue[256], key_queue_len; + char key_pressed[KEY_MAX + 1]; // under key_queue_mutex + int key_last_down; // under key_queue_mutex + bool key_long_press; // under key_queue_mutex + int key_down_count; // under key_queue_mutex + bool enable_reboot; // under key_queue_mutex + int rel_sum; + + int consecutive_power_keys; + int last_key; + + bool has_power_key; + bool has_up_key; + bool has_down_key; + + struct key_timer_t { + RecoveryUI* ui; + int key_code; + int count; + }; + + pthread_t input_thread_; + + void OnKeyDetected(int key_code); + + static int InputCallback(int fd, uint32_t epevents, void* data); + 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); +}; + +#endif // RECOVERY_UI_H
diff --git a/recovery/uncrypt/Android.mk b/recovery/uncrypt/Android.mk new file mode 100644 index 0000000..6422cb2 --- /dev/null +++ b/recovery/uncrypt/Android.mk
@@ -0,0 +1,31 @@ +# 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 := libbase liblog libfs_mgr libcutils + +LOCAL_INIT_RC := uncrypt.rc + +include $(BUILD_EXECUTABLE)
diff --git a/recovery/uncrypt/uncrypt.cpp b/recovery/uncrypt/uncrypt.cpp new file mode 100644 index 0000000..a1de6a1 --- /dev/null +++ b/recovery/uncrypt/uncrypt.cpp
@@ -0,0 +1,544 @@ +/* + * 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. + */ + +// This program takes a file on an ext4 filesystem and produces a list +// of the blocks that file occupies, which enables the file contents +// to be read directly from the block device without mounting the +// filesystem. +// +// If the filesystem is using an encrypted block device, it will also +// read the file and rewrite it to the same blocks of the underlying +// (unencrypted) block device, so the file contents can be read +// without the need for the decryption key. +// +// The output of this program is a "block map" which looks like this: +// +// /dev/block/platform/msm_sdcc.1/by-name/userdata # block device +// 49652 4096 # file size in bytes, block size +// 3 # count of block ranges +// 1000 1008 # block range 0 +// 2100 2102 # ... block range 1 +// 30 33 # ... block range 2 +// +// Each block range represents a half-open interval; the line "30 33" +// reprents the blocks [30, 31, 32]. +// +// Recovery can take this block map file and retrieve the underlying +// file data to use as an update package. + +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <libgen.h> +#include <linux/fs.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <algorithm> +#include <memory> +#include <vector> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/stringprintf.h> +#include <android-base/strings.h> +#include <android-base/unique_fd.h> +#include <cutils/android_reboot.h> +#include <cutils/properties.h> +#include <fs_mgr.h> + +#define LOG_TAG "uncrypt" +#include <log/log.h> + +#include "bootloader.h" + +#define WINDOW_SIZE 5 + +static const std::string CACHE_BLOCK_MAP = "/cache/recovery/block.map"; +static const std::string COMMAND_FILE = "/cache/recovery/command"; +static const std::string STATUS_FILE = "/cache/recovery/uncrypt_status"; +static const std::string UNCRYPT_PATH_FILE = "/cache/recovery/uncrypt_file"; + +static struct fstab* fstab = NULL; + +static int write_at_offset(unsigned char* buffer, size_t size, int wfd, off64_t offset) { + if (TEMP_FAILURE_RETRY(lseek64(wfd, offset, SEEK_SET)) == -1) { + ALOGE("error seeking to offset %" PRId64 ": %s", offset, strerror(errno)); + return -1; + } + if (!android::base::WriteFully(wfd, buffer, size)) { + ALOGE("error writing offset %" PRId64 ": %s", offset, strerror(errno)); + return -1; + } + return 0; +} + +static void add_block_to_ranges(std::vector<int>& ranges, int new_block) { + if (!ranges.empty() && new_block == ranges.back()) { + // If the new block comes immediately after the current range, + // all we have to do is extend the current range. + ++ranges.back(); + } else { + // We need to start a new range. + ranges.push_back(new_block); + ranges.push_back(new_block + 1); + } +} + +static struct fstab* read_fstab() { + fstab = NULL; + + // The fstab path is always "/fstab.${ro.hardware}". + char fstab_path[PATH_MAX+1] = "/fstab."; + if (!property_get("ro.hardware", fstab_path+strlen(fstab_path), "")) { + ALOGE("failed to get ro.hardware"); + return NULL; + } + + fstab = fs_mgr_read_fstab(fstab_path); + if (!fstab) { + ALOGE("failed to read %s", fstab_path); + return NULL; + } + + return fstab; +} + +static const char* find_block_device(const char* path, bool* encryptable, bool* encrypted) { + // Look for a volume whose mount point is the prefix of path and + // return its block device. Set encrypted if it's currently + // encrypted. + for (int i = 0; i < fstab->num_entries; ++i) { + struct fstab_rec* v = &fstab->recs[i]; + if (!v->mount_point) { + continue; + } + int len = strlen(v->mount_point); + if (strncmp(path, v->mount_point, len) == 0 && + (path[len] == '/' || path[len] == 0)) { + *encrypted = false; + *encryptable = false; + if (fs_mgr_is_encryptable(v) || fs_mgr_is_file_encrypted(v)) { + *encryptable = true; + char buffer[PROPERTY_VALUE_MAX+1]; + if (property_get("ro.crypto.state", buffer, "") && + strcmp(buffer, "encrypted") == 0) { + *encrypted = true; + } + } + return v->blk_device; + } + } + + return NULL; +} + +// Parse uncrypt_file to find the update package name. +static bool find_uncrypt_package(const std::string& uncrypt_path_file, std::string* package_name) { + CHECK(package_name != nullptr); + std::string uncrypt_path; + if (!android::base::ReadFileToString(uncrypt_path_file, &uncrypt_path)) { + ALOGE("failed to open \"%s\": %s", uncrypt_path_file.c_str(), strerror(errno)); + return false; + } + + // Remove the trailing '\n' if present. + *package_name = android::base::Trim(uncrypt_path); + return true; +} + +static int produce_block_map(const char* path, const char* map_file, const char* blk_dev, + bool encrypted, int status_fd) { + std::string err; + if (!android::base::RemoveFileIfExists(map_file, &err)) { + ALOGE("failed to remove the existing map file %s: %s", map_file, err.c_str()); + return -1; + } + std::string tmp_map_file = std::string(map_file) + ".tmp"; + android::base::unique_fd mapfd(open(tmp_map_file.c_str(), + O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)); + if (mapfd == -1) { + ALOGE("failed to open %s: %s\n", tmp_map_file.c_str(), strerror(errno)); + return -1; + } + + // Make sure we can write to the status_file. + if (!android::base::WriteStringToFd("0\n", status_fd)) { + ALOGE("failed to update \"%s\"\n", STATUS_FILE.c_str()); + return -1; + } + + struct stat sb; + if (stat(path, &sb) != 0) { + ALOGE("failed to stat %s", path); + return -1; + } + + ALOGI(" block size: %ld bytes", static_cast<long>(sb.st_blksize)); + + int blocks = ((sb.st_size-1) / sb.st_blksize) + 1; + ALOGI(" file size: %" PRId64 " bytes, %d blocks", sb.st_size, blocks); + + std::vector<int> ranges; + + std::string s = android::base::StringPrintf("%s\n%" PRId64 " %" PRId64 "\n", + blk_dev, static_cast<int64_t>(sb.st_size), + static_cast<int64_t>(sb.st_blksize)); + if (!android::base::WriteStringToFd(s, mapfd)) { + ALOGE("failed to write %s: %s", tmp_map_file.c_str(), strerror(errno)); + return -1; + } + + std::vector<std::vector<unsigned char>> buffers; + if (encrypted) { + buffers.resize(WINDOW_SIZE, std::vector<unsigned char>(sb.st_blksize)); + } + int head_block = 0; + int head = 0, tail = 0; + + android::base::unique_fd fd(open(path, O_RDONLY)); + if (fd == -1) { + ALOGE("failed to open %s for reading: %s", path, strerror(errno)); + return -1; + } + + android::base::unique_fd wfd; + if (encrypted) { + wfd.reset(open(blk_dev, O_WRONLY)); + if (wfd == -1) { + ALOGE("failed to open fd for writing: %s", strerror(errno)); + return -1; + } + } + + off64_t pos = 0; + int last_progress = 0; + while (pos < sb.st_size) { + // Update the status file, progress must be between [0, 99]. + int progress = static_cast<int>(100 * (double(pos) / double(sb.st_size))); + if (progress > last_progress) { + last_progress = progress; + android::base::WriteStringToFd(std::to_string(progress) + "\n", status_fd); + } + + if ((tail+1) % WINDOW_SIZE == head) { + // write out head buffer + int block = head_block; + if (ioctl(fd, FIBMAP, &block) != 0) { + ALOGE("failed to find block %d", head_block); + return -1; + } + add_block_to_ranges(ranges, block); + if (encrypted) { + if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd, + static_cast<off64_t>(sb.st_blksize) * block) != 0) { + return -1; + } + } + head = (head + 1) % WINDOW_SIZE; + ++head_block; + } + + // read next block to tail + if (encrypted) { + size_t to_read = static_cast<size_t>( + std::min(static_cast<off64_t>(sb.st_blksize), sb.st_size - pos)); + if (!android::base::ReadFully(fd, buffers[tail].data(), to_read)) { + ALOGE("failed to read: %s", strerror(errno)); + return -1; + } + pos += to_read; + } else { + // If we're not encrypting; we don't need to actually read + // anything, just skip pos forward as if we'd read a + // block. + pos += sb.st_blksize; + } + tail = (tail+1) % WINDOW_SIZE; + } + + while (head != tail) { + // write out head buffer + int block = head_block; + if (ioctl(fd, FIBMAP, &block) != 0) { + ALOGE("failed to find block %d", head_block); + return -1; + } + add_block_to_ranges(ranges, block); + if (encrypted) { + if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd, + static_cast<off64_t>(sb.st_blksize) * block) != 0) { + return -1; + } + } + head = (head + 1) % WINDOW_SIZE; + ++head_block; + } + + if (!android::base::WriteStringToFd( + android::base::StringPrintf("%zu\n", ranges.size() / 2), mapfd)) { + ALOGE("failed to write %s: %s", tmp_map_file.c_str(), strerror(errno)); + return -1; + } + for (size_t i = 0; i < ranges.size(); i += 2) { + if (!android::base::WriteStringToFd( + android::base::StringPrintf("%d %d\n", ranges[i], ranges[i+1]), mapfd)) { + ALOGE("failed to write %s: %s", tmp_map_file.c_str(), strerror(errno)); + return -1; + } + } + + if (fsync(mapfd) == -1) { + ALOGE("failed to fsync \"%s\": %s", tmp_map_file.c_str(), strerror(errno)); + return -1; + } + if (close(mapfd.release()) == -1) { + ALOGE("failed to close %s: %s", tmp_map_file.c_str(), strerror(errno)); + return -1; + } + + if (encrypted) { + if (fsync(wfd) == -1) { + ALOGE("failed to fsync \"%s\": %s", blk_dev, strerror(errno)); + return -1; + } + if (close(wfd.release()) == -1) { + ALOGE("failed to close %s: %s", blk_dev, strerror(errno)); + return -1; + } + } + + if (rename(tmp_map_file.c_str(), map_file) == -1) { + ALOGE("failed to rename %s to %s: %s", tmp_map_file.c_str(), map_file, strerror(errno)); + return -1; + } + // Sync dir to make rename() result written to disk. + std::string file_name = map_file; + std::string dir_name = dirname(&file_name[0]); + android::base::unique_fd dfd(open(dir_name.c_str(), O_RDONLY | O_DIRECTORY)); + if (dfd == -1) { + ALOGE("failed to open dir %s: %s", dir_name.c_str(), strerror(errno)); + return -1; + } + if (fsync(dfd) == -1) { + ALOGE("failed to fsync %s: %s", dir_name.c_str(), strerror(errno)); + return -1; + } + if (close(dfd.release()) == -1) { + ALOGE("failed to close %s: %s", dir_name.c_str(), strerror(errno)); + return -1; + } + return 0; +} + +static std::string get_misc_blk_device() { + struct fstab* fstab = read_fstab(); + if (fstab == nullptr) { + return ""; + } + for (int i = 0; i < fstab->num_entries; ++i) { + fstab_rec* v = &fstab->recs[i]; + if (v->mount_point != nullptr && strcmp(v->mount_point, "/misc") == 0) { + return v->blk_device; + } + } + return ""; +} + +static int write_bootloader_message(const bootloader_message* in) { + std::string misc_blk_device = get_misc_blk_device(); + if (misc_blk_device.empty()) { + ALOGE("failed to find /misc partition."); + return -1; + } + android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY | O_SYNC)); + if (fd == -1) { + ALOGE("failed to open %s: %s", misc_blk_device.c_str(), strerror(errno)); + return -1; + } + if (!android::base::WriteFully(fd, in, sizeof(*in))) { + ALOGE("failed to write %s: %s", misc_blk_device.c_str(), strerror(errno)); + return -1; + } + // TODO: O_SYNC and fsync() duplicates each other? + if (fsync(fd) == -1) { + ALOGE("failed to fsync %s: %s", misc_blk_device.c_str(), strerror(errno)); + return -1; + } + return 0; +} + +static void reboot_to_recovery() { + ALOGI("rebooting to recovery"); + property_set("sys.powerctl", "reboot,recovery"); + while (true) { + pause(); + } + ALOGE("reboot didn't succeed?"); +} + +static int uncrypt(const char* input_path, const char* map_file, int status_fd) { + + ALOGI("update package is \"%s\"", 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. + char path[PATH_MAX+1]; + if (realpath(input_path, path) == NULL) { + ALOGE("failed to convert \"%s\" to absolute path: %s", input_path, strerror(errno)); + return 1; + } + + if (read_fstab() == NULL) { + return 1; + } + + bool encryptable; + bool encrypted; + const char* blk_dev = find_block_device(path, &encryptable, &encrypted); + if (blk_dev == NULL) { + ALOGE("failed to find block device for %s", path); + return 1; + } + + // If the filesystem it's on isn't encrypted, we only produce the + // block map, we don't rewrite the file contents (it would be + // pointless to do so). + ALOGI("encryptable: %s", encryptable ? "yes" : "no"); + ALOGI(" encrypted: %s", encrypted ? "yes" : "no"); + + // Recovery supports installing packages from 3 paths: /cache, + // /data, and /sdcard. (On a particular device, other locations + // may work, but those are three we actually expect.) + // + // On /data we want to convert the file to a block map so that we + // can read the package without mounting the partition. On /cache + // and /sdcard we leave the file alone. + if (strncmp(path, "/data/", 6) == 0) { + ALOGI("writing block map %s", map_file); + if (produce_block_map(path, map_file, blk_dev, encrypted, status_fd) != 0) { + return 1; + } + } + + return 0; +} + +static int uncrypt_wrapper(const char* input_path, const char* map_file, + const std::string& status_file) { + // The pipe has been created by the system server. + android::base::unique_fd status_fd(open(status_file.c_str(), + O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR)); + if (status_fd == -1) { + ALOGE("failed to open pipe \"%s\": %s", status_file.c_str(), strerror(errno)); + return 1; + } + + std::string package; + if (input_path == nullptr) { + if (!find_uncrypt_package(UNCRYPT_PATH_FILE, &package)) { + android::base::WriteStringToFd("-1\n", status_fd); + return 1; + } + input_path = package.c_str(); + } + CHECK(map_file != nullptr); + int status = uncrypt(input_path, map_file, status_fd); + if (status != 0) { + android::base::WriteStringToFd("-1\n", status_fd); + return 1; + } + android::base::WriteStringToFd("100\n", status_fd); + return 0; +} + +static int clear_bcb(const std::string& status_file) { + android::base::unique_fd status_fd(open(status_file.c_str(), + O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR)); + if (status_fd == -1) { + ALOGE("failed to open pipe \"%s\": %s", status_file.c_str(), strerror(errno)); + return 1; + } + bootloader_message boot = {}; + if (write_bootloader_message(&boot) != 0) { + android::base::WriteStringToFd("-1\n", status_fd); + return 1; + } + android::base::WriteStringToFd("100\n", status_fd); + return 0; +} + +static int setup_bcb(const std::string& command_file, const std::string& status_file) { + android::base::unique_fd status_fd(open(status_file.c_str(), + O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR)); + if (status_fd == -1) { + ALOGE("failed to open pipe \"%s\": %s", status_file.c_str(), strerror(errno)); + return 1; + } + std::string content; + if (!android::base::ReadFileToString(command_file, &content)) { + ALOGE("failed to read \"%s\": %s", command_file.c_str(), strerror(errno)); + android::base::WriteStringToFd("-1\n", status_fd); + return 1; + } + bootloader_message boot = {}; + strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); + strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); + strlcat(boot.recovery, content.c_str(), sizeof(boot.recovery)); + if (write_bootloader_message(&boot) != 0) { + ALOGE("failed to set bootloader message"); + android::base::WriteStringToFd("-1\n", status_fd); + return 1; + } + android::base::WriteStringToFd("100\n", status_fd); + return 0; +} + +static void usage(const char* exename) { + fprintf(stderr, "Usage of %s:\n", exename); + fprintf(stderr, "%s [<package_path> <map_file>] Uncrypt ota package.\n", exename); + fprintf(stderr, "%s --reboot Clear BCB data and reboot to recovery.\n", exename); + fprintf(stderr, "%s --clear-bcb Clear BCB data in misc partition.\n", exename); + fprintf(stderr, "%s --setup-bcb Setup BCB data by command file.\n", exename); +} + +int main(int argc, char** argv) { + if (argc == 2) { + if (strcmp(argv[1], "--reboot") == 0) { + reboot_to_recovery(); + } else if (strcmp(argv[1], "--clear-bcb") == 0) { + return clear_bcb(STATUS_FILE); + } else if (strcmp(argv[1], "--setup-bcb") == 0) { + return setup_bcb(COMMAND_FILE, STATUS_FILE); + } + } else if (argc == 1 || argc == 3) { + const char* input_path = nullptr; + const char* map_file = CACHE_BLOCK_MAP.c_str(); + if (argc == 3) { + input_path = argv[1]; + map_file = argv[2]; + } + return uncrypt_wrapper(input_path, map_file, STATUS_FILE); + } + usage(argv[0]); + return 2; +}
diff --git a/recovery/uncrypt/uncrypt.rc b/recovery/uncrypt/uncrypt.rc new file mode 100644 index 0000000..b07c1da --- /dev/null +++ b/recovery/uncrypt/uncrypt.rc
@@ -0,0 +1,19 @@ +service uncrypt /system/bin/uncrypt + class main + disabled + oneshot + +service pre-recovery /system/bin/uncrypt --reboot + class main + disabled + oneshot + +service setup-bcb /system/bin/uncrypt --setup-bcb + class main + disabled + oneshot + +service clear-bcb /system/bin/uncrypt --clear-bcb + class main + disabled + oneshot \ No newline at end of file
diff --git a/recovery/update_verifier/Android.mk b/recovery/update_verifier/Android.mk new file mode 100644 index 0000000..7f28bce --- /dev/null +++ b/recovery/update_verifier/Android.mk
@@ -0,0 +1,24 @@ +# Copyright (C) 2015 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_CLANG := true +LOCAL_SRC_FILES := update_verifier.cpp +LOCAL_MODULE := update_verifier +LOCAL_SHARED_LIBRARIES := libhardware liblog + +include $(BUILD_EXECUTABLE)
diff --git a/recovery/update_verifier/update_verifier.cpp b/recovery/update_verifier/update_verifier.cpp new file mode 100644 index 0000000..be70cec --- /dev/null +++ b/recovery/update_verifier/update_verifier.cpp
@@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This program verifies the integrity of the partitions after an A/B OTA + * update. It gets invoked by init, and will only perform the verification if + * it's the first boot post an A/B OTA update. + * + * It relies on dm-verity to capture any corruption on the partitions being + * verified. dm-verity must be in enforcing mode, so that it will reboot the + * device on dm-verity failures. When that happens, the bootloader should + * mark the slot as unbootable and stops trying. We should never see a device + * started in dm-verity logging mode but with isSlotMarkedSuccessful equals to + * 0. + * + * The current slot will be marked as having booted successfully if the + * verifier reaches the end after the verification. + * + * TODO: The actual verification part will be added later after we have the + * A/B OTA package format in place. + */ + +#include <string.h> + +#include <hardware/boot_control.h> + +#define LOG_TAG "update_verifier" +#include <log/log.h> + +int main(int argc, char** argv) { + for (int i = 1; i < argc; i++) { + SLOGI("Started with arg %d: %s\n", i, argv[i]); + } + + const hw_module_t* hw_module; + if (hw_get_module("bootctrl", &hw_module) != 0) { + SLOGE("Error getting bootctrl module.\n"); + return -1; + } + + boot_control_module_t* module = reinterpret_cast<boot_control_module_t*>( + const_cast<hw_module_t*>(hw_module)); + module->init(module); + + unsigned current_slot = module->getCurrentSlot(module); + int is_successful= module->isSlotMarkedSuccessful(module, current_slot); + SLOGI("Booting slot %u: isSlotMarkedSuccessful=%d\n", current_slot, is_successful); + + if (is_successful == 0) { + // The current slot has not booted successfully. + + // TODO: Add the actual verification after we have the A/B OTA package + // format in place. + + // TODO: Assert the dm-verity mode. Bootloader should never boot a newly + // flashed slot (isSlotMarkedSuccessful == 0) with dm-verity logging mode. + + int ret = module->markBootSuccessful(module); + if (ret != 0) { + SLOGE("Error marking booted successfully: %s\n", strerror(-ret)); + return -1; + } + SLOGI("Marked slot %u as booted successfully.\n", current_slot); + } + + SLOGI("Leaving update_verifier.\n"); + return 0; +}
diff --git a/recovery/updater/Android.mk b/recovery/updater/Android.mk new file mode 100644 index 0000000..b4d427c --- /dev/null +++ b/recovery/updater/Android.mk
@@ -0,0 +1,115 @@ +# 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) + +# updater (static executable) +# =============================== +# Build a statically-linked binary to include in OTA packages. +include $(CLEAR_VARS) + +updater_src_files := \ + install.cpp \ + blockimg.cpp \ + updater.cpp + +LOCAL_CLANG := true +LOCAL_SRC_FILES := $(updater_src_files) + +LOCAL_STATIC_LIBRARIES += \ + $(TARGET_RECOVERY_UPDATER_LIBS) \ + $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) \ + libfec \ + libfec_rs \ + libext4_utils_static \ + libsquashfs_utils \ + libcrypto_utils \ + libcrypto \ + libapplypatch \ + libbase \ + libotafault \ + libedify \ + libminzip \ + libmounts \ + libz \ + libbz \ + libcutils \ + liblog \ + libselinux + +tune2fs_static_libraries := \ + libext2_com_err \ + libext2_blkid \ + libext2_quota \ + libext2_uuid_static \ + libext2_e2p \ + libext2fs + +LOCAL_STATIC_LIBRARIES += \ + libtune2fs \ + $(tune2fs_static_libraries) + +LOCAL_CFLAGS += -Wno-unused-parameter +LOCAL_C_INCLUDES += system/extras/ext4_utils +LOCAL_STATIC_LIBRARIES += \ + libsparse_static \ + libz + +LOCAL_C_INCLUDES += external/e2fsprogs/misc +LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. + +# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function +# named "Register_<libname>()". Here we emit a little C function that +# gets #included by updater.c. It calls all those registration +# functions. + +# Devices can also add libraries to TARGET_RECOVERY_UPDATER_EXTRA_LIBS. +# These libs are also linked in with updater, but we don't try to call +# any sort of registration function for these. Use this variable for +# any subsidiary static libraries required for your registered +# extension libs. + +inc := $(call intermediates-dir-for,PACKAGING,updater_extensions)/register.inc + +# Encode the value of TARGET_RECOVERY_UPDATER_LIBS into the filename of the dependency. +# So if TARGET_RECOVERY_UPDATER_LIBS is changed, a new dependency file will be generated. +# Note that we have to remove any existing depency files before creating new one, +# so no obsolete dependecy file gets used if you switch back to an old value. +inc_dep_file := $(inc).dep.$(subst $(space),-,$(sort $(TARGET_RECOVERY_UPDATER_LIBS))) +$(inc_dep_file): stem := $(inc).dep +$(inc_dep_file) : + $(hide) mkdir -p $(dir $@) + $(hide) rm -f $(stem).* + $(hide) touch $@ + +$(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS) +$(inc) : $(inc_dep_file) + $(hide) mkdir -p $(dir $@) + $(hide) echo "" > $@ + $(hide) $(foreach lib,$(libs),echo "extern void Register_$(lib)(void);" >> $@;) + $(hide) echo "void RegisterDeviceExtensions() {" >> $@ + $(hide) $(foreach lib,$(libs),echo " Register_$(lib)();" >> $@;) + $(hide) echo "}" >> $@ + +$(call intermediates-dir-for,EXECUTABLES,updater,,,$(TARGET_PREFER_32_BIT))/updater.o : $(inc) +LOCAL_C_INCLUDES += $(dir $(inc)) + +inc := +inc_dep_file := + +LOCAL_MODULE := updater + +LOCAL_FORCE_STATIC_EXECUTABLE := true + +include $(BUILD_EXECUTABLE)
diff --git a/recovery/updater/blockimg.cpp b/recovery/updater/blockimg.cpp new file mode 100644 index 0000000..12a549d --- /dev/null +++ b/recovery/updater/blockimg.cpp
@@ -0,0 +1,1819 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ctype.h> +#include <errno.h> +#include <dirent.h> +#include <fcntl.h> +#include <inttypes.h> +#include <linux/fs.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/ioctl.h> +#include <time.h> +#include <unistd.h> +#include <fec/io.h> + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include <android-base/parseint.h> +#include <android-base/strings.h> +#include <android-base/unique_fd.h> + +#include "applypatch/applypatch.h" +#include "edify/expr.h" +#include "install.h" +#include "openssl/sha.h" +#include "minzip/Hash.h" +#include "ota_io.h" +#include "print_sha1.h" +#include "updater.h" + +#define BLOCKSIZE 4096 + +// Set this to 0 to interpret 'erase' transfers to mean do a +// BLKDISCARD ioctl (the normal behavior). Set to 1 to interpret +// erase to mean fill the region with zeroes. +#define DEBUG_ERASE 0 + +#define STASH_DIRECTORY_BASE "/cache/recovery" +#define STASH_DIRECTORY_MODE 0700 +#define STASH_FILE_MODE 0600 + +struct RangeSet { + size_t count; // Limit is INT_MAX. + size_t size; + std::vector<size_t> pos; // Actual limit is INT_MAX. +}; + +static std::map<std::string, RangeSet> stash_map; + +static void parse_range(const std::string& range_text, RangeSet& rs) { + + std::vector<std::string> pieces = android::base::Split(range_text, ","); + if (pieces.size() < 3) { + goto err; + } + + size_t num; + if (!android::base::ParseUint(pieces[0].c_str(), &num, static_cast<size_t>(INT_MAX))) { + goto err; + } + + if (num == 0 || num % 2) { + goto err; // must be even + } else if (num != pieces.size() - 1) { + goto err; + } + + rs.pos.resize(num); + rs.count = num / 2; + rs.size = 0; + + for (size_t i = 0; i < num; i += 2) { + if (!android::base::ParseUint(pieces[i+1].c_str(), &rs.pos[i], + static_cast<size_t>(INT_MAX))) { + goto err; + } + + if (!android::base::ParseUint(pieces[i+2].c_str(), &rs.pos[i+1], + static_cast<size_t>(INT_MAX))) { + goto err; + } + + if (rs.pos[i] >= rs.pos[i+1]) { + goto err; // empty or negative range + } + + size_t sz = rs.pos[i+1] - rs.pos[i]; + if (rs.size > SIZE_MAX - sz) { + goto err; // overflow + } + + rs.size += sz; + } + + return; + +err: + fprintf(stderr, "failed to parse range '%s'\n", range_text.c_str()); + exit(1); +} + +static bool range_overlaps(const RangeSet& r1, const RangeSet& r2) { + for (size_t i = 0; i < r1.count; ++i) { + size_t r1_0 = r1.pos[i * 2]; + size_t r1_1 = r1.pos[i * 2 + 1]; + + for (size_t j = 0; j < r2.count; ++j) { + size_t r2_0 = r2.pos[j * 2]; + size_t r2_1 = r2.pos[j * 2 + 1]; + + if (!(r2_0 >= r1_1 || r1_0 >= r2_1)) { + return true; + } + } + } + + return false; +} + +static int read_all(int fd, uint8_t* data, size_t size) { + size_t so_far = 0; + while (so_far < size) { + ssize_t r = TEMP_FAILURE_RETRY(ota_read(fd, data+so_far, size-so_far)); + if (r == -1) { + fprintf(stderr, "read failed: %s\n", strerror(errno)); + return -1; + } + so_far += r; + } + return 0; +} + +static int read_all(int fd, std::vector<uint8_t>& buffer, size_t size) { + return read_all(fd, buffer.data(), size); +} + +static int write_all(int fd, const uint8_t* data, size_t size) { + size_t written = 0; + while (written < size) { + ssize_t w = TEMP_FAILURE_RETRY(ota_write(fd, data+written, size-written)); + if (w == -1) { + fprintf(stderr, "write failed: %s\n", strerror(errno)); + return -1; + } + written += w; + } + + return 0; +} + +static int write_all(int fd, const std::vector<uint8_t>& buffer, size_t size) { + return write_all(fd, buffer.data(), size); +} + +static bool check_lseek(int fd, off64_t offset, int whence) { + off64_t rc = TEMP_FAILURE_RETRY(lseek64(fd, offset, whence)); + if (rc == -1) { + fprintf(stderr, "lseek64 failed: %s\n", strerror(errno)); + return false; + } + return true; +} + +static void allocate(size_t size, std::vector<uint8_t>& buffer) { + // if the buffer's big enough, reuse it. + if (size <= buffer.size()) return; + + buffer.resize(size); +} + +struct RangeSinkState { + explicit RangeSinkState(RangeSet& rs) : tgt(rs) { }; + + int fd; + const RangeSet& tgt; + size_t p_block; + size_t p_remain; +}; + +static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) { + RangeSinkState* rss = reinterpret_cast<RangeSinkState*>(token); + + if (rss->p_remain == 0) { + fprintf(stderr, "range sink write overrun"); + return 0; + } + + ssize_t written = 0; + while (size > 0) { + size_t write_now = size; + + if (rss->p_remain < write_now) { + write_now = rss->p_remain; + } + + if (write_all(rss->fd, data, write_now) == -1) { + break; + } + + data += write_now; + size -= write_now; + + rss->p_remain -= write_now; + written += write_now; + + if (rss->p_remain == 0) { + // move to the next block + ++rss->p_block; + if (rss->p_block < rss->tgt.count) { + rss->p_remain = (rss->tgt.pos[rss->p_block * 2 + 1] - + rss->tgt.pos[rss->p_block * 2]) * BLOCKSIZE; + + if (!check_lseek(rss->fd, (off64_t)rss->tgt.pos[rss->p_block*2] * BLOCKSIZE, + SEEK_SET)) { + break; + } + } else { + // we can't write any more; return how many bytes have + // been written so far. + break; + } + } + } + + return written; +} + +// All of the data for all the 'new' transfers is contained in one +// file in the update package, concatenated together in the order in +// which transfers.list will need it. We want to stream it out of the +// archive (it's compressed) without writing it to a temp file, but we +// can't write each section until it's that transfer's turn to go. +// +// To achieve this, we expand the new data from the archive in a +// background thread, and block that threads 'receive uncompressed +// data' function until the main thread has reached a point where we +// want some new data to be written. We signal the background thread +// with the destination for the data and block the main thread, +// waiting for the background thread to complete writing that section. +// Then it signals the main thread to wake up and goes back to +// blocking waiting for a transfer. +// +// NewThreadInfo is the struct used to pass information back and forth +// between the two threads. When the main thread wants some data +// written, it sets rss to the destination location and signals the +// condition. When the background thread is done writing, it clears +// rss and signals the condition again. + +struct NewThreadInfo { + ZipArchive* za; + const ZipEntry* entry; + + RangeSinkState* rss; + + pthread_mutex_t mu; + pthread_cond_t cv; +}; + +static bool receive_new_data(const unsigned char* data, int size, void* cookie) { + NewThreadInfo* nti = reinterpret_cast<NewThreadInfo*>(cookie); + + while (size > 0) { + // Wait for nti->rss to be non-null, indicating some of this + // data is wanted. + pthread_mutex_lock(&nti->mu); + while (nti->rss == nullptr) { + pthread_cond_wait(&nti->cv, &nti->mu); + } + pthread_mutex_unlock(&nti->mu); + + // At this point nti->rss is set, and we own it. The main + // thread is waiting for it to disappear from nti. + ssize_t written = RangeSinkWrite(data, size, nti->rss); + data += written; + size -= written; + + if (nti->rss->p_block == nti->rss->tgt.count) { + // we have written all the bytes desired by this rss. + + pthread_mutex_lock(&nti->mu); + nti->rss = nullptr; + pthread_cond_broadcast(&nti->cv); + pthread_mutex_unlock(&nti->mu); + } + } + + return true; +} + +static void* unzip_new_data(void* cookie) { + NewThreadInfo* nti = (NewThreadInfo*) cookie; + mzProcessZipEntryContents(nti->za, nti->entry, receive_new_data, nti); + return nullptr; +} + +static int ReadBlocks(const RangeSet& src, std::vector<uint8_t>& buffer, int fd) { + size_t p = 0; + uint8_t* data = buffer.data(); + + for (size_t i = 0; i < src.count; ++i) { + if (!check_lseek(fd, (off64_t) src.pos[i * 2] * BLOCKSIZE, SEEK_SET)) { + return -1; + } + + size_t size = (src.pos[i * 2 + 1] - src.pos[i * 2]) * BLOCKSIZE; + + if (read_all(fd, data + p, size) == -1) { + return -1; + } + + p += size; + } + + return 0; +} + +static int WriteBlocks(const RangeSet& tgt, const std::vector<uint8_t>& buffer, int fd) { + const uint8_t* data = buffer.data(); + + size_t p = 0; + for (size_t i = 0; i < tgt.count; ++i) { + if (!check_lseek(fd, (off64_t) tgt.pos[i * 2] * BLOCKSIZE, SEEK_SET)) { + return -1; + } + + size_t size = (tgt.pos[i * 2 + 1] - tgt.pos[i * 2]) * BLOCKSIZE; + + if (write_all(fd, data + p, size) == -1) { + return -1; + } + + p += size; + } + + return 0; +} + +// Parameters for transfer list command functions +struct CommandParameters { + std::vector<std::string> tokens; + size_t cpos; + const char* cmdname; + const char* cmdline; + std::string freestash; + std::string stashbase; + bool canwrite; + int createdstash; + android::base::unique_fd fd; + bool foundwrites; + bool isunresumable; + int version; + size_t written; + NewThreadInfo nti; + pthread_t thread; + std::vector<uint8_t> buffer; + uint8_t* patch_start; +}; + +// Do a source/target load for move/bsdiff/imgdiff in version 1. +// We expect to parse the remainder of the parameter tokens as: +// +// <src_range> <tgt_range> +// +// The source range is loaded into the provided buffer, reallocating +// it to make it larger if necessary. + +static int LoadSrcTgtVersion1(CommandParameters& params, RangeSet& tgt, size_t& src_blocks, + std::vector<uint8_t>& buffer, int fd) { + + if (params.cpos + 1 >= params.tokens.size()) { + fprintf(stderr, "invalid parameters\n"); + return -1; + } + + // <src_range> + RangeSet src; + parse_range(params.tokens[params.cpos++], src); + + // <tgt_range> + parse_range(params.tokens[params.cpos++], tgt); + + allocate(src.size * BLOCKSIZE, buffer); + int rc = ReadBlocks(src, buffer, fd); + src_blocks = src.size; + + return rc; +} + +static int VerifyBlocks(const std::string& expected, const std::vector<uint8_t>& buffer, + const size_t blocks, bool printerror) { + uint8_t digest[SHA_DIGEST_LENGTH]; + const uint8_t* data = buffer.data(); + + SHA1(data, blocks * BLOCKSIZE, digest); + + std::string hexdigest = print_sha1(digest); + + if (hexdigest != expected) { + if (printerror) { + fprintf(stderr, "failed to verify blocks (expected %s, read %s)\n", + expected.c_str(), hexdigest.c_str()); + } + return -1; + } + + return 0; +} + +static std::string GetStashFileName(const std::string& base, const std::string& id, + const std::string& postfix) { + if (base.empty()) { + return ""; + } + + std::string fn(STASH_DIRECTORY_BASE); + fn += "/" + base + "/" + id + postfix; + + return fn; +} + +typedef void (*StashCallback)(const std::string&, void*); + +// Does a best effort enumeration of stash files. Ignores possible non-file +// items in the stash directory and continues despite of errors. Calls the +// 'callback' function for each file and passes 'data' to the function as a +// parameter. + +static void EnumerateStash(const std::string& dirname, StashCallback callback, void* data) { + if (dirname.empty() || callback == nullptr) { + return; + } + + std::unique_ptr<DIR, int(*)(DIR*)> directory(opendir(dirname.c_str()), closedir); + + if (directory == nullptr) { + if (errno != ENOENT) { + fprintf(stderr, "opendir \"%s\" failed: %s\n", dirname.c_str(), strerror(errno)); + } + return; + } + + struct dirent* item; + while ((item = readdir(directory.get())) != nullptr) { + if (item->d_type != DT_REG) { + continue; + } + + std::string fn = dirname + "/" + std::string(item->d_name); + callback(fn, data); + } +} + +static void UpdateFileSize(const std::string& fn, void* data) { + if (fn.empty() || !data) { + return; + } + + struct stat sb; + if (stat(fn.c_str(), &sb) == -1) { + fprintf(stderr, "stat \"%s\" failed: %s\n", fn.c_str(), strerror(errno)); + return; + } + + int* size = reinterpret_cast<int*>(data); + *size += sb.st_size; +} + +// Deletes the stash directory and all files in it. Assumes that it only +// contains files. There is nothing we can do about unlikely, but possible +// errors, so they are merely logged. + +static void DeleteFile(const std::string& fn, void* /* data */) { + if (!fn.empty()) { + fprintf(stderr, "deleting %s\n", fn.c_str()); + + if (unlink(fn.c_str()) == -1 && errno != ENOENT) { + fprintf(stderr, "unlink \"%s\" failed: %s\n", fn.c_str(), strerror(errno)); + } + } +} + +static void DeletePartial(const std::string& fn, void* data) { + if (android::base::EndsWith(fn, ".partial")) { + DeleteFile(fn, data); + } +} + +static void DeleteStash(const std::string& base) { + if (base.empty()) { + return; + } + + fprintf(stderr, "deleting stash %s\n", base.c_str()); + + std::string dirname = GetStashFileName(base, "", ""); + EnumerateStash(dirname, DeleteFile, nullptr); + + if (rmdir(dirname.c_str()) == -1) { + if (errno != ENOENT && errno != ENOTDIR) { + fprintf(stderr, "rmdir \"%s\" failed: %s\n", dirname.c_str(), strerror(errno)); + } + } +} + +static int LoadStash(CommandParameters& params, const std::string& base, const std::string& id, + bool verify, size_t* blocks, std::vector<uint8_t>& buffer, bool printnoent) { + // In verify mode, if source range_set was saved for the given hash, + // check contents in the source blocks first. If the check fails, + // search for the stashed files on /cache as usual. + if (!params.canwrite) { + if (stash_map.find(id) != stash_map.end()) { + const RangeSet& src = stash_map[id]; + allocate(src.size * BLOCKSIZE, buffer); + + if (ReadBlocks(src, buffer, params.fd) == -1) { + fprintf(stderr, "failed to read source blocks in stash map.\n"); + return -1; + } + if (VerifyBlocks(id, buffer, src.size, true) != 0) { + fprintf(stderr, "failed to verify loaded source blocks in stash map.\n"); + return -1; + } + return 0; + } + } + + if (base.empty()) { + return -1; + } + + size_t blockcount = 0; + + if (!blocks) { + blocks = &blockcount; + } + + std::string fn = GetStashFileName(base, id, ""); + + struct stat sb; + int res = stat(fn.c_str(), &sb); + + if (res == -1) { + if (errno != ENOENT || printnoent) { + fprintf(stderr, "stat \"%s\" failed: %s\n", fn.c_str(), strerror(errno)); + } + return -1; + } + + fprintf(stderr, " loading %s\n", fn.c_str()); + + if ((sb.st_size % BLOCKSIZE) != 0) { + fprintf(stderr, "%s size %" PRId64 " not multiple of block size %d", + fn.c_str(), static_cast<int64_t>(sb.st_size), BLOCKSIZE); + return -1; + } + + android::base::unique_fd fd(TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_RDONLY))); + if (fd == -1) { + fprintf(stderr, "open \"%s\" failed: %s\n", fn.c_str(), strerror(errno)); + return -1; + } + + allocate(sb.st_size, buffer); + + if (read_all(fd, buffer, sb.st_size) == -1) { + return -1; + } + + *blocks = sb.st_size / BLOCKSIZE; + + if (verify && VerifyBlocks(id, buffer, *blocks, true) != 0) { + fprintf(stderr, "unexpected contents in %s\n", fn.c_str()); + DeleteFile(fn, nullptr); + return -1; + } + + return 0; +} + +static int WriteStash(const std::string& base, const std::string& id, int blocks, + std::vector<uint8_t>& buffer, bool checkspace, bool *exists) { + if (base.empty()) { + return -1; + } + + if (checkspace && CacheSizeCheck(blocks * BLOCKSIZE) != 0) { + fprintf(stderr, "not enough space to write stash\n"); + return -1; + } + + std::string fn = GetStashFileName(base, id, ".partial"); + std::string cn = GetStashFileName(base, id, ""); + + if (exists) { + struct stat sb; + int res = stat(cn.c_str(), &sb); + + if (res == 0) { + // The file already exists and since the name is the hash of the contents, + // it's safe to assume the contents are identical (accidental hash collisions + // are unlikely) + fprintf(stderr, " skipping %d existing blocks in %s\n", blocks, cn.c_str()); + *exists = true; + return 0; + } + + *exists = false; + } + + fprintf(stderr, " writing %d blocks to %s\n", blocks, cn.c_str()); + + android::base::unique_fd fd(TEMP_FAILURE_RETRY(ota_open(fn.c_str(), + O_WRONLY | O_CREAT | O_TRUNC, + STASH_FILE_MODE))); + if (fd == -1) { + fprintf(stderr, "failed to create \"%s\": %s\n", fn.c_str(), strerror(errno)); + return -1; + } + + if (write_all(fd, buffer, blocks * BLOCKSIZE) == -1) { + return -1; + } + + if (ota_fsync(fd) == -1) { + fprintf(stderr, "fsync \"%s\" failed: %s\n", fn.c_str(), strerror(errno)); + return -1; + } + + if (rename(fn.c_str(), cn.c_str()) == -1) { + fprintf(stderr, "rename(\"%s\", \"%s\") failed: %s\n", fn.c_str(), cn.c_str(), + strerror(errno)); + return -1; + } + + std::string dname = GetStashFileName(base, "", ""); + android::base::unique_fd dfd(TEMP_FAILURE_RETRY(ota_open(dname.c_str(), + O_RDONLY | O_DIRECTORY))); + if (dfd == -1) { + fprintf(stderr, "failed to open \"%s\" failed: %s\n", dname.c_str(), strerror(errno)); + return -1; + } + + if (ota_fsync(dfd) == -1) { + fprintf(stderr, "fsync \"%s\" failed: %s\n", dname.c_str(), strerror(errno)); + return -1; + } + + return 0; +} + +// Creates a directory for storing stash files and checks if the /cache partition +// hash enough space for the expected amount of blocks we need to store. Returns +// >0 if we created the directory, zero if it existed already, and <0 of failure. + +static int CreateStash(State* state, int maxblocks, const char* blockdev, std::string& base) { + if (blockdev == nullptr) { + return -1; + } + + // Stash directory should be different for each partition to avoid conflicts + // when updating multiple partitions at the same time, so we use the hash of + // the block device name as the base directory + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast<const uint8_t*>(blockdev), strlen(blockdev), digest); + base = print_sha1(digest); + + std::string dirname = GetStashFileName(base, "", ""); + struct stat sb; + int res = stat(dirname.c_str(), &sb); + + if (res == -1 && errno != ENOENT) { + ErrorAbort(state, "stat \"%s\" failed: %s\n", dirname.c_str(), strerror(errno)); + return -1; + } else if (res != 0) { + fprintf(stderr, "creating stash %s\n", dirname.c_str()); + res = mkdir(dirname.c_str(), STASH_DIRECTORY_MODE); + + if (res != 0) { + ErrorAbort(state, "mkdir \"%s\" failed: %s\n", dirname.c_str(), strerror(errno)); + return -1; + } + + if (CacheSizeCheck(maxblocks * BLOCKSIZE) != 0) { + ErrorAbort(state, "not enough space for stash\n"); + return -1; + } + + return 1; // Created directory + } + + fprintf(stderr, "using existing stash %s\n", dirname.c_str()); + + // If the directory already exists, calculate the space already allocated to + // stash files and check if there's enough for all required blocks. Delete any + // partially completed stash files first. + + EnumerateStash(dirname, DeletePartial, nullptr); + int size = 0; + EnumerateStash(dirname, UpdateFileSize, &size); + + size = maxblocks * BLOCKSIZE - size; + + if (size > 0 && CacheSizeCheck(size) != 0) { + ErrorAbort(state, "not enough space for stash (%d more needed)\n", size); + return -1; + } + + return 0; // Using existing directory +} + +static int SaveStash(CommandParameters& params, const std::string& base, + std::vector<uint8_t>& buffer, int fd, bool usehash) { + + // <stash_id> <src_range> + if (params.cpos + 1 >= params.tokens.size()) { + fprintf(stderr, "missing id and/or src range fields in stash command\n"); + return -1; + } + const std::string& id = params.tokens[params.cpos++]; + + size_t blocks = 0; + if (usehash && LoadStash(params, base, id, true, &blocks, buffer, false) == 0) { + // Stash file already exists and has expected contents. Do not + // read from source again, as the source may have been already + // overwritten during a previous attempt. + return 0; + } + + RangeSet src; + parse_range(params.tokens[params.cpos++], src); + + allocate(src.size * BLOCKSIZE, buffer); + if (ReadBlocks(src, buffer, fd) == -1) { + return -1; + } + blocks = src.size; + + if (usehash && VerifyBlocks(id, buffer, blocks, true) != 0) { + // Source blocks have unexpected contents. If we actually need this + // data later, this is an unrecoverable error. However, the command + // that uses the data may have already completed previously, so the + // possible failure will occur during source block verification. + fprintf(stderr, "failed to load source blocks for stash %s\n", id.c_str()); + return 0; + } + + // In verify mode, save source range_set instead of stashing blocks. + if (!params.canwrite && usehash) { + stash_map[id] = src; + return 0; + } + + fprintf(stderr, "stashing %zu blocks to %s\n", blocks, id.c_str()); + return WriteStash(base, id, blocks, buffer, false, nullptr); +} + +static int FreeStash(const std::string& base, const std::string& id) { + if (base.empty() || id.empty()) { + return -1; + } + + std::string fn = GetStashFileName(base, id, ""); + DeleteFile(fn, nullptr); + + return 0; +} + +static void MoveRange(std::vector<uint8_t>& dest, const RangeSet& locs, + const std::vector<uint8_t>& source) { + // source contains packed data, which we want to move to the + // locations given in locs in the dest buffer. source and dest + // may be the same buffer. + + const uint8_t* from = source.data(); + uint8_t* to = dest.data(); + size_t start = locs.size; + for (int i = locs.count-1; i >= 0; --i) { + size_t blocks = locs.pos[i*2+1] - locs.pos[i*2]; + start -= blocks; + memmove(to + (locs.pos[i*2] * BLOCKSIZE), from + (start * BLOCKSIZE), + blocks * BLOCKSIZE); + } +} + +// Do a source/target load for move/bsdiff/imgdiff in version 2. +// We expect to parse the remainder of the parameter tokens as one of: +// +// <tgt_range> <src_block_count> <src_range> +// (loads data from source image only) +// +// <tgt_range> <src_block_count> - <[stash_id:stash_range] ...> +// (loads data from stashes only) +// +// <tgt_range> <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...> +// (loads data from both source image and stashes) +// +// On return, buffer is filled with the loaded source data (rearranged +// and combined with stashed data as necessary). buffer may be +// reallocated if needed to accommodate the source data. *tgt is the +// target RangeSet. Any stashes required are loaded using LoadStash. + +static int LoadSrcTgtVersion2(CommandParameters& params, RangeSet& tgt, size_t& src_blocks, + std::vector<uint8_t>& buffer, int fd, const std::string& stashbase, bool* overlap) { + + // At least it needs to provide three parameters: <tgt_range>, + // <src_block_count> and "-"/<src_range>. + if (params.cpos + 2 >= params.tokens.size()) { + fprintf(stderr, "invalid parameters\n"); + return -1; + } + + // <tgt_range> + parse_range(params.tokens[params.cpos++], tgt); + + // <src_block_count> + const std::string& token = params.tokens[params.cpos++]; + if (!android::base::ParseUint(token.c_str(), &src_blocks)) { + fprintf(stderr, "invalid src_block_count \"%s\"\n", token.c_str()); + return -1; + } + + allocate(src_blocks * BLOCKSIZE, buffer); + + // "-" or <src_range> [<src_loc>] + if (params.tokens[params.cpos] == "-") { + // no source ranges, only stashes + params.cpos++; + } else { + RangeSet src; + parse_range(params.tokens[params.cpos++], src); + int res = ReadBlocks(src, buffer, fd); + + if (overlap) { + *overlap = range_overlaps(src, tgt); + } + + if (res == -1) { + return -1; + } + + if (params.cpos >= params.tokens.size()) { + // no stashes, only source range + return 0; + } + + RangeSet locs; + parse_range(params.tokens[params.cpos++], locs); + MoveRange(buffer, locs, buffer); + } + + // <[stash_id:stash_range]> + while (params.cpos < params.tokens.size()) { + // Each word is a an index into the stash table, a colon, and + // then a rangeset describing where in the source block that + // stashed data should go. + std::vector<std::string> tokens = android::base::Split(params.tokens[params.cpos++], ":"); + if (tokens.size() != 2) { + fprintf(stderr, "invalid parameter\n"); + return -1; + } + + std::vector<uint8_t> stash; + int res = LoadStash(params, stashbase, tokens[0], false, nullptr, stash, true); + + if (res == -1) { + // These source blocks will fail verification if used later, but we + // will let the caller decide if this is a fatal failure + fprintf(stderr, "failed to load stash %s\n", tokens[0].c_str()); + continue; + } + + RangeSet locs; + parse_range(tokens[1], locs); + + MoveRange(buffer, locs, stash); + } + + return 0; +} + +// Do a source/target load for move/bsdiff/imgdiff in version 3. +// +// Parameters are the same as for LoadSrcTgtVersion2, except for 'onehash', which +// tells the function whether to expect separate source and targe block hashes, or +// if they are both the same and only one hash should be expected, and +// 'isunresumable', which receives a non-zero value if block verification fails in +// a way that the update cannot be resumed anymore. +// +// If the function is unable to load the necessary blocks or their contents don't +// match the hashes, the return value is -1 and the command should be aborted. +// +// If the return value is 1, the command has already been completed according to +// the contents of the target blocks, and should not be performed again. +// +// If the return value is 0, source blocks have expected content and the command +// can be performed. + +static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t& src_blocks, + bool onehash, bool& overlap) { + + if (params.cpos >= params.tokens.size()) { + fprintf(stderr, "missing source hash\n"); + return -1; + } + + std::string srchash = params.tokens[params.cpos++]; + std::string tgthash; + + if (onehash) { + tgthash = srchash; + } else { + if (params.cpos >= params.tokens.size()) { + fprintf(stderr, "missing target hash\n"); + return -1; + } + tgthash = params.tokens[params.cpos++]; + } + + if (LoadSrcTgtVersion2(params, tgt, src_blocks, params.buffer, params.fd, + params.stashbase, &overlap) == -1) { + return -1; + } + + std::vector<uint8_t> tgtbuffer(tgt.size * BLOCKSIZE); + + if (ReadBlocks(tgt, tgtbuffer, params.fd) == -1) { + return -1; + } + + if (VerifyBlocks(tgthash, tgtbuffer, tgt.size, false) == 0) { + // Target blocks already have expected content, command should be skipped + return 1; + } + + if (VerifyBlocks(srchash, params.buffer, src_blocks, true) == 0) { + // If source and target blocks overlap, stash the source blocks so we can + // resume from possible write errors. In verify mode, we can skip stashing + // because the source blocks won't be overwritten. + if (overlap && params.canwrite) { + fprintf(stderr, "stashing %zu overlapping blocks to %s\n", src_blocks, + srchash.c_str()); + + bool stash_exists = false; + if (WriteStash(params.stashbase, srchash, src_blocks, params.buffer, true, + &stash_exists) != 0) { + fprintf(stderr, "failed to stash overlapping source blocks\n"); + return -1; + } + + // Can be deleted when the write has completed + if (!stash_exists) { + params.freestash = srchash; + } + } + + // Source blocks have expected content, command can proceed + return 0; + } + + if (overlap && LoadStash(params, params.stashbase, srchash, true, nullptr, params.buffer, + true) == 0) { + // Overlapping source blocks were previously stashed, command can proceed. + // We are recovering from an interrupted command, so we don't know if the + // stash can safely be deleted after this command. + return 0; + } + + // Valid source data not available, update cannot be resumed + fprintf(stderr, "partition has unexpected contents\n"); + params.isunresumable = true; + + return -1; +} + +static int PerformCommandMove(CommandParameters& params) { + size_t blocks = 0; + bool overlap = false; + int status = 0; + RangeSet tgt; + + if (params.version == 1) { + status = LoadSrcTgtVersion1(params, tgt, blocks, params.buffer, params.fd); + } else if (params.version == 2) { + status = LoadSrcTgtVersion2(params, tgt, blocks, params.buffer, params.fd, + params.stashbase, nullptr); + } else if (params.version >= 3) { + status = LoadSrcTgtVersion3(params, tgt, blocks, true, overlap); + } + + if (status == -1) { + fprintf(stderr, "failed to read blocks for move\n"); + return -1; + } + + if (status == 0) { + params.foundwrites = true; + } else if (params.foundwrites) { + fprintf(stderr, "warning: commands executed out of order [%s]\n", params.cmdname); + } + + if (params.canwrite) { + if (status == 0) { + fprintf(stderr, " moving %zu blocks\n", blocks); + + if (WriteBlocks(tgt, params.buffer, params.fd) == -1) { + return -1; + } + } else { + fprintf(stderr, "skipping %zu already moved blocks\n", blocks); + } + + } + + if (!params.freestash.empty()) { + FreeStash(params.stashbase, params.freestash); + params.freestash.clear(); + } + + params.written += tgt.size; + + return 0; +} + +static int PerformCommandStash(CommandParameters& params) { + return SaveStash(params, params.stashbase, params.buffer, params.fd, + (params.version >= 3)); +} + +static int PerformCommandFree(CommandParameters& params) { + // <stash_id> + if (params.cpos >= params.tokens.size()) { + fprintf(stderr, "missing stash id in free command\n"); + return -1; + } + + const std::string& id = params.tokens[params.cpos++]; + + if (!params.canwrite && stash_map.find(id) != stash_map.end()) { + stash_map.erase(id); + return 0; + } + + if (params.createdstash || params.canwrite) { + return FreeStash(params.stashbase, id); + } + + return 0; +} + +static int PerformCommandZero(CommandParameters& params) { + + if (params.cpos >= params.tokens.size()) { + fprintf(stderr, "missing target blocks for zero\n"); + return -1; + } + + RangeSet tgt; + parse_range(params.tokens[params.cpos++], tgt); + + fprintf(stderr, " zeroing %zu blocks\n", tgt.size); + + allocate(BLOCKSIZE, params.buffer); + memset(params.buffer.data(), 0, BLOCKSIZE); + + if (params.canwrite) { + for (size_t i = 0; i < tgt.count; ++i) { + if (!check_lseek(params.fd, (off64_t) tgt.pos[i * 2] * BLOCKSIZE, SEEK_SET)) { + return -1; + } + + for (size_t j = tgt.pos[i * 2]; j < tgt.pos[i * 2 + 1]; ++j) { + if (write_all(params.fd, params.buffer, BLOCKSIZE) == -1) { + return -1; + } + } + } + } + + if (params.cmdname[0] == 'z') { + // Update only for the zero command, as the erase command will call + // this if DEBUG_ERASE is defined. + params.written += tgt.size; + } + + return 0; +} + +static int PerformCommandNew(CommandParameters& params) { + + if (params.cpos >= params.tokens.size()) { + fprintf(stderr, "missing target blocks for new\n"); + return -1; + } + + RangeSet tgt; + parse_range(params.tokens[params.cpos++], tgt); + + if (params.canwrite) { + fprintf(stderr, " writing %zu blocks of new data\n", tgt.size); + + RangeSinkState rss(tgt); + rss.fd = params.fd; + rss.p_block = 0; + rss.p_remain = (tgt.pos[1] - tgt.pos[0]) * BLOCKSIZE; + + if (!check_lseek(params.fd, (off64_t) tgt.pos[0] * BLOCKSIZE, SEEK_SET)) { + return -1; + } + + pthread_mutex_lock(¶ms.nti.mu); + params.nti.rss = &rss; + pthread_cond_broadcast(¶ms.nti.cv); + + while (params.nti.rss) { + pthread_cond_wait(¶ms.nti.cv, ¶ms.nti.mu); + } + + pthread_mutex_unlock(¶ms.nti.mu); + } + + params.written += tgt.size; + + return 0; +} + +static int PerformCommandDiff(CommandParameters& params) { + + // <offset> <length> + if (params.cpos + 1 >= params.tokens.size()) { + fprintf(stderr, "missing patch offset or length for %s\n", params.cmdname); + return -1; + } + + size_t offset; + if (!android::base::ParseUint(params.tokens[params.cpos++].c_str(), &offset)) { + fprintf(stderr, "invalid patch offset\n"); + return -1; + } + + size_t len; + if (!android::base::ParseUint(params.tokens[params.cpos++].c_str(), &len)) { + fprintf(stderr, "invalid patch offset\n"); + return -1; + } + + RangeSet tgt; + size_t blocks = 0; + bool overlap = false; + int status = 0; + if (params.version == 1) { + status = LoadSrcTgtVersion1(params, tgt, blocks, params.buffer, params.fd); + } else if (params.version == 2) { + status = LoadSrcTgtVersion2(params, tgt, blocks, params.buffer, params.fd, + params.stashbase, nullptr); + } else if (params.version >= 3) { + status = LoadSrcTgtVersion3(params, tgt, blocks, false, overlap); + } + + if (status == -1) { + fprintf(stderr, "failed to read blocks for diff\n"); + return -1; + } + + if (status == 0) { + params.foundwrites = true; + } else if (params.foundwrites) { + fprintf(stderr, "warning: commands executed out of order [%s]\n", params.cmdname); + } + + if (params.canwrite) { + if (status == 0) { + fprintf(stderr, "patching %zu blocks to %zu\n", blocks, tgt.size); + + Value patch_value; + patch_value.type = VAL_BLOB; + patch_value.size = len; + patch_value.data = (char*) (params.patch_start + offset); + + RangeSinkState rss(tgt); + rss.fd = params.fd; + rss.p_block = 0; + rss.p_remain = (tgt.pos[1] - tgt.pos[0]) * BLOCKSIZE; + + if (!check_lseek(params.fd, (off64_t) tgt.pos[0] * BLOCKSIZE, SEEK_SET)) { + return -1; + } + + if (params.cmdname[0] == 'i') { // imgdiff + ApplyImagePatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value, + &RangeSinkWrite, &rss, nullptr, nullptr); + } else { + ApplyBSDiffPatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value, 0, + &RangeSinkWrite, &rss, nullptr); + } + + // We expect the output of the patcher to fill the tgt ranges exactly. + if (rss.p_block != tgt.count || rss.p_remain != 0) { + fprintf(stderr, "range sink underrun?\n"); + } + } else { + fprintf(stderr, "skipping %zu blocks already patched to %zu [%s]\n", + blocks, tgt.size, params.cmdline); + } + } + + if (!params.freestash.empty()) { + FreeStash(params.stashbase, params.freestash); + params.freestash.clear(); + } + + params.written += tgt.size; + + return 0; +} + +static int PerformCommandErase(CommandParameters& params) { + if (DEBUG_ERASE) { + return PerformCommandZero(params); + } + + struct stat sb; + if (fstat(params.fd, &sb) == -1) { + fprintf(stderr, "failed to fstat device to erase: %s\n", strerror(errno)); + return -1; + } + + if (!S_ISBLK(sb.st_mode)) { + fprintf(stderr, "not a block device; skipping erase\n"); + return -1; + } + + if (params.cpos >= params.tokens.size()) { + fprintf(stderr, "missing target blocks for erase\n"); + return -1; + } + + RangeSet tgt; + parse_range(params.tokens[params.cpos++], tgt); + + if (params.canwrite) { + fprintf(stderr, " erasing %zu blocks\n", tgt.size); + + for (size_t i = 0; i < tgt.count; ++i) { + uint64_t blocks[2]; + // offset in bytes + blocks[0] = tgt.pos[i * 2] * (uint64_t) BLOCKSIZE; + // length in bytes + blocks[1] = (tgt.pos[i * 2 + 1] - tgt.pos[i * 2]) * (uint64_t) BLOCKSIZE; + + if (ioctl(params.fd, BLKDISCARD, &blocks) == -1) { + fprintf(stderr, "BLKDISCARD ioctl failed: %s\n", strerror(errno)); + return -1; + } + } + } + + return 0; +} + +// Definitions for transfer list command functions +typedef int (*CommandFunction)(CommandParameters&); + +struct Command { + const char* name; + CommandFunction f; +}; + +// CompareCommands and CompareCommandNames are for the hash table + +static int CompareCommands(const void* c1, const void* c2) { + return strcmp(((const Command*) c1)->name, ((const Command*) c2)->name); +} + +static int CompareCommandNames(const void* c1, const void* c2) { + return strcmp(((const Command*) c1)->name, (const char*) c2); +} + +// HashString is used to hash command names for the hash table + +static unsigned int HashString(const char *s) { + unsigned int hash = 0; + if (s) { + while (*s) { + hash = hash * 33 + *s++; + } + } + return hash; +} + +// args: +// - block device (or file) to modify in-place +// - transfer list (blob) +// - new data stream (filename within package.zip) +// - patch stream (filename within package.zip, must be uncompressed) + +static Value* PerformBlockImageUpdate(const char* name, State* state, int /* argc */, Expr* argv[], + const Command* commands, size_t cmdcount, bool dryrun) { + CommandParameters params = {}; + params.canwrite = !dryrun; + + fprintf(stderr, "performing %s\n", dryrun ? "verification" : "update"); + + Value* blockdev_filename = nullptr; + Value* transfer_list_value = nullptr; + Value* new_data_fn = nullptr; + Value* patch_data_fn = nullptr; + if (ReadValueArgs(state, argv, 4, &blockdev_filename, &transfer_list_value, + &new_data_fn, &patch_data_fn) < 0) { + return StringValue(strdup("")); + } + std::unique_ptr<Value, decltype(&FreeValue)> blockdev_filename_holder(blockdev_filename, + FreeValue); + std::unique_ptr<Value, decltype(&FreeValue)> transfer_list_value_holder(transfer_list_value, + FreeValue); + std::unique_ptr<Value, decltype(&FreeValue)> new_data_fn_holder(new_data_fn, FreeValue); + std::unique_ptr<Value, decltype(&FreeValue)> patch_data_fn_holder(patch_data_fn, FreeValue); + + if (blockdev_filename->type != VAL_STRING) { + ErrorAbort(state, "blockdev_filename argument to %s must be string", name); + return StringValue(strdup("")); + } + if (transfer_list_value->type != VAL_BLOB) { + ErrorAbort(state, "transfer_list argument to %s must be blob", name); + return StringValue(strdup("")); + } + if (new_data_fn->type != VAL_STRING) { + ErrorAbort(state, "new_data_fn argument to %s must be string", name); + return StringValue(strdup("")); + } + if (patch_data_fn->type != VAL_STRING) { + ErrorAbort(state, "patch_data_fn argument to %s must be string", name); + return StringValue(strdup("")); + } + + UpdaterInfo* ui = reinterpret_cast<UpdaterInfo*>(state->cookie); + + if (ui == nullptr) { + return StringValue(strdup("")); + } + + FILE* cmd_pipe = ui->cmd_pipe; + ZipArchive* za = ui->package_zip; + + if (cmd_pipe == nullptr || za == nullptr) { + return StringValue(strdup("")); + } + + const ZipEntry* patch_entry = mzFindZipEntry(za, patch_data_fn->data); + if (patch_entry == nullptr) { + fprintf(stderr, "%s(): no file \"%s\" in package", name, patch_data_fn->data); + return StringValue(strdup("")); + } + + params.patch_start = ui->package_zip_addr + mzGetZipEntryOffset(patch_entry); + const ZipEntry* new_entry = mzFindZipEntry(za, new_data_fn->data); + if (new_entry == nullptr) { + fprintf(stderr, "%s(): no file \"%s\" in package", name, new_data_fn->data); + return StringValue(strdup("")); + } + + params.fd.reset(TEMP_FAILURE_RETRY(ota_open(blockdev_filename->data, O_RDWR))); + if (params.fd == -1) { + fprintf(stderr, "open \"%s\" failed: %s\n", blockdev_filename->data, strerror(errno)); + return StringValue(strdup("")); + } + + if (params.canwrite) { + params.nti.za = za; + params.nti.entry = new_entry; + + pthread_mutex_init(¶ms.nti.mu, nullptr); + pthread_cond_init(¶ms.nti.cv, nullptr); + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + int error = pthread_create(¶ms.thread, &attr, unzip_new_data, ¶ms.nti); + if (error != 0) { + fprintf(stderr, "pthread_create failed: %s\n", strerror(error)); + return StringValue(strdup("")); + } + } + + // Copy all the lines in transfer_list_value into std::string for + // processing. + const std::string transfer_list(transfer_list_value->data, transfer_list_value->size); + std::vector<std::string> lines = android::base::Split(transfer_list, "\n"); + if (lines.size() < 2) { + ErrorAbort(state, "too few lines in the transfer list [%zd]\n", lines.size()); + return StringValue(strdup("")); + } + + // First line in transfer list is the version number + if (!android::base::ParseInt(lines[0].c_str(), ¶ms.version, 1, 4)) { + fprintf(stderr, "unexpected transfer list version [%s]\n", lines[0].c_str()); + return StringValue(strdup("")); + } + + fprintf(stderr, "blockimg version is %d\n", params.version); + + // Second line in transfer list is the total number of blocks we expect to write + int total_blocks; + if (!android::base::ParseInt(lines[1].c_str(), &total_blocks, 0)) { + ErrorAbort(state, "unexpected block count [%s]\n", lines[1].c_str()); + return StringValue(strdup("")); + } + + if (total_blocks == 0) { + return StringValue(strdup("t")); + } + + size_t start = 2; + if (params.version >= 2) { + if (lines.size() < 4) { + ErrorAbort(state, "too few lines in the transfer list [%zu]\n", lines.size()); + return StringValue(strdup("")); + } + + // Third line is how many stash entries are needed simultaneously + fprintf(stderr, "maximum stash entries %s\n", lines[2].c_str()); + + // Fourth line is the maximum number of blocks that will be stashed simultaneously + int stash_max_blocks; + if (!android::base::ParseInt(lines[3].c_str(), &stash_max_blocks, 0)) { + ErrorAbort(state, "unexpected maximum stash blocks [%s]\n", lines[3].c_str()); + return StringValue(strdup("")); + } + + int res = CreateStash(state, stash_max_blocks, blockdev_filename->data, params.stashbase); + if (res == -1) { + return StringValue(strdup("")); + } + + params.createdstash = res; + + start += 2; + } + + // Build a hash table of the available commands + HashTable* cmdht = mzHashTableCreate(cmdcount, nullptr); + std::unique_ptr<HashTable, decltype(&mzHashTableFree)> cmdht_holder(cmdht, mzHashTableFree); + + for (size_t i = 0; i < cmdcount; ++i) { + unsigned int cmdhash = HashString(commands[i].name); + mzHashTableLookup(cmdht, cmdhash, (void*) &commands[i], CompareCommands, true); + } + + int rc = -1; + + // Subsequent lines are all individual transfer commands + for (auto it = lines.cbegin() + start; it != lines.cend(); it++) { + const std::string& line_str(*it); + if (line_str.empty()) { + continue; + } + + params.tokens = android::base::Split(line_str, " "); + params.cpos = 0; + params.cmdname = params.tokens[params.cpos++].c_str(); + params.cmdline = line_str.c_str(); + + unsigned int cmdhash = HashString(params.cmdname); + const Command* cmd = reinterpret_cast<const Command*>(mzHashTableLookup(cmdht, cmdhash, + const_cast<char*>(params.cmdname), CompareCommandNames, + false)); + + if (cmd == nullptr) { + fprintf(stderr, "unexpected command [%s]\n", params.cmdname); + goto pbiudone; + } + + if (cmd->f != nullptr && cmd->f(params) == -1) { + fprintf(stderr, "failed to execute command [%s]\n", line_str.c_str()); + goto pbiudone; + } + + if (params.canwrite) { + if (ota_fsync(params.fd) == -1) { + fprintf(stderr, "fsync failed: %s\n", strerror(errno)); + goto pbiudone; + } + fprintf(cmd_pipe, "set_progress %.4f\n", (double) params.written / total_blocks); + fflush(cmd_pipe); + } + } + + if (params.canwrite) { + pthread_join(params.thread, nullptr); + + fprintf(stderr, "wrote %zu blocks; expected %d\n", params.written, total_blocks); + fprintf(stderr, "max alloc needed was %zu\n", params.buffer.size()); + + // Delete stash only after successfully completing the update, as it + // may contain blocks needed to complete the update later. + DeleteStash(params.stashbase); + } else { + fprintf(stderr, "verified partition contents; update may be resumed\n"); + } + + rc = 0; + +pbiudone: + if (ota_fsync(params.fd) == -1) { + fprintf(stderr, "fsync failed: %s\n", strerror(errno)); + } + // params.fd will be automatically closed because it's a unique_fd. + + // 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)) { + DeleteStash(params.stashbase); + } + + return StringValue(rc == 0 ? strdup("t") : strdup("")); +} + +// The transfer list is a text file containing commands to +// transfer data from one place to another on the target +// partition. We parse it and execute the commands in order: +// +// zero [rangeset] +// - fill the indicated blocks with zeros +// +// new [rangeset] +// - fill the blocks with data read from the new_data file +// +// erase [rangeset] +// - mark the given blocks as empty +// +// move <...> +// bsdiff <patchstart> <patchlen> <...> +// imgdiff <patchstart> <patchlen> <...> +// - read the source blocks, apply a patch (or not in the +// case of move), write result to target blocks. bsdiff or +// imgdiff specifies the type of patch; move means no patch +// at all. +// +// The format of <...> differs between versions 1 and 2; +// see the LoadSrcTgtVersion{1,2}() functions for a +// description of what's expected. +// +// stash <stash_id> <src_range> +// - (version 2+ only) load the given source range and stash +// the data in the given slot of the stash table. +// +// free <stash_id> +// - (version 3+ only) free the given stash data. +// +// The creator of the transfer list will guarantee that no block +// is read (ie, used as the source for a patch or move) after it +// has been written. +// +// In version 2, the creator will guarantee that a given stash is +// loaded (with a stash command) before it's used in a +// move/bsdiff/imgdiff command. +// +// Within one command the source and target ranges may overlap so +// in general we need to read the entire source into memory before +// writing anything to the target blocks. +// +// All the patch data is concatenated into one patch_data file in +// the update package. It must be stored uncompressed because we +// memory-map it in directly from the archive. (Since patches are +// already compressed, we lose very little by not compressing +// their concatenation.) +// +// In version 3, commands that read data from the partition (i.e. +// move/bsdiff/imgdiff/stash) have one or more additional hashes +// before the range parameters, which are used to check if the +// command has already been completed and verify the integrity of +// the source data. + +Value* BlockImageVerifyFn(const char* name, State* state, int argc, Expr* argv[]) { + // Commands which are not tested are set to nullptr to skip them completely + const Command commands[] = { + { "bsdiff", PerformCommandDiff }, + { "erase", nullptr }, + { "free", PerformCommandFree }, + { "imgdiff", PerformCommandDiff }, + { "move", PerformCommandMove }, + { "new", nullptr }, + { "stash", PerformCommandStash }, + { "zero", nullptr } + }; + + // Perform a dry run without writing to test if an update can proceed + return PerformBlockImageUpdate(name, state, argc, argv, commands, + sizeof(commands) / sizeof(commands[0]), true); +} + +Value* BlockImageUpdateFn(const char* name, State* state, int argc, Expr* argv[]) { + const Command commands[] = { + { "bsdiff", PerformCommandDiff }, + { "erase", PerformCommandErase }, + { "free", PerformCommandFree }, + { "imgdiff", PerformCommandDiff }, + { "move", PerformCommandMove }, + { "new", PerformCommandNew }, + { "stash", PerformCommandStash }, + { "zero", PerformCommandZero } + }; + + return PerformBlockImageUpdate(name, state, argc, argv, commands, + sizeof(commands) / sizeof(commands[0]), false); +} + +Value* RangeSha1Fn(const char* name, State* state, int /* argc */, Expr* argv[]) { + Value* blockdev_filename; + Value* ranges; + + if (ReadValueArgs(state, argv, 2, &blockdev_filename, &ranges) < 0) { + return StringValue(strdup("")); + } + std::unique_ptr<Value, decltype(&FreeValue)> ranges_holder(ranges, FreeValue); + std::unique_ptr<Value, decltype(&FreeValue)> blockdev_filename_holder(blockdev_filename, + FreeValue); + + if (blockdev_filename->type != VAL_STRING) { + ErrorAbort(state, "blockdev_filename argument to %s must be string", name); + return StringValue(strdup("")); + } + if (ranges->type != VAL_STRING) { + ErrorAbort(state, "ranges argument to %s must be string", name); + return StringValue(strdup("")); + } + + android::base::unique_fd fd(ota_open(blockdev_filename->data, O_RDWR)); + if (fd == -1) { + ErrorAbort(state, "open \"%s\" failed: %s", blockdev_filename->data, strerror(errno)); + return StringValue(strdup("")); + } + + RangeSet rs; + parse_range(ranges->data, rs); + + SHA_CTX ctx; + SHA1_Init(&ctx); + + std::vector<uint8_t> buffer(BLOCKSIZE); + for (size_t i = 0; i < rs.count; ++i) { + if (!check_lseek(fd, (off64_t)rs.pos[i*2] * BLOCKSIZE, SEEK_SET)) { + ErrorAbort(state, "failed to seek %s: %s", blockdev_filename->data, strerror(errno)); + return StringValue(strdup("")); + } + + for (size_t j = rs.pos[i*2]; j < rs.pos[i*2+1]; ++j) { + if (read_all(fd, buffer, BLOCKSIZE) == -1) { + ErrorAbort(state, "failed to read %s: %s", blockdev_filename->data, + strerror(errno)); + return StringValue(strdup("")); + } + + SHA1_Update(&ctx, buffer.data(), BLOCKSIZE); + } + } + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1_Final(digest, &ctx); + + return StringValue(strdup(print_sha1(digest).c_str())); +} + +// This function checks if a device has been remounted R/W prior to an incremental +// OTA update. This is an common cause of update abortion. The function reads the +// 1st block of each partition and check for mounting time/count. It return string "t" +// if executes successfully and an empty string otherwise. + +Value* CheckFirstBlockFn(const char* name, State* state, int argc, Expr* argv[]) { + Value* arg_filename; + + if (ReadValueArgs(state, argv, 1, &arg_filename) < 0) { + return nullptr; + } + std::unique_ptr<Value, decltype(&FreeValue)> filename(arg_filename, FreeValue); + + if (filename->type != VAL_STRING) { + ErrorAbort(state, "filename argument to %s must be string", name); + return StringValue(strdup("")); + } + + android::base::unique_fd fd(ota_open(arg_filename->data, O_RDONLY)); + if (fd == -1) { + ErrorAbort(state, "open \"%s\" failed: %s", arg_filename->data, strerror(errno)); + return StringValue(strdup("")); + } + + RangeSet blk0 {1 /*count*/, 1/*size*/, std::vector<size_t> {0, 1}/*position*/}; + std::vector<uint8_t> block0_buffer(BLOCKSIZE); + + if (ReadBlocks(blk0, block0_buffer, fd) == -1) { + ErrorAbort(state, "failed to read %s: %s", arg_filename->data, strerror(errno)); + return StringValue(strdup("")); + } + + // https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout + // Super block starts from block 0, offset 0x400 + // 0x2C: len32 Mount time + // 0x30: len32 Write time + // 0x34: len16 Number of mounts since the last fsck + // 0x38: len16 Magic signature 0xEF53 + + time_t mount_time = *reinterpret_cast<uint32_t*>(&block0_buffer[0x400+0x2C]); + uint16_t mount_count = *reinterpret_cast<uint16_t*>(&block0_buffer[0x400+0x34]); + + if (mount_count > 0) { + uiPrintf(state, "Device was remounted R/W %d times\n", mount_count); + uiPrintf(state, "Last remount happened on %s", ctime(&mount_time)); + } + + return StringValue(strdup("t")); +} + + +Value* BlockImageRecoverFn(const char* name, State* state, int argc, Expr* argv[]) { + Value* arg_filename; + Value* arg_ranges; + + if (ReadValueArgs(state, argv, 2, &arg_filename, &arg_ranges) < 0) { + return NULL; + } + + std::unique_ptr<Value, decltype(&FreeValue)> filename(arg_filename, FreeValue); + std::unique_ptr<Value, decltype(&FreeValue)> ranges(arg_ranges, FreeValue); + + if (filename->type != VAL_STRING) { + ErrorAbort(state, "filename argument to %s must be string", name); + return StringValue(strdup("")); + } + if (ranges->type != VAL_STRING) { + ErrorAbort(state, "ranges argument to %s must be string", name); + return StringValue(strdup("")); + } + + // Output notice to log when recover is attempted + fprintf(stderr, "%s image corrupted, attempting to recover...\n", filename->data); + + // When opened with O_RDWR, libfec rewrites corrupted blocks when they are read + fec::io fh(filename->data, O_RDWR); + + if (!fh) { + ErrorAbort(state, "fec_open \"%s\" failed: %s", filename->data, strerror(errno)); + return StringValue(strdup("")); + } + + if (!fh.has_ecc() || !fh.has_verity()) { + ErrorAbort(state, "unable to use metadata to correct errors"); + return StringValue(strdup("")); + } + + fec_status status; + + if (!fh.get_status(status)) { + ErrorAbort(state, "failed to read FEC status"); + return StringValue(strdup("")); + } + + RangeSet rs; + parse_range(ranges->data, rs); + + uint8_t buffer[BLOCKSIZE]; + + for (size_t i = 0; i < rs.count; ++i) { + for (size_t j = rs.pos[i * 2]; j < rs.pos[i * 2 + 1]; ++j) { + // Stay within the data area, libfec validates and corrects metadata + if (status.data_size <= (uint64_t)j * BLOCKSIZE) { + continue; + } + + if (fh.pread(buffer, BLOCKSIZE, (off64_t)j * BLOCKSIZE) != BLOCKSIZE) { + ErrorAbort(state, "failed to recover %s (block %zu): %s", filename->data, + j, strerror(errno)); + return StringValue(strdup("")); + } + + // If we want to be able to recover from a situation where rewriting a corrected + // block doesn't guarantee the same data will be returned when re-read later, we + // can save a copy of corrected blocks to /cache. Note: + // + // 1. Maximum space required from /cache is the same as the maximum number of + // corrupted blocks we can correct. For RS(255, 253) and a 2 GiB partition, + // this would be ~16 MiB, for example. + // + // 2. To find out if this block was corrupted, call fec_get_status after each + // read and check if the errors field value has increased. + } + } + fprintf(stderr, "...%s image recovered successfully.\n", filename->data); + return StringValue(strdup("t")); +} + +void RegisterBlockImageFunctions() { + RegisterFunction("block_image_verify", BlockImageVerifyFn); + RegisterFunction("block_image_update", BlockImageUpdateFn); + RegisterFunction("block_image_recover", BlockImageRecoverFn); + RegisterFunction("check_first_block", CheckFirstBlockFn); + RegisterFunction("range_sha1", RangeSha1Fn); +}
diff --git a/recovery/updater/blockimg.h b/recovery/updater/blockimg.h new file mode 100644 index 0000000..2f4ad3c --- /dev/null +++ b/recovery/updater/blockimg.h
@@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _UPDATER_BLOCKIMG_H_ +#define _UPDATER_BLOCKIMG_H_ + +void RegisterBlockImageFunctions(); + +#endif
diff --git a/recovery/updater/install.cpp b/recovery/updater/install.cpp new file mode 100644 index 0000000..3e7e928 --- /dev/null +++ b/recovery/updater/install.cpp
@@ -0,0 +1,1487 @@ +/* + * 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. + */ + +#include <ctype.h> +#include <errno.h> +#include <stdarg.h> +#include <stdio.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 <fcntl.h> +#include <time.h> +#include <ftw.h> +#include <sys/capability.h> +#include <sys/xattr.h> +#include <linux/xattr.h> +#include <inttypes.h> + +#include <memory> +#include <vector> + +#include <android-base/parseint.h> +#include <android-base/strings.h> +#include <android-base/stringprintf.h> +#include <selinux/label.h> +#include <selinux/selinux.h> + +#include "bootloader.h" +#include "applypatch/applypatch.h" +#include "cutils/android_reboot.h" +#include "cutils/misc.h" +#include "cutils/properties.h" +#include "edify/expr.h" +#include "openssl/sha.h" +#include "minzip/DirUtil.h" +#include "mounts.h" +#include "ota_io.h" +#include "updater.h" +#include "install.h" +#include "tune2fs.h" + +#include "make_ext4fs.h" +#include "wipe.h" + +// Send over the buffer to recovery though the command pipe. +static void uiPrint(State* state, const std::string& buffer) { + UpdaterInfo* ui = reinterpret_cast<UpdaterInfo*>(state->cookie); + + // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "". + // So skip sending empty strings to UI. + std::vector<std::string> lines = android::base::Split(buffer, "\n"); + for (auto& line: lines) { + if (!line.empty()) { + fprintf(ui->cmd_pipe, "ui_print %s\n", line.c_str()); + fprintf(ui->cmd_pipe, "ui_print\n"); + } + } + + // On the updater side, we need to dump the contents to stderr (which has + // been redirected to the log file). Because the recovery will only print + // the contents to screen when processing pipe command ui_print. + fprintf(stderr, "%s", buffer.c_str()); +} + +void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...) { + std::string error_msg; + + va_list ap; + va_start(ap, format); + android::base::StringAppendV(&error_msg, format, ap); + va_end(ap); + + uiPrint(state, error_msg); +} + +// Take a sha-1 digest and return it as a newly-allocated hex string. +char* PrintSha1(const uint8_t* digest) { + char* buffer = reinterpret_cast<char*>(malloc(SHA_DIGEST_LENGTH*2 + 1)); + const char* alphabet = "0123456789abcdef"; + size_t i; + for (i = 0; i < SHA_DIGEST_LENGTH; ++i) { + buffer[i*2] = alphabet[(digest[i] >> 4) & 0xf]; + buffer[i*2+1] = alphabet[digest[i] & 0xf]; + } + buffer[i*2] = '\0'; + return buffer; +} + +// mount(fs_type, partition_type, location, mount_point) +// +// fs_type="ext4" partition_type="EMMC" location=device +Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + if (argc != 4 && argc != 5) { + return ErrorAbort(state, "%s() expects 4-5 args, got %d", name, argc); + } + char* fs_type; + char* partition_type; + char* location; + char* mount_point; + char* mount_options; + bool has_mount_options; + if (argc == 5) { + has_mount_options = true; + if (ReadArgs(state, argv, 5, &fs_type, &partition_type, + &location, &mount_point, &mount_options) < 0) { + return NULL; + } + } else { + has_mount_options = false; + if (ReadArgs(state, argv, 4, &fs_type, &partition_type, + &location, &mount_point) < 0) { + return NULL; + } + } + + if (strlen(fs_type) == 0) { + ErrorAbort(state, "fs_type argument to %s() can't be empty", name); + goto done; + } + if (strlen(partition_type) == 0) { + ErrorAbort(state, "partition_type argument to %s() can't be empty", + name); + goto done; + } + if (strlen(location) == 0) { + ErrorAbort(state, "location argument to %s() can't be empty", name); + goto done; + } + if (strlen(mount_point) == 0) { + ErrorAbort(state, "mount_point argument to %s() can't be empty", name); + goto done; + } + + { + char *secontext = NULL; + + if (sehandle) { + selabel_lookup(sehandle, &secontext, mount_point, 0755); + setfscreatecon(secontext); + } + + mkdir(mount_point, 0755); + + if (secontext) { + freecon(secontext); + setfscreatecon(NULL); + } + } + + if (mount(location, mount_point, fs_type, + MS_NOATIME | MS_NODEV | MS_NODIRATIME, + has_mount_options ? mount_options : "") < 0) { + uiPrintf(state, "%s: failed to mount %s at %s: %s\n", + name, location, mount_point, strerror(errno)); + result = strdup(""); + } else { + result = mount_point; + } + +done: + free(fs_type); + free(partition_type); + free(location); + if (result != mount_point) free(mount_point); + if (has_mount_options) free(mount_options); + return StringValue(result); +} + + +// is_mounted(mount_point) +Value* IsMountedFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); + } + char* mount_point; + if (ReadArgs(state, argv, 1, &mount_point) < 0) { + return NULL; + } + if (strlen(mount_point) == 0) { + ErrorAbort(state, "mount_point argument to unmount() can't be empty"); + goto done; + } + + scan_mounted_volumes(); + { + MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point); + if (vol == NULL) { + result = strdup(""); + } else { + result = mount_point; + } + } + +done: + if (result != mount_point) free(mount_point); + return StringValue(result); +} + + +Value* UnmountFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); + } + char* mount_point; + if (ReadArgs(state, argv, 1, &mount_point) < 0) { + return NULL; + } + if (strlen(mount_point) == 0) { + ErrorAbort(state, "mount_point argument to unmount() can't be empty"); + goto done; + } + + scan_mounted_volumes(); + { + MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point); + if (vol == NULL) { + uiPrintf(state, "unmount of %s failed; no such volume\n", mount_point); + result = strdup(""); + } else { + int ret = unmount_mounted_volume(vol); + if (ret != 0) { + uiPrintf(state, "unmount of %s failed (%d): %s\n", + mount_point, ret, strerror(errno)); + } + result = mount_point; + } + } + +done: + if (result != mount_point) free(mount_point); + return StringValue(result); +} + +static int exec_cmd(const char* path, char* const argv[]) { + int status; + pid_t child; + if ((child = vfork()) == 0) { + execv(path, argv); + _exit(-1); + } + waitpid(child, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + printf("%s failed with status %d\n", path, WEXITSTATUS(status)); + } + return WEXITSTATUS(status); +} + + +// format(fs_type, partition_type, location, fs_size, mount_point) +// +// fs_type="ext4" partition_type="EMMC" location=device fs_size=<bytes> mount_point=<location> +// fs_type="f2fs" partition_type="EMMC" location=device fs_size=<bytes> mount_point=<location> +// if fs_size == 0, then make fs uses the entire partition. +// if fs_size > 0, that is the size to use +// if fs_size < 0, then reserve that many bytes at the end of the partition (not for "f2fs") +Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + if (argc != 5) { + return ErrorAbort(state, "%s() expects 5 args, got %d", name, argc); + } + char* fs_type; + char* partition_type; + char* location; + char* fs_size; + char* mount_point; + + if (ReadArgs(state, argv, 5, &fs_type, &partition_type, &location, &fs_size, &mount_point) < 0) { + return NULL; + } + + if (strlen(fs_type) == 0) { + ErrorAbort(state, "fs_type argument to %s() can't be empty", name); + goto done; + } + if (strlen(partition_type) == 0) { + ErrorAbort(state, "partition_type argument to %s() can't be empty", + name); + goto done; + } + if (strlen(location) == 0) { + ErrorAbort(state, "location argument to %s() can't be empty", name); + goto done; + } + + if (strlen(mount_point) == 0) { + ErrorAbort(state, "mount_point argument to %s() can't be empty", name); + goto done; + } + + if (strcmp(fs_type, "ext4") == 0) { + int status = make_ext4fs(location, atoll(fs_size), mount_point, sehandle); + if (status != 0) { + printf("%s: make_ext4fs failed (%d) on %s", + name, status, location); + result = strdup(""); + goto done; + } + result = location; + } else if (strcmp(fs_type, "f2fs") == 0) { + char *num_sectors; + if (asprintf(&num_sectors, "%lld", atoll(fs_size) / 512) <= 0) { + printf("format_volume: failed to create %s command for %s\n", fs_type, location); + result = strdup(""); + goto done; + } + const char *f2fs_path = "/sbin/mkfs.f2fs"; + const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", location, num_sectors, NULL}; + int status = exec_cmd(f2fs_path, (char* const*)f2fs_argv); + free(num_sectors); + if (status != 0) { + printf("%s: mkfs.f2fs failed (%d) on %s", + name, status, location); + result = strdup(""); + goto done; + } + result = location; + } else { + printf("%s: unsupported fs_type \"%s\" partition_type \"%s\"", + name, fs_type, partition_type); + } + +done: + free(fs_type); + free(partition_type); + if (result != location) free(location); + return StringValue(result); +} + +Value* RenameFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + + char* src_name; + char* dst_name; + + if (ReadArgs(state, argv, 2, &src_name, &dst_name) < 0) { + return NULL; + } + if (strlen(src_name) == 0) { + ErrorAbort(state, "src_name argument to %s() can't be empty", name); + goto done; + } + if (strlen(dst_name) == 0) { + ErrorAbort(state, "dst_name argument to %s() can't be empty", name); + goto done; + } + if (make_parents(dst_name) != 0) { + ErrorAbort(state, "Creating parent of %s failed, error %s", + dst_name, strerror(errno)); + } else if (access(dst_name, F_OK) == 0 && access(src_name, F_OK) != 0) { + // File was already moved + result = dst_name; + } else if (rename(src_name, dst_name) != 0) { + ErrorAbort(state, "Rename of %s to %s failed, error %s", + src_name, dst_name, strerror(errno)); + } else { + result = dst_name; + } + +done: + free(src_name); + if (result != dst_name) free(dst_name); + return StringValue(result); +} + +Value* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) { + char** paths = reinterpret_cast<char**>(malloc(argc * sizeof(char*))); + for (int i = 0; i < argc; ++i) { + paths[i] = Evaluate(state, argv[i]); + if (paths[i] == NULL) { + for (int j = 0; j < i; ++j) { + free(paths[j]); + } + free(paths); + return NULL; + } + } + + bool recursive = (strcmp(name, "delete_recursive") == 0); + + int success = 0; + for (int i = 0; i < argc; ++i) { + if ((recursive ? dirUnlinkHierarchy(paths[i]) : unlink(paths[i])) == 0) + ++success; + free(paths[i]); + } + free(paths); + + char buffer[10]; + sprintf(buffer, "%d", success); + return StringValue(strdup(buffer)); +} + + +Value* ShowProgressFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + char* frac_str; + char* sec_str; + if (ReadArgs(state, argv, 2, &frac_str, &sec_str) < 0) { + return NULL; + } + + double frac = strtod(frac_str, NULL); + int sec; + android::base::ParseInt(sec_str, &sec); + + UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); + fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec); + + free(sec_str); + return StringValue(frac_str); +} + +Value* SetProgressFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); + } + char* frac_str; + if (ReadArgs(state, argv, 1, &frac_str) < 0) { + return NULL; + } + + double frac = strtod(frac_str, NULL); + + UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); + fprintf(ui->cmd_pipe, "set_progress %f\n", frac); + + return StringValue(frac_str); +} + +// package_extract_dir(package_path, destination_path) +Value* PackageExtractDirFn(const char* name, State* state, + int argc, Expr* argv[]) { + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + char* zip_path; + char* dest_path; + if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL; + + ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; + + // To create a consistent system image, never use the clock for timestamps. + struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default + + bool success = mzExtractRecursive(za, zip_path, dest_path, + ×tamp, + NULL, NULL, sehandle); + free(zip_path); + free(dest_path); + return StringValue(strdup(success ? "t" : "")); +} + + +// package_extract_file(package_path, destination_path) +// or +// package_extract_file(package_path) +// to return the entire contents of the file as the result of this +// function (the char* returned is actually a FileContents*). +Value* PackageExtractFileFn(const char* name, State* state, + int argc, Expr* argv[]) { + if (argc < 1 || argc > 2) { + return ErrorAbort(state, "%s() expects 1 or 2 args, got %d", + name, argc); + } + bool success = false; + + if (argc == 2) { + // The two-argument version extracts to a file. + + ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; + + char* zip_path; + char* dest_path; + if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL; + + const ZipEntry* entry = mzFindZipEntry(za, zip_path); + if (entry == NULL) { + printf("%s: no %s in package\n", name, zip_path); + goto done2; + } + + { + int fd = TEMP_FAILURE_RETRY(ota_open(dest_path, O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR)); + if (fd == -1) { + printf("%s: can't open %s for write: %s\n", name, dest_path, strerror(errno)); + goto done2; + } + success = mzExtractZipEntryToFile(za, entry, fd); + if (ota_fsync(fd) == -1) { + printf("fsync of \"%s\" failed: %s\n", dest_path, strerror(errno)); + success = false; + } + if (ota_close(fd) == -1) { + printf("close of \"%s\" failed: %s\n", dest_path, strerror(errno)); + success = false; + } + } + + done2: + free(zip_path); + free(dest_path); + return StringValue(strdup(success ? "t" : "")); + } else { + // The one-argument version returns the contents of the file + // as the result. + + char* zip_path; + if (ReadArgs(state, argv, 1, &zip_path) < 0) return NULL; + + Value* v = reinterpret_cast<Value*>(malloc(sizeof(Value))); + v->type = VAL_BLOB; + v->size = -1; + v->data = NULL; + + ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; + const ZipEntry* entry = mzFindZipEntry(za, zip_path); + if (entry == NULL) { + printf("%s: no %s in package\n", name, zip_path); + goto done1; + } + + v->size = mzGetZipEntryUncompLen(entry); + v->data = reinterpret_cast<char*>(malloc(v->size)); + if (v->data == NULL) { + printf("%s: failed to allocate %zd bytes for %s\n", + name, v->size, zip_path); + goto done1; + } + + success = mzExtractZipEntryToBuffer(za, entry, + (unsigned char *)v->data); + + done1: + free(zip_path); + if (!success) { + free(v->data); + v->data = NULL; + v->size = -1; + } + return v; + } +} + +// Create all parent directories of name, if necessary. +static int make_parents(char* name) { + char* p; + for (p = name + (strlen(name)-1); p > name; --p) { + if (*p != '/') continue; + *p = '\0'; + if (make_parents(name) < 0) return -1; + int result = mkdir(name, 0700); + if (result == 0) printf("created [%s]\n", name); + *p = '/'; + if (result == 0 || errno == EEXIST) { + // successfully created or already existed; we're done + return 0; + } else { + printf("failed to mkdir %s: %s\n", name, strerror(errno)); + return -1; + } + } + return 0; +} + +// symlink target src1 src2 ... +// unlinks any previously existing src1, src2, etc before creating symlinks. +Value* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc == 0) { + return ErrorAbort(state, "%s() expects 1+ args, got %d", name, argc); + } + char* target; + target = Evaluate(state, argv[0]); + if (target == NULL) return NULL; + + char** srcs = ReadVarArgs(state, argc-1, argv+1); + if (srcs == NULL) { + free(target); + return NULL; + } + + int bad = 0; + int i; + for (i = 0; i < argc-1; ++i) { + if (unlink(srcs[i]) < 0) { + if (errno != ENOENT) { + printf("%s: failed to remove %s: %s\n", + name, srcs[i], strerror(errno)); + ++bad; + } + } + if (make_parents(srcs[i])) { + printf("%s: failed to symlink %s to %s: making parents failed\n", + name, srcs[i], target); + ++bad; + } + if (symlink(target, srcs[i]) < 0) { + printf("%s: failed to symlink %s to %s: %s\n", + name, srcs[i], target, strerror(errno)); + ++bad; + } + free(srcs[i]); + } + free(srcs); + if (bad) { + return ErrorAbort(state, "%s: some symlinks failed", name); + } + return StringValue(strdup("")); +} + +struct perm_parsed_args { + bool has_uid; + uid_t uid; + bool has_gid; + gid_t gid; + bool has_mode; + mode_t mode; + bool has_fmode; + mode_t fmode; + bool has_dmode; + mode_t dmode; + bool has_selabel; + char* selabel; + bool has_capabilities; + uint64_t capabilities; +}; + +static struct perm_parsed_args ParsePermArgs(State * state, int argc, char** args) { + int i; + struct perm_parsed_args parsed; + int bad = 0; + static int max_warnings = 20; + + memset(&parsed, 0, sizeof(parsed)); + + for (i = 1; i < argc; i += 2) { + if (strcmp("uid", args[i]) == 0) { + int64_t uid; + if (sscanf(args[i+1], "%" SCNd64, &uid) == 1) { + parsed.uid = uid; + parsed.has_uid = true; + } else { + uiPrintf(state, "ParsePermArgs: invalid UID \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("gid", args[i]) == 0) { + int64_t gid; + if (sscanf(args[i+1], "%" SCNd64, &gid) == 1) { + parsed.gid = gid; + parsed.has_gid = true; + } else { + uiPrintf(state, "ParsePermArgs: invalid GID \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("mode", args[i]) == 0) { + int32_t mode; + if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) { + parsed.mode = mode; + parsed.has_mode = true; + } else { + uiPrintf(state, "ParsePermArgs: invalid mode \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("dmode", args[i]) == 0) { + int32_t mode; + if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) { + parsed.dmode = mode; + parsed.has_dmode = true; + } else { + uiPrintf(state, "ParsePermArgs: invalid dmode \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("fmode", args[i]) == 0) { + int32_t mode; + if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) { + parsed.fmode = mode; + parsed.has_fmode = true; + } else { + uiPrintf(state, "ParsePermArgs: invalid fmode \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("capabilities", args[i]) == 0) { + int64_t capabilities; + if (sscanf(args[i+1], "%" SCNi64, &capabilities) == 1) { + parsed.capabilities = capabilities; + parsed.has_capabilities = true; + } else { + uiPrintf(state, "ParsePermArgs: invalid capabilities \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("selabel", args[i]) == 0) { + if (args[i+1][0] != '\0') { + parsed.selabel = args[i+1]; + parsed.has_selabel = true; + } else { + uiPrintf(state, "ParsePermArgs: invalid selabel \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (max_warnings != 0) { + printf("ParsedPermArgs: unknown key \"%s\", ignoring\n", args[i]); + max_warnings--; + if (max_warnings == 0) { + printf("ParsedPermArgs: suppressing further warnings\n"); + } + } + } + return parsed; +} + +static int ApplyParsedPerms( + State * state, + const char* filename, + const struct stat *statptr, + struct perm_parsed_args parsed) +{ + int bad = 0; + + if (parsed.has_selabel) { + if (lsetfilecon(filename, parsed.selabel) != 0) { + uiPrintf(state, "ApplyParsedPerms: lsetfilecon of %s to %s failed: %s\n", + filename, parsed.selabel, strerror(errno)); + bad++; + } + } + + /* ignore symlinks */ + if (S_ISLNK(statptr->st_mode)) { + return bad; + } + + if (parsed.has_uid) { + if (chown(filename, parsed.uid, -1) < 0) { + uiPrintf(state, "ApplyParsedPerms: chown of %s to %d failed: %s\n", + filename, parsed.uid, strerror(errno)); + bad++; + } + } + + if (parsed.has_gid) { + if (chown(filename, -1, parsed.gid) < 0) { + uiPrintf(state, "ApplyParsedPerms: chgrp of %s to %d failed: %s\n", + filename, parsed.gid, strerror(errno)); + bad++; + } + } + + if (parsed.has_mode) { + if (chmod(filename, parsed.mode) < 0) { + uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", + filename, parsed.mode, strerror(errno)); + bad++; + } + } + + if (parsed.has_dmode && S_ISDIR(statptr->st_mode)) { + if (chmod(filename, parsed.dmode) < 0) { + uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", + filename, parsed.dmode, strerror(errno)); + bad++; + } + } + + if (parsed.has_fmode && S_ISREG(statptr->st_mode)) { + if (chmod(filename, parsed.fmode) < 0) { + uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", + filename, parsed.fmode, strerror(errno)); + bad++; + } + } + + if (parsed.has_capabilities && S_ISREG(statptr->st_mode)) { + if (parsed.capabilities == 0) { + if ((removexattr(filename, XATTR_NAME_CAPS) == -1) && (errno != ENODATA)) { + // Report failure unless it's ENODATA (attribute not set) + uiPrintf(state, "ApplyParsedPerms: removexattr of %s to %" PRIx64 " failed: %s\n", + filename, parsed.capabilities, strerror(errno)); + bad++; + } + } else { + struct vfs_cap_data cap_data; + memset(&cap_data, 0, sizeof(cap_data)); + cap_data.magic_etc = VFS_CAP_REVISION | VFS_CAP_FLAGS_EFFECTIVE; + cap_data.data[0].permitted = (uint32_t) (parsed.capabilities & 0xffffffff); + cap_data.data[0].inheritable = 0; + cap_data.data[1].permitted = (uint32_t) (parsed.capabilities >> 32); + cap_data.data[1].inheritable = 0; + if (setxattr(filename, XATTR_NAME_CAPS, &cap_data, sizeof(cap_data), 0) < 0) { + uiPrintf(state, "ApplyParsedPerms: setcap of %s to %" PRIx64 " failed: %s\n", + filename, parsed.capabilities, strerror(errno)); + bad++; + } + } + } + + return bad; +} + +// nftw doesn't allow us to pass along context, so we need to use +// global variables. *sigh* +static struct perm_parsed_args recursive_parsed_args; +static State* recursive_state; + +static int do_SetMetadataRecursive(const char* filename, const struct stat *statptr, + int fileflags, struct FTW *pfwt) { + return ApplyParsedPerms(recursive_state, filename, statptr, recursive_parsed_args); +} + +static Value* SetMetadataFn(const char* name, State* state, int argc, Expr* argv[]) { + int bad = 0; + struct stat sb; + Value* result = NULL; + + bool recursive = (strcmp(name, "set_metadata_recursive") == 0); + + if ((argc % 2) != 1) { + return ErrorAbort(state, "%s() expects an odd number of arguments, got %d", name, argc); + } + + char** args = ReadVarArgs(state, argc, argv); + if (args == NULL) return NULL; + + if (lstat(args[0], &sb) == -1) { + result = ErrorAbort(state, "%s: Error on lstat of \"%s\": %s", name, args[0], strerror(errno)); + goto done; + } + + { + struct perm_parsed_args parsed = ParsePermArgs(state, argc, args); + + if (recursive) { + recursive_parsed_args = parsed; + recursive_state = state; + bad += nftw(args[0], do_SetMetadataRecursive, 30, FTW_CHDIR | FTW_DEPTH | FTW_PHYS); + memset(&recursive_parsed_args, 0, sizeof(recursive_parsed_args)); + recursive_state = NULL; + } else { + bad += ApplyParsedPerms(state, args[0], &sb, parsed); + } + } + +done: + for (int i = 0; i < argc; ++i) { + free(args[i]); + } + free(args); + + if (result != NULL) { + return result; + } + + if (bad > 0) { + return ErrorAbort(state, "%s: some changes failed", name); + } + + return StringValue(strdup("")); +} + +Value* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); + } + char* key = Evaluate(state, argv[0]); + if (key == NULL) return NULL; + + char value[PROPERTY_VALUE_MAX]; + property_get(key, value, ""); + free(key); + + return StringValue(strdup(value)); +} + + +// file_getprop(file, key) +// +// 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, int argc, Expr* argv[]) { + char* result = NULL; + char* buffer = NULL; + char* filename; + char* key; + if (ReadArgs(state, argv, 2, &filename, &key) < 0) { + return NULL; + } + + struct stat st; + if (stat(filename, &st) < 0) { + ErrorAbort(state, "%s: failed to stat \"%s\": %s", name, filename, strerror(errno)); + goto done; + } + +#define MAX_FILE_GETPROP_SIZE 65536 + + if (st.st_size > MAX_FILE_GETPROP_SIZE) { + ErrorAbort(state, "%s too large for %s (max %d)", filename, name, MAX_FILE_GETPROP_SIZE); + goto done; + } + + buffer = reinterpret_cast<char*>(malloc(st.st_size+1)); + if (buffer == NULL) { + ErrorAbort(state, "%s: failed to alloc %zu bytes", name, + static_cast<size_t>(st.st_size+1)); + goto done; + } + + FILE* f; + f = ota_fopen(filename, "rb"); + if (f == NULL) { + ErrorAbort(state, "%s: failed to open %s: %s", name, filename, strerror(errno)); + goto done; + } + + if (ota_fread(buffer, 1, st.st_size, f) != static_cast<size_t>(st.st_size)) { + ErrorAbort(state, "%s: failed to read %zu bytes from %s", + name, static_cast<size_t>(st.st_size), filename); + ota_fclose(f); + goto done; + } + buffer[st.st_size] = '\0'; + + ota_fclose(f); + + char* line; + line = strtok(buffer, "\n"); + do { + // skip whitespace at start of line + while (*line && isspace(*line)) ++line; + + // comment or blank line: skip to next line + if (*line == '\0' || *line == '#') continue; + + char* equal = strchr(line, '='); + if (equal == NULL) { + continue; + } + + // trim whitespace between key and '=' + char* key_end = equal-1; + while (key_end > line && isspace(*key_end)) --key_end; + key_end[1] = '\0'; + + // not the key we're looking for + if (strcmp(key, line) != 0) continue; + + // skip whitespace after the '=' to the start of the value + char* val_start = equal+1; + while(*val_start && isspace(*val_start)) ++val_start; + + // trim trailing whitespace + char* val_end = val_start + strlen(val_start)-1; + while (val_end > val_start && isspace(*val_end)) --val_end; + val_end[1] = '\0'; + + result = strdup(val_start); + break; + + } while ((line = strtok(NULL, "\n"))); + + if (result == NULL) result = strdup(""); + + done: + free(filename); + free(key); + free(buffer); + return StringValue(result); +} + +// apply_patch_space(bytes) +Value* ApplyPatchSpaceFn(const char* name, State* state, + int argc, Expr* argv[]) { + char* bytes_str; + if (ReadArgs(state, argv, 1, &bytes_str) < 0) { + return NULL; + } + + size_t bytes; + if (!android::base::ParseUint(bytes_str, &bytes)) { + ErrorAbort(state, "%s(): can't parse \"%s\" as byte count\n\n", name, bytes_str); + free(bytes_str); + return nullptr; + } + + return StringValue(strdup(CacheSizeCheck(bytes) ? "" : "t")); +} + +// apply_patch(file, size, init_sha1, tgt_sha1, patch) + +Value* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc < 6 || (argc % 2) == 1) { + return ErrorAbort(state, "%s(): expected at least 6 args and an " + "even number, got %d", + name, argc); + } + + char* source_filename; + char* target_filename; + char* target_sha1; + char* target_size_str; + if (ReadArgs(state, argv, 4, &source_filename, &target_filename, + &target_sha1, &target_size_str) < 0) { + return NULL; + } + + size_t target_size; + if (!android::base::ParseUint(target_size_str, &target_size)) { + ErrorAbort(state, "%s(): can't parse \"%s\" as byte count", name, target_size_str); + free(source_filename); + free(target_filename); + free(target_sha1); + free(target_size_str); + return nullptr; + } + + int patchcount = (argc-4) / 2; + std::unique_ptr<Value*, decltype(&free)> arg_values(ReadValueVarArgs(state, argc-4, argv+4), + free); + if (!arg_values) { + return nullptr; + } + std::vector<std::unique_ptr<Value, decltype(&FreeValue)>> patch_shas; + std::vector<std::unique_ptr<Value, decltype(&FreeValue)>> patches; + // Protect values by unique_ptrs first to get rid of memory leak. + for (int i = 0; i < patchcount * 2; i += 2) { + patch_shas.emplace_back(arg_values.get()[i], FreeValue); + patches.emplace_back(arg_values.get()[i+1], FreeValue); + } + + for (int i = 0; i < patchcount; ++i) { + if (patch_shas[i]->type != VAL_STRING) { + ErrorAbort(state, "%s(): sha-1 #%d is not string", name, i); + return nullptr; + } + if (patches[i]->type != VAL_BLOB) { + ErrorAbort(state, "%s(): patch #%d is not blob", name, i); + return nullptr; + } + } + + std::vector<char*> patch_sha_str; + std::vector<Value*> patch_ptrs; + for (int i = 0; i < patchcount; ++i) { + patch_sha_str.push_back(patch_shas[i]->data); + patch_ptrs.push_back(patches[i].get()); + } + + int result = applypatch(source_filename, target_filename, + target_sha1, target_size, + patchcount, patch_sha_str.data(), patch_ptrs.data(), NULL); + + return StringValue(strdup(result == 0 ? "t" : "")); +} + +// apply_patch_check(file, [sha1_1, ...]) +Value* ApplyPatchCheckFn(const char* name, State* state, + int argc, Expr* argv[]) { + if (argc < 1) { + return ErrorAbort(state, "%s(): expected at least 1 arg, got %d", + name, argc); + } + + char* filename; + if (ReadArgs(state, argv, 1, &filename) < 0) { + return NULL; + } + + int patchcount = argc-1; + char** sha1s = ReadVarArgs(state, argc-1, argv+1); + + int result = applypatch_check(filename, patchcount, sha1s); + + int i; + for (i = 0; i < patchcount; ++i) { + free(sha1s[i]); + } + free(sha1s); + + return StringValue(strdup(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, int argc, Expr* argv[]) { + char** args = ReadVarArgs(state, argc, argv); + if (args == NULL) { + return NULL; + } + + std::string buffer; + for (int i = 0; i < argc; ++i) { + buffer += args[i]; + free(args[i]); + } + free(args); + + buffer += "\n"; + uiPrint(state, buffer); + return StringValue(strdup(buffer.c_str())); +} + +Value* WipeCacheFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 0) { + return ErrorAbort(state, "%s() expects no args, got %d", name, argc); + } + fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe, "wipe_cache\n"); + return StringValue(strdup("t")); +} + +Value* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc < 1) { + return ErrorAbort(state, "%s() expects at least 1 arg", name); + } + char** args = ReadVarArgs(state, argc, argv); + if (args == NULL) { + return NULL; + } + + char** args2 = reinterpret_cast<char**>(malloc(sizeof(char*) * (argc+1))); + memcpy(args2, args, sizeof(char*) * argc); + args2[argc] = NULL; + + printf("about to run program [%s] with %d args\n", args2[0], argc); + + pid_t child = fork(); + if (child == 0) { + execv(args2[0], args2); + printf("run_program: execv failed: %s\n", strerror(errno)); + _exit(1); + } + int status; + waitpid(child, &status, 0); + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + printf("run_program: child exited with status %d\n", + WEXITSTATUS(status)); + } + } else if (WIFSIGNALED(status)) { + printf("run_program: child terminated by signal %d\n", + WTERMSIG(status)); + } + + int i; + for (i = 0; i < argc; ++i) { + free(args[i]); + } + free(args); + free(args2); + + char buffer[20]; + sprintf(buffer, "%d", status); + + return StringValue(strdup(buffer)); +} + +// 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, int argc, Expr* argv[]) { + if (argc < 1) { + return ErrorAbort(state, "%s() expects at least 1 arg", name); + } + + std::unique_ptr<Value*, decltype(&free)> arg_values(ReadValueVarArgs(state, argc, argv), free); + if (arg_values == nullptr) { + return nullptr; + } + std::vector<std::unique_ptr<Value, decltype(&FreeValue)>> args; + for (int i = 0; i < argc; ++i) { + args.emplace_back(arg_values.get()[i], FreeValue); + } + + if (args[0]->size < 0) { + return StringValue(strdup("")); + } + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast<uint8_t*>(args[0]->data), args[0]->size, digest); + + if (argc == 1) { + return StringValue(PrintSha1(digest)); + } + + int i; + uint8_t arg_digest[SHA_DIGEST_LENGTH]; + for (i = 1; i < argc; ++i) { + if (args[i]->type != VAL_STRING) { + printf("%s(): arg %d is not a string; skipping", + name, i); + } else if (ParseSha1(args[i]->data, arg_digest) != 0) { + // Warn about bad args and skip them. + printf("%s(): error parsing \"%s\" as sha-1; skipping", + name, args[i]->data); + } else if (memcmp(digest, arg_digest, SHA_DIGEST_LENGTH) == 0) { + break; + } + } + if (i >= argc) { + // Didn't match any of the hex strings; return false. + return StringValue(strdup("")); + } + // Found a match. + return args[i].release(); +} + +// Read a local file and return its contents (the Value* returned +// is actually a FileContents*). +Value* ReadFileFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); + } + char* filename; + if (ReadArgs(state, argv, 1, &filename) < 0) return NULL; + + Value* v = static_cast<Value*>(malloc(sizeof(Value))); + if (v == nullptr) { + return nullptr; + } + v->type = VAL_BLOB; + v->size = -1; + v->data = nullptr; + + FileContents fc; + if (LoadFileContents(filename, &fc) == 0) { + v->data = static_cast<char*>(malloc(fc.data.size())); + if (v->data != nullptr) { + memcpy(v->data, fc.data.data(), fc.data.size()); + v->size = fc.data.size(); + } + } + free(filename); + return v; +} + +// Immediately reboot the device. Recovery is not finished normally, +// so if you reboot into recovery it will re-start applying the +// current package (because nothing has cleared the copy of the +// arguments stored in the BCB). +// +// The argument is the partition name passed to the android reboot +// property. It can be "recovery" to boot from the recovery +// partition, or "" (empty string) to boot from the regular boot +// partition. +Value* RebootNowFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + + char* filename; + char* property; + if (ReadArgs(state, argv, 2, &filename, &property) < 0) return NULL; + + char buffer[80]; + + // zero out the 'command' field of the bootloader message. + memset(buffer, 0, sizeof(((struct bootloader_message*)0)->command)); + FILE* f = ota_fopen(filename, "r+b"); + fseek(f, offsetof(struct bootloader_message, command), SEEK_SET); + ota_fwrite(buffer, sizeof(((struct bootloader_message*)0)->command), 1, f); + ota_fclose(f); + free(filename); + + strcpy(buffer, "reboot,"); + if (property != NULL) { + strncat(buffer, property, sizeof(buffer)-10); + } + + property_set(ANDROID_RB_PROPERTY, buffer); + + sleep(5); + free(property); + ErrorAbort(state, "%s() failed to reboot", name); + return NULL; +} + +// Store a string value somewhere that future invocations of recovery +// can access it. This value is called the "stage" and can be used to +// drive packages that need to do reboots in the middle of +// installation and keep track of where they are in the multi-stage +// install. +// +// The first argument is the block device for the misc partition +// ("/misc" in the fstab), which is where this value is stored. The +// second argument is the string to store; it should not exceed 31 +// bytes. +Value* SetStageFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + + char* filename; + char* stagestr; + if (ReadArgs(state, argv, 2, &filename, &stagestr) < 0) return NULL; + + // Store this value in the misc partition, immediately after the + // bootloader message that the main recovery uses to save its + // arguments in case of the device restarting midway through + // package installation. + FILE* f = ota_fopen(filename, "r+b"); + fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET); + int to_write = strlen(stagestr)+1; + int max_size = sizeof(((struct bootloader_message*)0)->stage); + if (to_write > max_size) { + to_write = max_size; + stagestr[max_size-1] = 0; + } + ota_fwrite(stagestr, to_write, 1, f); + ota_fclose(f); + + free(stagestr); + return StringValue(filename); +} + +// Return the value most recently saved with SetStageFn. The argument +// is the block device for the misc partition. +Value* GetStageFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); + } + + char* filename; + if (ReadArgs(state, argv, 1, &filename) < 0) return NULL; + + char buffer[sizeof(((struct bootloader_message*)0)->stage)]; + FILE* f = ota_fopen(filename, "rb"); + fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET); + ota_fread(buffer, sizeof(buffer), 1, f); + ota_fclose(f); + buffer[sizeof(buffer)-1] = '\0'; + + return StringValue(strdup(buffer)); +} + +Value* WipeBlockDeviceFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + + char* filename; + char* len_str; + if (ReadArgs(state, argv, 2, &filename, &len_str) < 0) return NULL; + + size_t len; + android::base::ParseUint(len_str, &len); + int fd = ota_open(filename, O_WRONLY, 0644); + int success = wipe_block_device(fd, len); + + free(filename); + free(len_str); + + ota_close(fd); + + return StringValue(strdup(success ? "t" : "")); +} + +Value* EnableRebootFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 0) { + return ErrorAbort(state, "%s() expects no args, got %d", name, argc); + } + UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); + fprintf(ui->cmd_pipe, "enable_reboot\n"); + return StringValue(strdup("t")); +} + +Value* Tune2FsFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc == 0) { + return ErrorAbort(state, "%s() expects args, got %d", name, argc); + } + + char** args = ReadVarArgs(state, argc, argv); + if (args == NULL) { + return ErrorAbort(state, "%s() could not read args", name); + } + + char** args2 = reinterpret_cast<char**>(malloc(sizeof(char*) * (argc+1))); + // Tune2fs expects the program name as its args[0] + args2[0] = strdup(name); + for (int i = 0; i < argc; ++i) { + args2[i + 1] = args[i]; + } + int result = tune2fs_main(argc + 1, args2); + for (int i = 0; i < argc; ++i) { + free(args[i]); + } + free(args); + + free(args2[0]); + free(args2); + if (result != 0) { + return ErrorAbort(state, "%s() returned error code %d", name, result); + } + return StringValue(strdup("t")); +} + +void RegisterInstallFunctions() { + RegisterFunction("mount", MountFn); + RegisterFunction("is_mounted", IsMountedFn); + RegisterFunction("unmount", UnmountFn); + RegisterFunction("format", FormatFn); + RegisterFunction("show_progress", ShowProgressFn); + RegisterFunction("set_progress", SetProgressFn); + RegisterFunction("delete", DeleteFn); + RegisterFunction("delete_recursive", DeleteFn); + RegisterFunction("package_extract_dir", PackageExtractDirFn); + RegisterFunction("package_extract_file", PackageExtractFileFn); + RegisterFunction("symlink", SymlinkFn); + + // Usage: + // set_metadata("filename", "key1", "value1", "key2", "value2", ...) + // Example: + // set_metadata("/system/bin/netcfg", "uid", 0, "gid", 3003, "mode", 02750, "selabel", "u:object_r:system_file:s0", "capabilities", 0x0); + RegisterFunction("set_metadata", SetMetadataFn); + + // Usage: + // set_metadata_recursive("dirname", "key1", "value1", "key2", "value2", ...) + // Example: + // set_metadata_recursive("/system", "uid", 0, "gid", 0, "fmode", 0644, "dmode", 0755, "selabel", "u:object_r:system_file:s0", "capabilities", 0x0); + RegisterFunction("set_metadata_recursive", SetMetadataFn); + + RegisterFunction("getprop", GetPropFn); + RegisterFunction("file_getprop", FileGetPropFn); + + RegisterFunction("apply_patch", ApplyPatchFn); + RegisterFunction("apply_patch_check", ApplyPatchCheckFn); + RegisterFunction("apply_patch_space", ApplyPatchSpaceFn); + + RegisterFunction("wipe_block_device", WipeBlockDeviceFn); + + RegisterFunction("read_file", ReadFileFn); + RegisterFunction("sha1_check", Sha1CheckFn); + RegisterFunction("rename", RenameFn); + + RegisterFunction("wipe_cache", WipeCacheFn); + + RegisterFunction("ui_print", UIPrintFn); + + RegisterFunction("run_program", RunProgramFn); + + RegisterFunction("reboot_now", RebootNowFn); + RegisterFunction("get_stage", GetStageFn); + RegisterFunction("set_stage", SetStageFn); + + RegisterFunction("enable_reboot", EnableRebootFn); + RegisterFunction("tune2fs", Tune2FsFn); +}
diff --git a/recovery/updater/install.h b/recovery/updater/install.h new file mode 100644 index 0000000..b3b8a4d --- /dev/null +++ b/recovery/updater/install.h
@@ -0,0 +1,27 @@ +/* + * 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. + */ + +#ifndef _UPDATER_INSTALL_H_ +#define _UPDATER_INSTALL_H_ + +void RegisterInstallFunctions(); + +// uiPrintf function prints msg to screen as well as logs +void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...) __attribute__((__format__(printf, 2, 3))); + +static int make_parents(char* _Nonnull name); + +#endif
diff --git a/recovery/updater/updater.cpp b/recovery/updater/updater.cpp new file mode 100644 index 0000000..0497d6a --- /dev/null +++ b/recovery/updater/updater.cpp
@@ -0,0 +1,181 @@ +/* + * 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. + */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +#include "edify/expr.h" +#include "updater.h" +#include "install.h" +#include "blockimg.h" +#include "minzip/Zip.h" +#include "minzip/SysUtil.h" +#include "config.h" + +#include <selinux/label.h> +#include <selinux/selinux.h> + +// Generated by the makefile, this function defines the +// RegisterDeviceExtensions() function, which calls all the +// registration functions for device-specific extensions. +#include "register.inc" + +// Where in the package we expect to find the edify script to execute. +// (Note it's "updateR-script", not the older "update-script".) +#define SCRIPT_NAME "META-INF/com/google/android/updater-script" + +extern bool have_eio_error; + +struct selabel_handle *sehandle; + +int main(int argc, char** argv) { + // Various things log information to stdout or stderr more or less + // at random (though we've tried to standardize on stdout). The + // log file makes more sense if buffering is turned off so things + // appear in the right order. + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + if (argc != 4) { + printf("unexpected number of arguments (%d)\n", argc); + return 1; + } + + char* version = argv[1]; + if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || + version[1] != '\0') { + // We support version 1, 2, or 3. + printf("wrong updater binary API; expected 1, 2, or 3; " + "got %s\n", + argv[1]); + return 2; + } + + // Set up the pipe for sending commands back to the parent process. + + int fd = atoi(argv[2]); + FILE* cmd_pipe = fdopen(fd, "wb"); + setlinebuf(cmd_pipe); + + // Extract the script from the package. + + const char* package_filename = argv[3]; + MemMapping map; + if (sysMapFile(package_filename, &map) != 0) { + printf("failed to map package %s\n", argv[3]); + return 3; + } + ZipArchive za; + int err; + err = mzOpenZipArchive(map.addr, map.length, &za); + if (err != 0) { + printf("failed to open package %s: %s\n", + argv[3], strerror(err)); + return 3; + } + ota_io_init(&za); + + const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME); + if (script_entry == NULL) { + printf("failed to find %s in %s\n", SCRIPT_NAME, package_filename); + return 4; + } + + char* script = reinterpret_cast<char*>(malloc(script_entry->uncompLen+1)); + if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) { + printf("failed to read script from package\n"); + return 5; + } + script[script_entry->uncompLen] = '\0'; + + // Configure edify's functions. + + RegisterBuiltins(); + RegisterInstallFunctions(); + RegisterBlockImageFunctions(); + RegisterDeviceExtensions(); + FinishRegistration(); + + // Parse the script. + + Expr* root; + int error_count = 0; + int error = parse_string(script, &root, &error_count); + if (error != 0 || error_count > 0) { + printf("%d parse errors\n", error_count); + return 6; + } + + struct selinux_opt seopts[] = { + { SELABEL_OPT_PATH, "/file_contexts" } + }; + + sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1); + + if (!sehandle) { + fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n"); + } + + // Evaluate the parsed script. + + UpdaterInfo updater_info; + updater_info.cmd_pipe = cmd_pipe; + updater_info.package_zip = &za; + updater_info.version = atoi(version); + updater_info.package_zip_addr = map.addr; + updater_info.package_zip_len = map.length; + + State state; + state.cookie = &updater_info; + state.script = script; + state.errmsg = NULL; + + char* result = Evaluate(&state, root); + + if (have_eio_error) { + fprintf(cmd_pipe, "retry_update\n"); + } + + if (result == NULL) { + if (state.errmsg == NULL) { + printf("script aborted (no error message)\n"); + fprintf(cmd_pipe, "ui_print script aborted (no error message)\n"); + } else { + printf("script aborted: %s\n", state.errmsg); + char* line = strtok(state.errmsg, "\n"); + while (line) { + fprintf(cmd_pipe, "ui_print %s\n", line); + line = strtok(NULL, "\n"); + } + fprintf(cmd_pipe, "ui_print\n"); + } + free(state.errmsg); + return 7; + } else { + fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result); + free(result); + } + + if (updater_info.package_zip) { + mzCloseZipArchive(updater_info.package_zip); + } + sysReleaseMap(&map); + free(script); + + return 0; +}
diff --git a/recovery/updater/updater.h b/recovery/updater/updater.h new file mode 100644 index 0000000..d3a09b9 --- /dev/null +++ b/recovery/updater/updater.h
@@ -0,0 +1,35 @@ +/* + * 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. + */ + +#ifndef _UPDATER_UPDATER_H_ +#define _UPDATER_UPDATER_H_ + +#include <stdio.h> +#include "minzip/Zip.h" + +typedef struct { + FILE* cmd_pipe; + ZipArchive* package_zip; + int version; + + uint8_t* package_zip_addr; + size_t package_zip_len; +} UpdaterInfo; + +struct selabel_handle; +extern struct selabel_handle *sehandle; + +#endif
diff --git a/recovery/verifier.cpp b/recovery/verifier.cpp new file mode 100644 index 0000000..1d6cf81 --- /dev/null +++ b/recovery/verifier.cpp
@@ -0,0 +1,542 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <malloc.h> +#include <stdio.h> +#include <string.h> + +#include <algorithm> +#include <memory> + +#include <openssl/bn.h> +#include <openssl/ecdsa.h> +#include <openssl/obj_mac.h> + +#include "asn1_decoder.h" +#include "common.h" +#include "ui.h" +#include "verifier.h" + +extern RecoveryUI* ui; + +/* + * Simple version of PKCS#7 SignedData extraction. This extracts the + * signature OCTET STRING to be used for signature verification. + * + * For full details, see http://www.ietf.org/rfc/rfc3852.txt + * + * The PKCS#7 structure looks like: + * + * SEQUENCE (ContentInfo) + * OID (ContentType) + * [0] (content) + * SEQUENCE (SignedData) + * INTEGER (version CMSVersion) + * SET (DigestAlgorithmIdentifiers) + * SEQUENCE (EncapsulatedContentInfo) + * [0] (CertificateSet OPTIONAL) + * [1] (RevocationInfoChoices OPTIONAL) + * SET (SignerInfos) + * SEQUENCE (SignerInfo) + * INTEGER (CMSVersion) + * SEQUENCE (SignerIdentifier) + * SEQUENCE (DigestAlgorithmIdentifier) + * SEQUENCE (SignatureAlgorithmIdentifier) + * OCTET STRING (SignatureValue) + */ +static bool read_pkcs7(uint8_t* pkcs7_der, size_t pkcs7_der_len, uint8_t** sig_der, + size_t* sig_der_length) { + asn1_context_t* ctx = asn1_context_new(pkcs7_der, pkcs7_der_len); + if (ctx == NULL) { + return false; + } + + asn1_context_t* pkcs7_seq = asn1_sequence_get(ctx); + if (pkcs7_seq != NULL && asn1_sequence_next(pkcs7_seq)) { + asn1_context_t *signed_data_app = asn1_constructed_get(pkcs7_seq); + if (signed_data_app != NULL) { + asn1_context_t* signed_data_seq = asn1_sequence_get(signed_data_app); + if (signed_data_seq != NULL + && asn1_sequence_next(signed_data_seq) + && asn1_sequence_next(signed_data_seq) + && asn1_sequence_next(signed_data_seq) + && asn1_constructed_skip_all(signed_data_seq)) { + asn1_context_t *sig_set = asn1_set_get(signed_data_seq); + if (sig_set != NULL) { + asn1_context_t* sig_seq = asn1_sequence_get(sig_set); + if (sig_seq != NULL + && asn1_sequence_next(sig_seq) + && asn1_sequence_next(sig_seq) + && asn1_sequence_next(sig_seq) + && asn1_sequence_next(sig_seq)) { + uint8_t* sig_der_ptr; + if (asn1_octet_string_get(sig_seq, &sig_der_ptr, sig_der_length)) { + *sig_der = (uint8_t*) malloc(*sig_der_length); + if (*sig_der != NULL) { + memcpy(*sig_der, sig_der_ptr, *sig_der_length); + } + } + asn1_context_free(sig_seq); + } + asn1_context_free(sig_set); + } + asn1_context_free(signed_data_seq); + } + asn1_context_free(signed_data_app); + } + asn1_context_free(pkcs7_seq); + } + asn1_context_free(ctx); + + return *sig_der != NULL; +} + +// Look for an RSA signature embedded in the .ZIP file comment given +// the path to the zip. Verify it matches one of the given public +// keys. +// +// Return VERIFY_SUCCESS, VERIFY_FAILURE (if any error is encountered +// or no key matches the signature). + +int verify_file(unsigned char* addr, size_t length, + const std::vector<Certificate>& keys) { + ui->SetProgress(0.0); + + // An archive with a whole-file signature will end in six bytes: + // + // (2-byte signature start) $ff $ff (2-byte comment size) + // + // (As far as the ZIP format is concerned, these are part of the + // archive comment.) We start by reading this footer, this tells + // us how far back from the end we have to start reading to find + // the whole comment. + +#define FOOTER_SIZE 6 + + if (length < FOOTER_SIZE) { + LOGE("not big enough to contain footer\n"); + return VERIFY_FAILURE; + } + + unsigned char* footer = addr + length - FOOTER_SIZE; + + if (footer[2] != 0xff || footer[3] != 0xff) { + LOGE("footer is wrong\n"); + return VERIFY_FAILURE; + } + + size_t comment_size = footer[4] + (footer[5] << 8); + size_t signature_start = footer[0] + (footer[1] << 8); + LOGI("comment is %zu bytes; signature %zu bytes from end\n", + comment_size, signature_start); + + if (signature_start <= FOOTER_SIZE) { + LOGE("Signature start is in the footer"); + return VERIFY_FAILURE; + } + +#define EOCD_HEADER_SIZE 22 + + // The end-of-central-directory record is 22 bytes plus any + // comment length. + size_t eocd_size = comment_size + EOCD_HEADER_SIZE; + + if (length < eocd_size) { + LOGE("not big enough to contain EOCD\n"); + return VERIFY_FAILURE; + } + + // Determine how much of the file is covered by the signature. + // This is everything except the signature data and length, which + // includes all of the EOCD except for the comment length field (2 + // bytes) and the comment data. + size_t signed_len = length - eocd_size + EOCD_HEADER_SIZE - 2; + + unsigned char* eocd = addr + length - eocd_size; + + // If this is really is the EOCD record, it will begin with the + // magic number $50 $4b $05 $06. + if (eocd[0] != 0x50 || eocd[1] != 0x4b || + eocd[2] != 0x05 || eocd[3] != 0x06) { + LOGE("signature length doesn't match EOCD marker\n"); + return VERIFY_FAILURE; + } + + for (size_t i = 4; i < eocd_size-3; ++i) { + if (eocd[i ] == 0x50 && eocd[i+1] == 0x4b && + eocd[i+2] == 0x05 && eocd[i+3] == 0x06) { + // if the sequence $50 $4b $05 $06 appears anywhere after + // the real one, minzip will find the later (wrong) one, + // which could be exploitable. Fail verification if + // this sequence occurs anywhere after the real one. + LOGE("EOCD marker occurs after start of EOCD\n"); + return VERIFY_FAILURE; + } + } + +#define BUFFER_SIZE 4096 + + bool need_sha1 = false; + bool need_sha256 = false; + for (const auto& key : keys) { + switch (key.hash_len) { + case SHA_DIGEST_LENGTH: need_sha1 = true; break; + case SHA256_DIGEST_LENGTH: need_sha256 = true; break; + } + } + + SHA_CTX sha1_ctx; + SHA256_CTX sha256_ctx; + SHA1_Init(&sha1_ctx); + SHA256_Init(&sha256_ctx); + + double frac = -1.0; + size_t so_far = 0; + while (so_far < signed_len) { + size_t size = signed_len - so_far; + if (size > BUFFER_SIZE) size = BUFFER_SIZE; + + if (need_sha1) SHA1_Update(&sha1_ctx, addr + so_far, size); + if (need_sha256) SHA256_Update(&sha256_ctx, addr + so_far, size); + so_far += size; + + double f = so_far / (double)signed_len; + if (f > frac + 0.02 || size == so_far) { + ui->SetProgress(f); + frac = f; + } + } + + uint8_t sha1[SHA_DIGEST_LENGTH]; + SHA1_Final(sha1, &sha1_ctx); + uint8_t sha256[SHA256_DIGEST_LENGTH]; + SHA256_Final(sha256, &sha256_ctx); + + uint8_t* sig_der = nullptr; + size_t sig_der_length = 0; + + size_t signature_size = signature_start - FOOTER_SIZE; + if (!read_pkcs7(eocd + eocd_size - signature_start, signature_size, &sig_der, + &sig_der_length)) { + LOGE("Could not find signature DER block\n"); + return VERIFY_FAILURE; + } + + /* + * Check to make sure at least one of the keys matches the signature. Since + * any key can match, we need to try each before determining a verification + * failure has happened. + */ + size_t i = 0; + for (const auto& key : keys) { + const uint8_t* hash; + int hash_nid; + switch (key.hash_len) { + case SHA_DIGEST_LENGTH: + hash = sha1; + hash_nid = NID_sha1; + break; + case SHA256_DIGEST_LENGTH: + hash = sha256; + hash_nid = NID_sha256; + break; + default: + continue; + } + + // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that + // the signing tool appends after the signature itself. + if (key.key_type == Certificate::KEY_TYPE_RSA) { + if (!RSA_verify(hash_nid, hash, key.hash_len, sig_der, + sig_der_length, key.rsa.get())) { + LOGI("failed to verify against RSA key %zu\n", i); + continue; + } + + LOGI("whole-file signature verified against RSA key %zu\n", i); + free(sig_der); + return VERIFY_SUCCESS; + } else if (key.key_type == Certificate::KEY_TYPE_EC + && key.hash_len == SHA256_DIGEST_LENGTH) { + if (!ECDSA_verify(0, hash, key.hash_len, sig_der, + sig_der_length, key.ec.get())) { + LOGI("failed to verify against EC key %zu\n", i); + continue; + } + + LOGI("whole-file signature verified against EC key %zu\n", i); + free(sig_der); + return VERIFY_SUCCESS; + } else { + LOGI("Unknown key type %d\n", key.key_type); + } + i++; + } + free(sig_der); + LOGE("failed to verify whole-file signature\n"); + return VERIFY_FAILURE; +} + +std::unique_ptr<RSA, RSADeleter> parse_rsa_key(FILE* file, uint32_t exponent) { + // Read key length in words and n0inv. n0inv is a precomputed montgomery + // parameter derived from the modulus and can be used to speed up + // verification. n0inv is 32 bits wide here, assuming the verification logic + // uses 32 bit arithmetic. However, BoringSSL may use a word size of 64 bits + // internally, in which case we don't have a valid n0inv. Thus, we just + // ignore the montgomery parameters and have BoringSSL recompute them + // internally. If/When the speedup from using the montgomery parameters + // becomes relevant, we can add more sophisticated code here to obtain a + // 64-bit n0inv and initialize the montgomery parameters in the key object. + uint32_t key_len_words = 0; + uint32_t n0inv = 0; + if (fscanf(file, " %i , 0x%x", &key_len_words, &n0inv) != 2) { + return nullptr; + } + + if (key_len_words > 8192 / 32) { + LOGE("key length (%d) too large\n", key_len_words); + return nullptr; + } + + // Read the modulus. + std::unique_ptr<uint32_t[]> modulus(new uint32_t[key_len_words]); + if (fscanf(file, " , { %u", &modulus[0]) != 1) { + return nullptr; + } + for (uint32_t i = 1; i < key_len_words; ++i) { + if (fscanf(file, " , %u", &modulus[i]) != 1) { + return nullptr; + } + } + + // Cconvert from little-endian array of little-endian words to big-endian + // byte array suitable as input for BN_bin2bn. + std::reverse((uint8_t*)modulus.get(), + (uint8_t*)(modulus.get() + key_len_words)); + + // The next sequence of values is the montgomery parameter R^2. Since we + // generally don't have a valid |n0inv|, we ignore this (see comment above). + uint32_t rr_value; + if (fscanf(file, " } , { %u", &rr_value) != 1) { + return nullptr; + } + for (uint32_t i = 1; i < key_len_words; ++i) { + if (fscanf(file, " , %u", &rr_value) != 1) { + return nullptr; + } + } + if (fscanf(file, " } } ") != 0) { + return nullptr; + } + + // Initialize the key. + std::unique_ptr<RSA, RSADeleter> key(RSA_new()); + if (!key) { + return nullptr; + } + + key->n = BN_bin2bn((uint8_t*)modulus.get(), + key_len_words * sizeof(uint32_t), NULL); + if (!key->n) { + return nullptr; + } + + key->e = BN_new(); + if (!key->e || !BN_set_word(key->e, exponent)) { + return nullptr; + } + + return key; +} + +struct BNDeleter { + void operator()(BIGNUM* bn) { + BN_free(bn); + } +}; + +std::unique_ptr<EC_KEY, ECKEYDeleter> parse_ec_key(FILE* file) { + uint32_t key_len_bytes = 0; + if (fscanf(file, " %i", &key_len_bytes) != 1) { + return nullptr; + } + + std::unique_ptr<EC_GROUP, void (*)(EC_GROUP*)> group( + EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1), EC_GROUP_free); + if (!group) { + return nullptr; + } + + // Verify that |key_len| matches the group order. + if (key_len_bytes != BN_num_bytes(EC_GROUP_get0_order(group.get()))) { + return nullptr; + } + + // Read the public key coordinates. Note that the byte order in the file is + // little-endian, so we convert to big-endian here. + std::unique_ptr<uint8_t[]> bytes(new uint8_t[key_len_bytes]); + std::unique_ptr<BIGNUM, BNDeleter> point[2]; + for (int i = 0; i < 2; ++i) { + unsigned int byte = 0; + if (fscanf(file, " , { %u", &byte) != 1) { + return nullptr; + } + bytes[key_len_bytes - 1] = byte; + + for (size_t i = 1; i < key_len_bytes; ++i) { + if (fscanf(file, " , %u", &byte) != 1) { + return nullptr; + } + bytes[key_len_bytes - i - 1] = byte; + } + + point[i].reset(BN_bin2bn(bytes.get(), key_len_bytes, nullptr)); + if (!point[i]) { + return nullptr; + } + + if (fscanf(file, " }") != 0) { + return nullptr; + } + } + + if (fscanf(file, " } ") != 0) { + return nullptr; + } + + // Create and initialize the key. + std::unique_ptr<EC_KEY, ECKEYDeleter> key(EC_KEY_new()); + if (!key || !EC_KEY_set_group(key.get(), group.get()) || + !EC_KEY_set_public_key_affine_coordinates(key.get(), point[0].get(), + point[1].get())) { + return nullptr; + } + + return key; +} + +// Reads a file containing one or more public keys as produced by +// DumpPublicKey: this is an RSAPublicKey struct as it would appear +// as a C source literal, eg: +// +// "{64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}" +// +// For key versions newer than the original 2048-bit e=3 keys +// supported by Android, the string is preceded by a version +// identifier, eg: +// +// "v2 {64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}" +// +// (Note that the braces and commas in this example are actual +// characters the parser expects to find in the file; the ellipses +// indicate more numbers omitted from this example.) +// +// The file may contain multiple keys in this format, separated by +// commas. The last key must not be followed by a comma. +// +// A Certificate is a pair of an RSAPublicKey and a particular hash +// (we support SHA-1 and SHA-256; we store the hash length to signify +// which is being used). The hash used is implied by the version number. +// +// 1: 2048-bit RSA key with e=3 and SHA-1 hash +// 2: 2048-bit RSA key with e=65537 and SHA-1 hash +// 3: 2048-bit RSA key with e=3 and SHA-256 hash +// 4: 2048-bit RSA key with e=65537 and SHA-256 hash +// 5: 256-bit EC key using the NIST P-256 curve parameters and SHA-256 hash +// +// Returns true on success, and appends the found keys (at least one) to certs. +// Otherwise returns false if the file failed to parse, or if it contains zero +// keys. The contents in certs would be unspecified on failure. +bool load_keys(const char* filename, std::vector<Certificate>& certs) { + std::unique_ptr<FILE, decltype(&fclose)> f(fopen(filename, "r"), fclose); + if (!f) { + LOGE("opening %s: %s\n", filename, strerror(errno)); + return false; + } + + while (true) { + certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + Certificate& cert = certs.back(); + uint32_t exponent = 0; + + char start_char; + if (fscanf(f.get(), " %c", &start_char) != 1) return false; + if (start_char == '{') { + // a version 1 key has no version specifier. + cert.key_type = Certificate::KEY_TYPE_RSA; + exponent = 3; + cert.hash_len = SHA_DIGEST_LENGTH; + } else if (start_char == 'v') { + int version; + if (fscanf(f.get(), "%d {", &version) != 1) return false; + switch (version) { + case 2: + cert.key_type = Certificate::KEY_TYPE_RSA; + exponent = 65537; + cert.hash_len = SHA_DIGEST_LENGTH; + break; + case 3: + cert.key_type = Certificate::KEY_TYPE_RSA; + exponent = 3; + cert.hash_len = SHA256_DIGEST_LENGTH; + break; + case 4: + cert.key_type = Certificate::KEY_TYPE_RSA; + exponent = 65537; + cert.hash_len = SHA256_DIGEST_LENGTH; + break; + case 5: + cert.key_type = Certificate::KEY_TYPE_EC; + cert.hash_len = SHA256_DIGEST_LENGTH; + break; + default: + return false; + } + } + + if (cert.key_type == Certificate::KEY_TYPE_RSA) { + cert.rsa = parse_rsa_key(f.get(), exponent); + if (!cert.rsa) { + return false; + } + + LOGI("read key e=%d hash=%d\n", exponent, cert.hash_len); + } else if (cert.key_type == Certificate::KEY_TYPE_EC) { + cert.ec = parse_ec_key(f.get()); + if (!cert.ec) { + return false; + } + } else { + LOGE("Unknown key type %d\n", cert.key_type); + return false; + } + + // if the line ends in a comma, this file has more keys. + int ch = fgetc(f.get()); + if (ch == ',') { + // more keys to come. + continue; + } else if (ch == EOF) { + break; + } else { + LOGE("unexpected character between keys\n"); + return false; + } + } + + return true; +}
diff --git a/recovery/verifier.h b/recovery/verifier.h new file mode 100644 index 0000000..58083fe --- /dev/null +++ b/recovery/verifier.h
@@ -0,0 +1,74 @@ +/* + * 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. + */ + +#ifndef _RECOVERY_VERIFIER_H +#define _RECOVERY_VERIFIER_H + +#include <memory> +#include <vector> + +#include <openssl/ec_key.h> +#include <openssl/rsa.h> +#include <openssl/sha.h> + +struct RSADeleter { + void operator()(RSA* rsa) { + RSA_free(rsa); + } +}; + +struct ECKEYDeleter { + void operator()(EC_KEY* ec_key) { + EC_KEY_free(ec_key); + } +}; + +struct Certificate { + typedef enum { + KEY_TYPE_RSA, + KEY_TYPE_EC, + } KeyType; + + Certificate(int hash_len_, + KeyType key_type_, + std::unique_ptr<RSA, RSADeleter>&& rsa_, + std::unique_ptr<EC_KEY, ECKEYDeleter>&& ec_) + : hash_len(hash_len_), + key_type(key_type_), + rsa(std::move(rsa_)), + ec(std::move(ec_)) {} + + // SHA_DIGEST_LENGTH (SHA-1) or SHA256_DIGEST_LENGTH (SHA-256) + int hash_len; + KeyType key_type; + std::unique_ptr<RSA, RSADeleter> rsa; + std::unique_ptr<EC_KEY, ECKEYDeleter> ec; +}; + +/* addr and length define a an update package file that has been + * loaded (or mmap'ed, or whatever) into memory. Verify that the file + * is signed and the signature matches one of the given keys. Return + * one of the constants below. + */ +int verify_file(unsigned char* addr, size_t length, + const std::vector<Certificate>& keys); + +bool load_keys(const char* filename, std::vector<Certificate>& certs); + +#define VERIFY_SUCCESS 0 +#define VERIFY_FAILURE 1 + +#endif /* _RECOVERY_VERIFIER_H */
diff --git a/recovery/wear_ui.cpp b/recovery/wear_ui.cpp new file mode 100644 index 0000000..48278ff --- /dev/null +++ b/recovery/wear_ui.cpp
@@ -0,0 +1,655 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include <vector> + +#include "common.h" +#include "device.h" +#include "minui/minui.h" +#include "wear_ui.h" +#include "ui.h" +#include "cutils/properties.h" +#include "android-base/strings.h" + +static int char_width; +static int char_height; + +// 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() : + progress_bar_height(3), + progress_bar_width(200), + progress_bar_y(259), + outer_height(0), + outer_width(0), + menu_unusable_rows(0), + intro_frames(22), + loop_frames(60), + animation_fps(30), + currentIcon(NONE), + intro_done(false), + current_frame(0), + rtl_locale(false), + progressBarType(EMPTY), + progressScopeStart(0), + progressScopeSize(0), + progress(0), + text_cols(0), + text_rows(0), + text_col(0), + text_row(0), + text_top(0), + show_text(false), + show_text_ever(false), + show_menu(false), + menu_items(0), + menu_sel(0) { + + for (size_t i = 0; i < 5; i++) + backgroundIcon[i] = NULL; + + pthread_mutex_init(&updateMutex, NULL); + self = this; +} + +// Draw background frame on the screen. Does not flip pages. +// Should only be called with updateMutex locked. +void WearRecoveryUI::draw_background_locked(Icon icon) +{ + gr_color(0, 0, 0, 255); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + if (icon) { + GRSurface* surface; + if (icon == INSTALLING_UPDATE || icon == ERASING) { + if (!intro_done) { + surface = introFrames[current_frame]; + } else { + surface = loopFrames[current_frame]; + } + } + else { + surface = backgroundIcon[icon]; + } + + 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); + } +} + +// Draw the progress bar (if any) on the screen. Does not flip pages. +// Should only be called with updateMutex locked. +void WearRecoveryUI::draw_progress_locked() +{ + if (currentIcon == ERROR) return; + if (progressBarType != DETERMINATE) return; + + int width = progress_bar_width; + int height = progress_bar_height; + int dx = (gr_fb_width() - width)/2; + int dy = progress_bar_y; + + float p = progressScopeStart + progress * progressScopeSize; + int pos = (int) (p * width); + + gr_color(0x43, 0x43, 0x43, 0xff); + gr_fill(dx, dy, dx + width, dy + height); + + if (pos > 0) { + gr_color(0x02, 0xa8, 0xf3, 255); + if (rtl_locale) { + // Fill the progress bar from right to left. + gr_fill(dx + width - pos, dy, dx + width, dy + height); + } else { + // Fill the progress bar from left to right. + gr_fill(dx, dy, dx + pos, dy + height); + } + } +} + +void WearRecoveryUI::SetColor(UIElement e) { + switch (e) { + case HEADER: + gr_color(247, 0, 6, 255); + break; + case MENU: + case MENU_SEL_BG: + gr_color(0, 106, 157, 255); + break; + case MENU_SEL_FG: + gr_color(255, 255, 255, 255); + break; + case LOG: + gr_color(249, 194, 0, 255); + break; + case TEXT_FILL: + gr_color(0, 0, 0, 160); + break; + default: + gr_color(255, 255, 255, 255); + break; + } +} + +void WearRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) { + gr_text(x, *y, line, bold); + *y += char_height + 4; +} + +void WearRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) { + for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { + DrawTextLine(x, y, lines[i], false); + } +} + +static const char* HEADERS[] = { + "Swipe up/down to move.", + "Swipe left/right to select.", + "", + NULL +}; + +void WearRecoveryUI::draw_screen_locked() +{ + draw_background_locked(currentIcon); + draw_progress_locked(); + char cur_selection_str[50]; + + if (show_text) { + SetColor(TEXT_FILL); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + int y = outer_height; + int x = outer_width; + if (show_menu) { + char recovery_fingerprint[PROPERTY_VALUE_MAX]; + property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, ""); + SetColor(HEADER); + DrawTextLine(x + 4, &y, "Android Recovery", true); + for (auto& chunk: android::base::Split(recovery_fingerprint, ":")) { + DrawTextLine(x +4, &y, chunk.c_str(), false); + } + + // This is actually the help strings. + DrawTextLines(x + 4, &y, HEADERS); + SetColor(HEADER); + DrawTextLines(x + 4, &y, menu_headers_); + + // Show the current menu item number in relation to total number if + // items don't fit on the screen. + if (menu_items > menu_end - menu_start) { + sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items); + gr_text(x+4, y, cur_selection_str, 1); + y += char_height+4; + } + + // Menu begins here + SetColor(MENU); + + for (int i = menu_start; i < menu_end; ++i) { + + if (i == menu_sel) { + // draw the highlight bar + SetColor(MENU_SEL_BG); + gr_fill(x, y-2, gr_fb_width()-x, y+char_height+2); + // white text of selected item + SetColor(MENU_SEL_FG); + if (menu[i][0]) gr_text(x+4, y, menu[i], 1); + SetColor(MENU); + } else { + if (menu[i][0]) gr_text(x+4, y, menu[i], 0); + } + y += char_height+4; + } + SetColor(MENU); + y += 4; + gr_fill(0, y, gr_fb_width(), y+2); + y += 4; + } + + SetColor(LOG); + + // 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; + size_t count = 0; + for (int ty = gr_fb_height() - char_height - outer_height; + ty > y+2 && count < text_rows; + ty -= char_height, ++count) { + gr_text(x+4, ty, text[row], 0); + --row; + if (row < 0) row = text_rows-1; + } + } +} + +void WearRecoveryUI::update_screen_locked() +{ + draw_screen_locked(); + gr_flip(); +} + +// Keeps the progress bar updated, even when the process is otherwise busy. +void* WearRecoveryUI::progress_thread(void *cookie) { + self->progress_loop(); + return NULL; +} + +void WearRecoveryUI::progress_loop() { + double interval = 1.0 / animation_fps; + for (;;) { + double start = now(); + pthread_mutex_lock(&updateMutex); + int redraw = 0; + + if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) + && !show_text) { + if (!intro_done) { + if (current_frame == intro_frames - 1) { + intro_done = true; + current_frame = 0; + } else { + current_frame++; + } + } else { + current_frame = (current_frame + 1) % loop_frames; + } + redraw = 1; + } + + // move the progress bar forward on timed intervals, if configured + int duration = progressScopeDuration; + if (progressBarType == DETERMINATE && duration > 0) { + double elapsed = now() - progressScopeTime; + float p = 1.0 * elapsed / duration; + if (p > 1.0) p = 1.0; + if (p > progress) { + progress = p; + redraw = 1; + } + } + + if (redraw) + update_screen_locked(); + + pthread_mutex_unlock(&updateMutex); + double end = now(); + // minimum of 20ms delay between frames + double delay = interval - (end-start); + if (delay < 0.02) delay = 0.02; + usleep(static_cast<useconds_t>(delay * 1000000)); + } +} + +void WearRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) { + int result = res_create_display_surface(filename, surface); + if (result < 0) { + LOGE("missing bitmap %s\n(Code %d)\n", filename, result); + } +} + +void WearRecoveryUI::Init() +{ + gr_init(); + + gr_font_size(&char_width, &char_height); + + text_col = text_row = 0; + text_rows = (gr_fb_height()) / char_height; + visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height; + if (text_rows > kMaxRows) text_rows = kMaxRows; + text_top = 1; + + text_cols = (gr_fb_width() - (outer_width * 2)) / char_width; + if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1; + + LoadBitmap("icon_installing", &backgroundIcon[INSTALLING_UPDATE]); + backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE]; + LoadBitmap("icon_error", &backgroundIcon[ERROR]); + backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; + + introFrames = (GRSurface**)malloc(intro_frames * sizeof(GRSurface*)); + for (int i = 0; i < intro_frames; ++i) { + char filename[40]; + sprintf(filename, "intro%02d", i); + LoadBitmap(filename, introFrames + i); + } + + loopFrames = (GRSurface**)malloc(loop_frames * sizeof(GRSurface*)); + for (int i = 0; i < loop_frames; ++i) { + char filename[40]; + sprintf(filename, "loop%02d", i); + LoadBitmap(filename, loopFrames + i); + } + + pthread_create(&progress_t, NULL, progress_thread, NULL); + RecoveryUI::Init(); +} + +void WearRecoveryUI::SetLocale(const char* locale) { + if (locale) { + char* lang = strdup(locale); + for (char* p = lang; *p; ++p) { + if (*p == '_') { + *p = '\0'; + break; + } + } + + // A bit cheesy: keep an explicit list of supported languages + // that are RTL. + if (strcmp(lang, "ar") == 0 || // Arabic + strcmp(lang, "fa") == 0 || // Persian (Farsi) + strcmp(lang, "he") == 0 || // Hebrew (new language code) + strcmp(lang, "iw") == 0 || // Hebrew (old language code) + strcmp(lang, "ur") == 0) { // Urdu + rtl_locale = true; + } + free(lang); + } +} + +void WearRecoveryUI::SetBackground(Icon icon) +{ + pthread_mutex_lock(&updateMutex); + currentIcon = icon; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::SetProgressType(ProgressType type) +{ + pthread_mutex_lock(&updateMutex); + if (progressBarType != type) { + progressBarType = type; + } + progressScopeStart = 0; + progressScopeSize = 0; + progress = 0; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::ShowProgress(float portion, float seconds) +{ + pthread_mutex_lock(&updateMutex); + progressBarType = DETERMINATE; + progressScopeStart += progressScopeSize; + progressScopeSize = portion; + progressScopeTime = now(); + progressScopeDuration = seconds; + progress = 0; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::SetProgress(float fraction) +{ + pthread_mutex_lock(&updateMutex); + if (fraction < 0.0) fraction = 0.0; + if (fraction > 1.0) fraction = 1.0; + if (progressBarType == DETERMINATE && fraction > progress) { + // Skip updates that aren't visibly different. + int width = progress_bar_width; + float scale = width * progressScopeSize; + if ((int) (progress * scale) != (int) (fraction * scale)) { + progress = fraction; + update_screen_locked(); + } + } + pthread_mutex_unlock(&updateMutex); +} + +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::StartMenu(const char* const * headers, const char* const * items, + int initial_selection) { + pthread_mutex_lock(&updateMutex); + if (text_rows > 0 && text_cols > 0) { + menu_headers_ = headers; + size_t i = 0; + // "i < text_rows" is removed from the loop termination condition, + // which is different from the one in ScreenRecoveryUI::StartMenu(). + // Because WearRecoveryUI supports scrollable menu, it's fine to have + // more entries than text_rows. The menu may be truncated otherwise. + // Bug: 23752519 + for (; items[i] != nullptr; i++) { + strncpy(menu[i], items[i], text_cols - 1); + menu[i][text_cols - 1] = '\0'; + } + menu_items = i; + show_menu = 1; + menu_sel = initial_selection; + menu_start = 0; + menu_end = visible_text_rows - 1 - menu_unusable_rows; + if (menu_items <= menu_end) + menu_end = menu_items; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +int WearRecoveryUI::SelectMenu(int sel) { + int old_sel; + pthread_mutex_lock(&updateMutex); + if (show_menu > 0) { + old_sel = menu_sel; + menu_sel = sel; + if (menu_sel < 0) menu_sel = 0; + if (menu_sel >= menu_items) menu_sel = menu_items-1; + if (menu_sel < menu_start) { + menu_start--; + menu_end--; + } else if (menu_sel >= menu_end && menu_sel < menu_items) { + menu_end++; + menu_start++; + } + sel = menu_sel; + if (menu_sel != old_sel) update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); + return sel; +} + +void WearRecoveryUI::EndMenu() { + int i; + pthread_mutex_lock(&updateMutex); + if (show_menu > 0 && text_rows > 0 && text_cols > 0) { + show_menu = 0; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} + +bool WearRecoveryUI::IsTextVisible() +{ + pthread_mutex_lock(&updateMutex); + int visible = show_text; + pthread_mutex_unlock(&updateMutex); + return visible; +} + +bool WearRecoveryUI::WasTextEverVisible() +{ + pthread_mutex_lock(&updateMutex); + int ever_visible = show_text_ever; + pthread_mutex_unlock(&updateMutex); + return ever_visible; +} + +void WearRecoveryUI::ShowText(bool visible) +{ + pthread_mutex_lock(&updateMutex); + // Don't show text during ota install or factory reset + if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { + pthread_mutex_unlock(&updateMutex); + return; + } + show_text = visible; + if (show_text) show_text_ever = 1; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void WearRecoveryUI::Redraw() +{ + pthread_mutex_lock(&updateMutex); + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +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::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); + } + pthread_mutex_unlock(&updateMutex); +}
diff --git a/recovery/wear_ui.h b/recovery/wear_ui.h new file mode 100644 index 0000000..63c1b6e --- /dev/null +++ b/recovery/wear_ui.h
@@ -0,0 +1,138 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RECOVERY_WEAR_UI_H +#define RECOVERY_WEAR_UI_H + +#include <pthread.h> +#include <stdio.h> + +#include "ui.h" +#include "minui/minui.h" + +class WearRecoveryUI : public RecoveryUI { + public: + WearRecoveryUI(); + + void Init(); + void SetLocale(const char* locale); + + // overall recovery state ("background image") + void SetBackground(Icon icon); + + // progress indicator + void SetProgressType(ProgressType type); + void ShowProgress(float portion, float seconds); + void SetProgress(float fraction); + + void SetStage(int current, int max); + + // text log + void ShowText(bool visible); + bool IsTextVisible(); + bool WasTextEverVisible(); + + // printing messages + void Print(const char* fmt, ...); + void ShowFile(const char* filename); + void ShowFile(FILE* fp); + + // menu display + void StartMenu(const char* const * headers, const char* const * items, + int initial_selection); + int SelectMenu(int sel); + void EndMenu(); + + void Redraw(); + + enum UIElement { HEADER, MENU, MENU_SEL_BG, MENU_SEL_FG, LOG, TEXT_FILL }; + virtual void SetColor(UIElement e); + + protected: + int progress_bar_height, progress_bar_width; + + // progress bar vertical position, it's centered horizontally + int progress_bar_y; + + // outer of window + int outer_height, outer_width; + + // Unusable rows when displaying the recovery menu, including the lines + // for headers (Android Recovery, build id and etc) and the bottom lines + // that may otherwise go out of the screen. + int menu_unusable_rows; + + // number of intro frames (default: 22) and loop frames (default: 60) + int intro_frames; + int loop_frames; + + // Number of frames per sec (default: 30) for both of intro and loop. + int animation_fps; + + private: + Icon currentIcon; + + bool intro_done; + + int current_frame; + + bool rtl_locale; + + pthread_mutex_t updateMutex; + GRSurface* backgroundIcon[5]; + GRSurface* *introFrames; + GRSurface* *loopFrames; + + ProgressType progressBarType; + + float progressScopeStart, progressScopeSize, progress; + double progressScopeTime, progressScopeDuration; + + static const int kMaxCols = 96; + static const int kMaxRows = 96; + + // Log text overlay, displayed when a magic key is pressed + char text[kMaxRows][kMaxCols]; + size_t text_cols, text_rows; + // Number of text rows seen on screen + int visible_text_rows; + size_t text_col, text_row, text_top; + bool show_text; + bool show_text_ever; // has show_text ever been true? + + char menu[kMaxRows][kMaxCols]; + bool show_menu; + const char* const* menu_headers_; + int menu_items, menu_sel; + int menu_start, menu_end; + + pthread_t progress_t; + + private: + void draw_background_locked(Icon icon); + void draw_progress_locked(); + void draw_screen_locked(); + void update_screen_locked(); + static void* progress_thread(void* cookie); + void progress_loop(); + void LoadBitmap(const char* filename, GRSurface** surface); + void PutChar(char); + void ClearText(); + void DrawTextLine(int x, int* y, const char* line, bool bold); + void DrawTextLines(int x, int* y, const char* const* lines); +}; + +#endif // RECOVERY_WEAR_UI_H