diff options
29 files changed, 1192 insertions, 314 deletions
diff --git a/Android.mk b/Android.mk index 95e806ff9..355f4d841 100644 --- a/Android.mk +++ b/Android.mk @@ -43,6 +43,7 @@ LOCAL_SRC_FILES := \ ui.cpp \ verifier.cpp \ wear_ui.cpp \ + wear_touch.cpp \ LOCAL_MODULE := recovery diff --git a/install.cpp b/install.cpp index a7b59c3e7..7113fa286 100644 --- a/install.cpp +++ b/install.cpp @@ -23,6 +23,7 @@ #include <sys/wait.h> #include <unistd.h> +#include <chrono> #include <vector> #include "common.h" @@ -228,6 +229,7 @@ really_install_package(const char *path, bool* wipe_cache, bool needs_mount) return INSTALL_CORRUPT; } + // Load keys. std::vector<Certificate> loadedKeys; if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) { LOGE("Failed to load keys\n"); @@ -235,18 +237,19 @@ really_install_package(const char *path, bool* wipe_cache, bool needs_mount) } LOGI("%zu key(s) loaded from %s\n", loadedKeys.size(), PUBLIC_KEYS_FILE); + // Verify package. ui->Print("Verifying update package...\n"); - + auto t0 = std::chrono::system_clock::now(); int err = verify_file(map.addr, map.length, loadedKeys); - LOGI("verify_file returned %d\n", err); + std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0; + ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err); if (err != VERIFY_SUCCESS) { LOGE("signature verification failed\n"); sysReleaseMap(&map); return INSTALL_CORRUPT; } - /* Try to open the package. - */ + // Try to open the package. ZipArchive zip; err = mzOpenZipArchive(map.addr, map.length, &zip); if (err != 0) { @@ -255,8 +258,7 @@ really_install_package(const char *path, bool* wipe_cache, bool needs_mount) return INSTALL_CORRUPT; } - /* Verify and install the contents of the package. - */ + // Verify and install the contents of the package. ui->Print("Installing update...\n"); ui->SetEnableReboot(false); int result = try_update_binary(path, &zip, wipe_cache); diff --git a/minzip/SysUtil.c b/minzip/SysUtil.c index 09ec8768f..e7dd17b51 100644 --- a/minzip/SysUtil.c +++ b/minzip/SysUtil.c @@ -39,6 +39,11 @@ static bool sysMapFD(int fd, MemMapping* pMap) { pMap->length = sb.st_size; pMap->range_count = 1; pMap->ranges = malloc(sizeof(MappedRange)); + if (pMap->ranges == NULL) { + LOGE("malloc failed: %s\n", strerror(errno)); + munmap(memPtr, sb.st_size); + return false; + } pMap->ranges[0].addr = memPtr; pMap->ranges[0].length = sb.st_size; @@ -50,7 +55,7 @@ static int sysMapBlockFile(FILE* mapf, MemMapping* pMap) char block_dev[PATH_MAX+1]; size_t size; unsigned int blksize; - unsigned int blocks; + size_t blocks; unsigned int range_count; unsigned int i; @@ -69,49 +74,80 @@ static int sysMapBlockFile(FILE* mapf, MemMapping* pMap) LOGE("failed to parse block map header\n"); return -1; } - - blocks = ((size-1) / blksize) + 1; + if (blksize != 0) { + blocks = ((size-1) / blksize) + 1; + } + if (size == 0 || blksize == 0 || blocks > SIZE_MAX / blksize || range_count == 0) { + LOGE("invalid data in block map file: size %zu, blksize %u, range_count %u\n", + size, blksize, range_count); + return -1; + } pMap->range_count = range_count; - pMap->ranges = malloc(range_count * sizeof(MappedRange)); - memset(pMap->ranges, 0, range_count * sizeof(MappedRange)); + pMap->ranges = calloc(range_count, sizeof(MappedRange)); + if (pMap->ranges == NULL) { + LOGE("calloc(%u, %zu) failed: %s\n", range_count, sizeof(MappedRange), strerror(errno)); + return -1; + } // Reserve enough contiguous address space for the whole file. unsigned char* reserve; reserve = mmap64(NULL, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0); if (reserve == MAP_FAILED) { LOGE("failed to reserve address space: %s\n", strerror(errno)); + free(pMap->ranges); return -1; } - pMap->ranges[range_count-1].addr = reserve; - pMap->ranges[range_count-1].length = blocks * blksize; - int fd = open(block_dev, O_RDONLY); if (fd < 0) { LOGE("failed to open block device %s: %s\n", block_dev, strerror(errno)); + munmap(reserve, blocks * blksize); + free(pMap->ranges); return -1; } unsigned char* next = reserve; + size_t remaining_size = blocks * blksize; + bool success = true; for (i = 0; i < range_count; ++i) { - int start, end; - if (fscanf(mapf, "%d %d\n", &start, &end) != 2) { + size_t start, end; + if (fscanf(mapf, "%zu %zu\n", &start, &end) != 2) { LOGE("failed to parse range %d in block map\n", i); - return -1; + success = false; + break; + } + size_t length = (end - start) * blksize; + if (end <= start || (end - start) > SIZE_MAX / blksize || length > remaining_size) { + LOGE("unexpected range in block map: %zu %zu\n", start, end); + success = false; + break; } - void* addr = mmap64(next, (end-start)*blksize, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, ((off64_t)start)*blksize); + void* addr = mmap64(next, length, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, ((off64_t)start)*blksize); if (addr == MAP_FAILED) { LOGE("failed to map block %d: %s\n", i, strerror(errno)); - return -1; + success = false; + break; } pMap->ranges[i].addr = addr; - pMap->ranges[i].length = (end-start)*blksize; + pMap->ranges[i].length = length; - next += pMap->ranges[i].length; + next += length; + remaining_size -= length; + } + if (success && remaining_size != 0) { + LOGE("ranges in block map are invalid: remaining_size = %zu\n", remaining_size); + success = false; + } + if (!success) { + close(fd); + munmap(reserve, blocks * blksize); + free(pMap->ranges); + return -1; } + close(fd); pMap->addr = reserve; pMap->length = size; @@ -134,6 +170,7 @@ int sysMapFile(const char* fn, MemMapping* pMap) if (sysMapBlockFile(mapf, pMap) != 0) { LOGE("Map of '%s' failed\n", fn); + fclose(mapf); return -1; } diff --git a/otafault/Android.mk b/otafault/Android.mk index 50e385efb..d0b1174a4 100644 --- a/otafault/Android.mk +++ b/otafault/Android.mk @@ -14,8 +14,6 @@ LOCAL_PATH := $(call my-dir) -# otafault (static library) -# =============================== include $(CLEAR_VARS) otafault_static_libs := \ @@ -25,11 +23,12 @@ otafault_static_libs := \ libselinux LOCAL_SRC_FILES := config.cpp ota_io.cpp +LOCAL_MODULE_TAGS := eng LOCAL_MODULE := libotafault LOCAL_CLANG := true LOCAL_C_INCLUDES := bootable/recovery LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH) -LOCAL_STATIC_LIBRARIES := $(otafault_static_libs) +LOCAL_WHOLE_STATIC_LIBRARIES := $(otafault_static_libs) include $(BUILD_STATIC_LIBRARY) @@ -40,9 +39,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := config.cpp ota_io.cpp test.cpp LOCAL_MODULE_TAGS := tests LOCAL_MODULE := otafault_test -LOCAL_STATIC_LIBRARIES := \ - libotafault \ - $(otafault_static_libs) +LOCAL_STATIC_LIBRARIES := $(otafault_static_libs) LOCAL_C_INCLUDES := bootable/recovery LOCAL_FORCE_STATIC_EXECUTABLE := true diff --git a/otafault/ota_io.cpp b/otafault/ota_io.cpp index dd805e56e..94be8155a 100644 --- a/otafault/ota_io.cpp +++ b/otafault/ota_io.cpp @@ -29,7 +29,6 @@ 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() @@ -49,6 +48,8 @@ void ota_set_fault_files() { } } +bool have_eio_error = false; + int ota_open(const char* path, int oflags) { // Let the caller handle errors; we do not care if open succeeds or fails int fd = open(path, oflags); diff --git a/recovery.cpp b/recovery.cpp index 169413ad5..921c01b6b 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -83,7 +83,10 @@ static const char *COMMAND_FILE = "/cache/recovery/command"; static const char *LOG_FILE = "/cache/recovery/log"; static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install"; static const char *LOCALE_FILE = "/cache/recovery/last_locale"; +static const char *CONVERT_FBE_DIR = "/tmp/convert_fbe"; +static const char *CONVERT_FBE_FILE = "/tmp/convert_fbe/convert_fbe"; static const char *CACHE_ROOT = "/cache"; +static const char *DATA_ROOT = "/data"; static const char *SDCARD_ROOT = "/sdcard"; static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install"; @@ -530,6 +533,7 @@ typedef struct _saved_log_file { static bool erase_volume(const char* volume) { bool is_cache = (strcmp(volume, CACHE_ROOT) == 0); + bool is_data = (strcmp(volume, DATA_ROOT) == 0); ui->SetBackground(RecoveryUI::ERASING); ui->SetProgressType(RecoveryUI::INDETERMINATE); @@ -584,7 +588,28 @@ static bool erase_volume(const char* volume) { ui->Print("Formatting %s...\n", volume); ensure_path_unmounted(volume); - int result = format_volume(volume); + + int result; + + if (is_data && reason && strcmp(reason, "convert_fbe") == 0) { + // Create convert_fbe breadcrumb file to signal to init + // to convert to file based encryption, not full disk encryption + if (mkdir(CONVERT_FBE_DIR, 0700) != 0) { + ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno)); + return true; + } + FILE* f = fopen(CONVERT_FBE_FILE, "wb"); + if (!f) { + ui->Print("Failed to convert to file encryption %s\n", strerror(errno)); + return true; + } + fclose(f); + result = format_volume(volume, CONVERT_FBE_DIR); + remove(CONVERT_FBE_FILE); + rmdir(CONVERT_FBE_DIR); + } else { + result = format_volume(volume); + } if (is_cache) { while (head) { @@ -175,7 +175,7 @@ static int exec_cmd(const char* path, char* const argv[]) { return WEXITSTATUS(status); } -int format_volume(const char* volume) { +int format_volume(const char* volume, const char* directory) { Volume* v = volume_for_path(volume); if (v == NULL) { LOGE("unknown volume \"%s\"\n", volume); @@ -241,7 +241,7 @@ int format_volume(const char* volume) { } int result; if (strcmp(v->fs_type, "ext4") == 0) { - result = make_ext4fs(v->blk_device, length, volume, sehandle); + result = make_ext4fs_directory(v->blk_device, length, volume, sehandle, directory); } else { /* Has to be f2fs because we checked earlier. */ if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0 && length < 0) { LOGE("format_volume: crypt footer + negative length (%zd) not supported on %s\n", length, v->fs_type); @@ -273,6 +273,10 @@ int format_volume(const char* volume) { return -1; } +int format_volume(const char* volume) { + return format_volume(volume, NULL); +} + int setup_install_mounts() { if (fstab == NULL) { LOGE("can't set up install mounts: no fstab loaded\n"); @@ -41,6 +41,12 @@ int ensure_path_unmounted(const char* path); // it is mounted. int format_volume(const char* volume); +// Reformat the given volume (must be the mount point only, eg +// "/cache"), no paths permitted. Attempts to unmount the volume if +// it is mounted. +// Copies 'directory' to root of the newly formatted volume +int format_volume(const char* volume, const char* directory); + // Ensure that all and only the volumes that packages expect to find // mounted (/tmp and /cache) are mounted. Returns 0 on success. int setup_install_mounts(); diff --git a/screen_ui.cpp b/screen_ui.cpp index 522aa6b23..3614e7a83 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -40,8 +40,7 @@ #include "screen_ui.h" #include "ui.h" -static int char_width; -static int char_height; +#define TEXT_INDENT 4 // Return the current time as a double (including fractions of a second). static double now() { @@ -54,7 +53,6 @@ ScreenRecoveryUI::ScreenRecoveryUI() : currentIcon(NONE), installingFrame(0), locale(nullptr), - rtl_locale(false), progressBarType(EMPTY), progressScopeStart(0), progressScopeSize(0), @@ -76,7 +74,8 @@ ScreenRecoveryUI::ScreenRecoveryUI() : animation_fps(-1), installing_frames(-1), stage(-1), - max_stage(-1) { + max_stage(-1), + rtl_locale(false) { for (int i = 0; i < 5; i++) { backgroundIcon[i] = nullptr; @@ -213,14 +212,14 @@ void ScreenRecoveryUI::DrawHorizontalRule(int* y) { *y += 4; } -void ScreenRecoveryUI::DrawTextLine(int* y, const char* line, bool bold) { - gr_text(4, *y, line, bold); - *y += char_height + 4; +void ScreenRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) { + gr_text(x, *y, line, bold); + *y += char_height_ + 4; } -void ScreenRecoveryUI::DrawTextLines(int* y, const char* const* lines) { +void ScreenRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) { for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { - DrawTextLine(y, lines[i], false); + DrawTextLine(x, y, lines[i], false); } } @@ -251,14 +250,15 @@ void ScreenRecoveryUI::draw_screen_locked() { property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, ""); SetColor(INFO); - DrawTextLine(&y, "Android Recovery", true); + DrawTextLine(TEXT_INDENT, &y, "Android Recovery", true); for (auto& chunk : android::base::Split(recovery_fingerprint, ":")) { - DrawTextLine(&y, chunk.c_str(), false); + DrawTextLine(TEXT_INDENT, &y, chunk.c_str(), false); } - DrawTextLines(&y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); + DrawTextLines(TEXT_INDENT, &y, + HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); SetColor(HEADER); - DrawTextLines(&y, menu_headers_); + DrawTextLines(TEXT_INDENT, &y, menu_headers_); SetColor(MENU); DrawHorizontalRule(&y); @@ -267,7 +267,7 @@ void ScreenRecoveryUI::draw_screen_locked() { if (i == menu_sel) { // Draw the highlight bar. SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG); - gr_fill(0, y - 2, gr_fb_width(), y + char_height + 2); + gr_fill(0, y - 2, gr_fb_width(), y + char_height_ + 2); // Bold white text for the selected item. SetColor(MENU_SEL_FG); gr_text(4, y, menu_[i], true); @@ -275,7 +275,7 @@ void ScreenRecoveryUI::draw_screen_locked() { } else { gr_text(4, y, menu_[i], false); } - y += char_height + 4; + y += char_height_ + 4; } DrawHorizontalRule(&y); } @@ -286,9 +286,9 @@ void ScreenRecoveryUI::draw_screen_locked() { SetColor(LOG); int row = (text_top_ + text_rows_ - 1) % text_rows_; size_t count = 0; - for (int ty = gr_fb_height() - char_height; + for (int ty = gr_fb_height() - char_height_; ty >= y && count < text_rows_; - ty -= char_height, ++count) { + ty -= char_height_, ++count) { gr_text(0, ty, text_[row], false); --row; if (row < 0) row = text_rows_ - 1; @@ -394,9 +394,9 @@ static char** Alloc2d(size_t rows, size_t cols) { void ScreenRecoveryUI::Init() { gr_init(); - gr_font_size(&char_width, &char_height); - text_rows_ = gr_fb_height() / char_height; - text_cols_ = gr_fb_width() / char_width; + gr_font_size(&char_width_, &char_height_); + text_rows_ = gr_fb_height() / char_height_; + text_cols_ = gr_fb_width() / char_width_; text_ = Alloc2d(text_rows_, text_cols_ + 1); file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); diff --git a/screen_ui.h b/screen_ui.h index 08a5f44a9..9e1b2dfa1 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -71,9 +71,7 @@ class ScreenRecoveryUI : public RecoveryUI { Icon currentIcon; int installingFrame; const char* locale; - bool rtl_locale; - pthread_mutex_t updateMutex; GRSurface* backgroundIcon[5]; GRSurface* backgroundText[5]; GRSurface** installation; @@ -133,12 +131,17 @@ class ScreenRecoveryUI : public RecoveryUI { void ClearText(); void DrawHorizontalRule(int* y); - void DrawTextLine(int* y, const char* line, bool bold); - void DrawTextLines(int* y, const char* const* lines); - void LoadBitmap(const char* filename, GRSurface** surface); void LoadBitmapArray(const char* filename, int* frames, int* fps, GRSurface*** surface); void LoadLocalizedBitmap(const char* filename, GRSurface** surface); + protected: + int char_width_; + int char_height_; + pthread_mutex_t updateMutex; + bool rtl_locale; + void LoadBitmap(const char* filename, GRSurface** surface); + void DrawTextLine(int x, int* y, const char* line, bool bold); + void DrawTextLines(int x, int* y, const char* const* lines); }; #endif // RECOVERY_UI_H diff --git a/tests/Android.mk b/tests/Android.mk index 7b004b2a0..81435bea4 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -31,12 +31,18 @@ include $(BUILD_NATIVE_TEST) # Component tests include $(CLEAR_VARS) LOCAL_CLANG := true +LOCAL_CFLAGS += -Wno-unused-parameter LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk LOCAL_MODULE := recovery_component_test LOCAL_C_INCLUDES := bootable/recovery -LOCAL_SRC_FILES := component/verifier_test.cpp +LOCAL_SRC_FILES := \ + component/verifier_test.cpp \ + component/applypatch_test.cpp LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_STATIC_LIBRARIES := \ + libapplypatch \ + libotafault \ + libmtdutils \ libbase \ libverifier \ libcrypto_utils_static \ @@ -44,6 +50,8 @@ LOCAL_STATIC_LIBRARIES := \ libminui \ libminzip \ libcutils \ + libbz \ + libz \ libc testdata_out_path := $(TARGET_OUT_DATA_NATIVE_TESTS)/recovery diff --git a/tests/common/test_constants.h b/tests/common/test_constants.h new file mode 100644 index 000000000..3490f6805 --- /dev/null +++ b/tests/common/test_constants.h @@ -0,0 +1,25 @@ +/* + * 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 agree to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _OTA_TEST_CONSTANTS_H +#define _OTA_TEST_CONSTANTS_H + +#if defined(__LP64__) +#define NATIVE_TEST_PATH "/nativetest64" +#else +#define NATIVE_TEST_PATH "/nativetest" +#endif + +#endif diff --git a/tests/component/applypatch_test.cpp b/tests/component/applypatch_test.cpp new file mode 100644 index 000000000..b44ddd17c --- /dev/null +++ b/tests/component/applypatch_test.cpp @@ -0,0 +1,392 @@ +/* + * 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 agree to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fcntl.h> +#include <gtest/gtest.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#include <sys/types.h> +#include <time.h> + +#include <string> + +#include <android-base/file.h> +#include <android-base/stringprintf.h> +#include <android-base/test_utils.h> + +#include "applypatch/applypatch.h" +#include "common/test_constants.h" +#include "openssl/sha.h" +#include "print_sha1.h" + +static const std::string DATA_PATH = getenv("ANDROID_DATA"); +static const std::string TESTDATA_PATH = "/recovery/testdata"; +static const std::string WORK_FS = "/data"; + +static std::string sha1sum(const std::string& fname) { + uint8_t digest[SHA_DIGEST_LENGTH]; + std::string data; + android::base::ReadFileToString(fname, &data); + + SHA1((const uint8_t*)data.c_str(), data.size(), digest); + return print_sha1(digest); +} + +static void mangle_file(const std::string& fname) { + FILE* fh = fopen(&fname[0], "w"); + int r; + for (int i=0; i < 1024; i++) { + r = rand(); + fwrite(&r, sizeof(short), 1, fh); + } + fclose(fh); +} + +static bool file_cmp(std::string& f1, std::string& f2) { + std::string c1; + std::string c2; + android::base::ReadFileToString(f1, &c1); + android::base::ReadFileToString(f2, &c2); + return c1 == c2; +} + +static std::string from_testdata_base(const std::string fname) { + return android::base::StringPrintf("%s%s%s/%s", + &DATA_PATH[0], + &NATIVE_TEST_PATH[0], + &TESTDATA_PATH[0], + &fname[0]); +} + +class ApplyPatchTest : public ::testing::Test { + public: + static void SetUpTestCase() { + // set up files + old_file = from_testdata_base("old.file"); + new_file = from_testdata_base("new.file"); + patch_file = from_testdata_base("patch.bsdiff"); + rand_file = "/cache/applypatch_test_rand.file"; + cache_file = "/cache/saved.file"; + + // write stuff to rand_file + android::base::WriteStringToFile("hello", rand_file); + + // set up SHA constants + old_sha1 = sha1sum(old_file); + new_sha1 = sha1sum(new_file); + srand(time(NULL)); + bad_sha1_a = android::base::StringPrintf("%040x", rand()); + bad_sha1_b = android::base::StringPrintf("%040x", rand()); + + struct stat st; + stat(&new_file[0], &st); + new_size = st.st_size; + } + + static std::string old_file; + static std::string new_file; + static std::string rand_file; + static std::string cache_file; + static std::string patch_file; + + static std::string old_sha1; + static std::string new_sha1; + static std::string bad_sha1_a; + static std::string bad_sha1_b; + + static size_t new_size; +}; + +std::string ApplyPatchTest::old_file; +std::string ApplyPatchTest::new_file; + +static void cp(std::string src, std::string tgt) { + std::string cmd = android::base::StringPrintf("cp %s %s", + &src[0], + &tgt[0]); + system(&cmd[0]); +} + +static void backup_old() { + cp(ApplyPatchTest::old_file, ApplyPatchTest::cache_file); +} + +static void restore_old() { + cp(ApplyPatchTest::cache_file, ApplyPatchTest::old_file); +} + +class ApplyPatchCacheTest : public ApplyPatchTest { + public: + virtual void SetUp() { + backup_old(); + } + + virtual void TearDown() { + restore_old(); + } +}; + +class ApplyPatchFullTest : public ApplyPatchCacheTest { + public: + static void SetUpTestCase() { + ApplyPatchTest::SetUpTestCase(); + unsigned long free_kb = FreeSpaceForFile(&WORK_FS[0]); + ASSERT_GE(free_kb * 1024, new_size * 3 / 2); + output_f = new TemporaryFile(); + output_loc = std::string(output_f->path); + + struct FileContents fc; + + ASSERT_EQ(0, LoadFileContents(&rand_file[0], &fc)); + Value* patch1 = new Value(); + patch1->type = VAL_BLOB; + patch1->size = fc.data.size(); + patch1->data = static_cast<char*>(malloc(fc.data.size())); + memcpy(patch1->data, fc.data.data(), fc.data.size()); + patches.push_back(patch1); + + ASSERT_EQ(0, LoadFileContents(&patch_file[0], &fc)); + Value* patch2 = new Value(); + patch2->type = VAL_BLOB; + patch2->size = fc.st.st_size; + patch2->data = static_cast<char*>(malloc(fc.data.size())); + memcpy(patch2->data, fc.data.data(), fc.data.size()); + patches.push_back(patch2); + } + static void TearDownTestCase() { + delete output_f; + for (auto it = patches.begin(); it != patches.end(); ++it) { + free((*it)->data); + delete *it; + } + patches.clear(); + } + + static std::vector<Value*> patches; + static TemporaryFile* output_f; + static std::string output_loc; +}; + +class ApplyPatchDoubleCacheTest : public ApplyPatchFullTest { + public: + virtual void SetUp() { + ApplyPatchCacheTest::SetUp(); + cp(cache_file, "/cache/reallysaved.file"); + } + + virtual void TearDown() { + cp("/cache/reallysaved.file", cache_file); + ApplyPatchCacheTest::TearDown(); + } +}; + +std::string ApplyPatchTest::rand_file; +std::string ApplyPatchTest::patch_file; +std::string ApplyPatchTest::cache_file; +std::string ApplyPatchTest::old_sha1; +std::string ApplyPatchTest::new_sha1; +std::string ApplyPatchTest::bad_sha1_a; +std::string ApplyPatchTest::bad_sha1_b; + +size_t ApplyPatchTest::new_size; + +std::vector<Value*> ApplyPatchFullTest::patches; +TemporaryFile* ApplyPatchFullTest::output_f; +std::string ApplyPatchFullTest::output_loc; + +TEST_F(ApplyPatchTest, CheckModeSingle) { + char* s = &old_sha1[0]; + ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s)); +} + +TEST_F(ApplyPatchTest, CheckModeMultiple) { + char* argv[3] = { + &bad_sha1_a[0], + &old_sha1[0], + &bad_sha1_b[0] + }; + ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv)); +} + +TEST_F(ApplyPatchTest, CheckModeFailure) { + char* argv[2] = { + &bad_sha1_a[0], + &bad_sha1_b[0] + }; + ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv)); +} + +TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSingle) { + mangle_file(old_file); + char* s = &old_sha1[0]; + ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s)); +} + +TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedMultiple) { + mangle_file(old_file); + char* argv[3] = { + &bad_sha1_a[0], + &old_sha1[0], + &bad_sha1_b[0] + }; + ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv)); +} + +TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedFailure) { + mangle_file(old_file); + char* argv[2] = { + &bad_sha1_a[0], + &bad_sha1_b[0] + }; + ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv)); +} + +TEST_F(ApplyPatchCacheTest, CheckCacheMissingSingle) { + unlink(&old_file[0]); + char* s = &old_sha1[0]; + ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s)); +} + +TEST_F(ApplyPatchCacheTest, CheckCacheMissingMultiple) { + unlink(&old_file[0]); + char* argv[3] = { + &bad_sha1_a[0], + &old_sha1[0], + &bad_sha1_b[0] + }; + ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv)); +} + +TEST_F(ApplyPatchCacheTest, CheckCacheMissingFailure) { + unlink(&old_file[0]); + char* argv[2] = { + &bad_sha1_a[0], + &bad_sha1_b[0] + }; + ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv)); +} + +TEST_F(ApplyPatchFullTest, ApplyInPlace) { + std::vector<char*> sha1s; + sha1s.push_back(&bad_sha1_a[0]); + sha1s.push_back(&old_sha1[0]); + + int ap_result = applypatch(&old_file[0], + "-", + &new_sha1[0], + new_size, + 2, + sha1s.data(), + patches.data(), + nullptr); + ASSERT_EQ(0, ap_result); + ASSERT_TRUE(file_cmp(old_file, new_file)); + // reapply, applypatch is idempotent so it should succeed + ap_result = applypatch(&old_file[0], + "-", + &new_sha1[0], + new_size, + 2, + sha1s.data(), + patches.data(), + nullptr); + ASSERT_EQ(0, ap_result); + ASSERT_TRUE(file_cmp(old_file, new_file)); +} + +TEST_F(ApplyPatchFullTest, ApplyInNewLocation) { + std::vector<char*> sha1s; + sha1s.push_back(&bad_sha1_a[0]); + sha1s.push_back(&old_sha1[0]); + int ap_result = applypatch(&old_file[0], + &output_loc[0], + &new_sha1[0], + new_size, + 2, + sha1s.data(), + patches.data(), + nullptr); + ASSERT_EQ(0, ap_result); + ASSERT_TRUE(file_cmp(output_loc, new_file)); + ap_result = applypatch(&old_file[0], + &output_loc[0], + &new_sha1[0], + new_size, + 2, + sha1s.data(), + patches.data(), + nullptr); + ASSERT_EQ(0, ap_result); + ASSERT_TRUE(file_cmp(output_loc, new_file)); +} + +TEST_F(ApplyPatchFullTest, ApplyCorruptedInNewLocation) { + mangle_file(old_file); + std::vector<char*> sha1s; + sha1s.push_back(&bad_sha1_a[0]); + sha1s.push_back(&old_sha1[0]); + int ap_result = applypatch(&old_file[0], + &output_loc[0], + &new_sha1[0], + new_size, + 2, + sha1s.data(), + patches.data(), + nullptr); + ASSERT_EQ(0, ap_result); + ASSERT_TRUE(file_cmp(output_loc, new_file)); + ap_result = applypatch(&old_file[0], + &output_loc[0], + &new_sha1[0], + new_size, + 2, + sha1s.data(), + patches.data(), + nullptr); + ASSERT_EQ(0, ap_result); + ASSERT_TRUE(file_cmp(output_loc, new_file)); +} + +TEST_F(ApplyPatchDoubleCacheTest, ApplyDoubleCorruptedInNewLocation) { + mangle_file(old_file); + mangle_file(cache_file); + + std::vector<char*> sha1s; + sha1s.push_back(&bad_sha1_a[0]); + sha1s.push_back(&old_sha1[0]); + int ap_result = applypatch(&old_file[0], + &output_loc[0], + &new_sha1[0], + new_size, + 2, + sha1s.data(), + patches.data(), + nullptr); + ASSERT_NE(0, ap_result); + ASSERT_FALSE(file_cmp(output_loc, new_file)); + ap_result = applypatch(&old_file[0], + &output_loc[0], + &new_sha1[0], + new_size, + 2, + sha1s.data(), + patches.data(), + nullptr); + ASSERT_NE(0, ap_result); + ASSERT_FALSE(file_cmp(output_loc, new_file)); +} diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp index b5d70327e..6a5e36901 100644 --- a/tests/component/verifier_test.cpp +++ b/tests/component/verifier_test.cpp @@ -31,16 +31,11 @@ #include <android-base/stringprintf.h> #include "common.h" +#include "common/test_constants.h" #include "minzip/SysUtil.h" #include "ui.h" #include "verifier.h" -#if defined(__LP64__) -#define NATIVE_TEST_PATH "/nativetest64" -#else -#define NATIVE_TEST_PATH "/nativetest" -#endif - static const char* DATA_PATH = getenv("ANDROID_DATA"); static const char* TESTDATA_PATH = "/recovery/testdata/"; diff --git a/tests/testdata/new.file b/tests/testdata/new.file Binary files differnew file mode 100644 index 000000000..cdeb8fd50 --- /dev/null +++ b/tests/testdata/new.file diff --git a/tests/testdata/old.file b/tests/testdata/old.file Binary files differnew file mode 100644 index 000000000..166c8732e --- /dev/null +++ b/tests/testdata/old.file diff --git a/tests/testdata/patch.bsdiff b/tests/testdata/patch.bsdiff Binary files differnew file mode 100644 index 000000000..b78d38573 --- /dev/null +++ b/tests/testdata/patch.bsdiff diff --git a/tools/recovery_l10n/res/layout/main.xml b/tools/recovery_l10n/res/layout/main.xml index 0900b1102..05a16e1e4 100644 --- a/tools/recovery_l10n/res/layout/main.xml +++ b/tools/recovery_l10n/res/layout/main.xml @@ -19,7 +19,9 @@ <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor="#ffffffff" + android:fontFamily="sans-serif-medium" + android:textColor="#fff5f5f5" + android:textSize="14sp" android:background="#ff000000" android:maxWidth="480px" android:gravity="center" diff --git a/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java b/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java index 3f2bebe60..817a3ad7d 100644 --- a/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java +++ b/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java @@ -149,12 +149,9 @@ public class Main extends Activity { 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))); - } + for (String localeName : localeNames) { + Log.i(TAG, "locale = " + localeName); + locales.add(Locale.forLanguageTag(localeName)); } final Runnable seq = buildSequence(locales.toArray(new Locale[0])); diff --git a/uncrypt/Android.mk b/uncrypt/Android.mk index 6422cb2f4..09cfdfca5 100644 --- a/uncrypt/Android.mk +++ b/uncrypt/Android.mk @@ -15,6 +15,15 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) +LOCAL_CLANG := true +LOCAL_SRC_FILES := bootloader_message_writer.cpp +LOCAL_MODULE := libbootloader_message_writer +LOCAL_STATIC_LIBRARIES := libbase libfs_mgr +LOCAL_C_INCLUDES := $(LOCAL_PATH)/.. +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) LOCAL_CLANG := true @@ -24,7 +33,8 @@ LOCAL_C_INCLUDES := $(LOCAL_PATH)/.. LOCAL_MODULE := uncrypt -LOCAL_STATIC_LIBRARIES := libbase liblog libfs_mgr libcutils +LOCAL_STATIC_LIBRARIES := libbootloader_message_writer libbase \ + liblog libfs_mgr libcutils \ LOCAL_INIT_RC := uncrypt.rc diff --git a/uncrypt/bootloader_message_writer.cpp b/uncrypt/bootloader_message_writer.cpp new file mode 100644 index 000000000..3bb106aa0 --- /dev/null +++ b/uncrypt/bootloader_message_writer.cpp @@ -0,0 +1,107 @@ +/* + * 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 <errno.h> +#include <fcntl.h> +#include <string.h> +#include <sys/system_properties.h> + +#include <string> +#include <vector> + +#include <android-base/file.h> +#include <android-base/stringprintf.h> +#include <android-base/unique_fd.h> +#include <fs_mgr.h> + +#include "bootloader.h" + +static struct fstab* read_fstab(std::string* err) { + // The fstab path is always "/fstab.${ro.hardware}". + std::string fstab_path = "/fstab."; + char value[PROP_VALUE_MAX]; + if (__system_property_get("ro.hardware", value) == 0) { + *err = "failed to get ro.hardware"; + return nullptr; + } + fstab_path += value; + struct fstab* fstab = fs_mgr_read_fstab(fstab_path.c_str()); + if (fstab == nullptr) { + *err = "failed to read " + fstab_path; + } + return fstab; +} + +static std::string get_misc_blk_device(std::string* err) { + struct fstab* fstab = read_fstab(err); + if (fstab == nullptr) { + return ""; + } + fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab, "/misc"); + if (record == nullptr) { + *err = "failed to find /misc partition"; + return ""; + } + return record->blk_device; +} + +static bool write_bootloader_message(const bootloader_message& boot, std::string* err) { + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } + android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY | O_SYNC)); + if (fd.get() == -1) { + *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(), + strerror(errno)); + return false; + } + if (!android::base::WriteFully(fd.get(), &boot, sizeof(boot))) { + *err = android::base::StringPrintf("failed to write %s: %s", misc_blk_device.c_str(), + strerror(errno)); + return false; + } + // TODO: O_SYNC and fsync duplicates each other? + if (fsync(fd.get()) == -1) { + *err = android::base::StringPrintf("failed to fsync %s: %s", misc_blk_device.c_str(), + strerror(errno)); + return false; + } + return true; +} + +bool clear_bootloader_message(std::string* err) { + bootloader_message boot = {}; + return write_bootloader_message(boot, err); +} + +bool write_bootloader_message(const std::vector<std::string>& options, std::string* err) { + bootloader_message boot = {}; + strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); + strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); + for (const auto& s : options) { + strlcat(boot.recovery, s.c_str(), sizeof(boot.recovery)); + if (s.back() != '\n') { + strlcat(boot.recovery, "\n", sizeof(boot.recovery)); + } + } + return write_bootloader_message(boot, err); +} + +extern "C" bool write_bootloader_message(const char* options) { + std::string err; + return write_bootloader_message({options}, &err); +} diff --git a/uncrypt/include/bootloader_message_writer.h b/uncrypt/include/bootloader_message_writer.h new file mode 100644 index 000000000..e0ca3f44a --- /dev/null +++ b/uncrypt/include/bootloader_message_writer.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BOOTLOADER_MESSAGE_WRITER_H +#define BOOTLOADER_MESSAGE_WRITER_H + +#ifdef __cplusplus +#include <string> +#include <vector> + +bool clear_bootloader_message(std::string* err); + +bool write_bootloader_message(const std::vector<std::string>& options, std::string* err); + +#else +#include <stdbool.h> + +// C Interface. +bool write_bootloader_message(const char* options); +#endif + +#endif // BOOTLOADER_MESSAGE_WRITER_H diff --git a/uncrypt/uncrypt.cpp b/uncrypt/uncrypt.cpp index 43a2c2ab4..0e14da13f 100644 --- a/uncrypt/uncrypt.cpp +++ b/uncrypt/uncrypt.cpp @@ -39,6 +39,53 @@ // Recovery can take this block map file and retrieve the underlying // file data to use as an update package. +/** + * In addition to the uncrypt work, uncrypt also takes care of setting and + * clearing the bootloader control block (BCB) at /misc partition. + * + * uncrypt is triggered as init services on demand. It uses socket to + * communicate with its caller (i.e. system_server). The socket is managed by + * init (i.e. created prior to the service starts, and destroyed when uncrypt + * exits). + * + * Below is the uncrypt protocol. + * + * a. caller b. init c. uncrypt + * --------------- ------------ -------------- + * a1. ctl.start: + * setup-bcb / + * clear-bcb / + * uncrypt + * + * b2. create socket at + * /dev/socket/uncrypt + * + * c3. listen and accept + * + * a4. send a 4-byte int + * (message length) + * c5. receive message length + * a6. send message + * c7. receive message + * c8. <do the work; may send + * the progress> + * a9. <may handle progress> + * c10. <upon finishing> + * send "100" or "-1" + * + * a11. receive status code + * a12. send a 4-byte int to + * ack the receive of the + * final status code + * c13. receive and exit + * + * b14. destroy the socket + * + * Note that a12 and c13 are necessary to ensure a11 happens before the socket + * gets destroyed in b14. + */ + +#include <arpa/inet.h> #include <errno.h> #include <fcntl.h> #include <inttypes.h> @@ -49,6 +96,7 @@ #include <stdlib.h> #include <string.h> #include <sys/mman.h> +#include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> @@ -62,23 +110,32 @@ #include <android-base/stringprintf.h> #include <android-base/strings.h> #include <android-base/unique_fd.h> +#include <bootloader_message_writer.h> #include <cutils/android_reboot.h> #include <cutils/properties.h> +#include <cutils/sockets.h> #include <fs_mgr.h> #define LOG_TAG "uncrypt" #include <log/log.h> -#include "bootloader.h" - #define WINDOW_SIZE 5 +// uncrypt provides three services: SETUP_BCB, CLEAR_BCB and UNCRYPT. +// +// SETUP_BCB and CLEAR_BCB services use socket communication and do not rely +// on /cache partitions. They will handle requests to reboot into recovery +// (for applying updates for non-A/B devices, or factory resets for all +// devices). +// +// UNCRYPT service still needs files on /cache partition (UNCRYPT_PATH_FILE +// and CACHE_BLOCK_MAP). It will be working (and needed) only for non-A/B +// devices, on which /cache partitions always exist. static const std::string CACHE_BLOCK_MAP = "/cache/recovery/block.map"; -static const std::string COMMAND_FILE = "/cache/recovery/command"; -static const std::string STATUS_FILE = "/cache/recovery/uncrypt_status"; static const std::string UNCRYPT_PATH_FILE = "/cache/recovery/uncrypt_file"; +static const std::string UNCRYPT_SOCKET = "uncrypt"; -static struct fstab* fstab = NULL; +static struct fstab* fstab = nullptr; static int write_at_offset(unsigned char* buffer, size_t size, int wfd, off64_t offset) { if (TEMP_FAILURE_RETRY(lseek64(wfd, offset, SEEK_SET)) == -1) { @@ -152,6 +209,11 @@ static const char* find_block_device(const char* path, bool* encryptable, bool* return NULL; } +static bool write_status_to_socket(int status, int socket) { + int status_out = htonl(status); + return android::base::WriteFully(socket, &status_out, sizeof(int)); +} + // Parse uncrypt_file to find the update package name. static bool find_uncrypt_package(const std::string& uncrypt_path_file, std::string* package_name) { CHECK(package_name != nullptr); @@ -167,7 +229,7 @@ static bool find_uncrypt_package(const std::string& uncrypt_path_file, std::stri } static int produce_block_map(const char* path, const char* map_file, const char* blk_dev, - bool encrypted, int status_fd) { + bool encrypted, int socket) { std::string err; if (!android::base::RemoveFileIfExists(map_file, &err)) { ALOGE("failed to remove the existing map file %s: %s", map_file, err.c_str()); @@ -181,9 +243,9 @@ static int produce_block_map(const char* path, const char* map_file, const char* return -1; } - // Make sure we can write to the status_file. - if (!android::base::WriteStringToFd("0\n", status_fd)) { - ALOGE("failed to update \"%s\"\n", STATUS_FILE.c_str()); + // Make sure we can write to the socket. + if (!write_status_to_socket(0, socket)) { + ALOGE("failed to write to socket %d\n", socket); return -1; } @@ -235,8 +297,8 @@ static int produce_block_map(const char* path, const char* map_file, const char* // Update the status file, progress must be between [0, 99]. int progress = static_cast<int>(100 * (double(pos) / double(sb.st_size))); if (progress > last_progress) { - last_progress = progress; - android::base::WriteStringToFd(std::to_string(progress) + "\n", status_fd); + last_progress = progress; + write_status_to_socket(progress, socket); } if ((tail+1) % WINDOW_SIZE == head) { @@ -349,54 +411,7 @@ static int produce_block_map(const char* path, const char* map_file, const char* return 0; } -static std::string get_misc_blk_device() { - struct fstab* fstab = read_fstab(); - if (fstab == nullptr) { - return ""; - } - for (int i = 0; i < fstab->num_entries; ++i) { - fstab_rec* v = &fstab->recs[i]; - if (v->mount_point != nullptr && strcmp(v->mount_point, "/misc") == 0) { - return v->blk_device; - } - } - return ""; -} - -static int write_bootloader_message(const bootloader_message* in) { - std::string misc_blk_device = get_misc_blk_device(); - if (misc_blk_device.empty()) { - ALOGE("failed to find /misc partition."); - return -1; - } - android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY | O_SYNC)); - if (fd == -1) { - ALOGE("failed to open %s: %s", misc_blk_device.c_str(), strerror(errno)); - return -1; - } - if (!android::base::WriteFully(fd, in, sizeof(*in))) { - ALOGE("failed to write %s: %s", misc_blk_device.c_str(), strerror(errno)); - return -1; - } - // TODO: O_SYNC and fsync() duplicates each other? - if (fsync(fd) == -1) { - ALOGE("failed to fsync %s: %s", misc_blk_device.c_str(), strerror(errno)); - return -1; - } - return 0; -} - -static void reboot_to_recovery() { - ALOGI("rebooting to recovery"); - property_set("sys.powerctl", "reboot,recovery"); - while (true) { - pause(); - } - ALOGE("reboot didn't succeed?"); -} - -static int uncrypt(const char* input_path, const char* map_file, int status_fd) { - +static int uncrypt(const char* input_path, const char* map_file, const int socket) { ALOGI("update package is \"%s\"", input_path); // Turn the name of the file we're supposed to convert into an @@ -407,10 +422,6 @@ static int uncrypt(const char* input_path, const char* map_file, int status_fd) return 1; } - if (read_fstab() == NULL) { - return 1; - } - bool encryptable; bool encrypted; const char* blk_dev = find_block_device(path, &encryptable, &encrypted); @@ -434,7 +445,7 @@ static int uncrypt(const char* input_path, const char* map_file, int status_fd) // and /sdcard we leave the file alone. if (strncmp(path, "/data/", 6) == 0) { ALOGI("writing block map %s", map_file); - if (produce_block_map(path, map_file, blk_dev, encrypted, status_fd) != 0) { + if (produce_block_map(path, map_file, blk_dev, encrypted, socket) != 0) { return 1; } } @@ -442,102 +453,141 @@ static int uncrypt(const char* input_path, const char* map_file, int status_fd) return 0; } -static int uncrypt_wrapper(const char* input_path, const char* map_file, - const std::string& status_file) { - // The pipe has been created by the system server. - android::base::unique_fd status_fd(open(status_file.c_str(), - O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR)); - if (status_fd == -1) { - ALOGE("failed to open pipe \"%s\": %s", status_file.c_str(), strerror(errno)); - return 1; - } - +static bool uncrypt_wrapper(const char* input_path, const char* map_file, const int socket) { std::string package; if (input_path == nullptr) { if (!find_uncrypt_package(UNCRYPT_PATH_FILE, &package)) { - android::base::WriteStringToFd("-1\n", status_fd); - return 1; + write_status_to_socket(-1, socket); + return false; } input_path = package.c_str(); } CHECK(map_file != nullptr); - int status = uncrypt(input_path, map_file, status_fd); + int status = uncrypt(input_path, map_file, socket); if (status != 0) { - android::base::WriteStringToFd("-1\n", status_fd); - return 1; + write_status_to_socket(-1, socket); + return false; } - android::base::WriteStringToFd("100\n", status_fd); - return 0; + write_status_to_socket(100, socket); + return true; } -static int clear_bcb(const std::string& status_file) { - android::base::unique_fd status_fd(open(status_file.c_str(), - O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR)); - if (status_fd == -1) { - ALOGE("failed to open pipe \"%s\": %s", status_file.c_str(), strerror(errno)); - return 1; - } - bootloader_message boot = {}; - if (write_bootloader_message(&boot) != 0) { - android::base::WriteStringToFd("-1\n", status_fd); - return 1; +static bool clear_bcb(const int socket) { + std::string err; + if (!clear_bootloader_message(&err)) { + ALOGE("failed to clear bootloader message: %s", err.c_str()); + write_status_to_socket(-1, socket); + return false; } - android::base::WriteStringToFd("100\n", status_fd); - return 0; + write_status_to_socket(100, socket); + return true; } -static int setup_bcb(const std::string& command_file, const std::string& status_file) { - android::base::unique_fd status_fd(open(status_file.c_str(), - O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR)); - if (status_fd == -1) { - ALOGE("failed to open pipe \"%s\": %s", status_file.c_str(), strerror(errno)); - return 1; +static bool setup_bcb(const int socket) { + // c5. receive message length + int length; + if (!android::base::ReadFully(socket, &length, 4)) { + ALOGE("failed to read the length: %s", strerror(errno)); + return false; } + length = ntohl(length); + + // c7. receive message std::string content; - if (!android::base::ReadFileToString(command_file, &content)) { - ALOGE("failed to read \"%s\": %s", command_file.c_str(), strerror(errno)); - android::base::WriteStringToFd("-1\n", status_fd); - return 1; + content.resize(length); + if (!android::base::ReadFully(socket, &content[0], length)) { + ALOGE("failed to read the length: %s", strerror(errno)); + return false; } - bootloader_message boot = {}; - strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); - strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); - strlcat(boot.recovery, content.c_str(), sizeof(boot.recovery)); - if (write_bootloader_message(&boot) != 0) { - ALOGE("failed to set bootloader message"); - android::base::WriteStringToFd("-1\n", status_fd); - return 1; + ALOGI(" received command: [%s] (%zu)", content.c_str(), content.size()); + + // c8. setup the bcb command + std::string err; + if (!write_bootloader_message({content}, &err)) { + ALOGE("failed to set bootloader message: %s", err.c_str()); + write_status_to_socket(-1, socket); + return false; } - android::base::WriteStringToFd("100\n", status_fd); - return 0; + // c10. send "100" status + write_status_to_socket(100, socket); + return true; } static void usage(const char* exename) { fprintf(stderr, "Usage of %s:\n", exename); fprintf(stderr, "%s [<package_path> <map_file>] Uncrypt ota package.\n", exename); - fprintf(stderr, "%s --reboot Clear BCB data and reboot to recovery.\n", exename); fprintf(stderr, "%s --clear-bcb Clear BCB data in misc partition.\n", exename); fprintf(stderr, "%s --setup-bcb Setup BCB data by command file.\n", exename); } int main(int argc, char** argv) { - if (argc == 2) { - if (strcmp(argv[1], "--reboot") == 0) { - reboot_to_recovery(); - } else if (strcmp(argv[1], "--clear-bcb") == 0) { - return clear_bcb(STATUS_FILE); - } else if (strcmp(argv[1], "--setup-bcb") == 0) { - return setup_bcb(COMMAND_FILE, STATUS_FILE); - } - } else if (argc == 1 || argc == 3) { - const char* input_path = nullptr; - const char* map_file = CACHE_BLOCK_MAP.c_str(); - if (argc == 3) { - input_path = argv[1]; - map_file = argv[2]; - } - return uncrypt_wrapper(input_path, map_file, STATUS_FILE); + enum { UNCRYPT, SETUP_BCB, CLEAR_BCB } action; + const char* input_path = nullptr; + const char* map_file = CACHE_BLOCK_MAP.c_str(); + + if (argc == 2 && strcmp(argv[1], "--clear-bcb") == 0) { + action = CLEAR_BCB; + } else if (argc == 2 && strcmp(argv[1], "--setup-bcb") == 0) { + action = SETUP_BCB; + } else if (argc == 1) { + action = UNCRYPT; + } else if (argc == 3) { + input_path = argv[1]; + map_file = argv[2]; + action = UNCRYPT; + } else { + usage(argv[0]); + return 2; + } + + if ((fstab = read_fstab()) == nullptr) { + return 1; + } + + // c3. The socket is created by init when starting the service. uncrypt + // will use the socket to communicate with its caller. + android::base::unique_fd service_socket(android_get_control_socket(UNCRYPT_SOCKET.c_str())); + if (service_socket == -1) { + ALOGE("failed to open socket \"%s\": %s", UNCRYPT_SOCKET.c_str(), strerror(errno)); + return 1; + } + fcntl(service_socket, F_SETFD, FD_CLOEXEC); + + if (listen(service_socket, 1) == -1) { + ALOGE("failed to listen on socket %d: %s", service_socket.get(), strerror(errno)); + return 1; + } + + android::base::unique_fd socket_fd(accept4(service_socket, nullptr, nullptr, SOCK_CLOEXEC)); + if (socket_fd == -1) { + ALOGE("failed to accept on socket %d: %s", service_socket.get(), strerror(errno)); + return 1; + } + + bool success = false; + switch (action) { + case UNCRYPT: + success = uncrypt_wrapper(input_path, map_file, socket_fd); + break; + case SETUP_BCB: + success = setup_bcb(socket_fd); + break; + case CLEAR_BCB: + success = clear_bcb(socket_fd); + break; + default: // Should never happen. + ALOGE("Invalid uncrypt action code: %d", action); + return 1; + } + + // c13. Read a 4-byte code from the client before uncrypt exits. This is to + // ensure the client to receive the last status code before the socket gets + // destroyed. + int code; + if (android::base::ReadFully(socket_fd, &code, 4)) { + ALOGI(" received %d, exiting now", code); + } else { + ALOGE("failed to read the code: %s", strerror(errno)); } - usage(argv[0]); - return 2; + return success ? 0 : 1; } diff --git a/uncrypt/uncrypt.rc b/uncrypt/uncrypt.rc index b07c1dada..52f564eb6 100644 --- a/uncrypt/uncrypt.rc +++ b/uncrypt/uncrypt.rc @@ -1,19 +1,17 @@ service uncrypt /system/bin/uncrypt class main - disabled - oneshot - -service pre-recovery /system/bin/uncrypt --reboot - class main + socket uncrypt stream 600 system system disabled oneshot service setup-bcb /system/bin/uncrypt --setup-bcb class main + socket uncrypt stream 600 system system disabled oneshot service clear-bcb /system/bin/uncrypt --clear-bcb class main + socket uncrypt stream 600 system system disabled - oneshot
\ No newline at end of file + oneshot diff --git a/verifier.cpp b/verifier.cpp index 6e1581272..4004b0228 100644 --- a/verifier.cpp +++ b/verifier.cpp @@ -32,6 +32,8 @@ extern RecoveryUI* ui; +static constexpr size_t MiB = 1024 * 1024; + /* * Simple version of PKCS#7 SignedData extraction. This extracts the * signature OCTET STRING to be used for signature verification. @@ -187,8 +189,6 @@ int verify_file(unsigned char* addr, size_t length, } } -#define BUFFER_SIZE 4096 - bool need_sha1 = false; bool need_sha256 = false; for (const auto& key : keys) { @@ -206,8 +206,10 @@ int verify_file(unsigned char* addr, size_t length, double frac = -1.0; size_t so_far = 0; while (so_far < signed_len) { - size_t size = signed_len - so_far; - if (size > BUFFER_SIZE) size = BUFFER_SIZE; + // On a Nexus 9, experiment didn't show any performance improvement with + // larger sizes past 1MiB, and they reduce the granularity of the progress + // bar. http://b/28135231. + size_t size = std::min(signed_len - so_far, 1 * MiB); if (need_sha1) SHA1_Update(&sha1_ctx, addr + so_far, size); if (need_sha256) SHA256_Update(&sha256_ctx, addr + so_far, size); diff --git a/wear_touch.cpp b/wear_touch.cpp new file mode 100644 index 000000000..f22d40b88 --- /dev/null +++ b/wear_touch.cpp @@ -0,0 +1,177 @@ +/* + * 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 "common.h" +#include "wear_touch.h" + +#include <dirent.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> + +#include <linux/input.h> + +#define DEVICE_PATH "/dev/input" + +WearSwipeDetector::WearSwipeDetector(int low, int high, OnSwipeCallback callback, void* cookie): + mLowThreshold(low), + mHighThreshold(high), + mCallback(callback), + mCookie(cookie), + mCurrentSlot(-1) { + pthread_create(&mThread, NULL, touch_thread, this); +} + +WearSwipeDetector::~WearSwipeDetector() { +} + +void WearSwipeDetector::detect(int dx, int dy) { + enum SwipeDirection direction; + + if (abs(dy) < mLowThreshold && abs(dx) > mHighThreshold) { + direction = dx < 0 ? LEFT : RIGHT; + } else if (abs(dx) < mLowThreshold && abs(dy) > mHighThreshold) { + direction = dy < 0 ? UP : DOWN; + } else { + LOGD("Ignore %d %d\n", dx, dy); + return; + } + + LOGD("Swipe direction=%d\n", direction); + mCallback(mCookie, direction); +} + +void WearSwipeDetector::process(struct input_event *event) { + if (mCurrentSlot < 0) { + mCallback(mCookie, UP); + mCurrentSlot = 0; + } + + if (event->type == EV_ABS) { + if (event->code == ABS_MT_SLOT) + mCurrentSlot = event->value; + + // Ignore other fingers + if (mCurrentSlot > 0) { + return; + } + + switch (event->code) { + case ABS_MT_POSITION_X: + mX = event->value; + mFingerDown = true; + break; + + case ABS_MT_POSITION_Y: + mY = event->value; + mFingerDown = true; + break; + + case ABS_MT_TRACKING_ID: + if (event->value < 0) + mFingerDown = false; + break; + } + } else if (event->type == EV_SYN) { + if (event->code == SYN_REPORT) { + if (mFingerDown && !mSwiping) { + mStartX = mX; + mStartY = mY; + mSwiping = true; + } else if (!mFingerDown && mSwiping) { + mSwiping = false; + detect(mX - mStartX, mY - mStartY); + } + } + } +} + +void WearSwipeDetector::run() { + int fd = findDevice(DEVICE_PATH); + if (fd < 0) { + LOGE("no input devices found\n"); + return; + } + + struct input_event event; + while (read(fd, &event, sizeof(event)) == sizeof(event)) { + process(&event); + } + + close(fd); +} + +void* WearSwipeDetector::touch_thread(void* cookie) { + ((WearSwipeDetector*)cookie)->run(); + return NULL; +} + +#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8))) + +int WearSwipeDetector::openDevice(const char *device) { + int fd = open(device, O_RDONLY); + if (fd < 0) { + LOGE("could not open %s, %s\n", device, strerror(errno)); + return false; + } + + char name[80]; + name[sizeof(name) - 1] = '\0'; + if (ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) { + LOGE("could not get device name for %s, %s\n", device, strerror(errno)); + name[0] = '\0'; + } + + uint8_t bits[512]; + memset(bits, 0, sizeof(bits)); + int ret = ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(bits)), bits); + if (ret > 0) { + if (test_bit(ABS_MT_POSITION_X, bits) && test_bit(ABS_MT_POSITION_Y, bits)) { + LOGD("Found %s %s\n", device, name); + return fd; + } + } + + close(fd); + return -1; +} + +int WearSwipeDetector::findDevice(const char* path) { + DIR* dir = opendir(path); + if (dir == NULL) { + LOGE("Could not open directory %s", path); + return false; + } + + struct dirent* entry; + int ret = -1; + while (ret < 0 && (entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') continue; + + char device[PATH_MAX]; + device[PATH_MAX-1] = '\0'; + snprintf(device, PATH_MAX-1, "%s/%s", path, entry->d_name); + + ret = openDevice(device); + } + + closedir(dir); + return ret; +} + diff --git a/wear_touch.h b/wear_touch.h new file mode 100644 index 000000000..9a1d3150c --- /dev/null +++ b/wear_touch.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __WEAR_TOUCH_H +#define __WEAR_TOUCH_H + +#include <pthread.h> + +class WearSwipeDetector { + +public: + enum SwipeDirection { UP, DOWN, RIGHT, LEFT }; + typedef void (*OnSwipeCallback)(void* cookie, enum SwipeDirection direction); + + WearSwipeDetector(int low, int high, OnSwipeCallback cb, void* cookie); + ~WearSwipeDetector(); + +private: + void run(); + void process(struct input_event *event); + void detect(int dx, int dy); + + pthread_t mThread; + static void* touch_thread(void* cookie); + + int findDevice(const char* path); + int openDevice(const char* device); + + int mLowThreshold; + int mHighThreshold; + + OnSwipeCallback mCallback; + void *mCookie; + + int mX; + int mY; + int mStartX; + int mStartY; + + int mCurrentSlot; + bool mFingerDown; + bool mSwiping; +}; + +#endif // __WEAR_TOUCH_H diff --git a/wear_ui.cpp b/wear_ui.cpp index 50aeb3849..2502313ff 100644 --- a/wear_ui.cpp +++ b/wear_ui.cpp @@ -16,9 +16,7 @@ #include <errno.h> #include <fcntl.h> -#include <pthread.h> #include <stdarg.h> -#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> @@ -31,14 +29,10 @@ #include "common.h" #include "device.h" -#include "minui/minui.h" #include "wear_ui.h" -#include "ui.h" #include "cutils/properties.h" #include "android-base/strings.h" - -static int char_width; -static int char_height; +#include "android-base/stringprintf.h" // There's only (at most) one of these objects, and global callbacks // (for pthread_create, and the input event system) need to find it, @@ -65,7 +59,6 @@ WearRecoveryUI::WearRecoveryUI() : currentIcon(NONE), intro_done(false), current_frame(0), - rtl_locale(false), progressBarType(EMPTY), progressScopeStart(0), progressScopeSize(0), @@ -84,7 +77,6 @@ WearRecoveryUI::WearRecoveryUI() : for (size_t i = 0; i < 5; i++) backgroundIcon[i] = NULL; - pthread_mutex_init(&updateMutex, NULL); self = this; } @@ -148,41 +140,6 @@ void WearRecoveryUI::draw_progress_locked() } } -void WearRecoveryUI::SetColor(UIElement e) { - switch (e) { - case HEADER: - gr_color(247, 0, 6, 255); - break; - case MENU: - case MENU_SEL_BG: - gr_color(0, 106, 157, 255); - break; - case MENU_SEL_FG: - gr_color(255, 255, 255, 255); - break; - case LOG: - gr_color(249, 194, 0, 255); - break; - case TEXT_FILL: - gr_color(0, 0, 0, 160); - break; - default: - gr_color(255, 255, 255, 255); - break; - } -} - -void WearRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) { - gr_text(x, *y, line, bold); - *y += char_height + 4; -} - -void WearRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) { - for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { - DrawTextLine(x, y, lines[i], false); - } -} - static const char* HEADERS[] = { "Swipe up/down to move.", "Swipe left/right to select.", @@ -221,7 +178,7 @@ void WearRecoveryUI::draw_screen_locked() if (menu_items > menu_end - menu_start) { sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items); gr_text(x+4, y, cur_selection_str, 1); - y += char_height+4; + y += char_height_+4; } // Menu begins here @@ -232,7 +189,7 @@ void WearRecoveryUI::draw_screen_locked() if (i == menu_sel) { // draw the highlight bar SetColor(MENU_SEL_BG); - gr_fill(x, y-2, gr_fb_width()-x, y+char_height+2); + gr_fill(x, y-2, gr_fb_width()-x, y+char_height_+2); // white text of selected item SetColor(MENU_SEL_FG); if (menu[i][0]) gr_text(x+4, y, menu[i], 1); @@ -240,7 +197,7 @@ void WearRecoveryUI::draw_screen_locked() } else { if (menu[i][0]) gr_text(x+4, y, menu[i], 0); } - y += char_height+4; + y += char_height_+4; } SetColor(MENU); y += 4; @@ -256,9 +213,9 @@ void WearRecoveryUI::draw_screen_locked() int ty; int row = (text_top+text_rows-1) % text_rows; size_t count = 0; - for (int ty = gr_fb_height() - char_height - outer_height; + for (int ty = gr_fb_height() - char_height_ - outer_height; ty > y+2 && count < text_rows; - ty -= char_height, ++count) { + ty -= char_height_, ++count) { gr_text(x+4, ty, text[row], 0); --row; if (row < 0) row = text_rows-1; @@ -324,26 +281,19 @@ void WearRecoveryUI::progress_loop() { } } -void WearRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) { - int result = res_create_display_surface(filename, surface); - if (result < 0) { - LOGE("missing bitmap %s\n(Code %d)\n", filename, result); - } -} - void WearRecoveryUI::Init() { gr_init(); - gr_font_size(&char_width, &char_height); + gr_font_size(&char_width_, &char_height_); text_col = text_row = 0; - text_rows = (gr_fb_height()) / char_height; - visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height; + text_rows = (gr_fb_height()) / char_height_; + visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height_; if (text_rows > kMaxRows) text_rows = kMaxRows; text_top = 1; - text_cols = (gr_fb_width() - (outer_width * 2)) / char_width; + text_cols = (gr_fb_width() - (outer_width * 2)) / char_width_; if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1; LoadBitmap("icon_installing", &backgroundIcon[INSTALLING_UPDATE]); @@ -369,29 +319,6 @@ void WearRecoveryUI::Init() RecoveryUI::Init(); } -void WearRecoveryUI::SetLocale(const char* locale) { - if (locale) { - char* lang = strdup(locale); - for (char* p = lang; *p; ++p) { - if (*p == '_') { - *p = '\0'; - break; - } - } - - // A bit cheesy: keep an explicit list of supported languages - // that are RTL. - if (strcmp(lang, "ar") == 0 || // Arabic - strcmp(lang, "fa") == 0 || // Persian (Farsi) - strcmp(lang, "he") == 0 || // Hebrew (new language code) - strcmp(lang, "iw") == 0 || // Hebrew (old language code) - strcmp(lang, "ur") == 0) { // Urdu - rtl_locale = true; - } - free(lang); - } -} - void WearRecoveryUI::SetBackground(Icon icon) { pthread_mutex_lock(&updateMutex); @@ -653,3 +580,35 @@ void WearRecoveryUI::ClearText() { } pthread_mutex_unlock(&updateMutex); } + +void WearRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + PrintV(fmt, false, ap); + va_end(ap); +} + +void WearRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) { + std::string str; + android::base::StringAppendV(&str, fmt, ap); + + if (copy_to_stdout) { + fputs(str.c_str(), stdout); + } + + pthread_mutex_lock(&updateMutex); + if (text_rows > 0 && text_cols > 0) { + for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) { + if (*ptr == '\n' || text_col >= text_cols) { + text[text_row][text_col] = '\0'; + text_col = 0; + text_row = (text_row + 1) % text_rows; + if (text_row == text_top) text_top = (text_top + 1) % text_rows; + } + if (*ptr != '\n') text[text_row][text_col++] = *ptr; + } + text[text_row][text_col] = '\0'; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); +} @@ -17,19 +17,13 @@ #ifndef RECOVERY_WEAR_UI_H #define RECOVERY_WEAR_UI_H -#include <pthread.h> -#include <stdio.h> +#include "screen_ui.h" -#include "ui.h" -#include "minui/minui.h" - -class WearRecoveryUI : public RecoveryUI { +class WearRecoveryUI : public ScreenRecoveryUI { public: WearRecoveryUI(); void Init(); - void SetLocale(const char* locale); - // overall recovery state ("background image") void SetBackground(Icon icon); @@ -47,6 +41,7 @@ class WearRecoveryUI : public RecoveryUI { // printing messages void Print(const char* fmt, ...); + void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3); void ShowFile(const char* filename); void ShowFile(FILE* fp); @@ -58,9 +53,6 @@ class WearRecoveryUI : public RecoveryUI { void Redraw(); - enum UIElement { HEADER, MENU, MENU_SEL_BG, MENU_SEL_FG, LOG, TEXT_FILL }; - virtual void SetColor(UIElement e); - protected: int progress_bar_height, progress_bar_width; @@ -89,9 +81,6 @@ class WearRecoveryUI : public RecoveryUI { int current_frame; - bool rtl_locale; - - pthread_mutex_t updateMutex; GRSurface* backgroundIcon[5]; GRSurface* *introFrames; GRSurface* *loopFrames; @@ -128,11 +117,9 @@ class WearRecoveryUI : public RecoveryUI { void update_screen_locked(); static void* progress_thread(void* cookie); void progress_loop(); - void LoadBitmap(const char* filename, GRSurface** surface); void PutChar(char); void ClearText(); - void DrawTextLine(int x, int* y, const char* line, bool bold); - void DrawTextLines(int x, int* y, const char* const* lines); + void PrintV(const char*, bool, va_list); }; #endif // RECOVERY_WEAR_UI_H |