diff --git a/Android.mk b/Android.mk index 5879670..d4dd002 100644 --- a/Android.mk +++ b/Android.mk @@ -8,17 +8,17 @@ commands_recovery_local_path := $(LOCAL_PATH) # LOCAL_CPP_EXTENSION := .c LOCAL_SRC_FILES := \ - extendedcommands.c \ - nandroid.c \ - legacy.c \ - commands.c \ - recovery.c \ - bootloader.c \ - firmware.c \ - install.c \ - roots.c \ - ui.c \ - verifier.c + extendedcommands.c \ + nandroid.c \ + legacy.c \ + commands.c \ + recovery.c \ + bootloader.c \ + firmware.c \ + install.c \ + roots.c \ + ui.c \ + verifier.c LOCAL_SRC_FILES += test_roots.c @@ -28,7 +28,7 @@ LOCAL_FORCE_STATIC_EXECUTABLE := true RECOVERY_VERSION := ClockworkMod Recovery v2.0.1.3 LOCAL_CFLAGS := -DRECOVERY_VERSION="$(RECOVERY_VERSION)" -RECOVERY_API_VERSION := 2 +RECOVERY_API_VERSION := 3 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) ifeq ($(BOARD_HAS_NO_SELECT_BUTTON),true) @@ -133,14 +133,31 @@ LOCAL_SRC_FILES := killrecovery.sh include $(BUILD_PREBUILT) include $(commands_recovery_local_path)/amend/Android.mk + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := verifier_test.c verifier.c + +LOCAL_MODULE := verifier_test + +LOCAL_FORCE_STATIC_EXECUTABLE := true + +LOCAL_MODULE_TAGS := tests + +LOCAL_STATIC_LIBRARIES := libmincrypt libcutils libstdc++ libc + +include $(BUILD_EXECUTABLE) + + include $(commands_recovery_local_path)/minui/Android.mk include $(commands_recovery_local_path)/minzip/Android.mk include $(commands_recovery_local_path)/mtdutils/Android.mk include $(commands_recovery_local_path)/tools/Android.mk include $(commands_recovery_local_path)/edify/Android.mk include $(commands_recovery_local_path)/updater/Android.mk +include $(commands_recovery_local_path)/applypatch/Android.mk commands_recovery_local_path := endif # TARGET_ARCH == arm -endif # !TARGET_SIMULATOR +endif # !TARGET_SIMULATOR diff --git a/CleanSpec.mk b/CleanSpec.mk new file mode 100644 index 0000000..b84e1b6 --- /dev/null +++ b/CleanSpec.mk @@ -0,0 +1,49 @@ +# Copyright (C) 2007 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# If you don't need to do a full clean build but would like to touch +# a file or delete some intermediate files, add a clean step to the end +# of the list. These steps will only be run once, if they haven't been +# run before. +# +# E.g.: +# $(call add-clean-step, touch -c external/sqlite/sqlite3.h) +# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates) +# +# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with +# files that are missing or have been moved. +# +# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory. +# Use $(OUT_DIR) to refer to the "out" directory. +# +# If you need to re-do something that's already mentioned, just copy +# the command and add it to the bottom of the list. E.g., if a change +# that you made last week required touching a file and a change you +# made today requires touching the same file, just copy the old +# touch step and add it to the end of the list. +# +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ + +# For example: +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates) +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates) +#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f) +#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*) + +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ diff --git a/applypatch/Android.mk b/applypatch/Android.mk new file mode 100644 index 0000000..bb024f6 --- /dev/null +++ b/applypatch/Android.mk @@ -0,0 +1,61 @@ +# Copyright (C) 2008 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ifneq ($(TARGET_SIMULATOR),true) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := applypatch.c bspatch.c freecache.c imgpatch.c utils.c +LOCAL_MODULE := libapplypatch +LOCAL_MODULE_TAGS := eng +LOCAL_C_INCLUDES += external/bzip2 external/zlib bootable/recovery +LOCAL_STATIC_LIBRARIES += libmtdutils libmincrypt libbz libz + +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := main.c +LOCAL_MODULE := applypatch +LOCAL_C_INCLUDES += bootable/recovery +LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypt libbz +LOCAL_SHARED_LIBRARIES += libz libcutils libstdc++ libc + +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := main.c +LOCAL_MODULE := applypatch_static +LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_MODULE_TAGS := eng +LOCAL_C_INCLUDES += bootable/recovery +LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypt libbz +LOCAL_STATIC_LIBRARIES += libz libcutils libstdc++ libc + +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := imgdiff.c utils.c bsdiff.c +LOCAL_MODULE := imgdiff +LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_MODULE_TAGS := eng +LOCAL_C_INCLUDES += external/zlib external/bzip2 +LOCAL_STATIC_LIBRARIES += libz libbz + +include $(BUILD_HOST_EXECUTABLE) + +endif # !TARGET_SIMULATOR diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c new file mode 100644 index 0000000..99d3661 --- /dev/null +++ b/applypatch/applypatch.c @@ -0,0 +1,815 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mincrypt/sha.h" +#include "applypatch.h" +#include "mtdutils/mtdutils.h" +#include "edify/expr.h" + +int SaveFileContents(const char* filename, FileContents file); +int LoadMTDContents(const char* filename, FileContents* file); +int ParseSha1(const char* str, uint8_t* digest); +ssize_t FileSink(unsigned char* data, ssize_t len, void* token); + +static int mtd_partitions_scanned = 0; + +// Read a file into memory; store it and its associated metadata in +// *file. Return 0 on success. +int LoadFileContents(const char* filename, FileContents* file) { + file->data = NULL; + + // A special 'filename' beginning with "MTD:" means to load the + // contents of an MTD partition. + if (strncmp(filename, "MTD:", 4) == 0) { + return LoadMTDContents(filename, file); + } + + if (stat(filename, &file->st) != 0) { + printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); + return -1; + } + + file->size = file->st.st_size; + file->data = malloc(file->size); + + FILE* f = fopen(filename, "rb"); + if (f == NULL) { + printf("failed to open \"%s\": %s\n", filename, strerror(errno)); + free(file->data); + file->data = NULL; + return -1; + } + + ssize_t bytes_read = fread(file->data, 1, file->size, f); + if (bytes_read != file->size) { + printf("short read of \"%s\" (%ld bytes of %ld)\n", + filename, (long)bytes_read, (long)file->size); + free(file->data); + file->data = NULL; + return -1; + } + fclose(f); + + SHA(file->data, file->size, file->sha1); + return 0; +} + +static size_t* size_array; +// comparison function for qsort()ing an int array of indexes into +// size_array[]. +static int compare_size_indices(const void* a, const void* b) { + int aa = *(int*)a; + int bb = *(int*)b; + if (size_array[aa] < size_array[bb]) { + return -1; + } else if (size_array[aa] > size_array[bb]) { + return 1; + } else { + return 0; + } +} + +void FreeFileContents(FileContents* file) { + if (file) free(file->data); + free(file); +} + +// Load the contents of an MTD partition into the provided +// FileContents. filename should be a string of the form +// "MTD::::::...". +// The smallest size_n bytes for which that prefix of the mtd contents +// has the corresponding sha1 hash will be loaded. It is acceptable +// for a size value to be repeated with different sha1s. Will return +// 0 on success. +// +// This complexity is needed because if an OTA installation is +// interrupted, the partition might contain either the source or the +// target data, which might be of different lengths. We need to know +// the length in order to read from MTD (there is no "end-of-file" +// marker), so the caller must specify the possible lengths and the +// hash of the data, and we'll do the load expecting to find one of +// those hashes. +int LoadMTDContents(const char* filename, FileContents* file) { + char* copy = strdup(filename); + const char* magic = strtok(copy, ":"); + if (strcmp(magic, "MTD") != 0) { + printf("LoadMTDContents called with bad filename (%s)\n", + filename); + return -1; + } + const char* partition = strtok(NULL, ":"); + + int i; + int colons = 0; + for (i = 0; filename[i] != '\0'; ++i) { + if (filename[i] == ':') { + ++colons; + } + } + if (colons < 3 || colons%2 == 0) { + printf("LoadMTDContents called with bad filename (%s)\n", + filename); + } + + int pairs = (colons-1)/2; // # of (size,sha1) pairs in filename + int* index = malloc(pairs * sizeof(int)); + size_t* size = malloc(pairs * sizeof(size_t)); + char** sha1sum = malloc(pairs * sizeof(char*)); + + for (i = 0; i < pairs; ++i) { + const char* size_str = strtok(NULL, ":"); + size[i] = strtol(size_str, NULL, 10); + if (size[i] == 0) { + printf("LoadMTDContents called with bad size (%s)\n", filename); + return -1; + } + sha1sum[i] = strtok(NULL, ":"); + index[i] = i; + } + + // sort the index[] array so it indexes the pairs in order of + // increasing size. + size_array = size; + qsort(index, pairs, sizeof(int), compare_size_indices); + + if (!mtd_partitions_scanned) { + mtd_scan_partitions(); + mtd_partitions_scanned = 1; + } + + const MtdPartition* mtd = mtd_find_partition_by_name(partition); + if (mtd == NULL) { + printf("mtd partition \"%s\" not found (loading %s)\n", + partition, filename); + return -1; + } + + MtdReadContext* ctx = mtd_read_partition(mtd); + if (ctx == NULL) { + printf("failed to initialize read of mtd partition \"%s\"\n", + partition); + return -1; + } + + SHA_CTX sha_ctx; + SHA_init(&sha_ctx); + uint8_t parsed_sha[SHA_DIGEST_SIZE]; + + // allocate enough memory to hold the largest size. + file->data = malloc(size[index[pairs-1]]); + char* p = (char*)file->data; + file->size = 0; // # bytes read so far + + for (i = 0; i < pairs; ++i) { + // Read enough additional bytes to get us up to the next size + // (again, we're trying the possibilities in order of increasing + // size). + size_t next = size[index[i]] - file->size; + size_t read = 0; + if (next > 0) { + read = mtd_read_data(ctx, p, next); + if (next != read) { + printf("short read (%d bytes of %d) for partition \"%s\"\n", + read, next, partition); + free(file->data); + file->data = NULL; + return -1; + } + SHA_update(&sha_ctx, p, read); + file->size += read; + } + + // Duplicate the SHA context and finalize the duplicate so we can + // check it against this pair's expected hash. + SHA_CTX temp_ctx; + memcpy(&temp_ctx, &sha_ctx, sizeof(SHA_CTX)); + const uint8_t* sha_so_far = SHA_final(&temp_ctx); + + if (ParseSha1(sha1sum[index[i]], parsed_sha) != 0) { + printf("failed to parse sha1 %s in %s\n", + sha1sum[index[i]], filename); + free(file->data); + file->data = NULL; + return -1; + } + + 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("mtd read matched size %d sha %s\n", + size[index[i]], sha1sum[index[i]]); + break; + } + + p += read; + } + + mtd_read_close(ctx); + + if (i == pairs) { + // Ran off the end of the list of (size,sha1) pairs without + // finding a match. + printf("contents of MTD partition \"%s\" didn't match %s\n", + partition, filename); + free(file->data); + file->data = NULL; + return -1; + } + + const uint8_t* sha_final = SHA_final(&sha_ctx); + for (i = 0; i < SHA_DIGEST_SIZE; ++i) { + file->sha1[i] = sha_final[i]; + } + + // Fake some stat() info. + file->st.st_mode = 0644; + file->st.st_uid = 0; + file->st.st_gid = 0; + + free(copy); + free(index); + free(size); + free(sha1sum); + + return 0; +} + + +// Save the contents of the given FileContents object under the given +// filename. Return 0 on success. +int SaveFileContents(const char* filename, FileContents file) { + int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC); + if (fd < 0) { + printf("failed to open \"%s\" for write: %s\n", + filename, strerror(errno)); + return -1; + } + + ssize_t bytes_written = FileSink(file.data, file.size, &fd); + if (bytes_written != file.size) { + printf("short write of \"%s\" (%ld bytes of %ld) (%s)\n", + filename, (long)bytes_written, (long)file.size, + strerror(errno)); + close(fd); + return -1; + } + fsync(fd); + close(fd); + + if (chmod(filename, file.st.st_mode) != 0) { + printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } + if (chown(filename, file.st.st_uid, file.st.st_gid) != 0) { + printf("chown of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } + + return 0; +} + +// Write a memory buffer to target_mtd partition, a string of the form +// "MTD:[:...]". Return 0 on success. +int WriteToMTDPartition(unsigned char* data, size_t len, + const char* target_mtd) { + char* partition = strchr(target_mtd, ':'); + if (partition == NULL) { + printf("bad MTD target name \"%s\"\n", target_mtd); + return -1; + } + ++partition; + // Trim off anything after a colon, eg "MTD:boot:blah:blah:blah...". + // We want just the partition name "boot". + partition = strdup(partition); + char* end = strchr(partition, ':'); + if (end != NULL) + *end = '\0'; + + if (!mtd_partitions_scanned) { + mtd_scan_partitions(); + mtd_partitions_scanned = 1; + } + + const MtdPartition* mtd = mtd_find_partition_by_name(partition); + if (mtd == NULL) { + printf("mtd partition \"%s\" not found for writing\n", partition); + return -1; + } + + MtdWriteContext* ctx = mtd_write_partition(mtd); + if (ctx == NULL) { + printf("failed to init mtd partition \"%s\" for writing\n", + partition); + return -1; + } + + size_t written = mtd_write_data(ctx, (char*)data, len); + if (written != len) { + printf("only wrote %d of %d bytes to MTD %s\n", + written, len, partition); + mtd_write_close(ctx); + return -1; + } + + if (mtd_erase_blocks(ctx, -1) < 0) { + printf("error finishing mtd write of %s\n", partition); + mtd_write_close(ctx); + return -1; + } + + if (mtd_write_close(ctx)) { + printf("error closing mtd write of %s\n", partition); + return -1; + } + + free(partition); + return 0; +} + + +// Take a string 'str' of 40 hex digits and parse it into the 20 +// byte array 'digest'. 'str' may contain only the digest or be of +// the form ":". Return 0 on success, -1 on any +// error. +int ParseSha1(const char* str, uint8_t* digest) { + int i; + const char* ps = str; + uint8_t* pd = digest; + for (i = 0; i < SHA_DIGEST_SIZE * 2; ++i, ++ps) { + int digit; + if (*ps >= '0' && *ps <= '9') { + digit = *ps - '0'; + } else if (*ps >= 'a' && *ps <= 'f') { + digit = *ps - 'a' + 10; + } else if (*ps >= 'A' && *ps <= 'F') { + digit = *ps - 'A' + 10; + } else { + return -1; + } + if (i % 2 == 0) { + *pd = digit << 4; + } else { + *pd |= digit; + ++pd; + } + } + if (*ps != '\0') return -1; + return 0; +} + +// Search an array of sha1 strings for one matching the given sha1. +// Return the index of the match on success, or -1 if no match is +// found. +int FindMatchingPatch(uint8_t* sha1, char** const patch_sha1_str, + int num_patches) { + int i; + uint8_t patch_sha1[SHA_DIGEST_SIZE]; + for (i = 0; i < num_patches; ++i) { + if (ParseSha1(patch_sha1_str[i], patch_sha1) == 0 && + memcmp(patch_sha1, sha1, SHA_DIGEST_SIZE) == 0) { + return i; + } + } + return -1; +} + +// Returns 0 if the contents of the file (argv[2]) or the cached file +// match any of the sha1's on the command line (argv[3:]). Returns +// nonzero otherwise. +int applypatch_check(const char* filename, + int num_patches, char** const patch_sha1_str) { + FileContents file; + file.data = NULL; + + // It's okay to specify no sha1s; the check will pass if the + // LoadFileContents is successful. (Useful for reading MTD + // partitions, where the filename encodes the sha1s; no need to + // check them twice.) + if (LoadFileContents(filename, &file) != 0 || + (num_patches > 0 && + FindMatchingPatch(file.sha1, patch_sha1_str, num_patches) < 0)) { + printf("file \"%s\" doesn't have any of expected " + "sha1 sums; checking cache\n", filename); + + free(file.data); + + // If the source file is missing or corrupted, it might be because + // we were killed in the middle of patching it. A copy of it + // should have been made in CACHE_TEMP_SOURCE. If that file + // exists and matches the sha1 we're looking for, the check still + // passes. + + if (LoadFileContents(CACHE_TEMP_SOURCE, &file) != 0) { + printf("failed to load cache file\n"); + return 1; + } + + if (FindMatchingPatch(file.sha1, patch_sha1_str, num_patches) < 0) { + printf("cache bits don't match any sha1 for \"%s\"\n", filename); + free(file.data); + return 1; + } + } + + free(file.data); + return 0; +} + +int ShowLicenses() { + ShowBSDiffLicense(); + return 0; +} + +ssize_t FileSink(unsigned char* data, ssize_t len, void* token) { + int fd = *(int *)token; + ssize_t done = 0; + ssize_t wrote; + while (done < (ssize_t) len) { + wrote = write(fd, data+done, len-done); + if (wrote <= 0) { + printf("error writing %d bytes: %s\n", (int)(len-done), strerror(errno)); + return done; + } + done += wrote; + } + return done; +} + +typedef struct { + unsigned char* buffer; + ssize_t size; + ssize_t pos; +} MemorySinkInfo; + +ssize_t MemorySink(unsigned char* data, ssize_t len, void* token) { + MemorySinkInfo* msi = (MemorySinkInfo*)token; + if (msi->size - msi->pos < len) { + return -1; + } + memcpy(msi->buffer + msi->pos, data, len); + msi->pos += len; + return len; +} + +// Return the amount of free space (in bytes) on the filesystem +// containing filename. filename must exist. Return -1 on error. +size_t FreeSpaceForFile(const char* filename) { + struct statfs sf; + if (statfs(filename, &sf) != 0) { + printf("failed to statfs %s: %s\n", filename, strerror(errno)); + return -1; + } + return sf.f_bsize * sf.f_bfree; +} + +int CacheSizeCheck(size_t bytes) { + if (MakeFreeSpaceOnCache(bytes) < 0) { + printf("unable to make %ld bytes available on /cache\n", (long)bytes); + return 1; + } else { + return 0; + } +} + + +// This function applies binary patches to files in a way that is safe +// (the original file is not touched until we have the desired +// replacement for it) and idempotent (it's okay to run this program +// multiple times). +// +// - if the sha1 hash of is , +// does nothing and exits successfully. +// +// - otherwise, if the sha1 hash of is one of the +// entries in , the corresponding patch from +// (which must be a VAL_BLOB) is applied to produce a +// new file (the type of patch is automatically detected from the +// blob daat). If that new file has sha1 hash , +// moves it to replace , and exits successfully. +// Note that if and are not the +// same, is NOT deleted on success. +// may be the string "-" to mean "the same as +// source_filename". +// +// - otherwise, or if any error is encountered, exits with non-zero +// status. +// +// may refer to an MTD partition to read the source +// data. See the comments for the LoadMTDContents() function above +// for the format of such a filename. + +int applypatch(const char* source_filename, + const char* target_filename, + const char* target_sha1_str, + size_t target_size, + int num_patches, + char** const patch_sha1_str, + Value** patch_data) { + printf("\napplying patch to %s\n", source_filename); + + if (target_filename[0] == '-' && + target_filename[1] == '\0') { + target_filename = source_filename; + } + + uint8_t target_sha1[SHA_DIGEST_SIZE]; + if (ParseSha1(target_sha1_str, target_sha1) != 0) { + printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str); + return 1; + } + + FileContents copy_file; + FileContents source_file; + const Value* source_patch_value = NULL; + const Value* copy_patch_value = NULL; + int made_copy = 0; + + // We try to load the target file into the source_file object. + if (LoadFileContents(target_filename, &source_file) == 0) { + if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_SIZE) == 0) { + // The early-exit case: the patch was already applied, this file + // has the desired hash, nothing for us to do. + printf("\"%s\" is already target; no patch needed\n", + target_filename); + return 0; + } + } + + if (source_file.data == NULL || + (target_filename != source_filename && + strcmp(target_filename, source_filename) != 0)) { + // Need to load the source file: either we failed to load the + // target file, or we did but it's different from the source file. + free(source_file.data); + LoadFileContents(source_filename, &source_file); + } + + if (source_file.data != NULL) { + int to_use = FindMatchingPatch(source_file.sha1, + patch_sha1_str, num_patches); + if (to_use >= 0) { + source_patch_value = patch_data[to_use]; + } + } + + if (source_patch_value == NULL) { + free(source_file.data); + printf("source file is bad; trying copy\n"); + + if (LoadFileContents(CACHE_TEMP_SOURCE, ©_file) < 0) { + // fail. + printf("failed to read copy file\n"); + return 1; + } + + int to_use = FindMatchingPatch(copy_file.sha1, + patch_sha1_str, num_patches); + if (to_use > 0) { + copy_patch_value = patch_data[to_use]; + } + + if (copy_patch_value == NULL) { + // fail. + printf("copy file doesn't match source SHA-1s either\n"); + return 1; + } + } + + int retry = 1; + SHA_CTX ctx; + int output; + MemorySinkInfo msi; + FileContents* source_to_use; + char* outname; + + // assume that target_filename (eg "/system/app/Foo.apk") is located + // on the same filesystem as its top-level directory ("/system"). + // We need something that exists for calling statfs(). + char target_fs[strlen(target_filename)+1]; + char* slash = strchr(target_filename+1, '/'); + if (slash != NULL) { + int count = slash - target_filename; + strncpy(target_fs, target_filename, count); + target_fs[count] = '\0'; + } else { + strcpy(target_fs, target_filename); + } + + do { + // Is there enough room in the target filesystem to hold the patched + // file? + + if (strncmp(target_filename, "MTD:", 4) == 0) { + // If the target is an MTD partition, we're actually going to + // write the output to /tmp and then copy it to the partition. + // statfs() always returns 0 blocks free for /tmp, so instead + // we'll just assume that /tmp has enough space to hold the file. + + // We still write the original source to cache, in case the MTD + // write is interrupted. + if (MakeFreeSpaceOnCache(source_file.size) < 0) { + printf("not enough free space on /cache\n"); + return 1; + } + if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) { + printf("failed to back up source file\n"); + return 1; + } + made_copy = 1; + retry = 0; + } else { + int enough_space = 0; + if (retry > 0) { + size_t free_space = FreeSpaceForFile(target_fs); + int enough_space = + (free_space > (target_size * 3 / 2)); // 50% margin of error + printf("target %ld bytes; free space %ld bytes; retry %d; enough %d\n", + (long)target_size, (long)free_space, retry, enough_space); + } + + if (!enough_space) { + retry = 0; + } + + if (!enough_space && source_patch_value != NULL) { + // Using the original source, but not enough free space. First + // copy the source file to cache, then delete it from the original + // location. + + if (strncmp(source_filename, "MTD:", 4) == 0) { + // It's impossible to free space on the target filesystem by + // deleting the source if the source is an MTD partition. If + // we're ever in a state where we need to do this, fail. + printf("not enough free space for target but source is MTD\n"); + return 1; + } + + if (MakeFreeSpaceOnCache(source_file.size) < 0) { + printf("not enough free space on /cache\n"); + return 1; + } + + if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) { + printf("failed to back up source file\n"); + return 1; + } + made_copy = 1; + unlink(source_filename); + + size_t free_space = FreeSpaceForFile(target_fs); + printf("(now %ld bytes free for target)\n", (long)free_space); + } + } + + const Value* patch; + if (source_patch_value != NULL) { + source_to_use = &source_file; + patch = source_patch_value; + } else { + source_to_use = ©_file; + patch = copy_patch_value; + } + + if (patch->type != VAL_BLOB) { + printf("patch is not a blob\n"); + return 1; + } + + SinkFn sink = NULL; + void* token = NULL; + output = -1; + outname = NULL; + if (strncmp(target_filename, "MTD:", 4) == 0) { + // We store the decoded output in memory. + msi.buffer = malloc(target_size); + if (msi.buffer == NULL) { + printf("failed to alloc %ld bytes for output\n", + (long)target_size); + return 1; + } + msi.pos = 0; + msi.size = target_size; + sink = MemorySink; + token = &msi; + } else { + // We write the decoded output to ".patch". + outname = (char*)malloc(strlen(target_filename) + 10); + strcpy(outname, target_filename); + strcat(outname, ".patch"); + + output = open(outname, O_WRONLY | O_CREAT | O_TRUNC); + if (output < 0) { + printf("failed to open output file %s: %s\n", + outname, strerror(errno)); + return 1; + } + sink = FileSink; + token = &output; + } + + char* header = patch->data; + ssize_t header_bytes_read = patch->size; + + SHA_init(&ctx); + + int result; + + if (header_bytes_read >= 8 && + memcmp(header, "BSDIFF40", 8) == 0) { + result = ApplyBSDiffPatch(source_to_use->data, source_to_use->size, + patch, 0, sink, token, &ctx); + } else if (header_bytes_read >= 8 && + memcmp(header, "IMGDIFF2", 8) == 0) { + result = ApplyImagePatch(source_to_use->data, source_to_use->size, + patch, sink, token, &ctx); + } else { + printf("Unknown patch file format\n"); + return 1; + } + + if (output >= 0) { + fsync(output); + close(output); + } + + if (result != 0) { + if (retry == 0) { + printf("applying patch failed\n"); + return result != 0; + } else { + printf("applying patch failed; retrying\n"); + } + if (outname != NULL) { + unlink(outname); + } + } else { + // succeeded; no need to retry + break; + } + } while (retry-- > 0); + + const uint8_t* current_target_sha1 = SHA_final(&ctx); + if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_SIZE) != 0) { + printf("patch did not produce expected sha1\n"); + return 1; + } + + if (output < 0) { + // Copy the temp file to the MTD partition. + if (WriteToMTDPartition(msi.buffer, msi.pos, target_filename) != 0) { + printf("write of patched data to %s failed\n", target_filename); + return 1; + } + free(msi.buffer); + } else { + // Give the .patch file the same owner, group, and mode of the + // original source file. + if (chmod(outname, source_to_use->st.st_mode) != 0) { + printf("chmod of \"%s\" failed: %s\n", outname, strerror(errno)); + return 1; + } + if (chown(outname, source_to_use->st.st_uid, + source_to_use->st.st_gid) != 0) { + printf("chown of \"%s\" failed: %s\n", outname, strerror(errno)); + return 1; + } + + // Finally, rename the .patch file to replace the target file. + if (rename(outname, target_filename) != 0) { + printf("rename of .patch to \"%s\" failed: %s\n", + target_filename, strerror(errno)); + return 1; + } + } + + // If this run of applypatch created the copy, and we're here, we + // can delete it. + if (made_copy) unlink(CACHE_TEMP_SOURCE); + + // Success! + return 0; +} diff --git a/applypatch/applypatch.h b/applypatch/applypatch.h new file mode 100644 index 0000000..10c0125 --- /dev/null +++ b/applypatch/applypatch.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _APPLYPATCH_H +#define _APPLYPATCH_H + +#include +#include "mincrypt/sha.h" +#include "edify/expr.h" + +typedef struct _Patch { + uint8_t sha1[SHA_DIGEST_SIZE]; + const char* patch_filename; +} Patch; + +typedef struct _FileContents { + uint8_t sha1[SHA_DIGEST_SIZE]; + unsigned char* data; + ssize_t size; + struct stat st; +} FileContents; + +// When there isn't enough room on the target filesystem to hold the +// patched version of the file, we copy the original here and delete +// it to free up space. If the expected source file doesn't exist, or +// is corrupted, we look to see if this file contains the bits we want +// and use it as the source instead. +#define CACHE_TEMP_SOURCE "/cache/saved.file" + +typedef ssize_t (*SinkFn)(unsigned char*, ssize_t, void*); + +// applypatch.c +int ShowLicenses(); +size_t FreeSpaceForFile(const char* filename); +int CacheSizeCheck(size_t bytes); +int ParseSha1(const char* str, uint8_t* digest); + +int applypatch(const char* source_filename, + const char* target_filename, + const char* target_sha1_str, + size_t target_size, + int num_patches, + char** const patch_sha1_str, + Value** patch_data); +int applypatch_check(const char* filename, + int num_patches, + char** const patch_sha1_str); + +// Read a file into memory; store it and its associated metadata in +// *file. Return 0 on success. +int LoadFileContents(const char* filename, FileContents* file); +void FreeFileContents(FileContents* file); + +// bsdiff.c +void ShowBSDiffLicense(); +int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size, + const Value* patch, ssize_t patch_offset, + SinkFn sink, void* token, SHA_CTX* ctx); +int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, + const Value* patch, ssize_t patch_offset, + unsigned char** new_data, ssize_t* new_size); + +// imgpatch.c +int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, + const Value* patch, + SinkFn sink, void* token, SHA_CTX* ctx); + +// freecache.c +int MakeFreeSpaceOnCache(size_t bytes_needed); + +#endif diff --git a/applypatch/applypatch.sh b/applypatch/applypatch.sh new file mode 100755 index 0000000..8ea68a1 --- /dev/null +++ b/applypatch/applypatch.sh @@ -0,0 +1,350 @@ +#!/bin/bash +# +# A test suite for applypatch. Run in a client where you have done +# envsetup, choosecombo, etc. +# +# DO NOT RUN THIS ON A DEVICE YOU CARE ABOUT. It will mess up your +# system partition. +# +# +# TODO: find some way to get this run regularly along with the rest of +# the tests. + +EMULATOR_PORT=5580 +DATA_DIR=$ANDROID_BUILD_TOP/bootable/recovery/applypatch/testdata + +# This must be the filename that applypatch uses for its copies. +CACHE_TEMP_SOURCE=/cache/saved.file + +# Put all binaries and files here. We use /cache because it's a +# temporary filesystem in the emulator; it's created fresh each time +# the emulator starts. +WORK_DIR=/system + +# partition that WORK_DIR is located on, without the leading slash +WORK_FS=system + +# set to 0 to use a device instead +USE_EMULATOR=1 + +# ------------------------ + +tmpdir=$(mktemp -d) + +if [ "$USE_EMULATOR" == 1 ]; then + emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT & + pid_emulator=$! + ADB="adb -s emulator-$EMULATOR_PORT " +else + ADB="adb -d " +fi + +echo "waiting to connect to device" +$ADB wait-for-device +echo "device is available" +$ADB remount +# free up enough space on the system partition for the test to run. +$ADB shell rm -r /system/media + +# run a command on the device; exit with the exit status of the device +# command. +run_command() { + $ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}' +} + +testname() { + echo + echo "$1"... + testname="$1" +} + +fail() { + echo + echo FAIL: $testname + echo + [ "$open_pid" == "" ] || kill $open_pid + [ "$pid_emulator" == "" ] || kill $pid_emulator + exit 1 +} + +sha1() { + sha1sum $1 | awk '{print $1}' +} + +free_space() { + run_command df | awk "/$1/ {print gensub(/K/, \"\", \"g\", \$6)}" +} + +cleanup() { + # not necessary if we're about to kill the emulator, but nice for + # running on real devices or already-running emulators. + testname "removing test files" + run_command rm $WORK_DIR/bloat.dat + run_command rm $WORK_DIR/old.file + run_command rm $WORK_DIR/foo + run_command rm $WORK_DIR/patch.bsdiff + run_command rm $WORK_DIR/applypatch + run_command rm $CACHE_TEMP_SOURCE + run_command rm /cache/bloat*.dat + + [ "$pid_emulator" == "" ] || kill $pid_emulator + + if [ $# == 0 ]; then + rm -rf $tmpdir + fi +} + +cleanup leave_tmp + +$ADB push $ANDROID_PRODUCT_OUT/system/bin/applypatch $WORK_DIR/applypatch + +BAD1_SHA1=$(printf "%040x" $RANDOM) +BAD2_SHA1=$(printf "%040x" $RANDOM) +OLD_SHA1=$(sha1 $DATA_DIR/old.file) +NEW_SHA1=$(sha1 $DATA_DIR/new.file) +NEW_SIZE=$(stat -c %s $DATA_DIR/new.file) + +# --------------- basic execution ---------------------- + +testname "usage message" +run_command $WORK_DIR/applypatch && fail + +testname "display license" +run_command $WORK_DIR/applypatch -l | grep -q -i copyright || fail + + +# --------------- check mode ---------------------- + +$ADB push $DATA_DIR/old.file $WORK_DIR + +testname "check mode single" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail + +testname "check mode multiple" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail + +testname "check mode failure" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail + +$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE +# put some junk in the old file +run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail + +testname "check mode cache (corrupted) single" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail + +testname "check mode cache (corrupted) multiple" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail + +testname "check mode cache (corrupted) failure" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail + +# remove the old file entirely +run_command rm $WORK_DIR/old.file + +testname "check mode cache (missing) single" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail + +testname "check mode cache (missing) multiple" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail + +testname "check mode cache (missing) failure" +run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail + + +# --------------- apply patch ---------------------- + +$ADB push $DATA_DIR/old.file $WORK_DIR +$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR +echo hello > $tmpdir/foo +$ADB push $tmpdir/foo $WORK_DIR + +# Check that the partition has enough space to apply the patch without +# copying. If it doesn't, we'll be testing the low-space condition +# when we intend to test the not-low-space condition. +testname "apply patches (with enough space)" +free_kb=$(free_space $WORK_FS) +echo "${free_kb}kb free on /$WORK_FS." +if (( free_kb * 1024 < NEW_SIZE * 3 / 2 )); then + echo "Not enough space on /$WORK_FS to patch test file." + echo + echo "This doesn't mean that applypatch is necessarily broken;" + echo "just that /$WORK_FS doesn't have enough free space to" + echo "properly run this test." + exit 1 +fi + +testname "apply bsdiff patch" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/old.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + +testname "reapply bsdiff patch" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/old.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + + +# --------------- apply patch in new location ---------------------- + +$ADB push $DATA_DIR/old.file $WORK_DIR +$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR + +# Check that the partition has enough space to apply the patch without +# copying. If it doesn't, we'll be testing the low-space condition +# when we intend to test the not-low-space condition. +testname "apply patch to new location (with enough space)" +free_kb=$(free_space $WORK_FS) +echo "${free_kb}kb free on /$WORK_FS." +if (( free_kb * 1024 < NEW_SIZE * 3 / 2 )); then + echo "Not enough space on /$WORK_FS to patch test file." + echo + echo "This doesn't mean that applypatch is necessarily broken;" + echo "just that /$WORK_FS doesn't have enough free space to" + echo "properly run this test." + exit 1 +fi + +run_command rm $WORK_DIR/new.file +run_command rm $CACHE_TEMP_SOURCE + +testname "apply bsdiff patch to new location" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/new.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + +testname "reapply bsdiff patch to new location" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/new.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + +$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE +# put some junk in the old file +run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail + +testname "apply bsdiff patch to new location with corrupted source" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo || fail +$ADB pull $WORK_DIR/new.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + +# put some junk in the cache copy, too +run_command dd if=/dev/urandom of=$CACHE_TEMP_SOURCE count=100 bs=1024 || fail + +run_command rm $WORK_DIR/new.file +testname "apply bsdiff patch to new location with corrupted source and copy (no new file)" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo && fail + +# put some junk in the new file +run_command dd if=/dev/urandom of=$WORK_DIR/new.file count=100 bs=1024 || fail + +testname "apply bsdiff patch to new location with corrupted source and copy (bad new file)" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo && fail + +# --------------- apply patch with low space on /system ---------------------- + +$ADB push $DATA_DIR/old.file $WORK_DIR +$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR + +free_kb=$(free_space $WORK_FS) +echo "${free_kb}kb free on /$WORK_FS; we'll soon fix that." +echo run_command dd if=/dev/zero of=$WORK_DIR/bloat.dat count=$((free_kb-512)) bs=1024 || fail +run_command dd if=/dev/zero of=$WORK_DIR/bloat.dat count=$((free_kb-512)) bs=1024 || fail +free_kb=$(free_space $WORK_FS) +echo "${free_kb}kb free on /$WORK_FS now." + +testname "apply bsdiff patch with low space" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/old.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + +testname "reapply bsdiff patch with low space" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/old.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + +# --------------- apply patch with low space on /system and /cache ---------------------- + +$ADB push $DATA_DIR/old.file $WORK_DIR +$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR + +free_kb=$(free_space $WORK_FS) +echo "${free_kb}kb free on /$WORK_FS" + +run_command mkdir /cache/subdir +run_command 'echo > /cache/subdir/a.file' +run_command 'echo > /cache/a.file' +run_command mkdir /cache/recovery /cache/recovery/otatest +run_command 'echo > /cache/recovery/otatest/b.file' +run_command "echo > $CACHE_TEMP_SOURCE" +free_kb=$(free_space cache) +echo "${free_kb}kb free on /cache; we'll soon fix that." +run_command dd if=/dev/zero of=/cache/bloat_small.dat count=128 bs=1024 || fail +run_command dd if=/dev/zero of=/cache/bloat_large.dat count=$((free_kb-640)) bs=1024 || fail +free_kb=$(free_space cache) +echo "${free_kb}kb free on /cache now." + +testname "apply bsdiff patch with low space, full cache, can't delete enough" +$ADB shell 'cat >> /cache/bloat_large.dat' & open_pid=$! +echo "open_pid is $open_pid" + +# size check should fail even though it deletes some stuff +run_command $WORK_DIR/applypatch -s $NEW_SIZE && fail +run_command ls /cache/bloat_small.dat && fail # was deleted +run_command ls /cache/a.file && fail # was deleted +run_command ls /cache/recovery/otatest/b.file && fail # was deleted +run_command ls /cache/bloat_large.dat || fail # wasn't deleted because it was open +run_command ls /cache/subdir/a.file || fail # wasn't deleted because it's in a subdir +run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy + +# should fail; not enough files can be deleted +run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff && fail +run_command ls /cache/bloat_large.dat || fail # wasn't deleted because it was open +run_command ls /cache/subdir/a.file || fail # wasn't deleted because it's in a subdir +run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy + +kill $open_pid # /cache/bloat_large.dat is no longer open + +testname "apply bsdiff patch with low space, full cache, can delete enough" + +# should succeed after deleting /cache/bloat_large.dat +run_command $WORK_DIR/applypatch -s $NEW_SIZE || fail +run_command ls /cache/bloat_large.dat && fail # was deleted +run_command ls /cache/subdir/a.file || fail # still wasn't deleted because it's in a subdir +run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy + +# should succeed +run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/old.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail +run_command ls /cache/subdir/a.file || fail # still wasn't deleted because it's in a subdir +run_command ls $CACHE_TEMP_SOURCE && fail # was deleted because patching overwrote it, then deleted it + +# --------------- apply patch from cache ---------------------- + +$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE +# put some junk in the old file +run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail + +testname "apply bsdiff patch from cache (corrupted source) with low space" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/old.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + +$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE +# remove the old file entirely +run_command rm $WORK_DIR/old.file + +testname "apply bsdiff patch from cache (missing source) with low space" +run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail +$ADB pull $WORK_DIR/old.file $tmpdir/patched +diff -q $DATA_DIR/new.file $tmpdir/patched || fail + + +# --------------- cleanup ---------------------- + +cleanup + +echo +echo PASS +echo + diff --git a/applypatch/bsdiff.c b/applypatch/bsdiff.c new file mode 100644 index 0000000..b6d342b --- /dev/null +++ b/applypatch/bsdiff.c @@ -0,0 +1,410 @@ +/* + * 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. + */ + +/* + * Most of this code comes from bsdiff.c from the bsdiff-4.3 + * distribution, which is: + */ + +/*- + * Copyright 2003-2005 Colin Percival + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#define MIN(x,y) (((x)<(y)) ? (x) : (y)) + +static void split(off_t *I,off_t *V,off_t start,off_t len,off_t h) +{ + off_t i,j,k,x,tmp,jj,kk; + + if(len<16) { + for(k=start;kstart) split(I,V,start,jj-start,h); + + for(i=0;ikk) split(I,V,kk,start+len-kk,h); +} + +static void qsufsort(off_t *I,off_t *V,u_char *old,off_t oldsize) +{ + off_t buckets[256]; + off_t i,h,len; + + for(i=0;i<256;i++) buckets[i]=0; + for(i=0;i0;i--) buckets[i]=buckets[i-1]; + buckets[0]=0; + + for(i=0;iy) { + *pos=I[st]; + return x; + } else { + *pos=I[en]; + return y; + } + }; + + x=st+(en-st)/2; + if(memcmp(old+I[x],new,MIN(oldsize-I[x],newsize))<0) { + return search(I,old,oldsize,new,newsize,x,en,pos); + } else { + return search(I,old,oldsize,new,newsize,st,x,pos); + }; +} + +static void offtout(off_t x,u_char *buf) +{ + off_t y; + + if(x<0) y=-x; else y=x; + + buf[0]=y%256;y-=buf[0]; + y=y/256;buf[1]=y%256;y-=buf[1]; + y=y/256;buf[2]=y%256;y-=buf[2]; + y=y/256;buf[3]=y%256;y-=buf[3]; + y=y/256;buf[4]=y%256;y-=buf[4]; + y=y/256;buf[5]=y%256;y-=buf[5]; + y=y/256;buf[6]=y%256;y-=buf[6]; + y=y/256;buf[7]=y%256; + + if(x<0) buf[7]|=0x80; +} + +// This is main() from bsdiff.c, with the following changes: +// +// - old, oldsize, new, newsize are arguments; we don't load this +// data from files. old and new are owned by the caller; we +// don't free them at the end. +// +// - the "I" block of memory is owned by the caller, who passes a +// pointer to *I, which can be NULL. This way if we call +// bsdiff() multiple times with the same 'old' data, we only do +// the qsufsort() step the first time. +// +int bsdiff(u_char* old, off_t oldsize, off_t** IP, u_char* new, off_t newsize, + const char* patch_filename) +{ + int fd; + off_t *I; + off_t scan,pos,len; + off_t lastscan,lastpos,lastoffset; + off_t oldscore,scsc; + off_t s,Sf,lenf,Sb,lenb; + off_t overlap,Ss,lens; + off_t i; + off_t dblen,eblen; + u_char *db,*eb; + u_char buf[8]; + u_char header[32]; + FILE * pf; + BZFILE * pfbz2; + int bz2err; + + if (*IP == NULL) { + off_t* V; + *IP = malloc((oldsize+1) * sizeof(off_t)); + V = malloc((oldsize+1) * sizeof(off_t)); + qsufsort(*IP, V, old, oldsize); + free(V); + } + I = *IP; + + if(((db=malloc(newsize+1))==NULL) || + ((eb=malloc(newsize+1))==NULL)) err(1,NULL); + dblen=0; + eblen=0; + + /* Create the patch file */ + if ((pf = fopen(patch_filename, "w")) == NULL) + err(1, "%s", patch_filename); + + /* Header is + 0 8 "BSDIFF40" + 8 8 length of bzip2ed ctrl block + 16 8 length of bzip2ed diff block + 24 8 length of new file */ + /* File is + 0 32 Header + 32 ?? Bzip2ed ctrl block + ?? ?? Bzip2ed diff block + ?? ?? Bzip2ed extra block */ + memcpy(header,"BSDIFF40",8); + offtout(0, header + 8); + offtout(0, header + 16); + offtout(newsize, header + 24); + if (fwrite(header, 32, 1, pf) != 1) + err(1, "fwrite(%s)", patch_filename); + + /* Compute the differences, writing ctrl as we go */ + if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL) + errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err); + scan=0;len=0; + lastscan=0;lastpos=0;lastoffset=0; + while(scanoldscore+8)) break; + + if((scan+lastoffsetSf*2-lenf) { Sf=s; lenf=i; }; + }; + + lenb=0; + if(scan=lastscan+i)&&(pos>=i);i++) { + if(old[pos-i]==new[scan-i]) s++; + if(s*2-i>Sb*2-lenb) { Sb=s; lenb=i; }; + }; + }; + + if(lastscan+lenf>scan-lenb) { + overlap=(lastscan+lenf)-(scan-lenb); + s=0;Ss=0;lens=0; + for(i=0;iSs) { Ss=s; lens=i+1; }; + }; + + lenf+=lens-overlap; + lenb-=lens; + }; + + for(i=0;i +#include +#include +#include +#include + +#include + +#include "mincrypt/sha.h" +#include "applypatch.h" + +void ShowBSDiffLicense() { + puts("The bsdiff library used herein is:\n" + "\n" + "Copyright 2003-2005 Colin Percival\n" + "All rights reserved\n" + "\n" + "Redistribution and use in source and binary forms, with or without\n" + "modification, are permitted providing that the following conditions\n" + "are met:\n" + "1. Redistributions of source code must retain the above copyright\n" + " notice, this list of conditions and the following disclaimer.\n" + "2. Redistributions in binary form must reproduce the above copyright\n" + " notice, this list of conditions and the following disclaimer in the\n" + " documentation and/or other materials provided with the distribution.\n" + "\n" + "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n" + "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n" + "WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n" + "ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\n" + "DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n" + "DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n" + "OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n" + "HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n" + "STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\n" + "IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n" + "POSSIBILITY OF SUCH DAMAGE.\n" + "\n------------------\n\n" + "This program uses Julian R Seward's \"libbzip2\" library, available\n" + "from http://www.bzip.org/.\n" + ); +} + +static off_t offtin(u_char *buf) +{ + off_t y; + + y=buf[7]&0x7F; + y=y*256;y+=buf[6]; + y=y*256;y+=buf[5]; + y=y*256;y+=buf[4]; + y=y*256;y+=buf[3]; + y=y*256;y+=buf[2]; + y=y*256;y+=buf[1]; + y=y*256;y+=buf[0]; + + if(buf[7]&0x80) y=-y; + + return y; +} + +int FillBuffer(unsigned char* buffer, int size, bz_stream* stream) { + stream->next_out = (char*)buffer; + stream->avail_out = size; + while (stream->avail_out > 0) { + int bzerr = BZ2_bzDecompress(stream); + if (bzerr != BZ_OK && bzerr != BZ_STREAM_END) { + printf("bz error %d decompressing\n", bzerr); + return -1; + } + if (stream->avail_out > 0) { + printf("need %d more bytes\n", stream->avail_out); + } + } + return 0; +} + +int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size, + const Value* patch, ssize_t patch_offset, + SinkFn sink, void* token, SHA_CTX* ctx) { + + unsigned char* new_data; + ssize_t new_size; + if (ApplyBSDiffPatchMem(old_data, old_size, patch, patch_offset, + &new_data, &new_size) != 0) { + return -1; + } + + if (sink(new_data, new_size, token) < new_size) { + printf("short write of output: %d (%s)\n", errno, strerror(errno)); + return 1; + } + if (ctx) { + SHA_update(ctx, new_data, new_size); + } + free(new_data); + + return 0; +} + +int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, + const Value* patch, ssize_t patch_offset, + unsigned char** new_data, ssize_t* new_size) { + // Patch data format: + // 0 8 "BSDIFF40" + // 8 8 X + // 16 8 Y + // 24 8 sizeof(newfile) + // 32 X bzip2(control block) + // 32+X Y bzip2(diff block) + // 32+X+Y ??? bzip2(extra block) + // with control block a set of triples (x,y,z) meaning "add x bytes + // from oldfile to x bytes from the diff block; copy y bytes from the + // extra block; seek forwards in oldfile by z bytes". + + unsigned char* header = (unsigned char*) patch->data + patch_offset; + if (memcmp(header, "BSDIFF40", 8) != 0) { + printf("corrupt bsdiff patch file header (magic number)\n"); + return 1; + } + + ssize_t ctrl_len, data_len; + ctrl_len = offtin(header+8); + data_len = offtin(header+16); + *new_size = offtin(header+24); + + if (ctrl_len < 0 || data_len < 0 || *new_size < 0) { + printf("corrupt patch file header (data lengths)\n"); + return 1; + } + + int bzerr; + + bz_stream cstream; + cstream.next_in = patch->data + patch_offset + 32; + cstream.avail_in = ctrl_len; + cstream.bzalloc = NULL; + cstream.bzfree = NULL; + cstream.opaque = NULL; + if ((bzerr = BZ2_bzDecompressInit(&cstream, 0, 0)) != BZ_OK) { + printf("failed to bzinit control stream (%d)\n", bzerr); + } + + bz_stream dstream; + dstream.next_in = patch->data + patch_offset + 32 + ctrl_len; + dstream.avail_in = data_len; + dstream.bzalloc = NULL; + dstream.bzfree = NULL; + dstream.opaque = NULL; + if ((bzerr = BZ2_bzDecompressInit(&dstream, 0, 0)) != BZ_OK) { + printf("failed to bzinit diff stream (%d)\n", bzerr); + } + + bz_stream estream; + estream.next_in = patch->data + patch_offset + 32 + ctrl_len + data_len; + estream.avail_in = patch->size - (patch_offset + 32 + ctrl_len + data_len); + estream.bzalloc = NULL; + estream.bzfree = NULL; + estream.opaque = NULL; + if ((bzerr = BZ2_bzDecompressInit(&estream, 0, 0)) != BZ_OK) { + printf("failed to bzinit extra stream (%d)\n", bzerr); + } + + *new_data = malloc(*new_size); + if (*new_data == NULL) { + printf("failed to allocate %ld bytes of memory for output file\n", + (long)*new_size); + return 1; + } + + off_t oldpos = 0, newpos = 0; + off_t ctrl[3]; + off_t len_read; + int i; + unsigned char buf[24]; + while (newpos < *new_size) { + // Read control data + if (FillBuffer(buf, 24, &cstream) != 0) { + printf("error while reading control stream\n"); + return 1; + } + ctrl[0] = offtin(buf); + ctrl[1] = offtin(buf+8); + ctrl[2] = offtin(buf+16); + + // Sanity check + if (newpos + ctrl[0] > *new_size) { + printf("corrupt patch (new file overrun)\n"); + return 1; + } + + // Read diff string + if (FillBuffer(*new_data + newpos, ctrl[0], &dstream) != 0) { + printf("error while reading diff stream\n"); + return 1; + } + + // Add old data to diff string + for (i = 0; i < ctrl[0]; ++i) { + if ((oldpos+i >= 0) && (oldpos+i < old_size)) { + (*new_data)[newpos+i] += old_data[oldpos+i]; + } + } + + // Adjust pointers + newpos += ctrl[0]; + oldpos += ctrl[0]; + + // Sanity check + if (newpos + ctrl[1] > *new_size) { + printf("corrupt patch (new file overrun)\n"); + return 1; + } + + // Read extra string + if (FillBuffer(*new_data + newpos, ctrl[1], &estream) != 0) { + printf("error while reading extra stream\n"); + return 1; + } + + // Adjust pointers + newpos += ctrl[1]; + oldpos += ctrl[2]; + } + + BZ2_bzDecompressEnd(&cstream); + BZ2_bzDecompressEnd(&dstream); + BZ2_bzDecompressEnd(&estream); + return 0; +} diff --git a/applypatch/freecache.c b/applypatch/freecache.c new file mode 100644 index 0000000..9827fda --- /dev/null +++ b/applypatch/freecache.c @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "applypatch.h" + +static int EliminateOpenFiles(char** files, int file_count) { + DIR* d; + struct dirent* de; + d = opendir("/proc"); + if (d == NULL) { + printf("error opening /proc: %s\n", strerror(errno)); + return -1; + } + while ((de = readdir(d)) != 0) { + int i; + for (i = 0; de->d_name[i] != '\0' && isdigit(de->d_name[i]); ++i); + if (de->d_name[i]) continue; + + // de->d_name[i] is numeric + + char path[FILENAME_MAX]; + strcpy(path, "/proc/"); + strcat(path, de->d_name); + strcat(path, "/fd/"); + + DIR* fdd; + struct dirent* fdde; + fdd = opendir(path); + if (fdd == NULL) { + printf("error opening %s: %s\n", path, strerror(errno)); + continue; + } + while ((fdde = readdir(fdd)) != 0) { + char fd_path[FILENAME_MAX]; + char link[FILENAME_MAX]; + strcpy(fd_path, path); + strcat(fd_path, fdde->d_name); + + int count; + count = readlink(fd_path, link, sizeof(link)-1); + if (count >= 0) { + link[count] = '\0'; + + // This is inefficient, but it should only matter if there are + // lots of files in /cache, and lots of them are open (neither + // of which should be true, especially in recovery). + if (strncmp(link, "/cache/", 7) == 0) { + int j; + for (j = 0; j < file_count; ++j) { + if (files[j] && strcmp(files[j], link) == 0) { + printf("%s is open by %s\n", link, de->d_name); + free(files[j]); + files[j] = NULL; + } + } + } + } + } + closedir(fdd); + } + closedir(d); + + return 0; +} + +int FindExpendableFiles(char*** names, int* entries) { + DIR* d; + struct dirent* de; + int size = 32; + *entries = 0; + *names = malloc(size * sizeof(char*)); + + char path[FILENAME_MAX]; + + // We're allowed to delete unopened regular files in any of these + // directories. + const char* dirs[2] = {"/cache", "/cache/recovery/otatest"}; + + unsigned int i; + for (i = 0; i < sizeof(dirs)/sizeof(dirs[0]); ++i) { + d = opendir(dirs[i]); + if (d == NULL) { + printf("error opening %s: %s\n", dirs[i], strerror(errno)); + continue; + } + + // Look for regular files in the directory (not in any subdirectories). + while ((de = readdir(d)) != 0) { + strcpy(path, dirs[i]); + strcat(path, "/"); + strcat(path, de->d_name); + + // We can't delete CACHE_TEMP_SOURCE; if it's there we might have + // restarted during installation and could be depending on it to + // be there. + if (strcmp(path, CACHE_TEMP_SOURCE) == 0) continue; + + struct stat st; + if (stat(path, &st) == 0 && S_ISREG(st.st_mode)) { + if (*entries >= size) { + size *= 2; + *names = realloc(*names, size * sizeof(char*)); + } + (*names)[(*entries)++] = strdup(path); + } + } + + closedir(d); + } + + printf("%d regular files in deletable directories\n", *entries); + + if (EliminateOpenFiles(*names, *entries) < 0) { + return -1; + } + + return 0; +} + +int MakeFreeSpaceOnCache(size_t bytes_needed) { + size_t free_now = FreeSpaceForFile("/cache"); + printf("%ld bytes free on /cache (%ld needed)\n", + (long)free_now, (long)bytes_needed); + + if (free_now >= bytes_needed) { + return 0; + } + + char** names; + int entries; + + if (FindExpendableFiles(&names, &entries) < 0) { + return -1; + } + + if (entries == 0) { + // nothing we can delete to free up space! + printf("no files can be deleted to free space on /cache\n"); + return -1; + } + + // We could try to be smarter about which files to delete: the + // biggest ones? the smallest ones that will free up enough space? + // the oldest? the newest? + // + // Instead, we'll be dumb. + + int i; + for (i = 0; i < entries && free_now < bytes_needed; ++i) { + if (names[i]) { + unlink(names[i]); + free_now = FreeSpaceForFile("/cache"); + printf("deleted %s; now %ld bytes free\n", names[i], (long)free_now); + free(names[i]); + } + } + + for (; i < entries; ++i) { + free(names[i]); + } + free(names); + + return (free_now >= bytes_needed) ? 0 : -1; +} diff --git a/applypatch/imgdiff.c b/applypatch/imgdiff.c new file mode 100644 index 0000000..6b9ebee --- /dev/null +++ b/applypatch/imgdiff.c @@ -0,0 +1,1010 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This program constructs binary patches for images -- such as boot.img + * and recovery.img -- that consist primarily of large chunks of gzipped + * data interspersed with uncompressed data. Doing a naive bsdiff of + * these files is not useful because small changes in the data lead to + * large changes in the compressed bitstream; bsdiff patches of gzipped + * data are typically as large as the data itself. + * + * To patch these usefully, we break the source and target images up into + * chunks of two types: "normal" and "gzip". Normal chunks are simply + * patched using a plain bsdiff. Gzip chunks are first expanded, then a + * bsdiff is applied to the uncompressed data, then the patched data is + * gzipped using the same encoder parameters. Patched chunks are + * concatenated together to create the output file; the output image + * should be *exactly* the same series of bytes as the target image used + * originally to generate the patch. + * + * To work well with this tool, the gzipped sections of the target + * image must have been generated using the same deflate encoder that + * is available in applypatch, namely, the one in the zlib library. + * In practice this means that images should be compressed using the + * "minigzip" tool included in the zlib distribution, not the GNU gzip + * program. + * + * An "imgdiff" patch consists of a header describing the chunk structure + * of the file and any encoding parameters needed for the gzipped + * chunks, followed by N bsdiff patches, one per chunk. + * + * For a diff to be generated, the source and target images must have the + * same "chunk" structure: that is, the same number of gzipped and normal + * chunks in the same order. Android boot and recovery images currently + * consist of five chunks: a small normal header, a gzipped kernel, a + * small normal section, a gzipped ramdisk, and finally a small normal + * footer. + * + * Caveats: we locate gzipped sections within the source and target + * images by searching for the byte sequence 1f8b0800: 1f8b is the gzip + * magic number; 08 specifies the "deflate" encoding [the only encoding + * supported by the gzip standard]; and 00 is the flags byte. We do not + * currently support any extra header fields (which would be indicated by + * a nonzero flags byte). We also don't handle the case when that byte + * sequence appears spuriously in the file. (Note that it would have to + * occur spuriously within a normal chunk to be a problem.) + * + * + * The imgdiff patch header looks like this: + * + * "IMGDIFF1" (8) [magic number and version] + * chunk count (4) + * for each chunk: + * chunk type (4) [CHUNK_{NORMAL, GZIP, DEFLATE, RAW}] + * if chunk type == CHUNK_NORMAL: + * source start (8) + * source len (8) + * bsdiff patch offset (8) [from start of patch file] + * if chunk type == CHUNK_GZIP: (version 1 only) + * source start (8) + * source len (8) + * bsdiff patch offset (8) [from start of patch file] + * source expanded len (8) [size of uncompressed source] + * target expected len (8) [size of uncompressed target] + * gzip level (4) + * method (4) + * windowBits (4) + * memLevel (4) + * strategy (4) + * gzip header len (4) + * gzip header (gzip header len) + * gzip footer (8) + * if chunk type == CHUNK_DEFLATE: (version 2 only) + * source start (8) + * source len (8) + * bsdiff patch offset (8) [from start of patch file] + * source expanded len (8) [size of uncompressed source] + * target expected len (8) [size of uncompressed target] + * gzip level (4) + * method (4) + * windowBits (4) + * memLevel (4) + * strategy (4) + * if chunk type == RAW: (version 2 only) + * target len (4) + * data (target len) + * + * All integers are little-endian. "source start" and "source len" + * specify the section of the input image that comprises this chunk, + * including the gzip header and footer for gzip chunks. "source + * expanded len" is the size of the uncompressed source data. "target + * expected len" is the size of the uncompressed data after applying + * the bsdiff patch. The next five parameters specify the zlib + * parameters to be used when compressing the patched data, and the + * next three specify the header and footer to be wrapped around the + * compressed data to create the output chunk (so that header contents + * like the timestamp are recreated exactly). + * + * After the header there are 'chunk count' bsdiff patches; the offset + * of each from the beginning of the file is specified in the header. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "zlib.h" +#include "imgdiff.h" +#include "utils.h" + +typedef struct { + int type; // CHUNK_NORMAL, CHUNK_DEFLATE + size_t start; // offset of chunk in original image file + + size_t len; + unsigned char* data; // data to be patched (uncompressed, for deflate chunks) + + size_t source_start; + size_t source_len; + + off_t* I; // used by bsdiff + + // --- for CHUNK_DEFLATE chunks only: --- + + // original (compressed) deflate data + size_t deflate_len; + unsigned char* deflate_data; + + char* filename; // used for zip entries + + // deflate encoder parameters + int level, method, windowBits, memLevel, strategy; + + size_t source_uncompressed_len; +} ImageChunk; + +typedef struct { + int data_offset; + int deflate_len; + int uncomp_len; + char* filename; +} ZipFileEntry; + +static int fileentry_compare(const void* a, const void* b) { + int ao = ((ZipFileEntry*)a)->data_offset; + int bo = ((ZipFileEntry*)b)->data_offset; + if (ao < bo) { + return -1; + } else if (ao > bo) { + return 1; + } else { + return 0; + } +} + +// from bsdiff.c +int bsdiff(u_char* old, off_t oldsize, off_t** IP, u_char* new, off_t newsize, + const char* patch_filename); + +unsigned char* ReadZip(const char* filename, + int* num_chunks, ImageChunk** chunks, + int include_pseudo_chunk) { + struct stat st; + if (stat(filename, &st) != 0) { + printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); + return NULL; + } + + unsigned char* img = malloc(st.st_size); + FILE* f = fopen(filename, "rb"); + if (fread(img, 1, st.st_size, f) != st.st_size) { + printf("failed to read \"%s\" %s\n", filename, strerror(errno)); + fclose(f); + return NULL; + } + fclose(f); + + // look for the end-of-central-directory record. + + int i; + for (i = st.st_size-20; i >= 0 && i > st.st_size - 65600; --i) { + if (img[i] == 0x50 && img[i+1] == 0x4b && + img[i+2] == 0x05 && img[i+3] == 0x06) { + break; + } + } + // double-check: this archive consists of a single "disk" + if (!(img[i+4] == 0 && img[i+5] == 0 && img[i+6] == 0 && img[i+7] == 0)) { + printf("can't process multi-disk archive\n"); + return NULL; + } + + int cdcount = Read2(img+i+8); + int cdoffset = Read4(img+i+16); + + ZipFileEntry* temp_entries = malloc(cdcount * sizeof(ZipFileEntry)); + int entrycount = 0; + + unsigned char* cd = img+cdoffset; + for (i = 0; i < cdcount; ++i) { + if (!(cd[0] == 0x50 && cd[1] == 0x4b && cd[2] == 0x01 && cd[3] == 0x02)) { + printf("bad central directory entry %d\n", i); + return NULL; + } + + int clen = Read4(cd+20); // compressed len + int ulen = Read4(cd+24); // uncompressed len + int nlen = Read2(cd+28); // filename len + int xlen = Read2(cd+30); // extra field len + int mlen = Read2(cd+32); // file comment len + int hoffset = Read4(cd+42); // local header offset + + char* filename = malloc(nlen+1); + memcpy(filename, cd+46, nlen); + filename[nlen] = '\0'; + + int method = Read2(cd+10); + + cd += 46 + nlen + xlen + mlen; + + if (method != 8) { // 8 == deflate + free(filename); + continue; + } + + unsigned char* lh = img + hoffset; + + if (!(lh[0] == 0x50 && lh[1] == 0x4b && lh[2] == 0x03 && lh[3] == 0x04)) { + printf("bad local file header entry %d\n", i); + return NULL; + } + + if (Read2(lh+26) != nlen || memcmp(lh+30, filename, nlen) != 0) { + printf("central dir filename doesn't match local header\n"); + return NULL; + } + + xlen = Read2(lh+28); // extra field len; might be different from CD entry? + + temp_entries[entrycount].data_offset = hoffset+30+nlen+xlen; + temp_entries[entrycount].deflate_len = clen; + temp_entries[entrycount].uncomp_len = ulen; + temp_entries[entrycount].filename = filename; + ++entrycount; + } + + qsort(temp_entries, entrycount, sizeof(ZipFileEntry), fileentry_compare); + +#if 0 + printf("found %d deflated entries\n", entrycount); + for (i = 0; i < entrycount; ++i) { + printf("off %10d len %10d unlen %10d %p %s\n", + temp_entries[i].data_offset, + temp_entries[i].deflate_len, + temp_entries[i].uncomp_len, + temp_entries[i].filename, + temp_entries[i].filename); + } +#endif + + *num_chunks = 0; + *chunks = malloc((entrycount*2+2) * sizeof(ImageChunk)); + ImageChunk* curr = *chunks; + + if (include_pseudo_chunk) { + curr->type = CHUNK_NORMAL; + curr->start = 0; + curr->len = st.st_size; + curr->data = img; + curr->filename = NULL; + curr->I = NULL; + ++curr; + ++*num_chunks; + } + + int pos = 0; + int nextentry = 0; + + while (pos < st.st_size) { + if (nextentry < entrycount && pos == temp_entries[nextentry].data_offset) { + curr->type = CHUNK_DEFLATE; + curr->start = pos; + curr->deflate_len = temp_entries[nextentry].deflate_len; + curr->deflate_data = img + pos; + curr->filename = temp_entries[nextentry].filename; + curr->I = NULL; + + curr->len = temp_entries[nextentry].uncomp_len; + curr->data = malloc(curr->len); + + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = curr->deflate_len; + strm.next_in = curr->deflate_data; + + // -15 means we are decoding a 'raw' deflate stream; zlib will + // not expect zlib headers. + int ret = inflateInit2(&strm, -15); + + strm.avail_out = curr->len; + strm.next_out = curr->data; + ret = inflate(&strm, Z_NO_FLUSH); + if (ret != Z_STREAM_END) { + printf("failed to inflate \"%s\"; %d\n", curr->filename, ret); + return NULL; + } + + inflateEnd(&strm); + + pos += curr->deflate_len; + ++nextentry; + ++*num_chunks; + ++curr; + continue; + } + + // use a normal chunk to take all the data up to the start of the + // next deflate section. + + curr->type = CHUNK_NORMAL; + curr->start = pos; + if (nextentry < entrycount) { + curr->len = temp_entries[nextentry].data_offset - pos; + } else { + curr->len = st.st_size - pos; + } + curr->data = img + pos; + curr->filename = NULL; + curr->I = NULL; + pos += curr->len; + + ++*num_chunks; + ++curr; + } + + free(temp_entries); + return img; +} + +/* + * Read the given file and break it up into chunks, putting the number + * of chunks and their info in *num_chunks and **chunks, + * respectively. Returns a malloc'd block of memory containing the + * contents of the file; various pointers in the output chunk array + * will point into this block of memory. The caller should free the + * return value when done with all the chunks. Returns NULL on + * failure. + */ +unsigned char* ReadImage(const char* filename, + int* num_chunks, ImageChunk** chunks) { + struct stat st; + if (stat(filename, &st) != 0) { + printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); + return NULL; + } + + unsigned char* img = malloc(st.st_size + 4); + FILE* f = fopen(filename, "rb"); + if (fread(img, 1, st.st_size, f) != st.st_size) { + printf("failed to read \"%s\" %s\n", filename, strerror(errno)); + fclose(f); + return NULL; + } + fclose(f); + + // append 4 zero bytes to the data so we can always search for the + // four-byte string 1f8b0800 starting at any point in the actual + // file data, without special-casing the end of the data. + memset(img+st.st_size, 0, 4); + + size_t pos = 0; + + *num_chunks = 0; + *chunks = NULL; + + while (pos < st.st_size) { + unsigned char* p = img+pos; + + if (st.st_size - pos >= 4 && + p[0] == 0x1f && p[1] == 0x8b && + p[2] == 0x08 && // deflate compression + p[3] == 0x00) { // no header flags + // 'pos' is the offset of the start of a gzip chunk. + + *num_chunks += 3; + *chunks = realloc(*chunks, *num_chunks * sizeof(ImageChunk)); + ImageChunk* curr = *chunks + (*num_chunks-3); + + // create a normal chunk for the header. + curr->start = pos; + curr->type = CHUNK_NORMAL; + curr->len = GZIP_HEADER_LEN; + curr->data = p; + curr->I = NULL; + + pos += curr->len; + p += curr->len; + ++curr; + + curr->type = CHUNK_DEFLATE; + curr->filename = NULL; + curr->I = NULL; + + // We must decompress this chunk in order to discover where it + // ends, and so we can put the uncompressed data and its length + // into curr->data and curr->len. + + size_t allocated = 32768; + curr->len = 0; + curr->data = malloc(allocated); + curr->start = pos; + curr->deflate_data = p; + + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = st.st_size - pos; + strm.next_in = p; + + // -15 means we are decoding a 'raw' deflate stream; zlib will + // not expect zlib headers. + int ret = inflateInit2(&strm, -15); + + do { + strm.avail_out = allocated - curr->len; + strm.next_out = curr->data + curr->len; + ret = inflate(&strm, Z_NO_FLUSH); + curr->len = allocated - strm.avail_out; + if (strm.avail_out == 0) { + allocated *= 2; + curr->data = realloc(curr->data, allocated); + } + } while (ret != Z_STREAM_END); + + curr->deflate_len = st.st_size - strm.avail_in - pos; + inflateEnd(&strm); + pos += curr->deflate_len; + p += curr->deflate_len; + ++curr; + + // create a normal chunk for the footer + + curr->type = CHUNK_NORMAL; + curr->start = pos; + curr->len = GZIP_FOOTER_LEN; + curr->data = img+pos; + curr->I = NULL; + + pos += curr->len; + p += curr->len; + ++curr; + + // The footer (that we just skipped over) contains the size of + // the uncompressed data. Double-check to make sure that it + // matches the size of the data we got when we actually did + // the decompression. + size_t footer_size = Read4(p-4); + if (footer_size != curr[-2].len) { + printf("Error: footer size %d != decompressed size %d\n", + footer_size, curr[-2].len); + free(img); + return NULL; + } + } else { + // Reallocate the list for every chunk; we expect the number of + // chunks to be small (5 for typical boot and recovery images). + ++*num_chunks; + *chunks = realloc(*chunks, *num_chunks * sizeof(ImageChunk)); + ImageChunk* curr = *chunks + (*num_chunks-1); + curr->start = pos; + curr->I = NULL; + + // 'pos' is not the offset of the start of a gzip chunk, so scan + // forward until we find a gzip header. + curr->type = CHUNK_NORMAL; + curr->data = p; + + for (curr->len = 0; curr->len < (st.st_size - pos); ++curr->len) { + if (p[curr->len] == 0x1f && + p[curr->len+1] == 0x8b && + p[curr->len+2] == 0x08 && + p[curr->len+3] == 0x00) { + break; + } + } + pos += curr->len; + } + } + + return img; +} + +#define BUFFER_SIZE 32768 + +/* + * Takes the uncompressed data stored in the chunk, compresses it + * using the zlib parameters stored in the chunk, and checks that it + * matches exactly the compressed data we started with (also stored in + * the chunk). Return 0 on success. + */ +int TryReconstruction(ImageChunk* chunk, unsigned char* out) { + size_t p = 0; + +#if 0 + printf("trying %d %d %d %d %d\n", + chunk->level, chunk->method, chunk->windowBits, + chunk->memLevel, chunk->strategy); +#endif + + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = chunk->len; + strm.next_in = chunk->data; + int ret; + ret = deflateInit2(&strm, chunk->level, chunk->method, chunk->windowBits, + chunk->memLevel, chunk->strategy); + do { + strm.avail_out = BUFFER_SIZE; + strm.next_out = out; + ret = deflate(&strm, Z_FINISH); + size_t have = BUFFER_SIZE - strm.avail_out; + + if (memcmp(out, chunk->deflate_data+p, have) != 0) { + // mismatch; data isn't the same. + deflateEnd(&strm); + return -1; + } + p += have; + } while (ret != Z_STREAM_END); + deflateEnd(&strm); + if (p != chunk->deflate_len) { + // mismatch; ran out of data before we should have. + return -1; + } + return 0; +} + +/* + * Verify that we can reproduce exactly the same compressed data that + * we started with. Sets the level, method, windowBits, memLevel, and + * strategy fields in the chunk to the encoding parameters needed to + * produce the right output. Returns 0 on success. + */ +int ReconstructDeflateChunk(ImageChunk* chunk) { + if (chunk->type != CHUNK_DEFLATE) { + printf("attempt to reconstruct non-deflate chunk\n"); + return -1; + } + + size_t p = 0; + unsigned char* out = malloc(BUFFER_SIZE); + + // We only check two combinations of encoder parameters: level 6 + // (the default) and level 9 (the maximum). + for (chunk->level = 6; chunk->level <= 9; chunk->level += 3) { + chunk->windowBits = -15; // 32kb window; negative to indicate a raw stream. + chunk->memLevel = 8; // the default value. + chunk->method = Z_DEFLATED; + chunk->strategy = Z_DEFAULT_STRATEGY; + + if (TryReconstruction(chunk, out) == 0) { + free(out); + return 0; + } + } + + free(out); + return -1; +} + +/* + * Given source and target chunks, compute a bsdiff patch between them + * by running bsdiff in a subprocess. Return the patch data, placing + * its length in *size. Return NULL on failure. We expect the bsdiff + * program to be in the path. + */ +unsigned char* MakePatch(ImageChunk* src, ImageChunk* tgt, size_t* size) { + if (tgt->type == CHUNK_NORMAL) { + if (tgt->len <= 160) { + tgt->type = CHUNK_RAW; + *size = tgt->len; + return tgt->data; + } + } + + char ptemp[] = "/tmp/imgdiff-patch-XXXXXX"; + mkstemp(ptemp); + + int r = bsdiff(src->data, src->len, &(src->I), tgt->data, tgt->len, ptemp); + if (r != 0) { + printf("bsdiff() failed: %d\n", r); + return NULL; + } + + struct stat st; + if (stat(ptemp, &st) != 0) { + printf("failed to stat patch file %s: %s\n", + ptemp, strerror(errno)); + return NULL; + } + + unsigned char* data = malloc(st.st_size); + + if (tgt->type == CHUNK_NORMAL && tgt->len <= st.st_size) { + unlink(ptemp); + + tgt->type = CHUNK_RAW; + *size = tgt->len; + return tgt->data; + } + + *size = st.st_size; + + FILE* f = fopen(ptemp, "rb"); + if (f == NULL) { + printf("failed to open patch %s: %s\n", ptemp, strerror(errno)); + return NULL; + } + if (fread(data, 1, st.st_size, f) != st.st_size) { + printf("failed to read patch %s: %s\n", ptemp, strerror(errno)); + return NULL; + } + fclose(f); + + unlink(ptemp); + + tgt->source_start = src->start; + switch (tgt->type) { + case CHUNK_NORMAL: + tgt->source_len = src->len; + break; + case CHUNK_DEFLATE: + tgt->source_len = src->deflate_len; + tgt->source_uncompressed_len = src->len; + break; + } + + return data; +} + +/* + * Cause a gzip chunk to be treated as a normal chunk (ie, as a blob + * of uninterpreted data). The resulting patch will likely be about + * as big as the target file, but it lets us handle the case of images + * where some gzip chunks are reconstructible but others aren't (by + * treating the ones that aren't as normal chunks). + */ +void ChangeDeflateChunkToNormal(ImageChunk* ch) { + if (ch->type != CHUNK_DEFLATE) return; + ch->type = CHUNK_NORMAL; + free(ch->data); + ch->data = ch->deflate_data; + ch->len = ch->deflate_len; +} + +/* + * Return true if the data in the chunk is identical (including the + * compressed representation, for gzip chunks). + */ +int AreChunksEqual(ImageChunk* a, ImageChunk* b) { + if (a->type != b->type) return 0; + + switch (a->type) { + case CHUNK_NORMAL: + return a->len == b->len && memcmp(a->data, b->data, a->len) == 0; + + case CHUNK_DEFLATE: + return a->deflate_len == b->deflate_len && + memcmp(a->deflate_data, b->deflate_data, a->deflate_len) == 0; + + default: + printf("unknown chunk type %d\n", a->type); + return 0; + } +} + +/* + * Look for runs of adjacent normal chunks and compress them down into + * a single chunk. (Such runs can be produced when deflate chunks are + * changed to normal chunks.) + */ +void MergeAdjacentNormalChunks(ImageChunk* chunks, int* num_chunks) { + int out = 0; + int in_start = 0, in_end; + while (in_start < *num_chunks) { + if (chunks[in_start].type != CHUNK_NORMAL) { + in_end = in_start+1; + } else { + // in_start is a normal chunk. Look for a run of normal chunks + // that constitute a solid block of data (ie, each chunk begins + // where the previous one ended). + for (in_end = in_start+1; + in_end < *num_chunks && chunks[in_end].type == CHUNK_NORMAL && + (chunks[in_end].start == + chunks[in_end-1].start + chunks[in_end-1].len && + chunks[in_end].data == + chunks[in_end-1].data + chunks[in_end-1].len); + ++in_end); + } + + if (in_end == in_start+1) { +#if 0 + printf("chunk %d is now %d\n", in_start, out); +#endif + if (out != in_start) { + memcpy(chunks+out, chunks+in_start, sizeof(ImageChunk)); + } + } else { +#if 0 + printf("collapse normal chunks %d-%d into %d\n", in_start, in_end-1, out); +#endif + + // Merge chunks [in_start, in_end-1] into one chunk. Since the + // data member of each chunk is just a pointer into an in-memory + // copy of the file, this can be done without recopying (the + // output chunk has the first chunk's start location and data + // pointer, and length equal to the sum of the input chunk + // lengths). + chunks[out].type = CHUNK_NORMAL; + chunks[out].start = chunks[in_start].start; + chunks[out].data = chunks[in_start].data; + chunks[out].len = chunks[in_end-1].len + + (chunks[in_end-1].start - chunks[in_start].start); + } + + ++out; + in_start = in_end; + } + *num_chunks = out; +} + +ImageChunk* FindChunkByName(const char* name, + ImageChunk* chunks, int num_chunks) { + int i; + for (i = 0; i < num_chunks; ++i) { + if (chunks[i].type == CHUNK_DEFLATE && chunks[i].filename && + strcmp(name, chunks[i].filename) == 0) { + return chunks+i; + } + } + return NULL; +} + +void DumpChunks(ImageChunk* chunks, int num_chunks) { + int i; + for (i = 0; i < num_chunks; ++i) { + printf("chunk %d: type %d start %d len %d\n", + i, chunks[i].type, chunks[i].start, chunks[i].len); + } +} + +int main(int argc, char** argv) { + if (argc != 4 && argc != 5) { + usage: + printf("usage: %s [-z] \n", + argv[0]); + return 2; + } + + int zip_mode = 0; + + if (strcmp(argv[1], "-z") == 0) { + zip_mode = 1; + --argc; + ++argv; + } + + + int num_src_chunks; + ImageChunk* src_chunks; + int num_tgt_chunks; + ImageChunk* tgt_chunks; + int i; + + if (zip_mode) { + if (ReadZip(argv[1], &num_src_chunks, &src_chunks, 1) == NULL) { + printf("failed to break apart source zip file\n"); + return 1; + } + if (ReadZip(argv[2], &num_tgt_chunks, &tgt_chunks, 0) == NULL) { + printf("failed to break apart target zip file\n"); + return 1; + } + } else { + if (ReadImage(argv[1], &num_src_chunks, &src_chunks) == NULL) { + printf("failed to break apart source image\n"); + return 1; + } + if (ReadImage(argv[2], &num_tgt_chunks, &tgt_chunks) == NULL) { + printf("failed to break apart target image\n"); + return 1; + } + + // Verify that the source and target images have the same chunk + // structure (ie, the same sequence of deflate and normal chunks). + + if (!zip_mode) { + // Merge the gzip header and footer in with any adjacent + // normal chunks. + MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks); + MergeAdjacentNormalChunks(src_chunks, &num_src_chunks); + } + + if (num_src_chunks != num_tgt_chunks) { + printf("source and target don't have same number of chunks!\n"); + printf("source chunks:\n"); + DumpChunks(src_chunks, num_src_chunks); + printf("target chunks:\n"); + DumpChunks(tgt_chunks, num_tgt_chunks); + return 1; + } + for (i = 0; i < num_src_chunks; ++i) { + if (src_chunks[i].type != tgt_chunks[i].type) { + printf("source and target don't have same chunk " + "structure! (chunk %d)\n", i); + printf("source chunks:\n"); + DumpChunks(src_chunks, num_src_chunks); + printf("target chunks:\n"); + DumpChunks(tgt_chunks, num_tgt_chunks); + return 1; + } + } + } + + for (i = 0; i < num_tgt_chunks; ++i) { + if (tgt_chunks[i].type == CHUNK_DEFLATE) { + // Confirm that given the uncompressed chunk data in the target, we + // can recompress it and get exactly the same bits as are in the + // input target image. If this fails, treat the chunk as a normal + // non-deflated chunk. + if (ReconstructDeflateChunk(tgt_chunks+i) < 0) { + printf("failed to reconstruct target deflate chunk %d [%s]; " + "treating as normal\n", i, tgt_chunks[i].filename); + ChangeDeflateChunkToNormal(tgt_chunks+i); + if (zip_mode) { + ImageChunk* src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks); + if (src) { + ChangeDeflateChunkToNormal(src); + } + } else { + ChangeDeflateChunkToNormal(src_chunks+i); + } + continue; + } + + // If two deflate chunks are identical (eg, the kernel has not + // changed between two builds), treat them as normal chunks. + // This makes applypatch much faster -- it can apply a trivial + // patch to the compressed data, rather than uncompressing and + // recompressing to apply the trivial patch to the uncompressed + // data. + ImageChunk* src; + if (zip_mode) { + src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks); + } else { + src = src_chunks+i; + } + + if (src == NULL || AreChunksEqual(tgt_chunks+i, src)) { + ChangeDeflateChunkToNormal(tgt_chunks+i); + if (src) { + ChangeDeflateChunkToNormal(src); + } + } + } + } + + // Merging neighboring normal chunks. + if (zip_mode) { + // For zips, we only need to do this to the target: deflated + // chunks are matched via filename, and normal chunks are patched + // using the entire source file as the source. + MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks); + } else { + // For images, we need to maintain the parallel structure of the + // chunk lists, so do the merging in both the source and target + // lists. + MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks); + MergeAdjacentNormalChunks(src_chunks, &num_src_chunks); + if (num_src_chunks != num_tgt_chunks) { + // This shouldn't happen. + printf("merging normal chunks went awry\n"); + return 1; + } + } + + // Compute bsdiff patches for each chunk's data (the uncompressed + // data, in the case of deflate chunks). + + printf("Construct patches for %d chunks...\n", num_tgt_chunks); + unsigned char** patch_data = malloc(num_tgt_chunks * sizeof(unsigned char*)); + size_t* patch_size = malloc(num_tgt_chunks * sizeof(size_t)); + for (i = 0; i < num_tgt_chunks; ++i) { + if (zip_mode) { + ImageChunk* src; + if (tgt_chunks[i].type == CHUNK_DEFLATE && + (src = FindChunkByName(tgt_chunks[i].filename, src_chunks, + num_src_chunks))) { + patch_data[i] = MakePatch(src, tgt_chunks+i, patch_size+i); + } else { + patch_data[i] = MakePatch(src_chunks, tgt_chunks+i, patch_size+i); + } + } else { + patch_data[i] = MakePatch(src_chunks+i, tgt_chunks+i, patch_size+i); + } + printf("patch %3d is %d bytes (of %d)\n", + i, patch_size[i], tgt_chunks[i].source_len); + } + + // Figure out how big the imgdiff file header is going to be, so + // that we can correctly compute the offset of each bsdiff patch + // within the file. + + size_t total_header_size = 12; + for (i = 0; i < num_tgt_chunks; ++i) { + total_header_size += 4; + switch (tgt_chunks[i].type) { + case CHUNK_NORMAL: + total_header_size += 8*3; + break; + case CHUNK_DEFLATE: + total_header_size += 8*5 + 4*5; + break; + case CHUNK_RAW: + total_header_size += 4 + patch_size[i]; + break; + } + } + + size_t offset = total_header_size; + + FILE* f = fopen(argv[3], "wb"); + + // Write out the headers. + + fwrite("IMGDIFF2", 1, 8, f); + Write4(num_tgt_chunks, f); + for (i = 0; i < num_tgt_chunks; ++i) { + Write4(tgt_chunks[i].type, f); + + switch (tgt_chunks[i].type) { + case CHUNK_NORMAL: + printf("chunk %3d: normal (%10d, %10d) %10d\n", i, + tgt_chunks[i].start, tgt_chunks[i].len, patch_size[i]); + Write8(tgt_chunks[i].source_start, f); + Write8(tgt_chunks[i].source_len, f); + Write8(offset, f); + offset += patch_size[i]; + break; + + case CHUNK_DEFLATE: + printf("chunk %3d: deflate (%10d, %10d) %10d %s\n", i, + tgt_chunks[i].start, tgt_chunks[i].deflate_len, patch_size[i], + tgt_chunks[i].filename); + Write8(tgt_chunks[i].source_start, f); + Write8(tgt_chunks[i].source_len, f); + Write8(offset, f); + Write8(tgt_chunks[i].source_uncompressed_len, f); + Write8(tgt_chunks[i].len, f); + Write4(tgt_chunks[i].level, f); + Write4(tgt_chunks[i].method, f); + Write4(tgt_chunks[i].windowBits, f); + Write4(tgt_chunks[i].memLevel, f); + Write4(tgt_chunks[i].strategy, f); + offset += patch_size[i]; + break; + + case CHUNK_RAW: + printf("chunk %3d: raw (%10d, %10d)\n", i, + tgt_chunks[i].start, tgt_chunks[i].len); + Write4(patch_size[i], f); + fwrite(patch_data[i], 1, patch_size[i], f); + break; + } + } + + // Append each chunk's bsdiff patch, in order. + + for (i = 0; i < num_tgt_chunks; ++i) { + if (tgt_chunks[i].type != CHUNK_RAW) { + fwrite(patch_data[i], 1, patch_size[i], f); + } + } + + fclose(f); + + return 0; +} diff --git a/applypatch/imgdiff.h b/applypatch/imgdiff.h new file mode 100644 index 0000000..f2069b4 --- /dev/null +++ b/applypatch/imgdiff.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Image patch chunk types +#define CHUNK_NORMAL 0 +#define CHUNK_GZIP 1 // version 1 only +#define CHUNK_DEFLATE 2 // version 2 only +#define CHUNK_RAW 3 // version 2 only + +// The gzip header size is actually variable, but we currently don't +// support gzipped data with any of the optional fields, so for now it +// will always be ten bytes. See RFC 1952 for the definition of the +// gzip format. +#define GZIP_HEADER_LEN 10 + +// The gzip footer size really is fixed. +#define GZIP_FOOTER_LEN 8 diff --git a/applypatch/imgdiff_test.sh b/applypatch/imgdiff_test.sh new file mode 100755 index 0000000..dcdb922 --- /dev/null +++ b/applypatch/imgdiff_test.sh @@ -0,0 +1,118 @@ +#!/bin/bash +# +# A script for testing imgdiff/applypatch. It takes two full OTA +# packages as arguments. It generates (on the host) patches for all +# the zip/jar/apk files they have in common, as well as boot and +# recovery images. It then applies the patches on the device (or +# emulator) and checks that the resulting file is correct. + +EMULATOR_PORT=5580 + +# set to 0 to use a device instead +USE_EMULATOR=0 + +# where on the device to do all the patching. +WORK_DIR=/data/local/tmp + +START_OTA_PACKAGE=$1 +END_OTA_PACKAGE=$2 + +# ------------------------ + +tmpdir=$(mktemp -d) + +if [ "$USE_EMULATOR" == 1 ]; then + emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT & + pid_emulator=$! + ADB="adb -s emulator-$EMULATOR_PORT " +else + ADB="adb -d " +fi + +echo "waiting to connect to device" +$ADB wait-for-device + +# run a command on the device; exit with the exit status of the device +# command. +run_command() { + $ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}' +} + +testname() { + echo + echo "$1"... + testname="$1" +} + +fail() { + echo + echo FAIL: $testname + echo + [ "$open_pid" == "" ] || kill $open_pid + [ "$pid_emulator" == "" ] || kill $pid_emulator + exit 1 +} + +sha1() { + sha1sum $1 | awk '{print $1}' +} + +size() { + stat -c %s $1 | tr -d '\n' +} + +cleanup() { + # not necessary if we're about to kill the emulator, but nice for + # running on real devices or already-running emulators. + testname "removing test files" + run_command rm $WORK_DIR/applypatch + run_command rm $WORK_DIR/source + run_command rm $WORK_DIR/target + run_command rm $WORK_DIR/patch + + [ "$pid_emulator" == "" ] || kill $pid_emulator + + rm -rf $tmpdir +} + +$ADB push $ANDROID_PRODUCT_OUT/system/bin/applypatch $WORK_DIR/applypatch + +patch_and_apply() { + local fn=$1 + shift + + unzip -p $START_OTA_PACKAGE $fn > $tmpdir/source + unzip -p $END_OTA_PACKAGE $fn > $tmpdir/target + imgdiff "$@" $tmpdir/source $tmpdir/target $tmpdir/patch + bsdiff $tmpdir/source $tmpdir/target $tmpdir/patch.bs + echo "patch for $fn is $(size $tmpdir/patch) [of $(size $tmpdir/target)] ($(size $tmpdir/patch.bs) with bsdiff)" + echo "$fn $(size $tmpdir/patch) of $(size $tmpdir/target) bsdiff $(size $tmpdir/patch.bs)" >> /tmp/stats.txt + $ADB push $tmpdir/source $WORK_DIR/source || fail "source push failed" + run_command rm /data/local/tmp/target + $ADB push $tmpdir/patch $WORK_DIR/patch || fail "patch push failed" + run_command /data/local/tmp/applypatch /data/local/tmp/source \ + /data/local/tmp/target $(sha1 $tmpdir/target) $(size $tmpdir/target) \ + $(sha1 $tmpdir/source):/data/local/tmp/patch \ + || fail "applypatch of $fn failed" + $ADB pull /data/local/tmp/target $tmpdir/result + diff -q $tmpdir/target $tmpdir/result || fail "patch output not correct!" +} + +# --------------- basic execution ---------------------- + +for i in $((zipinfo -1 $START_OTA_PACKAGE; zipinfo -1 $END_OTA_PACKAGE) | \ + sort | uniq -d | egrep -e '[.](apk|jar|zip)$'); do + patch_and_apply $i -z +done +patch_and_apply boot.img +patch_and_apply system/recovery.img + + +# --------------- cleanup ---------------------- + +cleanup + +echo +echo PASS +echo + diff --git a/applypatch/imgpatch.c b/applypatch/imgpatch.c new file mode 100644 index 0000000..e3ee80a --- /dev/null +++ b/applypatch/imgpatch.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// See imgdiff.c in this directory for a description of the patch file +// format. + +#include +#include +#include +#include +#include + +#include "zlib.h" +#include "mincrypt/sha.h" +#include "applypatch.h" +#include "imgdiff.h" +#include "utils.h" + +/* + * Apply the patch given in 'patch_filename' to the source data given + * by (old_data, old_size). Write the patched output to the 'output' + * file, and update the SHA context with the output data as well. + * Return 0 on success. + */ +int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, + const Value* patch, + SinkFn sink, void* token, SHA_CTX* ctx) { + ssize_t pos = 12; + char* header = patch->data; + if (patch->size < 12) { + printf("patch too short to contain header\n"); + return -1; + } + + // IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW. + // (IMGDIFF1, which is no longer supported, used CHUNK_NORMAL and + // CHUNK_GZIP.) + if (memcmp(header, "IMGDIFF2", 8) != 0) { + printf("corrupt patch file header (magic number)\n"); + return -1; + } + + int num_chunks = Read4(header+8); + + int i; + for (i = 0; i < num_chunks; ++i) { + // each chunk's header record starts with 4 bytes. + if (pos + 4 > patch->size) { + printf("failed to read chunk %d record\n", i); + return -1; + } + int type = Read4(patch->data + pos); + pos += 4; + + if (type == CHUNK_NORMAL) { + char* normal_header = patch->data + pos; + pos += 24; + if (pos > patch->size) { + printf("failed to read chunk %d normal header data\n", i); + return -1; + } + + size_t src_start = Read8(normal_header); + size_t src_len = Read8(normal_header+8); + size_t patch_offset = Read8(normal_header+16); + + ApplyBSDiffPatch(old_data + src_start, src_len, + patch, patch_offset, sink, token, ctx); + } else if (type == CHUNK_RAW) { + char* raw_header = patch->data + pos; + pos += 4; + if (pos > patch->size) { + printf("failed to read chunk %d raw header data\n", i); + return -1; + } + + ssize_t data_len = Read4(raw_header); + + if (pos + data_len > patch->size) { + printf("failed to read chunk %d raw data\n", i); + return -1; + } + 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); + return -1; + } + pos += data_len; + } else if (type == CHUNK_DEFLATE) { + // deflate chunks have an additional 60 bytes in their chunk header. + char* deflate_header = patch->data + pos; + pos += 60; + if (pos > patch->size) { + printf("failed to read chunk %d deflate header data\n", i); + return -1; + } + + size_t src_start = Read8(deflate_header); + size_t src_len = Read8(deflate_header+8); + size_t patch_offset = Read8(deflate_header+16); + size_t expanded_len = Read8(deflate_header+24); + size_t target_len = Read8(deflate_header+32); + int level = Read4(deflate_header+40); + int method = Read4(deflate_header+44); + int windowBits = Read4(deflate_header+48); + int memLevel = Read4(deflate_header+52); + int strategy = Read4(deflate_header+56); + + // Decompress the source data; the chunk header tells us exactly + // how big we expect it to be when decompressed. + + unsigned char* expanded_source = malloc(expanded_len); + if (expanded_source == NULL) { + printf("failed to allocate %d bytes for expanded_source\n", + expanded_len); + return -1; + } + + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = src_len; + strm.next_in = (unsigned char*)(old_data + src_start); + strm.avail_out = expanded_len; + strm.next_out = expanded_source; + + int ret; + ret = inflateInit2(&strm, -15); + if (ret != Z_OK) { + printf("failed to init source inflation: %d\n", ret); + return -1; + } + + // Because we've provided enough room to accommodate the output + // data, we expect one call to inflate() to suffice. + ret = inflate(&strm, Z_SYNC_FLUSH); + if (ret != Z_STREAM_END) { + printf("source inflation returned %d\n", ret); + return -1; + } + // We should have filled the output buffer exactly. + if (strm.avail_out != 0) { + printf("source inflation short by %d bytes\n", strm.avail_out); + return -1; + } + inflateEnd(&strm); + + // Next, apply the bsdiff patch (in memory) to the uncompressed + // data. + unsigned char* uncompressed_target_data; + ssize_t uncompressed_target_size; + if (ApplyBSDiffPatchMem(expanded_source, expanded_len, + patch, patch_offset, + &uncompressed_target_data, + &uncompressed_target_size) != 0) { + return -1; + } + + // Now compress the target data and append it to the output. + + // we're done with the expanded_source data buffer, so we'll + // reuse that memory to receive the output of deflate. + unsigned char* temp_data = expanded_source; + ssize_t temp_size = expanded_len; + if (temp_size < 32768) { + // ... unless the buffer is too small, in which case we'll + // allocate a fresh one. + free(temp_data); + temp_data = malloc(32768); + temp_size = 32768; + } + + // now the deflate stream + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = uncompressed_target_size; + strm.next_in = uncompressed_target_data; + ret = deflateInit2(&strm, level, method, windowBits, memLevel, strategy); + do { + strm.avail_out = temp_size; + strm.next_out = temp_data; + ret = deflate(&strm, Z_FINISH); + ssize_t have = temp_size - strm.avail_out; + + if (sink(temp_data, have, token) != have) { + printf("failed to write %ld compressed bytes to output\n", + (long)have); + return -1; + } + SHA_update(ctx, temp_data, have); + } while (ret != Z_STREAM_END); + deflateEnd(&strm); + + free(temp_data); + free(uncompressed_target_data); + } else { + printf("patch chunk %d is unknown type %d\n", i, type); + return -1; + } + } + + return 0; +} diff --git a/applypatch/main.c b/applypatch/main.c new file mode 100644 index 0000000..3917f86 --- /dev/null +++ b/applypatch/main.c @@ -0,0 +1,195 @@ +/* + * 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 +#include +#include +#include + +#include "applypatch.h" +#include "edify/expr.h" +#include "mincrypt/sha.h" + +int CheckMode(int argc, char** argv) { + if (argc < 3) { + return 2; + } + return applypatch_check(argv[2], argc-3, argv+3); +} + +int SpaceMode(int argc, char** argv) { + if (argc != 3) { + return 2; + } + char* endptr; + size_t bytes = strtol(argv[2], &endptr, 10); + if (bytes == 0 && endptr == argv[2]) { + printf("can't parse \"%s\" as byte count\n\n", argv[2]); + return 1; + } + return CacheSizeCheck(bytes); +} + +// Parse arguments (which should be of the form "" or +// ":" into the new parallel arrays *sha1s and +// *patches (loading file contents into the patches). Returns 0 on +// success. +static int ParsePatchArgs(int argc, char** argv, + char*** sha1s, Value*** patches, int* num_patches) { + *num_patches = argc; + *sha1s = malloc(*num_patches * sizeof(char*)); + *patches = malloc(*num_patches * sizeof(Value*)); + memset(*patches, 0, *num_patches * sizeof(Value*)); + + uint8_t digest[SHA_DIGEST_SIZE]; + + int i; + for (i = 0; i < *num_patches; ++i) { + char* colon = strchr(argv[i], ':'); + if (colon != NULL) { + *colon = '\0'; + ++colon; + } + + if (ParseSha1(argv[i], digest) != 0) { + printf("failed to parse sha1 \"%s\"\n", argv[i]); + return -1; + } + + (*sha1s)[i] = argv[i]; + if (colon == NULL) { + (*patches)[i] = NULL; + } else { + FileContents fc; + if (LoadFileContents(colon, &fc) != 0) { + goto abort; + } + (*patches)[i] = malloc(sizeof(Value)); + (*patches)[i]->type = VAL_BLOB; + (*patches)[i]->size = fc.size; + (*patches)[i]->data = (char*)fc.data; + } + } + + return 0; + + abort: + for (i = 0; i < *num_patches; ++i) { + Value* p = (*patches)[i]; + if (p != NULL) { + free(p->data); + free(p); + } + } + free(*sha1s); + free(*patches); + return -1; +} + +int PatchMode(int argc, char** argv) { + if (argc < 6) { + return 2; + } + + char* endptr; + size_t target_size = strtol(argv[4], &endptr, 10); + if (target_size == 0 && endptr == argv[4]) { + printf("can't parse \"%s\" as byte count\n\n", argv[4]); + return 1; + } + + char** sha1s; + Value** patches; + int num_patches; + if (ParsePatchArgs(argc-5, argv+5, &sha1s, &patches, &num_patches) != 0) { + printf("failed to parse patch args\n"); + return 1; + } + + int result = applypatch(argv[1], argv[2], argv[3], target_size, + num_patches, sha1s, patches); + + int i; + for (i = 0; i < num_patches; ++i) { + Value* p = patches[i]; + if (p != NULL) { + free(p->data); + free(p); + } + } + free(sha1s); + free(patches); + + return result; +} + +// This program applies binary patches to files in a way that is safe +// (the original file is not touched until we have the desired +// replacement for it) and idempotent (it's okay to run this program +// multiple times). +// +// - if the sha1 hash of is , does nothing and exits +// successfully. +// +// - otherwise, if the sha1 hash of is , applies the +// bsdiff to to produce a new file (the type of patch +// is automatically detected from the file header). If that new +// file has sha1 hash , moves it to replace , and +// exits successfully. Note that if and are +// not the same, is NOT deleted on success. +// may be the string "-" to mean "the same as src-file". +// +// - otherwise, or if any error is encountered, exits with non-zero +// status. +// +// (or in check mode) may refer to an MTD partition +// to read the source data. See the comments for the +// LoadMTDContents() function above for the format of such a filename. + +int main(int argc, char** argv) { + if (argc < 2) { + usage: + printf( + "usage: %s " + "[: ...]\n" + " or %s -c [ ...]\n" + " or %s -s \n" + " or %s -l\n" + "\n" + "Filenames may be of the form\n" + " MTD::::::...\n" + "to specify reading from or writing to an MTD partition.\n\n", + argv[0], argv[0], argv[0], argv[0]); + return 2; + } + + int result; + + if (strncmp(argv[1], "-l", 3) == 0) { + result = ShowLicenses(); + } else if (strncmp(argv[1], "-c", 3) == 0) { + result = CheckMode(argc, argv); + } else if (strncmp(argv[1], "-s", 3) == 0) { + result = SpaceMode(argc, argv); + } else { + result = PatchMode(argc, argv); + } + + if (result == 2) { + goto usage; + } + return result; +} diff --git a/applypatch/testdata/new.file b/applypatch/testdata/new.file new file mode 100644 index 0000000..cdeb8fd Binary files /dev/null and b/applypatch/testdata/new.file differ diff --git a/applypatch/testdata/old.file b/applypatch/testdata/old.file new file mode 100644 index 0000000..166c873 Binary files /dev/null and b/applypatch/testdata/old.file differ diff --git a/applypatch/testdata/patch.bsdiff b/applypatch/testdata/patch.bsdiff new file mode 100644 index 0000000..b78d385 Binary files /dev/null and b/applypatch/testdata/patch.bsdiff differ diff --git a/applypatch/utils.c b/applypatch/utils.c new file mode 100644 index 0000000..41ff676 --- /dev/null +++ b/applypatch/utils.c @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "utils.h" + +/** Write a 4-byte value to f in little-endian order. */ +void Write4(int value, FILE* f) { + fputc(value & 0xff, f); + fputc((value >> 8) & 0xff, f); + fputc((value >> 16) & 0xff, f); + fputc((value >> 24) & 0xff, f); +} + +/** Write an 8-byte value to f in little-endian order. */ +void Write8(long long value, FILE* f) { + fputc(value & 0xff, f); + fputc((value >> 8) & 0xff, f); + fputc((value >> 16) & 0xff, f); + fputc((value >> 24) & 0xff, f); + fputc((value >> 32) & 0xff, f); + fputc((value >> 40) & 0xff, f); + fputc((value >> 48) & 0xff, f); + fputc((value >> 56) & 0xff, f); +} + +int Read2(void* pv) { + unsigned char* p = pv; + return (int)(((unsigned int)p[1] << 8) | + (unsigned int)p[0]); +} + +int Read4(void* pv) { + unsigned char* p = pv; + return (int)(((unsigned int)p[3] << 24) | + ((unsigned int)p[2] << 16) | + ((unsigned int)p[1] << 8) | + (unsigned int)p[0]); +} + +long long Read8(void* pv) { + unsigned char* p = pv; + return (long long)(((unsigned long long)p[7] << 56) | + ((unsigned long long)p[6] << 48) | + ((unsigned long long)p[5] << 40) | + ((unsigned long long)p[4] << 32) | + ((unsigned long long)p[3] << 24) | + ((unsigned long long)p[2] << 16) | + ((unsigned long long)p[1] << 8) | + (unsigned long long)p[0]); +} diff --git a/applypatch/utils.h b/applypatch/utils.h new file mode 100644 index 0000000..bc97f17 --- /dev/null +++ b/applypatch/utils.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _BUILD_TOOLS_APPLYPATCH_UTILS_H +#define _BUILD_TOOLS_APPLYPATCH_UTILS_H + +#include + +// Read and write little-endian values of various sizes. + +void Write4(int value, FILE* f); +void Write8(long long value, FILE* f); +int Read2(void* p); +int Read4(void* p); +long long Read8(void* p); + +#endif // _BUILD_TOOLS_APPLYPATCH_UTILS_H diff --git a/bootloader.c b/bootloader.c index bc79bee..38b5651 100644 --- a/bootloader.c +++ b/bootloader.c @@ -119,153 +119,3 @@ int set_bootloader_message(const struct bootloader_message *in) { LOGI("Set boot command \"%s\"\n", in->command[0] != 255 ? in->command : ""); return 0; } - -/* Update Image - * - * - will be stored in the "cache" partition - * - bad blocks will be ignored, like boot.img and recovery.img - * - the first block will be the image header (described below) - * - the size is in BYTES, inclusive of the header - * - offsets are in BYTES from the start of the update header - * - two raw bitmaps will be included, the "busy" and "fail" bitmaps - * - for dream, the bitmaps will be 320x480x16bpp RGB565 - */ - -#define UPDATE_MAGIC "MSM-RADIO-UPDATE" -#define UPDATE_MAGIC_SIZE 16 -#define UPDATE_VERSION 0x00010000 - -struct update_header { - unsigned char MAGIC[UPDATE_MAGIC_SIZE]; - - unsigned version; - unsigned size; - - unsigned image_offset; - unsigned image_length; - - unsigned bitmap_width; - unsigned bitmap_height; - unsigned bitmap_bpp; - - unsigned busy_bitmap_offset; - unsigned busy_bitmap_length; - - unsigned fail_bitmap_offset; - unsigned fail_bitmap_length; -}; - -int write_update_for_bootloader( - const char *update, int update_length, - int bitmap_width, int bitmap_height, int bitmap_bpp, - const char *busy_bitmap, const char *fail_bitmap) { - if (ensure_root_path_unmounted(CACHE_NAME)) { - LOGE("Can't unmount %s\n", CACHE_NAME); - return -1; - } - - const MtdPartition *part = get_root_mtd_partition(CACHE_NAME); - if (part == NULL) { - LOGE("Can't find %s\n", CACHE_NAME); - return -1; - } - - MtdWriteContext *write = mtd_write_partition(part); - if (write == NULL) { - LOGE("Can't open %s\n(%s)\n", CACHE_NAME, strerror(errno)); - return -1; - } - - /* Write an invalid (zero) header first, to disable any previous - * update and any other structured contents (like a filesystem), - * and as a placeholder for the amount of space required. - */ - - struct update_header header; - memset(&header, 0, sizeof(header)); - const ssize_t header_size = sizeof(header); - if (mtd_write_data(write, (char*) &header, header_size) != header_size) { - LOGE("Can't write header to %s\n(%s)\n", CACHE_NAME, strerror(errno)); - mtd_write_close(write); - return -1; - } - - /* Write each section individually block-aligned, so we can write - * each block independently without complicated buffering. - */ - - memcpy(&header.MAGIC, UPDATE_MAGIC, UPDATE_MAGIC_SIZE); - header.version = UPDATE_VERSION; - header.size = header_size; - - off_t image_start_pos = mtd_erase_blocks(write, 0); - header.image_length = update_length; - if ((int) header.image_offset == -1 || - mtd_write_data(write, update, update_length) != update_length) { - LOGE("Can't write update to %s\n(%s)\n", CACHE_NAME, strerror(errno)); - mtd_write_close(write); - return -1; - } - off_t busy_start_pos = mtd_erase_blocks(write, 0); - header.image_offset = mtd_find_write_start(write, image_start_pos); - - header.bitmap_width = bitmap_width; - header.bitmap_height = bitmap_height; - header.bitmap_bpp = bitmap_bpp; - - int bitmap_length = (bitmap_bpp + 7) / 8 * bitmap_width * bitmap_height; - - header.busy_bitmap_length = busy_bitmap != NULL ? bitmap_length : 0; - if ((int) header.busy_bitmap_offset == -1 || - mtd_write_data(write, busy_bitmap, bitmap_length) != bitmap_length) { - LOGE("Can't write bitmap to %s\n(%s)\n", CACHE_NAME, strerror(errno)); - mtd_write_close(write); - return -1; - } - off_t fail_start_pos = mtd_erase_blocks(write, 0); - header.busy_bitmap_offset = mtd_find_write_start(write, busy_start_pos); - - header.fail_bitmap_length = fail_bitmap != NULL ? bitmap_length : 0; - if ((int) header.fail_bitmap_offset == -1 || - mtd_write_data(write, fail_bitmap, bitmap_length) != bitmap_length) { - LOGE("Can't write bitmap to %s\n(%s)\n", CACHE_NAME, strerror(errno)); - mtd_write_close(write); - return -1; - } - mtd_erase_blocks(write, 0); - header.fail_bitmap_offset = mtd_find_write_start(write, fail_start_pos); - - /* Write the header last, after all the blocks it refers to, so that - * when the magic number is installed everything is valid. - */ - - if (mtd_write_close(write)) { - LOGE("Can't finish writing %s\n(%s)\n", CACHE_NAME, strerror(errno)); - return -1; - } - - write = mtd_write_partition(part); - if (write == NULL) { - LOGE("Can't reopen %s\n(%s)\n", CACHE_NAME, strerror(errno)); - return -1; - } - - if (mtd_write_data(write, (char*) &header, header_size) != header_size) { - LOGE("Can't rewrite header to %s\n(%s)\n", CACHE_NAME, strerror(errno)); - mtd_write_close(write); - return -1; - } - - if (mtd_erase_blocks(write, 0) != image_start_pos) { - LOGE("Misalignment rewriting %s\n(%s)\n", CACHE_NAME, strerror(errno)); - mtd_write_close(write); - return -1; - } - - if (mtd_write_close(write)) { - LOGE("Can't finish header of %s\n(%s)\n", CACHE_NAME, strerror(errno)); - return -1; - } - - return 0; -} diff --git a/bootloader.h b/bootloader.h index 3d4302f..2e749aa 100644 --- a/bootloader.h +++ b/bootloader.h @@ -47,13 +47,4 @@ struct bootloader_message { int get_bootloader_message(struct bootloader_message *out); int set_bootloader_message(const struct bootloader_message *in); -/* Write an update to the cache partition for update-radio or update-hboot. - * Note, this destroys any filesystem on the cache partition! - * The expected bitmap format is 240x320, 16bpp (2Bpp), RGB 5:6:5. - */ -int write_update_for_bootloader( - const char *update, int update_len, - int bitmap_width, int bitmap_height, int bitmap_bpp, - const char *busy_bitmap, const char *error_bitmap); - #endif diff --git a/common.h b/common.h index 81d9029..fd8605a 100644 --- a/common.h +++ b/common.h @@ -52,17 +52,10 @@ enum { BACKGROUND_ICON_NONE, BACKGROUND_ICON_INSTALLING, BACKGROUND_ICON_ERROR, - BACKGROUND_ICON_FIRMWARE_INSTALLING, - BACKGROUND_ICON_FIRMWARE_ERROR, NUM_BACKGROUND_ICONS }; void ui_set_background(int icon); -// Get a malloc'd copy of the screen image showing (only) the specified icon. -// Also returns the width, height, and bits per pixel of the returned image. -// TODO: Use some sort of "struct Bitmap" here instead of all these variables? -char *ui_copy_image(int icon, int *width, int *height, int *bpp); - // Show a progress bar and define the scope of the next operation: // portion - fraction of the progress bar the next operation will use // seconds - expected time interval (progress bar moves at this minimum rate) diff --git a/default_recovery_ui.c b/default_recovery_ui.c index f189da4..8e98a67 100644 --- a/default_recovery_ui.c +++ b/default_recovery_ui.c @@ -32,6 +32,10 @@ char* MENU_ITEMS[] = { "reboot system now", "advanced", NULL }; +int device_recovery_start() { + return 0; +} + int device_toggle_display(volatile char* key_pressed, int key_code) { int alt = key_pressed[KEY_LEFTALT] || key_pressed[KEY_RIGHTALT]; if (alt && key_code == KEY_L) diff --git a/edify/expr.c b/edify/expr.c index 72e5100..3600075 100644 --- a/edify/expr.c +++ b/edify/expr.c @@ -33,12 +33,40 @@ int BooleanString(const char* s) { } char* Evaluate(State* state, Expr* expr) { + Value* v = expr->fn(expr->name, state, expr->argc, expr->argv); + if (v == NULL) return NULL; + if (v->type != VAL_STRING) { + ErrorAbort(state, "expecting string, got value type %d", v->type); + FreeValue(v); + return NULL; + } + char* result = v->data; + free(v); + return result; +} + +Value* EvaluateValue(State* state, Expr* expr) { return expr->fn(expr->name, state, expr->argc, expr->argv); } -char* ConcatFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* StringValue(char* str) { + if (str == NULL) return NULL; + Value* v = malloc(sizeof(Value)); + v->type = VAL_STRING; + v->size = strlen(str); + v->data = str; + return v; +} + +void FreeValue(Value* v) { + if (v == NULL) return; + free(v->data); + free(v); +} + +Value* ConcatFn(const char* name, State* state, int argc, Expr* argv[]) { if (argc == 0) { - return strdup(""); + return StringValue(strdup("")); } char** strings = malloc(argc * sizeof(char*)); int i; @@ -67,10 +95,11 @@ char* ConcatFn(const char* name, State* state, int argc, Expr* argv[]) { for (i = 0; i < argc; ++i) { free(strings[i]); } - return result; + free(strings); + return StringValue(result); } -char* IfElseFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* IfElseFn(const char* name, State* state, int argc, Expr* argv[]) { if (argc != 2 && argc != 3) { free(state->errmsg); state->errmsg = strdup("ifelse expects 2 or 3 arguments"); @@ -83,18 +112,18 @@ char* IfElseFn(const char* name, State* state, int argc, Expr* argv[]) { if (BooleanString(cond) == true) { free(cond); - return Evaluate(state, argv[1]); + return EvaluateValue(state, argv[1]); } else { if (argc == 3) { free(cond); - return Evaluate(state, argv[2]); + return EvaluateValue(state, argv[2]); } else { - return cond; + return StringValue(cond); } } } -char* AbortFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* AbortFn(const char* name, State* state, int argc, Expr* argv[]) { char* msg = NULL; if (argc > 0) { msg = Evaluate(state, argv[0]); @@ -108,7 +137,7 @@ char* AbortFn(const char* name, State* state, int argc, Expr* argv[]) { return NULL; } -char* AssertFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* AssertFn(const char* name, State* state, int argc, Expr* argv[]) { int i; for (i = 0; i < argc; ++i) { char* v = Evaluate(state, argv[i]); @@ -130,20 +159,20 @@ char* AssertFn(const char* name, State* state, int argc, Expr* argv[]) { return NULL; } } - return strdup(""); + return StringValue(strdup("")); } -char* SleepFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* SleepFn(const char* name, State* state, int argc, Expr* argv[]) { char* val = Evaluate(state, argv[0]); if (val == NULL) { return NULL; } int v = strtol(val, NULL, 10); sleep(v); - return val; + return StringValue(val); } -char* StdoutFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* StdoutFn(const char* name, State* state, int argc, Expr* argv[]) { int i; for (i = 0; i < argc; ++i) { char* v = Evaluate(state, argv[i]); @@ -153,48 +182,44 @@ char* StdoutFn(const char* name, State* state, int argc, Expr* argv[]) { fputs(v, stdout); free(v); } - return strdup(""); + return StringValue(strdup("")); } -char* LogicalAndFn(const char* name, State* state, +Value* LogicalAndFn(const char* name, State* state, int argc, Expr* argv[]) { char* left = Evaluate(state, argv[0]); if (left == NULL) return NULL; if (BooleanString(left) == true) { free(left); - return Evaluate(state, argv[1]); + return EvaluateValue(state, argv[1]); } else { - return left; + return StringValue(left); } } -char* LogicalOrFn(const char* name, State* state, - int argc, Expr* argv[]) { +Value* LogicalOrFn(const char* name, State* state, + int argc, Expr* argv[]) { char* left = Evaluate(state, argv[0]); if (left == NULL) return NULL; if (BooleanString(left) == false) { free(left); - return Evaluate(state, argv[1]); + return EvaluateValue(state, argv[1]); } else { - return left; + return StringValue(left); } } -char* LogicalNotFn(const char* name, State* state, - int argc, Expr* argv[]) { +Value* LogicalNotFn(const char* name, State* state, + int argc, Expr* argv[]) { char* val = Evaluate(state, argv[0]); if (val == NULL) return NULL; bool bv = BooleanString(val); free(val); - if (bv) { - return strdup(""); - } else { - return strdup("t"); - } + return StringValue(strdup(bv ? "" : "t")); } -char* SubstringFn(const char* name, State* state, - int argc, Expr* argv[]) { +Value* SubstringFn(const char* name, State* state, + int argc, Expr* argv[]) { char* needle = Evaluate(state, argv[0]); if (needle == NULL) return NULL; char* haystack = Evaluate(state, argv[1]); @@ -206,10 +231,10 @@ char* SubstringFn(const char* name, State* state, char* result = strdup(strstr(haystack, needle) ? "t" : ""); free(needle); free(haystack); - return result; + return StringValue(result); } -char* EqualityFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* EqualityFn(const char* name, State* state, int argc, Expr* argv[]) { char* left = Evaluate(state, argv[0]); if (left == NULL) return NULL; char* right = Evaluate(state, argv[1]); @@ -221,10 +246,10 @@ char* EqualityFn(const char* name, State* state, int argc, Expr* argv[]) { char* result = strdup(strcmp(left, right) == 0 ? "t" : ""); free(left); free(right); - return result; + return StringValue(result); } -char* InequalityFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* InequalityFn(const char* name, State* state, int argc, Expr* argv[]) { char* left = Evaluate(state, argv[0]); if (left == NULL) return NULL; char* right = Evaluate(state, argv[1]); @@ -236,17 +261,17 @@ char* InequalityFn(const char* name, State* state, int argc, Expr* argv[]) { char* result = strdup(strcmp(left, right) != 0 ? "t" : ""); free(left); free(right); - return result; + return StringValue(result); } -char* SequenceFn(const char* name, State* state, int argc, Expr* argv[]) { - char* left = Evaluate(state, argv[0]); +Value* SequenceFn(const char* name, State* state, int argc, Expr* argv[]) { + Value* left = EvaluateValue(state, argv[0]); if (left == NULL) return NULL; - free(left); - return Evaluate(state, argv[1]); + FreeValue(left); + return EvaluateValue(state, argv[1]); } -char* LessThanIntFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* LessThanIntFn(const char* name, State* state, int argc, Expr* argv[]) { if (argc != 2) { free(state->errmsg); state->errmsg = strdup("less_than_int expects 2 arguments"); @@ -277,10 +302,11 @@ char* LessThanIntFn(const char* name, State* state, int argc, Expr* argv[]) { done: free(left); free(right); - return strdup(result ? "t" : ""); + return StringValue(strdup(result ? "t" : "")); } -char* GreaterThanIntFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* GreaterThanIntFn(const char* name, State* state, + int argc, Expr* argv[]) { if (argc != 2) { free(state->errmsg); state->errmsg = strdup("greater_than_int expects 2 arguments"); @@ -294,8 +320,8 @@ char* GreaterThanIntFn(const char* name, State* state, int argc, Expr* argv[]) { return LessThanIntFn(name, state, 2, temp); } -char* Literal(const char* name, State* state, int argc, Expr* argv[]) { - return strdup(name); +Value* Literal(const char* name, State* state, int argc, Expr* argv[]) { + return StringValue(strdup(name)); } Expr* Build(Function fn, YYLTYPE loc, int count, ...) { @@ -389,11 +415,39 @@ int ReadArgs(State* state, Expr* argv[], int count, ...) { for (j = 0; j < i; ++j) { free(args[j]); } + free(args); return -1; } *(va_arg(v, char**)) = args[i]; } va_end(v); + free(args); + return 0; +} + +// Evaluate the expressions in argv, giving 'count' Value* (the ... is +// zero or more Value** to put them in). If any expression evaluates +// to NULL, free the rest and return -1. Return 0 on success. +int ReadValueArgs(State* state, Expr* argv[], int count, ...) { + Value** args = malloc(count * sizeof(Value*)); + va_list v; + va_start(v, count); + int i; + for (i = 0; i < count; ++i) { + args[i] = EvaluateValue(state, argv[i]); + if (args[i] == NULL) { + va_end(v); + int j; + for (j = 0; j < i; ++j) { + FreeValue(args[j]); + } + free(args); + return -1; + } + *(va_arg(v, Value**)) = args[i]; + } + va_end(v); + free(args); return 0; } @@ -418,9 +472,30 @@ char** ReadVarArgs(State* state, int argc, Expr* argv[]) { return args; } +// Evaluate the expressions in argv, returning an array of Value* +// results. If any evaluate to NULL, free the rest and return NULL. +// The caller is responsible for freeing the returned array and the +// Values it contains. +Value** ReadValueVarArgs(State* state, int argc, Expr* argv[]) { + Value** args = (Value**)malloc(argc * sizeof(Value*)); + int i = 0; + for (i = 0; i < argc; ++i) { + args[i] = EvaluateValue(state, argv[i]); + if (args[i] == NULL) { + int j; + for (j = 0; j < i; ++j) { + FreeValue(args[j]); + } + free(args); + return NULL; + } + } + return args; +} + // Use printf-style arguments to compose an error message to put into // *state. Returns NULL. -char* ErrorAbort(State* state, char* format, ...) { +Value* ErrorAbort(State* state, char* format, ...) { char* buffer = malloc(4096); va_list v; va_start(v, format); diff --git a/edify/expr.h b/edify/expr.h index d2e7392..8e1c638 100644 --- a/edify/expr.h +++ b/edify/expr.h @@ -17,6 +17,8 @@ #ifndef _EXPRESSION_H #define _EXPRESSION_H +#include + #include "yydefs.h" #define MAX_STRING_LEN 1024 @@ -39,8 +41,17 @@ typedef struct { char* errmsg; } State; -typedef char* (*Function)(const char* name, State* state, - int argc, Expr* argv[]); +#define VAL_STRING 1 // data will be NULL-terminated; size doesn't count null +#define VAL_BLOB 2 + +typedef struct { + int type; + ssize_t size; + char* data; +} Value; + +typedef Value* (*Function)(const char* name, State* state, + int argc, Expr* argv[]); struct Expr { Function fn; @@ -50,31 +61,41 @@ struct Expr { int start, end; }; +// Take one of the Expr*s passed to the function as an argument, +// evaluate it, return the resulting Value. The caller takes +// ownership of the returned Value. +Value* EvaluateValue(State* state, Expr* expr); + +// Take one of the Expr*s passed to the function as an argument, +// evaluate it, assert that it is a string, and return the resulting +// char*. The caller takes ownership of the returned char*. This is +// a convenience function for older functions that want to deal only +// with strings. char* Evaluate(State* state, Expr* expr); // Glue to make an Expr out of a literal. -char* Literal(const char* name, State* state, int argc, Expr* argv[]); +Value* Literal(const char* name, State* state, int argc, Expr* argv[]); // Functions corresponding to various syntactic sugar operators. // ("concat" is also available as a builtin function, to concatenate // more than two strings.) -char* ConcatFn(const char* name, State* state, int argc, Expr* argv[]); -char* LogicalAndFn(const char* name, State* state, int argc, Expr* argv[]); -char* LogicalOrFn(const char* name, State* state, int argc, Expr* argv[]); -char* LogicalNotFn(const char* name, State* state, int argc, Expr* argv[]); -char* SubstringFn(const char* name, State* state, int argc, Expr* argv[]); -char* EqualityFn(const char* name, State* state, int argc, Expr* argv[]); -char* InequalityFn(const char* name, State* state, int argc, Expr* argv[]); -char* SequenceFn(const char* name, State* state, int argc, Expr* argv[]); +Value* ConcatFn(const char* name, State* state, int argc, Expr* argv[]); +Value* LogicalAndFn(const char* name, State* state, int argc, Expr* argv[]); +Value* LogicalOrFn(const char* name, State* state, int argc, Expr* argv[]); +Value* LogicalNotFn(const char* name, State* state, int argc, Expr* argv[]); +Value* SubstringFn(const char* name, State* state, int argc, Expr* argv[]); +Value* EqualityFn(const char* name, State* state, int argc, Expr* argv[]); +Value* InequalityFn(const char* name, State* state, int argc, Expr* argv[]); +Value* SequenceFn(const char* name, State* state, int argc, Expr* argv[]); // Convenience function for building expressions with a fixed number // of arguments. Expr* Build(Function fn, YYLTYPE loc, int count, ...); // Global builtins, registered by RegisterBuiltins(). -char* IfElseFn(const char* name, State* state, int argc, Expr* argv[]); -char* AssertFn(const char* name, State* state, int argc, Expr* argv[]); -char* AbortFn(const char* name, State* state, int argc, Expr* argv[]); +Value* IfElseFn(const char* name, State* state, int argc, Expr* argv[]); +Value* AssertFn(const char* name, State* state, int argc, Expr* argv[]); +Value* AbortFn(const char* name, State* state, int argc, Expr* argv[]); // For setting and getting the global error string (when returning @@ -112,15 +133,31 @@ Function FindFunction(const char* name); // to NULL, free the rest and return -1. Return 0 on success. int ReadArgs(State* state, Expr* argv[], int count, ...); +// Evaluate the expressions in argv, giving 'count' Value* (the ... is +// zero or more Value** to put them in). If any expression evaluates +// to NULL, free the rest and return -1. Return 0 on success. +int ReadValueArgs(State* state, Expr* argv[], int count, ...); + // Evaluate the expressions in argv, returning an array of char* // results. If any evaluate to NULL, free the rest and return NULL. // The caller is responsible for freeing the returned array and the // strings it contains. char** ReadVarArgs(State* state, int argc, Expr* argv[]); +// Evaluate the expressions in argv, returning an array of Value* +// results. If any evaluate to NULL, free the rest and return NULL. +// The caller is responsible for freeing the returned array and the +// Values it contains. +Value** ReadValueVarArgs(State* state, int argc, Expr* argv[]); + // Use printf-style arguments to compose an error message to put into // *state. Returns NULL. -char* ErrorAbort(State* state, char* format, ...); +Value* ErrorAbort(State* state, char* format, ...); +// Wrap a string into a Value, taking ownership of the string. +Value* StringValue(char* str); + +// Free a Value object. +void FreeValue(Value* v); #endif // _EXPRESSION_H diff --git a/edify/lexer.l b/edify/lexer.l index 2c4489c..fb2933b 100644 --- a/edify/lexer.l +++ b/edify/lexer.l @@ -15,6 +15,8 @@ * limitations under the License. */ +#include + #include "expr.h" #include "yydefs.h" #include "parser.h" diff --git a/edify/main.c b/edify/main.c index 0e36108..8557043 100644 --- a/edify/main.c +++ b/edify/main.c @@ -42,11 +42,12 @@ int expect(const char* expr_str, const char* expected, int* errors) { State state; state.cookie = NULL; - state.script = expr_str; + state.script = strdup(expr_str); state.errmsg = NULL; result = Evaluate(&state, e); free(state.errmsg); + free(state.script); if (result == NULL && expected != NULL) { fprintf(stderr, "error evaluating \"%s\"\n", expr_str); ++*errors; @@ -181,6 +182,10 @@ int main(int argc, char** argv) { } FILE* f = fopen(argv[1], "r"); + if (f == NULL) { + printf("%s: %s: No such file or directory\n", argv[0], argv[1]); + return 1; + } char buffer[8192]; int size = fread(buffer, 1, 8191, f); fclose(f); diff --git a/edify/yydefs.h b/edify/yydefs.h index 6257862..aca398f 100644 --- a/edify/yydefs.h +++ b/edify/yydefs.h @@ -33,4 +33,6 @@ typedef struct { } \ } while (0) +int yylex(); + #endif diff --git a/firmware.c b/firmware.c deleted file mode 100644 index e2e4fe6..0000000 --- a/firmware.c +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "bootloader.h" -#include "common.h" -#include "firmware.h" -#include "roots.h" - -#include -#include -#include - -static const char *update_type = NULL; -static const char *update_data = NULL; -static int update_length = 0; - -int remember_firmware_update(const char *type, const char *data, int length) { - if (update_type != NULL || update_data != NULL) { - LOGE("Multiple firmware images\n"); - return -1; - } - - update_type = type; - update_data = data; - update_length = length; - return 0; -} - -// Return true if there is a firmware update pending. -int firmware_update_pending() { - return update_data != NULL && update_length > 0; -} - -/* Bootloader / Recovery Flow - * - * On every boot, the bootloader will read the bootloader_message - * from flash and check the command field. The bootloader should - * deal with the command field not having a 0 terminator correctly - * (so as to not crash if the block is invalid or corrupt). - * - * The bootloader will have to publish the partition that contains - * the bootloader_message to the linux kernel so it can update it. - * - * if command == "boot-recovery" -> boot recovery.img - * else if command == "update-radio" -> update radio image (below) - * else if command == "update-hboot" -> update hboot image (below) - * else -> boot boot.img (normal boot) - * - * Radio/Hboot Update Flow - * 1. the bootloader will attempt to load and validate the header - * 2. if the header is invalid, status="invalid-update", goto #8 - * 3. display the busy image on-screen - * 4. if the update image is invalid, status="invalid-radio-image", goto #8 - * 5. attempt to update the firmware (depending on the command) - * 6. if successful, status="okay", goto #8 - * 7. if failed, and the old image can still boot, status="failed-update" - * 8. write the bootloader_message, leaving the recovery field - * unchanged, updating status, and setting command to - * "boot-recovery" - * 9. reboot - * - * The bootloader will not modify or erase the cache partition. - * It is recovery's responsibility to clean up the mess afterwards. - */ - -int maybe_install_firmware_update(const char *send_intent) { - if (update_data == NULL || update_length == 0) return 0; - - /* We destroy the cache partition to pass the update image to the - * bootloader, so all we can really do afterwards is wipe cache and reboot. - * Set up this instruction now, in case we're interrupted while writing. - */ - - struct bootloader_message boot; - memset(&boot, 0, sizeof(boot)); - strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); - strlcpy(boot.recovery, "recovery\n--wipe_cache\n", sizeof(boot.command)); - if (send_intent != NULL) { - strlcat(boot.recovery, "--send_intent=", sizeof(boot.recovery)); - strlcat(boot.recovery, send_intent, sizeof(boot.recovery)); - strlcat(boot.recovery, "\n", sizeof(boot.recovery)); - } - if (set_bootloader_message(&boot)) return -1; - - int width = 0, height = 0, bpp = 0; - char *busy_image = ui_copy_image( - BACKGROUND_ICON_FIRMWARE_INSTALLING, &width, &height, &bpp); - char *fail_image = ui_copy_image( - BACKGROUND_ICON_FIRMWARE_ERROR, &width, &height, &bpp); - - ui_print("Writing %s image...\n", update_type); - if (write_update_for_bootloader( - update_data, update_length, - width, height, bpp, busy_image, fail_image)) { - LOGE("Can't write %s image\n(%s)\n", update_type, strerror(errno)); - format_root_device("CACHE:"); // Attempt to clean cache up, at least. - return -1; - } - - free(busy_image); - free(fail_image); - - /* The update image is fully written, so now we can instruct the bootloader - * to install it. (After doing so, it will come back here, and we will - * wipe the cache and reboot into the system.) - */ - snprintf(boot.command, sizeof(boot.command), "update-%s", update_type); - if (set_bootloader_message(&boot)) { - format_root_device("CACHE:"); - return -1; - } - - reboot(RB_AUTOBOOT); - - // Can't reboot? WTF? - LOGE("Can't reboot\n"); - return -1; -} diff --git a/firmware.h b/firmware.h deleted file mode 100644 index aeb8f97..0000000 --- a/firmware.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _RECOVERY_FIRMWARE_H -#define _RECOVERY_FIRMWARE_H - -/* Save a radio or bootloader update image for later installation. - * The type should be one of "hboot" or "radio". - * Takes ownership of type and data. Returns nonzero on error. - */ -int remember_firmware_update(const char *type, const char *data, int length); - -/* Returns true if a firmware update has been saved. */ -int firmware_update_pending(); - -/* If an update was saved, reboot into the bootloader now to install it. - * Returns 0 if no radio image was defined, nonzero on error, - * doesn't return at all on success... - */ -int maybe_install_firmware_update(const char *send_intent); - -#endif diff --git a/install.c b/install.c index bbdb854..088f976 100644 --- a/install.c +++ b/install.c @@ -32,74 +32,16 @@ #include "mtdutils/mtdutils.h" #include "roots.h" #include "verifier.h" + #include "firmware.h" #include "legacy.h" #include "extendedcommands.h" + #define ASSUMED_UPDATE_BINARY_NAME "META-INF/com/google/android/update-binary" #define PUBLIC_KEYS_FILE "/res/keys" -// The update binary ask us to install a firmware file on reboot. Set -// that up. Takes ownership of type and filename. -static int -handle_firmware_update(char* type, char* filename, ZipArchive* zip) { - unsigned int data_size; - const ZipEntry* entry = NULL; - - if (strncmp(filename, "PACKAGE:", 8) == 0) { - entry = mzFindZipEntry(zip, filename+8); - if (entry == NULL) { - LOGE("Failed to find \"%s\" in package", filename+8); - return INSTALL_ERROR; - } - data_size = entry->uncompLen; - } else { - struct stat st_data; - if (stat(filename, &st_data) < 0) { - LOGE("Error stat'ing %s: %s\n", filename, strerror(errno)); - return INSTALL_ERROR; - } - data_size = st_data.st_size; - } - - LOGI("type is %s; size is %d; file is %s\n", - type, data_size, filename); - - char* data = malloc(data_size); - if (data == NULL) { - LOGI("Can't allocate %d bytes for firmware data\n", data_size); - return INSTALL_ERROR; - } - - if (entry) { - if (mzReadZipEntry(zip, entry, data, data_size) == false) { - LOGE("Failed to read \"%s\" from package", filename+8); - return INSTALL_ERROR; - } - } else { - FILE* f = fopen(filename, "rb"); - if (f == NULL) { - LOGE("Failed to open %s: %s\n", filename, strerror(errno)); - return INSTALL_ERROR; - } - if (fread(data, 1, data_size, f) != data_size) { - LOGE("Failed to read firmware data: %s\n", strerror(errno)); - return INSTALL_ERROR; - } - fclose(f); - } - - if (remember_firmware_update(type, data, data_size)) { - LOGE("Can't store %s image\n", type); - free(data); - return INSTALL_ERROR; - } - free(filename); - - return INSTALL_SUCCESS; -} - // If the package contains an update binary, extract it and run it. static int try_update_binary(const char *path, ZipArchive *zip) { @@ -148,9 +90,12 @@ try_update_binary(const char *path, ZipArchive *zip) { // // firmware <"hboot"|"radio"> // arrange to install the contents of in the - // given partition on reboot. (API v2: may - // start with "PACKAGE:" to indicate taking a file from - // the OTA package.) + // given partition on reboot. + // + // (API v2: may start with "PACKAGE:" to + // indicate taking a file from the OTA package.) + // + // (API v3: this command no longer exists.) // // ui_print // display on the screen. @@ -175,9 +120,6 @@ try_update_binary(const char *path, ZipArchive *zip) { } close(pipefd[1]); - char* firmware_type = NULL; - char* firmware_filename = NULL; - char buffer[1024]; FILE* from_child = fdopen(pipefd[0], "r"); while (fgets(buffer, sizeof(buffer), from_child) != NULL) { @@ -197,18 +139,6 @@ try_update_binary(const char *path, ZipArchive *zip) { char* fraction_s = strtok(NULL, " \n"); float fraction = strtof(fraction_s, NULL); ui_set_progress(fraction); - } else if (strcmp(command, "firmware") == 0) { - char* type = strtok(NULL, " \n"); - char* filename = strtok(NULL, " \n"); - - if (type != NULL && filename != NULL) { - if (firmware_type != NULL) { - LOGE("ignoring attempt to do multiple firmware updates"); - } else { - firmware_type = strdup(type); - firmware_filename = strdup(filename); - } - } } else if (strcmp(command, "ui_print") == 0) { char* str = strtok(NULL, "\n"); if (str) { @@ -229,11 +159,7 @@ try_update_binary(const char *path, ZipArchive *zip) { return INSTALL_ERROR; } - if (firmware_type != NULL) { - return handle_firmware_update(firmware_type, firmware_filename, zip); - } else { - return INSTALL_SUCCESS; - } + return INSTALL_SUCCESS; } static int @@ -295,7 +221,7 @@ load_keys(const char* filename, int* numKeys) { ++*numKeys; out = realloc(out, *numKeys * sizeof(RSAPublicKey)); RSAPublicKey* key = out + (*numKeys - 1); - if (fscanf(f, " { %i , %i , { %i", + if (fscanf(f, " { %i , 0x%x , { %u", &(key->len), &(key->n0inv), &(key->n[0])) != 3) { goto exit; } @@ -304,11 +230,11 @@ load_keys(const char* filename, int* numKeys) { goto exit; } for (i = 1; i < key->len; ++i) { - if (fscanf(f, " , %i", &(key->n[i])) != 1) goto exit; + if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit; } - if (fscanf(f, " } , { %i", &(key->rr[0])) != 1) goto exit; + if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit; for (i = 1; i < key->len; ++i) { - if (fscanf(f, " , %i", &(key->rr[i])) != 1) goto exit; + if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit; } fscanf(f, " } } "); diff --git a/minui/resources.c b/minui/resources.c index 7ecfeef..3d2c727 100644 --- a/minui/resources.c +++ b/minui/resources.c @@ -97,9 +97,10 @@ int res_create_surface(const char* name, gr_surface* pSurface) { 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 && channels != 4) || - (color_type != PNG_COLOR_TYPE_RGB && - color_type != PNG_COLOR_TYPE_RGBA)) { + 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)))) { return -7; goto exit; } @@ -118,6 +119,10 @@ int res_create_surface(const char* name, gr_surface* pSurface) { surface->format = (channels == 3) ? GGL_PIXEL_FORMAT_RGBX_8888 : GGL_PIXEL_FORMAT_RGBA_8888; + if (color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(png_ptr); + } + int y; if (channels == 3) { for (y = 0; y < height; ++y) { diff --git a/minzip/Zip.c b/minzip/Zip.c index 8cdb898..46d2f82 100644 --- a/minzip/Zip.c +++ b/minzip/Zip.c @@ -810,6 +810,43 @@ bool mzExtractZipEntryToFile(const ZipArchive *pArchive, return true; } +typedef struct { + unsigned char* buffer; + long len; +} BufferExtractCookie; + +static bool bufferProcessFunction(const unsigned char *data, int dataLen, + void *cookie) { + BufferExtractCookie *bec = (BufferExtractCookie*)cookie; + + memmove(bec->buffer, data, dataLen); + bec->buffer += dataLen; + bec->len -= dataLen; + + return true; +} + +/* + * Uncompress "pEntry" in "pArchive" to buffer, which must be large + * enough to hold mzGetZipEntryUncomplen(pEntry) bytes. + */ +bool mzExtractZipEntryToBuffer(const ZipArchive *pArchive, + const ZipEntry *pEntry, unsigned char *buffer) +{ + BufferExtractCookie bec; + bec.buffer = buffer; + bec.len = mzGetZipEntryUncompLen(pEntry); + + bool ret = mzProcessZipEntryContents(pArchive, pEntry, + bufferProcessFunction, (void*)&bec); + if (!ret || bec.len != 0) { + LOGE("Can't extract entry to memory buffer.\n"); + return false; + } + return true; +} + + /* Helper state to make path translation easier and less malloc-happy. */ typedef struct { diff --git a/minzip/Zip.h b/minzip/Zip.h index 1c1df2f..9f99fba 100644 --- a/minzip/Zip.h +++ b/minzip/Zip.h @@ -168,6 +168,13 @@ bool mzIsZipEntryIntact(const ZipArchive *pArchive, const ZipEntry *pEntry); bool mzExtractZipEntryToFile(const ZipArchive *pArchive, const ZipEntry *pEntry, int fd); +/* + * Inflate and write an entry to a memory buffer, which must be long + * enough to hold mzGetZipEntryUncomplen(pEntry) bytes. + */ +bool mzExtractZipEntryToBuffer(const ZipArchive *pArchive, + const ZipEntry *pEntry, unsigned char* buffer); + /* * Inflate all entries under zipDir to the directory specified by * targetDir, which must exist and be a writable directory. diff --git a/recovery.c b/recovery.c index 61f938d..8cfa251 100644 --- a/recovery.c +++ b/recovery.c @@ -32,7 +32,6 @@ #include "bootloader.h" #include "common.h" #include "cutils/properties.h" -#include "firmware.h" #include "install.h" #include "minui/minui.h" #include "minzip/DirUtil.h" @@ -69,6 +68,7 @@ static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; * --update_package=root:path - verify install an OTA package file * --wipe_data - erase user data (and cache), then reboot * --wipe_cache - wipe cache (but not user data), then reboot + * --set_encrypted_filesystem=on|off - enables / diasables encrypted fs * * After completing, we remove /cache/recovery/command and reboot. * Arguments may also be supplied in the bootloader control block (BCB). @@ -113,6 +113,26 @@ static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; * 8g. finish_recovery() erases BCB * -- after this, rebooting will (try to) restart the main system -- * 9. main() calls reboot() to boot main system + * + * ENCRYPTED FILE SYSTEMS ENABLE/DISABLE + * 1. user selects "enable encrypted file systems" + * 2. main system writes "--set_encrypted_filesystem=on|off" to + * /cache/recovery/command + * 3. main system reboots into recovery + * 4. get_args() writes BCB with "boot-recovery" and + * "--set_encrypted_filesystems=on|off" + * -- after this, rebooting will restart the transition -- + * 5. read_encrypted_fs_info() retrieves encrypted file systems settings from /data + * Settings include: property to specify the Encrypted FS istatus and + * FS encryption key if enabled (not yet implemented) + * 6. erase_root() reformats /data + * 7. erase_root() reformats /cache + * 8. restore_encrypted_fs_info() writes required encrypted file systems settings to /data + * Settings include: property to specify the Encrypted FS status and + * FS encryption key if enabled (not yet implemented) + * 9. finish_recovery() erases BCB + * -- after this, rebooting will restart the main system -- + * 10. main() calls reboot() to boot main system */ static const int MAX_ARG_LENGTH = 4096; @@ -218,8 +238,7 @@ get_args(int *argc, char ***argv) { } void -set_sdcard_update_bootloader_message() -{ +set_sdcard_update_bootloader_message() { struct bootloader_message boot; memset(&boot, 0, sizeof(boot)); strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); @@ -232,12 +251,13 @@ set_sdcard_update_bootloader_message() // record any intent we were asked to communicate back to the system. // this function is idempotent: call it as many times as you like. static void -finish_recovery(const char *send_intent) -{ +finish_recovery(const char *send_intent) { // By this point, we're ready to return to the main system... if (send_intent != NULL) { FILE *fp = fopen_root_path(INTENT_FILE, "w"); - if (fp != NULL) { + if (fp == NULL) { + LOGE("Can't open %s\n", INTENT_FILE); + } else { fputs(send_intent, fp); check_and_fclose(fp, INTENT_FILE); } @@ -245,7 +265,9 @@ finish_recovery(const char *send_intent) // Copy logs to cache so the system can find out what happened. FILE *log = fopen_root_path(LOG_FILE, "a"); - if (log != NULL) { + if (log == NULL) { + LOGE("Can't open %s\n", LOG_FILE); + } else { FILE *tmplog = fopen(TEMPORARY_LOG_FILE, "r"); if (tmplog == NULL) { LOGE("Can't open %s\n", TEMPORARY_LOG_FILE); @@ -260,7 +282,7 @@ finish_recovery(const char *send_intent) check_and_fclose(log, LOG_FILE); } - // Reset the bootloader message to revert to a normal main system boot. + // Reset to mormal system boot so recovery won't cycle indefinitely. struct bootloader_message boot; memset(&boot, 0, sizeof(boot)); set_bootloader_message(&boot); @@ -277,8 +299,7 @@ finish_recovery(const char *send_intent) } static int -erase_root(const char *root) -{ +erase_root(const char *root) { ui_set_background(BACKGROUND_ICON_INSTALLING); ui_show_indeterminate_progress(); ui_print("Formatting %s...\n", root); @@ -401,8 +422,7 @@ wipe_data(int confirm) { } static void -prompt_and_wait() -{ +prompt_and_wait() { char** headers = prepend_title(MENU_HEADERS); ui_print(EXPAND(RECOVERY_VERSION)"\n"); @@ -445,12 +465,7 @@ prompt_and_wait() } else if (!ui_text_visible()) { return; // reboot if logs aren't visible } else { - if (firmware_update_pending()) { - ui_print("\nReboot via menu to complete\n" - "installation.\n"); - } else { - ui_print("\nInstall from sdcard complete.\n"); - } + ui_print("\nInstall from sdcard complete.\n"); } break; case ITEM_INSTALL_ZIP: @@ -470,14 +485,12 @@ prompt_and_wait() } static void -print_property(const char *key, const char *name, void *cookie) -{ +print_property(const char *key, const char *name, void *cookie) { fprintf(stderr, "%s=%s\n", key, name); } int -main(int argc, char **argv) -{ +main(int argc, char **argv) { if (strstr(argv[0], "recovery") == NULL) { if (strstr(argv[0], "flash_image") != NULL) @@ -528,6 +541,8 @@ main(int argc, char **argv) } } + device_recovery_start(); + fprintf(stderr, "Command:"); for (arg = 0; arg < argc; arg++) { fprintf(stderr, " \"%s\"", argv[arg]); @@ -584,9 +599,6 @@ main(int argc, char **argv) if (status != INSTALL_SUCCESS && !is_user_initiated_recovery) ui_set_background(BACKGROUND_ICON_ERROR); if (status != INSTALL_SUCCESS || ui_text_visible()) prompt_and_wait(); - // If there is a radio image pending, reboot now to install it. - maybe_install_firmware_update(send_intent); - // Otherwise, get ready to boot the main system... finish_recovery(send_intent); ui_print("Rebooting...\n"); diff --git a/recovery_ui.h b/recovery_ui.h index daf01eb..662c6db 100644 --- a/recovery_ui.h +++ b/recovery_ui.h @@ -17,6 +17,9 @@ #ifndef _RECOVERY_UI_H #define _RECOVERY_UI_H +// Called when recovery starts up. Returns 0. +extern int device_recovery_start(); + // Called in the input thread when a new key (key_code) is pressed. // *key_pressed is an array of KEY_MAX+1 bytes indicating which other // keys are already pressed. Return true if the text display should diff --git a/res/images/icon_firmware_error.png b/res/images/icon_firmware_error.png deleted file mode 100644 index 0c32c9e..0000000 Binary files a/res/images/icon_firmware_error.png and /dev/null differ diff --git a/res/images/icon_firmware_install.png b/res/images/icon_firmware_install.png deleted file mode 100755 index 2da9e5f..0000000 Binary files a/res/images/icon_firmware_install.png and /dev/null differ diff --git a/res/images/indeterminate1.png b/res/images/indeterminate1.png index 264bf27..90cb9fb 100644 Binary files a/res/images/indeterminate1.png and b/res/images/indeterminate1.png differ diff --git a/res/images/indeterminate2.png b/res/images/indeterminate2.png index c30c049..f7fb289 100644 Binary files a/res/images/indeterminate2.png and b/res/images/indeterminate2.png differ diff --git a/res/images/indeterminate3.png b/res/images/indeterminate3.png index 891a000..ba10dfa 100644 Binary files a/res/images/indeterminate3.png and b/res/images/indeterminate3.png differ diff --git a/res/images/indeterminate4.png b/res/images/indeterminate4.png index 7a64151..ad5d9a5 100644 Binary files a/res/images/indeterminate4.png and b/res/images/indeterminate4.png differ diff --git a/res/images/indeterminate5.png b/res/images/indeterminate5.png index cd6ab20..8c19c8d 100644 Binary files a/res/images/indeterminate5.png and b/res/images/indeterminate5.png differ diff --git a/res/images/indeterminate6.png b/res/images/indeterminate6.png index ddd9e73..c0c6638 100644 Binary files a/res/images/indeterminate6.png and b/res/images/indeterminate6.png differ diff --git a/res/images/progress_bar_empty.png b/res/images/progress_bar_empty.png deleted file mode 100644 index 9013f04..0000000 Binary files a/res/images/progress_bar_empty.png and /dev/null differ diff --git a/res/images/progress_bar_empty_left_round.png b/res/images/progress_bar_empty_left_round.png deleted file mode 100644 index dae7d5d..0000000 Binary files a/res/images/progress_bar_empty_left_round.png and /dev/null differ diff --git a/res/images/progress_bar_empty_right_round.png b/res/images/progress_bar_empty_right_round.png deleted file mode 100644 index 5427088..0000000 Binary files a/res/images/progress_bar_empty_right_round.png and /dev/null differ diff --git a/res/images/progress_bar_fill.png b/res/images/progress_bar_fill.png deleted file mode 100644 index 37c04b4..0000000 Binary files a/res/images/progress_bar_fill.png and /dev/null differ diff --git a/res/images/progress_bar_left_round.png b/res/images/progress_bar_left_round.png deleted file mode 100644 index e72af47..0000000 Binary files a/res/images/progress_bar_left_round.png and /dev/null differ diff --git a/res/images/progress_bar_right_round.png b/res/images/progress_bar_right_round.png deleted file mode 100644 index d04c980..0000000 Binary files a/res/images/progress_bar_right_round.png and /dev/null differ diff --git a/res/images/progress_empty.png b/res/images/progress_empty.png new file mode 100644 index 0000000..4cb4998 Binary files /dev/null and b/res/images/progress_empty.png differ diff --git a/res/images/progress_fill.png b/res/images/progress_fill.png new file mode 100644 index 0000000..eb71754 Binary files /dev/null and b/res/images/progress_fill.png differ diff --git a/testdata/alter-footer.zip b/testdata/alter-footer.zip new file mode 100644 index 0000000..f497ec0 Binary files /dev/null and b/testdata/alter-footer.zip differ diff --git a/testdata/alter-metadata.zip b/testdata/alter-metadata.zip new file mode 100644 index 0000000..1c71fbc Binary files /dev/null and b/testdata/alter-metadata.zip differ diff --git a/testdata/fake-eocd.zip b/testdata/fake-eocd.zip new file mode 100644 index 0000000..15dc0a9 Binary files /dev/null and b/testdata/fake-eocd.zip differ diff --git a/testdata/jarsigned.zip b/testdata/jarsigned.zip new file mode 100644 index 0000000..8b1ef8b Binary files /dev/null and b/testdata/jarsigned.zip differ diff --git a/testdata/otasigned.zip b/testdata/otasigned.zip new file mode 100644 index 0000000..a6bc53e Binary files /dev/null and b/testdata/otasigned.zip differ diff --git a/testdata/random.zip b/testdata/random.zip new file mode 100644 index 0000000..18c0b3b Binary files /dev/null and b/testdata/random.zip differ diff --git a/testdata/unsigned.zip b/testdata/unsigned.zip new file mode 100644 index 0000000..24e3ead Binary files /dev/null and b/testdata/unsigned.zip differ diff --git a/ui.c b/ui.c index 651eea1..e08c4a4 100644 --- a/ui.c +++ b/ui.c @@ -41,33 +41,23 @@ #define PROGRESSBAR_INDETERMINATE_STATES 6 #define PROGRESSBAR_INDETERMINATE_FPS 15 -enum { LEFT_SIDE, CENTER_TILE, RIGHT_SIDE, NUM_SIDES }; - static pthread_mutex_t gUpdateMutex = PTHREAD_MUTEX_INITIALIZER; static gr_surface gBackgroundIcon[NUM_BACKGROUND_ICONS]; static gr_surface gProgressBarIndeterminate[PROGRESSBAR_INDETERMINATE_STATES]; -static gr_surface gProgressBarEmpty[NUM_SIDES]; -static gr_surface gProgressBarFill[NUM_SIDES]; +static gr_surface gProgressBarEmpty; +static gr_surface gProgressBarFill; static const struct { gr_surface* surface; const char *name; } BITMAPS[] = { { &gBackgroundIcon[BACKGROUND_ICON_INSTALLING], "icon_installing" }, { &gBackgroundIcon[BACKGROUND_ICON_ERROR], "icon_error" }, - { &gBackgroundIcon[BACKGROUND_ICON_FIRMWARE_INSTALLING], - "icon_firmware_install" }, - { &gBackgroundIcon[BACKGROUND_ICON_FIRMWARE_ERROR], - "icon_firmware_error" }, { &gProgressBarIndeterminate[0], "indeterminate1" }, { &gProgressBarIndeterminate[1], "indeterminate2" }, { &gProgressBarIndeterminate[2], "indeterminate3" }, { &gProgressBarIndeterminate[3], "indeterminate4" }, { &gProgressBarIndeterminate[4], "indeterminate5" }, { &gProgressBarIndeterminate[5], "indeterminate6" }, - { &gProgressBarEmpty[LEFT_SIDE], "progress_bar_empty_left_round" }, - { &gProgressBarEmpty[CENTER_TILE], "progress_bar_empty" }, - { &gProgressBarEmpty[RIGHT_SIDE], "progress_bar_empty_right_round" }, - { &gProgressBarFill[LEFT_SIDE], "progress_bar_left_round" }, - { &gProgressBarFill[CENTER_TILE], "progress_bar_fill" }, - { &gProgressBarFill[RIGHT_SIDE], "progress_bar_right_round" }, + { &gProgressBarEmpty, "progress_empty" }, + { &gProgressBarFill, "progress_fill" }, { NULL, NULL }, }; @@ -127,8 +117,8 @@ static void draw_progress_locked() if (gProgressBarType == PROGRESSBAR_TYPE_NONE) return; int iconHeight = gr_get_height(gBackgroundIcon[BACKGROUND_ICON_INSTALLING]); - int width = gr_get_width(gProgressBarIndeterminate[0]); - int height = gr_get_height(gProgressBarIndeterminate[0]); + int width = gr_get_width(gProgressBarEmpty); + int height = gr_get_height(gProgressBarEmpty); int dx = (gr_fb_width() - width)/2; int dy = (3*gr_fb_height() + iconHeight - 2*height)/4; @@ -141,18 +131,12 @@ static void draw_progress_locked() float progress = gProgressScopeStart + gProgress * gProgressScopeSize; int pos = (int) (progress * width); - gr_surface s = (pos ? gProgressBarFill : gProgressBarEmpty)[LEFT_SIDE]; - gr_blit(s, 0, 0, gr_get_width(s), gr_get_height(s), dx, dy); - - int x = gr_get_width(s); - while (x + (int) gr_get_width(gProgressBarEmpty[RIGHT_SIDE]) < width) { - s = (pos > x ? gProgressBarFill : gProgressBarEmpty)[CENTER_TILE]; - gr_blit(s, 0, 0, gr_get_width(s), gr_get_height(s), dx + x, dy); - x += gr_get_width(s); + if (pos > 0) { + gr_blit(gProgressBarFill, 0, 0, pos, height, dx, dy); + } + if (pos < width-1) { + gr_blit(gProgressBarEmpty, pos, 0, width-pos, height, dx+pos, dy); } - - s = (pos > x ? gProgressBarFill : gProgressBarEmpty)[RIGHT_SIDE]; - gr_blit(s, 0, 0, gr_get_width(s), gr_get_height(s), dx + x, dy); } if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE) { @@ -375,23 +359,6 @@ void ui_init(void) pthread_create(&t, NULL, input_thread, NULL); } -char *ui_copy_image(int icon, int *width, int *height, int *bpp) { - pthread_mutex_lock(&gUpdateMutex); - draw_background_locked(gBackgroundIcon[icon]); - *width = gr_fb_width(); - *height = gr_fb_height(); - *bpp = sizeof(gr_pixel) * 8; - int size = *width * *height * sizeof(gr_pixel); - char *ret = malloc(size); - if (ret == NULL) { - LOGE("Can't allocate %d bytes for image\n", size); - } else { - memcpy(ret, gr_fb_data(), size); - } - pthread_mutex_unlock(&gUpdateMutex); - return ret; -} - void ui_set_background(int icon) { pthread_mutex_lock(&gUpdateMutex); diff --git a/updater/install.c b/updater/install.c index aa80d75..e869134 100644 --- a/updater/install.c +++ b/updater/install.c @@ -29,17 +29,18 @@ #include "cutils/misc.h" #include "cutils/properties.h" #include "edify/expr.h" +#include "mincrypt/sha.h" #include "minzip/DirUtil.h" #include "mtdutils/mounts.h" #include "mtdutils/mtdutils.h" #include "updater.h" - +#include "applypatch/applypatch.h" // mount(type, location, mount_point) // // what: type="MTD" location="" to mount a yaffs2 filesystem // type="vfat" location="/dev/block/" to mount a device -char* MountFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) { char* result = NULL; if (argc != 3) { return ErrorAbort(state, "%s() expects 3 args, got %d", name, argc); @@ -98,12 +99,12 @@ done: free(type); free(location); if (result != mount_point) free(mount_point); - return result; + return StringValue(result); } // is_mounted(mount_point) -char* IsMountedFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* IsMountedFn(const char* name, State* state, int argc, Expr* argv[]) { char* result = NULL; if (argc != 1) { return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); @@ -127,11 +128,11 @@ char* IsMountedFn(const char* name, State* state, int argc, Expr* argv[]) { done: if (result != mount_point) free(mount_point); - return result; + return StringValue(result); } -char* UnmountFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* UnmountFn(const char* name, State* state, int argc, Expr* argv[]) { char* result = NULL; if (argc != 1) { return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); @@ -157,14 +158,14 @@ char* UnmountFn(const char* name, State* state, int argc, Expr* argv[]) { done: if (result != mount_point) free(mount_point); - return result; + return StringValue(result); } // format(type, location) // // type="MTD" location=partition -char* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* FormatFn(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); @@ -218,11 +219,11 @@ char* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { done: free(type); if (result != location) free(location); - return result; + return StringValue(result); } -char* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) { char** paths = malloc(argc * sizeof(char*)); int i; for (i = 0; i < argc; ++i) { @@ -249,11 +250,11 @@ char* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) { char buffer[10]; sprintf(buffer, "%d", success); - return strdup(buffer); + return StringValue(strdup(buffer)); } -char* ShowProgressFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* ShowProgressFn(const char* name, State* state, int argc, Expr* argv[]) { if (argc != 2) { return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); } @@ -270,10 +271,10 @@ char* ShowProgressFn(const char* name, State* state, int argc, Expr* argv[]) { fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec); free(sec_str); - return frac_str; + return StringValue(frac_str); } -char* SetProgressFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* SetProgressFn(const char* name, State* state, int argc, Expr* argv[]) { if (argc != 1) { return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); } @@ -287,11 +288,11 @@ char* SetProgressFn(const char* name, State* state, int argc, Expr* argv[]) { UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); fprintf(ui->cmd_pipe, "set_progress %f\n", frac); - return frac_str; + return StringValue(frac_str); } // package_extract_dir(package_path, destination_path) -char* PackageExtractDirFn(const char* name, State* state, +Value* PackageExtractDirFn(const char* name, State* state, int argc, Expr* argv[]) { if (argc != 2) { return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); @@ -310,48 +311,94 @@ char* PackageExtractDirFn(const char* name, State* state, NULL, NULL); free(zip_path); free(dest_path); - return strdup(success ? "t" : ""); + return StringValue(strdup(success ? "t" : "")); } // package_extract_file(package_path, destination_path) -char* PackageExtractFileFn(const char* name, State* state, +// or +// package_extract_file(package_path) +// to return the entire contents of the file as the result of this +// function (the char* returned is actually a FileContents*). +Value* PackageExtractFileFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 2) { - return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + if (argc != 1 && argc != 2) { + return ErrorAbort(state, "%s() expects 1 or 2 args, got %d", + name, argc); } - char* zip_path; - char* dest_path; - if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL; - bool success = false; + if (argc == 2) { + // The two-argument version extracts to a file. - ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; - const ZipEntry* entry = mzFindZipEntry(za, zip_path); - if (entry == NULL) { - fprintf(stderr, "%s: no %s in package\n", name, zip_path); - goto done; + 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) { + fprintf(stderr, "%s: no %s in package\n", name, zip_path); + goto done2; + } + + FILE* f = fopen(dest_path, "wb"); + if (f == NULL) { + fprintf(stderr, "%s: can't open %s for write: %s\n", + name, dest_path, strerror(errno)); + goto done2; + } + success = mzExtractZipEntryToFile(za, entry, fileno(f)); + fclose(f); + + done2: + free(zip_path); + free(dest_path); + return StringValue(strdup(success ? "t" : "")); + } else { + // The one-argument version returns the contents of the file + // as the result. + + char* zip_path; + Value* v = malloc(sizeof(Value)); + v->type = VAL_BLOB; + v->size = -1; + v->data = NULL; + + if (ReadArgs(state, argv, 1, &zip_path) < 0) return NULL; + + ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; + const ZipEntry* entry = mzFindZipEntry(za, zip_path); + if (entry == NULL) { + fprintf(stderr, "%s: no %s in package\n", name, zip_path); + goto done1; + } + + v->size = mzGetZipEntryUncompLen(entry); + v->data = malloc(v->size); + if (v->data == NULL) { + fprintf(stderr, "%s: failed to allocate %ld bytes for %s\n", + name, (long)v->size, zip_path); + goto done1; + } + + success = mzExtractZipEntryToBuffer(za, entry, + (unsigned char *)v->data); + + done1: + free(zip_path); + if (!success) { + free(v->data); + v->data = NULL; + v->size = -1; + } + return v; } - - FILE* f = fopen(dest_path, "wb"); - if (f == NULL) { - fprintf(stderr, "%s: can't open %s for write: %s\n", - name, dest_path, strerror(errno)); - goto done; - } - success = mzExtractZipEntryToFile(za, entry, fileno(f)); - fclose(f); - - done: - free(zip_path); - free(dest_path); - return strdup(success ? "t" : ""); } // symlink target src1 src2 ... // unlinks any previously existing src1, src2, etc before creating symlinks. -char* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) { if (argc == 0) { return ErrorAbort(state, "%s() expects 1+ args, got %d", name, argc); } @@ -380,11 +427,11 @@ char* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) { free(srcs[i]); } free(srcs); - return strdup(""); + return StringValue(strdup("")); } -char* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) { char* result = NULL; bool recursive = (strcmp(name, "set_perm_recursive") == 0); @@ -454,11 +501,11 @@ done: } free(args); - return result; + return StringValue(result); } -char* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) { if (argc != 1) { return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); } @@ -470,7 +517,7 @@ char* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) { property_get(key, value, ""); free(key); - return strdup(value); + return StringValue(strdup(value)); } @@ -479,7 +526,7 @@ char* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) { // 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). -char* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) { char* result = NULL; char* buffer = NULL; char* filename; @@ -569,7 +616,7 @@ char* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) { free(filename); free(key); free(buffer); - return result; + return StringValue(result); } @@ -582,7 +629,7 @@ static bool write_raw_image_cb(const unsigned char* data, } // write_raw_image(file, partition) -char* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) { char* result = NULL; char* partition; @@ -655,98 +702,130 @@ char* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) { done: if (result != partition) free(partition); free(filename); - return result; + return StringValue(result); } -// write_firmware_image(file, partition) -// -// partition is "radio" or "hboot" -// file is not used until after updater exits -// -// TODO: this should live in some HTC-specific library -char* WriteFirmwareImageFn(const char* name, State* state, - int argc, Expr* argv[]) { - char* result = NULL; - - char* partition; - char* filename; - if (ReadArgs(state, argv, 2, &filename, &partition) < 0) { +// apply_patch_space(bytes) +Value* ApplyPatchSpaceFn(const char* name, State* state, + int argc, Expr* argv[]) { + char* bytes_str; + if (ReadArgs(state, argv, 1, &bytes_str) < 0) { return NULL; } - if (strlen(partition) == 0) { - ErrorAbort(state, "partition argument to %s can't be empty", name); - goto done; - } - if (strlen(filename) == 0) { - ErrorAbort(state, "file argument to %s can't be empty", name); - goto done; + char* endptr; + size_t bytes = strtol(bytes_str, &endptr, 10); + if (bytes == 0 && endptr == bytes_str) { + ErrorAbort(state, "%s(): can't parse \"%s\" as byte count\n\n", + name, bytes_str); + free(bytes_str); + return NULL; } - FILE* cmd = ((UpdaterInfo*)(state->cookie))->cmd_pipe; - fprintf(cmd, "firmware %s %s\n", partition, filename); - - printf("will write %s firmware from %s\n", partition, filename); - result = partition; - -done: - if (result != partition) free(partition); - free(filename); - return result; + return StringValue(strdup(CacheSizeCheck(bytes) ? "" : "t")); } -extern int applypatch(int argc, char** argv); - -// apply_patch(srcfile, tgtfile, tgtsha1, tgtsize, sha1:patch, ...) -// apply_patch_check(file, sha1, ...) -// apply_patch_space(bytes) -char* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) { - printf("in applypatchfn (%s)\n", name); - - char* prepend = NULL; - if (strstr(name, "check") != NULL) { - prepend = "-c"; - } else if (strstr(name, "space") != NULL) { - prepend = "-s"; +// 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 " + "even number, got %d", + name, argc); } - char** args = ReadVarArgs(state, argc, argv); - if (args == NULL) return NULL; - - // insert the "program name" argv[0] and a copy of the "prepend" - // string (if any) at the start of the args. - - int extra = 1 + (prepend != NULL ? 1 : 0); - char** temp = malloc((argc+extra) * sizeof(char*)); - memcpy(temp+extra, args, argc * sizeof(char*)); - temp[0] = strdup("updater"); - if (prepend) { - temp[1] = strdup(prepend); + char* source_filename; + char* target_filename; + char* target_sha1; + char* target_size_str; + if (ReadArgs(state, argv, 4, &source_filename, &target_filename, + &target_sha1, &target_size_str) < 0) { + return NULL; } - free(args); - args = temp; - argc += extra; - printf("calling applypatch\n"); - fflush(stdout); - int result = applypatch(argc, args); - printf("applypatch returned %d\n", result); + char* endptr; + size_t target_size = strtol(target_size_str, &endptr, 10); + if (target_size == 0 && endptr == target_size_str) { + ErrorAbort(state, "%s(): can't parse \"%s\" as byte count", + name, target_size_str); + free(source_filename); + free(target_filename); + free(target_sha1); + free(target_size_str); + return NULL; + } + + int patchcount = (argc-4) / 2; + Value** patches = ReadValueVarArgs(state, argc-4, argv+4); int i; - for (i = 0; i < argc; ++i) { - free(args[i]); + for (i = 0; i < patchcount; ++i) { + if (patches[i*2]->type != VAL_STRING) { + ErrorAbort(state, "%s(): sha-1 #%d is not string", name, i); + break; + } + if (patches[i*2+1]->type != VAL_BLOB) { + ErrorAbort(state, "%s(): patch #%d is not blob", name, i); + break; + } + } + if (i != patchcount) { + for (i = 0; i < patchcount*2; ++i) { + FreeValue(patches[i]); + } + free(patches); + return NULL; } - free(args); - switch (result) { - case 0: return strdup("t"); - case 1: return strdup(""); - default: return ErrorAbort(state, "applypatch couldn't parse args"); + char** patch_sha_str = malloc(patchcount * sizeof(char*)); + for (i = 0; i < patchcount; ++i) { + patch_sha_str[i] = patches[i*2]->data; + patches[i*2]->data = NULL; + FreeValue(patches[i*2]); + patches[i] = patches[i*2+1]; } + + int result = applypatch(source_filename, target_filename, + target_sha1, target_size, + patchcount, patch_sha_str, patches); + + for (i = 0; i < patchcount; ++i) { + FreeValue(patches[i]); + } + free(patch_sha_str); + free(patches); + + return StringValue(strdup(result == 0 ? "t" : "")); } -char* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) { +// apply_patch_check(file, [sha1_1, ...]) +Value* ApplyPatchCheckFn(const char* name, State* state, + int argc, Expr* argv[]) { + if (argc < 1) { + return ErrorAbort(state, "%s(): expected at least 1 arg, got %d", + name, argc); + } + + char* filename; + if (ReadArgs(state, argv, 1, &filename) < 0) { + return NULL; + } + + int patchcount = argc-1; + char** sha1s = ReadVarArgs(state, argc-1, argv+1); + + int result = applypatch_check(filename, patchcount, sha1s); + + int i; + for (i = 0; i < patchcount; ++i) { + free(sha1s[i]); + } + free(sha1s); + + return StringValue(strdup(result == 0 ? "t" : "")); +} + +Value* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) { char** args = ReadVarArgs(state, argc, argv); if (args == NULL) { return NULL; @@ -775,10 +854,10 @@ char* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) { } fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe, "ui_print\n"); - return buffer; + return StringValue(buffer); } -char* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) { if (argc < 1) { return ErrorAbort(state, "%s() expects at least 1 arg", name); } @@ -821,9 +900,108 @@ char* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) { char buffer[20]; sprintf(buffer, "%d", status); - return strdup(buffer); + 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). +// +// sha1_check(data, sha1_hex, [sha1_hex, ...]) +// returns the sha1 of the file if it matches any of the hex +// strings passed, or "" if it does not equal any of them. +// +Value* Sha1CheckFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc < 1) { + return ErrorAbort(state, "%s() expects at least 1 arg", name); + } + + Value** args = ReadValueVarArgs(state, argc, argv); + if (args == NULL) { + return NULL; + } + + if (args[0]->size < 0) { + fprintf(stderr, "%s(): no file contents received", name); + return StringValue(strdup("")); + } + uint8_t digest[SHA_DIGEST_SIZE]; + SHA(args[0]->data, args[0]->size, digest); + FreeValue(args[0]); + + if (argc == 1) { + return StringValue(PrintSha1(digest)); + } + + int i; + uint8_t* arg_digest = malloc(SHA_DIGEST_SIZE); + for (i = 1; i < argc; ++i) { + if (args[i]->type != VAL_STRING) { + fprintf(stderr, "%s(): arg %d is not a string; skipping", + name, i); + } else if (ParseSha1(args[i]->data, arg_digest) != 0) { + // Warn about bad args and skip them. + fprintf(stderr, "%s(): error parsing \"%s\" as sha-1; skipping", + name, args[i]->data); + } else if (memcmp(digest, arg_digest, SHA_DIGEST_SIZE) == 0) { + break; + } + FreeValue(args[i]); + } + if (i >= argc) { + // Didn't match any of the hex strings; return false. + return StringValue(strdup("")); + } + // Found a match; free all the remaining arguments and return the + // matched one. + int j; + for (j = i+1; j < argc; ++j) { + FreeValue(args[j]); + } + return args[i]; +} + +// Read a local file and return its contents (the char* returned +// is actually a FileContents*). +Value* ReadFileFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); + } + char* filename; + if (ReadArgs(state, argv, 1, &filename) < 0) return NULL; + + Value* v = malloc(sizeof(Value)); + v->type = VAL_BLOB; + + FileContents fc; + if (LoadFileContents(filename, &fc) != 0) { + ErrorAbort(state, "%s() loading \"%s\" failed: %s", + name, filename, strerror(errno)); + free(filename); + free(v); + free(fc.data); + return NULL; + } + + v->size = fc.size; + v->data = (char*)fc.data; + + free(filename); + return v; +} void RegisterInstallFunctions() { RegisterFunction("mount", MountFn); @@ -843,11 +1021,13 @@ void RegisterInstallFunctions() { RegisterFunction("getprop", GetPropFn); RegisterFunction("file_getprop", FileGetPropFn); RegisterFunction("write_raw_image", WriteRawImageFn); - RegisterFunction("write_firmware_image", WriteFirmwareImageFn); RegisterFunction("apply_patch", ApplyPatchFn); - RegisterFunction("apply_patch_check", ApplyPatchFn); - RegisterFunction("apply_patch_space", ApplyPatchFn); + RegisterFunction("apply_patch_check", ApplyPatchCheckFn); + RegisterFunction("apply_patch_space", ApplyPatchSpaceFn); + + RegisterFunction("read_file", ReadFileFn); + RegisterFunction("sha1_check", Sha1CheckFn); RegisterFunction("ui_print", UIPrintFn); diff --git a/updater/updater.c b/updater/updater.c index 1aa277c..6537a94 100644 --- a/updater/updater.c +++ b/updater/updater.c @@ -33,15 +33,23 @@ #define SCRIPT_NAME "META-INF/com/google/android/updater-script" int main(int argc, char** argv) { + // Various things log information to stdout or stderr more or less + // at random. The log file makes more sense if buffering is + // turned off so things appear in the right order. + setbuf(stdout, NULL); + setbuf(stderr, NULL); + if (argc != 4) { fprintf(stderr, "unexpected number of arguments (%d)\n", argc); return 1; } char* version = argv[1]; - if ((version[0] != '1' && version[0] != '2') || version[1] != '\0') { - // We support version "1" or "2". - fprintf(stderr, "wrong updater binary API; expected 1 or 2, got %s\n", + if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || + version[1] != '\0') { + // We support version 1, 2, or 3. + fprintf(stderr, "wrong updater binary API; expected 1, 2, or 3; " + "got %s\n", argv[1]); return 2; } @@ -100,6 +108,7 @@ int main(int argc, char** argv) { UpdaterInfo updater_info; updater_info.cmd_pipe = cmd_pipe; updater_info.package_zip = &za; + updater_info.version = atoi(version); State state; state.cookie = &updater_info; diff --git a/updater/updater.h b/updater/updater.h index 22fbfd2..bd60dc1 100644 --- a/updater/updater.h +++ b/updater/updater.h @@ -23,6 +23,7 @@ typedef struct { FILE* cmd_pipe; ZipArchive* package_zip; + int version; } UpdaterInfo; #endif diff --git a/verifier.c b/verifier.c index 164fb4a..9d39fd1 100644 --- a/verifier.c +++ b/verifier.c @@ -42,7 +42,7 @@ int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKey // An archive with a whole-file signature will end in six bytes: // - // $ff $ff (2-byte comment size) (2-byte signature start) + // (2-byte signature start) $ff $ff (2-byte comment size) // // (As far as the ZIP format is concerned, these are part of the // archive comment.) We start by reading this footer, this tells @@ -169,7 +169,7 @@ int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKey const uint8_t* sha1 = SHA_final(&ctx); for (i = 0; i < numKeys; ++i) { - // The 6 bytes is the "$ff $ff (signature_start) (comment_size)" that + // 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, eocd + eocd_size - 6 - RSANUMBYTES, RSANUMBYTES, sha1)) { diff --git a/verifier_test.c b/verifier_test.c new file mode 100644 index 0000000..5b6c1f4 --- /dev/null +++ b/verifier_test.c @@ -0,0 +1,91 @@ +/* + * 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 +#include +#include + +#include "verifier.h" + +// This is build/target/product/security/testkey.x509.pem after being +// dumped out by dumpkey.jar. +RSAPublicKey test_key = + { 64, 0xc926ad21, + { 1795090719, 2141396315, 950055447, -1713398866, + -26044131, 1920809988, 546586521, -795969498, + 1776797858, -554906482, 1805317999, 1429410244, + 129622599, 1422441418, 1783893377, 1222374759, + -1731647369, 323993566, 28517732, 609753416, + 1826472888, 215237850, -33324596, -245884705, + -1066504894, 774857746, 154822455, -1797768399, + -1536767878, -1275951968, -1500189652, 87251430, + -1760039318, 120774784, 571297800, -599067824, + -1815042109, -483341846, -893134306, -1900097649, + -1027721089, 950095497, 555058928, 414729973, + 1136544882, -1250377212, 465547824, -236820568, + -1563171242, 1689838846, -404210357, 1048029507, + 895090649, 247140249, 178744550, -747082073, + -1129788053, 109881576, -350362881, 1044303212, + -522594267, -1309816990, -557446364, -695002876}, + { -857949815, -510492167, -1494742324, -1208744608, + 251333580, 2131931323, 512774938, 325948880, + -1637480859, 2102694287, -474399070, 792812816, + 1026422502, 2053275343, -1494078096, -1181380486, + 165549746, -21447327, -229719404, 1902789247, + 772932719, -353118870, -642223187, 216871947, + -1130566647, 1942378755, -298201445, 1055777370, + 964047799, 629391717, -2062222979, -384408304, + 191868569, -1536083459, -612150544, -1297252564, + -1592438046, -724266841, -518093464, -370899750, + -739277751, -1536141862, 1323144535, 61311905, + 1997411085, 376844204, 213777604, -217643712, + 9135381, 1625809335, -1490225159, -1342673351, + 1117190829, -57654514, 1825108855, -1281819325, + 1111251351, -1726129724, 1684324211, -1773988491, + 367251975, 810756730, -1941182952, 1175080310 } + }; + +void ui_print(const char* fmt, ...) { + char buf[256]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, 256, fmt, ap); + va_end(ap); + + fputs(buf, stderr); +} + +void ui_set_progress(float fraction) { +} + +int main(int argc, char **argv) { + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 2; + } + + int result = verify_file(argv[1], &test_key, 1); + if (result == VERIFY_SUCCESS) { + printf("SUCCESS\n"); + return 0; + } else if (result == VERIFY_FAILURE) { + printf("FAILURE\n"); + return 1; + } else { + printf("bad return value\n"); + return 3; + } +} diff --git a/verifier_test.sh b/verifier_test.sh new file mode 100755 index 0000000..6350e80 --- /dev/null +++ b/verifier_test.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# +# A test suite for applypatch. Run in a client where you have done +# envsetup, choosecombo, etc. +# +# DO NOT RUN THIS ON A DEVICE YOU CARE ABOUT. It will mess up your +# system partition. +# +# +# TODO: find some way to get this run regularly along with the rest of +# the tests. + +EMULATOR_PORT=5580 +DATA_DIR=$ANDROID_BUILD_TOP/bootable/recovery/testdata + +WORK_DIR=/data/local/tmp + +# set to 0 to use a device instead +USE_EMULATOR=0 + +# ------------------------ + +if [ "$USE_EMULATOR" == 1 ]; then + emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT & + pid_emulator=$! + ADB="adb -s emulator-$EMULATOR_PORT " +else + ADB="adb -d " +fi + +echo "waiting to connect to device" +$ADB wait-for-device + +# run a command on the device; exit with the exit status of the device +# command. +run_command() { + $ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}' +} + +testname() { + echo + echo "::: testing $1 :::" + testname="$1" +} + +fail() { + echo + echo FAIL: $testname + echo + [ "$open_pid" == "" ] || kill $open_pid + [ "$pid_emulator" == "" ] || kill $pid_emulator + exit 1 +} + + +cleanup() { + # not necessary if we're about to kill the emulator, but nice for + # running on real devices or already-running emulators. + run_command rm $WORK_DIR/verifier_test + run_command rm $WORK_DIR/package.zip + + [ "$pid_emulator" == "" ] || kill $pid_emulator +} + +$ADB push $ANDROID_PRODUCT_OUT/system/bin/verifier_test \ + $WORK_DIR/verifier_test + +expect_succeed() { + testname "$1 (should succeed)" + $ADB push $DATA_DIR/$1 $WORK_DIR/package.zip + run_command $WORK_DIR/verifier_test $WORK_DIR/package.zip || fail +} + +expect_fail() { + testname "$1 (should fail)" + $ADB push $DATA_DIR/$1 $WORK_DIR/package.zip + run_command $WORK_DIR/verifier_test $WORK_DIR/package.zip && fail +} + +expect_fail unsigned.zip +expect_fail jarsigned.zip +expect_succeed otasigned.zip +expect_fail random.zip +expect_fail fake-eocd.zip +expect_fail alter-metadata.zip +expect_fail alter-footer.zip + +# --------------- cleanup ---------------------- + +cleanup + +echo +echo PASS +echo