From 7101b2e2854985727b7ef65e5b5057e0ecf2d034 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Wed, 3 Jun 2015 10:49:29 -0700 Subject: recovery: Switch to clang And a few trival fixes to suppress warnings. Change-Id: Id28e3581aaca4bda59826afa80c0c1cdfb0442fc (cherry picked from commit 80e46e08de5f65702fa7f7cd3ef83f905d919bbc) --- Android.mk | 6 ++++-- adb_install.cpp | 2 +- applypatch/Android.mk | 5 +++++ edify/Android.mk | 2 ++ minadbd/Android.mk | 1 + minui/Android.mk | 1 + minzip/Android.mk | 2 ++ minzip/Zip.c | 2 -- mtdutils/Android.mk | 2 ++ recovery.cpp | 21 +++++++++++++++------ tests/Android.mk | 1 + uncrypt/Android.mk | 2 ++ updater/Android.mk | 2 ++ 13 files changed, 38 insertions(+), 11 deletions(-) diff --git a/Android.mk b/Android.mk index b31f73017..ac1ef83d8 100644 --- a/Android.mk +++ b/Android.mk @@ -14,11 +14,10 @@ LOCAL_PATH := $(call my-dir) - include $(CLEAR_VARS) LOCAL_SRC_FILES := fuse_sideload.c - +LOCAL_CLANG := true LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE @@ -55,6 +54,7 @@ RECOVERY_API_VERSION := 3 RECOVERY_FSTAB_VERSION := 2 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) LOCAL_CFLAGS += -Wno-unused-parameter +LOCAL_CLANG := true LOCAL_C_INCLUDES += \ system/vold \ @@ -99,6 +99,7 @@ include $(BUILD_EXECUTABLE) # All the APIs for testing include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_MODULE := libverifier LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := \ @@ -106,6 +107,7 @@ LOCAL_SRC_FILES := \ include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_MODULE := verifier_test LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_MODULE_TAGS := tests diff --git a/adb_install.cpp b/adb_install.cpp index e3b94ea59..4cfcb2ab8 100644 --- a/adb_install.cpp +++ b/adb_install.cpp @@ -91,7 +91,7 @@ apply_from_adb(RecoveryUI* ui_, bool* wipe_cache, const char* install_file) { // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the host // connects and starts serving a package. Poll for its // appearance. (Note that inotify doesn't work with FUSE.) - int result; + int result = INSTALL_ERROR; int status; bool waited = false; struct stat st; diff --git a/applypatch/Android.mk b/applypatch/Android.mk index 4984093dd..4936db2a2 100644 --- a/applypatch/Android.mk +++ b/applypatch/Android.mk @@ -13,8 +13,10 @@ # limitations under the License. LOCAL_PATH := $(call my-dir) + include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_SRC_FILES := applypatch.c bspatch.c freecache.c imgpatch.c utils.c LOCAL_MODULE := libapplypatch LOCAL_MODULE_TAGS := eng @@ -25,6 +27,7 @@ include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_SRC_FILES := main.c LOCAL_MODULE := applypatch LOCAL_C_INCLUDES += bootable/recovery @@ -35,6 +38,7 @@ include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_SRC_FILES := main.c LOCAL_MODULE := applypatch_static LOCAL_FORCE_STATIC_EXECUTABLE := true @@ -47,6 +51,7 @@ include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_SRC_FILES := imgdiff.c utils.c bsdiff.c LOCAL_MODULE := imgdiff LOCAL_FORCE_STATIC_EXECUTABLE := true diff --git a/edify/Android.mk b/edify/Android.mk index 03c04e432..c36645045 100644 --- a/edify/Android.mk +++ b/edify/Android.mk @@ -25,6 +25,7 @@ LOCAL_CFLAGS := $(edify_cflags) -g -O0 LOCAL_MODULE := edify LOCAL_YACCFLAGS := -v LOCAL_CFLAGS += -Wno-unused-parameter +LOCAL_CLANG := true include $(BUILD_HOST_EXECUTABLE) @@ -38,5 +39,6 @@ LOCAL_SRC_FILES := $(edify_src_files) LOCAL_CFLAGS := $(edify_cflags) LOCAL_CFLAGS += -Wno-unused-parameter LOCAL_MODULE := libedify +LOCAL_CLANG := true include $(BUILD_STATIC_LIBRARY) diff --git a/minadbd/Android.mk b/minadbd/Android.mk index a7a3e087d..3db3b4114 100644 --- a/minadbd/Android.mk +++ b/minadbd/Android.mk @@ -15,6 +15,7 @@ LOCAL_SRC_FILES := \ fuse_adb_provider.cpp \ services.cpp \ +LOCAL_CLANG := true LOCAL_MODULE := libminadbd LOCAL_CFLAGS := $(minadbd_cflags) LOCAL_CONLY_FLAGS := -Wimplicit-function-declaration diff --git a/minui/Android.mk b/minui/Android.mk index 97724fbf0..3057f452c 100644 --- a/minui/Android.mk +++ b/minui/Android.mk @@ -41,6 +41,7 @@ include $(BUILD_STATIC_LIBRARY) # Used by OEMs for factory test images. include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_MODULE := libminui LOCAL_WHOLE_STATIC_LIBRARIES += libminui LOCAL_SHARED_LIBRARIES := libpng diff --git a/minzip/Android.mk b/minzip/Android.mk index 045f35570..48d26bcb7 100644 --- a/minzip/Android.mk +++ b/minzip/Android.mk @@ -16,6 +16,8 @@ LOCAL_STATIC_LIBRARIES := libselinux LOCAL_MODULE := libminzip +LOCAL_CLANG := true + LOCAL_CFLAGS += -Wall include $(BUILD_STATIC_LIBRARY) diff --git a/minzip/Zip.c b/minzip/Zip.c index 40712e03a..a64c833f9 100644 --- a/minzip/Zip.c +++ b/minzip/Zip.c @@ -506,7 +506,6 @@ static bool processDeflatedEntry(const ZipArchive *pArchive, void *cookie) { long result = -1; - unsigned char readBuf[32 * 1024]; unsigned char procBuf[32 * 1024]; z_stream zstream; int zerr; @@ -603,7 +602,6 @@ bool mzProcessZipEntryContents(const ZipArchive *pArchive, void *cookie) { bool ret = false; - off_t oldOff; switch (pEntry->compression) { case STORED: diff --git a/mtdutils/Android.mk b/mtdutils/Android.mk index f04355b5e..b7d35c27a 100644 --- a/mtdutils/Android.mk +++ b/mtdutils/Android.mk @@ -6,10 +6,12 @@ LOCAL_SRC_FILES := \ mounts.c LOCAL_MODULE := libmtdutils +LOCAL_CLANG := true include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_SRC_FILES := flash_image.c LOCAL_MODULE := flash_image LOCAL_MODULE_TAGS := eng diff --git a/recovery.cpp b/recovery.cpp index b7a545898..a0c74524e 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -326,14 +326,18 @@ static void rotate_logs(int max) { ensure_path_mounted(LAST_KMSG_FILE); for (int i = max-1; i >= 0; --i) { - std::string old_log = android::base::StringPrintf((i == 0) ? "%s" : "%s.%d", - LAST_LOG_FILE, i); + std::string old_log = android::base::StringPrintf("%s", LAST_LOG_FILE); + if (i > 0) { + old_log += "." + std::to_string(i); + } std::string new_log = android::base::StringPrintf("%s.%d", LAST_LOG_FILE, i+1); // Ignore errors if old_log doesn't exist. rename(old_log.c_str(), new_log.c_str()); - std::string old_kmsg = android::base::StringPrintf((i == 0) ? "%s" : "%s.%d", - LAST_KMSG_FILE, i); + std::string old_kmsg = android::base::StringPrintf("%s", LAST_KMSG_FILE); + if (i > 0) { + old_kmsg += "." + std::to_string(i); + } std::string new_kmsg = android::base::StringPrintf("%s.%d", LAST_KMSG_FILE, i+1); rename(old_kmsg.c_str(), new_kmsg.c_str()); } @@ -706,7 +710,10 @@ static void choose_recovery_file(Device* device) { // Add LAST_KMSG_FILE + LAST_KMSG_FILE.x for (int i = 0; i < KEEP_LOG_COUNT; i++) { char* log_file; - if (asprintf(&log_file, (i == 0) ? "%s" : "%s.%d", LAST_LOG_FILE, i) == -1) { + int ret; + ret = (i == 0) ? asprintf(&log_file, "%s", LAST_LOG_FILE) : + asprintf(&log_file, "%s.%d", LAST_LOG_FILE, i); + if (ret == -1) { // memory allocation failure - return early. Should never happen. return; } @@ -717,7 +724,9 @@ static void choose_recovery_file(Device* device) { } char* kmsg_file; - if (asprintf(&kmsg_file, (i == 0) ? "%s" : "%s.%d", LAST_KMSG_FILE, i) == -1) { + ret = (i == 0) ? asprintf(&kmsg_file, "%s", LAST_KMSG_FILE) : + asprintf(&kmsg_file, "%s.%d", LAST_KMSG_FILE, i); + if (ret == -1) { // memory allocation failure - return early. Should never happen. return; } diff --git a/tests/Android.mk b/tests/Android.mk index 02a272a24..4ce00b457 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -17,6 +17,7 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) +LOCAL_CLANG := true LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk LOCAL_STATIC_LIBRARIES := libverifier LOCAL_SRC_FILES := asn1_decoder_test.cpp diff --git a/uncrypt/Android.mk b/uncrypt/Android.mk index c7d4d3746..e73c8f1b6 100644 --- a/uncrypt/Android.mk +++ b/uncrypt/Android.mk @@ -16,6 +16,8 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) +LOCAL_CLANG := true + LOCAL_SRC_FILES := uncrypt.cpp LOCAL_MODULE := uncrypt diff --git a/updater/Android.mk b/updater/Android.mk index ff02a33b0..a3df805ef 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -17,6 +17,8 @@ include $(CLEAR_VARS) # needed only for OTA packages.) LOCAL_MODULE_TAGS := eng +LOCAL_CLANG := true + LOCAL_SRC_FILES := $(updater_src_files) ifeq ($(TARGET_USERIMAGES_USE_EXT4), true) -- cgit v1.2.3 From 56deefba73fb318ba0498da49adc64de960a6e29 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Tue, 19 May 2015 11:33:18 -0700 Subject: Stop using libstdc++. These are already getting libc++, so it isn't necessary. If any of the other static libraries (such as adb) use new or delete from libc++, there will be symbol collisions. Change-Id: I55e43ec60006d3c2403122fa1174bde06f18e09f (cherry picked from commit e49a9e527a51f43db792263bb60bfc91293848da) --- Android.mk | 2 -- applypatch/Android.mk | 4 ++-- updater/Android.mk | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Android.mk b/Android.mk index ac1ef83d8..e43d55f66 100644 --- a/Android.mk +++ b/Android.mk @@ -77,7 +77,6 @@ LOCAL_STATIC_LIBRARIES := \ libcutils \ liblog \ libselinux \ - libstdc++ \ libm \ libc @@ -122,7 +121,6 @@ LOCAL_STATIC_LIBRARIES := \ libminui \ libminzip \ libcutils \ - libstdc++ \ libc include $(BUILD_EXECUTABLE) diff --git a/applypatch/Android.mk b/applypatch/Android.mk index 4936db2a2..eb3e4580e 100644 --- a/applypatch/Android.mk +++ b/applypatch/Android.mk @@ -32,7 +32,7 @@ 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 +LOCAL_SHARED_LIBRARIES += libz libcutils libc include $(BUILD_EXECUTABLE) @@ -45,7 +45,7 @@ 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 +LOCAL_STATIC_LIBRARIES += libz libcutils libc include $(BUILD_EXECUTABLE) diff --git a/updater/Android.mk b/updater/Android.mk index a3df805ef..a0ea06fa5 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -34,7 +34,7 @@ endif LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UPDATER_LIBS) $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) LOCAL_STATIC_LIBRARIES += libapplypatch libedify libmtdutils libminzip libz LOCAL_STATIC_LIBRARIES += libmincrypt libbz -LOCAL_STATIC_LIBRARIES += libcutils liblog libstdc++ libc +LOCAL_STATIC_LIBRARIES += libcutils liblog libc LOCAL_STATIC_LIBRARIES += libselinux tune2fs_static_libraries := \ libext2_com_err \ -- cgit v1.2.3 From 806f72f9e6ec0d15b550b79b0baa92a93fc646e3 Mon Sep 17 00:00:00 2001 From: Sami Tolvanen Date: Tue, 12 May 2015 12:48:46 +0100 Subject: Add error and range checks to parse_range Only trusted input is passed to parse_range, but check for invalid input to catch possible problems in transfer lists. Bug: 21033983 Bug: 21034030 Bug: 21034172 Bug: 21034406 Change-Id: I1e266de3de15c99ee596ebdb034419fdfe7eba1f (cherry picked from commit f2bac04e1ba0a5b79f8adbc35b493923b776f8b2) --- updater/blockimg.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 10 deletions(-) diff --git a/updater/blockimg.c b/updater/blockimg.c index b006d10c5..e0be03917 100644 --- a/updater/blockimg.c +++ b/updater/blockimg.c @@ -61,30 +61,91 @@ typedef struct { int pos[0]; } RangeSet; +#define RANGESET_MAX_POINTS \ + ((int)((INT_MAX / sizeof(int)) - sizeof(RangeSet))) + static RangeSet* parse_range(char* text) { char* save; - int num; - num = strtol(strtok_r(text, ",", &save), NULL, 0); + char* token; + int i, num; + long int val; + RangeSet* out = NULL; + size_t bufsize; - RangeSet* out = malloc(sizeof(RangeSet) + num * sizeof(int)); - if (out == NULL) { - fprintf(stderr, "failed to allocate range of %zu bytes\n", - sizeof(RangeSet) + num * sizeof(int)); - exit(1); + if (!text) { + goto err; + } + + token = strtok_r(text, ",", &save); + + if (!token) { + goto err; + } + + val = strtol(token, NULL, 0); + + if (val < 2 || val > RANGESET_MAX_POINTS) { + goto err; + } else if (val % 2) { + goto err; // must be even + } + + num = (int) val; + bufsize = sizeof(RangeSet) + num * sizeof(int); + + out = malloc(bufsize); + + if (!out) { + fprintf(stderr, "failed to allocate range of %zu bytes\n", bufsize); + goto err; } + out->count = num / 2; out->size = 0; - int i; + for (i = 0; i < num; ++i) { - out->pos[i] = strtol(strtok_r(NULL, ",", &save), NULL, 0); - if (i%2) { + token = strtok_r(NULL, ",", &save); + + if (!token) { + goto err; + } + + val = strtol(token, NULL, 0); + + if (val < 0 || val > INT_MAX) { + goto err; + } + + out->pos[i] = (int) val; + + if (i % 2) { + if (out->pos[i - 1] >= out->pos[i]) { + goto err; // empty or negative range + } + + if (out->size > INT_MAX - out->pos[i]) { + goto err; // overflow + } + out->size += out->pos[i]; } else { + if (out->size < 0) { + goto err; + } + out->size -= out->pos[i]; } } + if (out->size <= 0) { + goto err; + } + return out; + +err: + fprintf(stderr, "failed to parse range '%s'\n", text ? text : "NULL"); + exit(1); } static int range_overlaps(RangeSet* r1, RangeSet* r2) { -- cgit v1.2.3 From 818fa781d1dbe35c0c5bfff3ebff1b45a2a676f0 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Tue, 23 Jun 2015 23:23:33 -0700 Subject: DO NOT MERGE recovery: Switch applypatch/ and updater/ to cpp. Mostly trivial changes to make cpp compiler happy. Change-Id: I69bd1d96fcccf506007f6144faf37e11cfba1270 (cherry picked from commit ba9a42aa7e10686de186636fe9fecbf8c4cc7c19) --- applypatch/Android.mk | 8 +- applypatch/applypatch.c | 1048 ----------------------- applypatch/applypatch.cpp | 1025 +++++++++++++++++++++++ applypatch/bsdiff.c | 410 --------- applypatch/bsdiff.cpp | 410 +++++++++ applypatch/bspatch.c | 256 ------ applypatch/bspatch.cpp | 255 ++++++ applypatch/freecache.c | 172 ---- applypatch/freecache.cpp | 186 +++++ applypatch/imgdiff.c | 1060 ------------------------ applypatch/imgdiff.cpp | 1068 ++++++++++++++++++++++++ applypatch/imgpatch.c | 234 ------ applypatch/imgpatch.cpp | 234 ++++++ applypatch/main.c | 214 ----- applypatch/main.cpp | 213 +++++ applypatch/utils.c | 65 -- applypatch/utils.cpp | 65 ++ minzip/Hash.h | 8 + roots.cpp | 2 - updater/Android.mk | 18 +- updater/blockimg.c | 2014 --------------------------------------------- updater/blockimg.cpp | 2011 ++++++++++++++++++++++++++++++++++++++++++++ updater/install.c | 1625 ------------------------------------ updater/install.cpp | 1617 ++++++++++++++++++++++++++++++++++++ updater/updater.c | 169 ---- updater/updater.cpp | 169 ++++ 26 files changed, 7280 insertions(+), 7276 deletions(-) delete mode 100644 applypatch/applypatch.c create mode 100644 applypatch/applypatch.cpp delete mode 100644 applypatch/bsdiff.c create mode 100644 applypatch/bsdiff.cpp delete mode 100644 applypatch/bspatch.c create mode 100644 applypatch/bspatch.cpp delete mode 100644 applypatch/freecache.c create mode 100644 applypatch/freecache.cpp delete mode 100644 applypatch/imgdiff.c create mode 100644 applypatch/imgdiff.cpp delete mode 100644 applypatch/imgpatch.c create mode 100644 applypatch/imgpatch.cpp delete mode 100644 applypatch/main.c create mode 100644 applypatch/main.cpp delete mode 100644 applypatch/utils.c create mode 100644 applypatch/utils.cpp delete mode 100644 updater/blockimg.c create mode 100644 updater/blockimg.cpp delete mode 100644 updater/install.c create mode 100644 updater/install.cpp delete mode 100644 updater/updater.c create mode 100644 updater/updater.cpp diff --git a/applypatch/Android.mk b/applypatch/Android.mk index eb3e4580e..1f73fd897 100644 --- a/applypatch/Android.mk +++ b/applypatch/Android.mk @@ -17,7 +17,7 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_CLANG := true -LOCAL_SRC_FILES := applypatch.c bspatch.c freecache.c imgpatch.c utils.c +LOCAL_SRC_FILES := applypatch.cpp bspatch.cpp freecache.cpp imgpatch.cpp utils.cpp LOCAL_MODULE := libapplypatch LOCAL_MODULE_TAGS := eng LOCAL_C_INCLUDES += external/bzip2 external/zlib bootable/recovery @@ -28,7 +28,7 @@ include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_CLANG := true -LOCAL_SRC_FILES := main.c +LOCAL_SRC_FILES := main.cpp LOCAL_MODULE := applypatch LOCAL_C_INCLUDES += bootable/recovery LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypt libbz @@ -39,7 +39,7 @@ include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) LOCAL_CLANG := true -LOCAL_SRC_FILES := main.c +LOCAL_SRC_FILES := main.cpp LOCAL_MODULE := applypatch_static LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_MODULE_TAGS := eng @@ -52,7 +52,7 @@ include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) LOCAL_CLANG := true -LOCAL_SRC_FILES := imgdiff.c utils.c bsdiff.c +LOCAL_SRC_FILES := imgdiff.cpp utils.cpp bsdiff.cpp LOCAL_MODULE := imgdiff LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_C_INCLUDES += external/zlib external/bzip2 diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c deleted file mode 100644 index 2358d4292..000000000 --- a/applypatch/applypatch.c +++ /dev/null @@ -1,1048 +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 -#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" - -static int LoadPartitionContents(const char* filename, FileContents* file); -static ssize_t FileSink(const unsigned char* data, ssize_t len, void* token); -static int GenerateTarget(FileContents* source_file, - const Value* source_patch_value, - FileContents* copy_file, - const Value* copy_patch_value, - const char* source_filename, - const char* target_filename, - const uint8_t target_sha1[SHA_DIGEST_SIZE], - size_t target_size, - const Value* bonus_data); - -static int mtd_partitions_scanned = 0; - -// Read a file into memory; store the file contents and associated -// metadata in *file. -// -// Return 0 on success. -int LoadFileContents(const char* filename, FileContents* file) { - file->data = NULL; - - // A special 'filename' beginning with "MTD:" or "EMMC:" means to - // load the contents of a partition. - if (strncmp(filename, "MTD:", 4) == 0 || - strncmp(filename, "EMMC:", 5) == 0) { - return LoadPartitionContents(filename, file); - } - - if (stat(filename, &file->st) != 0) { - printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); - return -1; - } - - 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_hash(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; - } -} - -// Load the contents of an MTD or EMMC partition into the provided -// FileContents. filename should be a string of the form -// "MTD::::::..." (or -// "EMMC::..."). The smallest size_n bytes for -// which that prefix of the partition contents has the corresponding -// sha1 hash will be loaded. It is acceptable for a size value to be -// repeated with different sha1s. Will return 0 on success. -// -// This complexity is needed because if an OTA installation is -// interrupted, the partition might contain either the source or the -// target data, which might be of different lengths. We need to know -// the length in order to read from a partition (there is no -// "end-of-file" marker), so the caller must specify the possible -// lengths and the hash of the data, and we'll do the load expecting -// to find one of those hashes. -enum PartitionType { MTD, EMMC }; - -static int LoadPartitionContents(const char* filename, FileContents* file) { - char* copy = strdup(filename); - const char* magic = strtok(copy, ":"); - - enum PartitionType type; - - if (strcmp(magic, "MTD") == 0) { - type = MTD; - } else if (strcmp(magic, "EMMC") == 0) { - type = EMMC; - } else { - printf("LoadPartitionContents 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("LoadPartitionContents 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("LoadPartitionContents 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); - - MtdReadContext* ctx = NULL; - FILE* dev = NULL; - - switch (type) { - case MTD: - 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; - } - - ctx = mtd_read_partition(mtd); - if (ctx == NULL) { - printf("failed to initialize read of mtd partition \"%s\"\n", - partition); - return -1; - } - break; - - case EMMC: - dev = fopen(partition, "rb"); - if (dev == NULL) { - printf("failed to open emmc partition \"%s\": %s\n", - partition, strerror(errno)); - 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) { - switch (type) { - case MTD: - read = mtd_read_data(ctx, p, next); - break; - - case EMMC: - read = fread(p, 1, next, dev); - break; - } - if (next != read) { - printf("short read (%zu bytes of %zu) for partition \"%s\"\n", - read, next, partition); - free(file->data); - file->data = NULL; - return -1; - } - 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("partition read matched size %zu sha %s\n", - size[index[i]], sha1sum[index[i]]); - break; - } - - p += read; - } - - switch (type) { - case MTD: - mtd_read_close(ctx); - break; - - case EMMC: - fclose(dev); - break; - } - - - if (i == pairs) { - // Ran off the end of the list of (size,sha1) pairs without - // finding a match. - printf("contents of partition \"%s\" didn't match %s\n", - partition, filename); - free(file->data); - file->data = NULL; - return -1; - } - - 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, const FileContents* file) { - int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR); - if (fd < 0) { - printf("failed to open \"%s\" for write: %s\n", - filename, strerror(errno)); - 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; - } - if (fsync(fd) != 0) { - printf("fsync of \"%s\" failed: %s\n", filename, strerror(errno)); - return -1; - } - if (close(fd) != 0) { - printf("close of \"%s\" failed: %s\n", filename, strerror(errno)); - return -1; - } - - if (chmod(filename, file->st.st_mode) != 0) { - printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno)); - return -1; - } - if (chown(filename, file->st.st_uid, file->st.st_gid) != 0) { - printf("chown of \"%s\" failed: %s\n", filename, strerror(errno)); - return -1; - } - - return 0; -} - -// Write a memory buffer to 'target' partition, a string of the form -// "MTD:[:...]" or "EMMC::". Return 0 on -// success. -int WriteToPartition(unsigned char* data, size_t len, - const char* target) { - char* copy = strdup(target); - const char* magic = strtok(copy, ":"); - - enum PartitionType type; - if (strcmp(magic, "MTD") == 0) { - type = MTD; - } else if (strcmp(magic, "EMMC") == 0) { - type = EMMC; - } else { - printf("WriteToPartition called with bad target (%s)\n", target); - return -1; - } - const char* partition = strtok(NULL, ":"); - - if (partition == NULL) { - printf("bad partition target name \"%s\"\n", target); - return -1; - } - - switch (type) { - case MTD: - 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 %zu of %zu 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; - } - break; - - case EMMC: - { - size_t start = 0; - int success = 0; - int fd = open(partition, O_RDWR | O_SYNC); - if (fd < 0) { - printf("failed to open %s: %s\n", partition, strerror(errno)); - return -1; - } - int attempt; - - for (attempt = 0; attempt < 2; ++attempt) { - if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) { - printf("failed seek on %s: %s\n", - partition, strerror(errno)); - return -1; - } - while (start < len) { - size_t to_write = len - start; - if (to_write > 1<<20) to_write = 1<<20; - - ssize_t written = TEMP_FAILURE_RETRY(write(fd, data+start, to_write)); - if (written == -1) { - printf("failed write writing to %s: %s\n", partition, strerror(errno)); - return -1; - } - start += written; - } - if (fsync(fd) != 0) { - printf("failed to sync to %s (%s)\n", - partition, strerror(errno)); - return -1; - } - if (close(fd) != 0) { - printf("failed to close %s (%s)\n", - partition, strerror(errno)); - return -1; - } - fd = open(partition, O_RDONLY); - if (fd < 0) { - printf("failed to reopen %s for verify (%s)\n", - partition, strerror(errno)); - return -1; - } - - // drop caches so our subsequent verification read - // won't just be reading the cache. - sync(); - int dc = open("/proc/sys/vm/drop_caches", O_WRONLY); - if (TEMP_FAILURE_RETRY(write(dc, "3\n", 2)) == -1) { - printf("write to /proc/sys/vm/drop_caches failed: %s\n", strerror(errno)); - } else { - printf(" caches dropped\n"); - } - close(dc); - sleep(1); - - // verify - if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) { - printf("failed to seek back to beginning of %s: %s\n", - partition, strerror(errno)); - return -1; - } - unsigned char buffer[4096]; - start = len; - size_t p; - for (p = 0; p < len; p += sizeof(buffer)) { - size_t to_read = len - p; - if (to_read > sizeof(buffer)) to_read = sizeof(buffer); - - size_t so_far = 0; - while (so_far < to_read) { - ssize_t read_count = - TEMP_FAILURE_RETRY(read(fd, buffer+so_far, to_read-so_far)); - if (read_count == -1) { - printf("verify read error %s at %zu: %s\n", - partition, p, strerror(errno)); - return -1; - } - if ((size_t)read_count < to_read) { - printf("short verify read %s at %zu: %zd %zu %s\n", - partition, p, read_count, to_read, strerror(errno)); - } - so_far += read_count; - } - - if (memcmp(buffer, data+p, to_read)) { - printf("verification failed starting at %zu\n", p); - start = p; - break; - } - } - - if (start == len) { - printf("verification read succeeded (attempt %d)\n", attempt+1); - success = true; - break; - } - } - - if (!success) { - printf("failed to verify after all attempts\n"); - return -1; - } - - if (close(fd) != 0) { - printf("error closing %s (%s)\n", partition, strerror(errno)); - return -1; - } - sync(); - break; - } - } - - free(copy); - 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 * 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 - // 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); - file.data = NULL; - - // If the source file is missing or corrupted, it might be because - // we were killed in the middle of patching it. A copy of it - // should have been made in CACHE_TEMP_SOURCE. If that file - // 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(const 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 = TEMP_FAILURE_RETRY(write(fd, data+done, len-done)); - if (wrote == -1) { - 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(const 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_bavail; -} - -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; - } -} - -static void print_short_sha1(const uint8_t sha1[SHA_DIGEST_SIZE]) { - int i; - const char* hex = "0123456789abcdef"; - for (i = 0; i < 4; ++i) { - putchar(hex[(sha1[i]>>4) & 0xf]); - putchar(hex[sha1[i] & 0xf]); - } -} - -// 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 a partition to read the source data. -// See the comments for the LoadPartition Contents() function above -// for the format of such a filename. - -int applypatch(const char* source_filename, - const char* target_filename, - const char* target_sha1_str, - size_t target_size, - int num_patches, - char** const patch_sha1_str, - Value** patch_data, - Value* bonus_data) { - printf("patch %s: ", source_filename); - - if (target_filename[0] == '-' && - target_filename[1] == '\0') { - target_filename = source_filename; - } - - uint8_t target_sha1[SHA_DIGEST_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; - copy_file.data = NULL; - source_file.data = NULL; - const Value* source_patch_value = NULL; - const Value* copy_patch_value = NULL; - - // We try to load the target file into the source_file object. - if (LoadFileContents(target_filename, &source_file) == 0) { - if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_SIZE) == 0) { - // The early-exit case: the patch was already applied, this file - // has the desired hash, nothing for us to do. - printf("already "); - print_short_sha1(target_sha1); - putchar('\n'); - free(source_file.data); - 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); - source_file.data = NULL; - 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); - source_file.data = NULL; - 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"); - free(copy_file.data); - return 1; - } - } - - int result = GenerateTarget(&source_file, source_patch_value, - ©_file, copy_patch_value, - source_filename, target_filename, - target_sha1, target_size, bonus_data); - free(source_file.data); - free(copy_file.data); - - return result; -} - -static int GenerateTarget(FileContents* source_file, - const Value* source_patch_value, - FileContents* copy_file, - const Value* copy_patch_value, - const char* source_filename, - const char* target_filename, - const uint8_t target_sha1[SHA_DIGEST_SIZE], - size_t target_size, - const Value* bonus_data) { - int retry = 1; - SHA_CTX ctx; - int output; - MemorySinkInfo msi; - FileContents* source_to_use; - char* outname; - int made_copy = 0; - - // 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 || - strncmp(target_filename, "EMMC:", 5) == 0) { - // If the target is a partition, we're actually going to - // write the output to /tmp and then copy it to the - // partition. statfs() always returns 0 blocks free for - // /tmp, so instead we'll just assume that /tmp has enough - // space to hold the file. - - // We still write the original source to cache, in case - // the partition write is interrupted. - if (MakeFreeSpaceOnCache(source_file->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); - enough_space = - (free_space > (256 << 10)) && // 256k (two-block) minimum - (free_space > (target_size * 3 / 2)); // 50% margin of error - if (!enough_space) { - printf("target %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 || - strncmp(source_filename, "EMMC:", 5) == 0) { - // It's impossible to free space on the target filesystem by - // deleting the source if the source is a partition. If - // we're ever in a state where we need to do this, fail. - printf("not enough free space for target but source " - "is partition\n"); - return 1; - } - - if (MakeFreeSpaceOnCache(source_file->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) ", (long)free_space); - } - } - - const Value* patch; - if (source_patch_value != NULL) { - source_to_use = source_file; - patch = source_patch_value; - } else { - source_to_use = copy_file; - patch = copy_patch_value; - } - - if (patch->type != VAL_BLOB) { - printf("patch is not a blob\n"); - return 1; - } - - SinkFn sink = NULL; - void* token = NULL; - output = -1; - outname = NULL; - if (strncmp(target_filename, "MTD:", 4) == 0 || - strncmp(target_filename, "EMMC:", 5) == 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 | O_SYNC, - S_IRUSR | S_IWUSR); - 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, bonus_data); - } else { - printf("Unknown patch file format\n"); - return 1; - } - - if (output >= 0) { - if (fsync(output) != 0) { - printf("failed to fsync file \"%s\" (%s)\n", outname, strerror(errno)); - result = 1; - } - if (close(output) != 0) { - printf("failed to close file \"%s\" (%s)\n", outname, strerror(errno)); - result = 1; - } - } - - if (result != 0) { - 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; - } else { - printf("now "); - print_short_sha1(target_sha1); - putchar('\n'); - } - - if (output < 0) { - // Copy the temp file to the partition. - if (WriteToPartition(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.cpp b/applypatch/applypatch.cpp new file mode 100644 index 000000000..96bd88e88 --- /dev/null +++ b/applypatch/applypatch.cpp @@ -0,0 +1,1025 @@ +/* + * 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" + +static int LoadPartitionContents(const char* filename, FileContents* file); +static ssize_t FileSink(const unsigned char* data, ssize_t len, void* token); +static int GenerateTarget(FileContents* source_file, + const Value* source_patch_value, + FileContents* copy_file, + const Value* copy_patch_value, + const char* source_filename, + const char* target_filename, + const uint8_t target_sha1[SHA_DIGEST_SIZE], + size_t target_size, + const Value* bonus_data); + +static int mtd_partitions_scanned = 0; + +// Read a file into memory; store the file contents and associated +// metadata in *file. +// +// Return 0 on success. +int LoadFileContents(const char* filename, FileContents* file) { + file->data = NULL; + + // A special 'filename' beginning with "MTD:" or "EMMC:" means to + // load the contents of a partition. + if (strncmp(filename, "MTD:", 4) == 0 || + strncmp(filename, "EMMC:", 5) == 0) { + return LoadPartitionContents(filename, file); + } + + if (stat(filename, &file->st) != 0) { + printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); + return -1; + } + + file->size = file->st.st_size; + file->data = reinterpret_cast(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; + } + + size_t bytes_read = fread(file->data, 1, file->size, f); + if (bytes_read != static_cast(file->size)) { + printf("short read of \"%s\" (%zu bytes of %zd)\n", filename, bytes_read, file->size); + free(file->data); + file->data = NULL; + return -1; + } + fclose(f); + + SHA_hash(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) { + const int aa = *reinterpret_cast(a); + const int bb = *reinterpret_cast(b); + if (size_array[aa] < size_array[bb]) { + return -1; + } else if (size_array[aa] > size_array[bb]) { + return 1; + } else { + return 0; + } +} + +// Load the contents of an MTD or EMMC partition into the provided +// FileContents. filename should be a string of the form +// "MTD::::::..." (or +// "EMMC::..."). The smallest size_n bytes for +// which that prefix of the partition contents has the corresponding +// sha1 hash will be loaded. It is acceptable for a size value to be +// repeated with different sha1s. Will return 0 on success. +// +// This complexity is needed because if an OTA installation is +// interrupted, the partition might contain either the source or the +// target data, which might be of different lengths. We need to know +// the length in order to read from a partition (there is no +// "end-of-file" marker), so the caller must specify the possible +// lengths and the hash of the data, and we'll do the load expecting +// to find one of those hashes. +enum PartitionType { MTD, EMMC }; + +static int LoadPartitionContents(const char* filename, FileContents* file) { + char* copy = strdup(filename); + const char* magic = strtok(copy, ":"); + + enum PartitionType type; + + if (strcmp(magic, "MTD") == 0) { + type = MTD; + } else if (strcmp(magic, "EMMC") == 0) { + type = EMMC; + } else { + printf("LoadPartitionContents 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("LoadPartitionContents called with bad filename (%s)\n", + filename); + } + + int pairs = (colons-1)/2; // # of (size,sha1) pairs in filename + int* index = reinterpret_cast(malloc(pairs * sizeof(int))); + size_t* size = reinterpret_cast(malloc(pairs * sizeof(size_t))); + char** sha1sum = reinterpret_cast(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("LoadPartitionContents 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); + + MtdReadContext* ctx = NULL; + FILE* dev = NULL; + + switch (type) { + case MTD: { + 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; + } + + ctx = mtd_read_partition(mtd); + if (ctx == NULL) { + printf("failed to initialize read of mtd partition \"%s\"\n", + partition); + return -1; + } + break; + } + + case EMMC: + dev = fopen(partition, "rb"); + if (dev == NULL) { + printf("failed to open emmc partition \"%s\": %s\n", + partition, strerror(errno)); + 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 = reinterpret_cast(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) { + switch (type) { + case MTD: + read = mtd_read_data(ctx, p, next); + break; + + case EMMC: + read = fread(p, 1, next, dev); + break; + } + if (next != read) { + printf("short read (%zu bytes of %zu) for partition \"%s\"\n", + read, next, partition); + free(file->data); + file->data = NULL; + return -1; + } + 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("partition read matched size %zu sha %s\n", + size[index[i]], sha1sum[index[i]]); + break; + } + + p += read; + } + + switch (type) { + case MTD: + mtd_read_close(ctx); + break; + + case EMMC: + fclose(dev); + break; + } + + + if (i == pairs) { + // Ran off the end of the list of (size,sha1) pairs without + // finding a match. + printf("contents of partition \"%s\" didn't match %s\n", partition, filename); + free(file->data); + file->data = NULL; + return -1; + } + + const uint8_t* sha_final = SHA_final(&sha_ctx); + for (size_t 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, const FileContents* file) { + int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR); + if (fd < 0) { + printf("failed to open \"%s\" for write: %s\n", filename, strerror(errno)); + return -1; + } + + ssize_t bytes_written = FileSink(file->data, file->size, &fd); + if (bytes_written != file->size) { + printf("short write of \"%s\" (%zd bytes of %zd) (%s)\n", + filename, bytes_written, file->size, strerror(errno)); + close(fd); + return -1; + } + if (fsync(fd) != 0) { + printf("fsync of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } + if (close(fd) != 0) { + printf("close of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } + + if (chmod(filename, file->st.st_mode) != 0) { + printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } + if (chown(filename, file->st.st_uid, file->st.st_gid) != 0) { + printf("chown of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } + + return 0; +} + +// Write a memory buffer to 'target' partition, a string of the form +// "MTD:[:...]" or "EMMC::". Return 0 on +// success. +int WriteToPartition(unsigned char* data, size_t len, const char* target) { + char* copy = strdup(target); + const char* magic = strtok(copy, ":"); + + enum PartitionType type; + if (strcmp(magic, "MTD") == 0) { + type = MTD; + } else if (strcmp(magic, "EMMC") == 0) { + type = EMMC; + } else { + printf("WriteToPartition called with bad target (%s)\n", target); + return -1; + } + const char* partition = strtok(NULL, ":"); + + if (partition == NULL) { + printf("bad partition target name \"%s\"\n", target); + return -1; + } + + switch (type) { + case MTD: { + 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, reinterpret_cast(data), len); + if (written != len) { + printf("only wrote %zu of %zu 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; + } + break; + } + + case EMMC: { + size_t start = 0; + bool success = false; + int fd = open(partition, O_RDWR | O_SYNC); + if (fd < 0) { + printf("failed to open %s: %s\n", partition, strerror(errno)); + return -1; + } + + for (int attempt = 0; attempt < 2; ++attempt) { + if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) { + printf("failed seek on %s: %s\n", partition, strerror(errno)); + return -1; + } + while (start < len) { + size_t to_write = len - start; + if (to_write > 1<<20) to_write = 1<<20; + + ssize_t written = TEMP_FAILURE_RETRY(write(fd, data+start, to_write)); + if (written == -1) { + printf("failed write writing to %s: %s\n", partition, strerror(errno)); + return -1; + } + start += written; + } + if (fsync(fd) != 0) { + printf("failed to sync to %s (%s)\n", partition, strerror(errno)); + return -1; + } + if (close(fd) != 0) { + printf("failed to close %s (%s)\n", partition, strerror(errno)); + return -1; + } + fd = open(partition, O_RDONLY); + if (fd < 0) { + printf("failed to reopen %s for verify (%s)\n", partition, strerror(errno)); + return -1; + } + + // Drop caches so our subsequent verification read + // won't just be reading the cache. + sync(); + int dc = open("/proc/sys/vm/drop_caches", O_WRONLY); + if (TEMP_FAILURE_RETRY(write(dc, "3\n", 2)) == -1) { + printf("write to /proc/sys/vm/drop_caches failed: %s\n", strerror(errno)); + } else { + printf(" caches dropped\n"); + } + close(dc); + sleep(1); + + // verify + if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) { + printf("failed to seek back to beginning of %s: %s\n", + partition, strerror(errno)); + return -1; + } + unsigned char buffer[4096]; + start = len; + for (size_t p = 0; p < len; p += sizeof(buffer)) { + size_t to_read = len - p; + if (to_read > sizeof(buffer)) { + to_read = sizeof(buffer); + } + + size_t so_far = 0; + while (so_far < to_read) { + ssize_t read_count = + TEMP_FAILURE_RETRY(read(fd, buffer+so_far, to_read-so_far)); + if (read_count == -1) { + printf("verify read error %s at %zu: %s\n", + partition, p, strerror(errno)); + return -1; + } + if (static_cast(read_count) < to_read) { + printf("short verify read %s at %zu: %zd %zu %s\n", + partition, p, read_count, to_read, strerror(errno)); + } + so_far += read_count; + } + + if (memcmp(buffer, data+p, to_read) != 0) { + printf("verification failed starting at %zu\n", p); + start = p; + break; + } + } + + if (start == len) { + printf("verification read succeeded (attempt %d)\n", attempt+1); + success = true; + break; + } + } + + if (!success) { + printf("failed to verify after all attempts\n"); + return -1; + } + + if (close(fd) != 0) { + printf("error closing %s (%s)\n", partition, strerror(errno)); + return -1; + } + sync(); + break; + } + } + + free(copy); + 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) { + const char* ps = str; + uint8_t* pd = digest; + for (int 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 * const patch_sha1_str, + int num_patches) { + uint8_t patch_sha1[SHA_DIGEST_SIZE]; + for (int 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 + // 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); + file.data = NULL; + + // If the source file is missing or corrupted, it might be because + // we were killed in the middle of patching it. A copy of it + // should have been made in CACHE_TEMP_SOURCE. If that file + // 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(const unsigned char* data, ssize_t len, void* token) { + int fd = *reinterpret_cast(token); + ssize_t done = 0; + ssize_t wrote; + while (done < len) { + wrote = TEMP_FAILURE_RETRY(write(fd, data+done, len-done)); + if (wrote == -1) { + printf("error writing %zd bytes: %s\n", (len-done), strerror(errno)); + return done; + } + done += wrote; + } + return done; +} + +typedef struct { + unsigned char* buffer; + ssize_t size; + ssize_t pos; +} MemorySinkInfo; + +ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) { + MemorySinkInfo* msi = reinterpret_cast(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_bavail; +} + +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; + } +} + +static void print_short_sha1(const uint8_t sha1[SHA_DIGEST_SIZE]) { + const char* hex = "0123456789abcdef"; + for (size_t i = 0; i < 4; ++i) { + putchar(hex[(sha1[i]>>4) & 0xf]); + putchar(hex[sha1[i] & 0xf]); + } +} + +// 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 a partition to read the source data. +// See the comments for the LoadPartition Contents() function above +// for the format of such a filename. + +int applypatch(const char* source_filename, + const char* target_filename, + const char* target_sha1_str, + size_t target_size, + int num_patches, + char** const patch_sha1_str, + Value** patch_data, + Value* bonus_data) { + printf("patch %s: ", source_filename); + + if (target_filename[0] == '-' && target_filename[1] == '\0') { + target_filename = source_filename; + } + + uint8_t target_sha1[SHA_DIGEST_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; + copy_file.data = NULL; + source_file.data = NULL; + const Value* source_patch_value = NULL; + const Value* copy_patch_value = NULL; + + // We try to load the target file into the source_file object. + if (LoadFileContents(target_filename, &source_file) == 0) { + if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_SIZE) == 0) { + // The early-exit case: the patch was already applied, this file + // has the desired hash, nothing for us to do. + printf("already "); + print_short_sha1(target_sha1); + putchar('\n'); + free(source_file.data); + 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); + source_file.data = NULL; + 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); + source_file.data = NULL; + 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"); + free(copy_file.data); + return 1; + } + } + + int result = GenerateTarget(&source_file, source_patch_value, + ©_file, copy_patch_value, + source_filename, target_filename, + target_sha1, target_size, bonus_data); + free(source_file.data); + free(copy_file.data); + + return result; +} + +static int GenerateTarget(FileContents* source_file, + const Value* source_patch_value, + FileContents* copy_file, + const Value* copy_patch_value, + const char* source_filename, + const char* target_filename, + const uint8_t target_sha1[SHA_DIGEST_SIZE], + size_t target_size, + const Value* bonus_data) { + int retry = 1; + SHA_CTX ctx; + int output; + MemorySinkInfo msi; + FileContents* source_to_use; + char* outname; + int made_copy = 0; + + // 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 || + strncmp(target_filename, "EMMC:", 5) == 0) { + // If the target is a partition, we're actually going to + // write the output to /tmp and then copy it to the + // partition. statfs() always returns 0 blocks free for + // /tmp, so instead we'll just assume that /tmp has enough + // space to hold the file. + + // We still write the original source to cache, in case + // the partition write is interrupted. + if (MakeFreeSpaceOnCache(source_file->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); + enough_space = + (free_space > (256 << 10)) && // 256k (two-block) minimum + (free_space > (target_size * 3 / 2)); // 50% margin of error + if (!enough_space) { + printf("target %zu bytes; free space %zu bytes; retry %d; enough %d\n", + target_size, free_space, retry, enough_space); + } + } + + if (!enough_space) { + retry = 0; + } + + if (!enough_space && source_patch_value != NULL) { + // Using the original source, but not enough free space. First + // copy the source file to cache, then delete it from the original + // location. + + if (strncmp(source_filename, "MTD:", 4) == 0 || + strncmp(source_filename, "EMMC:", 5) == 0) { + // It's impossible to free space on the target filesystem by + // deleting the source if the source is a partition. If + // we're ever in a state where we need to do this, fail. + printf("not enough free space for target but source is partition\n"); + return 1; + } + + if (MakeFreeSpaceOnCache(source_file->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 %zu bytes free for target) ", free_space); + } + } + + const Value* patch; + if (source_patch_value != NULL) { + source_to_use = source_file; + patch = source_patch_value; + } else { + source_to_use = copy_file; + patch = copy_patch_value; + } + + if (patch->type != VAL_BLOB) { + printf("patch is not a blob\n"); + return 1; + } + + SinkFn sink = NULL; + void* token = NULL; + output = -1; + outname = NULL; + if (strncmp(target_filename, "MTD:", 4) == 0 || + strncmp(target_filename, "EMMC:", 5) == 0) { + // We store the decoded output in memory. + msi.buffer = reinterpret_cast(malloc(target_size)); + if (msi.buffer == NULL) { + printf("failed to alloc %zu bytes for output\n", target_size); + return 1; + } + msi.pos = 0; + msi.size = target_size; + sink = MemorySink; + token = &msi; + } else { + // We write the decoded output to ".patch". + outname = reinterpret_cast(malloc(strlen(target_filename) + 10)); + strcpy(outname, target_filename); + strcat(outname, ".patch"); + + output = open(outname, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR); + if (output < 0) { + printf("failed to open output file %s: %s\n", + outname, strerror(errno)); + 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, bonus_data); + } else { + printf("Unknown patch file format\n"); + return 1; + } + + if (output >= 0) { + if (fsync(output) != 0) { + printf("failed to fsync file \"%s\" (%s)\n", outname, strerror(errno)); + result = 1; + } + if (close(output) != 0) { + printf("failed to close file \"%s\" (%s)\n", outname, strerror(errno)); + result = 1; + } + } + + if (result != 0) { + 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; + } else { + printf("now "); + print_short_sha1(target_sha1); + putchar('\n'); + } + + if (output < 0) { + // Copy the temp file to the partition. + if (WriteToPartition(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/bsdiff.c b/applypatch/bsdiff.c deleted file mode 100644 index b6d342b7a..000000000 --- a/applypatch/bsdiff.c +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * 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 +#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],newdata,MIN(oldsize-I[x],newsize))<0) { + return search(I,old,oldsize,newdata,newsize,x,en,pos); + } else { + return search(I,old,oldsize,newdata,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, newdata, newsize are arguments; we don't load this +// data from files. old and newdata 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* newdata, 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 = reinterpret_cast(malloc((oldsize+1) * sizeof(off_t))); + V = reinterpret_cast(malloc((oldsize+1) * sizeof(off_t))); + qsufsort(*IP, V, old, oldsize); + free(V); + } + I = *IP; + + if(((db=reinterpret_cast(malloc(newsize+1)))==NULL) || + ((eb=reinterpret_cast(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]==newdata[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 - -#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); - - if (ctrl[0] < 0 || ctrl[1] < 0) { - printf("corrupt patch (negative byte counts)\n"); - return 1; - } - - // Sanity check - if (newpos + ctrl[0] > *new_size) { - printf("corrupt patch (new file overrun)\n"); - return 1; - } - - // Read diff string - if (FillBuffer(*new_data + 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/bspatch.cpp b/applypatch/bspatch.cpp new file mode 100644 index 000000000..9d201b477 --- /dev/null +++ b/applypatch/bspatch.cpp @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This file is a nearly line-for-line copy of bspatch.c from the +// bsdiff-4.3 distribution; the primary differences being how the +// input and output data are read and the error handling. Running +// applypatch with the -l option will display the bsdiff license +// notice. + +#include +#include +#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 = reinterpret_cast(malloc(*new_size)); + if (*new_data == NULL) { + printf("failed to allocate %zd bytes of memory for output file\n", *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); + + if (ctrl[0] < 0 || ctrl[1] < 0) { + printf("corrupt patch (negative byte counts)\n"); + return 1; + } + + // Sanity check + if (newpos + ctrl[0] > *new_size) { + printf("corrupt patch (new file overrun)\n"); + return 1; + } + + // Read diff string + if (FillBuffer(*new_data + 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 deleted file mode 100644 index 9827fda06..000000000 --- a/applypatch/freecache.c +++ /dev/null @@ -1,172 +0,0 @@ -#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/freecache.cpp b/applypatch/freecache.cpp new file mode 100644 index 000000000..2eb2f55ef --- /dev/null +++ b/applypatch/freecache.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#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 = reinterpret_cast(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"}; + + for (size_t 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 = reinterpret_cast(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("%zu bytes free on /cache (%zu needed)\n", free_now, 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 %zu bytes free\n", names[i], 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 deleted file mode 100644 index 3bac8be91..000000000 --- a/applypatch/imgdiff.c +++ /dev/null @@ -1,1060 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * This program constructs binary patches for images -- such as boot.img - * and recovery.img -- that consist primarily of large chunks of gzipped - * data interspersed with uncompressed data. Doing a naive bsdiff of - * these files is not useful because small changes in the data lead to - * large changes in the compressed bitstream; bsdiff patches of gzipped - * data are typically as large as the data itself. - * - * To patch these usefully, we break the source and target images up into - * chunks of two types: "normal" and "gzip". Normal chunks are simply - * patched using a plain bsdiff. Gzip chunks are first expanded, then a - * bsdiff is applied to the uncompressed data, then the patched data is - * gzipped using the same encoder parameters. Patched chunks are - * concatenated together to create the output file; the output image - * should be *exactly* the same series of bytes as the target image used - * originally to generate the patch. - * - * To work well with this tool, the gzipped sections of the target - * image must have been generated using the same deflate encoder that - * is available in applypatch, namely, the one in the zlib library. - * In practice this means that images should be compressed using the - * "minigzip" tool included in the zlib distribution, not the GNU gzip - * program. - * - * An "imgdiff" patch consists of a header describing the chunk structure - * of the file and any encoding parameters needed for the gzipped - * chunks, followed by N bsdiff patches, one per chunk. - * - * For a diff to be generated, the source and target images must have the - * same "chunk" structure: that is, the same number of gzipped and normal - * chunks in the same order. Android boot and recovery images currently - * consist of five chunks: a small normal header, a gzipped kernel, a - * small normal section, a gzipped ramdisk, and finally a small normal - * footer. - * - * Caveats: we locate gzipped sections within the source and target - * images by searching for the byte sequence 1f8b0800: 1f8b is the gzip - * magic number; 08 specifies the "deflate" encoding [the only encoding - * supported by the gzip standard]; and 00 is the flags byte. We do not - * currently support any extra header fields (which would be indicated by - * a nonzero flags byte). We also don't handle the case when that byte - * sequence appears spuriously in the file. (Note that it would have to - * occur spuriously within a normal chunk to be a problem.) - * - * - * The imgdiff patch header looks like this: - * - * "IMGDIFF1" (8) [magic number and version] - * chunk count (4) - * for each chunk: - * chunk type (4) [CHUNK_{NORMAL, GZIP, DEFLATE, RAW}] - * if chunk type == CHUNK_NORMAL: - * source start (8) - * source len (8) - * bsdiff patch offset (8) [from start of patch file] - * if chunk type == CHUNK_GZIP: (version 1 only) - * source start (8) - * source len (8) - * bsdiff patch offset (8) [from start of patch file] - * source expanded len (8) [size of uncompressed source] - * target expected len (8) [size of uncompressed target] - * gzip level (4) - * method (4) - * windowBits (4) - * memLevel (4) - * strategy (4) - * gzip header len (4) - * gzip header (gzip header len) - * gzip footer (8) - * if chunk type == CHUNK_DEFLATE: (version 2 only) - * source start (8) - * source len (8) - * bsdiff patch offset (8) [from start of patch file] - * source expanded len (8) [size of uncompressed source] - * target expected len (8) [size of uncompressed target] - * gzip level (4) - * method (4) - * windowBits (4) - * memLevel (4) - * strategy (4) - * if chunk type == RAW: (version 2 only) - * target len (4) - * data (target len) - * - * All integers are little-endian. "source start" and "source len" - * specify the section of the input image that comprises this chunk, - * including the gzip header and footer for gzip chunks. "source - * expanded len" is the size of the uncompressed source data. "target - * expected len" is the size of the uncompressed data after applying - * the bsdiff patch. The next five parameters specify the zlib - * parameters to be used when compressing the patched data, and the - * next three specify the header and footer to be wrapped around the - * compressed data to create the output chunk (so that header contents - * like the timestamp are recreated exactly). - * - * After the header there are 'chunk count' bsdiff patches; the offset - * of each from the beginning of the file is specified in the header. - * - * This tool can take an optional file of "bonus data". This is an - * extra file of data that is appended to chunk #1 after it is - * compressed (it must be a CHUNK_DEFLATE chunk). The same file must - * be available (and passed to applypatch with -b) when applying the - * patch. This is used to reduce the size of recovery-from-boot - * patches by combining the boot image with recovery ramdisk - * information that is stored on the system partition. - */ - -#include -#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. - size_t chunk_offset = pos; - - *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); - if (ret < 0) { - printf("Error: inflate failed [%s] at file offset [%zu]\n" - "imgdiff only supports gzip kernel compression," - " did you try CONFIG_KERNEL_LZO?\n", - strm.msg, chunk_offset); - free(img); - return NULL; - } - 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) { - int zip_mode = 0; - - if (argc >= 2 && strcmp(argv[1], "-z") == 0) { - zip_mode = 1; - --argc; - ++argv; - } - - size_t bonus_size = 0; - unsigned char* bonus_data = NULL; - if (argc >= 3 && strcmp(argv[1], "-b") == 0) { - struct stat st; - if (stat(argv[2], &st) != 0) { - printf("failed to stat bonus file %s: %s\n", argv[2], strerror(errno)); - return 1; - } - bonus_size = st.st_size; - bonus_data = malloc(bonus_size); - FILE* f = fopen(argv[2], "rb"); - if (f == NULL) { - printf("failed to open bonus file %s: %s\n", argv[2], strerror(errno)); - return 1; - } - if (fread(bonus_data, 1, bonus_size, f) != bonus_size) { - printf("failed to read bonus file %s: %s\n", argv[2], strerror(errno)); - return 1; - } - fclose(f); - - argc -= 2; - argv += 2; - } - - if (argc != 4) { - usage: - printf("usage: %s [-z] [-b ] \n", - argv[0]); - return 2; - } - - int num_src_chunks; - ImageChunk* src_chunks; - int num_tgt_chunks; - ImageChunk* tgt_chunks; - int i; - - if (zip_mode) { - if (ReadZip(argv[1], &num_src_chunks, &src_chunks, 1) == NULL) { - printf("failed to break apart source zip file\n"); - return 1; - } - if (ReadZip(argv[2], &num_tgt_chunks, &tgt_chunks, 0) == NULL) { - printf("failed to break apart target zip file\n"); - return 1; - } - } else { - if (ReadImage(argv[1], &num_src_chunks, &src_chunks) == NULL) { - printf("failed to break apart source image\n"); - return 1; - } - if (ReadImage(argv[2], &num_tgt_chunks, &tgt_chunks) == NULL) { - printf("failed to break apart target image\n"); - return 1; - } - - // Verify that the source and target images have the same chunk - // structure (ie, the same sequence of deflate and normal chunks). - - if (!zip_mode) { - // Merge the gzip header and footer in with any adjacent - // normal chunks. - MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks); - MergeAdjacentNormalChunks(src_chunks, &num_src_chunks); - } - - if (num_src_chunks != num_tgt_chunks) { - printf("source and target don't have same number of chunks!\n"); - printf("source chunks:\n"); - DumpChunks(src_chunks, num_src_chunks); - printf("target chunks:\n"); - DumpChunks(tgt_chunks, num_tgt_chunks); - return 1; - } - for (i = 0; i < num_src_chunks; ++i) { - if (src_chunks[i].type != tgt_chunks[i].type) { - printf("source and target don't have same chunk " - "structure! (chunk %d)\n", i); - printf("source chunks:\n"); - DumpChunks(src_chunks, num_src_chunks); - printf("target chunks:\n"); - DumpChunks(tgt_chunks, num_tgt_chunks); - return 1; - } - } - } - - for (i = 0; i < num_tgt_chunks; ++i) { - if (tgt_chunks[i].type == CHUNK_DEFLATE) { - // Confirm that given the uncompressed chunk data in the target, we - // can recompress it and get exactly the same bits as are in the - // input target image. If this fails, treat the chunk as a normal - // non-deflated chunk. - if (ReconstructDeflateChunk(tgt_chunks+i) < 0) { - printf("failed to reconstruct target deflate chunk %d [%s]; " - "treating as normal\n", i, tgt_chunks[i].filename); - ChangeDeflateChunkToNormal(tgt_chunks+i); - if (zip_mode) { - ImageChunk* src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks); - if (src) { - ChangeDeflateChunkToNormal(src); - } - } else { - ChangeDeflateChunkToNormal(src_chunks+i); - } - continue; - } - - // If two deflate chunks are identical (eg, the kernel has not - // changed between two builds), treat them as normal chunks. - // This makes applypatch much faster -- it can apply a trivial - // patch to the compressed data, rather than uncompressing and - // recompressing to apply the trivial patch to the uncompressed - // data. - ImageChunk* src; - if (zip_mode) { - src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks); - } else { - src = src_chunks+i; - } - - if (src == NULL || AreChunksEqual(tgt_chunks+i, src)) { - ChangeDeflateChunkToNormal(tgt_chunks+i); - if (src) { - ChangeDeflateChunkToNormal(src); - } - } - } - } - - // Merging neighboring normal chunks. - if (zip_mode) { - // For zips, we only need to do this to the target: deflated - // chunks are matched via filename, and normal chunks are patched - // using the entire source file as the source. - MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks); - } else { - // For images, we need to maintain the parallel structure of the - // chunk lists, so do the merging in both the source and target - // lists. - MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks); - MergeAdjacentNormalChunks(src_chunks, &num_src_chunks); - if (num_src_chunks != num_tgt_chunks) { - // This shouldn't happen. - printf("merging normal chunks went awry\n"); - return 1; - } - } - - // Compute bsdiff patches for each chunk's data (the uncompressed - // data, in the case of deflate chunks). - - DumpChunks(src_chunks, num_src_chunks); - - printf("Construct patches for %d chunks...\n", num_tgt_chunks); - unsigned char** patch_data = 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 { - if (i == 1 && bonus_data) { - printf(" using %d bytes of bonus data for chunk %d\n", bonus_size, i); - src_chunks[i].data = realloc(src_chunks[i].data, src_chunks[i].len + bonus_size); - memcpy(src_chunks[i].data+src_chunks[i].len, bonus_data, bonus_size); - src_chunks[i].len += bonus_size; - } - - patch_data[i] = MakePatch(src_chunks+i, tgt_chunks+i, patch_size+i); - } - printf("patch %3d is %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.cpp b/applypatch/imgdiff.cpp new file mode 100644 index 000000000..4d83ffb2e --- /dev/null +++ b/applypatch/imgdiff.cpp @@ -0,0 +1,1068 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This program constructs binary patches for images -- such as boot.img + * and recovery.img -- that consist primarily of large chunks of gzipped + * data interspersed with uncompressed data. Doing a naive bsdiff of + * these files is not useful because small changes in the data lead to + * large changes in the compressed bitstream; bsdiff patches of gzipped + * data are typically as large as the data itself. + * + * To patch these usefully, we break the source and target images up into + * chunks of two types: "normal" and "gzip". Normal chunks are simply + * patched using a plain bsdiff. Gzip chunks are first expanded, then a + * bsdiff is applied to the uncompressed data, then the patched data is + * gzipped using the same encoder parameters. Patched chunks are + * concatenated together to create the output file; the output image + * should be *exactly* the same series of bytes as the target image used + * originally to generate the patch. + * + * To work well with this tool, the gzipped sections of the target + * image must have been generated using the same deflate encoder that + * is available in applypatch, namely, the one in the zlib library. + * In practice this means that images should be compressed using the + * "minigzip" tool included in the zlib distribution, not the GNU gzip + * program. + * + * An "imgdiff" patch consists of a header describing the chunk structure + * of the file and any encoding parameters needed for the gzipped + * chunks, followed by N bsdiff patches, one per chunk. + * + * For a diff to be generated, the source and target images must have the + * same "chunk" structure: that is, the same number of gzipped and normal + * chunks in the same order. Android boot and recovery images currently + * consist of five chunks: a small normal header, a gzipped kernel, a + * small normal section, a gzipped ramdisk, and finally a small normal + * footer. + * + * Caveats: we locate gzipped sections within the source and target + * images by searching for the byte sequence 1f8b0800: 1f8b is the gzip + * magic number; 08 specifies the "deflate" encoding [the only encoding + * supported by the gzip standard]; and 00 is the flags byte. We do not + * currently support any extra header fields (which would be indicated by + * a nonzero flags byte). We also don't handle the case when that byte + * sequence appears spuriously in the file. (Note that it would have to + * occur spuriously within a normal chunk to be a problem.) + * + * + * The imgdiff patch header looks like this: + * + * "IMGDIFF1" (8) [magic number and version] + * chunk count (4) + * for each chunk: + * chunk type (4) [CHUNK_{NORMAL, GZIP, DEFLATE, RAW}] + * if chunk type == CHUNK_NORMAL: + * source start (8) + * source len (8) + * bsdiff patch offset (8) [from start of patch file] + * if chunk type == CHUNK_GZIP: (version 1 only) + * source start (8) + * source len (8) + * bsdiff patch offset (8) [from start of patch file] + * source expanded len (8) [size of uncompressed source] + * target expected len (8) [size of uncompressed target] + * gzip level (4) + * method (4) + * windowBits (4) + * memLevel (4) + * strategy (4) + * gzip header len (4) + * gzip header (gzip header len) + * gzip footer (8) + * if chunk type == CHUNK_DEFLATE: (version 2 only) + * source start (8) + * source len (8) + * bsdiff patch offset (8) [from start of patch file] + * source expanded len (8) [size of uncompressed source] + * target expected len (8) [size of uncompressed target] + * gzip level (4) + * method (4) + * windowBits (4) + * memLevel (4) + * strategy (4) + * if chunk type == RAW: (version 2 only) + * target len (4) + * data (target len) + * + * All integers are little-endian. "source start" and "source len" + * specify the section of the input image that comprises this chunk, + * including the gzip header and footer for gzip chunks. "source + * expanded len" is the size of the uncompressed source data. "target + * expected len" is the size of the uncompressed data after applying + * the bsdiff patch. The next five parameters specify the zlib + * parameters to be used when compressing the patched data, and the + * next three specify the header and footer to be wrapped around the + * compressed data to create the output chunk (so that header contents + * like the timestamp are recreated exactly). + * + * After the header there are 'chunk count' bsdiff patches; the offset + * of each from the beginning of the file is specified in the header. + * + * This tool can take an optional file of "bonus data". This is an + * extra file of data that is appended to chunk #1 after it is + * compressed (it must be a CHUNK_DEFLATE chunk). The same file must + * be available (and passed to applypatch with -b) when applying the + * patch. This is used to reduce the size of recovery-from-boot + * patches by combining the boot image with recovery ramdisk + * information that is stored on the system partition. + */ + +#include +#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* newdata, 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; + } + + size_t sz = static_cast(st.st_size); + unsigned char* img = reinterpret_cast(malloc(sz)); + FILE* f = fopen(filename, "rb"); + if (fread(img, 1, sz, f) != sz) { + printf("failed to read \"%s\" %s\n", filename, strerror(errno)); + fclose(f); + return NULL; + } + fclose(f); + + // look for the end-of-central-directory record. + + int i; + for (i = st.st_size-20; i >= 0 && i > st.st_size - 65600; --i) { + if (img[i] == 0x50 && img[i+1] == 0x4b && + img[i+2] == 0x05 && img[i+3] == 0x06) { + break; + } + } + // double-check: this archive consists of a single "disk" + if (!(img[i+4] == 0 && img[i+5] == 0 && img[i+6] == 0 && img[i+7] == 0)) { + printf("can't process multi-disk archive\n"); + return NULL; + } + + int cdcount = Read2(img+i+8); + int cdoffset = Read4(img+i+16); + + ZipFileEntry* temp_entries = reinterpret_cast(malloc( + cdcount * sizeof(ZipFileEntry))); + int entrycount = 0; + + unsigned char* cd = img+cdoffset; + for (i = 0; i < cdcount; ++i) { + if (!(cd[0] == 0x50 && cd[1] == 0x4b && cd[2] == 0x01 && cd[3] == 0x02)) { + printf("bad central directory entry %d\n", i); + return NULL; + } + + int clen = Read4(cd+20); // compressed len + int ulen = Read4(cd+24); // uncompressed len + int nlen = Read2(cd+28); // filename len + int xlen = Read2(cd+30); // extra field len + int mlen = Read2(cd+32); // file comment len + int hoffset = Read4(cd+42); // local header offset + + char* filename = reinterpret_cast(malloc(nlen+1)); + memcpy(filename, cd+46, nlen); + filename[nlen] = '\0'; + + int method = Read2(cd+10); + + cd += 46 + nlen + xlen + mlen; + + if (method != 8) { // 8 == deflate + free(filename); + continue; + } + + unsigned char* lh = img + hoffset; + + if (!(lh[0] == 0x50 && lh[1] == 0x4b && lh[2] == 0x03 && lh[3] == 0x04)) { + printf("bad local file header entry %d\n", i); + return NULL; + } + + if (Read2(lh+26) != nlen || memcmp(lh+30, filename, nlen) != 0) { + printf("central dir filename doesn't match local header\n"); + return NULL; + } + + xlen = Read2(lh+28); // extra field len; might be different from CD entry? + + temp_entries[entrycount].data_offset = hoffset+30+nlen+xlen; + temp_entries[entrycount].deflate_len = clen; + temp_entries[entrycount].uncomp_len = ulen; + temp_entries[entrycount].filename = filename; + ++entrycount; + } + + qsort(temp_entries, entrycount, sizeof(ZipFileEntry), fileentry_compare); + +#if 0 + printf("found %d deflated entries\n", entrycount); + for (i = 0; i < entrycount; ++i) { + printf("off %10d len %10d unlen %10d %p %s\n", + temp_entries[i].data_offset, + temp_entries[i].deflate_len, + temp_entries[i].uncomp_len, + temp_entries[i].filename, + temp_entries[i].filename); + } +#endif + + *num_chunks = 0; + *chunks = reinterpret_cast(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 = reinterpret_cast(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; + } + + size_t sz = static_cast(st.st_size); + unsigned char* img = reinterpret_cast(malloc(sz + 4)); + FILE* f = fopen(filename, "rb"); + if (fread(img, 1, sz, f) != sz) { + printf("failed to read \"%s\" %s\n", filename, strerror(errno)); + fclose(f); + return NULL; + } + fclose(f); + + // append 4 zero bytes to the data so we can always search for the + // four-byte string 1f8b0800 starting at any point in the actual + // file data, without special-casing the end of the data. + memset(img+sz, 0, 4); + + size_t pos = 0; + + *num_chunks = 0; + *chunks = NULL; + + while (pos < sz) { + unsigned char* p = img+pos; + + if (sz - pos >= 4 && + p[0] == 0x1f && p[1] == 0x8b && + p[2] == 0x08 && // deflate compression + p[3] == 0x00) { // no header flags + // 'pos' is the offset of the start of a gzip chunk. + size_t chunk_offset = pos; + + *num_chunks += 3; + *chunks = reinterpret_cast(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 = reinterpret_cast(malloc(allocated)); + curr->start = pos; + curr->deflate_data = p; + + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = sz - pos; + strm.next_in = p; + + // -15 means we are decoding a 'raw' deflate stream; zlib will + // not expect zlib headers. + int ret = inflateInit2(&strm, -15); + + do { + strm.avail_out = allocated - curr->len; + strm.next_out = curr->data + curr->len; + ret = inflate(&strm, Z_NO_FLUSH); + if (ret < 0) { + printf("Error: inflate failed [%s] at file offset [%zu]\n" + "imgdiff only supports gzip kernel compression," + " did you try CONFIG_KERNEL_LZO?\n", + strm.msg, chunk_offset); + free(img); + return NULL; + } + curr->len = allocated - strm.avail_out; + if (strm.avail_out == 0) { + allocated *= 2; + curr->data = reinterpret_cast(realloc(curr->data, allocated)); + } + } while (ret != Z_STREAM_END); + + curr->deflate_len = sz - 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 %zu != decompressed size %zu\n", + footer_size, curr[-2].len); + free(img); + return NULL; + } + } else { + // Reallocate the list for every chunk; we expect the number of + // chunks to be small (5 for typical boot and recovery images). + ++*num_chunks; + *chunks = reinterpret_cast(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 < (sz - pos); ++curr->len) { + if (p[curr->len] == 0x1f && + p[curr->len+1] == 0x8b && + p[curr->len+2] == 0x08 && + p[curr->len+3] == 0x00) { + break; + } + } + pos += curr->len; + } + } + + return img; +} + +#define BUFFER_SIZE 32768 + +/* + * Takes the uncompressed data stored in the chunk, compresses it + * using the zlib parameters stored in the chunk, and checks that it + * matches exactly the compressed data we started with (also stored in + * the chunk). Return 0 on success. + */ +int TryReconstruction(ImageChunk* chunk, unsigned char* out) { + size_t p = 0; + +#if 0 + printf("trying %d %d %d %d %d\n", + chunk->level, chunk->method, chunk->windowBits, + chunk->memLevel, chunk->strategy); +#endif + + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = chunk->len; + strm.next_in = chunk->data; + int ret; + ret = deflateInit2(&strm, chunk->level, chunk->method, chunk->windowBits, + chunk->memLevel, chunk->strategy); + do { + strm.avail_out = BUFFER_SIZE; + strm.next_out = out; + ret = deflate(&strm, Z_FINISH); + size_t have = BUFFER_SIZE - strm.avail_out; + + if (memcmp(out, chunk->deflate_data+p, have) != 0) { + // mismatch; data isn't the same. + deflateEnd(&strm); + return -1; + } + p += have; + } while (ret != Z_STREAM_END); + deflateEnd(&strm); + if (p != chunk->deflate_len) { + // mismatch; ran out of data before we should have. + return -1; + } + return 0; +} + +/* + * Verify that we can reproduce exactly the same compressed data that + * we started with. Sets the level, method, windowBits, memLevel, and + * strategy fields in the chunk to the encoding parameters needed to + * produce the right output. Returns 0 on success. + */ +int ReconstructDeflateChunk(ImageChunk* chunk) { + if (chunk->type != CHUNK_DEFLATE) { + printf("attempt to reconstruct non-deflate chunk\n"); + return -1; + } + + size_t p = 0; + unsigned char* out = reinterpret_cast(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; + } + + size_t sz = static_cast(st.st_size); + // TODO: Memory leak on error return. + unsigned char* data = reinterpret_cast(malloc(sz)); + + if (tgt->type == CHUNK_NORMAL && tgt->len <= sz) { + unlink(ptemp); + + tgt->type = CHUNK_RAW; + *size = tgt->len; + return tgt->data; + } + + *size = sz; + + FILE* f = fopen(ptemp, "rb"); + if (f == NULL) { + printf("failed to open patch %s: %s\n", ptemp, strerror(errno)); + return NULL; + } + if (fread(data, 1, sz, f) != sz) { + printf("failed to read patch %s: %s\n", ptemp, strerror(errno)); + return NULL; + } + fclose(f); + + unlink(ptemp); + + tgt->source_start = src->start; + switch (tgt->type) { + case CHUNK_NORMAL: + tgt->source_len = src->len; + break; + case CHUNK_DEFLATE: + tgt->source_len = src->deflate_len; + tgt->source_uncompressed_len = src->len; + break; + } + + return data; +} + +/* + * Cause a gzip chunk to be treated as a normal chunk (ie, as a blob + * of uninterpreted data). The resulting patch will likely be about + * as big as the target file, but it lets us handle the case of images + * where some gzip chunks are reconstructible but others aren't (by + * treating the ones that aren't as normal chunks). + */ +void ChangeDeflateChunkToNormal(ImageChunk* ch) { + if (ch->type != CHUNK_DEFLATE) return; + ch->type = CHUNK_NORMAL; + free(ch->data); + ch->data = ch->deflate_data; + ch->len = ch->deflate_len; +} + +/* + * Return true if the data in the chunk is identical (including the + * compressed representation, for gzip chunks). + */ +int AreChunksEqual(ImageChunk* a, ImageChunk* b) { + if (a->type != b->type) return 0; + + switch (a->type) { + case CHUNK_NORMAL: + return a->len == b->len && memcmp(a->data, b->data, a->len) == 0; + + case CHUNK_DEFLATE: + return a->deflate_len == b->deflate_len && + memcmp(a->deflate_data, b->deflate_data, a->deflate_len) == 0; + + default: + printf("unknown chunk type %d\n", a->type); + return 0; + } +} + +/* + * Look for runs of adjacent normal chunks and compress them down into + * a single chunk. (Such runs can be produced when deflate chunks are + * changed to normal chunks.) + */ +void MergeAdjacentNormalChunks(ImageChunk* chunks, int* num_chunks) { + int out = 0; + int in_start = 0, in_end; + while (in_start < *num_chunks) { + if (chunks[in_start].type != CHUNK_NORMAL) { + in_end = in_start+1; + } else { + // in_start is a normal chunk. Look for a run of normal chunks + // that constitute a solid block of data (ie, each chunk begins + // where the previous one ended). + for (in_end = in_start+1; + in_end < *num_chunks && chunks[in_end].type == CHUNK_NORMAL && + (chunks[in_end].start == + chunks[in_end-1].start + chunks[in_end-1].len && + chunks[in_end].data == + chunks[in_end-1].data + chunks[in_end-1].len); + ++in_end); + } + + if (in_end == in_start+1) { +#if 0 + printf("chunk %d is now %d\n", in_start, out); +#endif + if (out != in_start) { + memcpy(chunks+out, chunks+in_start, sizeof(ImageChunk)); + } + } else { +#if 0 + printf("collapse normal chunks %d-%d into %d\n", in_start, in_end-1, out); +#endif + + // Merge chunks [in_start, in_end-1] into one chunk. Since the + // data member of each chunk is just a pointer into an in-memory + // copy of the file, this can be done without recopying (the + // output chunk has the first chunk's start location and data + // pointer, and length equal to the sum of the input chunk + // lengths). + chunks[out].type = CHUNK_NORMAL; + chunks[out].start = chunks[in_start].start; + chunks[out].data = chunks[in_start].data; + chunks[out].len = chunks[in_end-1].len + + (chunks[in_end-1].start - chunks[in_start].start); + } + + ++out; + in_start = in_end; + } + *num_chunks = out; +} + +ImageChunk* FindChunkByName(const char* name, + ImageChunk* chunks, int num_chunks) { + int i; + for (i = 0; i < num_chunks; ++i) { + if (chunks[i].type == CHUNK_DEFLATE && chunks[i].filename && + strcmp(name, chunks[i].filename) == 0) { + return chunks+i; + } + } + return NULL; +} + +void DumpChunks(ImageChunk* chunks, int num_chunks) { + for (int i = 0; i < num_chunks; ++i) { + printf("chunk %d: type %d start %zu len %zu\n", + i, chunks[i].type, chunks[i].start, chunks[i].len); + } +} + +int main(int argc, char** argv) { + int zip_mode = 0; + + if (argc >= 2 && strcmp(argv[1], "-z") == 0) { + zip_mode = 1; + --argc; + ++argv; + } + + size_t bonus_size = 0; + unsigned char* bonus_data = NULL; + if (argc >= 3 && strcmp(argv[1], "-b") == 0) { + struct stat st; + if (stat(argv[2], &st) != 0) { + printf("failed to stat bonus file %s: %s\n", argv[2], strerror(errno)); + return 1; + } + bonus_size = st.st_size; + bonus_data = reinterpret_cast(malloc(bonus_size)); + FILE* f = fopen(argv[2], "rb"); + if (f == NULL) { + printf("failed to open bonus file %s: %s\n", argv[2], strerror(errno)); + return 1; + } + if (fread(bonus_data, 1, bonus_size, f) != bonus_size) { + printf("failed to read bonus file %s: %s\n", argv[2], strerror(errno)); + return 1; + } + fclose(f); + + argc -= 2; + argv += 2; + } + + if (argc != 4) { + usage: + printf("usage: %s [-z] [-b ] \n", + argv[0]); + return 2; + } + + int num_src_chunks; + ImageChunk* src_chunks; + int num_tgt_chunks; + ImageChunk* tgt_chunks; + int i; + + if (zip_mode) { + if (ReadZip(argv[1], &num_src_chunks, &src_chunks, 1) == NULL) { + printf("failed to break apart source zip file\n"); + return 1; + } + if (ReadZip(argv[2], &num_tgt_chunks, &tgt_chunks, 0) == NULL) { + printf("failed to break apart target zip file\n"); + return 1; + } + } else { + if (ReadImage(argv[1], &num_src_chunks, &src_chunks) == NULL) { + printf("failed to break apart source image\n"); + return 1; + } + if (ReadImage(argv[2], &num_tgt_chunks, &tgt_chunks) == NULL) { + printf("failed to break apart target image\n"); + return 1; + } + + // Verify that the source and target images have the same chunk + // structure (ie, the same sequence of deflate and normal chunks). + + if (!zip_mode) { + // Merge the gzip header and footer in with any adjacent + // normal chunks. + MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks); + MergeAdjacentNormalChunks(src_chunks, &num_src_chunks); + } + + if (num_src_chunks != num_tgt_chunks) { + printf("source and target don't have same number of chunks!\n"); + printf("source chunks:\n"); + DumpChunks(src_chunks, num_src_chunks); + printf("target chunks:\n"); + DumpChunks(tgt_chunks, num_tgt_chunks); + return 1; + } + for (i = 0; i < num_src_chunks; ++i) { + if (src_chunks[i].type != tgt_chunks[i].type) { + printf("source and target don't have same chunk " + "structure! (chunk %d)\n", i); + printf("source chunks:\n"); + DumpChunks(src_chunks, num_src_chunks); + printf("target chunks:\n"); + DumpChunks(tgt_chunks, num_tgt_chunks); + return 1; + } + } + } + + for (i = 0; i < num_tgt_chunks; ++i) { + if (tgt_chunks[i].type == CHUNK_DEFLATE) { + // Confirm that given the uncompressed chunk data in the target, we + // can recompress it and get exactly the same bits as are in the + // input target image. If this fails, treat the chunk as a normal + // non-deflated chunk. + if (ReconstructDeflateChunk(tgt_chunks+i) < 0) { + printf("failed to reconstruct target deflate chunk %d [%s]; " + "treating as normal\n", i, tgt_chunks[i].filename); + ChangeDeflateChunkToNormal(tgt_chunks+i); + if (zip_mode) { + ImageChunk* src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks); + if (src) { + ChangeDeflateChunkToNormal(src); + } + } else { + ChangeDeflateChunkToNormal(src_chunks+i); + } + continue; + } + + // If two deflate chunks are identical (eg, the kernel has not + // changed between two builds), treat them as normal chunks. + // This makes applypatch much faster -- it can apply a trivial + // patch to the compressed data, rather than uncompressing and + // recompressing to apply the trivial patch to the uncompressed + // data. + ImageChunk* src; + if (zip_mode) { + src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks); + } else { + src = src_chunks+i; + } + + if (src == NULL || AreChunksEqual(tgt_chunks+i, src)) { + ChangeDeflateChunkToNormal(tgt_chunks+i); + if (src) { + ChangeDeflateChunkToNormal(src); + } + } + } + } + + // Merging neighboring normal chunks. + if (zip_mode) { + // For zips, we only need to do this to the target: deflated + // chunks are matched via filename, and normal chunks are patched + // using the entire source file as the source. + MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks); + } else { + // For images, we need to maintain the parallel structure of the + // chunk lists, so do the merging in both the source and target + // lists. + MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks); + MergeAdjacentNormalChunks(src_chunks, &num_src_chunks); + if (num_src_chunks != num_tgt_chunks) { + // This shouldn't happen. + printf("merging normal chunks went awry\n"); + return 1; + } + } + + // Compute bsdiff patches for each chunk's data (the uncompressed + // data, in the case of deflate chunks). + + DumpChunks(src_chunks, num_src_chunks); + + printf("Construct patches for %d chunks...\n", num_tgt_chunks); + unsigned char** patch_data = reinterpret_cast(malloc( + num_tgt_chunks * sizeof(unsigned char*))); + size_t* patch_size = reinterpret_cast(malloc(num_tgt_chunks * sizeof(size_t))); + for (i = 0; i < num_tgt_chunks; ++i) { + if (zip_mode) { + ImageChunk* src; + if (tgt_chunks[i].type == CHUNK_DEFLATE && + (src = FindChunkByName(tgt_chunks[i].filename, src_chunks, + num_src_chunks))) { + patch_data[i] = MakePatch(src, tgt_chunks+i, patch_size+i); + } else { + patch_data[i] = MakePatch(src_chunks, tgt_chunks+i, patch_size+i); + } + } else { + if (i == 1 && bonus_data) { + printf(" using %zu bytes of bonus data for chunk %d\n", bonus_size, i); + src_chunks[i].data = reinterpret_cast(realloc(src_chunks[i].data, + src_chunks[i].len + bonus_size)); + memcpy(src_chunks[i].data+src_chunks[i].len, bonus_data, bonus_size); + src_chunks[i].len += bonus_size; + } + + patch_data[i] = MakePatch(src_chunks+i, tgt_chunks+i, patch_size+i); + } + printf("patch %3d is %zu bytes (of %zu)\n", + i, patch_size[i], tgt_chunks[i].source_len); + } + + // Figure out how big the imgdiff file header is going to be, so + // that we can correctly compute the offset of each bsdiff patch + // within the file. + + size_t total_header_size = 12; + for (i = 0; i < num_tgt_chunks; ++i) { + total_header_size += 4; + switch (tgt_chunks[i].type) { + case CHUNK_NORMAL: + total_header_size += 8*3; + break; + case CHUNK_DEFLATE: + total_header_size += 8*5 + 4*5; + break; + case CHUNK_RAW: + total_header_size += 4 + patch_size[i]; + break; + } + } + + size_t offset = total_header_size; + + FILE* f = fopen(argv[3], "wb"); + + // Write out the headers. + + fwrite("IMGDIFF2", 1, 8, f); + Write4(num_tgt_chunks, f); + for (i = 0; i < num_tgt_chunks; ++i) { + Write4(tgt_chunks[i].type, f); + + switch (tgt_chunks[i].type) { + case CHUNK_NORMAL: + printf("chunk %3d: normal (%10zu, %10zu) %10zu\n", i, + tgt_chunks[i].start, tgt_chunks[i].len, patch_size[i]); + Write8(tgt_chunks[i].source_start, f); + Write8(tgt_chunks[i].source_len, f); + Write8(offset, f); + offset += patch_size[i]; + break; + + case CHUNK_DEFLATE: + printf("chunk %3d: deflate (%10zu, %10zu) %10zu %s\n", i, + tgt_chunks[i].start, tgt_chunks[i].deflate_len, patch_size[i], + tgt_chunks[i].filename); + Write8(tgt_chunks[i].source_start, f); + Write8(tgt_chunks[i].source_len, f); + Write8(offset, f); + Write8(tgt_chunks[i].source_uncompressed_len, f); + Write8(tgt_chunks[i].len, f); + Write4(tgt_chunks[i].level, f); + Write4(tgt_chunks[i].method, f); + Write4(tgt_chunks[i].windowBits, f); + Write4(tgt_chunks[i].memLevel, f); + Write4(tgt_chunks[i].strategy, f); + offset += patch_size[i]; + break; + + case CHUNK_RAW: + printf("chunk %3d: raw (%10zu, %10zu)\n", i, + tgt_chunks[i].start, tgt_chunks[i].len); + Write4(patch_size[i], f); + fwrite(patch_data[i], 1, patch_size[i], f); + break; + } + } + + // Append each chunk's bsdiff patch, in order. + + for (i = 0; i < num_tgt_chunks; ++i) { + if (tgt_chunks[i].type != CHUNK_RAW) { + fwrite(patch_data[i], 1, patch_size[i], f); + } + } + + fclose(f); + + return 0; +} diff --git a/applypatch/imgpatch.c b/applypatch/imgpatch.c deleted file mode 100644 index 09b0a7397..000000000 --- a/applypatch/imgpatch.c +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// See imgdiff.c in this directory for a description of the patch file -// format. - -#include -#include -#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 __unused, - const Value* patch, - SinkFn sink, void* token, SHA_CTX* ctx, - const Value* bonus_data) { - ssize_t pos = 12; - char* header = patch->data; - if (patch->size < 12) { - printf("patch too short to contain header\n"); - return -1; - } - - // IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW. - // (IMGDIFF1, which is no longer supported, used CHUNK_NORMAL and - // CHUNK_GZIP.) - if (memcmp(header, "IMGDIFF2", 8) != 0) { - printf("corrupt patch file header (magic number)\n"); - return -1; - } - - int num_chunks = Read4(header+8); - - int i; - for (i = 0; i < num_chunks; ++i) { - // each chunk's header record starts with 4 bytes. - if (pos + 4 > patch->size) { - printf("failed to read chunk %d record\n", i); - return -1; - } - int type = Read4(patch->data + pos); - pos += 4; - - if (type == CHUNK_NORMAL) { - char* normal_header = patch->data + pos; - pos += 24; - if (pos > patch->size) { - printf("failed to read chunk %d normal header data\n", i); - return -1; - } - - size_t src_start = Read8(normal_header); - size_t src_len = Read8(normal_header+8); - size_t patch_offset = Read8(normal_header+16); - - ApplyBSDiffPatch(old_data + src_start, src_len, - patch, patch_offset, sink, token, ctx); - } else if (type == CHUNK_RAW) { - char* raw_header = patch->data + pos; - pos += 4; - if (pos > patch->size) { - printf("failed to read chunk %d raw header data\n", i); - return -1; - } - - ssize_t data_len = Read4(raw_header); - - if (pos + data_len > patch->size) { - printf("failed to read chunk %d raw data\n", i); - return -1; - } - if (ctx) 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. - - // Note: expanded_len will include the bonus data size if - // the patch was constructed with bonus data. The - // deflation will come up 'bonus_size' bytes short; these - // must be appended from the bonus_data value. - size_t bonus_size = (i == 1 && bonus_data != NULL) ? bonus_data->size : 0; - - unsigned char* expanded_source = malloc(expanded_len); - if (expanded_source == NULL) { - printf("failed to allocate %zu 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, except - // for the bonus_size. - if (strm.avail_out != bonus_size) { - printf("source inflation short by %zu bytes\n", strm.avail_out-bonus_size); - return -1; - } - inflateEnd(&strm); - - if (bonus_size) { - memcpy(expanded_source + (expanded_len - bonus_size), - bonus_data->data, bonus_size); - } - - // 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; - } - if (ctx) 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/imgpatch.cpp b/applypatch/imgpatch.cpp new file mode 100644 index 000000000..26888f8ee --- /dev/null +++ b/applypatch/imgpatch.cpp @@ -0,0 +1,234 @@ +/* + * 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 +#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 __unused, + const Value* patch, + SinkFn sink, void* token, SHA_CTX* ctx, + const Value* bonus_data) { + ssize_t pos = 12; + char* header = patch->data; + if (patch->size < 12) { + printf("patch too short to contain header\n"); + return -1; + } + + // IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW. + // (IMGDIFF1, which is no longer supported, used CHUNK_NORMAL and + // CHUNK_GZIP.) + if (memcmp(header, "IMGDIFF2", 8) != 0) { + printf("corrupt patch file header (magic number)\n"); + return -1; + } + + int num_chunks = Read4(header+8); + + int i; + for (i = 0; i < num_chunks; ++i) { + // each chunk's header record starts with 4 bytes. + if (pos + 4 > patch->size) { + printf("failed to read chunk %d record\n", i); + return -1; + } + int type = Read4(patch->data + pos); + pos += 4; + + if (type == CHUNK_NORMAL) { + char* normal_header = patch->data + pos; + pos += 24; + if (pos > patch->size) { + printf("failed to read chunk %d normal header data\n", i); + return -1; + } + + size_t src_start = Read8(normal_header); + size_t src_len = Read8(normal_header+8); + size_t patch_offset = Read8(normal_header+16); + + ApplyBSDiffPatch(old_data + src_start, src_len, + patch, patch_offset, sink, token, ctx); + } else if (type == CHUNK_RAW) { + char* raw_header = patch->data + pos; + pos += 4; + if (pos > patch->size) { + printf("failed to read chunk %d raw header data\n", i); + return -1; + } + + ssize_t data_len = Read4(raw_header); + + if (pos + data_len > patch->size) { + printf("failed to read chunk %d raw data\n", i); + return -1; + } + if (ctx) 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. + + // Note: expanded_len will include the bonus data size if + // the patch was constructed with bonus data. The + // deflation will come up 'bonus_size' bytes short; these + // must be appended from the bonus_data value. + size_t bonus_size = (i == 1 && bonus_data != NULL) ? bonus_data->size : 0; + + unsigned char* expanded_source = reinterpret_cast(malloc(expanded_len)); + if (expanded_source == NULL) { + printf("failed to allocate %zu 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, except + // for the bonus_size. + if (strm.avail_out != bonus_size) { + printf("source inflation short by %zu bytes\n", strm.avail_out-bonus_size); + return -1; + } + inflateEnd(&strm); + + if (bonus_size) { + memcpy(expanded_source + (expanded_len - bonus_size), + bonus_data->data, bonus_size); + } + + // 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 = reinterpret_cast(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; + } + if (ctx) 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 deleted file mode 100644 index 8e9fe80ef..000000000 --- a/applypatch/main.c +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#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) { - Value* bonus = NULL; - if (argc >= 3 && strcmp(argv[1], "-b") == 0) { - FileContents fc; - if (LoadFileContents(argv[2], &fc) != 0) { - printf("failed to load bonus file %s\n", argv[2]); - return 1; - } - bonus = malloc(sizeof(Value)); - bonus->type = VAL_BLOB; - bonus->size = fc.size; - bonus->data = (char*)fc.data; - argc -= 2; - argv += 2; - } - - 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, bonus); - - int i; - for (i = 0; i < num_patches; ++i) { - Value* p = patches[i]; - if (p != NULL) { - free(p->data); - free(p); - } - } - if (bonus) { - free(bonus->data); - free(bonus); - } - 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 [-b ] " - "[: ...]\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/main.cpp b/applypatch/main.cpp new file mode 100644 index 000000000..63ff5c2c0 --- /dev/null +++ b/applypatch/main.cpp @@ -0,0 +1,213 @@ +/* + * 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" + +static int CheckMode(int argc, char** argv) { + if (argc < 3) { + return 2; + } + return applypatch_check(argv[2], argc-3, argv+3); +} + +static int SpaceMode(int argc, char** argv) { + if (argc != 3) { + return 2; + } + char* endptr; + size_t bytes = strtol(argv[2], &endptr, 10); + if (bytes == 0 && endptr == argv[2]) { + printf("can't parse \"%s\" as byte count\n\n", argv[2]); + return 1; + } + return CacheSizeCheck(bytes); +} + +// Parse arguments (which should be of the form "" or +// ":" into the new parallel arrays *sha1s and +// *patches (loading file contents into the patches). Returns true on +// success. +static bool ParsePatchArgs(int argc, char** argv, + char*** sha1s, Value*** patches, int* num_patches) { + *num_patches = argc; + *sha1s = reinterpret_cast(malloc(*num_patches * sizeof(char*))); + *patches = reinterpret_cast(malloc(*num_patches * sizeof(Value*))); + memset(*patches, 0, *num_patches * sizeof(Value*)); + + uint8_t digest[SHA_DIGEST_SIZE]; + + for (int 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 false; + } + + (*sha1s)[i] = argv[i]; + if (colon == NULL) { + (*patches)[i] = NULL; + } else { + FileContents fc; + if (LoadFileContents(colon, &fc) != 0) { + goto abort; + } + (*patches)[i] = reinterpret_cast(malloc(sizeof(Value))); + (*patches)[i]->type = VAL_BLOB; + (*patches)[i]->size = fc.size; + (*patches)[i]->data = reinterpret_cast(fc.data); + } + } + + return true; + + abort: + for (int i = 0; i < *num_patches; ++i) { + Value* p = (*patches)[i]; + if (p != NULL) { + free(p->data); + free(p); + } + } + free(*sha1s); + free(*patches); + return false; +} + +int PatchMode(int argc, char** argv) { + Value* bonus = NULL; + if (argc >= 3 && strcmp(argv[1], "-b") == 0) { + FileContents fc; + if (LoadFileContents(argv[2], &fc) != 0) { + printf("failed to load bonus file %s\n", argv[2]); + return 1; + } + bonus = reinterpret_cast(malloc(sizeof(Value))); + bonus->type = VAL_BLOB; + bonus->size = fc.size; + bonus->data = (char*)fc.data; + argc -= 2; + argv += 2; + } + + 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)) { + printf("failed to parse patch args\n"); + return 1; + } + + int result = applypatch(argv[1], argv[2], argv[3], target_size, + num_patches, sha1s, patches, bonus); + + int i; + for (i = 0; i < num_patches; ++i) { + Value* p = patches[i]; + if (p != NULL) { + free(p->data); + free(p); + } + } + if (bonus) { + free(bonus->data); + free(bonus); + } + 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 [-b ] " + "[: ...]\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/utils.c b/applypatch/utils.c deleted file mode 100644 index 41ff676dc..000000000 --- a/applypatch/utils.c +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#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.cpp b/applypatch/utils.cpp new file mode 100644 index 000000000..4a80be75f --- /dev/null +++ b/applypatch/utils.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#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 = reinterpret_cast(pv); + return (int)(((unsigned int)p[1] << 8) | + (unsigned int)p[0]); +} + +int Read4(void* pv) { + unsigned char* p = reinterpret_cast(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 = reinterpret_cast(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/minzip/Hash.h b/minzip/Hash.h index 8194537f3..e83eac414 100644 --- a/minzip/Hash.h +++ b/minzip/Hash.h @@ -15,6 +15,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /* compute the hash of an item with a specific type */ typedef unsigned int (*HashCompute)(const void* item); @@ -183,4 +187,8 @@ typedef unsigned int (*HashCalcFunc)(const void* item); void mzHashTableProbeCount(HashTable* pHashTable, HashCalcFunc calcFunc, HashCompareFunc cmpFunc); +#ifdef __cplusplus +} +#endif + #endif /*_MINZIP_HASH*/ diff --git a/roots.cpp b/roots.cpp index 2bd457efe..9288177e7 100644 --- a/roots.cpp +++ b/roots.cpp @@ -30,10 +30,8 @@ #include "roots.h" #include "common.h" #include "make_ext4fs.h" -extern "C" { #include "wipe.h" #include "cryptfs.h" -} static struct fstab *fstab = NULL; diff --git a/updater/Android.mk b/updater/Android.mk index a0ea06fa5..0d4179b23 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -1,11 +1,23 @@ # Copyright 2009 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. LOCAL_PATH := $(call my-dir) updater_src_files := \ - install.c \ - blockimg.c \ - updater.c + install.cpp \ + blockimg.cpp \ + updater.cpp # # Build a statically-linked binary to include in OTA packages diff --git a/updater/blockimg.c b/updater/blockimg.c deleted file mode 100644 index e0be03917..000000000 --- a/updater/blockimg.c +++ /dev/null @@ -1,2014 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "applypatch/applypatch.h" -#include "edify/expr.h" -#include "mincrypt/sha.h" -#include "minzip/Hash.h" -#include "updater.h" - -#define BLOCKSIZE 4096 - -// Set this to 0 to interpret 'erase' transfers to mean do a -// BLKDISCARD ioctl (the normal behavior). Set to 1 to interpret -// erase to mean fill the region with zeroes. -#define DEBUG_ERASE 0 - -#ifndef BLKDISCARD -#define BLKDISCARD _IO(0x12,119) -#endif - -#define STASH_DIRECTORY_BASE "/cache/recovery" -#define STASH_DIRECTORY_MODE 0700 -#define STASH_FILE_MODE 0600 - -char* PrintSha1(const uint8_t* digest); - -typedef struct { - int count; - int size; - int pos[0]; -} RangeSet; - -#define RANGESET_MAX_POINTS \ - ((int)((INT_MAX / sizeof(int)) - sizeof(RangeSet))) - -static RangeSet* parse_range(char* text) { - char* save; - char* token; - int i, num; - long int val; - RangeSet* out = NULL; - size_t bufsize; - - if (!text) { - goto err; - } - - token = strtok_r(text, ",", &save); - - if (!token) { - goto err; - } - - val = strtol(token, NULL, 0); - - if (val < 2 || val > RANGESET_MAX_POINTS) { - goto err; - } else if (val % 2) { - goto err; // must be even - } - - num = (int) val; - bufsize = sizeof(RangeSet) + num * sizeof(int); - - out = malloc(bufsize); - - if (!out) { - fprintf(stderr, "failed to allocate range of %zu bytes\n", bufsize); - goto err; - } - - out->count = num / 2; - out->size = 0; - - for (i = 0; i < num; ++i) { - token = strtok_r(NULL, ",", &save); - - if (!token) { - goto err; - } - - val = strtol(token, NULL, 0); - - if (val < 0 || val > INT_MAX) { - goto err; - } - - out->pos[i] = (int) val; - - if (i % 2) { - if (out->pos[i - 1] >= out->pos[i]) { - goto err; // empty or negative range - } - - if (out->size > INT_MAX - out->pos[i]) { - goto err; // overflow - } - - out->size += out->pos[i]; - } else { - if (out->size < 0) { - goto err; - } - - out->size -= out->pos[i]; - } - } - - if (out->size <= 0) { - goto err; - } - - return out; - -err: - fprintf(stderr, "failed to parse range '%s'\n", text ? text : "NULL"); - exit(1); -} - -static int range_overlaps(RangeSet* r1, RangeSet* r2) { - int i, j, r1_0, r1_1, r2_0, r2_1; - - if (!r1 || !r2) { - return 0; - } - - for (i = 0; i < r1->count; ++i) { - r1_0 = r1->pos[i * 2]; - r1_1 = r1->pos[i * 2 + 1]; - - for (j = 0; j < r2->count; ++j) { - r2_0 = r2->pos[j * 2]; - r2_1 = r2->pos[j * 2 + 1]; - - if (!(r2_0 >= r1_1 || r1_0 >= r2_1)) { - return 1; - } - } - } - - return 0; -} - -static int read_all(int fd, uint8_t* data, size_t size) { - size_t so_far = 0; - while (so_far < size) { - ssize_t r = TEMP_FAILURE_RETRY(read(fd, data+so_far, size-so_far)); - if (r == -1) { - fprintf(stderr, "read failed: %s\n", strerror(errno)); - return -1; - } - so_far += r; - } - return 0; -} - -static int write_all(int fd, const uint8_t* data, size_t size) { - size_t written = 0; - while (written < size) { - ssize_t w = TEMP_FAILURE_RETRY(write(fd, data+written, size-written)); - if (w == -1) { - fprintf(stderr, "write failed: %s\n", strerror(errno)); - return -1; - } - written += w; - } - - if (fsync(fd) == -1) { - fprintf(stderr, "fsync failed: %s\n", strerror(errno)); - return -1; - } - - return 0; -} - -static bool check_lseek(int fd, off64_t offset, int whence) { - off64_t rc = TEMP_FAILURE_RETRY(lseek64(fd, offset, whence)); - if (rc == -1) { - fprintf(stderr, "lseek64 failed: %s\n", strerror(errno)); - return false; - } - return true; -} - -static void allocate(size_t size, uint8_t** buffer, size_t* buffer_alloc) { - // if the buffer's big enough, reuse it. - if (size <= *buffer_alloc) return; - - free(*buffer); - - *buffer = (uint8_t*) malloc(size); - if (*buffer == NULL) { - fprintf(stderr, "failed to allocate %zu bytes\n", size); - exit(1); - } - *buffer_alloc = size; -} - -typedef struct { - int fd; - RangeSet* tgt; - int p_block; - size_t p_remain; -} RangeSinkState; - -static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) { - RangeSinkState* rss = (RangeSinkState*) token; - - if (rss->p_remain <= 0) { - fprintf(stderr, "range sink write overrun"); - return 0; - } - - ssize_t written = 0; - while (size > 0) { - size_t write_now = size; - - if (rss->p_remain < write_now) { - write_now = rss->p_remain; - } - - if (write_all(rss->fd, data, write_now) == -1) { - break; - } - - data += write_now; - size -= write_now; - - rss->p_remain -= write_now; - written += write_now; - - if (rss->p_remain == 0) { - // move to the next block - ++rss->p_block; - if (rss->p_block < rss->tgt->count) { - rss->p_remain = (rss->tgt->pos[rss->p_block * 2 + 1] - - rss->tgt->pos[rss->p_block * 2]) * BLOCKSIZE; - - if (!check_lseek(rss->fd, (off64_t)rss->tgt->pos[rss->p_block*2] * BLOCKSIZE, - SEEK_SET)) { - break; - } - } else { - // we can't write any more; return how many bytes have - // been written so far. - break; - } - } - } - - return written; -} - -// All of the data for all the 'new' transfers is contained in one -// file in the update package, concatenated together in the order in -// which transfers.list will need it. We want to stream it out of the -// archive (it's compressed) without writing it to a temp file, but we -// can't write each section until it's that transfer's turn to go. -// -// To achieve this, we expand the new data from the archive in a -// background thread, and block that threads 'receive uncompressed -// data' function until the main thread has reached a point where we -// want some new data to be written. We signal the background thread -// with the destination for the data and block the main thread, -// waiting for the background thread to complete writing that section. -// Then it signals the main thread to wake up and goes back to -// blocking waiting for a transfer. -// -// NewThreadInfo is the struct used to pass information back and forth -// between the two threads. When the main thread wants some data -// written, it sets rss to the destination location and signals the -// condition. When the background thread is done writing, it clears -// rss and signals the condition again. - -typedef struct { - ZipArchive* za; - const ZipEntry* entry; - - RangeSinkState* rss; - - pthread_mutex_t mu; - pthread_cond_t cv; -} NewThreadInfo; - -static bool receive_new_data(const unsigned char* data, int size, void* cookie) { - NewThreadInfo* nti = (NewThreadInfo*) cookie; - - while (size > 0) { - // Wait for nti->rss to be non-NULL, indicating some of this - // data is wanted. - pthread_mutex_lock(&nti->mu); - while (nti->rss == NULL) { - pthread_cond_wait(&nti->cv, &nti->mu); - } - pthread_mutex_unlock(&nti->mu); - - // At this point nti->rss is set, and we own it. The main - // thread is waiting for it to disappear from nti. - ssize_t written = RangeSinkWrite(data, size, nti->rss); - data += written; - size -= written; - - if (nti->rss->p_block == nti->rss->tgt->count) { - // we have written all the bytes desired by this rss. - - pthread_mutex_lock(&nti->mu); - nti->rss = NULL; - pthread_cond_broadcast(&nti->cv); - pthread_mutex_unlock(&nti->mu); - } - } - - return true; -} - -static void* unzip_new_data(void* cookie) { - NewThreadInfo* nti = (NewThreadInfo*) cookie; - mzProcessZipEntryContents(nti->za, nti->entry, receive_new_data, nti); - return NULL; -} - -static int ReadBlocks(RangeSet* src, uint8_t* buffer, int fd) { - int i; - size_t p = 0; - size_t size; - - if (!src || !buffer) { - return -1; - } - - for (i = 0; i < src->count; ++i) { - if (!check_lseek(fd, (off64_t) src->pos[i * 2] * BLOCKSIZE, SEEK_SET)) { - return -1; - } - - size = (src->pos[i * 2 + 1] - src->pos[i * 2]) * BLOCKSIZE; - - if (read_all(fd, buffer + p, size) == -1) { - return -1; - } - - p += size; - } - - return 0; -} - -static int WriteBlocks(RangeSet* tgt, uint8_t* buffer, int fd) { - int i; - size_t p = 0; - size_t size; - - if (!tgt || !buffer) { - return -1; - } - - for (i = 0; i < tgt->count; ++i) { - if (!check_lseek(fd, (off64_t) tgt->pos[i * 2] * BLOCKSIZE, SEEK_SET)) { - return -1; - } - - size = (tgt->pos[i * 2 + 1] - tgt->pos[i * 2]) * BLOCKSIZE; - - if (write_all(fd, buffer + p, size) == -1) { - return -1; - } - - p += size; - } - - return 0; -} - -// Do a source/target load for move/bsdiff/imgdiff in version 1. -// 'wordsave' is the save_ptr of a strtok_r()-in-progress. We expect -// to parse the remainder of the string as: -// -// -// -// The source range is loaded into the provided buffer, reallocating -// it to make it larger if necessary. The target ranges are returned -// in *tgt, if tgt is non-NULL. - -static int LoadSrcTgtVersion1(char** wordsave, RangeSet** tgt, int* src_blocks, - uint8_t** buffer, size_t* buffer_alloc, int fd) { - char* word; - int rc; - - word = strtok_r(NULL, " ", wordsave); - RangeSet* src = parse_range(word); - - if (tgt != NULL) { - word = strtok_r(NULL, " ", wordsave); - *tgt = parse_range(word); - } - - allocate(src->size * BLOCKSIZE, buffer, buffer_alloc); - rc = ReadBlocks(src, *buffer, fd); - *src_blocks = src->size; - - free(src); - return rc; -} - -static int VerifyBlocks(const char *expected, const uint8_t *buffer, - size_t blocks, int printerror) { - char* hexdigest = NULL; - int rc = -1; - uint8_t digest[SHA_DIGEST_SIZE]; - - if (!expected || !buffer) { - return rc; - } - - SHA_hash(buffer, blocks * BLOCKSIZE, digest); - hexdigest = PrintSha1(digest); - - if (hexdigest != NULL) { - rc = strcmp(expected, hexdigest); - - if (rc != 0 && printerror) { - fprintf(stderr, "failed to verify blocks (expected %s, read %s)\n", - expected, hexdigest); - } - - free(hexdigest); - } - - return rc; -} - -static char* GetStashFileName(const char* base, const char* id, const char* postfix) { - char* fn; - int len; - int res; - - if (base == NULL) { - return NULL; - } - - if (id == NULL) { - id = ""; - } - - if (postfix == NULL) { - postfix = ""; - } - - len = strlen(STASH_DIRECTORY_BASE) + 1 + strlen(base) + 1 + strlen(id) + strlen(postfix) + 1; - fn = malloc(len); - - if (fn == NULL) { - fprintf(stderr, "failed to malloc %d bytes for fn\n", len); - return NULL; - } - - res = snprintf(fn, len, STASH_DIRECTORY_BASE "/%s/%s%s", base, id, postfix); - - if (res < 0 || res >= len) { - fprintf(stderr, "failed to format file name (return value %d)\n", res); - free(fn); - return NULL; - } - - return fn; -} - -typedef void (*StashCallback)(const char*, void*); - -// Does a best effort enumeration of stash files. Ignores possible non-file -// items in the stash directory and continues despite of errors. Calls the -// 'callback' function for each file and passes 'data' to the function as a -// parameter. - -static void EnumerateStash(const char* dirname, StashCallback callback, void* data) { - char* fn; - DIR* directory; - int len; - int res; - struct dirent* item; - - if (dirname == NULL || callback == NULL) { - return; - } - - directory = opendir(dirname); - - if (directory == NULL) { - if (errno != ENOENT) { - fprintf(stderr, "opendir \"%s\" failed: %s\n", dirname, strerror(errno)); - } - return; - } - - while ((item = readdir(directory)) != NULL) { - if (item->d_type != DT_REG) { - continue; - } - - len = strlen(dirname) + 1 + strlen(item->d_name) + 1; - fn = malloc(len); - - if (fn == NULL) { - fprintf(stderr, "failed to malloc %d bytes for fn\n", len); - continue; - } - - res = snprintf(fn, len, "%s/%s", dirname, item->d_name); - - if (res < 0 || res >= len) { - fprintf(stderr, "failed to format file name (return value %d)\n", res); - free(fn); - continue; - } - - callback(fn, data); - free(fn); - } - - if (closedir(directory) == -1) { - fprintf(stderr, "closedir \"%s\" failed: %s\n", dirname, strerror(errno)); - } -} - -static void UpdateFileSize(const char* fn, void* data) { - int* size = (int*) data; - struct stat st; - - if (!fn || !data) { - return; - } - - if (stat(fn, &st) == -1) { - fprintf(stderr, "stat \"%s\" failed: %s\n", fn, strerror(errno)); - return; - } - - *size += st.st_size; -} - -// Deletes the stash directory and all files in it. Assumes that it only -// contains files. There is nothing we can do about unlikely, but possible -// errors, so they are merely logged. - -static void DeleteFile(const char* fn, void* data) { - if (fn) { - fprintf(stderr, "deleting %s\n", fn); - - if (unlink(fn) == -1 && errno != ENOENT) { - fprintf(stderr, "unlink \"%s\" failed: %s\n", fn, strerror(errno)); - } - } -} - -static void DeletePartial(const char* fn, void* data) { - if (fn && strstr(fn, ".partial") != NULL) { - DeleteFile(fn, data); - } -} - -static void DeleteStash(const char* base) { - char* dirname; - - if (base == NULL) { - return; - } - - dirname = GetStashFileName(base, NULL, NULL); - - if (dirname == NULL) { - return; - } - - fprintf(stderr, "deleting stash %s\n", base); - EnumerateStash(dirname, DeleteFile, NULL); - - if (rmdir(dirname) == -1) { - if (errno != ENOENT && errno != ENOTDIR) { - fprintf(stderr, "rmdir \"%s\" failed: %s\n", dirname, strerror(errno)); - } - } - - free(dirname); -} - -static int LoadStash(const char* base, const char* id, int verify, int* blocks, uint8_t** buffer, - size_t* buffer_alloc, int printnoent) { - char *fn = NULL; - int blockcount = 0; - int fd = -1; - int rc = -1; - int res; - struct stat st; - - if (!base || !id || !buffer || !buffer_alloc) { - goto lsout; - } - - if (!blocks) { - blocks = &blockcount; - } - - fn = GetStashFileName(base, id, NULL); - - if (fn == NULL) { - goto lsout; - } - - res = stat(fn, &st); - - if (res == -1) { - if (errno != ENOENT || printnoent) { - fprintf(stderr, "stat \"%s\" failed: %s\n", fn, strerror(errno)); - } - goto lsout; - } - - fprintf(stderr, " loading %s\n", fn); - - if ((st.st_size % BLOCKSIZE) != 0) { - fprintf(stderr, "%s size %zd not multiple of block size %d", fn, st.st_size, BLOCKSIZE); - goto lsout; - } - - fd = TEMP_FAILURE_RETRY(open(fn, O_RDONLY)); - - if (fd == -1) { - fprintf(stderr, "open \"%s\" failed: %s\n", fn, strerror(errno)); - goto lsout; - } - - allocate(st.st_size, buffer, buffer_alloc); - - if (read_all(fd, *buffer, st.st_size) == -1) { - goto lsout; - } - - *blocks = st.st_size / BLOCKSIZE; - - if (verify && VerifyBlocks(id, *buffer, *blocks, 1) != 0) { - fprintf(stderr, "unexpected contents in %s\n", fn); - DeleteFile(fn, NULL); - goto lsout; - } - - rc = 0; - -lsout: - if (fd != -1) { - close(fd); - } - - if (fn) { - free(fn); - } - - return rc; -} - -static int WriteStash(const char* base, const char* id, int blocks, uint8_t* buffer, - int checkspace, int *exists) { - char *fn = NULL; - char *cn = NULL; - int fd = -1; - int rc = -1; - int dfd = -1; - int res; - struct stat st; - - if (base == NULL || buffer == NULL) { - goto wsout; - } - - if (checkspace && CacheSizeCheck(blocks * BLOCKSIZE) != 0) { - fprintf(stderr, "not enough space to write stash\n"); - goto wsout; - } - - fn = GetStashFileName(base, id, ".partial"); - cn = GetStashFileName(base, id, NULL); - - if (fn == NULL || cn == NULL) { - goto wsout; - } - - if (exists) { - res = stat(cn, &st); - - if (res == 0) { - // The file already exists and since the name is the hash of the contents, - // it's safe to assume the contents are identical (accidental hash collisions - // are unlikely) - fprintf(stderr, " skipping %d existing blocks in %s\n", blocks, cn); - *exists = 1; - rc = 0; - goto wsout; - } - - *exists = 0; - } - - fprintf(stderr, " writing %d blocks to %s\n", blocks, cn); - - fd = TEMP_FAILURE_RETRY(open(fn, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, STASH_FILE_MODE)); - - if (fd == -1) { - fprintf(stderr, "failed to create \"%s\": %s\n", fn, strerror(errno)); - goto wsout; - } - - if (write_all(fd, buffer, blocks * BLOCKSIZE) == -1) { - goto wsout; - } - - if (fsync(fd) == -1) { - fprintf(stderr, "fsync \"%s\" failed: %s\n", fn, strerror(errno)); - goto wsout; - } - - if (rename(fn, cn) == -1) { - fprintf(stderr, "rename(\"%s\", \"%s\") failed: %s\n", fn, cn, strerror(errno)); - goto wsout; - } - - const char* dname; - dname = dirname(cn); - dfd = TEMP_FAILURE_RETRY(open(dname, O_RDONLY | O_DIRECTORY)); - - if (dfd == -1) { - fprintf(stderr, "failed to open \"%s\" failed: %s\n", dname, strerror(errno)); - goto wsout; - } - - if (fsync(dfd) == -1) { - fprintf(stderr, "fsync \"%s\" failed: %s\n", dname, strerror(errno)); - goto wsout; - } - - rc = 0; - -wsout: - if (fd != -1) { - close(fd); - } - - if (dfd != -1) { - close(dfd); - } - - if (fn) { - free(fn); - } - - if (cn) { - free(cn); - } - - return rc; -} - -// Creates a directory for storing stash files and checks if the /cache partition -// hash enough space for the expected amount of blocks we need to store. Returns -// >0 if we created the directory, zero if it existed already, and <0 of failure. - -static int CreateStash(State* state, int maxblocks, const char* blockdev, char** base) { - char* dirname = NULL; - const uint8_t* digest; - int rc = -1; - int res; - int size = 0; - SHA_CTX ctx; - struct stat st; - - if (blockdev == NULL || base == NULL) { - goto csout; - } - - // Stash directory should be different for each partition to avoid conflicts - // when updating multiple partitions at the same time, so we use the hash of - // the block device name as the base directory - SHA_init(&ctx); - SHA_update(&ctx, blockdev, strlen(blockdev)); - digest = SHA_final(&ctx); - *base = PrintSha1(digest); - - if (*base == NULL) { - goto csout; - } - - dirname = GetStashFileName(*base, NULL, NULL); - - if (dirname == NULL) { - goto csout; - } - - res = stat(dirname, &st); - - if (res == -1 && errno != ENOENT) { - ErrorAbort(state, "stat \"%s\" failed: %s\n", dirname, strerror(errno)); - goto csout; - } else if (res != 0) { - fprintf(stderr, "creating stash %s\n", dirname); - res = mkdir(dirname, STASH_DIRECTORY_MODE); - - if (res != 0) { - ErrorAbort(state, "mkdir \"%s\" failed: %s\n", dirname, strerror(errno)); - goto csout; - } - - if (CacheSizeCheck(maxblocks * BLOCKSIZE) != 0) { - ErrorAbort(state, "not enough space for stash\n"); - goto csout; - } - - rc = 1; // Created directory - goto csout; - } - - fprintf(stderr, "using existing stash %s\n", dirname); - - // If the directory already exists, calculate the space already allocated to - // stash files and check if there's enough for all required blocks. Delete any - // partially completed stash files first. - - EnumerateStash(dirname, DeletePartial, NULL); - EnumerateStash(dirname, UpdateFileSize, &size); - - size = (maxblocks * BLOCKSIZE) - size; - - if (size > 0 && CacheSizeCheck(size) != 0) { - ErrorAbort(state, "not enough space for stash (%d more needed)\n", size); - goto csout; - } - - rc = 0; // Using existing directory - -csout: - if (dirname) { - free(dirname); - } - - return rc; -} - -static int SaveStash(const char* base, char** wordsave, uint8_t** buffer, size_t* buffer_alloc, - int fd, int usehash, int* isunresumable) { - char *id = NULL; - int res = -1; - int blocks = 0; - - if (!wordsave || !buffer || !buffer_alloc || !isunresumable) { - return -1; - } - - id = strtok_r(NULL, " ", wordsave); - - if (id == NULL) { - fprintf(stderr, "missing id field in stash command\n"); - return -1; - } - - if (usehash && LoadStash(base, id, 1, &blocks, buffer, buffer_alloc, 0) == 0) { - // Stash file already exists and has expected contents. Do not - // read from source again, as the source may have been already - // overwritten during a previous attempt. - return 0; - } - - if (LoadSrcTgtVersion1(wordsave, NULL, &blocks, buffer, buffer_alloc, fd) == -1) { - return -1; - } - - if (usehash && VerifyBlocks(id, *buffer, blocks, 1) != 0) { - // Source blocks have unexpected contents. If we actually need this - // data later, this is an unrecoverable error. However, the command - // that uses the data may have already completed previously, so the - // possible failure will occur during source block verification. - fprintf(stderr, "failed to load source blocks for stash %s\n", id); - return 0; - } - - fprintf(stderr, "stashing %d blocks to %s\n", blocks, id); - return WriteStash(base, id, blocks, *buffer, 0, NULL); -} - -static int FreeStash(const char* base, const char* id) { - char *fn = NULL; - - if (base == NULL || id == NULL) { - return -1; - } - - fn = GetStashFileName(base, id, NULL); - - if (fn == NULL) { - return -1; - } - - DeleteFile(fn, NULL); - free(fn); - - return 0; -} - -static void MoveRange(uint8_t* dest, RangeSet* locs, const uint8_t* source) { - // source contains packed data, which we want to move to the - // locations given in *locs in the dest buffer. source and dest - // may be the same buffer. - - int start = locs->size; - int i; - for (i = locs->count-1; i >= 0; --i) { - int blocks = locs->pos[i*2+1] - locs->pos[i*2]; - start -= blocks; - memmove(dest + (locs->pos[i*2] * BLOCKSIZE), source + (start * BLOCKSIZE), - blocks * BLOCKSIZE); - } -} - -// Do a source/target load for move/bsdiff/imgdiff in version 2. -// 'wordsave' is the save_ptr of a strtok_r()-in-progress. We expect -// to parse the remainder of the string as one of: -// -// -// (loads data from source image only) -// -// - <[stash_id:stash_range] ...> -// (loads data from stashes only) -// -// <[stash_id:stash_range] ...> -// (loads data from both source image and stashes) -// -// On return, buffer is filled with the loaded source data (rearranged -// and combined with stashed data as necessary). buffer may be -// reallocated if needed to accommodate the source data. *tgt is the -// target RangeSet. Any stashes required are loaded using LoadStash. - -static int LoadSrcTgtVersion2(char** wordsave, RangeSet** tgt, int* src_blocks, - uint8_t** buffer, size_t* buffer_alloc, int fd, - const char* stashbase, int* overlap) { - char* word; - char* colonsave; - char* colon; - int id; - int res; - RangeSet* locs; - size_t stashalloc = 0; - uint8_t* stash = NULL; - - if (tgt != NULL) { - word = strtok_r(NULL, " ", wordsave); - *tgt = parse_range(word); - } - - word = strtok_r(NULL, " ", wordsave); - *src_blocks = strtol(word, NULL, 0); - - allocate(*src_blocks * BLOCKSIZE, buffer, buffer_alloc); - - word = strtok_r(NULL, " ", wordsave); - if (word[0] == '-' && word[1] == '\0') { - // no source ranges, only stashes - } else { - RangeSet* src = parse_range(word); - res = ReadBlocks(src, *buffer, fd); - - if (overlap && tgt) { - *overlap = range_overlaps(src, *tgt); - } - - free(src); - - if (res == -1) { - return -1; - } - - word = strtok_r(NULL, " ", wordsave); - if (word == NULL) { - // no stashes, only source range - return 0; - } - - locs = parse_range(word); - MoveRange(*buffer, locs, *buffer); - free(locs); - } - - while ((word = strtok_r(NULL, " ", wordsave)) != NULL) { - // Each word is a an index into the stash table, a colon, and - // then a rangeset describing where in the source block that - // stashed data should go. - colonsave = NULL; - colon = strtok_r(word, ":", &colonsave); - - res = LoadStash(stashbase, colon, 0, NULL, &stash, &stashalloc, 1); - - if (res == -1) { - // These source blocks will fail verification if used later, but we - // will let the caller decide if this is a fatal failure - fprintf(stderr, "failed to load stash %s\n", colon); - continue; - } - - colon = strtok_r(NULL, ":", &colonsave); - locs = parse_range(colon); - - MoveRange(*buffer, locs, stash); - free(locs); - } - - if (stash) { - free(stash); - } - - return 0; -} - -// Parameters for transfer list command functions -typedef struct { - char* cmdname; - char* cpos; - char* freestash; - char* stashbase; - int canwrite; - int createdstash; - int fd; - int foundwrites; - int isunresumable; - int version; - int written; - NewThreadInfo nti; - pthread_t thread; - size_t bufsize; - uint8_t* buffer; - uint8_t* patch_start; -} CommandParameters; - -// Do a source/target load for move/bsdiff/imgdiff in version 3. -// -// Parameters are the same as for LoadSrcTgtVersion2, except for 'onehash', which -// tells the function whether to expect separate source and targe block hashes, or -// if they are both the same and only one hash should be expected, and -// 'isunresumable', which receives a non-zero value if block verification fails in -// a way that the update cannot be resumed anymore. -// -// If the function is unable to load the necessary blocks or their contents don't -// match the hashes, the return value is -1 and the command should be aborted. -// -// If the return value is 1, the command has already been completed according to -// the contents of the target blocks, and should not be performed again. -// -// If the return value is 0, source blocks have expected content and the command -// can be performed. - -static int LoadSrcTgtVersion3(CommandParameters* params, RangeSet** tgt, int* src_blocks, - int onehash, int* overlap) { - char* srchash = NULL; - char* tgthash = NULL; - int stash_exists = 0; - int overlap_blocks = 0; - int rc = -1; - uint8_t* tgtbuffer = NULL; - - if (!params|| !tgt || !src_blocks || !overlap) { - goto v3out; - } - - srchash = strtok_r(NULL, " ", ¶ms->cpos); - - if (srchash == NULL) { - fprintf(stderr, "missing source hash\n"); - goto v3out; - } - - if (onehash) { - tgthash = srchash; - } else { - tgthash = strtok_r(NULL, " ", ¶ms->cpos); - - if (tgthash == NULL) { - fprintf(stderr, "missing target hash\n"); - goto v3out; - } - } - - if (LoadSrcTgtVersion2(¶ms->cpos, tgt, src_blocks, ¶ms->buffer, ¶ms->bufsize, - params->fd, params->stashbase, overlap) == -1) { - goto v3out; - } - - tgtbuffer = (uint8_t*) malloc((*tgt)->size * BLOCKSIZE); - - if (tgtbuffer == NULL) { - fprintf(stderr, "failed to allocate %d bytes\n", (*tgt)->size * BLOCKSIZE); - goto v3out; - } - - if (ReadBlocks(*tgt, tgtbuffer, params->fd) == -1) { - goto v3out; - } - - if (VerifyBlocks(tgthash, tgtbuffer, (*tgt)->size, 0) == 0) { - // Target blocks already have expected content, command should be skipped - rc = 1; - goto v3out; - } - - if (VerifyBlocks(srchash, params->buffer, *src_blocks, 1) == 0) { - // If source and target blocks overlap, stash the source blocks so we can - // resume from possible write errors - if (*overlap) { - fprintf(stderr, "stashing %d overlapping blocks to %s\n", *src_blocks, - srchash); - - if (WriteStash(params->stashbase, srchash, *src_blocks, params->buffer, 1, - &stash_exists) != 0) { - fprintf(stderr, "failed to stash overlapping source blocks\n"); - goto v3out; - } - - // Can be deleted when the write has completed - if (!stash_exists) { - params->freestash = srchash; - } - } - - // Source blocks have expected content, command can proceed - rc = 0; - goto v3out; - } - - if (*overlap && LoadStash(params->stashbase, srchash, 1, NULL, ¶ms->buffer, - ¶ms->bufsize, 1) == 0) { - // Overlapping source blocks were previously stashed, command can proceed. - // We are recovering from an interrupted command, so we don't know if the - // stash can safely be deleted after this command. - rc = 0; - goto v3out; - } - - // Valid source data not available, update cannot be resumed - fprintf(stderr, "partition has unexpected contents\n"); - params->isunresumable = 1; - -v3out: - if (tgtbuffer) { - free(tgtbuffer); - } - - return rc; -} - -static int PerformCommandMove(CommandParameters* params) { - int blocks = 0; - int overlap = 0; - int rc = -1; - int status = 0; - RangeSet* tgt = NULL; - - if (!params) { - goto pcmout; - } - - if (params->version == 1) { - status = LoadSrcTgtVersion1(¶ms->cpos, &tgt, &blocks, ¶ms->buffer, - ¶ms->bufsize, params->fd); - } else if (params->version == 2) { - status = LoadSrcTgtVersion2(¶ms->cpos, &tgt, &blocks, ¶ms->buffer, - ¶ms->bufsize, params->fd, params->stashbase, NULL); - } else if (params->version >= 3) { - status = LoadSrcTgtVersion3(params, &tgt, &blocks, 1, &overlap); - } - - if (status == -1) { - fprintf(stderr, "failed to read blocks for move\n"); - goto pcmout; - } - - if (status == 0) { - params->foundwrites = 1; - } else if (params->foundwrites) { - fprintf(stderr, "warning: commands executed out of order [%s]\n", params->cmdname); - } - - if (params->canwrite) { - if (status == 0) { - fprintf(stderr, " moving %d blocks\n", blocks); - - if (WriteBlocks(tgt, params->buffer, params->fd) == -1) { - goto pcmout; - } - } else { - fprintf(stderr, "skipping %d already moved blocks\n", blocks); - } - - } - - if (params->freestash) { - FreeStash(params->stashbase, params->freestash); - params->freestash = NULL; - } - - params->written += tgt->size; - rc = 0; - -pcmout: - if (tgt) { - free(tgt); - } - - return rc; -} - -static int PerformCommandStash(CommandParameters* params) { - if (!params) { - return -1; - } - - return SaveStash(params->stashbase, ¶ms->cpos, ¶ms->buffer, ¶ms->bufsize, - params->fd, (params->version >= 3), ¶ms->isunresumable); -} - -static int PerformCommandFree(CommandParameters* params) { - if (!params) { - return -1; - } - - if (params->createdstash || params->canwrite) { - return FreeStash(params->stashbase, params->cpos); - } - - return 0; -} - -static int PerformCommandZero(CommandParameters* params) { - char* range = NULL; - int i; - int j; - int rc = -1; - RangeSet* tgt = NULL; - - if (!params) { - goto pczout; - } - - range = strtok_r(NULL, " ", ¶ms->cpos); - - if (range == NULL) { - fprintf(stderr, "missing target blocks for zero\n"); - goto pczout; - } - - tgt = parse_range(range); - - fprintf(stderr, " zeroing %d blocks\n", tgt->size); - - allocate(BLOCKSIZE, ¶ms->buffer, ¶ms->bufsize); - memset(params->buffer, 0, BLOCKSIZE); - - if (params->canwrite) { - for (i = 0; i < tgt->count; ++i) { - if (!check_lseek(params->fd, (off64_t) tgt->pos[i * 2] * BLOCKSIZE, SEEK_SET)) { - goto pczout; - } - - for (j = tgt->pos[i * 2]; j < tgt->pos[i * 2 + 1]; ++j) { - if (write_all(params->fd, params->buffer, BLOCKSIZE) == -1) { - goto pczout; - } - } - } - } - - if (params->cmdname[0] == 'z') { - // Update only for the zero command, as the erase command will call - // this if DEBUG_ERASE is defined. - params->written += tgt->size; - } - - rc = 0; - -pczout: - if (tgt) { - free(tgt); - } - - return rc; -} - -static int PerformCommandNew(CommandParameters* params) { - char* range = NULL; - int rc = -1; - RangeSet* tgt = NULL; - RangeSinkState rss; - - if (!params) { - goto pcnout; - } - - range = strtok_r(NULL, " ", ¶ms->cpos); - - if (range == NULL) { - goto pcnout; - } - - tgt = parse_range(range); - - if (params->canwrite) { - fprintf(stderr, " writing %d blocks of new data\n", tgt->size); - - rss.fd = params->fd; - rss.tgt = tgt; - rss.p_block = 0; - rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE; - - if (!check_lseek(params->fd, (off64_t) tgt->pos[0] * BLOCKSIZE, SEEK_SET)) { - goto pcnout; - } - - pthread_mutex_lock(¶ms->nti.mu); - params->nti.rss = &rss; - pthread_cond_broadcast(¶ms->nti.cv); - - while (params->nti.rss) { - pthread_cond_wait(¶ms->nti.cv, ¶ms->nti.mu); - } - - pthread_mutex_unlock(¶ms->nti.mu); - } - - params->written += tgt->size; - rc = 0; - -pcnout: - if (tgt) { - free(tgt); - } - - return rc; -} - -static int PerformCommandDiff(CommandParameters* params) { - char* logparams = NULL; - char* value = NULL; - int blocks = 0; - int overlap = 0; - int rc = -1; - int status = 0; - RangeSet* tgt = NULL; - RangeSinkState rss; - size_t len = 0; - size_t offset = 0; - Value patch_value; - - if (!params) { - goto pcdout; - } - - logparams = strdup(params->cpos); - value = strtok_r(NULL, " ", ¶ms->cpos); - - if (value == NULL) { - fprintf(stderr, "missing patch offset for %s\n", params->cmdname); - goto pcdout; - } - - offset = strtoul(value, NULL, 0); - - value = strtok_r(NULL, " ", ¶ms->cpos); - - if (value == NULL) { - fprintf(stderr, "missing patch length for %s\n", params->cmdname); - goto pcdout; - } - - len = strtoul(value, NULL, 0); - - if (params->version == 1) { - status = LoadSrcTgtVersion1(¶ms->cpos, &tgt, &blocks, ¶ms->buffer, - ¶ms->bufsize, params->fd); - } else if (params->version == 2) { - status = LoadSrcTgtVersion2(¶ms->cpos, &tgt, &blocks, ¶ms->buffer, - ¶ms->bufsize, params->fd, params->stashbase, NULL); - } else if (params->version >= 3) { - status = LoadSrcTgtVersion3(params, &tgt, &blocks, 0, &overlap); - } - - if (status == -1) { - fprintf(stderr, "failed to read blocks for diff\n"); - goto pcdout; - } - - if (status == 0) { - params->foundwrites = 1; - } else if (params->foundwrites) { - fprintf(stderr, "warning: commands executed out of order [%s]\n", params->cmdname); - } - - if (params->canwrite) { - if (status == 0) { - fprintf(stderr, "patching %d blocks to %d\n", blocks, tgt->size); - - patch_value.type = VAL_BLOB; - patch_value.size = len; - patch_value.data = (char*) (params->patch_start + offset); - - rss.fd = params->fd; - rss.tgt = tgt; - rss.p_block = 0; - rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE; - - if (!check_lseek(params->fd, (off64_t) tgt->pos[0] * BLOCKSIZE, SEEK_SET)) { - goto pcdout; - } - - if (params->cmdname[0] == 'i') { // imgdiff - ApplyImagePatch(params->buffer, blocks * BLOCKSIZE, &patch_value, - &RangeSinkWrite, &rss, NULL, NULL); - } else { - ApplyBSDiffPatch(params->buffer, blocks * BLOCKSIZE, &patch_value, - 0, &RangeSinkWrite, &rss, NULL); - } - - // We expect the output of the patcher to fill the tgt ranges exactly. - if (rss.p_block != tgt->count || rss.p_remain != 0) { - fprintf(stderr, "range sink underrun?\n"); - } - } else { - fprintf(stderr, "skipping %d blocks already patched to %d [%s]\n", - blocks, tgt->size, logparams); - } - } - - if (params->freestash) { - FreeStash(params->stashbase, params->freestash); - params->freestash = NULL; - } - - params->written += tgt->size; - rc = 0; - -pcdout: - if (logparams) { - free(logparams); - } - - if (tgt) { - free(tgt); - } - - return rc; -} - -static int PerformCommandErase(CommandParameters* params) { - char* range = NULL; - int i; - int rc = -1; - RangeSet* tgt = NULL; - struct stat st; - uint64_t blocks[2]; - - if (DEBUG_ERASE) { - return PerformCommandZero(params); - } - - if (!params) { - goto pceout; - } - - if (fstat(params->fd, &st) == -1) { - fprintf(stderr, "failed to fstat device to erase: %s\n", strerror(errno)); - goto pceout; - } - - if (!S_ISBLK(st.st_mode)) { - fprintf(stderr, "not a block device; skipping erase\n"); - goto pceout; - } - - range = strtok_r(NULL, " ", ¶ms->cpos); - - if (range == NULL) { - fprintf(stderr, "missing target blocks for zero\n"); - goto pceout; - } - - tgt = parse_range(range); - - if (params->canwrite) { - fprintf(stderr, " erasing %d blocks\n", tgt->size); - - for (i = 0; i < tgt->count; ++i) { - // offset in bytes - blocks[0] = tgt->pos[i * 2] * (uint64_t) BLOCKSIZE; - // length in bytes - blocks[1] = (tgt->pos[i * 2 + 1] - tgt->pos[i * 2]) * (uint64_t) BLOCKSIZE; - - if (ioctl(params->fd, BLKDISCARD, &blocks) == -1) { - fprintf(stderr, "BLKDISCARD ioctl failed: %s\n", strerror(errno)); - goto pceout; - } - } - } - - rc = 0; - -pceout: - if (tgt) { - free(tgt); - } - - return rc; -} - -// Definitions for transfer list command functions -typedef int (*CommandFunction)(CommandParameters*); - -typedef struct { - const char* name; - CommandFunction f; -} Command; - -// CompareCommands and CompareCommandNames are for the hash table - -static int CompareCommands(const void* c1, const void* c2) { - return strcmp(((const Command*) c1)->name, ((const Command*) c2)->name); -} - -static int CompareCommandNames(const void* c1, const void* c2) { - return strcmp(((const Command*) c1)->name, (const char*) c2); -} - -// HashString is used to hash command names for the hash table - -static unsigned int HashString(const char *s) { - unsigned int hash = 0; - if (s) { - while (*s) { - hash = hash * 33 + *s++; - } - } - return hash; -} - -// args: -// - block device (or file) to modify in-place -// - transfer list (blob) -// - new data stream (filename within package.zip) -// - patch stream (filename within package.zip, must be uncompressed) - -static Value* PerformBlockImageUpdate(const char* name, State* state, int argc, Expr* argv[], - const Command* commands, int cmdcount, int dryrun) { - - char* line = NULL; - char* linesave = NULL; - char* logcmd = NULL; - char* transfer_list = NULL; - CommandParameters params; - const Command* cmd = NULL; - const ZipEntry* new_entry = NULL; - const ZipEntry* patch_entry = NULL; - FILE* cmd_pipe = NULL; - HashTable* cmdht = NULL; - int i; - int res; - int rc = -1; - int stash_max_blocks = 0; - int total_blocks = 0; - pthread_attr_t attr; - unsigned int cmdhash; - UpdaterInfo* ui = NULL; - Value* blockdev_filename = NULL; - Value* new_data_fn = NULL; - Value* patch_data_fn = NULL; - Value* transfer_list_value = NULL; - ZipArchive* za = NULL; - - memset(¶ms, 0, sizeof(params)); - params.canwrite = !dryrun; - - fprintf(stderr, "performing %s\n", dryrun ? "verification" : "update"); - - if (ReadValueArgs(state, argv, 4, &blockdev_filename, &transfer_list_value, - &new_data_fn, &patch_data_fn) < 0) { - goto pbiudone; - } - - if (blockdev_filename->type != VAL_STRING) { - ErrorAbort(state, "blockdev_filename argument to %s must be string", name); - goto pbiudone; - } - if (transfer_list_value->type != VAL_BLOB) { - ErrorAbort(state, "transfer_list argument to %s must be blob", name); - goto pbiudone; - } - if (new_data_fn->type != VAL_STRING) { - ErrorAbort(state, "new_data_fn argument to %s must be string", name); - goto pbiudone; - } - if (patch_data_fn->type != VAL_STRING) { - ErrorAbort(state, "patch_data_fn argument to %s must be string", name); - goto pbiudone; - } - - ui = (UpdaterInfo*) state->cookie; - - if (ui == NULL) { - goto pbiudone; - } - - cmd_pipe = ui->cmd_pipe; - za = ui->package_zip; - - if (cmd_pipe == NULL || za == NULL) { - goto pbiudone; - } - - patch_entry = mzFindZipEntry(za, patch_data_fn->data); - - if (patch_entry == NULL) { - fprintf(stderr, "%s(): no file \"%s\" in package", name, patch_data_fn->data); - goto pbiudone; - } - - params.patch_start = ui->package_zip_addr + mzGetZipEntryOffset(patch_entry); - new_entry = mzFindZipEntry(za, new_data_fn->data); - - if (new_entry == NULL) { - fprintf(stderr, "%s(): no file \"%s\" in package", name, new_data_fn->data); - goto pbiudone; - } - - params.fd = TEMP_FAILURE_RETRY(open(blockdev_filename->data, O_RDWR)); - - if (params.fd == -1) { - fprintf(stderr, "open \"%s\" failed: %s\n", blockdev_filename->data, strerror(errno)); - goto pbiudone; - } - - if (params.canwrite) { - params.nti.za = za; - params.nti.entry = new_entry; - - pthread_mutex_init(¶ms.nti.mu, NULL); - pthread_cond_init(¶ms.nti.cv, NULL); - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - - int error = pthread_create(¶ms.thread, &attr, unzip_new_data, ¶ms.nti); - if (error != 0) { - fprintf(stderr, "pthread_create failed: %s\n", strerror(error)); - goto pbiudone; - } - } - - // The data in transfer_list_value is not necessarily null-terminated, so we need - // to copy it to a new buffer and add the null that strtok_r will need. - transfer_list = malloc(transfer_list_value->size + 1); - - if (transfer_list == NULL) { - fprintf(stderr, "failed to allocate %zd bytes for transfer list\n", - transfer_list_value->size + 1); - goto pbiudone; - } - - memcpy(transfer_list, transfer_list_value->data, transfer_list_value->size); - transfer_list[transfer_list_value->size] = '\0'; - - // First line in transfer list is the version number - line = strtok_r(transfer_list, "\n", &linesave); - params.version = strtol(line, NULL, 0); - - if (params.version < 1 || params.version > 3) { - fprintf(stderr, "unexpected transfer list version [%s]\n", line); - goto pbiudone; - } - - fprintf(stderr, "blockimg version is %d\n", params.version); - - // Second line in transfer list is the total number of blocks we expect to write - line = strtok_r(NULL, "\n", &linesave); - total_blocks = strtol(line, NULL, 0); - - if (total_blocks < 0) { - ErrorAbort(state, "unexpected block count [%s]\n", line); - goto pbiudone; - } else if (total_blocks == 0) { - rc = 0; - goto pbiudone; - } - - if (params.version >= 2) { - // Third line is how many stash entries are needed simultaneously - line = strtok_r(NULL, "\n", &linesave); - fprintf(stderr, "maximum stash entries %s\n", line); - - // Fourth line is the maximum number of blocks that will be stashed simultaneously - line = strtok_r(NULL, "\n", &linesave); - stash_max_blocks = strtol(line, NULL, 0); - - if (stash_max_blocks < 0) { - ErrorAbort(state, "unexpected maximum stash blocks [%s]\n", line); - goto pbiudone; - } - - if (stash_max_blocks >= 0) { - res = CreateStash(state, stash_max_blocks, blockdev_filename->data, - ¶ms.stashbase); - - if (res == -1) { - goto pbiudone; - } - - params.createdstash = res; - } - } - - // Build a hash table of the available commands - cmdht = mzHashTableCreate(cmdcount, NULL); - - for (i = 0; i < cmdcount; ++i) { - cmdhash = HashString(commands[i].name); - mzHashTableLookup(cmdht, cmdhash, (void*) &commands[i], CompareCommands, true); - } - - // Subsequent lines are all individual transfer commands - for (line = strtok_r(NULL, "\n", &linesave); line; - line = strtok_r(NULL, "\n", &linesave)) { - - logcmd = strdup(line); - params.cmdname = strtok_r(line, " ", ¶ms.cpos); - - if (params.cmdname == NULL) { - fprintf(stderr, "missing command [%s]\n", line); - goto pbiudone; - } - - cmdhash = HashString(params.cmdname); - cmd = (const Command*) mzHashTableLookup(cmdht, cmdhash, params.cmdname, - CompareCommandNames, false); - - if (cmd == NULL) { - fprintf(stderr, "unexpected command [%s]\n", params.cmdname); - goto pbiudone; - } - - if (cmd->f != NULL && cmd->f(¶ms) == -1) { - fprintf(stderr, "failed to execute command [%s]\n", - logcmd ? logcmd : params.cmdname); - goto pbiudone; - } - - if (logcmd) { - free(logcmd); - logcmd = NULL; - } - - if (params.canwrite) { - fprintf(cmd_pipe, "set_progress %.4f\n", (double) params.written / total_blocks); - fflush(cmd_pipe); - } - } - - if (params.canwrite) { - pthread_join(params.thread, NULL); - - fprintf(stderr, "wrote %d blocks; expected %d\n", params.written, total_blocks); - fprintf(stderr, "max alloc needed was %zu\n", params.bufsize); - - // Delete stash only after successfully completing the update, as it - // may contain blocks needed to complete the update later. - DeleteStash(params.stashbase); - } else { - fprintf(stderr, "verified partition contents; update may be resumed\n"); - } - - rc = 0; - -pbiudone: - if (params.fd != -1) { - if (fsync(params.fd) == -1) { - fprintf(stderr, "fsync failed: %s\n", strerror(errno)); - } - close(params.fd); - } - - if (logcmd) { - free(logcmd); - } - - if (cmdht) { - mzHashTableFree(cmdht); - } - - if (params.buffer) { - free(params.buffer); - } - - if (transfer_list) { - free(transfer_list); - } - - if (blockdev_filename) { - FreeValue(blockdev_filename); - } - - if (transfer_list_value) { - FreeValue(transfer_list_value); - } - - if (new_data_fn) { - FreeValue(new_data_fn); - } - - if (patch_data_fn) { - FreeValue(patch_data_fn); - } - - // Only delete the stash if the update cannot be resumed, or it's - // a verification run and we created the stash. - if (params.isunresumable || (!params.canwrite && params.createdstash)) { - DeleteStash(params.stashbase); - } - - if (params.stashbase) { - free(params.stashbase); - } - - return StringValue(rc == 0 ? strdup("t") : strdup("")); -} - -// The transfer list is a text file containing commands to -// transfer data from one place to another on the target -// partition. We parse it and execute the commands in order: -// -// zero [rangeset] -// - fill the indicated blocks with zeros -// -// new [rangeset] -// - fill the blocks with data read from the new_data file -// -// erase [rangeset] -// - mark the given blocks as empty -// -// move <...> -// bsdiff <...> -// imgdiff <...> -// - read the source blocks, apply a patch (or not in the -// case of move), write result to target blocks. bsdiff or -// imgdiff specifies the type of patch; move means no patch -// at all. -// -// The format of <...> differs between versions 1 and 2; -// see the LoadSrcTgtVersion{1,2}() functions for a -// description of what's expected. -// -// stash -// - (version 2+ only) load the given source range and stash -// the data in the given slot of the stash table. -// -// The creator of the transfer list will guarantee that no block -// is read (ie, used as the source for a patch or move) after it -// has been written. -// -// In version 2, the creator will guarantee that a given stash is -// loaded (with a stash command) before it's used in a -// move/bsdiff/imgdiff command. -// -// Within one command the source and target ranges may overlap so -// in general we need to read the entire source into memory before -// writing anything to the target blocks. -// -// All the patch data is concatenated into one patch_data file in -// the update package. It must be stored uncompressed because we -// memory-map it in directly from the archive. (Since patches are -// already compressed, we lose very little by not compressing -// their concatenation.) -// -// In version 3, commands that read data from the partition (i.e. -// move/bsdiff/imgdiff/stash) have one or more additional hashes -// before the range parameters, which are used to check if the -// command has already been completed and verify the integrity of -// the source data. - -Value* BlockImageVerifyFn(const char* name, State* state, int argc, Expr* argv[]) { - // Commands which are not tested are set to NULL to skip them completely - const Command commands[] = { - { "bsdiff", PerformCommandDiff }, - { "erase", NULL }, - { "free", PerformCommandFree }, - { "imgdiff", PerformCommandDiff }, - { "move", PerformCommandMove }, - { "new", NULL }, - { "stash", PerformCommandStash }, - { "zero", NULL } - }; - - // Perform a dry run without writing to test if an update can proceed - return PerformBlockImageUpdate(name, state, argc, argv, commands, - sizeof(commands) / sizeof(commands[0]), 1); -} - -Value* BlockImageUpdateFn(const char* name, State* state, int argc, Expr* argv[]) { - const Command commands[] = { - { "bsdiff", PerformCommandDiff }, - { "erase", PerformCommandErase }, - { "free", PerformCommandFree }, - { "imgdiff", PerformCommandDiff }, - { "move", PerformCommandMove }, - { "new", PerformCommandNew }, - { "stash", PerformCommandStash }, - { "zero", PerformCommandZero } - }; - - return PerformBlockImageUpdate(name, state, argc, argv, commands, - sizeof(commands) / sizeof(commands[0]), 0); -} - -Value* RangeSha1Fn(const char* name, State* state, int argc, Expr* argv[]) { - Value* blockdev_filename; - Value* ranges; - const uint8_t* digest = NULL; - if (ReadValueArgs(state, argv, 2, &blockdev_filename, &ranges) < 0) { - return NULL; - } - - if (blockdev_filename->type != VAL_STRING) { - ErrorAbort(state, "blockdev_filename argument to %s must be string", name); - goto done; - } - if (ranges->type != VAL_STRING) { - ErrorAbort(state, "ranges argument to %s must be string", name); - goto done; - } - - int fd = open(blockdev_filename->data, O_RDWR); - if (fd < 0) { - ErrorAbort(state, "open \"%s\" failed: %s", blockdev_filename->data, strerror(errno)); - goto done; - } - - RangeSet* rs = parse_range(ranges->data); - uint8_t buffer[BLOCKSIZE]; - - SHA_CTX ctx; - SHA_init(&ctx); - - int i, j; - for (i = 0; i < rs->count; ++i) { - if (!check_lseek(fd, (off64_t)rs->pos[i*2] * BLOCKSIZE, SEEK_SET)) { - ErrorAbort(state, "failed to seek %s: %s", blockdev_filename->data, - strerror(errno)); - goto done; - } - - for (j = rs->pos[i*2]; j < rs->pos[i*2+1]; ++j) { - if (read_all(fd, buffer, BLOCKSIZE) == -1) { - ErrorAbort(state, "failed to read %s: %s", blockdev_filename->data, - strerror(errno)); - goto done; - } - - SHA_update(&ctx, buffer, BLOCKSIZE); - } - } - digest = SHA_final(&ctx); - close(fd); - - done: - FreeValue(blockdev_filename); - FreeValue(ranges); - if (digest == NULL) { - return StringValue(strdup("")); - } else { - return StringValue(PrintSha1(digest)); - } -} - -void RegisterBlockImageFunctions() { - RegisterFunction("block_image_verify", BlockImageVerifyFn); - RegisterFunction("block_image_update", BlockImageUpdateFn); - RegisterFunction("range_sha1", RangeSha1Fn); -} diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp new file mode 100644 index 000000000..310bbf94c --- /dev/null +++ b/updater/blockimg.cpp @@ -0,0 +1,2011 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "applypatch/applypatch.h" +#include "edify/expr.h" +#include "mincrypt/sha.h" +#include "minzip/Hash.h" +#include "updater.h" + +#define BLOCKSIZE 4096 + +// Set this to 0 to interpret 'erase' transfers to mean do a +// BLKDISCARD ioctl (the normal behavior). Set to 1 to interpret +// erase to mean fill the region with zeroes. +#define DEBUG_ERASE 0 + +#define STASH_DIRECTORY_BASE "/cache/recovery" +#define STASH_DIRECTORY_MODE 0700 +#define STASH_FILE_MODE 0600 + +char* PrintSha1(const uint8_t* digest); + +typedef struct { + int count; + int size; + int pos[0]; +} RangeSet; + +#define RANGESET_MAX_POINTS \ + ((int)((INT_MAX / sizeof(int)) - sizeof(RangeSet))) + +static RangeSet* parse_range(char* text) { + char* save; + char* token; + int num; + long int val; + RangeSet* out = NULL; + size_t bufsize; + + if (!text) { + goto err; + } + + token = strtok_r(text, ",", &save); + + if (!token) { + goto err; + } + + val = strtol(token, NULL, 0); + + if (val < 2 || val > RANGESET_MAX_POINTS) { + goto err; + } else if (val % 2) { + goto err; // must be even + } + + num = (int) val; + bufsize = sizeof(RangeSet) + num * sizeof(int); + + out = reinterpret_cast(malloc(bufsize)); + + if (!out) { + fprintf(stderr, "failed to allocate range of %zu bytes\n", bufsize); + goto err; + } + + out->count = num / 2; + out->size = 0; + + for (int i = 0; i < num; ++i) { + token = strtok_r(NULL, ",", &save); + + if (!token) { + goto err; + } + + val = strtol(token, NULL, 0); + + if (val < 0 || val > INT_MAX) { + goto err; + } + + out->pos[i] = (int) val; + + if (i % 2) { + if (out->pos[i - 1] >= out->pos[i]) { + goto err; // empty or negative range + } + + if (out->size > INT_MAX - out->pos[i]) { + goto err; // overflow + } + + out->size += out->pos[i]; + } else { + if (out->size < 0) { + goto err; + } + + out->size -= out->pos[i]; + } + } + + if (out->size <= 0) { + goto err; + } + + return out; + +err: + fprintf(stderr, "failed to parse range '%s'\n", text ? text : "NULL"); + exit(1); +} + +static int range_overlaps(RangeSet* r1, RangeSet* r2) { + int i, j, r1_0, r1_1, r2_0, r2_1; + + if (!r1 || !r2) { + return 0; + } + + for (i = 0; i < r1->count; ++i) { + r1_0 = r1->pos[i * 2]; + r1_1 = r1->pos[i * 2 + 1]; + + for (j = 0; j < r2->count; ++j) { + r2_0 = r2->pos[j * 2]; + r2_1 = r2->pos[j * 2 + 1]; + + if (!(r2_0 >= r1_1 || r1_0 >= r2_1)) { + return 1; + } + } + } + + return 0; +} + +static int read_all(int fd, uint8_t* data, size_t size) { + size_t so_far = 0; + while (so_far < size) { + ssize_t r = TEMP_FAILURE_RETRY(read(fd, data+so_far, size-so_far)); + if (r == -1) { + fprintf(stderr, "read failed: %s\n", strerror(errno)); + return -1; + } + so_far += r; + } + return 0; +} + +static int write_all(int fd, const uint8_t* data, size_t size) { + size_t written = 0; + while (written < size) { + ssize_t w = TEMP_FAILURE_RETRY(write(fd, data+written, size-written)); + if (w == -1) { + fprintf(stderr, "write failed: %s\n", strerror(errno)); + return -1; + } + written += w; + } + + if (fsync(fd) == -1) { + fprintf(stderr, "fsync failed: %s\n", strerror(errno)); + return -1; + } + + return 0; +} + +static bool check_lseek(int fd, off64_t offset, int whence) { + off64_t rc = TEMP_FAILURE_RETRY(lseek64(fd, offset, whence)); + if (rc == -1) { + fprintf(stderr, "lseek64 failed: %s\n", strerror(errno)); + return false; + } + return true; +} + +static void allocate(size_t size, uint8_t** buffer, size_t* buffer_alloc) { + // if the buffer's big enough, reuse it. + if (size <= *buffer_alloc) return; + + free(*buffer); + + *buffer = (uint8_t*) malloc(size); + if (*buffer == NULL) { + fprintf(stderr, "failed to allocate %zu bytes\n", size); + exit(1); + } + *buffer_alloc = size; +} + +typedef struct { + int fd; + RangeSet* tgt; + int p_block; + size_t p_remain; +} RangeSinkState; + +static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) { + RangeSinkState* rss = (RangeSinkState*) token; + + if (rss->p_remain <= 0) { + fprintf(stderr, "range sink write overrun"); + return 0; + } + + ssize_t written = 0; + while (size > 0) { + size_t write_now = size; + + if (rss->p_remain < write_now) { + write_now = rss->p_remain; + } + + if (write_all(rss->fd, data, write_now) == -1) { + break; + } + + data += write_now; + size -= write_now; + + rss->p_remain -= write_now; + written += write_now; + + if (rss->p_remain == 0) { + // move to the next block + ++rss->p_block; + if (rss->p_block < rss->tgt->count) { + rss->p_remain = (rss->tgt->pos[rss->p_block * 2 + 1] - + rss->tgt->pos[rss->p_block * 2]) * BLOCKSIZE; + + if (!check_lseek(rss->fd, (off64_t)rss->tgt->pos[rss->p_block*2] * BLOCKSIZE, + SEEK_SET)) { + break; + } + } else { + // we can't write any more; return how many bytes have + // been written so far. + break; + } + } + } + + return written; +} + +// All of the data for all the 'new' transfers is contained in one +// file in the update package, concatenated together in the order in +// which transfers.list will need it. We want to stream it out of the +// archive (it's compressed) without writing it to a temp file, but we +// can't write each section until it's that transfer's turn to go. +// +// To achieve this, we expand the new data from the archive in a +// background thread, and block that threads 'receive uncompressed +// data' function until the main thread has reached a point where we +// want some new data to be written. We signal the background thread +// with the destination for the data and block the main thread, +// waiting for the background thread to complete writing that section. +// Then it signals the main thread to wake up and goes back to +// blocking waiting for a transfer. +// +// NewThreadInfo is the struct used to pass information back and forth +// between the two threads. When the main thread wants some data +// written, it sets rss to the destination location and signals the +// condition. When the background thread is done writing, it clears +// rss and signals the condition again. + +typedef struct { + ZipArchive* za; + const ZipEntry* entry; + + RangeSinkState* rss; + + pthread_mutex_t mu; + pthread_cond_t cv; +} NewThreadInfo; + +static bool receive_new_data(const unsigned char* data, int size, void* cookie) { + NewThreadInfo* nti = (NewThreadInfo*) cookie; + + while (size > 0) { + // Wait for nti->rss to be non-NULL, indicating some of this + // data is wanted. + pthread_mutex_lock(&nti->mu); + while (nti->rss == NULL) { + pthread_cond_wait(&nti->cv, &nti->mu); + } + pthread_mutex_unlock(&nti->mu); + + // At this point nti->rss is set, and we own it. The main + // thread is waiting for it to disappear from nti. + ssize_t written = RangeSinkWrite(data, size, nti->rss); + data += written; + size -= written; + + if (nti->rss->p_block == nti->rss->tgt->count) { + // we have written all the bytes desired by this rss. + + pthread_mutex_lock(&nti->mu); + nti->rss = NULL; + pthread_cond_broadcast(&nti->cv); + pthread_mutex_unlock(&nti->mu); + } + } + + return true; +} + +static void* unzip_new_data(void* cookie) { + NewThreadInfo* nti = (NewThreadInfo*) cookie; + mzProcessZipEntryContents(nti->za, nti->entry, receive_new_data, nti); + return NULL; +} + +static int ReadBlocks(RangeSet* src, uint8_t* buffer, int fd) { + int i; + size_t p = 0; + size_t size; + + if (!src || !buffer) { + return -1; + } + + for (i = 0; i < src->count; ++i) { + if (!check_lseek(fd, (off64_t) src->pos[i * 2] * BLOCKSIZE, SEEK_SET)) { + return -1; + } + + size = (src->pos[i * 2 + 1] - src->pos[i * 2]) * BLOCKSIZE; + + if (read_all(fd, buffer + p, size) == -1) { + return -1; + } + + p += size; + } + + return 0; +} + +static int WriteBlocks(RangeSet* tgt, uint8_t* buffer, int fd) { + int i; + size_t p = 0; + size_t size; + + if (!tgt || !buffer) { + return -1; + } + + for (i = 0; i < tgt->count; ++i) { + if (!check_lseek(fd, (off64_t) tgt->pos[i * 2] * BLOCKSIZE, SEEK_SET)) { + return -1; + } + + size = (tgt->pos[i * 2 + 1] - tgt->pos[i * 2]) * BLOCKSIZE; + + if (write_all(fd, buffer + p, size) == -1) { + return -1; + } + + p += size; + } + + return 0; +} + +// Do a source/target load for move/bsdiff/imgdiff in version 1. +// 'wordsave' is the save_ptr of a strtok_r()-in-progress. We expect +// to parse the remainder of the string as: +// +// +// +// The source range is loaded into the provided buffer, reallocating +// it to make it larger if necessary. The target ranges are returned +// in *tgt, if tgt is non-NULL. + +static int LoadSrcTgtVersion1(char** wordsave, RangeSet** tgt, int* src_blocks, + uint8_t** buffer, size_t* buffer_alloc, int fd) { + char* word; + int rc; + + word = strtok_r(NULL, " ", wordsave); + RangeSet* src = parse_range(word); + + if (tgt != NULL) { + word = strtok_r(NULL, " ", wordsave); + *tgt = parse_range(word); + } + + allocate(src->size * BLOCKSIZE, buffer, buffer_alloc); + rc = ReadBlocks(src, *buffer, fd); + *src_blocks = src->size; + + free(src); + return rc; +} + +static int VerifyBlocks(const char *expected, const uint8_t *buffer, + size_t blocks, int printerror) { + char* hexdigest = NULL; + int rc = -1; + uint8_t digest[SHA_DIGEST_SIZE]; + + if (!expected || !buffer) { + return rc; + } + + SHA_hash(buffer, blocks * BLOCKSIZE, digest); + hexdigest = PrintSha1(digest); + + if (hexdigest != NULL) { + rc = strcmp(expected, hexdigest); + + if (rc != 0 && printerror) { + fprintf(stderr, "failed to verify blocks (expected %s, read %s)\n", + expected, hexdigest); + } + + free(hexdigest); + } + + return rc; +} + +static char* GetStashFileName(const char* base, const char* id, const char* postfix) { + char* fn; + int len; + int res; + + if (base == NULL) { + return NULL; + } + + if (id == NULL) { + id = ""; + } + + if (postfix == NULL) { + postfix = ""; + } + + len = strlen(STASH_DIRECTORY_BASE) + 1 + strlen(base) + 1 + strlen(id) + strlen(postfix) + 1; + fn = reinterpret_cast(malloc(len)); + + if (fn == NULL) { + fprintf(stderr, "failed to malloc %d bytes for fn\n", len); + return NULL; + } + + res = snprintf(fn, len, STASH_DIRECTORY_BASE "/%s/%s%s", base, id, postfix); + + if (res < 0 || res >= len) { + fprintf(stderr, "failed to format file name (return value %d)\n", res); + free(fn); + return NULL; + } + + return fn; +} + +typedef void (*StashCallback)(const char*, void*); + +// Does a best effort enumeration of stash files. Ignores possible non-file +// items in the stash directory and continues despite of errors. Calls the +// 'callback' function for each file and passes 'data' to the function as a +// parameter. + +static void EnumerateStash(const char* dirname, StashCallback callback, void* data) { + char* fn; + DIR* directory; + int len; + int res; + struct dirent* item; + + if (dirname == NULL || callback == NULL) { + return; + } + + directory = opendir(dirname); + + if (directory == NULL) { + if (errno != ENOENT) { + fprintf(stderr, "opendir \"%s\" failed: %s\n", dirname, strerror(errno)); + } + return; + } + + while ((item = readdir(directory)) != NULL) { + if (item->d_type != DT_REG) { + continue; + } + + len = strlen(dirname) + 1 + strlen(item->d_name) + 1; + fn = reinterpret_cast(malloc(len)); + + if (fn == NULL) { + fprintf(stderr, "failed to malloc %d bytes for fn\n", len); + continue; + } + + res = snprintf(fn, len, "%s/%s", dirname, item->d_name); + + if (res < 0 || res >= len) { + fprintf(stderr, "failed to format file name (return value %d)\n", res); + free(fn); + continue; + } + + callback(fn, data); + free(fn); + } + + if (closedir(directory) == -1) { + fprintf(stderr, "closedir \"%s\" failed: %s\n", dirname, strerror(errno)); + } +} + +static void UpdateFileSize(const char* fn, void* data) { + int* size = (int*) data; + struct stat st; + + if (!fn || !data) { + return; + } + + if (stat(fn, &st) == -1) { + fprintf(stderr, "stat \"%s\" failed: %s\n", fn, strerror(errno)); + return; + } + + *size += st.st_size; +} + +// Deletes the stash directory and all files in it. Assumes that it only +// contains files. There is nothing we can do about unlikely, but possible +// errors, so they are merely logged. + +static void DeleteFile(const char* fn, void* data) { + if (fn) { + fprintf(stderr, "deleting %s\n", fn); + + if (unlink(fn) == -1 && errno != ENOENT) { + fprintf(stderr, "unlink \"%s\" failed: %s\n", fn, strerror(errno)); + } + } +} + +static void DeletePartial(const char* fn, void* data) { + if (fn && strstr(fn, ".partial") != NULL) { + DeleteFile(fn, data); + } +} + +static void DeleteStash(const char* base) { + char* dirname; + + if (base == NULL) { + return; + } + + dirname = GetStashFileName(base, NULL, NULL); + + if (dirname == NULL) { + return; + } + + fprintf(stderr, "deleting stash %s\n", base); + EnumerateStash(dirname, DeleteFile, NULL); + + if (rmdir(dirname) == -1) { + if (errno != ENOENT && errno != ENOTDIR) { + fprintf(stderr, "rmdir \"%s\" failed: %s\n", dirname, strerror(errno)); + } + } + + free(dirname); +} + +static int LoadStash(const char* base, const char* id, int verify, int* blocks, uint8_t** buffer, + size_t* buffer_alloc, int printnoent) { + char *fn = NULL; + int blockcount = 0; + int fd = -1; + int rc = -1; + int res; + struct stat st; + + if (!base || !id || !buffer || !buffer_alloc) { + goto lsout; + } + + if (!blocks) { + blocks = &blockcount; + } + + fn = GetStashFileName(base, id, NULL); + + if (fn == NULL) { + goto lsout; + } + + res = stat(fn, &st); + + if (res == -1) { + if (errno != ENOENT || printnoent) { + fprintf(stderr, "stat \"%s\" failed: %s\n", fn, strerror(errno)); + } + goto lsout; + } + + fprintf(stderr, " loading %s\n", fn); + + if ((st.st_size % BLOCKSIZE) != 0) { + fprintf(stderr, "%s size %" PRId64 " not multiple of block size %d", + fn, static_cast(st.st_size), BLOCKSIZE); + goto lsout; + } + + fd = TEMP_FAILURE_RETRY(open(fn, O_RDONLY)); + + if (fd == -1) { + fprintf(stderr, "open \"%s\" failed: %s\n", fn, strerror(errno)); + goto lsout; + } + + allocate(st.st_size, buffer, buffer_alloc); + + if (read_all(fd, *buffer, st.st_size) == -1) { + goto lsout; + } + + *blocks = st.st_size / BLOCKSIZE; + + if (verify && VerifyBlocks(id, *buffer, *blocks, 1) != 0) { + fprintf(stderr, "unexpected contents in %s\n", fn); + DeleteFile(fn, NULL); + goto lsout; + } + + rc = 0; + +lsout: + if (fd != -1) { + close(fd); + } + + if (fn) { + free(fn); + } + + return rc; +} + +static int WriteStash(const char* base, const char* id, int blocks, uint8_t* buffer, + int checkspace, int *exists) { + char *fn = NULL; + char *cn = NULL; + int fd = -1; + int rc = -1; + int dfd = -1; + int res; + struct stat st; + + if (base == NULL || buffer == NULL) { + goto wsout; + } + + if (checkspace && CacheSizeCheck(blocks * BLOCKSIZE) != 0) { + fprintf(stderr, "not enough space to write stash\n"); + goto wsout; + } + + fn = GetStashFileName(base, id, ".partial"); + cn = GetStashFileName(base, id, NULL); + + if (fn == NULL || cn == NULL) { + goto wsout; + } + + if (exists) { + res = stat(cn, &st); + + if (res == 0) { + // The file already exists and since the name is the hash of the contents, + // it's safe to assume the contents are identical (accidental hash collisions + // are unlikely) + fprintf(stderr, " skipping %d existing blocks in %s\n", blocks, cn); + *exists = 1; + rc = 0; + goto wsout; + } + + *exists = 0; + } + + fprintf(stderr, " writing %d blocks to %s\n", blocks, cn); + + fd = TEMP_FAILURE_RETRY(open(fn, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, STASH_FILE_MODE)); + + if (fd == -1) { + fprintf(stderr, "failed to create \"%s\": %s\n", fn, strerror(errno)); + goto wsout; + } + + if (write_all(fd, buffer, blocks * BLOCKSIZE) == -1) { + goto wsout; + } + + if (fsync(fd) == -1) { + fprintf(stderr, "fsync \"%s\" failed: %s\n", fn, strerror(errno)); + goto wsout; + } + + if (rename(fn, cn) == -1) { + fprintf(stderr, "rename(\"%s\", \"%s\") failed: %s\n", fn, cn, strerror(errno)); + goto wsout; + } + + const char* dname; + dname = dirname(cn); + dfd = TEMP_FAILURE_RETRY(open(dname, O_RDONLY | O_DIRECTORY)); + + if (dfd == -1) { + fprintf(stderr, "failed to open \"%s\" failed: %s\n", dname, strerror(errno)); + goto wsout; + } + + if (fsync(dfd) == -1) { + fprintf(stderr, "fsync \"%s\" failed: %s\n", dname, strerror(errno)); + goto wsout; + } + + rc = 0; + +wsout: + if (fd != -1) { + close(fd); + } + + if (dfd != -1) { + close(dfd); + } + + if (fn) { + free(fn); + } + + if (cn) { + free(cn); + } + + return rc; +} + +// Creates a directory for storing stash files and checks if the /cache partition +// hash enough space for the expected amount of blocks we need to store. Returns +// >0 if we created the directory, zero if it existed already, and <0 of failure. + +static int CreateStash(State* state, int maxblocks, const char* blockdev, char** base) { + char* dirname = NULL; + const uint8_t* digest; + int rc = -1; + int res; + int size = 0; + SHA_CTX ctx; + struct stat st; + + if (blockdev == NULL || base == NULL) { + goto csout; + } + + // Stash directory should be different for each partition to avoid conflicts + // when updating multiple partitions at the same time, so we use the hash of + // the block device name as the base directory + SHA_init(&ctx); + SHA_update(&ctx, blockdev, strlen(blockdev)); + digest = SHA_final(&ctx); + *base = PrintSha1(digest); + + if (*base == NULL) { + goto csout; + } + + dirname = GetStashFileName(*base, NULL, NULL); + + if (dirname == NULL) { + goto csout; + } + + res = stat(dirname, &st); + + if (res == -1 && errno != ENOENT) { + ErrorAbort(state, "stat \"%s\" failed: %s\n", dirname, strerror(errno)); + goto csout; + } else if (res != 0) { + fprintf(stderr, "creating stash %s\n", dirname); + res = mkdir(dirname, STASH_DIRECTORY_MODE); + + if (res != 0) { + ErrorAbort(state, "mkdir \"%s\" failed: %s\n", dirname, strerror(errno)); + goto csout; + } + + if (CacheSizeCheck(maxblocks * BLOCKSIZE) != 0) { + ErrorAbort(state, "not enough space for stash\n"); + goto csout; + } + + rc = 1; // Created directory + goto csout; + } + + fprintf(stderr, "using existing stash %s\n", dirname); + + // If the directory already exists, calculate the space already allocated to + // stash files and check if there's enough for all required blocks. Delete any + // partially completed stash files first. + + EnumerateStash(dirname, DeletePartial, NULL); + EnumerateStash(dirname, UpdateFileSize, &size); + + size = (maxblocks * BLOCKSIZE) - size; + + if (size > 0 && CacheSizeCheck(size) != 0) { + ErrorAbort(state, "not enough space for stash (%d more needed)\n", size); + goto csout; + } + + rc = 0; // Using existing directory + +csout: + if (dirname) { + free(dirname); + } + + return rc; +} + +static int SaveStash(const char* base, char** wordsave, uint8_t** buffer, size_t* buffer_alloc, + int fd, int usehash, int* isunresumable) { + char *id = NULL; + int blocks = 0; + + if (!wordsave || !buffer || !buffer_alloc || !isunresumable) { + return -1; + } + + id = strtok_r(NULL, " ", wordsave); + + if (id == NULL) { + fprintf(stderr, "missing id field in stash command\n"); + return -1; + } + + if (usehash && LoadStash(base, id, 1, &blocks, buffer, buffer_alloc, 0) == 0) { + // Stash file already exists and has expected contents. Do not + // read from source again, as the source may have been already + // overwritten during a previous attempt. + return 0; + } + + if (LoadSrcTgtVersion1(wordsave, NULL, &blocks, buffer, buffer_alloc, fd) == -1) { + return -1; + } + + if (usehash && VerifyBlocks(id, *buffer, blocks, 1) != 0) { + // Source blocks have unexpected contents. If we actually need this + // data later, this is an unrecoverable error. However, the command + // that uses the data may have already completed previously, so the + // possible failure will occur during source block verification. + fprintf(stderr, "failed to load source blocks for stash %s\n", id); + return 0; + } + + fprintf(stderr, "stashing %d blocks to %s\n", blocks, id); + return WriteStash(base, id, blocks, *buffer, 0, NULL); +} + +static int FreeStash(const char* base, const char* id) { + char *fn = NULL; + + if (base == NULL || id == NULL) { + return -1; + } + + fn = GetStashFileName(base, id, NULL); + + if (fn == NULL) { + return -1; + } + + DeleteFile(fn, NULL); + free(fn); + + return 0; +} + +static void MoveRange(uint8_t* dest, RangeSet* locs, const uint8_t* source) { + // source contains packed data, which we want to move to the + // locations given in *locs in the dest buffer. source and dest + // may be the same buffer. + + int start = locs->size; + int i; + for (i = locs->count-1; i >= 0; --i) { + int blocks = locs->pos[i*2+1] - locs->pos[i*2]; + start -= blocks; + memmove(dest + (locs->pos[i*2] * BLOCKSIZE), source + (start * BLOCKSIZE), + blocks * BLOCKSIZE); + } +} + +// Do a source/target load for move/bsdiff/imgdiff in version 2. +// 'wordsave' is the save_ptr of a strtok_r()-in-progress. We expect +// to parse the remainder of the string as one of: +// +// +// (loads data from source image only) +// +// - <[stash_id:stash_range] ...> +// (loads data from stashes only) +// +// <[stash_id:stash_range] ...> +// (loads data from both source image and stashes) +// +// On return, buffer is filled with the loaded source data (rearranged +// and combined with stashed data as necessary). buffer may be +// reallocated if needed to accommodate the source data. *tgt is the +// target RangeSet. Any stashes required are loaded using LoadStash. + +static int LoadSrcTgtVersion2(char** wordsave, RangeSet** tgt, int* src_blocks, + uint8_t** buffer, size_t* buffer_alloc, int fd, + const char* stashbase, int* overlap) { + char* word; + char* colonsave; + char* colon; + int res; + RangeSet* locs; + size_t stashalloc = 0; + uint8_t* stash = NULL; + + if (tgt != NULL) { + word = strtok_r(NULL, " ", wordsave); + *tgt = parse_range(word); + } + + word = strtok_r(NULL, " ", wordsave); + *src_blocks = strtol(word, NULL, 0); + + allocate(*src_blocks * BLOCKSIZE, buffer, buffer_alloc); + + word = strtok_r(NULL, " ", wordsave); + if (word[0] == '-' && word[1] == '\0') { + // no source ranges, only stashes + } else { + RangeSet* src = parse_range(word); + res = ReadBlocks(src, *buffer, fd); + + if (overlap && tgt) { + *overlap = range_overlaps(src, *tgt); + } + + free(src); + + if (res == -1) { + return -1; + } + + word = strtok_r(NULL, " ", wordsave); + if (word == NULL) { + // no stashes, only source range + return 0; + } + + locs = parse_range(word); + MoveRange(*buffer, locs, *buffer); + free(locs); + } + + while ((word = strtok_r(NULL, " ", wordsave)) != NULL) { + // Each word is a an index into the stash table, a colon, and + // then a rangeset describing where in the source block that + // stashed data should go. + colonsave = NULL; + colon = strtok_r(word, ":", &colonsave); + + res = LoadStash(stashbase, colon, 0, NULL, &stash, &stashalloc, 1); + + if (res == -1) { + // These source blocks will fail verification if used later, but we + // will let the caller decide if this is a fatal failure + fprintf(stderr, "failed to load stash %s\n", colon); + continue; + } + + colon = strtok_r(NULL, ":", &colonsave); + locs = parse_range(colon); + + MoveRange(*buffer, locs, stash); + free(locs); + } + + if (stash) { + free(stash); + } + + return 0; +} + +// Parameters for transfer list command functions +typedef struct { + char* cmdname; + char* cpos; + char* freestash; + char* stashbase; + int canwrite; + int createdstash; + int fd; + int foundwrites; + int isunresumable; + int version; + int written; + NewThreadInfo nti; + pthread_t thread; + size_t bufsize; + uint8_t* buffer; + uint8_t* patch_start; +} CommandParameters; + +// Do a source/target load for move/bsdiff/imgdiff in version 3. +// +// Parameters are the same as for LoadSrcTgtVersion2, except for 'onehash', which +// tells the function whether to expect separate source and targe block hashes, or +// if they are both the same and only one hash should be expected, and +// 'isunresumable', which receives a non-zero value if block verification fails in +// a way that the update cannot be resumed anymore. +// +// If the function is unable to load the necessary blocks or their contents don't +// match the hashes, the return value is -1 and the command should be aborted. +// +// If the return value is 1, the command has already been completed according to +// the contents of the target blocks, and should not be performed again. +// +// If the return value is 0, source blocks have expected content and the command +// can be performed. + +static int LoadSrcTgtVersion3(CommandParameters* params, RangeSet** tgt, int* src_blocks, + int onehash, int* overlap) { + char* srchash = NULL; + char* tgthash = NULL; + int stash_exists = 0; + int rc = -1; + uint8_t* tgtbuffer = NULL; + + if (!params|| !tgt || !src_blocks || !overlap) { + goto v3out; + } + + srchash = strtok_r(NULL, " ", ¶ms->cpos); + + if (srchash == NULL) { + fprintf(stderr, "missing source hash\n"); + goto v3out; + } + + if (onehash) { + tgthash = srchash; + } else { + tgthash = strtok_r(NULL, " ", ¶ms->cpos); + + if (tgthash == NULL) { + fprintf(stderr, "missing target hash\n"); + goto v3out; + } + } + + if (LoadSrcTgtVersion2(¶ms->cpos, tgt, src_blocks, ¶ms->buffer, ¶ms->bufsize, + params->fd, params->stashbase, overlap) == -1) { + goto v3out; + } + + tgtbuffer = (uint8_t*) malloc((*tgt)->size * BLOCKSIZE); + + if (tgtbuffer == NULL) { + fprintf(stderr, "failed to allocate %d bytes\n", (*tgt)->size * BLOCKSIZE); + goto v3out; + } + + if (ReadBlocks(*tgt, tgtbuffer, params->fd) == -1) { + goto v3out; + } + + if (VerifyBlocks(tgthash, tgtbuffer, (*tgt)->size, 0) == 0) { + // Target blocks already have expected content, command should be skipped + rc = 1; + goto v3out; + } + + if (VerifyBlocks(srchash, params->buffer, *src_blocks, 1) == 0) { + // If source and target blocks overlap, stash the source blocks so we can + // resume from possible write errors + if (*overlap) { + fprintf(stderr, "stashing %d overlapping blocks to %s\n", *src_blocks, + srchash); + + if (WriteStash(params->stashbase, srchash, *src_blocks, params->buffer, 1, + &stash_exists) != 0) { + fprintf(stderr, "failed to stash overlapping source blocks\n"); + goto v3out; + } + + // Can be deleted when the write has completed + if (!stash_exists) { + params->freestash = srchash; + } + } + + // Source blocks have expected content, command can proceed + rc = 0; + goto v3out; + } + + if (*overlap && LoadStash(params->stashbase, srchash, 1, NULL, ¶ms->buffer, + ¶ms->bufsize, 1) == 0) { + // Overlapping source blocks were previously stashed, command can proceed. + // We are recovering from an interrupted command, so we don't know if the + // stash can safely be deleted after this command. + rc = 0; + goto v3out; + } + + // Valid source data not available, update cannot be resumed + fprintf(stderr, "partition has unexpected contents\n"); + params->isunresumable = 1; + +v3out: + if (tgtbuffer) { + free(tgtbuffer); + } + + return rc; +} + +static int PerformCommandMove(CommandParameters* params) { + int blocks = 0; + int overlap = 0; + int rc = -1; + int status = 0; + RangeSet* tgt = NULL; + + if (!params) { + goto pcmout; + } + + if (params->version == 1) { + status = LoadSrcTgtVersion1(¶ms->cpos, &tgt, &blocks, ¶ms->buffer, + ¶ms->bufsize, params->fd); + } else if (params->version == 2) { + status = LoadSrcTgtVersion2(¶ms->cpos, &tgt, &blocks, ¶ms->buffer, + ¶ms->bufsize, params->fd, params->stashbase, NULL); + } else if (params->version >= 3) { + status = LoadSrcTgtVersion3(params, &tgt, &blocks, 1, &overlap); + } + + if (status == -1) { + fprintf(stderr, "failed to read blocks for move\n"); + goto pcmout; + } + + if (status == 0) { + params->foundwrites = 1; + } else if (params->foundwrites) { + fprintf(stderr, "warning: commands executed out of order [%s]\n", params->cmdname); + } + + if (params->canwrite) { + if (status == 0) { + fprintf(stderr, " moving %d blocks\n", blocks); + + if (WriteBlocks(tgt, params->buffer, params->fd) == -1) { + goto pcmout; + } + } else { + fprintf(stderr, "skipping %d already moved blocks\n", blocks); + } + + } + + if (params->freestash) { + FreeStash(params->stashbase, params->freestash); + params->freestash = NULL; + } + + params->written += tgt->size; + rc = 0; + +pcmout: + if (tgt) { + free(tgt); + } + + return rc; +} + +static int PerformCommandStash(CommandParameters* params) { + if (!params) { + return -1; + } + + return SaveStash(params->stashbase, ¶ms->cpos, ¶ms->buffer, ¶ms->bufsize, + params->fd, (params->version >= 3), ¶ms->isunresumable); +} + +static int PerformCommandFree(CommandParameters* params) { + if (!params) { + return -1; + } + + if (params->createdstash || params->canwrite) { + return FreeStash(params->stashbase, params->cpos); + } + + return 0; +} + +static int PerformCommandZero(CommandParameters* params) { + char* range = NULL; + int i; + int j; + int rc = -1; + RangeSet* tgt = NULL; + + if (!params) { + goto pczout; + } + + range = strtok_r(NULL, " ", ¶ms->cpos); + + if (range == NULL) { + fprintf(stderr, "missing target blocks for zero\n"); + goto pczout; + } + + tgt = parse_range(range); + + fprintf(stderr, " zeroing %d blocks\n", tgt->size); + + allocate(BLOCKSIZE, ¶ms->buffer, ¶ms->bufsize); + memset(params->buffer, 0, BLOCKSIZE); + + if (params->canwrite) { + for (i = 0; i < tgt->count; ++i) { + if (!check_lseek(params->fd, (off64_t) tgt->pos[i * 2] * BLOCKSIZE, SEEK_SET)) { + goto pczout; + } + + for (j = tgt->pos[i * 2]; j < tgt->pos[i * 2 + 1]; ++j) { + if (write_all(params->fd, params->buffer, BLOCKSIZE) == -1) { + goto pczout; + } + } + } + } + + if (params->cmdname[0] == 'z') { + // Update only for the zero command, as the erase command will call + // this if DEBUG_ERASE is defined. + params->written += tgt->size; + } + + rc = 0; + +pczout: + if (tgt) { + free(tgt); + } + + return rc; +} + +static int PerformCommandNew(CommandParameters* params) { + char* range = NULL; + int rc = -1; + RangeSet* tgt = NULL; + RangeSinkState rss; + + if (!params) { + goto pcnout; + } + + range = strtok_r(NULL, " ", ¶ms->cpos); + + if (range == NULL) { + goto pcnout; + } + + tgt = parse_range(range); + + if (params->canwrite) { + fprintf(stderr, " writing %d blocks of new data\n", tgt->size); + + rss.fd = params->fd; + rss.tgt = tgt; + rss.p_block = 0; + rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE; + + if (!check_lseek(params->fd, (off64_t) tgt->pos[0] * BLOCKSIZE, SEEK_SET)) { + goto pcnout; + } + + pthread_mutex_lock(¶ms->nti.mu); + params->nti.rss = &rss; + pthread_cond_broadcast(¶ms->nti.cv); + + while (params->nti.rss) { + pthread_cond_wait(¶ms->nti.cv, ¶ms->nti.mu); + } + + pthread_mutex_unlock(¶ms->nti.mu); + } + + params->written += tgt->size; + rc = 0; + +pcnout: + if (tgt) { + free(tgt); + } + + return rc; +} + +static int PerformCommandDiff(CommandParameters* params) { + char* logparams = NULL; + char* value = NULL; + int blocks = 0; + int overlap = 0; + int rc = -1; + int status = 0; + RangeSet* tgt = NULL; + RangeSinkState rss; + size_t len = 0; + size_t offset = 0; + Value patch_value; + + if (!params) { + goto pcdout; + } + + logparams = strdup(params->cpos); + value = strtok_r(NULL, " ", ¶ms->cpos); + + if (value == NULL) { + fprintf(stderr, "missing patch offset for %s\n", params->cmdname); + goto pcdout; + } + + offset = strtoul(value, NULL, 0); + + value = strtok_r(NULL, " ", ¶ms->cpos); + + if (value == NULL) { + fprintf(stderr, "missing patch length for %s\n", params->cmdname); + goto pcdout; + } + + len = strtoul(value, NULL, 0); + + if (params->version == 1) { + status = LoadSrcTgtVersion1(¶ms->cpos, &tgt, &blocks, ¶ms->buffer, + ¶ms->bufsize, params->fd); + } else if (params->version == 2) { + status = LoadSrcTgtVersion2(¶ms->cpos, &tgt, &blocks, ¶ms->buffer, + ¶ms->bufsize, params->fd, params->stashbase, NULL); + } else if (params->version >= 3) { + status = LoadSrcTgtVersion3(params, &tgt, &blocks, 0, &overlap); + } + + if (status == -1) { + fprintf(stderr, "failed to read blocks for diff\n"); + goto pcdout; + } + + if (status == 0) { + params->foundwrites = 1; + } else if (params->foundwrites) { + fprintf(stderr, "warning: commands executed out of order [%s]\n", params->cmdname); + } + + if (params->canwrite) { + if (status == 0) { + fprintf(stderr, "patching %d blocks to %d\n", blocks, tgt->size); + + patch_value.type = VAL_BLOB; + patch_value.size = len; + patch_value.data = (char*) (params->patch_start + offset); + + rss.fd = params->fd; + rss.tgt = tgt; + rss.p_block = 0; + rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE; + + if (!check_lseek(params->fd, (off64_t) tgt->pos[0] * BLOCKSIZE, SEEK_SET)) { + goto pcdout; + } + + if (params->cmdname[0] == 'i') { // imgdiff + ApplyImagePatch(params->buffer, blocks * BLOCKSIZE, &patch_value, + &RangeSinkWrite, &rss, NULL, NULL); + } else { + ApplyBSDiffPatch(params->buffer, blocks * BLOCKSIZE, &patch_value, + 0, &RangeSinkWrite, &rss, NULL); + } + + // We expect the output of the patcher to fill the tgt ranges exactly. + if (rss.p_block != tgt->count || rss.p_remain != 0) { + fprintf(stderr, "range sink underrun?\n"); + } + } else { + fprintf(stderr, "skipping %d blocks already patched to %d [%s]\n", + blocks, tgt->size, logparams); + } + } + + if (params->freestash) { + FreeStash(params->stashbase, params->freestash); + params->freestash = NULL; + } + + params->written += tgt->size; + rc = 0; + +pcdout: + if (logparams) { + free(logparams); + } + + if (tgt) { + free(tgt); + } + + return rc; +} + +static int PerformCommandErase(CommandParameters* params) { + char* range = NULL; + int i; + int rc = -1; + RangeSet* tgt = NULL; + struct stat st; + uint64_t blocks[2]; + + if (DEBUG_ERASE) { + return PerformCommandZero(params); + } + + if (!params) { + goto pceout; + } + + if (fstat(params->fd, &st) == -1) { + fprintf(stderr, "failed to fstat device to erase: %s\n", strerror(errno)); + goto pceout; + } + + if (!S_ISBLK(st.st_mode)) { + fprintf(stderr, "not a block device; skipping erase\n"); + goto pceout; + } + + range = strtok_r(NULL, " ", ¶ms->cpos); + + if (range == NULL) { + fprintf(stderr, "missing target blocks for erase\n"); + goto pceout; + } + + tgt = parse_range(range); + + if (params->canwrite) { + fprintf(stderr, " erasing %d blocks\n", tgt->size); + + for (i = 0; i < tgt->count; ++i) { + // offset in bytes + blocks[0] = tgt->pos[i * 2] * (uint64_t) BLOCKSIZE; + // length in bytes + blocks[1] = (tgt->pos[i * 2 + 1] - tgt->pos[i * 2]) * (uint64_t) BLOCKSIZE; + + if (ioctl(params->fd, BLKDISCARD, &blocks) == -1) { + fprintf(stderr, "BLKDISCARD ioctl failed: %s\n", strerror(errno)); + goto pceout; + } + } + } + + rc = 0; + +pceout: + if (tgt) { + free(tgt); + } + + return rc; +} + +// Definitions for transfer list command functions +typedef int (*CommandFunction)(CommandParameters*); + +typedef struct { + const char* name; + CommandFunction f; +} Command; + +// CompareCommands and CompareCommandNames are for the hash table + +static int CompareCommands(const void* c1, const void* c2) { + return strcmp(((const Command*) c1)->name, ((const Command*) c2)->name); +} + +static int CompareCommandNames(const void* c1, const void* c2) { + return strcmp(((const Command*) c1)->name, (const char*) c2); +} + +// HashString is used to hash command names for the hash table + +static unsigned int HashString(const char *s) { + unsigned int hash = 0; + if (s) { + while (*s) { + hash = hash * 33 + *s++; + } + } + return hash; +} + +// args: +// - block device (or file) to modify in-place +// - transfer list (blob) +// - new data stream (filename within package.zip) +// - patch stream (filename within package.zip, must be uncompressed) + +static Value* PerformBlockImageUpdate(const char* name, State* state, int argc, Expr* argv[], + const Command* commands, int cmdcount, int dryrun) { + + char* line = NULL; + char* linesave = NULL; + char* logcmd = NULL; + char* transfer_list = NULL; + CommandParameters params; + const Command* cmd = NULL; + const ZipEntry* new_entry = NULL; + const ZipEntry* patch_entry = NULL; + FILE* cmd_pipe = NULL; + HashTable* cmdht = NULL; + int i; + int res; + int rc = -1; + int stash_max_blocks = 0; + int total_blocks = 0; + pthread_attr_t attr; + unsigned int cmdhash; + UpdaterInfo* ui = NULL; + Value* blockdev_filename = NULL; + Value* new_data_fn = NULL; + Value* patch_data_fn = NULL; + Value* transfer_list_value = NULL; + ZipArchive* za = NULL; + + memset(¶ms, 0, sizeof(params)); + params.canwrite = !dryrun; + + fprintf(stderr, "performing %s\n", dryrun ? "verification" : "update"); + + if (ReadValueArgs(state, argv, 4, &blockdev_filename, &transfer_list_value, + &new_data_fn, &patch_data_fn) < 0) { + goto pbiudone; + } + + if (blockdev_filename->type != VAL_STRING) { + ErrorAbort(state, "blockdev_filename argument to %s must be string", name); + goto pbiudone; + } + if (transfer_list_value->type != VAL_BLOB) { + ErrorAbort(state, "transfer_list argument to %s must be blob", name); + goto pbiudone; + } + if (new_data_fn->type != VAL_STRING) { + ErrorAbort(state, "new_data_fn argument to %s must be string", name); + goto pbiudone; + } + if (patch_data_fn->type != VAL_STRING) { + ErrorAbort(state, "patch_data_fn argument to %s must be string", name); + goto pbiudone; + } + + ui = (UpdaterInfo*) state->cookie; + + if (ui == NULL) { + goto pbiudone; + } + + cmd_pipe = ui->cmd_pipe; + za = ui->package_zip; + + if (cmd_pipe == NULL || za == NULL) { + goto pbiudone; + } + + patch_entry = mzFindZipEntry(za, patch_data_fn->data); + + if (patch_entry == NULL) { + fprintf(stderr, "%s(): no file \"%s\" in package", name, patch_data_fn->data); + goto pbiudone; + } + + params.patch_start = ui->package_zip_addr + mzGetZipEntryOffset(patch_entry); + new_entry = mzFindZipEntry(za, new_data_fn->data); + + if (new_entry == NULL) { + fprintf(stderr, "%s(): no file \"%s\" in package", name, new_data_fn->data); + goto pbiudone; + } + + params.fd = TEMP_FAILURE_RETRY(open(blockdev_filename->data, O_RDWR)); + + if (params.fd == -1) { + fprintf(stderr, "open \"%s\" failed: %s\n", blockdev_filename->data, strerror(errno)); + goto pbiudone; + } + + if (params.canwrite) { + params.nti.za = za; + params.nti.entry = new_entry; + + pthread_mutex_init(¶ms.nti.mu, NULL); + pthread_cond_init(¶ms.nti.cv, NULL); + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + int error = pthread_create(¶ms.thread, &attr, unzip_new_data, ¶ms.nti); + if (error != 0) { + fprintf(stderr, "pthread_create failed: %s\n", strerror(error)); + goto pbiudone; + } + } + + // The data in transfer_list_value is not necessarily null-terminated, so we need + // to copy it to a new buffer and add the null that strtok_r will need. + transfer_list = reinterpret_cast(malloc(transfer_list_value->size + 1)); + + if (transfer_list == NULL) { + fprintf(stderr, "failed to allocate %zd bytes for transfer list\n", + transfer_list_value->size + 1); + goto pbiudone; + } + + memcpy(transfer_list, transfer_list_value->data, transfer_list_value->size); + transfer_list[transfer_list_value->size] = '\0'; + + // First line in transfer list is the version number + line = strtok_r(transfer_list, "\n", &linesave); + params.version = strtol(line, NULL, 0); + + if (params.version < 1 || params.version > 3) { + fprintf(stderr, "unexpected transfer list version [%s]\n", line); + goto pbiudone; + } + + fprintf(stderr, "blockimg version is %d\n", params.version); + + // Second line in transfer list is the total number of blocks we expect to write + line = strtok_r(NULL, "\n", &linesave); + total_blocks = strtol(line, NULL, 0); + + if (total_blocks < 0) { + ErrorAbort(state, "unexpected block count [%s]\n", line); + goto pbiudone; + } else if (total_blocks == 0) { + rc = 0; + goto pbiudone; + } + + if (params.version >= 2) { + // Third line is how many stash entries are needed simultaneously + line = strtok_r(NULL, "\n", &linesave); + fprintf(stderr, "maximum stash entries %s\n", line); + + // Fourth line is the maximum number of blocks that will be stashed simultaneously + line = strtok_r(NULL, "\n", &linesave); + stash_max_blocks = strtol(line, NULL, 0); + + if (stash_max_blocks < 0) { + ErrorAbort(state, "unexpected maximum stash blocks [%s]\n", line); + goto pbiudone; + } + + if (stash_max_blocks >= 0) { + res = CreateStash(state, stash_max_blocks, blockdev_filename->data, + ¶ms.stashbase); + + if (res == -1) { + goto pbiudone; + } + + params.createdstash = res; + } + } + + // Build a hash table of the available commands + cmdht = mzHashTableCreate(cmdcount, NULL); + + for (i = 0; i < cmdcount; ++i) { + cmdhash = HashString(commands[i].name); + mzHashTableLookup(cmdht, cmdhash, (void*) &commands[i], CompareCommands, true); + } + + // Subsequent lines are all individual transfer commands + for (line = strtok_r(NULL, "\n", &linesave); line; + line = strtok_r(NULL, "\n", &linesave)) { + + logcmd = strdup(line); + params.cmdname = strtok_r(line, " ", ¶ms.cpos); + + if (params.cmdname == NULL) { + fprintf(stderr, "missing command [%s]\n", line); + goto pbiudone; + } + + cmdhash = HashString(params.cmdname); + cmd = (const Command*) mzHashTableLookup(cmdht, cmdhash, params.cmdname, + CompareCommandNames, false); + + if (cmd == NULL) { + fprintf(stderr, "unexpected command [%s]\n", params.cmdname); + goto pbiudone; + } + + if (cmd->f != NULL && cmd->f(¶ms) == -1) { + fprintf(stderr, "failed to execute command [%s]\n", + logcmd ? logcmd : params.cmdname); + goto pbiudone; + } + + if (logcmd) { + free(logcmd); + logcmd = NULL; + } + + if (params.canwrite) { + fprintf(cmd_pipe, "set_progress %.4f\n", (double) params.written / total_blocks); + fflush(cmd_pipe); + } + } + + if (params.canwrite) { + pthread_join(params.thread, NULL); + + fprintf(stderr, "wrote %d blocks; expected %d\n", params.written, total_blocks); + fprintf(stderr, "max alloc needed was %zu\n", params.bufsize); + + // Delete stash only after successfully completing the update, as it + // may contain blocks needed to complete the update later. + DeleteStash(params.stashbase); + } else { + fprintf(stderr, "verified partition contents; update may be resumed\n"); + } + + rc = 0; + +pbiudone: + if (params.fd != -1) { + if (fsync(params.fd) == -1) { + fprintf(stderr, "fsync failed: %s\n", strerror(errno)); + } + close(params.fd); + } + + if (logcmd) { + free(logcmd); + } + + if (cmdht) { + mzHashTableFree(cmdht); + } + + if (params.buffer) { + free(params.buffer); + } + + if (transfer_list) { + free(transfer_list); + } + + if (blockdev_filename) { + FreeValue(blockdev_filename); + } + + if (transfer_list_value) { + FreeValue(transfer_list_value); + } + + if (new_data_fn) { + FreeValue(new_data_fn); + } + + if (patch_data_fn) { + FreeValue(patch_data_fn); + } + + // Only delete the stash if the update cannot be resumed, or it's + // a verification run and we created the stash. + if (params.isunresumable || (!params.canwrite && params.createdstash)) { + DeleteStash(params.stashbase); + } + + if (params.stashbase) { + free(params.stashbase); + } + + return StringValue(rc == 0 ? strdup("t") : strdup("")); +} + +// The transfer list is a text file containing commands to +// transfer data from one place to another on the target +// partition. We parse it and execute the commands in order: +// +// zero [rangeset] +// - fill the indicated blocks with zeros +// +// new [rangeset] +// - fill the blocks with data read from the new_data file +// +// erase [rangeset] +// - mark the given blocks as empty +// +// move <...> +// bsdiff <...> +// imgdiff <...> +// - read the source blocks, apply a patch (or not in the +// case of move), write result to target blocks. bsdiff or +// imgdiff specifies the type of patch; move means no patch +// at all. +// +// The format of <...> differs between versions 1 and 2; +// see the LoadSrcTgtVersion{1,2}() functions for a +// description of what's expected. +// +// stash +// - (version 2+ only) load the given source range and stash +// the data in the given slot of the stash table. +// +// The creator of the transfer list will guarantee that no block +// is read (ie, used as the source for a patch or move) after it +// has been written. +// +// In version 2, the creator will guarantee that a given stash is +// loaded (with a stash command) before it's used in a +// move/bsdiff/imgdiff command. +// +// Within one command the source and target ranges may overlap so +// in general we need to read the entire source into memory before +// writing anything to the target blocks. +// +// All the patch data is concatenated into one patch_data file in +// the update package. It must be stored uncompressed because we +// memory-map it in directly from the archive. (Since patches are +// already compressed, we lose very little by not compressing +// their concatenation.) +// +// In version 3, commands that read data from the partition (i.e. +// move/bsdiff/imgdiff/stash) have one or more additional hashes +// before the range parameters, which are used to check if the +// command has already been completed and verify the integrity of +// the source data. + +Value* BlockImageVerifyFn(const char* name, State* state, int argc, Expr* argv[]) { + // Commands which are not tested are set to NULL to skip them completely + const Command commands[] = { + { "bsdiff", PerformCommandDiff }, + { "erase", NULL }, + { "free", PerformCommandFree }, + { "imgdiff", PerformCommandDiff }, + { "move", PerformCommandMove }, + { "new", NULL }, + { "stash", PerformCommandStash }, + { "zero", NULL } + }; + + // Perform a dry run without writing to test if an update can proceed + return PerformBlockImageUpdate(name, state, argc, argv, commands, + sizeof(commands) / sizeof(commands[0]), 1); +} + +Value* BlockImageUpdateFn(const char* name, State* state, int argc, Expr* argv[]) { + const Command commands[] = { + { "bsdiff", PerformCommandDiff }, + { "erase", PerformCommandErase }, + { "free", PerformCommandFree }, + { "imgdiff", PerformCommandDiff }, + { "move", PerformCommandMove }, + { "new", PerformCommandNew }, + { "stash", PerformCommandStash }, + { "zero", PerformCommandZero } + }; + + return PerformBlockImageUpdate(name, state, argc, argv, commands, + sizeof(commands) / sizeof(commands[0]), 0); +} + +Value* RangeSha1Fn(const char* name, State* state, int argc, Expr* argv[]) { + Value* blockdev_filename; + Value* ranges; + const uint8_t* digest = NULL; + if (ReadValueArgs(state, argv, 2, &blockdev_filename, &ranges) < 0) { + return NULL; + } + + if (blockdev_filename->type != VAL_STRING) { + ErrorAbort(state, "blockdev_filename argument to %s must be string", name); + goto done; + } + if (ranges->type != VAL_STRING) { + ErrorAbort(state, "ranges argument to %s must be string", name); + goto done; + } + + int fd; + fd = open(blockdev_filename->data, O_RDWR); + if (fd < 0) { + ErrorAbort(state, "open \"%s\" failed: %s", blockdev_filename->data, strerror(errno)); + goto done; + } + + RangeSet* rs; + rs = parse_range(ranges->data); + uint8_t buffer[BLOCKSIZE]; + + SHA_CTX ctx; + SHA_init(&ctx); + + int i, j; + for (i = 0; i < rs->count; ++i) { + if (!check_lseek(fd, (off64_t)rs->pos[i*2] * BLOCKSIZE, SEEK_SET)) { + ErrorAbort(state, "failed to seek %s: %s", blockdev_filename->data, + strerror(errno)); + goto done; + } + + for (j = rs->pos[i*2]; j < rs->pos[i*2+1]; ++j) { + if (read_all(fd, buffer, BLOCKSIZE) == -1) { + ErrorAbort(state, "failed to read %s: %s", blockdev_filename->data, + strerror(errno)); + goto done; + } + + SHA_update(&ctx, buffer, BLOCKSIZE); + } + } + digest = SHA_final(&ctx); + close(fd); + + done: + FreeValue(blockdev_filename); + FreeValue(ranges); + if (digest == NULL) { + return StringValue(strdup("")); + } else { + return StringValue(PrintSha1(digest)); + } +} + +void RegisterBlockImageFunctions() { + RegisterFunction("block_image_verify", BlockImageVerifyFn); + RegisterFunction("block_image_update", BlockImageUpdateFn); + RegisterFunction("range_sha1", RangeSha1Fn); +} diff --git a/updater/install.c b/updater/install.c deleted file mode 100644 index 01a5dd24b..000000000 --- a/updater/install.c +++ /dev/null @@ -1,1625 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "bootloader.h" -#include "applypatch/applypatch.h" -#include "cutils/android_reboot.h" -#include "cutils/misc.h" -#include "cutils/properties.h" -#include "edify/expr.h" -#include "mincrypt/sha.h" -#include "minzip/DirUtil.h" -#include "mtdutils/mounts.h" -#include "mtdutils/mtdutils.h" -#include "updater.h" -#include "install.h" -#include "tune2fs.h" - -#ifdef USE_EXT4 -#include "make_ext4fs.h" -#include "wipe.h" -#endif - -void uiPrint(State* state, char* buffer) { - char* line = strtok(buffer, "\n"); - UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); - while (line) { - fprintf(ui->cmd_pipe, "ui_print %s\n", line); - line = strtok(NULL, "\n"); - } - fprintf(ui->cmd_pipe, "ui_print\n"); -} - -__attribute__((__format__(printf, 2, 3))) __nonnull((2)) -void uiPrintf(State* state, const char* format, ...) { - char error_msg[1024]; - va_list ap; - va_start(ap, format); - vsnprintf(error_msg, sizeof(error_msg), format, ap); - va_end(ap); - uiPrint(state, error_msg); -} - -// Take a sha-1 digest and return it as a newly-allocated hex string. -char* PrintSha1(const uint8_t* digest) { - char* buffer = malloc(SHA_DIGEST_SIZE*2 + 1); - int i; - const char* alphabet = "0123456789abcdef"; - for (i = 0; i < SHA_DIGEST_SIZE; ++i) { - buffer[i*2] = alphabet[(digest[i] >> 4) & 0xf]; - buffer[i*2+1] = alphabet[digest[i] & 0xf]; - } - buffer[i*2] = '\0'; - return buffer; -} - -// mount(fs_type, partition_type, location, mount_point) -// -// fs_type="yaffs2" partition_type="MTD" location=partition -// fs_type="ext4" partition_type="EMMC" location=device -Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) { - char* result = NULL; - if (argc != 4 && argc != 5) { - return ErrorAbort(state, "%s() expects 4-5 args, got %d", name, argc); - } - char* fs_type; - char* partition_type; - char* location; - char* mount_point; - char* mount_options; - bool has_mount_options; - if (argc == 5) { - has_mount_options = true; - if (ReadArgs(state, argv, 5, &fs_type, &partition_type, - &location, &mount_point, &mount_options) < 0) { - return NULL; - } - } else { - has_mount_options = false; - if (ReadArgs(state, argv, 4, &fs_type, &partition_type, - &location, &mount_point) < 0) { - return NULL; - } - } - - if (strlen(fs_type) == 0) { - ErrorAbort(state, "fs_type argument to %s() can't be empty", name); - goto done; - } - if (strlen(partition_type) == 0) { - ErrorAbort(state, "partition_type argument to %s() can't be empty", - name); - goto done; - } - if (strlen(location) == 0) { - ErrorAbort(state, "location argument to %s() can't be empty", name); - goto done; - } - if (strlen(mount_point) == 0) { - ErrorAbort(state, "mount_point argument to %s() can't be empty", name); - goto done; - } - - char *secontext = NULL; - - if (sehandle) { - selabel_lookup(sehandle, &secontext, mount_point, 0755); - setfscreatecon(secontext); - } - - mkdir(mount_point, 0755); - - if (secontext) { - freecon(secontext); - setfscreatecon(NULL); - } - - if (strcmp(partition_type, "MTD") == 0) { - mtd_scan_partitions(); - const MtdPartition* mtd; - mtd = mtd_find_partition_by_name(location); - if (mtd == NULL) { - uiPrintf(state, "%s: no mtd partition named \"%s\"", - name, location); - result = strdup(""); - goto done; - } - if (mtd_mount_partition(mtd, mount_point, fs_type, 0 /* rw */) != 0) { - uiPrintf(state, "mtd mount of %s failed: %s\n", - location, strerror(errno)); - result = strdup(""); - goto done; - } - result = mount_point; - } else { - if (mount(location, mount_point, fs_type, - MS_NOATIME | MS_NODEV | MS_NODIRATIME, - has_mount_options ? mount_options : "") < 0) { - uiPrintf(state, "%s: failed to mount %s at %s: %s\n", - name, location, mount_point, strerror(errno)); - result = strdup(""); - } else { - result = mount_point; - } - } - -done: - free(fs_type); - free(partition_type); - free(location); - if (result != mount_point) free(mount_point); - if (has_mount_options) free(mount_options); - return StringValue(result); -} - - -// is_mounted(mount_point) -Value* IsMountedFn(const char* name, State* state, int argc, Expr* argv[]) { - char* result = NULL; - if (argc != 1) { - return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); - } - char* mount_point; - if (ReadArgs(state, argv, 1, &mount_point) < 0) { - return NULL; - } - if (strlen(mount_point) == 0) { - ErrorAbort(state, "mount_point argument to unmount() can't be empty"); - goto done; - } - - scan_mounted_volumes(); - const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point); - if (vol == NULL) { - result = strdup(""); - } else { - result = mount_point; - } - -done: - if (result != mount_point) free(mount_point); - return StringValue(result); -} - - -Value* UnmountFn(const char* name, State* state, int argc, Expr* argv[]) { - char* result = NULL; - if (argc != 1) { - return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); - } - char* mount_point; - if (ReadArgs(state, argv, 1, &mount_point) < 0) { - return NULL; - } - if (strlen(mount_point) == 0) { - ErrorAbort(state, "mount_point argument to unmount() can't be empty"); - goto done; - } - - scan_mounted_volumes(); - const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point); - if (vol == NULL) { - uiPrintf(state, "unmount of %s failed; no such volume\n", mount_point); - result = strdup(""); - } else { - int ret = unmount_mounted_volume(vol); - if (ret != 0) { - uiPrintf(state, "unmount of %s failed (%d): %s\n", - mount_point, ret, strerror(errno)); - } - result = mount_point; - } - -done: - if (result != mount_point) free(mount_point); - return StringValue(result); -} - -static int exec_cmd(const char* path, char* const argv[]) { - int status; - pid_t child; - if ((child = vfork()) == 0) { - execv(path, argv); - _exit(-1); - } - waitpid(child, &status, 0); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - printf("%s failed with status %d\n", path, WEXITSTATUS(status)); - } - return WEXITSTATUS(status); -} - - -// format(fs_type, partition_type, location, fs_size, mount_point) -// -// fs_type="yaffs2" partition_type="MTD" location=partition fs_size= mount_point= -// fs_type="ext4" partition_type="EMMC" location=device fs_size= mount_point= -// fs_type="f2fs" partition_type="EMMC" location=device fs_size= mount_point= -// if fs_size == 0, then make fs uses the entire partition. -// if fs_size > 0, that is the size to use -// if fs_size < 0, then reserve that many bytes at the end of the partition (not for "f2fs") -Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { - char* result = NULL; - if (argc != 5) { - return ErrorAbort(state, "%s() expects 5 args, got %d", name, argc); - } - char* fs_type; - char* partition_type; - char* location; - char* fs_size; - char* mount_point; - - if (ReadArgs(state, argv, 5, &fs_type, &partition_type, &location, &fs_size, &mount_point) < 0) { - return NULL; - } - - if (strlen(fs_type) == 0) { - ErrorAbort(state, "fs_type argument to %s() can't be empty", name); - goto done; - } - if (strlen(partition_type) == 0) { - ErrorAbort(state, "partition_type argument to %s() can't be empty", - name); - goto done; - } - if (strlen(location) == 0) { - ErrorAbort(state, "location argument to %s() can't be empty", name); - goto done; - } - - if (strlen(mount_point) == 0) { - ErrorAbort(state, "mount_point argument to %s() can't be empty", name); - goto done; - } - - if (strcmp(partition_type, "MTD") == 0) { - mtd_scan_partitions(); - const MtdPartition* mtd = mtd_find_partition_by_name(location); - if (mtd == NULL) { - printf("%s: no mtd partition named \"%s\"", - name, location); - result = strdup(""); - goto done; - } - MtdWriteContext* ctx = mtd_write_partition(mtd); - if (ctx == NULL) { - printf("%s: can't write \"%s\"", name, location); - result = strdup(""); - goto done; - } - if (mtd_erase_blocks(ctx, -1) == -1) { - mtd_write_close(ctx); - printf("%s: failed to erase \"%s\"", name, location); - result = strdup(""); - goto done; - } - if (mtd_write_close(ctx) != 0) { - printf("%s: failed to close \"%s\"", name, location); - result = strdup(""); - goto done; - } - result = location; -#ifdef USE_EXT4 - } else if (strcmp(fs_type, "ext4") == 0) { - int status = make_ext4fs(location, atoll(fs_size), mount_point, sehandle); - if (status != 0) { - printf("%s: make_ext4fs failed (%d) on %s", - name, status, location); - result = strdup(""); - goto done; - } - result = location; - } else if (strcmp(fs_type, "f2fs") == 0) { - char *num_sectors; - if (asprintf(&num_sectors, "%lld", atoll(fs_size) / 512) <= 0) { - printf("format_volume: failed to create %s command for %s\n", fs_type, location); - result = strdup(""); - goto done; - } - const char *f2fs_path = "/sbin/mkfs.f2fs"; - const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", location, num_sectors, NULL}; - int status = exec_cmd(f2fs_path, (char* const*)f2fs_argv); - free(num_sectors); - if (status != 0) { - printf("%s: mkfs.f2fs failed (%d) on %s", - name, status, location); - result = strdup(""); - goto done; - } - result = location; -#endif - } else { - printf("%s: unsupported fs_type \"%s\" partition_type \"%s\"", - name, fs_type, partition_type); - } - -done: - free(fs_type); - free(partition_type); - if (result != location) free(location); - return StringValue(result); -} - -Value* RenameFn(const char* name, State* state, int argc, Expr* argv[]) { - char* result = NULL; - if (argc != 2) { - return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); - } - - char* src_name; - char* dst_name; - - if (ReadArgs(state, argv, 2, &src_name, &dst_name) < 0) { - return NULL; - } - if (strlen(src_name) == 0) { - ErrorAbort(state, "src_name argument to %s() can't be empty", name); - goto done; - } - if (strlen(dst_name) == 0) { - ErrorAbort(state, "dst_name argument to %s() can't be empty", name); - goto done; - } - if (make_parents(dst_name) != 0) { - ErrorAbort(state, "Creating parent of %s failed, error %s", - dst_name, strerror(errno)); - } else if (access(dst_name, F_OK) == 0 && access(src_name, F_OK) != 0) { - // File was already moved - result = dst_name; - } else if (rename(src_name, dst_name) != 0) { - ErrorAbort(state, "Rename of %s to %s failed, error %s", - src_name, dst_name, strerror(errno)); - } else { - result = dst_name; - } - -done: - free(src_name); - if (result != dst_name) free(dst_name); - return StringValue(result); -} - -Value* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) { - char** paths = malloc(argc * sizeof(char*)); - int i; - for (i = 0; i < argc; ++i) { - paths[i] = Evaluate(state, argv[i]); - if (paths[i] == NULL) { - int j; - for (j = 0; j < i; ++i) { - free(paths[j]); - } - free(paths); - return NULL; - } - } - - bool recursive = (strcmp(name, "delete_recursive") == 0); - - int success = 0; - for (i = 0; i < argc; ++i) { - if ((recursive ? dirUnlinkHierarchy(paths[i]) : unlink(paths[i])) == 0) - ++success; - free(paths[i]); - } - free(paths); - - char buffer[10]; - sprintf(buffer, "%d", success); - return StringValue(strdup(buffer)); -} - - -Value* ShowProgressFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 2) { - return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); - } - char* frac_str; - char* sec_str; - if (ReadArgs(state, argv, 2, &frac_str, &sec_str) < 0) { - return NULL; - } - - double frac = strtod(frac_str, NULL); - int sec = strtol(sec_str, NULL, 10); - - UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); - fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec); - - free(sec_str); - return StringValue(frac_str); -} - -Value* SetProgressFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 1) { - return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); - } - char* frac_str; - if (ReadArgs(state, argv, 1, &frac_str) < 0) { - return NULL; - } - - double frac = strtod(frac_str, NULL); - - UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); - fprintf(ui->cmd_pipe, "set_progress %f\n", frac); - - return StringValue(frac_str); -} - -// package_extract_dir(package_path, destination_path) -Value* PackageExtractDirFn(const char* name, State* state, - int argc, Expr* argv[]) { - if (argc != 2) { - return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); - } - char* zip_path; - char* dest_path; - if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL; - - ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; - - // To create a consistent system image, never use the clock for timestamps. - struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default - - bool success = mzExtractRecursive(za, zip_path, dest_path, - ×tamp, - NULL, NULL, sehandle); - free(zip_path); - free(dest_path); - return StringValue(strdup(success ? "t" : "")); -} - - -// package_extract_file(package_path, destination_path) -// or -// package_extract_file(package_path) -// to return the entire contents of the file as the result of this -// function (the char* returned is actually a FileContents*). -Value* PackageExtractFileFn(const char* name, State* state, - int argc, Expr* argv[]) { - if (argc < 1 || argc > 2) { - return ErrorAbort(state, "%s() expects 1 or 2 args, got %d", - name, argc); - } - bool success = false; - - UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); - - if (argc == 2) { - // The two-argument version extracts to a file. - - ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; - - char* zip_path; - char* dest_path; - if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL; - - const ZipEntry* entry = mzFindZipEntry(za, zip_path); - if (entry == NULL) { - printf("%s: no %s in package\n", name, zip_path); - goto done2; - } - - FILE* f = fopen(dest_path, "wb"); - if (f == NULL) { - printf("%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) { - printf("%s: no %s in package\n", name, zip_path); - goto done1; - } - - v->size = mzGetZipEntryUncompLen(entry); - v->data = malloc(v->size); - if (v->data == NULL) { - printf("%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; - } -} - -// Create all parent directories of name, if necessary. -static int make_parents(char* name) { - char* p; - for (p = name + (strlen(name)-1); p > name; --p) { - if (*p != '/') continue; - *p = '\0'; - if (make_parents(name) < 0) return -1; - int result = mkdir(name, 0700); - if (result == 0) printf("created [%s]\n", name); - *p = '/'; - if (result == 0 || errno == EEXIST) { - // successfully created or already existed; we're done - return 0; - } else { - printf("failed to mkdir %s: %s\n", name, strerror(errno)); - return -1; - } - } - return 0; -} - -// symlink target src1 src2 ... -// unlinks any previously existing src1, src2, etc before creating symlinks. -Value* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc == 0) { - return ErrorAbort(state, "%s() expects 1+ args, got %d", name, argc); - } - char* target; - target = Evaluate(state, argv[0]); - if (target == NULL) return NULL; - - char** srcs = ReadVarArgs(state, argc-1, argv+1); - if (srcs == NULL) { - free(target); - return NULL; - } - - int bad = 0; - int i; - for (i = 0; i < argc-1; ++i) { - if (unlink(srcs[i]) < 0) { - if (errno != ENOENT) { - printf("%s: failed to remove %s: %s\n", - name, srcs[i], strerror(errno)); - ++bad; - } - } - if (make_parents(srcs[i])) { - printf("%s: failed to symlink %s to %s: making parents failed\n", - name, srcs[i], target); - ++bad; - } - if (symlink(target, srcs[i]) < 0) { - printf("%s: failed to symlink %s to %s: %s\n", - name, srcs[i], target, strerror(errno)); - ++bad; - } - free(srcs[i]); - } - free(srcs); - if (bad) { - return ErrorAbort(state, "%s: some symlinks failed", name); - } - return StringValue(strdup("")); -} - -struct perm_parsed_args { - bool has_uid; - uid_t uid; - bool has_gid; - gid_t gid; - bool has_mode; - mode_t mode; - bool has_fmode; - mode_t fmode; - bool has_dmode; - mode_t dmode; - bool has_selabel; - char* selabel; - bool has_capabilities; - uint64_t capabilities; -}; - -static struct perm_parsed_args ParsePermArgs(State * state, int argc, char** args) { - int i; - struct perm_parsed_args parsed; - int bad = 0; - static int max_warnings = 20; - - memset(&parsed, 0, sizeof(parsed)); - - for (i = 1; i < argc; i += 2) { - if (strcmp("uid", args[i]) == 0) { - int64_t uid; - if (sscanf(args[i+1], "%" SCNd64, &uid) == 1) { - parsed.uid = uid; - parsed.has_uid = true; - } else { - uiPrintf(state, "ParsePermArgs: invalid UID \"%s\"\n", args[i + 1]); - bad++; - } - continue; - } - if (strcmp("gid", args[i]) == 0) { - int64_t gid; - if (sscanf(args[i+1], "%" SCNd64, &gid) == 1) { - parsed.gid = gid; - parsed.has_gid = true; - } else { - uiPrintf(state, "ParsePermArgs: invalid GID \"%s\"\n", args[i + 1]); - bad++; - } - continue; - } - if (strcmp("mode", args[i]) == 0) { - int32_t mode; - if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) { - parsed.mode = mode; - parsed.has_mode = true; - } else { - uiPrintf(state, "ParsePermArgs: invalid mode \"%s\"\n", args[i + 1]); - bad++; - } - continue; - } - if (strcmp("dmode", args[i]) == 0) { - int32_t mode; - if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) { - parsed.dmode = mode; - parsed.has_dmode = true; - } else { - uiPrintf(state, "ParsePermArgs: invalid dmode \"%s\"\n", args[i + 1]); - bad++; - } - continue; - } - if (strcmp("fmode", args[i]) == 0) { - int32_t mode; - if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) { - parsed.fmode = mode; - parsed.has_fmode = true; - } else { - uiPrintf(state, "ParsePermArgs: invalid fmode \"%s\"\n", args[i + 1]); - bad++; - } - continue; - } - if (strcmp("capabilities", args[i]) == 0) { - int64_t capabilities; - if (sscanf(args[i+1], "%" SCNi64, &capabilities) == 1) { - parsed.capabilities = capabilities; - parsed.has_capabilities = true; - } else { - uiPrintf(state, "ParsePermArgs: invalid capabilities \"%s\"\n", args[i + 1]); - bad++; - } - continue; - } - if (strcmp("selabel", args[i]) == 0) { - if (args[i+1][0] != '\0') { - parsed.selabel = args[i+1]; - parsed.has_selabel = true; - } else { - uiPrintf(state, "ParsePermArgs: invalid selabel \"%s\"\n", args[i + 1]); - bad++; - } - continue; - } - if (max_warnings != 0) { - printf("ParsedPermArgs: unknown key \"%s\", ignoring\n", args[i]); - max_warnings--; - if (max_warnings == 0) { - printf("ParsedPermArgs: suppressing further warnings\n"); - } - } - } - return parsed; -} - -static int ApplyParsedPerms( - State * state, - const char* filename, - const struct stat *statptr, - struct perm_parsed_args parsed) -{ - int bad = 0; - - if (parsed.has_selabel) { - if (lsetfilecon(filename, parsed.selabel) != 0) { - uiPrintf(state, "ApplyParsedPerms: lsetfilecon of %s to %s failed: %s\n", - filename, parsed.selabel, strerror(errno)); - bad++; - } - } - - /* ignore symlinks */ - if (S_ISLNK(statptr->st_mode)) { - return bad; - } - - if (parsed.has_uid) { - if (chown(filename, parsed.uid, -1) < 0) { - uiPrintf(state, "ApplyParsedPerms: chown of %s to %d failed: %s\n", - filename, parsed.uid, strerror(errno)); - bad++; - } - } - - if (parsed.has_gid) { - if (chown(filename, -1, parsed.gid) < 0) { - uiPrintf(state, "ApplyParsedPerms: chgrp of %s to %d failed: %s\n", - filename, parsed.gid, strerror(errno)); - bad++; - } - } - - if (parsed.has_mode) { - if (chmod(filename, parsed.mode) < 0) { - uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", - filename, parsed.mode, strerror(errno)); - bad++; - } - } - - if (parsed.has_dmode && S_ISDIR(statptr->st_mode)) { - if (chmod(filename, parsed.dmode) < 0) { - uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", - filename, parsed.dmode, strerror(errno)); - bad++; - } - } - - if (parsed.has_fmode && S_ISREG(statptr->st_mode)) { - if (chmod(filename, parsed.fmode) < 0) { - uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", - filename, parsed.fmode, strerror(errno)); - bad++; - } - } - - if (parsed.has_capabilities && S_ISREG(statptr->st_mode)) { - if (parsed.capabilities == 0) { - if ((removexattr(filename, XATTR_NAME_CAPS) == -1) && (errno != ENODATA)) { - // Report failure unless it's ENODATA (attribute not set) - uiPrintf(state, "ApplyParsedPerms: removexattr of %s to %" PRIx64 " failed: %s\n", - filename, parsed.capabilities, strerror(errno)); - bad++; - } - } else { - struct vfs_cap_data cap_data; - memset(&cap_data, 0, sizeof(cap_data)); - cap_data.magic_etc = VFS_CAP_REVISION | VFS_CAP_FLAGS_EFFECTIVE; - cap_data.data[0].permitted = (uint32_t) (parsed.capabilities & 0xffffffff); - cap_data.data[0].inheritable = 0; - cap_data.data[1].permitted = (uint32_t) (parsed.capabilities >> 32); - cap_data.data[1].inheritable = 0; - if (setxattr(filename, XATTR_NAME_CAPS, &cap_data, sizeof(cap_data), 0) < 0) { - uiPrintf(state, "ApplyParsedPerms: setcap of %s to %" PRIx64 " failed: %s\n", - filename, parsed.capabilities, strerror(errno)); - bad++; - } - } - } - - return bad; -} - -// nftw doesn't allow us to pass along context, so we need to use -// global variables. *sigh* -static struct perm_parsed_args recursive_parsed_args; -static State* recursive_state; - -static int do_SetMetadataRecursive(const char* filename, const struct stat *statptr, - int fileflags, struct FTW *pfwt) { - return ApplyParsedPerms(recursive_state, filename, statptr, recursive_parsed_args); -} - -static Value* SetMetadataFn(const char* name, State* state, int argc, Expr* argv[]) { - int i; - int bad = 0; - static int nwarnings = 0; - struct stat sb; - Value* result = NULL; - - bool recursive = (strcmp(name, "set_metadata_recursive") == 0); - - if ((argc % 2) != 1) { - return ErrorAbort(state, "%s() expects an odd number of arguments, got %d", - name, argc); - } - - char** args = ReadVarArgs(state, argc, argv); - if (args == NULL) return NULL; - - if (lstat(args[0], &sb) == -1) { - result = ErrorAbort(state, "%s: Error on lstat of \"%s\": %s", name, args[0], strerror(errno)); - goto done; - } - - struct perm_parsed_args parsed = ParsePermArgs(state, argc, args); - - if (recursive) { - recursive_parsed_args = parsed; - recursive_state = state; - bad += nftw(args[0], do_SetMetadataRecursive, 30, FTW_CHDIR | FTW_DEPTH | FTW_PHYS); - memset(&recursive_parsed_args, 0, sizeof(recursive_parsed_args)); - recursive_state = NULL; - } else { - bad += ApplyParsedPerms(state, args[0], &sb, parsed); - } - -done: - for (i = 0; i < argc; ++i) { - free(args[i]); - } - free(args); - - if (result != NULL) { - return result; - } - - if (bad > 0) { - return ErrorAbort(state, "%s: some changes failed", name); - } - - return StringValue(strdup("")); -} - -Value* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 1) { - return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); - } - char* key; - key = Evaluate(state, argv[0]); - if (key == NULL) return NULL; - - char value[PROPERTY_VALUE_MAX]; - property_get(key, value, ""); - free(key); - - return StringValue(strdup(value)); -} - - -// file_getprop(file, key) -// -// interprets 'file' as a getprop-style file (key=value pairs, one -// per line. # comment lines,blank lines, lines without '=' ignored), -// and returns the value for 'key' (or "" if it isn't defined). -Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) { - char* result = NULL; - char* buffer = NULL; - char* filename; - char* key; - if (ReadArgs(state, argv, 2, &filename, &key) < 0) { - return NULL; - } - - struct stat st; - if (stat(filename, &st) < 0) { - ErrorAbort(state, "%s: failed to stat \"%s\": %s", - name, filename, strerror(errno)); - goto done; - } - -#define MAX_FILE_GETPROP_SIZE 65536 - - if (st.st_size > MAX_FILE_GETPROP_SIZE) { - ErrorAbort(state, "%s too large for %s (max %d)", - filename, name, MAX_FILE_GETPROP_SIZE); - goto done; - } - - buffer = malloc(st.st_size+1); - if (buffer == NULL) { - ErrorAbort(state, "%s: failed to alloc %lld bytes", name, (long long)st.st_size+1); - goto done; - } - - FILE* f = fopen(filename, "rb"); - if (f == NULL) { - ErrorAbort(state, "%s: failed to open %s: %s", - name, filename, strerror(errno)); - goto done; - } - - if (fread(buffer, 1, st.st_size, f) != st.st_size) { - ErrorAbort(state, "%s: failed to read %lld bytes from %s", - name, (long long)st.st_size+1, filename); - fclose(f); - goto done; - } - buffer[st.st_size] = '\0'; - - fclose(f); - - char* line = strtok(buffer, "\n"); - do { - // skip whitespace at start of line - while (*line && isspace(*line)) ++line; - - // comment or blank line: skip to next line - if (*line == '\0' || *line == '#') continue; - - char* equal = strchr(line, '='); - if (equal == NULL) { - continue; - } - - // trim whitespace between key and '=' - char* key_end = equal-1; - while (key_end > line && isspace(*key_end)) --key_end; - key_end[1] = '\0'; - - // not the key we're looking for - if (strcmp(key, line) != 0) continue; - - // skip whitespace after the '=' to the start of the value - char* val_start = equal+1; - while(*val_start && isspace(*val_start)) ++val_start; - - // trim trailing whitespace - char* val_end = val_start + strlen(val_start)-1; - while (val_end > val_start && isspace(*val_end)) --val_end; - val_end[1] = '\0'; - - result = strdup(val_start); - break; - - } while ((line = strtok(NULL, "\n"))); - - if (result == NULL) result = strdup(""); - - done: - free(filename); - free(key); - free(buffer); - return StringValue(result); -} - - -static bool write_raw_image_cb(const unsigned char* data, - int data_len, void* ctx) { - int r = mtd_write_data((MtdWriteContext*)ctx, (const char *)data, data_len); - if (r == data_len) return true; - printf("%s\n", strerror(errno)); - return false; -} - -// write_raw_image(filename_or_blob, partition) -Value* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) { - char* result = NULL; - - Value* partition_value; - Value* contents; - if (ReadValueArgs(state, argv, 2, &contents, &partition_value) < 0) { - return NULL; - } - - char* partition = NULL; - if (partition_value->type != VAL_STRING) { - ErrorAbort(state, "partition argument to %s must be string", name); - goto done; - } - partition = partition_value->data; - if (strlen(partition) == 0) { - ErrorAbort(state, "partition argument to %s can't be empty", name); - goto done; - } - if (contents->type == VAL_STRING && strlen((char*) contents->data) == 0) { - ErrorAbort(state, "file argument to %s can't be empty", name); - goto done; - } - - mtd_scan_partitions(); - const MtdPartition* mtd = mtd_find_partition_by_name(partition); - if (mtd == NULL) { - printf("%s: no mtd partition named \"%s\"\n", name, partition); - result = strdup(""); - goto done; - } - - MtdWriteContext* ctx = mtd_write_partition(mtd); - if (ctx == NULL) { - printf("%s: can't write mtd partition \"%s\"\n", - name, partition); - result = strdup(""); - goto done; - } - - bool success; - - if (contents->type == VAL_STRING) { - // we're given a filename as the contents - char* filename = contents->data; - FILE* f = fopen(filename, "rb"); - if (f == NULL) { - printf("%s: can't open %s: %s\n", - name, filename, strerror(errno)); - result = strdup(""); - goto done; - } - - success = true; - char* buffer = malloc(BUFSIZ); - int read; - while (success && (read = fread(buffer, 1, BUFSIZ, f)) > 0) { - int wrote = mtd_write_data(ctx, buffer, read); - success = success && (wrote == read); - } - free(buffer); - fclose(f); - } else { - // we're given a blob as the contents - ssize_t wrote = mtd_write_data(ctx, contents->data, contents->size); - success = (wrote == contents->size); - } - if (!success) { - printf("mtd_write_data to %s failed: %s\n", - partition, strerror(errno)); - } - - if (mtd_erase_blocks(ctx, -1) == -1) { - printf("%s: error erasing blocks of %s\n", name, partition); - } - if (mtd_write_close(ctx) != 0) { - printf("%s: error closing write of %s\n", name, partition); - } - - printf("%s %s partition\n", - success ? "wrote" : "failed to write", partition); - - result = success ? partition : strdup(""); - -done: - if (result != partition) FreeValue(partition_value); - FreeValue(contents); - return StringValue(result); -} - -// apply_patch_space(bytes) -Value* ApplyPatchSpaceFn(const char* name, State* state, - int argc, Expr* argv[]) { - char* bytes_str; - if (ReadArgs(state, argv, 1, &bytes_str) < 0) { - return NULL; - } - - 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; - } - - return StringValue(strdup(CacheSizeCheck(bytes) ? "" : "t")); -} - -// apply_patch(file, size, init_sha1, tgt_sha1, patch) - -Value* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc < 6 || (argc % 2) == 1) { - return ErrorAbort(state, "%s(): expected at least 6 args and an " - "even number, got %d", - name, argc); - } - - char* source_filename; - char* target_filename; - char* target_sha1; - char* target_size_str; - if (ReadArgs(state, argv, 4, &source_filename, &target_filename, - &target_sha1, &target_size_str) < 0) { - return NULL; - } - - 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 < 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; - } - - 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, NULL); - - for (i = 0; i < patchcount; ++i) { - FreeValue(patches[i]); - } - free(patch_sha_str); - free(patches); - - return StringValue(strdup(result == 0 ? "t" : "")); -} - -// apply_patch_check(file, [sha1_1, ...]) -Value* ApplyPatchCheckFn(const char* name, State* state, - int argc, Expr* argv[]) { - if (argc < 1) { - return ErrorAbort(state, "%s(): expected at least 1 arg, got %d", - name, argc); - } - - char* filename; - if (ReadArgs(state, argv, 1, &filename) < 0) { - return NULL; - } - - int patchcount = argc-1; - char** sha1s = ReadVarArgs(state, argc-1, argv+1); - - int result = applypatch_check(filename, patchcount, sha1s); - - int i; - for (i = 0; i < patchcount; ++i) { - free(sha1s[i]); - } - free(sha1s); - - return StringValue(strdup(result == 0 ? "t" : "")); -} - -Value* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) { - char** args = ReadVarArgs(state, argc, argv); - if (args == NULL) { - return NULL; - } - - int size = 0; - int i; - for (i = 0; i < argc; ++i) { - size += strlen(args[i]); - } - char* buffer = malloc(size+1); - size = 0; - for (i = 0; i < argc; ++i) { - strcpy(buffer+size, args[i]); - size += strlen(args[i]); - free(args[i]); - } - free(args); - buffer[size] = '\0'; - uiPrint(state, buffer); - return StringValue(buffer); -} - -Value* WipeCacheFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 0) { - return ErrorAbort(state, "%s() expects no args, got %d", name, argc); - } - fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe, "wipe_cache\n"); - return StringValue(strdup("t")); -} - -Value* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc < 1) { - return ErrorAbort(state, "%s() expects at least 1 arg", name); - } - char** args = ReadVarArgs(state, argc, argv); - if (args == NULL) { - return NULL; - } - - char** args2 = malloc(sizeof(char*) * (argc+1)); - memcpy(args2, args, sizeof(char*) * argc); - args2[argc] = NULL; - - printf("about to run program [%s] with %d args\n", args2[0], argc); - - pid_t child = fork(); - if (child == 0) { - execv(args2[0], args2); - printf("run_program: execv failed: %s\n", strerror(errno)); - _exit(1); - } - int status; - waitpid(child, &status, 0); - if (WIFEXITED(status)) { - if (WEXITSTATUS(status) != 0) { - printf("run_program: child exited with status %d\n", - WEXITSTATUS(status)); - } - } else if (WIFSIGNALED(status)) { - printf("run_program: child terminated by signal %d\n", - WTERMSIG(status)); - } - - int i; - for (i = 0; i < argc; ++i) { - free(args[i]); - } - free(args); - free(args2); - - char buffer[20]; - sprintf(buffer, "%d", status); - - return StringValue(strdup(buffer)); -} - -// sha1_check(data) -// to return the sha1 of the data (given in the format returned by -// read_file). -// -// sha1_check(data, sha1_hex, [sha1_hex, ...]) -// returns the sha1 of the file if it matches any of the hex -// strings passed, or "" if it does not equal any of them. -// -Value* Sha1CheckFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc < 1) { - return ErrorAbort(state, "%s() expects at least 1 arg", name); - } - - Value** args = ReadValueVarArgs(state, argc, argv); - if (args == NULL) { - return NULL; - } - - if (args[0]->size < 0) { - return StringValue(strdup("")); - } - uint8_t digest[SHA_DIGEST_SIZE]; - SHA_hash(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) { - printf("%s(): arg %d is not a string; skipping", - name, i); - } else if (ParseSha1(args[i]->data, arg_digest) != 0) { - // Warn about bad args and skip them. - printf("%s(): error parsing \"%s\" as sha-1; skipping", - name, args[i]->data); - } else if (memcmp(digest, arg_digest, SHA_DIGEST_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 Value* returned -// is actually a FileContents*). -Value* ReadFileFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 1) { - return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); - } - char* filename; - if (ReadArgs(state, argv, 1, &filename) < 0) return NULL; - - Value* v = malloc(sizeof(Value)); - v->type = VAL_BLOB; - - FileContents fc; - if (LoadFileContents(filename, &fc) != 0) { - free(filename); - v->size = -1; - v->data = NULL; - free(fc.data); - return v; - } - - v->size = fc.size; - v->data = (char*)fc.data; - - free(filename); - return v; -} - -// Immediately reboot the device. Recovery is not finished normally, -// so if you reboot into recovery it will re-start applying the -// current package (because nothing has cleared the copy of the -// arguments stored in the BCB). -// -// The argument is the partition name passed to the android reboot -// property. It can be "recovery" to boot from the recovery -// partition, or "" (empty string) to boot from the regular boot -// partition. -Value* RebootNowFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 2) { - return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); - } - - char* filename; - char* property; - if (ReadArgs(state, argv, 2, &filename, &property) < 0) return NULL; - - char buffer[80]; - - // zero out the 'command' field of the bootloader message. - memset(buffer, 0, sizeof(((struct bootloader_message*)0)->command)); - FILE* f = fopen(filename, "r+b"); - fseek(f, offsetof(struct bootloader_message, command), SEEK_SET); - fwrite(buffer, sizeof(((struct bootloader_message*)0)->command), 1, f); - fclose(f); - free(filename); - - strcpy(buffer, "reboot,"); - if (property != NULL) { - strncat(buffer, property, sizeof(buffer)-10); - } - - property_set(ANDROID_RB_PROPERTY, buffer); - - sleep(5); - free(property); - ErrorAbort(state, "%s() failed to reboot", name); - return NULL; -} - -// Store a string value somewhere that future invocations of recovery -// can access it. This value is called the "stage" and can be used to -// drive packages that need to do reboots in the middle of -// installation and keep track of where they are in the multi-stage -// install. -// -// The first argument is the block device for the misc partition -// ("/misc" in the fstab), which is where this value is stored. The -// second argument is the string to store; it should not exceed 31 -// bytes. -Value* SetStageFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 2) { - return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); - } - - char* filename; - char* stagestr; - if (ReadArgs(state, argv, 2, &filename, &stagestr) < 0) return NULL; - - // Store this value in the misc partition, immediately after the - // bootloader message that the main recovery uses to save its - // arguments in case of the device restarting midway through - // package installation. - FILE* f = fopen(filename, "r+b"); - fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET); - int to_write = strlen(stagestr)+1; - int max_size = sizeof(((struct bootloader_message*)0)->stage); - if (to_write > max_size) { - to_write = max_size; - stagestr[max_size-1] = 0; - } - fwrite(stagestr, to_write, 1, f); - fclose(f); - - free(stagestr); - return StringValue(filename); -} - -// Return the value most recently saved with SetStageFn. The argument -// is the block device for the misc partition. -Value* GetStageFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 1) { - return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); - } - - char* filename; - if (ReadArgs(state, argv, 1, &filename) < 0) return NULL; - - char buffer[sizeof(((struct bootloader_message*)0)->stage)]; - FILE* f = fopen(filename, "rb"); - fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET); - fread(buffer, sizeof(buffer), 1, f); - fclose(f); - buffer[sizeof(buffer)-1] = '\0'; - - return StringValue(strdup(buffer)); -} - -Value* WipeBlockDeviceFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 2) { - return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); - } - - char* filename; - char* len_str; - if (ReadArgs(state, argv, 2, &filename, &len_str) < 0) return NULL; - - size_t len = strtoull(len_str, NULL, 0); - int fd = open(filename, O_WRONLY, 0644); - int success = wipe_block_device(fd, len); - - free(filename); - free(len_str); - - close(fd); - - return StringValue(strdup(success ? "t" : "")); -} - -Value* EnableRebootFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 0) { - return ErrorAbort(state, "%s() expects no args, got %d", name, argc); - } - UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); - fprintf(ui->cmd_pipe, "enable_reboot\n"); - return StringValue(strdup("t")); -} - -Value* Tune2FsFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc == 0) { - return ErrorAbort(state, "%s() expects args, got %d", name, argc); - } - - char** args = ReadVarArgs(state, argc, argv); - if (args == NULL) { - return ErrorAbort(state, "%s() could not read args", name); - } - - int i; - char** args2 = malloc(sizeof(char*) * (argc+1)); - // Tune2fs expects the program name as its args[0] - args2[0] = strdup(name); - for (i = 0; i < argc; ++i) { - args2[i + 1] = args[i]; - } - int result = tune2fs_main(argc + 1, args2); - for (i = 0; i < argc; ++i) { - free(args[i]); - } - free(args); - - free(args2[0]); - free(args2); - if (result != 0) { - return ErrorAbort(state, "%s() returned error code %d", name, result); - } - return StringValue(strdup("t")); -} - -void RegisterInstallFunctions() { - RegisterFunction("mount", MountFn); - RegisterFunction("is_mounted", IsMountedFn); - RegisterFunction("unmount", UnmountFn); - RegisterFunction("format", FormatFn); - RegisterFunction("show_progress", ShowProgressFn); - RegisterFunction("set_progress", SetProgressFn); - RegisterFunction("delete", DeleteFn); - RegisterFunction("delete_recursive", DeleteFn); - RegisterFunction("package_extract_dir", PackageExtractDirFn); - RegisterFunction("package_extract_file", PackageExtractFileFn); - RegisterFunction("symlink", SymlinkFn); - - // Usage: - // set_metadata("filename", "key1", "value1", "key2", "value2", ...) - // Example: - // set_metadata("/system/bin/netcfg", "uid", 0, "gid", 3003, "mode", 02750, "selabel", "u:object_r:system_file:s0", "capabilities", 0x0); - RegisterFunction("set_metadata", SetMetadataFn); - - // Usage: - // set_metadata_recursive("dirname", "key1", "value1", "key2", "value2", ...) - // Example: - // set_metadata_recursive("/system", "uid", 0, "gid", 0, "fmode", 0644, "dmode", 0755, "selabel", "u:object_r:system_file:s0", "capabilities", 0x0); - RegisterFunction("set_metadata_recursive", SetMetadataFn); - - RegisterFunction("getprop", GetPropFn); - RegisterFunction("file_getprop", FileGetPropFn); - RegisterFunction("write_raw_image", WriteRawImageFn); - - RegisterFunction("apply_patch", ApplyPatchFn); - RegisterFunction("apply_patch_check", ApplyPatchCheckFn); - RegisterFunction("apply_patch_space", ApplyPatchSpaceFn); - - RegisterFunction("wipe_block_device", WipeBlockDeviceFn); - - RegisterFunction("read_file", ReadFileFn); - RegisterFunction("sha1_check", Sha1CheckFn); - RegisterFunction("rename", RenameFn); - - RegisterFunction("wipe_cache", WipeCacheFn); - - RegisterFunction("ui_print", UIPrintFn); - - RegisterFunction("run_program", RunProgramFn); - - RegisterFunction("reboot_now", RebootNowFn); - RegisterFunction("get_stage", GetStageFn); - RegisterFunction("set_stage", SetStageFn); - - RegisterFunction("enable_reboot", EnableRebootFn); - RegisterFunction("tune2fs", Tune2FsFn); -} diff --git a/updater/install.cpp b/updater/install.cpp new file mode 100644 index 000000000..a2bc4029f --- /dev/null +++ b/updater/install.cpp @@ -0,0 +1,1617 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bootloader.h" +#include "applypatch/applypatch.h" +#include "cutils/android_reboot.h" +#include "cutils/misc.h" +#include "cutils/properties.h" +#include "edify/expr.h" +#include "mincrypt/sha.h" +#include "minzip/DirUtil.h" +#include "mtdutils/mounts.h" +#include "mtdutils/mtdutils.h" +#include "updater.h" +#include "install.h" +#include "tune2fs.h" + +#ifdef USE_EXT4 +#include "make_ext4fs.h" +#include "wipe.h" +#endif + +void uiPrint(State* state, char* buffer) { + char* line = strtok(buffer, "\n"); + UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); + while (line) { + fprintf(ui->cmd_pipe, "ui_print %s\n", line); + line = strtok(NULL, "\n"); + } + fprintf(ui->cmd_pipe, "ui_print\n"); +} + +__attribute__((__format__(printf, 2, 3))) __nonnull((2)) +void uiPrintf(State* state, const char* format, ...) { + char error_msg[1024]; + va_list ap; + va_start(ap, format); + vsnprintf(error_msg, sizeof(error_msg), format, ap); + va_end(ap); + uiPrint(state, error_msg); +} + +// Take a sha-1 digest and return it as a newly-allocated hex string. +char* PrintSha1(const uint8_t* digest) { + char* buffer = reinterpret_cast(malloc(SHA_DIGEST_SIZE*2 + 1)); + const char* alphabet = "0123456789abcdef"; + size_t i; + for (i = 0; i < SHA_DIGEST_SIZE; ++i) { + buffer[i*2] = alphabet[(digest[i] >> 4) & 0xf]; + buffer[i*2+1] = alphabet[digest[i] & 0xf]; + } + buffer[i*2] = '\0'; + return buffer; +} + +// mount(fs_type, partition_type, location, mount_point) +// +// fs_type="yaffs2" partition_type="MTD" location=partition +// fs_type="ext4" partition_type="EMMC" location=device +Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + if (argc != 4 && argc != 5) { + return ErrorAbort(state, "%s() expects 4-5 args, got %d", name, argc); + } + char* fs_type; + char* partition_type; + char* location; + char* mount_point; + char* mount_options; + bool has_mount_options; + if (argc == 5) { + has_mount_options = true; + if (ReadArgs(state, argv, 5, &fs_type, &partition_type, + &location, &mount_point, &mount_options) < 0) { + return NULL; + } + } else { + has_mount_options = false; + if (ReadArgs(state, argv, 4, &fs_type, &partition_type, + &location, &mount_point) < 0) { + return NULL; + } + } + + if (strlen(fs_type) == 0) { + ErrorAbort(state, "fs_type argument to %s() can't be empty", name); + goto done; + } + if (strlen(partition_type) == 0) { + ErrorAbort(state, "partition_type argument to %s() can't be empty", + name); + goto done; + } + if (strlen(location) == 0) { + ErrorAbort(state, "location argument to %s() can't be empty", name); + goto done; + } + if (strlen(mount_point) == 0) { + ErrorAbort(state, "mount_point argument to %s() can't be empty", name); + goto done; + } + + { + char *secontext = NULL; + + if (sehandle) { + selabel_lookup(sehandle, &secontext, mount_point, 0755); + setfscreatecon(secontext); + } + + mkdir(mount_point, 0755); + + if (secontext) { + freecon(secontext); + setfscreatecon(NULL); + } + } + + if (strcmp(partition_type, "MTD") == 0) { + mtd_scan_partitions(); + const MtdPartition* mtd; + mtd = mtd_find_partition_by_name(location); + if (mtd == NULL) { + uiPrintf(state, "%s: no mtd partition named \"%s\"", + name, location); + result = strdup(""); + goto done; + } + if (mtd_mount_partition(mtd, mount_point, fs_type, 0 /* rw */) != 0) { + uiPrintf(state, "mtd mount of %s failed: %s\n", + location, strerror(errno)); + result = strdup(""); + goto done; + } + result = mount_point; + } else { + if (mount(location, mount_point, fs_type, + MS_NOATIME | MS_NODEV | MS_NODIRATIME, + has_mount_options ? mount_options : "") < 0) { + uiPrintf(state, "%s: failed to mount %s at %s: %s\n", + name, location, mount_point, strerror(errno)); + result = strdup(""); + } else { + result = mount_point; + } + } + +done: + free(fs_type); + free(partition_type); + free(location); + if (result != mount_point) free(mount_point); + if (has_mount_options) free(mount_options); + return StringValue(result); +} + + +// is_mounted(mount_point) +Value* IsMountedFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); + } + char* mount_point; + if (ReadArgs(state, argv, 1, &mount_point) < 0) { + return NULL; + } + if (strlen(mount_point) == 0) { + ErrorAbort(state, "mount_point argument to unmount() can't be empty"); + goto done; + } + + scan_mounted_volumes(); + { + const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point); + if (vol == NULL) { + result = strdup(""); + } else { + result = mount_point; + } + } + +done: + if (result != mount_point) free(mount_point); + return StringValue(result); +} + + +Value* UnmountFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); + } + char* mount_point; + if (ReadArgs(state, argv, 1, &mount_point) < 0) { + return NULL; + } + if (strlen(mount_point) == 0) { + ErrorAbort(state, "mount_point argument to unmount() can't be empty"); + goto done; + } + + scan_mounted_volumes(); + { + const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point); + if (vol == NULL) { + uiPrintf(state, "unmount of %s failed; no such volume\n", mount_point); + result = strdup(""); + } else { + int ret = unmount_mounted_volume(vol); + if (ret != 0) { + uiPrintf(state, "unmount of %s failed (%d): %s\n", + mount_point, ret, strerror(errno)); + } + result = mount_point; + } + } + +done: + if (result != mount_point) free(mount_point); + return StringValue(result); +} + +static int exec_cmd(const char* path, char* const argv[]) { + int status; + pid_t child; + if ((child = vfork()) == 0) { + execv(path, argv); + _exit(-1); + } + waitpid(child, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + printf("%s failed with status %d\n", path, WEXITSTATUS(status)); + } + return WEXITSTATUS(status); +} + + +// format(fs_type, partition_type, location, fs_size, mount_point) +// +// fs_type="yaffs2" partition_type="MTD" location=partition fs_size= mount_point= +// fs_type="ext4" partition_type="EMMC" location=device fs_size= mount_point= +// fs_type="f2fs" partition_type="EMMC" location=device fs_size= mount_point= +// if fs_size == 0, then make fs uses the entire partition. +// if fs_size > 0, that is the size to use +// if fs_size < 0, then reserve that many bytes at the end of the partition (not for "f2fs") +Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + if (argc != 5) { + return ErrorAbort(state, "%s() expects 5 args, got %d", name, argc); + } + char* fs_type; + char* partition_type; + char* location; + char* fs_size; + char* mount_point; + + if (ReadArgs(state, argv, 5, &fs_type, &partition_type, &location, &fs_size, &mount_point) < 0) { + return NULL; + } + + if (strlen(fs_type) == 0) { + ErrorAbort(state, "fs_type argument to %s() can't be empty", name); + goto done; + } + if (strlen(partition_type) == 0) { + ErrorAbort(state, "partition_type argument to %s() can't be empty", + name); + goto done; + } + if (strlen(location) == 0) { + ErrorAbort(state, "location argument to %s() can't be empty", name); + goto done; + } + + if (strlen(mount_point) == 0) { + ErrorAbort(state, "mount_point argument to %s() can't be empty", name); + goto done; + } + + if (strcmp(partition_type, "MTD") == 0) { + mtd_scan_partitions(); + const MtdPartition* mtd = mtd_find_partition_by_name(location); + if (mtd == NULL) { + printf("%s: no mtd partition named \"%s\"", + name, location); + result = strdup(""); + goto done; + } + MtdWriteContext* ctx = mtd_write_partition(mtd); + if (ctx == NULL) { + printf("%s: can't write \"%s\"", name, location); + result = strdup(""); + goto done; + } + if (mtd_erase_blocks(ctx, -1) == -1) { + mtd_write_close(ctx); + printf("%s: failed to erase \"%s\"", name, location); + result = strdup(""); + goto done; + } + if (mtd_write_close(ctx) != 0) { + printf("%s: failed to close \"%s\"", name, location); + result = strdup(""); + goto done; + } + result = location; +#ifdef USE_EXT4 + } else if (strcmp(fs_type, "ext4") == 0) { + int status = make_ext4fs(location, atoll(fs_size), mount_point, sehandle); + if (status != 0) { + printf("%s: make_ext4fs failed (%d) on %s", + name, status, location); + result = strdup(""); + goto done; + } + result = location; + } else if (strcmp(fs_type, "f2fs") == 0) { + char *num_sectors; + if (asprintf(&num_sectors, "%lld", atoll(fs_size) / 512) <= 0) { + printf("format_volume: failed to create %s command for %s\n", fs_type, location); + result = strdup(""); + goto done; + } + const char *f2fs_path = "/sbin/mkfs.f2fs"; + const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", location, num_sectors, NULL}; + int status = exec_cmd(f2fs_path, (char* const*)f2fs_argv); + free(num_sectors); + if (status != 0) { + printf("%s: mkfs.f2fs failed (%d) on %s", + name, status, location); + result = strdup(""); + goto done; + } + result = location; +#endif + } else { + printf("%s: unsupported fs_type \"%s\" partition_type \"%s\"", + name, fs_type, partition_type); + } + +done: + free(fs_type); + free(partition_type); + if (result != location) free(location); + return StringValue(result); +} + +Value* RenameFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + + char* src_name; + char* dst_name; + + if (ReadArgs(state, argv, 2, &src_name, &dst_name) < 0) { + return NULL; + } + if (strlen(src_name) == 0) { + ErrorAbort(state, "src_name argument to %s() can't be empty", name); + goto done; + } + if (strlen(dst_name) == 0) { + ErrorAbort(state, "dst_name argument to %s() can't be empty", name); + goto done; + } + if (make_parents(dst_name) != 0) { + ErrorAbort(state, "Creating parent of %s failed, error %s", + dst_name, strerror(errno)); + } else if (access(dst_name, F_OK) == 0 && access(src_name, F_OK) != 0) { + // File was already moved + result = dst_name; + } else if (rename(src_name, dst_name) != 0) { + ErrorAbort(state, "Rename of %s to %s failed, error %s", + src_name, dst_name, strerror(errno)); + } else { + result = dst_name; + } + +done: + free(src_name); + if (result != dst_name) free(dst_name); + return StringValue(result); +} + +Value* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) { + char** paths = reinterpret_cast(malloc(argc * sizeof(char*))); + for (int i = 0; i < argc; ++i) { + paths[i] = Evaluate(state, argv[i]); + if (paths[i] == NULL) { + int j; + for (j = 0; j < i; ++i) { + free(paths[j]); + } + free(paths); + return NULL; + } + } + + bool recursive = (strcmp(name, "delete_recursive") == 0); + + int success = 0; + for (int i = 0; i < argc; ++i) { + if ((recursive ? dirUnlinkHierarchy(paths[i]) : unlink(paths[i])) == 0) + ++success; + free(paths[i]); + } + free(paths); + + char buffer[10]; + sprintf(buffer, "%d", success); + return StringValue(strdup(buffer)); +} + + +Value* ShowProgressFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + char* frac_str; + char* sec_str; + if (ReadArgs(state, argv, 2, &frac_str, &sec_str) < 0) { + return NULL; + } + + double frac = strtod(frac_str, NULL); + int sec = strtol(sec_str, NULL, 10); + + UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); + fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec); + + free(sec_str); + return StringValue(frac_str); +} + +Value* SetProgressFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); + } + char* frac_str; + if (ReadArgs(state, argv, 1, &frac_str) < 0) { + return NULL; + } + + double frac = strtod(frac_str, NULL); + + UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); + fprintf(ui->cmd_pipe, "set_progress %f\n", frac); + + return StringValue(frac_str); +} + +// package_extract_dir(package_path, destination_path) +Value* PackageExtractDirFn(const char* name, State* state, + int argc, Expr* argv[]) { + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + char* zip_path; + char* dest_path; + if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL; + + ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; + + // To create a consistent system image, never use the clock for timestamps. + struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default + + bool success = mzExtractRecursive(za, zip_path, dest_path, + ×tamp, + NULL, NULL, sehandle); + free(zip_path); + free(dest_path); + return StringValue(strdup(success ? "t" : "")); +} + + +// package_extract_file(package_path, destination_path) +// or +// package_extract_file(package_path) +// to return the entire contents of the file as the result of this +// function (the char* returned is actually a FileContents*). +Value* PackageExtractFileFn(const char* name, State* state, + int argc, Expr* argv[]) { + if (argc < 1 || argc > 2) { + return ErrorAbort(state, "%s() expects 1 or 2 args, got %d", + name, argc); + } + bool success = false; + + if (argc == 2) { + // The two-argument version extracts to a file. + + ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; + + char* zip_path; + char* dest_path; + if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL; + + const ZipEntry* entry = mzFindZipEntry(za, zip_path); + if (entry == NULL) { + printf("%s: no %s in package\n", name, zip_path); + goto done2; + } + + { + FILE* f = fopen(dest_path, "wb"); + if (f == NULL) { + printf("%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 = reinterpret_cast(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) { + printf("%s: no %s in package\n", name, zip_path); + goto done1; + } + + v->size = mzGetZipEntryUncompLen(entry); + v->data = reinterpret_cast(malloc(v->size)); + if (v->data == NULL) { + printf("%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; + } +} + +// Create all parent directories of name, if necessary. +static int make_parents(char* name) { + char* p; + for (p = name + (strlen(name)-1); p > name; --p) { + if (*p != '/') continue; + *p = '\0'; + if (make_parents(name) < 0) return -1; + int result = mkdir(name, 0700); + if (result == 0) printf("created [%s]\n", name); + *p = '/'; + if (result == 0 || errno == EEXIST) { + // successfully created or already existed; we're done + return 0; + } else { + printf("failed to mkdir %s: %s\n", name, strerror(errno)); + return -1; + } + } + return 0; +} + +// symlink target src1 src2 ... +// unlinks any previously existing src1, src2, etc before creating symlinks. +Value* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc == 0) { + return ErrorAbort(state, "%s() expects 1+ args, got %d", name, argc); + } + char* target; + target = Evaluate(state, argv[0]); + if (target == NULL) return NULL; + + char** srcs = ReadVarArgs(state, argc-1, argv+1); + if (srcs == NULL) { + free(target); + return NULL; + } + + int bad = 0; + int i; + for (i = 0; i < argc-1; ++i) { + if (unlink(srcs[i]) < 0) { + if (errno != ENOENT) { + printf("%s: failed to remove %s: %s\n", + name, srcs[i], strerror(errno)); + ++bad; + } + } + if (make_parents(srcs[i])) { + printf("%s: failed to symlink %s to %s: making parents failed\n", + name, srcs[i], target); + ++bad; + } + if (symlink(target, srcs[i]) < 0) { + printf("%s: failed to symlink %s to %s: %s\n", + name, srcs[i], target, strerror(errno)); + ++bad; + } + free(srcs[i]); + } + free(srcs); + if (bad) { + return ErrorAbort(state, "%s: some symlinks failed", name); + } + return StringValue(strdup("")); +} + +struct perm_parsed_args { + bool has_uid; + uid_t uid; + bool has_gid; + gid_t gid; + bool has_mode; + mode_t mode; + bool has_fmode; + mode_t fmode; + bool has_dmode; + mode_t dmode; + bool has_selabel; + char* selabel; + bool has_capabilities; + uint64_t capabilities; +}; + +static struct perm_parsed_args ParsePermArgs(State * state, int argc, char** args) { + int i; + struct perm_parsed_args parsed; + int bad = 0; + static int max_warnings = 20; + + memset(&parsed, 0, sizeof(parsed)); + + for (i = 1; i < argc; i += 2) { + if (strcmp("uid", args[i]) == 0) { + int64_t uid; + if (sscanf(args[i+1], "%" SCNd64, &uid) == 1) { + parsed.uid = uid; + parsed.has_uid = true; + } else { + uiPrintf(state, "ParsePermArgs: invalid UID \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("gid", args[i]) == 0) { + int64_t gid; + if (sscanf(args[i+1], "%" SCNd64, &gid) == 1) { + parsed.gid = gid; + parsed.has_gid = true; + } else { + uiPrintf(state, "ParsePermArgs: invalid GID \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("mode", args[i]) == 0) { + int32_t mode; + if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) { + parsed.mode = mode; + parsed.has_mode = true; + } else { + uiPrintf(state, "ParsePermArgs: invalid mode \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("dmode", args[i]) == 0) { + int32_t mode; + if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) { + parsed.dmode = mode; + parsed.has_dmode = true; + } else { + uiPrintf(state, "ParsePermArgs: invalid dmode \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("fmode", args[i]) == 0) { + int32_t mode; + if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) { + parsed.fmode = mode; + parsed.has_fmode = true; + } else { + uiPrintf(state, "ParsePermArgs: invalid fmode \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("capabilities", args[i]) == 0) { + int64_t capabilities; + if (sscanf(args[i+1], "%" SCNi64, &capabilities) == 1) { + parsed.capabilities = capabilities; + parsed.has_capabilities = true; + } else { + uiPrintf(state, "ParsePermArgs: invalid capabilities \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (strcmp("selabel", args[i]) == 0) { + if (args[i+1][0] != '\0') { + parsed.selabel = args[i+1]; + parsed.has_selabel = true; + } else { + uiPrintf(state, "ParsePermArgs: invalid selabel \"%s\"\n", args[i + 1]); + bad++; + } + continue; + } + if (max_warnings != 0) { + printf("ParsedPermArgs: unknown key \"%s\", ignoring\n", args[i]); + max_warnings--; + if (max_warnings == 0) { + printf("ParsedPermArgs: suppressing further warnings\n"); + } + } + } + return parsed; +} + +static int ApplyParsedPerms( + State * state, + const char* filename, + const struct stat *statptr, + struct perm_parsed_args parsed) +{ + int bad = 0; + + if (parsed.has_selabel) { + if (lsetfilecon(filename, parsed.selabel) != 0) { + uiPrintf(state, "ApplyParsedPerms: lsetfilecon of %s to %s failed: %s\n", + filename, parsed.selabel, strerror(errno)); + bad++; + } + } + + /* ignore symlinks */ + if (S_ISLNK(statptr->st_mode)) { + return bad; + } + + if (parsed.has_uid) { + if (chown(filename, parsed.uid, -1) < 0) { + uiPrintf(state, "ApplyParsedPerms: chown of %s to %d failed: %s\n", + filename, parsed.uid, strerror(errno)); + bad++; + } + } + + if (parsed.has_gid) { + if (chown(filename, -1, parsed.gid) < 0) { + uiPrintf(state, "ApplyParsedPerms: chgrp of %s to %d failed: %s\n", + filename, parsed.gid, strerror(errno)); + bad++; + } + } + + if (parsed.has_mode) { + if (chmod(filename, parsed.mode) < 0) { + uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", + filename, parsed.mode, strerror(errno)); + bad++; + } + } + + if (parsed.has_dmode && S_ISDIR(statptr->st_mode)) { + if (chmod(filename, parsed.dmode) < 0) { + uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", + filename, parsed.dmode, strerror(errno)); + bad++; + } + } + + if (parsed.has_fmode && S_ISREG(statptr->st_mode)) { + if (chmod(filename, parsed.fmode) < 0) { + uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", + filename, parsed.fmode, strerror(errno)); + bad++; + } + } + + if (parsed.has_capabilities && S_ISREG(statptr->st_mode)) { + if (parsed.capabilities == 0) { + if ((removexattr(filename, XATTR_NAME_CAPS) == -1) && (errno != ENODATA)) { + // Report failure unless it's ENODATA (attribute not set) + uiPrintf(state, "ApplyParsedPerms: removexattr of %s to %" PRIx64 " failed: %s\n", + filename, parsed.capabilities, strerror(errno)); + bad++; + } + } else { + struct vfs_cap_data cap_data; + memset(&cap_data, 0, sizeof(cap_data)); + cap_data.magic_etc = VFS_CAP_REVISION | VFS_CAP_FLAGS_EFFECTIVE; + cap_data.data[0].permitted = (uint32_t) (parsed.capabilities & 0xffffffff); + cap_data.data[0].inheritable = 0; + cap_data.data[1].permitted = (uint32_t) (parsed.capabilities >> 32); + cap_data.data[1].inheritable = 0; + if (setxattr(filename, XATTR_NAME_CAPS, &cap_data, sizeof(cap_data), 0) < 0) { + uiPrintf(state, "ApplyParsedPerms: setcap of %s to %" PRIx64 " failed: %s\n", + filename, parsed.capabilities, strerror(errno)); + bad++; + } + } + } + + return bad; +} + +// nftw doesn't allow us to pass along context, so we need to use +// global variables. *sigh* +static struct perm_parsed_args recursive_parsed_args; +static State* recursive_state; + +static int do_SetMetadataRecursive(const char* filename, const struct stat *statptr, + int fileflags, struct FTW *pfwt) { + return ApplyParsedPerms(recursive_state, filename, statptr, recursive_parsed_args); +} + +static Value* SetMetadataFn(const char* name, State* state, int argc, Expr* argv[]) { + int bad = 0; + struct stat sb; + Value* result = NULL; + + bool recursive = (strcmp(name, "set_metadata_recursive") == 0); + + if ((argc % 2) != 1) { + return ErrorAbort(state, "%s() expects an odd number of arguments, got %d", name, argc); + } + + char** args = ReadVarArgs(state, argc, argv); + if (args == NULL) return NULL; + + if (lstat(args[0], &sb) == -1) { + result = ErrorAbort(state, "%s: Error on lstat of \"%s\": %s", name, args[0], strerror(errno)); + goto done; + } + + { + struct perm_parsed_args parsed = ParsePermArgs(state, argc, args); + + if (recursive) { + recursive_parsed_args = parsed; + recursive_state = state; + bad += nftw(args[0], do_SetMetadataRecursive, 30, FTW_CHDIR | FTW_DEPTH | FTW_PHYS); + memset(&recursive_parsed_args, 0, sizeof(recursive_parsed_args)); + recursive_state = NULL; + } else { + bad += ApplyParsedPerms(state, args[0], &sb, parsed); + } + } + +done: + for (int i = 0; i < argc; ++i) { + free(args[i]); + } + free(args); + + if (result != NULL) { + return result; + } + + if (bad > 0) { + return ErrorAbort(state, "%s: some changes failed", name); + } + + return StringValue(strdup("")); +} + +Value* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); + } + char* key = Evaluate(state, argv[0]); + if (key == NULL) return NULL; + + char value[PROPERTY_VALUE_MAX]; + property_get(key, value, ""); + free(key); + + return StringValue(strdup(value)); +} + + +// file_getprop(file, key) +// +// interprets 'file' as a getprop-style file (key=value pairs, one +// per line. # comment lines,blank lines, lines without '=' ignored), +// and returns the value for 'key' (or "" if it isn't defined). +Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + char* buffer = NULL; + char* filename; + char* key; + if (ReadArgs(state, argv, 2, &filename, &key) < 0) { + return NULL; + } + + struct stat st; + if (stat(filename, &st) < 0) { + ErrorAbort(state, "%s: failed to stat \"%s\": %s", name, filename, strerror(errno)); + goto done; + } + +#define MAX_FILE_GETPROP_SIZE 65536 + + if (st.st_size > MAX_FILE_GETPROP_SIZE) { + ErrorAbort(state, "%s too large for %s (max %d)", filename, name, MAX_FILE_GETPROP_SIZE); + goto done; + } + + buffer = reinterpret_cast(malloc(st.st_size+1)); + if (buffer == NULL) { + ErrorAbort(state, "%s: failed to alloc %lld bytes", name, (long long)st.st_size+1); + goto done; + } + + FILE* f; + f = fopen(filename, "rb"); + if (f == NULL) { + ErrorAbort(state, "%s: failed to open %s: %s", name, filename, strerror(errno)); + goto done; + } + + if (fread(buffer, 1, st.st_size, f) != st.st_size) { + ErrorAbort(state, "%s: failed to read %lld bytes from %s", + name, (long long)st.st_size+1, filename); + fclose(f); + goto done; + } + buffer[st.st_size] = '\0'; + + fclose(f); + + char* line; + line = strtok(buffer, "\n"); + do { + // skip whitespace at start of line + while (*line && isspace(*line)) ++line; + + // comment or blank line: skip to next line + if (*line == '\0' || *line == '#') continue; + + char* equal = strchr(line, '='); + if (equal == NULL) { + continue; + } + + // trim whitespace between key and '=' + char* key_end = equal-1; + while (key_end > line && isspace(*key_end)) --key_end; + key_end[1] = '\0'; + + // not the key we're looking for + if (strcmp(key, line) != 0) continue; + + // skip whitespace after the '=' to the start of the value + char* val_start = equal+1; + while(*val_start && isspace(*val_start)) ++val_start; + + // trim trailing whitespace + char* val_end = val_start + strlen(val_start)-1; + while (val_end > val_start && isspace(*val_end)) --val_end; + val_end[1] = '\0'; + + result = strdup(val_start); + break; + + } while ((line = strtok(NULL, "\n"))); + + if (result == NULL) result = strdup(""); + + done: + free(filename); + free(key); + free(buffer); + return StringValue(result); +} + +// write_raw_image(filename_or_blob, partition) +Value* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) { + char* result = NULL; + + Value* partition_value; + Value* contents; + if (ReadValueArgs(state, argv, 2, &contents, &partition_value) < 0) { + return NULL; + } + + char* partition = NULL; + if (partition_value->type != VAL_STRING) { + ErrorAbort(state, "partition argument to %s must be string", name); + goto done; + } + partition = partition_value->data; + if (strlen(partition) == 0) { + ErrorAbort(state, "partition argument to %s can't be empty", name); + goto done; + } + if (contents->type == VAL_STRING && strlen((char*) contents->data) == 0) { + ErrorAbort(state, "file argument to %s can't be empty", name); + goto done; + } + + mtd_scan_partitions(); + const MtdPartition* mtd; + mtd = mtd_find_partition_by_name(partition); + if (mtd == NULL) { + printf("%s: no mtd partition named \"%s\"\n", name, partition); + result = strdup(""); + goto done; + } + + MtdWriteContext* ctx; + ctx = mtd_write_partition(mtd); + if (ctx == NULL) { + printf("%s: can't write mtd partition \"%s\"\n", + name, partition); + result = strdup(""); + goto done; + } + + bool success; + + if (contents->type == VAL_STRING) { + // we're given a filename as the contents + char* filename = contents->data; + FILE* f = fopen(filename, "rb"); + if (f == NULL) { + printf("%s: can't open %s: %s\n", name, filename, strerror(errno)); + result = strdup(""); + goto done; + } + + success = true; + char* buffer = reinterpret_cast(malloc(BUFSIZ)); + int read; + while (success && (read = fread(buffer, 1, BUFSIZ, f)) > 0) { + int wrote = mtd_write_data(ctx, buffer, read); + success = success && (wrote == read); + } + free(buffer); + fclose(f); + } else { + // we're given a blob as the contents + ssize_t wrote = mtd_write_data(ctx, contents->data, contents->size); + success = (wrote == contents->size); + } + if (!success) { + printf("mtd_write_data to %s failed: %s\n", + partition, strerror(errno)); + } + + if (mtd_erase_blocks(ctx, -1) == -1) { + printf("%s: error erasing blocks of %s\n", name, partition); + } + if (mtd_write_close(ctx) != 0) { + printf("%s: error closing write of %s\n", name, partition); + } + + printf("%s %s partition\n", + success ? "wrote" : "failed to write", partition); + + result = success ? partition : strdup(""); + +done: + if (result != partition) FreeValue(partition_value); + FreeValue(contents); + return StringValue(result); +} + +// apply_patch_space(bytes) +Value* ApplyPatchSpaceFn(const char* name, State* state, + int argc, Expr* argv[]) { + char* bytes_str; + if (ReadArgs(state, argv, 1, &bytes_str) < 0) { + return NULL; + } + + 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; + } + + return StringValue(strdup(CacheSizeCheck(bytes) ? "" : "t")); +} + +// apply_patch(file, size, init_sha1, tgt_sha1, patch) + +Value* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc < 6 || (argc % 2) == 1) { + return ErrorAbort(state, "%s(): expected at least 6 args and an " + "even number, got %d", + name, argc); + } + + char* source_filename; + char* target_filename; + char* target_sha1; + char* target_size_str; + if (ReadArgs(state, argv, 4, &source_filename, &target_filename, + &target_sha1, &target_size_str) < 0) { + return NULL; + } + + 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 < 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; + } + + char** patch_sha_str = reinterpret_cast(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, NULL); + + for (i = 0; i < patchcount; ++i) { + FreeValue(patches[i]); + } + free(patch_sha_str); + free(patches); + + return StringValue(strdup(result == 0 ? "t" : "")); +} + +// apply_patch_check(file, [sha1_1, ...]) +Value* ApplyPatchCheckFn(const char* name, State* state, + int argc, Expr* argv[]) { + if (argc < 1) { + return ErrorAbort(state, "%s(): expected at least 1 arg, got %d", + name, argc); + } + + char* filename; + if (ReadArgs(state, argv, 1, &filename) < 0) { + return NULL; + } + + int patchcount = argc-1; + char** sha1s = ReadVarArgs(state, argc-1, argv+1); + + int result = applypatch_check(filename, patchcount, sha1s); + + int i; + for (i = 0; i < patchcount; ++i) { + free(sha1s[i]); + } + free(sha1s); + + return StringValue(strdup(result == 0 ? "t" : "")); +} + +Value* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) { + char** args = ReadVarArgs(state, argc, argv); + if (args == NULL) { + return NULL; + } + + int size = 0; + int i; + for (i = 0; i < argc; ++i) { + size += strlen(args[i]); + } + char* buffer = reinterpret_cast(malloc(size+1)); + size = 0; + for (i = 0; i < argc; ++i) { + strcpy(buffer+size, args[i]); + size += strlen(args[i]); + free(args[i]); + } + free(args); + buffer[size] = '\0'; + uiPrint(state, buffer); + return StringValue(buffer); +} + +Value* WipeCacheFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 0) { + return ErrorAbort(state, "%s() expects no args, got %d", name, argc); + } + fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe, "wipe_cache\n"); + return StringValue(strdup("t")); +} + +Value* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc < 1) { + return ErrorAbort(state, "%s() expects at least 1 arg", name); + } + char** args = ReadVarArgs(state, argc, argv); + if (args == NULL) { + return NULL; + } + + char** args2 = reinterpret_cast(malloc(sizeof(char*) * (argc+1))); + memcpy(args2, args, sizeof(char*) * argc); + args2[argc] = NULL; + + printf("about to run program [%s] with %d args\n", args2[0], argc); + + pid_t child = fork(); + if (child == 0) { + execv(args2[0], args2); + printf("run_program: execv failed: %s\n", strerror(errno)); + _exit(1); + } + int status; + waitpid(child, &status, 0); + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + printf("run_program: child exited with status %d\n", + WEXITSTATUS(status)); + } + } else if (WIFSIGNALED(status)) { + printf("run_program: child terminated by signal %d\n", + WTERMSIG(status)); + } + + int i; + for (i = 0; i < argc; ++i) { + free(args[i]); + } + free(args); + free(args2); + + char buffer[20]; + sprintf(buffer, "%d", status); + + return StringValue(strdup(buffer)); +} + +// sha1_check(data) +// to return the sha1 of the data (given in the format returned by +// read_file). +// +// sha1_check(data, sha1_hex, [sha1_hex, ...]) +// returns the sha1 of the file if it matches any of the hex +// strings passed, or "" if it does not equal any of them. +// +Value* Sha1CheckFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc < 1) { + return ErrorAbort(state, "%s() expects at least 1 arg", name); + } + + Value** args = ReadValueVarArgs(state, argc, argv); + if (args == NULL) { + return NULL; + } + + if (args[0]->size < 0) { + return StringValue(strdup("")); + } + uint8_t digest[SHA_DIGEST_SIZE]; + SHA_hash(args[0]->data, args[0]->size, digest); + FreeValue(args[0]); + + if (argc == 1) { + return StringValue(PrintSha1(digest)); + } + + int i; + uint8_t* arg_digest = reinterpret_cast(malloc(SHA_DIGEST_SIZE)); + for (i = 1; i < argc; ++i) { + if (args[i]->type != VAL_STRING) { + printf("%s(): arg %d is not a string; skipping", + name, i); + } else if (ParseSha1(args[i]->data, arg_digest) != 0) { + // Warn about bad args and skip them. + printf("%s(): error parsing \"%s\" as sha-1; skipping", + name, args[i]->data); + } else if (memcmp(digest, arg_digest, SHA_DIGEST_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 Value* returned +// is actually a FileContents*). +Value* ReadFileFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); + } + char* filename; + if (ReadArgs(state, argv, 1, &filename) < 0) return NULL; + + Value* v = reinterpret_cast(malloc(sizeof(Value))); + v->type = VAL_BLOB; + + FileContents fc; + if (LoadFileContents(filename, &fc) != 0) { + free(filename); + v->size = -1; + v->data = NULL; + free(fc.data); + return v; + } + + v->size = fc.size; + v->data = (char*)fc.data; + + free(filename); + return v; +} + +// Immediately reboot the device. Recovery is not finished normally, +// so if you reboot into recovery it will re-start applying the +// current package (because nothing has cleared the copy of the +// arguments stored in the BCB). +// +// The argument is the partition name passed to the android reboot +// property. It can be "recovery" to boot from the recovery +// partition, or "" (empty string) to boot from the regular boot +// partition. +Value* RebootNowFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + + char* filename; + char* property; + if (ReadArgs(state, argv, 2, &filename, &property) < 0) return NULL; + + char buffer[80]; + + // zero out the 'command' field of the bootloader message. + memset(buffer, 0, sizeof(((struct bootloader_message*)0)->command)); + FILE* f = fopen(filename, "r+b"); + fseek(f, offsetof(struct bootloader_message, command), SEEK_SET); + fwrite(buffer, sizeof(((struct bootloader_message*)0)->command), 1, f); + fclose(f); + free(filename); + + strcpy(buffer, "reboot,"); + if (property != NULL) { + strncat(buffer, property, sizeof(buffer)-10); + } + + property_set(ANDROID_RB_PROPERTY, buffer); + + sleep(5); + free(property); + ErrorAbort(state, "%s() failed to reboot", name); + return NULL; +} + +// Store a string value somewhere that future invocations of recovery +// can access it. This value is called the "stage" and can be used to +// drive packages that need to do reboots in the middle of +// installation and keep track of where they are in the multi-stage +// install. +// +// The first argument is the block device for the misc partition +// ("/misc" in the fstab), which is where this value is stored. The +// second argument is the string to store; it should not exceed 31 +// bytes. +Value* SetStageFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + + char* filename; + char* stagestr; + if (ReadArgs(state, argv, 2, &filename, &stagestr) < 0) return NULL; + + // Store this value in the misc partition, immediately after the + // bootloader message that the main recovery uses to save its + // arguments in case of the device restarting midway through + // package installation. + FILE* f = fopen(filename, "r+b"); + fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET); + int to_write = strlen(stagestr)+1; + int max_size = sizeof(((struct bootloader_message*)0)->stage); + if (to_write > max_size) { + to_write = max_size; + stagestr[max_size-1] = 0; + } + fwrite(stagestr, to_write, 1, f); + fclose(f); + + free(stagestr); + return StringValue(filename); +} + +// Return the value most recently saved with SetStageFn. The argument +// is the block device for the misc partition. +Value* GetStageFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 1) { + return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc); + } + + char* filename; + if (ReadArgs(state, argv, 1, &filename) < 0) return NULL; + + char buffer[sizeof(((struct bootloader_message*)0)->stage)]; + FILE* f = fopen(filename, "rb"); + fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET); + fread(buffer, sizeof(buffer), 1, f); + fclose(f); + buffer[sizeof(buffer)-1] = '\0'; + + return StringValue(strdup(buffer)); +} + +Value* WipeBlockDeviceFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); + } + + char* filename; + char* len_str; + if (ReadArgs(state, argv, 2, &filename, &len_str) < 0) return NULL; + + size_t len = strtoull(len_str, NULL, 0); + int fd = open(filename, O_WRONLY, 0644); + int success = wipe_block_device(fd, len); + + free(filename); + free(len_str); + + close(fd); + + return StringValue(strdup(success ? "t" : "")); +} + +Value* EnableRebootFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 0) { + return ErrorAbort(state, "%s() expects no args, got %d", name, argc); + } + UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); + fprintf(ui->cmd_pipe, "enable_reboot\n"); + return StringValue(strdup("t")); +} + +Value* Tune2FsFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc == 0) { + return ErrorAbort(state, "%s() expects args, got %d", name, argc); + } + + char** args = ReadVarArgs(state, argc, argv); + if (args == NULL) { + return ErrorAbort(state, "%s() could not read args", name); + } + + char** args2 = reinterpret_cast(malloc(sizeof(char*) * (argc+1))); + // Tune2fs expects the program name as its args[0] + args2[0] = strdup(name); + for (int i = 0; i < argc; ++i) { + args2[i + 1] = args[i]; + } + int result = tune2fs_main(argc + 1, args2); + for (int i = 0; i < argc; ++i) { + free(args[i]); + } + free(args); + + free(args2[0]); + free(args2); + if (result != 0) { + return ErrorAbort(state, "%s() returned error code %d", name, result); + } + return StringValue(strdup("t")); +} + +void RegisterInstallFunctions() { + RegisterFunction("mount", MountFn); + RegisterFunction("is_mounted", IsMountedFn); + RegisterFunction("unmount", UnmountFn); + RegisterFunction("format", FormatFn); + RegisterFunction("show_progress", ShowProgressFn); + RegisterFunction("set_progress", SetProgressFn); + RegisterFunction("delete", DeleteFn); + RegisterFunction("delete_recursive", DeleteFn); + RegisterFunction("package_extract_dir", PackageExtractDirFn); + RegisterFunction("package_extract_file", PackageExtractFileFn); + RegisterFunction("symlink", SymlinkFn); + + // Usage: + // set_metadata("filename", "key1", "value1", "key2", "value2", ...) + // Example: + // set_metadata("/system/bin/netcfg", "uid", 0, "gid", 3003, "mode", 02750, "selabel", "u:object_r:system_file:s0", "capabilities", 0x0); + RegisterFunction("set_metadata", SetMetadataFn); + + // Usage: + // set_metadata_recursive("dirname", "key1", "value1", "key2", "value2", ...) + // Example: + // set_metadata_recursive("/system", "uid", 0, "gid", 0, "fmode", 0644, "dmode", 0755, "selabel", "u:object_r:system_file:s0", "capabilities", 0x0); + RegisterFunction("set_metadata_recursive", SetMetadataFn); + + RegisterFunction("getprop", GetPropFn); + RegisterFunction("file_getprop", FileGetPropFn); + RegisterFunction("write_raw_image", WriteRawImageFn); + + RegisterFunction("apply_patch", ApplyPatchFn); + RegisterFunction("apply_patch_check", ApplyPatchCheckFn); + RegisterFunction("apply_patch_space", ApplyPatchSpaceFn); + + RegisterFunction("wipe_block_device", WipeBlockDeviceFn); + + RegisterFunction("read_file", ReadFileFn); + RegisterFunction("sha1_check", Sha1CheckFn); + RegisterFunction("rename", RenameFn); + + RegisterFunction("wipe_cache", WipeCacheFn); + + RegisterFunction("ui_print", UIPrintFn); + + RegisterFunction("run_program", RunProgramFn); + + RegisterFunction("reboot_now", RebootNowFn); + RegisterFunction("get_stage", GetStageFn); + RegisterFunction("set_stage", SetStageFn); + + RegisterFunction("enable_reboot", EnableRebootFn); + RegisterFunction("tune2fs", Tune2FsFn); +} diff --git a/updater/updater.c b/updater/updater.c deleted file mode 100644 index 661f69587..000000000 --- a/updater/updater.c +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include - -#include "edify/expr.h" -#include "updater.h" -#include "install.h" -#include "blockimg.h" -#include "minzip/Zip.h" -#include "minzip/SysUtil.h" - -// Generated by the makefile, this function defines the -// RegisterDeviceExtensions() function, which calls all the -// registration functions for device-specific extensions. -#include "register.inc" - -// Where in the package we expect to find the edify script to execute. -// (Note it's "updateR-script", not the older "update-script".) -#define SCRIPT_NAME "META-INF/com/google/android/updater-script" - -struct selabel_handle *sehandle; - -int main(int argc, char** argv) { - // Various things log information to stdout or stderr more or less - // at random (though we've tried to standardize on stdout). The - // log file makes more sense if buffering is turned off so things - // appear in the right order. - setbuf(stdout, NULL); - setbuf(stderr, NULL); - - if (argc != 4) { - printf("unexpected number of arguments (%d)\n", argc); - return 1; - } - - char* version = argv[1]; - if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || - version[1] != '\0') { - // We support version 1, 2, or 3. - printf("wrong updater binary API; expected 1, 2, or 3; " - "got %s\n", - argv[1]); - return 2; - } - - // Set up the pipe for sending commands back to the parent process. - - int fd = atoi(argv[2]); - FILE* cmd_pipe = fdopen(fd, "wb"); - setlinebuf(cmd_pipe); - - // Extract the script from the package. - - const char* package_filename = argv[3]; - MemMapping map; - if (sysMapFile(package_filename, &map) != 0) { - printf("failed to map package %s\n", argv[3]); - return 3; - } - ZipArchive za; - int err; - err = mzOpenZipArchive(map.addr, map.length, &za); - if (err != 0) { - printf("failed to open package %s: %s\n", - argv[3], strerror(err)); - return 3; - } - - const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME); - if (script_entry == NULL) { - printf("failed to find %s in %s\n", SCRIPT_NAME, package_filename); - return 4; - } - - char* script = malloc(script_entry->uncompLen+1); - if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) { - printf("failed to read script from package\n"); - return 5; - } - script[script_entry->uncompLen] = '\0'; - - // Configure edify's functions. - - RegisterBuiltins(); - RegisterInstallFunctions(); - RegisterBlockImageFunctions(); - RegisterDeviceExtensions(); - FinishRegistration(); - - // Parse the script. - - Expr* root; - int error_count = 0; - int error = parse_string(script, &root, &error_count); - if (error != 0 || error_count > 0) { - printf("%d parse errors\n", error_count); - return 6; - } - - struct selinux_opt seopts[] = { - { SELABEL_OPT_PATH, "/file_contexts" } - }; - - sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1); - - if (!sehandle) { - fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n"); - } - - // Evaluate the parsed script. - - UpdaterInfo updater_info; - updater_info.cmd_pipe = cmd_pipe; - updater_info.package_zip = &za; - updater_info.version = atoi(version); - updater_info.package_zip_addr = map.addr; - updater_info.package_zip_len = map.length; - - State state; - state.cookie = &updater_info; - state.script = script; - state.errmsg = NULL; - - char* result = Evaluate(&state, root); - if (result == NULL) { - if (state.errmsg == NULL) { - printf("script aborted (no error message)\n"); - fprintf(cmd_pipe, "ui_print script aborted (no error message)\n"); - } else { - printf("script aborted: %s\n", state.errmsg); - char* line = strtok(state.errmsg, "\n"); - while (line) { - fprintf(cmd_pipe, "ui_print %s\n", line); - line = strtok(NULL, "\n"); - } - fprintf(cmd_pipe, "ui_print\n"); - } - free(state.errmsg); - return 7; - } else { - fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result); - free(result); - } - - if (updater_info.package_zip) { - mzCloseZipArchive(updater_info.package_zip); - } - sysReleaseMap(&map); - free(script); - - return 0; -} diff --git a/updater/updater.cpp b/updater/updater.cpp new file mode 100644 index 000000000..0f22e6d04 --- /dev/null +++ b/updater/updater.cpp @@ -0,0 +1,169 @@ +/* + * 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 "edify/expr.h" +#include "updater.h" +#include "install.h" +#include "blockimg.h" +#include "minzip/Zip.h" +#include "minzip/SysUtil.h" + +// Generated by the makefile, this function defines the +// RegisterDeviceExtensions() function, which calls all the +// registration functions for device-specific extensions. +#include "register.inc" + +// Where in the package we expect to find the edify script to execute. +// (Note it's "updateR-script", not the older "update-script".) +#define SCRIPT_NAME "META-INF/com/google/android/updater-script" + +struct selabel_handle *sehandle; + +int main(int argc, char** argv) { + // Various things log information to stdout or stderr more or less + // at random (though we've tried to standardize on stdout). The + // log file makes more sense if buffering is turned off so things + // appear in the right order. + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + if (argc != 4) { + printf("unexpected number of arguments (%d)\n", argc); + return 1; + } + + char* version = argv[1]; + if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || + version[1] != '\0') { + // We support version 1, 2, or 3. + printf("wrong updater binary API; expected 1, 2, or 3; " + "got %s\n", + argv[1]); + return 2; + } + + // Set up the pipe for sending commands back to the parent process. + + int fd = atoi(argv[2]); + FILE* cmd_pipe = fdopen(fd, "wb"); + setlinebuf(cmd_pipe); + + // Extract the script from the package. + + const char* package_filename = argv[3]; + MemMapping map; + if (sysMapFile(package_filename, &map) != 0) { + printf("failed to map package %s\n", argv[3]); + return 3; + } + ZipArchive za; + int err; + err = mzOpenZipArchive(map.addr, map.length, &za); + if (err != 0) { + printf("failed to open package %s: %s\n", + argv[3], strerror(err)); + return 3; + } + + const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME); + if (script_entry == NULL) { + printf("failed to find %s in %s\n", SCRIPT_NAME, package_filename); + return 4; + } + + char* script = reinterpret_cast(malloc(script_entry->uncompLen+1)); + if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) { + printf("failed to read script from package\n"); + return 5; + } + script[script_entry->uncompLen] = '\0'; + + // Configure edify's functions. + + RegisterBuiltins(); + RegisterInstallFunctions(); + RegisterBlockImageFunctions(); + RegisterDeviceExtensions(); + FinishRegistration(); + + // Parse the script. + + Expr* root; + int error_count = 0; + int error = parse_string(script, &root, &error_count); + if (error != 0 || error_count > 0) { + printf("%d parse errors\n", error_count); + return 6; + } + + struct selinux_opt seopts[] = { + { SELABEL_OPT_PATH, "/file_contexts" } + }; + + sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1); + + if (!sehandle) { + fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n"); + } + + // Evaluate the parsed script. + + UpdaterInfo updater_info; + updater_info.cmd_pipe = cmd_pipe; + updater_info.package_zip = &za; + updater_info.version = atoi(version); + updater_info.package_zip_addr = map.addr; + updater_info.package_zip_len = map.length; + + State state; + state.cookie = &updater_info; + state.script = script; + state.errmsg = NULL; + + char* result = Evaluate(&state, root); + if (result == NULL) { + if (state.errmsg == NULL) { + printf("script aborted (no error message)\n"); + fprintf(cmd_pipe, "ui_print script aborted (no error message)\n"); + } else { + printf("script aborted: %s\n", state.errmsg); + char* line = strtok(state.errmsg, "\n"); + while (line) { + fprintf(cmd_pipe, "ui_print %s\n", line); + line = strtok(NULL, "\n"); + } + fprintf(cmd_pipe, "ui_print\n"); + } + free(state.errmsg); + return 7; + } else { + fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result); + free(result); + } + + if (updater_info.package_zip) { + mzCloseZipArchive(updater_info.package_zip); + } + sysReleaseMap(&map); + free(script); + + return 0; +} -- cgit v1.2.3 From 27604fcbee0010b800bfc16b5bf7a48c365c2cf3 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Fri, 17 Jul 2015 11:47:44 -0700 Subject: applypatch: Refactor strtok(). We have android::base::Split() for the work. Change-Id: Ic529db42090f700e6455d465c8b84b7f52d34d63 (cherry picked from commit 0a47ce27de454e272a883a0c452fad627fd7f419) --- applypatch/Android.mk | 6 +- applypatch/applypatch.cpp | 136 ++++++++++++++++++---------------------------- 2 files changed, 56 insertions(+), 86 deletions(-) diff --git a/applypatch/Android.mk b/applypatch/Android.mk index 1f73fd897..cc17a13ae 100644 --- a/applypatch/Android.mk +++ b/applypatch/Android.mk @@ -21,7 +21,7 @@ LOCAL_SRC_FILES := applypatch.cpp bspatch.cpp freecache.cpp imgpatch.cpp 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 +LOCAL_STATIC_LIBRARIES += libbase libmtdutils libmincrypt libbz libz include $(BUILD_STATIC_LIBRARY) @@ -31,7 +31,7 @@ LOCAL_CLANG := true LOCAL_SRC_FILES := main.cpp LOCAL_MODULE := applypatch LOCAL_C_INCLUDES += bootable/recovery -LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypt libbz +LOCAL_STATIC_LIBRARIES += libapplypatch libbase libmtdutils libmincrypt libbz LOCAL_SHARED_LIBRARIES += libz libcutils libc include $(BUILD_EXECUTABLE) @@ -44,7 +44,7 @@ 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 += libapplypatch libbase libmtdutils libmincrypt libbz LOCAL_STATIC_LIBRARIES += libz libcutils libc include $(BUILD_EXECUTABLE) diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp index 96bd88e88..026863330 100644 --- a/applypatch/applypatch.cpp +++ b/applypatch/applypatch.cpp @@ -25,6 +25,8 @@ #include #include +#include + #include "mincrypt/sha.h" #include "applypatch.h" #include "mtdutils/mtdutils.h" @@ -42,7 +44,7 @@ static int GenerateTarget(FileContents* source_file, size_t target_size, const Value* bonus_data); -static int mtd_partitions_scanned = 0; +static bool mtd_partitions_scanned = false; // Read a file into memory; store the file contents and associated // metadata in *file. @@ -87,21 +89,6 @@ int LoadFileContents(const char* filename, FileContents* file) { 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) { - const int aa = *reinterpret_cast(a); - const int bb = *reinterpret_cast(b); - if (size_array[aa] < size_array[bb]) { - return -1; - } else if (size_array[aa] > size_array[bb]) { - return 1; - } else { - return 0; - } -} - // Load the contents of an MTD or EMMC partition into the provided // FileContents. filename should be a string of the form // "MTD::::::..." (or @@ -120,53 +107,45 @@ static int compare_size_indices(const void* a, const void* b) { enum PartitionType { MTD, EMMC }; static int LoadPartitionContents(const char* filename, FileContents* file) { - char* copy = strdup(filename); - const char* magic = strtok(copy, ":"); + std::string copy(filename); + std::vector pieces = android::base::Split(copy, ":"); + if (pieces.size() < 4 || pieces.size() % 2 != 0) { + printf("LoadPartitionContents called with bad filename (%s)\n", filename); + return -1; + } enum PartitionType type; - - if (strcmp(magic, "MTD") == 0) { + if (pieces[0] == "MTD") { type = MTD; - } else if (strcmp(magic, "EMMC") == 0) { + } else if (pieces[0] == "EMMC") { type = EMMC; } else { printf("LoadPartitionContents called with bad filename (%s)\n", filename); return -1; } - const char* partition = strtok(NULL, ":"); + const char* partition = pieces[1].c_str(); - int i; - int colons = 0; - for (i = 0; filename[i] != '\0'; ++i) { - if (filename[i] == ':') { - ++colons; - } - } - if (colons < 3 || colons%2 == 0) { - printf("LoadPartitionContents called with bad filename (%s)\n", - filename); - } - - int pairs = (colons-1)/2; // # of (size,sha1) pairs in filename - int* index = reinterpret_cast(malloc(pairs * sizeof(int))); - size_t* size = reinterpret_cast(malloc(pairs * sizeof(size_t))); - char** sha1sum = reinterpret_cast(malloc(pairs * sizeof(char*))); + size_t pairs = (pieces.size() - 2) / 2; // # of (size, sha1) pairs in filename + std::vector index(pairs); + std::vector size(pairs); + std::vector sha1sum(pairs); - for (i = 0; i < pairs; ++i) { - const char* size_str = strtok(NULL, ":"); - size[i] = strtol(size_str, NULL, 10); + for (size_t i = 0; i < pairs; ++i) { + size[i] = strtol(pieces[i*2+2].c_str(), NULL, 10); if (size[i] == 0) { printf("LoadPartitionContents called with bad size (%s)\n", filename); return -1; } - sha1sum[i] = strtok(NULL, ":"); + sha1sum[i] = pieces[i*2+3].c_str(); index[i] = i; } - // sort the index[] array so it indexes the pairs in order of - // increasing size. - size_array = size; - qsort(index, pairs, sizeof(int), compare_size_indices); + // Sort the index[] array so it indexes the pairs in order of increasing size. + sort(index.begin(), index.end(), + [&](const size_t& i, const size_t& j) { + return (size[i] < size[j]); + } + ); MtdReadContext* ctx = NULL; FILE* dev = NULL; @@ -175,20 +154,18 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { case MTD: { if (!mtd_partitions_scanned) { mtd_scan_partitions(); - mtd_partitions_scanned = 1; + mtd_partitions_scanned = true; } const MtdPartition* mtd = mtd_find_partition_by_name(partition); if (mtd == NULL) { - printf("mtd partition \"%s\" not found (loading %s)\n", - partition, filename); + printf("mtd partition \"%s\" not found (loading %s)\n", partition, filename); return -1; } ctx = mtd_read_partition(mtd); if (ctx == NULL) { - printf("failed to initialize read of mtd partition \"%s\"\n", - partition); + printf("failed to initialize read of mtd partition \"%s\"\n", partition); return -1; } break; @@ -197,8 +174,7 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { case EMMC: dev = fopen(partition, "rb"); if (dev == NULL) { - printf("failed to open emmc partition \"%s\": %s\n", - partition, strerror(errno)); + printf("failed to open emmc partition \"%s\": %s\n", partition, strerror(errno)); return -1; } } @@ -207,15 +183,15 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { SHA_init(&sha_ctx); uint8_t parsed_sha[SHA_DIGEST_SIZE]; - // allocate enough memory to hold the largest size. + // Allocate enough memory to hold the largest size. file->data = reinterpret_cast(malloc(size[index[pairs-1]])); char* p = (char*)file->data; file->size = 0; // # bytes read so far + bool found = false; - 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). + for (size_t i = 0; i < pairs; ++i) { + // Read enough additional bytes to get us up to the next size. (Again, + // we're trying the possibilities in order of increasing size). size_t next = size[index[i]] - file->size; size_t read = 0; if (next > 0) { @@ -245,8 +221,8 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { 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); + if (ParseSha1(sha1sum[index[i]].c_str(), parsed_sha) != 0) { + printf("failed to parse sha1 %s in %s\n", sha1sum[index[i]].c_str(), filename); free(file->data); file->data = NULL; return -1; @@ -256,7 +232,8 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { // we have a match. stop reading the partition; we'll return // the data we've read so far. printf("partition read matched size %zu sha %s\n", - size[index[i]], sha1sum[index[i]]); + size[index[i]], sha1sum[index[i]].c_str()); + found = true; break; } @@ -274,9 +251,8 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { } - if (i == pairs) { - // Ran off the end of the list of (size,sha1) pairs without - // finding a match. + if (!found) { + // Ran off the end of the list of (size,sha1) pairs without finding a match. printf("contents of partition \"%s\" didn't match %s\n", partition, filename); free(file->data); file->data = NULL; @@ -293,11 +269,6 @@ static int LoadPartitionContents(const char* filename, FileContents* file) { file->st.st_uid = 0; file->st.st_gid = 0; - free(copy); - free(index); - free(size); - free(sha1sum); - return 0; } @@ -340,33 +311,33 @@ int SaveFileContents(const char* filename, const FileContents* file) { } // Write a memory buffer to 'target' partition, a string of the form -// "MTD:[:...]" or "EMMC::". Return 0 on +// "MTD:[:...]" or "EMMC:". Return 0 on // success. int WriteToPartition(unsigned char* data, size_t len, const char* target) { - char* copy = strdup(target); - const char* magic = strtok(copy, ":"); + std::string copy(target); + std::vector pieces = android::base::Split(copy, ":"); + + if (pieces.size() != 2) { + printf("WriteToPartition called with bad target (%s)\n", target); + return -1; + } enum PartitionType type; - if (strcmp(magic, "MTD") == 0) { + if (pieces[0] == "MTD") { type = MTD; - } else if (strcmp(magic, "EMMC") == 0) { + } else if (pieces[0] == "EMMC") { type = EMMC; } else { printf("WriteToPartition called with bad target (%s)\n", target); return -1; } - const char* partition = strtok(NULL, ":"); - - if (partition == NULL) { - printf("bad partition target name \"%s\"\n", target); - return -1; - } + const char* partition = pieces[1].c_str(); switch (type) { case MTD: { if (!mtd_partitions_scanned) { mtd_scan_partitions(); - mtd_partitions_scanned = 1; + mtd_partitions_scanned = true; } const MtdPartition* mtd = mtd_find_partition_by_name(partition); @@ -410,7 +381,7 @@ int WriteToPartition(unsigned char* data, size_t len, const char* target) { return -1; } - for (int attempt = 0; attempt < 2; ++attempt) { + for (size_t attempt = 0; attempt < 2; ++attempt) { if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) { printf("failed seek on %s: %s\n", partition, strerror(errno)); return -1; @@ -510,7 +481,6 @@ int WriteToPartition(unsigned char* data, size_t len, const char* target) { } } - free(copy); return 0; } -- cgit v1.2.3 From 3b199267d6d4c279cbef3286a74e16bf92dd8d8a Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Fri, 17 Jul 2015 13:39:52 -0700 Subject: updater: libapplypatch needs libbase now. Change-Id: Ibe3173edd6274b61bd9ca5ec394d7f6b4a403639 (cherry picked from commit 1b1ea17d554d127a970afe1d6004dd4627cd596e) --- updater/Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/updater/Android.mk b/updater/Android.mk index 0d4179b23..82fa7e265 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -44,7 +44,7 @@ LOCAL_STATIC_LIBRARIES += \ endif LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UPDATER_LIBS) $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) -LOCAL_STATIC_LIBRARIES += libapplypatch libedify libmtdutils libminzip libz +LOCAL_STATIC_LIBRARIES += libapplypatch libbase libedify libmtdutils libminzip libz LOCAL_STATIC_LIBRARIES += libmincrypt libbz LOCAL_STATIC_LIBRARIES += libcutils liblog libc LOCAL_STATIC_LIBRARIES += libselinux -- cgit v1.2.3 From f47259b18492c921445d9e437a9f9ed11c52fac6 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Fri, 17 Jul 2015 18:11:12 -0700 Subject: applypatch: Support flash mode. We may carry a full copy of recovery image in the /system, and use /system/bin/install-recovery.sh to install the recovery. This CL adds support to flash the recovery partition with the given image. Bug: 22641135 Change-Id: I345eaaee269f6443527f45a9be7e4ee47f6b2b39 (cherry picked from commit 68c5a6796737bb583a8bdfa4c9cd9c7f12ef4276) --- applypatch/applypatch.cpp | 84 ++++++++++++++++++++++++++++++++++++++++------- applypatch/applypatch.h | 2 ++ applypatch/main.cpp | 27 ++++++++++++--- 3 files changed, 97 insertions(+), 16 deletions(-) diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp index 026863330..2446b2a68 100644 --- a/applypatch/applypatch.cpp +++ b/applypatch/applypatch.cpp @@ -43,6 +43,7 @@ static int GenerateTarget(FileContents* source_file, const uint8_t target_sha1[SHA_DIGEST_SIZE], size_t target_size, const Value* bonus_data); +static std::string short_sha1(const uint8_t sha1[SHA_DIGEST_SIZE]); static bool mtd_partitions_scanned = false; @@ -461,7 +462,7 @@ int WriteToPartition(unsigned char* data, size_t len, const char* target) { } if (start == len) { - printf("verification read succeeded (attempt %d)\n", attempt+1); + printf("verification read succeeded (attempt %zu)\n", attempt+1); success = true; break; } @@ -628,12 +629,14 @@ int CacheSizeCheck(size_t bytes) { } } -static void print_short_sha1(const uint8_t sha1[SHA_DIGEST_SIZE]) { +static std::string short_sha1(const uint8_t sha1[SHA_DIGEST_SIZE]) { const char* hex = "0123456789abcdef"; + std::string result = ""; for (size_t i = 0; i < 4; ++i) { - putchar(hex[(sha1[i]>>4) & 0xf]); - putchar(hex[sha1[i] & 0xf]); + result.push_back(hex[(sha1[i]>>4) & 0xf]); + result.push_back(hex[sha1[i] & 0xf]); } + return result; } // This function applies binary patches to files in a way that is safe @@ -648,7 +651,7 @@ static void print_short_sha1(const uint8_t sha1[SHA_DIGEST_SIZE]) { // 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 , +// blob data). 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. @@ -659,7 +662,7 @@ static void print_short_sha1(const uint8_t sha1[SHA_DIGEST_SIZE]) { // status. // // may refer to a partition to read the source data. -// See the comments for the LoadPartition Contents() function above +// See the comments for the LoadPartitionContents() function above // for the format of such a filename. int applypatch(const char* source_filename, @@ -694,9 +697,7 @@ int applypatch(const char* source_filename, 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("already "); - print_short_sha1(target_sha1); - putchar('\n'); + printf("already %s\n", short_sha1(target_sha1).c_str()); free(source_file.data); return 0; } @@ -753,6 +754,67 @@ int applypatch(const char* source_filename, return result; } +/* + * This function flashes a given image to the target partition. It verifies + * the target cheksum first, and will return if target has the desired hash. + * It checks the checksum of the given source image before flashing, and + * verifies the target partition afterwards. The function is idempotent. + * Returns zero on success. + */ +int applypatch_flash(const char* source_filename, const char* target_filename, + const char* target_sha1_str, size_t target_size) { + printf("flash %s: ", target_filename); + + uint8_t target_sha1[SHA_DIGEST_SIZE]; + if (ParseSha1(target_sha1_str, target_sha1) != 0) { + printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str); + return 1; + } + + FileContents source_file; + source_file.data = NULL; + std::string target_str(target_filename); + + std::vector pieces = android::base::Split(target_str, ":"); + if (pieces.size() != 2 || (pieces[0] != "MTD" && pieces[0] != "EMMC")) { + printf("invalid target name \"%s\"", target_filename); + return 1; + } + + // Load the target into the source_file object to see if already applied. + pieces.push_back(std::to_string(target_size)); + pieces.push_back(target_sha1_str); + std::string fullname = android::base::Join(pieces, ':'); + if (LoadPartitionContents(fullname.c_str(), &source_file) == 0 && + memcmp(source_file.sha1, target_sha1, SHA_DIGEST_SIZE) == 0) { + // The early-exit case: the image was already applied, this partition + // has the desired hash, nothing for us to do. + printf("already %s\n", short_sha1(target_sha1).c_str()); + free(source_file.data); + return 0; + } + + if (LoadFileContents(source_filename, &source_file) == 0) { + if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_SIZE) != 0) { + // The source doesn't have desired checksum. + printf("source \"%s\" doesn't have expected sha1 sum\n", source_filename); + printf("expected: %s, found: %s\n", short_sha1(target_sha1).c_str(), + short_sha1(source_file.sha1).c_str()); + free(source_file.data); + return 1; + } + } + + if (WriteToPartition(source_file.data, target_size, target_filename) != 0) { + printf("write of copied data to %s failed\n", target_filename); + free(source_file.data); + return 1; + } + + free(source_file.data); + return 0; +} + static int GenerateTarget(FileContents* source_file, const Value* source_patch_value, FileContents* copy_file, @@ -953,9 +1015,7 @@ static int GenerateTarget(FileContents* source_file, printf("patch did not produce expected sha1\n"); return 1; } else { - printf("now "); - print_short_sha1(target_sha1); - putchar('\n'); + printf("now %s\n", short_sha1(target_sha1).c_str()); } if (output < 0) { diff --git a/applypatch/applypatch.h b/applypatch/applypatch.h index edec84812..415bc1b3c 100644 --- a/applypatch/applypatch.h +++ b/applypatch/applypatch.h @@ -48,6 +48,8 @@ size_t FreeSpaceForFile(const char* filename); int CacheSizeCheck(size_t bytes); int ParseSha1(const char* str, uint8_t* digest); +int applypatch_flash(const char* source_filename, const char* target_filename, + const char* target_sha1_str, size_t target_size); int applypatch(const char* source_filename, const char* target_filename, const char* target_sha1_str, diff --git a/applypatch/main.cpp b/applypatch/main.cpp index 63ff5c2c0..966d8b91f 100644 --- a/applypatch/main.cpp +++ b/applypatch/main.cpp @@ -47,8 +47,8 @@ static int SpaceMode(int argc, char** argv) { // ":" into the new parallel arrays *sha1s and // *patches (loading file contents into the patches). Returns true on // success. -static bool ParsePatchArgs(int argc, char** argv, - char*** sha1s, Value*** patches, int* num_patches) { +static bool ParsePatchArgs(int argc, char** argv, char*** sha1s, + Value*** patches, int* num_patches) { *num_patches = argc; *sha1s = reinterpret_cast(malloc(*num_patches * sizeof(char*))); *patches = reinterpret_cast(malloc(*num_patches * sizeof(Value*))); @@ -98,7 +98,12 @@ static bool ParsePatchArgs(int argc, char** argv, return false; } -int PatchMode(int argc, char** argv) { +static int FlashMode(const char* src_filename, const char* tgt_filename, + const char* tgt_sha1, size_t tgt_size) { + return applypatch_flash(src_filename, tgt_filename, tgt_sha1, tgt_size); +} + +static int PatchMode(int argc, char** argv) { Value* bonus = NULL; if (argc >= 3 && strcmp(argv[1], "-b") == 0) { FileContents fc; @@ -114,7 +119,7 @@ int PatchMode(int argc, char** argv) { argv += 2; } - if (argc < 6) { + if (argc < 4) { return 2; } @@ -125,6 +130,16 @@ int PatchMode(int argc, char** argv) { return 1; } + // If no : is provided, it is in flash mode. + if (argc == 5) { + if (bonus != NULL) { + printf("bonus file not supported in flash mode\n"); + return 1; + } + return FlashMode(argv[1], argv[2], argv[3], target_size); + } + + char** sha1s; Value** patches; int num_patches; @@ -162,6 +177,10 @@ int PatchMode(int argc, char** argv) { // - if the sha1 hash of is , does nothing and exits // successfully. // +// - otherwise, if no : is provided, flashes with +// . must be a partition name, while must +// be a regular image file. will not be deleted on success. +// // - 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 -- cgit v1.2.3 From 32ac97675bade3681203c46d001f76b11a359fd5 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Fri, 24 Jul 2015 15:29:12 -0700 Subject: applypatch: Fix the checking in WriteToPartition(). WriteToPartition() should consider a target name as valid if it contains multiple colons. But only the first two fields will be used. Bug: 22725128 Change-Id: I9d0236eaf97df9db9704acf53690d0ef85188e45 (cherry picked from commit 1ce7a2a63db84527e6195a6b123b1617f87c0f38) --- applypatch/applypatch.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp index 2446b2a68..751d3e392 100644 --- a/applypatch/applypatch.cpp +++ b/applypatch/applypatch.cpp @@ -312,13 +312,14 @@ int SaveFileContents(const char* filename, const FileContents* file) { } // Write a memory buffer to 'target' partition, a string of the form -// "MTD:[:...]" or "EMMC:". Return 0 on -// success. +// "MTD:[:...]" or "EMMC:[:...]". The target name +// might contain multiple colons, but WriteToPartition() only uses the first +// two and ignores the rest. Return 0 on success. int WriteToPartition(unsigned char* data, size_t len, const char* target) { std::string copy(target); std::vector pieces = android::base::Split(copy, ":"); - if (pieces.size() != 2) { + if (pieces.size() < 2) { printf("WriteToPartition called with bad target (%s)\n", target); return -1; } -- cgit v1.2.3 From 8f90389966fabf532b24741d49245215279533e1 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Thu, 30 Jul 2015 14:43:27 -0700 Subject: recovery: Allow "Mount /system" for system_root_image. When system images contain the root directory, there is no entry of "/system" in the fstab. Change it to look for "/" instead if ro.build.system_root_image is true. We actually mount the partition to /system_root instead, and create a symlink to /system_root/system for /system. This allows "adb shell" to work properly. Bug: 22855115 Change-Id: I91864444950dc3229fda3cc133ddbadeb8817fb8 (cherry picked from commit abb8f7785ee24eac42f6d28dbfef37872a06c7e9) --- recovery.cpp | 19 +++++++++++++++++-- roots.cpp | 26 ++++++++++++++++++-------- roots.h | 13 ++++--------- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/recovery.cpp b/recovery.cpp index a0c74524e..09b061d7d 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -862,9 +862,24 @@ prompt_and_wait(Device* device, int status) { break; case Device::MOUNT_SYSTEM: - if (ensure_path_mounted("/system") != -1) { - ui->Print("Mounted /system.\n"); + char system_root_image[PROPERTY_VALUE_MAX]; + property_get("ro.build.system_root_image", system_root_image, ""); + + // For a system image built with the root directory (i.e. + // system_root_image == "true"), we mount it to /system_root, and symlink /system + // to /system_root/system to make adb shell work (the symlink is created through + // the build system). + // Bug: 22855115 + if (strcmp(system_root_image, "true") == 0) { + if (ensure_path_mounted_at("/", "/system_root") != -1) { + ui->Print("Mounted /system.\n"); + } + } else { + if (ensure_path_mounted("/system") != -1) { + ui->Print("Mounted /system.\n"); + } } + break; } } diff --git a/roots.cpp b/roots.cpp index 9288177e7..12c6b5ee2 100644 --- a/roots.cpp +++ b/roots.cpp @@ -70,7 +70,8 @@ Volume* volume_for_path(const char* path) { return fs_mgr_get_entry_for_mount_point(fstab, path); } -int ensure_path_mounted(const char* path) { +// Mount the volume specified by path at the given mount_point. +int ensure_path_mounted_at(const char* path, const char* mount_point) { Volume* v = volume_for_path(path); if (v == NULL) { LOGE("unknown volume for path [%s]\n", path); @@ -88,14 +89,18 @@ int ensure_path_mounted(const char* path) { return -1; } + if (!mount_point) { + mount_point = v->mount_point; + } + const MountedVolume* mv = - find_mounted_volume_by_mount_point(v->mount_point); + find_mounted_volume_by_mount_point(mount_point); if (mv) { // volume is already mounted return 0; } - mkdir(v->mount_point, 0755); // in case it doesn't already exist + mkdir(mount_point, 0755); // in case it doesn't already exist if (strcmp(v->fs_type, "yaffs2") == 0) { // mount an MTD partition as a YAFFS2 filesystem. @@ -104,25 +109,30 @@ int ensure_path_mounted(const char* path) { partition = mtd_find_partition_by_name(v->blk_device); if (partition == NULL) { LOGE("failed to find \"%s\" partition to mount at \"%s\"\n", - v->blk_device, v->mount_point); + v->blk_device, mount_point); return -1; } - return mtd_mount_partition(partition, v->mount_point, v->fs_type, 0); + return mtd_mount_partition(partition, mount_point, v->fs_type, 0); } else if (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "squashfs") == 0 || strcmp(v->fs_type, "vfat") == 0) { - result = mount(v->blk_device, v->mount_point, v->fs_type, + result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options); if (result == 0) return 0; - LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno)); + LOGE("failed to mount %s (%s)\n", mount_point, strerror(errno)); return -1; } - LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, v->mount_point); + LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, mount_point); return -1; } +int ensure_path_mounted(const char* path) { + // Mount at the default mount point. + return ensure_path_mounted_at(path, nullptr); +} + int ensure_path_unmounted(const char* path) { Volume* v = volume_for_path(path); if (v == NULL) { diff --git a/roots.h b/roots.h index 230d9ded3..6e3b24355 100644 --- a/roots.h +++ b/roots.h @@ -19,10 +19,6 @@ #include "common.h" -#ifdef __cplusplus -extern "C" { -#endif - // Load and parse volume data from /etc/recovery.fstab. void load_volume_table(); @@ -33,7 +29,10 @@ Volume* volume_for_path(const char* path); // success (volume is mounted). int ensure_path_mounted(const char* path); -// Make sure that the volume 'path' is on is mounted. Returns 0 on +// Similar to ensure_path_mounted, but allows one to specify the mount_point. +int ensure_path_mounted_at(const char* path, const char* mount_point); + +// Make sure that the volume 'path' is on is unmounted. Returns 0 on // success (volume is unmounted); int ensure_path_unmounted(const char* path); @@ -46,8 +45,4 @@ int format_volume(const char* volume); // mounted (/tmp and /cache) are mounted. Returns 0 on success. int setup_install_mounts(); -#ifdef __cplusplus -} -#endif - #endif // RECOVERY_ROOTS_H_ -- cgit v1.2.3 From 846c094fee9e50ed2b2e63dee17f5bafb2b9d1ce Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Wed, 2 Sep 2015 15:49:58 -0400 Subject: Add slot_suffix field to struct bootloader_message. This is needed by fs_mgr for certain A/B implementations. Change-Id: I7bb404d61198eb7a962c2b693911f5156745daae --- bootloader.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bootloader.h b/bootloader.h index c2895dd91..fd003a076 100644 --- a/bootloader.h +++ b/bootloader.h @@ -43,6 +43,13 @@ extern "C" { * multiple times, so that the UI can reflect which invocation of the * package it is. If the value is of the format "#/#" (eg, "1/3"), * the UI will add a simple indicator of that status. + * + * The slot_suffix field is used for A/B implementations where the + * bootloader does not set the androidboot.ro.boot.slot_suffix kernel + * commandline parameter. This is used by fs_mgr to mount /system and + * other partitions with the slotselect flag set in fstab. A/B + * implementations are free to use all 32 bytes and may store private + * data past the first NUL-byte in this field. */ struct bootloader_message { char command[32]; @@ -55,7 +62,8 @@ struct bootloader_message { // stage string (for multistage packages) and possible future // expansion. char stage[32]; - char reserved[224]; + char slot_suffix[32]; + char reserved[192]; }; /* Read and write the bootloader command from the "misc" partition. -- cgit v1.2.3 From 1171d3a12b13ca3f1d4301985cf068076e55ae26 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Sat, 5 Dec 2015 21:21:27 -0800 Subject: Add update_verifier for A/B OTA update. update_verifier checks the integrity of the updated system and vendor partitions on the first boot post an A/B OTA update. It marks the current slot as having booted successfully if it passes the verification. This CL doesn't perform any actual verification work which will be addressed in follow-up CLs. Bug: 26039641 Change-Id: Ia5504ed25b799b48b5886c2fc68073a360127f42 --- Android.mk | 1 + update_verifier/Android.mk | 24 +++++++++++ update_verifier/update_verifier.cpp | 84 +++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 update_verifier/Android.mk create mode 100644 update_verifier/update_verifier.cpp diff --git a/Android.mk b/Android.mk index e43d55f66..c2896f3fc 100644 --- a/Android.mk +++ b/Android.mk @@ -134,4 +134,5 @@ include $(LOCAL_PATH)/minui/Android.mk \ $(LOCAL_PATH)/edify/Android.mk \ $(LOCAL_PATH)/uncrypt/Android.mk \ $(LOCAL_PATH)/updater/Android.mk \ + $(LOCAL_PATH)/update_verifier/Android.mk \ $(LOCAL_PATH)/applypatch/Android.mk diff --git a/update_verifier/Android.mk b/update_verifier/Android.mk new file mode 100644 index 000000000..0bb054777 --- /dev/null +++ b/update_verifier/Android.mk @@ -0,0 +1,24 @@ +# Copyright (C) 2015 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_CLANG := true +LOCAL_SRC_FILES := update_verifier.cpp +LOCAL_MODULE := update_verifier +LOCAL_SHARED_LIBRARIES := libcutils libhardware + +include $(BUILD_EXECUTABLE) diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp new file mode 100644 index 000000000..9ba792b86 --- /dev/null +++ b/update_verifier/update_verifier.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This program verifies the integrity of the partitions after an A/B OTA + * update. It gets invoked by init, and will only perform the verification if + * it's the first boot post an A/B OTA update. + * + * It relies on dm-verity to capture any corruption on the partitions being + * verified. dm-verity must be in enforcing mode, so that it will reboot the + * device on dm-verity failures. When that happens, the bootloader should + * mark the slot as unbootable and stops trying. We should never see a device + * started in dm-verity logging mode but with isSlotBootable equals to 0. + * + * The current slot will be marked as having booted successfully if the + * verifier reaches the end after the verification. + * + * TODO: The actual verification part will be added later after we have the + * A/B OTA package format in place. + */ + +#include +#include + +#include + +#define LOG_TAG "update_verifier" +#define INFO(x...) KLOG_INFO(LOG_TAG, x) +#define ERROR(x...) KLOG_ERROR(LOG_TAG, x) + +int main(int argc, char** argv) { + klog_init(); + klog_set_level(6); + for (int i = 1; i < argc; i++) { + INFO("Started with arg %d: %s\n", i, argv[i]); + } + + const hw_module_t* hw_module; + if (hw_get_module("bootctrl", &hw_module) != 0) { + ERROR("Error getting bootctrl module.\n"); + return -1; + } + + boot_control_module_t* module = reinterpret_cast( + const_cast(hw_module)); + module->init(module); + + unsigned current_slot = module->getCurrentSlot(module); + int bootable = module->isSlotBootable(module, current_slot); + INFO("Booting slot %u: isSlotBootable=%d\n", current_slot, bootable); + + if (bootable == 0) { + // The current slot has not booted successfully. + + // TODO: Add the actual verification after we have the A/B OTA package + // format in place. + + // TODO: Assert the dm-verity mode. Bootloader should never boot a newly + // flashed slot (isSlotBootable == 0) with dm-verity logging mode. + + int ret = module->markBootSuccessful(module); + if (ret != 0) { + ERROR("Error marking booted successfully: %s\n", strerror(-ret)); + return -1; + } + INFO("Marked slot %u as booted successfully.\n", current_slot); + } + + INFO("Leaving update_verifier.\n"); + return 0; +} -- cgit v1.2.3 From 45eac58ef188679f6df2d80efc0391c6d7904cd8 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Mon, 7 Dec 2015 17:04:58 -0800 Subject: update_verifier: Log to logd instead of kernel log. logd already gets started before we call update_verifier. Bug: 26039641 Change-Id: If00669a77bf9a6e5534e33f4e50b42eabba2667a --- update_verifier/Android.mk | 2 +- update_verifier/update_verifier.cpp | 18 +++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/update_verifier/Android.mk b/update_verifier/Android.mk index 0bb054777..7f28bcedc 100644 --- a/update_verifier/Android.mk +++ b/update_verifier/Android.mk @@ -19,6 +19,6 @@ include $(CLEAR_VARS) LOCAL_CLANG := true LOCAL_SRC_FILES := update_verifier.cpp LOCAL_MODULE := update_verifier -LOCAL_SHARED_LIBRARIES := libcutils libhardware +LOCAL_SHARED_LIBRARIES := libhardware liblog include $(BUILD_EXECUTABLE) diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp index 9ba792b86..5e8881571 100644 --- a/update_verifier/update_verifier.cpp +++ b/update_verifier/update_verifier.cpp @@ -32,25 +32,21 @@ * A/B OTA package format in place. */ -#include #include #include #define LOG_TAG "update_verifier" -#define INFO(x...) KLOG_INFO(LOG_TAG, x) -#define ERROR(x...) KLOG_ERROR(LOG_TAG, x) +#include int main(int argc, char** argv) { - klog_init(); - klog_set_level(6); for (int i = 1; i < argc; i++) { - INFO("Started with arg %d: %s\n", i, argv[i]); + SLOGI("Started with arg %d: %s\n", i, argv[i]); } const hw_module_t* hw_module; if (hw_get_module("bootctrl", &hw_module) != 0) { - ERROR("Error getting bootctrl module.\n"); + SLOGE("Error getting bootctrl module.\n"); return -1; } @@ -60,7 +56,7 @@ int main(int argc, char** argv) { unsigned current_slot = module->getCurrentSlot(module); int bootable = module->isSlotBootable(module, current_slot); - INFO("Booting slot %u: isSlotBootable=%d\n", current_slot, bootable); + SLOGI("Booting slot %u: isSlotBootable=%d\n", current_slot, bootable); if (bootable == 0) { // The current slot has not booted successfully. @@ -73,12 +69,12 @@ int main(int argc, char** argv) { int ret = module->markBootSuccessful(module); if (ret != 0) { - ERROR("Error marking booted successfully: %s\n", strerror(-ret)); + SLOGE("Error marking booted successfully: %s\n", strerror(-ret)); return -1; } - INFO("Marked slot %u as booted successfully.\n", current_slot); + SLOGI("Marked slot %u as booted successfully.\n", current_slot); } - INFO("Leaving update_verifier.\n"); + SLOGI("Leaving update_verifier.\n"); return 0; } -- cgit v1.2.3 From a5d5082222b7420801cdb77f09305dd4c3afb4db Mon Sep 17 00:00:00 2001 From: Andriy Naborskyy Date: Fri, 8 Jan 2016 10:11:41 -0800 Subject: Revert "Byte swap to support BGRA in recovery mode" This reverts commit e5879c3639789d61803605c12371a4f291e0b3cc. The swap in page flip code is not needed any more. New changes take care of ABGR and BGRA formats swapping bytes in png and drawing routines See commit fd778e3e406a7e83536ea66776996f032f24af64 Bug: 26243152 Change-Id: I313ee41bee2c143b4e5412515285a65ac394ec77 --- minui/graphics_fbdev.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/minui/graphics_fbdev.cpp b/minui/graphics_fbdev.cpp index 997e9cac2..0788f7552 100644 --- a/minui/graphics_fbdev.cpp +++ b/minui/graphics_fbdev.cpp @@ -176,18 +176,6 @@ static GRSurface* fbdev_init(minui_backend* backend) { static GRSurface* fbdev_flip(minui_backend* backend __unused) { if (double_buffered) { -#if defined(RECOVERY_BGRA) - // In case of BGRA, do some byte swapping - unsigned int idx; - unsigned char tmp; - unsigned char* ucfb_vaddr = (unsigned char*)gr_draw->data; - for (idx = 0 ; idx < (gr_draw->height * gr_draw->row_bytes); - idx += 4) { - tmp = ucfb_vaddr[idx]; - ucfb_vaddr[idx ] = ucfb_vaddr[idx + 2]; - ucfb_vaddr[idx + 2] = tmp; - } -#endif // Change gr_draw to point to the buffer currently displayed, // then flip the driver so we're displaying the other buffer // instead. -- cgit v1.2.3