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