summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk43
-rw-r--r--applypatch/Android.mk64
-rw-r--r--applypatch/Makefile3
-rw-r--r--applypatch/applypatch.cpp107
-rw-r--r--applypatch/bspatch.cpp2
-rw-r--r--applypatch/freecache.cpp2
-rw-r--r--applypatch/imgdiff.cpp22
-rw-r--r--applypatch/imgpatch.cpp2
-rw-r--r--applypatch/include/applypatch/applypatch.h (renamed from applypatch/applypatch.h)12
-rw-r--r--applypatch/libimgpatch.pc6
-rw-r--r--applypatch/main.cpp69
-rw-r--r--bootloader.cpp10
-rw-r--r--etc/init.rc3
-rw-r--r--fuse_sideload.cpp19
-rw-r--r--install.cpp9
-rw-r--r--install.h3
-rw-r--r--minadbd/services.cpp3
-rw-r--r--minui/graphics_fbdev.cpp12
-rw-r--r--minui/resources.cpp6
-rw-r--r--minzip/DirUtil.cpp5
-rw-r--r--minzip/DirUtil.h3
-rw-r--r--minzip/Zip.c24
-rw-r--r--minzip/Zip.h13
-rw-r--r--otafault/Android.mk51
-rw-r--r--otafault/config.cpp69
-rw-r--r--otafault/config.h74
-rw-r--r--otafault/ota_io.cpp200
-rw-r--r--otafault/ota_io.h4
-rw-r--r--otafault/test.cpp6
-rw-r--r--recovery-persist.cpp205
-rw-r--r--recovery-persist.rc3
-rw-r--r--recovery-refresh.cpp128
-rw-r--r--recovery-refresh.rc2
-rw-r--r--recovery.cpp305
-rw-r--r--tests/Android.mk5
-rw-r--r--tests/component/verifier_test.cpp131
-rw-r--r--tests/testdata/test_key_e3.txt1
-rw-r--r--tests/testdata/test_key_ec.txt1
-rw-r--r--tests/testdata/test_key_f4.txt1
-rw-r--r--tests/unit/recovery_test.cpp92
-rw-r--r--tools/dumpkey/Android.mk (renamed from tools/ota/Android.mk)21
-rw-r--r--tools/dumpkey/DumpPublicKey.java270
-rw-r--r--tools/dumpkey/DumpPublicKey.mf1
-rw-r--r--tools/ota/add-property-tag.c141
-rw-r--r--tools/ota/check-lost+found.c145
-rw-r--r--tools/ota/convert-to-bmp.py79
-rw-r--r--tools/recovery_l10n/Android.mk12
-rw-r--r--tools/recovery_l10n/AndroidManifest.xml18
-rw-r--r--tools/recovery_l10n/res/layout/main.xml31
-rw-r--r--tools/recovery_l10n/res/values-af/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-am/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-ar/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-az-rAZ/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-bg/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-bn-rBD/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-ca/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-cs/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-da/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-de/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-el/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-en-rAU/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-en-rGB/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-en-rIN/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-es-rUS/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-es/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-et-rEE/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-eu-rES/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-fa/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-fi/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-fr-rCA/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-fr/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-gl-rES/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-gu-rIN/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-hi/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-hr/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-hu/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-hy-rAM/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-in/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-is-rIS/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-it/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-iw/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-ja/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-ka-rGE/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-kk-rKZ/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-km-rKH/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-kn-rIN/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-ko/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-ky-rKG/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-lo-rLA/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-lt/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-lv/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-mk-rMK/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-ml-rIN/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-mn-rMN/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-mr-rIN/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-ms-rMY/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-my-rMM/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-nb/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-ne-rNP/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-nl/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-pa-rIN/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-pl/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-pt-rBR/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-pt-rPT/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-pt/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-ro/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-ru/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-si-rLK/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-sk/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-sl/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-sq-rAL/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-sr/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-sv/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-sw/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-ta-rIN/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-te-rIN/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-th/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-tl/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-tr/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-uk/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-ur-rPK/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-uz-rUZ/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-vi/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-zh-rCN/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-zh-rHK/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-zh-rTW/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values-zu/strings.xml8
-rw-r--r--tools/recovery_l10n/res/values/strings.xml38
-rw-r--r--tools/recovery_l10n/src/com/android/recovery_l10n/Main.java319
-rw-r--r--uncrypt/uncrypt.cpp129
-rw-r--r--unique_fd.h62
-rw-r--r--updater/Android.mk67
-rw-r--r--updater/blockimg.cpp98
-rw-r--r--updater/install.cpp26
-rw-r--r--updater/updater.cpp12
-rw-r--r--updater/updater.h4
-rw-r--r--verifier.cpp310
-rw-r--r--verifier.h46
138 files changed, 2948 insertions, 1225 deletions
diff --git a/Android.mk b/Android.mk
index 4da34eef5..95e806ff9 100644
--- a/Android.mk
+++ b/Android.mk
@@ -14,18 +14,20 @@
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
LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
-
LOCAL_MODULE := libfusesideload
-
-LOCAL_STATIC_LIBRARIES := libcutils libc libmincrypt
+LOCAL_STATIC_LIBRARIES := libcutils libc libcrypto_static
include $(BUILD_STATIC_LIBRARY)
+# recovery (static executable)
+# ===============================
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
@@ -64,24 +66,29 @@ LOCAL_C_INCLUDES += \
system/core/adb \
LOCAL_STATIC_LIBRARIES := \
+ libbatterymonitor \
libext4_utils_static \
libsparse_static \
libminzip \
libz \
libmtdutils \
- libmincrypt \
libminadbd \
libfusesideload \
libminui \
libpng \
libfs_mgr \
+ libcrypto_utils_static \
+ libcrypto_static \
libbase \
libcutils \
+ libutils \
liblog \
libselinux \
libm \
libc
+LOCAL_HAL_STATIC_LIBRARIES := libhealthd
+
ifeq ($(TARGET_USERIMAGES_USE_EXT4), true)
LOCAL_CFLAGS += -DUSE_EXT4
LOCAL_C_INCLUDES += system/extras/ext4_utils
@@ -96,9 +103,34 @@ 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)
-# All the APIs for testing
+# libverifier (static library)
+# ===============================
include $(CLEAR_VARS)
LOCAL_CLANG := true
LOCAL_MODULE := libverifier
@@ -107,6 +139,7 @@ LOCAL_SRC_FILES := \
asn1_decoder.cpp \
verifier.cpp \
ui.cpp
+LOCAL_STATIC_LIBRARIES := libcrypto_utils_static libcrypto_static
include $(BUILD_STATIC_LIBRARY)
include $(LOCAL_PATH)/minui/Android.mk \
diff --git a/applypatch/Android.mk b/applypatch/Android.mk
index 90a86dcb0..9e64718c1 100644
--- a/applypatch/Android.mk
+++ b/applypatch/Android.mk
@@ -14,59 +14,83 @@
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_SRC_FILES := \
+ applypatch.cpp \
+ bspatch.cpp \
+ freecache.cpp \
+ imgpatch.cpp \
+ utils.cpp
LOCAL_MODULE := libapplypatch
LOCAL_MODULE_TAGS := eng
-LOCAL_C_INCLUDES += bootable/recovery
-LOCAL_STATIC_LIBRARIES += libbase libotafault libmtdutils libcrypto_static libbz libz
-
+LOCAL_C_INCLUDES += \
+ $(LOCAL_PATH)/include \
+ bootable/recovery
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_STATIC_LIBRARIES += \
+ libotafault \
+ libmtdutils \
+ libbase \
+ libcrypto_static \
+ 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 += bootable/recovery
+LOCAL_C_INCLUDES += \
+ $(LOCAL_PATH)/include \
+ bootable/recovery
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
LOCAL_STATIC_LIBRARIES += libcrypto_static libbz libz
-
include $(BUILD_STATIC_LIBRARY)
-ifeq ($(HOST_OS),linux)
+# libimgpatch (host static library)
+# ===============================
include $(CLEAR_VARS)
-
LOCAL_CLANG := true
LOCAL_SRC_FILES := bspatch.cpp imgpatch.cpp utils.cpp
LOCAL_MODULE := libimgpatch
-LOCAL_C_INCLUDES += bootable/recovery
+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_static libbz libz
-
include $(BUILD_HOST_STATIC_LIBRARY)
-endif # HOST_OS == linux
+# 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 libotafault libmtdutils libcrypto_static libbz libedify
+LOCAL_STATIC_LIBRARIES += \
+ libapplypatch \
+ libbase \
+ libedify \
+ libotafault \
+ libminzip \
+ libmtdutils \
+ libcrypto_static \
+ 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 bsdiff.cpp
LOCAL_MODULE := imgdiff
-LOCAL_FORCE_STATIC_EXECUTABLE := true
-LOCAL_C_INCLUDES += external/zlib external/bzip2
LOCAL_STATIC_LIBRARIES += libz libbz
-
+LOCAL_FORCE_STATIC_EXECUTABLE := true
include $(BUILD_HOST_EXECUTABLE)
diff --git a/applypatch/Makefile b/applypatch/Makefile
index fa6298d46..fb4984303 100644
--- a/applypatch/Makefile
+++ b/applypatch/Makefile
@@ -14,7 +14,7 @@
# This file is for building imgdiff in Chrome OS.
-CPPFLAGS += -iquote..
+CPPFLAGS += -iquote.. -Iinclude
CXXFLAGS += -std=c++11 -O3 -Wall -Werror
LDLIBS += -lbz2 -lz
@@ -28,5 +28,6 @@ clean:
imgdiff: imgdiff.o bsdiff.o utils.o
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDLIBS) -o $@ $^
+libimgpatch.a utils.o: CXXFLAGS += -fPIC
libimgpatch.a: imgpatch.o bspatch.o utils.o
${AR} rcs $@ $^
diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp
index 4eec33256..c8594c283 100644
--- a/applypatch/applypatch.cpp
+++ b/applypatch/applypatch.cpp
@@ -31,11 +31,11 @@
#include <android-base/strings.h>
#include "openssl/sha.h"
-#include "applypatch.h"
+#include "applypatch/applypatch.h"
#include "mtdutils/mtdutils.h"
#include "edify/expr.h"
+#include "ota_io.h"
#include "print_sha1.h"
-#include "otafault/ota_io.h"
static int LoadPartitionContents(const char* filename, FileContents* file);
static ssize_t FileSink(const unsigned char* data, ssize_t len, void* token);
@@ -56,8 +56,6 @@ static bool mtd_partitions_scanned = false;
//
// Return 0 on success.
int LoadFileContents(const char* filename, FileContents* file) {
- file->data = NULL;
-
// A special 'filename' beginning with "MTD:" or "EMMC:" means to
// load the contents of a partition.
if (strncmp(filename, "MTD:", 4) == 0 ||
@@ -70,31 +68,22 @@ int LoadFileContents(const char* filename, FileContents* file) {
return -1;
}
- file->size = file->st.st_size;
- file->data = nullptr;
-
- std::unique_ptr<unsigned char, decltype(&free)> data(
- static_cast<unsigned char*>(malloc(file->size)), free);
- if (data == nullptr) {
- printf("failed to allocate memory: %s\n", 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.get(), 1, file->size, f);
- if (bytes_read != static_cast<size_t>(file->size)) {
- printf("short read of \"%s\" (%zu bytes of %zd)\n", filename, bytes_read, file->size);
+ 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 = data.release();
- SHA1(file->data, file->size, file->sha1);
+ file->data = std::move(data);
+ SHA1(file->data.data(), file->data.size(), file->sha1);
return 0;
}
@@ -193,17 +182,17 @@ static int LoadPartitionContents(const char* filename, FileContents* file) {
uint8_t parsed_sha[SHA_DIGEST_LENGTH];
// Allocate enough memory to hold the largest size.
- file->data = static_cast<unsigned char*>(malloc(size[index[pairs-1]]));
- char* p = (char*)file->data;
- file->size = 0; // # bytes read so far
+ 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]] - file->size;
- size_t read = 0;
+ size_t next = size[index[i]] - data_size;
if (next > 0) {
+ size_t read = 0;
switch (type) {
case MTD:
read = mtd_read_data(ctx, p, next);
@@ -216,12 +205,11 @@ static int LoadPartitionContents(const char* filename, FileContents* file) {
if (next != read) {
printf("short read (%zu bytes of %zu) for partition \"%s\"\n",
read, next, partition);
- free(file->data);
- file->data = NULL;
return -1;
}
SHA1_Update(&sha_ctx, p, read);
- file->size += read;
+ data_size += read;
+ p += read;
}
// Duplicate the SHA context and finalize the duplicate so we can
@@ -233,8 +221,6 @@ static int LoadPartitionContents(const char* filename, FileContents* file) {
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);
- free(file->data);
- file->data = NULL;
return -1;
}
@@ -246,8 +232,6 @@ static int LoadPartitionContents(const char* filename, FileContents* file) {
found = true;
break;
}
-
- p += read;
}
switch (type) {
@@ -264,13 +248,13 @@ static int LoadPartitionContents(const char* filename, FileContents* file) {
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);
- free(file->data);
- file->data = NULL;
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;
@@ -289,10 +273,10 @@ int SaveFileContents(const char* filename, const FileContents* file) {
return -1;
}
- ssize_t bytes_written = FileSink(file->data, file->size, &fd);
- if (bytes_written != file->size) {
- printf("short write of \"%s\" (%zd bytes of %zd) (%s)\n",
- filename, bytes_written, file->size, strerror(errno));
+ 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;
}
@@ -543,7 +527,6 @@ int FindMatchingPatch(uint8_t* sha1, char* const * const patch_sha1_str,
int applypatch_check(const char* filename, int num_patches,
char** const patch_sha1_str) {
FileContents file;
- file.data = NULL;
// It's okay to specify no sha1s; the check will pass if the
// LoadFileContents is successful. (Useful for reading
@@ -555,9 +538,6 @@ int applypatch_check(const char* filename, int num_patches,
printf("file \"%s\" doesn't have any of expected "
"sha1 sums; checking cache\n", filename);
- free(file.data);
- file.data = NULL;
-
// 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
@@ -571,12 +551,9 @@ int applypatch_check(const char* filename, int num_patches,
if (FindMatchingPatch(file.sha1, patch_sha1_str, num_patches) < 0) {
printf("cache bits don't match any sha1 for \"%s\"\n", filename);
- free(file.data);
return 1;
}
}
-
- free(file.data);
return 0;
}
@@ -674,8 +651,6 @@ int applypatch(const char* source_filename,
FileContents copy_file;
FileContents source_file;
- copy_file.data = NULL;
- source_file.data = NULL;
const Value* source_patch_value = NULL;
const Value* copy_patch_value = NULL;
@@ -685,22 +660,20 @@ int applypatch(const char* source_filename,
// 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());
- free(source_file.data);
return 0;
}
}
- if (source_file.data == NULL ||
+ 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.
- free(source_file.data);
- source_file.data = NULL;
+ source_file.data.clear();
LoadFileContents(source_filename, &source_file);
}
- if (source_file.data != NULL) {
+ 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];
@@ -708,8 +681,7 @@ int applypatch(const char* source_filename,
}
if (source_patch_value == NULL) {
- free(source_file.data);
- source_file.data = NULL;
+ source_file.data.clear();
printf("source file is bad; trying copy\n");
if (LoadFileContents(CACHE_TEMP_SOURCE, &copy_file) < 0) {
@@ -726,19 +698,14 @@ int applypatch(const char* source_filename,
if (copy_patch_value == NULL) {
// fail.
printf("copy file doesn't match source SHA-1s either\n");
- free(copy_file.data);
return 1;
}
}
- int result = GenerateTarget(&source_file, source_patch_value,
- &copy_file, copy_patch_value,
- source_filename, target_filename,
- target_sha1, target_size, bonus_data);
- free(source_file.data);
- free(copy_file.data);
-
- return result;
+ return GenerateTarget(&source_file, source_patch_value,
+ &copy_file, copy_patch_value,
+ source_filename, target_filename,
+ target_sha1, target_size, bonus_data);
}
/*
@@ -759,7 +726,6 @@ int applypatch_flash(const char* source_filename, const char* target_filename,
}
FileContents source_file;
- source_file.data = NULL;
std::string target_str(target_filename);
std::vector<std::string> pieces = android::base::Split(target_str, ":");
@@ -777,7 +743,6 @@ int applypatch_flash(const char* source_filename, const char* target_filename,
// 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());
- free(source_file.data);
return 0;
}
@@ -787,18 +752,14 @@ int applypatch_flash(const char* source_filename, const char* target_filename,
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());
- free(source_file.data);
return 1;
}
}
- if (WriteToPartition(source_file.data, target_size, target_filename) != 0) {
+ if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) {
printf("write of copied data to %s failed\n", target_filename);
- free(source_file.data);
return 1;
}
-
- free(source_file.data);
return 0;
}
@@ -867,7 +828,7 @@ static int GenerateTarget(FileContents* source_file,
// We still write the original source to cache, in case
// the partition write is interrupted.
- if (MakeFreeSpaceOnCache(source_file->size) < 0) {
+ if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) {
printf("not enough free space on /cache\n");
return 1;
}
@@ -908,7 +869,7 @@ static int GenerateTarget(FileContents* source_file,
return 1;
}
- if (MakeFreeSpaceOnCache(source_file->size) < 0) {
+ if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) {
printf("not enough free space on /cache\n");
return 1;
}
@@ -951,10 +912,10 @@ static int GenerateTarget(FileContents* source_file,
int result;
if (use_bsdiff) {
- result = ApplyBSDiffPatch(source_to_use->data, source_to_use->size,
+ result = ApplyBSDiffPatch(source_to_use->data.data(), source_to_use->data.size(),
patch, 0, sink, token, &ctx);
} else {
- result = ApplyImagePatch(source_to_use->data, source_to_use->size,
+ result = ApplyImagePatch(source_to_use->data.data(), source_to_use->data.size(),
patch, sink, token, &ctx, bonus_data);
}
diff --git a/applypatch/bspatch.cpp b/applypatch/bspatch.cpp
index 1fc1455a6..a4945da28 100644
--- a/applypatch/bspatch.cpp
+++ b/applypatch/bspatch.cpp
@@ -30,7 +30,7 @@
#include <bzlib.h>
#include "openssl/sha.h"
-#include "applypatch.h"
+#include "applypatch/applypatch.h"
void ShowBSDiffLicense() {
puts("The bsdiff library used herein is:\n"
diff --git a/applypatch/freecache.cpp b/applypatch/freecache.cpp
index c84f42797..331cae265 100644
--- a/applypatch/freecache.cpp
+++ b/applypatch/freecache.cpp
@@ -32,7 +32,7 @@
#include <android-base/parseint.h>
#include <android-base/stringprintf.h>
-#include "applypatch.h"
+#include "applypatch/applypatch.h"
static int EliminateOpenFiles(std::set<std::string>* files) {
std::unique_ptr<DIR, decltype(&closedir)> d(opendir("/proc"), closedir);
diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp
index a3c10b40a..2aa4a6862 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -407,7 +407,6 @@ unsigned char* ReadImage(const char* filename,
while (pos < sz) {
unsigned char* p = img+pos;
- bool processed_deflate = false;
if (sz - pos >= 4 &&
p[0] == 0x1f && p[1] == 0x8b &&
p[2] == 0x08 && // deflate compression
@@ -461,28 +460,27 @@ unsigned char* ReadImage(const char* filename,
strm.next_out = curr->data + curr->len;
ret = inflate(&strm, Z_NO_FLUSH);
if (ret < 0) {
- if (!processed_deflate) {
- // This is the first chunk, assume that it's just a spurious
- // gzip header instead of a real one.
- break;
- }
- printf("Error: inflate failed [%s] at file offset [%zu]\n"
- "imgdiff only supports gzip kernel compression,"
- " did you try CONFIG_KERNEL_LZO?\n",
+ printf("Warning: inflate failed [%s] at offset [%zu],"
+ " treating as a normal chunk\n",
strm.msg, chunk_offset);
- free(img);
- return NULL;
+ break;
}
curr->len = allocated - strm.avail_out;
if (strm.avail_out == 0) {
allocated *= 2;
curr->data = reinterpret_cast<unsigned char*>(realloc(curr->data, allocated));
}
- processed_deflate = true;
} 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;
diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp
index 0ab995b30..4251c0120 100644
--- a/applypatch/imgpatch.cpp
+++ b/applypatch/imgpatch.cpp
@@ -28,7 +28,7 @@
#include "zlib.h"
#include "openssl/sha.h"
-#include "applypatch.h"
+#include "applypatch/applypatch.h"
#include "imgdiff.h"
#include "utils.h"
diff --git a/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h
index 096596f03..9ee39d293 100644
--- a/applypatch/applypatch.h
+++ b/applypatch/include/applypatch/applypatch.h
@@ -25,17 +25,11 @@
#include "openssl/sha.h"
#include "edify/expr.h"
-typedef struct _Patch {
+struct FileContents {
uint8_t sha1[SHA_DIGEST_LENGTH];
- const char* patch_filename;
-} Patch;
-
-typedef struct _FileContents {
- uint8_t sha1[SHA_DIGEST_LENGTH];
- unsigned char* data;
- ssize_t size;
+ std::vector<unsigned char> data;
struct stat st;
-} FileContents;
+};
// 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
diff --git a/applypatch/libimgpatch.pc b/applypatch/libimgpatch.pc
new file mode 100644
index 000000000..e5002934f
--- /dev/null
+++ b/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/applypatch/main.cpp b/applypatch/main.cpp
index 7606d5d3c..0ff8cbf9a 100644
--- a/applypatch/main.cpp
+++ b/applypatch/main.cpp
@@ -22,7 +22,7 @@
#include <memory>
#include <vector>
-#include "applypatch.h"
+#include "applypatch/applypatch.h"
#include "edify/expr.h"
#include "openssl/sha.h"
@@ -46,40 +46,32 @@ static int SpaceMode(int argc, char** argv) {
return CacheSizeCheck(bytes);
}
-// Parse arguments (which should be of the form "<sha1>" or
-// "<sha1>:<filename>" into the new parallel arrays *sha1s and
-// *patches (loading file contents into the patches). Returns true on
+// 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<std::unique_ptr<Value, decltype(&FreeValue)>>* patches) {
+ std::vector<FileContents>* files) {
uint8_t digest[SHA_DIGEST_LENGTH];
for (int i = 0; i < argc; ++i) {
char* colon = strchr(argv[i], ':');
- if (colon != NULL) {
- *colon = '\0';
- ++colon;
+ 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]);
- if (colon == NULL) {
- patches->emplace_back(nullptr, FreeValue);
- } else {
- FileContents fc;
- if (LoadFileContents(colon, &fc) != 0) {
- return false;
- }
- std::unique_ptr<Value, decltype(&FreeValue)> value(new Value, FreeValue);
- value->type = VAL_BLOB;
- value->size = fc.size;
- value->data = reinterpret_cast<char*>(fc.data);
- patches->push_back(std::move(value));
+ FileContents fc;
+ if (LoadFileContents(colon, &fc) != 0) {
+ return false;
}
+ files->push_back(std::move(fc));
}
return true;
}
@@ -90,17 +82,19 @@ static int FlashMode(const char* src_filename, const char* tgt_filename,
}
static int PatchMode(int argc, char** argv) {
- std::unique_ptr<Value, decltype(&FreeValue)> bonus(nullptr, FreeValue);
+ FileContents bonusFc;
+ Value bonusValue;
+ Value* bonus = nullptr;
+
if (argc >= 3 && strcmp(argv[1], "-b") == 0) {
- FileContents fc;
- if (LoadFileContents(argv[2], &fc) != 0) {
+ if (LoadFileContents(argv[2], &bonusFc) != 0) {
printf("failed to load bonus file %s\n", argv[2]);
return 1;
}
- bonus.reset(new Value);
+ bonus = &bonusValue;
bonus->type = VAL_BLOB;
- bonus->size = fc.size;
- bonus->data = reinterpret_cast<char*>(fc.data);
+ bonus->size = bonusFc.data.size();
+ bonus->data = reinterpret_cast<char*>(bonusFc.data.data());
argc -= 2;
argv += 2;
}
@@ -118,28 +112,29 @@ static int PatchMode(int argc, char** argv) {
// If no <src-sha1>:<patch> is provided, it is in flash mode.
if (argc == 5) {
- if (bonus != NULL) {
+ 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<std::unique_ptr<Value, decltype(&FreeValue)>> patches;
- if (!ParsePatchArgs(argc-5, argv+5, &sha1s, &patches)) {
+ std::vector<FileContents> files;
+ if (!ParsePatchArgs(argc-5, argv+5, &sha1s, &files)) {
printf("failed to parse patch args\n");
return 1;
}
-
- std::vector<Value*> patch_ptrs;
- for (const auto& p : patches) {
- patch_ptrs.push_back(p.get());
+ 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.get());
+ patch_ptrs.data(), bonus);
}
// This program applies binary patches to files in a way that is safe
diff --git a/bootloader.cpp b/bootloader.cpp
index d80c5e793..a32f8b4c6 100644
--- a/bootloader.cpp
+++ b/bootloader.cpp
@@ -29,7 +29,7 @@
#include "common.h"
#include "mtdutils/mtdutils.h"
#include "roots.h"
-#include "unique_fd.h"
+#include <android-base/unique_fd.h>
static int get_bootloader_message_mtd(bootloader_message* out, const Volume* v);
static int set_bootloader_message_mtd(const bootloader_message* in, const Volume* v);
@@ -191,8 +191,8 @@ static int get_bootloader_message_block(bootloader_message* out,
static int set_bootloader_message_block(const bootloader_message* in,
const Volume* v) {
wait_for_device(v->blk_device);
- unique_fd fd(open(v->blk_device, O_WRONLY | O_SYNC));
- if (fd.get() == -1) {
+ 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;
}
@@ -201,7 +201,7 @@ static int set_bootloader_message_block(const bootloader_message* in,
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.get(), start + written, total - written));
+ 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));
@@ -210,7 +210,7 @@ static int set_bootloader_message_block(const bootloader_message* in,
written += wrote;
}
- if (fsync(fd.get()) == -1) {
+ if (fsync(fd) == -1) {
LOGE("failed to fsync \"%s\": %s\n", v->blk_device, strerror(errno));
return -1;
}
diff --git a/etc/init.rc b/etc/init.rc
index dc1865986..5915b8d80 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -1,6 +1,9 @@
import /init.recovery.${ro.hardware}.rc
on early-init
+ # Set the security context of /postinstall if present.
+ restorecon /postinstall
+
start ueventd
start healthd
diff --git a/fuse_sideload.cpp b/fuse_sideload.cpp
index 9c3e75f89..1725e8823 100644
--- a/fuse_sideload.cpp
+++ b/fuse_sideload.cpp
@@ -61,7 +61,8 @@
#include <sys/uio.h>
#include <unistd.h>
-#include "mincrypt/sha256.h"
+#include <openssl/sha.h>
+
#include "fuse_sideload.h"
#define PACKAGE_FILE_ID (FUSE_ROOT_ID+1)
@@ -269,22 +270,22 @@ static int fetch_block(struct fuse_data* fd, uint32_t block) {
// block).
// - Otherwise, return -EINVAL for the read.
- uint8_t hash[SHA256_DIGEST_SIZE];
- SHA256_hash(fd->block_data, fd->block_size, hash);
- uint8_t* blockhash = fd->hashes + block * SHA256_DIGEST_SIZE;
- if (memcmp(hash, blockhash, SHA256_DIGEST_SIZE) == 0) {
+ 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_SIZE; ++i) {
+ for (i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
if (blockhash[i] != 0) {
fd->curr_block = -1;
return -EIO;
}
}
- memcpy(blockhash, hash, SHA256_DIGEST_SIZE);
+ memcpy(blockhash, hash, SHA256_DIGEST_LENGTH);
return 0;
}
@@ -393,10 +394,10 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie,
goto done;
}
- fd.hashes = (uint8_t*)calloc(fd.file_blocks, SHA256_DIGEST_SIZE);
+ 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_SIZE);
+ fd.file_blocks * SHA256_DIGEST_LENGTH);
result = -1;
goto done;
}
diff --git a/install.cpp b/install.cpp
index 33c1f5498..a7b59c3e7 100644
--- a/install.cpp
+++ b/install.cpp
@@ -27,15 +27,14 @@
#include "common.h"
#include "install.h"
-#include "mincrypt/rsa.h"
#include "minui/minui.h"
#include "minzip/SysUtil.h"
#include "minzip/Zip.h"
#include "mtdutils/mounts.h"
#include "mtdutils/mtdutils.h"
#include "roots.h"
-#include "verifier.h"
#include "ui.h"
+#include "verifier.h"
extern RecoveryUI* ui;
@@ -144,6 +143,7 @@ try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) {
close(pipefd[1]);
*wipe_cache = false;
+ bool retry_update = false;
char buffer[1024];
FILE* from_child = fdopen(pipefd[0], "r");
@@ -180,6 +180,8 @@ try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) {
// 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);
}
@@ -188,6 +190,9 @@ try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) {
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;
diff --git a/install.h b/install.h
index 680499db3..fd08e3c49 100644
--- a/install.h
+++ b/install.h
@@ -23,7 +23,8 @@
extern "C" {
#endif
-enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE };
+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.
diff --git a/minadbd/services.cpp b/minadbd/services.cpp
index d25648fb4..658a43f36 100644
--- a/minadbd/services.cpp
+++ b/minadbd/services.cpp
@@ -35,11 +35,10 @@ struct stinfo {
void *cookie;
};
-void* service_bootstrap_func(void* x) {
+void service_bootstrap_func(void* x) {
stinfo* sti = reinterpret_cast<stinfo*>(x);
sti->func(sti->fd, sti->cookie);
free(sti);
- return 0;
}
static void sideload_host_service(int sfd, void* data) {
diff --git a/minui/graphics_fbdev.cpp b/minui/graphics_fbdev.cpp
index 997e9cac2..0788f7552 100644
--- a/minui/graphics_fbdev.cpp
+++ b/minui/graphics_fbdev.cpp
@@ -176,18 +176,6 @@ static GRSurface* fbdev_init(minui_backend* backend) {
static GRSurface* fbdev_flip(minui_backend* backend __unused) {
if (double_buffered) {
-#if defined(RECOVERY_BGRA)
- // In case of BGRA, do some byte swapping
- unsigned int idx;
- unsigned char tmp;
- unsigned char* ucfb_vaddr = (unsigned char*)gr_draw->data;
- for (idx = 0 ; idx < (gr_draw->height * gr_draw->row_bytes);
- idx += 4) {
- tmp = ucfb_vaddr[idx];
- ucfb_vaddr[idx ] = ucfb_vaddr[idx + 2];
- ucfb_vaddr[idx + 2] = tmp;
- }
-#endif
// Change gr_draw to point to the buffer currently displayed,
// then flip the driver so we're displaying the other buffer
// instead.
diff --git a/minui/resources.cpp b/minui/resources.cpp
index fdbe554fe..8489d60ef 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -283,7 +283,7 @@ int res_create_multi_display_surface(const char* name, int* frames, int* fps,
goto exit;
}
- surface = reinterpret_cast<GRSurface**>(malloc(*frames * sizeof(GRSurface*)));
+ surface = reinterpret_cast<GRSurface**>(calloc(*frames, sizeof(GRSurface*)));
if (surface == NULL) {
result = -8;
goto exit;
@@ -318,7 +318,7 @@ exit:
if (result < 0) {
if (surface) {
for (int i = 0; i < *frames; ++i) {
- if (surface[i]) free(surface[i]);
+ free(surface[i]);
}
free(surface);
}
@@ -421,7 +421,7 @@ int res_create_localized_alpha_surface(const char* name,
png_read_row(png_ptr, row.data(), NULL);
int w = (row[1] << 8) | row[0];
int h = (row[3] << 8) | row[2];
- int len = row[4];
+ __unused int len = row[4];
char* loc = reinterpret_cast<char*>(&row[5]);
if (y+1+h >= height || matches_locale(loc, locale)) {
diff --git a/minzip/DirUtil.cpp b/minzip/DirUtil.cpp
index 823b6ed2f..e08e360c0 100644
--- a/minzip/DirUtil.cpp
+++ b/minzip/DirUtil.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include "DirUtil.h"
+
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
@@ -26,7 +28,8 @@
#include <string>
-#include "DirUtil.h"
+#include <selinux/label.h>
+#include <selinux/selinux.h>
typedef enum { DMISSING, DDIR, DILLEGAL } DirStatus;
diff --git a/minzip/DirUtil.h b/minzip/DirUtil.h
index 85a00128b..85b83c387 100644
--- a/minzip/DirUtil.h
+++ b/minzip/DirUtil.h
@@ -24,8 +24,7 @@
extern "C" {
#endif
-#include <selinux/selinux.h>
-#include <selinux/label.h>
+struct selabel_handle;
/* Like "mkdir -p", try to guarantee that all directories
* specified in path are present, creating as many directories
diff --git a/minzip/Zip.c b/minzip/Zip.c
index bdb565c64..9f550f8b4 100644
--- a/minzip/Zip.c
+++ b/minzip/Zip.c
@@ -23,6 +23,9 @@
#undef NDEBUG // do this after including Log.h
#include <assert.h>
+#include <selinux/label.h>
+#include <selinux/selinux.h>
+
#define SORT_ENTRIES 1
/*
@@ -91,7 +94,7 @@ enum {
static void dumpEntry(const ZipEntry* pEntry)
{
LOGI(" %p '%.*s'\n", pEntry->fileName,pEntry->fileNameLen,pEntry->fileName);
- LOGI(" off=%ld comp=%ld uncomp=%ld how=%d\n", pEntry->offset,
+ LOGI(" off=%u comp=%u uncomp=%u how=%d\n", pEntry->offset,
pEntry->compLen, pEntry->uncompLen, pEntry->compression);
}
#endif
@@ -505,13 +508,11 @@ static bool processDeflatedEntry(const ZipArchive *pArchive,
const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
void *cookie)
{
- long result = -1;
+ bool success = false;
+ unsigned long totalOut = 0;
unsigned char procBuf[32 * 1024];
z_stream zstream;
int zerr;
- long compRemaining;
-
- compRemaining = pEntry->compLen;
/*
* Initialize the zlib stream.
@@ -572,16 +573,17 @@ static bool processDeflatedEntry(const ZipArchive *pArchive,
assert(zerr == Z_STREAM_END); /* other errors should've been caught */
// success!
- result = zstream.total_out;
+ totalOut = zstream.total_out;
+ success = true;
z_bail:
inflateEnd(&zstream); /* free up any allocated structures */
bail:
- if (result != pEntry->uncompLen) {
- if (result != -1) // error already shown?
- LOGW("Size mismatch on inflated file (%ld vs %ld)\n",
- result, pEntry->uncompLen);
+ 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;
@@ -759,7 +761,7 @@ static const char *targetEntryPath(MzPathHelper *helper, ZipEntry *pEntry)
*/
needLen = helper->targetDirLen + 1 +
pEntry->fileNameLen - helper->zipDirLen + 1;
- if (needLen > helper->bufLen) {
+ if (firstTime || needLen > helper->bufLen) {
char *newBuf;
needLen *= 2;
diff --git a/minzip/Zip.h b/minzip/Zip.h
index 86d8db597..c932c1178 100644
--- a/minzip/Zip.h
+++ b/minzip/Zip.h
@@ -18,8 +18,7 @@
extern "C" {
#endif
-#include <selinux/selinux.h>
-#include <selinux/label.h>
+struct selabel_handle;
/*
* One entry in the Zip archive. Treat this as opaque -- use accessors below.
@@ -32,9 +31,9 @@ extern "C" {
typedef struct ZipEntry {
unsigned int fileNameLen;
const char* fileName; // not null-terminated
- long offset;
- long compLen;
- long uncompLen;
+ uint32_t offset;
+ uint32_t compLen;
+ uint32_t uncompLen;
int compression;
long modTime;
long crc32;
@@ -85,10 +84,10 @@ void mzCloseZipArchive(ZipArchive* pArchive);
const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive,
const char* entryName);
-INLINE long mzGetZipEntryOffset(const ZipEntry* pEntry) {
+INLINE uint32_t mzGetZipEntryOffset(const ZipEntry* pEntry) {
return pEntry->offset;
}
-INLINE long mzGetZipEntryUncompLen(const ZipEntry* pEntry) {
+INLINE uint32_t mzGetZipEntryUncompLen(const ZipEntry* pEntry) {
return pEntry->uncompLen;
}
diff --git a/otafault/Android.mk b/otafault/Android.mk
index 75617a146..50e385efb 100644
--- a/otafault/Android.mk
+++ b/otafault/Android.mk
@@ -1,10 +1,10 @@
-# Copyright 2015 The ANdroid Open Source Project
+# 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
+# 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,
@@ -14,45 +14,36 @@
LOCAL_PATH := $(call my-dir)
-empty :=
-space := $(empty) $(empty)
-comma := ,
-
-ifneq ($(TARGET_INJECT_FAULTS),)
-TARGET_INJECT_FAULTS := $(subst $(comma),$(space),$(strip $(TARGET_INJECT_FAULTS)))
-endif
-
+# otafault (static library)
+# ===============================
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := ota_io.cpp
-LOCAL_MODULE_TAGS := eng
+otafault_static_libs := \
+ libbase \
+ libminzip \
+ libz \
+ libselinux
+
+LOCAL_SRC_FILES := config.cpp ota_io.cpp
LOCAL_MODULE := libotafault
LOCAL_CLANG := true
-
-ifneq ($(TARGET_INJECT_FAULTS),)
-$(foreach ft,$(TARGET_INJECT_FAULTS),\
- $(eval LOCAL_CFLAGS += -DTARGET_$(ft)_FAULT=$(TARGET_$(ft)_FAULT_FILE)))
-LOCAL_CFLAGS += -Wno-unused-parameter
-LOCAL_CFLAGS += -DTARGET_INJECT_FAULTS
-endif
-
-LOCAL_STATIC_LIBRARIES := libc
+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 := ota_io.cpp test.cpp
+LOCAL_SRC_FILES := config.cpp ota_io.cpp test.cpp
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE := otafault_test
-LOCAL_STATIC_LIBRARIES := libc
+LOCAL_STATIC_LIBRARIES := \
+ libotafault \
+ $(otafault_static_libs)
+LOCAL_C_INCLUDES := bootable/recovery
LOCAL_FORCE_STATIC_EXECUTABLE := true
-LOCAL_CFLAGS += -Wno-unused-parameter -Wno-writable-strings
-
-ifneq ($(TARGET_INJECT_FAULTS),)
-$(foreach ft,$(TARGET_INJECT_FAULTS),\
- $(eval LOCAL_CFLAGS += -DTARGET_$(ft)_FAULT=$(TARGET_$(ft)_FAULT_FILE)))
-LOCAL_CFLAGS += -DTARGET_INJECT_FAULTS
-endif
include $(BUILD_EXECUTABLE)
diff --git a/otafault/config.cpp b/otafault/config.cpp
new file mode 100644
index 000000000..b4567392d
--- /dev/null
+++ b/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/otafault/config.h b/otafault/config.h
new file mode 100644
index 000000000..4430be3fb
--- /dev/null
+++ b/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/otafault/ota_io.cpp b/otafault/ota_io.cpp
index 02e80f9bf..dd805e56e 100644
--- a/otafault/ota_io.cpp
+++ b/otafault/ota_io.cpp
@@ -14,9 +14,7 @@
* limitations under the License.
*/
-#if defined (TARGET_INJECT_FAULTS)
#include <map>
-#endif
#include <errno.h>
#include <fcntl.h>
@@ -24,137 +22,155 @@
#include <sys/stat.h>
#include <unistd.h>
+#include "config.h"
#include "ota_io.h"
-#if defined (TARGET_INJECT_FAULTS)
-static std::map<int, const char*> FilenameCache;
-static std::string FaultFileName =
-#if defined (TARGET_READ_FAULT)
- TARGET_READ_FAULT;
-#elif defined (TARGET_WRITE_FAULT)
- TARGET_WRITE_FAULT;
-#elif defined (TARGET_FSYNC_FAULT)
- TARGET_FSYNC_FAULT;
-#endif // defined (TARGET_READ_FAULT)
-#endif // defined (TARGET_INJECT_FAULTS)
+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, 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) {
-#if defined (TARGET_INJECT_FAULTS)
// Let the caller handle errors; we do not care if open succeeds or fails
int fd = open(path, oflags);
- FilenameCache[fd] = path;
+ filename_cache[fd] = path;
return fd;
-#else
- return open(path, oflags);
-#endif
}
int ota_open(const char* path, int oflags, mode_t mode) {
-#if defined (TARGET_INJECT_FAULTS)
int fd = open(path, oflags, mode);
- FilenameCache[fd] = path;
- return fd;
-#else
- return open(path, oflags, mode);
-#endif
-}
+ filename_cache[fd] = path;
+ return fd; }
FILE* ota_fopen(const char* path, const char* mode) {
-#if defined (TARGET_INJECT_FAULTS)
FILE* fh = fopen(path, mode);
- FilenameCache[(intptr_t)fh] = path;
+ filename_cache[(intptr_t)fh] = path;
return fh;
-#else
- return fopen(path, mode);
-#endif
}
int ota_close(int fd) {
-#if defined (TARGET_INJECT_FAULTS)
- // descriptors can be reused, so make sure not to leave them in the cahce
- FilenameCache.erase(fd);
-#endif
+ // 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) {
-#if defined (TARGET_INJECT_FAULTS)
- FilenameCache.erase((intptr_t)fh);
-#endif
+ filename_cache.erase((intptr_t)fh);
return fclose(fh);
}
size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream) {
-#if defined (TARGET_READ_FAULT)
- if (FilenameCache.find((intptr_t)stream) != FilenameCache.end()
- && FilenameCache[(intptr_t)stream] == FaultFileName) {
- FaultFileName = "";
- errno = EIO;
- return 0;
- } else {
- return fread(ptr, size, nitems, 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;
}
-#else
- return fread(ptr, size, nitems, stream);
-#endif
+ return status;
}
ssize_t ota_read(int fd, void* buf, size_t nbyte) {
-#if defined (TARGET_READ_FAULT)
- if (FilenameCache.find(fd) != FilenameCache.end()
- && FilenameCache[fd] == FaultFileName) {
- FaultFileName = "";
- errno = EIO;
- return -1;
- } else {
- return read(fd, buf, 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;
+ }
}
-#else
- return read(fd, buf, nbyte);
-#endif
+ 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 defined (TARGET_WRITE_FAULT)
- if (FilenameCache.find((intptr_t)stream) != FilenameCache.end()
- && FilenameCache[(intptr_t)stream] == FaultFileName) {
- FaultFileName = "";
- errno = EIO;
- return 0;
- } else {
- return fwrite(ptr, size, count, 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;
}
-#else
- return fwrite(ptr, size, count, stream);
-#endif
+ return status;
}
ssize_t ota_write(int fd, const void* buf, size_t nbyte) {
-#if defined (TARGET_WRITE_FAULT)
- if (FilenameCache.find(fd) != FilenameCache.end()
- && FilenameCache[fd] == FaultFileName) {
- FaultFileName = "";
- errno = EIO;
- return -1;
- } else {
- return write(fd, buf, 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;
+ }
}
-#else
- return write(fd, buf, nbyte);
-#endif
+ ssize_t status = write(fd, buf, nbyte);
+ if (status == -1 && errno == EIO) {
+ have_eio_error = true;
+ }
+ return status;
}
int ota_fsync(int fd) {
-#if defined (TARGET_FSYNC_FAULT)
- if (FilenameCache.find(fd) != FilenameCache.end()
- && FilenameCache[fd] == FaultFileName) {
- FaultFileName = "";
- errno = EIO;
- return -1;
- } else {
- return fsync(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;
}
-#else
- return fsync(fd);
-#endif
+ return status;
}
+
diff --git a/otafault/ota_io.h b/otafault/ota_io.h
index 641a5ae0a..84187a76e 100644
--- a/otafault/ota_io.h
+++ b/otafault/ota_io.h
@@ -26,6 +26,10 @@
#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);
diff --git a/otafault/test.cpp b/otafault/test.cpp
index a0f731517..6514782bf 100644
--- a/otafault/test.cpp
+++ b/otafault/test.cpp
@@ -17,16 +17,18 @@
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
+#include <unistd.h>
#include "ota_io.h"
-int main(int argc, char **argv) {
+int main(int /* argc */, char** /* argv */) {
int fd = open("testdata/test.file", O_RDWR);
char buf[8];
- char *out = "321";
+ 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-persist.cpp b/recovery-persist.cpp
new file mode 100644
index 000000000..25df03f47
--- /dev/null
+++ b/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-persist.rc b/recovery-persist.rc
new file mode 100644
index 000000000..6761627d5
--- /dev/null
+++ b/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-refresh.cpp b/recovery-refresh.cpp
new file mode 100644
index 000000000..70adc70ee
--- /dev/null
+++ b/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 {
+ unsigned long long 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-refresh.rc b/recovery-refresh.rc
new file mode 100644
index 000000000..14b05cca4
--- /dev/null
+++ b/recovery-refresh.rc
@@ -0,0 +1,2 @@
+on post-fs
+ exec - system log -- /system/bin/recovery-refresh
diff --git a/recovery.cpp b/recovery.cpp
index 17e9eb66f..169413ad5 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -35,10 +35,17 @@
#include <chrono>
#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 <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"
@@ -56,8 +63,8 @@
struct selabel_handle *sehandle;
static const struct option OPTIONS[] = {
- { "send_intent", required_argument, NULL, 'i' },
{ "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' },
@@ -73,7 +80,6 @@ static const struct option OPTIONS[] = {
static const char *CACHE_LOG_DIR = "/cache/recovery";
static const char *COMMAND_FILE = "/cache/recovery/command";
-static const char *INTENT_FILE = "/cache/recovery/intent";
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";
@@ -84,21 +90,27 @@ 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;
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)
- * /cache/recovery/intent - OUTPUT - intent that was passed in
*
* The arguments which may be supplied in the recovery.command file:
- * --send_intent=anystring - write the text out to recovery.intent
* --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
@@ -302,8 +314,8 @@ get_args(int *argc, char ***argv) {
}
}
- // --- if that doesn't work, try the command file
- if (*argc <= 1) {
+ // --- 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;
@@ -366,6 +378,18 @@ static void save_kernel_log(const char* destination) {
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 long tmplog_offset = 0;
@@ -433,6 +457,15 @@ static void copy_logs() {
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.
@@ -450,32 +483,24 @@ static void copy_logs() {
}
// 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), and
-// record any intent we were asked to communicate back to the system.
-// this function is idempotent: call it as many times as you like.
+// 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(const char *send_intent) {
- // By this point, we're ready to return to the main system...
- if (send_intent != NULL) {
- FILE *fp = fopen_path(INTENT_FILE, "w");
- if (fp == NULL) {
- LOGE("Can't open %s\n", INTENT_FILE);
- } else {
- fputs(send_intent, fp);
- check_and_fclose(fp, INTENT_FILE);
- }
- }
-
+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) {
- LOGI("Saving locale \"%s\"\n", locale);
- FILE* fp = fopen_path(LOCALE_FILE, "w");
- fwrite(locale, 1, strlen(locale), fp);
- fflush(fp);
- fsync(fileno(fp));
- check_and_fclose(fp, LOCALE_FILE);
+ 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");
+ fwrite(locale, 1, len, fp);
+ fflush(fp);
+ fsync(fileno(fp));
+ check_and_fclose(fp, LOCALE_FILE);
+ }
}
copy_logs();
@@ -486,12 +511,13 @@ finish_recovery(const char *send_intent) {
set_bootloader_message(&boot);
// Remove the command file, so recovery won't repeat indefinitely.
- if (ensure_path_mounted(COMMAND_FILE) != 0 ||
- (unlink(COMMAND_FILE) && errno != ENOENT)) {
- LOGW("Can't unlink %s\n", COMMAND_FILE);
+ 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);
}
- ensure_path_unmounted(CACHE_ROOT);
sync(); // For good measure.
}
@@ -760,7 +786,7 @@ static bool wipe_data(int should_confirm, Device* device) {
bool success =
device->PreWipeData() &&
erase_volume("/data") &&
- erase_volume("/cache") &&
+ (has_cache ? erase_volume("/cache") : true) &&
device->PostWipeData();
ui->Print("Data wipe %s.\n", success ? "complete" : "failed");
return success;
@@ -768,6 +794,11 @@ static bool wipe_data(int should_confirm, Device* device) {
// 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;
}
@@ -781,6 +812,11 @@ static bool wipe_cache(bool should_confirm, Device* device) {
}
static void choose_recovery_file(Device* device) {
+ if (!has_cache) {
+ ui->Print("No /cache partition found.\n");
+ return;
+ }
+
// "Back" + KEEP_LOG_COUNT * 2 + terminating nullptr entry
char* entries[1 + KEEP_LOG_COUNT * 2 + 1];
memset(entries, 0, sizeof(entries));
@@ -919,7 +955,7 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) {
static Device::BuiltinAction
prompt_and_wait(Device* device, int status) {
for (;;) {
- finish_recovery(NULL);
+ finish_recovery();
switch (status) {
case INSTALL_SUCCESS:
case INSTALL_NONE:
@@ -1058,8 +1094,146 @@ ui_print(const char* format, ...) {
}
}
-int
-main(int argc, char **argv) {
+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 {
+ unsigned long long 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
@@ -1081,9 +1255,10 @@ main(int argc, char **argv) {
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 *send_intent = NULL;
const char *update_package = NULL;
bool should_wipe_data = false;
bool should_wipe_cache = false;
@@ -1092,11 +1267,12 @@ main(int argc, char **argv) {
bool sideload_auto_reboot = false;
bool just_exit = false;
bool shutdown_after = false;
+ int retry_count = 0;
int arg;
while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
switch (arg) {
- case 'i': send_intent = optarg; break;
+ 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;
@@ -1121,7 +1297,7 @@ main(int argc, char **argv) {
}
}
- if (locale == NULL) {
+ if (locale == nullptr && has_cache) {
load_locale_from_cache();
}
printf("locale is [%s]\n", locale);
@@ -1189,18 +1365,42 @@ main(int argc, char **argv) {
int status = INSTALL_SUCCESS;
if (update_package != NULL) {
- 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");
-
- // 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);
+ 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) {
@@ -1249,7 +1449,8 @@ main(int argc, char **argv) {
}
Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
- if ((status != INSTALL_SUCCESS && !sideload_auto_reboot) || ui->IsTextVisible()) {
+ 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;
@@ -1257,7 +1458,7 @@ main(int argc, char **argv) {
}
// Save logs and clean up before rebooting or shutting down.
- finish_recovery(send_intent);
+ finish_recovery();
switch (after) {
case Device::SHUTDOWN:
diff --git a/tests/Android.mk b/tests/Android.mk
index 262fb8bfd..7b004b2a0 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -23,7 +23,9 @@ 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
@@ -37,7 +39,8 @@ LOCAL_FORCE_STATIC_EXECUTABLE := true
LOCAL_STATIC_LIBRARIES := \
libbase \
libverifier \
- libmincrypt \
+ libcrypto_utils_static \
+ libcrypto_static \
libminui \
libminzip \
libcutils \
diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp
index d6f1e0bb9..b5d70327e 100644
--- a/tests/component/verifier_test.cpp
+++ b/tests/component/verifier_test.cpp
@@ -19,18 +19,18 @@
#include <gtest/gtest.h>
#include <stdio.h>
#include <stdlib.h>
-#include <sys/types.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 "mincrypt/sha.h"
-#include "mincrypt/sha256.h"
#include "minzip/SysUtil.h"
#include "ui.h"
#include "verifier.h"
@@ -44,94 +44,6 @@
static const char* DATA_PATH = getenv("ANDROID_DATA");
static const char* TESTDATA_PATH = "/recovery/testdata/";
-// This is build/target/product/security/testkey.x509.pem after being
-// dumped out by dumpkey.jar.
-RSAPublicKey test_key =
- { 64, 0xc926ad21,
- { 0x6afee91fu, 0x7fa31d5bu, 0x38a0b217u, 0x99df9baeu,
- 0xfe72991du, 0x727d3c04u, 0x20943f99u, 0xd08e7826u,
- 0x69e7c8a2u, 0xdeeccc8eu, 0x6b9af76fu, 0x553311c4u,
- 0x07b9e247u, 0x54c8bbcau, 0x6a540d81u, 0x48dbf567u,
- 0x98c92877u, 0x134fbfdeu, 0x01b32564u, 0x24581948u,
- 0x6cddc3b8u, 0x0cd444dau, 0xfe0381ccu, 0xf15818dfu,
- 0xc06e6d42u, 0x2e2f6412u, 0x093a6737u, 0x94d83b31u,
- 0xa466c87au, 0xb3f284a0u, 0xa694ec2cu, 0x053359e6u,
- 0x9717ee6au, 0x0732e080u, 0x220d5008u, 0xdc4af350u,
- 0x93d0a7c3u, 0xe330c9eau, 0xcac3da1eu, 0x8ebecf8fu,
- 0xc2be387fu, 0x38a14e89u, 0x211586f0u, 0x18b846f5u,
- 0x43be4c72u, 0xb578c204u, 0x1bbfb230u, 0xf1e267a8u,
- 0xa2d3e656u, 0x64b8e4feu, 0xe7e83d4bu, 0x3e77a943u,
- 0x3559ffd9u, 0x0ebb0f99u, 0x0aa76ce6u, 0xd3786ea7u,
- 0xbca8cd6bu, 0x068ca8e8u, 0xeb1de2ffu, 0x3e3ecd6cu,
- 0xe0d9d825u, 0xb1edc762u, 0xdec60b24u, 0xd6931904u},
- { 0xccdcb989u, 0xe19281f9u, 0xa6e80accu, 0xb7f40560u,
- 0x0efb0bccu, 0x7f12b0bbu, 0x1e90531au, 0x136d95d0u,
- 0x9e660665u, 0x7d54918fu, 0xe3b93ea2u, 0x2f415d10u,
- 0x3d2df6e6u, 0x7a627ecfu, 0xa6f22d70u, 0xb995907au,
- 0x09de16b2u, 0xfeb8bd61u, 0xf24ec294u, 0x716a427fu,
- 0x2e12046fu, 0xeaf3d56au, 0xd9b873adu, 0x0ced340bu,
- 0xbc9cec09u, 0x73c65903u, 0xee39ce9bu, 0x3eede25au,
- 0x397633b7u, 0x2583c165u, 0x8514f97du, 0xe9166510u,
- 0x0b6fae99u, 0xa47139fdu, 0xdb8352f0u, 0xb2ad7f2cu,
- 0xa11552e2u, 0xd4d490a7u, 0xe11e8568u, 0xe9e484dau,
- 0xd3ef8449u, 0xa47055dau, 0x4edd9557u, 0x03a78ba1u,
- 0x770e130du, 0x16762facu, 0x0cbdfcc4u, 0xf3070540u,
- 0x008b6515u, 0x60e7e1b7u, 0xa72cf7f9u, 0xaff86e39u,
- 0x4296faadu, 0xfc90430eu, 0x6cc8f377u, 0xb398fd43u,
- 0x423c5997u, 0x991d59c4u, 0x6464bf73u, 0x96431575u,
- 0x15e3d207u, 0x30532a7au, 0x8c4be618u, 0x460a4d76u },
- 3
- };
-
-RSAPublicKey test_f4_key =
- { 64, 0xc9bd1f21,
- { 0x1178db1fu, 0xbf5d0e55u, 0x3393a165u, 0x0ef4c287u,
- 0xbc472a4au, 0x383fc5a1u, 0x4a13b7d2u, 0xb1ff2ac3u,
- 0xaf66b4d9u, 0x9280acefu, 0xa2165bdbu, 0x6a4d6e5cu,
- 0x08ea676bu, 0xb7ac70c7u, 0xcd158139u, 0xa635ccfeu,
- 0xa46ab8a8u, 0x445a3e8bu, 0xdc81d9bbu, 0x91ce1a20u,
- 0x68021cdeu, 0x4516eda9u, 0x8d43c30cu, 0xed1eff14u,
- 0xca387e4cu, 0x58adc233u, 0x4657ab27u, 0xa95b521eu,
- 0xdfc0e30cu, 0x394d64a1u, 0xc6b321a1u, 0x2ca22cb8u,
- 0xb1892d5cu, 0x5d605f3eu, 0x6025483cu, 0x9afd5181u,
- 0x6e1a7105u, 0x03010593u, 0x70acd304u, 0xab957cbfu,
- 0x8844abbbu, 0x53846837u, 0x24e98a43u, 0x2ba060c1u,
- 0x8b88b88eu, 0x44eea405u, 0xb259fc41u, 0x0907ad9cu,
- 0x13003adau, 0xcf79634eu, 0x7d314ec9u, 0xfbbe4c2bu,
- 0xd84d0823u, 0xfd30fd88u, 0x68d8a909u, 0xfb4572d9u,
- 0xa21301c2u, 0xd00a4785u, 0x6862b50cu, 0xcfe49796u,
- 0xdaacbd83u, 0xfb620906u, 0xdf71e0ccu, 0xbbc5b030u },
- { 0x69a82189u, 0x1a8b22f4u, 0xcf49207bu, 0x68cc056au,
- 0xb206b7d2u, 0x1d449bbdu, 0xe9d342f2u, 0x29daea58u,
- 0xb19d011au, 0xc62f15e4u, 0x9452697au, 0xb62bb87eu,
- 0x60f95cc2u, 0x279ebb2du, 0x17c1efd8u, 0xec47558bu,
- 0xc81334d1u, 0x88fe7601u, 0x79992eb1u, 0xb4555615u,
- 0x2022ac8cu, 0xc79a4b8cu, 0xb288b034u, 0xd6b942f0u,
- 0x0caa32fbu, 0xa065ba51u, 0x4de9f154u, 0x29f64f6cu,
- 0x7910af5eu, 0x3ed4636au, 0xe4c81911u, 0x9183f37du,
- 0x5811e1c4u, 0x29c7a58cu, 0x9715d4d3u, 0xc7e2dce3u,
- 0x140972ebu, 0xf4c8a69eu, 0xa104d424u, 0x5dabbdfbu,
- 0x41cb4c6bu, 0xd7f44717u, 0x61785ff7u, 0x5e0bc273u,
- 0x36426c70u, 0x2aa6f08eu, 0x083badbfu, 0x3cab941bu,
- 0x8871da23u, 0x1ab3dbaeu, 0x7115a21du, 0xf5aa0965u,
- 0xf766f562u, 0x7f110225u, 0x86d96a04u, 0xc50a120eu,
- 0x3a751ca3u, 0xc21aa186u, 0xba7359d0u, 0x3ff2b257u,
- 0xd116e8bbu, 0xfc1318c0u, 0x070e5b1du, 0x83b759a6u },
- 65537
- };
-
-ECPublicKey test_ec_key =
- {
- {
- {0xd656fa24u, 0x931416cau, 0x1c0278c6u, 0x174ebe4cu,
- 0x6018236au, 0x45ba1656u, 0xe8c05d84u, 0x670ed500u}
- },
- {
- {0x0d179adeu, 0x4c16827du, 0x9f8cb992u, 0x8f69ff8au,
- 0x481b1020u, 0x798d91afu, 0x184db8e9u, 0xb5848dd9u}
- }
- };
-
RecoveryUI* ui = NULL;
class MockUI : public RecoveryUI {
@@ -183,31 +95,34 @@ class VerifierTest : public testing::TestWithParam<std::vector<std::string>> {
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());
+ 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) {
if (it->substr(it->length() - 3, it->length()) == "256") {
if (certs.empty()) {
FAIL() << "May only specify -sha256 after key type\n";
}
- certs.back().hash_len = SHA256_DIGEST_SIZE;
- } else if (*it == "ec") {
- certs.emplace_back(SHA_DIGEST_SIZE, Certificate::EC,
- nullptr, std::unique_ptr<ECPublicKey>(new ECPublicKey(test_ec_key)));
- } else if (*it == "e3") {
- certs.emplace_back(SHA_DIGEST_SIZE, Certificate::RSA,
- std::unique_ptr<RSAPublicKey>(new RSAPublicKey(test_key)), nullptr);
- } else if (*it == "f4") {
- certs.emplace_back(SHA_DIGEST_SIZE, Certificate::RSA,
- std::unique_ptr<RSAPublicKey>(new RSAPublicKey(test_f4_key)), nullptr);
+ certs.back().hash_len = SHA256_DIGEST_LENGTH;
+ } else {
+ std::string public_key_file = android::base::StringPrintf(
+ "%s%s%stest_key_%s.txt", DATA_PATH, NATIVE_TEST_PATH,
+ TESTDATA_PATH, it->c_str());
+ ASSERT_TRUE(load_keys(public_key_file.c_str(), certs));
+ certs.back().hash_len = SHA_DIGEST_LENGTH;
}
}
if (certs.empty()) {
- certs.emplace_back(SHA_DIGEST_SIZE, Certificate::RSA,
- std::unique_ptr<RSAPublicKey>(new RSAPublicKey(test_key)), nullptr);
- }
- if (sysMapFile(package.c_str(), &memmap) != 0) {
- FAIL() << "Failed to mmap " << package << ": " << strerror(errno) << "\n";
+ std::string public_key_file = android::base::StringPrintf(
+ "%s%s%stest_key_e3.txt", DATA_PATH, NATIVE_TEST_PATH,
+ TESTDATA_PATH);
+ ASSERT_TRUE(load_keys(public_key_file.c_str(), certs));
+ certs.back().hash_len = SHA_DIGEST_LENGTH;
}
}
diff --git a/tests/testdata/test_key_e3.txt b/tests/testdata/test_key_e3.txt
new file mode 100644
index 000000000..53f5297bd
--- /dev/null
+++ b/tests/testdata/test_key_e3.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/tests/testdata/test_key_ec.txt b/tests/testdata/test_key_ec.txt
new file mode 100644
index 000000000..72b4395d9
--- /dev/null
+++ b/tests/testdata/test_key_ec.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/tests/testdata/test_key_f4.txt b/tests/testdata/test_key_f4.txt
new file mode 100644
index 000000000..54ddbbad1
--- /dev/null
+++ b/tests/testdata/test_key_f4.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/tests/unit/recovery_test.cpp b/tests/unit/recovery_test.cpp
new file mode 100644
index 000000000..f397f258e
--- /dev/null
+++ b/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/tools/ota/Android.mk b/tools/dumpkey/Android.mk
index 142c3b257..31549146d 100644
--- a/tools/ota/Android.mk
+++ b/tools/dumpkey/Android.mk
@@ -15,19 +15,8 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_FORCE_STATIC_EXECUTABLE := true
-LOCAL_MODULE := add-property-tag
-LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
-LOCAL_MODULE_TAGS := debug
-LOCAL_SRC_FILES := add-property-tag.c
-LOCAL_STATIC_LIBRARIES := libc
-include $(BUILD_EXECUTABLE)
-
-include $(CLEAR_VARS)
-LOCAL_FORCE_STATIC_EXECUTABLE := true
-LOCAL_MODULE := check-lost+found
-LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
-LOCAL_MODULE_TAGS := debug
-LOCAL_SRC_FILES := check-lost+found.c
-LOCAL_STATIC_LIBRARIES := libcutils libc
-include $(BUILD_EXECUTABLE)
+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/tools/dumpkey/DumpPublicKey.java b/tools/dumpkey/DumpPublicKey.java
new file mode 100644
index 000000000..3eb139842
--- /dev/null
+++ b/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/tools/dumpkey/DumpPublicKey.mf b/tools/dumpkey/DumpPublicKey.mf
new file mode 100644
index 000000000..7bb3bc88d
--- /dev/null
+++ b/tools/dumpkey/DumpPublicKey.mf
@@ -0,0 +1 @@
+Main-Class: com.android.dumpkey.DumpPublicKey
diff --git a/tools/ota/add-property-tag.c b/tools/ota/add-property-tag.c
deleted file mode 100644
index aab30b2d0..000000000
--- a/tools/ota/add-property-tag.c
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <ctype.h>
-#include <errno.h>
-#include <getopt.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-/*
- * Append a tag to a property value in a .prop file if it isn't already there.
- * Normally used to modify build properties to record incremental updates.
- */
-
-// Return nonzero if the tag should be added to this line.
-int should_tag(const char *line, const char *propname) {
- const char *prop = strstr(line, propname);
- if (prop == NULL) return 0;
-
- // Make sure this is actually the property name (not an accidental hit)
- const char *ptr;
- for (ptr = line; ptr < prop && isspace(*ptr); ++ptr) ;
- if (ptr != prop) return 0; // Must be at the beginning of the line
-
- for (ptr += strlen(propname); *ptr != '\0' && isspace(*ptr); ++ptr) ;
- return (*ptr == '='); // Must be followed by a '='
-}
-
-// Remove existing tags from the line, return the following number (if any)
-int remove_tag(char *line, const char *tag) {
- char *pos = strstr(line, tag);
- if (pos == NULL) return 0;
-
- char *end;
- int num = strtoul(pos + strlen(tag), &end, 10);
- strcpy(pos, end);
- return num;
-}
-
-// Write line to output with the tag added, adding a number (if >0)
-void write_tagged(FILE *out, const char *line, const char *tag, int number) {
- const char *end = line + strlen(line);
- while (end > line && isspace(end[-1])) --end;
- if (number > 0) {
- fprintf(out, "%.*s%s%d%s", (int)(end - line), line, tag, number, end);
- } else {
- fprintf(out, "%.*s%s%s", (int)(end - line), line, tag, end);
- }
-}
-
-int main(int argc, char **argv) {
- const char *filename = "/system/build.prop";
- const char *propname = "ro.build.fingerprint";
- const char *tag = NULL;
- int do_remove = 0, do_number = 0;
-
- int opt;
- while ((opt = getopt(argc, argv, "f:p:rn")) != -1) {
- switch (opt) {
- case 'f': filename = optarg; break;
- case 'p': propname = optarg; break;
- case 'r': do_remove = 1; break;
- case 'n': do_number = 1; break;
- case '?': return 2;
- }
- }
-
- if (argc != optind + 1) {
- fprintf(stderr,
- "usage: add-property-tag [flags] tag-to-add\n"
- "flags: -f /dir/file.prop (default /system/build.prop)\n"
- " -p prop.name (default ro.build.fingerprint)\n"
- " -r (if set, remove the tag rather than adding it)\n"
- " -n (if set, add and increment a number after the tag)\n");
- return 2;
- }
-
- tag = argv[optind];
- FILE *input = fopen(filename, "r");
- if (input == NULL) {
- fprintf(stderr, "can't read %s: %s\n", filename, strerror(errno));
- return 1;
- }
-
- char tmpname[PATH_MAX];
- snprintf(tmpname, sizeof(tmpname), "%s.tmp", filename);
- FILE *output = fopen(tmpname, "w");
- if (output == NULL) {
- fprintf(stderr, "can't write %s: %s\n", tmpname, strerror(errno));
- return 1;
- }
-
- int found = 0;
- char line[4096];
- while (fgets(line, sizeof(line), input)) {
- if (!should_tag(line, propname)) {
- fputs(line, output); // Pass through unmodified
- } else {
- found = 1;
- int number = remove_tag(line, tag);
- if (do_remove) {
- fputs(line, output); // Remove the tag but don't re-add it
- } else {
- write_tagged(output, line, tag, number + do_number);
- }
- }
- }
-
- fclose(input);
- fclose(output);
-
- if (!found) {
- fprintf(stderr, "property %s not found in %s\n", propname, filename);
- remove(tmpname);
- return 1;
- }
-
- if (rename(tmpname, filename)) {
- fprintf(stderr, "can't rename %s to %s: %s\n",
- tmpname, filename, strerror(errno));
- remove(tmpname);
- return 1;
- }
-
- return 0;
-}
diff --git a/tools/ota/check-lost+found.c b/tools/ota/check-lost+found.c
deleted file mode 100644
index 8ce12d39f..000000000
--- a/tools/ota/check-lost+found.c
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/klog.h>
-#include <sys/reboot.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <time.h>
-#include <unistd.h>
-
-#include "private/android_filesystem_config.h"
-
-// Sentinel file used to track whether we've forced a reboot
-static const char *kMarkerFile = "/data/misc/check-lost+found-rebooted-2";
-
-// Output file in tombstones directory (first 8K will be uploaded)
-static const char *kOutputDir = "/data/tombstones";
-static const char *kOutputFile = "/data/tombstones/check-lost+found-log";
-
-// Partitions to check
-static const char *kPartitions[] = { "/system", "/data", "/cache", NULL };
-
-/*
- * 1. If /data/misc/forced-reboot is missing, touch it & force "unclean" boot.
- * 2. Write a log entry with the number of files in lost+found directories.
- */
-
-int main(int argc __attribute__((unused)), char **argv __attribute__((unused))) {
- mkdir(kOutputDir, 0755);
- chown(kOutputDir, AID_SYSTEM, AID_SYSTEM);
- FILE *out = fopen(kOutputFile, "a");
- if (out == NULL) {
- fprintf(stderr, "Can't write %s: %s\n", kOutputFile, strerror(errno));
- return 1;
- }
-
- // Note: only the first 8K of log will be uploaded, so be terse.
- time_t start = time(NULL);
- fprintf(out, "*** check-lost+found ***\nStarted: %s", ctime(&start));
-
- struct stat st;
- if (stat(kMarkerFile, &st)) {
- // No reboot marker -- need to force an unclean reboot.
- // But first, try to create the marker file. If that fails,
- // skip the reboot, so we don't get caught in an infinite loop.
-
- int fd = open(kMarkerFile, O_WRONLY|O_CREAT, 0444);
- if (fd >= 0 && close(fd) == 0) {
- fprintf(out, "Wrote %s, rebooting\n", kMarkerFile);
- fflush(out);
- sync(); // Make sure the marker file is committed to disk
-
- // If possible, dirty each of these partitions before rebooting,
- // to make sure the filesystem has to do a scan on mount.
- int i;
- for (i = 0; kPartitions[i] != NULL; ++i) {
- char fn[PATH_MAX];
- snprintf(fn, sizeof(fn), "%s/%s", kPartitions[i], "dirty");
- fd = open(fn, O_WRONLY|O_CREAT, 0444);
- if (fd >= 0) { // Don't sweat it if we can't write the file.
- TEMP_FAILURE_RETRY(write(fd, fn, sizeof(fn))); // write, you know, some data
- close(fd);
- unlink(fn);
- }
- }
-
- reboot(RB_AUTOBOOT); // reboot immediately, with dirty filesystems
- fprintf(out, "Reboot failed?!\n");
- exit(1);
- } else {
- fprintf(out, "Can't write %s: %s\n", kMarkerFile, strerror(errno));
- }
- } else {
- fprintf(out, "Found %s\n", kMarkerFile);
- }
-
- int i;
- for (i = 0; kPartitions[i] != NULL; ++i) {
- char fn[PATH_MAX];
- snprintf(fn, sizeof(fn), "%s/%s", kPartitions[i], "lost+found");
- DIR *dir = opendir(fn);
- if (dir == NULL) {
- fprintf(out, "Can't open %s: %s\n", fn, strerror(errno));
- } else {
- int count = 0;
- struct dirent *ent;
- while ((ent = readdir(dir))) {
- if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, ".."))
- ++count;
- }
- closedir(dir);
- if (count > 0) {
- fprintf(out, "OMGZ FOUND %d FILES IN %s\n", count, fn);
- } else {
- fprintf(out, "%s is clean\n", fn);
- }
- }
- }
-
- char dmesg[131073];
- int len = klogctl(KLOG_READ_ALL, dmesg, sizeof(dmesg) - 1);
- if (len < 0) {
- fprintf(out, "Can't read kernel log: %s\n", strerror(errno));
- } else { // To conserve space, only write lines with certain keywords
- fprintf(out, "--- Kernel log ---\n");
- dmesg[len] = '\0';
- char *saveptr, *line;
- int in_yaffs = 0;
- for (line = strtok_r(dmesg, "\n", &saveptr); line != NULL;
- line = strtok_r(NULL, "\n", &saveptr)) {
- if (strstr(line, "yaffs: dev is")) in_yaffs = 1;
-
- if (in_yaffs ||
- strstr(line, "yaffs") ||
- strstr(line, "mtd") ||
- strstr(line, "msm_nand")) {
- fprintf(out, "%s\n", line);
- }
-
- if (strstr(line, "yaffs_read_super: isCheckpointed")) in_yaffs = 0;
- }
- }
-
- return 0;
-}
diff --git a/tools/ota/convert-to-bmp.py b/tools/ota/convert-to-bmp.py
deleted file mode 100644
index 446c09da8..000000000
--- a/tools/ota/convert-to-bmp.py
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/python2.4
-
-"""A simple script to convert asset images to BMP files, that supports
-RGBA image."""
-
-import struct
-import Image
-import sys
-
-infile = sys.argv[1]
-outfile = sys.argv[2]
-
-if not outfile.endswith(".bmp"):
- print >> sys.stderr, "Warning: I'm expecting to write BMP files."
-
-im = Image.open(infile)
-if im.mode == 'RGB':
- im.save(outfile)
-elif im.mode == 'RGBA':
- # Python Imaging Library doesn't write RGBA BMP files, so we roll
- # our own.
-
- BMP_HEADER_FMT = ("<" # little-endian
- "H" # signature
- "L" # file size
- "HH" # reserved (set to 0)
- "L" # offset to start of bitmap data)
- )
-
- BITMAPINFO_HEADER_FMT= ("<" # little-endian
- "L" # size of this struct
- "L" # width
- "L" # height
- "H" # planes (set to 1)
- "H" # bit count
- "L" # compression (set to 0 for minui)
- "L" # size of image data (0 if uncompressed)
- "L" # x pixels per meter (1)
- "L" # y pixels per meter (1)
- "L" # colors used (0)
- "L" # important colors (0)
- )
-
- fileheadersize = struct.calcsize(BMP_HEADER_FMT)
- infoheadersize = struct.calcsize(BITMAPINFO_HEADER_FMT)
-
- header = struct.pack(BMP_HEADER_FMT,
- 0x4d42, # "BM" in little-endian
- (fileheadersize + infoheadersize +
- im.size[0] * im.size[1] * 4),
- 0, 0,
- fileheadersize + infoheadersize)
-
- info = struct.pack(BITMAPINFO_HEADER_FMT,
- infoheadersize,
- im.size[0],
- im.size[1],
- 1,
- 32,
- 0,
- 0,
- 1,
- 1,
- 0,
- 0)
-
- f = open(outfile, "wb")
- f.write(header)
- f.write(info)
- data = im.tostring()
- for j in range(im.size[1]-1, -1, -1): # rows bottom-to-top
- for i in range(j*im.size[0]*4, (j+1)*im.size[0]*4, 4):
- f.write(data[i+2]) # B
- f.write(data[i+1]) # G
- f.write(data[i+0]) # R
- f.write(data[i+3]) # A
- f.close()
-else:
- print >> sys.stderr, "Don't know how to handle image mode '%s'." % (im.mode,)
diff --git a/tools/recovery_l10n/Android.mk b/tools/recovery_l10n/Android.mk
new file mode 100644
index 000000000..937abd1e1
--- /dev/null
+++ b/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/tools/recovery_l10n/AndroidManifest.xml b/tools/recovery_l10n/AndroidManifest.xml
new file mode 100644
index 000000000..8c51a4e08
--- /dev/null
+++ b/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/tools/recovery_l10n/res/layout/main.xml b/tools/recovery_l10n/res/layout/main.xml
new file mode 100644
index 000000000..0900b1102
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-af/strings.xml b/tools/recovery_l10n/res/values-af/strings.xml
new file mode 100644
index 000000000..d5264184a
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-am/strings.xml b/tools/recovery_l10n/res/values-am/strings.xml
new file mode 100644
index 000000000..cddb099bc
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-ar/strings.xml b/tools/recovery_l10n/res/values-ar/strings.xml
new file mode 100644
index 000000000..d06b96644
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-az-rAZ/strings.xml b/tools/recovery_l10n/res/values-az-rAZ/strings.xml
new file mode 100644
index 000000000..3435573dc
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-bg/strings.xml b/tools/recovery_l10n/res/values-bg/strings.xml
new file mode 100644
index 000000000..004f3b93e
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-bn-rBD/strings.xml b/tools/recovery_l10n/res/values-bn-rBD/strings.xml
new file mode 100644
index 000000000..4d2e590f4
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-ca/strings.xml b/tools/recovery_l10n/res/values-ca/strings.xml
new file mode 100644
index 000000000..5d7b652c5
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-cs/strings.xml b/tools/recovery_l10n/res/values-cs/strings.xml
new file mode 100644
index 000000000..771235d04
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-da/strings.xml b/tools/recovery_l10n/res/values-da/strings.xml
new file mode 100644
index 000000000..c28a76fbd
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-de/strings.xml b/tools/recovery_l10n/res/values-de/strings.xml
new file mode 100644
index 000000000..02d259059
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-el/strings.xml b/tools/recovery_l10n/res/values-el/strings.xml
new file mode 100644
index 000000000..aa2626b4b
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-en-rAU/strings.xml b/tools/recovery_l10n/res/values-en-rAU/strings.xml
new file mode 100644
index 000000000..b70d678c1
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-en-rGB/strings.xml b/tools/recovery_l10n/res/values-en-rGB/strings.xml
new file mode 100644
index 000000000..b70d678c1
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-en-rIN/strings.xml b/tools/recovery_l10n/res/values-en-rIN/strings.xml
new file mode 100644
index 000000000..b70d678c1
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-es-rUS/strings.xml b/tools/recovery_l10n/res/values-es-rUS/strings.xml
new file mode 100644
index 000000000..256272ac7
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-es/strings.xml b/tools/recovery_l10n/res/values-es/strings.xml
new file mode 100644
index 000000000..323f05505
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-et-rEE/strings.xml b/tools/recovery_l10n/res/values-et-rEE/strings.xml
new file mode 100644
index 000000000..407a53d67
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-eu-rES/strings.xml b/tools/recovery_l10n/res/values-eu-rES/strings.xml
new file mode 100644
index 000000000..08d9c0672
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-fa/strings.xml b/tools/recovery_l10n/res/values-fa/strings.xml
new file mode 100644
index 000000000..dd002face
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-fi/strings.xml b/tools/recovery_l10n/res/values-fi/strings.xml
new file mode 100644
index 000000000..b77417a98
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-fr-rCA/strings.xml b/tools/recovery_l10n/res/values-fr-rCA/strings.xml
new file mode 100644
index 000000000..f2a85d86a
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-fr/strings.xml b/tools/recovery_l10n/res/values-fr/strings.xml
new file mode 100644
index 000000000..cdb4a2668
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-gl-rES/strings.xml b/tools/recovery_l10n/res/values-gl-rES/strings.xml
new file mode 100644
index 000000000..7546fbda4
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-gu-rIN/strings.xml b/tools/recovery_l10n/res/values-gu-rIN/strings.xml
new file mode 100644
index 000000000..a364b523c
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-hi/strings.xml b/tools/recovery_l10n/res/values-hi/strings.xml
new file mode 100644
index 000000000..a470d12b6
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-hr/strings.xml b/tools/recovery_l10n/res/values-hr/strings.xml
new file mode 100644
index 000000000..56225c015
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-hu/strings.xml b/tools/recovery_l10n/res/values-hu/strings.xml
new file mode 100644
index 000000000..a64f50176
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-hy-rAM/strings.xml b/tools/recovery_l10n/res/values-hy-rAM/strings.xml
new file mode 100644
index 000000000..7babe80c8
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-in/strings.xml b/tools/recovery_l10n/res/values-in/strings.xml
new file mode 100644
index 000000000..93f9c2876
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-is-rIS/strings.xml b/tools/recovery_l10n/res/values-is-rIS/strings.xml
new file mode 100644
index 000000000..926e85132
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-it/strings.xml b/tools/recovery_l10n/res/values-it/strings.xml
new file mode 100644
index 000000000..9defe36bd
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-iw/strings.xml b/tools/recovery_l10n/res/values-iw/strings.xml
new file mode 100644
index 000000000..e43bb20a9
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-ja/strings.xml b/tools/recovery_l10n/res/values-ja/strings.xml
new file mode 100644
index 000000000..da0fa623a
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-ka-rGE/strings.xml b/tools/recovery_l10n/res/values-ka-rGE/strings.xml
new file mode 100644
index 000000000..2d27c1799
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-kk-rKZ/strings.xml b/tools/recovery_l10n/res/values-kk-rKZ/strings.xml
new file mode 100644
index 000000000..3ca05b9eb
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-km-rKH/strings.xml b/tools/recovery_l10n/res/values-km-rKH/strings.xml
new file mode 100644
index 000000000..0c1c272e0
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-kn-rIN/strings.xml b/tools/recovery_l10n/res/values-kn-rIN/strings.xml
new file mode 100644
index 000000000..be25d7a9d
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-ko/strings.xml b/tools/recovery_l10n/res/values-ko/strings.xml
new file mode 100644
index 000000000..e46a87606
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-ky-rKG/strings.xml b/tools/recovery_l10n/res/values-ky-rKG/strings.xml
new file mode 100644
index 000000000..e2ced27a4
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-lo-rLA/strings.xml b/tools/recovery_l10n/res/values-lo-rLA/strings.xml
new file mode 100644
index 000000000..5880cca75
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-lt/strings.xml b/tools/recovery_l10n/res/values-lt/strings.xml
new file mode 100644
index 000000000..957ac7557
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-lv/strings.xml b/tools/recovery_l10n/res/values-lv/strings.xml
new file mode 100644
index 000000000..c5d5b93a6
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-mk-rMK/strings.xml b/tools/recovery_l10n/res/values-mk-rMK/strings.xml
new file mode 100644
index 000000000..d91a67cac
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-ml-rIN/strings.xml b/tools/recovery_l10n/res/values-ml-rIN/strings.xml
new file mode 100644
index 000000000..38ebcd120
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-mn-rMN/strings.xml b/tools/recovery_l10n/res/values-mn-rMN/strings.xml
new file mode 100644
index 000000000..463cafeaf
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-mr-rIN/strings.xml b/tools/recovery_l10n/res/values-mr-rIN/strings.xml
new file mode 100644
index 000000000..25c5d0c57
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-ms-rMY/strings.xml b/tools/recovery_l10n/res/values-ms-rMY/strings.xml
new file mode 100644
index 000000000..f5635910e
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-my-rMM/strings.xml b/tools/recovery_l10n/res/values-my-rMM/strings.xml
new file mode 100644
index 000000000..4091b1923
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-nb/strings.xml b/tools/recovery_l10n/res/values-nb/strings.xml
new file mode 100644
index 000000000..4e89ad7c8
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-ne-rNP/strings.xml b/tools/recovery_l10n/res/values-ne-rNP/strings.xml
new file mode 100644
index 000000000..835f275b4
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-nl/strings.xml b/tools/recovery_l10n/res/values-nl/strings.xml
new file mode 100644
index 000000000..be80a6b5c
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-pa-rIN/strings.xml b/tools/recovery_l10n/res/values-pa-rIN/strings.xml
new file mode 100644
index 000000000..39ef32f55
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-pl/strings.xml b/tools/recovery_l10n/res/values-pl/strings.xml
new file mode 100644
index 000000000..b1e5b7b66
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-pt-rBR/strings.xml b/tools/recovery_l10n/res/values-pt-rBR/strings.xml
new file mode 100644
index 000000000..3cc57234e
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-pt-rPT/strings.xml b/tools/recovery_l10n/res/values-pt-rPT/strings.xml
new file mode 100644
index 000000000..7d6bc18a9
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-pt/strings.xml b/tools/recovery_l10n/res/values-pt/strings.xml
new file mode 100644
index 000000000..3cc57234e
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-ro/strings.xml b/tools/recovery_l10n/res/values-ro/strings.xml
new file mode 100644
index 000000000..ad924da08
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-ru/strings.xml b/tools/recovery_l10n/res/values-ru/strings.xml
new file mode 100644
index 000000000..de0da4004
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-si-rLK/strings.xml b/tools/recovery_l10n/res/values-si-rLK/strings.xml
new file mode 100644
index 000000000..e717a9762
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-sk/strings.xml b/tools/recovery_l10n/res/values-sk/strings.xml
new file mode 100644
index 000000000..cae6bce7c
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-sl/strings.xml b/tools/recovery_l10n/res/values-sl/strings.xml
new file mode 100644
index 000000000..3f8d46fe6
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-sq-rAL/strings.xml b/tools/recovery_l10n/res/values-sq-rAL/strings.xml
new file mode 100644
index 000000000..29f8ef592
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-sr/strings.xml b/tools/recovery_l10n/res/values-sr/strings.xml
new file mode 100644
index 000000000..955326053
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-sv/strings.xml b/tools/recovery_l10n/res/values-sv/strings.xml
new file mode 100644
index 000000000..f875d3008
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-sw/strings.xml b/tools/recovery_l10n/res/values-sw/strings.xml
new file mode 100644
index 000000000..1a5304649
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-ta-rIN/strings.xml b/tools/recovery_l10n/res/values-ta-rIN/strings.xml
new file mode 100644
index 000000000..f6f3e0e6a
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-te-rIN/strings.xml b/tools/recovery_l10n/res/values-te-rIN/strings.xml
new file mode 100644
index 000000000..6d0d17af5
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-th/strings.xml b/tools/recovery_l10n/res/values-th/strings.xml
new file mode 100644
index 000000000..bcdfa2b25
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-tl/strings.xml b/tools/recovery_l10n/res/values-tl/strings.xml
new file mode 100644
index 000000000..be2ba264c
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-tr/strings.xml b/tools/recovery_l10n/res/values-tr/strings.xml
new file mode 100644
index 000000000..8629029ca
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-uk/strings.xml b/tools/recovery_l10n/res/values-uk/strings.xml
new file mode 100644
index 000000000..762c06ff3
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-ur-rPK/strings.xml b/tools/recovery_l10n/res/values-ur-rPK/strings.xml
new file mode 100644
index 000000000..dc6eb6aa1
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-uz-rUZ/strings.xml b/tools/recovery_l10n/res/values-uz-rUZ/strings.xml
new file mode 100644
index 000000000..287448418
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-vi/strings.xml b/tools/recovery_l10n/res/values-vi/strings.xml
new file mode 100644
index 000000000..ab4005b7f
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-zh-rCN/strings.xml b/tools/recovery_l10n/res/values-zh-rCN/strings.xml
new file mode 100644
index 000000000..2e1a6f57f
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-zh-rHK/strings.xml b/tools/recovery_l10n/res/values-zh-rHK/strings.xml
new file mode 100644
index 000000000..f615c7a29
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-zh-rTW/strings.xml b/tools/recovery_l10n/res/values-zh-rTW/strings.xml
new file mode 100644
index 000000000..f3f6a2c21
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values-zu/strings.xml b/tools/recovery_l10n/res/values-zu/strings.xml
new file mode 100644
index 000000000..1f904a203
--- /dev/null
+++ b/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/tools/recovery_l10n/res/values/strings.xml b/tools/recovery_l10n/res/values/strings.xml
new file mode 100644
index 000000000..f6193ab17
--- /dev/null
+++ b/tools/recovery_l10n/res/values/strings.xml
@@ -0,0 +1,38 @@
+<?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>
+ </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/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java b/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
new file mode 100644
index 000000000..3f2bebe60
--- /dev/null
+++ b/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
@@ -0,0 +1,319 @@
+/*
+ * 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;
+ }
+ }
+ @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/uncrypt/uncrypt.cpp b/uncrypt/uncrypt.cpp
index 705744eb6..43a2c2ab4 100644
--- a/uncrypt/uncrypt.cpp
+++ b/uncrypt/uncrypt.cpp
@@ -61,6 +61,7 @@
#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>
@@ -69,7 +70,6 @@
#include <log/log.h>
#include "bootloader.h"
-#include "unique_fd.h"
#define WINDOW_SIZE 5
@@ -174,8 +174,9 @@ static int produce_block_map(const char* path, const char* map_file, const char*
return -1;
}
std::string tmp_map_file = std::string(map_file) + ".tmp";
- unique_fd mapfd(open(tmp_map_file.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR));
- if (!mapfd) {
+ 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;
}
@@ -201,7 +202,7 @@ static int produce_block_map(const char* path, const char* map_file, const char*
std::string s = android::base::StringPrintf("%s\n%" PRId64 " %ld\n",
blk_dev, sb.st_size, static_cast<long>(sb.st_blksize));
- if (!android::base::WriteStringToFd(s, mapfd.get())) {
+ if (!android::base::WriteStringToFd(s, mapfd)) {
ALOGE("failed to write %s: %s", tmp_map_file.c_str(), strerror(errno));
return -1;
}
@@ -213,16 +214,16 @@ static int produce_block_map(const char* path, const char* map_file, const char*
int head_block = 0;
int head = 0, tail = 0;
- unique_fd fd(open(path, O_RDONLY));
- if (!fd) {
+ 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;
}
- unique_fd wfd(-1);
+ android::base::unique_fd wfd;
if (encrypted) {
- wfd = open(blk_dev, O_WRONLY);
- if (!wfd) {
+ wfd.reset(open(blk_dev, O_WRONLY));
+ if (wfd == -1) {
ALOGE("failed to open fd for writing: %s", strerror(errno));
return -1;
}
@@ -241,14 +242,14 @@ static int produce_block_map(const char* path, const char* map_file, const char*
if ((tail+1) % WINDOW_SIZE == head) {
// write out head buffer
int block = head_block;
- if (ioctl(fd.get(), FIBMAP, &block) != 0) {
+ 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.get(),
- static_cast<off64_t>(sb.st_blksize) * block) != 0) {
+ if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd,
+ static_cast<off64_t>(sb.st_blksize) * block) != 0) {
return -1;
}
}
@@ -260,7 +261,7 @@ static int produce_block_map(const char* path, const char* map_file, const char*
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.get(), buffers[tail].data(), to_read)) {
+ if (!android::base::ReadFully(fd, buffers[tail].data(), to_read)) {
ALOGE("failed to read: %s", strerror(errno));
return -1;
}
@@ -277,14 +278,14 @@ static int produce_block_map(const char* path, const char* map_file, const char*
while (head != tail) {
// write out head buffer
int block = head_block;
- if (ioctl(fd.get(), FIBMAP, &block) != 0) {
+ 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.get(),
- static_cast<off64_t>(sb.st_blksize) * block) != 0) {
+ if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd,
+ static_cast<off64_t>(sb.st_blksize) * block) != 0) {
return -1;
}
}
@@ -293,38 +294,36 @@ static int produce_block_map(const char* path, const char* map_file, const char*
}
if (!android::base::WriteStringToFd(
- android::base::StringPrintf("%zu\n", ranges.size() / 2), mapfd.get())) {
+ 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.get())) {
+ 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.get()) == -1) {
+ if (fsync(mapfd) == -1) {
ALOGE("failed to fsync \"%s\": %s", tmp_map_file.c_str(), strerror(errno));
return -1;
}
- if (close(mapfd.get() == -1)) {
+ if (close(mapfd.release()) == -1) {
ALOGE("failed to close %s: %s", tmp_map_file.c_str(), strerror(errno));
return -1;
}
- mapfd = -1;
if (encrypted) {
- if (fsync(wfd.get()) == -1) {
+ if (fsync(wfd) == -1) {
ALOGE("failed to fsync \"%s\": %s", blk_dev, strerror(errno));
return -1;
}
- if (close(wfd.get()) == -1) {
+ if (close(wfd.release()) == -1) {
ALOGE("failed to close %s: %s", blk_dev, strerror(errno));
return -1;
}
- wfd = -1;
}
if (rename(tmp_map_file.c_str(), map_file) == -1) {
@@ -334,20 +333,19 @@ static int produce_block_map(const char* path, const char* map_file, const char*
// Sync dir to make rename() result written to disk.
std::string file_name = map_file;
std::string dir_name = dirname(&file_name[0]);
- unique_fd dfd(open(dir_name.c_str(), O_RDONLY | O_DIRECTORY));
- if (!dfd) {
+ 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.get()) == -1) {
+ if (fsync(dfd) == -1) {
ALOGE("failed to fsync %s: %s", dir_name.c_str(), strerror(errno));
return -1;
}
- if (close(dfd.get() == -1)) {
+ if (close(dfd.release()) == -1) {
ALOGE("failed to close %s: %s", dir_name.c_str(), strerror(errno));
return -1;
}
- dfd = -1;
return 0;
}
@@ -365,41 +363,23 @@ static std::string get_misc_blk_device() {
return "";
}
-static int read_bootloader_message(bootloader_message* out) {
- std::string misc_blk_device = get_misc_blk_device();
- if (misc_blk_device.empty()) {
- ALOGE("failed to find /misc partition.");
- return -1;
- }
- unique_fd fd(open(misc_blk_device.c_str(), O_RDONLY));
- if (!fd) {
- ALOGE("failed to open %s: %s", misc_blk_device.c_str(), strerror(errno));
- return -1;
- }
- if (!android::base::ReadFully(fd.get(), out, sizeof(*out))) {
- ALOGE("failed to read %s: %s", misc_blk_device.c_str(), strerror(errno));
- return -1;
- }
- return 0;
-}
-
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;
}
- unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY | O_SYNC));
- if (!fd) {
+ 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.get(), in, sizeof(*in))) {
+ 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.get()) == -1) {
+ if (fsync(fd) == -1) {
ALOGE("failed to fsync %s: %s", misc_blk_device.c_str(), strerror(errno));
return -1;
}
@@ -465,8 +445,9 @@ static int uncrypt(const char* input_path, const char* map_file, int status_fd)
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.
- unique_fd status_fd(open(status_file.c_str(), O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR));
- if (!status_fd) {
+ 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;
}
@@ -474,46 +455,48 @@ static int uncrypt_wrapper(const char* input_path, const char* map_file,
std::string package;
if (input_path == nullptr) {
if (!find_uncrypt_package(UNCRYPT_PATH_FILE, &package)) {
- android::base::WriteStringToFd("-1\n", status_fd.get());
+ 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.get());
+ int status = uncrypt(input_path, map_file, status_fd);
if (status != 0) {
- android::base::WriteStringToFd("-1\n", status_fd.get());
+ android::base::WriteStringToFd("-1\n", status_fd);
return 1;
}
- android::base::WriteStringToFd("100\n", status_fd.get());
+ android::base::WriteStringToFd("100\n", status_fd);
return 0;
}
static int clear_bcb(const std::string& status_file) {
- unique_fd status_fd(open(status_file.c_str(), O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR));
- if (!status_fd) {
+ 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.get());
+ android::base::WriteStringToFd("-1\n", status_fd);
return 1;
}
- android::base::WriteStringToFd("100\n", status_fd.get());
+ android::base::WriteStringToFd("100\n", status_fd);
return 0;
}
static int setup_bcb(const std::string& command_file, const std::string& status_file) {
- unique_fd status_fd(open(status_file.c_str(), O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR));
- if (!status_fd) {
+ 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.get());
+ android::base::WriteStringToFd("-1\n", status_fd);
return 1;
}
bootloader_message boot = {};
@@ -522,21 +505,10 @@ static int setup_bcb(const std::string& command_file, const std::string& status_
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.get());
- return 1;
- }
- android::base::WriteStringToFd("100\n", status_fd.get());
- return 0;
-}
-
-static int read_bcb() {
- bootloader_message boot;
- if (read_bootloader_message(&boot) != 0) {
- ALOGE("failed to get bootloader message");
+ android::base::WriteStringToFd("-1\n", status_fd);
return 1;
}
- printf("bcb command: %s\n", boot.command);
- printf("bcb recovery:\n%s\n", boot.recovery);
+ android::base::WriteStringToFd("100\n", status_fd);
return 0;
}
@@ -546,7 +518,6 @@ static void usage(const char* 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);
- fprintf(stderr, "%s --read-bcb Read BCB data from misc partition.\n", exename);
}
int main(int argc, char** argv) {
@@ -557,8 +528,6 @@ int main(int argc, char** argv) {
return clear_bcb(STATUS_FILE);
} else if (strcmp(argv[1], "--setup-bcb") == 0) {
return setup_bcb(COMMAND_FILE, STATUS_FILE);
- } else if (strcmp(argv[1], "--read-bcb") == 0) {
- return read_bcb();
}
} else if (argc == 1 || argc == 3) {
const char* input_path = nullptr;
diff --git a/unique_fd.h b/unique_fd.h
deleted file mode 100644
index cc85383f8..000000000
--- a/unique_fd.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef UNIQUE_FD_H
-#define UNIQUE_FD_H
-
-#include <stdio.h>
-
-#include <memory>
-
-class unique_fd {
- public:
- unique_fd(int fd) : fd_(fd) { }
-
- unique_fd(unique_fd&& uf) {
- fd_ = uf.fd_;
- uf.fd_ = -1;
- }
-
- ~unique_fd() {
- if (fd_ != -1) {
- close(fd_);
- }
- }
-
- int get() {
- return fd_;
- }
-
- // Movable.
- unique_fd& operator=(unique_fd&& uf) {
- fd_ = uf.fd_;
- uf.fd_ = -1;
- return *this;
- }
-
- explicit operator bool() const {
- return fd_ != -1;
- }
-
- private:
- int fd_;
-
- // Non-copyable.
- unique_fd(const unique_fd&) = delete;
- unique_fd& operator=(const unique_fd&) = delete;
-};
-
-#endif // UNIQUE_FD_H
diff --git a/updater/Android.mk b/updater/Android.mk
index d7aa613e9..7c3f6160c 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -14,26 +14,51 @@
LOCAL_PATH := $(call my-dir)
-updater_src_files := \
- install.cpp \
- blockimg.cpp \
- updater.cpp
-
-#
-# Build a statically-linked binary to include in OTA packages
-#
+# updater (static executable)
+# ===============================
+# Build a statically-linked binary to include in OTA packages.
include $(CLEAR_VARS)
-# Build only in eng, so we don't end up with a copy of this in /system
-# on user builds. (TODO: find a better way to build device binaries
-# needed only for OTA packages.)
-LOCAL_MODULE_TAGS := eng
+updater_src_files := \
+ install.cpp \
+ blockimg.cpp \
+ updater.cpp
LOCAL_CLANG := true
-
LOCAL_SRC_FILES := $(updater_src_files)
-LOCAL_STATIC_LIBRARIES += libfec libfec_rs libext4_utils_static libsquashfs_utils libcrypto_static
+LOCAL_STATIC_LIBRARIES += \
+ $(TARGET_RECOVERY_UPDATER_LIBS) \
+ $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) \
+ libfec \
+ libfec_rs \
+ libext4_utils_static \
+ libsquashfs_utils \
+ libcrypto_utils_static \
+ libcrypto_static \
+ libapplypatch \
+ libbase \
+ libotafault \
+ libedify \
+ libmtdutils \
+ libminzip \
+ 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)
ifeq ($(TARGET_USERIMAGES_USE_EXT4), true)
LOCAL_CFLAGS += -DUSE_EXT4
@@ -44,20 +69,6 @@ LOCAL_STATIC_LIBRARIES += \
libz
endif
-LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UPDATER_LIBS) $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS)
-LOCAL_STATIC_LIBRARIES += libapplypatch libbase libotafault libedify libmtdutils libminzip libz
-LOCAL_STATIC_LIBRARIES += libbz
-LOCAL_STATIC_LIBRARIES += libcutils liblog libc
-LOCAL_STATIC_LIBRARIES += 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_C_INCLUDES += external/e2fsprogs/misc
LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index 44de4e031..908e11631 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -33,21 +33,22 @@
#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 "otafault/ota_io.h"
+#include "ota_io.h"
#include "print_sha1.h"
-#include "unique_fd.h"
#include "updater.h"
#define BLOCKSIZE 4096
@@ -67,6 +68,8 @@ struct RangeSet {
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, ",");
@@ -365,7 +368,7 @@ struct CommandParameters {
std::string stashbase;
bool canwrite;
int createdstash;
- int fd;
+ android::base::unique_fd fd;
bool foundwrites;
bool isunresumable;
int version;
@@ -522,8 +525,28 @@ static void DeleteStash(const std::string& base) {
}
}
-static int LoadStash(const std::string& base, const std::string& id, bool verify, size_t* blocks,
- std::vector<uint8_t>& buffer, bool printnoent) {
+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;
}
@@ -554,9 +577,7 @@ static int LoadStash(const std::string& base, const std::string& id, bool verify
return -1;
}
- int fd = TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_RDONLY));
- unique_fd fd_holder(fd);
-
+ 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;
@@ -611,9 +632,9 @@ static int WriteStash(const std::string& base, const std::string& id, int blocks
fprintf(stderr, " writing %d blocks to %s\n", blocks, cn.c_str());
- int fd = TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_WRONLY | O_CREAT | O_TRUNC, STASH_FILE_MODE));
- unique_fd fd_holder(fd);
-
+ 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;
@@ -635,9 +656,8 @@ static int WriteStash(const std::string& base, const std::string& id, int blocks
}
std::string dname = GetStashFileName(base, "", "");
- int dfd = TEMP_FAILURE_RETRY(ota_open(dname.c_str(), O_RDONLY | O_DIRECTORY));
- unique_fd dfd_holder(dfd);
-
+ 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;
@@ -722,7 +742,7 @@ static int SaveStash(CommandParameters& params, const std::string& base,
const std::string& id = params.tokens[params.cpos++];
size_t blocks = 0;
- if (usehash && LoadStash(base, id, true, &blocks, buffer, false) == 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.
@@ -747,6 +767,12 @@ static int SaveStash(CommandParameters& params, const std::string& base,
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);
}
@@ -857,7 +883,7 @@ static int LoadSrcTgtVersion2(CommandParameters& params, RangeSet& tgt, size_t&
}
std::vector<uint8_t> stash;
- int res = LoadStash(stashbase, tokens[0], false, nullptr, stash, true);
+ 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
@@ -913,8 +939,8 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t&
tgthash = params.tokens[params.cpos++];
}
- if (LoadSrcTgtVersion2(params, tgt, src_blocks, params.buffer, params.fd, params.stashbase,
- &overlap) == -1) {
+ if (LoadSrcTgtVersion2(params, tgt, src_blocks, params.buffer, params.fd,
+ params.stashbase, &overlap) == -1) {
return -1;
}
@@ -931,8 +957,9 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t&
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
- if (overlap) {
+ // 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());
@@ -953,7 +980,8 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t&
return 0;
}
- if (overlap && LoadStash(params.stashbase, srchash, true, nullptr, params.buffer, true) == 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.
@@ -1028,8 +1056,15 @@ static int PerformCommandFree(CommandParameters& params) {
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, params.tokens[params.cpos++]);
+ return FreeStash(params.stashbase, id);
}
return 0;
@@ -1347,9 +1382,7 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, int /* arg
return StringValue(strdup(""));
}
- params.fd = TEMP_FAILURE_RETRY(ota_open(blockdev_filename->data, O_RDWR));
- unique_fd fd_holder(params.fd);
-
+ 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(""));
@@ -1494,7 +1527,7 @@ pbiudone:
if (ota_fsync(params.fd) == -1) {
fprintf(stderr, "fsync failed: %s\n", strerror(errno));
}
- // params.fd will be automatically closed because of the fd_holder above.
+ // 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.
@@ -1615,9 +1648,8 @@ Value* RangeSha1Fn(const char* name, State* state, int /* argc */, Expr* argv[])
return StringValue(strdup(""));
}
- int fd = ota_open(blockdev_filename->data, O_RDWR);
- unique_fd fd_holder(fd);
- if (fd < 0) {
+ 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(""));
}
@@ -1638,7 +1670,7 @@ Value* RangeSha1Fn(const char* name, State* state, int /* argc */, Expr* argv[])
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));
+ strerror(errno));
return StringValue(strdup(""));
}
@@ -1669,8 +1701,7 @@ Value* CheckFirstBlockFn(const char* name, State* state, int argc, Expr* argv[])
return StringValue(strdup(""));
}
- int fd = ota_open(arg_filename->data, O_RDONLY);
- unique_fd fd_holder(fd);
+ 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(""));
@@ -1680,8 +1711,7 @@ Value* CheckFirstBlockFn(const char* name, State* state, int argc, Expr* argv[])
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));
+ ErrorAbort(state, "failed to read %s: %s", arg_filename->data, strerror(errno));
return StringValue(strdup(""));
}
diff --git a/updater/install.cpp b/updater/install.cpp
index 1cd9a5690..925604f31 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -27,7 +27,6 @@
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
-#include <selinux/selinux.h>
#include <ftw.h>
#include <sys/capability.h>
#include <sys/xattr.h>
@@ -40,6 +39,8 @@
#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"
@@ -51,7 +52,7 @@
#include "minzip/DirUtil.h"
#include "mtdutils/mounts.h"
#include "mtdutils/mtdutils.h"
-#include "otafault/ota_io.h"
+#include "ota_io.h"
#include "updater.h"
#include "install.h"
#include "tune2fs.h"
@@ -1398,21 +1399,22 @@ Value* ReadFileFn(const char* name, State* state, int argc, Expr* argv[]) {
char* filename;
if (ReadArgs(state, argv, 1, &filename) < 0) return NULL;
- Value* v = reinterpret_cast<Value*>(malloc(sizeof(Value)));
+ 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) {
- free(filename);
- v->size = -1;
- v->data = NULL;
- free(fc.data);
- return v;
+ 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();
+ }
}
-
- v->size = fc.size;
- v->data = (char*)fc.data;
-
free(filename);
return v;
}
diff --git a/updater/updater.cpp b/updater/updater.cpp
index 0f22e6d04..0497d6a85 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -25,6 +25,10 @@
#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
@@ -35,6 +39,8 @@
// (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) {
@@ -82,6 +88,7 @@ int main(int argc, char** argv) {
argv[3], strerror(err));
return 3;
}
+ ota_io_init(&za);
const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME);
if (script_entry == NULL) {
@@ -139,6 +146,11 @@ int main(int argc, char** argv) {
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");
diff --git a/updater/updater.h b/updater/updater.h
index d1dfdd05e..d3a09b93d 100644
--- a/updater/updater.h
+++ b/updater/updater.h
@@ -20,9 +20,6 @@
#include <stdio.h>
#include "minzip/Zip.h"
-#include <selinux/selinux.h>
-#include <selinux/label.h>
-
typedef struct {
FILE* cmd_pipe;
ZipArchive* package_zip;
@@ -32,6 +29,7 @@ typedef struct {
size_t package_zip_len;
} UpdaterInfo;
+struct selabel_handle;
extern struct selabel_handle *sehandle;
#endif
diff --git a/verifier.cpp b/verifier.cpp
index 9a2d60c66..6e1581272 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -14,23 +14,22 @@
* limitations under the License.
*/
-#include "asn1_decoder.h"
-#include "common.h"
-#include "ui.h"
-#include "verifier.h"
-
-#include "mincrypt/dsa_sig.h"
-#include "mincrypt/p256.h"
-#include "mincrypt/p256_ecdsa.h"
-#include "mincrypt/rsa.h"
-#include "mincrypt/sha.h"
-#include "mincrypt/sha256.h"
-
#include <errno.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
+#include <algorithm>
+#include <memory>
+
+#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;
/*
@@ -194,15 +193,15 @@ int verify_file(unsigned char* addr, size_t length,
bool need_sha256 = false;
for (const auto& key : keys) {
switch (key.hash_len) {
- case SHA_DIGEST_SIZE: need_sha1 = true; break;
- case SHA256_DIGEST_SIZE: need_sha256 = true; break;
+ case SHA_DIGEST_LENGTH: need_sha1 = true; break;
+ case SHA256_DIGEST_LENGTH: need_sha256 = true; break;
}
}
SHA_CTX sha1_ctx;
SHA256_CTX sha256_ctx;
- SHA_init(&sha1_ctx);
- SHA256_init(&sha256_ctx);
+ SHA1_Init(&sha1_ctx);
+ SHA256_Init(&sha256_ctx);
double frac = -1.0;
size_t so_far = 0;
@@ -210,8 +209,8 @@ int verify_file(unsigned char* addr, size_t length,
size_t size = signed_len - so_far;
if (size > BUFFER_SIZE) size = BUFFER_SIZE;
- if (need_sha1) SHA_update(&sha1_ctx, addr + so_far, size);
- if (need_sha256) SHA256_update(&sha256_ctx, addr + so_far, 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;
@@ -221,8 +220,10 @@ int verify_file(unsigned char* addr, size_t length,
}
}
- const uint8_t* sha1 = SHA_final(&sha1_ctx);
- const uint8_t* sha256 = SHA256_final(&sha256_ctx);
+ 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;
@@ -242,23 +243,25 @@ int verify_file(unsigned char* addr, size_t length,
size_t i = 0;
for (const auto& key : keys) {
const uint8_t* hash;
+ int hash_nid;
switch (key.hash_len) {
- case SHA_DIGEST_SIZE: hash = sha1; break;
- case SHA256_DIGEST_SIZE: hash = sha256; break;
- default: continue;
+ 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::RSA) {
- if (sig_der_length < RSANUMBYTES) {
- // "signature" block isn't big enough to contain an RSA block.
- LOGI("signature is too short for RSA key %zu\n", i);
- continue;
- }
-
- if (!RSA_verify(key.rsa.get(), sig_der, RSANUMBYTES,
- hash, key.hash_len)) {
+ 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;
}
@@ -266,18 +269,10 @@ int verify_file(unsigned char* addr, size_t length,
LOGI("whole-file signature verified against RSA key %zu\n", i);
free(sig_der);
return VERIFY_SUCCESS;
- } else if (key.key_type == Certificate::EC
- && key.hash_len == SHA256_DIGEST_SIZE) {
- p256_int r, s;
- if (!dsa_sig_unpack(sig_der, sig_der_length, &r, &s)) {
- LOGI("Not a DSA signature block for EC key %zu\n", i);
- continue;
- }
-
- p256_int p256_hash;
- p256_from_bin(hash, &p256_hash);
- if (!p256_ecdsa_verify(&(key.ec->x), &(key.ec->y),
- &p256_hash, &r, &s)) {
+ } 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;
}
@@ -295,6 +290,144 @@ int verify_file(unsigned char* addr, size_t length,
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:
@@ -335,94 +468,57 @@ bool load_keys(const char* filename, std::vector<Certificate>& certs) {
}
while (true) {
- certs.emplace_back(0, Certificate::RSA, nullptr, nullptr);
+ 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::RSA;
- cert.rsa = std::unique_ptr<RSAPublicKey>(new RSAPublicKey);
- cert.rsa->exponent = 3;
- cert.hash_len = SHA_DIGEST_SIZE;
+ 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::RSA;
- cert.rsa = std::unique_ptr<RSAPublicKey>(new RSAPublicKey);
- cert.rsa->exponent = 65537;
- cert.hash_len = SHA_DIGEST_SIZE;
+ cert.key_type = Certificate::KEY_TYPE_RSA;
+ exponent = 65537;
+ cert.hash_len = SHA_DIGEST_LENGTH;
break;
case 3:
- cert.key_type = Certificate::RSA;
- cert.rsa = std::unique_ptr<RSAPublicKey>(new RSAPublicKey);
- cert.rsa->exponent = 3;
- cert.hash_len = SHA256_DIGEST_SIZE;
+ cert.key_type = Certificate::KEY_TYPE_RSA;
+ exponent = 3;
+ cert.hash_len = SHA256_DIGEST_LENGTH;
break;
case 4:
- cert.key_type = Certificate::RSA;
- cert.rsa = std::unique_ptr<RSAPublicKey>(new RSAPublicKey);
- cert.rsa->exponent = 65537;
- cert.hash_len = SHA256_DIGEST_SIZE;
+ cert.key_type = Certificate::KEY_TYPE_RSA;
+ exponent = 65537;
+ cert.hash_len = SHA256_DIGEST_LENGTH;
break;
case 5:
- cert.key_type = Certificate::EC;
- cert.ec = std::unique_ptr<ECPublicKey>(new ECPublicKey);
- cert.hash_len = SHA256_DIGEST_SIZE;
+ cert.key_type = Certificate::KEY_TYPE_EC;
+ cert.hash_len = SHA256_DIGEST_LENGTH;
break;
default:
return false;
}
}
- if (cert.key_type == Certificate::RSA) {
- RSAPublicKey* key = cert.rsa.get();
- if (fscanf(f.get(), " %i , 0x%x , { %u", &(key->len), &(key->n0inv),
- &(key->n[0])) != 3) {
- return false;
- }
- if (key->len != RSANUMWORDS) {
- LOGE("key length (%d) does not match expected size\n", key->len);
- return false;
- }
- for (int i = 1; i < key->len; ++i) {
- if (fscanf(f.get(), " , %u", &(key->n[i])) != 1) return false;
+ if (cert.key_type == Certificate::KEY_TYPE_RSA) {
+ cert.rsa = parse_rsa_key(f.get(), exponent);
+ if (!cert.rsa) {
+ return false;
}
- if (fscanf(f.get(), " } , { %u", &(key->rr[0])) != 1) return false;
- for (int i = 1; i < key->len; ++i) {
- if (fscanf(f.get(), " , %u", &(key->rr[i])) != 1) return false;
- }
- fscanf(f.get(), " } } ");
-
- LOGI("read key e=%d hash=%d\n", key->exponent, cert.hash_len);
- } else if (cert.key_type == Certificate::EC) {
- ECPublicKey* key = cert.ec.get();
- int key_len;
- unsigned int byte;
- uint8_t x_bytes[P256_NBYTES];
- uint8_t y_bytes[P256_NBYTES];
- if (fscanf(f.get(), " %i , { %u", &key_len, &byte) != 2) return false;
- if (key_len != P256_NBYTES) {
- LOGE("Key length (%d) does not match expected size %d\n", key_len, P256_NBYTES);
- return false;
- }
- x_bytes[P256_NBYTES - 1] = byte;
- for (int i = P256_NBYTES - 2; i >= 0; --i) {
- if (fscanf(f.get(), " , %u", &byte) != 1) return false;
- x_bytes[i] = byte;
- }
- if (fscanf(f.get(), " } , { %u", &byte) != 1) return false;
- y_bytes[P256_NBYTES - 1] = byte;
- for (int i = P256_NBYTES - 2; i >= 0; --i) {
- if (fscanf(f.get(), " , %u", &byte) != 1) return false;
- y_bytes[i] = byte;
+
+ 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;
}
- fscanf(f.get(), " } } ");
- p256_from_bin(x_bytes, &key->x);
- p256_from_bin(y_bytes, &key->y);
} else {
LOGE("Unknown key type %d\n", cert.key_type);
return false;
diff --git a/verifier.h b/verifier.h
index 4eafc7565..58083fe14 100644
--- a/verifier.h
+++ b/verifier.h
@@ -20,32 +20,42 @@
#include <memory>
#include <vector>
-#include "mincrypt/p256.h"
-#include "mincrypt/rsa.h"
+#include <openssl/ec_key.h>
+#include <openssl/rsa.h>
+#include <openssl/sha.h>
-typedef struct {
- p256_int x;
- p256_int y;
-} ECPublicKey;
+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 {
- RSA,
- EC,
+ KEY_TYPE_RSA,
+ KEY_TYPE_EC,
} KeyType;
- Certificate(int hash_len_, KeyType key_type_,
- std::unique_ptr<RSAPublicKey>&& rsa_,
- std::unique_ptr<ECPublicKey>&& ec_) :
- hash_len(hash_len_),
- key_type(key_type_),
- rsa(std::move(rsa_)),
- ec(std::move(ec_)) { }
+ 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_)) {}
- int hash_len; // SHA_DIGEST_SIZE (SHA-1) or SHA256_DIGEST_SIZE (SHA-256)
+ // SHA_DIGEST_LENGTH (SHA-1) or SHA256_DIGEST_LENGTH (SHA-256)
+ int hash_len;
KeyType key_type;
- std::unique_ptr<RSAPublicKey> rsa;
- std::unique_ptr<ECPublicKey> ec;
+ 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