first commit
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..1dc34a5
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,140 @@
+ifneq ($(TARGET_SIMULATOR),true)
+ifeq ($(TARGET_ARCH),arm)
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+commands_recovery_local_path := $(LOCAL_PATH)
+# LOCAL_CPP_EXTENSION := .c
+
+LOCAL_SRC_FILES := \
+    recovery.c \
+    bootloader.c \
+    install.c \
+    roots.c \
+    ui.c \
+    verifier.c \
+    encryptedfs_provisioning.c \
+    mounts.c \
+	extendedcommands.c \
+	nandroid.c \
+    reboot.c \
+    edifyscripting.c \
+    setprop.c
+
+LOCAL_MODULE := recovery
+
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+
+RECOVERY_VERSION := ClockworkMod Recovery v3.0.0.5
+LOCAL_CFLAGS += -DRECOVERY_VERSION="$(RECOVERY_VERSION)"
+RECOVERY_API_VERSION := 2
+LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
+
+BOARD_RECOVERY_DEFINES := BOARD_HAS_NO_SELECT_BUTTON BOARD_HAS_MTD_CACHE BOARD_HAS_SMALL_RECOVERY BOARD_LDPI_RECOVERY BOARD_RECOVERY_IGNORE_BOOTABLES
+
+$(foreach board_define,$(BOARD_RECOVERY_DEFINES), \
+  $(if $($(board_define)), \
+    $(eval LOCAL_CFLAGS += -D$(board_define)=\"$($(board_define))\") \
+  ) \
+  )
+
+# This binary is in the recovery ramdisk, which is otherwise a copy of root.
+# It gets copied there in config/Makefile.  LOCAL_MODULE_TAGS suppresses
+# a (redundant) copy of the binary in /system/bin for user builds.
+# TODO: Build the ramdisk image in a more principled way.
+
+LOCAL_MODULE_TAGS := eng
+
+LOCAL_STATIC_LIBRARIES :=
+ifeq ($(BOARD_CUSTOM_RECOVERY_KEYMAPPING),)
+  LOCAL_SRC_FILES += default_recovery_ui.c
+else
+  LOCAL_SRC_FILES += $(BOARD_CUSTOM_RECOVERY_KEYMAPPING)
+endif
+
+LOCAL_STATIC_LIBRARIES += libext4_utils libz
+LOCAL_STATIC_LIBRARIES += libminzip libunz libmincrypt
+
+LOCAL_STATIC_LIBRARIES += libedify libbusybox libclearsilverregex libmkyaffs2image libunyaffs liberase_image libdump_image libflash_image
+LOCAL_STATIC_LIBRARIES += libflashutils libmtdutils libmmcutils libbmlutils
+
+LOCAL_STATIC_LIBRARIES += libminui libpixelflinger_static libpng libcutils
+LOCAL_STATIC_LIBRARIES += libstdc++ libc
+
+LOCAL_C_INCLUDES += system/extras/ext4_utils
+
+include $(BUILD_EXECUTABLE)
+
+RECOVERY_LINKS := edify busybox flash_image dump_image mkyaffs2image unyaffs erase_image nandroid reboot volume
+
+# nc is provided by external/netcat
+RECOVERY_SYMLINKS := $(addprefix $(TARGET_RECOVERY_ROOT_OUT)/sbin/,$(RECOVERY_LINKS))
+$(RECOVERY_SYMLINKS): RECOVERY_BINARY := $(LOCAL_MODULE)
+$(RECOVERY_SYMLINKS): $(LOCAL_INSTALLED_MODULE)
+	@echo "Symlink: $@ -> $(RECOVERY_BINARY)"
+	@mkdir -p $(dir $@)
+	@rm -rf $@
+	$(hide) ln -sf $(RECOVERY_BINARY) $@
+
+ALL_DEFAULT_INSTALLED_MODULES += $(RECOVERY_SYMLINKS)
+
+# Now let's do recovery symlinks
+BUSYBOX_LINKS := $(shell cat external/busybox/busybox-minimal.links)
+RECOVERY_BUSYBOX_SYMLINKS := $(addprefix $(TARGET_RECOVERY_ROOT_OUT)/sbin/,$(filter-out $(exclude),$(notdir $(BUSYBOX_LINKS))))
+$(RECOVERY_BUSYBOX_SYMLINKS): BUSYBOX_BINARY := busybox
+$(RECOVERY_BUSYBOX_SYMLINKS): $(LOCAL_INSTALLED_MODULE)
+	@echo "Symlink: $@ -> $(BUSYBOX_BINARY)"
+	@mkdir -p $(dir $@)
+	@rm -rf $@
+	$(hide) ln -sf $(BUSYBOX_BINARY) $@
+
+ALL_DEFAULT_INSTALLED_MODULES += $(RECOVERY_BUSYBOX_SYMLINKS)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := nandroid-md5.sh
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
+LOCAL_SRC_FILES := nandroid-md5.sh
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := killrecovery.sh
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
+LOCAL_SRC_FILES := killrecovery.sh
+include $(BUILD_PREBUILT)
+
+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)/bmlutils/Android.mk
+include $(commands_recovery_local_path)/flashutils/Android.mk
+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)/mmcutils/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
+include $(commands_recovery_local_path)/utilities/Android.mk
+commands_recovery_local_path :=
+
+endif   # TARGET_ARCH == arm
+endif    # !TARGET_SIMULATOR
+
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644
index 0000000..b84e1b6
--- /dev/null
+++ b/CleanSpec.mk
@@ -0,0 +1,49 @@
+# Copyright (C) 2007 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list.  These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+#     $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+#     $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list.  E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/applypatch/Android.mk b/applypatch/Android.mk
new file mode 100644
index 0000000..e91e4bf
--- /dev/null
+++ b/applypatch/Android.mk
@@ -0,0 +1,63 @@
+# 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)
+
+ifeq ($(TARGET_ARCH),arm)
+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_ARCH == arm
+endif  # !TARGET_SIMULATOR
diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c
new file mode 100644
index 0000000..fd8153a
--- /dev/null
+++ b/applypatch/applypatch.c
@@ -0,0 +1,889 @@
+/*
+ * 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"
+
+static int SaveFileContents(const char* filename, FileContents file);
+static int LoadPartitionContents(const char* filename, FileContents* file);
+int ParseSha1(const char* str, uint8_t* digest);
+static 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:" or "EMMC:" means to
+    // load the contents of a partition.
+    if (strncmp(filename, "MTD:", 4) == 0 ||
+        strncmp(filename, "EMMC:", 5) == 0) {
+        return LoadPartitionContents(filename, file);
+    }
+
+    if (stat(filename, &file->st) != 0) {
+        printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
+        return -1;
+    }
+
+    file->size = file->st.st_size;
+    file->data = malloc(file->size);
+
+    FILE* f = fopen(filename, "rb");
+    if (f == NULL) {
+        printf("failed to open \"%s\": %s\n", filename, strerror(errno));
+        free(file->data);
+        file->data = NULL;
+        return -1;
+    }
+
+    ssize_t bytes_read = fread(file->data, 1, file->size, f);
+    if (bytes_read != file->size) {
+        printf("short read of \"%s\" (%ld bytes of %ld)\n",
+               filename, (long)bytes_read, (long)file->size);
+        free(file->data);
+        file->data = NULL;
+        return -1;
+    }
+    fclose(f);
+
+    SHA(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 or EMMC partition into the provided
+// FileContents.  filename should be a string of the form
+// "MTD:<partition_name>:<size_1>:<sha1_1>:<size_2>:<sha1_2>:..."  (or
+// "EMMC:<partition_device>:...").  The smallest size_n bytes for
+// which that prefix of the partition contents has the corresponding
+// sha1 hash will be loaded.  It is acceptable for a size value to be
+// repeated with different sha1s.  Will return 0 on success.
+//
+// This complexity is needed because if an OTA installation is
+// interrupted, the partition might contain either the source or the
+// target data, which might be of different lengths.  We need to know
+// the length in order to read from a partition (there is no
+// "end-of-file" marker), so the caller must specify the possible
+// lengths and the hash of the data, and we'll do the load expecting
+// to find one of those hashes.
+enum PartitionType { MTD, EMMC };
+
+static int LoadPartitionContents(const char* filename, FileContents* file) {
+    char* copy = strdup(filename);
+    const char* magic = strtok(copy, ":");
+
+    enum PartitionType type;
+
+    if (strcmp(magic, "MTD") == 0) {
+        type = MTD;
+    } else if (strcmp(magic, "EMMC") == 0) {
+        type = EMMC;
+    } else {
+        printf("LoadPartitionContents called with bad filename (%s)\n",
+               filename);
+        return -1;
+    }
+    const char* partition = strtok(NULL, ":");
+
+    int i;
+    int colons = 0;
+    for (i = 0; filename[i] != '\0'; ++i) {
+        if (filename[i] == ':') {
+            ++colons;
+        }
+    }
+    if (colons < 3 || colons%2 == 0) {
+        printf("LoadPartitionContents called with bad filename (%s)\n",
+               filename);
+    }
+
+    int pairs = (colons-1)/2;     // # of (size,sha1) pairs in filename
+    int* index = malloc(pairs * sizeof(int));
+    size_t* size = malloc(pairs * sizeof(size_t));
+    char** sha1sum = malloc(pairs * sizeof(char*));
+
+    for (i = 0; i < pairs; ++i) {
+        const char* size_str = strtok(NULL, ":");
+        size[i] = strtol(size_str, NULL, 10);
+        if (size[i] == 0) {
+            printf("LoadPartitionContents called with bad size (%s)\n", filename);
+            return -1;
+        }
+        sha1sum[i] = strtok(NULL, ":");
+        index[i] = i;
+    }
+
+    // sort the index[] array so it indexes the pairs in order of
+    // increasing size.
+    size_array = size;
+    qsort(index, pairs, sizeof(int), compare_size_indices);
+
+    MtdReadContext* ctx = NULL;
+    FILE* dev = NULL;
+
+    switch (type) {
+        case MTD:
+            if (!mtd_partitions_scanned) {
+                mtd_scan_partitions();
+                mtd_partitions_scanned = 1;
+            }
+
+            const MtdPartition* mtd = mtd_find_partition_by_name(partition);
+            if (mtd == NULL) {
+                printf("mtd partition \"%s\" not found (loading %s)\n",
+                       partition, filename);
+                return -1;
+            }
+
+            ctx = mtd_read_partition(mtd);
+            if (ctx == NULL) {
+                printf("failed to initialize read of mtd partition \"%s\"\n",
+                       partition);
+                return -1;
+            }
+            break;
+
+        case EMMC:
+            dev = fopen(partition, "rb");
+            if (dev == NULL) {
+                printf("failed to open emmc partition \"%s\": %s\n",
+                       partition, strerror(errno));
+                return -1;
+            }
+    }
+
+    SHA_CTX sha_ctx;
+    SHA_init(&sha_ctx);
+    uint8_t parsed_sha[SHA_DIGEST_SIZE];
+
+    // allocate enough memory to hold the largest size.
+    file->data = malloc(size[index[pairs-1]]);
+    char* p = (char*)file->data;
+    file->size = 0;                // # bytes read so far
+
+    for (i = 0; i < pairs; ++i) {
+        // Read enough additional bytes to get us up to the next size
+        // (again, we're trying the possibilities in order of increasing
+        // size).
+        size_t next = size[index[i]] - file->size;
+        size_t read = 0;
+        if (next > 0) {
+            switch (type) {
+                case MTD:
+                    read = mtd_read_data(ctx, p, next);
+                    break;
+
+                case EMMC:
+                    read = fread(p, 1, next, dev);
+                    break;
+            }
+            if (next != read) {
+                printf("short read (%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("partition read matched size %d sha %s\n",
+                   size[index[i]], sha1sum[index[i]]);
+            break;
+        }
+
+        p += read;
+    }
+
+    switch (type) {
+        case MTD:
+            mtd_read_close(ctx);
+            break;
+
+        case EMMC:
+            fclose(dev);
+            break;
+    }
+
+
+    if (i == pairs) {
+        // Ran off the end of the list of (size,sha1) pairs without
+        // finding a match.
+        printf("contents of partition \"%s\" didn't match %s\n",
+               partition, filename);
+        free(file->data);
+        file->data = NULL;
+        return -1;
+    }
+
+    const uint8_t* sha_final = SHA_final(&sha_ctx);
+    for (i = 0; i < SHA_DIGEST_SIZE; ++i) {
+        file->sha1[i] = sha_final[i];
+    }
+
+    // Fake some stat() info.
+    file->st.st_mode = 0644;
+    file->st.st_uid = 0;
+    file->st.st_gid = 0;
+
+    free(copy);
+    free(index);
+    free(size);
+    free(sha1sum);
+
+    return 0;
+}
+
+
+// Save the contents of the given FileContents object under the given
+// filename.  Return 0 on success.
+static 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' partition, a string of the form
+// "MTD:<partition>[:...]" or "EMMC:<partition_device>:".  Return 0 on
+// success.
+int WriteToPartition(unsigned char* data, size_t len,
+                        const char* target) {
+    char* copy = strdup(target);
+    const char* magic = strtok(copy, ":");
+
+    enum PartitionType type;
+    if (strcmp(magic, "MTD") == 0) {
+        type = MTD;
+    } else if (strcmp(magic, "EMMC") == 0) {
+        type = EMMC;
+    } else {
+        printf("WriteToPartition called with bad target (%s)\n", target);
+        return -1;
+    }
+    const char* partition = strtok(NULL, ":");
+
+    if (partition == NULL) {
+        printf("bad partition target name \"%s\"\n", target);
+        return -1;
+    }
+
+    switch (type) {
+        case MTD:
+            if (!mtd_partitions_scanned) {
+                mtd_scan_partitions();
+                mtd_partitions_scanned = 1;
+            }
+
+            const MtdPartition* mtd = mtd_find_partition_by_name(partition);
+            if (mtd == NULL) {
+                printf("mtd partition \"%s\" not found for writing\n",
+                       partition);
+                return -1;
+            }
+
+            MtdWriteContext* ctx = mtd_write_partition(mtd);
+            if (ctx == NULL) {
+                printf("failed to init mtd partition \"%s\" for writing\n",
+                       partition);
+                return -1;
+            }
+
+            size_t written = mtd_write_data(ctx, (char*)data, len);
+            if (written != len) {
+                printf("only wrote %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;
+            }
+            break;
+
+        case EMMC:
+            ;
+            FILE* f = fopen(partition, "wb");
+            if (fwrite(data, 1, len, f) != len) {
+                printf("short write writing to %s (%s)\n",
+                       partition, strerror(errno));
+                return -1;
+            }
+            if (fclose(f) != 0) {
+                printf("error closing %s (%s)\n", partition, strerror(errno));
+                return -1;
+            }
+            break;
+    }
+
+    free(copy);
+    return 0;
+}
+
+
+// Take a string 'str' of 40 hex digits and parse it into the 20
+// byte array 'digest'.  'str' may contain only the digest or be of
+// the form "<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
+    // 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 a partition to read the source data.
+// See the comments for the LoadPartition Contents() function above
+// for the format of such a filename.
+
+int applypatch(const char* source_filename,
+               const char* target_filename,
+               const char* target_sha1_str,
+               size_t target_size,
+               int num_patches,
+               char** const patch_sha1_str,
+               Value** patch_data) {
+    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, &copy_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 ||
+            strncmp(target_filename, "EMMC:", 5) == 0) {
+            // If the target is a partition, we're actually going to
+            // write the output to /tmp and then copy it to the
+            // partition.  statfs() always returns 0 blocks free for
+            // /tmp, so instead we'll just assume that /tmp has enough
+            // space to hold the file.
+
+            // We still write the original source to cache, in case
+            // the partition write is interrupted.
+            if (MakeFreeSpaceOnCache(source_file.size) < 0) {
+                printf("not enough free space on /cache\n");
+                return 1;
+            }
+            if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) {
+                printf("failed to back up source file\n");
+                return 1;
+            }
+            made_copy = 1;
+            retry = 0;
+        } else {
+            int enough_space = 0;
+            if (retry > 0) {
+                size_t free_space = FreeSpaceForFile(target_fs);
+                enough_space =
+                    (free_space > (256 << 10)) &&          // 256k (two-block) minimum
+                    (free_space > (target_size * 3 / 2));  // 50% margin of error
+                printf("target %ld bytes; free space %ld bytes; retry %d; enough %d\n",
+                       (long)target_size, (long)free_space, retry, enough_space);
+            }
+
+            if (!enough_space) {
+                retry = 0;
+            }
+
+            if (!enough_space && source_patch_value != NULL) {
+                // Using the original source, but not enough free space.  First
+                // copy the source file to cache, then delete it from the original
+                // location.
+
+                if (strncmp(source_filename, "MTD:", 4) == 0 ||
+                    strncmp(source_filename, "EMMC:", 5) == 0) {
+                    // It's impossible to free space on the target filesystem by
+                    // deleting the source if the source is a partition.  If
+                    // we're ever in a state where we need to do this, fail.
+                    printf("not enough free space for target but source "
+                           "is partition\n");
+                    return 1;
+                }
+
+                if (MakeFreeSpaceOnCache(source_file.size) < 0) {
+                    printf("not enough free space on /cache\n");
+                    return 1;
+                }
+
+                if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) {
+                    printf("failed to back up source file\n");
+                    return 1;
+                }
+                made_copy = 1;
+                unlink(source_filename);
+
+                size_t free_space = FreeSpaceForFile(target_fs);
+                printf("(now %ld bytes free for target)\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 = &copy_file;
+            patch = copy_patch_value;
+        }
+
+        if (patch->type != VAL_BLOB) {
+            printf("patch is not a blob\n");
+            return 1;
+        }
+
+        SinkFn sink = NULL;
+        void* token = NULL;
+        output = -1;
+        outname = NULL;
+        if (strncmp(target_filename, "MTD:", 4) == 0 ||
+            strncmp(target_filename, "EMMC:", 5) == 0) {
+            // We store the decoded output in memory.
+            msi.buffer = malloc(target_size);
+            if (msi.buffer == NULL) {
+                printf("failed to alloc %ld bytes for output\n",
+                       (long)target_size);
+                return 1;
+            }
+            msi.pos = 0;
+            msi.size = target_size;
+            sink = MemorySink;
+            token = &msi;
+        } else {
+            // We write the decoded output to "<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 partition.
+        if (WriteToPartition(msi.buffer, msi.pos, target_filename) != 0) {
+            printf("write of patched data to %s failed\n", target_filename);
+            return 1;
+        }
+        free(msi.buffer);
+    } else {
+        // Give the .patch file the same owner, group, and mode of the
+        // original source file.
+        if (chmod(outname, source_to_use->st.st_mode) != 0) {
+            printf("chmod of \"%s\" failed: %s\n", outname, strerror(errno));
+            return 1;
+        }
+        if (chown(outname, source_to_use->st.st_uid,
+                  source_to_use->st.st_gid) != 0) {
+            printf("chown of \"%s\" failed: %s\n", outname, strerror(errno));
+            return 1;
+        }
+
+        // Finally, rename the .patch file to replace the target file.
+        if (rename(outname, target_filename) != 0) {
+            printf("rename of .patch to \"%s\" failed: %s\n",
+                   target_filename, strerror(errno));
+            return 1;
+        }
+    }
+
+    // If this run of applypatch created the copy, and we're here, we
+    // can delete it.
+    if (made_copy) unlink(CACHE_TEMP_SOURCE);
+
+    // Success!
+    return 0;
+}
diff --git a/applypatch/applypatch.h b/applypatch/applypatch.h
new file mode 100644
index 0000000..10c0125
--- /dev/null
+++ b/applypatch/applypatch.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _APPLYPATCH_H
+#define _APPLYPATCH_H
+
+#include <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
diff --git a/applypatch/applypatch.sh b/applypatch/applypatch.sh
new file mode 100755
index 0000000..8ea68a1
--- /dev/null
+++ b/applypatch/applypatch.sh
@@ -0,0 +1,350 @@
+#!/bin/bash
+#
+# A test suite for applypatch.  Run in a client where you have done
+# envsetup, choosecombo, etc.
+#
+# DO NOT RUN THIS ON A DEVICE YOU CARE ABOUT.  It will mess up your
+# system partition.
+#
+#
+# TODO: find some way to get this run regularly along with the rest of
+# the tests.
+
+EMULATOR_PORT=5580
+DATA_DIR=$ANDROID_BUILD_TOP/bootable/recovery/applypatch/testdata
+
+# This must be the filename that applypatch uses for its copies.
+CACHE_TEMP_SOURCE=/cache/saved.file
+
+# Put all binaries and files here.  We use /cache because it's a
+# temporary filesystem in the emulator; it's created fresh each time
+# the emulator starts.
+WORK_DIR=/system
+
+# partition that WORK_DIR is located on, without the leading slash
+WORK_FS=system
+
+# set to 0 to use a device instead
+USE_EMULATOR=1
+
+# ------------------------
+
+tmpdir=$(mktemp -d)
+
+if [ "$USE_EMULATOR" == 1 ]; then
+  emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT &
+  pid_emulator=$!
+  ADB="adb -s emulator-$EMULATOR_PORT "
+else
+  ADB="adb -d "
+fi
+
+echo "waiting to connect to device"
+$ADB wait-for-device
+echo "device is available"
+$ADB remount
+# free up enough space on the system partition for the test to run.
+$ADB shell rm -r /system/media
+
+# run a command on the device; exit with the exit status of the device
+# command.
+run_command() {
+  $ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}'
+}
+
+testname() {
+  echo
+  echo "$1"...
+  testname="$1"
+}
+
+fail() {
+  echo
+  echo FAIL: $testname
+  echo
+  [ "$open_pid" == "" ] || kill $open_pid
+  [ "$pid_emulator" == "" ] || kill $pid_emulator
+  exit 1
+}
+
+sha1() {
+  sha1sum $1 | awk '{print $1}'
+}
+
+free_space() {
+  run_command df | awk "/$1/ {print gensub(/K/, \"\", \"g\", \$6)}"
+}
+
+cleanup() {
+  # not necessary if we're about to kill the emulator, but nice for
+  # running on real devices or already-running emulators.
+  testname "removing test files"
+  run_command rm $WORK_DIR/bloat.dat
+  run_command rm $WORK_DIR/old.file
+  run_command rm $WORK_DIR/foo
+  run_command rm $WORK_DIR/patch.bsdiff
+  run_command rm $WORK_DIR/applypatch
+  run_command rm $CACHE_TEMP_SOURCE
+  run_command rm /cache/bloat*.dat
+
+  [ "$pid_emulator" == "" ] || kill $pid_emulator
+
+  if [ $# == 0 ]; then
+    rm -rf $tmpdir
+  fi
+}
+
+cleanup leave_tmp
+
+$ADB push $ANDROID_PRODUCT_OUT/system/bin/applypatch $WORK_DIR/applypatch
+
+BAD1_SHA1=$(printf "%040x" $RANDOM)
+BAD2_SHA1=$(printf "%040x" $RANDOM)
+OLD_SHA1=$(sha1 $DATA_DIR/old.file)
+NEW_SHA1=$(sha1 $DATA_DIR/new.file)
+NEW_SIZE=$(stat -c %s $DATA_DIR/new.file)
+
+# --------------- basic execution ----------------------
+
+testname "usage message"
+run_command $WORK_DIR/applypatch && fail
+
+testname "display license"
+run_command $WORK_DIR/applypatch -l | grep -q -i copyright || fail
+
+
+# --------------- check mode ----------------------
+
+$ADB push $DATA_DIR/old.file $WORK_DIR
+
+testname "check mode single"
+run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail
+
+testname "check mode multiple"
+run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail
+
+testname "check mode failure"
+run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail
+
+$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE
+# put some junk in the old file
+run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail
+
+testname "check mode cache (corrupted) single"
+run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail
+
+testname "check mode cache (corrupted) multiple"
+run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail
+
+testname "check mode cache (corrupted) failure"
+run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail
+
+# remove the old file entirely
+run_command rm $WORK_DIR/old.file
+
+testname "check mode cache (missing) single"
+run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail
+
+testname "check mode cache (missing) multiple"
+run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail
+
+testname "check mode cache (missing) failure"
+run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail
+
+
+# --------------- apply patch ----------------------
+
+$ADB push $DATA_DIR/old.file $WORK_DIR
+$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR
+echo hello > $tmpdir/foo
+$ADB push $tmpdir/foo $WORK_DIR
+
+# Check that the partition has enough space to apply the patch without
+# copying.  If it doesn't, we'll be testing the low-space condition
+# when we intend to test the not-low-space condition.
+testname "apply patches (with enough space)"
+free_kb=$(free_space $WORK_FS)
+echo "${free_kb}kb free on /$WORK_FS."
+if (( free_kb * 1024 < NEW_SIZE * 3 / 2 )); then
+  echo "Not enough space on /$WORK_FS to patch test file."
+  echo
+  echo "This doesn't mean that applypatch is necessarily broken;"
+  echo "just that /$WORK_FS doesn't have enough free space to"
+  echo "properly run this test."
+  exit 1
+fi
+
+testname "apply bsdiff patch"
+run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
+$ADB pull $WORK_DIR/old.file $tmpdir/patched
+diff -q $DATA_DIR/new.file $tmpdir/patched || fail
+
+testname "reapply bsdiff patch"
+run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
+$ADB pull $WORK_DIR/old.file $tmpdir/patched
+diff -q $DATA_DIR/new.file $tmpdir/patched || fail
+
+
+# --------------- apply patch in new location ----------------------
+
+$ADB push $DATA_DIR/old.file $WORK_DIR
+$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR
+
+# Check that the partition has enough space to apply the patch without
+# copying.  If it doesn't, we'll be testing the low-space condition
+# when we intend to test the not-low-space condition.
+testname "apply patch to new location (with enough space)"
+free_kb=$(free_space $WORK_FS)
+echo "${free_kb}kb free on /$WORK_FS."
+if (( free_kb * 1024 < NEW_SIZE * 3 / 2 )); then
+  echo "Not enough space on /$WORK_FS to patch test file."
+  echo
+  echo "This doesn't mean that applypatch is necessarily broken;"
+  echo "just that /$WORK_FS doesn't have enough free space to"
+  echo "properly run this test."
+  exit 1
+fi
+
+run_command rm $WORK_DIR/new.file
+run_command rm $CACHE_TEMP_SOURCE
+
+testname "apply bsdiff patch to new location"
+run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
+$ADB pull $WORK_DIR/new.file $tmpdir/patched
+diff -q $DATA_DIR/new.file $tmpdir/patched || fail
+
+testname "reapply bsdiff patch to new location"
+run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
+$ADB pull $WORK_DIR/new.file $tmpdir/patched
+diff -q $DATA_DIR/new.file $tmpdir/patched || fail
+
+$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE
+# put some junk in the old file
+run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail
+
+testname "apply bsdiff patch to new location with corrupted source"
+run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo || fail
+$ADB pull $WORK_DIR/new.file $tmpdir/patched
+diff -q $DATA_DIR/new.file $tmpdir/patched || fail
+
+# put some junk in the cache copy, too
+run_command dd if=/dev/urandom of=$CACHE_TEMP_SOURCE count=100 bs=1024 || fail
+
+run_command rm $WORK_DIR/new.file
+testname "apply bsdiff patch to new location with corrupted source and copy (no new file)"
+run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo && fail
+
+# put some junk in the new file
+run_command dd if=/dev/urandom of=$WORK_DIR/new.file count=100 bs=1024 || fail
+
+testname "apply bsdiff patch to new location with corrupted source and copy (bad new file)"
+run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo && fail
+
+# --------------- apply patch with low space on /system ----------------------
+
+$ADB push $DATA_DIR/old.file $WORK_DIR
+$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR
+
+free_kb=$(free_space $WORK_FS)
+echo "${free_kb}kb free on /$WORK_FS; we'll soon fix that."
+echo run_command dd if=/dev/zero of=$WORK_DIR/bloat.dat count=$((free_kb-512)) bs=1024 || fail
+run_command dd if=/dev/zero of=$WORK_DIR/bloat.dat count=$((free_kb-512)) bs=1024 || fail
+free_kb=$(free_space $WORK_FS)
+echo "${free_kb}kb free on /$WORK_FS now."
+
+testname "apply bsdiff patch with low space"
+run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
+$ADB pull $WORK_DIR/old.file $tmpdir/patched
+diff -q $DATA_DIR/new.file $tmpdir/patched || fail
+
+testname "reapply bsdiff patch with low space"
+run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
+$ADB pull $WORK_DIR/old.file $tmpdir/patched
+diff -q $DATA_DIR/new.file $tmpdir/patched || fail
+
+# --------------- apply patch with low space on /system and /cache ----------------------
+
+$ADB push $DATA_DIR/old.file $WORK_DIR
+$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR
+
+free_kb=$(free_space $WORK_FS)
+echo "${free_kb}kb free on /$WORK_FS"
+
+run_command mkdir /cache/subdir
+run_command 'echo > /cache/subdir/a.file'
+run_command 'echo > /cache/a.file'
+run_command mkdir /cache/recovery /cache/recovery/otatest
+run_command 'echo > /cache/recovery/otatest/b.file'
+run_command "echo > $CACHE_TEMP_SOURCE"
+free_kb=$(free_space cache)
+echo "${free_kb}kb free on /cache; we'll soon fix that."
+run_command dd if=/dev/zero of=/cache/bloat_small.dat count=128 bs=1024 || fail
+run_command dd if=/dev/zero of=/cache/bloat_large.dat count=$((free_kb-640)) bs=1024 || fail
+free_kb=$(free_space cache)
+echo "${free_kb}kb free on /cache now."
+
+testname "apply bsdiff patch with low space, full cache, can't delete enough"
+$ADB shell 'cat >> /cache/bloat_large.dat' & open_pid=$!
+echo "open_pid is $open_pid"
+
+# size check should fail even though it deletes some stuff
+run_command $WORK_DIR/applypatch -s $NEW_SIZE && fail
+run_command ls /cache/bloat_small.dat && fail          # was deleted
+run_command ls /cache/a.file && fail                   # was deleted
+run_command ls /cache/recovery/otatest/b.file && fail  # was deleted
+run_command ls /cache/bloat_large.dat || fail          # wasn't deleted because it was open
+run_command ls /cache/subdir/a.file || fail            # wasn't deleted because it's in a subdir
+run_command ls $CACHE_TEMP_SOURCE || fail              # wasn't deleted because it's the source file copy
+
+# should fail; not enough files can be deleted
+run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff && fail
+run_command ls /cache/bloat_large.dat || fail   # wasn't deleted because it was open
+run_command ls /cache/subdir/a.file || fail     # wasn't deleted because it's in a subdir
+run_command ls $CACHE_TEMP_SOURCE || fail       # wasn't deleted because it's the source file copy
+
+kill $open_pid   # /cache/bloat_large.dat is no longer open
+
+testname "apply bsdiff patch with low space, full cache, can delete enough"
+
+# should succeed after deleting /cache/bloat_large.dat
+run_command $WORK_DIR/applypatch -s $NEW_SIZE || fail
+run_command ls /cache/bloat_large.dat && fail   # was deleted
+run_command ls /cache/subdir/a.file || fail     # still wasn't deleted because it's in a subdir
+run_command ls $CACHE_TEMP_SOURCE || fail       # wasn't deleted because it's the source file copy
+
+# should succeed
+run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
+$ADB pull $WORK_DIR/old.file $tmpdir/patched
+diff -q $DATA_DIR/new.file $tmpdir/patched || fail
+run_command ls /cache/subdir/a.file || fail     # still wasn't deleted because it's in a subdir
+run_command ls $CACHE_TEMP_SOURCE && fail       # was deleted because patching overwrote it, then deleted it
+
+# --------------- apply patch from cache ----------------------
+
+$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE
+# put some junk in the old file
+run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail
+
+testname "apply bsdiff patch from cache (corrupted source) with low space"
+run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
+$ADB pull $WORK_DIR/old.file $tmpdir/patched
+diff -q $DATA_DIR/new.file $tmpdir/patched || fail
+
+$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE
+# remove the old file entirely
+run_command rm $WORK_DIR/old.file
+
+testname "apply bsdiff patch from cache (missing source) with low space"
+run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
+$ADB pull $WORK_DIR/old.file $tmpdir/patched
+diff -q $DATA_DIR/new.file $tmpdir/patched || fail
+
+
+# --------------- cleanup ----------------------
+
+cleanup
+
+echo
+echo PASS
+echo
+
diff --git a/applypatch/bsdiff.c b/applypatch/bsdiff.c
new file mode 100644
index 0000000..b6d342b
--- /dev/null
+++ b/applypatch/bsdiff.c
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Most of this code comes from bsdiff.c from the bsdiff-4.3
+ * distribution, which is:
+ */
+
+/*-
+ * Copyright 2003-2005 Colin Percival
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <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;
+}
diff --git a/applypatch/bspatch.c b/applypatch/bspatch.c
new file mode 100644
index 0000000..2e80f81
--- /dev/null
+++ b/applypatch/bspatch.c
@@ -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;
+}
diff --git a/applypatch/freecache.c b/applypatch/freecache.c
new file mode 100644
index 0000000..9827fda
--- /dev/null
+++ b/applypatch/freecache.c
@@ -0,0 +1,172 @@
+#include <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;
+}
diff --git a/applypatch/imgdiff.c b/applypatch/imgdiff.c
new file mode 100644
index 0000000..6b9ebee
--- /dev/null
+++ b/applypatch/imgdiff.c
@@ -0,0 +1,1010 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This program constructs binary patches for images -- such as boot.img
+ * and recovery.img -- that consist primarily of large chunks of gzipped
+ * data interspersed with uncompressed data.  Doing a naive bsdiff of
+ * these files is not useful because small changes in the data lead to
+ * large changes in the compressed bitstream; bsdiff patches of gzipped
+ * data are typically as large as the data itself.
+ *
+ * To patch these usefully, we break the source and target images up into
+ * chunks of two types: "normal" and "gzip".  Normal chunks are simply
+ * patched using a plain bsdiff.  Gzip chunks are first expanded, then a
+ * bsdiff is applied to the uncompressed data, then the patched data is
+ * gzipped using the same encoder parameters.  Patched chunks are
+ * concatenated together to create the output file; the output image
+ * should be *exactly* the same series of bytes as the target image used
+ * originally to generate the patch.
+ *
+ * To work well with this tool, the gzipped sections of the target
+ * image must have been generated using the same deflate encoder that
+ * is available in applypatch, namely, the one in the zlib library.
+ * In practice this means that images should be compressed using the
+ * "minigzip" tool included in the zlib distribution, not the GNU gzip
+ * program.
+ *
+ * An "imgdiff" patch consists of a header describing the chunk structure
+ * of the file and any encoding parameters needed for the gzipped
+ * chunks, followed by N bsdiff patches, one per chunk.
+ *
+ * For a diff to be generated, the source and target images must have the
+ * same "chunk" structure: that is, the same number of gzipped and normal
+ * chunks in the same order.  Android boot and recovery images currently
+ * consist of five chunks:  a small normal header, a gzipped kernel, a
+ * small normal section, a gzipped ramdisk, and finally a small normal
+ * footer.
+ *
+ * Caveats:  we locate gzipped sections within the source and target
+ * images by searching for the byte sequence 1f8b0800:  1f8b is the gzip
+ * magic number; 08 specifies the "deflate" encoding [the only encoding
+ * supported by the gzip standard]; and 00 is the flags byte.  We do not
+ * currently support any extra header fields (which would be indicated by
+ * a nonzero flags byte).  We also don't handle the case when that byte
+ * sequence appears spuriously in the file.  (Note that it would have to
+ * occur spuriously within a normal chunk to be a problem.)
+ *
+ *
+ * The imgdiff patch header looks like this:
+ *
+ *    "IMGDIFF1"                  (8)   [magic number and version]
+ *    chunk count                 (4)
+ *    for each chunk:
+ *        chunk type              (4)   [CHUNK_{NORMAL, GZIP, DEFLATE, RAW}]
+ *        if chunk type == CHUNK_NORMAL:
+ *           source start         (8)
+ *           source len           (8)
+ *           bsdiff patch offset  (8)   [from start of patch file]
+ *        if chunk type == CHUNK_GZIP:      (version 1 only)
+ *           source start         (8)
+ *           source len           (8)
+ *           bsdiff patch offset  (8)   [from start of patch file]
+ *           source expanded len  (8)   [size of uncompressed source]
+ *           target expected len  (8)   [size of uncompressed target]
+ *           gzip level           (4)
+ *                method          (4)
+ *                windowBits      (4)
+ *                memLevel        (4)
+ *                strategy        (4)
+ *           gzip header len      (4)
+ *           gzip header          (gzip header len)
+ *           gzip footer          (8)
+ *        if chunk type == CHUNK_DEFLATE:   (version 2 only)
+ *           source start         (8)
+ *           source len           (8)
+ *           bsdiff patch offset  (8)   [from start of patch file]
+ *           source expanded len  (8)   [size of uncompressed source]
+ *           target expected len  (8)   [size of uncompressed target]
+ *           gzip level           (4)
+ *                method          (4)
+ *                windowBits      (4)
+ *                memLevel        (4)
+ *                strategy        (4)
+ *        if chunk type == RAW:             (version 2 only)
+ *           target len           (4)
+ *           data                 (target len)
+ *
+ * All integers are little-endian.  "source start" and "source len"
+ * specify the section of the input image that comprises this chunk,
+ * including the gzip header and footer for gzip chunks.  "source
+ * expanded len" is the size of the uncompressed source data.  "target
+ * expected len" is the size of the uncompressed data after applying
+ * the bsdiff patch.  The next five parameters specify the zlib
+ * parameters to be used when compressing the patched data, and the
+ * next three specify the header and footer to be wrapped around the
+ * compressed data to create the output chunk (so that header contents
+ * like the timestamp are recreated exactly).
+ *
+ * After the header there are 'chunk count' bsdiff patches; the offset
+ * of each from the beginning of the file is specified in the header.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "zlib.h"
+#include "imgdiff.h"
+#include "utils.h"
+
+typedef struct {
+  int type;             // CHUNK_NORMAL, CHUNK_DEFLATE
+  size_t start;         // offset of chunk in original image file
+
+  size_t len;
+  unsigned char* data;  // data to be patched (uncompressed, for deflate chunks)
+
+  size_t source_start;
+  size_t source_len;
+
+  off_t* I;             // used by bsdiff
+
+  // --- for CHUNK_DEFLATE chunks only: ---
+
+  // original (compressed) deflate data
+  size_t deflate_len;
+  unsigned char* deflate_data;
+
+  char* filename;       // used for zip entries
+
+  // deflate encoder parameters
+  int level, method, windowBits, memLevel, strategy;
+
+  size_t source_uncompressed_len;
+} ImageChunk;
+
+typedef struct {
+  int data_offset;
+  int deflate_len;
+  int uncomp_len;
+  char* filename;
+} ZipFileEntry;
+
+static int fileentry_compare(const void* a, const void* b) {
+  int ao = ((ZipFileEntry*)a)->data_offset;
+  int bo = ((ZipFileEntry*)b)->data_offset;
+  if (ao < bo) {
+    return -1;
+  } else if (ao > bo) {
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+// from bsdiff.c
+int bsdiff(u_char* old, off_t oldsize, off_t** IP, u_char* new, off_t newsize,
+           const char* patch_filename);
+
+unsigned char* ReadZip(const char* filename,
+                       int* num_chunks, ImageChunk** chunks,
+                       int include_pseudo_chunk) {
+  struct stat st;
+  if (stat(filename, &st) != 0) {
+    printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
+    return NULL;
+  }
+
+  unsigned char* img = malloc(st.st_size);
+  FILE* f = fopen(filename, "rb");
+  if (fread(img, 1, st.st_size, f) != st.st_size) {
+    printf("failed to read \"%s\" %s\n", filename, strerror(errno));
+    fclose(f);
+    return NULL;
+  }
+  fclose(f);
+
+  // look for the end-of-central-directory record.
+
+  int i;
+  for (i = st.st_size-20; i >= 0 && i > st.st_size - 65600; --i) {
+    if (img[i] == 0x50 && img[i+1] == 0x4b &&
+        img[i+2] == 0x05 && img[i+3] == 0x06) {
+      break;
+    }
+  }
+  // double-check: this archive consists of a single "disk"
+  if (!(img[i+4] == 0 && img[i+5] == 0 && img[i+6] == 0 && img[i+7] == 0)) {
+    printf("can't process multi-disk archive\n");
+    return NULL;
+  }
+
+  int cdcount = Read2(img+i+8);
+  int cdoffset = Read4(img+i+16);
+
+  ZipFileEntry* temp_entries = malloc(cdcount * sizeof(ZipFileEntry));
+  int entrycount = 0;
+
+  unsigned char* cd = img+cdoffset;
+  for (i = 0; i < cdcount; ++i) {
+    if (!(cd[0] == 0x50 && cd[1] == 0x4b && cd[2] == 0x01 && cd[3] == 0x02)) {
+      printf("bad central directory entry %d\n", i);
+      return NULL;
+    }
+
+    int clen = Read4(cd+20);   // compressed len
+    int ulen = Read4(cd+24);   // uncompressed len
+    int nlen = Read2(cd+28);   // filename len
+    int xlen = Read2(cd+30);   // extra field len
+    int mlen = Read2(cd+32);   // file comment len
+    int hoffset = Read4(cd+42);   // local header offset
+
+    char* filename = malloc(nlen+1);
+    memcpy(filename, cd+46, nlen);
+    filename[nlen] = '\0';
+
+    int method = Read2(cd+10);
+
+    cd += 46 + nlen + xlen + mlen;
+
+    if (method != 8) {  // 8 == deflate
+      free(filename);
+      continue;
+    }
+
+    unsigned char* lh = img + hoffset;
+
+    if (!(lh[0] == 0x50 && lh[1] == 0x4b && lh[2] == 0x03 && lh[3] == 0x04)) {
+      printf("bad local file header entry %d\n", i);
+      return NULL;
+    }
+
+    if (Read2(lh+26) != nlen || memcmp(lh+30, filename, nlen) != 0) {
+      printf("central dir filename doesn't match local header\n");
+      return NULL;
+    }
+
+    xlen = Read2(lh+28);   // extra field len; might be different from CD entry?
+
+    temp_entries[entrycount].data_offset = hoffset+30+nlen+xlen;
+    temp_entries[entrycount].deflate_len = clen;
+    temp_entries[entrycount].uncomp_len = ulen;
+    temp_entries[entrycount].filename = filename;
+    ++entrycount;
+  }
+
+  qsort(temp_entries, entrycount, sizeof(ZipFileEntry), fileentry_compare);
+
+#if 0
+  printf("found %d deflated entries\n", entrycount);
+  for (i = 0; i < entrycount; ++i) {
+    printf("off %10d  len %10d unlen %10d   %p %s\n",
+           temp_entries[i].data_offset,
+           temp_entries[i].deflate_len,
+           temp_entries[i].uncomp_len,
+           temp_entries[i].filename,
+           temp_entries[i].filename);
+  }
+#endif
+
+  *num_chunks = 0;
+  *chunks = malloc((entrycount*2+2) * sizeof(ImageChunk));
+  ImageChunk* curr = *chunks;
+
+  if (include_pseudo_chunk) {
+    curr->type = CHUNK_NORMAL;
+    curr->start = 0;
+    curr->len = st.st_size;
+    curr->data = img;
+    curr->filename = NULL;
+    curr->I = NULL;
+    ++curr;
+    ++*num_chunks;
+  }
+
+  int pos = 0;
+  int nextentry = 0;
+
+  while (pos < st.st_size) {
+    if (nextentry < entrycount && pos == temp_entries[nextentry].data_offset) {
+      curr->type = CHUNK_DEFLATE;
+      curr->start = pos;
+      curr->deflate_len = temp_entries[nextentry].deflate_len;
+      curr->deflate_data = img + pos;
+      curr->filename = temp_entries[nextentry].filename;
+      curr->I = NULL;
+
+      curr->len = temp_entries[nextentry].uncomp_len;
+      curr->data = malloc(curr->len);
+
+      z_stream strm;
+      strm.zalloc = Z_NULL;
+      strm.zfree = Z_NULL;
+      strm.opaque = Z_NULL;
+      strm.avail_in = curr->deflate_len;
+      strm.next_in = curr->deflate_data;
+
+      // -15 means we are decoding a 'raw' deflate stream; zlib will
+      // not expect zlib headers.
+      int ret = inflateInit2(&strm, -15);
+
+      strm.avail_out = curr->len;
+      strm.next_out = curr->data;
+      ret = inflate(&strm, Z_NO_FLUSH);
+      if (ret != Z_STREAM_END) {
+        printf("failed to inflate \"%s\"; %d\n", curr->filename, ret);
+        return NULL;
+      }
+
+      inflateEnd(&strm);
+
+      pos += curr->deflate_len;
+      ++nextentry;
+      ++*num_chunks;
+      ++curr;
+      continue;
+    }
+
+    // use a normal chunk to take all the data up to the start of the
+    // next deflate section.
+
+    curr->type = CHUNK_NORMAL;
+    curr->start = pos;
+    if (nextentry < entrycount) {
+      curr->len = temp_entries[nextentry].data_offset - pos;
+    } else {
+      curr->len = st.st_size - pos;
+    }
+    curr->data = img + pos;
+    curr->filename = NULL;
+    curr->I = NULL;
+    pos += curr->len;
+
+    ++*num_chunks;
+    ++curr;
+  }
+
+  free(temp_entries);
+  return img;
+}
+
+/*
+ * Read the given file and break it up into chunks, putting the number
+ * of chunks and their info in *num_chunks and **chunks,
+ * respectively.  Returns a malloc'd block of memory containing the
+ * contents of the file; various pointers in the output chunk array
+ * will point into this block of memory.  The caller should free the
+ * return value when done with all the chunks.  Returns NULL on
+ * failure.
+ */
+unsigned char* ReadImage(const char* filename,
+                         int* num_chunks, ImageChunk** chunks) {
+  struct stat st;
+  if (stat(filename, &st) != 0) {
+    printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
+    return NULL;
+  }
+
+  unsigned char* img = malloc(st.st_size + 4);
+  FILE* f = fopen(filename, "rb");
+  if (fread(img, 1, st.st_size, f) != st.st_size) {
+    printf("failed to read \"%s\" %s\n", filename, strerror(errno));
+    fclose(f);
+    return NULL;
+  }
+  fclose(f);
+
+  // append 4 zero bytes to the data so we can always search for the
+  // four-byte string 1f8b0800 starting at any point in the actual
+  // file data, without special-casing the end of the data.
+  memset(img+st.st_size, 0, 4);
+
+  size_t pos = 0;
+
+  *num_chunks = 0;
+  *chunks = NULL;
+
+  while (pos < st.st_size) {
+    unsigned char* p = img+pos;
+
+    if (st.st_size - pos >= 4 &&
+        p[0] == 0x1f && p[1] == 0x8b &&
+        p[2] == 0x08 &&    // deflate compression
+        p[3] == 0x00) {    // no header flags
+      // 'pos' is the offset of the start of a gzip chunk.
+
+      *num_chunks += 3;
+      *chunks = realloc(*chunks, *num_chunks * sizeof(ImageChunk));
+      ImageChunk* curr = *chunks + (*num_chunks-3);
+
+      // create a normal chunk for the header.
+      curr->start = pos;
+      curr->type = CHUNK_NORMAL;
+      curr->len = GZIP_HEADER_LEN;
+      curr->data = p;
+      curr->I = NULL;
+
+      pos += curr->len;
+      p += curr->len;
+      ++curr;
+
+      curr->type = CHUNK_DEFLATE;
+      curr->filename = NULL;
+      curr->I = NULL;
+
+      // We must decompress this chunk in order to discover where it
+      // ends, and so we can put the uncompressed data and its length
+      // into curr->data and curr->len.
+
+      size_t allocated = 32768;
+      curr->len = 0;
+      curr->data = malloc(allocated);
+      curr->start = pos;
+      curr->deflate_data = p;
+
+      z_stream strm;
+      strm.zalloc = Z_NULL;
+      strm.zfree = Z_NULL;
+      strm.opaque = Z_NULL;
+      strm.avail_in = st.st_size - pos;
+      strm.next_in = p;
+
+      // -15 means we are decoding a 'raw' deflate stream; zlib will
+      // not expect zlib headers.
+      int ret = inflateInit2(&strm, -15);
+
+      do {
+        strm.avail_out = allocated - curr->len;
+        strm.next_out = curr->data + curr->len;
+        ret = inflate(&strm, Z_NO_FLUSH);
+        curr->len = allocated - strm.avail_out;
+        if (strm.avail_out == 0) {
+          allocated *= 2;
+          curr->data = realloc(curr->data, allocated);
+        }
+      } while (ret != Z_STREAM_END);
+
+      curr->deflate_len = st.st_size - strm.avail_in - pos;
+      inflateEnd(&strm);
+      pos += curr->deflate_len;
+      p += curr->deflate_len;
+      ++curr;
+
+      // create a normal chunk for the footer
+
+      curr->type = CHUNK_NORMAL;
+      curr->start = pos;
+      curr->len = GZIP_FOOTER_LEN;
+      curr->data = img+pos;
+      curr->I = NULL;
+
+      pos += curr->len;
+      p += curr->len;
+      ++curr;
+
+      // The footer (that we just skipped over) contains the size of
+      // the uncompressed data.  Double-check to make sure that it
+      // matches the size of the data we got when we actually did
+      // the decompression.
+      size_t footer_size = Read4(p-4);
+      if (footer_size != curr[-2].len) {
+        printf("Error: footer size %d != decompressed size %d\n",
+                footer_size, curr[-2].len);
+        free(img);
+        return NULL;
+      }
+    } else {
+      // Reallocate the list for every chunk; we expect the number of
+      // chunks to be small (5 for typical boot and recovery images).
+      ++*num_chunks;
+      *chunks = realloc(*chunks, *num_chunks * sizeof(ImageChunk));
+      ImageChunk* curr = *chunks + (*num_chunks-1);
+      curr->start = pos;
+      curr->I = NULL;
+
+      // 'pos' is not the offset of the start of a gzip chunk, so scan
+      // forward until we find a gzip header.
+      curr->type = CHUNK_NORMAL;
+      curr->data = p;
+
+      for (curr->len = 0; curr->len < (st.st_size - pos); ++curr->len) {
+        if (p[curr->len] == 0x1f &&
+            p[curr->len+1] == 0x8b &&
+            p[curr->len+2] == 0x08 &&
+            p[curr->len+3] == 0x00) {
+          break;
+        }
+      }
+      pos += curr->len;
+    }
+  }
+
+  return img;
+}
+
+#define BUFFER_SIZE 32768
+
+/*
+ * Takes the uncompressed data stored in the chunk, compresses it
+ * using the zlib parameters stored in the chunk, and checks that it
+ * matches exactly the compressed data we started with (also stored in
+ * the chunk).  Return 0 on success.
+ */
+int TryReconstruction(ImageChunk* chunk, unsigned char* out) {
+  size_t p = 0;
+
+#if 0
+  printf("trying %d %d %d %d %d\n",
+          chunk->level, chunk->method, chunk->windowBits,
+          chunk->memLevel, chunk->strategy);
+#endif
+
+  z_stream strm;
+  strm.zalloc = Z_NULL;
+  strm.zfree = Z_NULL;
+  strm.opaque = Z_NULL;
+  strm.avail_in = chunk->len;
+  strm.next_in = chunk->data;
+  int ret;
+  ret = deflateInit2(&strm, chunk->level, chunk->method, chunk->windowBits,
+                     chunk->memLevel, chunk->strategy);
+  do {
+    strm.avail_out = BUFFER_SIZE;
+    strm.next_out = out;
+    ret = deflate(&strm, Z_FINISH);
+    size_t have = BUFFER_SIZE - strm.avail_out;
+
+    if (memcmp(out, chunk->deflate_data+p, have) != 0) {
+      // mismatch; data isn't the same.
+      deflateEnd(&strm);
+      return -1;
+    }
+    p += have;
+  } while (ret != Z_STREAM_END);
+  deflateEnd(&strm);
+  if (p != chunk->deflate_len) {
+    // mismatch; ran out of data before we should have.
+    return -1;
+  }
+  return 0;
+}
+
+/*
+ * Verify that we can reproduce exactly the same compressed data that
+ * we started with.  Sets the level, method, windowBits, memLevel, and
+ * strategy fields in the chunk to the encoding parameters needed to
+ * produce the right output.  Returns 0 on success.
+ */
+int ReconstructDeflateChunk(ImageChunk* chunk) {
+  if (chunk->type != CHUNK_DEFLATE) {
+    printf("attempt to reconstruct non-deflate chunk\n");
+    return -1;
+  }
+
+  size_t p = 0;
+  unsigned char* out = malloc(BUFFER_SIZE);
+
+  // We only check two combinations of encoder parameters:  level 6
+  // (the default) and level 9 (the maximum).
+  for (chunk->level = 6; chunk->level <= 9; chunk->level += 3) {
+    chunk->windowBits = -15;  // 32kb window; negative to indicate a raw stream.
+    chunk->memLevel = 8;      // the default value.
+    chunk->method = Z_DEFLATED;
+    chunk->strategy = Z_DEFAULT_STRATEGY;
+
+    if (TryReconstruction(chunk, out) == 0) {
+      free(out);
+      return 0;
+    }
+  }
+
+  free(out);
+  return -1;
+}
+
+/*
+ * Given source and target chunks, compute a bsdiff patch between them
+ * by running bsdiff in a subprocess.  Return the patch data, placing
+ * its length in *size.  Return NULL on failure.  We expect the bsdiff
+ * program to be in the path.
+ */
+unsigned char* MakePatch(ImageChunk* src, ImageChunk* tgt, size_t* size) {
+  if (tgt->type == CHUNK_NORMAL) {
+    if (tgt->len <= 160) {
+      tgt->type = CHUNK_RAW;
+      *size = tgt->len;
+      return tgt->data;
+    }
+  }
+
+  char ptemp[] = "/tmp/imgdiff-patch-XXXXXX";
+  mkstemp(ptemp);
+
+  int r = bsdiff(src->data, src->len, &(src->I), tgt->data, tgt->len, ptemp);
+  if (r != 0) {
+    printf("bsdiff() failed: %d\n", r);
+    return NULL;
+  }
+
+  struct stat st;
+  if (stat(ptemp, &st) != 0) {
+    printf("failed to stat patch file %s: %s\n",
+            ptemp, strerror(errno));
+    return NULL;
+  }
+
+  unsigned char* data = malloc(st.st_size);
+
+  if (tgt->type == CHUNK_NORMAL && tgt->len <= st.st_size) {
+    unlink(ptemp);
+
+    tgt->type = CHUNK_RAW;
+    *size = tgt->len;
+    return tgt->data;
+  }
+
+  *size = st.st_size;
+
+  FILE* f = fopen(ptemp, "rb");
+  if (f == NULL) {
+    printf("failed to open patch %s: %s\n", ptemp, strerror(errno));
+    return NULL;
+  }
+  if (fread(data, 1, st.st_size, f) != st.st_size) {
+    printf("failed to read patch %s: %s\n", ptemp, strerror(errno));
+    return NULL;
+  }
+  fclose(f);
+
+  unlink(ptemp);
+
+  tgt->source_start = src->start;
+  switch (tgt->type) {
+    case CHUNK_NORMAL:
+      tgt->source_len = src->len;
+      break;
+    case CHUNK_DEFLATE:
+      tgt->source_len = src->deflate_len;
+      tgt->source_uncompressed_len = src->len;
+      break;
+  }
+
+  return data;
+}
+
+/*
+ * Cause a gzip chunk to be treated as a normal chunk (ie, as a blob
+ * of uninterpreted data).  The resulting patch will likely be about
+ * as big as the target file, but it lets us handle the case of images
+ * where some gzip chunks are reconstructible but others aren't (by
+ * treating the ones that aren't as normal chunks).
+ */
+void ChangeDeflateChunkToNormal(ImageChunk* ch) {
+  if (ch->type != CHUNK_DEFLATE) return;
+  ch->type = CHUNK_NORMAL;
+  free(ch->data);
+  ch->data = ch->deflate_data;
+  ch->len = ch->deflate_len;
+}
+
+/*
+ * Return true if the data in the chunk is identical (including the
+ * compressed representation, for gzip chunks).
+ */
+int AreChunksEqual(ImageChunk* a, ImageChunk* b) {
+    if (a->type != b->type) return 0;
+
+    switch (a->type) {
+        case CHUNK_NORMAL:
+            return a->len == b->len && memcmp(a->data, b->data, a->len) == 0;
+
+        case CHUNK_DEFLATE:
+            return a->deflate_len == b->deflate_len &&
+                memcmp(a->deflate_data, b->deflate_data, a->deflate_len) == 0;
+
+        default:
+            printf("unknown chunk type %d\n", a->type);
+            return 0;
+    }
+}
+
+/*
+ * Look for runs of adjacent normal chunks and compress them down into
+ * a single chunk.  (Such runs can be produced when deflate chunks are
+ * changed to normal chunks.)
+ */
+void MergeAdjacentNormalChunks(ImageChunk* chunks, int* num_chunks) {
+  int out = 0;
+  int in_start = 0, in_end;
+  while (in_start < *num_chunks) {
+    if (chunks[in_start].type != CHUNK_NORMAL) {
+      in_end = in_start+1;
+    } else {
+      // in_start is a normal chunk.  Look for a run of normal chunks
+      // that constitute a solid block of data (ie, each chunk begins
+      // where the previous one ended).
+      for (in_end = in_start+1;
+           in_end < *num_chunks && chunks[in_end].type == CHUNK_NORMAL &&
+             (chunks[in_end].start ==
+              chunks[in_end-1].start + chunks[in_end-1].len &&
+              chunks[in_end].data ==
+              chunks[in_end-1].data + chunks[in_end-1].len);
+           ++in_end);
+    }
+
+    if (in_end == in_start+1) {
+#if 0
+      printf("chunk %d is now %d\n", in_start, out);
+#endif
+      if (out != in_start) {
+        memcpy(chunks+out, chunks+in_start, sizeof(ImageChunk));
+      }
+    } else {
+#if 0
+      printf("collapse normal chunks %d-%d into %d\n", in_start, in_end-1, out);
+#endif
+
+      // Merge chunks [in_start, in_end-1] into one chunk.  Since the
+      // data member of each chunk is just a pointer into an in-memory
+      // copy of the file, this can be done without recopying (the
+      // output chunk has the first chunk's start location and data
+      // pointer, and length equal to the sum of the input chunk
+      // lengths).
+      chunks[out].type = CHUNK_NORMAL;
+      chunks[out].start = chunks[in_start].start;
+      chunks[out].data = chunks[in_start].data;
+      chunks[out].len = chunks[in_end-1].len +
+        (chunks[in_end-1].start - chunks[in_start].start);
+    }
+
+    ++out;
+    in_start = in_end;
+  }
+  *num_chunks = out;
+}
+
+ImageChunk* FindChunkByName(const char* name,
+                            ImageChunk* chunks, int num_chunks) {
+  int i;
+  for (i = 0; i < num_chunks; ++i) {
+    if (chunks[i].type == CHUNK_DEFLATE && chunks[i].filename &&
+        strcmp(name, chunks[i].filename) == 0) {
+      return chunks+i;
+    }
+  }
+  return NULL;
+}
+
+void DumpChunks(ImageChunk* chunks, int num_chunks) {
+    int i;
+    for (i = 0; i < num_chunks; ++i) {
+        printf("chunk %d: type %d start %d len %d\n",
+               i, chunks[i].type, chunks[i].start, chunks[i].len);
+    }
+}
+
+int main(int argc, char** argv) {
+  if (argc != 4 && argc != 5) {
+    usage:
+    printf("usage: %s [-z] <src-img> <tgt-img> <patch-file>\n",
+            argv[0]);
+    return 2;
+  }
+
+  int zip_mode = 0;
+
+  if (strcmp(argv[1], "-z") == 0) {
+    zip_mode = 1;
+    --argc;
+    ++argv;
+  }
+
+
+  int num_src_chunks;
+  ImageChunk* src_chunks;
+  int num_tgt_chunks;
+  ImageChunk* tgt_chunks;
+  int i;
+
+  if (zip_mode) {
+    if (ReadZip(argv[1], &num_src_chunks, &src_chunks, 1) == NULL) {
+      printf("failed to break apart source zip file\n");
+      return 1;
+    }
+    if (ReadZip(argv[2], &num_tgt_chunks, &tgt_chunks, 0) == NULL) {
+      printf("failed to break apart target zip file\n");
+      return 1;
+    }
+  } else {
+    if (ReadImage(argv[1], &num_src_chunks, &src_chunks) == NULL) {
+      printf("failed to break apart source image\n");
+      return 1;
+    }
+    if (ReadImage(argv[2], &num_tgt_chunks, &tgt_chunks) == NULL) {
+      printf("failed to break apart target image\n");
+      return 1;
+    }
+
+    // Verify that the source and target images have the same chunk
+    // structure (ie, the same sequence of deflate and normal chunks).
+
+    if (!zip_mode) {
+        // Merge the gzip header and footer in with any adjacent
+        // normal chunks.
+        MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks);
+        MergeAdjacentNormalChunks(src_chunks, &num_src_chunks);
+    }
+
+    if (num_src_chunks != num_tgt_chunks) {
+      printf("source and target don't have same number of chunks!\n");
+      printf("source chunks:\n");
+      DumpChunks(src_chunks, num_src_chunks);
+      printf("target chunks:\n");
+      DumpChunks(tgt_chunks, num_tgt_chunks);
+      return 1;
+    }
+    for (i = 0; i < num_src_chunks; ++i) {
+      if (src_chunks[i].type != tgt_chunks[i].type) {
+        printf("source and target don't have same chunk "
+                "structure! (chunk %d)\n", i);
+        printf("source chunks:\n");
+        DumpChunks(src_chunks, num_src_chunks);
+        printf("target chunks:\n");
+        DumpChunks(tgt_chunks, num_tgt_chunks);
+        return 1;
+      }
+    }
+  }
+
+  for (i = 0; i < num_tgt_chunks; ++i) {
+    if (tgt_chunks[i].type == CHUNK_DEFLATE) {
+      // Confirm that given the uncompressed chunk data in the target, we
+      // can recompress it and get exactly the same bits as are in the
+      // input target image.  If this fails, treat the chunk as a normal
+      // non-deflated chunk.
+      if (ReconstructDeflateChunk(tgt_chunks+i) < 0) {
+        printf("failed to reconstruct target deflate chunk %d [%s]; "
+               "treating as normal\n", i, tgt_chunks[i].filename);
+        ChangeDeflateChunkToNormal(tgt_chunks+i);
+        if (zip_mode) {
+          ImageChunk* src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks);
+          if (src) {
+            ChangeDeflateChunkToNormal(src);
+          }
+        } else {
+          ChangeDeflateChunkToNormal(src_chunks+i);
+        }
+        continue;
+      }
+
+      // If two deflate chunks are identical (eg, the kernel has not
+      // changed between two builds), treat them as normal chunks.
+      // This makes applypatch much faster -- it can apply a trivial
+      // patch to the compressed data, rather than uncompressing and
+      // recompressing to apply the trivial patch to the uncompressed
+      // data.
+      ImageChunk* src;
+      if (zip_mode) {
+        src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks);
+      } else {
+        src = src_chunks+i;
+      }
+
+      if (src == NULL || AreChunksEqual(tgt_chunks+i, src)) {
+        ChangeDeflateChunkToNormal(tgt_chunks+i);
+        if (src) {
+          ChangeDeflateChunkToNormal(src);
+        }
+      }
+    }
+  }
+
+  // Merging neighboring normal chunks.
+  if (zip_mode) {
+    // For zips, we only need to do this to the target:  deflated
+    // chunks are matched via filename, and normal chunks are patched
+    // using the entire source file as the source.
+    MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks);
+  } else {
+    // For images, we need to maintain the parallel structure of the
+    // chunk lists, so do the merging in both the source and target
+    // lists.
+    MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks);
+    MergeAdjacentNormalChunks(src_chunks, &num_src_chunks);
+    if (num_src_chunks != num_tgt_chunks) {
+      // This shouldn't happen.
+      printf("merging normal chunks went awry\n");
+      return 1;
+    }
+  }
+
+  // Compute bsdiff patches for each chunk's data (the uncompressed
+  // data, in the case of deflate chunks).
+
+  printf("Construct patches for %d chunks...\n", num_tgt_chunks);
+  unsigned char** patch_data = malloc(num_tgt_chunks * sizeof(unsigned char*));
+  size_t* patch_size = malloc(num_tgt_chunks * sizeof(size_t));
+  for (i = 0; i < num_tgt_chunks; ++i) {
+    if (zip_mode) {
+      ImageChunk* src;
+      if (tgt_chunks[i].type == CHUNK_DEFLATE &&
+          (src = FindChunkByName(tgt_chunks[i].filename, src_chunks,
+                                 num_src_chunks))) {
+        patch_data[i] = MakePatch(src, tgt_chunks+i, patch_size+i);
+      } else {
+        patch_data[i] = MakePatch(src_chunks, tgt_chunks+i, patch_size+i);
+      }
+    } else {
+      patch_data[i] = MakePatch(src_chunks+i, tgt_chunks+i, patch_size+i);
+    }
+    printf("patch %3d is %d bytes (of %d)\n",
+           i, patch_size[i], tgt_chunks[i].source_len);
+  }
+
+  // Figure out how big the imgdiff file header is going to be, so
+  // that we can correctly compute the offset of each bsdiff patch
+  // within the file.
+
+  size_t total_header_size = 12;
+  for (i = 0; i < num_tgt_chunks; ++i) {
+    total_header_size += 4;
+    switch (tgt_chunks[i].type) {
+      case CHUNK_NORMAL:
+        total_header_size += 8*3;
+        break;
+      case CHUNK_DEFLATE:
+        total_header_size += 8*5 + 4*5;
+        break;
+      case CHUNK_RAW:
+        total_header_size += 4 + patch_size[i];
+        break;
+    }
+  }
+
+  size_t offset = total_header_size;
+
+  FILE* f = fopen(argv[3], "wb");
+
+  // Write out the headers.
+
+  fwrite("IMGDIFF2", 1, 8, f);
+  Write4(num_tgt_chunks, f);
+  for (i = 0; i < num_tgt_chunks; ++i) {
+    Write4(tgt_chunks[i].type, f);
+
+    switch (tgt_chunks[i].type) {
+      case CHUNK_NORMAL:
+        printf("chunk %3d: normal   (%10d, %10d)  %10d\n", i,
+               tgt_chunks[i].start, tgt_chunks[i].len, patch_size[i]);
+        Write8(tgt_chunks[i].source_start, f);
+        Write8(tgt_chunks[i].source_len, f);
+        Write8(offset, f);
+        offset += patch_size[i];
+        break;
+
+      case CHUNK_DEFLATE:
+        printf("chunk %3d: deflate  (%10d, %10d)  %10d  %s\n", i,
+               tgt_chunks[i].start, tgt_chunks[i].deflate_len, patch_size[i],
+               tgt_chunks[i].filename);
+        Write8(tgt_chunks[i].source_start, f);
+        Write8(tgt_chunks[i].source_len, f);
+        Write8(offset, f);
+        Write8(tgt_chunks[i].source_uncompressed_len, f);
+        Write8(tgt_chunks[i].len, f);
+        Write4(tgt_chunks[i].level, f);
+        Write4(tgt_chunks[i].method, f);
+        Write4(tgt_chunks[i].windowBits, f);
+        Write4(tgt_chunks[i].memLevel, f);
+        Write4(tgt_chunks[i].strategy, f);
+        offset += patch_size[i];
+        break;
+
+      case CHUNK_RAW:
+        printf("chunk %3d: raw      (%10d, %10d)\n", i,
+               tgt_chunks[i].start, tgt_chunks[i].len);
+        Write4(patch_size[i], f);
+        fwrite(patch_data[i], 1, patch_size[i], f);
+        break;
+    }
+  }
+
+  // Append each chunk's bsdiff patch, in order.
+
+  for (i = 0; i < num_tgt_chunks; ++i) {
+    if (tgt_chunks[i].type != CHUNK_RAW) {
+      fwrite(patch_data[i], 1, patch_size[i], f);
+    }
+  }
+
+  fclose(f);
+
+  return 0;
+}
diff --git a/applypatch/imgdiff.h b/applypatch/imgdiff.h
new file mode 100644
index 0000000..f2069b4
--- /dev/null
+++ b/applypatch/imgdiff.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Image patch chunk types
+#define CHUNK_NORMAL   0
+#define CHUNK_GZIP     1   // version 1 only
+#define CHUNK_DEFLATE  2   // version 2 only
+#define CHUNK_RAW      3   // version 2 only
+
+// The gzip header size is actually variable, but we currently don't
+// support gzipped data with any of the optional fields, so for now it
+// will always be ten bytes.  See RFC 1952 for the definition of the
+// gzip format.
+#define GZIP_HEADER_LEN   10
+
+// The gzip footer size really is fixed.
+#define GZIP_FOOTER_LEN   8
diff --git a/applypatch/imgdiff_test.sh b/applypatch/imgdiff_test.sh
new file mode 100755
index 0000000..dcdb922
--- /dev/null
+++ b/applypatch/imgdiff_test.sh
@@ -0,0 +1,118 @@
+#!/bin/bash
+#
+# A script for testing imgdiff/applypatch.  It takes two full OTA
+# packages as arguments.  It generates (on the host) patches for all
+# the zip/jar/apk files they have in common, as well as boot and
+# recovery images.  It then applies the patches on the device (or
+# emulator) and checks that the resulting file is correct.
+
+EMULATOR_PORT=5580
+
+# set to 0 to use a device instead
+USE_EMULATOR=0
+
+# where on the device to do all the patching.
+WORK_DIR=/data/local/tmp
+
+START_OTA_PACKAGE=$1
+END_OTA_PACKAGE=$2
+
+# ------------------------
+
+tmpdir=$(mktemp -d)
+
+if [ "$USE_EMULATOR" == 1 ]; then
+  emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT &
+  pid_emulator=$!
+  ADB="adb -s emulator-$EMULATOR_PORT "
+else
+  ADB="adb -d "
+fi
+
+echo "waiting to connect to device"
+$ADB wait-for-device
+
+# run a command on the device; exit with the exit status of the device
+# command.
+run_command() {
+  $ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}'
+}
+
+testname() {
+  echo
+  echo "$1"...
+  testname="$1"
+}
+
+fail() {
+  echo
+  echo FAIL: $testname
+  echo
+  [ "$open_pid" == "" ] || kill $open_pid
+  [ "$pid_emulator" == "" ] || kill $pid_emulator
+  exit 1
+}
+
+sha1() {
+  sha1sum $1 | awk '{print $1}'
+}
+
+size() {
+  stat -c %s $1 | tr -d '\n'
+}
+
+cleanup() {
+  # not necessary if we're about to kill the emulator, but nice for
+  # running on real devices or already-running emulators.
+  testname "removing test files"
+  run_command rm $WORK_DIR/applypatch
+  run_command rm $WORK_DIR/source
+  run_command rm $WORK_DIR/target
+  run_command rm $WORK_DIR/patch
+
+  [ "$pid_emulator" == "" ] || kill $pid_emulator
+
+  rm -rf $tmpdir
+}
+
+$ADB push $ANDROID_PRODUCT_OUT/system/bin/applypatch $WORK_DIR/applypatch
+
+patch_and_apply() {
+  local fn=$1
+  shift
+
+  unzip -p $START_OTA_PACKAGE $fn > $tmpdir/source
+  unzip -p $END_OTA_PACKAGE $fn > $tmpdir/target
+  imgdiff "$@" $tmpdir/source $tmpdir/target $tmpdir/patch
+  bsdiff $tmpdir/source $tmpdir/target $tmpdir/patch.bs
+  echo "patch for $fn is $(size $tmpdir/patch) [of $(size $tmpdir/target)] ($(size $tmpdir/patch.bs) with bsdiff)"
+  echo "$fn $(size $tmpdir/patch) of $(size $tmpdir/target) bsdiff $(size $tmpdir/patch.bs)" >> /tmp/stats.txt
+  $ADB push $tmpdir/source $WORK_DIR/source || fail "source push failed"
+  run_command rm /data/local/tmp/target
+  $ADB push $tmpdir/patch $WORK_DIR/patch || fail "patch push failed"
+  run_command /data/local/tmp/applypatch /data/local/tmp/source \
+    /data/local/tmp/target $(sha1 $tmpdir/target) $(size $tmpdir/target) \
+    $(sha1 $tmpdir/source):/data/local/tmp/patch \
+    || fail "applypatch of $fn failed"
+  $ADB pull /data/local/tmp/target $tmpdir/result
+  diff -q $tmpdir/target $tmpdir/result || fail "patch output not correct!"
+}
+
+# --------------- basic execution ----------------------
+
+for i in $((zipinfo -1 $START_OTA_PACKAGE; zipinfo -1 $END_OTA_PACKAGE) | \
+           sort | uniq -d | egrep -e '[.](apk|jar|zip)$'); do
+  patch_and_apply $i -z
+done
+patch_and_apply boot.img
+patch_and_apply system/recovery.img
+
+
+# --------------- cleanup ----------------------
+
+cleanup
+
+echo
+echo PASS
+echo
+
diff --git a/applypatch/imgpatch.c b/applypatch/imgpatch.c
new file mode 100644
index 0000000..e3ee80a
--- /dev/null
+++ b/applypatch/imgpatch.c
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// See imgdiff.c in this directory for a description of the patch file
+// format.
+
+#include <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;
+}
diff --git a/applypatch/main.c b/applypatch/main.c
new file mode 100644
index 0000000..3917f86
--- /dev/null
+++ b/applypatch/main.c
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <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;
+}
diff --git a/applypatch/testdata/new.file b/applypatch/testdata/new.file
new file mode 100644
index 0000000..cdeb8fd
--- /dev/null
+++ b/applypatch/testdata/new.file
Binary files differ
diff --git a/applypatch/testdata/old.file b/applypatch/testdata/old.file
new file mode 100644
index 0000000..166c873
--- /dev/null
+++ b/applypatch/testdata/old.file
Binary files differ
diff --git a/applypatch/testdata/patch.bsdiff b/applypatch/testdata/patch.bsdiff
new file mode 100644
index 0000000..b78d385
--- /dev/null
+++ b/applypatch/testdata/patch.bsdiff
Binary files differ
diff --git a/applypatch/utils.c b/applypatch/utils.c
new file mode 100644
index 0000000..41ff676
--- /dev/null
+++ b/applypatch/utils.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <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]);
+}
diff --git a/applypatch/utils.h b/applypatch/utils.h
new file mode 100644
index 0000000..bc97f17
--- /dev/null
+++ b/applypatch/utils.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _BUILD_TOOLS_APPLYPATCH_UTILS_H
+#define _BUILD_TOOLS_APPLYPATCH_UTILS_H
+
+#include <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
diff --git a/bmlutils/Android.mk b/bmlutils/Android.mk
new file mode 100644
index 0000000..e95cb5f
--- /dev/null
+++ b/bmlutils/Android.mk
@@ -0,0 +1,8 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_CFLAGS += -DBOARD_BOOT_DEVICE=\"$(BOARD_BOOT_DEVICE)\"
+LOCAL_SRC_FILES := bmlutils.c
+LOCAL_MODULE := libbmlutils
+LOCAL_MODULE_TAGS := eng
+include $(BUILD_STATIC_LIBRARY)
diff --git a/bmlutils/bmlutils.c b/bmlutils/bmlutils.c
new file mode 100644
index 0000000..9f8ee2f
--- /dev/null
+++ b/bmlutils/bmlutils.c
@@ -0,0 +1,154 @@
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <linux/input.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/reboot.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/wait.h>
+#include <sys/limits.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include <signal.h>
+#include <sys/wait.h>
+
+extern int __system(const char *command);
+#define BML_UNLOCK_ALL				0x8A29		///< unlock all partition RO -> RW
+
+
+static int restore_internal(const char* bml, const char* filename)
+{
+    char buf[4096];
+    int dstfd, srcfd, bytes_read, bytes_written, total_read = 0;
+    if (filename == NULL)
+        srcfd = 0;
+    else {
+        srcfd = open(filename, O_RDONLY | O_LARGEFILE);
+        if (srcfd < 0)
+            return 2;
+    }
+    dstfd = open(bml, O_RDWR | O_LARGEFILE);
+    if (dstfd < 0)
+        return 3;
+    if (ioctl(dstfd, BML_UNLOCK_ALL, 0))
+        return 4;
+    do {
+        total_read += bytes_read = read(srcfd, buf, 4096);
+        if (!bytes_read)
+            break;
+        if (bytes_read < 4096)
+            memset(&buf[bytes_read], 0, 4096 - bytes_read);
+        if (write(dstfd, buf, 4096) < 4096)
+            return 5;
+    } while(bytes_read == 4096);
+    
+    close(dstfd);
+    close(srcfd);
+    
+    return 0;
+}
+
+int cmd_bml_restore_raw_partition(const char *partition, const char *filename)
+{
+    if (strcmp(partition, "boot") != 0 && strcmp(partition, "recovery") != 0)
+        return 6;
+
+    // always restore boot, regardless of whether recovery or boot is flashed.
+    // this is because boot and recovery are the same on some samsung phones.
+    int ret = restore_internal("/dev/block/bml7", filename);
+    if (ret != 0)
+        return ret;
+
+    if (strcmp(partition, "recovery") == 0)
+        ret = restore_internal("/dev/block/bml8", filename);
+    return ret;
+}
+
+int cmd_bml_backup_raw_partition(const char *partition, const char *out_file)
+{
+    char* bml;
+    if (strcmp("boot", partition) == 0)
+        bml = "/dev/block/bml7";
+    else if (strcmp("recovery", partition) == 0)
+        bml = "/dev/block/bml8";
+    else {
+        printf("Invalid partition.\n");
+        return -1;
+    }
+
+    int ch;
+    FILE *in;
+    FILE *out;
+    int val = 0;
+    char buf[512];
+    unsigned sz = 0;
+    unsigned i;
+    int ret = -1;
+    char *in_file = bml;
+
+    in  = fopen ( in_file,  "r" );
+    if (in == NULL)
+        goto ERROR3;
+
+    out = fopen ( out_file,  "w" );
+    if (out == NULL)
+        goto ERROR2;
+
+    fseek(in, 0L, SEEK_END);
+    sz = ftell(in);
+    fseek(in, 0L, SEEK_SET);
+
+    if (sz % 512)
+    {
+        while ( ( ch = fgetc ( in ) ) != EOF )
+            fputc ( ch, out );
+    }
+    else
+    {
+        for (i=0; i< (sz/512); i++)
+        {
+            if ((fread(buf, 512, 1, in)) != 1)
+                goto ERROR1;
+            if ((fwrite(buf, 512, 1, out)) != 1)
+                goto ERROR1;
+        }
+    }
+
+    fsync(out);
+    ret = 0;
+ERROR1:
+    fclose ( out );
+ERROR2:
+    fclose ( in );
+ERROR3:
+    return ret;
+}
+
+int cmd_bml_erase_raw_partition(const char *partition)
+{
+    // TODO: implement raw wipe
+    return 0;
+}
+
+int cmd_bml_erase_partition(const char *partition, const char *filesystem)
+{
+    return -1;
+}
+
+int cmd_bml_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only)
+{
+    return -1;
+}
+
+int cmd_bml_get_partition_device(const char *partition, char *device)
+{
+    return -1;
+}
diff --git a/bootloader.c b/bootloader.c
new file mode 100644
index 0000000..f3dc268
--- /dev/null
+++ b/bootloader.c
@@ -0,0 +1,183 @@
+/*
+ * 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 "mtdutils/mtdutils.h"
+#include "roots.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+static int get_bootloader_message_mtd(struct bootloader_message *out, const Volume* v);
+static int set_bootloader_message_mtd(const struct bootloader_message *in, const Volume* v);
+static int get_bootloader_message_block(struct bootloader_message *out, const Volume* v);
+static int set_bootloader_message_block(const struct bootloader_message *in, const Volume* v);
+
+int get_bootloader_message(struct bootloader_message *out) {
+    Volume* v = volume_for_path("/misc");
+    if(v)
+    {
+        if (strcmp(v->fs_type, "mtd") == 0) {
+                return get_bootloader_message_mtd(out, v);
+        } else if (strcmp(v->fs_type, "emmc") == 0) {
+                return get_bootloader_message_block(out, v);
+        }
+        LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type);
+        return -1;
+    }
+    LOGE("no misc partition\n");
+    return -1;
+}
+
+int set_bootloader_message(const struct bootloader_message *in) {
+    Volume* v = volume_for_path("/misc");
+    if(v)
+    {
+        if (strcmp(v->fs_type, "mtd") == 0) {
+            return set_bootloader_message_mtd(in, v);
+        } else if (strcmp(v->fs_type, "emmc") == 0) {
+            return set_bootloader_message_block(in, v);
+        }
+        LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type);
+        return -1;
+    }
+    LOGE("no misc partition\n");
+    return -1;
+}
+
+// ------------------------------
+// for misc partitions on MTD
+// ------------------------------
+
+static const int MISC_PAGES = 3;         // number of pages to save
+static const int MISC_COMMAND_PAGE = 1;  // bootloader command is this page
+
+static int get_bootloader_message_mtd(struct bootloader_message *out,
+                                      const Volume* v) {
+    size_t write_size;
+    mtd_scan_partitions();
+    const MtdPartition *part = mtd_find_partition_by_name(v->device);
+    if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) {
+        LOGE("Can't find %s\n", v->device);
+        return -1;
+    }
+
+    MtdReadContext *read = mtd_read_partition(part);
+    if (read == NULL) {
+        LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
+        return -1;
+    }
+
+    const ssize_t size = write_size * MISC_PAGES;
+    char data[size];
+    ssize_t r = mtd_read_data(read, data, size);
+    if (r != size) LOGE("Can't read %s\n(%s)\n", v->device, strerror(errno));
+    mtd_read_close(read);
+    if (r != size) return -1;
+
+    memcpy(out, &data[write_size * MISC_COMMAND_PAGE], sizeof(*out));
+    return 0;
+}
+static int set_bootloader_message_mtd(const struct bootloader_message *in,
+                                      const Volume* v) {
+    size_t write_size;
+    mtd_scan_partitions();
+    const MtdPartition *part = mtd_find_partition_by_name(v->device);
+    if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) {
+        LOGE("Can't find %s\n", v->device);
+        return -1;
+    }
+
+    MtdReadContext *read = mtd_read_partition(part);
+    if (read == NULL) {
+        LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
+        return -1;
+    }
+
+    ssize_t size = write_size * MISC_PAGES;
+    char data[size];
+    ssize_t r = mtd_read_data(read, data, size);
+    if (r != size) LOGE("Can't read %s\n(%s)\n", v->device, strerror(errno));
+    mtd_read_close(read);
+    if (r != size) return -1;
+
+    memcpy(&data[write_size * MISC_COMMAND_PAGE], in, sizeof(*in));
+
+    MtdWriteContext *write = mtd_write_partition(part);
+    if (write == NULL) {
+        LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
+        return -1;
+    }
+    if (mtd_write_data(write, data, size) != size) {
+        LOGE("Can't write %s\n(%s)\n", v->device, strerror(errno));
+        mtd_write_close(write);
+        return -1;
+    }
+    if (mtd_write_close(write)) {
+        LOGE("Can't finish %s\n(%s)\n", v->device, strerror(errno));
+        return -1;
+    }
+
+    LOGI("Set boot command \"%s\"\n", in->command[0] != 255 ? in->command : "");
+    return 0;
+}
+
+
+// ------------------------------------
+// for misc partitions on block devices
+// ------------------------------------
+
+static int get_bootloader_message_block(struct bootloader_message *out,
+                                        const Volume* v) {
+    FILE* f = fopen(v->device, "rb");
+    if (f == NULL) {
+        LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
+        return -1;
+    }
+    struct bootloader_message temp;
+    int count = fread(&temp, sizeof(temp), 1, f);
+    if (count != 1) {
+        LOGE("Failed reading %s\n(%s)\n", v->device, strerror(errno));
+        return -1;
+    }
+    if (fclose(f) != 0) {
+        LOGE("Failed closing %s\n(%s)\n", v->device, strerror(errno));
+        return -1;
+    }
+    memcpy(out, &temp, sizeof(temp));
+    return 0;
+}
+
+static int set_bootloader_message_block(const struct bootloader_message *in,
+                                        const Volume* v) {
+    FILE* f = fopen(v->device, "wb");
+    if (f == NULL) {
+        LOGE("Can't open %s\n(%s)\n", v->device, strerror(errno));
+        return -1;
+    }
+    int count = fwrite(in, sizeof(*in), 1, f);
+    if (count != 1) {
+        LOGE("Failed writing %s\n(%s)\n", v->device, strerror(errno));
+        return -1;
+    }
+    if (fclose(f) != 0) {
+        LOGE("Failed closing %s\n(%s)\n", v->device, strerror(errno));
+        return -1;
+    }
+    return 0;
+}
diff --git a/bootloader.h b/bootloader.h
new file mode 100644
index 0000000..3d4302f
--- /dev/null
+++ b/bootloader.h
@@ -0,0 +1,59 @@
+/*
+ * 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_BOOTLOADER_H
+#define _RECOVERY_BOOTLOADER_H
+
+/* Bootloader Message
+ *
+ * This structure describes the content of a block in flash
+ * that is used for recovery and the bootloader to talk to
+ * each other.
+ *
+ * The command field is updated by linux when it wants to
+ * reboot into recovery or to update radio or bootloader firmware.
+ * It is also updated by the bootloader when firmware update
+ * is complete (to boot into recovery for any final cleanup)
+ *
+ * The status field is written by the bootloader after the
+ * completion of an "update-radio" or "update-hboot" command.
+ *
+ * The recovery field is only written by linux and used
+ * for the system to send a message to recovery or the
+ * other way around.
+ */
+struct bootloader_message {
+    char command[32];
+    char status[32];
+    char recovery[1024];
+};
+
+/* Read and write the bootloader command from the "misc" partition.
+ * These return zero on success.
+ */
+int get_bootloader_message(struct bootloader_message *out);
+int set_bootloader_message(const struct bootloader_message *in);
+
+/* Write an update to the cache partition for update-radio or update-hboot.
+ * Note, this destroys any filesystem on the cache partition!
+ * The expected bitmap format is 240x320, 16bpp (2Bpp), RGB 5:6:5.
+ */
+int write_update_for_bootloader(
+        const char *update, int update_len,
+        int bitmap_width, int bitmap_height, int bitmap_bpp,
+        const char *busy_bitmap, const char *error_bitmap);
+
+#endif
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..a4acc39
--- /dev/null
+++ b/common.h
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+
+#ifndef RECOVERY_COMMON_H
+#define RECOVERY_COMMON_H
+
+#include <stdio.h>
+
+// Initialize the graphics system.
+void ui_init();
+
+// Use KEY_* codes from <linux/input.h> or KEY_DREAM_* from "minui/minui.h".
+int ui_wait_key();            // waits for a key/button press, returns the code
+int ui_key_pressed(int key);  // returns >0 if the code is currently pressed
+int ui_text_visible();        // returns >0 if text log is currently visible
+void ui_show_text(int visible);
+void ui_clear_key_queue();
+
+// Write a message to the on-screen log shown with Alt-L (also to stderr).
+// The screen is small, and users may need to report these messages to support,
+// so keep the output short and not too cryptic.
+void ui_print(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
+
+void ui_reset_text_col();
+void ui_set_show_text(int value);
+
+// Display some header text followed by a menu of items, which appears
+// at the top of the screen (in place of any scrolling ui_print()
+// output, if necessary).
+int ui_start_menu(char** headers, char** items, int initial_selection);
+// Set the menu highlight to the given index, and return it (capped to
+// the range [0..numitems).
+int ui_menu_select(int sel);
+// End menu mode, resetting the text overlay so that ui_print()
+// statements will be displayed.
+void ui_end_menu();
+
+int ui_get_showing_back_button();
+void ui_set_showing_back_button(int showBackButton);
+
+// Set the icon (normally the only thing visible besides the progress bar).
+enum {
+  BACKGROUND_ICON_NONE,
+  BACKGROUND_ICON_INSTALLING,
+  BACKGROUND_ICON_ERROR,
+  BACKGROUND_ICON_CLOCKWORK,
+  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)
+void ui_show_progress(float portion, int seconds);
+void ui_set_progress(float fraction);  // 0.0 - 1.0 within the defined scope
+
+// Default allocation of progress bar segments to operations
+static const int VERIFICATION_PROGRESS_TIME = 60;
+static const float VERIFICATION_PROGRESS_FRACTION = 0.25;
+static const float DEFAULT_FILES_PROGRESS_FRACTION = 0.4;
+static const float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1;
+
+// Show a rotating "barberpole" for ongoing operations.  Updates automatically.
+void ui_show_indeterminate_progress();
+
+// Hide and reset the progress bar.
+void ui_reset_progress();
+
+#define LOGE(...) ui_print("E:" __VA_ARGS__)
+#define LOGW(...) fprintf(stdout, "W:" __VA_ARGS__)
+#define LOGI(...) fprintf(stdout, "I:" __VA_ARGS__)
+
+#if 0
+#define LOGV(...) fprintf(stdout, "V:" __VA_ARGS__)
+#define LOGD(...) fprintf(stdout, "D:" __VA_ARGS__)
+#else
+#define LOGV(...) do {} while (0)
+#define LOGD(...) do {} while (0)
+#endif
+
+#define STRINGIFY(x) #x
+#define EXPAND(x) STRINGIFY(x)
+
+typedef struct {
+    const char* mount_point;  // eg. "/cache".  must live in the root directory.
+
+    const char* fs_type;      // "yaffs2" or "ext4" or "vfat"
+
+    const char* device;       // MTD partition name if fs_type == "yaffs"
+                              // block device if fs_type == "ext4" or "vfat"
+
+    const char* device2;      // alternative device to try if fs_type
+                              // == "ext4" or "vfat" and mounting
+                              // 'device' fails
+    const char* fallback_fs_type;
+} Volume;
+
+#endif  // RECOVERY_COMMON_H
diff --git a/default_recovery_ui.c b/default_recovery_ui.c
new file mode 100644
index 0000000..9c192a2
--- /dev/null
+++ b/default_recovery_ui.c
@@ -0,0 +1,100 @@
+/*
+ * 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 <linux/input.h>
+
+#include "recovery_ui.h"
+#include "common.h"
+#include "extendedcommands.h"
+
+char* MENU_HEADERS[] = { NULL };
+
+char* MENU_ITEMS[] = { "reboot system now",
+                       "apply update from sdcard",
+                       "wipe data/factory reset",
+                       "wipe cache partition",
+                       "install zip from sdcard",
+                       "backup and restore",
+                       "mounts and storage",
+                       "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)
+        return 1;
+    // allow toggling of the display if the correct key is pressed, and the display toggle is allowed or the display is currently off
+    if (ui_get_showing_back_button()) {
+        return get_allow_toggle_display() && (key_code == KEY_HOME || key_code == KEY_MENU || key_code == KEY_END);
+    }
+    return get_allow_toggle_display() && (key_code == KEY_HOME || key_code == KEY_MENU || key_code == KEY_POWER || key_code == KEY_END);
+}
+
+int device_reboot_now(volatile char* key_pressed, int key_code) {
+    return 0;
+}
+
+int device_handle_key(int key_code, int visible) {
+    if (visible) {
+        switch (key_code) {
+            case KEY_CAPSLOCK:
+            case KEY_DOWN:
+            case KEY_VOLUMEDOWN:
+                return HIGHLIGHT_DOWN;
+
+            case KEY_LEFTSHIFT:
+            case KEY_UP:
+            case KEY_VOLUMEUP:
+                return HIGHLIGHT_UP;
+
+            case KEY_POWER:
+                if (ui_get_showing_back_button()) {
+                    return SELECT_ITEM;
+                }
+                if (!get_allow_toggle_display())
+                    return GO_BACK;
+                break;
+            case KEY_LEFTBRACE:
+            case KEY_ENTER:
+            case BTN_MOUSE:
+            case KEY_CENTER:
+            case KEY_CAMERA:
+            case KEY_F21:
+            case KEY_SEND:
+                return SELECT_ITEM;
+            
+            case KEY_END:
+            case KEY_BACKSPACE:
+            case KEY_BACK:
+                if (!get_allow_toggle_display())
+                    return GO_BACK;
+        }
+    }
+
+    return NO_ACTION;
+}
+
+int device_perform_action(int which) {
+    return which;
+}
+
+int device_wipe_data() {
+    return 0;
+}
diff --git a/edify/Android.mk b/edify/Android.mk
new file mode 100644
index 0000000..fac0ba7
--- /dev/null
+++ b/edify/Android.mk
@@ -0,0 +1,39 @@
+# Copyright 2009 The Android Open Source Project
+
+LOCAL_PATH := $(call my-dir)
+
+edify_src_files := \
+	lexer.l \
+	parser.y \
+	expr.c
+
+# "-x c" forces the lex/yacc files to be compiled as c;
+# the build system otherwise forces them to be c++.
+edify_cflags := -x c
+
+#
+# Build the host-side command line tool
+#
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+		$(edify_src_files) \
+		main.c
+
+LOCAL_CFLAGS := $(edify_cflags) -g -O0
+LOCAL_MODULE := edify
+LOCAL_YACCFLAGS := -v
+
+include $(BUILD_HOST_EXECUTABLE)
+
+#
+# Build the device-side library
+#
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(edify_src_files)
+
+LOCAL_CFLAGS := $(edify_cflags)
+LOCAL_MODULE := libedify
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/edify/README b/edify/README
new file mode 100644
index 0000000..810455c
--- /dev/null
+++ b/edify/README
@@ -0,0 +1,108 @@
+Update scripts (from donut onwards) are written in a new little
+scripting language ("edify") that is superficially somewhat similar to
+the old one ("amend").  This is a brief overview of the new language.
+
+- The entire script is a single expression.
+
+- All expressions are string-valued.
+
+- String literals appear in double quotes.  \n, \t, \", and \\ are
+  understood, as are hexadecimal escapes like \x4a.
+
+- String literals consisting of only letters, numbers, colons,
+  underscores, slashes, and periods don't need to be in double quotes.
+
+- The following words are reserved:
+
+       if    then    else   endif
+
+  They have special meaning when unquoted.  (In quotes, they are just
+  string literals.)
+
+- When used as a boolean, the empty string is "false" and all other
+  strings are "true".
+
+- All functions are actually macros (in the Lisp sense); the body of
+  the function can control which (if any) of the arguments are
+  evaluated.  This means that functions can act as control
+  structures.
+
+- Operators (like "&&" and "||") are just syntactic sugar for builtin
+  functions, so they can act as control structures as well.
+
+- ";" is a binary operator; evaluating it just means to first evaluate
+  the left side, then the right.  It can also appear after any
+  expression.
+
+- Comments start with "#" and run to the end of the line.
+
+
+
+Some examples:
+
+- There's no distinction between quoted and unquoted strings; the
+  quotes are only needed if you want characters like whitespace to
+  appear in the string.  The following expressions all evaluate to the
+  same string.
+
+     "a b"
+     a + " " + b
+     "a" + " " + "b"
+     "a\x20b"
+     a + "\x20b"
+     concat(a, " ", "b")
+     "concat"(a, " ", "b")
+
+  As shown in the last example, function names are just strings,
+  too.  They must be string *literals*, however.  This is not legal:
+
+     ("con" + "cat")(a, " ", b)         # syntax error!
+
+
+- The ifelse() builtin takes three arguments:  it evaluates exactly
+  one of the second and third, depending on whether the first one is
+  true.  There is also some syntactic sugar to make expressions that
+  look like if/else statements:
+
+     # these are all equivalent
+     ifelse(something(), "yes", "no")
+     if something() then yes else no endif
+     if something() then "yes" else "no" endif
+
+  The else part is optional.
+
+     if something() then "yes" endif    # if something() is false,
+                                        # evaluates to false
+
+     ifelse(condition(), "", abort())   # abort() only called if
+                                        # condition() is false
+
+  The last example is equivalent to:
+
+     assert(condition())
+
+
+- The && and || operators can be used similarly; they evaluate their
+  second argument only if it's needed to determine the truth of the
+  expression.  Their value is the value of the last-evaluated
+  argument:
+
+     file_exists("/data/system/bad") && delete("/data/system/bad")
+
+     file_exists("/data/system/missing") || create("/data/system/missing")
+
+     get_it() || "xxx"     # returns value of get_it() if that value is
+                           # true, otherwise returns "xxx"
+
+
+- The purpose of ";" is to simulate imperative statements, of course,
+  but the operator can be used anywhere.  Its value is the value of
+  its right side:
+
+     concat(a;b;c, d, e;f)     # evaluates to "cdf"
+
+  A more useful example might be something like:
+
+     ifelse(condition(),
+            (first_step(); second_step();),   # second ; is optional
+            alternative_procedure())
diff --git a/edify/expr.c b/edify/expr.c
new file mode 100644
index 0000000..3600075
--- /dev/null
+++ b/edify/expr.c
@@ -0,0 +1,507 @@
+/*
+ * 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 <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+
+#include "expr.h"
+
+// Functions should:
+//
+//    - return a malloc()'d string
+//    - if Evaluate() on any argument returns NULL, return NULL.
+
+int BooleanString(const char* s) {
+    return s[0] != '\0';
+}
+
+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);
+}
+
+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 StringValue(strdup(""));
+    }
+    char** strings = malloc(argc * sizeof(char*));
+    int i;
+    for (i = 0; i < argc; ++i) {
+        strings[i] = NULL;
+    }
+    char* result = NULL;
+    int length = 0;
+    for (i = 0; i < argc; ++i) {
+        strings[i] = Evaluate(state, argv[i]);
+        if (strings[i] == NULL) {
+            goto done;
+        }
+        length += strlen(strings[i]);
+    }
+
+    result = malloc(length+1);
+    int p = 0;
+    for (i = 0; i < argc; ++i) {
+        strcpy(result+p, strings[i]);
+        p += strlen(strings[i]);
+    }
+    result[p] = '\0';
+
+  done:
+    for (i = 0; i < argc; ++i) {
+        free(strings[i]);
+    }
+    free(strings);
+    return StringValue(result);
+}
+
+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");
+        return NULL;
+    }
+    char* cond = Evaluate(state, argv[0]);
+    if (cond == NULL) {
+        return NULL;
+    }
+
+    if (BooleanString(cond) == true) {
+        free(cond);
+        return EvaluateValue(state, argv[1]);
+    } else {
+        if (argc == 3) {
+            free(cond);
+            return EvaluateValue(state, argv[2]);
+        } else {
+            return StringValue(cond);
+        }
+    }
+}
+
+Value* AbortFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* msg = NULL;
+    if (argc > 0) {
+        msg = Evaluate(state, argv[0]);
+    }
+    free(state->errmsg);
+    if (msg) {
+        state->errmsg = msg;
+    } else {
+        state->errmsg = strdup("called abort()");
+    }
+    return NULL;
+}
+
+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]);
+        if (v == NULL) {
+            return NULL;
+        }
+        int b = BooleanString(v);
+        free(v);
+        if (!b) {
+            int prefix_len;
+            int len = argv[i]->end - argv[i]->start;
+            char* err_src = malloc(len + 20);
+            strcpy(err_src, "assert failed: ");
+            prefix_len = strlen(err_src);
+            memcpy(err_src + prefix_len, state->script + argv[i]->start, len);
+            err_src[prefix_len + len] = '\0';
+            free(state->errmsg);
+            state->errmsg = err_src;
+            return NULL;
+        }
+    }
+    return StringValue(strdup(""));
+}
+
+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 StringValue(val);
+}
+
+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]);
+        if (v == NULL) {
+            return NULL;
+        }
+        fputs(v, stdout);
+        free(v);
+    }
+    return StringValue(strdup(""));
+}
+
+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 EvaluateValue(state, argv[1]);
+    } else {
+        return StringValue(left);
+    }
+}
+
+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 EvaluateValue(state, argv[1]);
+    } else {
+        return StringValue(left);
+    }
+}
+
+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);
+    return StringValue(strdup(bv ? "" : "t"));
+}
+
+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]);
+    if (haystack == NULL) {
+        free(needle);
+        return NULL;
+    }
+
+    char* result = strdup(strstr(haystack, needle) ? "t" : "");
+    free(needle);
+    free(haystack);
+    return StringValue(result);
+}
+
+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]);
+    if (right == NULL) {
+        free(left);
+        return NULL;
+    }
+
+    char* result = strdup(strcmp(left, right) == 0 ? "t" : "");
+    free(left);
+    free(right);
+    return StringValue(result);
+}
+
+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]);
+    if (right == NULL) {
+        free(left);
+        return NULL;
+    }
+
+    char* result = strdup(strcmp(left, right) != 0 ? "t" : "");
+    free(left);
+    free(right);
+    return StringValue(result);
+}
+
+Value* SequenceFn(const char* name, State* state, int argc, Expr* argv[]) {
+    Value* left = EvaluateValue(state, argv[0]);
+    if (left == NULL) return NULL;
+    FreeValue(left);
+    return EvaluateValue(state, argv[1]);
+}
+
+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");
+        return NULL;
+    }
+
+    char* left;
+    char* right;
+    if (ReadArgs(state, argv, 2, &left, &right) < 0) return NULL;
+
+    bool result = false;
+    char* end;
+
+    long l_int = strtol(left, &end, 10);
+    if (left[0] == '\0' || *end != '\0') {
+        fprintf(stderr, "[%s] is not an int\n", left);
+        goto done;
+    }
+
+    long r_int = strtol(right, &end, 10);
+    if (right[0] == '\0' || *end != '\0') {
+        fprintf(stderr, "[%s] is not an int\n", right);
+        goto done;
+    }
+
+    result = l_int < r_int;
+
+  done:
+    free(left);
+    free(right);
+    return StringValue(strdup(result ? "t" : ""));
+}
+
+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");
+        return NULL;
+    }
+
+    Expr* temp[2];
+    temp[0] = argv[1];
+    temp[1] = argv[0];
+
+    return LessThanIntFn(name, state, 2, temp);
+}
+
+Value* Literal(const char* name, State* state, int argc, Expr* argv[]) {
+    return StringValue(strdup(name));
+}
+
+Expr* Build(Function fn, YYLTYPE loc, int count, ...) {
+    va_list v;
+    va_start(v, count);
+    Expr* e = malloc(sizeof(Expr));
+    e->fn = fn;
+    e->name = "(operator)";
+    e->argc = count;
+    e->argv = malloc(count * sizeof(Expr*));
+    int i;
+    for (i = 0; i < count; ++i) {
+        e->argv[i] = va_arg(v, Expr*);
+    }
+    va_end(v);
+    e->start = loc.start;
+    e->end = loc.end;
+    return e;
+}
+
+// -----------------------------------------------------------------
+//   the function table
+// -----------------------------------------------------------------
+
+static int fn_entries = 0;
+static int fn_size = 0;
+NamedFunction* fn_table = NULL;
+
+void RegisterFunction(const char* name, Function fn) {
+    if (fn_entries >= fn_size) {
+        fn_size = fn_size*2 + 1;
+        fn_table = realloc(fn_table, fn_size * sizeof(NamedFunction));
+    }
+    fn_table[fn_entries].name = name;
+    fn_table[fn_entries].fn = fn;
+    ++fn_entries;
+}
+
+static int fn_entry_compare(const void* a, const void* b) {
+    const char* na = ((const NamedFunction*)a)->name;
+    const char* nb = ((const NamedFunction*)b)->name;
+    return strcmp(na, nb);
+}
+
+void FinishRegistration() {
+    qsort(fn_table, fn_entries, sizeof(NamedFunction), fn_entry_compare);
+}
+
+Function FindFunction(const char* name) {
+    NamedFunction key;
+    key.name = name;
+    NamedFunction* nf = bsearch(&key, fn_table, fn_entries,
+                                sizeof(NamedFunction), fn_entry_compare);
+    if (nf == NULL) {
+        return NULL;
+    }
+    return nf->fn;
+}
+
+void RegisterBuiltins() {
+    RegisterFunction("ifelse", IfElseFn);
+    RegisterFunction("abort", AbortFn);
+    RegisterFunction("assert", AssertFn);
+    RegisterFunction("concat", ConcatFn);
+    RegisterFunction("is_substring", SubstringFn);
+    RegisterFunction("stdout", StdoutFn);
+    RegisterFunction("sleep", SleepFn);
+
+    RegisterFunction("less_than_int", LessThanIntFn);
+    RegisterFunction("greater_than_int", GreaterThanIntFn);
+}
+
+
+// -----------------------------------------------------------------
+//   convenience methods for functions
+// -----------------------------------------------------------------
+
+// Evaluate the expressions in argv, giving 'count' char* (the ... is
+// zero or more char** to put them in).  If any expression evaluates
+// to NULL, free the rest and return -1.  Return 0 on success.
+int ReadArgs(State* state, Expr* argv[], int count, ...) {
+    char** args = malloc(count * sizeof(char*));
+    va_list v;
+    va_start(v, count);
+    int i;
+    for (i = 0; i < count; ++i) {
+        args[i] = Evaluate(state, argv[i]);
+        if (args[i] == NULL) {
+            va_end(v);
+            int j;
+            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;
+}
+
+// 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[]) {
+    char** args = (char**)malloc(argc * sizeof(char*));
+    int i = 0;
+    for (i = 0; i < argc; ++i) {
+        args[i] = Evaluate(state, argv[i]);
+        if (args[i] == NULL) {
+            int j;
+            for (j = 0; j < i; ++j) {
+                free(args[j]);
+            }
+            free(args);
+            return NULL;
+        }
+    }
+    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.
+Value* ErrorAbort(State* state, char* format, ...) {
+    char* buffer = malloc(4096);
+    va_list v;
+    va_start(v, format);
+    vsnprintf(buffer, 4096, format, v);
+    va_end(v);
+    free(state->errmsg);
+    state->errmsg = buffer;
+    return NULL;
+}
diff --git a/edify/expr.h b/edify/expr.h
new file mode 100644
index 0000000..8e1c638
--- /dev/null
+++ b/edify/expr.h
@@ -0,0 +1,163 @@
+/*
+ * 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 _EXPRESSION_H
+#define _EXPRESSION_H
+
+#include <unistd.h>
+
+#include "yydefs.h"
+
+#define MAX_STRING_LEN 1024
+
+typedef struct Expr Expr;
+
+typedef struct {
+    // Optional pointer to app-specific data; the core of edify never
+    // uses this value.
+    void* cookie;
+
+    // The source of the original script.  Must be NULL-terminated,
+    // and in writable memory (Evaluate may make temporary changes to
+    // it but will restore it when done).
+    char* script;
+
+    // The error message (if any) returned if the evaluation aborts.
+    // Should be NULL initially, will be either NULL or a malloc'd
+    // pointer after Evaluate() returns.
+    char* errmsg;
+} State;
+
+#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;
+    char* name;
+    int argc;
+    Expr** argv;
+    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.
+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.)
+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().
+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
+// NULL from a function).
+void SetError(const char* message);  // makes a copy
+const char* GetError();              // retains ownership
+void ClearError();
+
+
+typedef struct {
+  const char* name;
+  Function fn;
+} NamedFunction;
+
+// Register a new function.  The same Function may be registered under
+// multiple names, but a given name should only be used once.
+void RegisterFunction(const char* name, Function fn);
+
+// Register all the builtins.
+void RegisterBuiltins();
+
+// Call this after all calls to RegisterFunction() but before parsing
+// any scripts to finish building the function table.
+void FinishRegistration();
+
+// Find the Function for a given name; return NULL if no such function
+// exists.
+Function FindFunction(const char* name);
+
+
+// --- convenience functions for use in functions ---
+
+// Evaluate the expressions in argv, giving 'count' char* (the ... is
+// zero or more char** to put them in).  If any expression evaluates
+// 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.
+Value* ErrorAbort(State* state, char* format, ...);
+
+// Wrap a string into a Value, taking ownership of the string.
+Value* StringValue(char* str);
+
+// Free a Value object.
+void FreeValue(Value* v);
+
+#endif  // _EXPRESSION_H
diff --git a/edify/lexer.l b/edify/lexer.l
new file mode 100644
index 0000000..fb2933b
--- /dev/null
+++ b/edify/lexer.l
@@ -0,0 +1,112 @@
+%{
+/*
+ * 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 <string.h>
+
+#include "expr.h"
+#include "yydefs.h"
+#include "parser.h"
+
+int gLine = 1;
+int gColumn = 1;
+int gPos = 0;
+
+// TODO: enforce MAX_STRING_LEN during lexing
+char string_buffer[MAX_STRING_LEN];
+char* string_pos;
+
+#define ADVANCE do {yylloc.start=gPos; yylloc.end=gPos+yyleng; \
+                    gColumn+=yyleng; gPos+=yyleng;} while(0)
+
+%}
+
+%x STR
+
+%option noyywrap
+
+%%
+
+
+\" {
+    BEGIN(STR);
+    string_pos = string_buffer;
+    yylloc.start = gPos;
+    ++gColumn;
+    ++gPos;
+}
+
+<STR>{
+  \" {
+      ++gColumn;
+      ++gPos;
+      BEGIN(INITIAL);
+      *string_pos = '\0';
+      yylval.str = strdup(string_buffer);
+      yylloc.end = gPos;
+      return STRING;
+  }
+
+  \\n   { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\n'; }
+  \\t   { gColumn += yyleng; gPos += yyleng;  *string_pos++ = '\t'; }
+  \\\"  { gColumn += yyleng; gPos += yyleng;  *string_pos++ = '\"'; }
+  \\\\  { gColumn += yyleng; gPos += yyleng;  *string_pos++ = '\\'; }
+
+  \\x[0-9a-fA-F]{2} {
+      gColumn += yyleng;
+      gPos += yyleng;
+      int val;
+      sscanf(yytext+2, "%x", &val);
+      *string_pos++ = val;
+  }
+
+  \n {
+      ++gLine;
+      ++gPos;
+      gColumn = 1;
+      *string_pos++ = yytext[0];
+  }
+
+  . {
+      ++gColumn;
+      ++gPos;
+      *string_pos++ = yytext[0];
+  }
+}
+
+if                ADVANCE; return IF;
+then              ADVANCE; return THEN;
+else              ADVANCE; return ELSE;
+endif             ADVANCE; return ENDIF;
+
+[a-zA-Z0-9_:/.]+ {
+  ADVANCE;
+  yylval.str = strdup(yytext);
+  return STRING;
+}
+
+\&\&              ADVANCE; return AND;
+\|\|              ADVANCE; return OR;
+==                ADVANCE; return EQ;
+!=                ADVANCE; return NE;
+
+[+(),!;]          ADVANCE; return yytext[0];
+
+[ \t]+            ADVANCE;
+
+(#.*)?\n          gPos += yyleng; ++gLine; gColumn = 1;
+
+.                 return BAD;
diff --git a/edify/main.c b/edify/main.c
new file mode 100644
index 0000000..8557043
--- /dev/null
+++ b/edify/main.c
@@ -0,0 +1,218 @@
+/*
+ * 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 "expr.h"
+#include "parser.h"
+
+extern int yyparse(Expr** root, int* error_count);
+
+int expect(const char* expr_str, const char* expected, int* errors) {
+    Expr* e;
+    int error;
+    char* result;
+
+    printf(".");
+
+    yy_scan_string(expr_str);
+    int error_count = 0;
+    error = yyparse(&e, &error_count);
+    if (error > 0 || error_count > 0) {
+        fprintf(stderr, "error parsing \"%s\" (%d errors)\n",
+                expr_str, error_count);
+        ++*errors;
+        return 0;
+    }
+
+    State state;
+    state.cookie = NULL;
+    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;
+        return 0;
+    }
+
+    if (result == NULL && expected == NULL) {
+        return 1;
+    }
+
+    if (strcmp(result, expected) != 0) {
+        fprintf(stderr, "evaluating \"%s\": expected \"%s\", got \"%s\"\n",
+                expr_str, expected, result);
+        ++*errors;
+        free(result);
+        return 0;
+    }
+
+    free(result);
+    return 1;
+}
+
+int test() {
+    int errors = 0;
+
+    expect("a", "a", &errors);
+    expect("\"a\"", "a", &errors);
+    expect("\"\\x61\"", "a", &errors);
+    expect("# this is a comment\n"
+           "  a\n"
+           "   \n",
+           "a", &errors);
+
+
+    // sequence operator
+    expect("a; b; c", "c", &errors);
+
+    // string concat operator
+    expect("a + b", "ab", &errors);
+    expect("a + \n \"b\"", "ab", &errors);
+    expect("a + b +\nc\n", "abc", &errors);
+
+    // string concat function
+    expect("concat(a, b)", "ab", &errors);
+    expect("concat(a,\n \"b\")", "ab", &errors);
+    expect("concat(a + b,\nc,\"d\")", "abcd", &errors);
+    expect("\"concat\"(a + b,\nc,\"d\")", "abcd", &errors);
+
+    // logical and
+    expect("a && b", "b", &errors);
+    expect("a && \"\"", "", &errors);
+    expect("\"\" && b", "", &errors);
+    expect("\"\" && \"\"", "", &errors);
+    expect("\"\" && abort()", "", &errors);   // test short-circuiting
+    expect("t && abort()", NULL, &errors);
+
+    // logical or
+    expect("a || b", "a", &errors);
+    expect("a || \"\"", "a", &errors);
+    expect("\"\" || b", "b", &errors);
+    expect("\"\" || \"\"", "", &errors);
+    expect("a || abort()", "a", &errors);     // test short-circuiting
+    expect("\"\" || abort()", NULL, &errors);
+
+    // logical not
+    expect("!a", "", &errors);
+    expect("! \"\"", "t", &errors);
+    expect("!!a", "t", &errors);
+
+    // precedence
+    expect("\"\" == \"\" && b", "b", &errors);
+    expect("a + b == ab", "t", &errors);
+    expect("ab == a + b", "t", &errors);
+    expect("a + (b == ab)", "a", &errors);
+    expect("(ab == a) + b", "b", &errors);
+
+    // substring function
+    expect("is_substring(cad, abracadabra)", "t", &errors);
+    expect("is_substring(abrac, abracadabra)", "t", &errors);
+    expect("is_substring(dabra, abracadabra)", "t", &errors);
+    expect("is_substring(cad, abracxadabra)", "", &errors);
+    expect("is_substring(abrac, axbracadabra)", "", &errors);
+    expect("is_substring(dabra, abracadabrxa)", "", &errors);
+
+    // ifelse function
+    expect("ifelse(t, yes, no)", "yes", &errors);
+    expect("ifelse(!t, yes, no)", "no", &errors);
+    expect("ifelse(t, yes, abort())", "yes", &errors);
+    expect("ifelse(!t, abort(), no)", "no", &errors);
+
+    // if "statements"
+    expect("if t then yes else no endif", "yes", &errors);
+    expect("if \"\" then yes else no endif", "no", &errors);
+    expect("if \"\" then yes endif", "", &errors);
+    expect("if \"\"; t then yes endif", "yes", &errors);
+
+    // numeric comparisons
+    expect("less_than_int(3, 14)", "t", &errors);
+    expect("less_than_int(14, 3)", "", &errors);
+    expect("less_than_int(x, 3)", "", &errors);
+    expect("less_than_int(3, x)", "", &errors);
+    expect("greater_than_int(3, 14)", "", &errors);
+    expect("greater_than_int(14, 3)", "t", &errors);
+    expect("greater_than_int(x, 3)", "", &errors);
+    expect("greater_than_int(3, x)", "", &errors);
+
+    printf("\n");
+
+    return errors;
+}
+
+void ExprDump(int depth, Expr* n, char* script) {
+    printf("%*s", depth*2, "");
+    char temp = script[n->end];
+    script[n->end] = '\0';
+    printf("%s %p (%d-%d) \"%s\"\n",
+           n->name == NULL ? "(NULL)" : n->name, n->fn, n->start, n->end,
+           script+n->start);
+    script[n->end] = temp;
+    int i;
+    for (i = 0; i < n->argc; ++i) {
+        ExprDump(depth+1, n->argv[i], script);
+    }
+}
+
+int main(int argc, char** argv) {
+    RegisterBuiltins();
+    FinishRegistration();
+
+    if (argc == 1) {
+        return test() != 0;
+    }
+
+    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);
+    buffer[size] = '\0';
+
+    Expr* root;
+    int error_count = 0;
+    yy_scan_bytes(buffer, size);
+    int error = yyparse(&root, &error_count);
+    printf("parse returned %d; %d errors encountered\n", error, error_count);
+    if (error == 0 || error_count > 0) {
+
+        ExprDump(0, root, buffer);
+
+        State state;
+        state.cookie = NULL;
+        state.script = buffer;
+        state.errmsg = NULL;
+
+        char* result = Evaluate(&state, root);
+        if (result == NULL) {
+            printf("result was NULL, message is: %s\n",
+                   (state.errmsg == NULL ? "(NULL)" : state.errmsg));
+            free(state.errmsg);
+        } else {
+            printf("result is [%s]\n", result);
+        }
+    }
+    return 0;
+}
diff --git a/edify/parser.y b/edify/parser.y
new file mode 100644
index 0000000..3f9ade1
--- /dev/null
+++ b/edify/parser.y
@@ -0,0 +1,130 @@
+%{
+/*
+ * 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 "expr.h"
+#include "yydefs.h"
+#include "parser.h"
+
+extern int gLine;
+extern int gColumn;
+
+void yyerror(Expr** root, int* error_count, const char* s);
+int yyparse(Expr** root, int* error_count);
+
+%}
+
+%locations
+
+%union {
+    char* str;
+    Expr* expr;
+    struct {
+        int argc;
+        Expr** argv;
+    } args;
+}
+
+%token AND OR SUBSTR SUPERSTR EQ NE IF THEN ELSE ENDIF
+%token <str> STRING BAD
+%type <expr> expr
+%type <args> arglist
+
+%parse-param {Expr** root}
+%parse-param {int* error_count}
+%error-verbose
+
+/* declarations in increasing order of precedence */
+%left ';'
+%left ','
+%left OR
+%left AND
+%left EQ NE
+%left '+'
+%right '!'
+
+%%
+
+input:  expr           { *root = $1; }
+;
+
+expr:  STRING {
+    $$ = malloc(sizeof(Expr));
+    $$->fn = Literal;
+    $$->name = $1;
+    $$->argc = 0;
+    $$->argv = NULL;
+    $$->start = @$.start;
+    $$->end = @$.end;
+}
+|  '(' expr ')'                      { $$ = $2; $$->start=@$.start; $$->end=@$.end; }
+|  expr ';'                          { $$ = $1; $$->start=@1.start; $$->end=@1.end; }
+|  expr ';' expr                     { $$ = Build(SequenceFn, @$, 2, $1, $3); }
+|  error ';' expr                    { $$ = $3; $$->start=@$.start; $$->end=@$.end; }
+|  expr '+' expr                     { $$ = Build(ConcatFn, @$, 2, $1, $3); }
+|  expr EQ expr                      { $$ = Build(EqualityFn, @$, 2, $1, $3); }
+|  expr NE expr                      { $$ = Build(InequalityFn, @$, 2, $1, $3); }
+|  expr AND expr                     { $$ = Build(LogicalAndFn, @$, 2, $1, $3); }
+|  expr OR expr                      { $$ = Build(LogicalOrFn, @$, 2, $1, $3); }
+|  '!' expr                          { $$ = Build(LogicalNotFn, @$, 1, $2); }
+|  IF expr THEN expr ENDIF           { $$ = Build(IfElseFn, @$, 2, $2, $4); }
+|  IF expr THEN expr ELSE expr ENDIF { $$ = Build(IfElseFn, @$, 3, $2, $4, $6); }
+| STRING '(' arglist ')' {
+    $$ = malloc(sizeof(Expr));
+    $$->fn = FindFunction($1);
+    if ($$->fn == NULL) {
+        char buffer[256];
+        snprintf(buffer, sizeof(buffer), "unknown function \"%s\"", $1);
+        yyerror(root, error_count, buffer);
+        YYERROR;
+    }
+    $$->name = $1;
+    $$->argc = $3.argc;
+    $$->argv = $3.argv;
+    $$->start = @$.start;
+    $$->end = @$.end;
+}
+;
+
+arglist:    /* empty */ {
+    $$.argc = 0;
+    $$.argv = NULL;
+}
+| expr {
+    $$.argc = 1;
+    $$.argv = malloc(sizeof(Expr*));
+    $$.argv[0] = $1;
+}
+| arglist ',' expr {
+    $$.argc = $1.argc + 1;
+    $$.argv = realloc($$.argv, $$.argc * sizeof(Expr*));
+    $$.argv[$$.argc-1] = $3;
+}
+;
+
+%%
+
+void yyerror(Expr** root, int* error_count, const char* s) {
+  if (strlen(s) == 0) {
+    s = "syntax error";
+  }
+  printf("line %d col %d: %s\n", gLine, gColumn, s);
+  ++*error_count;
+}
diff --git a/edify/yydefs.h b/edify/yydefs.h
new file mode 100644
index 0000000..aca398f
--- /dev/null
+++ b/edify/yydefs.h
@@ -0,0 +1,38 @@
+/*
+ * 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 _YYDEFS_H_
+#define _YYDEFS_H_
+
+#define YYLTYPE YYLTYPE
+typedef struct {
+    int start, end;
+} YYLTYPE;
+
+#define YYLLOC_DEFAULT(Current, Rhs, N) \
+    do { \
+        if (N) { \
+            (Current).start = YYRHSLOC(Rhs, 1).start; \
+            (Current).end = YYRHSLOC(Rhs, N).end; \
+        } else { \
+            (Current).start = YYRHSLOC(Rhs, 0).start; \
+            (Current).end = YYRHSLOC(Rhs, 0).end; \
+        } \
+    } while (0)
+
+int yylex();
+
+#endif
diff --git a/edifyscripting.c b/edifyscripting.c
new file mode 100644
index 0000000..7d0d728
--- /dev/null
+++ b/edifyscripting.c
@@ -0,0 +1,267 @@
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <linux/input.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/reboot.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/wait.h>
+#include <sys/limits.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include <signal.h>
+#include <sys/wait.h>
+
+#include "bootloader.h"
+#include "common.h"
+#include "cutils/properties.h"
+#include "firmware.h"
+#include "install.h"
+#include "minui/minui.h"
+#include "minzip/DirUtil.h"
+#include "roots.h"
+#include "recovery_ui.h"
+
+#include "../../external/yaffs2/yaffs2/utils/mkyaffs2image.h"
+#include "../../external/yaffs2/yaffs2/utils/unyaffs.h"
+
+#include "extendedcommands.h"
+#include "nandroid.h"
+#include "mounts.h"
+#include "flashutils/flashutils.h"
+#include "edify/expr.h"
+#include "mtdutils/mtdutils.h"
+#include "mmcutils/mmcutils.h"
+//#include "edify/parser.h"
+
+Value* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char** args = ReadVarArgs(state, argc, argv);
+    if (args == NULL) {
+        return NULL;
+    }
+
+    int size = 0;
+    int i;
+    for (i = 0; i < argc; ++i) {
+        size += strlen(args[i]);
+    }
+    char* buffer = malloc(size+1);
+    size = 0;
+    for (i = 0; i < argc; ++i) {
+        strcpy(buffer+size, args[i]);
+        size += strlen(args[i]);
+        free(args[i]);
+    }
+    free(args);
+    buffer[size] = '\0';
+
+    char* line = strtok(buffer, "\n");
+    while (line) {
+        ui_print("%s\n", line);
+        line = strtok(NULL, "\n");
+    }
+
+    return StringValue(buffer);
+}
+
+Value* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc < 1) {
+        return ErrorAbort(state, "%s() expects at least 1 arg", name);
+    }
+    char** args = ReadVarArgs(state, argc, argv);
+    if (args == NULL) {
+        return NULL;
+    }
+
+    char** args2 = malloc(sizeof(char*) * (argc+1));
+    memcpy(args2, args, sizeof(char*) * argc);
+    args2[argc] = NULL;
+
+    fprintf(stderr, "about to run program [%s] with %d args\n", args2[0], argc);
+
+    pid_t child = fork();
+    if (child == 0) {
+        execv(args2[0], args2);
+        fprintf(stderr, "run_program: execv failed: %s\n", strerror(errno));
+        _exit(1);
+    }
+    int status;
+    waitpid(child, &status, 0);
+    if (WIFEXITED(status)) {
+        if (WEXITSTATUS(status) != 0) {
+            fprintf(stderr, "run_program: child exited with status %d\n",
+                    WEXITSTATUS(status));
+        }
+    } else if (WIFSIGNALED(status)) {
+        fprintf(stderr, "run_program: child terminated by signal %d\n",
+                WTERMSIG(status));
+    }
+
+    int i;
+    for (i = 0; i < argc; ++i) {
+        free(args[i]);
+    }
+    free(args);
+    free(args2);
+
+    char buffer[20];
+    sprintf(buffer, "%d", status);
+
+    return StringValue(strdup(buffer));
+}
+
+Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* result = NULL;
+    if (argc != 1) {
+        return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
+    }
+    
+    char *path;
+    if (ReadArgs(state, argv, 1, &path) < 0) {
+        return NULL;
+    }
+    
+    ui_print("Formatting %s...\n", path);
+    if (0 != format_volume(path)) {
+        free(path);
+        return StringValue(strdup(""));
+    }
+
+done:
+    return StringValue(strdup(path));
+}
+
+Value* BackupFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* result = NULL;
+    if (argc != 1) {
+        return ErrorAbort(state, "%s() expects 1 args, got %d", name, argc);
+    }
+    char* path;
+    if (ReadArgs(state, argv, 1, &path) < 0) {
+        return NULL;
+    }
+    
+    if (0 != nandroid_backup(path))
+        return StringValue(strdup(""));
+    
+    return StringValue(strdup(path));
+}
+
+Value* RestoreFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc < 1) {
+        return ErrorAbort(state, "%s() expects at least 1 arg", name);
+    }
+    char** args = ReadVarArgs(state, argc, argv);
+    if (args == NULL) {
+        return NULL;
+    }
+
+    char** args2 = malloc(sizeof(char*) * (argc+1));
+    memcpy(args2, args, sizeof(char*) * argc);
+    args2[argc] = NULL;
+    
+    char* path = strdup(args2[0]);
+    int restoreboot = 1;
+    int restoresystem = 1;
+    int restoredata = 1;
+    int restorecache = 1;
+    int restoresdext = 1;
+    int i;
+    for (i = 1; i < argc; i++)
+    {
+        if (args2[i] == NULL)
+            continue;
+        if (strcmp(args2[i], "noboot") == 0)
+            restoreboot = 0;
+        else if (strcmp(args2[i], "nosystem") == 0)
+            restoresystem = 0;
+        else if (strcmp(args2[i], "nodata") == 0)
+            restoredata = 0;
+        else if (strcmp(args2[i], "nocache") == 0)
+            restorecache = 0;
+        else if (strcmp(args2[i], "nosd-ext") == 0)
+            restoresdext = 0;
+    }
+    
+    for (i = 0; i < argc; ++i) {
+        free(args[i]);
+    }
+    free(args);
+    free(args2);
+
+    if (0 != nandroid_restore(path, restoreboot, restoresystem, restoredata, restorecache, restoresdext, 0)) {
+        free(path);
+        return StringValue(strdup(""));
+    }
+    
+    return StringValue(path);
+}
+
+Value* InstallZipFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* result = NULL;
+    if (argc != 1) {
+        return ErrorAbort(state, "%s() expects 1 args, got %d", name, argc);
+    }
+    char* path;
+    if (ReadArgs(state, argv, 1, &path) < 0) {
+        return NULL;
+    }
+    
+    if (0 != install_zip(path))
+        return StringValue(strdup(""));
+    
+    return StringValue(strdup(path));
+}
+
+void RegisterRecoveryHooks() {
+    RegisterFunction("format", FormatFn);
+    RegisterFunction("ui_print", UIPrintFn);
+    RegisterFunction("run_program", RunProgramFn);
+    RegisterFunction("backup_rom", BackupFn);
+    RegisterFunction("restore_rom", RestoreFn);
+    RegisterFunction("install_zip", InstallZipFn);
+}
+
+static int hasInitializedEdify = 0;
+int run_script_from_buffer(char* script_data, int script_len, char* filename)
+{
+    if (!hasInitializedEdify) {
+        RegisterBuiltins();
+        RegisterRecoveryHooks();
+        FinishRegistration();
+        hasInitializedEdify = 1;
+    }
+
+    Expr* root;
+    int error_count = 0;
+    yy_scan_bytes(script_data, script_len);
+    int error = yyparse(&root, &error_count);
+    printf("parse returned %d; %d errors encountered\n", error, error_count);
+    if (error == 0 || error_count > 0) {
+        //ExprDump(0, root, buffer);
+
+        State state;
+        state.cookie = NULL;
+        state.script = script_data;
+        state.errmsg = NULL;
+
+        char* result = Evaluate(&state, root);
+        if (result == NULL) {
+            printf("result was NULL, message is: %s\n",
+                   (state.errmsg == NULL ? "(NULL)" : state.errmsg));
+            free(state.errmsg);
+            return -1;
+        } else {
+            printf("result is [%s]\n", result);
+        }
+    }
+    return 0;
+}
diff --git a/encryptedfs_provisioning.c b/encryptedfs_provisioning.c
new file mode 100644
index 0000000..678c09f
--- /dev/null
+++ b/encryptedfs_provisioning.c
@@ -0,0 +1,283 @@
+/*
+ * 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 <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "encryptedfs_provisioning.h"
+#include "cutils/misc.h"
+#include "cutils/properties.h"
+#include "common.h"
+#include "mtdutils/mtdutils.h"
+#include "mounts.h"
+#include "roots.h"
+
+const char* encrypted_fs_enabled_property      = "persist.security.secfs.enabled";
+const char* encrypted_fs_property_dir          = "/data/property/";
+const char* encrypted_fs_system_dir            = "/data/system/";
+const char* encrypted_fs_key_file_name         = "/data/fs_key.dat";
+const char* encrypted_fs_salt_file_name        = "/data/hash_salt.dat";
+const char* encrypted_fs_hash_file_src_name    = "/data/system/password.key";
+const char* encrypted_fs_hash_file_dst_name    = "/data/hash.dat";
+const char* encrypted_fs_entropy_file_src_name = "/data/system/entropy.dat";
+const char* encrypted_fs_entropy_file_dst_name = "/data/ported_entropy.dat";
+
+void get_property_file_name(char *buffer, const char *property_name) {
+    sprintf(buffer, "%s%s", encrypted_fs_property_dir, property_name);
+}
+
+int get_binary_file_contents(char *buffer, int buf_size, const char *file_name, int *out_size) {
+    FILE *in_file;
+    int read_bytes;
+
+    in_file = fopen(file_name, "r");
+    if (in_file == NULL) {
+        LOGE("Secure FS: error accessing key file.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    read_bytes = fread(buffer, 1, buf_size, in_file);
+    if (out_size == NULL) {
+        if (read_bytes != buf_size) {
+            // Error or unexpected data
+            fclose(in_file);
+            LOGE("Secure FS: error reading conmplete key.");
+            return ENCRYPTED_FS_ERROR;
+        }
+    } else {
+        *out_size = read_bytes;
+    }
+    fclose(in_file);
+    return ENCRYPTED_FS_OK;
+}
+
+int set_binary_file_contents(char *buffer, int buf_size, const char *file_name) {
+    FILE *out_file;
+    int write_bytes;
+
+    out_file = fopen(file_name, "w");
+    if (out_file == NULL) {
+        LOGE("Secure FS: error setting up key file.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    write_bytes = fwrite(buffer, 1, buf_size, out_file);
+    if (write_bytes != buf_size) {
+        // Error or unexpected data
+        fclose(out_file);
+        LOGE("Secure FS: error reading conmplete key.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    fclose(out_file);
+    return ENCRYPTED_FS_OK;
+}
+
+int get_text_file_contents(char *buffer, int buf_size, char *file_name) {
+    FILE *in_file;
+    char *read_data;
+
+    in_file = fopen(file_name, "r");
+    if (in_file == NULL) {
+        LOGE("Secure FS: error accessing properties.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    read_data = fgets(buffer, buf_size, in_file);
+    if (read_data == NULL) {
+        // Error or unexpected data
+        fclose(in_file);
+        LOGE("Secure FS: error accessing properties.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    fclose(in_file);
+    return ENCRYPTED_FS_OK;
+}
+
+int set_text_file_contents(char *buffer, char *file_name) {
+    FILE *out_file;
+    int result;
+
+    out_file = fopen(file_name, "w");
+    if (out_file == NULL) {
+        LOGE("Secure FS: error setting up properties.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    result = fputs(buffer, out_file);
+    if (result != 0) {
+        // Error or unexpected data
+        fclose(out_file);
+        LOGE("Secure FS: error setting up properties.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    fflush(out_file);
+    fclose(out_file);
+    return ENCRYPTED_FS_OK;
+}
+
+int read_encrypted_fs_boolean_property(const char *prop_name, int *value) {
+    char prop_file_name[PROPERTY_KEY_MAX + 32];
+    char prop_value[PROPERTY_VALUE_MAX];
+    int result;
+
+    get_property_file_name(prop_file_name, prop_name);
+    result = get_text_file_contents(prop_value, PROPERTY_VALUE_MAX, prop_file_name);
+
+    if (result < 0) {
+        return result;
+    }
+
+    if (strncmp(prop_value, "1", 1) == 0) {
+        *value = 1;
+    } else if (strncmp(prop_value, "0", 1) == 0) {
+        *value = 0;
+    } else {
+        LOGE("Secure FS: error accessing properties.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    return ENCRYPTED_FS_OK;
+}
+
+int write_encrypted_fs_boolean_property(const char *prop_name, int value) {
+    char prop_file_name[PROPERTY_KEY_MAX + 32];
+    char prop_value[PROPERTY_VALUE_MAX];
+    int result;
+
+    get_property_file_name(prop_file_name, prop_name);
+
+    // Create the directory if needed
+    mkdir(encrypted_fs_property_dir, 0755);
+    if (value == 1) {
+        result = set_text_file_contents("1", prop_file_name);
+    } else if (value == 0) {
+        result = set_text_file_contents("0", prop_file_name);
+    } else {
+        return ENCRYPTED_FS_ERROR;
+    }
+    if (result < 0) {
+        return result;
+    }
+
+    return ENCRYPTED_FS_OK;
+}
+
+int read_encrypted_fs_info(encrypted_fs_info *encrypted_fs_data) {
+    int result;
+    int value;
+    result = ensure_path_mounted("/data");
+    if (result != 0) {
+        LOGE("Secure FS: error mounting userdata partition.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    // Read the pre-generated encrypted FS key, password hash and salt.
+    result = get_binary_file_contents(encrypted_fs_data->key, ENCRYPTED_FS_KEY_SIZE,
+            encrypted_fs_key_file_name, NULL);
+    if (result != 0) {
+        LOGE("Secure FS: error reading generated file system key.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    result = get_binary_file_contents(encrypted_fs_data->salt, ENCRYPTED_FS_SALT_SIZE,
+            encrypted_fs_salt_file_name, &(encrypted_fs_data->salt_length));
+    if (result != 0) {
+        LOGE("Secure FS: error reading file system salt.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    result = get_binary_file_contents(encrypted_fs_data->hash, ENCRYPTED_FS_MAX_HASH_SIZE,
+            encrypted_fs_hash_file_src_name, &(encrypted_fs_data->hash_length));
+    if (result != 0) {
+        LOGE("Secure FS: error reading password hash.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    result = get_binary_file_contents(encrypted_fs_data->entropy, ENTROPY_MAX_SIZE,
+            encrypted_fs_entropy_file_src_name, &(encrypted_fs_data->entropy_length));
+    if (result != 0) {
+        LOGE("Secure FS: error reading ported entropy.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    result = ensure_path_unmounted("/data");
+    if (result != 0) {
+        LOGE("Secure FS: error unmounting data partition.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    return ENCRYPTED_FS_OK;
+}
+
+int restore_encrypted_fs_info(encrypted_fs_info *encrypted_fs_data) {
+    int result;
+    result = ensure_path_mounted("/data");
+    if (result != 0) {
+        LOGE("Secure FS: error mounting userdata partition.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    // Write the pre-generated secure FS key, password hash and salt.
+    result = set_binary_file_contents(encrypted_fs_data->key, ENCRYPTED_FS_KEY_SIZE,
+            encrypted_fs_key_file_name);
+    if (result != 0) {
+        LOGE("Secure FS: error writing generated file system key.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    result = set_binary_file_contents(encrypted_fs_data->salt, encrypted_fs_data->salt_length,
+        encrypted_fs_salt_file_name);
+    if (result != 0) {
+        LOGE("Secure FS: error writing file system salt.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    result = set_binary_file_contents(encrypted_fs_data->hash, encrypted_fs_data->hash_length,
+            encrypted_fs_hash_file_dst_name);
+    if (result != 0) {
+        LOGE("Secure FS: error writing password hash.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    result = set_binary_file_contents(encrypted_fs_data->entropy, encrypted_fs_data->entropy_length,
+            encrypted_fs_entropy_file_dst_name);
+    if (result != 0) {
+        LOGE("Secure FS: error writing ported entropy.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    // Set the secure FS properties to their respective values
+    result = write_encrypted_fs_boolean_property(encrypted_fs_enabled_property, encrypted_fs_data->mode);
+    if (result != 0) {
+        return result;
+    }
+
+    result = ensure_path_unmounted("/data");
+    if (result != 0) {
+        LOGE("Secure FS: error unmounting data partition.");
+        return ENCRYPTED_FS_ERROR;
+    }
+
+    return ENCRYPTED_FS_OK;
+}
diff --git a/encryptedfs_provisioning.h b/encryptedfs_provisioning.h
new file mode 100644
index 0000000..284605d
--- /dev/null
+++ b/encryptedfs_provisioning.h
@@ -0,0 +1,51 @@
+/*
+ * 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>
+
+#ifndef __ENCRYPTEDFS_PROVISIONING_H__
+#define __ENCRYPTEDFS_PROVISIONING_H__
+
+#define MODE_ENCRYPTED_FS_DISABLED    0
+#define MODE_ENCRYPTED_FS_ENABLED     1
+
+#define ENCRYPTED_FS_OK               0
+#define ENCRYPTED_FS_ERROR          (-1)
+
+#define ENCRYPTED_FS_KEY_SIZE        16
+#define ENCRYPTED_FS_SALT_SIZE       16
+#define ENCRYPTED_FS_MAX_HASH_SIZE  128
+#define ENTROPY_MAX_SIZE        4096
+
+struct encrypted_fs_info {
+    int mode;
+    char key[ENCRYPTED_FS_KEY_SIZE];
+    char salt[ENCRYPTED_FS_SALT_SIZE];
+    int salt_length;
+    char hash[ENCRYPTED_FS_MAX_HASH_SIZE];
+    int hash_length;
+    char entropy[ENTROPY_MAX_SIZE];
+    int entropy_length;
+};
+
+typedef struct encrypted_fs_info encrypted_fs_info;
+
+int read_encrypted_fs_info(encrypted_fs_info *secure_fs_data);
+
+int restore_encrypted_fs_info(encrypted_fs_info *secure_data);
+
+#endif /* __ENCRYPTEDFS_PROVISIONING_H__ */
+
diff --git a/etc/META-INF/com/google/android/update-script b/etc/META-INF/com/google/android/update-script
new file mode 100644
index 0000000..b091b19
--- /dev/null
+++ b/etc/META-INF/com/google/android/update-script
@@ -0,0 +1,8 @@
+assert compatible_with("0.1") == "true"
+assert file_contains("SYSTEM:build.prop", "ro.product.device=dream") == "true" || file_contains("SYSTEM:build.prop", "ro.build.product=dream") == "true"
+assert file_contains("RECOVERY:default.prop", "ro.product.device=dream") == "true" || file_contains("RECOVERY:default.prop", "ro.build.product=dream") == "true"
+assert getprop("ro.product.device") == "dream"
+format BOOT:
+format SYSTEM:
+copy_dir PACKAGE:system SYSTEM:
+write_raw_image PACKAGE:boot.img BOOT:
diff --git a/etc/init.rc b/etc/init.rc
new file mode 100644
index 0000000..611f5b4
--- /dev/null
+++ b/etc/init.rc
@@ -0,0 +1,41 @@
+on early-init
+    start ueventd
+
+on init
+    export PATH /sbin
+    export ANDROID_ROOT /system
+    export ANDROID_DATA /data
+    export EXTERNAL_STORAGE /sdcard
+
+    symlink /system/etc /etc
+
+    mkdir /sdcard
+    mkdir /sd-ext
+    mkdir /datadata
+    mkdir /emmc
+    mkdir /system
+    mkdir /data
+    mkdir /cache
+    mount /tmp /tmp tmpfs
+
+on boot
+
+    ifup lo
+    hostname localhost
+    domainname localdomain
+
+    class_start default
+
+service ueventd /sbin/ueventd
+    critical
+
+service recovery /sbin/recovery
+
+service adbd /sbin/adbd recovery
+    disabled
+
+on property:persist.service.adb.enable=1
+    start adbd
+
+on property:persist.service.adb.enable=0
+    stop adbd
diff --git a/extendedcommands.c b/extendedcommands.c
new file mode 100644
index 0000000..95a5f07
--- /dev/null
+++ b/extendedcommands.c
@@ -0,0 +1,1127 @@
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <linux/input.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/reboot.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/wait.h>
+#include <sys/limits.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include <signal.h>
+#include <sys/wait.h>
+
+#include "bootloader.h"
+#include "common.h"
+#include "cutils/properties.h"
+#include "firmware.h"
+#include "install.h"
+#include "minui/minui.h"
+#include "minzip/DirUtil.h"
+#include "roots.h"
+#include "recovery_ui.h"
+
+#include "../../external/yaffs2/yaffs2/utils/mkyaffs2image.h"
+#include "../../external/yaffs2/yaffs2/utils/unyaffs.h"
+
+#include "extendedcommands.h"
+#include "nandroid.h"
+#include "mounts.h"
+#include "flashutils/flashutils.h"
+#include "edify/expr.h"
+
+int signature_check_enabled = 1;
+int script_assert_enabled = 1;
+static const char *SDCARD_UPDATE_FILE = "/sdcard/update.zip";
+
+void
+toggle_signature_check()
+{
+    signature_check_enabled = !signature_check_enabled;
+    ui_print("Signature Check: %s\n", signature_check_enabled ? "Enabled" : "Disabled");
+}
+
+void toggle_script_asserts()
+{
+    script_assert_enabled = !script_assert_enabled;
+    ui_print("Script Asserts: %s\n", script_assert_enabled ? "Enabled" : "Disabled");
+}
+
+int install_zip(const char* packagefilepath)
+{
+    ui_print("\n-- Installing: %s\n", packagefilepath);
+    if (device_flash_type() == MTD) {
+        set_sdcard_update_bootloader_message();
+    }
+    int status = install_package(packagefilepath);
+    ui_reset_progress();
+    if (status != INSTALL_SUCCESS) {
+        ui_set_background(BACKGROUND_ICON_ERROR);
+        ui_print("Installation aborted.\n");
+        return 1;
+    }
+    ui_set_background(BACKGROUND_ICON_NONE);
+    ui_print("\nInstall from sdcard complete.\n");
+    return 0;
+}
+
+char* INSTALL_MENU_ITEMS[] = {  "apply /sdcard/update.zip",
+                                "choose zip from sdcard",
+                                "toggle signature verification",
+                                "toggle script asserts",
+                                NULL };
+#define ITEM_APPLY_SDCARD     0
+#define ITEM_CHOOSE_ZIP       1
+#define ITEM_SIG_CHECK        2
+#define ITEM_ASSERTS          3
+
+void show_install_update_menu()
+{
+    static char* headers[] = {  "Apply update from .zip file on SD card",
+                                "",
+                                NULL
+    };
+    for (;;)
+    {
+        int chosen_item = get_menu_selection(headers, INSTALL_MENU_ITEMS, 0, 0);
+        switch (chosen_item)
+        {
+            case ITEM_ASSERTS:
+                toggle_script_asserts();
+                break;
+            case ITEM_SIG_CHECK:
+                toggle_signature_check();
+                break;
+            case ITEM_APPLY_SDCARD:
+            {
+                if (confirm_selection("Confirm install?", "Yes - Install /sdcard/update.zip"))
+                    install_zip(SDCARD_UPDATE_FILE);
+                break;
+            }
+            case ITEM_CHOOSE_ZIP:
+                show_choose_zip_menu();
+                break;
+            default:
+                return;
+        }
+
+    }
+}
+
+void free_string_array(char** array)
+{
+    if (array == NULL)
+        return;
+    char* cursor = array[0];
+    int i = 0;
+    while (cursor != NULL)
+    {
+        free(cursor);
+        cursor = array[++i];
+    }
+    free(array);
+}
+
+char** gather_files(const char* directory, const char* fileExtensionOrDirectory, int* numFiles)
+{
+    char path[PATH_MAX] = "";
+    DIR *dir;
+    struct dirent *de;
+    int total = 0;
+    int i;
+    char** files = NULL;
+    int pass;
+    *numFiles = 0;
+    int dirLen = strlen(directory);
+
+    dir = opendir(directory);
+    if (dir == NULL) {
+        ui_print("Couldn't open directory.\n");
+        return NULL;
+    }
+
+    int extension_length = 0;
+    if (fileExtensionOrDirectory != NULL)
+        extension_length = strlen(fileExtensionOrDirectory);
+
+    int isCounting = 1;
+    i = 0;
+    for (pass = 0; pass < 2; pass++) {
+        while ((de=readdir(dir)) != NULL) {
+            // skip hidden files
+            if (de->d_name[0] == '.')
+                continue;
+
+            // NULL means that we are gathering directories, so skip this
+            if (fileExtensionOrDirectory != NULL)
+            {
+                // make sure that we can have the desired extension (prevent seg fault)
+                if (strlen(de->d_name) < extension_length)
+                    continue;
+                // compare the extension
+                if (strcmp(de->d_name + strlen(de->d_name) - extension_length, fileExtensionOrDirectory) != 0)
+                    continue;
+            }
+            else
+            {
+                struct stat info;
+                char fullFileName[PATH_MAX];
+                strcpy(fullFileName, directory);
+                strcat(fullFileName, de->d_name);
+                stat(fullFileName, &info);
+                // make sure it is a directory
+                if (!(S_ISDIR(info.st_mode)))
+                    continue;
+            }
+
+            if (pass == 0)
+            {
+                total++;
+                continue;
+            }
+
+            files[i] = (char*) malloc(dirLen + strlen(de->d_name) + 2);
+            strcpy(files[i], directory);
+            strcat(files[i], de->d_name);
+            if (fileExtensionOrDirectory == NULL)
+                strcat(files[i], "/");
+            i++;
+        }
+        if (pass == 1)
+            break;
+        if (total == 0)
+            break;
+        rewinddir(dir);
+        *numFiles = total;
+        files = (char**) malloc((total+1)*sizeof(char*));
+        files[total]=NULL;
+    }
+
+    if(closedir(dir) < 0) {
+        LOGE("Failed to close directory.");
+    }
+
+    if (total==0) {
+        return NULL;
+    }
+
+    // sort the result
+    if (files != NULL) {
+        for (i = 0; i < total; i++) {
+            int curMax = -1;
+            int j;
+            for (j = 0; j < total - i; j++) {
+                if (curMax == -1 || strcmp(files[curMax], files[j]) < 0)
+                    curMax = j;
+            }
+            char* temp = files[curMax];
+            files[curMax] = files[total - i - 1];
+            files[total - i - 1] = temp;
+        }
+    }
+
+    return files;
+}
+
+// pass in NULL for fileExtensionOrDirectory and you will get a directory chooser
+char* choose_file_menu(const char* directory, const char* fileExtensionOrDirectory, const char* headers[])
+{
+    char path[PATH_MAX] = "";
+    DIR *dir;
+    struct dirent *de;
+    int numFiles = 0;
+    int numDirs = 0;
+    int i;
+    char* return_value = NULL;
+    int dir_len = strlen(directory);
+
+    char** files = gather_files(directory, fileExtensionOrDirectory, &numFiles);
+    char** dirs = NULL;
+    if (fileExtensionOrDirectory != NULL)
+        dirs = gather_files(directory, NULL, &numDirs);
+    int total = numDirs + numFiles;
+    if (total == 0)
+    {
+        ui_print("No files found.\n");
+    }
+    else
+    {
+        char** list = (char**) malloc((total + 1) * sizeof(char*));
+        list[total] = NULL;
+
+
+        for (i = 0 ; i < numDirs; i++)
+        {
+            list[i] = strdup(dirs[i] + dir_len);
+        }
+
+        for (i = 0 ; i < numFiles; i++)
+        {
+            list[numDirs + i] = strdup(files[i] + dir_len);
+        }
+
+        for (;;)
+        {
+            int chosen_item = get_menu_selection(headers, list, 0, 0);
+            if (chosen_item == GO_BACK)
+                break;
+            static char ret[PATH_MAX];
+            if (chosen_item < numDirs)
+            {
+                char* subret = choose_file_menu(dirs[chosen_item], fileExtensionOrDirectory, headers);
+                if (subret != NULL)
+                {
+                    strcpy(ret, subret);
+                    return_value = ret;
+                    break;
+                }
+                continue;
+            }
+            strcpy(ret, files[chosen_item - numDirs]);
+            return_value = ret;
+            break;
+        }
+        free_string_array(list);
+    }
+
+    free_string_array(files);
+    free_string_array(dirs);
+    return return_value;
+}
+
+void show_choose_zip_menu()
+{
+    if (ensure_path_mounted("/sdcard") != 0) {
+        LOGE ("Can't mount /sdcard\n");
+        return;
+    }
+
+    static char* headers[] = {  "Choose a zip to apply",
+                                "",
+                                NULL
+    };
+
+    char* file = choose_file_menu("/sdcard/", ".zip", headers);
+    if (file == NULL)
+        return;
+    static char* confirm_install  = "Confirm install?";
+    static char confirm[PATH_MAX];
+    sprintf(confirm, "Yes - Install %s", basename(file));
+    if (confirm_selection(confirm_install, confirm))
+        install_zip(file);
+}
+
+void show_nandroid_restore_menu()
+{
+    if (ensure_path_mounted("/sdcard") != 0) {
+        LOGE ("Can't mount /sdcard\n");
+        return;
+    }
+
+    static char* headers[] = {  "Choose an image to restore",
+                                "",
+                                NULL
+    };
+
+    char* file = choose_file_menu("/sdcard/clockworkmod/backup/", NULL, headers);
+    if (file == NULL)
+        return;
+
+    if (confirm_selection("Confirm restore?", "Yes - Restore"))
+        nandroid_restore(file, 1, 1, 1, 1, 1, 0);
+}
+
+void show_mount_usb_storage_menu()
+{
+    int fd;
+    Volume *vol = volume_for_path("/sdcard");
+    if ((fd = open("/sys/devices/platform/usb_mass_storage/lun0/file",
+                   O_WRONLY)) < 0) {
+        LOGE("Unable to open ums lunfile (%s)", strerror(errno));
+        return -1;
+    }
+
+    if (write(fd, vol->device, strlen(vol->device)) < 0) {
+        LOGE("Unable to write to ums lunfile (%s)", strerror(errno));
+        close(fd);
+        return -1;
+    }
+    static char* headers[] = {  "USB Mass Storage device",
+                                "Leaving this menu unmount",
+                                "your SD card from your PC.",
+                                "",
+                                NULL
+    };
+
+    static char* list[] = { "Unmount", NULL };
+
+    for (;;)
+    {
+        int chosen_item = get_menu_selection(headers, list, 0, 0);
+        if (chosen_item == GO_BACK || chosen_item == 0)
+            break;
+    }
+
+    if ((fd = open("/sys/devices/platform/usb_mass_storage/lun0/file", O_WRONLY)) < 0) {
+        LOGE("Unable to open ums lunfile (%s)", strerror(errno));
+        return -1;
+    }
+
+    char ch = 0;
+    if (write(fd, &ch, 1) < 0) {
+        LOGE("Unable to write to ums lunfile (%s)", strerror(errno));
+        close(fd);
+        return -1;
+    }
+}
+
+int confirm_selection(const char* title, const char* confirm)
+{
+    struct stat info;
+    if (0 == stat("/sdcard/clockworkmod/.no_confirm", &info))
+        return 1;
+
+    char* confirm_headers[]  = {  title, "  THIS CAN NOT BE UNDONE.", "", NULL };
+    char* items[] = { "No",
+                      "No",
+                      "No",
+                      "No",
+                      "No",
+                      "No",
+                      "No",
+                      confirm, //" Yes -- wipe partition",   // [7
+                      "No",
+                      "No",
+                      "No",
+                      NULL };
+
+    int chosen_item = get_menu_selection(confirm_headers, items, 0, 0);
+    return chosen_item == 7;
+}
+
+#define MKE2FS_BIN      "/sbin/mke2fs"
+#define TUNE2FS_BIN     "/sbin/tune2fs"
+#define E2FSCK_BIN      "/sbin/e2fsck"
+
+int format_unknown_device(const char *device, const char* path, const char *fs_type)
+{
+    LOGI("Formatting unknown device.\n");
+
+    // device may simply be a name, like "system"
+    if (device[0] != '/')
+        return erase_raw_partition(device);
+
+    // if this is SDEXT:, don't worry about it if it does not exist.
+    if (0 == strcmp(path, "/sd-ext"))
+    {
+        struct stat st;
+        Volume *vol = volume_for_path("/sd-ext");
+        if (vol == NULL || 0 != stat(vol->device, &st))
+        {
+            ui_print("No app2sd partition found. Skipping format of /sd-ext.\n");
+            return 0;
+        }
+    }
+
+    if (NULL != fs_type) {
+        if (strcmp("ext3", fs_type) == 0) {
+            LOGI("Formatting ext3 device.\n");
+            if (0 != ensure_path_unmounted(path)) {
+                LOGE("Error while unmounting %s.\n", path);
+                return -12;
+            }
+            return format_ext3_device(device);
+        }
+
+        if (strcmp("ext2", fs_type) == 0) {
+            LOGI("Formatting ext2 device.\n");
+            if (0 != ensure_path_unmounted(path)) {
+                LOGE("Error while unmounting %s.\n", path);
+                return -12;
+            }
+            return format_ext2_device(device);
+        }
+    }
+
+    if (0 != ensure_path_mounted(path))
+    {
+        ui_print("Error mounting %s!\n", path);
+        ui_print("Skipping format...\n");
+        return 0;
+    }
+
+    static char tmp[PATH_MAX];
+    sprintf(tmp, "rm -rf %s/*", path);
+    __system(tmp);
+    sprintf(tmp, "rm -rf %s/.*", path);
+    __system(tmp);
+
+    ensure_path_unmounted(path);
+    return 0;
+}
+
+//#define MOUNTABLE_COUNT 5
+//#define DEVICE_COUNT 4
+//#define MMC_COUNT 2
+
+void show_partition_menu()
+{
+    static char* headers[] = {  "Mounts and Storage Menu",
+                                "",
+                                NULL
+    };
+
+    typedef char* string;
+    string mounts[][3] = {
+        { "mount /system", "unmount /system", "/system" },
+        { "mount /data", "unmount /data", "/data" },
+        { "mount /cache", "unmount /cache", "/cache" },
+        { "mount /sdcard", "unmount /sdcard", "/sdcard" },
+#ifdef BOARD_HAS_SDCARD_INTERNAL
+        { "mount /emmc", "unmount /emmc", "/emmc" },
+#endif
+        { "mount /sd-ext", "unmount /sd-ext", "/sd-ext" }
+    };
+
+    string devices[][2] = {
+        { "format boot", "/boot" },
+        { "format system", "/system" },
+        { "format data", "/data" },
+        { "format cache", "/cache" },
+        { "format sdcard", "/sdcard" },
+        { "format sd-ext", "/sd-ext" },
+#ifdef BOARD_HAS_SDCARD_INTERNAL
+        { "format internal sdcard", "/emmc" }
+#endif
+    };
+
+    const int MOUNTABLE_COUNT = sizeof(mounts) / sizeof(string) / 3;
+    const int DEVICE_COUNT = sizeof(devices) / sizeof(string) / 2;
+
+    static char* confirm_format  = "Confirm format?";
+    static char* confirm = "Yes - Format";
+
+    for (;;)
+    {
+        int ismounted[MOUNTABLE_COUNT];
+        int i;
+        static string options[MOUNTABLE_COUNT + DEVICE_COUNT + 1 + 1]; // mountables, format mtds, format mmcs, usb storage, null
+        for (i = 0; i < MOUNTABLE_COUNT; i++)
+        {
+            ismounted[i] = is_path_mounted(mounts[i][2]);
+            options[i] = ismounted[i] ? mounts[i][1] : mounts[i][0];
+        }
+
+        for (i = 0; i < DEVICE_COUNT; i++)
+        {
+            options[MOUNTABLE_COUNT + i] = devices[i][0];
+        }
+
+        options[MOUNTABLE_COUNT + DEVICE_COUNT] = "mount USB storage";
+        options[MOUNTABLE_COUNT + DEVICE_COUNT + 1] = NULL;
+
+        int chosen_item = get_menu_selection(headers, options, 0, 0);
+        if (chosen_item == GO_BACK)
+            break;
+        if (chosen_item == MOUNTABLE_COUNT + DEVICE_COUNT)
+        {
+            show_mount_usb_storage_menu();
+        }
+        else if (chosen_item < MOUNTABLE_COUNT)
+        {
+            if (ismounted[chosen_item])
+            {
+                if (0 != ensure_path_unmounted(mounts[chosen_item][2]))
+                    ui_print("Error unmounting %s!\n", mounts[chosen_item][2]);
+            }
+            else
+            {
+                if (0 != ensure_path_mounted(mounts[chosen_item][2]))
+                    ui_print("Error mounting %s!\n", mounts[chosen_item][2]);
+            }
+        }
+        else if (chosen_item < MOUNTABLE_COUNT + DEVICE_COUNT)
+        {
+            chosen_item = chosen_item - MOUNTABLE_COUNT;
+            if (!confirm_selection(confirm_format, confirm))
+                continue;
+            ui_print("Formatting %s...\n", devices[chosen_item][1]);
+            if (0 != format_volume(devices[chosen_item][1]))
+                ui_print("Error formatting %s!\n", devices[chosen_item][1]);
+            else
+                ui_print("Done.\n");
+        }
+    }
+}
+
+#define EXTENDEDCOMMAND_SCRIPT "/cache/recovery/extendedcommand"
+
+int extendedcommand_file_exists()
+{
+    struct stat file_info;
+    return 0 == stat(EXTENDEDCOMMAND_SCRIPT, &file_info);
+}
+
+int edify_main(int argc, char** argv) {
+    load_volume_table();
+    process_volumes();
+    RegisterBuiltins();
+    RegisterRecoveryHooks();
+    FinishRegistration();
+
+    if (argc != 2) {
+        printf("edify <filename>\n");
+        return 1;
+    }
+
+    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);
+    buffer[size] = '\0';
+
+    Expr* root;
+    int error_count = 0;
+    yy_scan_bytes(buffer, size);
+    int error = yyparse(&root, &error_count);
+    printf("parse returned %d; %d errors encountered\n", error, error_count);
+    if (error == 0 || error_count > 0) {
+
+        //ExprDump(0, root, buffer);
+
+        State state;
+        state.cookie = NULL;
+        state.script = buffer;
+        state.errmsg = NULL;
+
+        char* result = Evaluate(&state, root);
+        if (result == NULL) {
+            printf("result was NULL, message is: %s\n",
+                   (state.errmsg == NULL ? "(NULL)" : state.errmsg));
+            free(state.errmsg);
+        } else {
+            printf("result is [%s]\n", result);
+        }
+    }
+    return 0;
+}
+
+int run_script(char* filename)
+{
+    struct stat file_info;
+    if (0 != stat(filename, &file_info)) {
+        printf("Error executing stat on file: %s\n", filename);
+        return 1;
+    }
+
+    int script_len = file_info.st_size;
+    char* script_data = (char*)malloc(script_len + 1);
+    FILE *file = fopen(filename, "rb");
+    fread(script_data, script_len, 1, file);
+    // supposedly not necessary, but let's be safe.
+    script_data[script_len] = '\0';
+    fclose(file);
+    LOGI("Running script:\n");
+    LOGI("\n%s\n", script_data);
+
+    int ret = run_script_from_buffer(script_data, script_len, filename);
+    free(script_data);
+    return ret;
+}
+
+int run_and_remove_extendedcommand()
+{
+    char tmp[PATH_MAX];
+    sprintf(tmp, "cp %s /tmp/%s", EXTENDEDCOMMAND_SCRIPT, basename(EXTENDEDCOMMAND_SCRIPT));
+    __system(tmp);
+    remove(EXTENDEDCOMMAND_SCRIPT);
+    int i = 0;
+    for (i = 20; i > 0; i--) {
+        ui_print("Waiting for SD Card to mount (%ds)\n", i);
+        if (ensure_path_mounted("/sdcard") == 0) {
+            ui_print("SD Card mounted...\n");
+            break;
+        }
+        sleep(1);
+    }
+    remove("/sdcard/clockworkmod/.recoverycheckpoint");
+    if (i == 0) {
+        ui_print("Timed out waiting for SD card... continuing anyways.");
+    }
+
+    sprintf(tmp, "/tmp/%s", basename(EXTENDEDCOMMAND_SCRIPT));
+    return run_script(tmp);
+}
+
+void show_nandroid_advanced_restore_menu()
+{
+    if (ensure_path_mounted("/sdcard") != 0) {
+        LOGE ("Can't mount /sdcard\n");
+        return;
+    }
+
+    static char* advancedheaders[] = {  "Choose an image to restore",
+                                "",
+                                "Choose an image to restore",
+                                "first. The next menu will",
+                                "you more options.",
+                                "",
+                                NULL
+    };
+
+    char* file = choose_file_menu("/sdcard/clockworkmod/backup/", NULL, advancedheaders);
+    if (file == NULL)
+        return;
+
+    static char* headers[] = {  "Nandroid Advanced Restore",
+                                "",
+                                NULL
+    };
+
+    static char* list[] = { "Restore boot",
+                            "Restore system",
+                            "Restore data",
+                            "Restore cache",
+                            "Restore sd-ext",
+                            "Restore wimax",
+                            NULL
+    };
+    
+    char tmp[PATH_MAX];
+    if (0 != get_partition_device("wimax", tmp)) {
+        // disable wimax restore option
+        list[5] = NULL;
+    }
+
+    static char* confirm_restore  = "Confirm restore?";
+
+    int chosen_item = get_menu_selection(headers, list, 0, 0);
+    switch (chosen_item)
+    {
+        case 0:
+            if (confirm_selection(confirm_restore, "Yes - Restore boot"))
+                nandroid_restore(file, 1, 0, 0, 0, 0, 0);
+            break;
+        case 1:
+            if (confirm_selection(confirm_restore, "Yes - Restore system"))
+                nandroid_restore(file, 0, 1, 0, 0, 0, 0);
+            break;
+        case 2:
+            if (confirm_selection(confirm_restore, "Yes - Restore data"))
+                nandroid_restore(file, 0, 0, 1, 0, 0, 0);
+            break;
+        case 3:
+            if (confirm_selection(confirm_restore, "Yes - Restore cache"))
+                nandroid_restore(file, 0, 0, 0, 1, 0, 0);
+            break;
+        case 4:
+            if (confirm_selection(confirm_restore, "Yes - Restore sd-ext"))
+                nandroid_restore(file, 0, 0, 0, 0, 1, 0);
+            break;
+        case 5:
+            if (confirm_selection(confirm_restore, "Yes - Restore wimax"))
+                nandroid_restore(file, 0, 0, 0, 0, 0, 1);
+            break;
+    }
+}
+
+void show_nandroid_menu()
+{
+    static char* headers[] = {  "Nandroid",
+                                "",
+                                NULL
+    };
+
+    static char* list[] = { "Backup",
+                            "Restore",
+                            "Advanced Restore",
+                            NULL
+    };
+
+    int chosen_item = get_menu_selection(headers, list, 0, 0);
+    switch (chosen_item)
+    {
+        case 0:
+            {
+                char backup_path[PATH_MAX];
+                time_t t = time(NULL);
+                struct tm *tmp = localtime(&t);
+                if (tmp == NULL)
+                {
+                    struct timeval tp;
+                    gettimeofday(&tp, NULL);
+                    sprintf(backup_path, "/sdcard/clockworkmod/backup/%d", tp.tv_sec);
+                }
+                else
+                {
+                    strftime(backup_path, sizeof(backup_path), "/sdcard/clockworkmod/backup/%F.%H.%M.%S", tmp);
+                }
+                nandroid_backup(backup_path);
+            }
+            break;
+        case 1:
+            show_nandroid_restore_menu();
+            break;
+        case 2:
+            show_nandroid_advanced_restore_menu();
+            break;
+    }
+}
+
+void wipe_battery_stats()
+{
+    ensure_path_mounted("/data");
+    remove("/data/system/batterystats.bin");
+    ensure_path_unmounted("/data");
+}
+
+void show_advanced_menu()
+{
+    static char* headers[] = {  "Advanced and Debugging Menu",
+                                "",
+                                NULL
+    };
+
+    static char* list[] = { "Reboot Recovery",
+                            "Wipe Dalvik Cache",
+                            "Wipe Battery Stats",
+                            "Report Error",
+                            "Key Test",
+#ifndef BOARD_HAS_SMALL_RECOVERY
+                            "Partition SD Card",
+                            "Fix Permissions",
+#ifdef BOARD_HAS_SDCARD_INTERNAL
+                            "Partition Internal SD Card",
+#endif
+#endif
+                            NULL
+    };
+
+    for (;;)
+    {
+        int chosen_item = get_menu_selection(headers, list, 0, 0);
+        if (chosen_item == GO_BACK)
+            break;
+        switch (chosen_item)
+        {
+            case 0:
+                __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, "recovery");
+                break;
+            case 1:
+            {
+                if (0 != ensure_path_mounted("/data"))
+                    break;
+                ensure_path_mounted("/sd-ext");
+                ensure_path_mounted("/cache");
+                if (confirm_selection( "Confirm wipe?", "Yes - Wipe Dalvik Cache")) {
+                    __system("rm -r /data/dalvik-cache");
+                    __system("rm -r /cache/dalvik-cache");
+                    __system("rm -r /sd-ext/dalvik-cache");
+                }
+                ensure_path_unmounted("/data");
+                ui_print("Dalvik Cache wiped.\n");
+                break;
+            }
+            case 2:
+            {
+                if (confirm_selection( "Confirm wipe?", "Yes - Wipe Battery Stats"))
+                    wipe_battery_stats();
+                break;
+            }
+            case 3:
+                handle_failure(1);
+                break;
+            case 4:
+            {
+                ui_print("Outputting key codes.\n");
+                ui_print("Go back to end debugging.\n");
+                int key;
+                int action;
+                do
+                {
+                    key = ui_wait_key();
+                    action = device_handle_key(key, 1);
+                    ui_print("Key: %d\n", key);
+                }
+                while (action != GO_BACK);
+                break;
+            }
+            case 5:
+            {
+                static char* ext_sizes[] = { "128M",
+                                             "256M",
+                                             "512M",
+                                             "1024M",
+                                             "2048M",
+                                             "4096M",
+                                             NULL };
+
+                static char* swap_sizes[] = { "0M",
+                                              "32M",
+                                              "64M",
+                                              "128M",
+                                              "256M",
+                                              NULL };
+
+                static char* ext_headers[] = { "Ext Size", "", NULL };
+                static char* swap_headers[] = { "Swap Size", "", NULL };
+
+                int ext_size = get_menu_selection(ext_headers, ext_sizes, 0, 0);
+                if (ext_size == GO_BACK)
+                    continue;
+
+                int swap_size = get_menu_selection(swap_headers, swap_sizes, 0, 0);
+                if (swap_size == GO_BACK)
+                    continue;
+
+                char sddevice[256];
+                Volume *vol = volume_for_path("/sdcard");
+                strcpy(sddevice, vol->device);
+                // we only want the mmcblk, not the partition
+                sddevice[strlen("/dev/block/mmcblkX")] = NULL;
+                char cmd[PATH_MAX];
+                setenv("SDPATH", sddevice, 1);
+                sprintf(cmd, "sdparted -es %s -ss %s -efs ext3 -s", ext_sizes[ext_size], swap_sizes[swap_size]);
+                ui_print("Partitioning SD Card... please wait...\n");
+                if (0 == __system(cmd))
+                    ui_print("Done!\n");
+                else
+                    ui_print("An error occured while partitioning your SD Card. Please see /tmp/recovery.log for more details.\n");
+                break;
+            }
+            case 6:
+            {
+                ensure_path_mounted("/system");
+                ensure_path_mounted("/data");
+                ui_print("Fixing permissions...\n");
+                __system("fix_permissions");
+                ui_print("Done!\n");
+                break;
+            }
+            case 7:
+            {
+                static char* ext_sizes[] = { "128M",
+                                             "256M",
+                                             "512M",
+                                             "1024M",
+                                             "2048M",
+                                             "4096M",
+                                             NULL };
+
+                static char* swap_sizes[] = { "0M",
+                                              "32M",
+                                              "64M",
+                                              "128M",
+                                              "256M",
+                                              NULL };
+
+                static char* ext_headers[] = { "Data Size", "", NULL };
+                static char* swap_headers[] = { "Swap Size", "", NULL };
+
+                int ext_size = get_menu_selection(ext_headers, ext_sizes, 0, 0);
+                if (ext_size == GO_BACK)
+                    continue;
+
+                int swap_size = 0;
+                if (swap_size == GO_BACK)
+                    continue;
+
+                char sddevice[256];
+                Volume *vol = volume_for_path("/emmc");
+                strcpy(sddevice, vol->device);
+                // we only want the mmcblk, not the partition
+                sddevice[strlen("/dev/block/mmcblkX")] = NULL;
+                char cmd[PATH_MAX];
+                setenv("SDPATH", sddevice, 1);
+                sprintf(cmd, "sdparted -es %s -ss %s -efs ext3 -s", ext_sizes[ext_size], swap_sizes[swap_size]);
+                ui_print("Partitioning Internal SD Card... please wait...\n");
+                if (0 == __system(cmd))
+                    ui_print("Done!\n");
+                else
+                    ui_print("An error occured while partitioning your Internal SD Card. Please see /tmp/recovery.log for more details.\n");
+                break;
+            }
+        }
+    }
+}
+
+void write_fstab_root(char *path, FILE *file)
+{
+    Volume *vol = volume_for_path(path);
+    if (vol == NULL) {
+        LOGW("Unable to get recovery.fstab info for %s during fstab generation!\n", path);
+        return;
+    }
+
+    char device[200];
+    if (vol->device[0] != '/')
+        get_partition_device(vol->device, device);
+    else
+        strcpy(device, vol->device);
+
+    fprintf(file, "%s ", device);
+    fprintf(file, "%s ", path);
+    fprintf(file, "%s rw\n", vol->fs_type);
+}
+
+void create_fstab()
+{
+    struct stat info;
+    __system("touch /etc/mtab");
+    FILE *file = fopen("/etc/fstab", "w");
+    if (file == NULL) {
+        LOGW("Unable to create /etc/fstab!\n");
+        return;
+    }
+    write_fstab_root("/cache", file);
+    write_fstab_root("/data", file);
+    if (has_datadata()) {
+        write_fstab_root("/datadata", file);
+    }
+    write_fstab_root("/system", file);
+    write_fstab_root("/sdcard", file);
+    write_fstab_root("/sd-ext", file);
+    fclose(file);
+    LOGI("Completed outputting fstab.\n");
+}
+
+int bml_check_volume(const char *path) {
+    ui_print("Checking %s...\n", path);
+    ensure_path_unmounted(path);
+    if (0 == ensure_path_mounted(path)) {
+        ensure_path_unmounted(path);
+        return 0;
+    }
+    
+    Volume *vol = volume_for_path(path);
+    if (vol == NULL) {
+        LOGE("Unable process volume! Skipping...\n");
+        return 0;
+    }
+    
+    ui_print("%s may be rfs. Checking...\n", path);
+    char tmp[PATH_MAX];
+    sprintf(tmp, "mount -t rfs %s %s", vol->device, path);
+    int ret = __system(tmp);
+    printf("%d\n", ret);
+    return ret == 0 ? 1 : 0;
+}
+
+void process_volumes() {
+    create_fstab();
+    
+    if (device_flash_type() != BML)
+        return;
+
+    ui_print("Checking for ext4 partitions...\n");
+    int ret = 0;
+    ret = bml_check_volume("/system");
+    ret |= bml_check_volume("/data");
+    if (has_datadata())
+        ret |= bml_check_volume("/datadata");
+    ret |= bml_check_volume("/cache");
+    
+    if (ret == 0) {
+        ui_print("Done!\n");
+        return;
+    }
+    
+    char backup_path[PATH_MAX];
+    time_t t = time(NULL);
+    char backup_name[PATH_MAX];
+    struct timeval tp;
+    gettimeofday(&tp, NULL);
+    sprintf(backup_name, "before-ext4-convert-%d", tp.tv_sec);
+    sprintf(backup_path, "/sdcard/clockworkmod/backup/%s", backup_name);
+
+    ui_set_show_text(1);
+    ui_print("Filesystems need to be converted to ext4.\n");
+    ui_print("A backup and restore will now take place.\n");
+    ui_print("If anything goes wrong, your backup will be\n");
+    ui_print("named %s. Try restoring it\n", backup_name);
+    ui_print("in case of error.\n");
+
+    nandroid_backup(backup_path);
+    nandroid_restore(backup_path, 1, 1, 1, 1, 1, 0);
+    ui_set_show_text(0);
+}
+
+void handle_failure(int ret)
+{
+    if (ret == 0)
+        return;
+    if (0 != ensure_path_mounted("/sdcard"))
+        return;
+    mkdir("/sdcard/clockworkmod", S_IRWXU);
+    __system("cp /tmp/recovery.log /sdcard/clockworkmod/recovery.log");
+    ui_print("/tmp/recovery.log was copied to /sdcard/clockworkmod/recovery.log. Please open ROM Manager to report the issue.\n");
+}
+
+int is_path_mounted(const char* path) {
+    Volume* v = volume_for_path(path);
+    if (v == NULL) {
+        return 0;
+    }
+    if (strcmp(v->fs_type, "ramdisk") == 0) {
+        // the ramdisk is always mounted.
+        return 1;
+    }
+
+    int result;
+    result = scan_mounted_volumes();
+    if (result < 0) {
+        LOGE("failed to scan mounted volumes\n");
+        return 0;
+    }
+
+    const MountedVolume* mv =
+        find_mounted_volume_by_mount_point(v->mount_point);
+    if (mv) {
+        // volume is already mounted
+        return 1;
+    }
+    return 0;
+}
+
+int has_datadata() {
+    Volume *vol = volume_for_path("/datadata");
+    return vol != NULL;
+}
+
+int volume_main(int argc, char **argv) {
+    load_volume_table();
+    return 0;
+}
+
+void handle_chargemode() {
+    const char* filename = "/proc/cmdline";
+    struct stat file_info;
+    if (0 != stat(filename, &file_info))
+        return;
+
+    int file_len = file_info.st_size;
+    char* file_data = (char*)malloc(file_len + 1);
+    FILE *file = fopen(filename, "rb");
+    if (file == NULL)
+        return;
+    fread(file_data, file_len, 1, file);
+    // supposedly not necessary, but let's be safe.
+    file_data[file_len] = '\0';
+    fclose(file);
+    
+    if (strstr(file_data, "androidboot.mode=offmode_charging") != NULL)
+        reboot(RB_POWER_OFF);
+ }
\ No newline at end of file
diff --git a/extendedcommands.h b/extendedcommands.h
new file mode 100644
index 0000000..9297f83
--- /dev/null
+++ b/extendedcommands.h
@@ -0,0 +1,47 @@
+extern int signature_check_enabled;
+extern int script_assert_enabled;
+
+void
+toggle_signature_check();
+
+void
+toggle_script_asserts();
+
+void
+show_choose_zip_menu();
+
+int
+do_nandroid_backup(const char* backup_name);
+
+int
+do_nandroid_restore();
+
+void
+show_nandroid_restore_menu();
+
+void
+show_nandroid_menu();
+
+void
+show_partition_menu();
+
+void
+show_choose_zip_menu();
+
+int
+install_zip(const char* packagefilepath);
+
+int
+__system(const char *command);
+
+void
+show_advanced_menu();
+
+int format_unknown_device(const char *device, const char* path, const char *fs_type);
+
+void
+wipe_battery_stats();
+
+void create_fstab();
+
+int has_datadata();
\ No newline at end of file
diff --git a/firmware.h b/firmware.h
new file mode 100644
index 0000000..aeb8f97
--- /dev/null
+++ b/firmware.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _RECOVERY_FIRMWARE_H
+#define _RECOVERY_FIRMWARE_H
+
+/* Save a radio or bootloader update image for later installation.
+ * The type should be one of "hboot" or "radio".
+ * Takes ownership of type and data.  Returns nonzero on error.
+ */
+int remember_firmware_update(const char *type, const char *data, int length);
+
+/* Returns true if a firmware update has been saved. */
+int firmware_update_pending();
+
+/* If an update was saved, reboot into the bootloader now to install it.
+ * Returns 0 if no radio image was defined, nonzero on error,
+ * doesn't return at all on success...
+ */
+int maybe_install_firmware_update(const char *send_intent);
+
+#endif
diff --git a/flashutils/Android.mk b/flashutils/Android.mk
new file mode 100644
index 0000000..324480a
--- /dev/null
+++ b/flashutils/Android.mk
@@ -0,0 +1,98 @@
+LOCAL_PATH := $(call my-dir)
+
+ifneq ($(TARGET_SIMULATOR),true)
+ifeq ($(TARGET_ARCH),arm)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := flashutils.c
+LOCAL_MODULE := libflashutils
+LOCAL_MODULE_TAGS := eng
+LOCAL_C_INCLUDES += bootable/recovery
+LOCAL_STATIC_LIBRARIES := libmmcutils libmtdutils libbmlutils
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := flash_image.c
+LOCAL_MODULE := flash_image
+LOCAL_MODULE_TAGS := eng
+#LOCAL_STATIC_LIBRARIES += $(BOARD_FLASH_LIBRARY)
+LOCAL_STATIC_LIBRARIES := libflashutils libmtdutils libmmcutils libbmlutils
+LOCAL_SHARED_LIBRARIES := libcutils libc
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := dump_image.c
+LOCAL_MODULE := dump_image
+LOCAL_MODULE_TAGS := eng
+LOCAL_STATIC_LIBRARIES := libflashutils libmtdutils libmmcutils libbmlutils
+LOCAL_SHARED_LIBRARIES := libcutils libc
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := erase_image.c
+LOCAL_MODULE := erase_image
+LOCAL_MODULE_TAGS := eng
+LOCAL_STATIC_LIBRARIES := libflashutils libmtdutils libmmcutils libbmlutils
+LOCAL_SHARED_LIBRARIES := libcutils libc
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := flash_image.c
+LOCAL_MODULE := libflash_image
+LOCAL_MODULE_TAGS := eng
+LOCAL_CFLAGS += -Dmain=flash_image_main
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := dump_image.c
+LOCAL_MODULE := libdump_image
+LOCAL_MODULE_TAGS := eng
+LOCAL_CFLAGS += -Dmain=dump_image_main
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := erase_image.c
+LOCAL_MODULE := liberase_image
+LOCAL_MODULE_TAGS := eng
+LOCAL_CFLAGS += -Dmain=erase_image_main
+include $(BUILD_STATIC_LIBRARY)
+
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := dump_image.c
+LOCAL_MODULE := utility_dump_image
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE_CLASS := UTILITY_EXECUTABLES
+LOCAL_MODULE_PATH := $(PRODUCT_OUT)/utilities
+LOCAL_UNSTRIPPED_PATH := $(PRODUCT_OUT)/symbols/utilities
+LOCAL_MODULE_STEM := dump_image
+LOCAL_STATIC_LIBRARIES := libflashutils libmtdutils libmmcutils libbmlutils libcutils libc
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := flash_image.c
+LOCAL_MODULE := utility_flash_image
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE_CLASS := UTILITY_EXECUTABLES
+LOCAL_MODULE_PATH := $(PRODUCT_OUT)/utilities
+LOCAL_UNSTRIPPED_PATH := $(PRODUCT_OUT)/symbols/utilities
+LOCAL_MODULE_STEM := flash_image
+LOCAL_STATIC_LIBRARIES := libflashutils libmtdutils libmmcutils libbmlutils libcutils libc
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := erase_image.c
+LOCAL_MODULE := utility_erase_image
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE_CLASS := UTILITY_EXECUTABLES
+LOCAL_MODULE_PATH := $(PRODUCT_OUT)/utilities
+LOCAL_UNSTRIPPED_PATH := $(PRODUCT_OUT)/symbols/utilities
+LOCAL_MODULE_STEM := erase_image
+LOCAL_STATIC_LIBRARIES := libflashutils libmtdutils libmmcutils libbmlutils libcutils libc
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+include $(BUILD_EXECUTABLE)
+
+endif	# TARGET_ARCH == arm
+endif	# !TARGET_SIMULATOR
diff --git a/flashutils/dump_image.c b/flashutils/dump_image.c
new file mode 100644
index 0000000..4db1f74
--- /dev/null
+++ b/flashutils/dump_image.c
@@ -0,0 +1,150 @@
+/*
+ * 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 <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+#include "cutils/log.h"
+#include "flashutils.h"
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+
+#if 0
+
+#define LOG_TAG "dump_image"
+
+#define BLOCK_SIZE    2048
+#define SPARE_SIZE    (BLOCK_SIZE >> 5)
+
+static int die(const char *msg, ...) {
+    int err = errno;
+    va_list args;
+    va_start(args, msg);
+    char buf[1024];
+    vsnprintf(buf, sizeof(buf), msg, args);
+    va_end(args);
+
+    if (err != 0) {
+        strlcat(buf, ": ", sizeof(buf));
+        strlcat(buf, strerror(err), sizeof(buf));
+    }
+
+    fprintf(stderr, "%s\n", buf);
+    return 1;
+}
+
+/* Read a flash partition and write it to an image file. */
+
+int dump_image(char* partition_name, char* filename, dump_image_callback callback) {
+    MtdReadContext *in;
+    const MtdPartition *partition;
+    char buf[BLOCK_SIZE + SPARE_SIZE];
+    size_t partition_size;
+    size_t read_size;
+    size_t total;
+    int fd;
+    int wrote;
+    int len;
+    
+    if (mtd_scan_partitions() <= 0)
+        return die("error scanning partitions");
+
+    partition = mtd_find_partition_by_name(partition_name);
+    if (partition == NULL)
+        return die("can't find %s partition", partition_name);
+
+    if (mtd_partition_info(partition, &partition_size, NULL, NULL)) {
+        return die("can't get info of partition %s", partition_name);
+    }
+
+    if (!strcmp(filename, "-")) {
+        fd = fileno(stdout);
+    } 
+    else {
+        fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+    }
+
+    if (fd < 0)
+        return die("error opening %s", filename);
+
+    in = mtd_read_partition(partition);
+    if (in == NULL) {
+        close(fd);
+        unlink(filename);
+        return die("error opening %s: %s\n", partition_name, strerror(errno));
+    }
+
+    total = 0;
+    while ((len = mtd_read_data(in, buf, BLOCK_SIZE)) > 0) {
+        wrote = write(fd, buf, len);
+        if (wrote != len) {
+            close(fd);
+            unlink(filename);
+            return die("error writing %s", filename);
+        }
+        total += BLOCK_SIZE;
+        if (callback != NULL)
+            callback(total, partition_size);
+    }
+
+    mtd_read_close(in);
+
+    if (close(fd)) {
+        unlink(filename);
+        return die("error closing %s", filename);
+    }
+    return 0;
+}
+
+int main(int argc, char **argv)
+{
+    ssize_t (*read_func) (MtdReadContext *, char *, size_t);
+    MtdReadContext *in;
+    const MtdPartition *partition;
+    char buf[BLOCK_SIZE + SPARE_SIZE];
+    size_t partition_size;
+    size_t read_size;
+    size_t total;
+    int fd;
+    int wrote;
+    int len;
+
+    if (argc != 3) {
+        fprintf(stderr, "usage: %s partition file.img\n", argv[0]);
+        return 2;
+    }
+
+    return dump_image(argv[1], argv[2], NULL);
+}
+
+#endif
+
+int main(int argc, char **argv)
+{
+    if (argc != 3) {
+        fprintf(stderr, "usage: %s partition file.img\n", argv[0]);
+        return 2;
+    }
+
+    return backup_raw_partition(argv[1], argv[2]);
+}
diff --git a/flashutils/erase_image.c b/flashutils/erase_image.c
new file mode 100644
index 0000000..c495255
--- /dev/null
+++ b/flashutils/erase_image.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Portions Copyright (C) 2010 Magnus Eriksson <packetlss@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+
+#include "cutils/log.h"
+#include "flashutils.h"
+
+#if 0
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+
+
+#define LOG_TAG "erase_image"
+
+static int die(const char *msg, ...) {
+    int err = errno;
+    va_list args;
+    va_start(args, msg);
+    char buf[1024];
+    vsnprintf(buf, sizeof(buf), msg, args);
+    va_end(args);
+
+    if (err != 0) {
+        strlcat(buf, ": ", sizeof(buf));
+        strlcat(buf, strerror(err), sizeof(buf));
+    }
+
+    fprintf(stderr, "%s\n", buf);
+    LOGE("%s\n", buf);
+    return 3;
+}
+
+
+int erase_image(char* partition_name) {
+    MtdWriteContext *out;
+    size_t erased;
+    size_t total_size;
+    size_t erase_size;
+
+    if (mtd_scan_partitions() <= 0) die("error scanning partitions");
+    const MtdPartition *partition = mtd_find_partition_by_name(partition_name);
+    if (partition == NULL) return die("can't find %s partition", partition_name);
+
+    out = mtd_write_partition(partition);
+    if (out == NULL) return die("could not estabilish write context for %s", partition_name);
+
+    // do the actual erase, -1 = full partition erase
+    erased = mtd_erase_blocks(out, -1);
+
+    // erased = bytes erased, if zero, something borked
+    if (!erased) return die("error erasing %s", partition_name);
+
+    return 0;
+}
+
+
+/* Erase a mtd partition */
+
+int main(int argc, char **argv) {
+    if (argc != 2) {
+        fprintf(stderr, "usage: %s <partition>\n", argv[0]);
+        return 2;
+    }
+    
+    return erase_image(argv[1]);
+}
+
+#endif
+
+
+int main(int argc, char **argv)
+{
+    if (argc != 2) {
+        fprintf(stderr, "usage: %s partition\n", argv[0]);
+        return 2;
+    }
+
+    return erase_raw_partition(argv[1]);
+}
diff --git a/flashutils/flash_image.c b/flashutils/flash_image.c
new file mode 100644
index 0000000..3966c42
--- /dev/null
+++ b/flashutils/flash_image.c
@@ -0,0 +1,154 @@
+/*
+ * 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 <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "cutils/log.h"
+
+#if 0
+#define LOG_TAG "flash_image"
+
+#define HEADER_SIZE 2048  // size of header to compare for equality
+
+void die(const char *msg, ...) {
+    int err = errno;
+    va_list args;
+    va_start(args, msg);
+    char buf[1024];
+    vsnprintf(buf, sizeof(buf), msg, args);
+    va_end(args);
+
+    if (err != 0) {
+        strlcat(buf, ": ", sizeof(buf));
+        strlcat(buf, strerror(err), sizeof(buf));
+    }
+
+    fprintf(stderr, "%s\n", buf);
+    LOGE("%s\n", buf);
+    exit(1);
+}
+
+/* Read an image file and write it to a flash partition. */
+
+int main(int argc, char **argv) {
+    const MtdPartition *ptn;
+    MtdWriteContext *write;
+    void *data;
+    unsigned sz;
+
+    if (argc != 3) {
+        fprintf(stderr, "usage: %s partition file.img\n", argv[0]);
+        return 2;
+    }
+
+    if (mtd_scan_partitions() <= 0) die("error scanning partitions");
+    const MtdPartition *partition = mtd_find_partition_by_name(argv[1]);
+    if (partition == NULL) die("can't find %s partition", argv[1]);
+
+    // If the first part of the file matches the partition, skip writing
+
+    int fd = open(argv[2], O_RDONLY);
+    if (fd < 0) die("error opening %s", argv[2]);
+
+    char header[HEADER_SIZE];
+    int headerlen = read(fd, header, sizeof(header));
+    if (headerlen <= 0) die("error reading %s header", argv[2]);
+
+    MtdReadContext *in = mtd_read_partition(partition);
+    if (in == NULL) {
+        LOGW("error opening %s: %s\n", argv[1], strerror(errno));
+        // just assume it needs re-writing
+    } else {
+        char check[HEADER_SIZE];
+        int checklen = mtd_read_data(in, check, sizeof(check));
+        if (checklen <= 0) {
+            LOGW("error reading %s: %s\n", argv[1], strerror(errno));
+            // just assume it needs re-writing
+        } else if (checklen == headerlen && !memcmp(header, check, headerlen)) {
+            LOGI("header is the same, not flashing %s\n", argv[1]);
+            return 0;
+        }
+        mtd_read_close(in);
+    }
+
+    // Skip the header (we'll come back to it), write everything else
+    LOGI("flashing %s from %s\n", argv[1], argv[2]);
+
+    MtdWriteContext *out = mtd_write_partition(partition);
+    if (out == NULL) die("error writing %s", argv[1]);
+
+    char buf[HEADER_SIZE];
+    memset(buf, 0, headerlen);
+    int wrote = mtd_write_data(out, buf, headerlen);
+    if (wrote != headerlen) die("error writing %s", argv[1]);
+
+    int len;
+    while ((len = read(fd, buf, sizeof(buf))) > 0) {
+        wrote = mtd_write_data(out, buf, len);
+        if (wrote != len) die("error writing %s", argv[1]);
+    }
+    if (len < 0) die("error reading %s", argv[2]);
+
+    if (mtd_write_close(out)) die("error closing %s", argv[1]);
+
+    // Now come back and write the header last
+
+    out = mtd_write_partition(partition);
+    if (out == NULL) die("error re-opening %s", argv[1]);
+
+    wrote = mtd_write_data(out, header, headerlen);
+    if (wrote != headerlen) die("error re-writing %s", argv[1]);
+
+    // Need to write a complete block, so write the rest of the first block
+    size_t block_size;
+    if (mtd_partition_info(partition, NULL, &block_size, NULL))
+        die("error getting %s block size", argv[1]);
+
+    if (lseek(fd, headerlen, SEEK_SET) != headerlen)
+        die("error rewinding %s", argv[2]);
+
+    int left = block_size - headerlen;
+    while (left < 0) left += block_size;
+    while (left > 0) {
+        len = read(fd, buf, left > (int)sizeof(buf) ? (int)sizeof(buf) : left);
+        if (len <= 0) die("error reading %s", argv[2]);
+        if (mtd_write_data(out, buf, len) != len)
+            die("error writing %s", argv[1]);
+        left -= len;
+    }
+
+    if (mtd_write_close(out)) die("error closing %s", argv[1]);
+    return 0;
+}
+#endif
+
+int main(int argc, char **argv)
+{
+    if (argc != 3) {
+        fprintf(stderr, "usage: %s partition file.img\n", argv[0]);
+        return 2;
+    }
+
+    int ret = restore_raw_partition(argv[1], argv[2]);
+    if (ret != 0)
+        fprintf(stderr, "failed with error: %d\n", ret);
+    return ret;
+}
diff --git a/flashutils/flashutils.c b/flashutils/flashutils.c
new file mode 100644
index 0000000..b71d4fa
--- /dev/null
+++ b/flashutils/flashutils.c
@@ -0,0 +1,160 @@
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+#include "flashutils/flashutils.h"
+
+int the_flash_type = UNKNOWN;
+
+int device_flash_type()
+{
+    if (the_flash_type == UNKNOWN) {
+        if (access("/dev/block/bml1", F_OK) == 0) {
+            the_flash_type = BML;
+        } else if (access("/proc/emmc", F_OK) == 0) {
+            the_flash_type = MMC;
+        } else if (access("/proc/mtd", F_OK) == 0) {
+            the_flash_type = MTD;
+        } else {
+            the_flash_type = UNSUPPORTED;
+        }
+    }
+    return the_flash_type;
+}
+
+char* get_default_filesystem()
+{
+    return device_flash_type() == MMC ? "ext3" : "yaffs2";
+}
+
+// This was pulled from bionic: The default system command always looks
+// for shell in /system/bin/sh. This is bad.
+#define _PATH_BSHELL "/sbin/sh"
+
+extern char **environ;
+int
+__system(const char *command)
+{
+  pid_t pid;
+    sig_t intsave, quitsave;
+    sigset_t mask, omask;
+    int pstat;
+    char *argp[] = {"sh", "-c", NULL, NULL};
+
+    if (!command)        /* just checking... */
+        return(1);
+
+    argp[2] = (char *)command;
+
+    sigemptyset(&mask);
+    sigaddset(&mask, SIGCHLD);
+    sigprocmask(SIG_BLOCK, &mask, &omask);
+    switch (pid = vfork()) {
+    case -1:            /* error */
+        sigprocmask(SIG_SETMASK, &omask, NULL);
+        return(-1);
+    case 0:                /* child */
+        sigprocmask(SIG_SETMASK, &omask, NULL);
+        execve(_PATH_BSHELL, argp, environ);
+    _exit(127);
+  }
+
+    intsave = (sig_t)  bsd_signal(SIGINT, SIG_IGN);
+    quitsave = (sig_t) bsd_signal(SIGQUIT, SIG_IGN);
+    pid = waitpid(pid, (int *)&pstat, 0);
+    sigprocmask(SIG_SETMASK, &omask, NULL);
+    (void)bsd_signal(SIGINT, intsave);
+    (void)bsd_signal(SIGQUIT, quitsave);
+    return (pid == -1 ? -1 : pstat);
+}
+
+int restore_raw_partition(const char *partition, const char *filename)
+{
+    int type = device_flash_type();
+    switch (type) {
+        case MTD:
+            return cmd_mtd_restore_raw_partition(partition, filename);
+        case MMC:
+            return cmd_mmc_restore_raw_partition(partition, filename);
+        case BML:
+            return cmd_bml_restore_raw_partition(partition, filename);
+        default:
+            return -1;
+    }
+}
+
+int backup_raw_partition(const char *partition, const char *filename)
+{
+    int type = device_flash_type();
+    switch (type) {
+        case MTD:
+            return cmd_mtd_backup_raw_partition(partition, filename);
+        case MMC:
+            return cmd_mmc_backup_raw_partition(partition, filename);
+        case BML:
+            return cmd_bml_backup_raw_partition(partition, filename);
+        default:
+            return -1;
+    }
+}
+
+int erase_raw_partition(const char *partition)
+{
+    int type = device_flash_type();
+    switch (type) {
+        case MTD:
+            return cmd_mtd_erase_raw_partition(partition);
+        case MMC:
+            return cmd_mmc_erase_raw_partition(partition);
+        case BML:
+            return cmd_bml_erase_raw_partition(partition);
+        default:
+            return -1;
+    }
+}
+
+int erase_partition(const char *partition, const char *filesystem)
+{
+    int type = device_flash_type();
+    switch (type) {
+        case MTD:
+            return cmd_mtd_erase_partition(partition, filesystem);
+        case MMC:
+            return cmd_mmc_erase_partition(partition, filesystem);
+        case BML:
+            return cmd_bml_erase_partition(partition, filesystem);
+        default:
+            return -1;
+    }
+}
+
+int mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only)
+{
+    int type = device_flash_type();
+    switch (type) {
+        case MTD:
+            return cmd_mtd_mount_partition(partition, mount_point, filesystem, read_only);
+        case MMC:
+            return cmd_mmc_mount_partition(partition, mount_point, filesystem, read_only);
+        case BML:
+            return cmd_bml_mount_partition(partition, mount_point, filesystem, read_only);
+        default:
+            return -1;
+    }
+}
+
+int get_partition_device(const char *partition, char *device)
+{
+    int type = device_flash_type();
+    switch (type) {
+        case MTD:
+            return cmd_mtd_get_partition_device(partition, device);
+        case MMC:
+            return cmd_mmc_get_partition_device(partition, device);
+        case BML:
+            return cmd_bml_get_partition_device(partition, device);
+        default:
+            return -1;
+    }
+}
diff --git a/flashutils/flashutils.h b/flashutils/flashutils.h
new file mode 100644
index 0000000..d5dadcb
--- /dev/null
+++ b/flashutils/flashutils.h
@@ -0,0 +1,51 @@
+#ifndef FLASHUTILS_H
+#define FLASHUTILS_H
+
+int restore_raw_partition(const char *partition, const char *filename);
+int backup_raw_partition(const char *partition, const char *filename);
+int erase_raw_partition(const char *partition);
+int erase_partition(const char *partition, const char *filesystem);
+int mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only);
+int get_partition_device(const char *partition, char *device);
+
+#define FLASH_MTD 0
+#define FLASH_MMC 1
+#define FLASH_BML 2
+
+int is_mtd_device();
+char* get_default_filesystem();
+
+int __system(const char *command);
+
+extern int cmd_mtd_restore_raw_partition(const char *partition, const char *filename);
+extern int cmd_mtd_backup_raw_partition(const char *partition, const char *filename);
+extern int cmd_mtd_erase_raw_partition(const char *partition);
+extern int cmd_mtd_erase_partition(const char *partition, const char *filesystem);
+extern int cmd_mtd_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only);
+extern int cmd_mtd_get_partition_device(const char *partition, char *device);
+
+extern int cmd_mmc_restore_raw_partition(const char *partition, const char *filename);
+extern int cmd_mmc_backup_raw_partition(const char *partition, const char *filename);
+extern int cmd_mmc_erase_raw_partition(const char *partition);
+extern int cmd_mmc_erase_partition(const char *partition, const char *filesystem);
+extern int cmd_mmc_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only);
+extern int cmd_mmc_get_partition_device(const char *partition, char *device);
+
+extern int cmd_bml_restore_raw_partition(const char *partition, const char *filename);
+extern int cmd_bml_backup_raw_partition(const char *partition, const char *filename);
+extern int cmd_bml_erase_raw_partition(const char *partition);
+extern int cmd_bml_erase_partition(const char *partition, const char *filesystem);
+extern int cmd_bml_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only);
+extern int cmd_bml_get_partition_device(const char *partition, char *device);
+
+extern int device_flash_type();
+
+enum flash_type {
+    UNSUPPORTED = -1,
+    UNKNOWN = 0,
+    MTD = 1,
+    MMC = 2,
+    BML = 3
+};
+
+#endif
\ No newline at end of file
diff --git a/install.c b/install.c
new file mode 100644
index 0000000..ad2c21d
--- /dev/null
+++ b/install.c
@@ -0,0 +1,381 @@
+/*
+ * 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.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "install.h"
+#include "mincrypt/rsa.h"
+#include "minui/minui.h"
+#include "minzip/SysUtil.h"
+#include "minzip/Zip.h"
+#include "mounts.h"
+#include "mtdutils/mtdutils.h"
+#include "roots.h"
+#include "verifier.h"
+
+#include "firmware.h"
+
+#include "extendedcommands.h"
+
+
+#define ASSUMED_UPDATE_BINARY_NAME  "META-INF/com/google/android/update-binary"
+#define ASSUMED_UPDATE_SCRIPT_NAME  "META-INF/com/google/android/update-script"
+#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);
+    }
+
+    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) {
+    const ZipEntry* binary_entry =
+            mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
+    if (binary_entry == NULL) {
+        const ZipEntry* update_script_entry =
+                mzFindZipEntry(zip, ASSUMED_UPDATE_SCRIPT_NAME);
+        if (update_script_entry != NULL) {
+            ui_print("Amend scripting (update-script) is no longer supported.\n");
+            ui_print("Amend scripting was deprecated by Google in Android 1.5.\n");
+            ui_print("It was necessary to remove it when upgrading to the ClockworkMod 3.0 Gingerbread based recovery.\n");
+            ui_print("Please switch to Edify scripting (updater-script and update-binary) to create working update zip packages.\n");
+            return INSTALL_UPDATE_BINARY_MISSING;
+        }
+
+        mzCloseZipArchive(zip);
+        return INSTALL_UPDATE_BINARY_MISSING;
+    }
+
+    char* binary = "/tmp/update_binary";
+    unlink(binary);
+    int fd = creat(binary, 0755);
+    if (fd < 0) {
+        mzCloseZipArchive(zip);
+        LOGE("Can't make %s\n", binary);
+        return 1;
+    }
+    bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);
+    close(fd);
+    mzCloseZipArchive(zip);
+
+    if (!ok) {
+        LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME);
+        return 1;
+    }
+
+    int pipefd[2];
+    pipe(pipefd);
+
+    // When executing the update binary contained in the package, the
+    // arguments passed are:
+    //
+    //   - the version number for this interface
+    //
+    //   - an fd to which the program can write in order to update the
+    //     progress bar.  The program can write single-line commands:
+    //
+    //        progress <frac> <secs>
+    //            fill up the next <frac> part of of the progress bar
+    //            over <secs> seconds.  If <secs> is zero, use
+    //            set_progress commands to manually control the
+    //            progress of this segment of the bar
+    //
+    //        set_progress <frac>
+    //            <frac> should be between 0.0 and 1.0; sets the
+    //            progress bar within the segment defined by the most
+    //            recent progress command.
+    //
+    //        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.)
+    //
+    //            (API v3: this command no longer exists.)
+    //
+    //        ui_print <string>
+    //            display <string> on the screen.
+    //
+    //   - the name of the package zip file.
+    //
+
+    char** args = malloc(sizeof(char*) * 5);
+    args[0] = binary;
+    args[1] = EXPAND(RECOVERY_API_VERSION);   // defined in Android.mk
+    args[2] = malloc(10);
+    sprintf(args[2], "%d", pipefd[1]);
+    args[3] = (char*)path;
+    args[4] = NULL;
+
+    pid_t pid = fork();
+    if (pid == 0) {
+        close(pipefd[0]);
+        execv(binary, args);
+        fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
+        _exit(-1);
+    }
+    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) {
+        char* command = strtok(buffer, " \n");
+        if (command == NULL) {
+            continue;
+        } else if (strcmp(command, "progress") == 0) {
+            char* fraction_s = strtok(NULL, " \n");
+            char* seconds_s = strtok(NULL, " \n");
+
+            float fraction = strtof(fraction_s, NULL);
+            int seconds = strtol(seconds_s, NULL, 10);
+
+            ui_show_progress(fraction * (1-VERIFICATION_PROGRESS_FRACTION),
+                             seconds);
+        } else if (strcmp(command, "set_progress") == 0) {
+            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) {
+                ui_print("%s", str);
+            } else {
+                ui_print("\n");
+            }
+        } else {
+            LOGE("unknown command [%s]\n", command);
+        }
+    }
+    fclose(from_child);
+
+    int status;
+    waitpid(pid, &status, 0);
+    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+        LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status));
+        return INSTALL_ERROR;
+    }
+
+    if (firmware_type != NULL) {
+        return handle_firmware_update(firmware_type, firmware_filename, zip);
+    } else {
+        return INSTALL_SUCCESS;
+    }
+    return INSTALL_SUCCESS;
+}
+
+// Reads a file containing one or more public keys as produced by
+// DumpPublicKey:  this is an RSAPublicKey struct as it would appear
+// as a C source literal, eg:
+//
+//  "{64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}"
+//
+// (Note that the braces and commas in this example are actual
+// characters the parser expects to find in the file; the ellipses
+// indicate more numbers omitted from this example.)
+//
+// The file may contain multiple keys in this format, separated by
+// commas.  The last key must not be followed by a comma.
+//
+// Returns NULL if the file failed to parse, or if it contain zero keys.
+static RSAPublicKey*
+load_keys(const char* filename, int* numKeys) {
+    RSAPublicKey* out = NULL;
+    *numKeys = 0;
+
+    FILE* f = fopen(filename, "r");
+    if (f == NULL) {
+        LOGE("opening %s: %s\n", filename, strerror(errno));
+        goto exit;
+    }
+
+    int i;
+    bool done = false;
+    while (!done) {
+        ++*numKeys;
+        out = realloc(out, *numKeys * sizeof(RSAPublicKey));
+        RSAPublicKey* key = out + (*numKeys - 1);
+        if (fscanf(f, " { %i , 0x%x , { %u",
+                   &(key->len), &(key->n0inv), &(key->n[0])) != 3) {
+            goto exit;
+        }
+        if (key->len != RSANUMWORDS) {
+            LOGE("key length (%d) does not match expected size\n", key->len);
+            goto exit;
+        }
+        for (i = 1; i < key->len; ++i) {
+            if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit;
+        }
+        if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit;
+        for (i = 1; i < key->len; ++i) {
+            if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit;
+        }
+        fscanf(f, " } } ");
+
+        // if the line ends in a comma, this file has more keys.
+        switch (fgetc(f)) {
+            case ',':
+                // more keys to come.
+                break;
+
+            case EOF:
+                done = true;
+                break;
+
+            default:
+                LOGE("unexpected character between keys\n");
+                goto exit;
+        }
+    }
+
+    fclose(f);
+    return out;
+
+exit:
+    if (f) fclose(f);
+    free(out);
+    *numKeys = 0;
+    return NULL;
+}
+
+int
+install_package(const char *path)
+{
+    ui_set_background(BACKGROUND_ICON_INSTALLING);
+    ui_print("Finding update package...\n");
+    ui_show_indeterminate_progress();
+    LOGI("Update location: %s\n", path);
+
+    if (ensure_path_mounted(path) != 0) {
+        LOGE("Can't mount %s\n", path);
+        return INSTALL_CORRUPT;
+    }
+
+    ui_print("Opening update package...\n");
+
+    int err;
+
+    if (signature_check_enabled) {
+        int numKeys;
+        RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
+        if (loadedKeys == NULL) {
+            LOGE("Failed to load keys\n");
+            return INSTALL_CORRUPT;
+        }
+        LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE);
+
+        // Give verification half the progress bar...
+        ui_print("Verifying update package...\n");
+        ui_show_progress(
+                VERIFICATION_PROGRESS_FRACTION,
+                VERIFICATION_PROGRESS_TIME);
+
+        err = verify_file(path, loadedKeys, numKeys);
+        free(loadedKeys);
+        LOGI("verify_file returned %d\n", err);
+        if (err != VERIFY_SUCCESS) {
+            LOGE("signature verification failed\n");
+            return INSTALL_CORRUPT;
+        }
+    }
+
+    /* Try to open the package.
+     */
+    ZipArchive zip;
+    err = mzOpenZipArchive(path, &zip);
+    if (err != 0) {
+        LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad");
+        return INSTALL_CORRUPT;
+    }
+
+    /* Verify and install the contents of the package.
+     */
+    ui_print("Installing update...\n");
+    return try_update_binary(path, &zip);
+}
diff --git a/install.h b/install.h
new file mode 100644
index 0000000..ec97d39
--- /dev/null
+++ b/install.h
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#ifndef RECOVERY_INSTALL_H_
+#define RECOVERY_INSTALL_H_
+
+#include "common.h"
+
+enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_UPDATE_SCRIPT_MISSING, INSTALL_UPDATE_BINARY_MISSING };
+int install_package(const char *root_path);
+
+#endif  // RECOVERY_INSTALL_H_
diff --git a/killrecovery.sh b/killrecovery.sh
new file mode 100755
index 0000000..352cb4b
--- /dev/null
+++ b/killrecovery.sh
@@ -0,0 +1,22 @@
+#!/sbin/sh
+mkdir -p /sd-ext
+rm /cache/recovery/command
+rm /cache/update.zip
+touch /tmp/.ignorebootmessage
+kill $(ps | grep /sbin/adbd)
+kill $(ps | grep /sbin/recovery)
+
+# On the Galaxy S, the recovery comes test signed, but the
+# recovery is not automatically restarted.
+if [ -f /init.smdkc110.rc ]
+then
+    /sbin/recovery &
+fi
+
+# Droid X
+if [ -f /init.mapphone_cdma.rc ]
+then
+    /sbin/recovery &
+fi
+
+exit 1
diff --git a/minui/Android.mk b/minui/Android.mk
new file mode 100644
index 0000000..d71cea2
--- /dev/null
+++ b/minui/Android.mk
@@ -0,0 +1,20 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := graphics.c events.c resources.c
+
+LOCAL_C_INCLUDES +=\
+    external/libpng\
+    external/zlib
+
+ifneq ($(BOARD_LDPI_RECOVERY),)
+    LOCAL_CFLAGS += -DBOARD_LDPI_RECOVERY='"$(BOARD_LDPI_RECOVERY)"'
+endif
+
+ifneq ($(BOARD_HAS_JANKY_BACKBUFFER),)
+    LOCAL_CFLAGS += -DBOARD_HAS_JANKY_BACKBUFFER
+endif
+
+LOCAL_MODULE := libminui
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/minui/events.c b/minui/events.c
new file mode 100644
index 0000000..efdca95
--- /dev/null
+++ b/minui/events.c
@@ -0,0 +1,364 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/poll.h>
+#include <limits.h>
+
+#include <linux/input.h>
+
+#include "../common.h"
+
+#include "minui.h"
+
+#define MAX_DEVICES 16
+
+#define VIBRATOR_TIMEOUT_FILE	"/sys/class/timed_output/vibrator/enable"
+#define VIBRATOR_TIME_MS	50
+
+#define PRESS_THRESHHOLD    10
+
+#define ABS_MT_POSITION_X 0x35
+#define ABS_MT_POSITION_Y 0x36
+#define ABS_MT_TOUCH_MAJOR 0x30
+#define SYN_MT_REPORT 2
+
+struct virtualkey {
+    int scancode;
+    int centerx, centery;
+    int width, height;
+};
+
+struct position {
+    int x, y;
+    int pressed;
+    struct input_absinfo xi, yi;
+};
+
+struct ev {
+    struct pollfd *fd;
+
+    struct virtualkey *vks;
+    int vk_count;
+
+    struct position p, mt_p;
+    int sent, mt_idx;
+};
+
+static struct pollfd ev_fds[MAX_DEVICES];
+static struct ev evs[MAX_DEVICES];
+static unsigned ev_count = 0;
+
+static inline int ABS(int x) {
+    return x<0?-x:x;
+}
+
+int vibrate(int timeout_ms)
+{
+    char str[20];
+    int fd;
+    int ret;
+
+    fd = open(VIBRATOR_TIMEOUT_FILE, O_WRONLY);
+    if (fd < 0)
+        return -1;
+
+    ret = snprintf(str, sizeof(str), "%d", timeout_ms);
+    ret = write(fd, str, ret);
+    close(fd);
+
+    if (ret < 0)
+       return -1;
+
+    return 0;
+}
+
+/* Returns empty tokens */
+static char *vk_strtok_r(char *str, const char *delim, char **save_str)
+{
+    if(!str) {
+        if(!*save_str) return NULL;
+        str = (*save_str) + 1;
+    }
+    *save_str = strpbrk(str, delim);
+    if(*save_str) **save_str = '\0';
+    return str;
+}
+
+static int vk_init(struct ev *e)
+{
+    char vk_path[PATH_MAX] = "/sys/board_properties/virtualkeys.";
+    char vks[2048], *ts;
+    ssize_t len;
+    int vk_fd;
+    int i;
+
+    e->vk_count = 0;
+
+    len = strlen(vk_path);
+    len = ioctl(e->fd->fd, EVIOCGNAME(sizeof(vk_path) - len), vk_path + len);
+    if (len <= 0)
+        return -1;
+
+    vk_fd = open(vk_path, O_RDONLY);
+    if (vk_fd < 0)
+        return -1;
+
+    len = read(vk_fd, vks, sizeof(vks)-1);
+    close(vk_fd);
+    if (len <= 0)
+        return -1;
+
+    vks[len] = '\0';
+
+    /* Parse a line like:
+        keytype:keycode:centerx:centery:width:height:keytype2:keycode2:centerx2:...
+    */
+    for (ts = vks, e->vk_count = 1; *ts; ++ts) {
+        if (*ts == ':')
+            ++e->vk_count;
+    }
+
+    if (e->vk_count % 6) {
+        LOGW("minui: %s is %d %% 6\n", vk_path, e->vk_count % 6);
+    }
+    e->vk_count /= 6;
+    if (e->vk_count <= 0)
+        return -1;
+
+    e->sent = 0;
+    e->mt_idx = 0;
+
+    ioctl(e->fd->fd, EVIOCGABS(ABS_X), &e->p.xi);
+    ioctl(e->fd->fd, EVIOCGABS(ABS_Y), &e->p.yi);
+    e->p.pressed = 0;
+
+    ioctl(e->fd->fd, EVIOCGABS(ABS_MT_POSITION_X), &e->mt_p.xi);
+    ioctl(e->fd->fd, EVIOCGABS(ABS_MT_POSITION_Y), &e->mt_p.yi);
+    e->mt_p.pressed = 0;
+
+    e->vks = malloc(sizeof(*e->vks) * e->vk_count);
+
+    for (i = 0; i < e->vk_count; ++i) {
+        char *token[6];
+        int j;
+
+        for (j = 0; j < 6; ++j) {
+            token[j] = vk_strtok_r((i||j)?NULL:vks, ":", &ts);
+        }
+
+        if (strcmp(token[0], "0x01") != 0) {
+            /* Java does string compare, so we do too. */
+            LOGW("minui: %s: ignoring unknown virtual key type %s\n", vk_path, token[0]);
+            continue;
+        }
+
+        e->vks[i].scancode = strtol(token[1], NULL, 0);
+        e->vks[i].centerx = strtol(token[2], NULL, 0);
+        e->vks[i].centery = strtol(token[3], NULL, 0);
+        e->vks[i].width = strtol(token[4], NULL, 0);
+        e->vks[i].height = strtol(token[5], NULL, 0);
+    }
+
+    return 0;
+}
+
+int ev_init(void)
+{
+    DIR *dir;
+    struct dirent *de;
+    int fd;
+
+    dir = opendir("/dev/input");
+    if(dir != 0) {
+        while((de = readdir(dir))) {
+//            fprintf(stderr,"/dev/input/%s\n", de->d_name);
+            if(strncmp(de->d_name,"event",5)) continue;
+            fd = openat(dirfd(dir), de->d_name, O_RDONLY);
+            if(fd < 0) continue;
+
+            ev_fds[ev_count].fd = fd;
+            ev_fds[ev_count].events = POLLIN;
+            evs[ev_count].fd = &ev_fds[ev_count];
+
+            /* Load virtualkeys if there are any */
+            vk_init(&evs[ev_count]);
+
+            ev_count++;
+            if(ev_count == MAX_DEVICES) break;
+        }
+    }
+
+    return 0;
+}
+
+void ev_exit(void)
+{
+    while (ev_count-- > 0) {
+	if (evs[ev_count].vk_count) {
+		free(evs[ev_count].vks);
+		evs[ev_count].vk_count = 0;
+	}
+        close(ev_fds[ev_count].fd);
+    }
+}
+
+static int vk_inside_display(__s32 value, struct input_absinfo *info, int screen_size)
+{
+    int screen_pos;
+
+    if (info->minimum == info->maximum)
+        return 0;
+
+    screen_pos = (value - info->minimum) * (screen_size - 1) / (info->maximum - info->minimum);
+    return (screen_pos >= 0 && screen_pos < screen_size);
+}
+
+static int vk_tp_to_screen(struct position *p, int *x, int *y)
+{
+    if (p->xi.minimum == p->xi.maximum || p->yi.minimum == p->yi.maximum)
+        return 0;
+
+    *x = (p->x - p->xi.minimum) * (gr_fb_width() - 1) / (p->xi.maximum - p->xi.minimum);
+    *y = (p->y - p->yi.minimum) * (gr_fb_height() - 1) / (p->yi.maximum - p->yi.minimum);
+
+    if (*x >= 0 && *x < gr_fb_width() &&
+           *y >= 0 && *y < gr_fb_height()) {
+        return 0;
+    }
+
+    return 1;
+}
+
+/* Translate a virtual key in to a real key event, if needed */
+/* Returns non-zero when the event should be consumed */
+static int vk_modify(struct ev *e, struct input_event *ev)
+{
+    int i;
+    int x, y;
+
+    if (ev->type == EV_KEY) {
+        if (ev->code == BTN_TOUCH)
+            e->p.pressed = ev->value;
+        return 0;
+    }
+
+    if (ev->type == EV_ABS) {
+        switch (ev->code) {
+        case ABS_X:
+            e->p.x = ev->value;
+            return !vk_inside_display(e->p.x, &e->p.xi, gr_fb_width());
+        case ABS_Y:
+            e->p.y = ev->value;
+            return !vk_inside_display(e->p.y, &e->p.yi, gr_fb_height());
+        case ABS_MT_POSITION_X:
+            if (e->mt_idx) return 1;
+            e->mt_p.x = ev->value;
+            return !vk_inside_display(e->mt_p.x, &e->mt_p.xi, gr_fb_width());
+        case ABS_MT_POSITION_Y:
+            if (e->mt_idx) return 1;
+            e->mt_p.y = ev->value;
+            return !vk_inside_display(e->mt_p.y, &e->mt_p.yi, gr_fb_height());
+        case ABS_MT_TOUCH_MAJOR:
+            if (e->mt_idx) return 1;
+            if (e->sent)
+                e->mt_p.pressed = (ev->value > 0);
+            else
+                e->mt_p.pressed = (ev->value > PRESS_THRESHHOLD);
+            return 0;
+        }
+
+        return 0;
+    }
+
+    if (ev->type != EV_SYN)
+        return 0;
+
+    if (ev->code == SYN_MT_REPORT) {
+        /* Ignore the rest of the points */
+        ++e->mt_idx;
+        return 1;
+    }
+    if (ev->code != SYN_REPORT)
+        return 0;
+
+    /* Report complete */
+
+    e->mt_idx = 0;
+
+    if (!e->p.pressed && !e->mt_p.pressed) {
+        /* No touch */
+        e->sent = 0;
+        return 0;
+    }
+
+    if (!(e->p.pressed && vk_tp_to_screen(&e->p, &x, &y)) &&
+            !(e->mt_p.pressed && vk_tp_to_screen(&e->mt_p, &x, &y))) {
+        /* No touch inside vk area */
+        return 0;
+    }
+
+    if (e->sent) {
+        /* We've already sent a fake key for this touch */
+        return 1;
+    }
+
+    /* The screen is being touched on the vk area */
+    e->sent = 1;
+
+    for (i = 0; i < e->vk_count; ++i) {
+        int xd = ABS(e->vks[i].centerx - x);
+        int yd = ABS(e->vks[i].centery - y);
+        if (xd < e->vks[i].width/2 && yd < e->vks[i].height/2) {
+            /* Fake a key event */
+            ev->type = EV_KEY;
+            ev->code = e->vks[i].scancode;
+            ev->value = 1;
+
+            vibrate(VIBRATOR_TIME_MS);
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+int ev_get(struct input_event *ev, unsigned dont_wait)
+{
+    int r;
+    unsigned n;
+
+    do {
+        r = poll(ev_fds, ev_count, dont_wait ? 0 : -1);
+
+        if(r > 0) {
+            for(n = 0; n < ev_count; n++) {
+                if(ev_fds[n].revents & POLLIN) {
+                    r = read(ev_fds[n].fd, ev, sizeof(*ev));
+                    if(r == sizeof(*ev)) {
+                        if (!vk_modify(&evs[n], ev))
+                            return 0;
+                    }
+                }
+            }
+        }
+    } while(dont_wait == 0);
+
+    return -1;
+}
diff --git a/minui/font_10x18.h b/minui/font_10x18.h
new file mode 100644
index 0000000..7f96465
--- /dev/null
+++ b/minui/font_10x18.h
@@ -0,0 +1,214 @@
+struct {
+  unsigned width;
+  unsigned height;
+  unsigned cwidth;
+  unsigned cheight;
+  unsigned char rundata[];
+} font = {
+  .width = 960,
+  .height = 18,
+  .cwidth = 10,
+  .cheight = 18,
+  .rundata = {
+0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x55,0x82,0x06,0x82,0x02,0x82,0x10,0x82,
+0x11,0x83,0x08,0x82,0x0a,0x82,0x04,0x82,0x46,0x82,0x08,0x82,0x07,0x84,0x06,
+0x84,0x0a,0x81,0x03,0x88,0x04,0x84,0x04,0x88,0x04,0x84,0x06,0x84,0x1e,0x81,
+0x0e,0x81,0x0a,0x84,0x06,0x84,0x07,0x82,0x05,0x85,0x07,0x84,0x04,0x86,0x04,
+0x88,0x02,0x88,0x04,0x84,0x04,0x82,0x04,0x82,0x02,0x88,0x05,0x86,0x01,0x82,
+0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x84,0x04,
+0x86,0x06,0x84,0x04,0x86,0x06,0x84,0x04,0x88,0x02,0x82,0x04,0x82,0x02,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,
+0x88,0x03,0x86,0x0e,0x86,0x06,0x82,0x11,0x82,0x10,0x82,0x18,0x82,0x0f,0x84,
+0x0d,0x82,0x1c,0x82,0x09,0x84,0x7f,0x16,0x84,0x05,0x82,0x05,0x84,0x07,0x83,
+0x02,0x82,0x19,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x82,0x03,0x86,0x04,
+0x83,0x02,0x82,0x03,0x82,0x01,0x82,0x07,0x82,0x09,0x82,0x06,0x82,0x3e,0x82,
+0x04,0x84,0x06,0x83,0x06,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x03,
+0x82,0x09,0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,
+0x1c,0x82,0x0e,0x82,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x05,0x84,0x04,
+0x82,0x02,0x82,0x05,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,
+0x09,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x04,
+0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x03,0x82,0x02,0x82,
+0x03,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x02,
+0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
+0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x03,0x82,0x08,0x82,0x0c,
+0x82,0x05,0x84,0x11,0x82,0x0f,0x82,0x18,0x82,0x0e,0x82,0x02,0x82,0x0c,0x82,
+0x1c,0x82,0x0b,0x82,0x7f,0x15,0x82,0x08,0x82,0x08,0x82,0x05,0x82,0x01,0x82,
+0x01,0x82,0x19,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x82,0x02,0x82,0x01,
+0x82,0x01,0x82,0x02,0x82,0x01,0x82,0x01,0x82,0x03,0x82,0x01,0x82,0x07,0x82,
+0x08,0x82,0x08,0x82,0x3d,0x82,0x03,0x82,0x02,0x82,0x04,0x84,0x05,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x06,0x83,0x03,0x82,0x08,0x82,0x04,0x81,0x09,0x82,
+0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x1a,0x82,0x10,0x82,0x06,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,
+0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x83,
+0x02,0x83,0x02,0x83,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,
+0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x08,0x82,0x0c,0x82,0x04,0x82,0x02,0x82,
+0x11,0x82,0x0e,0x82,0x18,0x82,0x0e,0x82,0x02,0x82,0x0c,0x82,0x0b,0x82,0x0b,
+0x82,0x02,0x82,0x0b,0x82,0x4d,0x82,0x45,0x82,0x08,0x82,0x08,0x82,0x05,0x82,
+0x02,0x83,0x1a,0x82,0x07,0x81,0x02,0x81,0x07,0x82,0x01,0x82,0x02,0x82,0x01,
+0x82,0x05,0x82,0x01,0x84,0x04,0x82,0x01,0x82,0x07,0x82,0x08,0x82,0x08,0x82,
+0x06,0x82,0x02,0x82,0x06,0x82,0x28,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x01,
+0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x84,0x03,0x82,0x08,0x82,
+0x0d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x19,0x82,0x12,0x82,0x05,
+0x82,0x04,0x82,0x02,0x82,0x02,0x84,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82,
+0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x04,
+0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x83,0x02,0x83,
+0x02,0x84,0x02,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x0b,0x82,0x05,0x82,0x04,0x82,0x02,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,
+0x82,0x04,0x82,0x09,0x82,0x0b,0x82,0x03,0x82,0x04,0x82,0x20,0x82,0x18,0x82,
+0x0e,0x82,0x10,0x82,0x0b,0x82,0x0b,0x82,0x02,0x82,0x0b,0x82,0x4d,0x82,0x45,
+0x82,0x08,0x82,0x08,0x82,0x26,0x82,0x10,0x88,0x01,0x82,0x01,0x82,0x06,0x83,
+0x01,0x82,0x04,0x84,0x08,0x81,0x08,0x82,0x0a,0x82,0x05,0x82,0x02,0x82,0x06,
+0x82,0x28,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x08,0x82,0x04,0x82,
+0x01,0x82,0x03,0x82,0x08,0x82,0x0d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x18,0x82,0x06,0x88,0x06,0x82,0x04,0x82,0x04,0x82,0x02,0x82,0x01,0x85,
+0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x04,0x82,0x02,
+0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,
+0x02,0x82,0x04,0x82,0x08,0x88,0x02,0x84,0x02,0x82,0x02,0x82,0x04,0x82,0x02,
+0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x0b,0x82,
+0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x04,0x84,0x06,
+0x84,0x08,0x82,0x05,0x82,0x09,0x82,0x0b,0x82,0x2b,0x82,0x18,0x82,0x0e,0x82,
+0x10,0x82,0x1c,0x82,0x0b,0x82,0x4d,0x82,0x45,0x82,0x08,0x82,0x08,0x82,0x26,
+0x82,0x11,0x82,0x01,0x82,0x03,0x82,0x01,0x82,0x09,0x82,0x06,0x82,0x12,0x82,
+0x0a,0x82,0x06,0x84,0x07,0x82,0x27,0x82,0x04,0x82,0x04,0x82,0x05,0x82,0x0b,
+0x82,0x07,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x01,0x83,0x04,0x82,0x01,0x83,
+0x08,0x82,0x05,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x05,0x83,0x07,0x83,0x05,
+0x82,0x16,0x82,0x08,0x82,0x03,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,
+0x02,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,
+0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,
+0x08,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,
+0x0a,0x82,0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,
+0x82,0x04,0x84,0x06,0x84,0x08,0x82,0x05,0x82,0x0a,0x82,0x0a,0x82,0x23,0x85,
+0x03,0x82,0x01,0x83,0x06,0x85,0x05,0x83,0x01,0x82,0x04,0x84,0x04,0x86,0x05,
+0x85,0x01,0x81,0x02,0x82,0x01,0x83,0x05,0x84,0x09,0x84,0x02,0x82,0x03,0x82,
+0x06,0x82,0x05,0x81,0x01,0x82,0x01,0x82,0x03,0x82,0x01,0x83,0x06,0x84,0x04,
+0x82,0x01,0x83,0x06,0x83,0x01,0x82,0x02,0x82,0x01,0x84,0x04,0x86,0x03,0x86,
+0x04,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x03,0x87,0x05,0x82,0x08,0x82,0x08,0x82,0x26,0x82,
+0x11,0x82,0x01,0x82,0x04,0x86,0x07,0x82,0x05,0x83,0x12,0x82,0x0a,0x82,0x04,
+0x88,0x02,0x88,0x0c,0x88,0x10,0x82,0x04,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,
+0x06,0x83,0x04,0x82,0x03,0x82,0x03,0x83,0x02,0x82,0x03,0x83,0x02,0x82,0x07,
+0x82,0x06,0x84,0x05,0x82,0x02,0x83,0x05,0x83,0x07,0x83,0x04,0x82,0x18,0x82,
+0x06,0x82,0x04,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x86,0x04,
+0x82,0x08,0x82,0x04,0x82,0x02,0x86,0x04,0x86,0x04,0x82,0x02,0x84,0x02,0x88,
+0x05,0x82,0x0a,0x82,0x03,0x85,0x05,0x82,0x08,0x82,0x01,0x82,0x01,0x82,0x02,
+0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,
+0x04,0x82,0x02,0x82,0x03,0x82,0x05,0x84,0x07,0x82,0x05,0x82,0x04,0x82,0x03,
+0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,0x82,0x05,0x82,0x08,0x82,0x08,0x82,
+0x06,0x82,0x0a,0x82,0x0a,0x82,0x22,0x82,0x03,0x82,0x02,0x83,0x02,0x82,0x04,
+0x82,0x03,0x82,0x03,0x82,0x02,0x83,0x03,0x82,0x02,0x82,0x05,0x82,0x06,0x82,
+0x03,0x83,0x02,0x83,0x02,0x82,0x06,0x82,0x0b,0x82,0x02,0x82,0x02,0x82,0x07,
+0x82,0x05,0x88,0x02,0x83,0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x82,
+0x04,0x82,0x02,0x83,0x03,0x83,0x02,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06,
+0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,
+0x03,0x82,0x04,0x82,0x08,0x82,0x02,0x84,0x09,0x82,0x09,0x84,0x23,0x82,0x11,
+0x82,0x01,0x82,0x06,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x01,0x82,0x11,0x82,
+0x0a,0x82,0x06,0x84,0x07,0x82,0x26,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x08,
+0x83,0x09,0x82,0x03,0x82,0x03,0x82,0x09,0x82,0x02,0x82,0x04,0x82,0x05,0x82,
+0x06,0x82,0x02,0x82,0x05,0x83,0x01,0x82,0x17,0x82,0x16,0x82,0x06,0x82,0x05,
+0x82,0x01,0x82,0x01,0x82,0x02,0x88,0x02,0x82,0x03,0x82,0x03,0x82,0x08,0x82,
+0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,
+0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x01,0x82,0x01,0x82,
+0x02,0x82,0x02,0x84,0x02,0x82,0x04,0x82,0x02,0x86,0x04,0x82,0x04,0x82,0x02,
+0x86,0x09,0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x82,
+0x01,0x82,0x04,0x84,0x07,0x82,0x07,0x82,0x07,0x82,0x0b,0x82,0x09,0x82,0x27,
+0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
+0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,
+0x82,0x01,0x82,0x08,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,
+0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x07,
+0x82,0x0a,0x82,0x06,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x04,0x82,
+0x04,0x84,0x04,0x82,0x04,0x82,0x07,0x82,0x06,0x82,0x08,0x82,0x08,0x82,0x26,
+0x82,0x0f,0x88,0x05,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x02,0x82,0x01,0x82,
+0x0d,0x82,0x0a,0x82,0x05,0x82,0x02,0x82,0x06,0x82,0x26,0x82,0x05,0x82,0x04,
+0x82,0x05,0x82,0x07,0x82,0x0c,0x82,0x02,0x88,0x08,0x82,0x02,0x82,0x04,0x82,
+0x05,0x82,0x05,0x82,0x04,0x82,0x08,0x82,0x18,0x82,0x14,0x82,0x07,0x82,0x05,
+0x82,0x01,0x84,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,
+0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,
+0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x01,0x82,0x01,0x82,
+0x02,0x82,0x02,0x84,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,
+0x82,0x02,0x82,0x0a,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x82,
+0x01,0x82,0x01,0x82,0x04,0x84,0x07,0x82,0x07,0x82,0x07,0x82,0x0b,0x82,0x09,
+0x82,0x22,0x87,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x88,
+0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,
+0x84,0x09,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x08,0x86,0x05,
+0x82,0x06,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,0x82,
+0x05,0x82,0x05,0x82,0x04,0x82,0x06,0x82,0x07,0x82,0x08,0x82,0x08,0x82,0x26,
+0x82,0x10,0x82,0x01,0x82,0x07,0x82,0x01,0x82,0x04,0x82,0x01,0x83,0x02,0x82,
+0x03,0x83,0x0f,0x82,0x08,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x25,0x82,0x07,
+0x82,0x02,0x82,0x06,0x82,0x06,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x09,0x82,
+0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x04,0x82,0x08,0x82,0x19,0x82,0x05,
+0x88,0x05,0x82,0x08,0x82,0x05,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x02,0x82,
+0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x03,0x82,0x03,0x82,0x03,0x82,
+0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02,
+0x82,0x08,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x03,0x82,0x09,0x82,0x05,0x82,
+0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x83,0x02,0x83,0x03,0x82,0x02,0x82,0x06,
+0x82,0x06,0x82,0x08,0x82,0x0c,0x82,0x08,0x82,0x21,0x82,0x04,0x82,0x02,0x82,
+0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a,0x82,0x06,0x82,0x03,
+0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x85,0x08,0x82,0x05,0x82,
+0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0d,0x82,0x04,0x82,0x06,0x82,0x04,0x82,
+0x04,0x84,0x04,0x82,0x01,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x05,
+0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x38,0x82,0x01,0x82,0x04,0x82,0x01,0x82,
+0x01,0x82,0x04,0x84,0x01,0x82,0x01,0x82,0x03,0x82,0x10,0x82,0x08,0x82,0x30,
+0x83,0x06,0x82,0x07,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x08,0x82,0x04,0x82,
+0x07,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x04,
+0x82,0x03,0x81,0x04,0x82,0x1a,0x82,0x10,0x82,0x10,0x82,0x08,0x82,0x04,0x82,
+0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,
+0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x03,0x82,
+0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,
+0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x02,0x84,0x02,0x82,0x03,0x82,0x03,0x82,
+0x04,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x05,0x83,0x02,0x83,0x03,
+0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x09,0x82,0x0c,0x82,0x08,0x82,0x21,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a,
+0x82,0x07,0x85,0x04,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x82,0x02,0x82,
+0x07,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0d,0x82,0x04,0x82,
+0x06,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x82,0x01,0x82,0x04,0x84,0x04,
+0x82,0x04,0x82,0x04,0x82,0x09,0x82,0x08,0x82,0x08,0x82,0x26,0x82,0x10,0x82,
+0x01,0x82,0x05,0x86,0x04,0x82,0x01,0x82,0x01,0x82,0x01,0x83,0x01,0x84,0x10,
+0x82,0x06,0x82,0x1d,0x83,0x11,0x83,0x05,0x82,0x09,0x84,0x07,0x82,0x05,0x82,
+0x09,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x06,0x83,0x07,0x83,0x09,0x82,
+0x0e,0x82,0x0a,0x82,0x06,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x03,
+0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x09,0x82,
+0x02,0x83,0x02,0x82,0x04,0x82,0x05,0x82,0x06,0x82,0x01,0x82,0x04,0x82,0x04,
+0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,
+0x03,0x82,0x09,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x06,
+0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
+0x05,0x82,0x05,0x82,0x09,0x82,0x0d,0x82,0x07,0x82,0x21,0x82,0x04,0x82,0x02,
+0x83,0x02,0x82,0x04,0x82,0x03,0x82,0x03,0x82,0x02,0x83,0x03,0x82,0x03,0x82,
+0x04,0x82,0x06,0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x82,0x03,
+0x82,0x06,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x03,0x82,
+0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x07,0x82,0x04,
+0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x02,0x83,0x05,0x82,0x05,0x88,0x03,0x82,
+0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x0a,0x82,0x08,0x82,0x08,0x82,0x26,
+0x82,0x1c,0x82,0x06,0x82,0x02,0x83,0x03,0x84,0x02,0x82,0x10,0x82,0x04,0x82,
+0x1e,0x83,0x11,0x83,0x05,0x82,0x0a,0x82,0x05,0x88,0x02,0x88,0x04,0x84,0x09,
+0x82,0x05,0x84,0x06,0x84,0x05,0x82,0x09,0x84,0x06,0x84,0x07,0x83,0x07,0x83,
+0x0a,0x81,0x0e,0x81,0x0b,0x82,0x07,0x85,0x03,0x82,0x04,0x82,0x02,0x86,0x06,
+0x84,0x04,0x86,0x04,0x88,0x02,0x82,0x0a,0x84,0x01,0x81,0x02,0x82,0x04,0x82,
+0x02,0x88,0x04,0x83,0x05,0x82,0x04,0x82,0x02,0x88,0x02,0x82,0x04,0x82,0x02,
+0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x0a,0x85,0x03,0x82,0x04,0x82,0x04,0x84,
+0x07,0x82,0x07,0x84,0x07,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,
+0x82,0x05,0x88,0x03,0x86,0x09,0x82,0x03,0x86,0x22,0x85,0x01,0x81,0x02,0x82,
+0x01,0x83,0x06,0x85,0x05,0x83,0x01,0x82,0x04,0x85,0x05,0x82,0x07,0x86,0x03,
+0x82,0x04,0x82,0x02,0x88,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x88,0x02,0x82,
+0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x83,0x06,
+0x83,0x01,0x82,0x03,0x82,0x08,0x86,0x06,0x84,0x05,0x83,0x01,0x82,0x05,0x82,
+0x06,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x04,0x83,0x01,0x82,0x03,0x87,0x06,
+0x84,0x05,0x82,0x05,0x84,0x7f,0x15,0x83,0x7f,0x14,0x83,0x7f,0x5e,0x82,0x7f,
+0x05,0x89,0x47,0x82,0x04,0x82,0x17,0x82,0x03,0x82,0x34,0x82,0x0e,0x82,0x4e,
+0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0a,0x82,0x04,0x82,0x17,0x82,0x03,0x82,
+0x34,0x82,0x0e,0x82,0x48,0x82,0x04,0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0a,
+0x82,0x04,0x82,0x17,0x82,0x03,0x82,0x34,0x82,0x0e,0x82,0x49,0x82,0x02,0x82,
+0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0c,0x86,0x19,0x85,0x35,0x82,0x0e,0x82,0x4a,
+0x84,0x3f,
+0x00,
+  }
+};
diff --git a/minui/font_7x16.h b/minui/font_7x16.h
new file mode 100644
index 0000000..31c94fc
--- /dev/null
+++ b/minui/font_7x16.h
@@ -0,0 +1,15 @@
+struct {
+  unsigned width;
+  unsigned height;
+  unsigned cwidth;
+  unsigned cheight;
+  unsigned char rundata[];
+} font = {
+  .width = 668,
+  .height = 16,
+  .cwidth = 7,
+  .cheight = 16,
+  .rundata = {
+0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7e,0x82,0x03,0x82,0x7f,0x7f,0x5f,0x82,0x0b,0x82,0x14,0x81,0x0b,0x81,0x11,0x81,0x0c,0x82,0x09,0x81,0x08,0x81,0x07,0x81,0x03,0x81,0x06,0x83,0x68,0x83,0x04,0x81,0x04,0x83,0x17,0x81,0x05,0x81,0x01,0x81,0x0c,0x81,0x04,0x82,0x07,0x83,0x04,0x81,0x07,0x81,0x05,0x81,0x06,0x81,0x25,0x81,0x02,0x84,0x02,0x83,0x05,0x84,0x03,0x84,0x05,0x82,0x02,0x85,0x04,0x83,0x02,0x86,0x02,0x84,0x03,0x84,0x27,0x83,0x0b,0x82,0x03,0x85,0x04,0x83,0x02,0x84,0x03,0x86,0x01,0x86,0x03,0x83,0x02,0x81,0x04,0x81,0x01,0x85,0x04,0x83,0x02,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x82,0x03,0x81,0x02,0x84,0x02,0x85,0x03,0x84,0x02,0x85,0x03,0x84,0x01,0x87,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x82,0x05,0x81,0x01,0x81,0x04,0x82,0x05,0x81,0x01,0x86,0x03,0x81,0x04,0x81,0x08,0x81,0x05,0x82,0x0e,0x81,0x0a,0x81,0x11,0x81,0x0b,0x81,0x0b,0x81,0x14,0x81,0x08,0x81,0x37,0x81,0x30,0x81,0x06,0x81,0x06,0x81,0x17,0x81,0x05,0x81,0x01,0x81,0x05,0x81,0x01,0x81,0x03,0x83,0x02,0x81,0x02,0x81,0x05,0x81,0x07,0x81,0x07,0x81,0x05,0x81,0x04,0x81,0x01,0x81,0x01,0x81,0x22,0x81,0x03,0x81,0x02,0x81,0x04,0x81,0x04,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x04,0x82,0x02,0x81,0x07,0x81,0x03,0x81,0x05,0x82,0x01,0x81,0x04,0x81,0x01,0x82,0x02,0x81,0x26,0x81,0x03,0x81,0x03,0x83,0x04,0x82,0x03,0x81,0x04,0x81,0x02,0x81,0x03,0x81,0x01,0x81,0x03,0x81,0x02,0x81,0x06,0x81,0x07,0x81,0x03,0x81,0x01,0x81,0x04,0x81,0x03,0x81,0x08,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x06,0x82,0x02,0x82,0x01,0x82,0x03,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x04,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x01,0x81,0x04,0x82,0x02,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x03,0x81,0x06,0x82,0x03,0x81,0x05,0x81,0x07,0x81,0x04,0x81,0x02,0x81,0x18,0x81,0x11,0x81,0x0b,0x81,0x0b,0x81,0x14,0x81,0x08,0x81,0x37,0x81,0x30,0x81,0x06,0x81,0x06,0x81,0x17,0x81,0x05,0x81,0x01,0x81,0x04,0x81,0x02,0x81,0x02,0x81,0x01,0x81,0x01,0x81,0x01,0x81,0x02,0x81,0x05,0x81,0x07,0x81,0x06,0x81,0x07,0x81,0x04,0x83,0x05,0x81,0x1d,0x81,0x02,0x81,0x04,0x81,0x03,0x81,0x09,0x81,0x06,0x81,0x03,0x81,0x01,0x81,0x02,0x81,0x06,0x81,0x0a,0x81,0x02,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x14,0x81,0x08,0x81,0x0b,0x81,0x02,0x81,0x02,0x82,0x03,0x82,0x03,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x06,0x81,0x06,0x81,0x04,0x81,0x03,0x81,0x08,0x81,0x02,0x81,0x02,0x81,0x03,0x81,0x06,0x82,0x02,0x82,0x01,0x81,0x01,0x81,0x02,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x08,0x81,0x04,0x81,0x04,0x81,0x02,0x81,0x02,0x81,0x01,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x03,0x81,0x01,0x81,0x07,0x81,0x04,0x81,0x05,0x81,0x07,0x81,0x03,0x81,0x04,0x81,0x11,0x83,0x03,0x84,0x04,0x83,0x04,0x84,0x03,0x83,0x03,0x85,0x03,0x84,0x02,0x81,0x01,0x82,0x03,0x83,0x05,0x83,0x03,0x81,0x03,0x81,0x04,0x81,0x04,0x85,0x02,0x81,0x01,0x82,0x04,0x83,0x03,0x84,0x04,0x84,0x03,0x84,0x03,0x83,0x03,0x85,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x01,0x81,0x05,0x81,0x01,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x85,0x04,0x81,0x06,0x81,0x06,0x81,0x17,0x81,0x0b,0x86,0x01,0x81,0x01,0x81,0x04,0x82,0x02,0x81,0x03,0x82,0x0d,0x81,0x07,0x81,0x04,0x83,0x05,0x81,0x1c,0x81,0x03,0x81,0x04,0x81,0x03,0x81,0x09,0x81,0x06,0x81,0x02,0x82,0x01,0x81,0x02,0x85,0x02,0x81,0x01,0x83,0x06,0x81,0x02,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x03,0x81,0x06,0x81,0x06,0x83,0x0a,0x83,0x06,0x82,0x02,0x81,0x04,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x06,0x81,0x06,0x81,0x04,0x81,0x03,0x81,0x08,0x81,0x02,0x81,0x01,0x81,0x04,0x81,0x06,0x81,0x01,0x82,0x01,0x81,0x01,0x81,0x01,0x81,0x02,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x01,0x82,0x07,0x81,0x04,0x81,0x04,0x81,0x02,0x81,0x02,0x81,0x01,0x81,0x01,0x81,0x01,0x81,0x01,0x81,0x03,0x82,0x04,0x81,0x01,0x81,0x06,0x81,0x05,0x81,0x06,0x81,0x06,0x81,0x19,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x82,0x05,0x81,0x03,0x81,0x02,0x82,0x02,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x02,0x82,0x02,0x81,0x04,0x81,0x07,0x81,0x03,0x81,0x02,0x81,0x05,0x81,0x04,0x81,0x01,0x81,0x01,0x81,0x02,0x82,0x02,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x03,0x82,0x02,0x81,0x01,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x01,0x81,0x05,0x81,0x02,0x81,0x01,0x81,0x03,0x81,0x03,0x81,0x06,0x81,0x04,0x81,0x06,0x81,0x06,0x81,0x17,0x81,0x0c,0x81,0x01,0x81,0x03,0x83,0x06,0x82,0x04,0x82,0x0d,0x81,0x07,0x81,0x03,0x81,0x01,0x81,0x01,0x81,0x04,0x81,0x1c,0x81,0x03,0x81,0x02,0x81,0x01,0x81,0x03,0x81,0x08,0x81,0x04,0x83,0x03,0x81,0x02,0x81,0x06,0x82,0x01,0x82,0x02,0x82,0x04,0x81,0x04,0x84,0x02,0x81,0x03,0x82,0x03,0x81,0x06,0x81,0x04,0x82,0x05,0x86,0x05,0x82,0x03,0x82,0x03,0x81,0x02,0x83,0x02,0x81,0x02,0x81,0x02,0x85,0x02,0x81,0x06,0x81,0x04,0x81,0x01,0x86,0x01,0x86,0x01,0x81,0x03,0x82,0x01,0x86,0x03,0x81,0x08,0x81,0x02,0x83,0x04,0x81,0x06,0x81,0x01,0x82,0x01,0x81,0x01,0x81,0x01,0x82,0x01,0x81,0x01,0x81,0x04,0x81,0x01,0x85,0x02,0x81,0x04,0x81,0x01,0x85,0x03,0x84,0x04,0x81,0x04,0x81,0x04,0x81,0x02,0x81,0x02,0x81,0x01,0x81,0x01,0x81,0x01,0x81,0x01,0x81,0x03,0x82,0x05,0x81,0x06,0x82,0x05,0x81,0x06,0x81,0x06,0x81,0x1d,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x06,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x04,0x81,0x07,0x81,0x03,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x01,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x03,0x81,0x05,0x81,0x08,0x81,0x04,0x81,0x03,0x81,0x03,0x81,0x01,0x81,0x03,0x81,0x01,0x81,0x01,0x81,0x03,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x03,0x82,0x07,0x81,0x07,0x82,0x02,0x83,0x10,0x81,0x0c,0x81,0x01,0x81,0x05,0x83,0x02,0x82,0x01,0x82,0x02,0x81,0x02,0x81,0x01,0x81,0x0a,0x81,0x07,0x81,0x05,0x81,0x03,0x87,0x09,0x83,0x0c,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x07,0x81,0x08,0x81,0x01,0x81,0x03,0x81,0x07,0x81,0x01,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x04,0x81,0x02,0x83,0x01,0x81,0x0f,0x82,0x10,0x82,0x03,0x81,0x04,0x81,0x01,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x02,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x03,0x81,0x08,0x81,0x02,0x81,0x02,0x81,0x03,0x81,0x06,0x81,0x01,0x82,0x01,0x81,0x01,0x81,0x02,0x81,0x01,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x03,0x81,0x07,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x02,0x81,0x02,0x81,0x01,0x81,0x01,0x81,0x01,0x81,0x01,0x81,0x03,0x82,0x05,0x81,0x06,0x81,0x06,0x81,0x07,0x81,0x05,0x81,0x1a,0x84,0x02,0x81,0x03,0x81,0x02,0x81,0x06,0x81,0x03,0x81,0x02,0x85,0x04,0x81,0x04,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x04,0x81,0x07,0x81,0x03,0x82,0x07,0x81,0x04,0x81,0x01,0x81,0x01,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x03,0x81,0x06,0x83,0x05,0x81,0x04,0x81,0x03,0x81,0x03,0x81,0x01,0x81,0x03,0x81,0x01,0x81,0x01,0x81,0x04,0x81,0x05,0x81,0x01,0x81,0x05,0x81,0x06,0x81,0x06,0x81,0x06,0x81,0x07,0x83,0x18,0x86,0x04,0x81,0x01,0x81,0x04,0x81,0x02,0x81,0x01,0x81,0x02,0x83,0x0a,0x81,0x07,0x81,0x0c,0x81,0x1b,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x06,0x81,0x09,0x81,0x01,0x86,0x06,0x81,0x01,0x81,0x04,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x06,0x81,0x11,0x83,0x02,0x86,0x02,0x83,0x0a,0x81,0x01,0x81,0x02,0x81,0x02,0x84,0x02,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x03,0x81,0x08,0x81,0x02,0x81,0x02,0x82,0x02,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x02,0x81,0x01,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x06,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x03,0x82,0x03,0x82,0x01,0x82,0x03,0x81,0x02,0x81,0x04,0x81,0x05,0x81,0x07,0x81,0x07,0x81,0x05,0x81,0x19,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x06,0x81,0x03,0x81,0x02,0x81,0x08,0x81,0x04,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x04,0x81,0x07,0x81,0x03,0x81,0x01,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x01,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x03,0x81,0x09,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x03,0x81,0x01,0x81,0x03,0x82,0x01,0x82,0x03,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x07,0x81,0x06,0x81,0x06,0x81,0x17,0x81,0x0b,0x81,0x02,0x81,0x03,0x81,0x01,0x81,0x01,0x81,0x04,0x81,0x02,0x81,0x01,0x82,0x02,0x81,0x0c,0x81,0x05,0x81,0x0d,0x81,0x06,0x81,0x0d,0x81,0x05,0x81,0x06,0x81,0x02,0x81,0x04,0x81,0x05,0x81,0x05,0x81,0x04,0x81,0x05,0x81,0x02,0x81,0x03,0x82,0x02,0x81,0x02,0x82,0x03,0x81,0x04,0x81,0x04,0x81,0x01,0x81,0x03,0x81,0x04,0x81,0x06,0x81,0x09,0x81,0x08,0x81,0x08,0x81,0x04,0x81,0x02,0x83,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x02,0x81,0x03,0x81,0x01,0x81,0x03,0x81,0x02,0x81,0x06,0x81,0x07,0x81,0x03,0x81,0x01,0x81,0x04,0x81,0x03,0x81,0x04,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x06,0x81,0x04,0x81,0x01,0x81,0x03,0x82,0x02,0x81,0x02,0x81,0x02,0x81,0x07,0x81,0x02,0x82,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x03,0x82,0x03,0x81,0x03,0x81,0x03,0x81,0x02,0x81,0x04,0x81,0x04,0x82,0x07,0x81,0x08,0x81,0x04,0x81,0x19,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x82,0x05,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x04,0x81,0x07,0x81,0x03,0x81,0x02,0x81,0x05,0x81,0x04,0x81,0x01,0x81,0x01,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x03,0x81,0x05,0x81,0x03,0x81,0x04,0x81,0x04,0x81,0x03,0x81,0x04,0x81,0x05,0x81,0x01,0x81,0x04,0x81,0x01,0x81,0x04,0x82,0x04,0x81,0x08,0x81,0x06,0x81,0x06,0x81,0x17,0x81,0x0b,0x81,0x01,0x81,0x05,0x83,0x06,0x82,0x03,0x83,0x01,0x81,0x0b,0x81,0x05,0x81,0x0d,0x81,0x06,0x81,0x0d,0x81,0x05,0x81,0x06,0x84,0x02,0x85,0x02,0x86,0x02,0x84,0x06,0x81,0x03,0x84,0x03,0x84,0x03,0x81,0x06,0x84,0x03,0x83,0x05,0x81,0x06,0x81,0x1b,0x81,0x04,0x82,0x05,0x81,0x04,0x81,0x01,0x85,0x04,0x83,0x02,0x84,0x03,0x86,0x01,0x81,0x08,0x83,0x02,0x81,0x04,0x81,0x01,0x85,0x03,0x83,0x03,0x81,0x04,0x81,0x01,0x86,0x01,0x81,0x04,0x81,0x01,0x81,0x03,0x82,0x02,0x84,0x02,0x81,0x07,0x84,0x02,0x81,0x05,0x81,0x01,0x84,0x04,0x81,0x05,0x84,0x04,0x82,0x03,0x81,0x03,0x81,0x02,0x81,0x04,0x81,0x03,0x81,0x04,0x86,0x03,0x81,0x08,0x81,0x04,0x81,0x1a,0x84,0x02,0x84,0x04,0x83,0x04,0x84,0x03,0x83,0x05,0x81,0x05,0x84,0x02,0x81,0x03,0x81,0x02,0x85,0x05,0x81,0x03,0x81,0x03,0x81,0x05,0x82,0x02,0x81,0x01,0x81,0x01,0x81,0x02,0x81,0x03,0x81,0x03,0x83,0x03,0x84,0x04,0x84,0x03,0x81,0x06,0x83,0x05,0x83,0x03,0x84,0x04,0x81,0x05,0x81,0x01,0x81,0x03,0x81,0x03,0x81,0x04,0x81,0x04,0x85,0x04,0x81,0x06,0x81,0x06,0x81,0x2c,0x81,0x1c,0x82,0x03,0x82,0x13,0x81,0x13,0x81,0x54,0x81,0x22,0x81,0x79,0x81,0x43,0x82,0x08,0x81,0x02,0x82,0x47,0x81,0x13,0x81,0x26,0x81,0x0a,0x81,0x35,0x81,0x0d,0x83,0x04,0x81,0x04,0x83,0x2c,0x81,0x7f,0x44,0x83,0x76,0x81,0x7f,0x17,0x81,0x02,0x81,0x13,0x81,0x26,0x81,0x0a,0x81,0x34,0x81,0x15,0x81,0x7f,0x7f,0x7f,0x50,0x87,0x34,0x82,0x12,0x82,0x27,0x81,0x0a,0x81,0x33,0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x4b,0x00,0x49,0x48,0x44,0x52,0x00,0x00,0x00,0x00,0x49,0x44,0x41,0x54,0x00,0x00,0x00,0x00,0x49,0x45,0x4e,0x44,0x00,0x00,0x00,0x00,0x50,0x4c,0x54,0x45,0x00,0x00,0x00,0x00,0x62,0x4b,0x47,0x44,0x00,0x00,0x00,0x00,0x63,0x48,0x52,0x4d,0x00,0x00,0x00,0x00,0x67,0x41,0x4d,0x41,0x00,0x00,0x00,0x00,0x68,0x49,0x53,0x54,0x00,0x00,0x00,0x00,0x69,0x43,0x43,0x50,0x00,0x00,0x00,0x00,0x69,0x54,0x58,0x74,0x00,0x00,0x00,0x00,0x6f,0x46,0x46,0x73,0x00,0x00,0x00,0x00,0x70,0x43,0x41,0x4c,0x00,0x00,0x00,0x00,0x73,0x43,0x41,0x4c,0x00,0x00,0x00,0x00,0x70,0x48,0x59,0x73,0x00,0x00,0x00,0x00,0x73,0x42,0x49,0x54,0x00,0x00,0x00,0x00,0x73,0x50,0x4c,0x54,0x00,0x00,0x00,0x00,0x73,0x52,0x47,0x42,0x00,0x00,0x00,0x00,0x74,0x45,0x58,0x74,0x00,0x00,0x00,0x00,0x74,0x49,0x4d,0x45,0x00,0x00,0x00,0x00,0x74,0x52,0x4e,0x53,0x00,0x00,0x00,0x00,0x7a,0x54,0x58,0x74,0x00, 
+  }
+};
diff --git a/minui/graphics.c b/minui/graphics.c
new file mode 100644
index 0000000..915873b
--- /dev/null
+++ b/minui/graphics.c
@@ -0,0 +1,328 @@
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include <linux/fb.h>
+#include <linux/kd.h>
+
+#include <pixelflinger/pixelflinger.h>
+
+#ifndef BOARD_LDPI_RECOVERY
+	#include "font_10x18.h"
+#else
+	#include "font_7x16.h"
+#endif
+
+#include "minui.h"
+
+typedef struct {
+    GGLSurface texture;
+    unsigned cwidth;
+    unsigned cheight;
+    unsigned ascent;
+} GRFont;
+
+static GRFont *gr_font = 0;
+static GGLContext *gr_context = 0;
+static GGLSurface gr_font_texture;
+static GGLSurface gr_framebuffer[2];
+static GGLSurface gr_mem_surface;
+static unsigned gr_active_fb = 0;
+
+static int gr_fb_fd = -1;
+static int gr_vt_fd = -1;
+
+static struct fb_var_screeninfo vi;
+
+static int get_framebuffer(GGLSurface *fb)
+{
+    int fd;
+    struct fb_fix_screeninfo fi;
+    void *bits;
+
+    fd = open("/dev/graphics/fb0", O_RDWR);
+    if (fd < 0) {
+        perror("cannot open fb0");
+        return -1;
+    }
+
+    if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
+        perror("failed to get fb0 info");
+        close(fd);
+        return -1;
+    }
+
+    if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
+        perror("failed to get fb0 info");
+        close(fd);
+        return -1;
+    }
+
+    bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+    if (bits == MAP_FAILED) {
+        perror("failed to mmap framebuffer");
+        close(fd);
+        return -1;
+    }
+
+    fb->version = sizeof(*fb);
+    fb->width = vi.xres;
+    fb->height = vi.yres;
+#ifdef BOARD_HAS_JANKY_BACKBUFFER
+    fb->stride = fi.line_length/2;
+#else
+    fb->stride = vi.xres;
+#endif
+    fb->data = bits;
+    fb->format = GGL_PIXEL_FORMAT_RGB_565;
+    memset(fb->data, 0, vi.yres * vi.xres * 2);
+
+    fb++;
+
+    fb->version = sizeof(*fb);
+    fb->width = vi.xres;
+    fb->height = vi.yres;
+#ifdef BOARD_HAS_JANKY_BACKBUFFER
+    fb->stride = fi.line_length/2;
+    fb->data = (void*) (((unsigned) bits) + vi.yres * fi.line_length);
+#else
+    fb->stride = vi.xres;
+    fb->data = (void*) (((unsigned) bits) + vi.yres * vi.xres * 2);
+#endif
+    fb->format = GGL_PIXEL_FORMAT_RGB_565;
+    memset(fb->data, 0, vi.yres * vi.xres * 2);
+
+    return fd;
+}
+
+static void get_memory_surface(GGLSurface* ms) {
+  ms->version = sizeof(*ms);
+  ms->width = vi.xres;
+  ms->height = vi.yres;
+  ms->stride = vi.xres;
+  ms->data = malloc(vi.xres * vi.yres * 2);
+  ms->format = GGL_PIXEL_FORMAT_RGB_565;
+}
+
+static void set_active_framebuffer(unsigned n)
+{
+    if (n > 1) return;
+    vi.yres_virtual = vi.yres * 2;
+    vi.yoffset = n * vi.yres;
+    vi.bits_per_pixel = 16;
+    if (ioctl(gr_fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
+        perror("active fb swap failed");
+    }
+}
+
+void gr_flip(void)
+{
+    GGLContext *gl = gr_context;
+
+    /* swap front and back buffers */
+    gr_active_fb = (gr_active_fb + 1) & 1;
+
+    /* copy data from the in-memory surface to the buffer we're about
+     * to make active. */
+    memcpy(gr_framebuffer[gr_active_fb].data, gr_mem_surface.data,
+           vi.xres * vi.yres * 2);
+
+    /* inform the display driver */
+    set_active_framebuffer(gr_active_fb);
+}
+
+void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+    GGLContext *gl = gr_context;
+    GGLint color[4];
+    color[0] = ((r << 8) | r) + 1;
+    color[1] = ((g << 8) | g) + 1;
+    color[2] = ((b << 8) | b) + 1;
+    color[3] = ((a << 8) | a) + 1;
+    gl->color4xv(gl, color);
+}
+
+int gr_measure(const char *s)
+{
+    return gr_font->cwidth * strlen(s);
+}
+
+int gr_text(int x, int y, const char *s)
+{
+    GGLContext *gl = gr_context;
+    GRFont *font = gr_font;
+    unsigned off;
+
+    y -= font->ascent;
+
+    gl->bindTexture(gl, &font->texture);
+    gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
+    gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
+    gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
+    gl->enable(gl, GGL_TEXTURE_2D);
+
+    while((off = *s++)) {
+        off -= 32;
+        if (off < 96) {
+            gl->texCoord2i(gl, (off * font->cwidth) - x, 0 - y);
+            gl->recti(gl, x, y, x + font->cwidth, y + font->cheight);
+        }
+        x += font->cwidth;
+    }
+
+    return x;
+}
+
+void gr_fill(int x, int y, int w, int h)
+{
+    GGLContext *gl = gr_context;
+    gl->disable(gl, GGL_TEXTURE_2D);
+    gl->recti(gl, x, y, w, h);
+}
+
+void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy) {
+    if (gr_context == NULL) {
+        return;
+    }
+    GGLContext *gl = gr_context;
+
+    gl->bindTexture(gl, (GGLSurface*) source);
+    gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
+    gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
+    gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
+    gl->enable(gl, GGL_TEXTURE_2D);
+    gl->texCoord2i(gl, sx - dx, sy - dy);
+    gl->recti(gl, dx, dy, dx + w, dy + h);
+}
+
+unsigned int gr_get_width(gr_surface surface) {
+    if (surface == NULL) {
+        return 0;
+    }
+    return ((GGLSurface*) surface)->width;
+}
+
+unsigned int gr_get_height(gr_surface surface) {
+    if (surface == NULL) {
+        return 0;
+    }
+    return ((GGLSurface*) surface)->height;
+}
+
+static void gr_init_font(void)
+{
+    GGLSurface *ftex;
+    unsigned char *bits, *rle;
+    unsigned char *in, data;
+
+    gr_font = calloc(sizeof(*gr_font), 1);
+    ftex = &gr_font->texture;
+
+    bits = malloc(font.width * font.height);
+
+    ftex->version = sizeof(*ftex);
+    ftex->width = font.width;
+    ftex->height = font.height;
+    ftex->stride = font.width;
+    ftex->data = (void*) bits;
+    ftex->format = GGL_PIXEL_FORMAT_A_8;
+
+    in = font.rundata;
+    while((data = *in++)) {
+        memset(bits, (data & 0x80) ? 255 : 0, data & 0x7f);
+        bits += (data & 0x7f);
+    }
+
+    gr_font->cwidth = font.cwidth;
+    gr_font->cheight = font.cheight;
+    gr_font->ascent = font.cheight - 2;
+}
+
+int gr_init(void)
+{
+    gglInit(&gr_context);
+    GGLContext *gl = gr_context;
+
+    gr_init_font();
+    gr_vt_fd = open("/dev/tty0", O_RDWR | O_SYNC);
+    if (gr_vt_fd < 0) {
+        // This is non-fatal; post-Cupcake kernels don't have tty0.
+        perror("can't open /dev/tty0");
+    } else if (ioctl(gr_vt_fd, KDSETMODE, (void*) KD_GRAPHICS)) {
+        // However, if we do open tty0, we expect the ioctl to work.
+        perror("failed KDSETMODE to KD_GRAPHICS on tty0");
+        gr_exit();
+        return -1;
+    }
+
+    gr_fb_fd = get_framebuffer(gr_framebuffer);
+    if (gr_fb_fd < 0) {
+        gr_exit();
+        return -1;
+    }
+
+    get_memory_surface(&gr_mem_surface);
+
+    fprintf(stderr, "framebuffer: fd %d (%d x %d)\n",
+            gr_fb_fd, gr_framebuffer[0].width, gr_framebuffer[0].height);
+
+        /* start with 0 as front (displayed) and 1 as back (drawing) */
+    gr_active_fb = 0;
+    set_active_framebuffer(0);
+    gl->colorBuffer(gl, &gr_mem_surface);
+
+    gl->activeTexture(gl, 0);
+    gl->enable(gl, GGL_BLEND);
+    gl->blendFunc(gl, GGL_SRC_ALPHA, GGL_ONE_MINUS_SRC_ALPHA);
+
+    return 0;
+}
+
+void gr_exit(void)
+{
+    close(gr_fb_fd);
+    gr_fb_fd = -1;
+
+    free(gr_mem_surface.data);
+
+    ioctl(gr_vt_fd, KDSETMODE, (void*) KD_TEXT);
+    close(gr_vt_fd);
+    gr_vt_fd = -1;
+}
+
+int gr_fb_width(void)
+{
+    return gr_framebuffer[0].width;
+}
+
+int gr_fb_height(void)
+{
+    return gr_framebuffer[0].height;
+}
+
+gr_pixel *gr_fb_data(void)
+{
+    return (unsigned short *) gr_mem_surface.data;
+}
diff --git a/minui/minui.h b/minui/minui.h
new file mode 100644
index 0000000..567d421
--- /dev/null
+++ b/minui/minui.h
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+#ifndef _MINUI_H_
+#define _MINUI_H_
+
+typedef void* gr_surface;
+typedef unsigned short gr_pixel;
+
+int gr_init(void);
+void gr_exit(void);
+
+int gr_fb_width(void);
+int gr_fb_height(void);
+gr_pixel *gr_fb_data(void);
+void gr_flip(void);
+
+void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
+void gr_fill(int x, int y, int w, int h);
+int gr_text(int x, int y, const char *s);
+int gr_measure(const char *s);
+
+void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy);
+unsigned int gr_get_width(gr_surface surface);
+unsigned int gr_get_height(gr_surface surface);
+
+// input event structure, include <linux/input.h> for the definition.
+// see http://www.mjmwired.net/kernel/Documentation/input/ for info.
+struct input_event;
+
+int ev_init(void);
+void ev_exit(void);
+int ev_get(struct input_event *ev, unsigned dont_wait);
+
+// Resources
+
+// Returns 0 if no error, else negative.
+int res_create_surface(const char* name, gr_surface* pSurface);
+void res_free_surface(gr_surface surface);
+
+#endif
diff --git a/minui/mkfont.c b/minui/mkfont.c
new file mode 100644
index 0000000..61a5ede
--- /dev/null
+++ b/minui/mkfont.c
@@ -0,0 +1,54 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(int argc, char *argv)
+{
+    unsigned n;
+    unsigned char *x;
+    unsigned m;
+    unsigned run_val;
+    unsigned run_count;
+ 
+    n = gimp_image.width * gimp_image.height;
+    m = 0;
+    x = gimp_image.pixel_data;
+
+    printf("struct {\n");
+    printf("  unsigned width;\n");
+    printf("  unsigned height;\n");
+    printf("  unsigned cwidth;\n");
+    printf("  unsigned cheight;\n");
+    printf("  unsigned char rundata[];\n");
+    printf("} font = {\n");
+    printf("  .width = %d,\n  .height = %d,\n  .cwidth = %d,\n  .cheight = %d,\n", gimp_image.width, gimp_image.height,
+           gimp_image.width / 96, gimp_image.height);
+    printf("  .rundata = {\n");
+   
+    run_val = (*x ? 0 : 255);
+    run_count = 1;
+    n--;
+    x+=3;
+
+    while(n-- > 0) {
+        unsigned val = (*x ? 0 : 255);
+        x+=3;
+        if((val == run_val) && (run_count < 127)) {
+            run_count++;
+        } else {
+eject:
+            printf("0x%02x,",run_count | (run_val ? 0x80 : 0x00));
+            run_val = val;
+            run_count = 1;
+            m += 5;
+            if(m >= 75) {
+                printf("\n");
+                m = 0;
+            }
+        }
+    }
+    printf("0x%02x,",run_count | (run_val ? 0x80 : 0x00));
+    printf("\n0x00,");
+    printf("\n");
+    printf("  }\n};\n");
+    return 0;
+}
diff --git a/minui/resources.c b/minui/resources.c
new file mode 100644
index 0000000..e055e68
--- /dev/null
+++ b/minui/resources.c
@@ -0,0 +1,174 @@
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include <linux/fb.h>
+#include <linux/kd.h>
+
+#include <pixelflinger/pixelflinger.h>
+
+#include <png.h>
+
+#include "minui.h"
+
+// libpng gives "undefined reference to 'pow'" errors, and I have no
+// idea how to convince the build system to link with -lm.  We don't
+// need this functionality (it's used for gamma adjustment) so provide
+// a dummy implementation to satisfy the linker.
+double pow(double x, double y) {
+    return x;
+}
+
+int res_create_surface(const char* name, gr_surface* pSurface) {
+    char resPath[256];
+    GGLSurface* surface = NULL;
+    int result = 0;
+    unsigned char header[8];
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+
+    snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name);
+    resPath[sizeof(resPath)-1] = '\0';
+    FILE* fp = fopen(resPath, "rb");
+    if (fp == NULL) {
+        result = -1;
+        goto exit;
+    }
+
+    size_t bytesRead = fread(header, 1, sizeof(header), fp);
+    if (bytesRead != sizeof(header)) {
+        result = -2;
+        goto exit;
+    }
+
+    if (png_sig_cmp(header, 0, sizeof(header))) {
+        result = -3;
+        goto exit;
+    }
+
+    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (!png_ptr) {
+        result = -4;
+        goto exit;
+    }
+
+    info_ptr = png_create_info_struct(png_ptr);
+    if (!info_ptr) {
+        result = -5;
+        goto exit;
+    }
+
+    if (setjmp(png_jmpbuf(png_ptr))) {
+        result = -6;
+        goto exit;
+    }
+
+    png_init_io(png_ptr, fp);
+    png_set_sig_bytes(png_ptr, sizeof(header));
+    png_read_info(png_ptr, info_ptr);
+
+    size_t width = info_ptr->width;
+    size_t height = info_ptr->height;
+    size_t stride = 4 * width;
+    size_t pixelSize = stride * height;
+
+    int color_type = info_ptr->color_type;
+    int bit_depth = info_ptr->bit_depth;
+    int channels = info_ptr->channels;
+    if (!(bit_depth == 8 &&
+          ((channels == 3 && color_type == PNG_COLOR_TYPE_RGB) ||
+           (channels == 4 && color_type == PNG_COLOR_TYPE_RGBA) ||
+           (channels == 1 && color_type == PNG_COLOR_TYPE_PALETTE)))) {
+        return -7;
+        goto exit;
+    }
+
+    surface = malloc(sizeof(GGLSurface) + pixelSize);
+    if (surface == NULL) {
+        result = -8;
+        goto exit;
+    }
+    unsigned char* pData = (unsigned char*) (surface + 1);
+    surface->version = sizeof(GGLSurface);
+    surface->width = width;
+    surface->height = height;
+    surface->stride = width; /* Yes, pixels, not bytes */
+    surface->data = pData;
+    surface->format = (channels == 3) ?
+            GGL_PIXEL_FORMAT_RGBX_8888 : 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 < (int)height; ++y) {
+            unsigned char* pRow = pData + y * stride;
+            png_read_row(png_ptr, pRow, NULL);
+
+            int x;
+            for(x = width - 1; x >= 0; x--) {
+                int sx = x * 3;
+                int dx = x * 4;
+                unsigned char r = pRow[sx];
+                unsigned char g = pRow[sx + 1];
+                unsigned char b = pRow[sx + 2];
+                unsigned char a = 0xff;
+                pRow[dx    ] = r; // r
+                pRow[dx + 1] = g; // g
+                pRow[dx + 2] = b; // b
+                pRow[dx + 3] = a;
+            }
+        }
+    } else {
+        for (y = 0; y < (int)height; ++y) {
+            unsigned char* pRow = pData + y * stride;
+            png_read_row(png_ptr, pRow, NULL);
+        }
+    }
+
+    *pSurface = (gr_surface) surface;
+
+exit:
+    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+
+    if (fp != NULL) {
+        fclose(fp);
+    }
+    if (result < 0) {
+        if (surface) {
+            free(surface);
+        }
+    }
+    return result;
+}
+
+void res_free_surface(gr_surface surface) {
+    GGLSurface* pSurface = (GGLSurface*) surface;
+    if (pSurface) {
+        free(pSurface);
+    }
+}
diff --git a/minzip/Android.mk b/minzip/Android.mk
new file mode 100644
index 0000000..b1ee674
--- /dev/null
+++ b/minzip/Android.mk
@@ -0,0 +1,19 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	Hash.c \
+	SysUtil.c \
+	DirUtil.c \
+	Inlines.c \
+	Zip.c
+
+LOCAL_C_INCLUDES += \
+	external/zlib \
+	external/safe-iop/include
+	
+LOCAL_MODULE := libminzip
+
+LOCAL_CFLAGS += -Wall
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/minzip/Bits.h b/minzip/Bits.h
new file mode 100644
index 0000000..f96e6c4
--- /dev/null
+++ b/minzip/Bits.h
@@ -0,0 +1,357 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Some handy functions for manipulating bits and bytes.
+ */
+#ifndef _MINZIP_BITS
+#define _MINZIP_BITS
+
+#include "inline_magic.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * Get 1 byte.  (Included to make the code more legible.)
+ */
+INLINE unsigned char get1(unsigned const char* pSrc)
+{
+    return *pSrc;
+}
+
+/*
+ * Get 2 big-endian bytes.
+ */
+INLINE unsigned short get2BE(unsigned char const* pSrc)
+{
+    unsigned short result;
+
+    result = *pSrc++ << 8;
+    result |= *pSrc++;
+
+    return result;
+}
+
+/*
+ * Get 4 big-endian bytes.
+ */
+INLINE unsigned int get4BE(unsigned char const* pSrc)
+{
+    unsigned int result;
+
+    result = *pSrc++ << 24;
+    result |= *pSrc++ << 16;
+    result |= *pSrc++ << 8;
+    result |= *pSrc++;
+
+    return result;
+}
+
+/*
+ * Get 8 big-endian bytes.
+ */
+INLINE unsigned long long get8BE(unsigned char const* pSrc)
+{
+    unsigned long long result;
+
+    result = (unsigned long long) *pSrc++ << 56;
+    result |= (unsigned long long) *pSrc++ << 48;
+    result |= (unsigned long long) *pSrc++ << 40;
+    result |= (unsigned long long) *pSrc++ << 32;
+    result |= (unsigned long long) *pSrc++ << 24;
+    result |= (unsigned long long) *pSrc++ << 16;
+    result |= (unsigned long long) *pSrc++ << 8;
+    result |= (unsigned long long) *pSrc++;
+
+    return result;
+}
+
+/*
+ * Get 2 little-endian bytes.
+ */
+INLINE unsigned short get2LE(unsigned char const* pSrc)
+{
+    unsigned short result;
+
+    result = *pSrc++;
+    result |= *pSrc++ << 8;
+
+    return result;
+}
+
+/*
+ * Get 4 little-endian bytes.
+ */
+INLINE unsigned int get4LE(unsigned char const* pSrc)
+{
+    unsigned int result;
+
+    result = *pSrc++;
+    result |= *pSrc++ << 8;
+    result |= *pSrc++ << 16;
+    result |= *pSrc++ << 24;
+
+    return result;
+}
+
+/*
+ * Get 8 little-endian bytes.
+ */
+INLINE unsigned long long get8LE(unsigned char const* pSrc)
+{
+    unsigned long long result;
+
+    result = (unsigned long long) *pSrc++;
+    result |= (unsigned long long) *pSrc++ << 8;
+    result |= (unsigned long long) *pSrc++ << 16;
+    result |= (unsigned long long) *pSrc++ << 24;
+    result |= (unsigned long long) *pSrc++ << 32;
+    result |= (unsigned long long) *pSrc++ << 40;
+    result |= (unsigned long long) *pSrc++ << 48;
+    result |= (unsigned long long) *pSrc++ << 56;
+
+    return result;
+}
+
+/*
+ * Grab 1 byte and advance the data pointer.
+ */
+INLINE unsigned char read1(unsigned const char** ppSrc)
+{
+    return *(*ppSrc)++;
+}
+
+/*
+ * Grab 2 big-endian bytes and advance the data pointer.
+ */
+INLINE unsigned short read2BE(unsigned char const** ppSrc)
+{
+    unsigned short result;
+
+    result = *(*ppSrc)++ << 8;
+    result |= *(*ppSrc)++;
+
+    return result;
+}
+
+/*
+ * Grab 4 big-endian bytes and advance the data pointer.
+ */
+INLINE unsigned int read4BE(unsigned char const** ppSrc)
+{
+    unsigned int result;
+
+    result = *(*ppSrc)++ << 24;
+    result |= *(*ppSrc)++ << 16;
+    result |= *(*ppSrc)++ << 8;
+    result |= *(*ppSrc)++;
+
+    return result;
+}
+
+/*
+ * Get 8 big-endian bytes.
+ */
+INLINE unsigned long long read8BE(unsigned char const** ppSrc)
+{
+    unsigned long long result;
+
+    result = (unsigned long long) *(*ppSrc)++ << 56;
+    result |= (unsigned long long) *(*ppSrc)++ << 48;
+    result |= (unsigned long long) *(*ppSrc)++ << 40;
+    result |= (unsigned long long) *(*ppSrc)++ << 32;
+    result |= (unsigned long long) *(*ppSrc)++ << 24;
+    result |= (unsigned long long) *(*ppSrc)++ << 16;
+    result |= (unsigned long long) *(*ppSrc)++ << 8;
+    result |= (unsigned long long) *(*ppSrc)++;
+
+    return result;
+}
+
+/*
+ * Grab 2 little-endian bytes and advance the data pointer.
+ */
+INLINE unsigned short read2LE(unsigned char const** ppSrc)
+{
+    unsigned short result;
+
+    result = *(*ppSrc)++;
+    result |= *(*ppSrc)++ << 8;
+
+    return result;
+}
+
+/*
+ * Grab 4 little-endian bytes and advance the data pointer.
+ */
+INLINE unsigned int read4LE(unsigned char const** ppSrc)
+{
+    unsigned int result;
+
+    result = *(*ppSrc)++;
+    result |= *(*ppSrc)++ << 8;
+    result |= *(*ppSrc)++ << 16;
+    result |= *(*ppSrc)++ << 24;
+
+    return result;
+}
+
+/*
+ * Get 8 little-endian bytes.
+ */
+INLINE unsigned long long read8LE(unsigned char const** ppSrc)
+{
+    unsigned long long result;
+
+    result = (unsigned long long) *(*ppSrc)++;
+    result |= (unsigned long long) *(*ppSrc)++ << 8;
+    result |= (unsigned long long) *(*ppSrc)++ << 16;
+    result |= (unsigned long long) *(*ppSrc)++ << 24;
+    result |= (unsigned long long) *(*ppSrc)++ << 32;
+    result |= (unsigned long long) *(*ppSrc)++ << 40;
+    result |= (unsigned long long) *(*ppSrc)++ << 48;
+    result |= (unsigned long long) *(*ppSrc)++ << 56;
+
+    return result;
+}
+
+/*
+ * Skip over a UTF-8 string.
+ */
+INLINE void skipUtf8String(unsigned char const** ppSrc)
+{
+    unsigned int length = read4BE(ppSrc);
+
+    (*ppSrc) += length;
+}
+
+/*
+ * Read a UTF-8 string into a fixed-size buffer, and null-terminate it.
+ *
+ * Returns the length of the original string.
+ */
+INLINE int readUtf8String(unsigned char const** ppSrc, char* buf, size_t bufLen)
+{
+    unsigned int length = read4BE(ppSrc);
+    size_t copyLen = (length < bufLen) ? length : bufLen-1;
+
+    memcpy(buf, *ppSrc, copyLen);
+    buf[copyLen] = '\0';
+
+    (*ppSrc) += length;
+    return length;
+}
+
+/*
+ * Read a UTF-8 string into newly-allocated storage, and null-terminate it.
+ *
+ * Returns the string and its length.  (The latter is probably unnecessary
+ * for the way we're using UTF8.)
+ */
+INLINE char* readNewUtf8String(unsigned char const** ppSrc, size_t* pLength)
+{
+    unsigned int length = read4BE(ppSrc);
+    char* buf;
+
+    buf = (char*) malloc(length+1);
+
+    memcpy(buf, *ppSrc, length);
+    buf[length] = '\0';
+
+    (*ppSrc) += length;
+
+    *pLength = length;
+    return buf;
+}
+
+
+/*
+ * Set 1 byte.  (Included to make the code more legible.)
+ */
+INLINE void set1(unsigned char* buf, unsigned char val)
+{
+    *buf = (unsigned char)(val);
+}
+
+/*
+ * Set 2 big-endian bytes.
+ */
+INLINE void set2BE(unsigned char* buf, unsigned short val)
+{
+    *buf++ = (unsigned char)(val >> 8);
+    *buf = (unsigned char)(val);
+}
+
+/*
+ * Set 4 big-endian bytes.
+ */
+INLINE void set4BE(unsigned char* buf, unsigned int val)
+{
+    *buf++ = (unsigned char)(val >> 24);
+    *buf++ = (unsigned char)(val >> 16);
+    *buf++ = (unsigned char)(val >> 8);
+    *buf = (unsigned char)(val);
+}
+
+/*
+ * Set 8 big-endian bytes.
+ */
+INLINE void set8BE(unsigned char* buf, unsigned long long val)
+{
+    *buf++ = (unsigned char)(val >> 56);
+    *buf++ = (unsigned char)(val >> 48);
+    *buf++ = (unsigned char)(val >> 40);
+    *buf++ = (unsigned char)(val >> 32);
+    *buf++ = (unsigned char)(val >> 24);
+    *buf++ = (unsigned char)(val >> 16);
+    *buf++ = (unsigned char)(val >> 8);
+    *buf = (unsigned char)(val);
+}
+
+/*
+ * Set 2 little-endian bytes.
+ */
+INLINE void set2LE(unsigned char* buf, unsigned short val)
+{
+    *buf++ = (unsigned char)(val);
+    *buf = (unsigned char)(val >> 8);
+}
+
+/*
+ * Set 4 little-endian bytes.
+ */
+INLINE void set4LE(unsigned char* buf, unsigned int val)
+{
+    *buf++ = (unsigned char)(val);
+    *buf++ = (unsigned char)(val >> 8);
+    *buf++ = (unsigned char)(val >> 16);
+    *buf = (unsigned char)(val >> 24);
+}
+
+/*
+ * Set 8 little-endian bytes.
+ */
+INLINE void set8LE(unsigned char* buf, unsigned long long val)
+{
+    *buf++ = (unsigned char)(val);
+    *buf++ = (unsigned char)(val >> 8);
+    *buf++ = (unsigned char)(val >> 16);
+    *buf++ = (unsigned char)(val >> 24);
+    *buf++ = (unsigned char)(val >> 32);
+    *buf++ = (unsigned char)(val >> 40);
+    *buf++ = (unsigned char)(val >> 48);
+    *buf = (unsigned char)(val >> 56);
+}
+
+/*
+ * Stuff a UTF-8 string into the buffer.
+ */
+INLINE void setUtf8String(unsigned char* buf, const unsigned char* str)
+{
+    unsigned int strLen = strlen((const char*)str);
+
+    set4BE(buf, strLen);
+    memcpy(buf + sizeof(unsigned int), str, strLen);
+}
+
+#endif /*_MINZIP_BITS*/
diff --git a/minzip/DirUtil.c b/minzip/DirUtil.c
new file mode 100644
index 0000000..20c89cd
--- /dev/null
+++ b/minzip/DirUtil.c
@@ -0,0 +1,280 @@
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <dirent.h>
+#include <limits.h>
+
+#include "DirUtil.h"
+
+typedef enum { DMISSING, DDIR, DILLEGAL } DirStatus;
+
+static DirStatus
+getPathDirStatus(const char *path)
+{
+    struct stat st;
+    int err;
+
+    err = stat(path, &st);
+    if (err == 0) {
+        /* Something's there; make sure it's a directory.
+         */
+        if (S_ISDIR(st.st_mode)) {
+            return DDIR;
+        }
+        errno = ENOTDIR;
+        return DILLEGAL;
+    } else if (errno != ENOENT) {
+        /* Something went wrong, or something in the path
+         * is bad.  Can't do anything in this situation.
+         */
+        return DILLEGAL;
+    }
+    return DMISSING;
+}
+
+int
+dirCreateHierarchy(const char *path, int mode,
+        const struct utimbuf *timestamp, bool stripFileName)
+{
+    DirStatus ds;
+
+    /* Check for an empty string before we bother
+     * making any syscalls.
+     */
+    if (path[0] == '\0') {
+        errno = ENOENT;
+        return -1;
+    }
+
+    /* Allocate a path that we can modify; stick a slash on
+     * the end to make things easier.
+     */
+    size_t pathLen = strlen(path);
+    char *cpath = (char *)malloc(pathLen + 2);
+    if (cpath == NULL) {
+        errno = ENOMEM;
+        return -1;
+    }
+    memcpy(cpath, path, pathLen);
+    if (stripFileName) {
+        /* Strip everything after the last slash.
+         */
+        char *c = cpath + pathLen - 1;
+        while (c != cpath && *c != '/') {
+            c--;
+        }
+        if (c == cpath) {
+//xxx test this path
+            /* No directory component.  Act like the path was empty.
+             */
+            errno = ENOENT;
+            free(cpath);
+            return -1;
+        }
+        c[1] = '\0';    // Terminate after the slash we found.
+    } else {
+        /* Make sure that the path ends in a slash.
+         */
+        cpath[pathLen] = '/';
+        cpath[pathLen + 1] = '\0';
+    }
+
+    /* See if it already exists.
+     */
+    ds = getPathDirStatus(cpath);
+    if (ds == DDIR) {
+        return 0;
+    } else if (ds == DILLEGAL) {
+        return -1;
+    }
+
+    /* Walk up the path from the root and make each level.
+     * If a directory already exists, no big deal.
+     */
+    char *p = cpath;
+    while (*p != '\0') {
+        /* Skip any slashes, watching out for the end of the string.
+         */
+        while (*p != '\0' && *p == '/') {
+            p++;
+        }
+        if (*p == '\0') {
+            break;
+        }
+
+        /* Find the end of the next path component.
+         * We know that we'll see a slash before the NUL,
+         * because we added it, above.
+         */
+        while (*p != '/') {
+            p++;
+        }
+        *p = '\0';
+
+        /* Check this part of the path and make a new directory
+         * if necessary.
+         */
+        ds = getPathDirStatus(cpath);
+        if (ds == DILLEGAL) {
+            /* Could happen if some other process/thread is
+             * messing with the filesystem.
+             */
+            free(cpath);
+            return -1;
+        } else if (ds == DMISSING) {
+            int err;
+
+            err = mkdir(cpath, mode);
+            if (err != 0) {
+                free(cpath);
+                return -1;
+            }
+            if (timestamp != NULL && utime(cpath, timestamp)) {
+                free(cpath);
+                return -1;
+            }
+        }
+        // else, this directory already exists.
+        
+        /* Repair the path and continue.
+         */
+        *p = '/';
+    }
+    free(cpath);
+
+    return 0;
+}
+
+int
+dirUnlinkHierarchy(const char *path)
+{
+    struct stat st;
+    DIR *dir;
+    struct dirent *de;
+    int fail = 0;
+
+    /* is it a file or directory? */
+    if (lstat(path, &st) < 0) {
+        return -1;
+    }
+
+    /* a file, so unlink it */
+    if (!S_ISDIR(st.st_mode)) {
+        return unlink(path);
+    }
+
+    /* a directory, so open handle */
+    dir = opendir(path);
+    if (dir == NULL) {
+        return -1;
+    }
+
+    /* recurse over components */
+    errno = 0;
+    while ((de = readdir(dir)) != NULL) {
+//TODO: don't blow the stack
+        char dn[PATH_MAX];
+        if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) {
+            continue;
+        }
+        snprintf(dn, sizeof(dn), "%s/%s", path, de->d_name);
+        if (dirUnlinkHierarchy(dn) < 0) {
+            fail = 1;
+            break;
+        }
+        errno = 0;
+    }
+    /* in case readdir or unlink_recursive failed */
+    if (fail || errno < 0) {
+        int save = errno;
+        closedir(dir);
+        errno = save;
+        return -1;
+    }
+
+    /* close directory handle */
+    if (closedir(dir) < 0) {
+        return -1;
+    }
+
+    /* delete target directory */
+    return rmdir(path);
+}
+
+int
+dirSetHierarchyPermissions(const char *path,
+        int uid, int gid, int dirMode, int fileMode)
+{
+    struct stat st;
+    if (lstat(path, &st)) {
+        return -1;
+    }
+
+    /* ignore symlinks */
+    if (S_ISLNK(st.st_mode)) {
+        return 0;
+    }
+
+    /* directories and files get different permissions */
+    if (chown(path, uid, gid) ||
+        chmod(path, S_ISDIR(st.st_mode) ? dirMode : fileMode)) {
+        return -1;
+    }
+
+    /* recurse over directory components */
+    if (S_ISDIR(st.st_mode)) {
+        DIR *dir = opendir(path);
+        if (dir == NULL) {
+            return -1;
+        }
+
+        errno = 0;
+        const struct dirent *de;
+        while (errno == 0 && (de = readdir(dir)) != NULL) {
+            if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) {
+                continue;
+            }
+
+            char dn[PATH_MAX];
+            snprintf(dn, sizeof(dn), "%s/%s", path, de->d_name);
+            if (!dirSetHierarchyPermissions(dn, uid, gid, dirMode, fileMode)) {
+                errno = 0;
+            } else if (errno == 0) {
+                errno = -1;
+            }
+        }
+
+        if (errno != 0) {
+            int save = errno;
+            closedir(dir);
+            errno = save;
+            return -1;
+        }
+
+        if (closedir(dir)) {
+            return -1;
+        }
+    }
+
+    return 0;
+}
diff --git a/minzip/DirUtil.h b/minzip/DirUtil.h
new file mode 100644
index 0000000..5d881f5
--- /dev/null
+++ b/minzip/DirUtil.h
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#ifndef MINZIP_DIRUTIL_H_
+#define MINZIP_DIRUTIL_H_
+
+#include <stdbool.h>
+#include <utime.h>
+
+/* Like "mkdir -p", try to guarantee that all directories
+ * specified in path are present, creating as many directories
+ * as necessary.  The specified mode is passed to all mkdir
+ * calls;  no modifications are made to umask.
+ *
+ * If stripFileName is set, everything after the final '/'
+ * is stripped before creating the directory hierarchy.
+ *
+ * If timestamp is non-NULL, new directories will be timestamped accordingly.
+ *
+ * Returns 0 on success; returns -1 (and sets errno) on failure
+ * (usually if some element of path is not a directory).
+ */
+int dirCreateHierarchy(const char *path, int mode,
+        const struct utimbuf *timestamp, bool stripFileName);
+
+/* rm -rf <path>
+ */
+int dirUnlinkHierarchy(const char *path);
+
+/* chown -R <uid>:<gid> <path>
+ * chmod -R <mode> <path>
+ *
+ * Sets directories to <dirMode> and files to <fileMode>.  Skips symlinks.
+ */
+int dirSetHierarchyPermissions(const char *path,
+         int uid, int gid, int dirMode, int fileMode);
+
+#endif  // MINZIP_DIRUTIL_H_
diff --git a/minzip/Hash.c b/minzip/Hash.c
new file mode 100644
index 0000000..8c6ca9b
--- /dev/null
+++ b/minzip/Hash.c
@@ -0,0 +1,390 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Hash table.  The dominant calls are add and lookup, with removals
+ * happening very infrequently.  We use probing, and don't worry much
+ * about tombstone removal.
+ */
+#include <stdlib.h>
+#include <assert.h>
+
+#define LOG_TAG "minzip"
+#include "Log.h"
+#include "Hash.h"
+
+/* table load factor, i.e. how full can it get before we resize */
+//#define LOAD_NUMER  3       // 75%
+//#define LOAD_DENOM  4
+#define LOAD_NUMER  5       // 62.5%
+#define LOAD_DENOM  8
+//#define LOAD_NUMER  1       // 50%
+//#define LOAD_DENOM  2
+
+/*
+ * Compute the capacity needed for a table to hold "size" elements.
+ */
+size_t mzHashSize(size_t size) {
+    return (size * LOAD_DENOM) / LOAD_NUMER +1;
+}
+
+/*
+ * Round up to the next highest power of 2.
+ *
+ * Found on http://graphics.stanford.edu/~seander/bithacks.html.
+ */
+unsigned int roundUpPower2(unsigned int val)
+{
+    val--;
+    val |= val >> 1;
+    val |= val >> 2;
+    val |= val >> 4;
+    val |= val >> 8;
+    val |= val >> 16;
+    val++;
+
+    return val;
+}
+
+/*
+ * Create and initialize a hash table.
+ */
+HashTable* mzHashTableCreate(size_t initialSize, HashFreeFunc freeFunc)
+{
+    HashTable* pHashTable;
+
+    assert(initialSize > 0);
+
+    pHashTable = (HashTable*) malloc(sizeof(*pHashTable));
+    if (pHashTable == NULL)
+        return NULL;
+
+    pHashTable->tableSize = roundUpPower2(initialSize);
+    pHashTable->numEntries = pHashTable->numDeadEntries = 0;
+    pHashTable->freeFunc = freeFunc;
+    pHashTable->pEntries =
+        (HashEntry*) calloc((size_t)pHashTable->tableSize, sizeof(HashTable));
+    if (pHashTable->pEntries == NULL) {
+        free(pHashTable);
+        return NULL;
+    }
+
+    return pHashTable;
+}
+
+/*
+ * Clear out all entries.
+ */
+void mzHashTableClear(HashTable* pHashTable)
+{
+    HashEntry* pEnt;
+    int i;
+
+    pEnt = pHashTable->pEntries;
+    for (i = 0; i < pHashTable->tableSize; i++, pEnt++) {
+        if (pEnt->data == HASH_TOMBSTONE) {
+            // nuke entry
+            pEnt->data = NULL;
+        } else if (pEnt->data != NULL) {
+            // call free func then nuke entry
+            if (pHashTable->freeFunc != NULL)
+                (*pHashTable->freeFunc)(pEnt->data);
+            pEnt->data = NULL;
+        }
+    }
+
+    pHashTable->numEntries = 0;
+    pHashTable->numDeadEntries = 0;
+}
+
+/*
+ * Free the table.
+ */
+void mzHashTableFree(HashTable* pHashTable)
+{
+    if (pHashTable == NULL)
+        return;
+    mzHashTableClear(pHashTable);
+    free(pHashTable->pEntries);
+    free(pHashTable);
+}
+
+#ifndef NDEBUG
+/*
+ * Count up the number of tombstone entries in the hash table.
+ */
+static int countTombStones(HashTable* pHashTable)
+{
+    int i, count;
+
+    for (count = i = 0; i < pHashTable->tableSize; i++) {
+        if (pHashTable->pEntries[i].data == HASH_TOMBSTONE)
+            count++;
+    }
+    return count;
+}
+#endif
+
+/*
+ * Resize a hash table.  We do this when adding an entry increased the
+ * size of the table beyond its comfy limit.
+ *
+ * This essentially requires re-inserting all elements into the new storage.
+ *
+ * If multiple threads can access the hash table, the table's lock should
+ * have been grabbed before issuing the "lookup+add" call that led to the
+ * resize, so we don't have a synchronization problem here.
+ */
+static bool resizeHash(HashTable* pHashTable, int newSize)
+{
+    HashEntry* pNewEntries;
+    int i;
+
+    assert(countTombStones(pHashTable) == pHashTable->numDeadEntries);
+    //LOGI("before: dead=%d\n", pHashTable->numDeadEntries);
+
+    pNewEntries = (HashEntry*) calloc(newSize, sizeof(HashTable));
+    if (pNewEntries == NULL)
+        return false;
+
+    for (i = 0; i < pHashTable->tableSize; i++) {
+        void* data = pHashTable->pEntries[i].data;
+        if (data != NULL && data != HASH_TOMBSTONE) {
+            int hashValue = pHashTable->pEntries[i].hashValue;
+            int newIdx;
+
+            /* probe for new spot, wrapping around */
+            newIdx = hashValue & (newSize-1);
+            while (pNewEntries[newIdx].data != NULL)
+                newIdx = (newIdx + 1) & (newSize-1);
+
+            pNewEntries[newIdx].hashValue = hashValue;
+            pNewEntries[newIdx].data = data;
+        }
+    }
+
+    free(pHashTable->pEntries);
+    pHashTable->pEntries = pNewEntries;
+    pHashTable->tableSize = newSize;
+    pHashTable->numDeadEntries = 0;
+
+    assert(countTombStones(pHashTable) == 0);
+    return true;
+}
+
+/*
+ * Look up an entry.
+ *
+ * We probe on collisions, wrapping around the table.
+ */
+void* mzHashTableLookup(HashTable* pHashTable, unsigned int itemHash, void* item,
+    HashCompareFunc cmpFunc, bool doAdd)
+{
+    HashEntry* pEntry;
+    HashEntry* pEnd;
+    void* result = NULL;
+
+    assert(pHashTable->tableSize > 0);
+    assert(item != HASH_TOMBSTONE);
+    assert(item != NULL);
+
+    /* jump to the first entry and probe for a match */
+    pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)];
+    pEnd = &pHashTable->pEntries[pHashTable->tableSize];
+    while (pEntry->data != NULL) {
+        if (pEntry->data != HASH_TOMBSTONE &&
+            pEntry->hashValue == itemHash &&
+            (*cmpFunc)(pEntry->data, item) == 0)
+        {
+            /* match */
+            //LOGD("+++ match on entry %d\n", pEntry - pHashTable->pEntries);
+            break;
+        }
+
+        pEntry++;
+        if (pEntry == pEnd) {     /* wrap around to start */
+            if (pHashTable->tableSize == 1)
+                break;      /* edge case - single-entry table */
+            pEntry = pHashTable->pEntries;
+        }
+
+        //LOGI("+++ look probing %d...\n", pEntry - pHashTable->pEntries);
+    }
+
+    if (pEntry->data == NULL) {
+        if (doAdd) {
+            pEntry->hashValue = itemHash;
+            pEntry->data = item;
+            pHashTable->numEntries++;
+
+            /*
+             * We've added an entry.  See if this brings us too close to full.
+             */
+            if ((pHashTable->numEntries+pHashTable->numDeadEntries) * LOAD_DENOM
+                > pHashTable->tableSize * LOAD_NUMER)
+            {
+                if (!resizeHash(pHashTable, pHashTable->tableSize * 2)) {
+                    /* don't really have a way to indicate failure */
+                    LOGE("Dalvik hash resize failure\n");
+                    abort();
+                }
+                /* note "pEntry" is now invalid */
+            } else {
+                //LOGW("okay %d/%d/%d\n",
+                //    pHashTable->numEntries, pHashTable->tableSize,
+                //    (pHashTable->tableSize * LOAD_NUMER) / LOAD_DENOM);
+            }
+
+            /* full table is bad -- search for nonexistent never halts */
+            assert(pHashTable->numEntries < pHashTable->tableSize);
+            result = item;
+        } else {
+            assert(result == NULL);
+        }
+    } else {
+        result = pEntry->data;
+    }
+
+    return result;
+}
+
+/*
+ * Remove an entry from the table.
+ *
+ * Does NOT invoke the "free" function on the item.
+ */
+bool mzHashTableRemove(HashTable* pHashTable, unsigned int itemHash, void* item)
+{
+    HashEntry* pEntry;
+    HashEntry* pEnd;
+
+    assert(pHashTable->tableSize > 0);
+
+    /* jump to the first entry and probe for a match */
+    pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)];
+    pEnd = &pHashTable->pEntries[pHashTable->tableSize];
+    while (pEntry->data != NULL) {
+        if (pEntry->data == item) {
+            //LOGI("+++ stepping on entry %d\n", pEntry - pHashTable->pEntries);
+            pEntry->data = HASH_TOMBSTONE;
+            pHashTable->numEntries--;
+            pHashTable->numDeadEntries++;
+            return true;
+        }
+
+        pEntry++;
+        if (pEntry == pEnd) {     /* wrap around to start */
+            if (pHashTable->tableSize == 1)
+                break;      /* edge case - single-entry table */
+            pEntry = pHashTable->pEntries;
+        }
+
+        //LOGI("+++ del probing %d...\n", pEntry - pHashTable->pEntries);
+    }
+
+    return false;
+}
+
+/*
+ * Execute a function on every entry in the hash table.
+ *
+ * If "func" returns a nonzero value, terminate early and return the value.
+ */
+int mzHashForeach(HashTable* pHashTable, HashForeachFunc func, void* arg)
+{
+    int i, val;
+
+    for (i = 0; i < pHashTable->tableSize; i++) {
+        HashEntry* pEnt = &pHashTable->pEntries[i];
+
+        if (pEnt->data != NULL && pEnt->data != HASH_TOMBSTONE) {
+            val = (*func)(pEnt->data, arg);
+            if (val != 0)
+                return val;
+        }
+    }
+
+    return 0;
+}
+
+
+/*
+ * Look up an entry, counting the number of times we have to probe.
+ *
+ * Returns -1 if the entry wasn't found.
+ */
+int countProbes(HashTable* pHashTable, unsigned int itemHash, const void* item,
+    HashCompareFunc cmpFunc)
+{
+    HashEntry* pEntry;
+    HashEntry* pEnd;
+    int count = 0;
+
+    assert(pHashTable->tableSize > 0);
+    assert(item != HASH_TOMBSTONE);
+    assert(item != NULL);
+
+    /* jump to the first entry and probe for a match */
+    pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)];
+    pEnd = &pHashTable->pEntries[pHashTable->tableSize];
+    while (pEntry->data != NULL) {
+        if (pEntry->data != HASH_TOMBSTONE &&
+            pEntry->hashValue == itemHash &&
+            (*cmpFunc)(pEntry->data, item) == 0)
+        {
+            /* match */
+            break;
+        }
+
+        pEntry++;
+        if (pEntry == pEnd) {     /* wrap around to start */
+            if (pHashTable->tableSize == 1)
+                break;      /* edge case - single-entry table */
+            pEntry = pHashTable->pEntries;
+        }
+
+        count++;
+    }
+    if (pEntry->data == NULL)
+        return -1;
+
+    return count;
+}
+
+/*
+ * Evaluate the amount of probing required for the specified hash table.
+ *
+ * We do this by running through all entries in the hash table, computing
+ * the hash value and then doing a lookup.
+ *
+ * The caller should lock the table before calling here.
+ */
+void mzHashTableProbeCount(HashTable* pHashTable, HashCalcFunc calcFunc,
+    HashCompareFunc cmpFunc)
+{
+    int numEntries, minProbe, maxProbe, totalProbe;
+    HashIter iter;
+
+    numEntries = maxProbe = totalProbe = 0;
+    minProbe = 65536*32767;
+
+    for (mzHashIterBegin(pHashTable, &iter); !mzHashIterDone(&iter);
+        mzHashIterNext(&iter))
+    {
+        const void* data = (const void*)mzHashIterData(&iter);
+        int count;
+            
+        count = countProbes(pHashTable, (*calcFunc)(data), data, cmpFunc);
+
+        numEntries++;
+
+        if (count < minProbe)
+            minProbe = count;
+        if (count > maxProbe)
+            maxProbe = count;
+        totalProbe += count;
+    }
+
+    LOGI("Probe: min=%d max=%d, total=%d in %d (%d), avg=%.3f\n",
+        minProbe, maxProbe, totalProbe, numEntries, pHashTable->tableSize,
+        (float) totalProbe / (float) numEntries);
+}
diff --git a/minzip/Hash.h b/minzip/Hash.h
new file mode 100644
index 0000000..8194537
--- /dev/null
+++ b/minzip/Hash.h
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2007 The Android Open Source Project
+ *
+ * General purpose hash table, used for finding classes, methods, etc.
+ *
+ * When the number of elements reaches 3/4 of the table's capacity, the
+ * table will be resized.
+ */
+#ifndef _MINZIP_HASH
+#define _MINZIP_HASH
+
+#include "inline_magic.h"
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <assert.h>
+
+/* compute the hash of an item with a specific type */
+typedef unsigned int (*HashCompute)(const void* item);
+
+/*
+ * Compare a hash entry with a "loose" item after their hash values match.
+ * Returns { <0, 0, >0 } depending on ordering of items (same semantics
+ * as strcmp()).
+ */
+typedef int (*HashCompareFunc)(const void* tableItem, const void* looseItem);
+
+/*
+ * This function will be used to free entries in the table.  This can be
+ * NULL if no free is required, free(), or a custom function.
+ */
+typedef void (*HashFreeFunc)(void* ptr);
+
+/*
+ * Used by mzHashForeach().
+ */
+typedef int (*HashForeachFunc)(void* data, void* arg);
+
+/*
+ * One entry in the hash table.  "data" values are expected to be (or have
+ * the same characteristics as) valid pointers.  In particular, a NULL
+ * value for "data" indicates an empty slot, and HASH_TOMBSTONE indicates
+ * a no-longer-used slot that must be stepped over during probing.
+ *
+ * Attempting to add a NULL or tombstone value is an error.
+ *
+ * When an entry is released, we will call (HashFreeFunc)(entry->data).
+ */
+typedef struct HashEntry {
+    unsigned int hashValue;
+    void* data;
+} HashEntry;
+
+#define HASH_TOMBSTONE ((void*) 0xcbcacccd)     // invalid ptr value
+
+/*
+ * Expandable hash table.
+ *
+ * This structure should be considered opaque.
+ */
+typedef struct HashTable {
+    int         tableSize;          /* must be power of 2 */
+    int         numEntries;         /* current #of "live" entries */
+    int         numDeadEntries;     /* current #of tombstone entries */
+    HashEntry*  pEntries;           /* array on heap */
+    HashFreeFunc freeFunc;
+} HashTable;
+
+/*
+ * Create and initialize a HashTable structure, using "initialSize" as
+ * a basis for the initial capacity of the table.  (The actual initial
+ * table size may be adjusted upward.)  If you know exactly how many
+ * elements the table will hold, pass the result from mzHashSize() in.)
+ *
+ * Returns "false" if unable to allocate the table.
+ */
+HashTable* mzHashTableCreate(size_t initialSize, HashFreeFunc freeFunc);
+
+/*
+ * Compute the capacity needed for a table to hold "size" elements.  Use
+ * this when you know ahead of time how many elements the table will hold.
+ * Pass this value into mzHashTableCreate() to ensure that you can add
+ * all elements without needing to reallocate the table.
+ */
+size_t mzHashSize(size_t size);
+
+/*
+ * Clear out a hash table, freeing the contents of any used entries.
+ */
+void mzHashTableClear(HashTable* pHashTable);
+
+/*
+ * Free a hash table.
+ */
+void mzHashTableFree(HashTable* pHashTable);
+
+/*
+ * Get #of entries in hash table.
+ */
+INLINE int mzHashTableNumEntries(HashTable* pHashTable) {
+    return pHashTable->numEntries;
+}
+
+/*
+ * Get total size of hash table (for memory usage calculations).
+ */
+INLINE int mzHashTableMemUsage(HashTable* pHashTable) {
+    return sizeof(HashTable) + pHashTable->tableSize * sizeof(HashEntry);
+}
+
+/*
+ * Look up an entry in the table, possibly adding it if it's not there.
+ *
+ * If "item" is not found, and "doAdd" is false, NULL is returned.
+ * Otherwise, a pointer to the found or added item is returned.  (You can
+ * tell the difference by seeing if return value == item.)
+ *
+ * An "add" operation may cause the entire table to be reallocated.
+ */
+void* mzHashTableLookup(HashTable* pHashTable, unsigned int itemHash, void* item,
+    HashCompareFunc cmpFunc, bool doAdd);
+
+/*
+ * Remove an item from the hash table, given its "data" pointer.  Does not
+ * invoke the "free" function; just detaches it from the table.
+ */
+bool mzHashTableRemove(HashTable* pHashTable, unsigned int hash, void* item);
+
+/*
+ * Execute "func" on every entry in the hash table.
+ *
+ * If "func" returns a nonzero value, terminate early and return the value.
+ */
+int mzHashForeach(HashTable* pHashTable, HashForeachFunc func, void* arg);
+
+/*
+ * An alternative to mzHashForeach(), using an iterator.
+ *
+ * Use like this:
+ *   HashIter iter;
+ *   for (mzHashIterBegin(hashTable, &iter); !mzHashIterDone(&iter);
+ *       mzHashIterNext(&iter))
+ *   {
+ *       MyData* data = (MyData*)mzHashIterData(&iter);
+ *   }
+ */
+typedef struct HashIter {
+    void*       data;
+    HashTable*  pHashTable;
+    int         idx;
+} HashIter;
+INLINE void mzHashIterNext(HashIter* pIter) {
+    int i = pIter->idx +1;
+    int lim = pIter->pHashTable->tableSize;
+    for ( ; i < lim; i++) {
+        void* data = pIter->pHashTable->pEntries[i].data;
+        if (data != NULL && data != HASH_TOMBSTONE)
+            break;
+    }
+    pIter->idx = i;
+}
+INLINE void mzHashIterBegin(HashTable* pHashTable, HashIter* pIter) {
+    pIter->pHashTable = pHashTable;
+    pIter->idx = -1;
+    mzHashIterNext(pIter);
+}
+INLINE bool mzHashIterDone(HashIter* pIter) {
+    return (pIter->idx >= pIter->pHashTable->tableSize);
+}
+INLINE void* mzHashIterData(HashIter* pIter) {
+    assert(pIter->idx >= 0 && pIter->idx < pIter->pHashTable->tableSize);
+    return pIter->pHashTable->pEntries[pIter->idx].data;
+}
+
+
+/*
+ * Evaluate hash table performance by examining the number of times we
+ * have to probe for an entry.
+ *
+ * The caller should lock the table beforehand.
+ */
+typedef unsigned int (*HashCalcFunc)(const void* item);
+void mzHashTableProbeCount(HashTable* pHashTable, HashCalcFunc calcFunc,
+    HashCompareFunc cmpFunc);
+
+#endif /*_MINZIP_HASH*/
diff --git a/minzip/Inlines.c b/minzip/Inlines.c
new file mode 100644
index 0000000..91f8775
--- /dev/null
+++ b/minzip/Inlines.c
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+/* Make sure that non-inlined versions of INLINED-marked functions
+ * exist so that debug builds (which don't generally do inlining)
+ * don't break.
+ */
+#define MINZIP_GENERATE_INLINES 1
+#include "Bits.h"
+#include "Hash.h"
+#include "SysUtil.h"
+#include "Zip.h"
diff --git a/minzip/Log.h b/minzip/Log.h
new file mode 100644
index 0000000..36e62f5
--- /dev/null
+++ b/minzip/Log.h
@@ -0,0 +1,207 @@
+//
+// Copyright 2005 The Android Open Source Project
+//
+// C/C++ logging functions.  See the logging documentation for API details.
+//
+// We'd like these to be available from C code (in case we import some from
+// somewhere), so this has a C interface.
+//
+// The output will be correct when the log file is shared between multiple
+// threads and/or multiple processes so long as the operating system
+// supports O_APPEND.  These calls have mutex-protected data structures
+// and so are NOT reentrant.  Do not use LOG in a signal handler.
+//
+#ifndef _MINZIP_LOG_H
+#define _MINZIP_LOG_H
+
+#include <stdio.h>
+
+// ---------------------------------------------------------------------
+
+/*
+ * Normally we strip LOGV (VERBOSE messages) from release builds.
+ * You can modify this (for example with "#define LOG_NDEBUG 0"
+ * at the top of your source file) to change that behavior.
+ */
+#ifndef LOG_NDEBUG
+#ifdef NDEBUG
+#define LOG_NDEBUG 1
+#else
+#define LOG_NDEBUG 0
+#endif
+#endif
+
+/*
+ * This is the local tag used for the following simplified
+ * logging macros.  You can change this preprocessor definition
+ * before using the other macros to change the tag.
+ */
+#ifndef LOG_TAG
+#define LOG_TAG NULL
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Simplified macro to send a verbose log message using the current LOG_TAG.
+ */
+#ifndef LOGV
+#if LOG_NDEBUG
+#define LOGV(...)   ((void)0)
+#else
+#define LOGV(...) ((void)LOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
+#endif
+#endif
+
+#define CONDITION(cond)     (__builtin_expect((cond)!=0, 0))
+
+#ifndef LOGV_IF
+#if LOG_NDEBUG
+#define LOGV_IF(cond, ...)   ((void)0)
+#else
+#define LOGV_IF(cond, ...) \
+    ( (CONDITION(cond)) \
+    ? ((void)LOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
+    : (void)0 )
+#endif
+#endif
+
+#define LOGVV LOGV
+#define LOGVV_IF LOGV_IF
+
+/*
+ * Simplified macro to send a debug log message using the current LOG_TAG.
+ */
+#ifndef LOGD
+#define LOGD(...) ((void)LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef LOGD_IF
+#define LOGD_IF(cond, ...) \
+    ( (CONDITION(cond)) \
+    ? ((void)LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
+    : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an info log message using the current LOG_TAG.
+ */
+#ifndef LOGI
+#define LOGI(...) ((void)LOG(LOG_INFO, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef LOGI_IF
+#define LOGI_IF(cond, ...) \
+    ( (CONDITION(cond)) \
+    ? ((void)LOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \
+    : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send a warning log message using the current LOG_TAG.
+ */
+#ifndef LOGW
+#define LOGW(...) ((void)LOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef LOGW_IF
+#define LOGW_IF(cond, ...) \
+    ( (CONDITION(cond)) \
+    ? ((void)LOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \
+    : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an error log message using the current LOG_TAG.
+ */
+#ifndef LOGE
+#define LOGE(...) ((void)LOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef LOGE_IF
+#define LOGE_IF(cond, ...) \
+    ( (CONDITION(cond)) \
+    ? ((void)LOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
+    : (void)0 )
+#endif
+
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * verbose priority.
+ */
+#ifndef IF_LOGV
+#if LOG_NDEBUG
+#define IF_LOGV() if (false)
+#else
+#define IF_LOGV() IF_LOG(LOG_VERBOSE, LOG_TAG)
+#endif
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * debug priority.
+ */
+#ifndef IF_LOGD
+#define IF_LOGD() IF_LOG(LOG_DEBUG, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * info priority.
+ */
+#ifndef IF_LOGI
+#define IF_LOGI() IF_LOG(LOG_INFO, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * warn priority.
+ */
+#ifndef IF_LOGW
+#define IF_LOGW() IF_LOG(LOG_WARN, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * error priority.
+ */
+#ifndef IF_LOGE
+#define IF_LOGE() IF_LOG(LOG_ERROR, LOG_TAG)
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Basic log message macro.
+ *
+ * Example:
+ *  LOG(LOG_WARN, NULL, "Failed with error %d", errno);
+ *
+ * The second argument may be NULL or "" to indicate the "global" tag.
+ *
+ * Non-gcc probably won't have __FUNCTION__.  It's not vital.  gcc also
+ * offers __PRETTY_FUNCTION__, which is rather more than we need.
+ */
+#ifndef LOG
+#define LOG(priority, tag, ...) \
+    LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
+#endif
+
+/*
+ * Log macro that allows you to specify a number for the priority.
+ */
+#ifndef LOG_PRI
+#define LOG_PRI(priority, tag, ...) \
+    printf(tag ": " __VA_ARGS__)
+#endif
+
+/*
+ * Conditional given a desired logging priority and tag.
+ */
+#ifndef IF_LOG
+#define IF_LOG(priority, tag) \
+    if (1)
+#endif
+
+#endif // _MINZIP_LOG_H
diff --git a/minzip/SysUtil.c b/minzip/SysUtil.c
new file mode 100644
index 0000000..49a2522
--- /dev/null
+++ b/minzip/SysUtil.c
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * System utilities.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <limits.h>
+#include <errno.h>
+#include <assert.h>
+
+#define LOG_TAG "minzip"
+#include "Log.h"
+#include "SysUtil.h"
+
+/*
+ * Having trouble finding a portable way to get this.  sysconf(_SC_PAGE_SIZE)
+ * seems appropriate, but we don't have that on the device.  Some systems
+ * have getpagesize(2), though the linux man page has some odd cautions.
+ */
+#define DEFAULT_PAGE_SIZE   4096
+
+
+/*
+ * Create an anonymous shared memory segment large enough to hold "length"
+ * bytes.  The actual segment may be larger because mmap() operates on
+ * page boundaries (usually 4K).
+ */
+static void* sysCreateAnonShmem(size_t length)
+{
+    void* ptr;
+
+    ptr = mmap(NULL, length, PROT_READ | PROT_WRITE,
+            MAP_SHARED | MAP_ANON, -1, 0);
+    if (ptr == MAP_FAILED) {
+        LOGW("mmap(%d, RW, SHARED|ANON) failed: %s\n", (int) length,
+            strerror(errno));
+        return NULL;
+    }
+
+    return ptr;
+}
+
+static int getFileStartAndLength(int fd, off_t *start_, size_t *length_)
+{
+    off_t start, end;
+    size_t length;
+
+    assert(start_ != NULL);
+    assert(length_ != NULL);
+
+    start = lseek(fd, 0L, SEEK_CUR);
+    end = lseek(fd, 0L, SEEK_END);
+    (void) lseek(fd, start, SEEK_SET);
+
+    if (start == (off_t) -1 || end == (off_t) -1) {
+        LOGE("could not determine length of file\n");
+        return -1;
+    }
+
+    length = end - start;
+    if (length == 0) {
+        LOGE("file is empty\n");
+        return -1;
+    }
+
+    *start_ = start;
+    *length_ = length;
+
+    return 0;
+}
+
+/*
+ * Pull the contents of a file into an new shared memory segment.  We grab
+ * everything from fd's current offset on.
+ *
+ * We need to know the length ahead of time so we can allocate a segment
+ * of sufficient size.
+ */
+int sysLoadFileInShmem(int fd, MemMapping* pMap)
+{
+    off_t start;
+    size_t length, actual;
+    void* memPtr;
+
+    assert(pMap != NULL);
+
+    if (getFileStartAndLength(fd, &start, &length) < 0)
+        return -1;
+
+    memPtr = sysCreateAnonShmem(length);
+    if (memPtr == NULL)
+        return -1;
+
+    actual = read(fd, memPtr, length);
+    if (actual != length) {
+        LOGE("only read %d of %d bytes\n", (int) actual, (int) length);
+        sysReleaseShmem(pMap);
+        return -1;
+    }
+
+    pMap->baseAddr = pMap->addr = memPtr;
+    pMap->baseLength = pMap->length = length;
+
+    return 0;
+}
+
+/*
+ * Map a file (from fd's current offset) into a shared, read-only memory
+ * segment.  The file offset must be a multiple of the page size.
+ *
+ * On success, returns 0 and fills out "pMap".  On failure, returns a nonzero
+ * value and does not disturb "pMap".
+ */
+int sysMapFileInShmem(int fd, MemMapping* pMap)
+{
+    off_t start;
+    size_t length;
+    void* memPtr;
+
+    assert(pMap != NULL);
+
+    if (getFileStartAndLength(fd, &start, &length) < 0)
+        return -1;
+
+    memPtr = mmap(NULL, length, PROT_READ, MAP_FILE | MAP_SHARED, fd, start);
+    if (memPtr == MAP_FAILED) {
+        LOGW("mmap(%d, R, FILE|SHARED, %d, %d) failed: %s\n", (int) length,
+            fd, (int) start, strerror(errno));
+        return -1;
+    }
+
+    pMap->baseAddr = pMap->addr = memPtr;
+    pMap->baseLength = pMap->length = length;
+
+    return 0;
+}
+
+/*
+ * Map part of a file (from fd's current offset) into a shared, read-only
+ * memory segment.
+ *
+ * On success, returns 0 and fills out "pMap".  On failure, returns a nonzero
+ * value and does not disturb "pMap".
+ */
+int sysMapFileSegmentInShmem(int fd, off_t start, long length,
+    MemMapping* pMap)
+{
+    off_t dummy;
+    size_t fileLength, actualLength;
+    off_t actualStart;
+    int adjust;
+    void* memPtr;
+
+    assert(pMap != NULL);
+
+    if (getFileStartAndLength(fd, &dummy, &fileLength) < 0)
+        return -1;
+
+    if (start + length > (long)fileLength) {
+        LOGW("bad segment: st=%d len=%ld flen=%d\n",
+            (int) start, length, (int) fileLength);
+        return -1;
+    }
+
+    /* adjust to be page-aligned */
+    adjust = start % DEFAULT_PAGE_SIZE;
+    actualStart = start - adjust;
+    actualLength = length + adjust;
+
+    memPtr = mmap(NULL, actualLength, PROT_READ, MAP_FILE | MAP_SHARED,
+                fd, actualStart);
+    if (memPtr == MAP_FAILED) {
+        LOGW("mmap(%d, R, FILE|SHARED, %d, %d) failed: %s\n",
+            (int) actualLength, fd, (int) actualStart, strerror(errno));
+        return -1;
+    }
+
+    pMap->baseAddr = memPtr;
+    pMap->baseLength = actualLength;
+    pMap->addr = (char*)memPtr + adjust;
+    pMap->length = length;
+
+    LOGVV("mmap seg (st=%d ln=%d): bp=%p bl=%d ad=%p ln=%d\n",
+        (int) start, (int) length,
+        pMap->baseAddr, (int) pMap->baseLength,
+        pMap->addr, (int) pMap->length);
+
+    return 0;
+}
+
+/*
+ * Release a memory mapping.
+ */
+void sysReleaseShmem(MemMapping* pMap)
+{
+    if (pMap->baseAddr == NULL && pMap->baseLength == 0)
+        return;
+
+    if (munmap(pMap->baseAddr, pMap->baseLength) < 0) {
+        LOGW("munmap(%p, %d) failed: %s\n",
+            pMap->baseAddr, (int)pMap->baseLength, strerror(errno));
+    } else {
+        LOGV("munmap(%p, %d) succeeded\n", pMap->baseAddr, pMap->baseLength);
+        pMap->baseAddr = NULL;
+        pMap->baseLength = 0;
+    }
+}
+
diff --git a/minzip/SysUtil.h b/minzip/SysUtil.h
new file mode 100644
index 0000000..ec3a4bc
--- /dev/null
+++ b/minzip/SysUtil.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * System utilities.
+ */
+#ifndef _MINZIP_SYSUTIL
+#define _MINZIP_SYSUTIL
+
+#include "inline_magic.h"
+
+#include <sys/types.h>
+
+/*
+ * Use this to keep track of mapped segments.
+ */
+typedef struct MemMapping {
+    void*   addr;           /* start of data */
+    size_t  length;         /* length of data */
+
+    void*   baseAddr;       /* page-aligned base address */
+    size_t  baseLength;     /* length of mapping */
+} MemMapping;
+
+/* copy a map */
+INLINE void sysCopyMap(MemMapping* dst, const MemMapping* src) {
+    *dst = *src;
+}
+
+/*
+ * Load a file into a new shared memory segment.  All data from the current
+ * offset to the end of the file is pulled in.
+ *
+ * The segment is read-write, allowing VM fixups.  (It should be modified
+ * to support .gz/.zip compressed data.)
+ *
+ * On success, "pMap" is filled in, and zero is returned.
+ */
+int sysLoadFileInShmem(int fd, MemMapping* pMap);
+
+/*
+ * Map a file (from fd's current offset) into a shared,
+ * read-only memory segment.
+ *
+ * On success, "pMap" is filled in, and zero is returned.
+ */
+int sysMapFileInShmem(int fd, MemMapping* pMap);
+
+/*
+ * Like sysMapFileInShmem, but on only part of a file.
+ */
+int sysMapFileSegmentInShmem(int fd, off_t start, long length,
+    MemMapping* pMap);
+
+/*
+ * Release the pages associated with a shared memory segment.
+ *
+ * This does not free "pMap"; it just releases the memory.
+ */
+void sysReleaseShmem(MemMapping* pMap);
+
+#endif /*_MINZIP_SYSUTIL*/
diff --git a/minzip/Zip.c b/minzip/Zip.c
new file mode 100644
index 0000000..46d2f82
--- /dev/null
+++ b/minzip/Zip.c
@@ -0,0 +1,1149 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Simple Zip file support.
+ */
+#include "safe_iop.h"
+#include "zlib.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdint.h>     // for uintptr_t
+#include <stdlib.h>
+#include <sys/stat.h>   // for S_ISLNK()
+#include <unistd.h>
+
+#define LOG_TAG "minzip"
+#include "Zip.h"
+#include "Bits.h"
+#include "Log.h"
+#include "DirUtil.h"
+
+#undef NDEBUG   // do this after including Log.h
+#include <assert.h>
+
+#define SORT_ENTRIES 1
+
+/*
+ * Offset and length constants (java.util.zip naming convention).
+ */
+enum {
+    CENSIG = 0x02014b50,      // PK12
+    CENHDR = 46,
+
+    CENVEM =  4,
+    CENVER =  6,
+    CENFLG =  8,
+    CENHOW = 10,
+    CENTIM = 12,
+    CENCRC = 16,
+    CENSIZ = 20,
+    CENLEN = 24,
+    CENNAM = 28,
+    CENEXT = 30,
+    CENCOM = 32,
+    CENDSK = 34,
+    CENATT = 36,
+    CENATX = 38,
+    CENOFF = 42,
+
+    ENDSIG = 0x06054b50,     // PK56
+    ENDHDR = 22,
+
+    ENDSUB =  8,
+    ENDTOT = 10,
+    ENDSIZ = 12,
+    ENDOFF = 16,
+    ENDCOM = 20,
+
+    EXTSIG = 0x08074b50,     // PK78
+    EXTHDR = 16,
+
+    EXTCRC =  4,
+    EXTSIZ =  8,
+    EXTLEN = 12,
+
+    LOCSIG = 0x04034b50,      // PK34
+    LOCHDR = 30,
+
+    LOCVER =  4,
+    LOCFLG =  6,
+    LOCHOW =  8,
+    LOCTIM = 10,
+    LOCCRC = 14,
+    LOCSIZ = 18,
+    LOCLEN = 22,
+    LOCNAM = 26,
+    LOCEXT = 28,
+
+    STORED = 0,
+    DEFLATED = 8,
+
+    CENVEM_UNIX = 3 << 8,   // the high byte of CENVEM
+};
+
+
+/*
+ * For debugging, dump the contents of a ZipEntry.
+ */
+#if 0
+static void dumpEntry(const ZipEntry* pEntry)
+{
+    LOGI(" %p '%.*s'\n", pEntry->fileName,pEntry->fileNameLen,pEntry->fileName);
+    LOGI("   off=%ld comp=%ld uncomp=%ld how=%d\n", pEntry->offset,
+        pEntry->compLen, pEntry->uncompLen, pEntry->compression);
+}
+#endif
+
+/*
+ * (This is a mzHashTableLookup callback.)
+ *
+ * Compare two ZipEntry structs, by name.
+ */
+static int hashcmpZipEntry(const void* ventry1, const void* ventry2)
+{
+    const ZipEntry* entry1 = (const ZipEntry*) ventry1;
+    const ZipEntry* entry2 = (const ZipEntry*) ventry2;
+
+    if (entry1->fileNameLen != entry2->fileNameLen)
+        return entry1->fileNameLen - entry2->fileNameLen;
+    return memcmp(entry1->fileName, entry2->fileName, entry1->fileNameLen);
+}
+
+/*
+ * (This is a mzHashTableLookup callback.)
+ *
+ * find a ZipEntry struct by name.
+ */
+static int hashcmpZipName(const void* ventry, const void* vname)
+{
+    const ZipEntry* entry = (const ZipEntry*) ventry;
+    const char* name = (const char*) vname;
+    unsigned int nameLen = strlen(name);
+
+    if (entry->fileNameLen != nameLen)
+        return entry->fileNameLen - nameLen;
+    return memcmp(entry->fileName, name, nameLen);
+}
+
+/*
+ * Compute the hash code for a ZipEntry filename.
+ *
+ * Not expected to be compatible with any other hash function, so we init
+ * to 2 to ensure it doesn't happen to match.
+ */
+static unsigned int computeHash(const char* name, int nameLen)
+{
+    unsigned int hash = 2;
+
+    while (nameLen--)
+        hash = hash * 31 + *name++;
+
+    return hash;
+}
+
+static void addEntryToHashTable(HashTable* pHash, ZipEntry* pEntry)
+{
+    unsigned int itemHash = computeHash(pEntry->fileName, pEntry->fileNameLen);
+    const ZipEntry* found;
+
+    found = (const ZipEntry*)mzHashTableLookup(pHash,
+                itemHash, pEntry, hashcmpZipEntry, true);
+    if (found != pEntry) {
+        LOGW("WARNING: duplicate entry '%.*s' in Zip\n",
+            found->fileNameLen, found->fileName);
+        /* keep going */
+    }
+}
+
+static int validFilename(const char *fileName, unsigned int fileNameLen)
+{
+    // Forbid super long filenames.
+    if (fileNameLen >= PATH_MAX) {
+        LOGW("Filename too long (%d chatacters)\n", fileNameLen);
+        return 0;
+    }
+
+    // Require all characters to be printable ASCII (no NUL, no UTF-8, etc).
+    unsigned int i;
+    for (i = 0; i < fileNameLen; ++i) {
+        if (fileName[i] < 32 || fileName[i] >= 127) {
+            LOGW("Filename contains invalid character '\%03o'\n", fileName[i]);
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+/*
+ * Parse the contents of a Zip archive.  After confirming that the file
+ * is in fact a Zip, we scan out the contents of the central directory and
+ * store it in a hash table.
+ *
+ * Returns "true" on success.
+ */
+static bool parseZipArchive(ZipArchive* pArchive, const MemMapping* pMap)
+{
+    bool result = false;
+    const unsigned char* ptr;
+    unsigned int i, numEntries, cdOffset;
+    unsigned int val;
+
+    /*
+     * The first 4 bytes of the file will either be the local header
+     * signature for the first file (LOCSIG) or, if the archive doesn't
+     * have any files in it, the end-of-central-directory signature (ENDSIG).
+     */
+    val = get4LE(pMap->addr);
+    if (val == ENDSIG) {
+        LOGI("Found Zip archive, but it looks empty\n");
+        goto bail;
+    } else if (val != LOCSIG) {
+        LOGV("Not a Zip archive (found 0x%08x)\n", val);
+        goto bail;
+    }
+
+    /*
+     * Find the EOCD.  We'll find it immediately unless they have a file
+     * comment.
+     */
+    ptr = pMap->addr + pMap->length - ENDHDR;
+
+    while (ptr >= (const unsigned char*) pMap->addr) {
+        if (*ptr == (ENDSIG & 0xff) && get4LE(ptr) == ENDSIG)
+            break;
+        ptr--;
+    }
+    if (ptr < (const unsigned char*) pMap->addr) {
+        LOGI("Could not find end-of-central-directory in Zip\n");
+        goto bail;
+    }
+
+    /*
+     * There are two interesting items in the EOCD block: the number of
+     * entries in the file, and the file offset of the start of the
+     * central directory.
+     */
+    numEntries = get2LE(ptr + ENDSUB);
+    cdOffset = get4LE(ptr + ENDOFF);
+
+    LOGVV("numEntries=%d cdOffset=%d\n", numEntries, cdOffset);
+    if (numEntries == 0 || cdOffset >= pMap->length) {
+        LOGW("Invalid entries=%d offset=%d (len=%zd)\n",
+            numEntries, cdOffset, pMap->length);
+        goto bail;
+    }
+
+    /*
+     * Create data structures to hold entries.
+     */
+    pArchive->numEntries = numEntries;
+    pArchive->pEntries = (ZipEntry*) calloc(numEntries, sizeof(ZipEntry));
+    pArchive->pHash = mzHashTableCreate(mzHashSize(numEntries), NULL);
+    if (pArchive->pEntries == NULL || pArchive->pHash == NULL)
+        goto bail;
+
+    ptr = pMap->addr + cdOffset;
+    for (i = 0; i < numEntries; i++) {
+        ZipEntry* pEntry;
+        unsigned int fileNameLen, extraLen, commentLen, localHdrOffset;
+        const unsigned char* localHdr;
+        const char *fileName;
+
+        if (ptr + CENHDR > (const unsigned char*)pMap->addr + pMap->length) {
+            LOGW("Ran off the end (at %d)\n", i);
+            goto bail;
+        }
+        if (get4LE(ptr) != CENSIG) {
+            LOGW("Missed a central dir sig (at %d)\n", i);
+            goto bail;
+        }
+
+        localHdrOffset = get4LE(ptr + CENOFF);
+        fileNameLen = get2LE(ptr + CENNAM);
+        extraLen = get2LE(ptr + CENEXT);
+        commentLen = get2LE(ptr + CENCOM);
+        fileName = (const char*)ptr + CENHDR;
+        if (fileName + fileNameLen > (const char*)pMap->addr + pMap->length) {
+            LOGW("Filename ran off the end (at %d)\n", i);
+            goto bail;
+        }
+        if (!validFilename(fileName, fileNameLen)) {
+            LOGW("Invalid filename (at %d)\n", i);
+            goto bail;
+        }
+
+#if SORT_ENTRIES
+        /* Figure out where this entry should go (binary search).
+         */
+        if (i > 0) {
+            int low, high;
+
+            low = 0;
+            high = i - 1;
+            while (low <= high) {
+                int mid;
+                int diff;
+                int diffLen;
+
+                mid = low + ((high - low) / 2); // avoid overflow
+
+                if (pArchive->pEntries[mid].fileNameLen < fileNameLen) {
+                    diffLen = pArchive->pEntries[mid].fileNameLen;
+                } else {
+                    diffLen = fileNameLen;
+                }
+                diff = strncmp(pArchive->pEntries[mid].fileName, fileName,
+                        diffLen);
+                if (diff == 0) {
+                    diff = pArchive->pEntries[mid].fileNameLen - fileNameLen;
+                }
+                if (diff < 0) {
+                    low = mid + 1;
+                } else if (diff > 0) {
+                    high = mid - 1;
+                } else {
+                    high = mid;
+                    break;
+                }
+            }
+
+            unsigned int target = high + 1;
+            assert(target <= i);
+            if (target != i) {
+                /* It belongs somewhere other than at the end of
+                 * the list.  Make some room at [target].
+                 */
+                memmove(pArchive->pEntries + target + 1,
+                        pArchive->pEntries + target,
+                        (i - target) * sizeof(ZipEntry));
+            }
+            pEntry = &pArchive->pEntries[target];
+        } else {
+            pEntry = &pArchive->pEntries[0];
+        }
+#else
+        pEntry = &pArchive->pEntries[i];
+#endif
+
+        //LOGI("%d: localHdr=%d fnl=%d el=%d cl=%d\n",
+        //    i, localHdrOffset, fileNameLen, extraLen, commentLen);
+
+        pEntry->fileNameLen = fileNameLen;
+        pEntry->fileName = fileName;
+
+        pEntry->compLen = get4LE(ptr + CENSIZ);
+        pEntry->uncompLen = get4LE(ptr + CENLEN);
+        pEntry->compression = get2LE(ptr + CENHOW);
+        pEntry->modTime = get4LE(ptr + CENTIM);
+        pEntry->crc32 = get4LE(ptr + CENCRC);
+
+        /* These two are necessary for finding the mode of the file.
+         */
+        pEntry->versionMadeBy = get2LE(ptr + CENVEM);
+        if ((pEntry->versionMadeBy & 0xff00) != 0 &&
+                (pEntry->versionMadeBy & 0xff00) != CENVEM_UNIX)
+        {
+            LOGW("Incompatible \"version made by\": 0x%02x (at %d)\n",
+                    pEntry->versionMadeBy >> 8, i);
+            goto bail;
+        }
+        pEntry->externalFileAttributes = get4LE(ptr + CENATX);
+
+        // Perform pMap->addr + localHdrOffset, ensuring that it won't
+        // overflow. This is needed because localHdrOffset is untrusted.
+        if (!safe_add((uintptr_t *)&localHdr, (uintptr_t)pMap->addr,
+            (uintptr_t)localHdrOffset)) {
+            LOGW("Integer overflow adding in parseZipArchive\n");
+            goto bail;
+        }
+        if ((uintptr_t)localHdr + LOCHDR >
+            (uintptr_t)pMap->addr + pMap->length) {
+            LOGW("Bad offset to local header: %d (at %d)\n", localHdrOffset, i);
+            goto bail;
+        }
+        if (get4LE(localHdr) != LOCSIG) {
+            LOGW("Missed a local header sig (at %d)\n", i);
+            goto bail;
+        }
+        pEntry->offset = localHdrOffset + LOCHDR
+            + get2LE(localHdr + LOCNAM) + get2LE(localHdr + LOCEXT);
+        if (!safe_add(NULL, pEntry->offset, pEntry->compLen)) {
+            LOGW("Integer overflow adding in parseZipArchive\n");
+            goto bail;
+        }
+        if ((size_t)pEntry->offset + pEntry->compLen > pMap->length) {
+            LOGW("Data ran off the end (at %d)\n", i);
+            goto bail;
+        }
+
+#if !SORT_ENTRIES
+        /* Add to hash table; no need to lock here.
+         * Can't do this now if we're sorting, because entries
+         * will move around.
+         */
+        addEntryToHashTable(pArchive->pHash, pEntry);
+#endif
+
+        //dumpEntry(pEntry);
+        ptr += CENHDR + fileNameLen + extraLen + commentLen;
+    }
+
+#if SORT_ENTRIES
+    /* If we're sorting, we have to wait until all entries
+     * are in their final places, otherwise the pointers will
+     * probably point to the wrong things.
+     */
+    for (i = 0; i < numEntries; i++) {
+        /* Add to hash table; no need to lock here.
+         */
+        addEntryToHashTable(pArchive->pHash, &pArchive->pEntries[i]);
+    }
+#endif
+
+    result = true;
+
+bail:
+    if (!result) {
+        mzHashTableFree(pArchive->pHash);
+        pArchive->pHash = NULL;
+    }
+    return result;
+}
+
+/*
+ * Open a Zip archive and scan out the contents.
+ *
+ * The easiest way to do this is to mmap() the whole thing and do the
+ * traditional backward scan for central directory.  Since the EOCD is
+ * a relatively small bit at the end, we should end up only touching a
+ * small set of pages.
+ *
+ * This will be called on non-Zip files, especially during startup, so
+ * we don't want to be too noisy about failures.  (Do we want a "quiet"
+ * flag?)
+ *
+ * On success, we fill out the contents of "pArchive".
+ */
+int mzOpenZipArchive(const char* fileName, ZipArchive* pArchive)
+{
+    MemMapping map;
+    int err;
+
+    LOGV("Opening archive '%s' %p\n", fileName, pArchive);
+
+    map.addr = NULL;
+    memset(pArchive, 0, sizeof(*pArchive));
+
+    pArchive->fd = open(fileName, O_RDONLY, 0);
+    if (pArchive->fd < 0) {
+        err = errno ? errno : -1;
+        LOGV("Unable to open '%s': %s\n", fileName, strerror(err));
+        goto bail;
+    }
+
+    if (sysMapFileInShmem(pArchive->fd, &map) != 0) {
+        err = -1;
+        LOGW("Map of '%s' failed\n", fileName);
+        goto bail;
+    }
+
+    if (map.length < ENDHDR) {
+        err = -1;
+        LOGV("File '%s' too small to be zip (%zd)\n", fileName, map.length);
+        goto bail;
+    }
+
+    if (!parseZipArchive(pArchive, &map)) {
+        err = -1;
+        LOGV("Parsing '%s' failed\n", fileName);
+        goto bail;
+    }
+
+    err = 0;
+    sysCopyMap(&pArchive->map, &map);
+    map.addr = NULL;
+
+bail:
+    if (err != 0)
+        mzCloseZipArchive(pArchive);
+    if (map.addr != NULL)
+        sysReleaseShmem(&map);
+    return err;
+}
+
+/*
+ * Close a ZipArchive, closing the file and freeing the contents.
+ *
+ * NOTE: the ZipArchive may not have been fully created.
+ */
+void mzCloseZipArchive(ZipArchive* pArchive)
+{
+    LOGV("Closing archive %p\n", pArchive);
+
+    if (pArchive->fd >= 0)
+        close(pArchive->fd);
+    if (pArchive->map.addr != NULL)
+        sysReleaseShmem(&pArchive->map);
+
+    free(pArchive->pEntries);
+
+    mzHashTableFree(pArchive->pHash);
+
+    pArchive->fd = -1;
+    pArchive->pHash = NULL;
+    pArchive->pEntries = NULL;
+}
+
+/*
+ * Find a matching entry.
+ *
+ * Returns NULL if no matching entry found.
+ */
+const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive,
+        const char* entryName)
+{
+    unsigned int itemHash = computeHash(entryName, strlen(entryName));
+
+    return (const ZipEntry*)mzHashTableLookup(pArchive->pHash,
+                itemHash, (char*) entryName, hashcmpZipName, false);
+}
+
+/*
+ * Return true if the entry is a symbolic link.
+ */
+bool mzIsZipEntrySymlink(const ZipEntry* pEntry)
+{
+    if ((pEntry->versionMadeBy & 0xff00) == CENVEM_UNIX) {
+        return S_ISLNK(pEntry->externalFileAttributes >> 16);
+    }
+    return false;
+}
+
+/* Call processFunction on the uncompressed data of a STORED entry.
+ */
+static bool processStoredEntry(const ZipArchive *pArchive,
+    const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
+    void *cookie)
+{
+    size_t bytesLeft = pEntry->compLen;
+    while (bytesLeft > 0) {
+        unsigned char buf[32 * 1024];
+        ssize_t n;
+        size_t count;
+        bool ret;
+
+        count = bytesLeft;
+        if (count > sizeof(buf)) {
+            count = sizeof(buf);
+        }
+        n = read(pArchive->fd, buf, count);
+        if (n < 0 || (size_t)n != count) {
+            LOGE("Can't read %zu bytes from zip file: %ld\n", count, n);
+            return false;
+        }
+        ret = processFunction(buf, n, cookie);
+        if (!ret) {
+            return false;
+        }
+        bytesLeft -= count;
+    }
+    return true;
+}
+
+static bool processDeflatedEntry(const ZipArchive *pArchive,
+    const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
+    void *cookie)
+{
+    long result = -1;
+    unsigned char readBuf[32 * 1024];
+    unsigned char procBuf[32 * 1024];
+    z_stream zstream;
+    int zerr;
+    long compRemaining;
+
+    compRemaining = pEntry->compLen;
+
+    /*
+     * Initialize the zlib stream.
+     */
+    memset(&zstream, 0, sizeof(zstream));
+    zstream.zalloc = Z_NULL;
+    zstream.zfree = Z_NULL;
+    zstream.opaque = Z_NULL;
+    zstream.next_in = NULL;
+    zstream.avail_in = 0;
+    zstream.next_out = (Bytef*) procBuf;
+    zstream.avail_out = sizeof(procBuf);
+    zstream.data_type = Z_UNKNOWN;
+
+    /*
+     * Use the undocumented "negative window bits" feature to tell zlib
+     * that there's no zlib header waiting for it.
+     */
+    zerr = inflateInit2(&zstream, -MAX_WBITS);
+    if (zerr != Z_OK) {
+        if (zerr == Z_VERSION_ERROR) {
+            LOGE("Installed zlib is not compatible with linked version (%s)\n",
+                ZLIB_VERSION);
+        } else {
+            LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
+        }
+        goto bail;
+    }
+
+    /*
+     * Loop while we have data.
+     */
+    do {
+        /* read as much as we can */
+        if (zstream.avail_in == 0) {
+            long getSize = (compRemaining > (long)sizeof(readBuf)) ?
+                        (long)sizeof(readBuf) : compRemaining;
+            LOGVV("+++ reading %ld bytes (%ld left)\n",
+                getSize, compRemaining);
+
+            int cc = read(pArchive->fd, readBuf, getSize);
+            if (cc != (int) getSize) {
+                LOGW("inflate read failed (%d vs %ld)\n", cc, getSize);
+                goto z_bail;
+            }
+
+            compRemaining -= getSize;
+
+            zstream.next_in = readBuf;
+            zstream.avail_in = getSize;
+        }
+
+        /* uncompress the data */
+        zerr = inflate(&zstream, Z_NO_FLUSH);
+        if (zerr != Z_OK && zerr != Z_STREAM_END) {
+            LOGD("zlib inflate call failed (zerr=%d)\n", zerr);
+            goto z_bail;
+        }
+
+        /* write when we're full or when we're done */
+        if (zstream.avail_out == 0 ||
+            (zerr == Z_STREAM_END && zstream.avail_out != sizeof(procBuf)))
+        {
+            long procSize = zstream.next_out - procBuf;
+            LOGVV("+++ processing %d bytes\n", (int) procSize);
+            bool ret = processFunction(procBuf, procSize, cookie);
+            if (!ret) {
+                LOGW("Process function elected to fail (in inflate)\n");
+                goto z_bail;
+            }
+
+            zstream.next_out = procBuf;
+            zstream.avail_out = sizeof(procBuf);
+        }
+    } while (zerr == Z_OK);
+
+    assert(zerr == Z_STREAM_END);       /* other errors should've been caught */
+
+    // success!
+    result = zstream.total_out;
+
+z_bail:
+    inflateEnd(&zstream);        /* free up any allocated structures */
+
+bail:
+    if (result != pEntry->uncompLen) {
+        if (result != -1)        // error already shown?
+            LOGW("Size mismatch on inflated file (%ld vs %ld)\n",
+                result, pEntry->uncompLen);
+        return false;
+    }
+    return true;
+}
+
+/*
+ * Stream the uncompressed data through the supplied function,
+ * passing cookie to it each time it gets called.  processFunction
+ * may be called more than once.
+ *
+ * If processFunction returns false, the operation is abandoned and
+ * mzProcessZipEntryContents() immediately returns false.
+ *
+ * This is useful for calculating the hash of an entry's uncompressed contents.
+ */
+bool mzProcessZipEntryContents(const ZipArchive *pArchive,
+    const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
+    void *cookie)
+{
+    bool ret = false;
+    off_t oldOff;
+
+    /* save current offset */
+    oldOff = lseek(pArchive->fd, 0, SEEK_CUR);
+
+    /* Seek to the beginning of the entry's compressed data. */
+    lseek(pArchive->fd, pEntry->offset, SEEK_SET);
+
+    switch (pEntry->compression) {
+    case STORED:
+        ret = processStoredEntry(pArchive, pEntry, processFunction, cookie);
+        break;
+    case DEFLATED:
+        ret = processDeflatedEntry(pArchive, pEntry, processFunction, cookie);
+        break;
+    default:
+        LOGE("Unsupported compression type %d for entry '%s'\n",
+                pEntry->compression, pEntry->fileName);
+        break;
+    }
+
+    /* restore file offset */
+    lseek(pArchive->fd, oldOff, SEEK_SET);
+    return ret;
+}
+
+static bool crcProcessFunction(const unsigned char *data, int dataLen,
+        void *crc)
+{
+    *(unsigned long *)crc = crc32(*(unsigned long *)crc, data, dataLen);
+    return true;
+}
+
+/*
+ * Check the CRC on this entry; return true if it is correct.
+ * May do other internal checks as well.
+ */
+bool mzIsZipEntryIntact(const ZipArchive *pArchive, const ZipEntry *pEntry)
+{
+    unsigned long crc;
+    bool ret;
+
+    crc = crc32(0L, Z_NULL, 0);
+    ret = mzProcessZipEntryContents(pArchive, pEntry, crcProcessFunction,
+            (void *)&crc);
+    if (!ret) {
+        LOGE("Can't calculate CRC for entry\n");
+        return false;
+    }
+    if (crc != (unsigned long)pEntry->crc32) {
+        LOGW("CRC for entry %.*s (0x%08lx) != expected (0x%08lx)\n",
+                pEntry->fileNameLen, pEntry->fileName, crc, pEntry->crc32);
+        return false;
+    }
+    return true;
+}
+
+typedef struct {
+    char *buf;
+    int bufLen;
+} CopyProcessArgs;
+
+static bool copyProcessFunction(const unsigned char *data, int dataLen,
+        void *cookie)
+{
+    CopyProcessArgs *args = (CopyProcessArgs *)cookie;
+    if (dataLen <= args->bufLen) {
+        memcpy(args->buf, data, dataLen);
+        args->buf += dataLen;
+        args->bufLen -= dataLen;
+        return true;
+    }
+    return false;
+}
+
+/*
+ * Read an entry into a buffer allocated by the caller.
+ */
+bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry,
+        char *buf, int bufLen)
+{
+    CopyProcessArgs args;
+    bool ret;
+
+    args.buf = buf;
+    args.bufLen = bufLen;
+    ret = mzProcessZipEntryContents(pArchive, pEntry, copyProcessFunction,
+            (void *)&args);
+    if (!ret) {
+        LOGE("Can't extract entry to buffer.\n");
+        return false;
+    }
+    return true;
+}
+
+static bool writeProcessFunction(const unsigned char *data, int dataLen,
+                                 void *cookie)
+{
+    int fd = (int)cookie;
+
+    ssize_t soFar = 0;
+    while (true) {
+        ssize_t n = write(fd, data+soFar, dataLen-soFar);
+        if (n <= 0) {
+            LOGE("Error writing %ld bytes from zip file from %p: %s\n",
+                 dataLen-soFar, data+soFar, strerror(errno));
+            if (errno != EINTR) {
+              return false;
+            }
+        } else if (n > 0) {
+            soFar += n;
+            if (soFar == dataLen) return true;
+            if (soFar > dataLen) {
+                LOGE("write overrun?  (%ld bytes instead of %d)\n",
+                     soFar, dataLen);
+                return false;
+            }
+        }
+    }
+}
+
+/*
+ * Uncompress "pEntry" in "pArchive" to "fd" at the current offset.
+ */
+bool mzExtractZipEntryToFile(const ZipArchive *pArchive,
+    const ZipEntry *pEntry, int fd)
+{
+    bool ret = mzProcessZipEntryContents(pArchive, pEntry, writeProcessFunction,
+                                         (void*)fd);
+    if (!ret) {
+        LOGE("Can't extract entry to file.\n");
+        return false;
+    }
+    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 {
+    const char *targetDir;
+    const char *zipDir;
+    char *buf;
+    int targetDirLen;
+    int zipDirLen;
+    int bufLen;
+} MzPathHelper;
+
+/* Given the values of targetDir and zipDir in the helper,
+ * return the target filename of the provided entry.
+ * The helper must be initialized first.
+ */
+static const char *targetEntryPath(MzPathHelper *helper, ZipEntry *pEntry)
+{
+    int needLen;
+    bool firstTime = (helper->buf == NULL);
+
+    /* target file <-- targetDir + / + entry[zipDirLen:]
+     */
+    needLen = helper->targetDirLen + 1 +
+            pEntry->fileNameLen - helper->zipDirLen + 1;
+    if (needLen > helper->bufLen) {
+        char *newBuf;
+
+        needLen *= 2;
+        newBuf = (char *)realloc(helper->buf, needLen);
+        if (newBuf == NULL) {
+            return NULL;
+        }
+        helper->buf = newBuf;
+        helper->bufLen = needLen;
+    }
+
+    /* Every path will start with the target path and a slash.
+     */
+    if (firstTime) {
+        char *p = helper->buf;
+        memcpy(p, helper->targetDir, helper->targetDirLen);
+        p += helper->targetDirLen;
+        if (p == helper->buf || p[-1] != '/') {
+            helper->targetDirLen += 1;
+            *p++ = '/';
+        }
+    }
+
+    /* Replace the custom part of the path with the appropriate
+     * part of the entry's path.
+     */
+    char *epath = helper->buf + helper->targetDirLen;
+    memcpy(epath, pEntry->fileName + helper->zipDirLen,
+            pEntry->fileNameLen - helper->zipDirLen);
+    epath += pEntry->fileNameLen - helper->zipDirLen;
+    *epath = '\0';
+
+    return helper->buf;
+}
+
+/*
+ * Inflate all entries under zipDir to the directory specified by
+ * targetDir, which must exist and be a writable directory.
+ *
+ * The immediate children of zipDir will become the immediate
+ * children of targetDir; e.g., if the archive contains the entries
+ *
+ *     a/b/c/one
+ *     a/b/c/two
+ *     a/b/c/d/three
+ *
+ * and mzExtractRecursive(a, "a/b/c", "/tmp") is called, the resulting
+ * files will be
+ *
+ *     /tmp/one
+ *     /tmp/two
+ *     /tmp/d/three
+ *
+ * Returns true on success, false on failure.
+ */
+bool mzExtractRecursive(const ZipArchive *pArchive,
+                        const char *zipDir, const char *targetDir,
+                        int flags, const struct utimbuf *timestamp,
+                        void (*callback)(const char *fn, void *), void *cookie)
+{
+    if (zipDir[0] == '/') {
+        LOGE("mzExtractRecursive(): zipDir must be a relative path.\n");
+        return false;
+    }
+    if (targetDir[0] != '/') {
+        LOGE("mzExtractRecursive(): targetDir must be an absolute path.\n");
+        return false;
+    }
+
+    unsigned int zipDirLen;
+    char *zpath;
+
+    zipDirLen = strlen(zipDir);
+    zpath = (char *)malloc(zipDirLen + 2);
+    if (zpath == NULL) {
+        LOGE("Can't allocate %d bytes for zip path\n", zipDirLen + 2);
+        return false;
+    }
+    /* If zipDir is empty, we'll extract the entire zip file.
+     * Otherwise, canonicalize the path.
+     */
+    if (zipDirLen > 0) {
+        /* Make sure there's (hopefully, exactly one) slash at the
+         * end of the path.  This way we don't need to worry about
+         * accidentally extracting "one/twothree" when a path like
+         * "one/two" is specified.
+         */
+        memcpy(zpath, zipDir, zipDirLen);
+        if (zpath[zipDirLen-1] != '/') {
+            zpath[zipDirLen++] = '/';
+        }
+    }
+    zpath[zipDirLen] = '\0';
+
+    /* Set up the helper structure that we'll use to assemble paths.
+     */
+    MzPathHelper helper;
+    helper.targetDir = targetDir;
+    helper.targetDirLen = strlen(helper.targetDir);
+    helper.zipDir = zpath;
+    helper.zipDirLen = strlen(helper.zipDir);
+    helper.buf = NULL;
+    helper.bufLen = 0;
+
+    /* Walk through the entries and extract anything whose path begins
+     * with zpath.
+//TODO: since the entries are sorted, binary search for the first match
+//      and stop after the first non-match.
+     */
+    unsigned int i;
+    bool seenMatch = false;
+    int ok = true;
+    for (i = 0; i < pArchive->numEntries; i++) {
+        ZipEntry *pEntry = pArchive->pEntries + i;
+        if (pEntry->fileNameLen < zipDirLen) {
+//TODO: look out for a single empty directory entry that matches zpath, but
+//      missing the trailing slash.  Most zip files seem to include
+//      the trailing slash, but I think it's legal to leave it off.
+//      e.g., zpath "a/b/", entry "a/b", with no children of the entry.
+            /* No chance of matching.
+             */
+#if SORT_ENTRIES
+            if (seenMatch) {
+                /* Since the entries are sorted, we can give up
+                 * on the first mismatch after the first match.
+                 */
+                break;
+            }
+#endif
+            continue;
+        }
+        /* If zpath is empty, this strncmp() will match everything,
+         * which is what we want.
+         */
+        if (strncmp(pEntry->fileName, zpath, zipDirLen) != 0) {
+#if SORT_ENTRIES
+            if (seenMatch) {
+                /* Since the entries are sorted, we can give up
+                 * on the first mismatch after the first match.
+                 */
+                break;
+            }
+#endif
+            continue;
+        }
+        /* This entry begins with zipDir, so we'll extract it.
+         */
+        seenMatch = true;
+
+        /* Find the target location of the entry.
+         */
+        const char *targetFile = targetEntryPath(&helper, pEntry);
+        if (targetFile == NULL) {
+            LOGE("Can't assemble target path for \"%.*s\"\n",
+                    pEntry->fileNameLen, pEntry->fileName);
+            ok = false;
+            break;
+        }
+
+        /* With DRY_RUN set, invoke the callback but don't do anything else.
+         */
+        if (flags & MZ_EXTRACT_DRY_RUN) {
+            if (callback != NULL) callback(targetFile, cookie);
+            continue;
+        }
+
+        /* Create the file or directory.
+         */
+#define UNZIP_DIRMODE 0755
+#define UNZIP_FILEMODE 0644
+        if (pEntry->fileName[pEntry->fileNameLen-1] == '/') {
+            if (!(flags & MZ_EXTRACT_FILES_ONLY)) {
+                int ret = dirCreateHierarchy(
+                        targetFile, UNZIP_DIRMODE, timestamp, false);
+                if (ret != 0) {
+                    LOGE("Can't create containing directory for \"%s\": %s\n",
+                            targetFile, strerror(errno));
+                    ok = false;
+                    break;
+                }
+                LOGD("Extracted dir \"%s\"\n", targetFile);
+            }
+        } else {
+            /* This is not a directory.  First, make sure that
+             * the containing directory exists.
+             */
+            int ret = dirCreateHierarchy(
+                    targetFile, UNZIP_DIRMODE, timestamp, true);
+            if (ret != 0) {
+                LOGE("Can't create containing directory for \"%s\": %s\n",
+                        targetFile, strerror(errno));
+                ok = false;
+                break;
+            }
+
+            /* With FILES_ONLY set, we need to ignore metadata entirely,
+             * so treat symlinks as regular files.
+             */
+            if (!(flags & MZ_EXTRACT_FILES_ONLY) && mzIsZipEntrySymlink(pEntry)) {
+                /* The entry is a symbolic link.
+                 * The relative target of the symlink is in the
+                 * data section of this entry.
+                 */
+                if (pEntry->uncompLen == 0) {
+                    LOGE("Symlink entry \"%s\" has no target\n",
+                            targetFile);
+                    ok = false;
+                    break;
+                }
+                char *linkTarget = malloc(pEntry->uncompLen + 1);
+                if (linkTarget == NULL) {
+                    ok = false;
+                    break;
+                }
+                ok = mzReadZipEntry(pArchive, pEntry, linkTarget,
+                        pEntry->uncompLen);
+                if (!ok) {
+                    LOGE("Can't read symlink target for \"%s\"\n",
+                            targetFile);
+                    free(linkTarget);
+                    break;
+                }
+                linkTarget[pEntry->uncompLen] = '\0';
+
+                /* Make the link.
+                 */
+                ret = symlink(linkTarget, targetFile);
+                if (ret != 0) {
+                    LOGE("Can't symlink \"%s\" to \"%s\": %s\n",
+                            targetFile, linkTarget, strerror(errno));
+                    free(linkTarget);
+                    ok = false;
+                    break;
+                }
+                LOGD("Extracted symlink \"%s\" -> \"%s\"\n",
+                        targetFile, linkTarget);
+                free(linkTarget);
+            } else {
+                /* The entry is a regular file.
+                 * Open the target for writing.
+                 */
+                int fd = creat(targetFile, UNZIP_FILEMODE);
+                if (fd < 0) {
+                    LOGE("Can't create target file \"%s\": %s\n",
+                            targetFile, strerror(errno));
+                    ok = false;
+                    break;
+                }
+
+                bool ok = mzExtractZipEntryToFile(pArchive, pEntry, fd);
+                close(fd);
+                if (!ok) {
+                    LOGE("Error extracting \"%s\"\n", targetFile);
+                    ok = false;
+                    break;
+                }
+
+                if (timestamp != NULL && utime(targetFile, timestamp)) {
+                    LOGE("Error touching \"%s\"\n", targetFile);
+                    ok = false;
+                    break;
+                }
+
+                LOGD("Extracted file \"%s\"\n", targetFile);
+            }
+        }
+
+        if (callback != NULL) callback(targetFile, cookie);
+    }
+
+    free(helper.buf);
+    free(zpath);
+
+    return ok;
+}
diff --git a/minzip/Zip.h b/minzip/Zip.h
new file mode 100644
index 0000000..9f99fba
--- /dev/null
+++ b/minzip/Zip.h
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Simple Zip archive support.
+ */
+#ifndef _MINZIP_ZIP
+#define _MINZIP_ZIP
+
+#include "inline_magic.h"
+
+#include <stdlib.h>
+#include <utime.h>
+
+#include "Hash.h"
+#include "SysUtil.h"
+
+/*
+ * One entry in the Zip archive.  Treat this as opaque -- use accessors below.
+ *
+ * TODO: we're now keeping the pages mapped so we don't have to copy the
+ * filename.  We can change the accessors to retrieve the various pieces
+ * directly from the source file instead of copying them out, for a very
+ * slight speed hit and a modest reduction in memory usage.
+ */
+typedef struct ZipEntry {
+    unsigned int fileNameLen;
+    const char*  fileName;       // not null-terminated
+    long         offset;
+    long         compLen;
+    long         uncompLen;
+    int          compression;
+    long         modTime;
+    long         crc32;
+    int          versionMadeBy;
+    long         externalFileAttributes;
+} ZipEntry;
+
+/*
+ * One Zip archive.  Treat as opaque.
+ */
+typedef struct ZipArchive {
+    int         fd;
+    unsigned int numEntries;
+    ZipEntry*   pEntries;
+    HashTable*  pHash;          // maps file name to ZipEntry
+    MemMapping  map;
+} ZipArchive;
+
+/*
+ * Represents a non-NUL-terminated string,
+ * which is how entry names are stored.
+ */
+typedef struct {
+    const char *str;
+    size_t len;
+} UnterminatedString;
+
+/*
+ * Open a Zip archive.
+ *
+ * On success, returns 0 and populates "pArchive".  Returns nonzero errno
+ * value on failure.
+ */
+int mzOpenZipArchive(const char* fileName, ZipArchive* pArchive);
+
+/*
+ * Close archive, releasing resources associated with it.
+ *
+ * Depending on the implementation this could unmap pages used by classes
+ * stored in a Jar.  This should only be done after unloading classes.
+ */
+void mzCloseZipArchive(ZipArchive* pArchive);
+
+
+/*
+ * Find an entry in the Zip archive, by name.
+ */
+const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive,
+        const char* entryName);
+
+/*
+ * Get the number of entries in the Zip archive.
+ */
+INLINE unsigned int mzZipEntryCount(const ZipArchive* pArchive) {
+    return pArchive->numEntries;
+}
+
+/*
+ * Get an entry by index.  Returns NULL if the index is out-of-bounds.
+ */
+INLINE const ZipEntry*
+mzGetZipEntryAt(const ZipArchive* pArchive, unsigned int index)
+{
+    if (index < pArchive->numEntries) {
+        return pArchive->pEntries + index;
+    }
+    return NULL;
+}
+
+/*
+ * Get the index number of an entry in the archive.
+ */
+INLINE unsigned int
+mzGetZipEntryIndex(const ZipArchive *pArchive, const ZipEntry *pEntry) {
+    return pEntry - pArchive->pEntries;
+}
+
+/*
+ * Simple accessors.
+ */
+INLINE UnterminatedString mzGetZipEntryFileName(const ZipEntry* pEntry) {
+    UnterminatedString ret;
+    ret.str = pEntry->fileName;
+    ret.len = pEntry->fileNameLen;
+    return ret;
+}
+INLINE long mzGetZipEntryOffset(const ZipEntry* pEntry) {
+    return pEntry->offset;
+}
+INLINE long mzGetZipEntryUncompLen(const ZipEntry* pEntry) {
+    return pEntry->uncompLen;
+}
+INLINE long mzGetZipEntryModTime(const ZipEntry* pEntry) {
+    return pEntry->modTime;
+}
+INLINE long mzGetZipEntryCrc32(const ZipEntry* pEntry) {
+    return pEntry->crc32;
+}
+bool mzIsZipEntrySymlink(const ZipEntry* pEntry);
+
+
+/*
+ * Type definition for the callback function used by
+ * mzProcessZipEntryContents().
+ */
+typedef bool (*ProcessZipEntryContentsFunction)(const unsigned char *data,
+    int dataLen, void *cookie);
+
+/*
+ * Stream the uncompressed data through the supplied function,
+ * passing cookie to it each time it gets called.  processFunction
+ * may be called more than once.
+ *
+ * If processFunction returns false, the operation is abandoned and
+ * mzProcessZipEntryContents() immediately returns false.
+ *
+ * This is useful for calculating the hash of an entry's uncompressed contents.
+ */
+bool mzProcessZipEntryContents(const ZipArchive *pArchive,
+    const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
+    void *cookie);
+
+/*
+ * Read an entry into a buffer allocated by the caller.
+ */
+bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry,
+        char* buf, int bufLen);
+
+/*
+ * Check the CRC on this entry; return true if it is correct.
+ * May do other internal checks as well.
+ */
+bool mzIsZipEntryIntact(const ZipArchive *pArchive, const ZipEntry *pEntry);
+
+/*
+ * Inflate and write an entry to a file.
+ */
+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.
+ *
+ * The immediate children of zipDir will become the immediate
+ * children of targetDir; e.g., if the archive contains the entries
+ *
+ *     a/b/c/one
+ *     a/b/c/two
+ *     a/b/c/d/three
+ *
+ * and mzExtractRecursive(a, "a/b/c", "/tmp", ...) is called, the resulting
+ * files will be
+ *
+ *     /tmp/one
+ *     /tmp/two
+ *     /tmp/d/three
+ *
+ * flags is zero or more of the following:
+ *
+ *     MZ_EXTRACT_FILES_ONLY - only unpack files, not directories or symlinks
+ *     MZ_EXTRACT_DRY_RUN - don't do anything, but do invoke the callback
+ *
+ * If timestamp is non-NULL, file timestamps will be set accordingly.
+ *
+ * If callback is non-NULL, it will be invoked with each unpacked file.
+ *
+ * Returns true on success, false on failure.
+ */
+enum { MZ_EXTRACT_FILES_ONLY = 1, MZ_EXTRACT_DRY_RUN = 2 };
+bool mzExtractRecursive(const ZipArchive *pArchive,
+        const char *zipDir, const char *targetDir,
+        int flags, const struct utimbuf *timestamp,
+        void (*callback)(const char *fn, void*), void *cookie);
+
+#endif /*_MINZIP_ZIP*/
diff --git a/minzip/inline_magic.h b/minzip/inline_magic.h
new file mode 100644
index 0000000..8c185e1
--- /dev/null
+++ b/minzip/inline_magic.h
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+#ifndef MINZIP_INLINE_MAGIC_H_
+#define MINZIP_INLINE_MAGIC_H_
+
+#ifndef MINZIP_GENERATE_INLINES
+#define INLINE extern __inline__
+#else
+#define INLINE
+#endif
+
+#endif  // MINZIP_INLINE_MAGIC_H_
diff --git a/mmcutils/Android.mk b/mmcutils/Android.mk
new file mode 100644
index 0000000..f1fe294
--- /dev/null
+++ b/mmcutils/Android.mk
@@ -0,0 +1,16 @@
+ifneq ($(TARGET_SIMULATOR),true)
+ifeq ($(TARGET_ARCH),arm)
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	mmcutils.c
+
+LOCAL_MODULE := libmmcutils
+LOCAL_MODULE_TAGS := eng
+
+include $(BUILD_STATIC_LIBRARY)
+
+endif	# TARGET_ARCH == arm
+endif	# !TARGET_SIMULATOR
diff --git a/mmcutils/mmcutils.c b/mmcutils/mmcutils.c
new file mode 100644
index 0000000..e0efe55
--- /dev/null
+++ b/mmcutils/mmcutils.c
@@ -0,0 +1,637 @@
+/*
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * 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.
+ *   * Neither the name of Code Aurora Forum, Inc. nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/mount.h>  // for _IOW, _IOR, mount()
+
+#include "mmcutils.h"
+
+unsigned ext3_count = 0;
+char *ext3_partitions[] = {"system", "userdata", "cache", "NONE"};
+
+unsigned vfat_count = 0;
+char *vfat_partitions[] = {"modem", "NONE"};
+
+struct MmcPartition {
+    char *device_index;
+    char *filesystem;
+    char *name;
+    unsigned dstatus;
+    unsigned dtype ;
+    unsigned dfirstsec;
+    unsigned dsize;
+};
+
+typedef struct {
+    MmcPartition *partitions;
+    int partitions_allocd;
+    int partition_count;
+} MmcState;
+
+static MmcState g_mmc_state = {
+    NULL,   // partitions
+    0,      // partitions_allocd
+    -1      // partition_count
+};
+
+#define MMC_DEVICENAME "/dev/block/mmcblk0"
+
+static void
+mmc_partition_name (MmcPartition *mbr, unsigned int type) {
+    switch(type)
+    {
+        char name[64];
+        case MMC_BOOT_TYPE:
+            sprintf(name,"boot");
+            mbr->name = strdup(name);
+            break;
+        case MMC_RECOVERY_TYPE:
+            sprintf(name,"recovery");
+            mbr->name = strdup(name);
+            break;
+        case MMC_EXT3_TYPE:
+            if (strcmp("NONE", ext3_partitions[ext3_count])) {
+                strcpy((char *)name,(const char *)ext3_partitions[ext3_count]);
+                mbr->name = strdup(name);
+                ext3_count++;
+            }
+            mbr->filesystem = strdup("ext3");
+            break;
+        case MMC_VFAT_TYPE:
+            if (strcmp("NONE", vfat_partitions[vfat_count])) {
+                strcpy((char *)name,(const char *)vfat_partitions[vfat_count]);
+                mbr->name = strdup(name);
+                vfat_count++;
+            }
+            mbr->filesystem = strdup("vfat");
+            break;
+    };
+}
+
+static int
+mmc_read_mbr (const char *device, MmcPartition *mbr) {
+    FILE *fd;
+    unsigned char buffer[512];
+    int idx, i;
+    unsigned mmc_partition_count = 0;
+    unsigned int dtype;
+    unsigned int dfirstsec;
+    unsigned int EBR_first_sec;
+    unsigned int EBR_current_sec;
+    int ret = -1;
+
+    fd = fopen(device, "r");
+    if(fd == NULL)
+    {
+        printf("Can't open device: \"%s\"\n", device);
+        goto ERROR2;
+    }
+    if ((fread(buffer, 512, 1, fd)) != 1)
+    {
+        printf("Can't read device: \"%s\"\n", device);
+        goto ERROR1;
+    }
+    /* Check to see if signature exists */
+    if ((buffer[TABLE_SIGNATURE] != 0x55) || \
+        (buffer[TABLE_SIGNATURE + 1] != 0xAA))
+    {
+        printf("Incorrect mbr signatures!\n");
+        goto ERROR1;
+    }
+    idx = TABLE_ENTRY_0;
+    for (i = 0; i < 4; i++)
+    {
+        char device_index[128];
+
+        mbr[mmc_partition_count].dstatus = \
+                    buffer[idx + i * TABLE_ENTRY_SIZE + OFFSET_STATUS];
+        mbr[mmc_partition_count].dtype   = \
+                    buffer[idx + i * TABLE_ENTRY_SIZE + OFFSET_TYPE];
+        mbr[mmc_partition_count].dfirstsec = \
+                    GET_LWORD_FROM_BYTE(&buffer[idx + \
+                                        i * TABLE_ENTRY_SIZE + \
+                                        OFFSET_FIRST_SEC]);
+        mbr[mmc_partition_count].dsize  = \
+                    GET_LWORD_FROM_BYTE(&buffer[idx + \
+                                        i * TABLE_ENTRY_SIZE + \
+                                        OFFSET_SIZE]);
+        dtype  = mbr[mmc_partition_count].dtype;
+        dfirstsec = mbr[mmc_partition_count].dfirstsec;
+        mmc_partition_name(&mbr[mmc_partition_count], \
+                        mbr[mmc_partition_count].dtype);
+
+        sprintf(device_index, "%sp%d", device, (mmc_partition_count+1));
+        mbr[mmc_partition_count].device_index = strdup(device_index);
+
+        mmc_partition_count++;
+        if (mmc_partition_count == MAX_PARTITIONS)
+            goto SUCCESS;
+    }
+
+    /* See if the last partition is EBR, if not, parsing is done */
+    if (dtype != 0x05)
+    {
+        goto SUCCESS;
+    }
+
+    EBR_first_sec = dfirstsec;
+    EBR_current_sec = dfirstsec;
+
+    fseek (fd, (EBR_first_sec * 512), SEEK_SET);
+    if ((fread(buffer, 512, 1, fd)) != 1)
+        goto ERROR1;
+
+    /* Loop to parse the EBR */
+    for (i = 0;; i++)
+    {
+        char device_index[128];
+
+        if ((buffer[TABLE_SIGNATURE] != 0x55) || (buffer[TABLE_SIGNATURE + 1] != 0xAA))
+        {
+            break;
+        }
+        mbr[mmc_partition_count].dstatus = \
+                    buffer[TABLE_ENTRY_0 + OFFSET_STATUS];
+        mbr[mmc_partition_count].dtype   = \
+                    buffer[TABLE_ENTRY_0 + OFFSET_TYPE];
+        mbr[mmc_partition_count].dfirstsec = \
+                    GET_LWORD_FROM_BYTE(&buffer[TABLE_ENTRY_0 + \
+                                        OFFSET_FIRST_SEC])    + \
+                                        EBR_current_sec;
+        mbr[mmc_partition_count].dsize = \
+                    GET_LWORD_FROM_BYTE(&buffer[TABLE_ENTRY_0 + \
+                                        OFFSET_SIZE]);
+        mmc_partition_name(&mbr[mmc_partition_count], \
+                        mbr[mmc_partition_count].dtype);
+
+        sprintf(device_index, "%sp%d", device, (mmc_partition_count+1));
+        mbr[mmc_partition_count].device_index = strdup(device_index);
+
+        mmc_partition_count++;
+        if (mmc_partition_count == MAX_PARTITIONS)
+            goto SUCCESS;
+
+        dfirstsec = GET_LWORD_FROM_BYTE(&buffer[TABLE_ENTRY_1 + OFFSET_FIRST_SEC]);
+        if(dfirstsec == 0)
+        {
+            /* Getting to the end of the EBR tables */
+            break;
+        }
+        /* More EBR to follow - read in the next EBR sector */
+        fseek (fd,  ((EBR_first_sec + dfirstsec) * 512), SEEK_SET);
+        if ((fread(buffer, 512, 1, fd)) != 1)
+            goto ERROR1;
+
+        EBR_current_sec = EBR_first_sec + dfirstsec;
+    }
+
+SUCCESS:
+    ret = mmc_partition_count;
+ERROR1:
+    fclose(fd);
+ERROR2:
+    return ret;
+}
+
+int
+mmc_scan_partitions() {
+    int i;
+    ssize_t nbytes;
+
+    if (g_mmc_state.partitions == NULL) {
+        const int nump = MAX_PARTITIONS;
+        MmcPartition *partitions = malloc(nump * sizeof(*partitions));
+        if (partitions == NULL) {
+            errno = ENOMEM;
+            return -1;
+        }
+        g_mmc_state.partitions = partitions;
+        g_mmc_state.partitions_allocd = nump;
+        memset(partitions, 0, nump * sizeof(*partitions));
+    }
+    g_mmc_state.partition_count = 0;
+    ext3_count = 0;
+    vfat_count = 0;
+
+    /* Initialize all of the entries to make things easier later.
+     * (Lets us handle sparsely-numbered partitions, which
+     * may not even be possible.)
+     */
+    for (i = 0; i < g_mmc_state.partitions_allocd; i++) {
+        MmcPartition *p = &g_mmc_state.partitions[i];
+        if (p->device_index != NULL) {
+            free(p->device_index);
+            p->device_index = NULL;
+        }
+        if (p->name != NULL) {
+            free(p->name);
+            p->name = NULL;
+        }
+        if (p->filesystem != NULL) {
+            free(p->filesystem);
+            p->filesystem = NULL;
+        }
+    }
+
+    g_mmc_state.partition_count = mmc_read_mbr(MMC_DEVICENAME, g_mmc_state.partitions);
+    if(g_mmc_state.partition_count == -1)
+    {
+        printf("Error in reading mbr!\n");
+        // keep "partitions" around so we can free the names on a rescan.
+        g_mmc_state.partition_count = -1;
+    }
+    return g_mmc_state.partition_count;
+}
+
+static const MmcPartition *
+mmc_find_partition_by_device_index(const char *device_index)
+{
+    if (g_mmc_state.partitions != NULL) {
+        int i;
+        for (i = 0; i < g_mmc_state.partitions_allocd; i++) {
+            MmcPartition *p = &g_mmc_state.partitions[i];
+            if (p->device_index !=NULL && p->name != NULL) {
+                if (strcmp(p->device_index, device_index) == 0) {
+                    return p;
+                }
+            }
+        }
+    }
+    return NULL;
+}
+
+const MmcPartition *
+mmc_find_partition_by_name(const char *name)
+{
+    if (name[0] == '/') {
+        return mmc_find_partition_by_device_index(name);
+    }
+
+    if (g_mmc_state.partitions != NULL) {
+        int i;
+        for (i = 0; i < g_mmc_state.partitions_allocd; i++) {
+            MmcPartition *p = &g_mmc_state.partitions[i];
+            if (p->device_index !=NULL && p->name != NULL) {
+                if (strcmp(p->name, name) == 0) {
+                    return p;
+                }
+            }
+        }
+    }
+    return NULL;
+}
+
+#define MKE2FS_BIN      "/sbin/mke2fs"
+#define TUNE2FS_BIN     "/sbin/tune2fs"
+#define E2FSCK_BIN      "/sbin/e2fsck"
+
+int
+run_exec_process ( char **argv) {
+    pid_t pid;
+    int status;
+    pid = fork();
+    if (pid == 0) {
+        execv(argv[0], argv);
+        fprintf(stderr, "E:Can't run (%s)\n",strerror(errno));
+        _exit(-1);
+    }
+
+    waitpid(pid, &status, 0);
+    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+        return 1;
+    }
+    return 0;
+}
+
+int
+format_ext3_device (const char *device) {
+    // Run mke2fs
+    char *const mke2fs[] = {MKE2FS_BIN, "-j", device, NULL};
+    if(run_exec_process(mke2fs))
+        return -1;
+
+    // Run tune2fs
+    char *const tune2fs[] = {TUNE2FS_BIN, "-j", "-C", "1", device, NULL};
+    if(run_exec_process(tune2fs))
+        return -1;
+
+    // Run e2fsck
+    char *const e2fsck[] = {E2FSCK_BIN, "-fy", device, NULL};
+    if(run_exec_process(e2fsck))
+        return -1;
+
+    return 0;
+}
+
+int
+format_ext2_device (const char *device) {
+    // Run mke2fs
+    char *const mke2fs[] = {MKE2FS_BIN, device, NULL};
+    if(run_exec_process(mke2fs))
+        return -1;
+
+    // Run tune2fs
+    char *const tune2fs[] = {TUNE2FS_BIN, "-C", "1", device, NULL};
+    if(run_exec_process(tune2fs))
+        return -1;
+
+    // Run e2fsck
+    char *const e2fsck[] = {E2FSCK_BIN, "-fy", device, NULL};
+    if(run_exec_process(e2fsck))
+        return -1;
+
+    return 0;
+}
+
+int
+mmc_format_ext3 (MmcPartition *partition) {
+    char device[128];
+    strcpy(device, partition->device_index);
+    return format_ext3_device(device);
+}
+
+int
+mmc_mount_partition(const MmcPartition *partition, const char *mount_point,
+        int read_only)
+{
+    const unsigned long flags = MS_NOATIME | MS_NODEV | MS_NODIRATIME;
+    char devname[128];
+    int rv = -1;
+    strcpy(devname, partition->device_index);
+    if (partition->filesystem == NULL) {
+        printf("Null filesystem!\n");
+        return rv;
+    }
+    if (!read_only) {
+        rv = mount(devname, mount_point, partition->filesystem, flags, NULL);
+    }
+    if (read_only || rv < 0) {
+        rv = mount(devname, mount_point, partition->filesystem, flags | MS_RDONLY, 0);
+        if (rv < 0) {
+            printf("Failed to mount %s on %s: %s\n",
+                    devname, mount_point, strerror(errno));
+        } else {
+            printf("Mount %s on %s read-only\n", devname, mount_point);
+        }
+    }
+    return rv;
+}
+
+int
+mmc_raw_copy (const MmcPartition *partition, char *in_file) {
+    int ch;
+    FILE *in;
+    FILE *out;
+    int val = 0;
+    char buf[512];
+    unsigned sz = 0;
+    unsigned i;
+    int ret = -1;
+    char *out_file = partition->device_index;
+
+    in  = fopen ( in_file,  "r" );
+    if (in == NULL)
+        goto ERROR3;
+
+    out = fopen ( out_file,  "w" );
+    if (out == NULL)
+        goto ERROR2;
+
+    fseek(in, 0L, SEEK_END);
+    sz = ftell(in);
+    fseek(in, 0L, SEEK_SET);
+
+    if (sz % 512)
+    {
+        while ( ( ch = fgetc ( in ) ) != EOF )
+            fputc ( ch, out );
+    }
+    else
+    {
+        for (i=0; i< (sz/512); i++)
+        {
+            if ((fread(buf, 512, 1, in)) != 1)
+                goto ERROR1;
+            if ((fwrite(buf, 512, 1, out)) != 1)
+                goto ERROR1;
+        }
+    }
+
+    fsync(out);
+    ret = 0;
+ERROR1:
+    fclose ( out );
+ERROR2:
+    fclose ( in );
+ERROR3:
+    return ret;
+
+}
+
+
+// TODO: refactor this to not be a giant copy paste mess
+int
+mmc_raw_dump (const MmcPartition *partition, char *out_file) {
+    int ch;
+    FILE *in;
+    FILE *out;
+    int val = 0;
+    char buf[512];
+    unsigned sz = 0;
+    unsigned i;
+    int ret = -1;
+    char *in_file = partition->device_index;
+
+    in  = fopen ( in_file,  "r" );
+    if (in == NULL)
+        goto ERROR3;
+
+    out = fopen ( out_file,  "w" );
+    if (out == NULL)
+        goto ERROR2;
+
+    fseek(in, 0L, SEEK_END);
+    sz = ftell(in);
+    fseek(in, 0L, SEEK_SET);
+
+    if (sz % 512)
+    {
+        while ( ( ch = fgetc ( in ) ) != EOF )
+            fputc ( ch, out );
+    }
+    else
+    {
+        for (i=0; i< (sz/512); i++)
+        {
+            if ((fread(buf, 512, 1, in)) != 1)
+                goto ERROR1;
+            if ((fwrite(buf, 512, 1, out)) != 1)
+                goto ERROR1;
+        }
+    }
+
+    fsync(out);
+    ret = 0;
+ERROR1:
+    fclose ( out );
+ERROR2:
+    fclose ( in );
+ERROR3:
+    return ret;
+
+}
+
+
+int
+mmc_raw_read (const MmcPartition *partition, char *data, int data_size) {
+    int ch;
+    FILE *in;
+    int val = 0;
+    char buf[512];
+    unsigned sz = 0;
+    unsigned i;
+    int ret = -1;
+    char *in_file = partition->device_index;
+
+    in  = fopen ( in_file,  "r" );
+    if (in == NULL)
+        goto ERROR3;
+
+    fseek(in, 0L, SEEK_END);
+    sz = ftell(in);
+    fseek(in, 0L, SEEK_SET);
+
+    fread(data, data_size, 1, in);
+
+    ret = 0;
+ERROR1:
+ERROR2:
+    fclose ( in );
+ERROR3:
+    return ret;
+
+}
+
+int
+mmc_raw_write (const MmcPartition *partition, char *data, int data_size) {
+    int ch;
+    FILE *out;
+    int val = 0;
+    char buf[512];
+    unsigned sz = 0;
+    unsigned i;
+    int ret = -1;
+    char *out_file = partition->device_index;
+
+    out  = fopen ( out_file,  "w" );
+    if (out == NULL)
+        goto ERROR3;
+
+    fwrite(data, data_size, 1, out);
+
+    ret = 0;
+ERROR1:
+ERROR2:
+    fclose ( out );
+ERROR3:
+    return ret;
+
+}
+
+int cmd_mmc_restore_raw_partition(const char *partition, const char *filename)
+{
+    mmc_scan_partitions();
+    const MmcPartition *p;
+    p = mmc_find_partition_by_name(partition);
+    if (p == NULL)
+        return -1;
+    return mmc_raw_copy(p, filename);
+}
+
+int cmd_mmc_backup_raw_partition(const char *partition, const char *filename)
+{
+    mmc_scan_partitions();
+    const MmcPartition *p;
+    p = mmc_find_partition_by_name(partition);
+    if (p == NULL)
+        return -1;
+    return mmc_raw_dump(p, filename);
+}
+
+int cmd_mmc_erase_raw_partition(const char *partition)
+{
+    mmc_scan_partitions();
+    const MmcPartition *p;
+    p = mmc_find_partition_by_name(partition);
+    if (p == NULL)
+        return -1;
+
+    // TODO: implement raw wipe
+    return 0;
+}
+
+int cmd_mmc_erase_partition(const char *partition, const char *filesystem)
+{
+    mmc_scan_partitions();
+    const MmcPartition *p;
+    p = mmc_find_partition_by_name(partition);
+    if (p == NULL)
+        return -1;
+    return mmc_format_ext3 (p);
+}
+
+int cmd_mmc_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only)
+{
+    mmc_scan_partitions();
+    const MmcPartition *p;
+    p = mmc_find_partition_by_name(partition);
+    if (p == NULL)
+        return -1;
+    return mmc_mount_partition(p, mount_point, read_only);
+}
+
+int cmd_mmc_get_partition_device(const char *partition, char *device)
+{
+    mmc_scan_partitions();
+    const MmcPartition *p;
+    p = mmc_find_partition_by_name(partition);
+    if (p == NULL)
+        return -1;
+    strcpy(device, p->device_index);
+    return 0;
+}
diff --git a/mmcutils/mmcutils.h b/mmcutils/mmcutils.h
new file mode 100644
index 0000000..5b10fdc
--- /dev/null
+++ b/mmcutils/mmcutils.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * 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.
+ *   * Neither the name of Code Aurora Forum, Inc. nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * 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.
+ */
+
+#ifndef MMCUTILS_H_
+#define MMCUTILS_H_
+
+/* Some useful define used to access the MBR/EBR table */
+#define BLOCK_SIZE                0x200
+#define TABLE_ENTRY_0             0x1BE
+#define TABLE_ENTRY_1             0x1CE
+#define TABLE_ENTRY_2             0x1DE
+#define TABLE_ENTRY_3             0x1EE
+#define TABLE_SIGNATURE           0x1FE
+#define TABLE_ENTRY_SIZE          0x010
+
+#define OFFSET_STATUS             0x00
+#define OFFSET_TYPE               0x04
+#define OFFSET_FIRST_SEC          0x08
+#define OFFSET_SIZE               0x0C
+#define COPYBUFF_SIZE             (1024 * 16)
+#define BINARY_IN_TABLE_SIZE      (16 * 512)
+#define MAX_FILE_ENTRIES          20
+
+#define MMC_BOOT_TYPE 0x48
+#define MMC_SYSTEM_TYPE 0x82
+#define MMC_USERDATA_TYPE 0x83
+#define MMC_RECOVERY_TYPE 0x71
+
+#define MMC_RCA 2
+
+#define MAX_PARTITIONS 64
+
+#define GET_LWORD_FROM_BYTE(x)    ((unsigned)*(x) | \
+        ((unsigned)*((x)+1) << 8) | \
+        ((unsigned)*((x)+2) << 16) | \
+        ((unsigned)*((x)+3) << 24))
+
+#define PUT_LWORD_TO_BYTE(x, y)   do{*(x) = (y) & 0xff;     \
+    *((x)+1) = ((y) >> 8) & 0xff;     \
+    *((x)+2) = ((y) >> 16) & 0xff;     \
+    *((x)+3) = ((y) >> 24) & 0xff; }while(0)
+
+#define GET_PAR_NUM_FROM_POS(x) ((((x) & 0x0000FF00) >> 8) + ((x) & 0x000000FF))
+
+#define MMC_BOOT_TYPE 0x48
+#define MMC_EXT3_TYPE 0x83
+#define MMC_VFAT_TYPE 0xC
+typedef struct MmcPartition MmcPartition;
+
+/* Functions */
+int mmc_scan_partitions();
+const MmcPartition *mmc_find_partition_by_name(const char *name);
+int mmc_format_ext3 (MmcPartition *partition);
+int mmc_mount_partition(const MmcPartition *partition, const char *mount_point, \
+                        int read_only);
+int mmc_raw_copy (const MmcPartition *partition, char *in_file);
+int mmc_raw_read (const MmcPartition *partition, char *data, int data_size);
+int mmc_raw_write (const MmcPartition *partition, char *data, int data_size);
+
+int format_ext2_device(const char *device);
+int format_ext3_device(const char *device);
+
+#endif  // MMCUTILS_H_
+
+
diff --git a/mounts.c b/mounts.c
new file mode 100644
index 0000000..c90fc8a
--- /dev/null
+++ b/mounts.c
@@ -0,0 +1,222 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/mount.h>
+
+#include "mounts.h"
+
+struct MountedVolume {
+    const char *device;
+    const char *mount_point;
+    const char *filesystem;
+    const char *flags;
+};
+
+typedef struct {
+    MountedVolume *volumes;
+    int volumes_allocd;
+    int volume_count;
+} MountsState;
+
+static MountsState g_mounts_state = {
+    NULL,   // volumes
+    0,      // volumes_allocd
+    0       // volume_count
+};
+
+static inline void
+free_volume_internals(const MountedVolume *volume, int zero)
+{
+    free((char *)volume->device);
+    free((char *)volume->mount_point);
+    free((char *)volume->filesystem);
+    free((char *)volume->flags);
+    if (zero) {
+        memset((void *)volume, 0, sizeof(*volume));
+    }
+}
+
+#define PROC_MOUNTS_FILENAME   "/proc/mounts"
+
+int
+scan_mounted_volumes()
+{
+    char buf[2048];
+    const char *bufp;
+    int fd;
+    ssize_t nbytes;
+
+    if (g_mounts_state.volumes == NULL) {
+        const int numv = 32;
+        MountedVolume *volumes = malloc(numv * sizeof(*volumes));
+        if (volumes == NULL) {
+            errno = ENOMEM;
+            return -1;
+        }
+        g_mounts_state.volumes = volumes;
+        g_mounts_state.volumes_allocd = numv;
+        memset(volumes, 0, numv * sizeof(*volumes));
+    } else {
+        /* Free the old volume strings.
+         */
+        int i;
+        for (i = 0; i < g_mounts_state.volume_count; i++) {
+            free_volume_internals(&g_mounts_state.volumes[i], 1);
+        }
+    }
+    g_mounts_state.volume_count = 0;
+
+    /* Open and read the file contents.
+     */
+    fd = open(PROC_MOUNTS_FILENAME, O_RDONLY);
+    if (fd < 0) {
+        goto bail;
+    }
+    nbytes = read(fd, buf, sizeof(buf) - 1);
+    close(fd);
+    if (nbytes < 0) {
+        goto bail;
+    }
+    buf[nbytes] = '\0';
+
+    /* Parse the contents of the file, which looks like:
+     *
+     *     # cat /proc/mounts
+     *     rootfs / rootfs rw 0 0
+     *     /dev/pts /dev/pts devpts rw 0 0
+     *     /proc /proc proc rw 0 0
+     *     /sys /sys sysfs rw 0 0
+     *     /dev/block/mtdblock4 /system yaffs2 rw,nodev,noatime,nodiratime 0 0
+     *     /dev/block/mtdblock5 /data yaffs2 rw,nodev,noatime,nodiratime 0 0
+     *     /dev/block/mmcblk0p1 /sdcard vfat rw,sync,dirsync,fmask=0000,dmask=0000,codepage=cp437,iocharset=iso8859-1,utf8 0 0
+     *
+     * The zeroes at the end are dummy placeholder fields to make the
+     * output match Linux's /etc/mtab, but don't represent anything here.
+     */
+    bufp = buf;
+    while (nbytes > 0) {
+        char device[64];
+        char mount_point[64];
+        char filesystem[64];
+        char flags[128];
+        int matches;
+
+        /* %as is a gnu extension that malloc()s a string for each field.
+         */
+        matches = sscanf(bufp, "%63s %63s %63s %127s",
+                device, mount_point, filesystem, flags);
+
+        if (matches == 4) {
+            device[sizeof(device)-1] = '\0';
+            mount_point[sizeof(mount_point)-1] = '\0';
+            filesystem[sizeof(filesystem)-1] = '\0';
+            flags[sizeof(flags)-1] = '\0';
+
+            MountedVolume *v =
+                    &g_mounts_state.volumes[g_mounts_state.volume_count++];
+            v->device = strdup(device);
+            v->mount_point = strdup(mount_point);
+            v->filesystem = strdup(filesystem);
+            v->flags = strdup(flags);
+        } else {
+printf("matches was %d on <<%.40s>>\n", matches, bufp);
+        }
+
+        /* Eat the line.
+         */
+        while (nbytes > 0 && *bufp != '\n') {
+            bufp++;
+            nbytes--;
+        }
+        if (nbytes > 0) {
+            bufp++;
+            nbytes--;
+        }
+    }
+
+    return 0;
+
+bail:
+//TODO: free the strings we've allocated.
+    g_mounts_state.volume_count = 0;
+    return -1;
+}
+
+const MountedVolume *
+find_mounted_volume_by_device(const char *device)
+{
+    if (g_mounts_state.volumes != NULL) {
+        int i;
+        for (i = 0; i < g_mounts_state.volume_count; i++) {
+            MountedVolume *v = &g_mounts_state.volumes[i];
+            /* May be null if it was unmounted and we haven't rescanned.
+             */
+            if (v->device != NULL) {
+                if (strcmp(v->device, device) == 0) {
+                    return v;
+                }
+            }
+        }
+    }
+    return NULL;
+}
+
+const MountedVolume *
+find_mounted_volume_by_mount_point(const char *mount_point)
+{
+    if (g_mounts_state.volumes != NULL) {
+        int i;
+        for (i = 0; i < g_mounts_state.volume_count; i++) {
+            MountedVolume *v = &g_mounts_state.volumes[i];
+            /* May be null if it was unmounted and we haven't rescanned.
+             */
+            if (v->mount_point != NULL) {
+                if (strcmp(v->mount_point, mount_point) == 0) {
+                    return v;
+                }
+            }
+        }
+    }
+    return NULL;
+}
+
+int
+unmount_mounted_volume(const MountedVolume *volume)
+{
+    /* Intentionally pass NULL to umount if the caller tries
+     * to unmount a volume they already unmounted using this
+     * function.
+     */
+    int ret = umount(volume->mount_point);
+    if (ret == 0) {
+        free_volume_internals(volume, 1);
+        return 0;
+    }
+    return ret;
+}
+
+int
+remount_read_only(const MountedVolume* volume)
+{
+    return mount(volume->device, volume->mount_point, volume->filesystem,
+                 MS_NOATIME | MS_NODEV | MS_NODIRATIME |
+                 MS_RDONLY | MS_REMOUNT, 0);
+}
diff --git a/mounts.h b/mounts.h
new file mode 100644
index 0000000..30b2927
--- /dev/null
+++ b/mounts.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#ifndef MTDUTILS_MOUNTS_H_
+#define MTDUTILS_MOUNTS_H_
+
+typedef struct MountedVolume MountedVolume;
+
+int scan_mounted_volumes(void);
+
+const MountedVolume *find_mounted_volume_by_device(const char *device);
+
+const MountedVolume *
+find_mounted_volume_by_mount_point(const char *mount_point);
+
+int unmount_mounted_volume(const MountedVolume *volume);
+
+int remount_read_only(const MountedVolume* volume);
+
+#endif  // MTDUTILS_MOUNTS_H_
diff --git a/mtdutils/Android.mk b/mtdutils/Android.mk
new file mode 100644
index 0000000..90e97fd
--- /dev/null
+++ b/mtdutils/Android.mk
@@ -0,0 +1,16 @@
+ifneq ($(TARGET_SIMULATOR),true)
+ifeq ($(TARGET_ARCH),arm)
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	mtdutils.c
+
+LOCAL_MODULE := libmtdutils
+
+include $(BUILD_STATIC_LIBRARY)
+
+
+endif	# TARGET_ARCH == arm
+endif	# !TARGET_SIMULATOR
diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c
new file mode 100644
index 0000000..c8dbba4
--- /dev/null
+++ b/mtdutils/mtdutils.c
@@ -0,0 +1,856 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/mount.h>  // for _IOW, _IOR, mount()
+#include <sys/stat.h>
+#include <mtd/mtd-user.h>
+#undef NDEBUG
+#include <assert.h>
+
+#include "mtdutils.h"
+
+struct MtdReadContext {
+    const MtdPartition *partition;
+    char *buffer;
+    size_t consumed;
+    int fd;
+};
+
+struct MtdWriteContext {
+    const MtdPartition *partition;
+    char *buffer;
+    size_t stored;
+    int fd;
+
+    off_t* bad_block_offsets;
+    int bad_block_alloc;
+    int bad_block_count;
+};
+
+typedef struct {
+    MtdPartition *partitions;
+    int partitions_allocd;
+    int partition_count;
+} MtdState;
+
+static MtdState g_mtd_state = {
+    NULL,   // partitions
+    0,      // partitions_allocd
+    -1      // partition_count
+};
+
+#define MTD_PROC_FILENAME   "/proc/mtd"
+
+int
+mtd_scan_partitions()
+{
+    char buf[2048];
+    const char *bufp;
+    int fd;
+    int i;
+    ssize_t nbytes;
+
+    if (g_mtd_state.partitions == NULL) {
+        const int nump = 32;
+        MtdPartition *partitions = malloc(nump * sizeof(*partitions));
+        if (partitions == NULL) {
+            errno = ENOMEM;
+            return -1;
+        }
+        g_mtd_state.partitions = partitions;
+        g_mtd_state.partitions_allocd = nump;
+        memset(partitions, 0, nump * sizeof(*partitions));
+    }
+    g_mtd_state.partition_count = 0;
+
+    /* Initialize all of the entries to make things easier later.
+     * (Lets us handle sparsely-numbered partitions, which
+     * may not even be possible.)
+     */
+    for (i = 0; i < g_mtd_state.partitions_allocd; i++) {
+        MtdPartition *p = &g_mtd_state.partitions[i];
+        if (p->name != NULL) {
+            free(p->name);
+            p->name = NULL;
+        }
+        p->device_index = -1;
+    }
+
+    /* Open and read the file contents.
+     */
+    fd = open(MTD_PROC_FILENAME, O_RDONLY);
+    if (fd < 0) {
+        goto bail;
+    }
+    nbytes = read(fd, buf, sizeof(buf) - 1);
+    close(fd);
+    if (nbytes < 0) {
+        goto bail;
+    }
+    buf[nbytes] = '\0';
+
+    /* Parse the contents of the file, which looks like:
+     *
+     *     # cat /proc/mtd
+     *     dev:    size   erasesize  name
+     *     mtd0: 00080000 00020000 "bootloader"
+     *     mtd1: 00400000 00020000 "mfg_and_gsm"
+     *     mtd2: 00400000 00020000 "0000000c"
+     *     mtd3: 00200000 00020000 "0000000d"
+     *     mtd4: 04000000 00020000 "system"
+     *     mtd5: 03280000 00020000 "userdata"
+     */
+    bufp = buf;
+    while (nbytes > 0) {
+        int mtdnum, mtdsize, mtderasesize;
+        int matches;
+        char mtdname[64];
+        mtdname[0] = '\0';
+        mtdnum = -1;
+
+        matches = sscanf(bufp, "mtd%d: %x %x \"%63[^\"]",
+                &mtdnum, &mtdsize, &mtderasesize, mtdname);
+        /* This will fail on the first line, which just contains
+         * column headers.
+         */
+        if (matches == 4) {
+            MtdPartition *p = &g_mtd_state.partitions[mtdnum];
+            p->device_index = mtdnum;
+            p->size = mtdsize;
+            p->erase_size = mtderasesize;
+            p->name = strdup(mtdname);
+            if (p->name == NULL) {
+                errno = ENOMEM;
+                goto bail;
+            }
+            g_mtd_state.partition_count++;
+        }
+
+        /* Eat the line.
+         */
+        while (nbytes > 0 && *bufp != '\n') {
+            bufp++;
+            nbytes--;
+        }
+        if (nbytes > 0) {
+            bufp++;
+            nbytes--;
+        }
+    }
+
+    return g_mtd_state.partition_count;
+
+bail:
+    // keep "partitions" around so we can free the names on a rescan.
+    g_mtd_state.partition_count = -1;
+    return -1;
+}
+
+const MtdPartition *
+mtd_find_partition_by_name(const char *name)
+{
+    if (g_mtd_state.partitions != NULL) {
+        int i;
+        for (i = 0; i < g_mtd_state.partitions_allocd; i++) {
+            MtdPartition *p = &g_mtd_state.partitions[i];
+            if (p->device_index >= 0 && p->name != NULL) {
+                if (strcmp(p->name, name) == 0) {
+                    return p;
+                }
+            }
+        }
+    }
+    return NULL;
+}
+
+int
+mtd_mount_partition(const MtdPartition *partition, const char *mount_point,
+        const char *filesystem, int read_only)
+{
+    const unsigned long flags = MS_NOATIME | MS_NODEV | MS_NODIRATIME;
+    char devname[64];
+    int rv = -1;
+
+    sprintf(devname, "/dev/block/mtdblock%d", partition->device_index);
+    if (!read_only) {
+        rv = mount(devname, mount_point, filesystem, flags, NULL);
+    }
+    if (read_only || rv < 0) {
+        rv = mount(devname, mount_point, filesystem, flags | MS_RDONLY, 0);
+        if (rv < 0) {
+            printf("Failed to mount %s on %s: %s\n",
+                    devname, mount_point, strerror(errno));
+        } else {
+            printf("Mount %s on %s read-only\n", devname, mount_point);
+        }
+    }
+#if 1   //TODO: figure out why this is happening; remove include of stat.h
+    if (rv >= 0) {
+        /* For some reason, the x bits sometimes aren't set on the root
+         * of mounted volumes.
+         */
+        struct stat st;
+        rv = stat(mount_point, &st);
+        if (rv < 0) {
+            return rv;
+        }
+        mode_t new_mode = st.st_mode | S_IXUSR | S_IXGRP | S_IXOTH;
+        if (new_mode != st.st_mode) {
+printf("Fixing execute permissions for %s\n", mount_point);
+            rv = chmod(mount_point, new_mode);
+            if (rv < 0) {
+                printf("Couldn't fix permissions for %s: %s\n",
+                        mount_point, strerror(errno));
+            }
+        }
+    }
+#endif
+    return rv;
+}
+
+int
+mtd_partition_info(const MtdPartition *partition,
+        size_t *total_size, size_t *erase_size, size_t *write_size)
+{
+    char mtddevname[32];
+    sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
+    int fd = open(mtddevname, O_RDONLY);
+    if (fd < 0) return -1;
+
+    struct mtd_info_user mtd_info;
+    int ret = ioctl(fd, MEMGETINFO, &mtd_info);
+    close(fd);
+    if (ret < 0) return -1;
+
+    if (total_size != NULL) *total_size = mtd_info.size;
+    if (erase_size != NULL) *erase_size = mtd_info.erasesize;
+    if (write_size != NULL) *write_size = mtd_info.writesize;
+    return 0;
+}
+
+MtdReadContext *mtd_read_partition(const MtdPartition *partition)
+{
+    MtdReadContext *ctx = (MtdReadContext*) malloc(sizeof(MtdReadContext));
+    if (ctx == NULL) return NULL;
+
+    ctx->buffer = malloc(partition->erase_size);
+    if (ctx->buffer == NULL) {
+        free(ctx);
+        return NULL;
+    }
+
+    char mtddevname[32];
+    sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
+    ctx->fd = open(mtddevname, O_RDONLY);
+    if (ctx->fd < 0) {
+        free(ctx);
+        free(ctx->buffer);
+        return NULL;
+    }
+
+    ctx->partition = partition;
+    ctx->consumed = partition->erase_size;
+    return ctx;
+}
+
+// Seeks to a location in the partition.  Don't mix with reads of
+// anything other than whole blocks; unpredictable things will result.
+void mtd_read_skip_to(const MtdReadContext* ctx, size_t offset) {
+    lseek64(ctx->fd, offset, SEEK_SET);
+}
+
+static int read_block(const MtdPartition *partition, int fd, char *data)
+{
+    struct mtd_ecc_stats before, after;
+    if (ioctl(fd, ECCGETSTATS, &before)) {
+        fprintf(stderr, "mtd: ECCGETSTATS error (%s)\n", strerror(errno));
+        return -1;
+    }
+
+    loff_t pos = lseek64(fd, 0, SEEK_CUR);
+
+    ssize_t size = partition->erase_size;
+    int mgbb;
+
+    while (pos + size <= (int) partition->size) {
+        if (lseek64(fd, pos, SEEK_SET) != pos || read(fd, data, size) != size) {
+            fprintf(stderr, "mtd: read error at 0x%08llx (%s)\n",
+                    pos, strerror(errno));
+        } else if (ioctl(fd, ECCGETSTATS, &after)) {
+            fprintf(stderr, "mtd: ECCGETSTATS error (%s)\n", strerror(errno));
+            return -1;
+        } else if (after.failed != before.failed) {
+            fprintf(stderr, "mtd: ECC errors (%d soft, %d hard) at 0x%08llx\n",
+                    after.corrected - before.corrected,
+                    after.failed - before.failed, pos);
+            // copy the comparison baseline for the next read.
+            memcpy(&before, &after, sizeof(struct mtd_ecc_stats));
+        } else if ((mgbb = ioctl(fd, MEMGETBADBLOCK, &pos))) {
+            fprintf(stderr,
+                    "mtd: MEMGETBADBLOCK returned %d at 0x%08llx (errno=%d)\n",
+                    mgbb, pos, errno);
+        } else {
+            return 0;  // Success!
+        }
+
+        pos += partition->erase_size;
+    }
+
+    errno = ENOSPC;
+    return -1;
+}
+
+ssize_t mtd_read_data(MtdReadContext *ctx, char *data, size_t len)
+{
+    ssize_t read = 0;
+    while (read < (int) len) {
+        if (ctx->consumed < ctx->partition->erase_size) {
+            size_t avail = ctx->partition->erase_size - ctx->consumed;
+            size_t copy = len - read < avail ? len - read : avail;
+            memcpy(data + read, ctx->buffer + ctx->consumed, copy);
+            ctx->consumed += copy;
+            read += copy;
+        }
+
+        // Read complete blocks directly into the user's buffer
+        while (ctx->consumed == ctx->partition->erase_size &&
+               len - read >= ctx->partition->erase_size) {
+            if (read_block(ctx->partition, ctx->fd, data + read)) return -1;
+            read += ctx->partition->erase_size;
+        }
+
+        if (read >= (int)len) {
+            return read;
+        }
+
+        // Read the next block into the buffer
+        if (ctx->consumed == ctx->partition->erase_size && read < (int) len) {
+            if (read_block(ctx->partition, ctx->fd, ctx->buffer)) return -1;
+            ctx->consumed = 0;
+        }
+    }
+
+    return read;
+}
+
+void mtd_read_close(MtdReadContext *ctx)
+{
+    close(ctx->fd);
+    free(ctx->buffer);
+    free(ctx);
+}
+
+MtdWriteContext *mtd_write_partition(const MtdPartition *partition)
+{
+    MtdWriteContext *ctx = (MtdWriteContext*) malloc(sizeof(MtdWriteContext));
+    if (ctx == NULL) return NULL;
+
+    ctx->bad_block_offsets = NULL;
+    ctx->bad_block_alloc = 0;
+    ctx->bad_block_count = 0;
+
+    ctx->buffer = malloc(partition->erase_size);
+    if (ctx->buffer == NULL) {
+        free(ctx);
+        return NULL;
+    }
+
+    char mtddevname[32];
+    sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
+    ctx->fd = open(mtddevname, O_RDWR);
+    if (ctx->fd < 0) {
+        free(ctx->buffer);
+        free(ctx);
+        return NULL;
+    }
+
+    ctx->partition = partition;
+    ctx->stored = 0;
+    return ctx;
+}
+
+static void add_bad_block_offset(MtdWriteContext *ctx, off_t pos) {
+    if (ctx->bad_block_count + 1 > ctx->bad_block_alloc) {
+        ctx->bad_block_alloc = (ctx->bad_block_alloc*2) + 1;
+        ctx->bad_block_offsets = realloc(ctx->bad_block_offsets,
+                                         ctx->bad_block_alloc * sizeof(off_t));
+    }
+    ctx->bad_block_offsets[ctx->bad_block_count++] = pos;
+}
+
+static int write_block(MtdWriteContext *ctx, const char *data)
+{
+    const MtdPartition *partition = ctx->partition;
+    int fd = ctx->fd;
+
+    off_t pos = lseek(fd, 0, SEEK_CUR);
+    if (pos == (off_t) -1) return 1;
+
+    ssize_t size = partition->erase_size;
+    while (pos + size <= (int) partition->size) {
+        loff_t bpos = pos;
+        int ret = ioctl(fd, MEMGETBADBLOCK, &bpos);
+        if (ret != 0 && !(ret == -1 && errno == EOPNOTSUPP)) {
+            add_bad_block_offset(ctx, pos);
+            fprintf(stderr,
+                    "mtd: not writing bad block at 0x%08lx (ret %d errno %d)\n",
+                    pos, ret, errno);
+            pos += partition->erase_size;
+            continue;  // Don't try to erase known factory-bad blocks.
+        }
+
+        struct erase_info_user erase_info;
+        erase_info.start = pos;
+        erase_info.length = size;
+        int retry;
+        for (retry = 0; retry < 2; ++retry) {
+            if (ioctl(fd, MEMERASE, &erase_info) < 0) {
+                fprintf(stderr, "mtd: erase failure at 0x%08lx (%s)\n",
+                        pos, strerror(errno));
+                continue;
+            }
+            if (lseek(fd, pos, SEEK_SET) != pos ||
+                write(fd, data, size) != size) {
+                fprintf(stderr, "mtd: write error at 0x%08lx (%s)\n",
+                        pos, strerror(errno));
+            }
+
+            char verify[size];
+            if (lseek(fd, pos, SEEK_SET) != pos ||
+                read(fd, verify, size) != size) {
+                fprintf(stderr, "mtd: re-read error at 0x%08lx (%s)\n",
+                        pos, strerror(errno));
+                continue;
+            }
+            if (memcmp(data, verify, size) != 0) {
+                fprintf(stderr, "mtd: verification error at 0x%08lx (%s)\n",
+                        pos, strerror(errno));
+                continue;
+            }
+
+            if (retry > 0) {
+                fprintf(stderr, "mtd: wrote block after %d retries\n", retry);
+            }
+            fprintf(stderr, "mtd: successfully wrote block at %llx\n", pos);
+            return 0;  // Success!
+        }
+
+        // Try to erase it once more as we give up on this block
+        add_bad_block_offset(ctx, pos);
+        fprintf(stderr, "mtd: skipping write block at 0x%08lx\n", pos);
+        ioctl(fd, MEMERASE, &erase_info);
+        pos += partition->erase_size;
+    }
+
+    // Ran out of space on the device
+    errno = ENOSPC;
+    return -1;
+}
+
+ssize_t mtd_write_data(MtdWriteContext *ctx, const char *data, size_t len)
+{
+    size_t wrote = 0;
+    while (wrote < len) {
+        // Coalesce partial writes into complete blocks
+        if (ctx->stored > 0 || len - wrote < ctx->partition->erase_size) {
+            size_t avail = ctx->partition->erase_size - ctx->stored;
+            size_t copy = len - wrote < avail ? len - wrote : avail;
+            memcpy(ctx->buffer + ctx->stored, data + wrote, copy);
+            ctx->stored += copy;
+            wrote += copy;
+        }
+
+        // If a complete block was accumulated, write it
+        if (ctx->stored == ctx->partition->erase_size) {
+            if (write_block(ctx, ctx->buffer)) return -1;
+            ctx->stored = 0;
+        }
+
+        // Write complete blocks directly from the user's buffer
+        while (ctx->stored == 0 && len - wrote >= ctx->partition->erase_size) {
+            if (write_block(ctx, data + wrote)) return -1;
+            wrote += ctx->partition->erase_size;
+        }
+    }
+
+    return wrote;
+}
+
+off_t mtd_erase_blocks(MtdWriteContext *ctx, int blocks)
+{
+    // Zero-pad and write any pending data to get us to a block boundary
+    if (ctx->stored > 0) {
+        size_t zero = ctx->partition->erase_size - ctx->stored;
+        memset(ctx->buffer + ctx->stored, 0, zero);
+        if (write_block(ctx, ctx->buffer)) return -1;
+        ctx->stored = 0;
+    }
+
+    off_t pos = lseek(ctx->fd, 0, SEEK_CUR);
+    if ((off_t) pos == (off_t) -1) return pos;
+
+    const int total = (ctx->partition->size - pos) / ctx->partition->erase_size;
+    if (blocks < 0) blocks = total;
+    if (blocks > total) {
+        errno = ENOSPC;
+        return -1;
+    }
+
+    // Erase the specified number of blocks
+    while (blocks-- > 0) {
+        loff_t bpos = pos;
+        if (ioctl(ctx->fd, MEMGETBADBLOCK, &bpos) > 0) {
+            fprintf(stderr, "mtd: not erasing bad block at 0x%08lx\n", pos);
+            pos += ctx->partition->erase_size;
+            continue;  // Don't try to erase known factory-bad blocks.
+        }
+
+        struct erase_info_user erase_info;
+        erase_info.start = pos;
+        erase_info.length = ctx->partition->erase_size;
+        if (ioctl(ctx->fd, MEMERASE, &erase_info) < 0) {
+            fprintf(stderr, "mtd: erase failure at 0x%08lx\n", pos);
+        }
+        pos += ctx->partition->erase_size;
+    }
+
+    return pos;
+}
+
+int mtd_write_close(MtdWriteContext *ctx)
+{
+    int r = 0;
+    // Make sure any pending data gets written
+    if (mtd_erase_blocks(ctx, 0) == (off_t) -1) r = -1;
+    if (close(ctx->fd)) r = -1;
+    free(ctx->bad_block_offsets);
+    free(ctx->buffer);
+    free(ctx);
+    return r;
+}
+
+/* Return the offset of the first good block at or after pos (which
+ * might be pos itself).
+ */
+off_t mtd_find_write_start(MtdWriteContext *ctx, off_t pos) {
+    int i;
+    for (i = 0; i < ctx->bad_block_count; ++i) {
+        if (ctx->bad_block_offsets[i] == pos) {
+            pos += ctx->partition->erase_size;
+        } else if (ctx->bad_block_offsets[i] > pos) {
+            return pos;
+        }
+    }
+    return pos;
+}
+
+#define BLOCK_SIZE    2048
+#define SPARE_SIZE    (BLOCK_SIZE >> 5)
+#define HEADER_SIZE 2048
+
+int cmd_mtd_restore_raw_partition(const char *partition_name, const char *filename)
+{
+    const MtdPartition *ptn;
+    MtdWriteContext *write;
+    void *data;
+    unsigned sz;
+
+    if (mtd_scan_partitions() <= 0)
+    {
+        printf("error scanning partitions");
+        return -1;
+    }
+    const MtdPartition *partition = mtd_find_partition_by_name(partition_name);
+    if (partition == NULL)
+    {
+        printf("can't find %s partition", partition_name);
+        return -1;
+    }
+
+    // If the first part of the file matches the partition, skip writing
+
+    int fd = open(filename, O_RDONLY);
+    if (fd < 0)
+    {
+        printf("error opening %s", filename);
+        return -1;
+    }
+
+    char header[HEADER_SIZE];
+    int headerlen = read(fd, header, sizeof(header));
+    if (headerlen <= 0)
+    {
+        printf("error reading %s header", filename);
+        return -1;
+    }
+
+    MtdReadContext *in = mtd_read_partition(partition);
+    if (in == NULL) {
+        printf("error opening %s: %s\n", partition, strerror(errno));
+        // just assume it needs re-writing
+    } else {
+        char check[HEADER_SIZE];
+        int checklen = mtd_read_data(in, check, sizeof(check));
+        if (checklen <= 0) {
+            printf("error reading %s: %s\n", partition_name, strerror(errno));
+            // just assume it needs re-writing
+        } else if (checklen == headerlen && !memcmp(header, check, headerlen)) {
+            printf("header is the same, not flashing %s\n", partition_name);
+            return 0;
+        }
+        mtd_read_close(in);
+    }
+
+    // Skip the header (we'll come back to it), write everything else
+    printf("flashing %s from %s\n", partition_name, filename);
+
+    MtdWriteContext *out = mtd_write_partition(partition);
+    if (out == NULL)
+    {
+       printf("error writing %s", partition_name);
+       return -1;
+    }
+
+    char buf[HEADER_SIZE];
+    memset(buf, 0, headerlen);
+    int wrote = mtd_write_data(out, buf, headerlen);
+    if (wrote != headerlen)
+    {
+        printf("error writing %s", partition_name);
+        return -1;
+    }
+
+    int len;
+    while ((len = read(fd, buf, sizeof(buf))) > 0) {
+        wrote = mtd_write_data(out, buf, len);
+        if (wrote != len)
+        {
+            printf("error writing %s", partition_name);
+            return -1;
+        }
+    }
+    if (len < 0)
+    {
+       printf("error reading %s", filename);
+       return -1;
+    }
+
+    if (mtd_write_close(out))
+    {
+        printf("error closing %s", partition_name);
+        return -1;
+    }
+
+    // Now come back and write the header last
+
+    out = mtd_write_partition(partition);
+    if (out == NULL)
+    {
+        printf("error re-opening %s", partition_name);
+        return -1;
+    }
+
+    wrote = mtd_write_data(out, header, headerlen);
+    if (wrote != headerlen)
+    {
+        printf("error re-writing %s", partition_name);
+        return -1;
+    }
+
+    // Need to write a complete block, so write the rest of the first block
+    size_t block_size;
+    if (mtd_partition_info(partition, NULL, &block_size, NULL))
+    {
+        printf("error getting %s block size", partition_name);
+        return -1;
+    }
+
+    if (lseek(fd, headerlen, SEEK_SET) != headerlen)
+    {
+        printf("error rewinding %s", filename);
+        return -1;
+    }
+
+    int left = block_size - headerlen;
+    while (left < 0) left += block_size;
+    while (left > 0) {
+        len = read(fd, buf, left > (int)sizeof(buf) ? (int)sizeof(buf) : left);
+        if (len <= 0){
+            printf("error reading %s", filename);
+            return -1;
+        }
+        if (mtd_write_data(out, buf, len) != len)
+        {
+            printf("error writing %s", partition_name);
+            return -1;
+        }
+
+        left -= len;
+    }
+
+    if (mtd_write_close(out))
+    {
+        printf("error closing %s", partition_name);
+        return -1;
+    }
+    return 0;
+}
+
+
+int cmd_mtd_backup_raw_partition(const char *partition_name, const char *filename)
+{
+    MtdReadContext *in;
+    const MtdPartition *partition;
+    char buf[BLOCK_SIZE + SPARE_SIZE];
+    size_t partition_size;
+    size_t read_size;
+    size_t total;
+    int fd;
+    int wrote;
+    int len;
+
+    if (mtd_scan_partitions() <= 0)
+    {
+        printf("error scanning partitions");
+        return -1;
+    }
+
+    partition = mtd_find_partition_by_name(partition_name);
+    if (partition == NULL)
+    {
+        printf("can't find %s partition", partition_name);
+        return -1;
+    }
+
+    if (mtd_partition_info(partition, &partition_size, NULL, NULL)) {
+        printf("can't get info of partition %s", partition_name);
+        return -1;
+    }
+
+    if (!strcmp(filename, "-")) {
+        fd = fileno(stdout);
+    }
+    else {
+        fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+    }
+
+    if (fd < 0)
+    {
+       printf("error opening %s", filename);
+       return -1;
+    }
+
+    in = mtd_read_partition(partition);
+    if (in == NULL) {
+        close(fd);
+        unlink(filename);
+        printf("error opening %s: %s\n", partition_name, strerror(errno));
+        return -1;
+    }
+
+    total = 0;
+    while ((len = mtd_read_data(in, buf, BLOCK_SIZE)) > 0) {
+        wrote = write(fd, buf, len);
+        if (wrote != len) {
+            close(fd);
+            unlink(filename);
+            printf("error writing %s", filename);
+            return -1;
+        }
+        total += BLOCK_SIZE;
+    }
+
+    mtd_read_close(in);
+
+    if (close(fd)) {
+        unlink(filename);
+        printf("error closing %s", filename);
+        return -1;
+    }
+    return 0;
+}
+
+int cmd_mtd_erase_raw_partition(const char *partition_name)
+{
+    MtdWriteContext *out;
+    size_t erased;
+    size_t total_size;
+    size_t erase_size;
+
+    if (mtd_scan_partitions() <= 0)
+    {
+        printf("error scanning partitions");
+        return -1;
+    }
+    const MtdPartition *p = mtd_find_partition_by_name(partition_name);
+    if (p == NULL)
+    {
+        printf("can't find %s partition", partition_name);
+        return -1;
+    }
+
+    out = mtd_write_partition(p);
+    if (out == NULL)
+    {
+        printf("could not estabilish write context for %s", partition_name);
+        return -1;
+    }
+
+    // do the actual erase, -1 = full partition erase
+    erased = mtd_erase_blocks(out, -1);
+
+    // erased = bytes erased, if zero, something borked
+    if (!erased)
+    {
+        printf("error erasing %s", partition_name);
+        return -1;
+    }
+
+    return 0;
+}
+
+int cmd_mtd_erase_partition(const char *partition, const char *filesystem)
+{
+    return cmd_mtd_erase_raw_partition(partition);
+}
+
+
+int cmd_mtd_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only)
+{
+    mtd_scan_partitions();
+    const MtdPartition *p;
+    p = mtd_find_partition_by_name(partition);
+    if (p == NULL) {
+        return -1;
+    }
+    return mtd_mount_partition(p, mount_point, filesystem, read_only);
+}
+
+int cmd_mtd_get_partition_device(const char *partition, char *device)
+{
+    mtd_scan_partitions();
+    MtdPartition *p = mtd_find_partition_by_name(partition);
+    if (p == NULL)
+        return -1;
+    sprintf(device, "/dev/block/mtdblock%d", p->device_index);
+    return 0;
+}
diff --git a/mtdutils/mtdutils.h b/mtdutils/mtdutils.h
new file mode 100644
index 0000000..c57d45d
--- /dev/null
+++ b/mtdutils/mtdutils.h
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+#ifndef MTDUTILS_H_
+#define MTDUTILS_H_
+
+#include <sys/types.h>  // for size_t, etc.
+
+typedef struct MtdPartition MtdPartition;
+
+int mtd_scan_partitions(void);
+
+const MtdPartition *mtd_find_partition_by_name(const char *name);
+
+/* mount_point is like "/system"
+ * filesystem is like "yaffs2"
+ */
+int mtd_mount_partition(const MtdPartition *partition, const char *mount_point,
+        const char *filesystem, int read_only);
+
+/* get the partition and the minimum erase/write block size.  NULL is ok.
+ */
+int mtd_partition_info(const MtdPartition *partition,
+        size_t *total_size, size_t *erase_size, size_t *write_size);
+
+/* read or write raw data from a partition, starting at the beginning.
+ * skips bad blocks as best we can.
+ */
+typedef struct MtdReadContext MtdReadContext;
+typedef struct MtdWriteContext MtdWriteContext;
+
+MtdReadContext *mtd_read_partition(const MtdPartition *);
+ssize_t mtd_read_data(MtdReadContext *, char *data, size_t data_len);
+void mtd_read_close(MtdReadContext *);
+void mtd_read_skip_to(const MtdReadContext *, size_t offset);
+
+MtdWriteContext *mtd_write_partition(const MtdPartition *);
+ssize_t mtd_write_data(MtdWriteContext *, const char *data, size_t data_len);
+off_t mtd_erase_blocks(MtdWriteContext *, int blocks);  /* 0 ok, -1 for all */
+off_t mtd_find_write_start(MtdWriteContext *ctx, off_t pos);
+int mtd_write_close(MtdWriteContext *);
+
+struct MtdPartition {
+    int device_index;
+    unsigned int size;
+    unsigned int erase_size;
+    char *name;
+};
+
+#endif  // MTDUTILS_H_
diff --git a/nandroid-md5.sh b/nandroid-md5.sh
new file mode 100755
index 0000000..0db53ee
--- /dev/null
+++ b/nandroid-md5.sh
@@ -0,0 +1,4 @@
+#!/sbin/sh
+cd $1
+md5sum *img > nandroid.md5
+return $?
\ No newline at end of file
diff --git a/nandroid.c b/nandroid.c
new file mode 100644
index 0000000..45417fb
--- /dev/null
+++ b/nandroid.c
@@ -0,0 +1,425 @@
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <linux/input.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/reboot.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/wait.h>
+#include <sys/limits.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include <signal.h>
+#include <sys/wait.h>
+
+#include "bootloader.h"
+#include "common.h"
+#include "cutils/properties.h"
+#include "firmware.h"
+#include "install.h"
+#include "minui/minui.h"
+#include "minzip/DirUtil.h"
+#include "roots.h"
+#include "recovery_ui.h"
+
+#include "../../external/yaffs2/yaffs2/utils/mkyaffs2image.h"
+#include "../../external/yaffs2/yaffs2/utils/unyaffs.h"
+
+#include <sys/vfs.h>
+
+#include "extendedcommands.h"
+#include "nandroid.h"
+
+int print_and_error(const char* message) {
+    ui_print("%s", message);
+    return 1;
+}
+
+int yaffs_files_total = 0;
+int yaffs_files_count = 0;
+void yaffs_callback(char* filename)
+{
+    char* justfile = basename(filename);
+    if (strlen(justfile) < 30)
+        ui_print("%s", justfile);
+    yaffs_files_count++;
+    if (yaffs_files_total != 0)
+        ui_set_progress((float)yaffs_files_count / (float)yaffs_files_total);
+    ui_reset_text_col();
+}
+
+void compute_directory_stats(char* directory)
+{
+    char tmp[PATH_MAX];
+    sprintf(tmp, "find %s | wc -l > /tmp/dircount", directory);
+    __system(tmp);
+    char count_text[100];
+    FILE* f = fopen("/tmp/dircount", "r");
+    fread(count_text, 1, sizeof(count_text), f);
+    fclose(f);
+    yaffs_files_count = 0;
+    yaffs_files_total = atoi(count_text);
+    ui_reset_progress();
+    ui_show_progress(1, 0);
+}
+
+int nandroid_backup_partition_extended(const char* backup_path, const char* mount_point, int umount_when_finished) {
+    int ret = 0;
+    char* name = basename(mount_point);
+
+    struct stat file_info;
+    mkyaffs2image_callback callback = NULL;
+    if (0 != stat("/sdcard/clockworkmod/.hidenandroidprogress", &file_info)) {
+        callback = yaffs_callback;
+    }
+    
+    ui_print("Backing up %s...\n", name);
+    if (0 != (ret = ensure_path_mounted(mount_point) != 0)) {
+        ui_print("Can't mount %s!\n", mount_point);
+        return ret;
+    }
+    compute_directory_stats(mount_point);
+    char tmp[PATH_MAX];
+    sprintf(tmp, "%s/%s.img", backup_path, name);
+    ret = mkyaffs2image(mount_point, tmp, 0, callback);
+    if (umount_when_finished) {
+        ensure_path_unmounted(mount_point);
+    }
+    if (0 != ret) {
+        ui_print("Error while making a yaffs2 image of %s!\n", mount_point);
+        return ret;
+    }
+    return 0;
+}
+
+int nandroid_backup_partition(const char* backup_path, const char* root) {
+    Volume *vol = volume_for_path(root);
+    // make sure the volume exists before attempting anything...
+    if (vol == NULL || vol->fs_type == NULL)
+        return NULL;
+
+    // see if we need a raw backup (mtd)
+    char tmp[PATH_MAX];
+    int ret;
+    if (strcmp(vol->fs_type, "mtd") == 0 ||
+            strcmp(vol->fs_type, "emmc") == 0) {
+        const char* name = basename(root);
+        sprintf(tmp, "%s/%s.img", backup_path, name);
+        ui_print("Backing up %s image...\n", name);
+        if (0 != (ret = backup_raw_partition(vol->device, tmp))) {
+            ui_print("Error while backing up %s image!", name);
+            return ret;
+        }
+        return 0;
+    }
+
+    return nandroid_backup_partition_extended(backup_path, root, 1);
+}
+
+int nandroid_backup(const char* backup_path)
+{
+    ui_set_background(BACKGROUND_ICON_INSTALLING);
+    
+    if (ensure_path_mounted("/sdcard") != 0)
+        return print_and_error("Can't mount /sdcard\n");
+    
+    int ret;
+    struct statfs s;
+    if (0 != (ret = statfs("/sdcard", &s)))
+        return print_and_error("Unable to stat /sdcard\n");
+    uint64_t bavail = s.f_bavail;
+    uint64_t bsize = s.f_bsize;
+    uint64_t sdcard_free = bavail * bsize;
+    uint64_t sdcard_free_mb = sdcard_free / (uint64_t)(1024 * 1024);
+    ui_print("SD Card space free: %lluMB\n", sdcard_free_mb);
+    if (sdcard_free_mb < 150)
+        ui_print("There may not be enough free space to complete backup... continuing...\n");
+    
+    char tmp[PATH_MAX];
+    sprintf(tmp, "mkdir -p %s", backup_path);
+    __system(tmp);
+
+    if (0 != (ret = nandroid_backup_partition(backup_path, "/boot")))
+        return ret;
+
+    if (0 != (ret = nandroid_backup_partition(backup_path, "/recovery")))
+        return ret;
+
+    if (0 == (ret = get_partition_device("wimax", tmp)))
+    {
+        char serialno[PROPERTY_VALUE_MAX];
+        ui_print("Backing up WiMAX...\n");
+        serialno[0] = 0;
+        property_get("ro.serialno", serialno, "");
+        sprintf(tmp, "%s/wimax.%s.img", backup_path, serialno);
+        ret = backup_raw_partition("wimax", tmp);
+        if (0 != ret)
+            return print_and_error("Error while dumping WiMAX image!\n");
+    }
+
+    if (0 != (ret = nandroid_backup_partition(backup_path, "/system")))
+        return ret;
+
+    if (0 != (ret = nandroid_backup_partition(backup_path, "/data")))
+        return ret;
+
+    if (has_datadata()) {
+        if (0 != (ret = nandroid_backup_partition(backup_path, "/datadata")))
+            return ret;
+    }
+
+    struct stat st;
+    if (0 != stat("/sdcard/.android_secure", &st))
+    {
+        ui_print("No /sdcard/.android_secure found. Skipping backup of applications on external storage.\n");
+    }
+    else
+    {
+        if (0 != (ret = nandroid_backup_partition_extended(backup_path, "/sdcard/.android_secure", 0)))
+            return ret;
+    }
+
+    if (0 != (ret = nandroid_backup_partition_extended(backup_path, "/cache", 0)))
+        return ret;
+
+    Volume *vol = volume_for_path("/sd-ext");
+    if (vol == NULL || 0 != stat(vol->device, &st))
+    {
+        ui_print("No sd-ext found. Skipping backup of sd-ext.\n");
+    }
+    else
+    {
+        if (0 != ensure_path_mounted("/sd-ext"))
+            ui_print("Could not mount sd-ext. sd-ext backup may not be supported on this device. Skipping backup of sd-ext.\n");
+        else if (0 != (ret = nandroid_backup_partition(backup_path, "/sd-ext")))
+            return ret;
+    }
+
+    ui_print("Generating md5 sum...\n");
+    sprintf(tmp, "nandroid-md5.sh %s", backup_path);
+    if (0 != (ret = __system(tmp))) {
+        ui_print("Error while generating md5 sum!\n");
+        return ret;
+    }
+    
+    sync();
+    ui_set_background(BACKGROUND_ICON_NONE);
+    ui_reset_progress();
+    ui_print("\nBackup complete!\n");
+    return 0;
+}
+
+typedef int (*format_function)(char* root);
+
+static void ensure_directory(const char* dir) {
+    char tmp[PATH_MAX];
+    sprintf(tmp, "mkdir -p %s", dir);
+    __system(tmp);
+}
+
+int nandroid_restore_partition_extended(const char* backup_path, const char* mount_point, int umount_when_finished) {
+    int ret = 0;
+    char* name = basename(mount_point);
+    
+    char tmp[PATH_MAX];
+    sprintf(tmp, "%s/%s.img", backup_path, name);
+    struct stat file_info;
+    if (0 != (ret = statfs(tmp, &file_info))) {
+        ui_print("%s.img not found. Skipping restore of %s.\n", name, mount_point);
+        return 0;
+    }
+
+    ensure_directory(mount_point);
+
+    unyaffs_callback callback = NULL;
+    if (0 != stat("/sdcard/clockworkmod/.hidenandroidprogress", &file_info)) {
+        callback = yaffs_callback;
+    }
+
+    ui_print("Restoring %s...\n", name);
+    /*
+    if (0 != (ret = ensure_root_path_unmounted(root))) {
+        ui_print("Can't unmount %s!\n", mount_point);
+        return ret;
+    }
+    */
+    if (0 != (ret = format_volume(mount_point))) {
+        ui_print("Error while formatting %s!\n", mount_point);
+        return ret;
+    }
+    
+    if (0 != (ret = ensure_path_mounted(mount_point))) {
+        ui_print("Can't mount %s!\n", mount_point);
+        return ret;
+    }
+    
+    if (0 != (ret = unyaffs(tmp, mount_point, callback))) {
+        ui_print("Error while restoring %s!\n", mount_point);
+        return ret;
+    }
+
+    if (umount_when_finished) {
+        ensure_path_unmounted(mount_point);
+    }
+    
+    return 0;
+}
+
+int nandroid_restore_partition(const char* backup_path, const char* root) {
+    Volume *vol = volume_for_path(root);
+    // make sure the volume exists...
+    if (vol == NULL || vol->fs_type == NULL)
+        return 0;
+
+    // see if we need a raw restore (mtd)
+    char tmp[PATH_MAX];
+    if (strcmp(vol->fs_type, "mtd") == 0 ||
+            strcmp(vol->fs_type, "emmc") == 0) {
+        int ret;
+        const char* name = basename(root);
+        ui_print("Erasing %s before restore...\n", name);
+        if (0 != (ret = format_volume(root))) {
+            ui_print("Error while erasing %s image!", name);
+            return ret;
+        }
+        sprintf(tmp, "%s%s.img", backup_path, root);
+        ui_print("Restoring %s image...\n", name);
+        if (0 != (ret = restore_raw_partition(vol->device, tmp))) {
+            ui_print("Error while flashing %s image!", name);
+            return ret;
+        }
+        return 0;
+    }
+    return nandroid_restore_partition_extended(backup_path, root, 1);
+}
+
+int nandroid_restore(const char* backup_path, int restore_boot, int restore_system, int restore_data, int restore_cache, int restore_sdext, int restore_wimax)
+{
+    ui_set_background(BACKGROUND_ICON_INSTALLING);
+    ui_show_indeterminate_progress();
+    yaffs_files_total = 0;
+
+    if (ensure_path_mounted("/sdcard") != 0)
+        return print_and_error("Can't mount /sdcard\n");
+    
+    char tmp[PATH_MAX];
+
+    ui_print("Checking MD5 sums...\n");
+    sprintf(tmp, "cd %s && md5sum -c nandroid.md5", backup_path);
+    if (0 != __system(tmp))
+        return print_and_error("MD5 mismatch!\n");
+    
+    int ret;
+
+    if (restore_boot && NULL != volume_for_path("/boot") && 0 != (ret = nandroid_restore_partition(backup_path, "/boot")))
+        return ret;
+    
+    if (restore_wimax && 0 == (ret = get_partition_device("wimax", tmp)))
+    {
+        char serialno[PROPERTY_VALUE_MAX];
+        
+        serialno[0] = 0;
+        property_get("ro.serialno", serialno, "");
+        sprintf(tmp, "%s/wimax.%s.img", backup_path, serialno);
+
+        struct stat st;
+        if (0 != stat(tmp, &st))
+        {
+            ui_print("WARNING: WiMAX partition exists, but nandroid\n");
+            ui_print("         backup does not contain WiMAX image.\n");
+            ui_print("         You should create a new backup to\n");
+            ui_print("         protect your WiMAX keys.\n");
+        }
+        else
+        {
+            ui_print("Erasing WiMAX before restore...\n");
+            if (0 != (ret = format_volume("/wimax")))
+                return print_and_error("Error while formatting wimax!\n");
+            ui_print("Restoring WiMAX image...\n");
+            if (0 != (ret = restore_raw_partition("wimax", tmp)))
+                return ret;
+        }
+    }
+
+    if (restore_system && 0 != (ret = nandroid_restore_partition(backup_path, "/system")))
+        return ret;
+
+    if (restore_data && 0 != (ret = nandroid_restore_partition(backup_path, "/data")))
+        return ret;
+        
+    if (has_datadata()) {
+        if (restore_data && 0 != (ret = nandroid_restore_partition(backup_path, "/datadata")))
+            return ret;
+    }
+
+    if (restore_data && 0 != (ret = nandroid_restore_partition_extended(backup_path, "/sdcard/.android_secure", 0)))
+        return ret;
+
+    if (restore_cache && 0 != (ret = nandroid_restore_partition_extended(backup_path, "/cache", 0)))
+        return ret;
+
+    if (restore_sdext && 0 != (ret = nandroid_restore_partition(backup_path, "/sd-ext")))
+        return ret;
+
+    sync();
+    ui_set_background(BACKGROUND_ICON_NONE);
+    ui_reset_progress();
+    ui_print("\nRestore complete!\n");
+    return 0;
+}
+
+void nandroid_generate_timestamp_path(char* backup_path)
+{
+    time_t t = time(NULL);
+    struct tm *tmp = localtime(&t);
+    if (tmp == NULL)
+    {
+        struct timeval tp;
+        gettimeofday(&tp, NULL);
+        sprintf(backup_path, "/sdcard/clockworkmod/backup/%d", tp.tv_sec);
+    }
+    else
+    {
+        strftime(backup_path, PATH_MAX, "/sdcard/clockworkmod/backup/%F.%H.%M.%S", tmp);
+    }
+}
+
+int nandroid_usage()
+{
+    printf("Usage: nandroid backup\n");
+    printf("Usage: nandroid restore <directory>\n");
+    return 1;
+}
+
+int nandroid_main(int argc, char** argv)
+{
+    if (argc > 3 || argc < 2)
+        return nandroid_usage();
+    
+    if (strcmp("backup", argv[1]) == 0)
+    {
+        if (argc != 2)
+            return nandroid_usage();
+        
+        char backup_path[PATH_MAX];
+        nandroid_generate_timestamp_path(backup_path);
+        return nandroid_backup(backup_path);
+    }
+
+    if (strcmp("restore", argv[1]) == 0)
+    {
+        if (argc != 3)
+            return nandroid_usage();
+        return nandroid_restore(argv[2], 1, 1, 1, 1, 1, 0);
+    }
+    
+    return nandroid_usage();
+}
diff --git a/nandroid.h b/nandroid.h
new file mode 100644
index 0000000..926bf30
--- /dev/null
+++ b/nandroid.h
@@ -0,0 +1,9 @@
+#ifndef NANDROID_H
+#define NANDROID_H
+
+int nandroid_main(int argc, char** argv);
+int nandroid_backup(const char* backup_path);
+int nandroid_restore(const char* backup_path, int restore_boot, int restore_system, int restore_data, int restore_cache, int restore_sdext, int restore_wimax);
+void nandroid_generate_timestamp_path(char* backup_path);
+
+#endif
\ No newline at end of file
diff --git a/reboot.c b/reboot.c
new file mode 100644
index 0000000..04aa9ae
--- /dev/null
+++ b/reboot.c
@@ -0,0 +1,72 @@
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/reboot.h>
+#include <unistd.h>
+#include <cutils/properties.h>
+
+int reboot_main(int argc, char *argv[])
+{
+    int ret;
+    int nosync = 0;
+    int poweroff = 0;
+    int force = 0;
+
+    opterr = 0;
+    do {
+        int c;
+
+        c = getopt(argc, argv, "npf");
+        
+        if (c == EOF) {
+            break;
+        }
+        
+        switch (c) {
+        case 'n':
+            nosync = 1;
+            break;
+        case 'p':
+            poweroff = 1;
+            break;
+    	case 'f':
+	        force = 1;
+	        break;
+        case '?':
+            fprintf(stderr, "usage: %s [-n] [-p] [rebootcommand]\n", argv[0]);
+            exit(EXIT_FAILURE);
+        }
+    } while (1);
+
+    if(argc > optind + 1) {
+        fprintf(stderr, "%s: too many arguments\n", argv[0]);
+        exit(EXIT_FAILURE);
+    }
+
+    if(!nosync)
+        sync();
+
+    if(force || argc > optind) {
+        if(poweroff)
+            ret = __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_POWER_OFF, NULL);
+        else if(argc > optind)
+            ret = __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, argv[optind]);
+        else
+            ret = reboot(RB_AUTOBOOT);
+    } else {
+        if(poweroff) {
+            property_set("ctl.start", "poweroff");
+            ret = 0;
+        } else {
+            property_set("ctl.start", "reboot");
+            ret = 0;
+        }
+    }
+
+    if(ret < 0) {
+        perror("reboot");
+        exit(EXIT_FAILURE);
+    }
+    fprintf(stderr, "reboot returned\n");
+    return 0;
+}
diff --git a/recovery.c b/recovery.c
new file mode 100644
index 0000000..bb4c1be
--- /dev/null
+++ b/recovery.c
@@ -0,0 +1,950 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <linux/input.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include "bootloader.h"
+#include "common.h"
+#include "cutils/properties.h"
+#include "install.h"
+#include "minui/minui.h"
+#include "minzip/DirUtil.h"
+#include "roots.h"
+#include "recovery_ui.h"
+#include "encryptedfs_provisioning.h"
+
+#include "extendedcommands.h"
+#include "flashutils/flashutils.h"
+
+static const struct option OPTIONS[] = {
+  { "send_intent", required_argument, NULL, 's' },
+  { "update_package", required_argument, NULL, 'u' },
+  { "wipe_data", no_argument, NULL, 'w' },
+  { "wipe_cache", no_argument, NULL, 'c' },
+  { "set_encrypted_filesystems", required_argument, NULL, 'e' },
+  { "show_text", no_argument, NULL, 't' },
+  { NULL, 0, NULL, 0 },
+};
+
+static const char *COMMAND_FILE = "/cache/recovery/command";
+static const char *INTENT_FILE = "/cache/recovery/intent";
+static const char *LOG_FILE = "/cache/recovery/log";
+static const char *LAST_LOG_FILE = "/cache/recovery/last_log";
+static const char *SDCARD_ROOT = "/sdcard";
+static int allow_display_toggle = 1;
+static const char *SDCARD_PACKAGE_FILE = "/sdcard/update.zip";
+static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log";
+static const char *SIDELOAD_TEMP_DIR = "/tmp/sideload";
+
+/*
+ * The recovery tool communicates with the main system through /cache files.
+ *   /cache/recovery/command - INPUT - command line for tool, one arg per line
+ *   /cache/recovery/log - OUTPUT - combined log file from recovery run(s)
+ *   /cache/recovery/intent - OUTPUT - intent that was passed in
+ *
+ * The arguments which may be supplied in the recovery.command file:
+ *   --send_intent=anystring - write the text out to recovery.intent
+ *   --update_package=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).
+ * These important scenarios must be safely restartable at any point:
+ *
+ * FACTORY RESET
+ * 1. user selects "factory reset"
+ * 2. main system writes "--wipe_data" to /cache/recovery/command
+ * 3. main system reboots into recovery
+ * 4. get_args() writes BCB with "boot-recovery" and "--wipe_data"
+ *    -- after this, rebooting will restart the erase --
+ * 5. erase_volume() reformats /data
+ * 6. erase_volume() reformats /cache
+ * 7. finish_recovery() erases BCB
+ *    -- after this, rebooting will restart the main system --
+ * 8. main() calls reboot() to boot main system
+ *
+ * OTA INSTALL
+ * 1. main system downloads OTA package to /cache/some-filename.zip
+ * 2. main system writes "--update_package=/cache/some-filename.zip"
+ * 3. main system reboots into recovery
+ * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..."
+ *    -- after this, rebooting will attempt to reinstall the update --
+ * 5. install_package() attempts to install the update
+ *    NOTE: the package install must itself be restartable from any point
+ * 6. finish_recovery() erases BCB
+ *    -- after this, rebooting will (try to) restart the main system --
+ * 7. ** if install failed **
+ *    7a. prompt_and_wait() shows an error icon and waits for the user
+ *    7b; the user reboots (pulling the battery, etc) into the main system
+ * 8. main() calls maybe_install_firmware_update()
+ *    ** if the update contained radio/hboot firmware **:
+ *    8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache"
+ *        -- after this, rebooting will reformat cache & restart main system --
+ *    8b. m_i_f_u() writes firmware image into raw cache partition
+ *    8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache"
+ *        -- after this, rebooting will attempt to reinstall firmware --
+ *    8d. bootloader tries to flash firmware
+ *    8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache")
+ *        -- after this, rebooting will reformat cache & restart main system --
+ *    8f. erase_volume() reformats /cache
+ *    8g. finish_recovery() erases BCB
+ *        -- after this, rebooting will (try to) restart the main system --
+ * 9. main() calls reboot() to boot main system
+ *
+ * SECURE FILE SYSTEMS ENABLE/DISABLE
+ * 1. user selects "enable encrypted file systems"
+ * 2. main system writes "--set_encrypted_filesystems=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_volume() reformats /data
+ * 7. erase_volume() 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;
+static const int MAX_ARGS = 100;
+
+// open a given path, mounting partitions as necessary
+static FILE*
+fopen_path(const char *path, const char *mode) {
+    if (ensure_path_mounted(path) != 0) {
+        LOGE("Can't mount %s\n", path);
+        return NULL;
+    }
+
+    // When writing, try to create the containing directory, if necessary.
+    // Use generous permissions, the system (init.rc) will reset them.
+    if (strchr("wa", mode[0])) dirCreateHierarchy(path, 0777, NULL, 1);
+
+    FILE *fp = fopen(path, mode);
+    if (fp == NULL && path != COMMAND_FILE) LOGE("Can't open %s\n", path);
+    return fp;
+}
+
+// close a file, log an error if the error indicator is set
+static void
+check_and_fclose(FILE *fp, const char *name) {
+    fflush(fp);
+    if (ferror(fp)) LOGE("Error in %s\n(%s)\n", name, strerror(errno));
+    fclose(fp);
+}
+
+// command line args come from, in decreasing precedence:
+//   - the actual command line
+//   - the bootloader control block (one per line, after "recovery")
+//   - the contents of COMMAND_FILE (one per line)
+static void
+get_args(int *argc, char ***argv) {
+    struct bootloader_message boot;
+    memset(&boot, 0, sizeof(boot));
+    if (device_flash_type() == MTD) {
+        get_bootloader_message(&boot);  // this may fail, leaving a zeroed structure
+    }
+
+    if (boot.command[0] != 0 && boot.command[0] != 255) {
+        LOGI("Boot command: %.*s\n", sizeof(boot.command), boot.command);
+    }
+
+    if (boot.status[0] != 0 && boot.status[0] != 255) {
+        LOGI("Boot status: %.*s\n", sizeof(boot.status), boot.status);
+    }
+
+    struct stat file_info;
+
+    // --- if arguments weren't supplied, look in the bootloader control block
+    if (*argc <= 1 && 0 != stat("/tmp/.ignorebootmessage", &file_info)) {
+        boot.recovery[sizeof(boot.recovery) - 1] = '\0';  // Ensure termination
+        const char *arg = strtok(boot.recovery, "\n");
+        if (arg != NULL && !strcmp(arg, "recovery")) {
+            *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
+            (*argv)[0] = strdup(arg);
+            for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
+                if ((arg = strtok(NULL, "\n")) == NULL) break;
+                (*argv)[*argc] = strdup(arg);
+            }
+            LOGI("Got arguments from boot message\n");
+        } else if (boot.recovery[0] != 0 && boot.recovery[0] != 255) {
+            LOGE("Bad boot message\n\"%.20s\"\n", boot.recovery);
+        }
+    }
+
+    // --- if that doesn't work, try the command file
+    if (*argc <= 1) {
+        FILE *fp = fopen_path(COMMAND_FILE, "r");
+        if (fp != NULL) {
+            char *argv0 = (*argv)[0];
+            *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
+            (*argv)[0] = argv0;  // use the same program name
+
+            char buf[MAX_ARG_LENGTH];
+            for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
+                if (!fgets(buf, sizeof(buf), fp)) break;
+                (*argv)[*argc] = strdup(strtok(buf, "\r\n"));  // Strip newline.
+            }
+
+            check_and_fclose(fp, COMMAND_FILE);
+            LOGI("Got arguments from %s\n", COMMAND_FILE);
+        }
+    }
+
+    // --> write the arguments we have back into the bootloader control block
+    // always boot into recovery after this (until finish_recovery() is called)
+    strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
+    strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
+    int i;
+    for (i = 1; i < *argc; ++i) {
+        strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery));
+        strlcat(boot.recovery, "\n", sizeof(boot.recovery));
+    }
+    if (device_flash_type() == MTD) {
+        set_bootloader_message(&boot);
+    }
+}
+
+void
+set_sdcard_update_bootloader_message() {
+    struct bootloader_message boot;
+    memset(&boot, 0, sizeof(boot));
+    strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
+    strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
+    set_bootloader_message(&boot);
+}
+
+// How much of the temp log we have copied to the copy in cache.
+static long tmplog_offset = 0;
+
+static void
+copy_log_file(const char* destination, int append) {
+    FILE *log = fopen_path(destination, append ? "a" : "w");
+    if (log == NULL) {
+        LOGE("Can't open %s\n", destination);
+    } else {
+        FILE *tmplog = fopen(TEMPORARY_LOG_FILE, "r");
+        if (tmplog == NULL) {
+            LOGE("Can't open %s\n", TEMPORARY_LOG_FILE);
+        } else {
+            if (append) {
+                fseek(tmplog, tmplog_offset, SEEK_SET);  // Since last write
+            }
+            char buf[4096];
+            while (fgets(buf, sizeof(buf), tmplog)) fputs(buf, log);
+            if (append) {
+                tmplog_offset = ftell(tmplog);
+            }
+            check_and_fclose(tmplog, TEMPORARY_LOG_FILE);
+        }
+        check_and_fclose(log, destination);
+    }
+}
+
+
+// clear the recovery command and prepare to boot a (hopefully working) system,
+// copy our log file to cache as well (for the system to read), and
+// 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) {
+    // By this point, we're ready to return to the main system...
+    if (send_intent != NULL) {
+        FILE *fp = fopen_path(INTENT_FILE, "w");
+        if (fp == NULL) {
+            LOGE("Can't open %s\n", INTENT_FILE);
+        } else {
+            fputs(send_intent, fp);
+            check_and_fclose(fp, INTENT_FILE);
+        }
+    }
+
+    // Copy logs to cache so the system can find out what happened.
+    copy_log_file(LOG_FILE, true);
+    copy_log_file(LAST_LOG_FILE, false);
+    chmod(LAST_LOG_FILE, 0640);
+
+    if (device_flash_type() == MTD) {
+        // Reset to mormal system boot so recovery won't cycle indefinitely.
+        struct bootloader_message boot;
+        memset(&boot, 0, sizeof(boot));
+        set_bootloader_message(&boot);
+    }
+
+    // Remove the command file, so recovery won't repeat indefinitely.
+    if (ensure_path_mounted(COMMAND_FILE) != 0 ||
+        (unlink(COMMAND_FILE) && errno != ENOENT)) {
+        LOGW("Can't unlink %s\n", COMMAND_FILE);
+    }
+
+    sync();  // For good measure.
+}
+
+static int
+erase_volume(const char *volume) {
+    ui_set_background(BACKGROUND_ICON_INSTALLING);
+    ui_show_indeterminate_progress();
+    ui_print("Formatting %s...\n", volume);
+
+    if (strcmp(volume, "/cache") == 0) {
+        // Any part of the log we'd copied to cache is now gone.
+        // Reset the pointer so we copy from the beginning of the temp
+        // log.
+        tmplog_offset = 0;
+    }
+
+    return format_volume(volume);
+}
+
+static char*
+copy_sideloaded_package(const char* original_path) {
+  if (ensure_path_mounted(original_path) != 0) {
+    LOGE("Can't mount %s\n", original_path);
+    return NULL;
+  }
+
+  if (ensure_path_mounted(SIDELOAD_TEMP_DIR) != 0) {
+    LOGE("Can't mount %s\n", SIDELOAD_TEMP_DIR);
+    return NULL;
+  }
+
+  if (mkdir(SIDELOAD_TEMP_DIR, 0700) != 0) {
+    if (errno != EEXIST) {
+      LOGE("Can't mkdir %s (%s)\n", SIDELOAD_TEMP_DIR, strerror(errno));
+      return NULL;
+    }
+  }
+
+  // verify that SIDELOAD_TEMP_DIR is exactly what we expect: a
+  // directory, owned by root, readable and writable only by root.
+  struct stat st;
+  if (stat(SIDELOAD_TEMP_DIR, &st) != 0) {
+    LOGE("failed to stat %s (%s)\n", SIDELOAD_TEMP_DIR, strerror(errno));
+    return NULL;
+  }
+  if (!S_ISDIR(st.st_mode)) {
+    LOGE("%s isn't a directory\n", SIDELOAD_TEMP_DIR);
+    return NULL;
+  }
+  if ((st.st_mode & 0777) != 0700) {
+    LOGE("%s has perms %o\n", SIDELOAD_TEMP_DIR, st.st_mode);
+    return NULL;
+  }
+  if (st.st_uid != 0) {
+    LOGE("%s owned by %lu; not root\n", SIDELOAD_TEMP_DIR, st.st_uid);
+    return NULL;
+  }
+
+  char copy_path[PATH_MAX];
+  strcpy(copy_path, SIDELOAD_TEMP_DIR);
+  strcat(copy_path, "/package.zip");
+
+  char* buffer = malloc(BUFSIZ);
+  if (buffer == NULL) {
+    LOGE("Failed to allocate buffer\n");
+    return NULL;
+  }
+
+  size_t read;
+  FILE* fin = fopen(original_path, "rb");
+  if (fin == NULL) {
+    LOGE("Failed to open %s (%s)\n", original_path, strerror(errno));
+    return NULL;
+  }
+  FILE* fout = fopen(copy_path, "wb");
+  if (fout == NULL) {
+    LOGE("Failed to open %s (%s)\n", copy_path, strerror(errno));
+    return NULL;
+  }
+
+  while ((read = fread(buffer, 1, BUFSIZ, fin)) > 0) {
+    if (fwrite(buffer, 1, read, fout) != read) {
+      LOGE("Short write of %s (%s)\n", copy_path, strerror(errno));
+      return NULL;
+    }
+  }
+
+  free(buffer);
+
+  if (fclose(fout) != 0) {
+    LOGE("Failed to close %s (%s)\n", copy_path, strerror(errno));
+    return NULL;
+  }
+
+  if (fclose(fin) != 0) {
+    LOGE("Failed to close %s (%s)\n", original_path, strerror(errno));
+    return NULL;
+  }
+
+  // "adb push" is happy to overwrite read-only files when it's
+  // running as root, but we'll try anyway.
+  if (chmod(copy_path, 0400) != 0) {
+    LOGE("Failed to chmod %s (%s)\n", copy_path, strerror(errno));
+    return NULL;
+  }
+
+  return strdup(copy_path);
+}
+
+static char**
+prepend_title(char** headers) {
+    char* title[] = { EXPAND(RECOVERY_VERSION),
+                      "",
+                      NULL };
+
+    // count the number of lines in our title, plus the
+    // caller-provided headers.
+    int count = 0;
+    char** p;
+    for (p = title; *p; ++p, ++count);
+    for (p = headers; *p; ++p, ++count);
+
+    char** new_headers = malloc((count+1) * sizeof(char*));
+    char** h = new_headers;
+    for (p = title; *p; ++p, ++h) *h = *p;
+    for (p = headers; *p; ++p, ++h) *h = *p;
+    *h = NULL;
+
+    return new_headers;
+}
+
+int
+get_menu_selection(char** headers, char** items, int menu_only,
+                   int initial_selection) {
+    // throw away keys pressed previously, so user doesn't
+    // accidentally trigger menu items.
+    ui_clear_key_queue();
+
+    int item_count = ui_start_menu(headers, items, initial_selection);
+    int selected = initial_selection;
+    int chosen_item = -1;
+
+    // Some users with dead enter keys need a way to turn on power to select.
+    // Jiggering across the wrapping menu is one "secret" way to enable it.
+    // We can't rely on /cache or /sdcard since they may not be available.
+    int wrap_count = 0;
+
+    while (chosen_item < 0 && chosen_item != GO_BACK) {
+        int key = ui_wait_key();
+        int visible = ui_text_visible();
+
+        int action = device_handle_key(key, visible);
+
+        int old_selected = selected;
+
+        if (action < 0) {
+            switch (action) {
+                case HIGHLIGHT_UP:
+                    --selected;
+                    selected = ui_menu_select(selected);
+                    break;
+                case HIGHLIGHT_DOWN:
+                    ++selected;
+                    selected = ui_menu_select(selected);
+                    break;
+                case SELECT_ITEM:
+                    chosen_item = selected;
+                    if (ui_get_showing_back_button()) {
+                        if (chosen_item == item_count) {
+                            chosen_item = GO_BACK;
+                        }
+                    }
+                    break;
+                case NO_ACTION:
+                    break;
+                case GO_BACK:
+                    chosen_item = GO_BACK;
+                    break;
+            }
+        } else if (!menu_only) {
+            chosen_item = action;
+        }
+
+        if (abs(selected - old_selected) > 1) {
+            wrap_count++;
+            if (wrap_count == 3) {
+                wrap_count = 0;
+                if (ui_get_showing_back_button()) {
+                    ui_print("Back menu button disabled.\n");
+                    ui_set_showing_back_button(0);
+                }
+                else {
+                    ui_print("Back menu button enabled.\n");
+                    ui_set_showing_back_button(1);
+                }
+            }
+        }
+    }
+
+    ui_end_menu();
+    ui_clear_key_queue();
+    return chosen_item;
+}
+
+static int compare_string(const void* a, const void* b) {
+    return strcmp(*(const char**)a, *(const char**)b);
+}
+
+static int
+sdcard_directory(const char* path) {
+    ensure_path_mounted(SDCARD_ROOT);
+
+    const char* MENU_HEADERS[] = { "Choose a package to install:",
+                                   path,
+                                   "",
+                                   NULL };
+    DIR* d;
+    struct dirent* de;
+    d = opendir(path);
+    if (d == NULL) {
+        LOGE("error opening %s: %s\n", path, strerror(errno));
+        ensure_path_unmounted(SDCARD_ROOT);
+        return 0;
+    }
+
+    char** headers = prepend_title(MENU_HEADERS);
+
+    int d_size = 0;
+    int d_alloc = 10;
+    char** dirs = malloc(d_alloc * sizeof(char*));
+    int z_size = 1;
+    int z_alloc = 10;
+    char** zips = malloc(z_alloc * sizeof(char*));
+    zips[0] = strdup("../");
+
+    while ((de = readdir(d)) != NULL) {
+        int name_len = strlen(de->d_name);
+
+        if (de->d_type == DT_DIR) {
+            // skip "." and ".." entries
+            if (name_len == 1 && de->d_name[0] == '.') continue;
+            if (name_len == 2 && de->d_name[0] == '.' &&
+                de->d_name[1] == '.') continue;
+
+            if (d_size >= d_alloc) {
+                d_alloc *= 2;
+                dirs = realloc(dirs, d_alloc * sizeof(char*));
+            }
+            dirs[d_size] = malloc(name_len + 2);
+            strcpy(dirs[d_size], de->d_name);
+            dirs[d_size][name_len] = '/';
+            dirs[d_size][name_len+1] = '\0';
+            ++d_size;
+        } else if (de->d_type == DT_REG &&
+                   name_len >= 4 &&
+                   strncasecmp(de->d_name + (name_len-4), ".zip", 4) == 0) {
+            if (z_size >= z_alloc) {
+                z_alloc *= 2;
+                zips = realloc(zips, z_alloc * sizeof(char*));
+            }
+            zips[z_size++] = strdup(de->d_name);
+        }
+    }
+    closedir(d);
+
+    qsort(dirs, d_size, sizeof(char*), compare_string);
+    qsort(zips, z_size, sizeof(char*), compare_string);
+
+    // append dirs to the zips list
+    if (d_size + z_size + 1 > z_alloc) {
+        z_alloc = d_size + z_size + 1;
+        zips = realloc(zips, z_alloc * sizeof(char*));
+    }
+    memcpy(zips + z_size, dirs, d_size * sizeof(char*));
+    free(dirs);
+    z_size += d_size;
+    zips[z_size] = NULL;
+
+    int result;
+    int chosen_item = 0;
+    do {
+        chosen_item = get_menu_selection(headers, zips, 1, chosen_item);
+
+        char* item = zips[chosen_item];
+        int item_len = strlen(item);
+        if (chosen_item == 0) {          // item 0 is always "../"
+            // go up but continue browsing (if the caller is sdcard_directory)
+            result = -1;
+            break;
+        } else if (item[item_len-1] == '/') {
+            // recurse down into a subdirectory
+            char new_path[PATH_MAX];
+            strlcpy(new_path, path, PATH_MAX);
+            strlcat(new_path, "/", PATH_MAX);
+            strlcat(new_path, item, PATH_MAX);
+            new_path[strlen(new_path)-1] = '\0';  // truncate the trailing '/'
+            result = sdcard_directory(new_path);
+            if (result >= 0) break;
+        } else {
+            // selected a zip file:  attempt to install it, and return
+            // the status to the caller.
+            char new_path[PATH_MAX];
+            strlcpy(new_path, path, PATH_MAX);
+            strlcat(new_path, "/", PATH_MAX);
+            strlcat(new_path, item, PATH_MAX);
+
+            ui_print("\n-- Install %s ...\n", path);
+            set_sdcard_update_bootloader_message();
+            char* copy = copy_sideloaded_package(new_path);
+            ensure_path_unmounted(SDCARD_ROOT);
+            if (copy) {
+                result = install_package(copy);
+                free(copy);
+            } else {
+                result = INSTALL_ERROR;
+            }
+            break;
+        }
+    } while (true);
+
+    int i;
+    for (i = 0; i < z_size; ++i) free(zips[i]);
+    free(zips);
+    free(headers);
+
+    ensure_path_unmounted(SDCARD_ROOT);
+    return result;
+}
+
+static void
+wipe_data(int confirm) {
+    if (confirm) {
+        static char** title_headers = NULL;
+
+        if (title_headers == NULL) {
+            char* headers[] = { "Confirm wipe of all user data?",
+                                "  THIS CAN NOT BE UNDONE.",
+                                "",
+                                NULL };
+            title_headers = prepend_title((const char**)headers);
+        }
+
+        char* items[] = { " No",
+                          " No",
+                          " No",
+                          " No",
+                          " No",
+                          " No",
+                          " No",
+                          " Yes -- delete all user data",   // [7]
+                          " No",
+                          " No",
+                          " No",
+                          NULL };
+
+        int chosen_item = get_menu_selection(title_headers, items, 1, 0);
+        if (chosen_item != 7) {
+            return;
+        }
+    }
+
+    ui_print("\n-- Wiping data...\n");
+    device_wipe_data();
+    erase_volume("/data");
+    erase_volume("/cache");
+    if (has_datadata()) {
+        erase_volume("/datadata");
+    }
+    erase_volume("/sd-ext");
+    erase_volume("/sdcard/.android_secure");
+    ui_print("Data wipe complete.\n");
+}
+
+static void
+prompt_and_wait() {
+    char** headers = prepend_title((const char**)MENU_HEADERS);
+
+    for (;;) {
+        finish_recovery(NULL);
+        ui_reset_progress();
+
+        allow_display_toggle = 1;
+        int chosen_item = get_menu_selection(headers, MENU_ITEMS, 0, 0);
+        allow_display_toggle = 0;
+
+        // device-specific code may take some action here.  It may
+        // return one of the core actions handled in the switch
+        // statement below.
+        chosen_item = device_perform_action(chosen_item);
+
+        switch (chosen_item) {
+            case ITEM_REBOOT:
+                return;
+
+            case ITEM_WIPE_DATA:
+                wipe_data(ui_text_visible());
+                if (!ui_text_visible()) return;
+                break;
+
+            case ITEM_WIPE_CACHE:
+                if (confirm_selection("Confirm wipe?", "Yes - Wipe Cache"))
+                {
+                    ui_print("\n-- Wiping cache...\n");
+                    erase_volume("/cache");
+                    ui_print("Cache wipe complete.\n");
+                    if (!ui_text_visible()) return;
+                }
+                break;
+
+            case ITEM_APPLY_SDCARD:
+                if (confirm_selection("Confirm install?", "Yes - Install /sdcard/update.zip"))
+                {
+                    ui_print("\n-- Install from sdcard...\n");
+                    int status = install_package(SDCARD_PACKAGE_FILE);
+                    if (status != INSTALL_SUCCESS) {
+                        ui_set_background(BACKGROUND_ICON_ERROR);
+                        ui_print("Installation aborted.\n");
+                    } else if (!ui_text_visible()) {
+                        return;  // reboot if logs aren't visible
+                    } else {
+                        ui_print("\nInstall from sdcard complete.\n");
+                    }
+                }
+                break;
+            case ITEM_INSTALL_ZIP:
+                show_install_update_menu();
+                break;
+            case ITEM_NANDROID:
+                show_nandroid_menu();
+                break;
+            case ITEM_PARTITION:
+                show_partition_menu();
+                break;
+            case ITEM_ADVANCED:
+                show_advanced_menu();
+                break;
+        }
+    }
+}
+
+static void
+print_property(const char *key, const char *name, void *cookie) {
+    printf("%s=%s\n", key, name);
+}
+
+int
+main(int argc, char **argv) {
+	if (strstr(argv[0], "recovery") == NULL)
+	{
+	    if (strstr(argv[0], "flash_image") != NULL)
+	        return flash_image_main(argc, argv);
+	    if (strstr(argv[0], "volume") != NULL)
+	        return volume_main(argc, argv);
+	    if (strstr(argv[0], "edify") != NULL)
+	        return edify_main(argc, argv);
+	    if (strstr(argv[0], "dump_image") != NULL)
+	        return dump_image_main(argc, argv);
+	    if (strstr(argv[0], "erase_image") != NULL)
+	        return erase_image_main(argc, argv);
+	    if (strstr(argv[0], "mkyaffs2image") != NULL)
+	        return mkyaffs2image_main(argc, argv);
+	    if (strstr(argv[0], "unyaffs") != NULL)
+	        return unyaffs_main(argc, argv);
+        if (strstr(argv[0], "nandroid"))
+            return nandroid_main(argc, argv);
+        if (strstr(argv[0], "reboot"))
+            return reboot_main(argc, argv);
+        if (strstr(argv[0], "setprop"))
+            return setprop_main(argc, argv);
+		return busybox_driver(argc, argv);
+	}
+    handle_chargemode();
+    __system("/sbin/postrecoveryboot.sh");
+    
+    int is_user_initiated_recovery = 0;
+    time_t start = time(NULL);
+
+    // If these fail, there's not really anywhere to complain...
+    freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);
+    freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);
+    printf("Starting recovery on %s", ctime(&start));
+
+    ui_init();
+    ui_print(EXPAND(RECOVERY_VERSION)"\n");
+    load_volume_table();
+    process_volumes();
+    LOGI("Processing arguments.\n");
+    get_args(&argc, &argv);
+
+    int previous_runs = 0;
+    const char *send_intent = NULL;
+    const char *update_package = NULL;
+    const char *encrypted_fs_mode = NULL;
+    int wipe_data = 0, wipe_cache = 0;
+    int toggle_secure_fs = 0;
+    encrypted_fs_info encrypted_fs_data;
+
+    LOGI("Checking arguments.\n");
+    int arg;
+    while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
+        switch (arg) {
+        case 'p': previous_runs = atoi(optarg); break;
+        case 's': send_intent = optarg; break;
+        case 'u': update_package = optarg; break;
+        case 'w': wipe_data = wipe_cache = 1; break;
+        case 'c': wipe_cache = 1; break;
+        case 'e': encrypted_fs_mode = optarg; toggle_secure_fs = 1; break;
+        case 't': ui_show_text(1); break;
+        case '?':
+            LOGE("Invalid command argument\n");
+            continue;
+        }
+    }
+
+    LOGI("device_recovery_start()\n");
+    device_recovery_start();
+
+    printf("Command:");
+    for (arg = 0; arg < argc; arg++) {
+        printf(" \"%s\"", argv[arg]);
+    }
+    printf("\n");
+
+    if (update_package) {
+        // For backwards compatibility on the cache partition only, if
+        // we're given an old 'root' path "CACHE:foo", change it to
+        // "/cache/foo".
+        if (strncmp(update_package, "CACHE:", 6) == 0) {
+            int len = strlen(update_package) + 10;
+            char* modified_path = malloc(len);
+            strlcpy(modified_path, "/cache/", len);
+            strlcat(modified_path, update_package+6, len);
+            printf("(replacing path \"%s\" with \"%s\")\n",
+                   update_package, modified_path);
+            update_package = modified_path;
+        }
+    }
+    printf("\n");
+
+    property_list(print_property, NULL);
+    printf("\n");
+
+    int status = INSTALL_SUCCESS;
+    
+    if (toggle_secure_fs) {
+        if (strcmp(encrypted_fs_mode,"on") == 0) {
+            encrypted_fs_data.mode = MODE_ENCRYPTED_FS_ENABLED;
+            ui_print("Enabling Encrypted FS.\n");
+        } else if (strcmp(encrypted_fs_mode,"off") == 0) {
+            encrypted_fs_data.mode = MODE_ENCRYPTED_FS_DISABLED;
+            ui_print("Disabling Encrypted FS.\n");
+        } else {
+            ui_print("Error: invalid Encrypted FS setting.\n");
+            status = INSTALL_ERROR;
+        }
+
+        // Recovery strategy: if the data partition is damaged, disable encrypted file systems.
+        // This preventsthe device recycling endlessly in recovery mode.
+        if ((encrypted_fs_data.mode == MODE_ENCRYPTED_FS_ENABLED) &&
+                (read_encrypted_fs_info(&encrypted_fs_data))) {
+            ui_print("Encrypted FS change aborted, resetting to disabled state.\n");
+            encrypted_fs_data.mode = MODE_ENCRYPTED_FS_DISABLED;
+        }
+
+        if (status != INSTALL_ERROR) {
+            if (erase_volume("/data")) {
+                ui_print("Data wipe failed.\n");
+                status = INSTALL_ERROR;
+            } else if (erase_volume("/cache")) {
+                ui_print("Cache wipe failed.\n");
+                status = INSTALL_ERROR;
+            } else if ((encrypted_fs_data.mode == MODE_ENCRYPTED_FS_ENABLED) &&
+                      (restore_encrypted_fs_info(&encrypted_fs_data))) {
+                ui_print("Encrypted FS change aborted.\n");
+                status = INSTALL_ERROR;
+            } else {
+                ui_print("Successfully updated Encrypted FS.\n");
+                status = INSTALL_SUCCESS;
+            }
+        }
+    } else if (update_package != NULL) {
+        status = install_package(update_package);
+        if (status != INSTALL_SUCCESS) ui_print("Installation aborted.\n");
+    } else if (wipe_data) {
+        if (device_wipe_data()) status = INSTALL_ERROR;
+        if (erase_volume("/data")) status = INSTALL_ERROR;
+        if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
+        if (status != INSTALL_SUCCESS) ui_print("Data wipe failed.\n");
+    } else if (wipe_cache) {
+        if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
+        if (status != INSTALL_SUCCESS) ui_print("Cache wipe failed.\n");
+    } else {
+        LOGI("Checking for extendedcommand...\n");
+        status = INSTALL_ERROR;  // No command specified
+        // we are starting up in user initiated recovery here
+        // let's set up some default options
+        signature_check_enabled = 0;
+        script_assert_enabled = 0;
+        is_user_initiated_recovery = 1;
+        ui_set_show_text(1);
+        ui_set_background(BACKGROUND_ICON_CLOCKWORK);
+        
+        if (extendedcommand_file_exists()) {
+            LOGI("Running extendedcommand...\n");
+            int ret;
+            if (0 == (ret = run_and_remove_extendedcommand())) {
+                status = INSTALL_SUCCESS;
+                ui_set_show_text(0);
+            }
+            else {
+                handle_failure(ret);
+            }
+        } else {
+            LOGI("Skipping execution of extendedcommand, file not found...\n");
+        }
+    }
+
+    if (status != INSTALL_SUCCESS && !is_user_initiated_recovery) ui_set_background(BACKGROUND_ICON_ERROR);
+    if (status != INSTALL_SUCCESS || ui_text_visible()) {
+        prompt_and_wait();
+    }
+
+    // Otherwise, get ready to boot the main system...
+    finish_recovery(send_intent);
+    ui_print("Rebooting...\n");
+    sync();
+    reboot(RB_AUTOBOOT);
+    return EXIT_SUCCESS;
+}
+
+int get_allow_toggle_display() {
+    return allow_display_toggle;
+}
diff --git a/recovery_ui.h b/recovery_ui.h
new file mode 100644
index 0000000..f34365f
--- /dev/null
+++ b/recovery_ui.h
@@ -0,0 +1,90 @@
+/*
+ * 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 _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
+// be toggled.
+extern int device_toggle_display(volatile char* key_pressed, int key_code);
+
+// 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 device should reboot
+// immediately.
+extern int device_reboot_now(volatile char* key_pressed, int key_code);
+
+// Called from the main thread when recovery is waiting for input and
+// a key is pressed.  key is the code of the key pressed; visible is
+// true if the recovery menu is being shown.  Implementations can call
+// ui_key_pressed() to discover if other keys are being held down.
+// Return one of the defined constants below in order to:
+//
+//   - move the menu highlight (HIGHLIGHT_*)
+//   - invoke the highlighted item (SELECT_ITEM)
+//   - do nothing (NO_ACTION)
+//   - invoke a specific action (a menu position: any non-negative number)
+extern int device_handle_key(int key, int visible);
+
+// Perform a recovery action selected from the menu.  'which' will be
+// the item number of the selected menu item, or a non-negative number
+// returned from device_handle_key().  The menu will be hidden when
+// this is called; implementations can call ui_print() to print
+// information to the screen.
+extern int device_perform_action(int which);
+
+// Called when we do a wipe data/factory reset operation (either via a
+// reboot from the main system with the --wipe_data flag, or when the
+// user boots into recovery manually and selects the option from the
+// menu.)  Can perform whatever device-specific wiping actions are
+// needed.  Return 0 on success.  The userdata and cache partitions
+// are erased after this returns (whether it returns success or not).
+int device_wipe_data();
+
+#define NO_ACTION           -1
+
+#define HIGHLIGHT_UP        -2
+#define HIGHLIGHT_DOWN      -3
+#define SELECT_ITEM         -4
+#define GO_BACK             -5
+
+#define ITEM_REBOOT          0
+#define ITEM_APPLY_SDCARD    1
+#define ITEM_WIPE_DATA       2
+#define ITEM_WIPE_CACHE      3
+#define ITEM_INSTALL_ZIP     4
+#define ITEM_NANDROID        5
+#define ITEM_PARTITION       6
+#define ITEM_ADVANCED        7
+
+// Header text to display above the main menu.
+extern char* MENU_HEADERS[];
+
+// Text of menu items.
+extern char* MENU_ITEMS[];
+
+int
+get_menu_selection(char** headers, char** items, int menu_only, int initial_selection);
+
+void
+set_sdcard_update_bootloader_message();
+
+#endif
diff --git a/res/images/icon_clockwork.png b/res/images/icon_clockwork.png
new file mode 100755
index 0000000..b05951d
--- /dev/null
+++ b/res/images/icon_clockwork.png
Binary files differ
diff --git a/res/images/icon_error.png b/res/images/icon_error.png
new file mode 100755
index 0000000..90c8d87
--- /dev/null
+++ b/res/images/icon_error.png
Binary files differ
diff --git a/res/images/icon_installing.png b/res/images/icon_installing.png
new file mode 100755
index 0000000..d428f57
--- /dev/null
+++ b/res/images/icon_installing.png
Binary files differ
diff --git a/res/images/indeterminate1.png b/res/images/indeterminate1.png
new file mode 100644
index 0000000..90cb9fb
--- /dev/null
+++ b/res/images/indeterminate1.png
Binary files differ
diff --git a/res/images/indeterminate2.png b/res/images/indeterminate2.png
new file mode 100644
index 0000000..f7fb289
--- /dev/null
+++ b/res/images/indeterminate2.png
Binary files differ
diff --git a/res/images/indeterminate3.png b/res/images/indeterminate3.png
new file mode 100644
index 0000000..ba10dfa
--- /dev/null
+++ b/res/images/indeterminate3.png
Binary files differ
diff --git a/res/images/indeterminate4.png b/res/images/indeterminate4.png
new file mode 100644
index 0000000..ad5d9a5
--- /dev/null
+++ b/res/images/indeterminate4.png
Binary files differ
diff --git a/res/images/indeterminate5.png b/res/images/indeterminate5.png
new file mode 100644
index 0000000..8c19c8d
--- /dev/null
+++ b/res/images/indeterminate5.png
Binary files differ
diff --git a/res/images/indeterminate6.png b/res/images/indeterminate6.png
new file mode 100644
index 0000000..c0c6638
--- /dev/null
+++ b/res/images/indeterminate6.png
Binary files differ
diff --git a/res/images/progress_empty.png b/res/images/progress_empty.png
new file mode 100644
index 0000000..4cb4998
--- /dev/null
+++ b/res/images/progress_empty.png
Binary files differ
diff --git a/res/images/progress_fill.png b/res/images/progress_fill.png
new file mode 100644
index 0000000..eb71754
--- /dev/null
+++ b/res/images/progress_fill.png
Binary files differ
diff --git a/roots.c b/roots.c
new file mode 100644
index 0000000..d4c0033
--- /dev/null
+++ b/roots.c
@@ -0,0 +1,270 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "mtdutils/mtdutils.h"
+#include "mounts.h"
+#include "roots.h"
+#include "common.h"
+#include "make_ext4fs.h"
+
+static int num_volumes = 0;
+static Volume* device_volumes = NULL;
+
+void load_volume_table() {
+    int alloc = 2;
+    device_volumes = malloc(alloc * sizeof(Volume));
+
+    // Insert an entry for /tmp, which is the ramdisk and is always mounted.
+    device_volumes[0].mount_point = "/tmp";
+    device_volumes[0].fs_type = "ramdisk";
+    device_volumes[0].device = NULL;
+    device_volumes[0].device2 = NULL;
+    num_volumes = 1;
+
+    FILE* fstab = fopen("/etc/recovery.fstab", "r");
+    if (fstab == NULL) {
+        LOGE("failed to open /etc/recovery.fstab (%s)\n", strerror(errno));
+        return;
+    }
+
+    char buffer[1024];
+    int i;
+    while (fgets(buffer, sizeof(buffer)-1, fstab)) {
+        for (i = 0; buffer[i] && isspace(buffer[i]); ++i);
+        if (buffer[i] == '\0' || buffer[i] == '#') continue;
+
+        char* original = strdup(buffer);
+
+        char* mount_point = strtok(buffer+i, " \t\n");
+        char* fs_type = strtok(NULL, " \t\n");
+        char* device = strtok(NULL, " \t\n");
+        // lines may optionally have a second device, to use if
+        // mounting the first one fails.
+        char* device2 = strtok(NULL, " \t\n");
+
+        if (mount_point && fs_type && device) {
+            while (num_volumes >= alloc) {
+                alloc *= 2;
+                device_volumes = realloc(device_volumes, alloc*sizeof(Volume));
+            }
+            device_volumes[num_volumes].mount_point = strdup(mount_point);
+            device_volumes[num_volumes].fs_type = strdup(fs_type);
+            device_volumes[num_volumes].device = strdup(device);
+            device_volumes[num_volumes].device2 =
+                device2 ? strdup(device2) : NULL;
+            ++num_volumes;
+        } else {
+            LOGE("skipping malformed recovery.fstab line: %s\n", original);
+        }
+        free(original);
+    }
+
+    fclose(fstab);
+
+    printf("recovery filesystem table\n");
+    printf("=========================\n");
+    for (i = 0; i < num_volumes; ++i) {
+        Volume* v = &device_volumes[i];
+        printf("  %d %s %s %s %s\n", i, v->mount_point, v->fs_type,
+               v->device, v->device2);
+    }
+    printf("\n");
+}
+
+Volume* volume_for_path(const char* path) {
+    int i;
+    for (i = 0; i < num_volumes; ++i) {
+        Volume* v = device_volumes+i;
+        int len = strlen(v->mount_point);
+        if (strncmp(path, v->mount_point, len) == 0 &&
+            (path[len] == '\0' || path[len] == '/')) {
+            return v;
+        }
+    }
+    return NULL;
+}
+
+int ensure_path_mounted(const char* path) {
+    Volume* v = volume_for_path(path);
+    if (v == NULL) {
+        LOGE("unknown volume for path [%s]\n", path);
+        return -1;
+    }
+    if (strcmp(v->fs_type, "ramdisk") == 0) {
+        // the ramdisk is always mounted.
+        return 0;
+    }
+
+    int result;
+    result = scan_mounted_volumes();
+    if (result < 0) {
+        LOGE("failed to scan mounted volumes\n");
+        return -1;
+    }
+
+    const MountedVolume* mv =
+        find_mounted_volume_by_mount_point(v->mount_point);
+    if (mv) {
+        // volume is already mounted
+        return 0;
+    }
+
+    mkdir(v->mount_point, 0755);  // in case it doesn't already exist
+
+    if (strcmp(v->fs_type, "yaffs2") == 0) {
+        // mount an MTD partition as a YAFFS2 filesystem.
+        mtd_scan_partitions();
+        const MtdPartition* partition;
+        partition = mtd_find_partition_by_name(v->device);
+        if (partition == NULL) {
+            LOGE("failed to find \"%s\" partition to mount at \"%s\"\n",
+                 v->device, v->mount_point);
+            return -1;
+        }
+        return mtd_mount_partition(partition, v->mount_point, v->fs_type, 0);
+    } else if (strcmp(v->fs_type, "ext4") == 0 ||
+               strcmp(v->fs_type, "vfat") == 0) {
+        result = mount(v->device, v->mount_point, v->fs_type,
+                       MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
+        if (result == 0) return 0;
+
+        if (v->device2) {
+            LOGW("failed to mount %s (%s); trying %s\n",
+                 v->device, strerror(errno), v->device2);
+            result = mount(v->device2, v->mount_point, v->fs_type,
+                           MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
+            if (result == 0) return 0;
+        }
+
+        LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno));
+        return -1;
+    } else {
+        // let's try mounting with the mount binary and hope for the best.
+        char mount_cmd[PATH_MAX];
+        sprintf(mount_cmd, "mount %s", path);
+        return __system(mount_cmd);
+    }
+
+    LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, v->mount_point);
+    return -1;
+}
+
+int ensure_path_unmounted(const char* path) {
+    Volume* v = volume_for_path(path);
+    if (v == NULL) {
+        LOGE("unknown volume for path [%s]\n", path);
+        return -1;
+    }
+    if (strcmp(v->fs_type, "ramdisk") == 0) {
+        // the ramdisk is always mounted; you can't unmount it.
+        return -1;
+    }
+
+    int result;
+    result = scan_mounted_volumes();
+    if (result < 0) {
+        LOGE("failed to scan mounted volumes\n");
+        return -1;
+    }
+
+    const MountedVolume* mv =
+        find_mounted_volume_by_mount_point(v->mount_point);
+    if (mv == NULL) {
+        // volume is already unmounted
+        return 0;
+    }
+
+    return unmount_mounted_volume(mv);
+}
+
+int format_volume(const char* volume) {
+    Volume* v = volume_for_path(volume);
+    if (v == NULL) {
+        // silent failure for sd-ext
+        if (strcmp(volume, "/sd-ext") == 0)
+            return -1;
+        LOGE("unknown volume \"%s\"\n", volume);
+        return -1;
+    }
+    if (strcmp(v->fs_type, "ramdisk") == 0) {
+        // you can't format the ramdisk.
+        LOGE("can't format_volume \"%s\"", volume);
+        return -1;
+    }
+    if (strcmp(v->mount_point, volume) != 0) {
+#if 0
+        LOGE("can't give path \"%s\" to format_volume\n", volume);
+        return -1;
+#endif
+        return format_unknown_device(v->device, volume, NULL);
+    }
+
+    if (ensure_path_unmounted(volume) != 0) {
+        LOGE("format_volume failed to unmount \"%s\"\n", v->mount_point);
+        return -1;
+    }
+
+    if (strcmp(v->fs_type, "yaffs2") == 0 || strcmp(v->fs_type, "mtd") == 0) {
+        mtd_scan_partitions();
+        const MtdPartition* partition = mtd_find_partition_by_name(v->device);
+        if (partition == NULL) {
+            LOGE("format_volume: no MTD partition \"%s\"\n", v->device);
+            return -1;
+        }
+
+        MtdWriteContext *write = mtd_write_partition(partition);
+        if (write == NULL) {
+            LOGW("format_volume: can't open MTD \"%s\"\n", v->device);
+            return -1;
+        } else if (mtd_erase_blocks(write, -1) == (off_t) -1) {
+            LOGW("format_volume: can't erase MTD \"%s\"\n", v->device);
+            mtd_write_close(write);
+            return -1;
+        } else if (mtd_write_close(write)) {
+            LOGW("format_volume: can't close MTD \"%s\"\n", v->device);
+            return -1;
+        }
+        return 0;
+    }
+
+    if (strcmp(v->fs_type, "emmc") == 0) {
+        return erase_raw_partition(v->device);
+    }
+
+    if (strcmp(v->fs_type, "ext4") == 0) {
+        reset_ext4fs_info();
+        int result = make_ext4fs(v->device, NULL, NULL, 0, 0, 0);
+        if (result != 0) {
+            LOGE("format_volume: make_extf4fs failed on %s\n", v->device);
+            return -1;
+        }
+        return 0;
+    }
+
+#if 0
+    LOGE("format_volume: fs_type \"%s\" unsupported\n", v->fs_type);
+    return -1;
+#endif
+    return format_unknown_device(v->device, volume, v->fs_type);
+}
diff --git a/roots.h b/roots.h
new file mode 100644
index 0000000..cf59bfd
--- /dev/null
+++ b/roots.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#ifndef RECOVERY_ROOTS_H_
+#define RECOVERY_ROOTS_H_
+
+#include "common.h"
+
+// Load and parse volume data from /etc/recovery.fstab.
+void load_volume_table();
+
+// Return the Volume* record for this path (or NULL).
+Volume* volume_for_path(const char* path);
+
+// Make sure that the volume 'path' is on is mounted.  Returns 0 on
+// success (volume is mounted).
+int ensure_path_mounted(const char* path);
+
+// Make sure that the volume 'path' is on is mounted.  Returns 0 on
+// success (volume is unmounted);
+int ensure_path_unmounted(const char* path);
+
+// Reformat the given volume (must be the mount point only, eg
+// "/cache"), no paths permitted.  Attempts to unmount the volume if
+// it is mounted.
+int format_volume(const char* volume);
+
+#endif  // RECOVERY_ROOTS_H_
diff --git a/setprop.c b/setprop.c
new file mode 100644
index 0000000..63ad2b4
--- /dev/null
+++ b/setprop.c
@@ -0,0 +1,18 @@
+#include <stdio.h>
+
+#include <cutils/properties.h>
+
+int setprop_main(int argc, char *argv[])
+{
+    if(argc != 3) {
+        fprintf(stderr,"usage: setprop <key> <value>\n");
+        return 1;
+    }
+
+    if(property_set(argv[1], argv[2])){
+        fprintf(stderr,"could not set property\n");
+        return 1;
+    }
+
+    return 0;
+}
diff --git a/testdata/alter-footer.zip b/testdata/alter-footer.zip
new file mode 100644
index 0000000..f497ec0
--- /dev/null
+++ b/testdata/alter-footer.zip
Binary files differ
diff --git a/testdata/alter-metadata.zip b/testdata/alter-metadata.zip
new file mode 100644
index 0000000..1c71fbc
--- /dev/null
+++ b/testdata/alter-metadata.zip
Binary files differ
diff --git a/testdata/fake-eocd.zip b/testdata/fake-eocd.zip
new file mode 100644
index 0000000..15dc0a9
--- /dev/null
+++ b/testdata/fake-eocd.zip
Binary files differ
diff --git a/testdata/jarsigned.zip b/testdata/jarsigned.zip
new file mode 100644
index 0000000..8b1ef8b
--- /dev/null
+++ b/testdata/jarsigned.zip
Binary files differ
diff --git a/testdata/otasigned.zip b/testdata/otasigned.zip
new file mode 100644
index 0000000..a6bc53e
--- /dev/null
+++ b/testdata/otasigned.zip
Binary files differ
diff --git a/testdata/random.zip b/testdata/random.zip
new file mode 100644
index 0000000..18c0b3b
--- /dev/null
+++ b/testdata/random.zip
Binary files differ
diff --git a/testdata/unsigned.zip b/testdata/unsigned.zip
new file mode 100644
index 0000000..24e3ead
--- /dev/null
+++ b/testdata/unsigned.zip
Binary files differ
diff --git a/tools/Android.mk b/tools/Android.mk
new file mode 100644
index 0000000..6571161
--- /dev/null
+++ b/tools/Android.mk
@@ -0,0 +1 @@
+include $(all-subdir-makefiles)
diff --git a/tools/ota/Android.mk b/tools/ota/Android.mk
new file mode 100644
index 0000000..0bde7ee
--- /dev/null
+++ b/tools/ota/Android.mk
@@ -0,0 +1,37 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+ifneq ($(TARGET_SIMULATOR),true)
+
+include $(CLEAR_VARS)
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_MODULE := add-property-tag
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
+LOCAL_SRC_FILES := add-property-tag.c
+LOCAL_STATIC_LIBRARIES := libc
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_MODULE := check-lost+found
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
+LOCAL_SRC_FILES := check-lost+found.c
+LOCAL_STATIC_LIBRARIES := libcutils libc
+include $(BUILD_EXECUTABLE)
+
+endif  # !TARGET_SIMULATOR
diff --git a/tools/ota/add-property-tag.c b/tools/ota/add-property-tag.c
new file mode 100644
index 0000000..5277edd
--- /dev/null
+++ b/tools/ota/add-property-tag.c
@@ -0,0 +1,141 @@
+/*
+ * 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 <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * Append a tag to a property value in a .prop file if it isn't already there.
+ * Normally used to modify build properties to record incremental updates.
+ */
+
+// Return nonzero if the tag should be added to this line.
+int should_tag(const char *line, const char *propname) {
+    const char *prop = strstr(line, propname);
+    if (prop == NULL) return 0;
+
+    // Make sure this is actually the property name (not an accidental hit)
+    const char *ptr;
+    for (ptr = line; ptr < prop && isspace(*ptr); ++ptr) ;
+    if (ptr != prop) return 0;  // Must be at the beginning of the line
+
+    for (ptr += strlen(propname); *ptr != '\0' && isspace(*ptr); ++ptr) ;
+    return (*ptr == '=');  // Must be followed by a '='
+}
+
+// Remove existing tags from the line, return the following number (if any)
+int remove_tag(char *line, const char *tag) {
+    char *pos = strstr(line, tag);
+    if (pos == NULL) return 0;
+
+    char *end;
+    int num = strtoul(pos + strlen(tag), &end, 10);
+    strcpy(pos, end);
+    return num;
+}
+
+// Write line to output with the tag added, adding a number (if >0)
+void write_tagged(FILE *out, const char *line, const char *tag, int number) {
+    const char *end = line + strlen(line);
+    while (end > line && isspace(end[-1])) --end;
+    if (number > 0) {
+        fprintf(out, "%.*s%s%d%s", end - line, line, tag, number, end);
+    } else {
+        fprintf(out, "%.*s%s%s", end - line, line, tag, end);
+    }
+}
+
+int main(int argc, char **argv) {
+    const char *filename = "/system/build.prop";
+    const char *propname = "ro.build.fingerprint";
+    const char *tag = NULL;
+    int do_remove = 0, do_number = 0;
+
+    int opt;
+    while ((opt = getopt(argc, argv, "f:p:rn")) != -1) {
+        switch (opt) {
+        case 'f': filename = optarg; break;
+        case 'p': propname = optarg; break;
+        case 'r': do_remove = 1; break;
+        case 'n': do_number = 1; break;
+        case '?': return 2;
+        }
+    }
+
+    if (argc != optind + 1) {
+        fprintf(stderr,
+            "usage: add-property-tag [flags] tag-to-add\n"
+            "flags: -f /dir/file.prop (default /system/build.prop)\n"
+            "       -p prop.name (default ro.build.fingerprint)\n"
+            "       -r (if set, remove the tag rather than adding it)\n"
+            "       -n (if set, add and increment a number after the tag)\n");
+        return 2;
+    }
+
+    tag = argv[optind];
+    FILE *input = fopen(filename, "r");
+    if (input == NULL) {
+        fprintf(stderr, "can't read %s: %s\n", filename, strerror(errno));
+        return 1;
+    }
+
+    char tmpname[PATH_MAX];
+    snprintf(tmpname, sizeof(tmpname), "%s.tmp", filename);
+    FILE *output = fopen(tmpname, "w");
+    if (output == NULL) {
+        fprintf(stderr, "can't write %s: %s\n", tmpname, strerror(errno));
+        return 1;
+    }
+
+    int found = 0;
+    char line[4096];
+    while (fgets(line, sizeof(line), input)) {
+        if (!should_tag(line, propname)) {
+            fputs(line, output);  // Pass through unmodified
+        } else {
+            found = 1;
+            int number = remove_tag(line, tag);
+            if (do_remove) {
+                fputs(line, output);  // Remove the tag but don't re-add it
+            } else {
+                write_tagged(output, line, tag, number + do_number);
+            }
+        }
+    }
+
+    fclose(input);
+    fclose(output);
+
+    if (!found) {
+        fprintf(stderr, "property %s not found in %s\n", propname, filename);
+        remove(tmpname);
+        return 1;
+    }
+
+    if (rename(tmpname, filename)) {
+        fprintf(stderr, "can't rename %s to %s: %s\n",
+            tmpname, filename, strerror(errno));
+        remove(tmpname);
+        return 1;
+    }
+
+    return 0;
+}
diff --git a/tools/ota/check-lost+found.c b/tools/ota/check-lost+found.c
new file mode 100644
index 0000000..f856275
--- /dev/null
+++ b/tools/ota/check-lost+found.c
@@ -0,0 +1,144 @@
+/*
+ * 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 <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/klog.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include "private/android_filesystem_config.h"
+
+// Sentinel file used to track whether we've forced a reboot
+static const char *kMarkerFile = "/data/misc/check-lost+found-rebooted-2";
+
+// Output file in tombstones directory (first 8K will be uploaded)
+static const char *kOutputDir = "/data/tombstones";
+static const char *kOutputFile = "/data/tombstones/check-lost+found-log";
+
+// Partitions to check
+static const char *kPartitions[] = { "/system", "/data", "/cache", NULL };
+
+/*
+ * 1. If /data/misc/forced-reboot is missing, touch it & force "unclean" boot.
+ * 2. Write a log entry with the number of files in lost+found directories.
+ */
+
+int main(int argc, char **argv) {
+    mkdir(kOutputDir, 0755);
+    chown(kOutputDir, AID_SYSTEM, AID_SYSTEM);
+    FILE *out = fopen(kOutputFile, "a");
+    if (out == NULL) {
+        fprintf(stderr, "Can't write %s: %s\n", kOutputFile, strerror(errno));
+        return 1;
+    }
+
+    // Note: only the first 8K of log will be uploaded, so be terse.
+    time_t start = time(NULL);
+    fprintf(out, "*** check-lost+found ***\nStarted: %s", ctime(&start));
+
+    struct stat st;
+    if (stat(kMarkerFile, &st)) {
+        // No reboot marker -- need to force an unclean reboot.
+        // But first, try to create the marker file.  If that fails,
+        // skip the reboot, so we don't get caught in an infinite loop.
+
+        int fd = open(kMarkerFile, O_WRONLY|O_CREAT, 0444);
+        if (fd >= 0 && close(fd) == 0) {
+            fprintf(out, "Wrote %s, rebooting\n", kMarkerFile);
+            fflush(out);
+            sync();  // Make sure the marker file is committed to disk
+
+            // If possible, dirty each of these partitions before rebooting,
+            // to make sure the filesystem has to do a scan on mount.
+            int i;
+            for (i = 0; kPartitions[i] != NULL; ++i) {
+                char fn[PATH_MAX];
+                snprintf(fn, sizeof(fn), "%s/%s", kPartitions[i], "dirty");
+                fd = open(fn, O_WRONLY|O_CREAT, 0444);
+                if (fd >= 0) {  // Don't sweat it if we can't write the file.
+                    write(fd, fn, sizeof(fn));  // write, you know, some data
+                    close(fd);
+                    unlink(fn);
+                }
+            }
+
+            reboot(RB_AUTOBOOT);  // reboot immediately, with dirty filesystems
+            fprintf(out, "Reboot failed?!\n");
+            exit(1);
+        } else {
+            fprintf(out, "Can't write %s: %s\n", kMarkerFile, strerror(errno));
+        }
+    } else {
+        fprintf(out, "Found %s\n", kMarkerFile);
+    }
+
+    int i;
+    for (i = 0; kPartitions[i] != NULL; ++i) {
+        char fn[PATH_MAX];
+        snprintf(fn, sizeof(fn), "%s/%s", kPartitions[i], "lost+found");
+        DIR *dir = opendir(fn);
+        if (dir == NULL) {
+            fprintf(out, "Can't open %s: %s\n", fn, strerror(errno));
+        } else {
+            int count = 0;
+            struct dirent *ent;
+            while ((ent = readdir(dir))) {
+                if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, ".."))
+                    ++count;
+            }
+            closedir(dir);
+            if (count > 0) {
+                fprintf(out, "OMGZ FOUND %d FILES IN %s\n", count, fn);
+            } else {
+                fprintf(out, "%s is clean\n", fn);
+            }
+        }
+    }
+
+    char dmesg[131073];
+    int len = klogctl(KLOG_READ_ALL, dmesg, sizeof(dmesg) - 1);
+    if (len < 0) {
+        fprintf(out, "Can't read kernel log: %s\n", strerror(errno));
+    } else {  // To conserve space, only write lines with certain keywords
+        fprintf(out, "--- Kernel log ---\n");
+        dmesg[len] = '\0';
+        char *saveptr, *line;
+        int in_yaffs = 0;
+        for (line = strtok_r(dmesg, "\n", &saveptr); line != NULL;
+             line = strtok_r(NULL, "\n", &saveptr)) {
+            if (strstr(line, "yaffs: dev is")) in_yaffs = 1;
+
+            if (in_yaffs ||
+                    strstr(line, "yaffs") ||
+                    strstr(line, "mtd") ||
+                    strstr(line, "msm_nand")) {
+                fprintf(out, "%s\n", line);
+            }
+
+            if (strstr(line, "yaffs_read_super: isCheckpointed")) in_yaffs = 0;
+        }
+    }
+
+    return 0;
+}
diff --git a/tools/ota/convert-to-bmp.py b/tools/ota/convert-to-bmp.py
new file mode 100644
index 0000000..446c09d
--- /dev/null
+++ b/tools/ota/convert-to-bmp.py
@@ -0,0 +1,79 @@
+#!/usr/bin/python2.4
+
+"""A simple script to convert asset images to BMP files, that supports
+RGBA image."""
+
+import struct
+import Image
+import sys
+
+infile = sys.argv[1]
+outfile = sys.argv[2]
+
+if not outfile.endswith(".bmp"):
+  print >> sys.stderr, "Warning: I'm expecting to write BMP files."
+
+im = Image.open(infile)
+if im.mode == 'RGB':
+  im.save(outfile)
+elif im.mode == 'RGBA':
+  # Python Imaging Library doesn't write RGBA BMP files, so we roll
+  # our own.
+
+  BMP_HEADER_FMT = ("<"      # little-endian
+                    "H"      # signature
+                    "L"      # file size
+                    "HH"     # reserved (set to 0)
+                    "L"      # offset to start of bitmap data)
+                    )
+
+  BITMAPINFO_HEADER_FMT= ("<"      # little-endian
+                          "L"      # size of this struct
+                          "L"      # width
+                          "L"      # height
+                          "H"      # planes (set to 1)
+                          "H"      # bit count
+                          "L"      # compression (set to 0 for minui)
+                          "L"      # size of image data (0 if uncompressed)
+                          "L"      # x pixels per meter (1)
+                          "L"      # y pixels per meter (1)
+                          "L"      # colors used (0)
+                          "L"      # important colors (0)
+                          )
+
+  fileheadersize = struct.calcsize(BMP_HEADER_FMT)
+  infoheadersize = struct.calcsize(BITMAPINFO_HEADER_FMT)
+
+  header = struct.pack(BMP_HEADER_FMT,
+                       0x4d42,   # "BM" in little-endian
+                       (fileheadersize + infoheadersize +
+                        im.size[0] * im.size[1] * 4),
+                       0, 0,
+                       fileheadersize + infoheadersize)
+
+  info = struct.pack(BITMAPINFO_HEADER_FMT,
+                     infoheadersize,
+                     im.size[0],
+                     im.size[1],
+                     1,
+                     32,
+                     0,
+                     0,
+                     1,
+                     1,
+                     0,
+                     0)
+
+  f = open(outfile, "wb")
+  f.write(header)
+  f.write(info)
+  data = im.tostring()
+  for j in range(im.size[1]-1, -1, -1):   # rows bottom-to-top
+    for i in range(j*im.size[0]*4, (j+1)*im.size[0]*4, 4):
+      f.write(data[i+2])    # B
+      f.write(data[i+1])    # G
+      f.write(data[i+0])    # R
+      f.write(data[i+3])    # A
+  f.close()
+else:
+  print >> sys.stderr, "Don't know how to handle image mode '%s'." % (im.mode,)
diff --git a/ui.c b/ui.c
new file mode 100644
index 0000000..133f4da
--- /dev/null
+++ b/ui.c
@@ -0,0 +1,616 @@
+/*
+ * 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.
+ */
+
+#include <linux/input.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/reboot.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "minui/minui.h"
+#include "recovery_ui.h"
+
+#ifdef BOARD_HAS_NO_SELECT_BUTTON
+static int gShowBackButton = 1;
+#else
+static int gShowBackButton = 0;
+#endif
+
+#define MAX_COLS 96
+#define MAX_ROWS 32
+
+#define MENU_MAX_COLS 64
+#define MENU_MAX_ROWS 250
+
+#ifndef BOARD_LDPI_RECOVERY
+  #define CHAR_WIDTH 10
+  #define CHAR_HEIGHT 18
+#else
+  #define CHAR_WIDTH 7
+  #define CHAR_HEIGHT 16
+#endif
+
+#define PROGRESSBAR_INDETERMINATE_STATES 6
+#define PROGRESSBAR_INDETERMINATE_FPS 15
+
+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;
+static gr_surface gProgressBarFill;
+static int ui_has_initialized = 0;
+
+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_CLOCKWORK],  "icon_clockwork" },
+    { &gProgressBarIndeterminate[0],    "indeterminate1" },
+    { &gProgressBarIndeterminate[1],    "indeterminate2" },
+    { &gProgressBarIndeterminate[2],    "indeterminate3" },
+    { &gProgressBarIndeterminate[3],    "indeterminate4" },
+    { &gProgressBarIndeterminate[4],    "indeterminate5" },
+    { &gProgressBarIndeterminate[5],    "indeterminate6" },
+    { &gProgressBarEmpty,               "progress_empty" },
+    { &gProgressBarFill,                "progress_fill" },
+    { NULL,                             NULL },
+};
+
+static gr_surface gCurrentIcon = NULL;
+
+static enum ProgressBarType {
+    PROGRESSBAR_TYPE_NONE,
+    PROGRESSBAR_TYPE_INDETERMINATE,
+    PROGRESSBAR_TYPE_NORMAL,
+} gProgressBarType = PROGRESSBAR_TYPE_NONE;
+
+// Progress bar scope of current operation
+static float gProgressScopeStart = 0, gProgressScopeSize = 0, gProgress = 0;
+static time_t gProgressScopeTime, gProgressScopeDuration;
+
+// Set to 1 when both graphics pages are the same (except for the progress bar)
+static int gPagesIdentical = 0;
+
+// Log text overlay, displayed when a magic key is pressed
+static char text[MAX_ROWS][MAX_COLS];
+static int text_cols = 0, text_rows = 0;
+static int text_col = 0, text_row = 0, text_top = 0;
+static int show_text = 0;
+
+static char menu[MENU_MAX_ROWS][MENU_MAX_COLS];
+static int show_menu = 0;
+static int menu_top = 0, menu_items = 0, menu_sel = 0;
+static int menu_show_start = 0;             // this is line which menu display is starting at 
+
+// Key event input queue
+static pthread_mutex_t key_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t key_queue_cond = PTHREAD_COND_INITIALIZER;
+static int key_queue[256], key_queue_len = 0;
+static volatile char key_pressed[KEY_MAX + 1];
+
+// Clear the screen and draw the currently selected background icon (if any).
+// Should only be called with gUpdateMutex locked.
+static void draw_background_locked(gr_surface icon)
+{
+    gPagesIdentical = 0;
+    gr_color(0, 0, 0, 255);
+    gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+
+    if (icon) {
+        int iconWidth = gr_get_width(icon);
+        int iconHeight = gr_get_height(icon);
+        int iconX = (gr_fb_width() - iconWidth) / 2;
+        int iconY = (gr_fb_height() - iconHeight) / 2;
+        gr_blit(icon, 0, 0, iconWidth, iconHeight, iconX, iconY);
+    }
+}
+
+// Draw the progress bar (if any) on the screen.  Does not flip pages.
+// Should only be called with gUpdateMutex locked.
+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(gProgressBarEmpty);
+    int height = gr_get_height(gProgressBarEmpty);
+
+    int dx = (gr_fb_width() - width)/2;
+    int dy = (3*gr_fb_height() + iconHeight - 2*height)/4;
+
+    // Erase behind the progress bar (in case this was a progress-only update)
+    gr_color(0, 0, 0, 255);
+    gr_fill(dx, dy, width, height);
+
+    if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL) {
+        float progress = gProgressScopeStart + gProgress * gProgressScopeSize;
+        int pos = (int) (progress * width);
+
+        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);
+        }
+    }
+
+    if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE) {
+        static int frame = 0;
+        gr_blit(gProgressBarIndeterminate[frame], 0, 0, width, height, dx, dy);
+        frame = (frame + 1) % PROGRESSBAR_INDETERMINATE_STATES;
+    }
+}
+
+static void draw_text_line(int row, const char* t) {
+  if (t[0] != '\0') {
+    gr_text(0, (row+1)*CHAR_HEIGHT-1, t);
+  }
+}
+
+#define MENU_TEXT_COLOR 255, 160, 49, 255
+#define NORMAL_TEXT_COLOR 200, 200, 200, 255
+#define HEADER_TEXT_COLOR NORMAL_TEXT_COLOR
+
+// Redraw everything on the screen.  Does not flip pages.
+// Should only be called with gUpdateMutex locked.
+static void draw_screen_locked(void)
+{
+    if (!ui_has_initialized) return;
+    draw_background_locked(gCurrentIcon);
+    draw_progress_locked();
+
+    if (show_text) {
+        gr_color(0, 0, 0, 160);
+        gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+
+        int i = 0;
+        int j = 0;
+        int row = 0;            // current row that we are drawing on
+        if (show_menu) {
+            gr_color(MENU_TEXT_COLOR);
+            gr_fill(0, (menu_top + menu_sel - menu_show_start) * CHAR_HEIGHT,
+                    gr_fb_width(), (menu_top + menu_sel - menu_show_start + 1)*CHAR_HEIGHT+1);
+
+            gr_color(HEADER_TEXT_COLOR);
+            for (i = 0; i < menu_top; ++i) {
+                draw_text_line(i, menu[i]);
+                row++;
+            }
+
+            if (menu_items - menu_show_start + menu_top >= MAX_ROWS)
+                j = MAX_ROWS - menu_top;
+            else
+                j = menu_items - menu_show_start;
+
+            gr_color(MENU_TEXT_COLOR);
+            for (i = menu_show_start + menu_top; i < (menu_show_start + menu_top + j); ++i) {
+                if (i == menu_top + menu_sel) {
+                    gr_color(255, 255, 255, 255);
+                    draw_text_line(i - menu_show_start , menu[i]);
+                    gr_color(MENU_TEXT_COLOR);
+                } else {
+                    gr_color(MENU_TEXT_COLOR);
+                    draw_text_line(i - menu_show_start, menu[i]);
+                }
+                row++;
+            }
+            gr_fill(0, row*CHAR_HEIGHT+CHAR_HEIGHT/2-1,
+                    gr_fb_width(), row*CHAR_HEIGHT+CHAR_HEIGHT/2+1);
+        }
+
+        gr_color(NORMAL_TEXT_COLOR);
+        for (; row < text_rows; ++row) {
+            draw_text_line(row, text[(row+text_top) % text_rows]);
+        }
+    }
+}
+
+// Redraw everything on the screen and flip the screen (make it visible).
+// Should only be called with gUpdateMutex locked.
+static void update_screen_locked(void)
+{
+    if (!ui_has_initialized) return;
+    draw_screen_locked();
+    gr_flip();
+}
+
+// Updates only the progress bar, if possible, otherwise redraws the screen.
+// Should only be called with gUpdateMutex locked.
+static void update_progress_locked(void)
+{
+    if (!ui_has_initialized) return;
+    if (show_text || !gPagesIdentical) {
+        draw_screen_locked();    // Must redraw the whole screen
+        gPagesIdentical = 1;
+    } else {
+        draw_progress_locked();  // Draw only the progress bar
+    }
+    gr_flip();
+}
+
+// Keeps the progress bar updated, even when the process is otherwise busy.
+static void *progress_thread(void *cookie)
+{
+    for (;;) {
+        usleep(1000000 / PROGRESSBAR_INDETERMINATE_FPS);
+        pthread_mutex_lock(&gUpdateMutex);
+
+        // update the progress bar animation, if active
+        // skip this if we have a text overlay (too expensive to update)
+        if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE && !show_text) {
+            update_progress_locked();
+        }
+
+        // move the progress bar forward on timed intervals, if configured
+        int duration = gProgressScopeDuration;
+        if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && duration > 0) {
+            int elapsed = time(NULL) - gProgressScopeTime;
+            float progress = 1.0 * elapsed / duration;
+            if (progress > 1.0) progress = 1.0;
+            if (progress > gProgress) {
+                gProgress = progress;
+                update_progress_locked();
+            }
+        }
+
+        pthread_mutex_unlock(&gUpdateMutex);
+    }
+    return NULL;
+}
+
+// Reads input events, handles special hot keys, and adds to the key queue.
+static void *input_thread(void *cookie)
+{
+    int rel_sum = 0;
+    int fake_key = 0;
+    for (;;) {
+        // wait for the next key event
+        struct input_event ev;
+        do {
+            ev_get(&ev, 0);
+
+            if (ev.type == EV_SYN) {
+                continue;
+            } else if (ev.type == EV_REL) {
+                if (ev.code == REL_Y) {
+                    // accumulate the up or down motion reported by
+                    // the trackball.  When it exceeds a threshold
+                    // (positive or negative), fake an up/down
+                    // key event.
+                    rel_sum += ev.value;
+                    if (rel_sum > 3) {
+                        fake_key = 1;
+                        ev.type = EV_KEY;
+                        ev.code = KEY_DOWN;
+                        ev.value = 1;
+                        rel_sum = 0;
+                    } else if (rel_sum < -3) {
+                        fake_key = 1;
+                        ev.type = EV_KEY;
+                        ev.code = KEY_UP;
+                        ev.value = 1;
+                        rel_sum = 0;
+                    }
+                }
+            } else {
+                rel_sum = 0;
+            }
+        } while (ev.type != EV_KEY || ev.code > KEY_MAX);
+
+        pthread_mutex_lock(&key_queue_mutex);
+        if (!fake_key) {
+            // our "fake" keys only report a key-down event (no
+            // key-up), so don't record them in the key_pressed
+            // table.
+            key_pressed[ev.code] = ev.value;
+        }
+        fake_key = 0;
+        const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);
+        if (ev.value > 0 && key_queue_len < queue_max) {
+            key_queue[key_queue_len++] = ev.code;
+            pthread_cond_signal(&key_queue_cond);
+        }
+        pthread_mutex_unlock(&key_queue_mutex);
+
+        if (ev.value > 0 && device_toggle_display(key_pressed, ev.code)) {
+            pthread_mutex_lock(&gUpdateMutex);
+            show_text = !show_text;
+            update_screen_locked();
+            pthread_mutex_unlock(&gUpdateMutex);
+        }
+
+        if (ev.value > 0 && device_reboot_now(key_pressed, ev.code)) {
+            reboot(RB_AUTOBOOT);
+        }
+    }
+    return NULL;
+}
+
+void ui_init(void)
+{
+    ui_has_initialized = 1;
+    gr_init();
+    ev_init();
+
+    text_col = text_row = 0;
+    text_rows = gr_fb_height() / CHAR_HEIGHT;
+    if (text_rows > MAX_ROWS) text_rows = MAX_ROWS;
+    text_top = 1;
+
+    text_cols = gr_fb_width() / CHAR_WIDTH;
+    if (text_cols > MAX_COLS - 1) text_cols = MAX_COLS - 1;
+
+    int i;
+    for (i = 0; BITMAPS[i].name != NULL; ++i) {
+        int result = res_create_surface(BITMAPS[i].name, BITMAPS[i].surface);
+        if (result < 0) {
+            if (result == -2) {
+                LOGI("Bitmap %s missing header\n", BITMAPS[i].name);
+            } else {
+                LOGE("Missing bitmap %s\n(Code %d)\n", BITMAPS[i].name, result);
+            }
+            *BITMAPS[i].surface = NULL;
+        }
+    }
+
+    pthread_t t;
+    pthread_create(&t, NULL, progress_thread, NULL);
+    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);
+    gCurrentIcon = gBackgroundIcon[icon];
+    update_screen_locked();
+    pthread_mutex_unlock(&gUpdateMutex);
+}
+
+void ui_show_indeterminate_progress()
+{
+    pthread_mutex_lock(&gUpdateMutex);
+    if (gProgressBarType != PROGRESSBAR_TYPE_INDETERMINATE) {
+        gProgressBarType = PROGRESSBAR_TYPE_INDETERMINATE;
+        update_progress_locked();
+    }
+    pthread_mutex_unlock(&gUpdateMutex);
+}
+
+void ui_show_progress(float portion, int seconds)
+{
+    pthread_mutex_lock(&gUpdateMutex);
+    gProgressBarType = PROGRESSBAR_TYPE_NORMAL;
+    gProgressScopeStart += gProgressScopeSize;
+    gProgressScopeSize = portion;
+    gProgressScopeTime = time(NULL);
+    gProgressScopeDuration = seconds;
+    gProgress = 0;
+    update_progress_locked();
+    pthread_mutex_unlock(&gUpdateMutex);
+}
+
+void ui_set_progress(float fraction)
+{
+    pthread_mutex_lock(&gUpdateMutex);
+    if (fraction < 0.0) fraction = 0.0;
+    if (fraction > 1.0) fraction = 1.0;
+    if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && fraction > gProgress) {
+        // Skip updates that aren't visibly different.
+        int width = gr_get_width(gProgressBarIndeterminate[0]);
+        float scale = width * gProgressScopeSize;
+        if ((int) (gProgress * scale) != (int) (fraction * scale)) {
+            gProgress = fraction;
+            update_progress_locked();
+        }
+    }
+    pthread_mutex_unlock(&gUpdateMutex);
+}
+
+void ui_reset_progress()
+{
+    pthread_mutex_lock(&gUpdateMutex);
+    gProgressBarType = PROGRESSBAR_TYPE_NONE;
+    gProgressScopeStart = gProgressScopeSize = 0;
+    gProgressScopeTime = gProgressScopeDuration = 0;
+    gProgress = 0;
+    update_screen_locked();
+    pthread_mutex_unlock(&gUpdateMutex);
+}
+
+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, stdout);
+
+    // This can get called before ui_init(), so be careful.
+    pthread_mutex_lock(&gUpdateMutex);
+    if (text_rows > 0 && text_cols > 0) {
+        char *ptr;
+        for (ptr = buf; *ptr != '\0'; ++ptr) {
+            if (*ptr == '\n' || text_col >= text_cols) {
+                text[text_row][text_col] = '\0';
+                text_col = 0;
+                text_row = (text_row + 1) % text_rows;
+                if (text_row == text_top) text_top = (text_top + 1) % text_rows;
+            }
+            if (*ptr != '\n') text[text_row][text_col++] = *ptr;
+        }
+        text[text_row][text_col] = '\0';
+        update_screen_locked();
+    }
+    pthread_mutex_unlock(&gUpdateMutex);
+}
+
+void ui_reset_text_col()
+{
+    pthread_mutex_lock(&gUpdateMutex);
+    text_col = 0;
+    pthread_mutex_unlock(&gUpdateMutex);
+}
+
+#define MENU_ITEM_HEADER " - "
+#define MENU_ITEM_HEADER_LENGTH strlen(MENU_ITEM_HEADER)
+
+int ui_start_menu(char** headers, char** items, int initial_selection) {
+    int i;
+    pthread_mutex_lock(&gUpdateMutex);
+    if (text_rows > 0 && text_cols > 0) {
+        for (i = 0; i < text_rows; ++i) {
+            if (headers[i] == NULL) break;
+            strncpy(menu[i], headers[i], text_cols-1);
+            menu[i][text_cols-1] = '\0';
+        }
+        menu_top = i;
+        for (; i < MENU_MAX_ROWS; ++i) {
+            if (items[i-menu_top] == NULL) break;
+            strcpy(menu[i], MENU_ITEM_HEADER);
+            strncpy(menu[i] + MENU_ITEM_HEADER_LENGTH, items[i-menu_top], text_cols-1 - MENU_ITEM_HEADER_LENGTH);
+            menu[i][text_cols-1] = '\0';
+        }
+
+        if (gShowBackButton) {
+            strcpy(menu[i], " - +++++Go Back+++++");
+            ++i;
+        }
+
+        menu_items = i - menu_top;
+        show_menu = 1;
+        menu_sel = menu_show_start = initial_selection;
+        update_screen_locked();
+    }
+    pthread_mutex_unlock(&gUpdateMutex);
+    if (gShowBackButton) {
+        return menu_items - 1;
+    }
+    return menu_items;
+}
+
+int ui_menu_select(int sel) {
+    int old_sel;
+    pthread_mutex_lock(&gUpdateMutex);
+    if (show_menu > 0) {
+        old_sel = menu_sel;
+        menu_sel = sel;
+
+        if (menu_sel < 0) menu_sel = menu_items + menu_sel;
+        if (menu_sel >= menu_items) menu_sel = menu_sel - menu_items;
+
+
+        if (menu_sel < menu_show_start && menu_show_start > 0) {
+            menu_show_start = menu_sel;
+        }
+
+        if (menu_sel - menu_show_start + menu_top >= text_rows) {
+            menu_show_start = menu_sel + menu_top - text_rows + 1;
+        }
+
+        sel = menu_sel;
+
+        if (menu_sel != old_sel) update_screen_locked();
+    }
+    pthread_mutex_unlock(&gUpdateMutex);
+    return sel;
+}
+
+void ui_end_menu() {
+    int i;
+    pthread_mutex_lock(&gUpdateMutex);
+    if (show_menu > 0 && text_rows > 0 && text_cols > 0) {
+        show_menu = 0;
+        update_screen_locked();
+    }
+    pthread_mutex_unlock(&gUpdateMutex);
+}
+
+int ui_text_visible()
+{
+    pthread_mutex_lock(&gUpdateMutex);
+    int visible = show_text;
+    pthread_mutex_unlock(&gUpdateMutex);
+    return visible;
+}
+
+void ui_show_text(int visible)
+{
+    pthread_mutex_lock(&gUpdateMutex);
+    show_text = visible;
+    update_screen_locked();
+    pthread_mutex_unlock(&gUpdateMutex);
+}
+
+int ui_wait_key()
+{
+    pthread_mutex_lock(&key_queue_mutex);
+    while (key_queue_len == 0) {
+        pthread_cond_wait(&key_queue_cond, &key_queue_mutex);
+    }
+
+    int key = key_queue[0];
+    memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
+    pthread_mutex_unlock(&key_queue_mutex);
+    return key;
+}
+
+int ui_key_pressed(int key)
+{
+    // This is a volatile static array, don't bother locking
+    return key_pressed[key];
+}
+
+void ui_clear_key_queue() {
+    pthread_mutex_lock(&key_queue_mutex);
+    key_queue_len = 0;
+    pthread_mutex_unlock(&key_queue_mutex);
+}
+
+void ui_set_show_text(int value) {
+    show_text = value;
+}
+
+void ui_set_showing_back_button(int showBackButton) {
+    gShowBackButton = showBackButton;
+}
+
+int ui_get_showing_back_button() {
+    return gShowBackButton;
+}
diff --git a/updater/Android.mk b/updater/Android.mk
new file mode 100644
index 0000000..5c43e1c
--- /dev/null
+++ b/updater/Android.mk
@@ -0,0 +1,81 @@
+# Copyright 2009 The Android Open Source Project
+
+LOCAL_PATH := $(call my-dir)
+
+updater_src_files := \
+	install.c \
+	../mounts.c \
+	updater.c
+
+#
+# Build a statically-linked binary to include in OTA packages
+#
+include $(CLEAR_VARS)
+
+# Build only in eng, so we don't end up with a copy of this in /system
+# on user builds.  (TODO: find a better way to build device binaries
+# needed only for OTA packages.)
+LOCAL_MODULE_TAGS := eng
+
+LOCAL_SRC_FILES := $(updater_src_files)
+
+ifeq ($(TARGET_USERIMAGES_USE_EXT4), true)
+LOCAL_CFLAGS += -DUSE_EXT4
+LOCAL_C_INCLUDES += system/extras/ext4_utils
+LOCAL_STATIC_LIBRARIES += libext4_utils libz
+endif
+
+LOCAL_STATIC_LIBRARIES += libflashutils libmtdutils libmmcutils libbmlutils
+
+LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UPDATER_LIBS) $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS)
+LOCAL_STATIC_LIBRARIES += libapplypatch libedify libmtdutils libminzip libz
+LOCAL_STATIC_LIBRARIES += libmincrypt libbz
+LOCAL_STATIC_LIBRARIES += libcutils libstdc++ libc
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
+
+# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function
+# named "Register_<libname>()".  Here we emit a little C function that
+# gets #included by updater.c.  It calls all those registration
+# functions.
+
+# Devices can also add libraries to TARGET_RECOVERY_UPDATER_EXTRA_LIBS.
+# These libs are also linked in with updater, but we don't try to call
+# any sort of registration function for these.  Use this variable for
+# any subsidiary static libraries required for your registered
+# extension libs.
+
+inc := $(call intermediates-dir-for,PACKAGING,updater_extensions)/register.inc
+
+# During the first pass of reading the makefiles, we dump the list of
+# extension libs to a temp file, then copy that to the ".list" file if
+# it is different than the existing .list (if any).  The register.inc
+# file then uses the .list as a prerequisite, so it is only rebuilt
+# (and updater.o recompiled) when the list of extension libs changes.
+
+junk := $(shell mkdir -p $(dir $(inc));\
+	        echo $(TARGET_RECOVERY_UPDATER_LIBS) > $(inc).temp;\
+	        diff -q $(inc).temp $(inc).list || cp -f $(inc).temp $(inc).list)
+
+$(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS)
+$(inc) : $(inc).list
+	$(hide) mkdir -p $(dir $@)
+	$(hide) echo "" > $@
+	$(hide) $(foreach lib,$(libs),echo "extern void Register_$(lib)(void);" >> $@)
+	$(hide) echo "void RegisterDeviceExtensions() {" >> $@
+	$(hide) $(foreach lib,$(libs),echo "  Register_$(lib)();" >> $@)
+	$(hide) echo "}" >> $@
+
+$(call intermediates-dir-for,EXECUTABLES,updater)/updater.o : $(inc)
+LOCAL_C_INCLUDES += $(dir $(inc))
+
+LOCAL_MODULE := updater
+
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+
+include $(BUILD_EXECUTABLE)
+
+
+file := $(PRODUCT_OUT)/utilities/update-binary
+ALL_PREBUILT += $(file)
+$(file) : $(TARGET_OUT)/bin/updater | $(ACP)
+	$(transform-prebuilt-to-target)
diff --git a/updater/install.c b/updater/install.c
new file mode 100644
index 0000000..d23ec64
--- /dev/null
+++ b/updater/install.c
@@ -0,0 +1,1039 @@
+/*
+ * 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 <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "cutils/misc.h"
+#include "cutils/properties.h"
+#include "edify/expr.h"
+#include "mincrypt/sha.h"
+#include "minzip/DirUtil.h"
+#include "mounts.h"
+#include "mtdutils/mtdutils.h"
+#include "updater.h"
+#include "applypatch/applypatch.h"
+
+#ifdef USE_EXT4
+#include "make_ext4fs.h"
+#endif
+
+// mount(fs_type, partition_type, location, mount_point)
+//
+//    fs_type="yaffs2" partition_type="MTD"     location=partition
+//    fs_type="ext4"   partition_type="EMMC"    location=device
+Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* result = NULL;
+    if (argc != 4) {
+        return ErrorAbort(state, "%s() expects 4 args, got %d", name, argc);
+    }
+    char* fs_type;
+    char* partition_type;
+    char* location;
+    char* mount_point;
+    if (ReadArgs(state, argv, 4, &fs_type, &partition_type,
+                 &location, &mount_point) < 0) {
+        return NULL;
+    }
+
+    if (strlen(fs_type) == 0) {
+        ErrorAbort(state, "fs_type argument to %s() can't be empty", name);
+        goto done;
+    }
+    if (strlen(partition_type) == 0) {
+        ErrorAbort(state, "partition_type argument to %s() can't be empty",
+                   name);
+        goto done;
+    }
+    if (strlen(location) == 0) {
+        ErrorAbort(state, "location argument to %s() can't be empty", name);
+        goto done;
+    }
+    if (strlen(mount_point) == 0) {
+        ErrorAbort(state, "mount_point argument to %s() can't be empty", name);
+        goto done;
+    }
+
+    mkdir(mount_point, 0755);
+
+    if (strcmp(partition_type, "MTD") == 0) {
+        mtd_scan_partitions();
+        const MtdPartition* mtd;
+        mtd = mtd_find_partition_by_name(location);
+        if (mtd == NULL) {
+            fprintf(stderr, "%s: no mtd partition named \"%s\"",
+                    name, location);
+            result = strdup("");
+            goto done;
+        }
+        if (mtd_mount_partition(mtd, mount_point, fs_type, 0 /* rw */) != 0) {
+            fprintf(stderr, "mtd mount of %s failed: %s\n",
+                    location, strerror(errno));
+            result = strdup("");
+            goto done;
+        }
+        result = mount_point;
+    } else {
+        if (mount(location, mount_point, fs_type,
+                  MS_NOATIME | MS_NODEV | MS_NODIRATIME, "") < 0) {
+            fprintf(stderr, "%s: failed to mount %s at %s: %s\n",
+                    name, location, mount_point, strerror(errno));
+            result = strdup("");
+        } else {
+            result = mount_point;
+        }
+    }
+
+done:
+    free(fs_type);
+    free(partition_type);
+    free(location);
+    if (result != mount_point) free(mount_point);
+    return StringValue(result);
+}
+
+
+// is_mounted(mount_point)
+Value* IsMountedFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* result = NULL;
+    if (argc != 1) {
+        return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
+    }
+    char* mount_point;
+    if (ReadArgs(state, argv, 1, &mount_point) < 0) {
+        return NULL;
+    }
+    if (strlen(mount_point) == 0) {
+        ErrorAbort(state, "mount_point argument to unmount() can't be empty");
+        goto done;
+    }
+
+    scan_mounted_volumes();
+    const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point);
+    if (vol == NULL) {
+        result = strdup("");
+    } else {
+        result = mount_point;
+    }
+
+done:
+    if (result != mount_point) free(mount_point);
+    return StringValue(result);
+}
+
+
+Value* UnmountFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* result = NULL;
+    if (argc != 1) {
+        return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
+    }
+    char* mount_point;
+    if (ReadArgs(state, argv, 1, &mount_point) < 0) {
+        return NULL;
+    }
+    if (strlen(mount_point) == 0) {
+        ErrorAbort(state, "mount_point argument to unmount() can't be empty");
+        goto done;
+    }
+
+    scan_mounted_volumes();
+    const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point);
+    if (vol == NULL) {
+        fprintf(stderr, "unmount of %s failed; no such volume\n", mount_point);
+        result = strdup("");
+    } else {
+        unmount_mounted_volume(vol);
+        result = mount_point;
+    }
+
+done:
+    if (result != mount_point) free(mount_point);
+    return StringValue(result);
+}
+
+
+// format(fs_type, partition_type, location)
+//
+//    fs_type="yaffs2" partition_type="MTD"     location=partition
+//    fs_type="ext4"   partition_type="EMMC"    location=device
+Value* FormatFn(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);
+    }
+    char* fs_type;
+    char* partition_type;
+    char* location;
+    if (ReadArgs(state, argv, 3, &fs_type, &partition_type, &location) < 0) {
+        return NULL;
+    }
+
+    if (strlen(fs_type) == 0) {
+        ErrorAbort(state, "fs_type argument to %s() can't be empty", name);
+        goto done;
+    }
+    if (strlen(partition_type) == 0) {
+        ErrorAbort(state, "partition_type argument to %s() can't be empty",
+                   name);
+        goto done;
+    }
+    if (strlen(location) == 0) {
+        ErrorAbort(state, "location argument to %s() can't be empty", name);
+        goto done;
+    }
+
+    if (strcmp(partition_type, "MTD") == 0) {
+        mtd_scan_partitions();
+        const MtdPartition* mtd = mtd_find_partition_by_name(location);
+        if (mtd == NULL) {
+            fprintf(stderr, "%s: no mtd partition named \"%s\"",
+                    name, location);
+            result = strdup("");
+            goto done;
+        }
+        MtdWriteContext* ctx = mtd_write_partition(mtd);
+        if (ctx == NULL) {
+            fprintf(stderr, "%s: can't write \"%s\"", name, location);
+            result = strdup("");
+            goto done;
+        }
+        if (mtd_erase_blocks(ctx, -1) == -1) {
+            mtd_write_close(ctx);
+            fprintf(stderr, "%s: failed to erase \"%s\"", name, location);
+            result = strdup("");
+            goto done;
+        }
+        if (mtd_write_close(ctx) != 0) {
+            fprintf(stderr, "%s: failed to close \"%s\"", name, location);
+            result = strdup("");
+            goto done;
+        }
+        result = location;
+#ifdef USE_EXT4
+    } else if (strcmp(fs_type, "ext4") == 0) {
+        reset_ext4fs_info();
+        int status = make_ext4fs(location, NULL, NULL, 0, 0, 0);
+        if (status != 0) {
+            fprintf(stderr, "%s: make_ext4fs failed (%d) on %s",
+                    name, status, location);
+            result = strdup("");
+            goto done;
+        }
+        result = location;
+#endif
+    } else if (strcmp(fs_type, "ext2") == 0) {
+        int status = format_ext2_device(location);
+        if (status != 0) {
+            fprintf(stderr, "%s: format_ext2_device failed (%d) on %s",
+                    name, status, location);
+            result = strdup("");
+            goto done;
+        }
+        result = location;
+    } else if (strcmp(fs_type, "ext3") == 0) {
+        int status = format_ext3_device(location);
+        if (status != 0) {
+            fprintf(stderr, "%s: format_ext3_device failed (%d) on %s",
+                    name, status, location);
+            result = strdup("");
+            goto done;
+        }
+        result = location;
+    } else {
+        fprintf(stderr, "%s: unsupported fs_type \"%s\" partition_type \"%s\"",
+                name, fs_type, partition_type);
+    }
+
+done:
+    free(fs_type);
+    free(partition_type);
+    if (result != location) free(location);
+    return StringValue(result);
+}
+
+
+Value* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char** paths = malloc(argc * sizeof(char*));
+    int i;
+    for (i = 0; i < argc; ++i) {
+        paths[i] = Evaluate(state, argv[i]);
+        if (paths[i] == NULL) {
+            int j;
+            for (j = 0; j < i; ++i) {
+                free(paths[j]);
+            }
+            free(paths);
+            return NULL;
+        }
+    }
+
+    bool recursive = (strcmp(name, "delete_recursive") == 0);
+
+    int success = 0;
+    for (i = 0; i < argc; ++i) {
+        if ((recursive ? dirUnlinkHierarchy(paths[i]) : unlink(paths[i])) == 0)
+            ++success;
+        free(paths[i]);
+    }
+    free(paths);
+
+    char buffer[10];
+    sprintf(buffer, "%d", success);
+    return StringValue(strdup(buffer));
+}
+
+
+Value* ShowProgressFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc != 2) {
+        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
+    }
+    char* frac_str;
+    char* sec_str;
+    if (ReadArgs(state, argv, 2, &frac_str, &sec_str) < 0) {
+        return NULL;
+    }
+
+    double frac = strtod(frac_str, NULL);
+    int sec = strtol(sec_str, NULL, 10);
+
+    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
+    fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec);
+
+    free(sec_str);
+    return StringValue(frac_str);
+}
+
+Value* SetProgressFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc != 1) {
+        return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
+    }
+    char* frac_str;
+    if (ReadArgs(state, argv, 1, &frac_str) < 0) {
+        return NULL;
+    }
+
+    double frac = strtod(frac_str, NULL);
+
+    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
+    fprintf(ui->cmd_pipe, "set_progress %f\n", frac);
+
+    return StringValue(frac_str);
+}
+
+// package_extract_dir(package_path, destination_path)
+Value* PackageExtractDirFn(const char* name, State* state,
+                          int argc, Expr* argv[]) {
+    if (argc != 2) {
+        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
+    }
+    char* zip_path;
+    char* dest_path;
+    if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL;
+
+    ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
+
+    // To create a consistent system image, never use the clock for timestamps.
+    struct utimbuf timestamp = { 1217592000, 1217592000 };  // 8/1/2008 default
+
+    bool success = mzExtractRecursive(za, zip_path, dest_path,
+                                      MZ_EXTRACT_FILES_ONLY, &timestamp,
+                                      NULL, NULL);
+    free(zip_path);
+    free(dest_path);
+    return StringValue(strdup(success ? "t" : ""));
+}
+
+
+// package_extract_file(package_path, destination_path)
+//   or
+// package_extract_file(package_path)
+//   to return the entire contents of the file as the result of this
+//   function (the char* returned is actually a FileContents*).
+Value* PackageExtractFileFn(const char* name, State* state,
+                           int argc, Expr* argv[]) {
+    if (argc != 1 && argc != 2) {
+        return ErrorAbort(state, "%s() expects 1 or 2 args, got %d",
+                          name, argc);
+    }
+    bool success = false;
+    if (argc == 2) {
+        // The two-argument version extracts to a file.
+
+        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;
+    }
+}
+
+
+// symlink target src1 src2 ...
+//    unlinks any previously existing src1, src2, etc before creating symlinks.
+Value* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc == 0) {
+        return ErrorAbort(state, "%s() expects 1+ args, got %d", name, argc);
+    }
+    char* target;
+    target = Evaluate(state, argv[0]);
+    if (target == NULL) return NULL;
+
+    char** srcs = ReadVarArgs(state, argc-1, argv+1);
+    if (srcs == NULL) {
+        free(target);
+        return NULL;
+    }
+
+    int i;
+    for (i = 0; i < argc-1; ++i) {
+        if (unlink(srcs[i]) < 0) {
+            if (errno != ENOENT) {
+                fprintf(stderr, "%s: failed to remove %s: %s\n",
+                        name, srcs[i], strerror(errno));
+            }
+        }
+        if (symlink(target, srcs[i]) < 0) {
+            fprintf(stderr, "%s: failed to symlink %s to %s: %s\n",
+                    name, srcs[i], target, strerror(errno));
+        }
+        free(srcs[i]);
+    }
+    free(srcs);
+    return StringValue(strdup(""));
+}
+
+
+Value* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* result = NULL;
+    bool recursive = (strcmp(name, "set_perm_recursive") == 0);
+
+    int min_args = 4 + (recursive ? 1 : 0);
+    if (argc < min_args) {
+        return ErrorAbort(state, "%s() expects %d+ args, got %d", name, argc);
+    }
+
+    char** args = ReadVarArgs(state, argc, argv);
+    if (args == NULL) return NULL;
+
+    char* end;
+    int i;
+
+    int uid = strtoul(args[0], &end, 0);
+    if (*end != '\0' || args[0][0] == 0) {
+        ErrorAbort(state, "%s: \"%s\" not a valid uid", name, args[0]);
+        goto done;
+    }
+
+    int gid = strtoul(args[1], &end, 0);
+    if (*end != '\0' || args[1][0] == 0) {
+        ErrorAbort(state, "%s: \"%s\" not a valid gid", name, args[1]);
+        goto done;
+    }
+
+    if (recursive) {
+        int dir_mode = strtoul(args[2], &end, 0);
+        if (*end != '\0' || args[2][0] == 0) {
+            ErrorAbort(state, "%s: \"%s\" not a valid dirmode", name, args[2]);
+            goto done;
+        }
+
+        int file_mode = strtoul(args[3], &end, 0);
+        if (*end != '\0' || args[3][0] == 0) {
+            ErrorAbort(state, "%s: \"%s\" not a valid filemode",
+                       name, args[3]);
+            goto done;
+        }
+
+        for (i = 4; i < argc; ++i) {
+            dirSetHierarchyPermissions(args[i], uid, gid, dir_mode, file_mode);
+        }
+    } else {
+        int mode = strtoul(args[2], &end, 0);
+        if (*end != '\0' || args[2][0] == 0) {
+            ErrorAbort(state, "%s: \"%s\" not a valid mode", name, args[2]);
+            goto done;
+        }
+
+        for (i = 3; i < argc; ++i) {
+            if (chown(args[i], uid, gid) < 0) {
+                fprintf(stderr, "%s: chown of %s to %d %d failed: %s\n",
+                        name, args[i], uid, gid, strerror(errno));
+            }
+            if (chmod(args[i], mode) < 0) {
+                fprintf(stderr, "%s: chmod of %s to %o failed: %s\n",
+                        name, args[i], mode, strerror(errno));
+            }
+        }
+    }
+    result = strdup("");
+
+done:
+    for (i = 0; i < argc; ++i) {
+        free(args[i]);
+    }
+    free(args);
+
+    return StringValue(result);
+}
+
+
+Value* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc != 1) {
+        return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
+    }
+    char* key;
+    key = Evaluate(state, argv[0]);
+    if (key == NULL) return NULL;
+
+    char value[PROPERTY_VALUE_MAX];
+    property_get(key, value, "");
+    free(key);
+
+    return StringValue(strdup(value));
+}
+
+
+// file_getprop(file, key)
+//
+//   interprets 'file' as a getprop-style file (key=value pairs, one
+//   per line, # comment lines and blank lines okay), and returns the value
+//   for 'key' (or "" if it isn't defined).
+Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* result = NULL;
+    char* buffer = NULL;
+    char* filename;
+    char* key;
+    if (ReadArgs(state, argv, 2, &filename, &key) < 0) {
+        return NULL;
+    }
+
+    struct stat st;
+    if (stat(filename, &st) < 0) {
+        ErrorAbort(state, "%s: failed to stat \"%s\": %s",
+                   name, filename, strerror(errno));
+        goto done;
+    }
+
+#define MAX_FILE_GETPROP_SIZE    65536
+
+    if (st.st_size > MAX_FILE_GETPROP_SIZE) {
+        ErrorAbort(state, "%s too large for %s (max %d)",
+                   filename, name, MAX_FILE_GETPROP_SIZE);
+        goto done;
+    }
+
+    buffer = malloc(st.st_size+1);
+    if (buffer == NULL) {
+        ErrorAbort(state, "%s: failed to alloc %d bytes", name, st.st_size+1);
+        goto done;
+    }
+
+    FILE* f = fopen(filename, "rb");
+    if (f == NULL) {
+        ErrorAbort(state, "%s: failed to open %s: %s",
+                   name, filename, strerror(errno));
+        goto done;
+    }
+
+    if (fread(buffer, 1, st.st_size, f) != st.st_size) {
+        ErrorAbort(state, "%s: failed to read %d bytes from %s",
+                   name, st.st_size+1, filename);
+        fclose(f);
+        goto done;
+    }
+    buffer[st.st_size] = '\0';
+
+    fclose(f);
+
+    char* line = strtok(buffer, "\n");
+    do {
+        // skip whitespace at start of line
+        while (*line && isspace(*line)) ++line;
+
+        // comment or blank line: skip to next line
+        if (*line == '\0' || *line == '#') continue;
+
+        char* equal = strchr(line, '=');
+        if (equal == NULL) {
+            ErrorAbort(state, "%s: malformed line \"%s\": %s not a prop file?",
+                       name, line, filename);
+            goto done;
+        }
+
+        // trim whitespace between key and '='
+        char* key_end = equal-1;
+        while (key_end > line && isspace(*key_end)) --key_end;
+        key_end[1] = '\0';
+
+        // not the key we're looking for
+        if (strcmp(key, line) != 0) continue;
+
+        // skip whitespace after the '=' to the start of the value
+        char* val_start = equal+1;
+        while(*val_start && isspace(*val_start)) ++val_start;
+
+        // trim trailing whitespace
+        char* val_end = val_start + strlen(val_start)-1;
+        while (val_end > val_start && isspace(*val_end)) --val_end;
+        val_end[1] = '\0';
+
+        result = strdup(val_start);
+        break;
+
+    } while ((line = strtok(NULL, "\n")));
+
+    if (result == NULL) result = strdup("");
+
+  done:
+    free(filename);
+    free(key);
+    free(buffer);
+    return StringValue(result);
+}
+
+
+static bool write_raw_image_cb(const unsigned char* data,
+                               int data_len, void* ctx) {
+    int r = mtd_write_data((MtdWriteContext*)ctx, (const char *)data, data_len);
+    if (r == data_len) return true;
+    fprintf(stderr, "%s\n", strerror(errno));
+    return false;
+}
+
+// write_raw_image(file, partition)
+Value* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* result = NULL;
+
+    char* partition;
+    char* filename;
+    if (ReadArgs(state, argv, 2, &filename, &partition) < 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;
+    }
+
+    if (0 == restore_raw_partition(partition, filename))
+        result = strdup(partition);
+    else
+        result = strdup("");
+
+done:
+    if (result != partition) free(partition);
+    free(filename);
+    return StringValue(result);
+}
+
+// apply_patch_space(bytes)
+Value* ApplyPatchSpaceFn(const char* name, State* state,
+                         int argc, Expr* argv[]) {
+    char* bytes_str;
+    if (ReadArgs(state, argv, 1, &bytes_str) < 0) {
+        return NULL;
+    }
+
+    char* endptr;
+    size_t bytes = strtol(bytes_str, &endptr, 10);
+    if (bytes == 0 && endptr == bytes_str) {
+        ErrorAbort(state, "%s(): can't parse \"%s\" as byte count\n\n",
+                   name, bytes_str);
+        free(bytes_str);
+        return NULL;
+    }
+
+    return StringValue(strdup(CacheSizeCheck(bytes) ? "" : "t"));
+}
+
+
+// apply_patch(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* source_filename;
+    char* target_filename;
+    char* target_sha1;
+    char* target_size_str;
+    if (ReadArgs(state, argv, 4, &source_filename, &target_filename,
+                 &target_sha1, &target_size_str) < 0) {
+        return NULL;
+    }
+
+    char* endptr;
+    size_t target_size = strtol(target_size_str, &endptr, 10);
+    if (target_size == 0 && endptr == target_size_str) {
+        ErrorAbort(state, "%s(): can't parse \"%s\" as byte count",
+                   name, target_size_str);
+        free(source_filename);
+        free(target_filename);
+        free(target_sha1);
+        free(target_size_str);
+        return NULL;
+    }
+
+    int patchcount = (argc-4) / 2;
+    Value** patches = ReadValueVarArgs(state, argc-4, argv+4);
+
+    int i;
+    for (i = 0; i < patchcount; ++i) {
+        if (patches[i*2]->type != VAL_STRING) {
+            ErrorAbort(state, "%s(): sha-1 #%d is not string", name, i);
+            break;
+        }
+        if (patches[i*2+1]->type != VAL_BLOB) {
+            ErrorAbort(state, "%s(): patch #%d is not blob", name, i);
+            break;
+        }
+    }
+    if (i != patchcount) {
+        for (i = 0; i < patchcount*2; ++i) {
+            FreeValue(patches[i]);
+        }
+        free(patches);
+        return NULL;
+    }
+
+    char** patch_sha_str = malloc(patchcount * sizeof(char*));
+    for (i = 0; i < patchcount; ++i) {
+        patch_sha_str[i] = patches[i*2]->data;
+        patches[i*2]->data = NULL;
+        FreeValue(patches[i*2]);
+        patches[i] = patches[i*2+1];
+    }
+
+    int result = applypatch(source_filename, target_filename,
+                            target_sha1, target_size,
+                            patchcount, patch_sha_str, patches);
+
+    for (i = 0; i < patchcount; ++i) {
+        FreeValue(patches[i]);
+    }
+    free(patch_sha_str);
+    free(patches);
+
+    return StringValue(strdup(result == 0 ? "t" : ""));
+}
+
+// apply_patch_check(file, [sha1_1, ...])
+Value* ApplyPatchCheckFn(const char* name, State* state,
+                         int argc, Expr* argv[]) {
+    if (argc < 1) {
+        return ErrorAbort(state, "%s(): expected at least 1 arg, got %d",
+                          name, argc);
+    }
+
+    char* filename;
+    if (ReadArgs(state, argv, 1, &filename) < 0) {
+        return NULL;
+    }
+
+    int patchcount = argc-1;
+    char** sha1s = ReadVarArgs(state, argc-1, argv+1);
+
+    int result = applypatch_check(filename, patchcount, sha1s);
+
+    int i;
+    for (i = 0; i < patchcount; ++i) {
+        free(sha1s[i]);
+    }
+    free(sha1s);
+
+    return StringValue(strdup(result == 0 ? "t" : ""));
+}
+
+Value* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char** args = ReadVarArgs(state, argc, argv);
+    if (args == NULL) {
+        return NULL;
+    }
+
+    int size = 0;
+    int i;
+    for (i = 0; i < argc; ++i) {
+        size += strlen(args[i]);
+    }
+    char* buffer = malloc(size+1);
+    size = 0;
+    for (i = 0; i < argc; ++i) {
+        strcpy(buffer+size, args[i]);
+        size += strlen(args[i]);
+        free(args[i]);
+    }
+    free(args);
+    buffer[size] = '\0';
+
+    char* line = strtok(buffer, "\n");
+    while (line) {
+        fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe,
+                "ui_print %s\n", line);
+        line = strtok(NULL, "\n");
+    }
+    fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe, "ui_print\n");
+
+    return StringValue(buffer);
+}
+
+Value* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc < 1) {
+        return ErrorAbort(state, "%s() expects at least 1 arg", name);
+    }
+    char** args = ReadVarArgs(state, argc, argv);
+    if (args == NULL) {
+        return NULL;
+    }
+
+    char** args2 = malloc(sizeof(char*) * (argc+1));
+    memcpy(args2, args, sizeof(char*) * argc);
+    args2[argc] = NULL;
+
+    fprintf(stderr, "about to run program [%s] with %d args\n", args2[0], argc);
+
+    pid_t child = fork();
+    if (child == 0) {
+        execv(args2[0], args2);
+        fprintf(stderr, "run_program: execv failed: %s\n", strerror(errno));
+        _exit(1);
+    }
+    int status;
+    waitpid(child, &status, 0);
+    if (WIFEXITED(status)) {
+        if (WEXITSTATUS(status) != 0) {
+            fprintf(stderr, "run_program: child exited with status %d\n",
+                    WEXITSTATUS(status));
+        }
+    } else if (WIFSIGNALED(status)) {
+        fprintf(stderr, "run_program: child terminated by signal %d\n",
+                WTERMSIG(status));
+    }
+
+    int i;
+    for (i = 0; i < argc; ++i) {
+        free(args[i]);
+    }
+    free(args);
+    free(args2);
+
+    char buffer[20];
+    sprintf(buffer, "%d", status);
+
+    return StringValue(strdup(buffer));
+}
+
+// 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);
+    RegisterFunction("is_mounted", IsMountedFn);
+    RegisterFunction("unmount", UnmountFn);
+    RegisterFunction("format", FormatFn);
+    RegisterFunction("show_progress", ShowProgressFn);
+    RegisterFunction("set_progress", SetProgressFn);
+    RegisterFunction("delete", DeleteFn);
+    RegisterFunction("delete_recursive", DeleteFn);
+    RegisterFunction("package_extract_dir", PackageExtractDirFn);
+    RegisterFunction("package_extract_file", PackageExtractFileFn);
+    RegisterFunction("symlink", SymlinkFn);
+    RegisterFunction("set_perm", SetPermFn);
+    RegisterFunction("set_perm_recursive", SetPermFn);
+
+    RegisterFunction("getprop", GetPropFn);
+    RegisterFunction("file_getprop", FileGetPropFn);
+    RegisterFunction("write_raw_image", WriteRawImageFn);
+
+    RegisterFunction("apply_patch", ApplyPatchFn);
+    RegisterFunction("apply_patch_check", ApplyPatchCheckFn);
+    RegisterFunction("apply_patch_space", ApplyPatchSpaceFn);
+
+    RegisterFunction("read_file", ReadFileFn);
+    RegisterFunction("sha1_check", Sha1CheckFn);
+
+    RegisterFunction("ui_print", UIPrintFn);
+
+    RegisterFunction("run_program", RunProgramFn);
+}
diff --git a/updater/install.h b/updater/install.h
new file mode 100644
index 0000000..94f344f
--- /dev/null
+++ b/updater/install.h
@@ -0,0 +1,22 @@
+/*
+ * 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 _UPDATER_INSTALL_H_
+#define _UPDATER_INSTALL_H_
+
+void RegisterInstallFunctions();
+
+#endif
diff --git a/updater/updater.c b/updater/updater.c
new file mode 100644
index 0000000..aa626d2
--- /dev/null
+++ b/updater/updater.c
@@ -0,0 +1,145 @@
+/*
+ * 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 <unistd.h>
+#include <stdlib.h>
+
+#include "edify/expr.h"
+#include "updater.h"
+#include "install.h"
+#include "minzip/Zip.h"
+
+// Generated by the makefile, this function defines the
+// RegisterDeviceExtensions() function, which calls all the
+// registration functions for device-specific extensions.
+#include "register.inc"
+
+// Where in the package we expect to find the edify script to execute.
+// (Note it's "updateR-script", not the older "update-script".)
+#define SCRIPT_NAME "META-INF/com/google/android/updater-script"
+
+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[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;
+    }
+
+    // Set up the pipe for sending commands back to the parent process.
+
+    int fd = atoi(argv[2]);
+    FILE* cmd_pipe = fdopen(fd, "wb");
+    setlinebuf(cmd_pipe);
+
+    // Extract the script from the package.
+
+    char* package_data = argv[3];
+    ZipArchive za;
+    int err;
+    err = mzOpenZipArchive(package_data, &za);
+    if (err != 0) {
+        fprintf(stderr, "failed to open package %s: %s\n",
+                package_data, strerror(err));
+        return 3;
+    }
+
+    const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME);
+    if (script_entry == NULL) {
+        fprintf(stderr, "failed to find %s in %s\n", SCRIPT_NAME, package_data);
+        return 4;
+    }
+
+    char* script = malloc(script_entry->uncompLen+1);
+    if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) {
+        fprintf(stderr, "failed to read script from package\n");
+        return 5;
+    }
+    script[script_entry->uncompLen] = '\0';
+
+    // Configure edify's functions.
+
+    RegisterBuiltins();
+    RegisterInstallFunctions();
+    RegisterDeviceExtensions();
+    FinishRegistration();
+
+    // Parse the script.
+
+    Expr* root;
+    int error_count = 0;
+    yy_scan_string(script);
+    int error = yyparse(&root, &error_count);
+    if (error != 0 || error_count > 0) {
+        fprintf(stderr, "%d parse errors\n", error_count);
+        return 6;
+    }
+
+    // Evaluate the parsed script.
+
+    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;
+    state.script = script;
+    state.errmsg = NULL;
+
+    char* result = Evaluate(&state, root);
+    if (result == NULL) {
+        if (state.errmsg == NULL) {
+            fprintf(stderr, "script aborted (no error message)\n");
+            fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
+        } else {
+            fprintf(stderr, "script aborted: %s\n", state.errmsg);
+            char* line = strtok(state.errmsg, "\n");
+            while (line) {
+                fprintf(cmd_pipe, "ui_print %s\n", line);
+                line = strtok(NULL, "\n");
+            }
+            fprintf(cmd_pipe, "ui_print\n");
+        }
+        free(state.errmsg);
+        return 7;
+    } else {
+        fprintf(stderr, "script result was [%s]\n", result);
+        free(result);
+    }
+
+    if (updater_info.package_zip) {
+        mzCloseZipArchive(updater_info.package_zip);
+    }
+    free(script);
+
+    return 0;
+}
diff --git a/updater/updater.h b/updater/updater.h
new file mode 100644
index 0000000..bd60dc1
--- /dev/null
+++ b/updater/updater.h
@@ -0,0 +1,29 @@
+/*
+ * 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 _UPDATER_UPDATER_H_
+#define _UPDATER_UPDATER_H_
+
+#include <stdio.h>
+#include "minzip/Zip.h"
+
+typedef struct {
+    FILE* cmd_pipe;
+    ZipArchive* package_zip;
+    int version;
+} UpdaterInfo;
+
+#endif
diff --git a/utilities/Android.mk b/utilities/Android.mk
new file mode 100644
index 0000000..f6b6e64
--- /dev/null
+++ b/utilities/Android.mk
@@ -0,0 +1,45 @@
+LOCAL_PATH := $(call my-dir)
+
+ifndef BOARD_HAS_SMALL_RECOVERY
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := e2fsck
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := fix_permissions
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := parted
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := sdparted
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := tune2fs
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+endif
diff --git a/utilities/e2fsck b/utilities/e2fsck
new file mode 100755
index 0000000..2844a1d
--- /dev/null
+++ b/utilities/e2fsck
Binary files differ
diff --git a/utilities/fix_permissions b/utilities/fix_permissions
new file mode 100644
index 0000000..a6db514
--- /dev/null
+++ b/utilities/fix_permissions
@@ -0,0 +1,484 @@
+#! /system/bin/sh
+#
+# Warning: if you want to run this script in cm-recovery change the above to #!/sbin/sh
+#
+# fix_permissions - fixes permissions on Android data directories after upgrade
+# shade@chemlab.org
+#
+# original concept: http://blog.elsdoerfer.name/2009/05/25/android-fix-package-uid-mismatches/
+# implementation by: Cyanogen
+# improved by: ankn, smeat, thenefield, farmatito, rikupw, Kastro
+#
+# v1.1-v1.31r3 - many improvements and concepts from XDA developers.
+# v1.34 through v2.00 -  A lot of frustration [by Kastro]
+# v2.01	- Completely rewrote the script for SPEED, thanks for the input farmatito
+#         /data/data depth recursion is tweaked;
+#         fixed single mode;
+#         functions created for modularity;
+#         logging can be disabled via CLI for more speed;
+#         runtime computation added to end (Runtime: mins secs);
+#         progress (current # of total) added to screen;
+#         fixed CLI argument parsing, now you can have more than one option!;
+#         debug cli option;
+#         verbosity can be disabled via CLI option for less noise;;
+#         [by Kastro, (XDA: k4str0), twitter;mattcarver]
+# v2.02 - ignore com.htc.resources.apk if it exists and minor code cleanups,
+#         fix help text, implement simulated run (-s) [farmatito]
+# v2.03 - fixed chown group ownership output [Kastro]
+# v2.04 - replaced /system/sd with $SD_EXT_DIRECTORY [Firerat]
+VERSION="2.04"
+
+# Defaults
+DEBUG=0 # Debug off by default
+LOGGING=1 # Logging on by default
+VERBOSE=1 # Verbose on by default
+
+# Messages
+UID_MSG="Changing user ownership for:"
+GID_MSG="Changing group ownership for:"
+PERM_MSG="Changing permissions for:"
+
+# Programs needed
+ECHO="busybox echo"
+GREP="busybox grep"
+EGREP="busybox egrep"
+CAT="busybox cat"
+CHOWN="busybox chown"
+CHMOD="busybox chmod"
+MOUNT="busybox mount"
+UMOUNT="busybox umount"
+CUT="busybox cut"
+FIND="busybox find"
+LS="busybox ls"
+TR="busybox tr"
+TEE="busybox tee"
+TEST="busybox test"
+SED="busybox sed"
+RM="busybox rm"
+WC="busybox wc"
+EXPR="busybox expr"
+DATE="busybox date"
+
+# Initialise vars
+CODEPATH=""
+UID=""
+GID=""
+PACKAGE=""
+REMOVE=0
+NOSYSTEM=0
+ONLY_ONE=""
+SIMULATE=0
+SYSREMOUNT=0
+SYSMOUNT=0
+DATAMOUNT=0
+SYSSDMOUNT=0
+FP_STARTTIME=$( $DATE +"%m-%d-%Y %H:%M:%S" )
+FP_STARTEPOCH=$( $DATE +%s )
+if $TEST "$SD_EXT_DIRECTORY" = ""; then
+    #check for mount point, /system/sd included in tests for backward compatibility
+    for MP in /sd-ext /system/sd;do
+        if $TEST -d $MP; then
+            SD_EXT_DIRECTORY=$MP
+            break
+        fi
+    done
+fi
+fp_usage()
+{
+   $ECHO "Usage $0 [OPTIONS] [APK_PATH]"
+   $ECHO "      -d         turn on debug"
+   $ECHO "      -f         fix only package APK_PATH"
+   $ECHO "      -l         disable logging for this run (faster)"
+   $ECHO "      -r         remove stale data directories"
+   $ECHO "                 of uninstalled packages while fixing permissions"
+   $ECHO "      -s         simulate only"
+   $ECHO "      -u         check only non-system directories"
+   $ECHO "      -v         disable verbosity for this run (less output)"
+   $ECHO "      -V         print version"
+   $ECHO "      -h         this help"
+}
+
+fp_parseargs()
+{
+   # Parse options
+   while $TEST $# -ne 0; do
+      case "$1" in
+         -d)
+            DEBUG=1
+         ;;
+         -f)
+            if $TEST $# -lt 2; then
+               $ECHO "$0: missing argument for option $1"
+               exit 1
+            else
+               if $TEST $( $ECHO $2 | $CUT -c1 ) != "-"; then
+                  ONLY_ONE=$2
+                  shift;
+               else
+                  $ECHO "$0: missing argument for option $1"
+                  exit 1
+               fi
+            fi
+         ;;
+         -r)
+            REMOVE=1
+         ;;
+         -s)
+            SIMULATE=1
+         ;;
+         -l)
+            if $TEST $LOGGING -eq 0; then
+               LOGGING=1
+            else
+               LOGGING=0
+            fi
+         ;;
+         -v)
+            if $TEST $VERBOSE -eq 0; then
+               VERBOSE=1
+            else
+               VERBOSE=0
+            fi
+         ;;
+         -u)
+            NOSYSTEM=1
+         ;;
+         -V)
+            $ECHO "$0 $VERSION"
+            exit 0
+         ;;
+         -h)
+            fp_usage
+            exit 0
+         ;;
+         -*)
+            $ECHO "$0: unknown option $1"
+            $ECHO
+            fp_usage
+            exit 1
+         ;;
+      esac
+      shift;
+   done
+}
+
+fp_print()
+{
+   MSG=$@
+   if $TEST $LOGGING -eq 1; then
+      $ECHO $MSG | $TEE -a $LOG_FILE
+   else
+      $ECHO $MSG
+   fi
+}
+
+fp_start()
+{
+   if $TEST $SIMULATE -eq 0 ; then
+      if $TEST $( $GREP -c " /system " "/proc/mounts" ) -ne 0; then
+         DEVICE=$( $GREP " /system " "/proc/mounts" | $CUT -d ' ' -f1 )
+         if $TEST $DEBUG -eq 1; then
+            fp_print "/system mounted on $DEVICE"
+         fi
+         if $TEST $( $GREP " /system " "/proc/mounts" | $GREP -c " ro " ) -ne 0; then
+            $MOUNT -o remount,rw $DEVICE /system
+            SYSREMOUNT=1
+         fi
+      else
+         $MOUNT /system > /dev/null 2>&1
+         SYSMOUNT=1
+      fi
+      
+      if $TEST $( $GREP -c " /data " "/proc/mounts" ) -eq 0; then
+         $MOUNT /data > /dev/null 2>&1
+         DATAMOUNT=1
+      fi
+      
+      if $TEST -e /dev/block/mmcblk0p2 && $TEST $( $GREP -c " $SD_EXT_DIRECTORY " "/proc/mounts" ) -eq 0; then
+         $MOUNT $SD_EXT_DIRECTORY > /dev/null 2>&1
+         SYSSDMOUNT=1
+      fi
+   fi
+   if $TEST $( $MOUNT | $GREP -c /sdcard ) -eq 0; then
+      LOG_FILE="/data/fix_permissions.log"
+   else
+      LOG_FILE="/sdcard/fix_permissions.log"
+   fi
+   if $TEST ! -e "$LOG_FILE"; then
+      > $LOG_FILE
+   fi
+   
+   fp_print "$0 $VERSION started at $FP_STARTTIME"
+}
+
+fp_chown_uid()
+{
+   FP_OLDUID=$1
+   FP_UID=$2
+   FP_FILE=$3
+   
+   #if user ownership doesn't equal then change them
+   if $TEST "$FP_OLDUID" != "$FP_UID"; then
+      if $TEST $VERBOSE -ne 0; then
+         fp_print "$UID_MSG $FP_FILE from '$FP_OLDUID' to '$FP_UID'"
+      fi
+      if $TEST $SIMULATE -eq 0; then
+         $CHOWN $FP_UID "$FP_FILE"
+      fi
+   fi
+}
+
+fp_chown_gid()
+{
+   FP_OLDGID=$1
+   FP_GID=$2
+   FP_FILE=$3
+   
+   #if group ownership doesn't equal then change them
+   if $TEST "$FP_OLDGID" != "$FP_GID"; then
+      if $TEST $VERBOSE -ne 0; then
+         fp_print "$GID_MSG $FP_FILE from '$FP_OLDGID' to '$FP_GID'"
+      fi
+      if $TEST $SIMULATE -eq 0; then
+         $CHOWN :$FP_GID "$FP_FILE"
+      fi
+   fi
+}
+
+fp_chmod()
+{
+   FP_OLDPER=$1
+   FP_OLDPER=$( $ECHO $FP_OLDPER | cut -c2-10 )
+   FP_PERSTR=$2
+   FP_PERNUM=$3
+   FP_FILE=$4
+   
+   #if the permissions are not equal
+   if $TEST "$FP_OLDPER" != "$FP_PERSTR"; then
+      if $TEST $VERBOSE -ne 0; then
+         fp_print "$PERM_MSG $FP_FILE from '$FP_OLDPER' to '$FP_PERSTR' ($FP_PERNUM)"
+      fi
+      #change the permissions
+      if $TEST $SIMULATE -eq 0; then
+         $CHMOD $FP_PERNUM "$FP_FILE"
+      fi
+   fi
+}
+
+fp_all()
+{
+   FP_NUMS=$( $CAT /data/system/packages.xml | $EGREP "^<package.*serId" | $GREP -v framework-res.apk | $GREP -v com.htc.resources.apk | $WC -l )
+   I=0
+   $CAT /data/system/packages.xml | $EGREP "^<package.*serId" | $GREP -v framework-res.apk | $GREP -v com.htc.resources.apk | while read all_line; do
+      I=$( $EXPR $I + 1 )
+      fp_package "$all_line" $I $FP_NUMS
+   done
+}
+
+fp_single()
+{
+   FP_SFOUND=$( $CAT /data/system/packages.xml | $EGREP "^<package.*serId" | $GREP -v framework-res.apk | $GREP -v com.htc.resources.apk | $GREP -i $ONLY_ONE | wc -l )
+   if $TEST $FP_SFOUND -gt 1; then
+      fp_print "Cannot perform single operation on $FP_SFOUND matched package(s)."
+      elif $TEST $FP_SFOUND = "" -o $FP_SFOUND -eq 0; then
+      fp_print "Could not find the package you specified in the packages.xml file."
+   else
+      FP_SPKG=$( $CAT /data/system/packages.xml | $EGREP "^<package.*serId" | $GREP -v framework-res.apk | $GREP -v com.htc.resources.apk | $GREP -i $ONLY_ONE )
+      fp_package "${FP_SPKG}" 1 1
+   fi
+}
+
+fp_package()
+{
+   pkgline=$1
+   curnum=$2
+   endnum=$3
+   CODEPATH=$( $ECHO $pkgline | $SED 's%.* codePath="\(.*\)".*%\1%' |  $CUT -d '"' -f1 )
+   PACKAGE=$( $ECHO $pkgline | $SED 's%.* name="\(.*\)".*%\1%' | $CUT -d '"' -f1 )
+   UID=$( $ECHO $pkgline | $SED 's%.*serId="\(.*\)".*%\1%' |  $CUT -d '"' -f1 )
+   GID=$UID
+   APPDIR=$( $ECHO $CODEPATH | $SED 's%^\(.*\)/.*%\1%' )
+   APK=$( $ECHO $CODEPATH | $SED 's%^.*/\(.*\..*\)$%\1%' )
+   
+   #debug
+   if $TEST $DEBUG -eq 1; then
+      fp_print "CODEPATH: $CODEPATH APPDIR: $APPDIR APK:$APK UID/GID:$UID:$GID"
+   fi
+   
+   #check for existence of apk
+   if $TEST -e $CODEPATH;  then
+      fp_print "Processing ($curnum of $endnum): $PACKAGE..."
+      
+      #lets get existing permissions of CODEPATH
+      OLD_UGD=$( $LS -ln "$CODEPATH" )
+      OLD_PER=$( $ECHO $OLD_UGD | $CUT -d ' ' -f1 )
+      OLD_UID=$( $ECHO $OLD_UGD | $CUT -d ' ' -f3 )
+      OLD_GID=$( $ECHO $OLD_UGD | $CUT -d ' ' -f4 )
+      
+      #apk source dirs
+      if $TEST "$APPDIR" = "/system/app"; then
+         #skip system apps if set
+         if $TEST "$NOSYSTEM" = "1"; then
+            fp_print "***SKIPPING SYSTEM APP ($PACKAGE)!"
+            return
+         fi
+         fp_chown_uid $OLD_UID 0 "$CODEPATH"
+         fp_chown_gid $OLD_GID 0 "$CODEPATH"
+         fp_chmod $OLD_PER "rw-r--r--" 644 "$CODEPATH"
+         elif $TEST "$APPDIR" = "/data/app" || $TEST "$APPDIR" = "/sd-ext/app"; then
+         fp_chown_uid $OLD_UID 1000 "$CODEPATH"
+         fp_chown_gid $OLD_GID 1000 "$CODEPATH"
+         fp_chmod $OLD_PER "rw-r--r--" 644 "$CODEPATH"
+         elif $TEST "$APPDIR" = "/data/app-private" || $TEST "$APPDIR" = "/sd-ext/app-private"; then
+         fp_chown_uid $OLD_UID 1000 "$CODEPATH"
+         fp_chown_gid $OLD_GID $GID "$CODEPATH"
+         fp_chmod $OLD_PER "rw-r-----" 640 "$CODEPATH"
+      fi
+   else
+      fp_print "$CODEPATH does not exist ($curnum of $endnum). Reinstall..."
+      if $TEST $REMOVE -eq 1; then
+         if $TEST -d /data/data/$PACKAGE ; then
+            fp_print "Removing stale dir /data/data/$PACKAGE"
+            if $TEST $SIMULATE -eq 0 ; then
+               $RM -R /data/data/$PACKAGE
+            fi
+         fi
+      fi
+   fi
+   
+   #the data/data for the package
+   if $TEST -d "/data/data/$PACKAGE"; then
+      #find all directories in /data/data/$PACKAGE
+      $FIND /data/data/$PACKAGE -type d -exec $LS -ldn {} \; | while read dataline; do
+         #get existing permissions of that directory
+         OLD_PER=$( $ECHO $dataline | $CUT -d ' ' -f1 )
+         OLD_UID=$( $ECHO $dataline | $CUT -d ' ' -f3 )
+         OLD_GID=$( $ECHO $dataline | $CUT -d ' ' -f4 )
+         FILEDIR=$( $ECHO $dataline | $CUT -d ' ' -f9 )
+         FOURDIR=$( $ECHO $FILEDIR | $CUT -d '/' -f5 )
+         
+         #set defaults for iteration
+         ISLIB=0
+         REVPERM=755
+         REVPSTR="rwxr-xr-x"
+         REVUID=$UID
+         REVGID=$GID
+         
+         if $TEST "$FOURDIR" = ""; then
+            #package directory, perms:755 owner:$UID:$GID
+            fp_chmod $OLD_PER "rwxr-xr-x" 755 "$FILEDIR"
+            elif $TEST "$FOURDIR" = "lib"; then
+            #lib directory, perms:755 owner:1000:1000
+            #lib files, perms:755 owner:1000:1000
+            ISLIB=1
+            REVPERM=755
+            REVPSTR="rwxr-xr-x"
+            REVUID=1000
+            REVGID=1000
+            fp_chmod $OLD_PER "rwxr-xr-x" 755 "$FILEDIR"
+            elif $TEST "$FOURDIR" = "shared_prefs"; then
+            #shared_prefs directories, perms:771 owner:$UID:$GID
+            #shared_prefs files, perms:660 owner:$UID:$GID
+            REVPERM=660
+            REVPSTR="rw-rw----"
+            fp_chmod $OLD_PER "rwxrwx--x" 771 "$FILEDIR"
+            elif $TEST "$FOURDIR" = "databases"; then
+            #databases directories, perms:771 owner:$UID:$GID
+            #databases files, perms:660 owner:$UID:$GID
+            REVPERM=660
+            REVPSTR="rw-rw----"
+            fp_chmod $OLD_PER "rwxrwx--x" 771 "$FILEDIR"
+            elif $TEST "$FOURDIR" = "cache"; then
+            #cache directories, perms:771 owner:$UID:$GID
+            #cache files, perms:600 owner:$UID:GID
+            REVPERM=600
+            REVPSTR="rw-------"
+            fp_chmod $OLD_PER "rwxrwx--x" 771 "$FILEDIR"
+         else
+            #other directories, perms:771 owner:$UID:$GID
+            REVPERM=771
+            REVPSTR="rwxrwx--x"
+            fp_chmod $OLD_PER "rwxrwx--x" 771 "$FILEDIR"
+         fi
+         
+         #change ownership of directories matched
+         if $TEST "$ISLIB" = "1"; then
+            fp_chown_uid $OLD_UID 1000 "$FILEDIR"
+            fp_chown_gid $OLD_GID 1000 "$FILEDIR"
+         else
+            fp_chown_uid $OLD_UID $UID "$FILEDIR"
+            fp_chown_gid $OLD_GID $GID "$FILEDIR"
+         fi
+         
+         #if any files exist in directory with improper permissions reset them
+         $FIND $FILEDIR -type f -maxdepth 1 ! -perm $REVPERM -exec $LS -ln {} \; | while read subline; do
+            OLD_PER=$( $ECHO $subline | $CUT -d ' ' -f1 )
+            SUBFILE=$( $ECHO $subline | $CUT -d ' ' -f9 )
+            fp_chmod $OLD_PER $REVPSTR $REVPERM "$SUBFILE"
+         done
+         
+         #if any files exist in directory with improper user reset them
+         $FIND $FILEDIR -type f -maxdepth 1 ! -user $REVUID -exec $LS -ln {} \; | while read subline; do
+            OLD_UID=$( $ECHO $subline | $CUT -d ' ' -f3 )
+            SUBFILE=$( $ECHO $subline | $CUT -d ' ' -f9 )
+            fp_chown_uid $OLD_UID $REVUID "$SUBFILE"
+         done
+         
+         #if any files exist in directory with improper group reset them
+         $FIND $FILEDIR -type f -maxdepth 1 ! -group $REVGID -exec $LS -ln {} \; | while read subline; do
+            OLD_GID=$( $ECHO $subline | $CUT -d ' ' -f4 )
+            SUBFILE=$( $ECHO $subline | $CUT -d ' ' -f9 )
+            fp_chown_gid $OLD_GID $REVGID "$SUBFILE"
+         done
+      done
+   fi
+}
+
+date_diff()
+{
+   if $TEST $# -ne 2; then
+      FP_DDM="E"
+      FP_DDS="E"
+      return
+   fi
+   FP_DDD=$( $EXPR $2 - $1 )
+   FP_DDM=$( $EXPR $FP_DDD / 60 )
+   FP_DDS=$( $EXPR $FP_DDD % 60 )
+}
+
+fp_end()
+{
+   if $TEST $SYSREMOUNT -eq 1; then
+      $MOUNT -o remount,ro $DEVICE /system > /dev/null 2>&1
+   fi
+   
+   if $TEST $SYSSDMOUNT -eq 1; then
+      $UMOUNT $SD_EXT_DIRECTORY > /dev/null 2>&1
+   fi
+   
+   if $TEST $SYSMOUNT -eq 1; then
+      $UMOUNT /system > /dev/null 2>&1
+   fi
+   
+   if $TEST $DATAMOUNT -eq 1; then
+      $UMOUNT /data > /dev/null 2>&1
+   fi
+   
+   FP_ENDTIME=$( $DATE +"%m-%d-%Y %H:%M:%S" )
+   FP_ENDEPOCH=$( $DATE +%s )
+   
+   date_diff $FP_STARTEPOCH $FP_ENDEPOCH
+   
+   fp_print "$0 $VERSION ended at $FP_ENDTIME (Runtime:${FP_DDM}m${FP_DDS}s)"
+}
+
+#MAIN SCRIPT
+
+fp_parseargs $@
+fp_start
+if $TEST "$ONLY_ONE" != "" -a "$ONLY_ONE" != "0" ; then
+   fp_single "$ONLY_ONE"
+else
+   fp_all
+fi
+fp_end
diff --git a/utilities/parted b/utilities/parted
new file mode 100644
index 0000000..bb3d432
--- /dev/null
+++ b/utilities/parted
Binary files differ
diff --git a/utilities/sdparted b/utilities/sdparted
new file mode 100644
index 0000000..13e62f4
--- /dev/null
+++ b/utilities/sdparted
@@ -0,0 +1,638 @@
+#!/sbin/sh
+
+# do logging, if not excluded with -x
+LOGFILE="/data/sdparted.log"
+[ "$1" != "-x" ] && echo "$0" "$@" >> "$LOGFILE" && "$0" -x "$@" 2>&1 | tee -a "$LOGFILE" && exit
+shift
+
+ShowError() { echo ; echo " err: $1" ; echo ; exit 1 ; }
+
+ShowMessage() { echo ; echo " msg: $1" ; }
+
+ShowHelp() {
+
+cat <<DONEHELP
+
+$SCRIPTNAME v$SCRIPTREV created by $MYNAME
+
+if you use this script in your work, please give some credit. thanks.
+
+requirements: cm-recovery-v1.4
+
+usage: $SCRIPTNAME [options]
+
+
+options:
+
+ --fatsize|-fs SIZE[MG]     set the size of the fat32 partition to <SIZE>.
+                            default=total sdcard size - (ext + swap)
+						
+ --extsize|-es SIZE[MG]     set the size of the ext partition to <SIZE>.
+                            default=$EXTSIZE
+						
+ --swapsize|-ss SIZE[MG]    set the size of the swap partition to <SIZE>.
+                            if set to 0, no swap partition will be created.
+                            default=$SWAPSIZE
+						
+ --extfs|-efs TYPE          set the filesystem of ext partition to <TYPE>.
+                            valid types=ext2, ext3, ext4
+                            default=$EXTFS
+
+
+ --upgradefs|-ufs TYPE      upgrades existing ext partition to <TYPE>.
+                            this operation will NOT wipe your sdcard and
+                            cannot be used with any partition creation options.
+                            valid types=ext3, ext4
+
+ --downgradefs|-dfs TYPE    downgrades existing ext partition to <TYPE>.
+                            this operation will NOT wipe your sdcard and
+                            cannot be used with any partition creation options.
+                            valid types=ext2
+
+							
+ --interactive|-i           interactive mode
+
+ --help|-h                  display this help
+
+ --printonly|-po            display sdcard information
+
+ --silent|-s                do not prompt user, not even initial warning.
+
+
+examples:
+ $SCRIPTNAME                     creates swap=$SWAPSIZE ext2=$EXTSIZE fat32=remaining free space
+ $SCRIPTNAME -efs ext4           creates swap=$SWAPSIZE ext4=$EXTSIZE fat32=remaining free space
+ $SCRIPTNAME -fs 1.5G -efs ext3  creates swap=$SWAPSIZE ext3=$EXTSIZE fat32=1536
+ $SCRIPTNAME -es 256M -ss 0      creates no swap ext2=256 fat32=remaining free space
+ $SCRIPTNAME -ufs ext4           upgrades ext partition to ext4
+
+DONEHELP
+
+}
+
+UserAbort() {
+	
+	WHILEEXIT=
+
+	while [ -z "$WHILEEXIT" ]
+	do
+		echo -n "do you want to continue? (Y/n) "
+		read response
+		echo
+		[ "$response" = "Y" ] || [ "$response" = "n" ] || [ "$response" = "N" ] && WHILEEXIT="$response"
+	done
+	
+	echo "$response" > /dev/null 2>&1 >>"$LOGFILE"
+	
+	[ "$response" != "Y" ]
+	
+}
+
+UnmountAll () {
+
+	# unmount all partitions so we can work with $SDPATH
+	# i'm assuming no more than 3 partitions
+	# maybe make a little more elegant later
+	echo -n "unmounting all partitions..."
+	umount "$FATPATH" > /dev/null 2>&1 >>"$LOGFILE"
+	umount "$EXTPATH" > /dev/null 2>&1 >>"$LOGFILE"
+	umount "$SWAPPATH" > /dev/null 2>&1 >>"$LOGFILE"
+	echo "done"
+	echo
+	
+}
+
+
+CheckReqs() {
+
+	echo -n "checking script requirements..."
+	# check for valid sdcard
+	[ -e $SDPATH ] || ShowError "$SDPATH does not exist!"
+	
+	# look for necessary programs
+	[ -e $CMPARTED ] || ShowError "$CMPARTED does not exist!"
+	[ -e $CMTUNE2FS ] || ShowError "$CMTUNE2FS does not exist!"
+	[ -e $CME2FSCK ] || ShowError "$CME2FSCK does not exist!"
+	
+	# verify cm-v1.4
+	PARTEDREV=`"$CMPARTED" "$SDPATH" version | grep Parted | cut -d" " -f3`
+	[ "$PARTEDREV" == "1.8.8.1.179-aef3" ] || ShowError "you are not using parted v1.8.8.1.179-aef3!"
+	echo "done"
+	echo
+	
+}
+
+CheckTableType() {
+
+	TABLETYPE=`"$CMPARTED" "$SDPATH" print | grep Table: | cut -d" " -f3`
+	
+	[ "$TABLETYPE" == "loop" -o "$TABLETYPE" == "msdos" ] && TTISOK=1 || TTISOK=0
+	[ "$TABLETYPE" == "loop" ] && TTISLOOP=1 || TTISLOOP=0
+	[ "$TABLETYPE" == "msdos" ] && TTISMSDOS=1 || TTISMOSDOS=0
+	
+}
+
+ValidateExtArg() {
+
+	FUNC_RET="nonzero"
+		
+	# validating argument
+	[ "$1" != "ext2" ] && [ "$1" != "ext3" ] && [ "$1" != "ext4" ] && FUNC_RET=
+		
+	[ -z "$FUNC_RET" ] && [ -z "$IMODE" ] && ShowError "$1 is not a valid filesystem."
+	[ -z "$FUNC_RET" ] && [ -n "$IMODE" ] && ShowMessage "$1 is not a valid filesystem."
+	
+	# return valid argument
+	[ -n "$FUNC_RET" ] && FUNC_RET="$1"
+	
+}
+
+ValidateSizeArg() {
+
+	# check for zero-length arg to protect expr length
+	[ -z "$1" ] && ShowError "zero-length argument passed to size-validator"
+		
+	SIZEMB=
+	ARGLEN=`expr length $1`
+	SIZELEN=$(($ARGLEN-1))
+	SIZEARG=`expr substr $1 1 $SIZELEN`
+	SIZEUNIT=`expr substr $1 $ARGLEN 1`
+	
+	# check if SIZEARG is an integer
+	if [ $SIZEARG -eq $SIZEARG 2> /dev/null ] ; then
+		# look for G
+		[ "$SIZEUNIT" == "G" ] && SIZEMB=$(($SIZEARG * 1024))
+		# look for M
+		[ "$SIZEUNIT" == "M" ] && SIZEMB=$SIZEARG
+		# no units on arg AND prevents using bogus size units
+		[ -z "$SIZEMB" ] && [ $SIZEUNIT -eq $SIZEUNIT 2> /dev/null ] && SIZEMB=$1
+	# check if SIZEARG is a floating point number, GB only
+	elif [ `expr index "$SIZEARG" .` != 0 ] && [ "$SIZEUNIT" == "G" ]  ; then
+		INT=`echo "$SIZEARG" | cut -d"." -f1`
+		FRAC=`echo "$SIZEARG" | cut -d"." -f2`
+		SIGDIGITS=`expr length $FRAC`
+		
+		[ -z "$INT" ] && INT=0
+		INTMB=$(($INT * 1024))
+		FRACMB=$((($FRAC * 1024) / (10**$SIGDIGITS)))
+		SIZEMB=$(($INTMB + $FRACMB))
+	# it's not a valid size
+	else
+		[ -z "$IMODE" ] && ShowError "$1 is not a valid size"
+	fi
+	
+	[ -z "$SIZEMB" ] && [ -n "$IMODE" ] && ShowMessage "$1 is not a valid size"
+	
+	# return valid argument in MB
+	FUNC_RET=$SIZEMB
+	
+}
+
+CalculatePartitions() {
+
+	# get size of sdcard in MB & do some math
+	SDSIZEMB=`"$CMPARTED" "$SDPATH" unit MB print | grep $SDPATH | cut -d" " -f3`
+	SDSIZE=${SDSIZEMB%MB}
+	[ -n "$FATSIZE" ] || FATSIZE=$(($SDSIZE - $EXTSIZE - $SWAPSIZE))
+	EXTEND=$(($FATSIZE + $EXTSIZE))
+	SWAPEND=$(($EXTEND + $SWAPSIZE))
+	
+	# check for fatsize of 0
+	[ $FATSIZE -le 0 ] && ShowError "must have a fat32 partition greater than 0MB"
+	
+	# check for zero-length sdsize...
+	# indicative of parted not reporting length 
+	# correctly b/c of error on sdcard
+	[ -z "$SDSIZE" ] && ShowError "zero-length argument passed to partition-calculator"
+	
+	# make sure we're not being asked to do the impossible
+	[ $(($FATSIZE + $EXTSIZE + $SWAPSIZE)) -gt $SDSIZE ] && [ -z "$IMODE" ] && ShowError "sum of requested partitions is greater than sdcard size"
+
+}
+
+
+UpgradeDowngradeOnly() {
+
+	if [ -n "$UEXTFSONLY" ] ; then
+		echo
+		[ -n "$CREATEPART" ] && ShowError "cannot use upgrade option when creating partitions, use -efs instead"
+		[ -n "$DEXTFSONLY" ] && ShowError "cannot upgrade AND downgrade, it just doesn't make sense"
+		echo "you have chosen to upgrade $EXTPATH to $UEXTFSONLY."
+		echo "this action will NOT delete any data from sdcard."
+		echo
+		[ -z "$SILENTRUN" ] &&	UserAbort && ShowError "script canceled by user"
+		echo
+		UpgradeExt "$UEXTFSONLY"
+		ShowCardInfo
+	elif [ -n "$DEXTFSONLY" ] ; then
+		echo
+		[ -n "$CREATEPART" ] && ShowError "cannot use downgrade option when creating partitions."
+		[ -n "$UEXTFSONLY" ] && ShowError "cannot downgrade AND upgrade, it just doesn't make sense."
+		echo "you have chosen to downgrade $EXTPATH to $DEXTFSONLY."
+		echo "this action will NOT delete any data from sdcard."
+		echo
+		[ -z "$SILENTRUN" ] &&	UserAbort && ShowError "script canceled by user"
+		echo
+		DowngradeExt "$DEXTFSONLY"
+		ShowCardInfo
+	fi
+	
+}
+
+PrepareSdCard() {
+
+	echo
+	if [ $TTISOK -eq 0 ] ; then
+		echo "partition 1 may not be aligned to cylinder boundaries."
+		echo "to continue, this must be corrected."
+	elif [ $TTISLOOP -gt 0 ] ; then
+		echo "your sdcard's partition table type is $TABLETYPE."
+		echo "to continue, partition table must be set to 'msdos'."
+	elif [ $TTISMSDOS -gt 0 ] ; then
+		# just a reminder..in a later version, 
+		# i may implement resizing of partitions, 
+		# so this will be unnecessary. but until then...
+		echo "to continue, all existing partitions must be removed."
+	else
+		# this is not good, and should never happen
+		# if it does, there is a serious problem
+		ShowError "sdcard failed table type check."
+	fi
+	
+	echo
+	echo "this action will remove all data from your sdcard."
+	echo
+	[ -z "$SILENTRUN" ] &&	UserAbort && ShowError "script canceled by user"
+	
+	[ $TTISOK -eq 0 ] && echo -n "correcting cylinder boundaries..."
+	[ $TTISLOOP -gt 0 ] && echo -n "setting partition table to msdos..."
+	[ $TTISMSDOS -gt 0 ] && echo -n "removing all partitions..."
+	
+	"$CMPARTED" -s "$SDPATH" mklabel msdos 2>&1 >>"$LOGFILE"
+	echo "done"
+	echo
+	
+}
+
+ShowActions() {
+
+	echo
+	echo "total size of sdcard=$SDSIZEMB"
+	echo
+	echo "the following actions will be performed:"
+	echo " -create $FATSIZE""MB fat32 partition"
+	[ $EXTSIZE -gt 0 ] && echo " -create $EXTSIZE""MB ext2 partition"
+	[ $SWAPSIZE -gt 0 ] && echo " -create $SWAPSIZE""MB swap partition"
+	[ "$EXTFS" != "ext2" ] && echo " -ext2 partition will be upgraded to $EXTFS"
+	echo
+	[ -z "$SILENTRUN" ] &&	UserAbort && ShowError "script canceled by user"
+	echo
+	
+}
+
+ShowCardInfo() {
+
+	CheckTableType
+	
+	echo
+	echo "retrieving current sdcard information..."
+	
+	if [ $TTISOK -gt 0 ] ; then
+		echo
+		parted "$SDPATH" print
+		echo
+		echo "script log is located @ /data/sdparted.log"
+		exit 0
+	else
+		echo
+		echo "partition 1 may not be aligned to cylinder boundaries."
+		ShowError "cannot complete print operation."
+	fi
+	echo
+	
+}
+
+
+PartitionSdCard() {
+
+	echo "performing selected actions..."
+	echo
+	
+	if [ $FATSIZE -gt 0 ] ; then
+		echo -n "creating fat32 partition..."
+		"$CMPARTED" -s "$SDPATH" mkpartfs primary fat32 0 "$FATSIZE"MB 2>&1 >>"$LOGFILE"
+		echo "done"
+	fi
+	
+	if [ $EXTSIZE -gt 0 ] ; then
+		echo -n "creating ext2 partition..."
+		"$CMPARTED" -s "$SDPATH" mkpartfs primary ext2 "$FATSIZE"MB "$EXTEND"MB 2>&1 >>"$LOGFILE"
+		"$CMTUNE2FS" -L sd-ext "$EXTPATH" 2>&1 >>"$LOGFILE"
+		echo "done"
+	fi
+	
+	if [ $SWAPSIZE -gt 0 ] ; then
+		echo -n "creating swap partition..."
+		"$CMPARTED" -s "$SDPATH" mkpartfs primary linux-swap "$EXTEND"MB "$SWAPEND"MB 2>&1 >>"$LOGFILE"
+		echo "done"
+	fi
+	echo
+	
+}
+
+UpgradeExt() {
+
+	# check for no upgrade
+	[ "$1" == "ext2" ] && return
+	# check for ext partition
+	[ ! -e "$EXTPATH" ] && ShowError "$EXTPATH does not exist"
+
+	# have to use -m switch for this check b/c parted incorrectly
+	# reports all ext partitions as ext2 when running print <number>
+	CHECKEXTFS=`"$CMPARTED" -m "$SDPATH" print | grep ext | cut -d":" -f5`
+	[ "$CHECKEXTFS" == "$1" ] && ShowError "$EXTPATH is already $1"
+
+	# grabbed the code bits for ext3 from upgrade_fs(credit:cyanogen)
+	# check for ext2...must upgrade to ext3 first b4 ext4
+	if [ "$1" == "ext3" -o "$1" == "ext4" ] ; then
+		echo -n "adding journaling to $EXTPATH..."
+		umount /system/sd > /dev/null 2>&1 >>"$LOGFILE"
+		"$CME2FSCK" -p "$EXTPATH" 2>&1 >>"$LOGFILE"
+		"$CMTUNE2FS" -c0 -i0 -j "$EXTPATH" 2>&1 >>"$LOGFILE"
+		echo "done"
+	fi
+
+	# and got convert to ext4 from xda-forum(credit:Denkai)
+	if [ "$1" == "ext4" ] ; then
+		echo -n "converting $EXTPATH to ext4 filesystem..."
+		umount /system/sd > /dev/null 2>&1 >>"$LOGFILE"
+		"$CMTUNE2FS" -O extents,uninit_bg,dir_index "$EXTPATH" 2>&1 >>"$LOGFILE"
+		"$CME2FSCK" -fpDC0 "$EXTPATH" 2>&1 >>"$LOGFILE"
+		echo "done"
+	fi
+	echo
+	
+}
+
+DowngradeExt() {
+
+	# check for ext partition
+	[ ! -e "$EXTPATH" ] && ShowError "$EXTPATH does not exist"
+
+	# have to use print for this check b/c parted incorrectly
+	# reports all ext partitions as ext2 when running print <number>
+	CHECKEXTFS=`"$CMPARTED" -m "$SDPATH" print | grep ext | cut -d":" -f5`
+	[ "$CHECKEXTFS" == "$1" ] && ShowError "$EXTPATH is already $1"
+	
+	if [ "$CHECKEXTFS" == "ext4" -o "$1" == "ext3" ] ; then
+		# interweb says downgrading from ext4 is not possible
+		# without a backup/restore procedure.
+		# if i figure it out, i'll implement it.
+		ShowError "downgrading from ext4 is not currently supported"
+	fi
+	
+	if [ "$1" == "ext2" ] ; then
+		echo -n "removing journaling from $EXTPATH..."
+		umount /system/sd > /dev/null 2>&1 >>"$LOGFILE"
+		"$CMTUNE2FS" -O ^has_journal "$EXTPATH" 2>&1 >>"$LOGFILE"
+		"$CME2FSCK" -fp "$EXTPATH" 2>&1 >>"$LOGFILE"
+		echo "done"
+	fi
+	echo
+	
+}
+
+
+Interactive() {
+
+cat <<DONEINSTRUCT
+
+sdparted interactive mode
+
+rules:
+1. no answer means you accept default value
+2. enter '0' for no partition
+
+DONEINSTRUCT
+
+	UserAbort && ShowError "script canceled by user"
+	
+	CalculatePartitions
+	
+	GetSwapSize
+	
+	FATSIZE=
+	
+	CalculatePartitions
+	
+	GetExtSize
+	
+	GetExtType
+	
+	FATSIZE=
+	
+	CalculatePartitions
+	
+	GetFatSize
+	
+}
+
+GetSwapSize() {
+
+	SWAPTEST=
+	
+	while [ -z "$SWAPTEST" ]
+	do
+		echo
+		echo -n "swap partition size [default=$SWAPSIZE]: "
+		read SWAPRESP
+		
+		[ -z "$SWAPRESP" ] && SWAPRESP="$SWAPSIZE"
+		echo "$SWAPRESP" > /dev/null 2>&1 >>"$LOGFILE"
+		
+		ValidateSizeArg "$SWAPRESP"
+		SWAPTEST="$FUNC_RET"
+		[ -n "$SWAPTEST" ] && [ $SWAPTEST -gt $SDSIZE ] && ShowMessage "$SWAPRESP > available space($(($SDSIZE))M)." && SWAPTEST=
+	done
+	
+	SWAPSIZE=$SWAPTEST
+	
+}
+
+GetExtSize() {
+
+	EXTTEST=
+	
+	while [ -z "$EXTTEST" ]
+	do
+		echo
+		echo -n "ext partition size [default=$EXTSIZE]: "
+		read EXTRESP
+		
+		[ -z "$EXTRESP" ] && EXTRESP="$EXTSIZE"
+		echo "$EXTRESP" > /dev/null 2>&1 >>"$LOGFILE"
+		
+		ValidateSizeArg "$EXTRESP"
+		EXTTEST="$FUNC_RET"
+		
+		[ -n "$EXTTEST" ] && [ $EXTTEST -gt $(($SDSIZE - $SWAPSIZE)) ] && ShowMessage "$EXTRESP > available space($(($SDSIZE - $SWAPSIZE))M)." && EXTTEST=
+	done
+	
+	EXTSIZE=$EXTTEST
+	
+}
+
+GetExtType() {
+
+	FSTEST=
+	
+	while [ -z "$FSTEST" ]
+	do
+		echo
+		echo -n "ext partition type [default=$EXTFS]: "
+		read FSRESP
+		
+		[ -z "$FSRESP" ] && FSRESP="$EXTFS"
+		echo "$FSRESP" > /dev/null 2>&1 >>"$LOGFILE"
+		
+		ValidateExtArg "$FSRESP"
+		FSTEST="$FUNC_RET"
+	done
+	
+	EXTFS="$FSTEST"
+	
+}
+
+GetFatSize() {
+
+	FATTEST=
+	
+	while [ -z "$FATTEST" ]
+	do
+		echo
+		echo -n "fat partition size [default=$FATSIZE]: "
+		read FATRESP
+		
+		[ -z "$FATRESP" ] && FATRESP="$FATSIZE"
+		echo "$FATRESP" > /dev/null 2>&1 >>"$LOGFILE"
+		
+		ValidateSizeArg "$FATRESP"
+		FATTEST="$FUNC_RET"
+		
+		[ -n "$FATTEST" ] && [ $FATTEST -gt $FATSIZE ] && ShowMessage "$FATRESP > available space($(($SDSIZE - $SWAPSIZE - $EXTSIZE))M)." && FATTEST=
+		[ -n "$FATTEST" ] && [ $FATTEST -le 0 ] && ShowMessage "must have a fat32 partition greater than 0MB" && FATTEST=
+	done
+	
+	FATSIZE=$FATTEST
+	
+}
+
+
+SCRIPTNAME="sdparted"
+SCRIPTREV="0.6"
+MYNAME="51dusty"
+
+IMODE=
+SILENTRUN=
+CREATEPART=
+FUNC_RET=
+
+UEXTFSONLY=
+DEXTFSONLY=
+
+TTISOK=
+TTISLOOP=
+TTISMSDOS=
+
+SDSIZE=
+SDSIZEMB=
+if [ -z "$SDPATH" ]
+then
+    SDPATH="/dev/block/mmcblk0"
+else
+    echo Found SDPATH=$SDPATH
+fi
+
+FATSIZE=
+FATTYPE="fat32"
+FATPATH=$SDPATH"p1"
+
+EXTSIZE=512
+EXTFS="ext2"
+EXTPATH=$SDPATH"p2"
+EXTEND=
+
+SWAPSIZE=32
+SWAPTYPE="linux-swap"
+SWAPPATH=$SDPATH"p3"
+SWAPEND=
+
+CMPARTED="/sbin/parted"
+CMTUNE2FS="/sbin/tune2fs"
+CME2FSCK="/sbin/e2fsck"
+
+# give the output some breathing room
+echo "$SCRIPTREV" >> "$LOGFILE" 
+echo
+
+# check for arguments
+while [ $# -gt 0 ] ; do
+  case "$1" in
+
+    -h|--help) ShowHelp ; exit 0 ;;
+    
+    -fs|--fatsize) shift ; ValidateSizeArg "$1" ; FATSIZE="$FUNC_RET" ; CREATEPART="$1" ;;
+    -es|--extsize) shift ; ValidateSizeArg "$1" ; EXTSIZE="$FUNC_RET" ; CREATEPART="$1" ;;
+	-ss|--swapsize) shift ; ValidateSizeArg "$1" ; SWAPSIZE="$FUNC_RET" ; CREATEPART="$1" ;;
+	-efs|--extfs) shift ; ValidateExtArg "$1" ; EXTFS="$FUNC_RET" ; CREATEPART="$1" ;;
+	
+	-ufs|--upgradefs) shift ; ValidateExtArg "$1" ; UEXTFSONLY="$FUNC_RET" ;;
+	-dfs|--downgradefs) shift ; ValidateExtArg "$1" ; DEXTFSONLY="$FUNC_RET" ;;
+	
+	-i|--interactive) IMODE="$1" ;;
+	
+	-s|--silent) SILENTRUN="$1" ;;
+	
+	-po|--printonly) ShowCardInfo ;;
+	
+	*) ShowHelp ; ShowError "unknown argument '$1'" ;;
+
+  esac
+  shift
+done
+
+# can't do silent when in interactive mode
+[ -n "$IMODE" ] && SILENTRUN=
+
+# make sure sdcard exists and all needed files are here
+CheckReqs
+
+# unmount all
+UnmountAll
+
+# upgrade only? downgrade only?
+UpgradeDowngradeOnly
+
+# check table
+CheckTableType
+
+# prep card
+PrepareSdCard
+
+# check for interactive mode
+[ -n "$IMODE" ] && Interactive
+
+# do some math
+CalculatePartitions
+
+# last chance to cancel
+ShowActions
+
+# partition card
+PartitionSdCard
+
+# upgrade fs if necessary
+UpgradeExt "$EXTFS"
+
+# say goodbye and show print output
+ShowCardInfo
diff --git a/utilities/tune2fs b/utilities/tune2fs
new file mode 100755
index 0000000..4bd02f6
--- /dev/null
+++ b/utilities/tune2fs
Binary files differ
diff --git a/verifier.c b/verifier.c
new file mode 100644
index 0000000..9d39fd1
--- /dev/null
+++ b/verifier.c
@@ -0,0 +1,184 @@
+/*
+ * 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 "common.h"
+#include "verifier.h"
+
+#include "mincrypt/rsa.h"
+#include "mincrypt/sha.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+// Look for an RSA signature embedded in the .ZIP file comment given
+// the path to the zip.  Verify it matches one of the given public
+// keys.
+//
+// Return VERIFY_SUCCESS, VERIFY_FAILURE (if any error is encountered
+// or no key matches the signature).
+
+int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKeys) {
+    ui_set_progress(0.0);
+
+    FILE* f = fopen(path, "rb");
+    if (f == NULL) {
+        LOGE("failed to open %s (%s)\n", path, strerror(errno));
+        return VERIFY_FAILURE;
+    }
+
+    // An archive with a whole-file signature will end in six bytes:
+    //
+    //   (2-byte signature start) $ff $ff (2-byte comment size)
+    //
+    // (As far as the ZIP format is concerned, these are part of the
+    // archive comment.)  We start by reading this footer, this tells
+    // us how far back from the end we have to start reading to find
+    // the whole comment.
+
+#define FOOTER_SIZE 6
+
+    if (fseek(f, -FOOTER_SIZE, SEEK_END) != 0) {
+        LOGE("failed to seek in %s (%s)\n", path, strerror(errno));
+        fclose(f);
+        return VERIFY_FAILURE;
+    }
+
+    unsigned char footer[FOOTER_SIZE];
+    if (fread(footer, 1, FOOTER_SIZE, f) != FOOTER_SIZE) {
+        LOGE("failed to read footer from %s (%s)\n", path, strerror(errno));
+        fclose(f);
+        return VERIFY_FAILURE;
+    }
+
+    if (footer[2] != 0xff || footer[3] != 0xff) {
+        fclose(f);
+        return VERIFY_FAILURE;
+    }
+
+    int comment_size = footer[4] + (footer[5] << 8);
+    int signature_start = footer[0] + (footer[1] << 8);
+    LOGI("comment is %d bytes; signature %d bytes from end\n",
+         comment_size, signature_start);
+
+    if (signature_start - FOOTER_SIZE < RSANUMBYTES) {
+        // "signature" block isn't big enough to contain an RSA block.
+        LOGE("signature is too short\n");
+        fclose(f);
+        return VERIFY_FAILURE;
+    }
+
+#define EOCD_HEADER_SIZE 22
+
+    // The end-of-central-directory record is 22 bytes plus any
+    // comment length.
+    size_t eocd_size = comment_size + EOCD_HEADER_SIZE;
+
+    if (fseek(f, -eocd_size, SEEK_END) != 0) {
+        LOGE("failed to seek in %s (%s)\n", path, strerror(errno));
+        fclose(f);
+        return VERIFY_FAILURE;
+    }
+
+    // Determine how much of the file is covered by the signature.
+    // This is everything except the signature data and length, which
+    // includes all of the EOCD except for the comment length field (2
+    // bytes) and the comment data.
+    size_t signed_len = ftell(f) + EOCD_HEADER_SIZE - 2;
+
+    unsigned char* eocd = malloc(eocd_size);
+    if (eocd == NULL) {
+        LOGE("malloc for EOCD record failed\n");
+        fclose(f);
+        return VERIFY_FAILURE;
+    }
+    if (fread(eocd, 1, eocd_size, f) != eocd_size) {
+        LOGE("failed to read eocd from %s (%s)\n", path, strerror(errno));
+        fclose(f);
+        return VERIFY_FAILURE;
+    }
+
+    // If this is really is the EOCD record, it will begin with the
+    // magic number $50 $4b $05 $06.
+    if (eocd[0] != 0x50 || eocd[1] != 0x4b ||
+        eocd[2] != 0x05 || eocd[3] != 0x06) {
+        LOGE("signature length doesn't match EOCD marker\n");
+        fclose(f);
+        return VERIFY_FAILURE;
+    }
+
+    int i;
+    for (i = 4; i < eocd_size-3; ++i) {
+        if (eocd[i  ] == 0x50 && eocd[i+1] == 0x4b &&
+            eocd[i+2] == 0x05 && eocd[i+3] == 0x06) {
+            // if the sequence $50 $4b $05 $06 appears anywhere after
+            // the real one, minzip will find the later (wrong) one,
+            // which could be exploitable.  Fail verification if
+            // this sequence occurs anywhere after the real one.
+            LOGE("EOCD marker occurs after start of EOCD\n");
+            fclose(f);
+            return VERIFY_FAILURE;
+        }
+    }
+
+#define BUFFER_SIZE 4096
+
+    SHA_CTX ctx;
+    SHA_init(&ctx);
+    unsigned char* buffer = malloc(BUFFER_SIZE);
+    if (buffer == NULL) {
+        LOGE("failed to alloc memory for sha1 buffer\n");
+        fclose(f);
+        return VERIFY_FAILURE;
+    }
+
+    double frac = -1.0;
+    size_t so_far = 0;
+    fseek(f, 0, SEEK_SET);
+    while (so_far < signed_len) {
+        int size = BUFFER_SIZE;
+        if (signed_len - so_far < size) size = signed_len - so_far;
+        if (fread(buffer, 1, size, f) != size) {
+            LOGE("failed to read data from %s (%s)\n", path, strerror(errno));
+            fclose(f);
+            return VERIFY_FAILURE;
+        }
+        SHA_update(&ctx, buffer, size);
+        so_far += size;
+        double f = so_far / (double)signed_len;
+        if (f > frac + 0.02 || size == so_far) {
+            ui_set_progress(f);
+            frac = f;
+        }
+    }
+    fclose(f);
+    free(buffer);
+
+    const uint8_t* sha1 = SHA_final(&ctx);
+    for (i = 0; i < numKeys; ++i) {
+        // 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)) {
+            LOGI("whole-file signature verified\n");
+            free(eocd);
+            return VERIFY_SUCCESS;
+        }
+    }
+    free(eocd);
+    LOGE("failed to verify whole-file signature\n");
+    return VERIFY_FAILURE;
+}
diff --git a/verifier.h b/verifier.h
new file mode 100644
index 0000000..1bdfca6
--- /dev/null
+++ b/verifier.h
@@ -0,0 +1,30 @@
+/*
+ * 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_VERIFIER_H
+#define _RECOVERY_VERIFIER_H
+
+#include "mincrypt/rsa.h"
+
+/* Look in the file for a signature footer, and verify that it
+ * matches one of the given keys.  Return one of the constants below.
+ */
+int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKeys);
+
+#define VERIFY_SUCCESS        0
+#define VERIFY_FAILURE        1
+
+#endif  /* _RECOVERY_VERIFIER_H */
diff --git a/verifier_test.c b/verifier_test.c
new file mode 100644
index 0000000..5b6c1f4
--- /dev/null
+++ b/verifier_test.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <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;
+    }
+}
diff --git a/verifier_test.sh b/verifier_test.sh
new file mode 100755
index 0000000..6350e80
--- /dev/null
+++ b/verifier_test.sh
@@ -0,0 +1,94 @@
+#!/bin/bash
+#
+# A test suite for applypatch.  Run in a client where you have done
+# envsetup, choosecombo, etc.
+#
+# DO NOT RUN THIS ON A DEVICE YOU CARE ABOUT.  It will mess up your
+# system partition.
+#
+#
+# TODO: find some way to get this run regularly along with the rest of
+# the tests.
+
+EMULATOR_PORT=5580
+DATA_DIR=$ANDROID_BUILD_TOP/bootable/recovery/testdata
+
+WORK_DIR=/data/local/tmp
+
+# set to 0 to use a device instead
+USE_EMULATOR=0
+
+# ------------------------
+
+if [ "$USE_EMULATOR" == 1 ]; then
+  emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT &
+  pid_emulator=$!
+  ADB="adb -s emulator-$EMULATOR_PORT "
+else
+  ADB="adb -d "
+fi
+
+echo "waiting to connect to device"
+$ADB wait-for-device
+
+# run a command on the device; exit with the exit status of the device
+# command.
+run_command() {
+  $ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}'
+}
+
+testname() {
+  echo
+  echo "::: testing $1 :::"
+  testname="$1"
+}
+
+fail() {
+  echo
+  echo FAIL: $testname
+  echo
+  [ "$open_pid" == "" ] || kill $open_pid
+  [ "$pid_emulator" == "" ] || kill $pid_emulator
+  exit 1
+}
+
+
+cleanup() {
+  # not necessary if we're about to kill the emulator, but nice for
+  # running on real devices or already-running emulators.
+  run_command rm $WORK_DIR/verifier_test
+  run_command rm $WORK_DIR/package.zip
+
+  [ "$pid_emulator" == "" ] || kill $pid_emulator
+}
+
+$ADB push $ANDROID_PRODUCT_OUT/system/bin/verifier_test \
+          $WORK_DIR/verifier_test
+
+expect_succeed() {
+  testname "$1 (should succeed)"
+  $ADB push $DATA_DIR/$1 $WORK_DIR/package.zip
+  run_command $WORK_DIR/verifier_test $WORK_DIR/package.zip || fail
+}
+
+expect_fail() {
+  testname "$1 (should fail)"
+  $ADB push $DATA_DIR/$1 $WORK_DIR/package.zip
+  run_command $WORK_DIR/verifier_test $WORK_DIR/package.zip && fail
+}
+
+expect_fail unsigned.zip
+expect_fail jarsigned.zip
+expect_succeed otasigned.zip
+expect_fail random.zip
+expect_fail fake-eocd.zip
+expect_fail alter-metadata.zip
+expect_fail alter-footer.zip
+
+# --------------- cleanup ----------------------
+
+cleanup
+
+echo
+echo PASS
+echo