diff options
author | Ethan Yonker <dees_troy@teamw.in> | 2014-11-06 15:35:10 +0100 |
---|---|---|
committer | Ethan Yonker <dees_troy@teamw.in> | 2014-11-06 15:35:13 +0100 |
commit | a167416289a8aef5d4c35861c9f4181f87b8bfd0 (patch) | |
tree | cfb0b940141a4273ac6ddb58070e36ea706b7358 | |
parent | 2.8.2.0 (diff) | |
parent | Use more aggressive sync writing to applypatch. (diff) | |
download | android_bootable_recovery-a167416289a8aef5d4c35861c9f4181f87b8bfd0.tar android_bootable_recovery-a167416289a8aef5d4c35861c9f4181f87b8bfd0.tar.gz android_bootable_recovery-a167416289a8aef5d4c35861c9f4181f87b8bfd0.tar.bz2 android_bootable_recovery-a167416289a8aef5d4c35861c9f4181f87b8bfd0.tar.lz android_bootable_recovery-a167416289a8aef5d4c35861c9f4181f87b8bfd0.tar.xz android_bootable_recovery-a167416289a8aef5d4c35861c9f4181f87b8bfd0.tar.zst android_bootable_recovery-a167416289a8aef5d4c35861c9f4181f87b8bfd0.zip |
Diffstat (limited to '')
134 files changed, 5629 insertions, 1746 deletions
diff --git a/Android.mk b/Android.mk index dfdff7dbc..835bb7184 100644 --- a/Android.mk +++ b/Android.mk @@ -52,13 +52,32 @@ ifneq ($(TARGET_RECOVERY_REBOOT_SRC),) LOCAL_SRC_FILES += $(TARGET_RECOVERY_REBOOT_SRC) endif +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + recovery.cpp \ + bootloader.cpp \ + install.cpp \ + roots.cpp \ + ui.cpp \ + screen_ui.cpp \ + asn1_decoder.cpp \ + verifier.cpp \ + adb_install.cpp \ + fuse_sdcard_provider.c + LOCAL_MODULE := recovery #LOCAL_FORCE_STATIC_EXECUTABLE := true +#ifeq ($(HOST_OS),linux) +#LOCAL_REQUIRED_MODULES := mkfs.f2fs +#endif + RECOVERY_API_VERSION := 3 RECOVERY_FSTAB_VERSION := 2 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) +LOCAL_CFLAGS += -Wno-unused-parameter #LOCAL_STATIC_LIBRARIES := \ # libext4_utils_static \ @@ -411,18 +430,44 @@ include $(BUILD_PHONY_PACKAGE) RECOVERY_BUSYBOX_SYMLINKS := endif # !TW_USE_TOOLBOX +# All the APIs for testing +include $(CLEAR_VARS) +LOCAL_MODULE := libverifier +LOCAL_MODULE_TAGS := tests +LOCAL_SRC_FILES := \ + asn1_decoder.cpp +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := fuse_sideload.c + +LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter +LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE + +LOCAL_MODULE := libfusesideload + +LOCAL_SHARED_LIBRARIES := libcutils libc libmincrypttwrp +include $(BUILD_SHARED_LIBRARY) + include $(CLEAR_VARS) LOCAL_MODULE := verifier_test LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_MODULE_TAGS := tests + LOCAL_C_INCLUDES := $(LOCAL_PATH)/libmincrypt/includes + +LOCAL_CFLAGS += -DNO_RECOVERY_MOUNT +LOCAL_CFLAGS += -Wno-unused-parameter + LOCAL_SRC_FILES := \ verifier_test.cpp \ + asn1_decoder.cpp \ verifier.cpp \ ui.cpp LOCAL_STATIC_LIBRARIES := \ libmincrypttwrp \ libminui \ + libminzip \ libcutils \ libstdc++ \ libc @@ -434,7 +479,7 @@ LOCAL_MODULE := libaosprecovery LOCAL_MODULE_TAGS := eng optional LOCAL_C_INCLUDES := $(LOCAL_PATH)/libmincrypt/includes LOCAL_SRC_FILES = adb_install.cpp bootloader.cpp verifier.cpp mtdutils/mtdutils.c legacy_property_service.c -LOCAL_SHARED_LIBRARIES += libc liblog libcutils libmtdutils +LOCAL_SHARED_LIBRARIES += libc liblog libcutils libmtdutils libfusesideload LOCAL_STATIC_LIBRARIES += libmincrypttwrp ifneq ($(BOARD_RECOVERY_BLDRMSG_OFFSET),) @@ -445,10 +490,14 @@ include $(BUILD_SHARED_LIBRARY) commands_recovery_local_path := $(LOCAL_PATH) include $(LOCAL_PATH)/minui/Android.mk \ - $(LOCAL_PATH)/minelf/Android.mk \ $(LOCAL_PATH)/minadbd/Android.mk \ + $(LOCAL_PATH)/minzip/Android.mk \ + $(LOCAL_PATH)/minadbd/Android.mk \ + $(LOCAL_PATH)/mtdutils/Android.mk \ + $(LOCAL_PATH)/tests/Android.mk \ $(LOCAL_PATH)/tools/Android.mk \ $(LOCAL_PATH)/edify/Android.mk \ + $(LOCAL_PATH)/uncrypt/Android.mk \ $(LOCAL_PATH)/updater/Android.mk \ $(LOCAL_PATH)/applypatch/Android.mk diff --git a/CleanSpec.mk b/CleanSpec.mk index ecf89ae75..e2d97d42b 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -48,3 +48,4 @@ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/recovery_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminui_intermediates/import_includes) diff --git a/adb_install.cpp b/adb_install.cpp index c18126d00..e10cb4a2e 100644 --- a/adb_install.cpp +++ b/adb_install.cpp @@ -30,7 +30,8 @@ #include "cutils/properties.h" #include "adb_install.h" extern "C" { -#include "minadbd/adb.h" +#include "minadbd/fuse_adb_provider.h" +#include "fuse_sideload.h" } static RecoveryUI* ui = NULL; @@ -78,6 +79,10 @@ maybe_restart_adbd() { } } +// How long (in seconds) we wait for the host to start sending us a +// package, before timing out. +#define ADB_INSTALL_TIMEOUT 300 + int apply_from_adb(const char* install_file) { @@ -92,22 +97,62 @@ apply_from_adb(const char* install_file) { execl("/sbin/recovery", "recovery", "--adbd", install_file, NULL); _exit(-1); } + char child_prop[PROPERTY_VALUE_MAX]; sprintf(child_prop, "%i", child); property_set("tw_child_pid", child_prop); + + // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the host + // connects and starts serving a package. Poll for its + // appearance. (Note that inotify doesn't work with FUSE.) + int result; int status; - // TODO(dougz): there should be a way to cancel waiting for a - // package (by pushing some button combo on the device). For now - // you just have to 'adb sideload' a file that's not a valid - // package, like "/dev/null". - waitpid(child, &status, 0); + bool waited = false; + struct stat st; + for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) { + if (waitpid(child, &status, WNOHANG) != 0) { + result = INSTALL_ERROR; + waited = true; + break; + } + + if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) { + if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT-1) { + sleep(1); + continue; + } else { + ui->Print("\nTimed out waiting for package.\n\n", strerror(errno)); + result = INSTALL_ERROR; + kill(child, SIGKILL); + break; + } + } + result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, install_file, false); + break; + } + + if (!waited) { + // Calling stat() on this magic filename signals the minadbd + // subprocess to shut down. + stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); + + // TODO(dougz): there should be a way to cancel waiting for a + // package (by pushing some button combo on the device). For now + // you just have to 'adb sideload' a file that's not a valid + // package, like "/dev/null". + waitpid(child, &status, 0); + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - printf("status %d\n", WEXITSTATUS(status)); + if (WEXITSTATUS(status) == 3) { + printf("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n"); + } else if (!WIFSIGNALED(status)) { + printf("status %d\n", WEXITSTATUS(status)); + } } set_usb_driver(false); maybe_restart_adbd(); - struct stat st; if (stat(install_file, &st) != 0) { if (errno == ENOENT) { printf("No package received.\n"); diff --git a/applypatch/Android.mk b/applypatch/Android.mk index 04ddfd874..2cce81f6d 100644 --- a/applypatch/Android.mk +++ b/applypatch/Android.mk @@ -40,7 +40,7 @@ LOCAL_SRC_FILES := main.c LOCAL_MODULE := applypatch LOCAL_MODULE_TAGS := optional LOCAL_C_INCLUDES += $(commands_recovery_local_path) -LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypttwrp libbz libminelf +LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypttwrp libbz LOCAL_SHARED_LIBRARIES += libz libcutils libstdc++ libc include $(BUILD_EXECUTABLE) @@ -50,9 +50,9 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := main.c LOCAL_MODULE := applypatch_static LOCAL_FORCE_STATIC_EXECUTABLE := true -LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_TAGS := optional eng LOCAL_C_INCLUDES += $(commands_recovery_local_path) -LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypttwrp libbz libminelf +LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypttwrp libbz LOCAL_STATIC_LIBRARIES += libz libcutils libstdc++ libc include $(BUILD_EXECUTABLE) diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c index b5ff12c80..73195d96a 100644 --- a/applypatch/applypatch.c +++ b/applypatch/applypatch.c @@ -24,6 +24,7 @@ #include <sys/types.h> #include <fcntl.h> #include <unistd.h> +#include <stdbool.h> #include "mincrypt/sha.h" #include "applypatch.h" @@ -32,7 +33,7 @@ #include "edify/expr.h" static int LoadPartitionContents(const char* filename, FileContents* file); -static ssize_t FileSink(unsigned char* data, ssize_t len, void* token); +static ssize_t FileSink(const unsigned char* data, ssize_t len, void* token); static int GenerateTarget(FileContents* source_file, const Value* source_patch_value, FileContents* copy_file, @@ -45,14 +46,11 @@ static int GenerateTarget(FileContents* source_file, static int mtd_partitions_scanned = 0; -// Read a file into memory; optionally (retouch_flag == RETOUCH_DO_MASK) mask -// the retouched entries back to their original value (such that SHA-1 checks -// don't fail due to randomization); store the file contents and associated +// Read a file into memory; store the file contents and associated // metadata in *file. // // Return 0 on success. -int LoadFileContents(const char* filename, FileContents* file, - int retouch_flag) { +int LoadFileContents(const char* filename, FileContents* file) { file->data = NULL; // A special 'filename' beginning with "MTD:" or "EMMC:" means to @@ -89,20 +87,6 @@ int LoadFileContents(const char* filename, FileContents* file, } fclose(f); - // apply_patch[_check] functions are blind to randomization. Randomization - // is taken care of in [Undo]RetouchBinariesFn. If there is a mismatch - // within a file, this means the file is assumed "corrupt" for simplicity. - if (retouch_flag) { - int32_t desired_offset = 0; - if (retouch_mask_data(file->data, file->size, - &desired_offset, NULL) != RETOUCH_DATA_MATCHED) { - printf("error trying to mask retouch entries\n"); - free(file->data); - file->data = NULL; - return -1; - } - } - SHA_hash(file->data, file->size, file->sha1); return 0; } @@ -259,7 +243,7 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { break; } if (next != read) { - printf("short read (%d bytes of %d) for partition \"%s\"\n", + printf("short read (%zu bytes of %zu) for partition \"%s\"\n", read, next, partition); free(file->data); file->data = NULL; @@ -286,7 +270,7 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_SIZE) == 0) { // we have a match. stop reading the partition; we'll return // the data we've read so far. - printf("partition read matched size %d sha %s\n", + printf("partition read matched size %zu sha %s\n", size[index[i]], sha1sum[index[i]]); break; } @@ -337,7 +321,7 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { // Save the contents of the given FileContents object under the given // filename. Return 0 on success. int SaveFileContents(const char* filename, const FileContents* file) { - int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR); if (fd < 0) { printf("failed to open \"%s\" for write: %s\n", filename, strerror(errno)); @@ -352,8 +336,14 @@ int SaveFileContents(const char* filename, const FileContents* file) { close(fd); return -1; } - fsync(fd); - close(fd); + if (fsync(fd) != 0) { + printf("fsync of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } + if (close(fd) != 0) { + printf("close of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } if (chmod(filename, file->st.st_mode) != 0) { printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno)); @@ -433,7 +423,7 @@ int WriteToPartition(unsigned char* data, size_t len, size_t written = mtd_write_data(ctx, (char*)data, len); if (written != len) { - printf("only wrote %d of %d bytes to MTD %s\n", + printf("only wrote %zu of %zu bytes to MTD %s\n", written, len, partition); mtd_write_close(ctx); return -1; @@ -462,13 +452,11 @@ int WriteToPartition(unsigned char* data, size_t len, } int attempt; - for (attempt = 0; attempt < 10; ++attempt) { - size_t next_sync = start + (1<<20); - printf("raw O_SYNC write %s attempt %d start at %d\n", partition, attempt+1, start); + for (attempt = 0; attempt < 2; ++attempt) { lseek(fd, start, SEEK_SET); while (start < len) { size_t to_write = len - start; - if (to_write > 4096) to_write = 4096; + if (to_write > 1<<20) to_write = 1<<20; ssize_t written = write(fd, data+start, to_write); if (written < 0) { @@ -481,12 +469,23 @@ int WriteToPartition(unsigned char* data, size_t len, } } start += written; - if (start >= next_sync) { - fsync(fd); - next_sync = start + (1<<20); - } } - fsync(fd); + if (fsync(fd) != 0) { + printf("failed to sync to %s (%s)\n", + partition, strerror(errno)); + return -1; + } + if (close(fd) != 0) { + printf("failed to close %s (%s)\n", + partition, strerror(errno)); + return -1; + } + fd = open(partition, O_RDONLY); + if (fd < 0) { + printf("failed to reopen %s for verify (%s)\n", + partition, strerror(errno)); + return -1; + } // drop caches so our subsequent verification read // won't just be reading the cache. @@ -513,20 +512,20 @@ int WriteToPartition(unsigned char* data, size_t len, if (errno == EINTR) { read_count = 0; } else { - printf("verify read error %s at %d: %s\n", + printf("verify read error %s at %zu: %s\n", partition, p, strerror(errno)); return -1; } } if ((size_t)read_count < to_read) { - printf("short verify read %s at %d: %d %d %s\n", + printf("short verify read %s at %zu: %zd %zu %s\n", partition, p, read_count, to_read, strerror(errno)); } so_far += read_count; } if (memcmp(buffer, data+p, to_read)) { - printf("verification failed starting at %d\n", p); + printf("verification failed starting at %zu\n", p); start = p; break; } @@ -537,8 +536,6 @@ int WriteToPartition(unsigned char* data, size_t len, success = true; break; } - - sleep(2); } if (!success) { @@ -550,11 +547,7 @@ int WriteToPartition(unsigned char* data, size_t len, printf("error closing %s (%s)\n", partition, strerror(errno)); return -1; } - // hack: sync and sleep after closing in hopes of getting - // the data actually onto flash. - printf("sleeping after close\n"); sync(); - sleep(5); break; } } @@ -622,7 +615,7 @@ int applypatch_check(const char* filename, // LoadFileContents is successful. (Useful for reading // partitions, where the filename encodes the sha1s; no need to // check them twice.) - if (LoadFileContents(filename, &file, RETOUCH_DO_MASK) != 0 || + if (LoadFileContents(filename, &file) != 0 || (num_patches > 0 && FindMatchingPatch(file.sha1, patch_sha1_str, num_patches) < 0)) { printf("file \"%s\" doesn't have any of expected " @@ -637,7 +630,7 @@ int applypatch_check(const char* filename, // exists and matches the sha1 we're looking for, the check still // passes. - if (LoadFileContents(CACHE_TEMP_SOURCE, &file, RETOUCH_DO_MASK) != 0) { + if (LoadFileContents(CACHE_TEMP_SOURCE, &file) != 0) { printf("failed to load cache file\n"); return 1; } @@ -658,7 +651,7 @@ int ShowLicenses() { return 0; } -ssize_t FileSink(unsigned char* data, ssize_t len, void* token) { +ssize_t FileSink(const unsigned char* data, ssize_t len, void* token) { int fd = *(int *)token; ssize_t done = 0; ssize_t wrote; @@ -679,7 +672,7 @@ typedef struct { ssize_t pos; } MemorySinkInfo; -ssize_t MemorySink(unsigned char* data, ssize_t len, void* token) { +ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) { MemorySinkInfo* msi = (MemorySinkInfo*)token; if (msi->size - msi->pos < len) { return -1; @@ -773,8 +766,7 @@ int applypatch(const char* source_filename, const Value* copy_patch_value = NULL; // We try to load the target file into the source_file object. - if (LoadFileContents(target_filename, &source_file, - RETOUCH_DO_MASK) == 0) { + if (LoadFileContents(target_filename, &source_file) == 0) { if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_SIZE) == 0) { // The early-exit case: the patch was already applied, this file // has the desired hash, nothing for us to do. @@ -793,8 +785,7 @@ int applypatch(const char* source_filename, // target file, or we did but it's different from the source file. free(source_file.data); source_file.data = NULL; - LoadFileContents(source_filename, &source_file, - RETOUCH_DO_MASK); + LoadFileContents(source_filename, &source_file); } if (source_file.data != NULL) { @@ -810,8 +801,7 @@ int applypatch(const char* source_filename, source_file.data = NULL; printf("source file is bad; trying copy\n"); - if (LoadFileContents(CACHE_TEMP_SOURCE, ©_file, - RETOUCH_DO_MASK) < 0) { + if (LoadFileContents(CACHE_TEMP_SOURCE, ©_file) < 0) { // fail. printf("failed to read copy file\n"); return 1; @@ -984,7 +974,8 @@ static int GenerateTarget(FileContents* source_file, strcpy(outname, target_filename); strcat(outname, ".patch"); - output = open(outname, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + output = open(outname, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, + S_IRUSR | S_IWUSR); if (output < 0) { printf("failed to open output file %s: %s\n", outname, strerror(errno)); @@ -1015,8 +1006,14 @@ static int GenerateTarget(FileContents* source_file, } if (output >= 0) { - fsync(output); - close(output); + if (fsync(output) != 0) { + printf("failed to fsync file \"%s\" (%s)\n", outname, strerror(errno)); + result = 1; + } + if (close(output) != 0) { + printf("failed to close file \"%s\" (%s)\n", outname, strerror(errno)); + result = 1; + } } if (result != 0) { diff --git a/applypatch/applypatch.h b/applypatch/applypatch.h index f1f13a100..edec84812 100644 --- a/applypatch/applypatch.h +++ b/applypatch/applypatch.h @@ -19,7 +19,6 @@ #include <sys/stat.h> #include "mincrypt/sha.h" -#include "minelf/Retouch.h" #include "edify/expr.h" typedef struct _Patch { @@ -41,7 +40,7 @@ typedef struct _FileContents { // and use it as the source instead. #define CACHE_TEMP_SOURCE "/cache/saved.file" -typedef ssize_t (*SinkFn)(unsigned char*, ssize_t, void*); +typedef ssize_t (*SinkFn)(const unsigned char*, ssize_t, void*); // applypatch.c int ShowLicenses(); @@ -61,8 +60,7 @@ int applypatch_check(const char* filename, int num_patches, char** const patch_sha1_str); -int LoadFileContents(const char* filename, FileContents* file, - int retouch_flag); +int LoadFileContents(const char* filename, FileContents* file); int SaveFileContents(const char* filename, const FileContents* file); void FreeFileContents(FileContents* file); int FindMatchingPatch(uint8_t* sha1, char* const * const patch_sha1_str, diff --git a/applypatch/bspatch.c b/applypatch/bspatch.c index 2e80f81d0..b34ec2a88 100644 --- a/applypatch/bspatch.c +++ b/applypatch/bspatch.c @@ -112,9 +112,7 @@ int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size, printf("short write of output: %d (%s)\n", errno, strerror(errno)); return 1; } - if (ctx) { - SHA_update(ctx, new_data, new_size); - } + if (ctx) SHA_update(ctx, new_data, new_size); free(new_data); return 0; @@ -205,6 +203,11 @@ int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, ctrl[1] = offtin(buf+8); ctrl[2] = offtin(buf+16); + if (ctrl[0] < 0 || ctrl[1] < 0) { + printf("corrupt patch (negative byte counts)\n"); + return 1; + } + // Sanity check if (newpos + ctrl[0] > *new_size) { printf("corrupt patch (new file overrun)\n"); diff --git a/applypatch/imgpatch.c b/applypatch/imgpatch.c index 3a1df3872..33c448762 100644 --- a/applypatch/imgpatch.c +++ b/applypatch/imgpatch.c @@ -18,6 +18,7 @@ // format. #include <stdio.h> +#include <sys/cdefs.h> #include <sys/stat.h> #include <errno.h> #include <unistd.h> @@ -35,7 +36,7 @@ * file, and update the SHA context with the output data as well. * Return 0 on success. */ -int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, +int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size __unused, const Value* patch, SinkFn sink, void* token, SHA_CTX* ctx, const Value* bonus_data) { @@ -94,7 +95,7 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, printf("failed to read chunk %d raw data\n", i); return -1; } - SHA_update(ctx, patch->data + pos, data_len); + if (ctx) SHA_update(ctx, patch->data + pos, data_len); if (sink((unsigned char*)patch->data + pos, data_len, token) != data_len) { printf("failed to write chunk %d raw data\n", i); @@ -132,7 +133,7 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, unsigned char* expanded_source = malloc(expanded_len); if (expanded_source == NULL) { - printf("failed to allocate %d bytes for expanded_source\n", + printf("failed to allocate %zu bytes for expanded_source\n", expanded_len); return -1; } @@ -163,7 +164,7 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, // We should have filled the output buffer exactly, except // for the bonus_size. if (strm.avail_out != bonus_size) { - printf("source inflation short by %d bytes\n", strm.avail_out-bonus_size); + printf("source inflation short by %zu bytes\n", strm.avail_out-bonus_size); return -1; } inflateEnd(&strm); @@ -216,7 +217,7 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, (long)have); return -1; } - SHA_update(ctx, temp_data, have); + if (ctx) SHA_update(ctx, temp_data, have); } while (ret != Z_STREAM_END); deflateEnd(&strm); diff --git a/applypatch/main.c b/applypatch/main.c index f61db5d9e..8e9fe80ef 100644 --- a/applypatch/main.c +++ b/applypatch/main.c @@ -74,7 +74,7 @@ static int ParsePatchArgs(int argc, char** argv, (*patches)[i] = NULL; } else { FileContents fc; - if (LoadFileContents(colon, &fc, RETOUCH_DONT_MASK) != 0) { + if (LoadFileContents(colon, &fc) != 0) { goto abort; } (*patches)[i] = malloc(sizeof(Value)); @@ -103,7 +103,7 @@ int PatchMode(int argc, char** argv) { Value* bonus = NULL; if (argc >= 3 && strcmp(argv[1], "-b") == 0) { FileContents fc; - if (LoadFileContents(argv[2], &fc, RETOUCH_DONT_MASK) != 0) { + if (LoadFileContents(argv[2], &fc) != 0) { printf("failed to load bonus file %s\n", argv[2]); return 1; } diff --git a/asn1_decoder.cpp b/asn1_decoder.cpp new file mode 100644 index 000000000..7280f7480 --- /dev/null +++ b/asn1_decoder.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdint.h> +#include <string.h> + +#include "asn1_decoder.h" + + +typedef struct asn1_context { + size_t length; + uint8_t* p; + int app_type; +} asn1_context_t; + + +static const int kMaskConstructed = 0xE0; +static const int kMaskTag = 0x7F; +static const int kMaskAppType = 0x1F; + +static const int kTagOctetString = 0x04; +static const int kTagOid = 0x06; +static const int kTagSequence = 0x30; +static const int kTagSet = 0x31; +static const int kTagConstructed = 0xA0; + +asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length) { + asn1_context_t* ctx = (asn1_context_t*) calloc(1, sizeof(asn1_context_t)); + if (ctx == NULL) { + return NULL; + } + ctx->p = buffer; + ctx->length = length; + return ctx; +} + +void asn1_context_free(asn1_context_t* ctx) { + free(ctx); +} + +static inline int peek_byte(asn1_context_t* ctx) { + if (ctx->length <= 0) { + return -1; + } + return *ctx->p; +} + +static inline int get_byte(asn1_context_t* ctx) { + if (ctx->length <= 0) { + return -1; + } + int byte = *ctx->p; + ctx->p++; + ctx->length--; + return byte; +} + +static inline bool skip_bytes(asn1_context_t* ctx, size_t num_skip) { + if (ctx->length < num_skip) { + return false; + } + ctx->p += num_skip; + ctx->length -= num_skip; + return true; +} + +static bool decode_length(asn1_context_t* ctx, size_t* out_len) { + int num_octets = get_byte(ctx); + if (num_octets == -1) { + return false; + } + if ((num_octets & 0x80) == 0x00) { + *out_len = num_octets; + return 1; + } + num_octets &= kMaskTag; + if ((size_t)num_octets >= sizeof(size_t)) { + return false; + } + size_t length = 0; + for (int i = 0; i < num_octets; ++i) { + int byte = get_byte(ctx); + if (byte == -1) { + return false; + } + length <<= 8; + length += byte; + } + *out_len = length; + return true; +} + +/** + * Returns the constructed type and advances the pointer. E.g. A0 -> 0 + */ +asn1_context_t* asn1_constructed_get(asn1_context_t* ctx) { + int type = get_byte(ctx); + if (type == -1 || (type & kMaskConstructed) != kTagConstructed) { + return NULL; + } + size_t length; + if (!decode_length(ctx, &length) || length > ctx->length) { + return NULL; + } + asn1_context_t* app_ctx = asn1_context_new(ctx->p, length); + app_ctx->app_type = type & kMaskAppType; + return app_ctx; +} + +bool asn1_constructed_skip_all(asn1_context_t* ctx) { + int byte = peek_byte(ctx); + while (byte != -1 && (byte & kMaskConstructed) == kTagConstructed) { + skip_bytes(ctx, 1); + size_t length; + if (!decode_length(ctx, &length) || !skip_bytes(ctx, length)) { + return false; + } + byte = peek_byte(ctx); + } + return byte != -1; +} + +int asn1_constructed_type(asn1_context_t* ctx) { + return ctx->app_type; +} + +asn1_context_t* asn1_sequence_get(asn1_context_t* ctx) { + if ((get_byte(ctx) & kMaskTag) != kTagSequence) { + return NULL; + } + size_t length; + if (!decode_length(ctx, &length) || length > ctx->length) { + return NULL; + } + return asn1_context_new(ctx->p, length); +} + +asn1_context_t* asn1_set_get(asn1_context_t* ctx) { + if ((get_byte(ctx) & kMaskTag) != kTagSet) { + return NULL; + } + size_t length; + if (!decode_length(ctx, &length) || length > ctx->length) { + return NULL; + } + return asn1_context_new(ctx->p, length); +} + +bool asn1_sequence_next(asn1_context_t* ctx) { + size_t length; + if (get_byte(ctx) == -1 || !decode_length(ctx, &length) || !skip_bytes(ctx, length)) { + return false; + } + return true; +} + +bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length) { + if (get_byte(ctx) != kTagOid) { + return false; + } + if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) { + return false; + } + *oid = ctx->p; + return true; +} + +bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length) { + if (get_byte(ctx) != kTagOctetString) { + return false; + } + if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) { + return false; + } + *octet_string = ctx->p; + return true; +} diff --git a/asn1_decoder.h b/asn1_decoder.h new file mode 100644 index 000000000..b17141c44 --- /dev/null +++ b/asn1_decoder.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#ifndef ASN1_DECODER_H_ +#define ASN1_DECODER_H_ + +#include <stdint.h> + +typedef struct asn1_context asn1_context_t; + +asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length); +void asn1_context_free(asn1_context_t* ctx); +asn1_context_t* asn1_constructed_get(asn1_context_t* ctx); +bool asn1_constructed_skip_all(asn1_context_t* ctx); +int asn1_constructed_type(asn1_context_t* ctx); +asn1_context_t* asn1_sequence_get(asn1_context_t* ctx); +asn1_context_t* asn1_set_get(asn1_context_t* ctx); +bool asn1_sequence_next(asn1_context_t* seq); +bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length); +bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length); + +#endif /* ASN1_DECODER_H_ */ diff --git a/bootloader.h b/bootloader.h index 0a682b28a..15e0a5c99 100644 --- a/bootloader.h +++ b/bootloader.h @@ -38,11 +38,24 @@ extern "C" { * The recovery field is only written by linux and used * for the system to send a message to recovery or the * other way around. + * + * The stage field is written by packages which restart themselves + * multiple times, so that the UI can reflect which invocation of the + * package it is. If the value is of the format "#/#" (eg, "1/3"), + * the UI will add a simple indicator of that status. */ struct bootloader_message { char command[32]; char status[32]; - char recovery[1024]; + char recovery[768]; + + // The 'recovery' field used to be 1024 bytes. It has only ever + // been used to store the recovery command line, so 768 bytes + // should be plenty. We carve off the last 256 bytes to store the + // stage string (for multistage packages) and possible future + // expansion. + char stage[32]; + char reserved[224]; }; /* Read and write the bootloader command from the "misc" partition. diff --git a/default_device.cpp b/default_device.cpp index 648eaec4e..97806ac58 100644 --- a/default_device.cpp +++ b/default_device.cpp @@ -29,22 +29,15 @@ static const char* ITEMS[] = {"reboot system now", "apply update from ADB", "wipe data/factory reset", "wipe cache partition", + "reboot to bootloader", + "power down", + "view recovery logs", NULL }; -class DefaultUI : public ScreenRecoveryUI { - public: - virtual KeyAction CheckKey(int key) { - if (key == KEY_HOME) { - return TOGGLE; - } - return ENQUEUE; - } -}; - class DefaultDevice : public Device { public: DefaultDevice() : - ui(new DefaultUI) { + ui(new ScreenRecoveryUI) { } RecoveryUI* GetUI() { return ui; } @@ -61,6 +54,7 @@ class DefaultDevice : public Device { return kHighlightUp; case KEY_ENTER: + case KEY_POWER: return kInvokeItem; } } @@ -74,6 +68,9 @@ class DefaultDevice : public Device { case 1: return APPLY_ADB_SIDELOAD; case 2: return WIPE_DATA; case 3: return WIPE_CACHE; + case 4: return REBOOT_BOOTLOADER; + case 5: return SHUTDOWN; + case 6: return READ_RECOVERY_LASTLOG; default: return NO_ACTION; } } @@ -65,8 +65,10 @@ class Device { // - invoke a specific action (a menu position: any non-negative number) virtual int HandleMenuKey(int key, int visible) = 0; - enum BuiltinAction { NO_ACTION, REBOOT, APPLY_EXT, APPLY_CACHE, - APPLY_ADB_SIDELOAD, WIPE_DATA, WIPE_CACHE }; + enum BuiltinAction { NO_ACTION, REBOOT, APPLY_EXT, + APPLY_CACHE, // APPLY_CACHE is deprecated; has no effect + APPLY_ADB_SIDELOAD, WIPE_DATA, WIPE_CACHE, + REBOOT_BOOTLOADER, SHUTDOWN, READ_RECOVERY_LASTLOG }; // Perform a recovery action selected from the menu. // 'menu_position' will be the item number of the selected menu diff --git a/edify/Android.mk b/edify/Android.mk index fac0ba712..61ed6fa17 100644 --- a/edify/Android.mk +++ b/edify/Android.mk @@ -23,6 +23,7 @@ LOCAL_SRC_FILES := \ LOCAL_CFLAGS := $(edify_cflags) -g -O0 LOCAL_MODULE := edify LOCAL_YACCFLAGS := -v +LOCAL_CFLAGS += -Wno-unused-parameter include $(BUILD_HOST_EXECUTABLE) @@ -34,6 +35,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := $(edify_src_files) LOCAL_CFLAGS := $(edify_cflags) +LOCAL_CFLAGS += -Wno-unused-parameter LOCAL_MODULE := libedify include $(BUILD_STATIC_LIBRARY) diff --git a/edify/expr.c b/edify/expr.c index a2f1f99d7..79f6282d8 100644 --- a/edify/expr.c +++ b/edify/expr.c @@ -287,13 +287,11 @@ Value* LessThanIntFn(const char* name, State* state, int argc, Expr* argv[]) { long l_int = strtol(left, &end, 10); if (left[0] == '\0' || *end != '\0') { - printf("[%s] is not an int\n", left); goto done; } long r_int = strtol(right, &end, 10); if (right[0] == '\0' || *end != '\0') { - printf("[%s] is not an int\n", right); goto done; } diff --git a/edify/expr.h b/edify/expr.h index 0d8ed8f57..a9ed2f9c5 100644 --- a/edify/expr.h +++ b/edify/expr.h @@ -164,6 +164,8 @@ Value* StringValue(char* str); // Free a Value object. void FreeValue(Value* v); +int parse_string(const char* str, Expr** root, int* error_count); + #ifdef __cplusplus } // extern "C" #endif diff --git a/edify/main.c b/edify/main.c index 9e6bab7ca..b3fad53b8 100644 --- a/edify/main.c +++ b/edify/main.c @@ -30,9 +30,7 @@ int expect(const char* expr_str, const char* expected, int* errors) { printf("."); - yy_scan_string(expr_str); - int error_count = 0; - error = yyparse(&e, &error_count); + int error_count = parse_string(expr_str, &e, &error_count); if (error > 0 || error_count > 0) { printf("error parsing \"%s\" (%d errors)\n", expr_str, error_count); @@ -193,8 +191,7 @@ int main(int argc, char** argv) { Expr* root; int error_count = 0; - yy_scan_bytes(buffer, size); - int error = yyparse(&root, &error_count); + int error = parse_string(buffer, &root, &error_count); printf("parse returned %d; %d errors encountered\n", error, error_count); if (error == 0 || error_count > 0) { diff --git a/edify/parser.y b/edify/parser.y index 3f9ade144..f8fb2d12f 100644 --- a/edify/parser.y +++ b/edify/parser.y @@ -29,6 +29,10 @@ extern int gColumn; void yyerror(Expr** root, int* error_count, const char* s); int yyparse(Expr** root, int* error_count); +struct yy_buffer_state; +void yy_switch_to_buffer(struct yy_buffer_state* new_buffer); +struct yy_buffer_state* yy_scan_string(const char* yystr); + %} %locations @@ -128,3 +132,8 @@ void yyerror(Expr** root, int* error_count, const char* s) { printf("line %d col %d: %s\n", gLine, gColumn, s); ++*error_count; } + +int parse_string(const char* str, Expr** root, int* error_count) { + yy_switch_to_buffer(yy_scan_string(str)); + return yyparse(root, error_count); +} diff --git a/etc/init.rc b/etc/init.rc index 8b797662c..67b6a6f14 100644 --- a/etc/init.rc +++ b/etc/init.rc @@ -1,6 +1,13 @@ import /init.recovery.${ro.hardware}.rc on early-init + # Apply strict SELinux checking of PROT_EXEC on mmap/mprotect calls. + write /sys/fs/selinux/checkreqprot 0 + + # Set the security context for the init process. + # This should occur before anything else (e.g. ueventd) is started. + setcon u:r:init:s0 + start ueventd start healthd @@ -16,11 +23,14 @@ on init mkdir /system mkdir /data mkdir /cache + mkdir /sideload mount tmpfs tmpfs /tmp chown root shell /tmp chmod 0775 /tmp + write /proc/sys/kernel/panic_on_oops 1 + on fs mkdir /dev/usb-ffs 0770 shell shell mkdir /dev/usb-ffs/adb 0770 shell shell @@ -37,13 +47,31 @@ on fs on boot - ifup lo hostname localhost domainname localdomain class_start default +# Load properties from /system/ + /factory after fs mount. +on load_all_props_action + load_all_props + +# Mount filesystems and start core system services. +on late-init + trigger early-fs + trigger fs + trigger post-fs + trigger post-fs-data + + # Load properties from /system/ + /factory after fs mount. Place + # this in another action so that the load will be scheduled after the prior + # issued fs triggers have completed. + trigger load_all_props_action + + trigger early-boot + trigger boot + on property:sys.powerctl=* powerctl ${sys.powerctl} @@ -78,15 +106,19 @@ on property:sys.usb.config=adb service ueventd /sbin/ueventd critical + seclabel u:r:ueventd:s0 -service healthd /sbin/healthd -n +service healthd /sbin/healthd -r critical + seclabel u:r:healthd:s0 service recovery /sbin/recovery + seclabel u:r:recovery:s0 -service adbd /sbin/adbd recovery +service adbd /sbin/adbd --root_seclabel=u:r:su:s0 --device_banner=recovery disabled socket adbd stream 660 system system + seclabel u:r:adbd:s0 # Always start adbd on userdebug and eng builds on property:ro.debuggable=1 diff --git a/fuse_sdcard_provider.c b/fuse_sdcard_provider.c new file mode 100644 index 000000000..19fb52df0 --- /dev/null +++ b/fuse_sdcard_provider.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <pthread.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> + +#include "fuse_sideload.h" + +struct file_data { + int fd; // the underlying sdcard file + + uint64_t file_size; + uint32_t block_size; +}; + +static int read_block_file(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size) { + struct file_data* fd = (struct file_data*)cookie; + + if (lseek(fd->fd, block * fd->block_size, SEEK_SET) < 0) { + printf("seek on sdcard failed: %s\n", strerror(errno)); + return -EIO; + } + + while (fetch_size > 0) { + ssize_t r = read(fd->fd, buffer, fetch_size); + if (r < 0) { + if (r != -EINTR) { + printf("read on sdcard failed: %s\n", strerror(errno)); + return -EIO; + } + r = 0; + } + fetch_size -= r; + buffer += r; + } + + return 0; +} + +static void close_file(void* cookie) { + struct file_data* fd = (struct file_data*)cookie; + close(fd->fd); +} + +struct token { + pthread_t th; + const char* path; + int result; +}; + +static void* run_sdcard_fuse(void* cookie) { + struct token* t = (struct token*)cookie; + + struct stat sb; + if (stat(t->path, &sb) < 0) { + fprintf(stderr, "failed to stat %s: %s\n", t->path, strerror(errno)); + t->result = -1; + return NULL; + } + + struct file_data fd; + struct provider_vtab vtab; + + fd.fd = open(t->path, O_RDONLY); + if (fd.fd < 0) { + fprintf(stderr, "failed to open %s: %s\n", t->path, strerror(errno)); + t->result = -1; + return NULL; + } + fd.file_size = sb.st_size; + fd.block_size = 65536; + + vtab.read_block = read_block_file; + vtab.close = close_file; + + t->result = run_fuse_sideload(&vtab, &fd, fd.file_size, fd.block_size); + return NULL; +} + +// How long (in seconds) we wait for the fuse-provided package file to +// appear, before timing out. +#define SDCARD_INSTALL_TIMEOUT 10 + +void* start_sdcard_fuse(const char* path) { + struct token* t = malloc(sizeof(struct token)); + + t->path = path; + pthread_create(&(t->th), NULL, run_sdcard_fuse, t); + + struct stat st; + int i; + for (i = 0; i < SDCARD_INSTALL_TIMEOUT; ++i) { + if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) { + if (errno == ENOENT && i < SDCARD_INSTALL_TIMEOUT-1) { + sleep(1); + continue; + } else { + return NULL; + } + } + } + + // The installation process expects to find the sdcard unmounted. + // Unmount it with MNT_DETACH so that our open file continues to + // work but new references see it as unmounted. + umount2("/sdcard", MNT_DETACH); + + return t; +} + +void finish_sdcard_fuse(void* cookie) { + if (cookie == NULL) return; + struct token* t = (struct token*)cookie; + + // Calling stat() on this magic filename signals the fuse + // filesystem to shut down. + struct stat st; + stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); + + pthread_join(t->th, NULL); + free(t); +} diff --git a/fuse_sdcard_provider.h b/fuse_sdcard_provider.h new file mode 100644 index 000000000..dc2982ca0 --- /dev/null +++ b/fuse_sdcard_provider.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FUSE_SDCARD_PROVIDER_H +#define __FUSE_SDCARD_PROVIDER_H + +void* start_sdcard_fuse(const char* path); +void finish_sdcard_fuse(void* token); + +#endif diff --git a/fuse_sideload.c b/fuse_sideload.c new file mode 100644 index 000000000..ab91defbf --- /dev/null +++ b/fuse_sideload.c @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This module creates a special filesystem containing two files. +// +// "/sideload/package.zip" appears to be a normal file, but reading +// from it causes data to be fetched from the adb host. We can use +// this to sideload packages over an adb connection without having to +// store the entire package in RAM on the device. +// +// Because we may not trust the adb host, this filesystem maintains +// the following invariant: each read of a given position returns the +// same data as the first read at that position. That is, once a +// section of the file is read, future reads of that section return +// the same data. (Otherwise, a malicious adb host process could +// return one set of bits when the package is read for signature +// verification, and then different bits for when the package is +// accessed by the installer.) If the adb host returns something +// different than it did on the first read, the reader of the file +// will see their read fail with EINVAL. +// +// The other file, "/sideload/exit", is used to control the subprocess +// that creates this filesystem. Calling stat() on the exit file +// causes the filesystem to be unmounted and the adb process on the +// device shut down. +// +// Note that only the minimal set of file operations needed for these +// two files is implemented. In particular, you can't opendir() or +// readdir() on the "/sideload" directory; ls on it won't work. + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <linux/fuse.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/inotify.h> +#include <sys/mount.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <sys/time.h> +#include <sys/uio.h> +#include <unistd.h> + +#include "mincrypt/sha256.h" +#include "fuse_sideload.h" + +#define PACKAGE_FILE_ID (FUSE_ROOT_ID+1) +#define EXIT_FLAG_ID (FUSE_ROOT_ID+2) + +#define NO_STATUS 1 +#define NO_STATUS_EXIT 2 + +struct fuse_data { + int ffd; // file descriptor for the fuse socket + + struct provider_vtab* vtab; + void* cookie; + + uint64_t file_size; // bytes + + uint32_t block_size; // block size that the adb host is using to send the file to us + uint32_t file_blocks; // file size in block_size blocks + + uid_t uid; + gid_t gid; + + uint32_t curr_block; // cache the block most recently read from the host + uint8_t* block_data; + + uint8_t* extra_block; // another block of storage for reads that + // span two blocks + + uint8_t* hashes; // SHA-256 hash of each block (all zeros + // if block hasn't been read yet) +}; + +static void fuse_reply(struct fuse_data* fd, __u64 unique, const void *data, size_t len) +{ + struct fuse_out_header hdr; + struct iovec vec[2]; + int res; + + hdr.len = len + sizeof(hdr); + hdr.error = 0; + hdr.unique = unique; + + vec[0].iov_base = &hdr; + vec[0].iov_len = sizeof(hdr); + vec[1].iov_base = data; + vec[1].iov_len = len; + + res = writev(fd->ffd, vec, 2); + if (res < 0) { + printf("*** REPLY FAILED *** %d\n", errno); + } +} + +static int handle_init(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { + const struct fuse_init_in* req = data; + struct fuse_init_out out; + + out.major = FUSE_KERNEL_VERSION; + out.minor = FUSE_KERNEL_MINOR_VERSION; + out.max_readahead = req->max_readahead; + out.flags = 0; + out.max_background = 32; + out.congestion_threshold = 32; + out.max_write = 4096; + fuse_reply(fd, hdr->unique, &out, sizeof(out)); + + return NO_STATUS; +} + +static void fill_attr(struct fuse_attr* attr, struct fuse_data* fd, + uint64_t nodeid, uint64_t size, uint32_t mode) { + memset(attr, 0, sizeof(*attr)); + attr->nlink = 1; + attr->uid = fd->uid; + attr->gid = fd->gid; + attr->blksize = 4096; + + attr->ino = nodeid; + attr->size = size; + attr->blocks = (size == 0) ? 0 : (((size-1) / attr->blksize) + 1); + attr->mode = mode; +} + +static int handle_getattr(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { + const struct fuse_getattr_in* req = data; + struct fuse_attr_out out; + memset(&out, 0, sizeof(out)); + out.attr_valid = 10; + + if (hdr->nodeid == FUSE_ROOT_ID) { + fill_attr(&(out.attr), fd, hdr->nodeid, 4096, S_IFDIR | 0555); + } else if (hdr->nodeid == PACKAGE_FILE_ID) { + fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444); + } else if (hdr->nodeid == EXIT_FLAG_ID) { + fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0); + } else { + return -ENOENT; + } + + fuse_reply(fd, hdr->unique, &out, sizeof(out)); + return (hdr->nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS; +} + +static int handle_lookup(void* data, struct fuse_data* fd, + const struct fuse_in_header* hdr) { + struct fuse_entry_out out; + memset(&out, 0, sizeof(out)); + out.entry_valid = 10; + out.attr_valid = 10; + + if (strncmp(FUSE_SIDELOAD_HOST_FILENAME, data, + sizeof(FUSE_SIDELOAD_HOST_FILENAME)) == 0) { + out.nodeid = PACKAGE_FILE_ID; + out.generation = PACKAGE_FILE_ID; + fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444); + } else if (strncmp(FUSE_SIDELOAD_HOST_EXIT_FLAG, data, + sizeof(FUSE_SIDELOAD_HOST_EXIT_FLAG)) == 0) { + out.nodeid = EXIT_FLAG_ID; + out.generation = EXIT_FLAG_ID; + fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0); + } else { + return -ENOENT; + } + + fuse_reply(fd, hdr->unique, &out, sizeof(out)); + return (out.nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS; +} + +static int handle_open(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { + const struct fuse_open_in* req = data; + + if (hdr->nodeid == EXIT_FLAG_ID) return -EPERM; + if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; + + struct fuse_open_out out; + memset(&out, 0, sizeof(out)); + out.fh = 10; // an arbitrary number; we always use the same handle + fuse_reply(fd, hdr->unique, &out, sizeof(out)); + return NO_STATUS; +} + +static int handle_flush(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { + return 0; +} + +static int handle_release(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { + return 0; +} + +// Fetch a block from the host into fd->curr_block and fd->block_data. +// Returns 0 on successful fetch, negative otherwise. +static int fetch_block(struct fuse_data* fd, uint32_t block) { + if (block == fd->curr_block) { + return 0; + } + + if (block >= fd->file_blocks) { + memset(fd->block_data, 0, fd->block_size); + fd->curr_block = block; + return 0; + } + + size_t fetch_size = fd->block_size; + if (block * fd->block_size + fetch_size > fd->file_size) { + // If we're reading the last (partial) block of the file, + // expect a shorter response from the host, and pad the rest + // of the block with zeroes. + fetch_size = fd->file_size - (block * fd->block_size); + memset(fd->block_data + fetch_size, 0, fd->block_size - fetch_size); + } + + int result = fd->vtab->read_block(fd->cookie, block, fd->block_data, fetch_size); + if (result < 0) return result; + + fd->curr_block = block; + + // Verify the hash of the block we just got from the host. + // + // - If the hash of the just-received data matches the stored hash + // for the block, accept it. + // - If the stored hash is all zeroes, store the new hash and + // accept the block (this is the first time we've read this + // block). + // - Otherwise, return -EINVAL for the read. + + uint8_t hash[SHA256_DIGEST_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) { + return 0; + } + + int i; + for (i = 0; i < SHA256_DIGEST_SIZE; ++i) { + if (blockhash[i] != 0) { + fd->curr_block = -1; + return -EIO; + } + } + + memcpy(blockhash, hash, SHA256_DIGEST_SIZE); + return 0; +} + +static int handle_read(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { + const struct fuse_read_in* req = data; + struct fuse_out_header outhdr; + struct iovec vec[3]; + int vec_used; + int result; + + if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; + + uint64_t offset = req->offset; + uint32_t size = req->size; + + // The docs on the fuse kernel interface are vague about what to + // do when a read request extends past the end of the file. We + // can return a short read -- the return structure does include a + // length field -- but in testing that caused the program using + // the file to segfault. (I speculate that this is due to the + // reading program accessing it via mmap; maybe mmap dislikes when + // you return something short of a whole page?) To fix this we + // zero-pad reads that extend past the end of the file so we're + // always returning exactly as many bytes as were requested. + // (Users of the mapped file have to know its real length anyway.) + + outhdr.len = sizeof(outhdr) + size; + outhdr.error = 0; + outhdr.unique = hdr->unique; + vec[0].iov_base = &outhdr; + vec[0].iov_len = sizeof(outhdr); + + uint32_t block = offset / fd->block_size; + result = fetch_block(fd, block); + if (result != 0) return result; + + // Two cases: + // + // - the read request is entirely within this block. In this + // case we can reply immediately. + // + // - the read request goes over into the next block. Note that + // since we mount the filesystem with max_read=block_size, a + // read can never span more than two blocks. In this case we + // copy the block to extra_block and issue a fetch for the + // following block. + + uint32_t block_offset = offset - (block * fd->block_size); + + if (size + block_offset <= fd->block_size) { + // First case: the read fits entirely in the first block. + + vec[1].iov_base = fd->block_data + block_offset; + vec[1].iov_len = size; + vec_used = 2; + } else { + // Second case: the read spills over into the next block. + + memcpy(fd->extra_block, fd->block_data + block_offset, + fd->block_size - block_offset); + vec[1].iov_base = fd->extra_block; + vec[1].iov_len = fd->block_size - block_offset; + + result = fetch_block(fd, block+1); + if (result != 0) return result; + vec[2].iov_base = fd->block_data; + vec[2].iov_len = size - vec[1].iov_len; + vec_used = 3; + } + + if (writev(fd->ffd, vec, vec_used) < 0) { + printf("*** READ REPLY FAILED: %s ***\n", strerror(errno)); + } + return NO_STATUS; +} + +int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, + uint64_t file_size, uint32_t block_size) +{ + int result; + + // If something's already mounted on our mountpoint, try to remove + // it. (Mostly in case of a previous abnormal exit.) + umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_FORCE); + + if (block_size < 1024) { + fprintf(stderr, "block size (%u) is too small\n", block_size); + return -1; + } + if (block_size > (1<<22)) { // 4 MiB + fprintf(stderr, "block size (%u) is too large\n", block_size); + return -1; + } + + struct fuse_data fd; + memset(&fd, 0, sizeof(fd)); + fd.vtab = vtab; + fd.cookie = cookie; + fd.file_size = file_size; + fd.block_size = block_size; + fd.file_blocks = (file_size == 0) ? 0 : (((file_size-1) / block_size) + 1); + + if (fd.file_blocks > (1<<18)) { + fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks); + result = -1; + goto done; + } + + fd.hashes = (uint8_t*)calloc(fd.file_blocks, SHA256_DIGEST_SIZE); + if (fd.hashes == NULL) { + fprintf(stderr, "failed to allocate %d bites for hashes\n", + fd.file_blocks * SHA256_DIGEST_SIZE); + result = -1; + goto done; + } + + fd.uid = getuid(); + fd.gid = getgid(); + + fd.curr_block = -1; + fd.block_data = (uint8_t*)malloc(block_size); + if (fd.block_data == NULL) { + fprintf(stderr, "failed to allocate %d bites for block_data\n", block_size); + result = -1; + goto done; + } + fd.extra_block = (uint8_t*)malloc(block_size); + if (fd.extra_block == NULL) { + fprintf(stderr, "failed to allocate %d bites for extra_block\n", block_size); + result = -1; + goto done; + } + + fd.ffd = open("/dev/fuse", O_RDWR); + if (fd.ffd < 0) { + perror("open /dev/fuse"); + result = -1; + goto done; + } + + char opts[256]; + snprintf(opts, sizeof(opts), + ("fd=%d,user_id=%d,group_id=%d,max_read=%zu," + "allow_other,rootmode=040000"), + fd.ffd, fd.uid, fd.gid, block_size); + + result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT, + "fuse", MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts); + if (result < 0) { + perror("mount"); + goto done; + } + uint8_t request_buffer[sizeof(struct fuse_in_header) + PATH_MAX*8]; + for (;;) { + ssize_t len = read(fd.ffd, request_buffer, sizeof(request_buffer)); + if (len < 0) { + if (errno != EINTR) { + perror("read request"); + if (errno == ENODEV) { + result = -1; + break; + } + } + continue; + } + + if ((size_t)len < sizeof(struct fuse_in_header)) { + fprintf(stderr, "request too short: len=%zu\n", (size_t)len); + continue; + } + + struct fuse_in_header* hdr = (struct fuse_in_header*) request_buffer; + void* data = request_buffer + sizeof(struct fuse_in_header); + + result = -ENOSYS; + + switch (hdr->opcode) { + case FUSE_INIT: + result = handle_init(data, &fd, hdr); + break; + + case FUSE_LOOKUP: + result = handle_lookup(data, &fd, hdr); + break; + + case FUSE_GETATTR: + result = handle_getattr(data, &fd, hdr); + break; + + case FUSE_OPEN: + result = handle_open(data, &fd, hdr); + break; + + case FUSE_READ: + result = handle_read(data, &fd, hdr); + break; + + case FUSE_FLUSH: + result = handle_flush(data, &fd, hdr); + break; + + case FUSE_RELEASE: + result = handle_release(data, &fd, hdr); + break; + + default: + fprintf(stderr, "unknown fuse request opcode %d\n", hdr->opcode); + break; + } + + if (result == NO_STATUS_EXIT) { + result = 0; + break; + } + + if (result != NO_STATUS) { + struct fuse_out_header outhdr; + outhdr.len = sizeof(outhdr); + outhdr.error = result; + outhdr.unique = hdr->unique; + write(fd.ffd, &outhdr, sizeof(outhdr)); + } + } + + done: + fd.vtab->close(fd.cookie); + + result = umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_DETACH); + if (result < 0) { + printf("fuse_sideload umount failed: %s\n", strerror(errno)); + } + + if (fd.ffd) close(fd.ffd); + free(fd.hashes); + free(fd.block_data); + free(fd.extra_block); + + return result; +} diff --git a/fuse_sideload.h b/fuse_sideload.h new file mode 100644 index 000000000..c0b16efbe --- /dev/null +++ b/fuse_sideload.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FUSE_SIDELOAD_H +#define __FUSE_SIDELOAD_H + +// define the filenames created by the sideload FUSE filesystem +#define FUSE_SIDELOAD_HOST_MOUNTPOINT "/sideload" +#define FUSE_SIDELOAD_HOST_FILENAME "package.zip" +#define FUSE_SIDELOAD_HOST_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_FILENAME) +#define FUSE_SIDELOAD_HOST_EXIT_FLAG "exit" +#define FUSE_SIDELOAD_HOST_EXIT_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_EXIT_FLAG) + +struct provider_vtab { + // read a block + int (*read_block)(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size); + + // close down + void (*close)(void* cookie); +}; + +int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, + uint64_t file_size, uint32_t block_size); + +#endif diff --git a/install.cpp b/install.cpp index 797a525fd..9db5640a0 100644 --- a/install.cpp +++ b/install.cpp @@ -120,6 +120,7 @@ try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { pid_t pid = fork(); if (pid == 0) { + umask(022); close(pipefd[0]); execv(binary, (char* const*)args); fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno)); @@ -159,6 +160,11 @@ try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { *wipe_cache = 1; } else if (strcmp(command, "clear_display") == 0) { ui->SetBackground(RecoveryUI::NONE); + } else if (strcmp(command, "enable_reboot") == 0) { + // packages can explicitly request that they want the user + // to be able to reboot during installation (useful for + // debugging packages that don't exit). + ui->SetEnableReboot(true); } else { LOGE("unknown command [%s]\n", command); } @@ -176,7 +182,7 @@ try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { } static int -really_install_package(const char *path, int* wipe_cache) +really_install_package(const char *path, int* wipe_cache, bool needs_mount) { ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); ui->Print("Finding update package...\n"); @@ -185,12 +191,22 @@ really_install_package(const char *path, int* wipe_cache) ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME); LOGI("Update location: %s\n", path); - if (ensure_path_mounted(path) != 0) { - LOGE("Can't mount %s\n", path); - return INSTALL_CORRUPT; + // Map the update package into memory. + ui->Print("Opening update package...\n"); + + if (path && needs_mount) { + if (path[0] == '@') { + ensure_path_mounted(path+1); + } else { + ensure_path_mounted(path); + } } - ui->Print("Opening update package...\n"); + MemMapping map; + if (sysMapFile(path, &map) != 0) { + LOGE("failed to map file\n"); + return INSTALL_CORRUPT; + } int numKeys; Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys); @@ -203,31 +219,41 @@ really_install_package(const char *path, int* wipe_cache) ui->Print("Verifying update package...\n"); int err; - err = verify_file(path, loadedKeys, numKeys); + err = verify_file(map.addr, map.length, loadedKeys, numKeys); free(loadedKeys); LOGI("verify_file returned %d\n", err); if (err != VERIFY_SUCCESS) { LOGE("signature verification failed\n"); + sysReleaseMap(&map); return INSTALL_CORRUPT; } /* Try to open the package. */ ZipArchive zip; - err = mzOpenZipArchive(path, &zip); + err = mzOpenZipArchive(map.addr, map.length, &zip); if (err != 0) { LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad"); + sysReleaseMap(&map); return INSTALL_CORRUPT; } /* Verify and install the contents of the package. */ ui->Print("Installing update...\n"); - return try_update_binary(path, &zip, wipe_cache); + ui->SetEnableReboot(false); + int result = try_update_binary(path, &zip, wipe_cache); + ui->SetEnableReboot(true); + ui->Print("\n"); + + sysReleaseMap(&map); + + return result; } int -install_package(const char* path, int* wipe_cache, const char* install_file) +install_package(const char* path, int* wipe_cache, const char* install_file, + bool needs_mount) { FILE* install_log = fopen_path(install_file, "w"); if (install_log) { @@ -241,7 +267,7 @@ install_package(const char* path, int* wipe_cache, const char* install_file) LOGE("failed to set up expected mounts for install; aborting\n"); result = INSTALL_ERROR; } else { - result = really_install_package(path, wipe_cache); + result = really_install_package(path, wipe_cache, needs_mount); } if (install_log) { fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log); @@ -29,7 +29,7 @@ enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE }; // returned and *wipe_cache is true on exit, caller should wipe the // cache partition. int install_package(const char *root_path, int* wipe_cache, - const char* install_file); + const char* install_file, bool needs_mount); RSAPublicKey* load_keys(const char* filename, int* numKeys); #ifdef __cplusplus diff --git a/interlace-frames.py b/interlace-frames.py new file mode 100644 index 000000000..243e565e7 --- /dev/null +++ b/interlace-frames.py @@ -0,0 +1,53 @@ +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Script to take a set of frames (PNG files) for a recovery animation +and turn it into a single output image which contains the input frames +interlaced by row. Run with the names of all the input frames on the +command line, in order, followed by the name of the output file.""" + +import sys +try: + import Image + import PngImagePlugin +except ImportError: + print "This script requires the Python Imaging Library to be installed." + sys.exit(1) + +frames = [Image.open(fn).convert("RGB") for fn in sys.argv[1:-1]] +assert len(frames) > 0, "Must have at least one input frame." +sizes = set() +for fr in frames: + sizes.add(fr.size) + +assert len(sizes) == 1, "All input images must have the same size." +w, h = sizes.pop() +N = len(frames) + +out = Image.new("RGB", (w, h*N)) +for j in range(h): + for i in range(w): + for fn, f in enumerate(frames): + out.putpixel((i, j*N+fn), f.getpixel((i, j))) + +# When loading this image, the graphics library expects to find a text +# chunk that specifies how many frames this animation represents. If +# you post-process the output of this script with some kind of +# optimizer tool (eg pngcrush or zopflipng) make sure that your +# optimizer preserves this text chunk. + +meta = PngImagePlugin.PngInfo() +meta.add_text("Frames", str(N)) + +out.save(sys.argv[-1], pnginfo=meta) diff --git a/make-overlay.py b/make-overlay.py deleted file mode 100644 index 7f931b373..000000000 --- a/make-overlay.py +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright (C) 2011 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Script to take a set of frames (PNG files) for a recovery -"installing" icon animation and turn it into a base image plus a set -of overlays, as needed by the recovery UI code. Run with the names of -all the input frames on the command line, in order.""" - -import sys -try: - import Image -except ImportError: - print "This script requires the Python Imaging Library to be installed." - sys.exit(1) - -# Find the smallest box that contains all the pixels which change -# between images. - -print "reading", sys.argv[1] -base = Image.open(sys.argv[1]) - -minmini = base.size[0]-1 -maxmaxi = 0 -minminj = base.size[1]-1 -maxmaxj = 0 - -for top_name in sys.argv[2:]: - print "reading", top_name - top = Image.open(top_name) - - assert base.size == top.size - - mini = base.size[0]-1 - maxi = 0 - minj = base.size[1]-1 - maxj = 0 - - h, w = base.size - for j in range(w): - for i in range(h): - b = base.getpixel((i,j)) - t = top.getpixel((i,j)) - if b != t: - if i < mini: mini = i - if i > maxi: maxi = i - if j < minj: minj = j - if j > maxj: maxj = j - - minmini = min(minmini, mini) - maxmaxi = max(maxmaxi, maxi) - minminj = min(minminj, minj) - maxmaxj = max(maxmaxj, maxj) - -w = maxmaxi - minmini + 1 -h = maxmaxj - minminj + 1 - -# Now write out an image containing just that box, for each frame. - -for num, top_name in enumerate(sys.argv[1:]): - top = Image.open(top_name) - - out = Image.new("RGB", (w, h)) - for i in range(w): - for j in range(h): - t = top.getpixel((i+minmini, j+minminj)) - out.putpixel((i, j), t) - - fn = "icon_installing_overlay%02d.png" % (num+1,) - out.save(fn) - print "saved", fn - -# Write out the base icon, which is the first frame with that box -# blacked out (just to make the file smaller, since it's always -# displayed with one of the overlays on top of it). - -for i in range(w): - for j in range(h): - base.putpixel((i+minmini, j+minminj), (0, 0, 0)) -fn = "icon_installing.png" -base.save(fn) -print "saved", fn - -# The device_ui_init() function needs to tell the recovery UI the -# position of the overlay box. - -print -print "add this to your device_ui_init() function:" -print "-" * 40 -print " ui_parameters->install_overlay_offset_x = %d;" % (minmini,) -print " ui_parameters->install_overlay_offset_y = %d;" % (minminj,) -print "-" * 40 diff --git a/minadbd/Android.mk b/minadbd/Android.mk index c08fd46b0..4430a2baa 100644 --- a/minadbd/Android.mk +++ b/minadbd/Android.mk @@ -13,6 +13,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ adb.c \ fdevent.c \ + fuse_adb_provider.c \ transport.c \ transport_usb.c \ sockets.c \ @@ -26,8 +27,5 @@ LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE LOCAL_MODULE_TAGS := eng LOCAL_MODULE := libminadbd -LOCAL_SHARED_LIBRARIES := libcutils libc +LOCAL_SHARED_LIBRARIES := libfusesideload libcutils libc include $(BUILD_SHARED_LIBRARY) - - - diff --git a/minadbd/adb.c b/minadbd/adb.c index 4626bd3aa..c35e8300b 100644 --- a/minadbd/adb.c +++ b/minadbd/adb.c @@ -407,6 +407,7 @@ int adb_main(const char* path) fprintf(stderr, "userid is %d\n", getuid()); */ + D("Event loop starting\n"); fdevent_loop(); diff --git a/minadbd/adb.h b/minadbd/adb.h index c899b5862..08ee989d6 100644 --- a/minadbd/adb.h +++ b/minadbd/adb.h @@ -244,15 +244,11 @@ void kick_transport( atransport* t ); #if ADB_HOST int get_available_local_transport_index(); #endif -int init_socket_transport(atransport *t, int s, int port, int local); void init_usb_transport(atransport *t, usb_handle *usb, int state); /* for MacOS X cleanup */ void close_usb_devices(); -/* cause new transports to be init'd and added to the list */ -void register_socket_transport(int s, const char *serial, int port, int local); - /* these should only be used for the "adb disconnect" command */ void unregister_transport(atransport *t); void unregister_all_tcp_transports(); @@ -404,6 +400,7 @@ int connection_state(atransport *t); #define CS_RECOVERY 4 #define CS_NOPERM 5 /* Insufficient permissions to communicate with the device */ #define CS_SIDELOAD 6 +#define CS_UNAUTHORIZED 7 extern int HOST; extern int SHELL_EXIT_NOTIFY_FD; diff --git a/minadbd/fuse_adb_provider.c b/minadbd/fuse_adb_provider.c new file mode 100644 index 000000000..f80533a8c --- /dev/null +++ b/minadbd/fuse_adb_provider.c @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> + +#include "adb.h" +#include "fuse_sideload.h" + +struct adb_data { + int sfd; // file descriptor for the adb channel + + uint64_t file_size; + uint32_t block_size; +}; + +static int read_block_adb(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size) { + struct adb_data* ad = (struct adb_data*)cookie; + + char buf[10]; + snprintf(buf, sizeof(buf), "%08u", block); + if (writex(ad->sfd, buf, 8) < 0) { + fprintf(stderr, "failed to write to adb host: %s\n", strerror(errno)); + return -EIO; + } + + if (readx(ad->sfd, buffer, fetch_size) < 0) { + fprintf(stderr, "failed to read from adb host: %s\n", strerror(errno)); + return -EIO; + } + + return 0; +} + +static void close_adb(void* cookie) { + struct adb_data* ad = (struct adb_data*)cookie; + + writex(ad->sfd, "DONEDONE", 8); +} + +int run_adb_fuse(int sfd, uint64_t file_size, uint32_t block_size) { + struct adb_data ad; + struct provider_vtab vtab; + + ad.sfd = sfd; + ad.file_size = file_size; + ad.block_size = block_size; + + vtab.read_block = read_block_adb; + vtab.close = close_adb; + + return run_fuse_sideload(&vtab, &ad, file_size, block_size); +} diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h new file mode 100644 index 000000000..0eb1f79d1 --- /dev/null +++ b/minadbd/fuse_adb_provider.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FUSE_ADB_PROVIDER_H +#define __FUSE_ADB_PROVIDER_H + +int run_adb_fuse(int sfd, uint64_t file_size, uint32_t block_size); + +#endif diff --git a/minadbd/services.c b/minadbd/services.c index aef37f7e4..218b84a38 100644 --- a/minadbd/services.c +++ b/minadbd/services.c @@ -22,6 +22,7 @@ #include "sysdeps.h" #include "fdevent.h" +#include "fuse_adb_provider.h" #define TRACE_TAG TRACE_SERVICES #include "adb.h" @@ -43,44 +44,23 @@ void *service_bootstrap_func(void *x) return 0; } -static void sideload_service(int s, void *cookie) +static void sideload_host_service(int sfd, void* cookie) { - unsigned char buf[4096]; - unsigned count = (unsigned) cookie; - int fd; - - fprintf(stderr, "sideload_service invoked\n"); - - fd = adb_creat(ADB_SIDELOAD_FILENAME, 0644); - if(fd < 0) { - fprintf(stderr, "failed to create %s\n", ADB_SIDELOAD_FILENAME); - adb_close(s); - return; - } + char* saveptr; + const char* s = strtok_r(cookie, ":", &saveptr); + uint64_t file_size = strtoull(s, NULL, 10); + s = strtok_r(NULL, ":", &saveptr); + uint32_t block_size = strtoul(s, NULL, 10); - while(count > 0) { - unsigned xfer = (count > 4096) ? 4096 : count; - if(readx(s, buf, xfer)) break; - if(writex(fd, buf, xfer)) break; - count -= xfer; - } + printf("sideload-host file size %llu block size %lu\n", file_size, block_size); - if(count == 0) { - writex(s, "OKAY", 4); - } else { - writex(s, "FAIL", 4); - } - adb_close(fd); - adb_close(s); + int result = run_adb_fuse(sfd, file_size, block_size); - if (count == 0) { - fprintf(stderr, "adbd exiting after successful sideload\n"); - sleep(1); - exit(0); - } + printf("sideload_host finished\n"); + sleep(1); + exit(result == 0 ? 0 : 1); } - #if 0 static void echo_service(int fd, void *cookie) { @@ -149,7 +129,12 @@ int service_to_fd(const char *name) int ret = -1; if (!strncmp(name, "sideload:", 9)) { - ret = create_service_thread(sideload_service, (void*) atoi(name + 9)); + // this exit status causes recovery to print a special error + // message saying to use a newer adb (that supports + // sideload-host). + exit(3); + } else if (!strncmp(name, "sideload-host:", 14)) { + ret = create_service_thread(sideload_host_service, (void*)(name + 14)); #if 0 } else if(!strncmp(name, "echo:", 5)){ ret = create_service_thread(echo_service, 0); diff --git a/minadbd/sockets.c b/minadbd/sockets.c index 2dd646159..817410d13 100644 --- a/minadbd/sockets.c +++ b/minadbd/sockets.c @@ -319,7 +319,8 @@ static void local_socket_event_func(int fd, unsigned ev, void *_s) while(avail > 0) { r = adb_read(fd, x, avail); - D("LS(%d): post adb_read(fd=%d,...) r=%d (errno=%d) avail=%d\n", s->id, s->fd, r, r<0?errno:0, avail); + D("LS(%d): post adb_read(fd=%d,...) r=%d (errno=%d) avail=%zu\n", + s->id, s->fd, r, r<0?errno:0, avail); if(r > 0) { avail -= r; x += r; diff --git a/minadbd/transport.c b/minadbd/transport.c index ff2004932..92679f518 100644 --- a/minadbd/transport.c +++ b/minadbd/transport.c @@ -678,27 +678,6 @@ retry: return result; } -void register_socket_transport(int s, const char *serial, int port, int local) -{ - atransport *t = calloc(1, sizeof(atransport)); - char buff[32]; - - if (!serial) { - snprintf(buff, sizeof buff, "T-%p", t); - serial = buff; - } - D("transport: %s init'ing for socket %d, on port %d\n", serial, s, port); - if ( init_socket_transport(t, s, port, local) < 0 ) { - adb_close(s); - free(t); - return; - } - if(serial) { - t->serial = strdup(serial); - } - register_transport(t); -} - void register_usb_transport(usb_handle *usb, const char *serial, unsigned writeable) { atransport *t = calloc(1, sizeof(atransport)); @@ -734,7 +713,7 @@ int readx(int fd, void *ptr, size_t len) char *p = ptr; int r; #if ADB_TRACE - int len0 = len; + size_t len0 = len; #endif D("readx: fd=%d wanted=%d\n", fd, (int)len); while(len > 0) { @@ -755,7 +734,7 @@ int readx(int fd, void *ptr, size_t len) } #if ADB_TRACE - D("readx: fd=%d wanted=%d got=%d\n", fd, len0, len0 - len); + D("readx: fd=%d wanted=%zu got=%zu\n", fd, len0, len0 - len); dump_hex( ptr, len0 ); #endif return 0; diff --git a/minadbd/usb_linux_client.c b/minadbd/usb_linux_client.c index c135d6396..29bab1558 100644 --- a/minadbd/usb_linux_client.c +++ b/minadbd/usb_linux_client.c @@ -388,7 +388,7 @@ static int bulk_read(int bulk_out, char *buf, size_t length) ret = adb_read(bulk_out, buf + count, length - count); if (ret < 0) { if (errno != EINTR) { - D("[ bulk_read failed fd=%d length=%d count=%d ]\n", + D("[ bulk_read failed fd=%d length=%zu count=%zu ]\n", bulk_out, length, count); return ret; } diff --git a/minelf/Retouch.c b/minelf/Retouch.c deleted file mode 100644 index d75eec1e8..000000000 --- a/minelf/Retouch.c +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <errno.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <stdio.h> -#include <unistd.h> -#include <string.h> -#include <strings.h> -#include "Retouch.h" -#include "applypatch/applypatch.h" - -typedef struct { - int32_t mmap_addr; - char tag[4]; /* 'P', 'R', 'E', ' ' */ -} prelink_info_t __attribute__((packed)); - -#define false 0 -#define true 1 - -static int32_t offs_prev; -static uint32_t cont_prev; - -static void init_compression_state(void) { - offs_prev = 0; - cont_prev = 0; -} - -// For details on the encoding used for relocation lists, please -// refer to build/tools/retouch/retouch-prepare.c. The intent is to -// save space by removing most of the inherent redundancy. - -static void decode_bytes(uint8_t *encoded_bytes, int encoded_size, - int32_t *dst_offset, uint32_t *dst_contents) { - if (encoded_size == 2) { - *dst_offset = offs_prev + (((encoded_bytes[0]&0x60)>>5)+1)*4; - - // if the original was negative, we need to 1-pad before applying delta - int32_t tmp = (((encoded_bytes[0] & 0x0000001f) << 8) | - encoded_bytes[1]); - if (tmp & 0x1000) tmp = 0xffffe000 | tmp; - *dst_contents = cont_prev + tmp; - } else if (encoded_size == 3) { - *dst_offset = offs_prev + (((encoded_bytes[0]&0x30)>>4)+1)*4; - - // if the original was negative, we need to 1-pad before applying delta - int32_t tmp = (((encoded_bytes[0] & 0x0000000f) << 16) | - (encoded_bytes[1] << 8) | - encoded_bytes[2]); - if (tmp & 0x80000) tmp = 0xfff00000 | tmp; - *dst_contents = cont_prev + tmp; - } else { - *dst_offset = - (encoded_bytes[0]<<24) | - (encoded_bytes[1]<<16) | - (encoded_bytes[2]<<8) | - encoded_bytes[3]; - if (*dst_offset == 0x3fffffff) *dst_offset = -1; - *dst_contents = - (encoded_bytes[4]<<24) | - (encoded_bytes[5]<<16) | - (encoded_bytes[6]<<8) | - encoded_bytes[7]; - } -} - -static uint8_t *decode_in_memory(uint8_t *encoded_bytes, - int32_t *offset, uint32_t *contents) { - int input_size, charIx; - uint8_t input[8]; - - input[0] = *(encoded_bytes++); - if (input[0] & 0x80) - input_size = 2; - else if (input[0] & 0x40) - input_size = 3; - else - input_size = 8; - - // we already read one byte.. - charIx = 1; - while (charIx < input_size) { - input[charIx++] = *(encoded_bytes++); - } - - // depends on the decoder state! - decode_bytes(input, input_size, offset, contents); - - offs_prev = *offset; - cont_prev = *contents; - - return encoded_bytes; -} - -int retouch_mask_data(uint8_t *binary_object, - int32_t binary_size, - int32_t *desired_offset, - int32_t *retouch_offset) { - retouch_info_t *r_info; - prelink_info_t *p_info; - - int32_t target_offset = 0; - if (desired_offset) target_offset = *desired_offset; - - int32_t p_offs = binary_size-sizeof(prelink_info_t); // prelink_info_t - int32_t r_offs = p_offs-sizeof(retouch_info_t); // retouch_info_t - int32_t b_offs; // retouch data blob - - // If not retouched, we say it was a match. This might get invoked on - // non-retouched binaries, so that's why we need to do this. - if (retouch_offset != NULL) *retouch_offset = target_offset; - if (r_offs < 0) return (desired_offset == NULL) ? - RETOUCH_DATA_NOTAPPLICABLE : RETOUCH_DATA_MATCHED; - p_info = (prelink_info_t *)(binary_object+p_offs); - r_info = (retouch_info_t *)(binary_object+r_offs); - if (strncmp(p_info->tag, "PRE ", 4) || - strncmp(r_info->tag, "RETOUCH ", 8)) - return (desired_offset == NULL) ? - RETOUCH_DATA_NOTAPPLICABLE : RETOUCH_DATA_MATCHED; - - b_offs = r_offs-r_info->blob_size; - if (b_offs < 0) { - printf("negative binary offset: %d = %d - %d\n", - b_offs, r_offs, r_info->blob_size); - return RETOUCH_DATA_ERROR; - } - uint8_t *b_ptr = binary_object+b_offs; - - // Retouched: let's go through the work then. - int32_t offset_candidate = target_offset; - bool offset_set = false, offset_mismatch = false; - init_compression_state(); - while (b_ptr < (uint8_t *)r_info) { - int32_t retouch_entry_offset; - uint32_t *retouch_entry; - uint32_t retouch_original_value; - - b_ptr = decode_in_memory(b_ptr, - &retouch_entry_offset, - &retouch_original_value); - if (retouch_entry_offset < (-1) || - retouch_entry_offset >= b_offs) { - printf("bad retouch_entry_offset: %d", retouch_entry_offset); - return RETOUCH_DATA_ERROR; - } - - // "-1" means this is the value in prelink_info_t, which also gets - // randomized. - if (retouch_entry_offset == -1) - retouch_entry = (uint32_t *)&(p_info->mmap_addr); - else - retouch_entry = (uint32_t *)(binary_object+retouch_entry_offset); - - if (desired_offset) - *retouch_entry = retouch_original_value + target_offset; - - // Infer the randomization shift, compare to previously inferred. - int32_t offset_of_this_entry = (int32_t)(*retouch_entry- - retouch_original_value); - if (!offset_set) { - offset_candidate = offset_of_this_entry; - offset_set = true; - } else { - if (offset_candidate != offset_of_this_entry) { - offset_mismatch = true; - printf("offset is mismatched: %d, this entry is %d," - " original 0x%x @ 0x%x", - offset_candidate, offset_of_this_entry, - retouch_original_value, retouch_entry_offset); - } - } - } - if (b_ptr > (uint8_t *)r_info) { - printf("b_ptr went too far: %p, while r_info is %p", - b_ptr, r_info); - return RETOUCH_DATA_ERROR; - } - - if (offset_mismatch) return RETOUCH_DATA_MISMATCHED; - if (retouch_offset != NULL) *retouch_offset = offset_candidate; - return RETOUCH_DATA_MATCHED; -} diff --git a/minelf/Retouch.h b/minelf/Retouch.h deleted file mode 100644 index 13bacd5ad..000000000 --- a/minelf/Retouch.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _MINELF_RETOUCH -#define _MINELF_RETOUCH - -#include <stdbool.h> -#include <sys/types.h> - -typedef struct { - char tag[8]; /* "RETOUCH ", not zero-terminated */ - uint32_t blob_size; /* in bytes, located right before this struct */ -} retouch_info_t __attribute__((packed)); - -#define RETOUCH_DONT_MASK 0 -#define RETOUCH_DO_MASK 1 - -#define RETOUCH_DATA_ERROR 0 // This is bad. Should not happen. -#define RETOUCH_DATA_MATCHED 1 // Up to an uniform random offset. -#define RETOUCH_DATA_MISMATCHED 2 // Partially randomized, or total mess. -#define RETOUCH_DATA_NOTAPPLICABLE 3 // Not retouched. Only when inferring. - -// Mask retouching in-memory. Used before apply_patch[_check]. -// Also used to determine status of retouching after a crash. -// -// If desired_offset is not NULL, then apply retouching instead, -// and return that in retouch_offset. -int retouch_mask_data(uint8_t *binary_object, - int32_t binary_size, - int32_t *desired_offset, - int32_t *retouch_offset); -#endif diff --git a/minui/Android.mk b/minui/Android.mk index 54eb06166..7b401567d 100644 --- a/minui/Android.mk +++ b/minui/Android.mk @@ -1,7 +1,7 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := graphics_overlay.c events.c resources.c +LOCAL_SRC_FILES := graphics_adf.c graphics_fbdev.c graphics_overlay.c events.c resources.c ifneq ($(BOARD_CUSTOM_GRAPHICS),) LOCAL_SRC_FILES += $(BOARD_CUSTOM_GRAPHICS) else @@ -25,6 +25,8 @@ else endif LOCAL_STATIC_LIBRARY := libpng +LOCAL_WHOLE_STATIC_LIBRARIES += libadf + LOCAL_MODULE := libminui # This used to compare against values in double-quotes (which are just @@ -60,7 +62,7 @@ include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) -LOCAL_SRC_FILES := graphics_overlay.c events.c resources.c +LOCAL_SRC_FILES := graphics_adf.c graphics_fbdev.c graphics_overlay.c events.c resources.c ifneq ($(BOARD_CUSTOM_GRAPHICS),) LOCAL_SRC_FILES += $(BOARD_CUSTOM_GRAPHICS) else @@ -87,6 +89,7 @@ LOCAL_MODULE := libminui LOCAL_ARM_MODE:= arm LOCAL_SHARED_LIBRARIES := libpng libpixelflinger +LOCAL_WHOLE_STATIC_LIBRARIES += libadf # This used to compare against values in double-quotes (which are just # ordinary characters in this context). Strip double-quotes from the # value so that either will work. diff --git a/minui/events.c b/minui/events.c index 2918afaa8..df7dad448 100644 --- a/minui/events.c +++ b/minui/events.c @@ -18,7 +18,7 @@ #include <stdlib.h> #include <fcntl.h> #include <dirent.h> -#include <sys/poll.h> +#include <sys/epoll.h> #include <linux/input.h> @@ -34,11 +34,15 @@ ((array)[(bit)/BITS_PER_LONG] & (1 << ((bit) % BITS_PER_LONG))) struct fd_info { + int fd; ev_callback cb; void *data; }; -static struct pollfd ev_fds[MAX_DEVICES + MAX_MISC_FDS]; +static int epollfd; +static struct epoll_event polledevents[MAX_DEVICES + MAX_MISC_FDS]; +static int npolledevents; + static struct fd_info ev_fdinfo[MAX_DEVICES + MAX_MISC_FDS]; static unsigned ev_count = 0; @@ -50,6 +54,12 @@ int ev_init(ev_callback input_cb, void *data) DIR *dir; struct dirent *de; int fd; + struct epoll_event ev; + bool epollctlfail = false; + + epollfd = epoll_create(MAX_DEVICES + MAX_MISC_FDS); + if (epollfd == -1) + return -1; dir = opendir("/dev/input"); if(dir != 0) { @@ -74,8 +84,15 @@ int ev_init(ev_callback input_cb, void *data) continue; } - ev_fds[ev_count].fd = fd; - ev_fds[ev_count].events = POLLIN; + ev.events = EPOLLIN | EPOLLWAKEUP; + ev.data.ptr = (void *)&ev_fdinfo[ev_count]; + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev)) { + close(fd); + epollctlfail = true; + continue; + } + + ev_fdinfo[ev_count].fd = fd; ev_fdinfo[ev_count].cb = input_cb; ev_fdinfo[ev_count].data = data; ev_count++; @@ -84,59 +101,78 @@ int ev_init(ev_callback input_cb, void *data) } } + if (epollctlfail && !ev_count) { + close(epollfd); + epollfd = -1; + return -1; + } + return 0; } int ev_add_fd(int fd, ev_callback cb, void *data) { + struct epoll_event ev; + int ret; + if (ev_misc_count == MAX_MISC_FDS || cb == NULL) return -1; - ev_fds[ev_count].fd = fd; - ev_fds[ev_count].events = POLLIN; - ev_fdinfo[ev_count].cb = cb; - ev_fdinfo[ev_count].data = data; - ev_count++; - ev_misc_count++; - return 0; + ev.events = EPOLLIN | EPOLLWAKEUP; + ev.data.ptr = (void *)&ev_fdinfo[ev_count]; + ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev); + if (!ret) { + ev_fdinfo[ev_count].fd = fd; + ev_fdinfo[ev_count].cb = cb; + ev_fdinfo[ev_count].data = data; + ev_count++; + ev_misc_count++; + } + + return ret; +} + +int ev_get_epollfd(void) +{ + return epollfd; } void ev_exit(void) { while (ev_count > 0) { - close(ev_fds[--ev_count].fd); + close(ev_fdinfo[--ev_count].fd); } ev_misc_count = 0; ev_dev_count = 0; + close(epollfd); } int ev_wait(int timeout) { - int r; - - r = poll(ev_fds, ev_count, timeout); - if (r <= 0) + npolledevents = epoll_wait(epollfd, polledevents, ev_count, timeout); + if (npolledevents <= 0) return -1; return 0; } void ev_dispatch(void) { - unsigned n; + int n; int ret; - for (n = 0; n < ev_count; n++) { - ev_callback cb = ev_fdinfo[n].cb; - if (cb && (ev_fds[n].revents & ev_fds[n].events)) - cb(ev_fds[n].fd, ev_fds[n].revents, ev_fdinfo[n].data); + for (n = 0; n < npolledevents; n++) { + struct fd_info *fdi = polledevents[n].data.ptr; + ev_callback cb = fdi->cb; + if (cb) + cb(fdi->fd, polledevents[n].events, fdi->data); } } -int ev_get_input(int fd, short revents, struct input_event *ev) +int ev_get_input(int fd, uint32_t epevents, struct input_event *ev) { int r; - if (revents & POLLIN) { + if (epevents & EPOLLIN) { r = read(fd, ev, sizeof(*ev)); if (r == sizeof(*ev)) return 0; @@ -157,11 +193,11 @@ int ev_sync_key_state(ev_set_key_callback set_key_cb, void *data) memset(key_bits, 0, sizeof(key_bits)); memset(ev_bits, 0, sizeof(ev_bits)); - ret = ioctl(ev_fds[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits); + ret = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits); if (ret < 0 || !test_bit(EV_KEY, ev_bits)) continue; - ret = ioctl(ev_fds[i].fd, EVIOCGKEY(sizeof(key_bits)), key_bits); + ret = ioctl(ev_fdinfo[i].fd, EVIOCGKEY(sizeof(key_bits)), key_bits); if (ret < 0) continue; diff --git a/minui/font_10x18.h b/minui/font_10x18.h index 7f96465cc..29d705344 100644 --- a/minui/font_10x18.h +++ b/minui/font_10x18.h @@ -3,7 +3,7 @@ struct { unsigned height; unsigned cwidth; unsigned cheight; - unsigned char rundata[]; + unsigned char rundata[2973]; } font = { .width = 960, .height = 18, diff --git a/minui/graphics.c b/minui/graphics.c index 8f951756d..470e735c9 100644 --- a/minui/graphics.c +++ b/minui/graphics.c @@ -29,44 +29,34 @@ #include <linux/fb.h> #include <linux/kd.h> -#include <pixelflinger/pixelflinger.h> +#include <time.h> #include "font_10x18.h" #include "minui.h" - -#if defined(RECOVERY_BGRA) -#define PIXEL_FORMAT GGL_PIXEL_FORMAT_BGRA_8888 -#define PIXEL_SIZE 4 -#elif defined(RECOVERY_RGBX) -#define PIXEL_FORMAT GGL_PIXEL_FORMAT_RGBX_8888 -#define PIXEL_SIZE 4 -#else -#define PIXEL_FORMAT GGL_PIXEL_FORMAT_RGB_565 -#define PIXEL_SIZE 2 -#endif - -#define NUM_BUFFERS 2 +#include "graphics.h" typedef struct { - GGLSurface* texture; - unsigned cwidth; - unsigned cheight; + GRSurface* texture; + int cwidth; + int cheight; } GRFont; -static GRFont *gr_font = 0; -static GGLContext *gr_context = 0; -static GGLSurface gr_font_texture; -static GGLSurface gr_framebuffer[NUM_BUFFERS]; -static GGLSurface gr_mem_surface; -static unsigned gr_active_fb = 0; -static unsigned double_buffering = 0; +static GRFont* gr_font = NULL; +static minui_backend* gr_backend = NULL; + static int overscan_percent = OVERSCAN_PERCENT; static int overscan_offset_x = 0; static int overscan_offset_y = 0; -static int gr_fb_fd = -1; static int gr_vt_fd = -1; +static unsigned char gr_current_r = 255; +static unsigned char gr_current_g = 255; +static unsigned char gr_current_b = 255; +static unsigned char gr_current_a = 255; + +static GRSurface* gr_draw = NULL; + static struct fb_var_screeninfo vi; static struct fb_fix_screeninfo fi; @@ -167,8 +157,6 @@ static int get_framebuffer(GGLSurface *fb) if (vi.yres * fi.line_length * 2 > fi.smem_len) return fd; - double_buffering = 1; - fb->version = sizeof(*fb); fb->width = vi.xres; fb->height = vi.yres; @@ -233,6 +221,11 @@ void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a gl->color4xv(gl, color); } +static bool outside(int x, int y) +{ + return x < 0 || x >= gr_draw->width || y < 0 || y >= gr_draw->height; +} + int gr_measure(const char *s) { return gr_font->cwidth * strlen(s); @@ -249,58 +242,118 @@ int gr_text(int x, int y, const char *s, ...) return gr_text_impl(x, y, s, 0); } +static void text_blend(unsigned char* src_p, int src_row_bytes, + unsigned char* dst_p, int dst_row_bytes, + int width, int height) +{ + int i, j; + for (j = 0; j < height; ++j) { + unsigned char* sx = src_p; + unsigned char* px = dst_p; + for (i = 0; i < width; ++i) { + unsigned char a = *sx++; + if (gr_current_a < 255) a = ((int)a * gr_current_a) / 255; + if (a == 255) { + *px++ = gr_current_r; + *px++ = gr_current_g; + *px++ = gr_current_b; + px++; + } else if (a > 0) { + *px = (*px * (255-a) + gr_current_r * a) / 255; + ++px; + *px = (*px * (255-a) + gr_current_g * a) / 255; + ++px; + *px = (*px * (255-a) + gr_current_b * a) / 255; + ++px; + ++px; + } else { + px += 4; + } + } + src_p += src_row_bytes; + dst_p += dst_row_bytes; + } +} + + int gr_text_impl(int x, int y, const char *s, int bold) { - GGLContext *gl = gr_context; GRFont *font = gr_font; unsigned off; - if (!font->texture) return x; + if (!font->texture) return; + if (gr_current_a == 0) return; bold = bold && (font->texture->height != font->cheight); x += overscan_offset_x; y += overscan_offset_y; - gl->bindTexture(gl, font->texture); - gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE); - gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); - gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); - gl->enable(gl, GGL_TEXTURE_2D); - while((off = *s++)) { off -= 32; + if (outside(x, y) || outside(x+font->cwidth-1, y+font->cheight-1)) break; if (off < 96) { - gl->texCoord2i(gl, (off * font->cwidth) - x, - (bold ? font->cheight : 0) - y); - gl->recti(gl, x, y, x + font->cwidth, y + font->cheight); + + unsigned char* src_p = font->texture->data + (off * font->cwidth) + + (bold ? font->cheight * font->texture->row_bytes : 0); + unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes; + + text_blend(src_p, font->texture->row_bytes, + dst_p, gr_draw->row_bytes, + font->cwidth, font->cheight); + } x += font->cwidth; } - - return x; } -void gr_texticon(int x, int y, gr_surface icon) { - if (gr_context == NULL || icon == NULL) { +void gr_texticon(int x, int y, GRSurface* icon) { + if (icon == NULL) return; + + if (icon->pixel_bytes != 1) { + printf("gr_texticon: source has wrong format\n"); return; } - GGLContext* gl = gr_context; x += overscan_offset_x; y += overscan_offset_y; - gl->bindTexture(gl, (GGLSurface*) icon); - gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE); - gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); - gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); - gl->enable(gl, GGL_TEXTURE_2D); + if (outside(x, y) || outside(x+icon->width-1, y+icon->height-1)) return; - int w = gr_get_width(icon); - int h = gr_get_height(icon); + unsigned char* src_p = icon->data; + unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes; - gl->texCoord2i(gl, -x, -y); - gl->recti(gl, x, y, x+gr_get_width(icon), y+gr_get_height(icon)); + text_blend(src_p, icon->row_bytes, + dst_p, gr_draw->row_bytes, + icon->width, icon->height); +} + +void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + gr_current_r = r; + gr_current_g = g; + gr_current_b = b; + gr_current_a = a; +} + +void gr_clear() +{ + if (gr_current_r == gr_current_g && + gr_current_r == gr_current_b) { + memset(gr_draw->data, gr_current_r, gr_draw->height * gr_draw->row_bytes); + } else { + int x, y; + unsigned char* px = gr_draw->data; + for (y = 0; y < gr_draw->height; ++y) { + for (x = 0; x < gr_draw->width; ++x) { + *px++ = gr_current_r; + *px++ = gr_current_g; + *px++ = gr_current_b; + px++; + } + px += gr_draw->row_bytes - (gr_draw->width * gr_draw->pixel_bytes); + } + } } void gr_fill(int x1, int y1, int x2, int y2) @@ -311,48 +364,82 @@ void gr_fill(int x1, int y1, int x2, int y2) x2 += overscan_offset_x; y2 += overscan_offset_y; - GGLContext *gl = gr_context; - gl->disable(gl, GGL_TEXTURE_2D); - gl->recti(gl, x1, y1, x2, y2); + if (outside(x1, y1) || outside(x2-1, y2-1)) return; + + unsigned char* p = gr_draw->data + y1 * gr_draw->row_bytes + x1 * gr_draw->pixel_bytes; + if (gr_current_a == 255) { + int x, y; + for (y = y1; y < y2; ++y) { + unsigned char* px = p; + for (x = x1; x < x2; ++x) { + *px++ = gr_current_r; + *px++ = gr_current_g; + *px++ = gr_current_b; + px++; + } + p += gr_draw->row_bytes; + } + } else if (gr_current_a > 0) { + int x, y; + for (y = y1; y < y2; ++y) { + unsigned char* px = p; + for (x = x1; x < x2; ++x) { + *px = (*px * (255-gr_current_a) + gr_current_r * gr_current_a) / 255; + ++px; + *px = (*px * (255-gr_current_a) + gr_current_g * gr_current_a) / 255; + ++px; + *px = (*px * (255-gr_current_a) + gr_current_b * gr_current_a) / 255; + ++px; + ++px; + } + p += gr_draw->row_bytes; + } + } } -void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy) { - if (gr_context == NULL || source == NULL) { +void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) { + if (source == NULL) return; + + if (gr_draw->pixel_bytes != source->pixel_bytes) { + printf("gr_blit: source has wrong format\n"); return; } - GGLContext *gl = gr_context; dx += overscan_offset_x; dy += overscan_offset_y; - gl->bindTexture(gl, (GGLSurface*) source); - gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE); - gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); - gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); - gl->enable(gl, GGL_TEXTURE_2D); - gl->texCoord2i(gl, sx - dx, sy - dy); - gl->recti(gl, dx, dy, dx + w, dy + h); + if (outside(dx, dy) || outside(dx+w-1, dy+h-1)) return; + + unsigned char* src_p = source->data + sy*source->row_bytes + sx*source->pixel_bytes; + unsigned char* dst_p = gr_draw->data + dy*gr_draw->row_bytes + dx*gr_draw->pixel_bytes; + + int i; + for (i = 0; i < h; ++i) { + memcpy(dst_p, src_p, w * source->pixel_bytes); + src_p += source->row_bytes; + dst_p += gr_draw->row_bytes; + } } -unsigned int gr_get_width(gr_surface surface) { +unsigned int gr_get_width(GRSurface* surface) { if (surface == NULL) { return 0; } - return ((GGLSurface*) surface)->width; + return surface->width; } -unsigned int gr_get_height(gr_surface surface) { +unsigned int gr_get_height(GRSurface* surface) { if (surface == NULL) { return 0; } - return ((GGLSurface*) surface)->height; + return surface->height; } static void gr_init_font(void) { gr_font = calloc(sizeof(*gr_font), 1); - int res = res_create_surface("font", (void**)&(gr_font->texture)); + int res = res_create_alpha_surface("font", &(gr_font->texture)); if (res == 0) { // The font image should be a 96x2 array of character images. The // columns are the printable ASCII characters 0x20 - 0x7f. The @@ -366,7 +453,8 @@ static void gr_init_font(void) gr_font->texture = malloc(sizeof(*gr_font->texture)); gr_font->texture->width = font.width; gr_font->texture->height = font.height; - gr_font->texture->stride = font.width; + gr_font->texture->row_bytes = font.width; + gr_font->texture->pixel_bytes = 1; unsigned char* bits = malloc(font.width * font.height); gr_font->texture->data = (void*) bits; @@ -381,17 +469,65 @@ static void gr_init_font(void) gr_font->cwidth = font.cwidth; gr_font->cheight = font.cheight; } +} + +#if 0 +// Exercises many of the gr_*() functions; useful for testing. +static void gr_test() { + GRSurface** images; + int frames; + int result = res_create_multi_surface("icon_installing", &frames, &images); + if (result < 0) { + printf("create surface %d\n", result); + gr_exit(); + return; + } - // interpret the grayscale as alpha - gr_font->texture->format = GGL_PIXEL_FORMAT_A_8; + time_t start = time(NULL); + int x; + for (x = 0; x <= 1200; ++x) { + if (x < 400) { + gr_color(0, 0, 0, 255); + } else { + gr_color(0, (x-400)%128, 0, 255); + } + gr_clear(); + + gr_color(255, 0, 0, 255); + gr_surface frame = images[x%frames]; + gr_blit(frame, 0, 0, frame->width, frame->height, x, 0); + + gr_color(255, 0, 0, 128); + gr_fill(400, 150, 600, 350); + + gr_color(255, 255, 255, 255); + gr_text(500, 225, "hello, world!", 0); + gr_color(255, 255, 0, 128); + gr_text(300+x, 275, "pack my box with five dozen liquor jugs", 1); + + gr_color(0, 0, 255, 128); + gr_fill(gr_draw->width - 200 - x, 300, gr_draw->width - x, 500); + + gr_draw = gr_backend->flip(gr_backend); + } + printf("getting end time\n"); + time_t end = time(NULL); + printf("got end time\n"); + printf("start %ld end %ld\n", (long)start, (long)end); + if (end > start) { + printf("%.2f fps\n", ((double)x) / (end-start)); + } +} +#endif + +void gr_flip() { + gr_draw = gr_backend->flip(gr_backend); } int gr_init(void) { - gglInit(&gr_context); - GGLContext *gl = gr_context; - gr_init_font(); + gr_vt_fd = open("/dev/tty0", O_RDWR | O_SYNC); if (gr_vt_fd < 0) { // This is non-fatal; post-Cupcake kernels don't have tty0. @@ -403,10 +539,12 @@ int gr_init(void) return -1; } - gr_fb_fd = get_framebuffer(gr_framebuffer); - if (gr_fb_fd < 0) { - gr_exit(); - return -1; + gr_backend = open_adf(); + if (gr_backend) { + gr_draw = gr_backend->init(gr_backend); + if (!gr_draw) { + gr_backend->exit(gr_backend); + } } get_memory_surface(&gr_mem_surface); @@ -420,12 +558,19 @@ int gr_init(void) set_active_framebuffer(0); gl->colorBuffer(gl, &gr_mem_surface); - gl->activeTexture(gl, 0); - gl->enable(gl, GGL_BLEND); - gl->blendFunc(gl, GGL_SRC_ALPHA, GGL_ONE_MINUS_SRC_ALPHA); + if (!gr_draw) { + gr_backend = open_fbdev(); + gr_draw = gr_backend->init(gr_backend); + if (gr_draw == NULL) { + return -1; + } + } + + overscan_offset_x = gr_draw->width * overscan_percent / 100; + overscan_offset_y = gr_draw->height * overscan_percent / 100; - gr_fb_blank(true); - gr_fb_blank(false); + gr_flip(); + gr_flip(); if (!alloc_ion_mem(fi.line_length * vi.yres)) allocate_overlay(gr_fb_fd, gr_framebuffer); @@ -443,6 +588,8 @@ void gr_exit(void) free(gr_mem_surface.data); + gr_backend->exit(gr_backend); + ioctl(gr_vt_fd, KDSETMODE, (void*) KD_TEXT); close(gr_vt_fd); gr_vt_fd = -1; @@ -450,17 +597,12 @@ void gr_exit(void) int gr_fb_width(void) { - return gr_framebuffer[0].width - 2*overscan_offset_x; + return gr_draw->width - 2*overscan_offset_x; } int gr_fb_height(void) { - return gr_framebuffer[0].height - 2*overscan_offset_y; -} - -gr_pixel *gr_fb_data(void) -{ - return (unsigned short *) gr_mem_surface.data; + return gr_draw->height - 2*overscan_offset_y; } void gr_fb_blank(bool blank) @@ -478,14 +620,11 @@ void gr_fb_blank(bool blank) write(fd, blank ? "000" : brightness, 3); close(fd); #else - int ret; + gr_backend->blank(gr_backend, blank); + if (blank) free_overlay(gr_fb_fd); - ret = ioctl(gr_fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK); - if (ret < 0) - perror("ioctl(): blank"); - if (!blank) allocate_overlay(gr_fb_fd, gr_framebuffer); #endif diff --git a/minui/graphics.h b/minui/graphics.h new file mode 100644 index 000000000..993e986ee --- /dev/null +++ b/minui/graphics.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _GRAPHICS_H_ +#define _GRAPHICS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdbool.h> +#include "minui.h" + +typedef struct minui_backend { + // Initializes the backend and returns a gr_surface to draw into. + gr_surface (*init)(struct minui_backend*); + + // Causes the current drawing surface (returned by the most recent + // call to flip() or init()) to be displayed, and returns a new + // drawing surface. + gr_surface (*flip)(struct minui_backend*); + + // Blank (or unblank) the screen. + void (*blank)(struct minui_backend*, bool); + + // Device cleanup when drawing is done. + void (*exit)(struct minui_backend*); +} minui_backend; + +minui_backend* open_fbdev(); +minui_backend* open_adf(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/minui/graphics_adf.c b/minui/graphics_adf.c new file mode 100644 index 000000000..ac6d64e9e --- /dev/null +++ b/minui/graphics_adf.c @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <sys/cdefs.h> +#include <sys/mman.h> + +#include <adf/adf.h> + +#include "graphics.h" + +struct adf_surface_pdata { + GRSurface base; + int fd; + __u32 offset; + __u32 pitch; +}; + +struct adf_pdata { + minui_backend base; + int intf_fd; + adf_id_t eng_id; + __u32 format; + + unsigned int current_surface; + unsigned int n_surfaces; + struct adf_surface_pdata surfaces[2]; +}; + +static gr_surface adf_flip(struct minui_backend *backend); +static void adf_blank(struct minui_backend *backend, bool blank); + +static int adf_surface_init(struct adf_pdata *pdata, + struct drm_mode_modeinfo *mode, struct adf_surface_pdata *surf) +{ + memset(surf, 0, sizeof(*surf)); + + surf->fd = adf_interface_simple_buffer_alloc(pdata->intf_fd, mode->hdisplay, + mode->vdisplay, pdata->format, &surf->offset, &surf->pitch); + if (surf->fd < 0) + return surf->fd; + + surf->base.width = mode->hdisplay; + surf->base.height = mode->vdisplay; + surf->base.row_bytes = surf->pitch; + surf->base.pixel_bytes = (pdata->format == DRM_FORMAT_RGB565) ? 2 : 4; + + surf->base.data = mmap(NULL, surf->pitch * surf->base.height, PROT_WRITE, + MAP_SHARED, surf->fd, surf->offset); + if (surf->base.data == MAP_FAILED) { + close(surf->fd); + return -errno; + } + + return 0; +} + +static int adf_interface_init(struct adf_pdata *pdata) +{ + struct adf_interface_data intf_data; + int ret = 0; + int err; + + err = adf_get_interface_data(pdata->intf_fd, &intf_data); + if (err < 0) + return err; + + err = adf_surface_init(pdata, &intf_data.current_mode, &pdata->surfaces[0]); + if (err < 0) { + fprintf(stderr, "allocating surface 0 failed: %s\n", strerror(-err)); + ret = err; + goto done; + } + + err = adf_surface_init(pdata, &intf_data.current_mode, + &pdata->surfaces[1]); + if (err < 0) { + fprintf(stderr, "allocating surface 1 failed: %s\n", strerror(-err)); + memset(&pdata->surfaces[1], 0, sizeof(pdata->surfaces[1])); + pdata->n_surfaces = 1; + } else { + pdata->n_surfaces = 2; + } + +done: + adf_free_interface_data(&intf_data); + return ret; +} + +static int adf_device_init(struct adf_pdata *pdata, struct adf_device *dev) +{ + adf_id_t intf_id; + int intf_fd; + int err; + + err = adf_find_simple_post_configuration(dev, &pdata->format, 1, &intf_id, + &pdata->eng_id); + if (err < 0) + return err; + + err = adf_device_attach(dev, pdata->eng_id, intf_id); + if (err < 0 && err != -EALREADY) + return err; + + pdata->intf_fd = adf_interface_open(dev, intf_id, O_RDWR); + if (pdata->intf_fd < 0) + return pdata->intf_fd; + + err = adf_interface_init(pdata); + if (err < 0) { + close(pdata->intf_fd); + pdata->intf_fd = -1; + } + + return err; +} + +static gr_surface adf_init(minui_backend *backend) +{ + struct adf_pdata *pdata = (struct adf_pdata *)backend; + adf_id_t *dev_ids = NULL; + ssize_t n_dev_ids, i; + gr_surface ret; + +#if defined(RECOVERY_BGRA) + pdata->format = DRM_FORMAT_BGRA8888; +#elif defined(RECOVERY_RGBX) + pdata->format = DRM_FORMAT_RGBX8888; +#else + pdata->format = DRM_FORMAT_RGB565; +#endif + + n_dev_ids = adf_devices(&dev_ids); + if (n_dev_ids == 0) { + return NULL; + } else if (n_dev_ids < 0) { + fprintf(stderr, "enumerating adf devices failed: %s\n", + strerror(-n_dev_ids)); + return NULL; + } + + pdata->intf_fd = -1; + + for (i = 0; i < n_dev_ids && pdata->intf_fd < 0; i++) { + struct adf_device dev; + + int err = adf_device_open(dev_ids[i], O_RDWR, &dev); + if (err < 0) { + fprintf(stderr, "opening adf device %u failed: %s\n", dev_ids[i], + strerror(-err)); + continue; + } + + err = adf_device_init(pdata, &dev); + if (err < 0) + fprintf(stderr, "initializing adf device %u failed: %s\n", + dev_ids[i], strerror(-err)); + + adf_device_close(&dev); + } + + free(dev_ids); + + if (pdata->intf_fd < 0) + return NULL; + + ret = adf_flip(backend); + + adf_blank(backend, true); + adf_blank(backend, false); + + return ret; +} + +static gr_surface adf_flip(struct minui_backend *backend) +{ + struct adf_pdata *pdata = (struct adf_pdata *)backend; + struct adf_surface_pdata *surf = &pdata->surfaces[pdata->current_surface]; + + int fence_fd = adf_interface_simple_post(pdata->intf_fd, pdata->eng_id, + surf->base.width, surf->base.height, pdata->format, surf->fd, + surf->offset, surf->pitch, -1); + if (fence_fd >= 0) + close(fence_fd); + + pdata->current_surface = (pdata->current_surface + 1) % pdata->n_surfaces; + return &pdata->surfaces[pdata->current_surface].base; +} + +static void adf_blank(struct minui_backend *backend, bool blank) +{ + struct adf_pdata *pdata = (struct adf_pdata *)backend; + adf_interface_blank(pdata->intf_fd, + blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON); +} + +static void adf_surface_destroy(struct adf_surface_pdata *surf) +{ + munmap(surf->base.data, surf->pitch * surf->base.height); + close(surf->fd); +} + +static void adf_exit(struct minui_backend *backend) +{ + struct adf_pdata *pdata = (struct adf_pdata *)backend; + unsigned int i; + + for (i = 0; i < pdata->n_surfaces; i++) + adf_surface_destroy(&pdata->surfaces[i]); + if (pdata->intf_fd >= 0) + close(pdata->intf_fd); + free(pdata); +} + +minui_backend *open_adf() +{ + struct adf_pdata *pdata = calloc(1, sizeof(*pdata)); + if (!pdata) { + perror("allocating adf backend failed"); + return NULL; + } + + pdata->base.init = adf_init; + pdata->base.flip = adf_flip; + pdata->base.blank = adf_blank; + pdata->base.exit = adf_exit; + return &pdata->base; +} diff --git a/minui/graphics_fbdev.c b/minui/graphics_fbdev.c new file mode 100644 index 000000000..c0c1bcb1a --- /dev/null +++ b/minui/graphics_fbdev.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> + +#include <fcntl.h> +#include <stdio.h> + +#include <sys/cdefs.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/types.h> + +#include <linux/fb.h> +#include <linux/kd.h> + +#include "minui.h" +#include "graphics.h" + +static gr_surface fbdev_init(minui_backend*); +static gr_surface fbdev_flip(minui_backend*); +static void fbdev_blank(minui_backend*, bool); +static void fbdev_exit(minui_backend*); + +static GRSurface gr_framebuffer[2]; +static bool double_buffered; +static GRSurface* gr_draw = NULL; +static int displayed_buffer; + +static struct fb_var_screeninfo vi; +static int fb_fd = -1; + +static minui_backend my_backend = { + .init = fbdev_init, + .flip = fbdev_flip, + .blank = fbdev_blank, + .exit = fbdev_exit, +}; + +minui_backend* open_fbdev() { + return &my_backend; +} + +static void fbdev_blank(minui_backend* backend __unused, bool blank) +{ + int ret; + + ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK); + if (ret < 0) + perror("ioctl(): blank"); +} + +static void set_displayed_framebuffer(unsigned n) +{ + if (n > 1 || !double_buffered) return; + + vi.yres_virtual = gr_framebuffer[0].height * 2; + vi.yoffset = n * gr_framebuffer[0].height; + vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8; + if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) { + perror("active fb swap failed"); + } + displayed_buffer = n; +} + +static gr_surface fbdev_init(minui_backend* backend) { + int fd; + void *bits; + + struct fb_fix_screeninfo fi; + + fd = open("/dev/graphics/fb0", O_RDWR); + if (fd < 0) { + perror("cannot open fb0"); + return NULL; + } + + if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) { + perror("failed to get fb0 info"); + close(fd); + return NULL; + } + + if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) { + perror("failed to get fb0 info"); + close(fd); + return NULL; + } + + // We print this out for informational purposes only, but + // throughout we assume that the framebuffer device uses an RGBX + // pixel format. This is the case for every development device I + // have access to. For some of those devices (eg, hammerhead aka + // Nexus 5), FBIOGET_VSCREENINFO *reports* that it wants a + // different format (XBGR) but actually produces the correct + // results on the display when you write RGBX. + // + // If you have a device that actually *needs* another pixel format + // (ie, BGRX, or 565), patches welcome... + + printf("fb0 reports (possibly inaccurate):\n" + " vi.bits_per_pixel = %d\n" + " vi.red.offset = %3d .length = %3d\n" + " vi.green.offset = %3d .length = %3d\n" + " vi.blue.offset = %3d .length = %3d\n", + vi.bits_per_pixel, + vi.red.offset, vi.red.length, + vi.green.offset, vi.green.length, + vi.blue.offset, vi.blue.length); + + bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (bits == MAP_FAILED) { + perror("failed to mmap framebuffer"); + close(fd); + return NULL; + } + + memset(bits, 0, fi.smem_len); + + gr_framebuffer[0].width = vi.xres; + gr_framebuffer[0].height = vi.yres; + gr_framebuffer[0].row_bytes = fi.line_length; + gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8; + gr_framebuffer[0].data = bits; + memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes); + + /* check if we can use double buffering */ + if (vi.yres * fi.line_length * 2 <= fi.smem_len) { + double_buffered = true; + + memcpy(gr_framebuffer+1, gr_framebuffer, sizeof(GRSurface)); + gr_framebuffer[1].data = gr_framebuffer[0].data + + gr_framebuffer[0].height * gr_framebuffer[0].row_bytes; + + gr_draw = gr_framebuffer+1; + + } else { + double_buffered = false; + + // Without double-buffering, we allocate RAM for a buffer to + // draw in, and then "flipping" the buffer consists of a + // memcpy from the buffer we allocated to the framebuffer. + + gr_draw = (GRSurface*) malloc(sizeof(GRSurface)); + memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface)); + gr_draw->data = (unsigned char*) malloc(gr_draw->height * gr_draw->row_bytes); + if (!gr_draw->data) { + perror("failed to allocate in-memory surface"); + return NULL; + } + } + + memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes); + fb_fd = fd; + set_displayed_framebuffer(0); + + printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height); + + fbdev_blank(backend, true); + fbdev_blank(backend, false); + + return gr_draw; +} + +static gr_surface fbdev_flip(minui_backend* backend __unused) { + if (double_buffered) { + // Change gr_draw to point to the buffer currently displayed, + // then flip the driver so we're displaying the other buffer + // instead. + gr_draw = gr_framebuffer + displayed_buffer; + set_displayed_framebuffer(1-displayed_buffer); + } else { + // Copy from the in-memory surface to the framebuffer. + +#if defined(RECOVERY_BGRA) + unsigned int idx; + unsigned char* ucfb_vaddr = (unsigned char*)gr_framebuffer[0].data; + unsigned char* ucbuffer_vaddr = (unsigned char*)gr_draw->data; + for (idx = 0 ; idx < (gr_draw->height * gr_draw->row_bytes); idx += 4) { + ucfb_vaddr[idx ] = ucbuffer_vaddr[idx + 2]; + ucfb_vaddr[idx + 1] = ucbuffer_vaddr[idx + 1]; + ucfb_vaddr[idx + 2] = ucbuffer_vaddr[idx ]; + ucfb_vaddr[idx + 3] = ucbuffer_vaddr[idx + 3]; + } +#else + memcpy(gr_framebuffer[0].data, gr_draw->data, + gr_draw->height * gr_draw->row_bytes); +#endif + } + return gr_draw; +} + +static void fbdev_exit(minui_backend* backend __unused) { + close(fb_fd); + fb_fd = -1; + + if (!double_buffered && gr_draw) { + free(gr_draw->data); + free(gr_draw); + } + gr_draw = NULL; +} diff --git a/minui/minui.h b/minui/minui.h index 103318aa7..82282486d 100644 --- a/minui/minui.h +++ b/minui/minui.h @@ -17,24 +17,34 @@ #ifndef _MINUI_H_ #define _MINUI_H_ +#include <sys/types.h> + #include <stdbool.h> #ifdef __cplusplus extern "C" { #endif -typedef void* gr_surface; -typedef unsigned short gr_pixel; +typedef struct { + int width; + int height; + int row_bytes; + int pixel_bytes; + unsigned char* data; +} GRSurface; + +typedef GRSurface* gr_surface; int gr_init(void); void gr_exit(void); int gr_fb_width(void); int gr_fb_height(void); -gr_pixel *gr_fb_data(void); + void gr_flip(void); void gr_fb_blank(bool blank); +void gr_clear(); // clear entire surface to current color void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a); void gr_fill(int x1, int y1, int x2, int y2); @@ -43,7 +53,7 @@ void gr_fill(int x1, int y1, int x2, int y2); int gr_text(int x, int y, const char *s, ...); int gr_text_impl(int x, int y, const char *s, int bold); - void gr_texticon(int x, int y, gr_surface icon); +void gr_texticon(int x, int y, gr_surface icon); int gr_measure(const char *s); void gr_font_size(int *x, int *y); void gr_get_memory_surface(gr_surface); @@ -56,7 +66,7 @@ unsigned int gr_get_height(gr_surface surface); // see http://www.mjmwired.net/kernel/Documentation/input/ for info. struct input_event; -typedef int (*ev_callback)(int fd, short revents, void *data); +typedef int (*ev_callback)(int fd, uint32_t epevents, void *data); typedef int (*ev_set_key_callback)(int code, int value, void *data); int ev_init(ev_callback input_cb, void *data); @@ -71,14 +81,46 @@ int ev_sync_key_state(ev_set_key_callback set_key_cb, void *data); */ int ev_wait(int timeout); -int ev_get_input(int fd, short revents, struct input_event *ev); +int ev_get_input(int fd, uint32_t epevents, struct input_event *ev); void ev_dispatch(void); +int ev_get_epollfd(void); // Resources -// Returns 0 if no error, else negative. -int res_create_surface(const char* name, gr_surface* pSurface); -int res_create_localized_surface(const char* name, gr_surface* pSurface); +// res_create_*_surface() functions return 0 if no error, else +// negative. +// +// A "display" surface is one that is intended to be drawn to the +// screen with gr_blit(). An "alpha" surface is a grayscale image +// interpreted as an alpha mask used to render text in the current +// color (with gr_text() or gr_texticon()). +// +// All these functions load PNG images from "/res/images/${name}.png". + +// Load a single display surface from a PNG image. +int res_create_display_surface(const char* name, gr_surface* pSurface); + +// Load an array of display surfaces from a single PNG image. The PNG +// should have a 'Frames' text chunk whose value is the number of +// frames this image represents. The pixel data itself is interlaced +// by row. +int res_create_multi_display_surface(const char* name, + int* frames, gr_surface** pSurface); + +// Load a single alpha surface from a grayscale PNG image. +int res_create_alpha_surface(const char* name, gr_surface* pSurface); + +// Load part of a grayscale PNG image that is the first match for the +// given locale. The image is expected to be a composite of multiple +// translations of the same text, with special added rows that encode +// the subimages' size and intended locale in the pixel data. See +// development/tools/recovery_l10n for an app that will generate these +// specialized images from Android resources. +int res_create_localized_alpha_surface(const char* name, const char* locale, + gr_surface* pSurface); + +// Free a surface allocated by any of the res_create_*_surface() +// functions. void res_free_surface(gr_surface surface); static inline int res_create_display_surface(const char* name, gr_surface* pSurface) { return res_create_surface(name, pSurface); diff --git a/minui/resources.c b/minui/resources.c index 39f83c76f..92a3d316a 100644 --- a/minui/resources.c +++ b/minui/resources.c @@ -27,8 +27,6 @@ #include <linux/fb.h> #include <linux/kd.h> -#include <pixelflinger/pixelflinger.h> - #include <png.h> #include "minui.h" @@ -39,23 +37,22 @@ char *locale = NULL; extern char* locale; #endif -// libpng gives "undefined reference to 'pow'" errors, and I have no -// idea how to convince the build system to link with -lm. We don't -// need this functionality (it's used for gamma adjustment) so provide -// a dummy implementation to satisfy the linker. -double pow(double x, double y) { - return x * y; +#define SURFACE_DATA_ALIGNMENT 8 + +static gr_surface malloc_surface(size_t data_size) { + unsigned char* temp = malloc(sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT); + if (temp == NULL) return NULL; + gr_surface surface = (gr_surface) temp; + surface->data = temp + sizeof(GRSurface) + + (SURFACE_DATA_ALIGNMENT - (sizeof(GRSurface) % SURFACE_DATA_ALIGNMENT)); + return surface; } -int res_create_surface(const char* name, gr_surface* pSurface) { +static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr, + png_uint_32* width, png_uint_32* height, png_byte* channels) { char resPath[256]; - GGLSurface* surface = NULL; - int result = 0; unsigned char header[8]; - png_structp png_ptr = NULL; - png_infop info_ptr = NULL; - - *pSurface = NULL; + int result = 0; snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name); resPath[sizeof(resPath)-1] = '\0'; @@ -76,114 +73,286 @@ int res_create_surface(const char* name, gr_surface* pSurface) { goto exit; } - png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (!png_ptr) { + *png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!*png_ptr) { result = -4; goto exit; } - info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) { + *info_ptr = png_create_info_struct(*png_ptr); + if (!*info_ptr) { result = -5; goto exit; } - if (setjmp(png_jmpbuf(png_ptr))) { + if (setjmp(png_jmpbuf(*png_ptr))) { result = -6; goto exit; } - png_init_io(png_ptr, fp); - png_set_sig_bytes(png_ptr, sizeof(header)); - png_read_info(png_ptr, info_ptr); - - int color_type = info_ptr->color_type; - int bit_depth = info_ptr->bit_depth; - int channels = info_ptr->channels; - if (!(bit_depth == 8 && - ((channels == 3 && color_type == PNG_COLOR_TYPE_RGB) || - (channels == 4 && color_type == PNG_COLOR_TYPE_RGBA) || - (channels == 1 && (color_type == PNG_COLOR_TYPE_PALETTE || - color_type == PNG_COLOR_TYPE_GRAY))))) { - return -7; + png_init_io(*png_ptr, fp); + png_set_sig_bytes(*png_ptr, sizeof(header)); + png_read_info(*png_ptr, *info_ptr); + + int color_type, bit_depth; + png_get_IHDR(*png_ptr, *info_ptr, width, height, &bit_depth, + &color_type, NULL, NULL, NULL); + + *channels = png_get_channels(*png_ptr, *info_ptr); + + if (bit_depth == 8 && *channels == 3 && color_type == PNG_COLOR_TYPE_RGB) { + // 8-bit RGB images: great, nothing to do. + } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_GRAY) { + // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray. + png_set_expand_gray_1_2_4_to_8(*png_ptr); + } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_PALETTE) { + // paletted images: expand to 8-bit RGB. Note that we DON'T + // currently expand the tRNS chunk (if any) to an alpha + // channel, because minui doesn't support alpha channels in + // general. + png_set_palette_to_rgb(*png_ptr); + *channels = 3; + } else { + fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n", + bit_depth, *channels, color_type); + result = -7; goto exit; } - size_t width = info_ptr->width; - size_t height = info_ptr->height; - size_t stride = (color_type == PNG_COLOR_TYPE_GRAY ? 1 : 4) * width; - size_t pixelSize = stride * height; + return result; - surface = malloc(sizeof(GGLSurface) + pixelSize); - if (surface == NULL) { - result = -8; - goto exit; + exit: + if (result < 0) { + png_destroy_read_struct(png_ptr, info_ptr, NULL); } - unsigned char* pData = (unsigned char*) (surface + 1); - surface->version = sizeof(GGLSurface); + if (fp != NULL) { + fclose(fp); + } + + return result; +} + +// "display" surfaces are transformed into the framebuffer's required +// pixel format (currently only RGBX is supported) at load time, so +// gr_blit() can be nothing more than a memcpy() for each row. The +// next two functions are the only ones that know anything about the +// framebuffer pixel format; they need to be modified if the +// framebuffer format changes (but nothing else should). + +// Allocate and return a gr_surface sufficient for storing an image of +// the indicated size in the framebuffer pixel format. +static gr_surface init_display_surface(png_uint_32 width, png_uint_32 height) { + gr_surface surface; + + surface = malloc_surface(width * height * 4); + if (surface == NULL) return NULL; + surface->width = width; surface->height = height; - surface->stride = width; /* Yes, pixels, not bytes */ - surface->data = pData; - surface->format = (channels == 3) ? GGL_PIXEL_FORMAT_RGBX_8888 : - ((color_type == PNG_COLOR_TYPE_PALETTE ? GGL_PIXEL_FORMAT_RGBA_8888 : GGL_PIXEL_FORMAT_L_8)); - - int alpha = 0; - if (color_type == PNG_COLOR_TYPE_PALETTE) { - png_set_palette_to_rgb(png_ptr); - } - if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { - png_set_tRNS_to_alpha(png_ptr); - alpha = 1; + surface->row_bytes = width * 4; + surface->pixel_bytes = 4; + + return surface; +} + +// Copy 'input_row' to 'output_row', transforming it to the +// framebuffer pixel format. The input format depends on the value of +// 'channels': +// +// 1 - input is 8-bit grayscale +// 3 - input is 24-bit RGB +// 4 - input is 32-bit RGBA/RGBX +// +// 'width' is the number of pixels in the row. +static void transform_rgb_to_draw(unsigned char* input_row, + unsigned char* output_row, + int channels, int width) { + int x; + unsigned char* ip = input_row; + unsigned char* op = output_row; + + switch (channels) { + case 1: + // expand gray level to RGBX + for (x = 0; x < width; ++x) { + *op++ = *ip; + *op++ = *ip; + *op++ = *ip; + *op++ = 0xff; + ip++; + } + break; + + case 3: + // expand RGBA to RGBX + for (x = 0; x < width; ++x) { + *op++ = *ip++; + *op++ = *ip++; + *op++ = *ip++; + *op++ = 0xff; + } + break; + + case 4: + // copy RGBA to RGBX + memcpy(output_row, input_row, width*4); + break; } - if (color_type == PNG_COLOR_TYPE_GRAY) { - alpha = 1; +} + +int res_create_display_surface(const char* name, gr_surface* pSurface) { + gr_surface surface = NULL; + int result = 0; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + png_uint_32 width, height; + png_byte channels; + + *pSurface = NULL; + + result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels); + if (result < 0) return result; + + surface = init_display_surface(width, height); + if (surface == NULL) { + result = -8; + goto exit; } + unsigned char* p_row = malloc(width * 4); unsigned int y; - if (channels == 3 || (channels == 1 && !alpha)) { - for (y = 0; y < height; ++y) { - unsigned char* pRow = pData + y * stride; - png_read_row(png_ptr, pRow, NULL); - - int x; - for(x = width - 1; x >= 0; x--) { - int sx = x * 3; - int dx = x * 4; - unsigned char r = pRow[sx]; - unsigned char g = pRow[sx + 1]; - unsigned char b = pRow[sx + 2]; - unsigned char a = 0xff; - pRow[dx ] = r; // r - pRow[dx + 1] = g; // g - pRow[dx + 2] = b; // b - pRow[dx + 3] = a; + for (y = 0; y < height; ++y) { + png_read_row(png_ptr, p_row, NULL); + transform_rgb_to_draw(p_row, surface->data + y * surface->row_bytes, channels, width); + } + free(p_row); + + *pSurface = surface; + + exit: + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + if (result < 0 && surface != NULL) free(surface); + return result; +} + +int res_create_multi_display_surface(const char* name, int* frames, gr_surface** pSurface) { + gr_surface* surface = NULL; + int result = 0; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + png_uint_32 width, height; + png_byte channels; + int i; + + *pSurface = NULL; + *frames = -1; + + result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels); + if (result < 0) return result; + + *frames = 1; + png_textp text; + int num_text; + if (png_get_text(png_ptr, info_ptr, &text, &num_text)) { + for (i = 0; i < num_text; ++i) { + if (text[i].key && strcmp(text[i].key, "Frames") == 0 && text[i].text) { + *frames = atoi(text[i].text); + break; } } - } else { - for (y = 0; y < height; ++y) { - unsigned char* pRow = pData + y * stride; - png_read_row(png_ptr, pRow, NULL); + printf(" found frames = %d\n", *frames); + } + + if (height % *frames != 0) { + printf("bad height (%d) for frame count (%d)\n", height, *frames); + result = -9; + goto exit; + } + + surface = malloc(*frames * sizeof(gr_surface)); + if (surface == NULL) { + result = -8; + goto exit; + } + for (i = 0; i < *frames; ++i) { + surface[i] = init_display_surface(width, height / *frames); + if (surface[i] == NULL) { + result = -8; + goto exit; } } - *pSurface = (gr_surface) surface; + unsigned char* p_row = malloc(width * 4); + unsigned int y; + for (y = 0; y < height; ++y) { + png_read_row(png_ptr, p_row, NULL); + int frame = y % *frames; + unsigned char* out_row = surface[frame]->data + + (y / *frames) * surface[frame]->row_bytes; + transform_rgb_to_draw(p_row, out_row, channels, width); + } + free(p_row); + + *pSurface = (gr_surface*) surface; exit: png_destroy_read_struct(&png_ptr, &info_ptr, NULL); - if (fp != NULL) { - fclose(fp); - } if (result < 0) { if (surface) { + for (i = 0; i < *frames; ++i) { + if (surface[i]) free(surface[i]); + } free(surface); } } return result; } -static int matches_locale(const char* loc) { +int res_create_alpha_surface(const char* name, gr_surface* pSurface) { + gr_surface surface = NULL; + int result = 0; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + png_uint_32 width, height; + png_byte channels; + + *pSurface = NULL; + + result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels); + if (result < 0) return result; + + if (channels != 1) { + result = -7; + goto exit; + } + + surface = malloc_surface(width * height); + if (surface == NULL) { + result = -8; + goto exit; + } + surface->width = width; + surface->height = height; + surface->row_bytes = width; + surface->pixel_bytes = 1; + + unsigned char* p_row; + unsigned int y; + for (y = 0; y < height; ++y) { + p_row = surface->data + y * surface->row_bytes; + png_read_row(png_ptr, p_row, NULL); + } + + *pSurface = surface; + + exit: + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + if (result < 0 && surface != NULL) free(surface); + return result; +} + +static int matches_locale(const char* loc, const char* locale) { if (locale == NULL) return 0; if (strcmp(loc, locale) == 0) return 1; @@ -200,100 +369,61 @@ static int matches_locale(const char* loc) { return (strncmp(locale, loc, i) == 0 && locale[i] == '_'); } -int res_create_localized_surface(const char* name, gr_surface* pSurface) { - char resPath[256]; - GGLSurface* surface = NULL; +int res_create_localized_alpha_surface(const char* name, + const char* locale, + gr_surface* pSurface) { + gr_surface surface = NULL; int result = 0; - unsigned char header[8]; png_structp png_ptr = NULL; png_infop info_ptr = NULL; + png_uint_32 width, height; + png_byte channels; *pSurface = NULL; - snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name); - resPath[sizeof(resPath)-1] = '\0'; - FILE* fp = fopen(resPath, "rb"); - if (fp == NULL) { - result = -1; - goto exit; - } - - size_t bytesRead = fread(header, 1, sizeof(header), fp); - if (bytesRead != sizeof(header)) { - result = -2; + if (locale == NULL) { + surface = malloc_surface(0); + surface->width = 0; + surface->height = 0; + surface->row_bytes = 0; + surface->pixel_bytes = 1; goto exit; } - if (png_sig_cmp(header, 0, sizeof(header))) { - result = -3; - goto exit; - } + result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels); + if (result < 0) return result; - png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (!png_ptr) { - result = -4; - goto exit; - } - - info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) { - result = -5; - goto exit; - } - - if (setjmp(png_jmpbuf(png_ptr))) { - result = -6; - goto exit; - } - - png_init_io(png_ptr, fp); - png_set_sig_bytes(png_ptr, sizeof(header)); - png_read_info(png_ptr, info_ptr); - - size_t width = info_ptr->width; - size_t height = info_ptr->height; - size_t stride = 4 * width; - - int color_type = info_ptr->color_type; - int bit_depth = info_ptr->bit_depth; - int channels = info_ptr->channels; - - if (!(bit_depth == 8 && - (channels == 1 && color_type == PNG_COLOR_TYPE_GRAY))) { - return -7; + if (channels != 1) { + result = -7; goto exit; } unsigned char* row = malloc(width); - int y; + png_uint_32 y; for (y = 0; y < height; ++y) { png_read_row(png_ptr, row, NULL); int w = (row[1] << 8) | row[0]; int h = (row[3] << 8) | row[2]; int len = row[4]; - char* loc = row+5; + char* loc = (char*)row+5; - if (y+1+h >= height || matches_locale(loc)) { + if (y+1+h >= height || matches_locale(loc, locale)) { printf(" %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y); - surface = malloc(sizeof(GGLSurface)); + surface = malloc_surface(w*h); if (surface == NULL) { result = -8; goto exit; } - unsigned char* pData = malloc(w*h); - - surface->version = sizeof(GGLSurface); surface->width = w; surface->height = h; - surface->stride = w; /* Yes, pixels, not bytes */ - surface->data = pData; - surface->format = GGL_PIXEL_FORMAT_A_8; + surface->row_bytes = w; + surface->pixel_bytes = 1; int i; for (i = 0; i < h; ++i, ++y) { png_read_row(png_ptr, row, NULL); - memcpy(pData + i*w, row, w); + memcpy(surface->data + i*w, row, w); } *pSurface = (gr_surface) surface; @@ -308,21 +438,10 @@ int res_create_localized_surface(const char* name, gr_surface* pSurface) { exit: png_destroy_read_struct(&png_ptr, &info_ptr, NULL); - - if (fp != NULL) { - fclose(fp); - } - if (result < 0) { - if (surface) { - free(surface); - } - } + if (result < 0 && surface != NULL) free(surface); return result; } void res_free_surface(gr_surface surface) { - GGLSurface* pSurface = (GGLSurface*) surface; - if (pSurface) { - free(pSurface); - } + free(surface); } diff --git a/minzip/DirUtil.c b/minzip/DirUtil.c index 8dd5da1da..fe2c880ac 100644 --- a/minzip/DirUtil.c +++ b/minzip/DirUtil.c @@ -234,61 +234,3 @@ dirUnlinkHierarchy(const char *path) /* delete target directory */ return rmdir(path); } - -int -dirSetHierarchyPermissions(const char *path, - int uid, int gid, int dirMode, int fileMode) -{ - struct stat st; - if (lstat(path, &st)) { - return -1; - } - - /* ignore symlinks */ - if (S_ISLNK(st.st_mode)) { - return 0; - } - - /* directories and files get different permissions */ - if (chown(path, uid, gid) || - chmod(path, S_ISDIR(st.st_mode) ? dirMode : fileMode)) { - return -1; - } - - /* recurse over directory components */ - if (S_ISDIR(st.st_mode)) { - DIR *dir = opendir(path); - if (dir == NULL) { - return -1; - } - - errno = 0; - const struct dirent *de; - while (errno == 0 && (de = readdir(dir)) != NULL) { - if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) { - continue; - } - - char dn[PATH_MAX]; - snprintf(dn, sizeof(dn), "%s/%s", path, de->d_name); - if (!dirSetHierarchyPermissions(dn, uid, gid, dirMode, fileMode)) { - errno = 0; - } else if (errno == 0) { - errno = -1; - } - } - - if (errno != 0) { - int save = errno; - closedir(dir); - errno = save; - return -1; - } - - if (closedir(dir)) { - return -1; - } - } - - return 0; -} diff --git a/minzip/DirUtil.h b/minzip/DirUtil.h index a5cfa761b..85a00128b 100644 --- a/minzip/DirUtil.h +++ b/minzip/DirUtil.h @@ -48,14 +48,6 @@ int dirCreateHierarchy(const char *path, int mode, */ int dirUnlinkHierarchy(const char *path); -/* chown -R <uid>:<gid> <path> - * chmod -R <mode> <path> - * - * Sets directories to <dirMode> and files to <fileMode>. Skips symlinks. - */ -int dirSetHierarchyPermissions(const char *path, - int uid, int gid, int dirMode, int fileMode); - #ifdef __cplusplus } #endif diff --git a/minzip/SysUtil.c b/minzip/SysUtil.c index 31c76d6d4..ac6f5c33f 100644 --- a/minzip/SysUtil.c +++ b/minzip/SysUtil.c @@ -8,42 +8,17 @@ #include <unistd.h> #include <string.h> #include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> #include <limits.h> #include <errno.h> #include <assert.h> -#define LOG_TAG "minzip" +#define LOG_TAG "sysutil" #include "Log.h" #include "SysUtil.h" -/* - * Having trouble finding a portable way to get this. sysconf(_SC_PAGE_SIZE) - * seems appropriate, but we don't have that on the device. Some systems - * have getpagesize(2), though the linux man page has some odd cautions. - */ -#define DEFAULT_PAGE_SIZE 4096 - - -/* - * Create an anonymous shared memory segment large enough to hold "length" - * bytes. The actual segment may be larger because mmap() operates on - * page boundaries (usually 4K). - */ -static void* sysCreateAnonShmem(size_t length) -{ - void* ptr; - - ptr = mmap(NULL, length, PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_ANON, -1, 0); - if (ptr == MAP_FAILED) { - LOGW("mmap(%d, RW, SHARED|ANON) failed: %s\n", (int) length, - strerror(errno)); - return NULL; - } - - return ptr; -} - static int getFileStartAndLength(int fd, off_t *start_, size_t *length_) { off_t start, end; @@ -74,48 +49,13 @@ static int getFileStartAndLength(int fd, off_t *start_, size_t *length_) } /* - * Pull the contents of a file into an new shared memory segment. We grab - * everything from fd's current offset on. - * - * We need to know the length ahead of time so we can allocate a segment - * of sufficient size. - */ -int sysLoadFileInShmem(int fd, MemMapping* pMap) -{ - off_t start; - size_t length, actual; - void* memPtr; - - assert(pMap != NULL); - - if (getFileStartAndLength(fd, &start, &length) < 0) - return -1; - - memPtr = sysCreateAnonShmem(length); - if (memPtr == NULL) - return -1; - - pMap->baseAddr = pMap->addr = memPtr; - pMap->baseLength = pMap->length = length; - - actual = TEMP_FAILURE_RETRY(read(fd, memPtr, length)); - if (actual != length) { - LOGE("only read %d of %d bytes\n", (int) actual, (int) length); - sysReleaseShmem(pMap); - return -1; - } - - return 0; -} - -/* - * Map a file (from fd's current offset) into a shared, read-only memory + * Map a file (from fd's current offset) into a private, read-only memory * segment. The file offset must be a multiple of the page size. * * On success, returns 0 and fills out "pMap". On failure, returns a nonzero * value and does not disturb "pMap". */ -int sysMapFileInShmem(int fd, MemMapping* pMap) +static int sysMapFD(int fd, MemMapping* pMap) { off_t start; size_t length; @@ -126,87 +66,148 @@ int sysMapFileInShmem(int fd, MemMapping* pMap) if (getFileStartAndLength(fd, &start, &length) < 0) return -1; - memPtr = mmap(NULL, length, PROT_READ, MAP_FILE | MAP_SHARED, fd, start); + memPtr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, start); if (memPtr == MAP_FAILED) { - LOGW("mmap(%d, R, FILE|SHARED, %d, %d) failed: %s\n", (int) length, + LOGW("mmap(%d, R, PRIVATE, %d, %d) failed: %s\n", (int) length, fd, (int) start, strerror(errno)); return -1; } - pMap->baseAddr = pMap->addr = memPtr; - pMap->baseLength = pMap->length = length; + pMap->addr = memPtr; + pMap->length = length; + pMap->range_count = 1; + pMap->ranges = malloc(sizeof(MappedRange)); + pMap->ranges[0].addr = memPtr; + pMap->ranges[0].length = length; return 0; } -/* - * Map part of a file (from fd's current offset) into a shared, read-only - * memory segment. - * - * On success, returns 0 and fills out "pMap". On failure, returns a nonzero - * value and does not disturb "pMap". - */ -int sysMapFileSegmentInShmem(int fd, off_t start, long length, - MemMapping* pMap) +static int sysMapBlockFile(FILE* mapf, MemMapping* pMap) { - off_t dummy; - size_t fileLength, actualLength; - off_t actualStart; - int adjust; - void* memPtr; - - assert(pMap != NULL); + char block_dev[PATH_MAX+1]; + size_t size; + unsigned int blksize; + unsigned int blocks; + unsigned int range_count; + unsigned int i; + + if (fgets(block_dev, sizeof(block_dev), mapf) == NULL) { + LOGW("failed to read block device from header\n"); + return -1; + } + for (i = 0; i < sizeof(block_dev); ++i) { + if (block_dev[i] == '\n') { + block_dev[i] = 0; + break; + } + } - if (getFileStartAndLength(fd, &dummy, &fileLength) < 0) + if (fscanf(mapf, "%zu %u\n%u\n", &size, &blksize, &range_count) != 3) { + LOGW("failed to parse block map header\n"); return -1; + } - if (start + length > (long)fileLength) { - LOGW("bad segment: st=%d len=%ld flen=%d\n", - (int) start, length, (int) fileLength); + blocks = ((size-1) / blksize) + 1; + + pMap->range_count = range_count; + pMap->ranges = malloc(range_count * sizeof(MappedRange)); + memset(pMap->ranges, 0, range_count * sizeof(MappedRange)); + + // Reserve enough contiguous address space for the whole file. + unsigned char* reserve; + reserve = mmap64(NULL, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (reserve == MAP_FAILED) { + LOGW("failed to reserve address space: %s\n", strerror(errno)); return -1; } - /* adjust to be page-aligned */ - adjust = start % DEFAULT_PAGE_SIZE; - actualStart = start - adjust; - actualLength = length + adjust; + pMap->ranges[range_count-1].addr = reserve; + pMap->ranges[range_count-1].length = blocks * blksize; - memPtr = mmap(NULL, actualLength, PROT_READ, MAP_FILE | MAP_SHARED, - fd, actualStart); - if (memPtr == MAP_FAILED) { - LOGW("mmap(%d, R, FILE|SHARED, %d, %d) failed: %s\n", - (int) actualLength, fd, (int) actualStart, strerror(errno)); + int fd = open(block_dev, O_RDONLY); + if (fd < 0) { + LOGW("failed to open block device %s: %s\n", block_dev, strerror(errno)); return -1; } - pMap->baseAddr = memPtr; - pMap->baseLength = actualLength; - pMap->addr = (char*)memPtr + adjust; - pMap->length = length; + unsigned char* next = reserve; + for (i = 0; i < range_count; ++i) { + int start, end; + if (fscanf(mapf, "%d %d\n", &start, &end) != 2) { + LOGW("failed to parse range %d in block map\n", i); + return -1; + } + + void* addr = mmap64(next, (end-start)*blksize, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, ((off64_t)start)*blksize); + if (addr == MAP_FAILED) { + LOGW("failed to map block %d: %s\n", i, strerror(errno)); + return -1; + } + pMap->ranges[i].addr = addr; + pMap->ranges[i].length = (end-start)*blksize; + + next += pMap->ranges[i].length; + } + + pMap->addr = reserve; + pMap->length = size; - LOGVV("mmap seg (st=%d ln=%d): bp=%p bl=%d ad=%p ln=%d\n", - (int) start, (int) length, - pMap->baseAddr, (int) pMap->baseLength, - pMap->addr, (int) pMap->length); + LOGI("mmapped %d ranges\n", range_count); return 0; } +int sysMapFile(const char* fn, MemMapping* pMap) +{ + memset(pMap, 0, sizeof(*pMap)); + + if (fn && fn[0] == '@') { + // A map of blocks + FILE* mapf = fopen(fn+1, "r"); + if (mapf == NULL) { + LOGV("Unable to open '%s': %s\n", fn+1, strerror(errno)); + return -1; + } + + if (sysMapBlockFile(mapf, pMap) != 0) { + LOGW("Map of '%s' failed\n", fn); + return -1; + } + + fclose(mapf); + } else { + // This is a regular file. + int fd = open(fn, O_RDONLY, 0); + if (fd < 0) { + LOGE("Unable to open '%s': %s\n", fn, strerror(errno)); + return -1; + } + + if (sysMapFD(fd, pMap) != 0) { + LOGE("Map of '%s' failed\n", fn); + close(fd); + return -1; + } + + close(fd); + } + return 0; +} + /* * Release a memory mapping. */ -void sysReleaseShmem(MemMapping* pMap) +void sysReleaseMap(MemMapping* pMap) { - if (pMap->baseAddr == NULL && pMap->baseLength == 0) - return; - - if (munmap(pMap->baseAddr, pMap->baseLength) < 0) { - LOGW("munmap(%p, %d) failed: %s\n", - pMap->baseAddr, (int)pMap->baseLength, strerror(errno)); - } else { - LOGV("munmap(%p, %d) succeeded\n", pMap->baseAddr, pMap->baseLength); - pMap->baseAddr = NULL; - pMap->baseLength = 0; + int i; + for (i = 0; i < pMap->range_count; ++i) { + if (munmap(pMap->ranges[i].addr, pMap->ranges[i].length) < 0) { + LOGW("munmap(%p, %d) failed: %s\n", + pMap->ranges[i].addr, (int)pMap->ranges[i].length, strerror(errno)); + } } + free(pMap->ranges); + pMap->ranges = NULL; + pMap->range_count = 0; } - diff --git a/minzip/SysUtil.h b/minzip/SysUtil.h index ec3a4bcfb..7adff1e54 100644 --- a/minzip/SysUtil.h +++ b/minzip/SysUtil.h @@ -6,56 +6,47 @@ #ifndef _MINZIP_SYSUTIL #define _MINZIP_SYSUTIL -#include "inline_magic.h" - +#include <stdio.h> #include <sys/types.h> +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MappedRange { + void* addr; + size_t length; +} MappedRange; + /* * Use this to keep track of mapped segments. */ typedef struct MemMapping { - void* addr; /* start of data */ - size_t length; /* length of data */ + unsigned char* addr; /* start of data */ + size_t length; /* length of data */ - void* baseAddr; /* page-aligned base address */ - size_t baseLength; /* length of mapping */ + int range_count; + MappedRange* ranges; } MemMapping; -/* copy a map */ -INLINE void sysCopyMap(MemMapping* dst, const MemMapping* src) { - *dst = *src; -} - -/* - * Load a file into a new shared memory segment. All data from the current - * offset to the end of the file is pulled in. - * - * The segment is read-write, allowing VM fixups. (It should be modified - * to support .gz/.zip compressed data.) - * - * On success, "pMap" is filled in, and zero is returned. - */ -int sysLoadFileInShmem(int fd, MemMapping* pMap); - /* - * Map a file (from fd's current offset) into a shared, - * read-only memory segment. + * Map a file into a private, read-only memory segment. If 'fn' + * begins with an '@' character, it is a map of blocks to be mapped, + * otherwise it is treated as an ordinary file. * * On success, "pMap" is filled in, and zero is returned. */ -int sysMapFileInShmem(int fd, MemMapping* pMap); - -/* - * Like sysMapFileInShmem, but on only part of a file. - */ -int sysMapFileSegmentInShmem(int fd, off_t start, long length, - MemMapping* pMap); +int sysMapFile(const char* fn, MemMapping* pMap); /* * Release the pages associated with a shared memory segment. * * This does not free "pMap"; it just releases the memory. */ -void sysReleaseShmem(MemMapping* pMap); +void sysReleaseMap(MemMapping* pMap); + +#ifdef __cplusplus +} +#endif #endif /*_MINZIP_SYSUTIL*/ diff --git a/minzip/Zip.c b/minzip/Zip.c index 439e5d9cd..70aff00cd 100644 --- a/minzip/Zip.c +++ b/minzip/Zip.c @@ -184,7 +184,7 @@ static int validFilename(const char *fileName, unsigned int fileNameLen) * * Returns "true" on success. */ -static bool parseZipArchive(ZipArchive* pArchive, const MemMapping* pMap) +static bool parseZipArchive(ZipArchive* pArchive) { bool result = false; const unsigned char* ptr; @@ -196,7 +196,7 @@ static bool parseZipArchive(ZipArchive* pArchive, const MemMapping* pMap) * signature for the first file (LOCSIG) or, if the archive doesn't * have any files in it, the end-of-central-directory signature (ENDSIG). */ - val = get4LE(pMap->addr); + val = get4LE(pArchive->addr); if (val == ENDSIG) { LOGI("Found Zip archive, but it looks empty\n"); goto bail; @@ -209,14 +209,14 @@ static bool parseZipArchive(ZipArchive* pArchive, const MemMapping* pMap) * Find the EOCD. We'll find it immediately unless they have a file * comment. */ - ptr = pMap->addr + pMap->length - ENDHDR; + ptr = pArchive->addr + pArchive->length - ENDHDR; - while (ptr >= (const unsigned char*) pMap->addr) { + while (ptr >= (const unsigned char*) pArchive->addr) { if (*ptr == (ENDSIG & 0xff) && get4LE(ptr) == ENDSIG) break; ptr--; } - if (ptr < (const unsigned char*) pMap->addr) { + if (ptr < (const unsigned char*) pArchive->addr) { LOGI("Could not find end-of-central-directory in Zip\n"); goto bail; } @@ -230,9 +230,9 @@ static bool parseZipArchive(ZipArchive* pArchive, const MemMapping* pMap) cdOffset = get4LE(ptr + ENDOFF); LOGVV("numEntries=%d cdOffset=%d\n", numEntries, cdOffset); - if (numEntries == 0 || cdOffset >= pMap->length) { + if (numEntries == 0 || cdOffset >= pArchive->length) { LOGW("Invalid entries=%d offset=%d (len=%zd)\n", - numEntries, cdOffset, pMap->length); + numEntries, cdOffset, pArchive->length); goto bail; } @@ -245,14 +245,14 @@ static bool parseZipArchive(ZipArchive* pArchive, const MemMapping* pMap) if (pArchive->pEntries == NULL || pArchive->pHash == NULL) goto bail; - ptr = pMap->addr + cdOffset; + ptr = pArchive->addr + cdOffset; for (i = 0; i < numEntries; i++) { ZipEntry* pEntry; unsigned int fileNameLen, extraLen, commentLen, localHdrOffset; const unsigned char* localHdr; const char *fileName; - if (ptr + CENHDR > (const unsigned char*)pMap->addr + pMap->length) { + if (ptr + CENHDR > (const unsigned char*)pArchive->addr + pArchive->length) { LOGW("Ran off the end (at %d)\n", i); goto bail; } @@ -266,7 +266,7 @@ static bool parseZipArchive(ZipArchive* pArchive, const MemMapping* pMap) extraLen = get2LE(ptr + CENEXT); commentLen = get2LE(ptr + CENCOM); fileName = (const char*)ptr + CENHDR; - if (fileName + fileNameLen > (const char*)pMap->addr + pMap->length) { + if (fileName + fileNameLen > (const char*)pArchive->addr + pArchive->length) { LOGW("Filename ran off the end (at %d)\n", i); goto bail; } @@ -352,15 +352,15 @@ static bool parseZipArchive(ZipArchive* pArchive, const MemMapping* pMap) } pEntry->externalFileAttributes = get4LE(ptr + CENATX); - // Perform pMap->addr + localHdrOffset, ensuring that it won't + // Perform pArchive->addr + localHdrOffset, ensuring that it won't // overflow. This is needed because localHdrOffset is untrusted. - if (!safe_add((uintptr_t *)&localHdr, (uintptr_t)pMap->addr, + if (!safe_add((uintptr_t *)&localHdr, (uintptr_t)pArchive->addr, (uintptr_t)localHdrOffset)) { LOGW("Integer overflow adding in parseZipArchive\n"); goto bail; } if ((uintptr_t)localHdr + LOCHDR > - (uintptr_t)pMap->addr + pMap->length) { + (uintptr_t)pArchive->addr + pArchive->length) { LOGW("Bad offset to local header: %d (at %d)\n", localHdrOffset, i); goto bail; } @@ -374,7 +374,7 @@ static bool parseZipArchive(ZipArchive* pArchive, const MemMapping* pMap) LOGW("Integer overflow adding in parseZipArchive\n"); goto bail; } - if ((size_t)pEntry->offset + pEntry->compLen > pMap->length) { + if ((size_t)pEntry->offset + pEntry->compLen > pArchive->length) { LOGW("Data ran off the end (at %d)\n", i); goto bail; } @@ -427,50 +427,30 @@ bail: * * On success, we fill out the contents of "pArchive". */ -int mzOpenZipArchive(const char* fileName, ZipArchive* pArchive) +int mzOpenZipArchive(unsigned char* addr, size_t length, ZipArchive* pArchive) { - MemMapping map; int err; - LOGV("Opening archive '%s' %p\n", fileName, pArchive); - - map.addr = NULL; - memset(pArchive, 0, sizeof(*pArchive)); - - pArchive->fd = open(fileName, O_RDONLY, 0); - if (pArchive->fd < 0) { - err = errno ? errno : -1; - LOGV("Unable to open '%s': %s\n", fileName, strerror(err)); - goto bail; - } - - if (sysMapFileInShmem(pArchive->fd, &map) != 0) { - err = -1; - LOGW("Map of '%s' failed\n", fileName); - goto bail; - } - - if (map.length < ENDHDR) { + if (length < ENDHDR) { err = -1; LOGV("File '%s' too small to be zip (%zd)\n", fileName, map.length); goto bail; } - if (!parseZipArchive(pArchive, &map)) { + pArchive->addr = addr; + pArchive->length = length; + + if (!parseZipArchive(pArchive)) { err = -1; LOGV("Parsing '%s' failed\n", fileName); goto bail; } err = 0; - sysCopyMap(&pArchive->map, &map); - map.addr = NULL; bail: if (err != 0) mzCloseZipArchive(pArchive); - if (map.addr != NULL) - sysReleaseShmem(&map); return err; } @@ -483,16 +463,10 @@ void mzCloseZipArchive(ZipArchive* pArchive) { LOGV("Closing archive %p\n", pArchive); - if (pArchive->fd >= 0) - close(pArchive->fd); - if (pArchive->map.addr != NULL) - sysReleaseShmem(&pArchive->map); - free(pArchive->pEntries); mzHashTableFree(pArchive->pHash); - pArchive->fd = -1; pArchive->pHash = NULL; pArchive->pEntries = NULL; } @@ -528,29 +502,7 @@ static bool processStoredEntry(const ZipArchive *pArchive, const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, void *cookie) { - size_t bytesLeft = pEntry->compLen; - while (bytesLeft > 0) { - unsigned char buf[32 * 1024]; - ssize_t n; - size_t count; - bool ret; - - count = bytesLeft; - if (count > sizeof(buf)) { - count = sizeof(buf); - } - n = read(pArchive->fd, buf, count); - if (n < 0 || (size_t)n != count) { - LOGE("Can't read %zu bytes from zip file: %ld\n", count, n); - return false; - } - ret = processFunction(buf, n, cookie); - if (!ret) { - return false; - } - bytesLeft -= count; - } - return true; + return processFunction(pArchive->addr + pEntry->offset, pEntry->uncompLen, cookie); } static bool processDeflatedEntry(const ZipArchive *pArchive, @@ -573,8 +525,8 @@ static bool processDeflatedEntry(const ZipArchive *pArchive, zstream.zalloc = Z_NULL; zstream.zfree = Z_NULL; zstream.opaque = Z_NULL; - zstream.next_in = NULL; - zstream.avail_in = 0; + zstream.next_in = pArchive->addr + pEntry->offset; + zstream.avail_in = pEntry->compLen; zstream.next_out = (Bytef*) procBuf; zstream.avail_out = sizeof(procBuf); zstream.data_type = Z_UNKNOWN; @@ -598,25 +550,6 @@ static bool processDeflatedEntry(const ZipArchive *pArchive, * Loop while we have data. */ do { - /* read as much as we can */ - if (zstream.avail_in == 0) { - long getSize = (compRemaining > (long)sizeof(readBuf)) ? - (long)sizeof(readBuf) : compRemaining; - LOGVV("+++ reading %ld bytes (%ld left)\n", - getSize, compRemaining); - - int cc = read(pArchive->fd, readBuf, getSize); - if (cc != (int) getSize) { - LOGW("inflate read failed (%d vs %ld)\n", cc, getSize); - goto z_bail; - } - - compRemaining -= getSize; - - zstream.next_in = readBuf; - zstream.avail_in = getSize; - } - /* uncompress the data */ zerr = inflate(&zstream, Z_NO_FLUSH); if (zerr != Z_OK && zerr != Z_STREAM_END) { @@ -676,12 +609,6 @@ bool mzProcessZipEntryContents(const ZipArchive *pArchive, bool ret = false; off_t oldOff; - /* save current offset */ - oldOff = lseek(pArchive->fd, 0, SEEK_CUR); - - /* Seek to the beginning of the entry's compressed data. */ - lseek(pArchive->fd, pEntry->offset, SEEK_SET); - switch (pEntry->compression) { case STORED: ret = processStoredEntry(pArchive, pEntry, processFunction, cookie); @@ -695,8 +622,6 @@ bool mzProcessZipEntryContents(const ZipArchive *pArchive, break; } - /* restore file offset */ - lseek(pArchive->fd, oldOff, SEEK_SET); return ret; } @@ -772,13 +697,15 @@ bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry, static bool writeProcessFunction(const unsigned char *data, int dataLen, void *cookie) { - int fd = (int)cookie; - + int fd = (int)(intptr_t)cookie; + if (dataLen == 0) { + return true; + } ssize_t soFar = 0; while (true) { ssize_t n = write(fd, data+soFar, dataLen-soFar); if (n <= 0) { - LOGE("Error writing %ld bytes from zip file from %p: %s\n", + LOGE("Error writing %zd bytes from zip file from %p: %s\n", dataLen-soFar, data+soFar, strerror(errno)); if (errno != EINTR) { return false; @@ -787,7 +714,7 @@ static bool writeProcessFunction(const unsigned char *data, int dataLen, soFar += n; if (soFar == dataLen) return true; if (soFar > dataLen) { - LOGE("write overrun? (%ld bytes instead of %d)\n", + LOGE("write overrun? (%zd bytes instead of %d)\n", soFar, dataLen); return false; } @@ -802,7 +729,7 @@ bool mzExtractZipEntryToFile(const ZipArchive *pArchive, const ZipEntry *pEntry, int fd) { bool ret = mzProcessZipEntryContents(pArchive, pEntry, writeProcessFunction, - (void*)fd); + (void*)(intptr_t)fd); if (!ret) { LOGE("Can't extract entry to file.\n"); return false; @@ -810,6 +737,23 @@ bool mzExtractZipEntryToFile(const ZipArchive *pArchive, return true; } +/* + * Obtain a pointer to the in-memory representation of a stored entry. + */ +bool mzGetStoredEntry(const ZipArchive *pArchive, + const ZipEntry *pEntry, unsigned char **addr, size_t *length) +{ + if (pEntry->compression != STORED) { + LOGE("Can't getStoredEntry for '%s'; not stored\n", + pEntry->fileName); + return false; + } + + *addr = pArchive->addr + pEntry->offset; + *length = pEntry->uncompLen; + return true; +} + typedef struct { unsigned char* buffer; long len; @@ -1123,7 +1067,8 @@ bool mzExtractRecursive(const ZipArchive *pArchive, setfscreatecon(secontext); } - int fd = creat(targetFile, UNZIP_FILEMODE); + int fd = open(targetFile, O_CREAT|O_WRONLY|O_TRUNC|O_SYNC + , UNZIP_FILEMODE); if (secontext) { freecon(secontext); @@ -1138,7 +1083,12 @@ bool mzExtractRecursive(const ZipArchive *pArchive, } bool ok = mzExtractZipEntryToFile(pArchive, pEntry, fd); - close(fd); + if (ok) { + ok = (fsync(fd) == 0); + } + if (close(fd) != 0) { + ok = false; + } if (!ok) { LOGE("Error extracting \"%s\"\n", targetFile); ok = false; diff --git a/minzip/Zip.h b/minzip/Zip.h index c94282827..2054b38a4 100644 --- a/minzip/Zip.h +++ b/minzip/Zip.h @@ -46,11 +46,11 @@ typedef struct ZipEntry { * One Zip archive. Treat as opaque. */ typedef struct ZipArchive { - int fd; - unsigned int numEntries; - ZipEntry* pEntries; - HashTable* pHash; // maps file name to ZipEntry - MemMapping map; + unsigned int numEntries; + ZipEntry* pEntries; + HashTable* pHash; // maps file name to ZipEntry + unsigned char* addr; + size_t length; } ZipArchive; /* @@ -68,7 +68,7 @@ typedef struct { * On success, returns 0 and populates "pArchive". Returns nonzero errno * value on failure. */ -int mzOpenZipArchive(const char* fileName, ZipArchive* pArchive); +int mzOpenZipArchive(unsigned char* addr, size_t length, ZipArchive* pArchive); /* * Close archive, releasing resources associated with it. @@ -183,6 +183,17 @@ bool mzExtractZipEntryToBuffer(const ZipArchive *pArchive, const ZipEntry *pEntry, unsigned char* buffer); /* + * Return a pointer and length for a given entry. The returned region + * should be valid until pArchive is closed, and should be treated as + * read-only. + * + * Only makes sense for entries which are stored (ie, not compressed). + * No guarantees are made regarding alignment of the returned pointer. + */ +bool mzGetStoredEntry(const ZipArchive *pArchive, + const ZipEntry* pEntry, unsigned char **addr, size_t *length); + +/* * Inflate all entries under zipDir to the directory specified by * targetDir, which must exist and be a writable directory. * diff --git a/mtdutils/flash_image.c b/mtdutils/flash_image.c index a39d60001..5657dfc82 100644 --- a/mtdutils/flash_image.c +++ b/mtdutils/flash_image.c @@ -24,6 +24,9 @@ #include "cutils/log.h" #include "mtdutils.h" +#ifdef LOG_TAG +#undef LOG_TAG +#endif #define LOG_TAG "flash_image" #define HEADER_SIZE 2048 // size of header to compare for equality diff --git a/recovery.cpp b/recovery.cpp index 64091305a..c6e12702d 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -48,6 +48,8 @@ #include "adb_install.h" extern "C" { #include "minadbd/adb.h" +#include "fuse_sideload.h" +#include "fuse_sdcard_provider.h" } extern "C" { @@ -72,6 +74,9 @@ static const struct option OPTIONS[] = { { "show_text", no_argument, NULL, 't' }, { "just_exit", no_argument, NULL, 'x' }, { "locale", required_argument, NULL, 'l' }, + { "stages", required_argument, NULL, 'g' }, + { "shutdown_after", no_argument, NULL, 'p' }, + { "reason", required_argument, NULL, 'r' }, { NULL, 0, NULL, 0 }, }; @@ -87,11 +92,14 @@ static const char *CACHE_ROOT = "/cache"; static const char *SDCARD_ROOT = "/sdcard"; static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install"; -static const char *SIDELOAD_TEMP_DIR = "/tmp/sideload"; + +#define KEEP_LOG_COUNT 10 RecoveryUI* ui = NULL; char* locale = NULL; char recovery_version[PROPERTY_VALUE_MAX+1]; +char* stage = NULL; +char* reason = NULL; /* * The recovery tool communicates with the main system through /cache files. @@ -171,6 +179,12 @@ fopen_path(const char *path, const char *mode) { return fp; } +static void redirect_stdio(const char* filename) { + // If these fail, there's not really anywhere to complain... + freopen(filename, "a", stdout); setbuf(stdout, NULL); + freopen(filename, "a", stderr); setbuf(stderr, NULL); +} + // close a file, log an error if the error indicator is set static void check_and_fclose(FILE *fp, const char *name) { @@ -188,13 +202,14 @@ get_args(int *argc, char ***argv) { struct bootloader_message boot; memset(&boot, 0, sizeof(boot)); get_bootloader_message(&boot); // this may fail, leaving a zeroed structure + stage = strndup(boot.stage, sizeof(boot.stage)); if (boot.command[0] != 0 && boot.command[0] != 255) { - LOGI("Boot command: %.*s\n", sizeof(boot.command), boot.command); + LOGI("Boot command: %.*s\n", (int)sizeof(boot.command), boot.command); } if (boot.status[0] != 0 && boot.status[0] != 255) { - LOGI("Boot status: %.*s\n", sizeof(boot.status), boot.status); + LOGI("Boot status: %.*s\n", (int)sizeof(boot.status), boot.status); } // --- if arguments weren't supplied, look in the bootloader control block @@ -451,96 +466,6 @@ erase_volume(const char *volume) { return result; } -static char* -copy_sideloaded_package(const char* original_path) { - if (ensure_path_mounted(original_path) != 0) { - LOGE("Can't mount %s\n", original_path); - return NULL; - } - - if (ensure_path_mounted(SIDELOAD_TEMP_DIR) != 0) { - LOGE("Can't mount %s\n", SIDELOAD_TEMP_DIR); - return NULL; - } - - if (mkdir(SIDELOAD_TEMP_DIR, 0700) != 0) { - if (errno != EEXIST) { - LOGE("Can't mkdir %s (%s)\n", SIDELOAD_TEMP_DIR, strerror(errno)); - return NULL; - } - } - - // verify that SIDELOAD_TEMP_DIR is exactly what we expect: a - // directory, owned by root, readable and writable only by root. - struct stat st; - if (stat(SIDELOAD_TEMP_DIR, &st) != 0) { - LOGE("failed to stat %s (%s)\n", SIDELOAD_TEMP_DIR, strerror(errno)); - return NULL; - } - if (!S_ISDIR(st.st_mode)) { - LOGE("%s isn't a directory\n", SIDELOAD_TEMP_DIR); - return NULL; - } - if ((st.st_mode & 0777) != 0700) { - LOGE("%s has perms %o\n", SIDELOAD_TEMP_DIR, st.st_mode); - return NULL; - } - if (st.st_uid != 0) { - LOGE("%s owned by %lu; not root\n", SIDELOAD_TEMP_DIR, st.st_uid); - return NULL; - } - - char copy_path[PATH_MAX]; - strcpy(copy_path, SIDELOAD_TEMP_DIR); - strcat(copy_path, "/package.zip"); - - char* buffer = (char*)malloc(BUFSIZ); - if (buffer == NULL) { - LOGE("Failed to allocate buffer\n"); - return NULL; - } - - size_t read; - FILE* fin = fopen(original_path, "rb"); - if (fin == NULL) { - LOGE("Failed to open %s (%s)\n", original_path, strerror(errno)); - return NULL; - } - FILE* fout = fopen(copy_path, "wb"); - if (fout == NULL) { - LOGE("Failed to open %s (%s)\n", copy_path, strerror(errno)); - return NULL; - } - - while ((read = fread(buffer, 1, BUFSIZ, fin)) > 0) { - if (fwrite(buffer, 1, read, fout) != read) { - LOGE("Short write of %s (%s)\n", copy_path, strerror(errno)); - return NULL; - } - } - - free(buffer); - - if (fclose(fout) != 0) { - LOGE("Failed to close %s (%s)\n", copy_path, strerror(errno)); - return NULL; - } - - if (fclose(fin) != 0) { - LOGE("Failed to close %s (%s)\n", original_path, strerror(errno)); - return NULL; - } - - // "adb push" is happy to overwrite read-only files when it's - // running as root, but we'll try anyway. - if (chmod(copy_path, 0400) != 0) { - LOGE("Failed to chmod %s (%s)\n", copy_path, strerror(errno)); - return NULL; - } - - return strdup(copy_path); -} - static const char** prepend_title(const char* const* headers) { // count the number of lines in our title, plus the @@ -616,9 +541,9 @@ static int compare_string(const void* a, const void* b) { return strcmp(*(const char**)a, *(const char**)b); } -static int -update_directory(const char* path, const char* unmount_when_done, - int* wipe_cache, Device* device) { +// Returns a malloc'd path, or NULL. +static char* +browse_directory(const char* path, Device* device) { ensure_path_mounted(path); const char* MENU_HEADERS[] = { "Choose a package to install:", @@ -630,10 +555,7 @@ update_directory(const char* path, const char* unmount_when_done, d = opendir(path); if (d == NULL) { LOGE("error opening %s: %s\n", path, strerror(errno)); - if (unmount_when_done != NULL) { - ensure_path_unmounted(unmount_when_done); - } - return 0; + return NULL; } const char** headers = prepend_title(MENU_HEADERS); @@ -689,58 +611,41 @@ update_directory(const char* path, const char* unmount_when_done, z_size += d_size; zips[z_size] = NULL; - int result; + char* result; int chosen_item = 0; - do { + while (true) { chosen_item = get_menu_selection(headers, zips, 1, chosen_item, device); char* item = zips[chosen_item]; int item_len = strlen(item); if (chosen_item == 0) { // item 0 is always "../" // go up but continue browsing (if the caller is update_directory) - result = -1; + result = NULL; break; - } else if (item[item_len-1] == '/') { + } + + char new_path[PATH_MAX]; + strlcpy(new_path, path, PATH_MAX); + strlcat(new_path, "/", PATH_MAX); + strlcat(new_path, item, PATH_MAX); + + if (item[item_len-1] == '/') { // recurse down into a subdirectory - char new_path[PATH_MAX]; - strlcpy(new_path, path, PATH_MAX); - strlcat(new_path, "/", PATH_MAX); - strlcat(new_path, item, PATH_MAX); new_path[strlen(new_path)-1] = '\0'; // truncate the trailing '/' - result = update_directory(new_path, unmount_when_done, wipe_cache, device); - if (result >= 0) break; + result = browse_directory(new_path, device); + if (result) break; } else { - // selected a zip file: attempt to install it, and return - // the status to the caller. - char new_path[PATH_MAX]; - strlcpy(new_path, path, PATH_MAX); - strlcat(new_path, "/", PATH_MAX); - strlcat(new_path, item, PATH_MAX); - - ui->Print("\n-- Install %s ...\n", path); - set_sdcard_update_bootloader_message(); - char* copy = copy_sideloaded_package(new_path); - if (unmount_when_done != NULL) { - ensure_path_unmounted(unmount_when_done); - } - if (copy) { - result = install_package(copy, wipe_cache, TEMPORARY_INSTALL_FILE); - free(copy); - } else { - result = INSTALL_ERROR; - } + // selected a zip file: return the malloc'd path to the caller. + result = strdup(new_path); break; } - } while (true); + } int i; for (i = 0; i < z_size; ++i) free(zips[i]); free(zips); free(headers); - if (unmount_when_done != NULL) { - ensure_path_unmounted(unmount_when_done); - } return result; } @@ -780,10 +685,73 @@ wipe_data(int confirm, Device* device) { device->WipeData(); erase_volume("/data"); erase_volume("/cache"); + erase_persistent_partition(); ui->Print("Data wipe complete.\n"); } -static void +static void file_to_ui(const char* fn) { + FILE *fp = fopen_path(fn, "re"); + if (fp == NULL) { + ui->Print(" Unable to open %s: %s\n", fn, strerror(errno)); + return; + } + char line[1024]; + int ct = 0; + redirect_stdio("/dev/null"); + while(fgets(line, sizeof(line), fp) != NULL) { + ui->Print("%s", line); + ct++; + if (ct % 30 == 0) { + // give the user time to glance at the entries + ui->WaitKey(); + } + } + redirect_stdio(TEMPORARY_LOG_FILE); + fclose(fp); +} + +static void choose_recovery_file(Device* device) { + int i; + static const char** title_headers = NULL; + char *filename; + const char* headers[] = { "Select file to view", + "", + NULL }; + char* entries[KEEP_LOG_COUNT + 2]; + memset(entries, 0, sizeof(entries)); + + for (i = 0; i < KEEP_LOG_COUNT; i++) { + char *filename; + if (asprintf(&filename, (i==0) ? LAST_LOG_FILE : (LAST_LOG_FILE ".%d"), i) == -1) { + // memory allocation failure - return early. Should never happen. + return; + } + if ((ensure_path_mounted(filename) != 0) || (access(filename, R_OK) == -1)) { + free(filename); + entries[i+1] = NULL; + break; + } + entries[i+1] = filename; + } + + entries[0] = strdup("Go back"); + title_headers = prepend_title((const char**)headers); + + while(1) { + int chosen_item = get_menu_selection(title_headers, entries, 1, 0, device); + if (chosen_item == 0) break; + file_to_ui(entries[chosen_item]); + } + + for (i = 0; i < KEEP_LOG_COUNT + 1; i++) { + free(entries[i]); + } +} + +// Return REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION +// means to take the default, which is to reboot or shutdown depending +// on if the --shutdown_after flag was passed to recovery. +static Device::BuiltinAction prompt_and_wait(Device* device, int status) { const char* const* headers = prepend_title(device->GetMenuHeaders()); @@ -807,27 +775,48 @@ prompt_and_wait(Device* device, int status) { // device-specific code may take some action here. It may // return one of the core actions handled in the switch // statement below. - chosen_item = device->InvokeMenuItem(chosen_item); + Device::BuiltinAction chosen_action = device->InvokeMenuItem(chosen_item); + + int wipe_cache = 0; + switch (chosen_action) { + case Device::NO_ACTION: + break; - int wipe_cache; - switch (chosen_item) { case Device::REBOOT: - return; + case Device::SHUTDOWN: + case Device::REBOOT_BOOTLOADER: + return chosen_action; case Device::WIPE_DATA: wipe_data(ui->IsTextVisible(), device); - if (!ui->IsTextVisible()) return; + if (!ui->IsTextVisible()) return Device::NO_ACTION; break; case Device::WIPE_CACHE: ui->Print("\n-- Wiping cache...\n"); erase_volume("/cache"); ui->Print("Cache wipe complete.\n"); - if (!ui->IsTextVisible()) return; + if (!ui->IsTextVisible()) return Device::NO_ACTION; break; - case Device::APPLY_EXT: - status = update_directory(SDCARD_ROOT, SDCARD_ROOT, &wipe_cache, device); + case Device::APPLY_EXT: { + ensure_path_mounted(SDCARD_ROOT); + char* path = browse_directory(SDCARD_ROOT, device); + if (path == NULL) { + ui->Print("\n-- No package file selected.\n", path); + break; + } + + ui->Print("\n-- Install %s ...\n", path); + set_sdcard_update_bootloader_message(); + void* token = start_sdcard_fuse(path); + + int status = install_package(FUSE_SIDELOAD_HOST_PATHNAME, &wipe_cache, + TEMPORARY_INSTALL_FILE, false); + + finish_sdcard_fuse(token); + ensure_path_unmounted(SDCARD_ROOT); + if (status == INSTALL_SUCCESS && wipe_cache) { ui->Print("\n-- Wiping cache (at package request)...\n"); if (erase_volume("/cache")) { @@ -836,39 +825,26 @@ prompt_and_wait(Device* device, int status) { ui->Print("Cache wipe complete.\n"); } } + if (status >= 0) { if (status != INSTALL_SUCCESS) { ui->SetBackground(RecoveryUI::ERROR); ui->Print("Installation aborted.\n"); } else if (!ui->IsTextVisible()) { - return; // reboot if logs aren't visible + return Device::NO_ACTION; // reboot if logs aren't visible } else { ui->Print("\nInstall from sdcard complete.\n"); } } break; + } case Device::APPLY_CACHE: - // Don't unmount cache at the end of this. - status = update_directory(CACHE_ROOT, NULL, &wipe_cache, device); - if (status == INSTALL_SUCCESS && wipe_cache) { - ui->Print("\n-- Wiping cache (at package request)...\n"); - if (erase_volume("/cache")) { - ui->Print("Cache wipe failed.\n"); - } else { - ui->Print("Cache wipe complete.\n"); - } - } - if (status >= 0) { - if (status != INSTALL_SUCCESS) { - ui->SetBackground(RecoveryUI::ERROR); - ui->Print("Installation aborted.\n"); - } else if (!ui->IsTextVisible()) { - return; // reboot if logs aren't visible - } else { - ui->Print("\nInstall from cache complete.\n"); - } - } + ui->Print("\nAPPLY_CACHE is deprecated.\n"); + break; + + case Device::READ_RECOVERY_LASTLOG: + choose_recovery_file(device); break; case Device::APPLY_ADB_SIDELOAD: @@ -879,7 +855,7 @@ prompt_and_wait(Device* device, int status) { ui->Print("Installation aborted.\n"); copy_logs(); } else if (!ui->IsTextVisible()) { - return; // reboot if logs aren't visible + return Device::NO_ACTION; // reboot if logs aren't visible } else { ui->Print("\nInstall from ADB complete.\n"); } @@ -939,9 +915,7 @@ main(int argc, char **argv) { time_t start = time(NULL); - // If these fail, there's not really anywhere to complain... - freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL); - freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL); + redirect_stdio(TEMPORARY_LOG_FILE); // If this binary is started with the single argument "--adbd", // instead of being the normal recovery binary, it turns into kind @@ -955,6 +929,7 @@ main(int argc, char **argv) { return 0; } +<<<<<<< HEAD printf("Starting TWRP %s on %s", TW_VERSION_STR, ctime(&start)); Device* device = make_device(); @@ -984,20 +959,19 @@ main(int argc, char **argv) { load_volume_table(); ensure_path_mounted(LAST_LOG_FILE); - rotate_last_logs(10); + rotate_last_logs(KEEP_LOG_COUNT); get_args(&argc, &argv); - int previous_runs = 0; const char *send_intent = NULL; const char *update_package = NULL; int wipe_data = 0, wipe_cache = 0, show_text = 0; bool just_exit = false; bool perform_backup = false; + bool shutdown_after = false; int arg; while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) { switch (arg) { - case 'p': previous_runs = atoi(optarg); break; case 's': send_intent = optarg; break; case 'u': update_package = optarg; break; case 'w': wipe_data = wipe_cache = 1; break; @@ -1005,6 +979,16 @@ main(int argc, char **argv) { case 't': show_text = 1; break; case 'x': just_exit = true; break; case 'l': locale = optarg; break; + case 'g': { + if (stage == NULL || *stage == '\0') { + char buffer[20] = "1/"; + strncat(buffer, optarg, sizeof(buffer)-3); + stage = strdup(buffer); + } + break; + } + case 'p': shutdown_after = true; break; + case 'r': reason = optarg; break; case '?': LOGE("Invalid command argument\n"); continue; @@ -1015,13 +999,21 @@ main(int argc, char **argv) { load_locale_from_cache(); } printf("locale is [%s]\n", locale); + printf("stage is [%s]\n", stage); + printf("reason is [%s]\n", reason); Device* device = make_device(); ui = device->GetUI(); gCurrentUI = ui; - ui->Init(); ui->SetLocale(locale); + ui->Init(); + + int st_cur, st_max; + if (stage != NULL && sscanf(stage, "%d/%d", &st_cur, &st_max) == 2) { + ui->SetStage(st_cur, st_max); + } + ui->SetBackground(RecoveryUI::NONE); if (show_text) ui->ShowText(true); @@ -1102,7 +1094,7 @@ main(int argc, char **argv) { else status = INSTALL_ERROR; /* - status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE); + status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE, true); if (status == INSTALL_SUCCESS && wipe_cache) { if (erase_volume("/cache")) { LOGE("Cache wipe (requested by package) failed."); @@ -1128,6 +1120,7 @@ main(int argc, char **argv) { if (device->WipeData()) status = INSTALL_ERROR; if (erase_volume("/data")) status = INSTALL_ERROR; if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR; + if (erase_persistent_partition() == -1 ) status = INSTALL_ERROR; */ if (status != INSTALL_SUCCESS) ui->Print("Data wipe failed.\n"); } else if (wipe_cache) { @@ -1186,11 +1179,13 @@ main(int argc, char **argv) { copy_logs(); ui->SetBackground(RecoveryUI::ERROR); } + Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; if (status != INSTALL_SUCCESS || ui->IsTextVisible()) { - prompt_and_wait(device, status); + Device::BuiltinAction temp = prompt_and_wait(device, status); + if (temp != Device::NO_ACTION) after = temp; } - // Otherwise, get ready to boot the main system... + // Save logs and clean up before rebooting or shutting down. finish_recovery(send_intent); ui->Print("Rebooting...\n"); char backup_arg_char[50]; @@ -1213,5 +1208,23 @@ main(int argc, char **argv) { reboot(RB_AUTOBOOT); #endif property_set(ANDROID_RB_PROPERTY, "reboot,"); + + switch (after) { + case Device::SHUTDOWN: + ui->Print("Shutting down...\n"); + property_set(ANDROID_RB_PROPERTY, "shutdown,"); + break; + + case Device::REBOOT_BOOTLOADER: + ui->Print("Rebooting to bootloader...\n"); + property_set(ANDROID_RB_PROPERTY, "reboot,bootloader"); + break; + + default: + ui->Print("Rebooting...\n"); + property_set(ANDROID_RB_PROPERTY, "reboot,"); + break; + } + sleep(5); // should reboot before this finishes return EXIT_SUCCESS; } diff --git a/res-hdpi/images/erasing_text.png b/res-hdpi/images/erasing_text.png Binary files differnew file mode 100644 index 000000000..774244c84 --- /dev/null +++ b/res-hdpi/images/erasing_text.png diff --git a/res-hdpi/images/error_text.png b/res-hdpi/images/error_text.png Binary files differnew file mode 100644 index 000000000..64a57ec4e --- /dev/null +++ b/res-hdpi/images/error_text.png diff --git a/res-hdpi/images/icon_error.png b/res-hdpi/images/icon_error.png Binary files differnew file mode 100644 index 000000000..cb3d1ab22 --- /dev/null +++ b/res-hdpi/images/icon_error.png diff --git a/res-hdpi/images/icon_installing.png b/res-hdpi/images/icon_installing.png Binary files differnew file mode 100644 index 000000000..c2c020162 --- /dev/null +++ b/res-hdpi/images/icon_installing.png diff --git a/res-hdpi/images/installing_text.png b/res-hdpi/images/installing_text.png Binary files differnew file mode 100644 index 000000000..33b54f1bf --- /dev/null +++ b/res-hdpi/images/installing_text.png diff --git a/res-hdpi/images/no_command_text.png b/res-hdpi/images/no_command_text.png Binary files differnew file mode 100644 index 000000000..9927ecb6d --- /dev/null +++ b/res-hdpi/images/no_command_text.png diff --git a/res-hdpi/images/progress_empty.png b/res-hdpi/images/progress_empty.png Binary files differnew file mode 100644 index 000000000..72581832c --- /dev/null +++ b/res-hdpi/images/progress_empty.png diff --git a/res-hdpi/images/progress_fill.png b/res-hdpi/images/progress_fill.png Binary files differnew file mode 100644 index 000000000..becf87bdf --- /dev/null +++ b/res-hdpi/images/progress_fill.png diff --git a/res-hdpi/images/stage_empty.png b/res-hdpi/images/stage_empty.png Binary files differnew file mode 100644 index 000000000..251ec1969 --- /dev/null +++ b/res-hdpi/images/stage_empty.png diff --git a/res-hdpi/images/stage_fill.png b/res-hdpi/images/stage_fill.png Binary files differnew file mode 100644 index 000000000..1ab79e862 --- /dev/null +++ b/res-hdpi/images/stage_fill.png diff --git a/res-mdpi/images/erasing_text.png b/res-mdpi/images/erasing_text.png Binary files differnew file mode 100644 index 000000000..fd86c3f6e --- /dev/null +++ b/res-mdpi/images/erasing_text.png diff --git a/res-mdpi/images/error_text.png b/res-mdpi/images/error_text.png Binary files differnew file mode 100644 index 000000000..f1b44c9b3 --- /dev/null +++ b/res-mdpi/images/error_text.png diff --git a/res-mdpi/images/icon_error.png b/res-mdpi/images/icon_error.png Binary files differnew file mode 100644 index 000000000..cb3d1ab22 --- /dev/null +++ b/res-mdpi/images/icon_error.png diff --git a/res-mdpi/images/icon_installing.png b/res-mdpi/images/icon_installing.png Binary files differnew file mode 100644 index 000000000..c2c020162 --- /dev/null +++ b/res-mdpi/images/icon_installing.png diff --git a/res-mdpi/images/installing_text.png b/res-mdpi/images/installing_text.png Binary files differnew file mode 100644 index 000000000..064b2a317 --- /dev/null +++ b/res-mdpi/images/installing_text.png diff --git a/res-mdpi/images/no_command_text.png b/res-mdpi/images/no_command_text.png Binary files differnew file mode 100644 index 000000000..1f29b8951 --- /dev/null +++ b/res-mdpi/images/no_command_text.png diff --git a/res-mdpi/images/progress_empty.png b/res-mdpi/images/progress_empty.png Binary files differnew file mode 100644 index 000000000..72581832c --- /dev/null +++ b/res-mdpi/images/progress_empty.png diff --git a/res-mdpi/images/progress_fill.png b/res-mdpi/images/progress_fill.png Binary files differnew file mode 100644 index 000000000..becf87bdf --- /dev/null +++ b/res-mdpi/images/progress_fill.png diff --git a/res-mdpi/images/stage_empty.png b/res-mdpi/images/stage_empty.png Binary files differnew file mode 100644 index 000000000..251ec1969 --- /dev/null +++ b/res-mdpi/images/stage_empty.png diff --git a/res-mdpi/images/stage_fill.png b/res-mdpi/images/stage_fill.png Binary files differnew file mode 100644 index 000000000..1ab79e862 --- /dev/null +++ b/res-mdpi/images/stage_fill.png diff --git a/res-xhdpi/images/erasing_text.png b/res-xhdpi/images/erasing_text.png Binary files differnew file mode 100644 index 000000000..f88e0e6a8 --- /dev/null +++ b/res-xhdpi/images/erasing_text.png diff --git a/res-xhdpi/images/error_text.png b/res-xhdpi/images/error_text.png Binary files differnew file mode 100644 index 000000000..c3a4cc6f8 --- /dev/null +++ b/res-xhdpi/images/error_text.png diff --git a/res-xhdpi/images/icon_error.png b/res-xhdpi/images/icon_error.png Binary files differnew file mode 100644 index 000000000..cb3d1ab22 --- /dev/null +++ b/res-xhdpi/images/icon_error.png diff --git a/res-xhdpi/images/icon_installing.png b/res-xhdpi/images/icon_installing.png Binary files differnew file mode 100644 index 000000000..c2c020162 --- /dev/null +++ b/res-xhdpi/images/icon_installing.png diff --git a/res-xhdpi/images/installing_text.png b/res-xhdpi/images/installing_text.png Binary files differnew file mode 100644 index 000000000..a4dacd0f6 --- /dev/null +++ b/res-xhdpi/images/installing_text.png diff --git a/res-xhdpi/images/no_command_text.png b/res-xhdpi/images/no_command_text.png Binary files differnew file mode 100644 index 000000000..eb34e94b3 --- /dev/null +++ b/res-xhdpi/images/no_command_text.png diff --git a/res-xhdpi/images/progress_empty.png b/res-xhdpi/images/progress_empty.png Binary files differnew file mode 100644 index 000000000..72581832c --- /dev/null +++ b/res-xhdpi/images/progress_empty.png diff --git a/res-xhdpi/images/progress_fill.png b/res-xhdpi/images/progress_fill.png Binary files differnew file mode 100644 index 000000000..becf87bdf --- /dev/null +++ b/res-xhdpi/images/progress_fill.png diff --git a/res-xhdpi/images/stage_empty.png b/res-xhdpi/images/stage_empty.png Binary files differnew file mode 100644 index 000000000..251ec1969 --- /dev/null +++ b/res-xhdpi/images/stage_empty.png diff --git a/res-xhdpi/images/stage_fill.png b/res-xhdpi/images/stage_fill.png Binary files differnew file mode 100644 index 000000000..1ab79e862 --- /dev/null +++ b/res-xhdpi/images/stage_fill.png diff --git a/res-xxhdpi/images/erasing_text.png b/res-xxhdpi/images/erasing_text.png Binary files differnew file mode 100644 index 000000000..c87fd52b4 --- /dev/null +++ b/res-xxhdpi/images/erasing_text.png diff --git a/res-xxhdpi/images/error_text.png b/res-xxhdpi/images/error_text.png Binary files differnew file mode 100644 index 000000000..486e951df --- /dev/null +++ b/res-xxhdpi/images/error_text.png diff --git a/res-xxhdpi/images/icon_error.png b/res-xxhdpi/images/icon_error.png Binary files differnew file mode 100644 index 000000000..cb3d1ab22 --- /dev/null +++ b/res-xxhdpi/images/icon_error.png diff --git a/res-xxhdpi/images/icon_installing.png b/res-xxhdpi/images/icon_installing.png Binary files differnew file mode 100644 index 000000000..c2c020162 --- /dev/null +++ b/res-xxhdpi/images/icon_installing.png diff --git a/res-xxhdpi/images/installing_text.png b/res-xxhdpi/images/installing_text.png Binary files differnew file mode 100644 index 000000000..ef6e8f3f0 --- /dev/null +++ b/res-xxhdpi/images/installing_text.png diff --git a/res-xxhdpi/images/no_command_text.png b/res-xxhdpi/images/no_command_text.png Binary files differnew file mode 100644 index 000000000..cc98bb18a --- /dev/null +++ b/res-xxhdpi/images/no_command_text.png diff --git a/res-xxhdpi/images/progress_empty.png b/res-xxhdpi/images/progress_empty.png Binary files differnew file mode 100644 index 000000000..72581832c --- /dev/null +++ b/res-xxhdpi/images/progress_empty.png diff --git a/res-xxhdpi/images/progress_fill.png b/res-xxhdpi/images/progress_fill.png Binary files differnew file mode 100644 index 000000000..becf87bdf --- /dev/null +++ b/res-xxhdpi/images/progress_fill.png diff --git a/res-xxhdpi/images/stage_empty.png b/res-xxhdpi/images/stage_empty.png Binary files differnew file mode 100644 index 000000000..251ec1969 --- /dev/null +++ b/res-xxhdpi/images/stage_empty.png diff --git a/res-xxhdpi/images/stage_fill.png b/res-xxhdpi/images/stage_fill.png Binary files differnew file mode 100644 index 000000000..1ab79e862 --- /dev/null +++ b/res-xxhdpi/images/stage_fill.png diff --git a/res-xxxhdpi/images/erasing_text.png b/res-xxxhdpi/images/erasing_text.png Binary files differnew file mode 100644 index 000000000..612e7a390 --- /dev/null +++ b/res-xxxhdpi/images/erasing_text.png diff --git a/res-xxxhdpi/images/error_text.png b/res-xxxhdpi/images/error_text.png Binary files differnew file mode 100644 index 000000000..50d2fadb5 --- /dev/null +++ b/res-xxxhdpi/images/error_text.png diff --git a/res-xxxhdpi/images/icon_error.png b/res-xxxhdpi/images/icon_error.png Binary files differnew file mode 100644 index 000000000..cb3d1ab22 --- /dev/null +++ b/res-xxxhdpi/images/icon_error.png diff --git a/res-xxxhdpi/images/icon_installing.png b/res-xxxhdpi/images/icon_installing.png Binary files differnew file mode 100644 index 000000000..c2c020162 --- /dev/null +++ b/res-xxxhdpi/images/icon_installing.png diff --git a/res-xxxhdpi/images/installing_text.png b/res-xxxhdpi/images/installing_text.png Binary files differnew file mode 100644 index 000000000..9bd093bf4 --- /dev/null +++ b/res-xxxhdpi/images/installing_text.png diff --git a/res-xxxhdpi/images/no_command_text.png b/res-xxxhdpi/images/no_command_text.png Binary files differnew file mode 100644 index 000000000..6354e6a99 --- /dev/null +++ b/res-xxxhdpi/images/no_command_text.png diff --git a/res-xxxhdpi/images/progress_empty.png b/res-xxxhdpi/images/progress_empty.png Binary files differnew file mode 100644 index 000000000..72581832c --- /dev/null +++ b/res-xxxhdpi/images/progress_empty.png diff --git a/res-xxxhdpi/images/progress_fill.png b/res-xxxhdpi/images/progress_fill.png Binary files differnew file mode 100644 index 000000000..becf87bdf --- /dev/null +++ b/res-xxxhdpi/images/progress_fill.png diff --git a/res-xxxhdpi/images/stage_empty.png b/res-xxxhdpi/images/stage_empty.png Binary files differnew file mode 100644 index 000000000..251ec1969 --- /dev/null +++ b/res-xxxhdpi/images/stage_empty.png diff --git a/res-xxxhdpi/images/stage_fill.png b/res-xxxhdpi/images/stage_fill.png Binary files differnew file mode 100644 index 000000000..1ab79e862 --- /dev/null +++ b/res-xxxhdpi/images/stage_fill.png @@ -19,8 +19,10 @@ #include <sys/mount.h> #include <sys/stat.h> #include <sys/types.h> +#include <sys/wait.h> #include <unistd.h> #include <ctype.h> +#include <fcntl.h> extern "C" { #include <fs_mgr.h> @@ -30,12 +32,17 @@ extern "C" { #include "roots.h" #include "common.h" #include "make_ext4fs.h" -#include "partitions.hpp" +extern "C" { +#include "wipe.h" +#include "cryptfs.h" +} static struct fstab *fstab = NULL; extern struct selabel_handle *sehandle; +static const char* PERSISTENT_PATH = "/persistent"; + void load_volume_table() { int i; @@ -47,7 +54,7 @@ void load_volume_table() return; } - ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk", 0); + ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk"); if (ret < 0 ) { LOGE("failed to add /tmp entry to fstab\n"); fs_mgr_free_fstab(fstab); @@ -157,6 +164,20 @@ int ensure_path_unmounted(const char* path) { return unmount_mounted_volume(mv); } +static int exec_cmd(const char* path, char* const argv[]) { + int status; + pid_t child; + if ((child = vfork()) == 0) { + execv(path, argv); + _exit(-1); + } + waitpid(child, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + LOGE("%s failed with status %d\n", path, WEXITSTATUS(status)); + } + return WEXITSTATUS(status); +} + int format_volume(const char* volume) { if (PartitionManager.Wipe_By_Path(volume)) return 0; @@ -205,10 +226,51 @@ int format_volume(const char* volume) { return 0; } - if (strcmp(v->fs_type, "ext4") == 0) { - int result = make_ext4fs(v->blk_device, v->length, volume, sehandle); + if (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "f2fs") == 0) { + // if there's a key_loc that looks like a path, it should be a + // block device for storing encryption metadata. wipe it too. + if (v->key_loc != NULL && v->key_loc[0] == '/') { + LOGI("wiping %s\n", v->key_loc); + int fd = open(v->key_loc, O_WRONLY | O_CREAT, 0644); + if (fd < 0) { + LOGE("format_volume: failed to open %s\n", v->key_loc); + return -1; + } + wipe_block_device(fd, get_file_size(fd)); + close(fd); + } + + ssize_t length = 0; + if (v->length != 0) { + length = v->length; + } else if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0) { + length = -CRYPT_FOOTER_OFFSET; + } + int result; + if (strcmp(v->fs_type, "ext4") == 0) { + result = make_ext4fs(v->blk_device, length, volume, sehandle); + } else { /* Has to be f2fs because we checked earlier. */ + if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0 && length < 0) { + LOGE("format_volume: crypt footer + negative length (%zd) not supported on %s\n", length, v->fs_type); + return -1; + } + if (length < 0) { + LOGE("format_volume: negative length (%zd) not supported on %s\n", length, v->fs_type); + return -1; + } + char *num_sectors; + if (asprintf(&num_sectors, "%zd", length / 512) <= 0) { + LOGE("format_volume: failed to create %s command for %s\n", v->fs_type, v->blk_device); + return -1; + } + const char *f2fs_path = "/sbin/mkfs.f2fs"; + const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", v->blk_device, num_sectors, NULL}; + + result = exec_cmd(f2fs_path, (char* const*)f2fs_argv); + free(num_sectors); + } if (result != 0) { - LOGE("format_volume: make_extf4fs failed on %s\n", v->blk_device); + LOGE("format_volume: make %s failed on %s with %d(%s)\n", v->fs_type, v->blk_device, result, strerror(errno)); return -1; } return 0; @@ -218,6 +280,41 @@ int format_volume(const char* volume) { return -1; } +int erase_persistent_partition() { + Volume *v = volume_for_path(PERSISTENT_PATH); + if (v == NULL) { + // most devices won't have /persistent, so this is not an error. + return 0; + } + + int fd = open(v->blk_device, O_RDWR); + uint64_t size = get_file_size(fd); + if (size == 0) { + LOGE("failed to stat size of /persistent\n"); + close(fd); + return -1; + } + + char oem_unlock_enabled; + lseek(fd, size - 1, SEEK_SET); + read(fd, &oem_unlock_enabled, 1); + + if (oem_unlock_enabled) { + if (wipe_block_device(fd, size)) { + LOGE("error wiping /persistent: %s\n", strerror(errno)); + close(fd); + return -1; + } + + lseek(fd, size - 1, SEEK_SET); + write(fd, &oem_unlock_enabled, 1); + } + + close(fd); + + return (int) oem_unlock_enabled; +} + int setup_install_mounts() { if (fstab == NULL) { LOGE("can't set up install mounts: no fstab loaded\n"); @@ -228,10 +325,16 @@ int setup_install_mounts() { if (strcmp(v->mount_point, "/tmp") == 0 || strcmp(v->mount_point, "/cache") == 0) { - if (ensure_path_mounted(v->mount_point) != 0) return -1; + if (ensure_path_mounted(v->mount_point) != 0) { + LOGE("failed to mount %s\n", v->mount_point); + return -1; + } } else { - if (ensure_path_unmounted(v->mount_point) != 0) return -1; + if (ensure_path_unmounted(v->mount_point) != 0) { + LOGE("failed to unmount %s\n", v->mount_point); + return -1; + } } } return 0; @@ -46,6 +46,11 @@ int format_volume(const char* volume); // mounted (/tmp and /cache) are mounted. Returns 0 on success. int setup_install_mounts(); +// Conditionally wipes the /persistent partition if it's marked +// to wipe. Returns -1 on failure, 1 if the partition was wiped +// and 0 if the partition was not wiped. +int erase_persistent_partition(); + #ifdef __cplusplus } #endif diff --git a/screen_ui.cpp b/screen_ui.cpp index 7e7bd8a99..6fff30a25 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -58,6 +58,7 @@ static double now() { ScreenRecoveryUI::ScreenRecoveryUI() : currentIcon(NONE), installingFrame(0), + locale(NULL), rtl_locale(false), progressBarType(EMPTY), progressScopeStart(0), @@ -75,67 +76,59 @@ ScreenRecoveryUI::ScreenRecoveryUI() : menu_top(0), menu_items(0), menu_sel(0), - - // These values are correct for the default image resources - // provided with the android platform. Devices which use - // different resources should have a subclass of ScreenRecoveryUI - // that overrides Init() to set these values appropriately and - // then call the superclass Init(). animation_fps(20), - indeterminate_frames(6), - installing_frames(7), - install_overlay_offset_x(13), - install_overlay_offset_y(190), - overlay_offset_x(-1), - overlay_offset_y(-1) { + installing_frames(-1), + stage(-1), + max_stage(-1) { for (int i = 0; i < 5; i++) backgroundIcon[i] = NULL; + memset(text, 0, sizeof(text)); + pthread_mutex_init(&updateMutex, NULL); self = this; } -// Draw the given frame over the installation overlay animation. The -// background is not cleared or draw with the base icon first; we -// assume that the frame already contains some other frame of the -// animation. Does nothing if no overlay animation is defined. -// Should only be called with updateMutex locked. -void ScreenRecoveryUI::draw_install_overlay_locked(int frame) { - if (installationOverlay == NULL || overlay_offset_x < 0) return; - gr_surface surface = installationOverlay[frame]; - int iconWidth = gr_get_width(surface); - int iconHeight = gr_get_height(surface); - gr_blit(surface, 0, 0, iconWidth, iconHeight, - overlay_offset_x, overlay_offset_y); -} - // Clear the screen and draw the currently selected background icon (if any). // Should only be called with updateMutex locked. void ScreenRecoveryUI::draw_background_locked(Icon icon) { pagesIdentical = false; gr_color(0, 0, 0, 255); - gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + gr_clear(); if (icon) { gr_surface surface = backgroundIcon[icon]; + if (icon == INSTALLING_UPDATE || icon == ERASING) { + surface = installation[installingFrame]; + } gr_surface text_surface = backgroundText[icon]; int iconWidth = gr_get_width(surface); int iconHeight = gr_get_height(surface); int textWidth = gr_get_width(text_surface); int textHeight = gr_get_height(text_surface); + int stageHeight = gr_get_height(stageMarkerEmpty); + + int sh = (max_stage >= 0) ? stageHeight : 0; - int iconX = (gr_fb_width() - iconWidth) / 2; - int iconY = (gr_fb_height() - (iconHeight+textHeight+40)) / 2; + iconX = (gr_fb_width() - iconWidth) / 2; + iconY = (gr_fb_height() - (iconHeight+textHeight+40+sh)) / 2; int textX = (gr_fb_width() - textWidth) / 2; - int textY = ((gr_fb_height() - (iconHeight+textHeight+40)) / 2) + iconHeight + 40; + int textY = ((gr_fb_height() - (iconHeight+textHeight+40+sh)) / 2) + iconHeight + 40; gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY); - if (icon == INSTALLING_UPDATE || icon == ERASING) { - draw_install_overlay_locked(installingFrame); + if (stageHeight > 0) { + int sw = gr_get_width(stageMarkerEmpty); + int x = (gr_fb_width() - max_stage * gr_get_width(stageMarkerEmpty)) / 2; + int y = iconY + iconHeight + 20; + for (int i = 0; i < max_stage; ++i) { + gr_blit((i < stage) ? stageMarkerFill : stageMarkerEmpty, + 0, 0, sw, stageHeight, x, y); + x += sw; + } } gr_color(255, 255, 255, 255); @@ -150,7 +143,8 @@ void ScreenRecoveryUI::draw_progress_locked() if (currentIcon == ERROR) return; if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { - draw_install_overlay_locked(installingFrame); + gr_surface icon = installation[installingFrame]; + gr_blit(icon, 0, 0, gr_get_width(icon), gr_get_height(icon), iconX, iconY); } if (progressBarType != EMPTY) { @@ -187,18 +181,6 @@ void ScreenRecoveryUI::draw_progress_locked() } } } - - if (progressBarType == INDETERMINATE) { - static int frame = 0; - gr_blit(progressBarIndeterminate[frame], 0, 0, width, height, dx, dy); - // in RTL locales, we run the animation backwards, which - // makes the spinner spin the other way. - if (rtl_locale) { - frame = (frame + indeterminate_frames - 1) % indeterminate_frames; - } else { - frame = (frame + 1) % indeterminate_frames; - } - } } } @@ -230,12 +212,12 @@ void ScreenRecoveryUI::SetColor(UIElement e) { // Should only be called with updateMutex locked. void ScreenRecoveryUI::draw_screen_locked() { - draw_background_locked(currentIcon); - draw_progress_locked(); - - if (show_text) { - SetColor(TEXT_FILL); - gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + if (!show_text) { + draw_background_locked(currentIcon); + draw_progress_locked(); + } else { + gr_color(0, 0, 0, 255); + gr_clear(); int y = 0; int i = 0; @@ -325,12 +307,6 @@ void ScreenRecoveryUI::progress_loop() { redraw = 1; } - // update the progress bar animation, if active - // skip this if we have a text overlay (too expensive to update) - if (progressBarType == INDETERMINATE && !show_text) { - redraw = 1; - } - // move the progress bar forward on timed intervals, if configured int duration = progressScopeDuration; if (progressBarType == DETERMINATE && duration > 0) { @@ -355,14 +331,21 @@ void ScreenRecoveryUI::progress_loop() { } void ScreenRecoveryUI::LoadBitmap(const char* filename, gr_surface* surface) { - int result = res_create_surface(filename, surface); + int result = res_create_display_surface(filename, surface); + if (result < 0) { + LOGE("missing bitmap %s\n(Code %d)\n", filename, result); + } +} + +void ScreenRecoveryUI::LoadBitmapArray(const char* filename, int* frames, gr_surface** surface) { + int result = res_create_multi_display_surface(filename, frames, surface); if (result < 0) { LOGE("missing bitmap %s\n(Code %d)\n", filename, result); } } void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, gr_surface* surface) { - int result = res_create_localized_surface(filename, surface); + int result = res_create_localized_alpha_surface(filename, locale, surface); if (result < 0) { LOGE("missing bitmap %s\n(Code %d)\n", filename, result); } @@ -382,51 +365,31 @@ void ScreenRecoveryUI::Init() text_cols = gr_fb_width() / char_width; if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1; - LoadBitmap("icon_installing", &backgroundIcon[INSTALLING_UPDATE]); + backgroundIcon[NONE] = NULL; + LoadBitmapArray("icon_installing", &installing_frames, &installation); + backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : NULL; backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE]; LoadBitmap("icon_error", &backgroundIcon[ERROR]); backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; LoadBitmap("progress_empty", &progressBarEmpty); LoadBitmap("progress_fill", &progressBarFill); + LoadBitmap("stage_empty", &stageMarkerEmpty); + LoadBitmap("stage_fill", &stageMarkerFill); LoadLocalizedBitmap("installing_text", &backgroundText[INSTALLING_UPDATE]); LoadLocalizedBitmap("erasing_text", &backgroundText[ERASING]); LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]); LoadLocalizedBitmap("error_text", &backgroundText[ERROR]); - int i; - - progressBarIndeterminate = (gr_surface*)malloc(indeterminate_frames * - sizeof(gr_surface)); - for (i = 0; i < indeterminate_frames; ++i) { - char filename[40]; - // "indeterminate01.png", "indeterminate02.png", ... - sprintf(filename, "indeterminate%02d", i+1); - LoadBitmap(filename, progressBarIndeterminate+i); - } - - if (installing_frames > 0) { - installationOverlay = (gr_surface*)malloc(installing_frames * - sizeof(gr_surface)); - for (i = 0; i < installing_frames; ++i) { - char filename[40]; - // "icon_installing_overlay01.png", - // "icon_installing_overlay02.png", ... - sprintf(filename, "icon_installing_overlay%02d", i+1); - LoadBitmap(filename, installationOverlay+i); - } - } else { - installationOverlay = NULL; - } - pthread_create(&progress_t, NULL, progress_thread, NULL); RecoveryUI::Init(); } -void ScreenRecoveryUI::SetLocale(const char* locale) { - if (locale) { +void ScreenRecoveryUI::SetLocale(const char* new_locale) { + if (new_locale) { + this->locale = new_locale; char* lang = strdup(locale); for (char* p = lang; *p; ++p) { if (*p == '_') { @@ -445,6 +408,8 @@ void ScreenRecoveryUI::SetLocale(const char* locale) { rtl_locale = true; } free(lang); + } else { + new_locale = NULL; } } @@ -452,16 +417,6 @@ void ScreenRecoveryUI::SetBackground(Icon icon) { pthread_mutex_lock(&updateMutex); - // Adjust the offset to account for the positioning of the - // base image on the screen. - if (backgroundIcon[icon] != NULL) { - gr_surface bg = backgroundIcon[icon]; - gr_surface text = backgroundText[icon]; - overlay_offset_x = install_overlay_offset_x + (gr_fb_width() - gr_get_width(bg)) / 2; - overlay_offset_y = install_overlay_offset_y + - (gr_fb_height() - (gr_get_height(bg) + gr_get_height(text) + 40)) / 2; - } - currentIcon = icon; update_screen_locked(); @@ -506,7 +461,7 @@ void ScreenRecoveryUI::SetProgress(float fraction) if (fraction > 1.0) fraction = 1.0; if (progressBarType == DETERMINATE && fraction > progress) { // Skip updates that aren't visibly different. - int width = gr_get_width(progressBarIndeterminate[0]); + int width = gr_get_width(progressBarEmpty); float scale = width * progressScopeSize; if ((int) (progress * scale) != (int) (fraction * scale)) { progress = fraction; @@ -516,6 +471,13 @@ void ScreenRecoveryUI::SetProgress(float fraction) pthread_mutex_unlock(&updateMutex); } +void ScreenRecoveryUI::SetStage(int current, int max) { + pthread_mutex_lock(&updateMutex); + stage = current; + max_stage = max; + pthread_mutex_unlock(&updateMutex); +} + void ScreenRecoveryUI::Print(const char *fmt, ...) { char buf[256]; diff --git a/screen_ui.h b/screen_ui.h index fc35d95b6..01a33bfe2 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -39,6 +39,8 @@ class ScreenRecoveryUI : public RecoveryUI { void ShowProgress(float portion, float seconds); void SetProgress(float fraction); + void SetStage(int current, int max); + // text log void ShowText(bool visible); bool IsTextVisible(); @@ -58,21 +60,20 @@ class ScreenRecoveryUI : public RecoveryUI { enum UIElement { HEADER, MENU, MENU_SEL_BG, MENU_SEL_FG, LOG, TEXT_FILL }; virtual void SetColor(UIElement e); - protected: - int install_overlay_offset_x, install_overlay_offset_y; - private: Icon currentIcon; int installingFrame; + const char* locale; bool rtl_locale; pthread_mutex_t updateMutex; gr_surface backgroundIcon[5]; gr_surface backgroundText[5]; - gr_surface *installationOverlay; - gr_surface *progressBarIndeterminate; + gr_surface *installation; gr_surface progressBarEmpty; gr_surface progressBarFill; + gr_surface stageMarkerEmpty; + gr_surface stageMarkerFill; ProgressType progressBarType; @@ -100,11 +101,14 @@ class ScreenRecoveryUI : public RecoveryUI { pthread_t progress_t; int animation_fps; - int indeterminate_frames; int installing_frames; - int overlay_offset_x, overlay_offset_y; + protected: + private: + + int iconX, iconY; + + int stage, max_stage; - void draw_install_overlay_locked(int frame); void draw_background_locked(Icon icon); void draw_progress_locked(); void draw_screen_locked(); @@ -114,6 +118,7 @@ class ScreenRecoveryUI : public RecoveryUI { void progress_loop(); void LoadBitmap(const char* filename, gr_surface* surface); + void LoadBitmapArray(const char* filename, int* frames, gr_surface** surface); void LoadLocalizedBitmap(const char* filename, gr_surface* surface); }; diff --git a/testdata/otasigned_ecdsa_sha256.zip b/testdata/otasigned_ecdsa_sha256.zip Binary files differnew file mode 100644 index 000000000..999fcdd0f --- /dev/null +++ b/testdata/otasigned_ecdsa_sha256.zip diff --git a/testdata/testkey_ecdsa.pk8 b/testdata/testkey_ecdsa.pk8 Binary files differnew file mode 100644 index 000000000..9a521c8cf --- /dev/null +++ b/testdata/testkey_ecdsa.pk8 diff --git a/testdata/testkey_ecdsa.x509.pem b/testdata/testkey_ecdsa.x509.pem new file mode 100644 index 000000000..b12283645 --- /dev/null +++ b/testdata/testkey_ecdsa.x509.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBezCCASACCQC4g5wurPSmtzAKBggqhkjOPQQDAjBFMQswCQYDVQQGEwJBVTET +MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMB4XDTEzMTAwODIxMTAxM1oXDTE0MTAwODIxMTAxM1owRTELMAkGA1UE +BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp +ZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGcO1QDowF2E +RboWVmAYI2oXTr5MHAJ4xpMUFsrWVvoktYSN2RhNuOl5jZGvSBsQII9p/4qfjLmS +TBaCfQ0Xmt4wCgYIKoZIzj0EAwIDSQAwRgIhAIJjWmZAwngc2VcHUhYp2oSLoCQ+ +P+7AtbAn5242AqfOAiEAghO0t6jTKs0LUhLJrQwbOkHyZMVdZaG2vcwV9y9H5Qc= +-----END CERTIFICATE----- diff --git a/tests/Android.mk b/tests/Android.mk new file mode 100644 index 000000000..4d99d5249 --- /dev/null +++ b/tests/Android.mk @@ -0,0 +1,26 @@ +# Build the unit tests. +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +# Build the unit tests. +test_src_files := \ + asn1_decoder_test.cpp + +shared_libraries := \ + liblog \ + libcutils + +static_libraries := \ + libgtest \ + libgtest_main \ + libverifier + +$(foreach file,$(test_src_files), \ + $(eval include $(CLEAR_VARS)) \ + $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \ + $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \ + $(eval LOCAL_SRC_FILES := $(file)) \ + $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \ + $(eval LOCAL_C_INCLUDES := $(LOCAL_PATH)/..) \ + $(eval include $(BUILD_NATIVE_TEST)) \ +)
\ No newline at end of file diff --git a/tests/asn1_decoder_test.cpp b/tests/asn1_decoder_test.cpp new file mode 100644 index 000000000..af96d87d2 --- /dev/null +++ b/tests/asn1_decoder_test.cpp @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "asn1_decoder_test" + +#include <cutils/log.h> +#include <gtest/gtest.h> +#include <stdint.h> +#include <unistd.h> + +#include "asn1_decoder.h" + +namespace android { + +class Asn1DecoderTest : public testing::Test { +}; + +TEST_F(Asn1DecoderTest, Empty_Failure) { + uint8_t empty[] = { }; + asn1_context_t* ctx = asn1_context_new(empty, sizeof(empty)); + + EXPECT_EQ(NULL, asn1_constructed_get(ctx)); + EXPECT_FALSE(asn1_constructed_skip_all(ctx)); + EXPECT_EQ(0, asn1_constructed_type(ctx)); + EXPECT_EQ(NULL, asn1_sequence_get(ctx)); + EXPECT_EQ(NULL, asn1_set_get(ctx)); + EXPECT_FALSE(asn1_sequence_next(ctx)); + + uint8_t* junk; + size_t length; + EXPECT_FALSE(asn1_oid_get(ctx, &junk, &length)); + EXPECT_FALSE(asn1_octet_string_get(ctx, &junk, &length)); + + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, ConstructedGet_TruncatedLength_Failure) { + uint8_t truncated[] = { 0xA0, 0x82, }; + asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated)); + EXPECT_EQ(NULL, asn1_constructed_get(ctx)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, ConstructedGet_LengthTooBig_Failure) { + uint8_t truncated[] = { 0xA0, 0x8a, 0xA5, 0x5A, 0xA5, 0x5A, + 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, }; + asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated)); + EXPECT_EQ(NULL, asn1_constructed_get(ctx)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, ConstructedGet_TooSmallForChild_Failure) { + uint8_t data[] = { 0xA5, 0x02, 0x06, 0x01, 0x01, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + asn1_context_t* ptr = asn1_constructed_get(ctx); + ASSERT_NE((asn1_context_t*)NULL, ptr); + EXPECT_EQ(5, asn1_constructed_type(ptr)); + uint8_t* oid; + size_t length; + EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length)); + asn1_context_free(ptr); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, ConstructedGet_Success) { + uint8_t data[] = { 0xA5, 0x03, 0x06, 0x01, 0x01, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + asn1_context_t* ptr = asn1_constructed_get(ctx); + ASSERT_NE((asn1_context_t*)NULL, ptr); + EXPECT_EQ(5, asn1_constructed_type(ptr)); + uint8_t* oid; + size_t length; + ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length)); + EXPECT_EQ(1U, length); + EXPECT_EQ(0x01U, *oid); + asn1_context_free(ptr); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, ConstructedSkipAll_TruncatedLength_Failure) { + uint8_t truncated[] = { 0xA2, 0x82, }; + asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated)); + EXPECT_FALSE(asn1_constructed_skip_all(ctx)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, ConstructedSkipAll_Success) { + uint8_t data[] = { 0xA0, 0x03, 0x02, 0x01, 0x01, + 0xA1, 0x03, 0x02, 0x01, 0x01, + 0x06, 0x01, 0xA5, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + ASSERT_TRUE(asn1_constructed_skip_all(ctx)); + uint8_t* oid; + size_t length; + ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length)); + EXPECT_EQ(1U, length); + EXPECT_EQ(0xA5U, *oid); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, SequenceGet_TruncatedLength_Failure) { + uint8_t truncated[] = { 0x30, 0x82, }; + asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated)); + EXPECT_EQ(NULL, asn1_sequence_get(ctx)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, SequenceGet_TooSmallForChild_Failure) { + uint8_t data[] = { 0x30, 0x02, 0x06, 0x01, 0x01, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + asn1_context_t* ptr = asn1_sequence_get(ctx); + ASSERT_NE((asn1_context_t*)NULL, ptr); + uint8_t* oid; + size_t length; + EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length)); + asn1_context_free(ptr); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, SequenceGet_Success) { + uint8_t data[] = { 0x30, 0x03, 0x06, 0x01, 0x01, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + asn1_context_t* ptr = asn1_sequence_get(ctx); + ASSERT_NE((asn1_context_t*)NULL, ptr); + uint8_t* oid; + size_t length; + ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length)); + EXPECT_EQ(1U, length); + EXPECT_EQ(0x01U, *oid); + asn1_context_free(ptr); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, SetGet_TruncatedLength_Failure) { + uint8_t truncated[] = { 0x31, 0x82, }; + asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated)); + EXPECT_EQ(NULL, asn1_set_get(ctx)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, SetGet_TooSmallForChild_Failure) { + uint8_t data[] = { 0x31, 0x02, 0x06, 0x01, 0x01, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + asn1_context_t* ptr = asn1_set_get(ctx); + ASSERT_NE((asn1_context_t*)NULL, ptr); + uint8_t* oid; + size_t length; + EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length)); + asn1_context_free(ptr); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, SetGet_Success) { + uint8_t data[] = { 0x31, 0x03, 0x06, 0x01, 0xBA, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + asn1_context_t* ptr = asn1_set_get(ctx); + ASSERT_NE((asn1_context_t*)NULL, ptr); + uint8_t* oid; + size_t length; + ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length)); + EXPECT_EQ(1U, length); + EXPECT_EQ(0xBAU, *oid); + asn1_context_free(ptr); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, OidGet_LengthZero_Failure) { + uint8_t data[] = { 0x06, 0x00, 0x01, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + uint8_t* oid; + size_t length; + EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, OidGet_TooSmall_Failure) { + uint8_t data[] = { 0x06, 0x01, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + uint8_t* oid; + size_t length; + EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, OidGet_Success) { + uint8_t data[] = { 0x06, 0x01, 0x99, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + uint8_t* oid; + size_t length; + ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length)); + EXPECT_EQ(1U, length); + EXPECT_EQ(0x99U, *oid); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, OctetStringGet_LengthZero_Failure) { + uint8_t data[] = { 0x04, 0x00, 0x55, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + uint8_t* string; + size_t length; + ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, OctetStringGet_TooSmall_Failure) { + uint8_t data[] = { 0x04, 0x01, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + uint8_t* string; + size_t length; + ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length)); + asn1_context_free(ctx); +} + +TEST_F(Asn1DecoderTest, OctetStringGet_Success) { + uint8_t data[] = { 0x04, 0x01, 0xAA, }; + asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); + uint8_t* string; + size_t length; + ASSERT_TRUE(asn1_octet_string_get(ctx, &string, &length)); + EXPECT_EQ(1U, length); + EXPECT_EQ(0xAAU, *string); + asn1_context_free(ctx); +} + +} // namespace android diff --git a/tools/ota/add-property-tag.c b/tools/ota/add-property-tag.c index 5277edd9c..aab30b2d0 100644 --- a/tools/ota/add-property-tag.c +++ b/tools/ota/add-property-tag.c @@ -57,9 +57,9 @@ 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", end - line, line, tag, number, end); + fprintf(out, "%.*s%s%d%s", (int)(end - line), line, tag, number, end); } else { - fprintf(out, "%.*s%s%s", end - line, line, tag, end); + fprintf(out, "%.*s%s%s", (int)(end - line), line, tag, end); } } @@ -91,7 +91,7 @@ int main(int argc, char **argv) { property_set("ro.twrp.version", TW_VERSION_STR); time_t StartupTime = time(NULL); - printf("Starting TWRP %s on %s", TW_VERSION_STR, ctime(&StartupTime)); + printf("Starting TWRP %s on %s (pid %d)", TW_VERSION_STR, ctime(&StartupTime), getpid()); // Load default values to set DataManager constants and handle ifdefs DataManager::SetDefaultValues(); @@ -160,7 +160,7 @@ int main(int argc, char **argv) { PartitionManager.Mount_By_Path("/cache", true); string Zip_File, Reboot_Value; - bool Cache_Wipe = false, Factory_Reset = false, Perform_Backup = false; + bool Cache_Wipe = false, Factory_Reset = false, Perform_Backup = false, Shutdown = false; { TWPartition* misc = PartitionManager.Find_Partition_By_Path("/misc"); @@ -206,6 +206,8 @@ int main(int argc, char **argv) { Cache_Wipe = true; } else if (*argptr == 'n') { Perform_Backup = true; + } else if (*argptr == 'p') { + Shutdown = true; } else if (*argptr == 's') { ptr = argptr; index2 = 0; @@ -33,6 +33,7 @@ #endif #include "common.h" +#include "roots.h" #include "device.h" #include "minui/minui.h" #include "screen_ui.h" @@ -49,10 +50,15 @@ RecoveryUI::RecoveryUI() : key_queue_len(0), key_last_down(-1), key_long_press(false), - key_down_count(0) { + key_down_count(0), + enable_reboot(true), + consecutive_power_keys(0), + consecutive_alternate_keys(0), + last_key(-1) { pthread_mutex_init(&key_queue_mutex, NULL); pthread_cond_init(&key_queue_cond, NULL); self = this; + memset(key_pressed, 0, sizeof(key_pressed)); } void RecoveryUI::Init() { @@ -61,12 +67,12 @@ void RecoveryUI::Init() { } -int RecoveryUI::input_callback(int fd, short revents, void* data) +int RecoveryUI::input_callback(int fd, uint32_t epevents, void* data) { struct input_event ev; int ret; - ret = ev_get_input(fd, revents, &ev); + ret = ev_get_input(fd, epevents, &ev); if (ret) return -1; @@ -114,6 +120,7 @@ int RecoveryUI::input_callback(int fd, short revents, void* data) void RecoveryUI::process_key(int key_code, int updown) { bool register_key = false; bool long_press = false; + bool reboot_enabled; pthread_mutex_lock(&key_queue_mutex); key_pressed[key_code] = updown; @@ -135,6 +142,7 @@ void RecoveryUI::process_key(int key_code, int updown) { } key_last_down = -1; } + reboot_enabled = enable_reboot; pthread_mutex_unlock(&key_queue_mutex); if (register_key) { @@ -149,13 +157,22 @@ void RecoveryUI::process_key(int key_code, int updown) { case RecoveryUI::REBOOT: #ifdef ANDROID_RB_RESTART - android_reboot(ANDROID_RB_RESTART, 0, 0); + if (reboot_enabled) { + android_reboot(ANDROID_RB_RESTART, 0, 0); + } #endif break; case RecoveryUI::ENQUEUE: EnqueueKey(key_code); break; + + case RecoveryUI::MOUNT_SYSTEM: +#ifndef NO_RECOVERY_MOUNT + ensure_path_mounted("/system"); + Print("Mounted /system."); +#endif + break; } } } @@ -262,8 +279,47 @@ void RecoveryUI::FlushKeys() { pthread_mutex_unlock(&key_queue_mutex); } +// The default CheckKey implementation assumes the device has power, +// volume up, and volume down keys. +// +// - Hold power and press vol-up to toggle display. +// - Press power seven times in a row to reboot. +// - Alternate vol-up and vol-down seven times to mount /system. RecoveryUI::KeyAction RecoveryUI::CheckKey(int key) { - return RecoveryUI::ENQUEUE; + if ((IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) || key == KEY_HOME) { + return TOGGLE; + } + + if (key == KEY_POWER) { + pthread_mutex_lock(&key_queue_mutex); + bool reboot_enabled = enable_reboot; + pthread_mutex_unlock(&key_queue_mutex); + + if (reboot_enabled) { + ++consecutive_power_keys; + if (consecutive_power_keys >= 7) { + return REBOOT; + } + } + } else { + consecutive_power_keys = 0; + } + + if ((key == KEY_VOLUMEUP && + (last_key == KEY_VOLUMEDOWN || last_key == -1)) || + (key == KEY_VOLUMEDOWN && + (last_key == KEY_VOLUMEUP || last_key == -1))) { + ++consecutive_alternate_keys; + if (consecutive_alternate_keys >= 7) { + consecutive_alternate_keys = 0; + return MOUNT_SYSTEM; + } + } else { + consecutive_alternate_keys = 0; + } + last_key = key; + + return ENQUEUE; } void RecoveryUI::NextCheckKeyIsLong(bool is_long_press) { @@ -271,3 +327,9 @@ void RecoveryUI::NextCheckKeyIsLong(bool is_long_press) { void RecoveryUI::KeyLongPress(int key) { } + +void RecoveryUI::SetEnableReboot(bool enabled) { + pthread_mutex_lock(&key_queue_mutex); + enable_reboot = enabled; + pthread_mutex_unlock(&key_queue_mutex); +} @@ -30,6 +30,8 @@ class RecoveryUI { // Initialize the object; called before anything else. virtual void Init(); + // Show a stage indicator. Call immediately after Init(). + virtual void SetStage(int current, int max) { } // After calling Init(), you can tell the UI what locale it is operating in. virtual void SetLocale(const char* locale) { } @@ -77,7 +79,7 @@ class RecoveryUI { // Return value indicates whether an immediate operation should be // triggered (toggling the display, rebooting the device), or if // the key should be enqueued for use by the main thread. - enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE }; + enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE, MOUNT_SYSTEM }; virtual KeyAction CheckKey(int key); // Called immediately before each call to CheckKey(), tell you if @@ -91,6 +93,13 @@ class RecoveryUI { // be called with "true". virtual void KeyLongPress(int key); + // Normally in recovery there's a key sequence that triggers + // immediate reboot of the device, regardless of what recovery is + // doing (with the default CheckKey implementation, it's pressing + // the power button 7 times in row). Call this to enable or + // disable that feature. It is enabled by default. + virtual void SetEnableReboot(bool enabled); + // --- menu display --- // Display some header text followed by a menu of items, which appears @@ -119,8 +128,13 @@ private: int key_last_down; // under key_queue_mutex bool key_long_press; // under key_queue_mutex int key_down_count; // under key_queue_mutex + bool enable_reboot; // under key_queue_mutex int rel_sum; + int consecutive_power_keys; + int consecutive_alternate_keys; + int last_key; + typedef struct { RecoveryUI* ui; int key_code; @@ -130,7 +144,7 @@ private: pthread_t input_t; static void* input_thread(void* cookie); - static int input_callback(int fd, short revents, void* data); + static int input_callback(int fd, uint32_t epevents, void* data); void process_key(int key_code, int updown); bool usb_connected(); diff --git a/minelf/Android.mk b/uncrypt/Android.mk index 10818eae4..b8755fb08 100644 --- a/minelf/Android.mk +++ b/uncrypt/Android.mk @@ -1,4 +1,4 @@ -# Copyright (C) 2009 The Android Open Source Project +# Copyright (C) 2014 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,15 +13,14 @@ # limitations under the License. LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_SRC_FILES := \ - Retouch.c +include $(CLEAR_VARS) LOCAL_C_INCLUDES += $(commands_recovery_local_path) +LOCAL_SRC_FILES := uncrypt.c -LOCAL_MODULE := libminelf +LOCAL_MODULE := uncrypt -LOCAL_CFLAGS += -Wall +LOCAL_STATIC_LIBRARIES := libfs_mgr liblog libcutils -include $(BUILD_STATIC_LIBRARY) +include $(BUILD_EXECUTABLE) diff --git a/uncrypt/uncrypt.c b/uncrypt/uncrypt.c new file mode 100644 index 000000000..189fa57e1 --- /dev/null +++ b/uncrypt/uncrypt.c @@ -0,0 +1,427 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This program takes a file on an ext4 filesystem and produces a list +// of the blocks that file occupies, which enables the file contents +// to be read directly from the block device without mounting the +// filesystem. +// +// If the filesystem is using an encrypted block device, it will also +// read the file and rewrite it to the same blocks of the underlying +// (unencrypted) block device, so the file contents can be read +// without the need for the decryption key. +// +// The output of this program is a "block map" which looks like this: +// +// /dev/block/platform/msm_sdcc.1/by-name/userdata # block device +// 49652 4096 # file size in bytes, block size +// 3 # count of block ranges +// 1000 1008 # block range 0 +// 2100 2102 # ... block range 1 +// 30 33 # ... block range 2 +// +// Each block range represents a half-open interval; the line "30 33" +// reprents the blocks [30, 31, 32]. +// +// Recovery can take this block map file and retrieve the underlying +// file data to use as an update package. + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <linux/fs.h> +#include <sys/mman.h> + +#define LOG_TAG "uncrypt" +#include <log/log.h> +#include <cutils/properties.h> +#include <fs_mgr.h> + +#define WINDOW_SIZE 5 +#define RECOVERY_COMMAND_FILE "/cache/recovery/command" +#define RECOVERY_COMMAND_FILE_TMP "/cache/recovery/command.tmp" +#define CACHE_BLOCK_MAP "/cache/recovery/block.map" + +static struct fstab* fstab = NULL; + +static int write_at_offset(unsigned char* buffer, size_t size, + int wfd, off64_t offset) +{ + lseek64(wfd, offset, SEEK_SET); + size_t written = 0; + while (written < size) { + ssize_t wrote = write(wfd, buffer + written, size - written); + if (wrote < 0) { + ALOGE("error writing offset %lld: %s\n", offset, strerror(errno)); + return -1; + } + written += wrote; + } + return 0; +} + +void add_block_to_ranges(int** ranges, int* range_alloc, int* range_used, int new_block) +{ + // If the current block start is < 0, set the start to the new + // block. (This only happens for the very first block of the very + // first range.) + if ((*ranges)[*range_used*2-2] < 0) { + (*ranges)[*range_used*2-2] = new_block; + (*ranges)[*range_used*2-1] = new_block; + } + + if (new_block == (*ranges)[*range_used*2-1]) { + // If the new block comes immediately after the current range, + // all we have to do is extend the current range. + ++(*ranges)[*range_used*2-1]; + } else { + // We need to start a new range. + + // If there isn't enough room in the array, we need to expand it. + if (*range_used >= *range_alloc) { + *range_alloc *= 2; + *ranges = realloc(*ranges, *range_alloc * 2 * sizeof(int)); + } + + ++*range_used; + (*ranges)[*range_used*2-2] = new_block; + (*ranges)[*range_used*2-1] = new_block+1; + } +} + +static struct fstab* read_fstab() +{ + fstab = NULL; + + // The fstab path is always "/fstab.${ro.hardware}". + char fstab_path[PATH_MAX+1] = "/fstab."; + if (!property_get("ro.hardware", fstab_path+strlen(fstab_path), "")) { + ALOGE("failed to get ro.hardware\n"); + return NULL; + } + + fstab = fs_mgr_read_fstab(fstab_path); + if (!fstab) { + ALOGE("failed to read %s\n", fstab_path); + return NULL; + } + + return fstab; +} + +const char* find_block_device(const char* path, int* encryptable, int* encrypted) +{ + // Look for a volume whose mount point is the prefix of path and + // return its block device. Set encrypted if it's currently + // encrypted. + int i; + for (i = 0; i < fstab->num_entries; ++i) { + struct fstab_rec* v = &fstab->recs[i]; + if (!v->mount_point) continue; + int len = strlen(v->mount_point); + if (strncmp(path, v->mount_point, len) == 0 && + (path[len] == '/' || path[len] == 0)) { + *encrypted = 0; + *encryptable = 0; + if (fs_mgr_is_encryptable(v)) { + *encryptable = 1; + char buffer[PROPERTY_VALUE_MAX+1]; + if (property_get("ro.crypto.state", buffer, "") && + strcmp(buffer, "encrypted") == 0) { + *encrypted = 1; + } + } + return v->blk_device; + } + } + + return NULL; +} + +char* parse_recovery_command_file() +{ + char* fn = NULL; + int count = 0; + char temp[1024]; + + FILE* f = fopen(RECOVERY_COMMAND_FILE, "r"); + if (f == NULL) { + return NULL; + } + FILE* fo = fopen(RECOVERY_COMMAND_FILE_TMP, "w"); + + while (fgets(temp, sizeof(temp), f)) { + printf("read: %s", temp); + if (strncmp(temp, "--update_package=/data/", strlen("--update_package=/data/")) == 0) { + fn = strdup(temp + strlen("--update_package=")); + strcpy(temp, "--update_package=@" CACHE_BLOCK_MAP "\n"); + } + fputs(temp, fo); + } + fclose(f); + fclose(fo); + + if (fn) { + char* newline = strchr(fn, '\n'); + if (newline) *newline = 0; + } + return fn; +} + +int produce_block_map(const char* path, const char* map_file, const char* blk_dev, + int encrypted) +{ + struct stat sb; + int ret; + + FILE* mapf = fopen(map_file, "w"); + + ret = stat(path, &sb); + if (ret != 0) { + ALOGE("failed to stat %s\n", path); + return -1; + } + + ALOGI(" block size: %ld bytes\n", (long)sb.st_blksize); + + int blocks = ((sb.st_size-1) / sb.st_blksize) + 1; + ALOGI(" file size: %lld bytes, %d blocks\n", (long long)sb.st_size, blocks); + + int* ranges; + int range_alloc = 1; + int range_used = 1; + ranges = malloc(range_alloc * 2 * sizeof(int)); + ranges[0] = -1; + ranges[1] = -1; + + fprintf(mapf, "%s\n%lld %lu\n", blk_dev, (long long)sb.st_size, (unsigned long)sb.st_blksize); + + unsigned char* buffers[WINDOW_SIZE]; + int i; + if (encrypted) { + for (i = 0; i < WINDOW_SIZE; ++i) { + buffers[i] = malloc(sb.st_blksize); + } + } + int head_block = 0; + int head = 0, tail = 0; + size_t pos = 0; + + int fd = open(path, O_RDONLY); + if (fd < 0) { + ALOGE("failed to open fd for reading: %s\n", strerror(errno)); + return -1; + } + fsync(fd); + + int wfd = -1; + if (encrypted) { + wfd = open(blk_dev, O_WRONLY); + if (wfd < 0) { + ALOGE("failed to open fd for writing: %s\n", strerror(errno)); + return -1; + } + } + + while (pos < sb.st_size) { + if ((tail+1) % WINDOW_SIZE == head) { + // write out head buffer + int block = head_block; + ret = ioctl(fd, FIBMAP, &block); + if (ret != 0) { + ALOGE("failed to find block %d\n", head_block); + return -1; + } + add_block_to_ranges(&ranges, &range_alloc, &range_used, block); + if (encrypted) { + if (write_at_offset(buffers[head], sb.st_blksize, wfd, (off64_t)sb.st_blksize * block) != 0) { + return -1; + } + } + head = (head + 1) % WINDOW_SIZE; + ++head_block; + } + + // read next block to tail + if (encrypted) { + size_t so_far = 0; + while (so_far < sb.st_blksize && pos < sb.st_size) { + ssize_t this_read = read(fd, buffers[tail] + so_far, sb.st_blksize - so_far); + if (this_read < 0) { + ALOGE("failed to read: %s\n", strerror(errno)); + return -1; + } + so_far += this_read; + pos += this_read; + } + } else { + // If we're not encrypting; we don't need to actually read + // anything, just skip pos forward as if we'd read a + // block. + pos += sb.st_blksize; + } + tail = (tail+1) % WINDOW_SIZE; + } + + while (head != tail) { + // write out head buffer + int block = head_block; + ret = ioctl(fd, FIBMAP, &block); + if (ret != 0) { + ALOGE("failed to find block %d\n", head_block); + return -1; + } + add_block_to_ranges(&ranges, &range_alloc, &range_used, block); + if (encrypted) { + if (write_at_offset(buffers[head], sb.st_blksize, wfd, (off64_t)sb.st_blksize * block) != 0) { + return -1; + } + } + head = (head + 1) % WINDOW_SIZE; + ++head_block; + } + + fprintf(mapf, "%d\n", range_used); + for (i = 0; i < range_used; ++i) { + fprintf(mapf, "%d %d\n", ranges[i*2], ranges[i*2+1]); + } + + fclose(mapf); + close(fd); + if (encrypted) { + close(wfd); + } + + return 0; +} + +void wipe_misc() { + ALOGI("removing old commands from misc"); + int i; + for (i = 0; i < fstab->num_entries; ++i) { + struct fstab_rec* v = &fstab->recs[i]; + if (!v->mount_point) continue; + if (strcmp(v->mount_point, "/misc") == 0) { + int fd = open(v->blk_device, O_WRONLY); + uint8_t zeroes[1088]; // sizeof(bootloader_message) from recovery + memset(zeroes, 0, sizeof(zeroes)); + + size_t written = 0; + size_t size = sizeof(zeroes); + while (written < size) { + ssize_t w = write(fd, zeroes, size-written); + if (w < 0 && errno != EINTR) { + ALOGE("zero write failed: %s\n", strerror(errno)); + return; + } else { + written += w; + } + } + + close(fd); + } + } +} + +void reboot_to_recovery() { + ALOGI("rebooting to recovery"); + property_set("sys.powerctl", "reboot,recovery"); + sleep(10); + ALOGE("reboot didn't succeed?"); +} + +int main(int argc, char** argv) +{ + const char* input_path; + const char* map_file; + int do_reboot = 1; + + if (argc != 1 && argc != 3) { + fprintf(stderr, "usage: %s [<transform_path> <map_file>]\n", argv[0]); + return 2; + } + + if (argc == 3) { + // when command-line args are given this binary is being used + // for debugging; don't reboot to recovery at the end. + input_path = argv[1]; + map_file = argv[2]; + do_reboot = 0; + } else { + input_path = parse_recovery_command_file(); + if (input_path == NULL) { + // if we're rebooting to recovery without a package (say, + // to wipe data), then we don't need to do anything before + // going to recovery. + ALOGI("no recovery command file or no update package arg"); + reboot_to_recovery(); + return 1; + } + map_file = CACHE_BLOCK_MAP; + } + + ALOGI("update package is %s", input_path); + + // Turn the name of the file we're supposed to convert into an + // absolute path, so we can find what filesystem it's on. + char path[PATH_MAX+1]; + if (realpath(input_path, path) == NULL) { + ALOGE("failed to convert %s to absolute path: %s", input_path, strerror(errno)); + return 1; + } + + int encryptable; + int encrypted; + if (read_fstab() == NULL) { + return 1; + } + const char* blk_dev = find_block_device(path, &encryptable, &encrypted); + if (blk_dev == NULL) { + ALOGE("failed to find block device for %s", path); + return 1; + } + + // If the filesystem it's on isn't encrypted, we only produce the + // block map, we don't rewrite the file contents (it would be + // pointless to do so). + ALOGI("encryptable: %s\n", encryptable ? "yes" : "no"); + ALOGI(" encrypted: %s\n", encrypted ? "yes" : "no"); + + // Recovery supports installing packages from 3 paths: /cache, + // /data, and /sdcard. (On a particular device, other locations + // may work, but those are three we actually expect.) + // + // On /data we want to convert the file to a block map so that we + // can read the package without mounting the partition. On /cache + // and /sdcard we leave the file alone. + if (strncmp(path, "/data/", 6) != 0) { + // path does not start with "/data/"; leave it alone. + unlink(RECOVERY_COMMAND_FILE_TMP); + } else { + ALOGI("writing block map %s", map_file); + if (produce_block_map(path, map_file, blk_dev, encrypted) != 0) { + return 1; + } + } + + wipe_misc(); + rename(RECOVERY_COMMAND_FILE_TMP, RECOVERY_COMMAND_FILE); + if (do_reboot) reboot_to_recovery(); + return 0; +} diff --git a/updater/Android.mk b/updater/Android.mk index d86fb9e5f..5c2896963 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -4,6 +4,7 @@ LOCAL_PATH := $(call my-dir) updater_src_files := \ install.c \ + blockimg.c \ updater.c # @@ -20,6 +21,7 @@ LOCAL_SRC_FILES := $(updater_src_files) ifeq ($(TARGET_USERIMAGES_USE_EXT4), true) LOCAL_CFLAGS += -DUSE_EXT4 +LOCAL_CFLAGS += -Wno-unused-parameter LOCAL_C_INCLUDES += system/extras/ext4_utils LOCAL_STATIC_LIBRARIES += \ libext4_utils \ diff --git a/updater/MODULE_LICENSE_GPL b/updater/MODULE_LICENSE_GPL new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/updater/MODULE_LICENSE_GPL diff --git a/updater/NOTICE b/updater/NOTICE new file mode 100644 index 000000000..e77696ae8 --- /dev/null +++ b/updater/NOTICE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/updater/blockimg.c b/updater/blockimg.c new file mode 100644 index 000000000..c3319c973 --- /dev/null +++ b/updater/blockimg.c @@ -0,0 +1,645 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/ioctl.h> +#include <time.h> +#include <unistd.h> + +#include "applypatch/applypatch.h" +#include "edify/expr.h" +#include "mincrypt/sha.h" +#include "minzip/DirUtil.h" +#include "updater.h" + +#define BLOCKSIZE 4096 + +// Set this to 0 to interpret 'erase' transfers to mean do a +// BLKDISCARD ioctl (the normal behavior). Set to 1 to interpret +// erase to mean fill the region with zeroes. +#define DEBUG_ERASE 0 + +#ifndef BLKDISCARD +#define BLKDISCARD _IO(0x12,119) +#endif + +char* PrintSha1(const uint8_t* digest); + +typedef struct { + int count; + int size; + int pos[0]; +} RangeSet; + +static RangeSet* parse_range(char* text) { + char* save; + int num; + num = strtol(strtok_r(text, ",", &save), NULL, 0); + + RangeSet* out = malloc(sizeof(RangeSet) + num * sizeof(int)); + if (out == NULL) { + fprintf(stderr, "failed to allocate range of %lu bytes\n", + sizeof(RangeSet) + num * sizeof(int)); + exit(1); + } + out->count = num / 2; + out->size = 0; + int i; + for (i = 0; i < num; ++i) { + out->pos[i] = strtol(strtok_r(NULL, ",", &save), NULL, 0); + if (i%2) { + out->size += out->pos[i]; + } else { + out->size -= out->pos[i]; + } + } + + return out; +} + +static void readblock(int fd, uint8_t* data, size_t size) { + size_t so_far = 0; + while (so_far < size) { + ssize_t r = read(fd, data+so_far, size-so_far); + if (r < 0 && errno != EINTR) { + fprintf(stderr, "read failed: %s\n", strerror(errno)); + return; + } else { + so_far += r; + } + } +} + +static void writeblock(int fd, const uint8_t* data, size_t size) { + size_t written = 0; + while (written < size) { + ssize_t w = write(fd, data+written, size-written); + if (w < 0 && errno != EINTR) { + fprintf(stderr, "write failed: %s\n", strerror(errno)); + return; + } else { + written += w; + } + } +} + +static void check_lseek(int fd, off64_t offset, int whence) { + while (true) { + off64_t ret = lseek64(fd, offset, whence); + if (ret < 0) { + if (errno != EINTR) { + fprintf(stderr, "lseek64 failed: %s\n", strerror(errno)); + exit(1); + } + } else { + break; + } + } +} + +static void allocate(size_t size, uint8_t** buffer, size_t* buffer_alloc) { + // if the buffer's big enough, reuse it. + if (size <= *buffer_alloc) return; + + free(*buffer); + + *buffer = (uint8_t*) malloc(size); + if (*buffer == NULL) { + fprintf(stderr, "failed to allocate %zu bytes\n", size); + exit(1); + } + *buffer_alloc = size; +} + +typedef struct { + int fd; + RangeSet* tgt; + int p_block; + size_t p_remain; +} RangeSinkState; + +static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) { + RangeSinkState* rss = (RangeSinkState*) token; + + if (rss->p_remain <= 0) { + fprintf(stderr, "range sink write overrun"); + exit(1); + } + + ssize_t written = 0; + while (size > 0) { + size_t write_now = size; + if (rss->p_remain < write_now) write_now = rss->p_remain; + writeblock(rss->fd, data, write_now); + data += write_now; + size -= write_now; + + rss->p_remain -= write_now; + written += write_now; + + if (rss->p_remain == 0) { + // move to the next block + ++rss->p_block; + if (rss->p_block < rss->tgt->count) { + rss->p_remain = (rss->tgt->pos[rss->p_block*2+1] - rss->tgt->pos[rss->p_block*2]) * BLOCKSIZE; + check_lseek(rss->fd, (off64_t)rss->tgt->pos[rss->p_block*2] * BLOCKSIZE, SEEK_SET); + } else { + // we can't write any more; return how many bytes have + // been written so far. + return written; + } + } + } + + return written; +} + +// All of the data for all the 'new' transfers is contained in one +// file in the update package, concatenated together in the order in +// which transfers.list will need it. We want to stream it out of the +// archive (it's compressed) without writing it to a temp file, but we +// can't write each section until it's that transfer's turn to go. +// +// To achieve this, we expand the new data from the archive in a +// background thread, and block that threads 'receive uncompressed +// data' function until the main thread has reached a point where we +// want some new data to be written. We signal the background thread +// with the destination for the data and block the main thread, +// waiting for the background thread to complete writing that section. +// Then it signals the main thread to wake up and goes back to +// blocking waiting for a transfer. +// +// NewThreadInfo is the struct used to pass information back and forth +// between the two threads. When the main thread wants some data +// written, it sets rss to the destination location and signals the +// condition. When the background thread is done writing, it clears +// rss and signals the condition again. + +typedef struct { + ZipArchive* za; + const ZipEntry* entry; + + RangeSinkState* rss; + + pthread_mutex_t mu; + pthread_cond_t cv; +} NewThreadInfo; + +static bool receive_new_data(const unsigned char* data, int size, void* cookie) { + NewThreadInfo* nti = (NewThreadInfo*) cookie; + + while (size > 0) { + // Wait for nti->rss to be non-NULL, indicating some of this + // data is wanted. + pthread_mutex_lock(&nti->mu); + while (nti->rss == NULL) { + pthread_cond_wait(&nti->cv, &nti->mu); + } + pthread_mutex_unlock(&nti->mu); + + // At this point nti->rss is set, and we own it. The main + // thread is waiting for it to disappear from nti. + ssize_t written = RangeSinkWrite(data, size, nti->rss); + data += written; + size -= written; + + if (nti->rss->p_block == nti->rss->tgt->count) { + // we have written all the bytes desired by this rss. + + pthread_mutex_lock(&nti->mu); + nti->rss = NULL; + pthread_cond_broadcast(&nti->cv); + pthread_mutex_unlock(&nti->mu); + } + } + + return true; +} + +static void* unzip_new_data(void* cookie) { + NewThreadInfo* nti = (NewThreadInfo*) cookie; + mzProcessZipEntryContents(nti->za, nti->entry, receive_new_data, nti); + return NULL; +} + +// args: +// - block device (or file) to modify in-place +// - transfer list (blob) +// - new data stream (filename within package.zip) +// - patch stream (filename within package.zip, must be uncompressed) + +Value* BlockImageUpdateFn(const char* name, State* state, int argc, Expr* argv[]) { + Value* blockdev_filename; + Value* transfer_list_value; + char* transfer_list = NULL; + Value* new_data_fn; + Value* patch_data_fn; + bool success = false; + + if (ReadValueArgs(state, argv, 4, &blockdev_filename, &transfer_list_value, + &new_data_fn, &patch_data_fn) < 0) { + return NULL; + } + + if (blockdev_filename->type != VAL_STRING) { + ErrorAbort(state, "blockdev_filename argument to %s must be string", name); + goto done; + } + if (transfer_list_value->type != VAL_BLOB) { + ErrorAbort(state, "transfer_list argument to %s must be blob", name); + goto done; + } + if (new_data_fn->type != VAL_STRING) { + ErrorAbort(state, "new_data_fn argument to %s must be string", name); + goto done; + } + if (patch_data_fn->type != VAL_STRING) { + ErrorAbort(state, "patch_data_fn argument to %s must be string", name); + goto done; + } + + UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); + FILE* cmd_pipe = ui->cmd_pipe; + + ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; + + const ZipEntry* patch_entry = mzFindZipEntry(za, patch_data_fn->data); + if (patch_entry == NULL) { + ErrorAbort(state, "%s(): no file \"%s\" in package", name, patch_data_fn->data); + goto done; + } + + uint8_t* patch_start = ((UpdaterInfo*)(state->cookie))->package_zip_addr + + mzGetZipEntryOffset(patch_entry); + + const ZipEntry* new_entry = mzFindZipEntry(za, new_data_fn->data); + if (new_entry == NULL) { + ErrorAbort(state, "%s(): no file \"%s\" in package", name, new_data_fn->data); + goto done; + } + + // The transfer list is a text file containing commands to + // transfer data from one place to another on the target + // partition. We parse it and execute the commands in order: + // + // zero [rangeset] + // - fill the indicated blocks with zeros + // + // new [rangeset] + // - fill the blocks with data read from the new_data file + // + // bsdiff patchstart patchlen [src rangeset] [tgt rangeset] + // imgdiff patchstart patchlen [src rangeset] [tgt rangeset] + // - read the source blocks, apply a patch, write result to + // target blocks. bsdiff or imgdiff specifies the type of + // patch. + // + // move [src rangeset] [tgt rangeset] + // - copy data from source blocks to target blocks (no patch + // needed; rangesets are the same size) + // + // erase [rangeset] + // - mark the given blocks as empty + // + // The creator of the transfer list will guarantee that no block + // is read (ie, used as the source for a patch or move) after it + // has been written. + // + // Within one command the source and target ranges may overlap so + // in general we need to read the entire source into memory before + // writing anything to the target blocks. + // + // All the patch data is concatenated into one patch_data file in + // the update package. It must be stored uncompressed because we + // memory-map it in directly from the archive. (Since patches are + // already compressed, we lose very little by not compressing + // their concatenation.) + + pthread_t new_data_thread; + NewThreadInfo nti; + nti.za = za; + nti.entry = new_entry; + nti.rss = NULL; + pthread_mutex_init(&nti.mu, NULL); + pthread_cond_init(&nti.cv, NULL); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + pthread_create(&new_data_thread, &attr, unzip_new_data, &nti); + + int i, j; + + char* linesave; + char* wordsave; + + int fd = open(blockdev_filename->data, O_RDWR); + if (fd < 0) { + ErrorAbort(state, "failed to open %s: %s", blockdev_filename->data, strerror(errno)); + goto done; + } + + char* line; + char* word; + + // The data in transfer_list_value is not necessarily + // null-terminated, so we need to copy it to a new buffer and add + // the null that strtok_r will need. + transfer_list = malloc(transfer_list_value->size+1); + if (transfer_list == NULL) { + fprintf(stderr, "failed to allocate %zd bytes for transfer list\n", + transfer_list_value->size+1); + exit(1); + } + memcpy(transfer_list, transfer_list_value->data, transfer_list_value->size); + transfer_list[transfer_list_value->size] = '\0'; + + line = strtok_r(transfer_list, "\n", &linesave); + + // first line in transfer list is the version number; currently + // there's only version 1. + if (strcmp(line, "1") != 0) { + ErrorAbort(state, "unexpected transfer list version [%s]\n", line); + goto done; + } + + // second line in transfer list is the total number of blocks we + // expect to write. + line = strtok_r(NULL, "\n", &linesave); + int total_blocks = strtol(line, NULL, 0); + // shouldn't happen, but avoid divide by zero. + if (total_blocks == 0) ++total_blocks; + int blocks_so_far = 0; + + uint8_t* buffer = NULL; + size_t buffer_alloc = 0; + + // third and subsequent lines are all individual transfer commands. + for (line = strtok_r(NULL, "\n", &linesave); line; + line = strtok_r(NULL, "\n", &linesave)) { + char* style; + style = strtok_r(line, " ", &wordsave); + + if (strcmp("move", style) == 0) { + word = strtok_r(NULL, " ", &wordsave); + RangeSet* src = parse_range(word); + word = strtok_r(NULL, " ", &wordsave); + RangeSet* tgt = parse_range(word); + + printf(" moving %d blocks\n", src->size); + + allocate(src->size * BLOCKSIZE, &buffer, &buffer_alloc); + size_t p = 0; + for (i = 0; i < src->count; ++i) { + check_lseek(fd, (off64_t)src->pos[i*2] * BLOCKSIZE, SEEK_SET); + size_t sz = (src->pos[i*2+1] - src->pos[i*2]) * BLOCKSIZE; + readblock(fd, buffer+p, sz); + p += sz; + } + + p = 0; + for (i = 0; i < tgt->count; ++i) { + check_lseek(fd, (off64_t)tgt->pos[i*2] * BLOCKSIZE, SEEK_SET); + size_t sz = (tgt->pos[i*2+1] - tgt->pos[i*2]) * BLOCKSIZE; + writeblock(fd, buffer+p, sz); + p += sz; + } + + blocks_so_far += tgt->size; + fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks); + fflush(cmd_pipe); + + free(src); + free(tgt); + + } else if (strcmp("zero", style) == 0 || + (DEBUG_ERASE && strcmp("erase", style) == 0)) { + word = strtok_r(NULL, " ", &wordsave); + RangeSet* tgt = parse_range(word); + + printf(" zeroing %d blocks\n", tgt->size); + + allocate(BLOCKSIZE, &buffer, &buffer_alloc); + memset(buffer, 0, BLOCKSIZE); + for (i = 0; i < tgt->count; ++i) { + check_lseek(fd, (off64_t)tgt->pos[i*2] * BLOCKSIZE, SEEK_SET); + for (j = tgt->pos[i*2]; j < tgt->pos[i*2+1]; ++j) { + writeblock(fd, buffer, BLOCKSIZE); + } + } + + if (style[0] == 'z') { // "zero" but not "erase" + blocks_so_far += tgt->size; + fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks); + fflush(cmd_pipe); + } + + free(tgt); + } else if (strcmp("new", style) == 0) { + + word = strtok_r(NULL, " ", &wordsave); + RangeSet* tgt = parse_range(word); + + printf(" writing %d blocks of new data\n", tgt->size); + + RangeSinkState rss; + rss.fd = fd; + rss.tgt = tgt; + rss.p_block = 0; + rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE; + check_lseek(fd, (off64_t)tgt->pos[0] * BLOCKSIZE, SEEK_SET); + + pthread_mutex_lock(&nti.mu); + nti.rss = &rss; + pthread_cond_broadcast(&nti.cv); + while (nti.rss) { + pthread_cond_wait(&nti.cv, &nti.mu); + } + pthread_mutex_unlock(&nti.mu); + + blocks_so_far += tgt->size; + fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks); + fflush(cmd_pipe); + + free(tgt); + + } else if (strcmp("bsdiff", style) == 0 || + strcmp("imgdiff", style) == 0) { + word = strtok_r(NULL, " ", &wordsave); + size_t patch_offset = strtoul(word, NULL, 0); + word = strtok_r(NULL, " ", &wordsave); + size_t patch_len = strtoul(word, NULL, 0); + + word = strtok_r(NULL, " ", &wordsave); + RangeSet* src = parse_range(word); + word = strtok_r(NULL, " ", &wordsave); + RangeSet* tgt = parse_range(word); + + printf(" patching %d blocks to %d\n", src->size, tgt->size); + + // Read the source into memory. + allocate(src->size * BLOCKSIZE, &buffer, &buffer_alloc); + size_t p = 0; + for (i = 0; i < src->count; ++i) { + check_lseek(fd, (off64_t)src->pos[i*2] * BLOCKSIZE, SEEK_SET); + size_t sz = (src->pos[i*2+1] - src->pos[i*2]) * BLOCKSIZE; + readblock(fd, buffer+p, sz); + p += sz; + } + + Value patch_value; + patch_value.type = VAL_BLOB; + patch_value.size = patch_len; + patch_value.data = (char*)(patch_start + patch_offset); + + RangeSinkState rss; + rss.fd = fd; + rss.tgt = tgt; + rss.p_block = 0; + rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE; + check_lseek(fd, (off64_t)tgt->pos[0] * BLOCKSIZE, SEEK_SET); + + if (style[0] == 'i') { // imgdiff + ApplyImagePatch(buffer, src->size * BLOCKSIZE, + &patch_value, + &RangeSinkWrite, &rss, NULL, NULL); + } else { + ApplyBSDiffPatch(buffer, src->size * BLOCKSIZE, + &patch_value, 0, + &RangeSinkWrite, &rss, NULL); + } + + // We expect the output of the patcher to fill the tgt ranges exactly. + if (rss.p_block != tgt->count || rss.p_remain != 0) { + fprintf(stderr, "range sink underrun?\n"); + } + + blocks_so_far += tgt->size; + fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks); + fflush(cmd_pipe); + + free(src); + free(tgt); + } else if (!DEBUG_ERASE && strcmp("erase", style) == 0) { + struct stat st; + if (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode)) { + word = strtok_r(NULL, " ", &wordsave); + RangeSet* tgt = parse_range(word); + + printf(" erasing %d blocks\n", tgt->size); + + for (i = 0; i < tgt->count; ++i) { + uint64_t range[2]; + // offset in bytes + range[0] = tgt->pos[i*2] * (uint64_t)BLOCKSIZE; + // len in bytes + range[1] = (tgt->pos[i*2+1] - tgt->pos[i*2]) * (uint64_t)BLOCKSIZE; + + if (ioctl(fd, BLKDISCARD, &range) < 0) { + printf(" blkdiscard failed: %s\n", strerror(errno)); + } + } + + free(tgt); + } else { + printf(" ignoring erase (not block device)\n"); + } + } else { + fprintf(stderr, "unknown transfer style \"%s\"\n", style); + exit(1); + } + } + + pthread_join(new_data_thread, NULL); + success = true; + + free(buffer); + printf("wrote %d blocks; expected %d\n", blocks_so_far, total_blocks); + printf("max alloc needed was %zu\n", buffer_alloc); + + done: + free(transfer_list); + FreeValue(blockdev_filename); + FreeValue(transfer_list_value); + FreeValue(new_data_fn); + FreeValue(patch_data_fn); + return StringValue(success ? strdup("t") : strdup("")); +} + +Value* RangeSha1Fn(const char* name, State* state, int argc, Expr* argv[]) { + Value* blockdev_filename; + Value* ranges; + const uint8_t* digest = NULL; + if (ReadValueArgs(state, argv, 2, &blockdev_filename, &ranges) < 0) { + return NULL; + } + + if (blockdev_filename->type != VAL_STRING) { + ErrorAbort(state, "blockdev_filename argument to %s must be string", name); + goto done; + } + if (ranges->type != VAL_STRING) { + ErrorAbort(state, "ranges argument to %s must be string", name); + goto done; + } + + int fd = open(blockdev_filename->data, O_RDWR); + if (fd < 0) { + ErrorAbort(state, "failed to open %s: %s", blockdev_filename->data, strerror(errno)); + goto done; + } + + RangeSet* rs = parse_range(ranges->data); + uint8_t buffer[BLOCKSIZE]; + + SHA_CTX ctx; + SHA_init(&ctx); + + int i, j; + for (i = 0; i < rs->count; ++i) { + check_lseek(fd, (off64_t)rs->pos[i*2] * BLOCKSIZE, SEEK_SET); + for (j = rs->pos[i*2]; j < rs->pos[i*2+1]; ++j) { + readblock(fd, buffer, BLOCKSIZE); + SHA_update(&ctx, buffer, BLOCKSIZE); + } + } + digest = SHA_final(&ctx); + close(fd); + + done: + FreeValue(blockdev_filename); + FreeValue(ranges); + if (digest == NULL) { + return StringValue(strdup("")); + } else { + return StringValue(PrintSha1(digest)); + } +} + +void RegisterBlockImageFunctions() { + RegisterFunction("block_image_update", BlockImageUpdateFn); + RegisterFunction("range_sha1", RangeSha1Fn); +} diff --git a/updater/blockimg.h b/updater/blockimg.h new file mode 100644 index 000000000..2f4ad3c04 --- /dev/null +++ b/updater/blockimg.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _UPDATER_BLOCKIMG_H_ +#define _UPDATER_BLOCKIMG_H_ + +void RegisterBlockImageFunctions(); + +#endif diff --git a/updater/install.c b/updater/install.c index ff4dd5829..003064799 100644 --- a/updater/install.c +++ b/updater/install.c @@ -34,6 +34,9 @@ #include <linux/xattr.h> #include <inttypes.h> +#include "bootloader.h" +#include "applypatch/applypatch.h" +#include "cutils/android_reboot.h" #include "cutils/misc.h" #include "cutils/properties.h" #include "edify/expr.h" @@ -44,27 +47,73 @@ #include "updater.h" #include "applypatch/applypatch.h" #include "flashutils/flashutils.h" +#include "install.h" #ifdef USE_EXT4 #include "make_ext4fs.h" +#include "wipe.h" #endif +void uiPrint(State* state, char* buffer) { + char* line = strtok(buffer, "\n"); + UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); + while (line) { + fprintf(ui->cmd_pipe, "ui_print %s\n", line); + line = strtok(NULL, "\n"); + } + fprintf(ui->cmd_pipe, "ui_print\n"); +} + +__attribute__((__format__(printf, 2, 3))) __nonnull((2)) +void uiPrintf(State* state, const char* format, ...) { + char error_msg[1024]; + va_list ap; + va_start(ap, format); + vsnprintf(error_msg, sizeof(error_msg), format, ap); + va_end(ap); + uiPrint(state, error_msg); +} + +// Take a sha-1 digest and return it as a newly-allocated hex string. +char* PrintSha1(const uint8_t* digest) { + char* buffer = malloc(SHA_DIGEST_SIZE*2 + 1); + int i; + const char* alphabet = "0123456789abcdef"; + for (i = 0; i < SHA_DIGEST_SIZE; ++i) { + buffer[i*2] = alphabet[(digest[i] >> 4) & 0xf]; + buffer[i*2+1] = alphabet[digest[i] & 0xf]; + } + buffer[i*2] = '\0'; + return buffer; +} + // mount(fs_type, partition_type, location, mount_point) // // fs_type="yaffs2" partition_type="MTD" location=partition // fs_type="ext4" partition_type="EMMC" location=device Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) { char* result = NULL; - if (argc != 4) { - return ErrorAbort(state, "%s() expects 4 args, got %d", name, argc); + if (argc != 4 && argc != 5) { + return ErrorAbort(state, "%s() expects 4-5 args, got %d", name, argc); } char* fs_type; char* partition_type; char* location; char* mount_point; - if (ReadArgs(state, argv, 4, &fs_type, &partition_type, + char* mount_options; + bool has_mount_options; + if (argc == 5) { + has_mount_options = true; + if (ReadArgs(state, argv, 5, &fs_type, &partition_type, + &location, &mount_point, &mount_options) < 0) { + return NULL; + } + } else { + has_mount_options = false; + if (ReadArgs(state, argv, 4, &fs_type, &partition_type, &location, &mount_point) < 0) { - return NULL; + return NULL; + } } if (strlen(fs_type) == 0) { @@ -104,13 +153,13 @@ Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) { const MtdPartition* mtd; mtd = mtd_find_partition_by_name(location); if (mtd == NULL) { - printf("%s: no mtd partition named \"%s\"", + uiPrintf(state, "%s: no mtd partition named \"%s\"", name, location); result = strdup(""); goto done; } if (mtd_mount_partition(mtd, mount_point, fs_type, 0 /* rw */) != 0) { - printf("mtd mount of %s failed: %s\n", + uiPrintf(state, "mtd mount of %s failed: %s\n", location, strerror(errno)); result = strdup(""); goto done; @@ -118,8 +167,9 @@ Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) { result = mount_point; } else { if (mount(location, mount_point, fs_type, - MS_NOATIME | MS_NODEV | MS_NODIRATIME, "") < 0) { - printf("%s: failed to mount %s at %s: %s\n", + MS_NOATIME | MS_NODEV | MS_NODIRATIME, + has_mount_options ? mount_options : "") < 0) { + uiPrintf(state, "%s: failed to mount %s at %s: %s\n", name, location, mount_point, strerror(errno)); result = strdup(""); } else { @@ -132,6 +182,7 @@ done: free(partition_type); free(location); if (result != mount_point) free(mount_point); + if (has_mount_options) free(mount_options); return StringValue(result); } @@ -182,10 +233,14 @@ Value* UnmountFn(const char* name, State* state, int argc, Expr* argv[]) { scan_mounted_volumes(); const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point); if (vol == NULL) { - printf("unmount of %s failed; no such volume\n", mount_point); + uiPrintf(state, "unmount of %s failed; no such volume\n", mount_point); result = strdup(""); } else { - unmount_mounted_volume(vol); + int ret = unmount_mounted_volume(vol); + if (ret != 0) { + uiPrintf(state, "unmount of %s failed (%d): %s\n", + mount_point, ret, strerror(errno)); + } result = mount_point; } @@ -194,14 +249,29 @@ done: return StringValue(result); } +static int exec_cmd(const char* path, char* const argv[]) { + int status; + pid_t child; + if ((child = vfork()) == 0) { + execv(path, argv); + _exit(-1); + } + waitpid(child, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + printf("%s failed with status %d\n", path, WEXITSTATUS(status)); + } + return WEXITSTATUS(status); +} + // format(fs_type, partition_type, location, fs_size, mount_point) // // fs_type="yaffs2" partition_type="MTD" location=partition fs_size=<bytes> mount_point=<location> // fs_type="ext4" partition_type="EMMC" location=device fs_size=<bytes> mount_point=<location> -// if fs_size == 0, then make_ext4fs uses the entire partition. +// fs_type="f2fs" partition_type="EMMC" location=device fs_size=<bytes> mount_point=<location> +// if fs_size == 0, then make fs uses the entire partition. // if fs_size > 0, that is the size to use -// if fs_size < 0, then reserve that many bytes at the end of the partition +// if fs_size < 0, then reserve that many bytes at the end of the partition (not for "f2fs") Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { char* result = NULL; if (argc != 5) { @@ -273,6 +343,24 @@ Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { goto done; } result = location; + } else if (strcmp(fs_type, "f2fs") == 0) { + char *num_sectors; + if (asprintf(&num_sectors, "%lld", atoll(fs_size) / 512) <= 0) { + printf("format_volume: failed to create %s command for %s\n", fs_type, location); + result = strdup(""); + goto done; + } + const char *f2fs_path = "/sbin/mkfs.f2fs"; + const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", location, num_sectors, NULL}; + int status = exec_cmd(f2fs_path, (char* const*)f2fs_argv); + free(num_sectors); + if (status != 0) { + printf("%s: mkfs.f2fs failed (%d) on %s", + name, status, location); + result = strdup(""); + goto done; + } + result = location; #endif } else { printf("%s: unsupported fs_type \"%s\" partition_type \"%s\"", @@ -286,6 +374,44 @@ done: return StringValue(result); } +Value* RenameFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + + char* src_name; + char* dst_name; + + if (ReadArgs(state, argv, 2, &src_name, &dst_name) < 0) { + return NULL; + } + if (strlen(src_name) == 0) { + ErrorAbort(state, "src_name argument to %s() can't be empty", name); + goto done; + } + if (strlen(dst_name) == 0) { + ErrorAbort(state, "dst_name argument to %s() can't be empty", name); + goto done; + } + if (make_parents(dst_name) != 0) { + ErrorAbort(state, "Creating parent of %s failed, error %s", + dst_name, strerror(errno)); + } else if (access(dst_name, F_OK) == 0 && access(src_name, F_OK) != 0) { + // File was already moved + result = dst_name; + } else if (rename(src_name, dst_name) != 0) { + ErrorAbort(state, "Rename of %s to %s failed, error %s", + src_name, dst_name, strerror(errno)); + } else { + result = dst_name; + } + +done: + free(src_name); + if (result != dst_name) free(dst_name); + return StringValue(result); +} Value* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) { char** paths = malloc(argc * sizeof(char*)); @@ -386,19 +512,23 @@ Value* PackageExtractDirFn(const char* name, State* state, // function (the char* returned is actually a FileContents*). Value* PackageExtractFileFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 1 && argc != 2) { + if (argc < 1 || argc > 2) { return ErrorAbort(state, "%s() expects 1 or 2 args, got %d", name, argc); } bool success = false; + + UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); + if (argc == 2) { // The two-argument version extracts to a file. + ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; + char* zip_path; char* dest_path; if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL; - ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; const ZipEntry* entry = mzFindZipEntry(za, zip_path); if (entry == NULL) { printf("%s: no %s in package\n", name, zip_path); @@ -467,7 +597,7 @@ static int make_parents(char* name) { *p = '\0'; if (make_parents(name) < 0) return -1; int result = mkdir(name, 0700); - if (result == 0) printf("symlink(): created [%s]\n", name); + if (result == 0) printf("created [%s]\n", name); *p = '/'; if (result == 0 || errno == EEXIST) { // successfully created or already existed; we're done @@ -525,88 +655,6 @@ Value* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) { return StringValue(strdup("")); } - -Value* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) { - char* result = NULL; - bool recursive = (strcmp(name, "set_perm_recursive") == 0); - - int min_args = 4 + (recursive ? 1 : 0); - if (argc < min_args) { - return ErrorAbort(state, "%s() expects %d+ args, got %d", - name, min_args, argc); - } - - char** args = ReadVarArgs(state, argc, argv); - if (args == NULL) return NULL; - - char* end; - int i; - int bad = 0; - - int uid = strtoul(args[0], &end, 0); - if (*end != '\0' || args[0][0] == 0) { - ErrorAbort(state, "%s: \"%s\" not a valid uid", name, args[0]); - goto done; - } - - int gid = strtoul(args[1], &end, 0); - if (*end != '\0' || args[1][0] == 0) { - ErrorAbort(state, "%s: \"%s\" not a valid gid", name, args[1]); - goto done; - } - - if (recursive) { - int dir_mode = strtoul(args[2], &end, 0); - if (*end != '\0' || args[2][0] == 0) { - ErrorAbort(state, "%s: \"%s\" not a valid dirmode", name, args[2]); - goto done; - } - - int file_mode = strtoul(args[3], &end, 0); - if (*end != '\0' || args[3][0] == 0) { - ErrorAbort(state, "%s: \"%s\" not a valid filemode", - name, args[3]); - goto done; - } - - for (i = 4; i < argc; ++i) { - dirSetHierarchyPermissions(args[i], uid, gid, dir_mode, file_mode); - } - } else { - int mode = strtoul(args[2], &end, 0); - if (*end != '\0' || args[2][0] == 0) { - ErrorAbort(state, "%s: \"%s\" not a valid mode", name, args[2]); - goto done; - } - - for (i = 3; i < argc; ++i) { - if (chown(args[i], uid, gid) < 0) { - printf("%s: chown of %s to %d %d failed: %s\n", - name, args[i], uid, gid, strerror(errno)); - ++bad; - } - if (chmod(args[i], mode) < 0) { - printf("%s: chmod of %s to %o failed: %s\n", - name, args[i], mode, strerror(errno)); - ++bad; - } - } - } - result = strdup(""); - -done: - for (i = 0; i < argc; ++i) { - free(args[i]); - } - free(args); - - if (bad) { - free(result); - return ErrorAbort(state, "%s: some changes failed", name); - } - return StringValue(result); -} - struct perm_parsed_args { bool has_uid; uid_t uid; @@ -624,7 +672,7 @@ struct perm_parsed_args { uint64_t capabilities; }; -static struct perm_parsed_args ParsePermArgs(int argc, char** args) { +static struct perm_parsed_args ParsePermArgs(State * state, int argc, char** args) { int i; struct perm_parsed_args parsed; int bad = 0; @@ -639,7 +687,7 @@ static struct perm_parsed_args ParsePermArgs(int argc, char** args) { parsed.uid = uid; parsed.has_uid = true; } else { - printf("ParsePermArgs: invalid UID \"%s\"\n", args[i + 1]); + uiPrintf(state, "ParsePermArgs: invalid UID \"%s\"\n", args[i + 1]); bad++; } continue; @@ -650,7 +698,7 @@ static struct perm_parsed_args ParsePermArgs(int argc, char** args) { parsed.gid = gid; parsed.has_gid = true; } else { - printf("ParsePermArgs: invalid GID \"%s\"\n", args[i + 1]); + uiPrintf(state, "ParsePermArgs: invalid GID \"%s\"\n", args[i + 1]); bad++; } continue; @@ -661,7 +709,7 @@ static struct perm_parsed_args ParsePermArgs(int argc, char** args) { parsed.mode = mode; parsed.has_mode = true; } else { - printf("ParsePermArgs: invalid mode \"%s\"\n", args[i + 1]); + uiPrintf(state, "ParsePermArgs: invalid mode \"%s\"\n", args[i + 1]); bad++; } continue; @@ -672,7 +720,7 @@ static struct perm_parsed_args ParsePermArgs(int argc, char** args) { parsed.dmode = mode; parsed.has_dmode = true; } else { - printf("ParsePermArgs: invalid dmode \"%s\"\n", args[i + 1]); + uiPrintf(state, "ParsePermArgs: invalid dmode \"%s\"\n", args[i + 1]); bad++; } continue; @@ -683,7 +731,7 @@ static struct perm_parsed_args ParsePermArgs(int argc, char** args) { parsed.fmode = mode; parsed.has_fmode = true; } else { - printf("ParsePermArgs: invalid fmode \"%s\"\n", args[i + 1]); + uiPrintf(state, "ParsePermArgs: invalid fmode \"%s\"\n", args[i + 1]); bad++; } continue; @@ -694,7 +742,7 @@ static struct perm_parsed_args ParsePermArgs(int argc, char** args) { parsed.capabilities = capabilities; parsed.has_capabilities = true; } else { - printf("ParsePermArgs: invalid capabilities \"%s\"\n", args[i + 1]); + uiPrintf(state, "ParsePermArgs: invalid capabilities \"%s\"\n", args[i + 1]); bad++; } continue; @@ -704,7 +752,7 @@ static struct perm_parsed_args ParsePermArgs(int argc, char** args) { parsed.selabel = args[i+1]; parsed.has_selabel = true; } else { - printf("ParsePermArgs: invalid selabel \"%s\"\n", args[i + 1]); + uiPrintf(state, "ParsePermArgs: invalid selabel \"%s\"\n", args[i + 1]); bad++; } continue; @@ -721,71 +769,71 @@ static struct perm_parsed_args ParsePermArgs(int argc, char** args) { } static int ApplyParsedPerms( + State * state, const char* filename, const struct stat *statptr, struct perm_parsed_args parsed) { int bad = 0; + if (parsed.has_selabel) { + if (lsetfilecon(filename, parsed.selabel) != 0) { + uiPrintf(state, "ApplyParsedPerms: lsetfilecon of %s to %s failed: %s\n", + filename, parsed.selabel, strerror(errno)); + bad++; + } + } + /* ignore symlinks */ if (S_ISLNK(statptr->st_mode)) { - return 0; + return bad; } if (parsed.has_uid) { if (chown(filename, parsed.uid, -1) < 0) { - printf("ApplyParsedPerms: chown of %s to %d failed: %s\n", - filename, parsed.uid, strerror(errno)); + uiPrintf(state, "ApplyParsedPerms: chown of %s to %d failed: %s\n", + filename, parsed.uid, strerror(errno)); bad++; } } if (parsed.has_gid) { if (chown(filename, -1, parsed.gid) < 0) { - printf("ApplyParsedPerms: chgrp of %s to %d failed: %s\n", - filename, parsed.gid, strerror(errno)); + uiPrintf(state, "ApplyParsedPerms: chgrp of %s to %d failed: %s\n", + filename, parsed.gid, strerror(errno)); bad++; } } if (parsed.has_mode) { if (chmod(filename, parsed.mode) < 0) { - printf("ApplyParsedPerms: chmod of %s to %d failed: %s\n", - filename, parsed.mode, strerror(errno)); + uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", + filename, parsed.mode, strerror(errno)); bad++; } } if (parsed.has_dmode && S_ISDIR(statptr->st_mode)) { if (chmod(filename, parsed.dmode) < 0) { - printf("ApplyParsedPerms: chmod of %s to %d failed: %s\n", - filename, parsed.dmode, strerror(errno)); + uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", + filename, parsed.dmode, strerror(errno)); bad++; } } if (parsed.has_fmode && S_ISREG(statptr->st_mode)) { if (chmod(filename, parsed.fmode) < 0) { - printf("ApplyParsedPerms: chmod of %s to %d failed: %s\n", + uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", filename, parsed.fmode, strerror(errno)); bad++; } } - if (parsed.has_selabel) { - // TODO: Don't silently ignore ENOTSUP - if (lsetfilecon(filename, parsed.selabel) && (errno != ENOTSUP)) { - printf("ApplyParsedPerms: lsetfilecon of %s to %s failed: %s\n", - filename, parsed.selabel, strerror(errno)); - bad++; - } - } - if (parsed.has_capabilities && S_ISREG(statptr->st_mode)) { if (parsed.capabilities == 0) { if ((removexattr(filename, XATTR_NAME_CAPS) == -1) && (errno != ENODATA)) { // Report failure unless it's ENODATA (attribute not set) - printf("ApplyParsedPerms: removexattr of %s to %" PRIx64 " failed: %s\n", + uiPrintf(state, "ApplyParsedPerms: removexattr of %s to %" PRIx64 " failed: %s\n", filename, parsed.capabilities, strerror(errno)); bad++; } @@ -798,8 +846,8 @@ static int ApplyParsedPerms( cap_data.data[1].permitted = (uint32_t) (parsed.capabilities >> 32); cap_data.data[1].inheritable = 0; if (setxattr(filename, XATTR_NAME_CAPS, &cap_data, sizeof(cap_data), 0) < 0) { - printf("ApplyParsedPerms: setcap of %s to %" PRIx64 " failed: %s\n", - filename, parsed.capabilities, strerror(errno)); + uiPrintf(state, "ApplyParsedPerms: setcap of %s to %" PRIx64 " failed: %s\n", + filename, parsed.capabilities, strerror(errno)); bad++; } } @@ -811,10 +859,11 @@ static int ApplyParsedPerms( // nftw doesn't allow us to pass along context, so we need to use // global variables. *sigh* static struct perm_parsed_args recursive_parsed_args; +static State* recursive_state; static int do_SetMetadataRecursive(const char* filename, const struct stat *statptr, int fileflags, struct FTW *pfwt) { - return ApplyParsedPerms(filename, statptr, recursive_parsed_args); + return ApplyParsedPerms(recursive_state, filename, statptr, recursive_parsed_args); } static Value* SetMetadataFn(const char* name, State* state, int argc, Expr* argv[]) { @@ -839,14 +888,16 @@ static Value* SetMetadataFn(const char* name, State* state, int argc, Expr* argv goto done; } - struct perm_parsed_args parsed = ParsePermArgs(argc, args); + struct perm_parsed_args parsed = ParsePermArgs(state, argc, args); if (recursive) { recursive_parsed_args = parsed; + recursive_state = state; bad += nftw(args[0], do_SetMetadataRecursive, 30, FTW_CHDIR | FTW_DEPTH | FTW_PHYS); memset(&recursive_parsed_args, 0, sizeof(recursive_parsed_args)); + recursive_state = NULL; } else { - bad += ApplyParsedPerms(args[0], &sb, parsed); + bad += ApplyParsedPerms(state, args[0], &sb, parsed); } done: @@ -885,8 +936,8 @@ Value* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) { // file_getprop(file, key) // // interprets 'file' as a getprop-style file (key=value pairs, one -// per line, # comment lines and blank lines okay), and returns the value -// for 'key' (or "" if it isn't defined). +// per line. # comment lines,blank lines, lines without '=' ignored), +// and returns the value for 'key' (or "" if it isn't defined). Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) { char* result = NULL; char* buffer = NULL; @@ -913,7 +964,7 @@ Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) { buffer = malloc(st.st_size+1); if (buffer == NULL) { - ErrorAbort(state, "%s: failed to alloc %lld bytes", name, st.st_size+1); + ErrorAbort(state, "%s: failed to alloc %lld bytes", name, (long long)st.st_size+1); goto done; } @@ -926,7 +977,7 @@ Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) { if (fread(buffer, 1, st.st_size, f) != st.st_size) { ErrorAbort(state, "%s: failed to read %lld bytes from %s", - name, st.st_size+1, filename); + name, (long long)st.st_size+1, filename); fclose(f); goto done; } @@ -944,9 +995,7 @@ Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) { char* equal = strchr(line, '='); if (equal == NULL) { - ErrorAbort(state, "%s: malformed line \"%s\": %s not a prop file?", - name, line, filename); - goto done; + continue; } // trim whitespace between key and '=' @@ -1048,8 +1097,8 @@ Value* ApplyPatchSpaceFn(const char* name, State* state, return StringValue(strdup(CacheSizeCheck(bytes) ? "" : "t")); } +// apply_patch(file, size, init_sha1, tgt_sha1, patch) -// apply_patch(srcfile, tgtfile, tgtsha1, tgtsize, sha1_1, patch_1, ...) Value* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) { if (argc < 6 || (argc % 2) == 1) { return ErrorAbort(state, "%s(): expected at least 6 args and an " @@ -1168,15 +1217,7 @@ Value* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) { } free(args); buffer[size] = '\0'; - - char* line = strtok(buffer, "\n"); - while (line) { - fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe, - "ui_print %s\n", line); - line = strtok(NULL, "\n"); - } - fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe, "ui_print\n"); - + uiPrint(state, buffer); return StringValue(buffer); } @@ -1234,19 +1275,6 @@ Value* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) { return StringValue(strdup(buffer)); } -// Take a sha-1 digest and return it as a newly-allocated hex string. -static char* PrintSha1(uint8_t* digest) { - char* buffer = malloc(SHA_DIGEST_SIZE*2 + 1); - int i; - const char* alphabet = "0123456789abcdef"; - for (i = 0; i < SHA_DIGEST_SIZE; ++i) { - buffer[i*2] = alphabet[(digest[i] >> 4) & 0xf]; - buffer[i*2+1] = alphabet[digest[i] & 0xf]; - } - buffer[i*2] = '\0'; - return buffer; -} - // sha1_check(data) // to return the sha1 of the data (given in the format returned by // read_file). @@ -1266,7 +1294,6 @@ Value* Sha1CheckFn(const char* name, State* state, int argc, Expr* argv[]) { } if (args[0]->size < 0) { - printf("%s(): no file contents received", name); return StringValue(strdup("")); } uint8_t digest[SHA_DIGEST_SIZE]; @@ -1318,13 +1345,12 @@ Value* ReadFileFn(const char* name, State* state, int argc, Expr* argv[]) { v->type = VAL_BLOB; FileContents fc; - if (LoadFileContents(filename, &fc, RETOUCH_DONT_MASK) != 0) { - ErrorAbort(state, "%s() loading \"%s\" failed: %s", - name, filename, strerror(errno)); + if (LoadFileContents(filename, &fc) != 0) { free(filename); - free(v); + v->size = -1; + v->data = NULL; free(fc.data); - return NULL; + return v; } v->size = fc.size; @@ -1334,6 +1360,135 @@ Value* ReadFileFn(const char* name, State* state, int argc, Expr* argv[]) { return v; } +// Immediately reboot the device. Recovery is not finished normally, +// so if you reboot into recovery it will re-start applying the +// current package (because nothing has cleared the copy of the +// arguments stored in the BCB). +// +// The argument is the partition name passed to the android reboot +// property. It can be "recovery" to boot from the recovery +// partition, or "" (empty string) to boot from the regular boot +// partition. +Value* RebootNowFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + + char* filename; + char* property; + if (ReadArgs(state, argv, 2, &filename, &property) < 0) return NULL; + + char buffer[80]; + + // zero out the 'command' field of the bootloader message. + memset(buffer, 0, sizeof(((struct bootloader_message*)0)->command)); + FILE* f = fopen(filename, "r+b"); + fseek(f, offsetof(struct bootloader_message, command), SEEK_SET); + fwrite(buffer, sizeof(((struct bootloader_message*)0)->command), 1, f); + fclose(f); + free(filename); + + strcpy(buffer, "reboot,"); + if (property != NULL) { + strncat(buffer, property, sizeof(buffer)-10); + } + + property_set(ANDROID_RB_PROPERTY, buffer); + + sleep(5); + free(property); + ErrorAbort(state, "%s() failed to reboot", name); + return NULL; +} + +// Store a string value somewhere that future invocations of recovery +// can access it. This value is called the "stage" and can be used to +// drive packages that need to do reboots in the middle of +// installation and keep track of where they are in the multi-stage +// install. +// +// The first argument is the block device for the misc partition +// ("/misc" in the fstab), which is where this value is stored. The +// second argument is the string to store; it should not exceed 31 +// bytes. +Value* SetStageFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + + char* filename; + char* stagestr; + if (ReadArgs(state, argv, 2, &filename, &stagestr) < 0) return NULL; + + // Store this value in the misc partition, immediately after the + // bootloader message that the main recovery uses to save its + // arguments in case of the device restarting midway through + // package installation. + FILE* f = fopen(filename, "r+b"); + fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET); + int to_write = strlen(stagestr)+1; + int max_size = sizeof(((struct bootloader_message*)0)->stage); + if (to_write > max_size) { + to_write = max_size; + stagestr[max_size-1] = 0; + } + fwrite(stagestr, to_write, 1, f); + fclose(f); + + free(stagestr); + return StringValue(filename); +} + +// Return the value most recently saved with SetStageFn. The argument +// is the block device for the misc partition. +Value* GetStageFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); + } + + char* filename; + if (ReadArgs(state, argv, 1, &filename) < 0) return NULL; + + char buffer[sizeof(((struct bootloader_message*)0)->stage)]; + FILE* f = fopen(filename, "rb"); + fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET); + fread(buffer, sizeof(buffer), 1, f); + fclose(f); + buffer[sizeof(buffer)-1] = '\0'; + + return StringValue(strdup(buffer)); +} + +Value* WipeBlockDeviceFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + + char* filename; + char* len_str; + if (ReadArgs(state, argv, 2, &filename, &len_str) < 0) return NULL; + + size_t len = strtoull(len_str, NULL, 0); + int fd = open(filename, O_WRONLY, 0644); + int success = wipe_block_device(fd, len); + + free(filename); + free(len_str); + + close(fd); + + return StringValue(strdup(success ? "t" : "")); +} + +Value* EnableRebootFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 0) { + return ErrorAbort(state, "%s() expects no args, got %d", name, argc); + } + UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); + fprintf(ui->cmd_pipe, "enable_reboot\n"); + return StringValue(strdup("t")); +} + void RegisterInstallFunctions() { RegisterFunction("mount", MountFn); RegisterFunction("is_mounted", IsMountedFn); @@ -1347,11 +1502,6 @@ void RegisterInstallFunctions() { RegisterFunction("package_extract_file", PackageExtractFileFn); RegisterFunction("symlink", SymlinkFn); - // Maybe, at some future point, we can delete these functions? They have been - // replaced by perm_set and perm_set_recursive. - RegisterFunction("set_perm", SetPermFn); - RegisterFunction("set_perm_recursive", SetPermFn); - // Usage: // set_metadata("filename", "key1", "value1", "key2", "value2", ...) // Example: @@ -1372,12 +1522,21 @@ void RegisterInstallFunctions() { RegisterFunction("apply_patch_check", ApplyPatchCheckFn); RegisterFunction("apply_patch_space", ApplyPatchSpaceFn); + RegisterFunction("wipe_block_device", WipeBlockDeviceFn); + RegisterFunction("read_file", ReadFileFn); RegisterFunction("sha1_check", Sha1CheckFn); + RegisterFunction("rename", RenameFn); RegisterFunction("wipe_cache", WipeCacheFn); RegisterFunction("ui_print", UIPrintFn); RegisterFunction("run_program", RunProgramFn); + + RegisterFunction("reboot_now", RebootNowFn); + RegisterFunction("get_stage", GetStageFn); + RegisterFunction("set_stage", SetStageFn); + + RegisterFunction("enable_reboot", EnableRebootFn); } diff --git a/updater/install.h b/updater/install.h index 94f344f8e..659c8b41c 100644 --- a/updater/install.h +++ b/updater/install.h @@ -19,4 +19,6 @@ void RegisterInstallFunctions(); +static int make_parents(char* name); + #endif diff --git a/updater/updater.c b/updater/updater.c index 39c52b49d..78c0dc1a6 100644 --- a/updater/updater.c +++ b/updater/updater.c @@ -22,7 +22,9 @@ #include "edify/expr.h" #include "updater.h" #include "install.h" +#include "blockimg.h" #include "minzip/Zip.h" +#include "minzip/SysUtil.h" // Generated by the makefile, this function defines the // RegisterDeviceExtensions() function, which calls all the @@ -68,19 +70,24 @@ int main(int argc, char** argv) { // Extract the script from the package. - char* package_data = argv[3]; + const char* package_filename = argv[3]; + MemMapping map; + if (sysMapFile(package_filename, &map) != 0) { + printf("failed to map package %s\n", argv[3]); + return 3; + } ZipArchive za; int err; - err = mzOpenZipArchive(package_data, &za); + err = mzOpenZipArchive(map.addr, map.length, &za); if (err != 0) { printf("failed to open package %s: %s\n", - package_data, strerror(err)); + argv[3], strerror(err)); return 3; } const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME); if (script_entry == NULL) { - printf("failed to find %s in %s\n", SCRIPT_NAME, package_data); + printf("failed to find %s in %s\n", SCRIPT_NAME, package_filename); return 4; } @@ -112,6 +119,7 @@ int main(int argc, char** argv) { RegisterBuiltins(); RegisterInstallFunctions(); + RegisterBlockImageFunctions(); RegisterDeviceExtensions(); FinishRegistration(); @@ -119,8 +127,7 @@ int main(int argc, char** argv) { Expr* root; int error_count = 0; - yy_scan_string(script); - int error = yyparse(&root, &error_count); + int error = parse_string(script, &root, &error_count); if (error != 0 || error_count > 0) { printf("%d parse errors\n", error_count); return 6; @@ -150,6 +157,8 @@ int main(int argc, char** argv) { updater_info.cmd_pipe = cmd_pipe; updater_info.package_zip = &za; updater_info.version = atoi(version); + updater_info.package_zip_addr = map.addr; + updater_info.package_zip_len = map.length; State state; state.cookie = &updater_info; @@ -180,6 +189,7 @@ int main(int argc, char** argv) { if (updater_info.package_zip) { mzCloseZipArchive(updater_info.package_zip); } + sysReleaseMap(&map); free(script); return 0; diff --git a/updater/updater.h b/updater/updater.h index d2e901141..d1dfdd05e 100644 --- a/updater/updater.h +++ b/updater/updater.h @@ -27,6 +27,9 @@ typedef struct { FILE* cmd_pipe; ZipArchive* package_zip; int version; + + uint8_t* package_zip_addr; + size_t package_zip_len; } UpdaterInfo; extern struct selabel_handle *sehandle; diff --git a/verifier.cpp b/verifier.cpp index ccc0742d2..b96ba3a0d 100644 --- a/verifier.cpp +++ b/verifier.cpp @@ -14,10 +14,14 @@ * limitations under the License. */ +#include "asn1_decoder.h" #include "common.h" -#include "verifier.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" @@ -30,13 +34,85 @@ #define PUBLIC_KEYS_FILE "/res/keys" +/* + * Simple version of PKCS#7 SignedData extraction. This extracts the + * signature OCTET STRING to be used for signature verification. + * + * For full details, see http://www.ietf.org/rfc/rfc3852.txt + * + * The PKCS#7 structure looks like: + * + * SEQUENCE (ContentInfo) + * OID (ContentType) + * [0] (content) + * SEQUENCE (SignedData) + * INTEGER (version CMSVersion) + * SET (DigestAlgorithmIdentifiers) + * SEQUENCE (EncapsulatedContentInfo) + * [0] (CertificateSet OPTIONAL) + * [1] (RevocationInfoChoices OPTIONAL) + * SET (SignerInfos) + * SEQUENCE (SignerInfo) + * INTEGER (CMSVersion) + * SEQUENCE (SignerIdentifier) + * SEQUENCE (DigestAlgorithmIdentifier) + * SEQUENCE (SignatureAlgorithmIdentifier) + * OCTET STRING (SignatureValue) + */ +static bool read_pkcs7(uint8_t* pkcs7_der, size_t pkcs7_der_len, uint8_t** sig_der, + size_t* sig_der_length) { + asn1_context_t* ctx = asn1_context_new(pkcs7_der, pkcs7_der_len); + if (ctx == NULL) { + return false; + } + + asn1_context_t* pkcs7_seq = asn1_sequence_get(ctx); + if (pkcs7_seq != NULL && asn1_sequence_next(pkcs7_seq)) { + asn1_context_t *signed_data_app = asn1_constructed_get(pkcs7_seq); + if (signed_data_app != NULL) { + asn1_context_t* signed_data_seq = asn1_sequence_get(signed_data_app); + if (signed_data_seq != NULL + && asn1_sequence_next(signed_data_seq) + && asn1_sequence_next(signed_data_seq) + && asn1_sequence_next(signed_data_seq) + && asn1_constructed_skip_all(signed_data_seq)) { + asn1_context_t *sig_set = asn1_set_get(signed_data_seq); + if (sig_set != NULL) { + asn1_context_t* sig_seq = asn1_sequence_get(sig_set); + if (sig_seq != NULL + && asn1_sequence_next(sig_seq) + && asn1_sequence_next(sig_seq) + && asn1_sequence_next(sig_seq) + && asn1_sequence_next(sig_seq)) { + uint8_t* sig_der_ptr; + if (asn1_octet_string_get(sig_seq, &sig_der_ptr, sig_der_length)) { + *sig_der = (uint8_t*) malloc(*sig_der_length); + if (*sig_der != NULL) { + memcpy(*sig_der, sig_der_ptr, *sig_der_length); + } + } + asn1_context_free(sig_seq); + } + asn1_context_free(sig_set); + } + asn1_context_free(signed_data_seq); + } + asn1_context_free(signed_data_app); + } + asn1_context_free(pkcs7_seq); + } + asn1_context_free(ctx); + + return *sig_der != NULL; +} + // Look for an RSA signature embedded in the .ZIP file comment given // the path to the zip. Verify it matches one of the given public // keys. // // Return VERIFY_SUCCESS, VERIFY_FAILURE (if any error is encountered // or no key matches the signature). -int verify_file(const char* path) { +int verify_file(unsigned char* addr, size_t length) { //ui->SetProgress(0.0); int numKeys; @@ -47,12 +123,6 @@ int verify_file(const char* path) { } LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE); - FILE* f = fopen(path, "rb"); - if (f == NULL) { - LOGE("failed to open %s (%s)\n", path, strerror(errno)); - return VERIFY_FAILURE; - } - // An archive with a whole-file signature will end in six bytes: // // (2-byte signature start) $ff $ff (2-byte comment size) @@ -64,34 +134,25 @@ int verify_file(const char* path) { #define FOOTER_SIZE 6 - if (fseek(f, -FOOTER_SIZE, SEEK_END) != 0) { - LOGE("failed to seek in %s (%s)\n", path, strerror(errno)); - fclose(f); + if (length < FOOTER_SIZE) { + LOGE("not big enough to contain footer\n"); return VERIFY_FAILURE; } - unsigned char footer[FOOTER_SIZE]; - if (fread(footer, 1, FOOTER_SIZE, f) != FOOTER_SIZE) { - LOGE("failed to read footer from %s (%s)\n", path, strerror(errno)); - fclose(f); - return VERIFY_FAILURE; - } + unsigned char* footer = addr + length - FOOTER_SIZE; if (footer[2] != 0xff || footer[3] != 0xff) { LOGE("footer is wrong\n"); - fclose(f); return VERIFY_FAILURE; } size_t comment_size = footer[4] + (footer[5] << 8); size_t signature_start = footer[0] + (footer[1] << 8); - LOGI("comment is %d bytes; signature %d bytes from end\n", + LOGI("comment is %zu bytes; signature %zu bytes from end\n", comment_size, signature_start); - if (signature_start - FOOTER_SIZE < RSANUMBYTES) { - // "signature" block isn't big enough to contain an RSA block. - LOGE("signature is too short\n"); - fclose(f); + if (signature_start <= FOOTER_SIZE) { + LOGE("Signature start is in the footer"); return VERIFY_FAILURE; } @@ -101,9 +162,8 @@ int verify_file(const char* path) { // comment length. size_t eocd_size = comment_size + EOCD_HEADER_SIZE; - if (fseek(f, -eocd_size, SEEK_END) != 0) { - LOGE("failed to seek in %s (%s)\n", path, strerror(errno)); - fclose(f); + if (length < eocd_size) { + LOGE("not big enough to contain EOCD\n"); return VERIFY_FAILURE; } @@ -111,26 +171,15 @@ int verify_file(const char* path) { // This is everything except the signature data and length, which // includes all of the EOCD except for the comment length field (2 // bytes) and the comment data. - size_t signed_len = ftell(f) + EOCD_HEADER_SIZE - 2; + size_t signed_len = length - eocd_size + EOCD_HEADER_SIZE - 2; - unsigned char* eocd = (unsigned char*)malloc(eocd_size); - if (eocd == NULL) { - LOGE("malloc for EOCD record failed\n"); - fclose(f); - return VERIFY_FAILURE; - } - if (fread(eocd, 1, eocd_size, f) != eocd_size) { - LOGE("failed to read eocd from %s (%s)\n", path, strerror(errno)); - fclose(f); - return VERIFY_FAILURE; - } + unsigned char* eocd = addr + length - eocd_size; // If this is really is the EOCD record, it will begin with the // magic number $50 $4b $05 $06. if (eocd[0] != 0x50 || eocd[1] != 0x4b || eocd[2] != 0x05 || eocd[3] != 0x06) { LOGE("signature length doesn't match EOCD marker\n"); - fclose(f); return VERIFY_FAILURE; } @@ -143,7 +192,6 @@ int verify_file(const char* path) { // which could be exploitable. Fail verification if // this sequence occurs anywhere after the real one. LOGE("EOCD marker occurs after start of EOCD\n"); - fclose(f); return VERIFY_FAILURE; } } @@ -163,39 +211,42 @@ int verify_file(const char* path) { SHA256_CTX sha256_ctx; SHA_init(&sha1_ctx); SHA256_init(&sha256_ctx); - unsigned char* buffer = (unsigned char*)malloc(BUFFER_SIZE); - if (buffer == NULL) { - LOGE("failed to alloc memory for sha1 buffer\n"); - fclose(f); - return VERIFY_FAILURE; - } double frac = -1.0; size_t so_far = 0; - fseek(f, 0, SEEK_SET); while (so_far < signed_len) { - size_t size = BUFFER_SIZE; - if (signed_len - so_far < size) size = signed_len - so_far; - if (fread(buffer, 1, size, f) != size) { - LOGE("failed to read data from %s (%s)\n", path, strerror(errno)); - fclose(f); - return VERIFY_FAILURE; - } - if (need_sha1) SHA_update(&sha1_ctx, buffer, size); - if (need_sha256) SHA256_update(&sha256_ctx, buffer, size); + 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); so_far += size; + double f = so_far / (double)signed_len; if (f > frac + 0.02 || size == so_far) { //ui->SetProgress(f); frac = f; } } - fclose(f); - free(buffer); const uint8_t* sha1 = SHA_final(&sha1_ctx); const uint8_t* sha256 = SHA256_final(&sha256_ctx); + uint8_t* sig_der = NULL; + size_t sig_der_length = 0; + + size_t signature_size = signature_start - FOOTER_SIZE; + if (!read_pkcs7(eocd + eocd_size - signature_start, signature_size, &sig_der, + &sig_der_length)) { + LOGE("Could not find signature DER block\n"); + return VERIFY_FAILURE; + } + + /* + * Check to make sure at least one of the keys matches the signature. Since + * any key can match, we need to try each before determining a verification + * failure has happened. + */ for (i = 0; i < numKeys; ++i) { const uint8_t* hash; switch (pKeys[i].hash_len) { @@ -206,17 +257,47 @@ int verify_file(const char* path) { // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that // the signing tool appends after the signature itself. - if (RSA_verify(pKeys[i].public_key, eocd + eocd_size - 6 - RSANUMBYTES, - RSANUMBYTES, hash, pKeys[i].hash_len)) { - LOGI("whole-file signature verified against key %d\n", i); - free(eocd); + if (pKeys[i].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(pKeys[i].rsa, sig_der, RSANUMBYTES, + hash, pKeys[i].hash_len)) { + LOGI("failed to verify against RSA key %zu\n", i); + continue; + } + + LOGI("whole-file signature verified against RSA key %zu\n", i); + free(sig_der); + return VERIFY_SUCCESS; + } else if (pKeys[i].key_type == Certificate::EC + && pKeys[i].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(&(pKeys[i].ec->x), &(pKeys[i].ec->y), + &p256_hash, &r, &s)) { + LOGI("failed to verify against EC key %zu\n", i); + continue; + } + + LOGI("whole-file signature verified against EC key %zu\n", i); + free(sig_der); return VERIFY_SUCCESS; } else { - LOGI("failed to verify against key %d\n", i); + LOGI("Unknown key type %d\n", pKeys[i].key_type); } LOGI("i: %i, eocd_size: %i, RSANUMBYTES: %i\n", i, eocd_size, RSANUMBYTES); } - free(eocd); + free(sig_der); LOGE("failed to verify whole-file signature\n"); return VERIFY_FAILURE; } @@ -248,6 +329,7 @@ int verify_file(const char* path) { // 2: 2048-bit RSA key with e=65537 and SHA-1 hash // 3: 2048-bit RSA key with e=3 and SHA-256 hash // 4: 2048-bit RSA key with e=65537 and SHA-256 hash +// 5: 256-bit EC key using the NIST P-256 curve parameters and SHA-256 hash // // Returns NULL if the file failed to parse, or if it contain zero keys. Certificate* @@ -268,28 +350,41 @@ load_keys(const char* filename, int* numKeys) { ++*numKeys; out = (Certificate*)realloc(out, *numKeys * sizeof(Certificate)); Certificate* cert = out + (*numKeys - 1); - cert->public_key = (RSAPublicKey*)malloc(sizeof(RSAPublicKey)); + memset(cert, '\0', sizeof(Certificate)); char start_char; if (fscanf(f, " %c", &start_char) != 1) goto exit; if (start_char == '{') { // a version 1 key has no version specifier. - cert->public_key->exponent = 3; + cert->key_type = Certificate::RSA; + cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey)); + cert->rsa->exponent = 3; cert->hash_len = SHA_DIGEST_SIZE; } else if (start_char == 'v') { int version; if (fscanf(f, "%d {", &version) != 1) goto exit; switch (version) { case 2: - cert->public_key->exponent = 65537; + cert->key_type = Certificate::RSA; + cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey)); + cert->rsa->exponent = 65537; cert->hash_len = SHA_DIGEST_SIZE; break; case 3: - cert->public_key->exponent = 3; + cert->key_type = Certificate::RSA; + cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey)); + cert->rsa->exponent = 3; cert->hash_len = SHA256_DIGEST_SIZE; break; case 4: - cert->public_key->exponent = 65537; + cert->key_type = Certificate::RSA; + cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey)); + cert->rsa->exponent = 65537; + cert->hash_len = SHA256_DIGEST_SIZE; + break; + case 5: + cert->key_type = Certificate::EC; + cert->ec = (ECPublicKey*)calloc(1, sizeof(ECPublicKey)); cert->hash_len = SHA256_DIGEST_SIZE; break; default: @@ -297,23 +392,55 @@ load_keys(const char* filename, int* numKeys) { } } - RSAPublicKey* key = cert->public_key; - if (fscanf(f, " %i , 0x%x , { %u", - &(key->len), &(key->n0inv), &(key->n[0])) != 3) { - goto exit; - } - if (key->len != RSANUMWORDS) { - LOGE("key length (%d) does not match expected size\n", key->len); + if (cert->key_type == Certificate::RSA) { + RSAPublicKey* key = cert->rsa; + if (fscanf(f, " %i , 0x%x , { %u", + &(key->len), &(key->n0inv), &(key->n[0])) != 3) { + goto exit; + } + if (key->len != RSANUMWORDS) { + LOGE("key length (%d) does not match expected size\n", key->len); + goto exit; + } + for (i = 1; i < key->len; ++i) { + if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit; + } + if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit; + for (i = 1; i < key->len; ++i) { + if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit; + } + fscanf(f, " } } "); + + LOGI("read key e=%d hash=%d\n", key->exponent, cert->hash_len); + } else if (cert->key_type == Certificate::EC) { + ECPublicKey* key = cert->ec; + int key_len; + unsigned int byte; + uint8_t x_bytes[P256_NBYTES]; + uint8_t y_bytes[P256_NBYTES]; + if (fscanf(f, " %i , { %u", &key_len, &byte) != 2) goto exit; + if (key_len != P256_NBYTES) { + LOGE("Key length (%d) does not match expected size %d\n", key_len, P256_NBYTES); + goto exit; + } + x_bytes[P256_NBYTES - 1] = byte; + for (i = P256_NBYTES - 2; i >= 0; --i) { + if (fscanf(f, " , %u", &byte) != 1) goto exit; + x_bytes[i] = byte; + } + if (fscanf(f, " } , { %u", &byte) != 1) goto exit; + y_bytes[P256_NBYTES - 1] = byte; + for (i = P256_NBYTES - 2; i >= 0; --i) { + if (fscanf(f, " , %u", &byte) != 1) goto exit; + y_bytes[i] = byte; + } + fscanf(f, " } } "); + p256_from_bin(x_bytes, &key->x); + p256_from_bin(y_bytes, &key->y); + } else { + LOGE("Unknown key type %d\n", cert->key_type); goto exit; } - for (i = 1; i < key->len; ++i) { - if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit; - } - if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit; - for (i = 1; i < key->len; ++i) { - if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit; - } - fscanf(f, " } } "); // if the line ends in a comma, this file has more keys. switch (fgetc(f)) { @@ -329,7 +456,10 @@ load_keys(const char* filename, int* numKeys) { LOGE("unexpected character between keys\n"); goto exit; } +<<<<<<< HEAD LOGI("read key e=%d hash=%d\n", key->exponent, cert->hash_len); +======= +>>>>>>> cddb68b5eafbeba696d5276bda1f1a9f70bbde42 } } diff --git a/verifier.h b/verifier.h index d70417340..43fd5adcd 100644 --- a/verifier.h +++ b/verifier.h @@ -17,6 +17,7 @@ #ifndef _RECOVERY_VERIFIER_H #define _RECOVERY_VERIFIER_H +#include "mincrypt/p256.h" #include "mincrypt/rsa.h" #define ASSUMED_UPDATE_BINARY_NAME "META-INF/com/google/android/update-binary" @@ -26,14 +27,30 @@ enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT }; static const float VERIFICATION_PROGRESS_FRACTION = 0.25; typedef struct Certificate { + +typedef struct { + p256_int x; + p256_int y; +} ECPublicKey; + +typedef struct { + typedef enum { + RSA, + EC, + } KeyType; + int hash_len; // SHA_DIGEST_SIZE (SHA-1) or SHA256_DIGEST_SIZE (SHA-256) - RSAPublicKey* public_key; + KeyType key_type; + RSAPublicKey* rsa; + ECPublicKey* ec; } Certificate; -/* Look in the file for a signature footer, and verify that it - * matches one of the given keys. Return one of the constants below. +/* addr and length define a an update package file that has been + * loaded (or mmap'ed, or whatever) into memory. Verify that the file + * is signed and the signature matches one of the given keys. Return + * one of the constants below. */ -int verify_file(const char* path); +int verify_file(unsigned char* addr, size_t length); Certificate* load_keys(const char* filename, int* numKeys); diff --git a/verifier_test.cpp b/verifier_test.cpp index 20aa3d1de..3ba270de7 100644 --- a/verifier_test.cpp +++ b/verifier_test.cpp @@ -17,6 +17,9 @@ #include <stdio.h> #include <stdlib.h> #include <stdarg.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> /* #include "common.h" @@ -25,6 +28,7 @@ #include "ui.h" #include "mincrypt/sha.h" #include "mincrypt/sha256.h" +#include "minzip/SysUtil.h" // This is build/target/product/security/testkey.x509.pem after being // dumped out by dumpkey.jar. @@ -102,6 +106,18 @@ RSAPublicKey test_f4_key = 65537 }; +ECPublicKey test_ec_key = + { + { + {0xd656fa24u, 0x931416cau, 0x1c0278c6u, 0x174ebe4cu, + 0x6018236au, 0x45ba1656u, 0xe8c05d84u, 0x670ed500u} + }, + { + {0x0d179adeu, 0x4c16827du, 0x9f8cb992u, 0x8f69ff8au, + 0x481b1020u, 0x798d91afu, 0x184db8e9u, 0xb5848dd9u} + } + }; + RecoveryUI* ui = NULL; // verifier expects to find a UI object; we provide one that does @@ -138,37 +154,93 @@ ui_print(const char* format, ...) { va_end(ap); } +static Certificate* add_certificate(Certificate** certsp, int* num_keys, + Certificate::KeyType key_type) { + int i = *num_keys; + *num_keys = *num_keys + 1; + *certsp = (Certificate*) realloc(*certsp, *num_keys * sizeof(Certificate)); + Certificate* certs = *certsp; + certs[i].rsa = NULL; + certs[i].ec = NULL; + certs[i].key_type = key_type; + certs[i].hash_len = SHA_DIGEST_SIZE; + return &certs[i]; +} + int main(int argc, char **argv) { - if (argc < 2 || argc > 4) { - fprintf(stderr, "Usage: %s [-sha256] [-f4 | -file <keys>] <package>\n", argv[0]); + if (argc < 2) { + fprintf(stderr, "Usage: %s [-sha256] [-ec | -f4 | -file <keys>] <package>\n", argv[0]); return 2; } + Certificate* certs = NULL; + int num_keys = 0; - Certificate default_cert; - Certificate* cert = &default_cert; - cert->public_key = &test_key; - cert->hash_len = SHA_DIGEST_SIZE; - int num_keys = 1; - ++argv; - if (strcmp(argv[0], "-sha256") == 0) { - ++argv; - cert->hash_len = SHA256_DIGEST_SIZE; + int argn = 1; + while (argn < argc) { + if (strcmp(argv[argn], "-sha256") == 0) { + if (num_keys == 0) { + fprintf(stderr, "May only specify -sha256 after key type\n"); + return 2; + } + ++argn; + Certificate* cert = &certs[num_keys - 1]; + cert->hash_len = SHA256_DIGEST_SIZE; + } else if (strcmp(argv[argn], "-ec") == 0) { + ++argn; + Certificate* cert = add_certificate(&certs, &num_keys, Certificate::EC); + cert->ec = &test_ec_key; + } else if (strcmp(argv[argn], "-e3") == 0) { + ++argn; + Certificate* cert = add_certificate(&certs, &num_keys, Certificate::RSA); + cert->rsa = &test_key; + } else if (strcmp(argv[argn], "-f4") == 0) { + ++argn; + Certificate* cert = add_certificate(&certs, &num_keys, Certificate::RSA); + cert->rsa = &test_f4_key; + } else if (strcmp(argv[argn], "-file") == 0) { + if (certs != NULL) { + fprintf(stderr, "Cannot specify -file with other certs specified\n"); + return 2; + } + ++argn; + certs = load_keys(argv[argn], &num_keys); + ++argn; + } else if (argv[argn][0] == '-') { + fprintf(stderr, "Unknown argument %s\n", argv[argn]); + return 2; + } else { + break; + } } - if (strcmp(argv[0], "-f4") == 0) { - ++argv; - cert->public_key = &test_f4_key; - } else if (strcmp(argv[0], "-file") == 0) { - ++argv; - cert = load_keys(argv[0], &num_keys); - ++argv; + + if (argn == argc) { + fprintf(stderr, "Must specify package to verify\n"); + return 2; + } + + if (num_keys == 0) { + certs = (Certificate*) calloc(1, sizeof(Certificate)); + if (certs == NULL) { + fprintf(stderr, "Failure allocating memory for default certificate\n"); + return 1; + } + certs->key_type = Certificate::RSA; + certs->rsa = &test_key; + certs->ec = NULL; + certs->hash_len = SHA_DIGEST_SIZE; + num_keys = 1; } ui = new FakeUI(); -/* - int result = verify_file(*argv, cert, num_keys); -*/ - int result = verify_file(*argv); + MemMapping map; + if (sysMapFile(argv[argn], &map) != 0) { + fprintf(stderr, "failed to mmap %s: %s\n", argv[argn], strerror(errno)); + return 4; + } + + int result = verify_file(map.addr, map.length); + if (result == VERIFY_SUCCESS) { printf("VERIFIED\n"); return 0; diff --git a/verifier_test.sh b/verifier_test.sh index 65f77f401..4761cef4a 100755 --- a/verifier_test.sh +++ b/verifier_test.sh @@ -81,20 +81,30 @@ expect_fail unsigned.zip expect_fail jarsigned.zip # success cases -expect_succeed otasigned.zip +expect_succeed otasigned.zip -e3 expect_succeed otasigned_f4.zip -f4 -expect_succeed otasigned_sha256.zip -sha256 -expect_succeed otasigned_f4_sha256.zip -sha256 -f4 +expect_succeed otasigned_sha256.zip -e3 -sha256 +expect_succeed otasigned_f4_sha256.zip -f4 -sha256 +expect_succeed otasigned_ecdsa_sha256.zip -ec -sha256 + +# success with multiple keys +expect_succeed otasigned.zip -f4 -e3 +expect_succeed otasigned_f4.zip -ec -f4 +expect_succeed otasigned_sha256.zip -ec -e3 -e3 -sha256 +expect_succeed otasigned_f4_sha256.zip -ec -sha256 -e3 -f4 -sha256 +expect_succeed otasigned_ecdsa_sha256.zip -f4 -sha256 -e3 -ec -sha256 # verified against different key expect_fail otasigned.zip -f4 -expect_fail otasigned_f4.zip +expect_fail otasigned_f4.zip -e3 +expect_fail otasigned_ecdsa_sha256.zip -e3 -sha256 # verified against right key but wrong hash algorithm -expect_fail otasigned.zip -sha256 -expect_fail otasigned_f4.zip -sha256 -f4 +expect_fail otasigned.zip -e3 -sha256 +expect_fail otasigned_f4.zip -f4 -sha256 expect_fail otasigned_sha256.zip expect_fail otasigned_f4_sha256.zip -f4 +expect_fail otasigned_ecdsa_sha256.zip # various other cases expect_fail random.zip |