resolved conflicts for merge of c02c37a1 to master

Change-Id: Iafb9cb4adf27a7086d587d95e94ab1bd050099dc
diff --git a/Android.mk b/Android.mk
index b0fefbd..4a3d285 100644
--- a/Android.mk
+++ b/Android.mk
@@ -24,6 +24,14 @@
 RECOVERY_API_VERSION := 3
 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
 
+LOCAL_STATIC_LIBRARIES :=
+
+ifeq ($(TARGET_USERIMAGES_USE_EXT4), true)
+LOCAL_CFLAGS += -DUSE_EXT4
+LOCAL_C_INCLUDES += system/extras/ext4_utils
+LOCAL_STATIC_LIBRARIES += libext4_utils libz
+endif
+
 # 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.
@@ -31,7 +39,6 @@
 
 LOCAL_MODULE_TAGS := eng
 
-LOCAL_STATIC_LIBRARIES :=
 ifeq ($(TARGET_RECOVERY_UI_LIB),)
   LOCAL_SRC_FILES += default_recovery_ui.c
 else
diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c
index 99d3661..d182821 100644
--- a/applypatch/applypatch.c
+++ b/applypatch/applypatch.c
@@ -30,10 +30,10 @@
 #include "mtdutils/mtdutils.h"
 #include "edify/expr.h"
 
-int SaveFileContents(const char* filename, FileContents file);
-int LoadMTDContents(const char* filename, FileContents* file);
+static int SaveFileContents(const char* filename, FileContents file);
+static int LoadPartitionContents(const char* filename, FileContents* file);
 int ParseSha1(const char* str, uint8_t* digest);
-ssize_t FileSink(unsigned char* data, ssize_t len, void* token);
+static ssize_t FileSink(unsigned char* data, ssize_t len, void* token);
 
 static int mtd_partitions_scanned = 0;
 
@@ -42,10 +42,11 @@
 int LoadFileContents(const char* filename, FileContents* file) {
     file->data = NULL;
 
-    // A special 'filename' beginning with "MTD:" means to load the
-    // contents of an MTD partition.
-    if (strncmp(filename, "MTD:", 4) == 0) {
-        return LoadMTDContents(filename, file);
+    // 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) {
@@ -98,26 +99,35 @@
     free(file);
 }
 
-// Load the contents of an MTD partition into the provided
+// 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>:...".
-// The smallest size_n bytes for which that prefix of the mtd contents
-// has the corresponding sha1 hash will be loaded.  It is acceptable
-// for a size value to be repeated with different sha1s.  Will return
-// 0 on success.
+// "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 MTD (there is no "end-of-file"
-// marker), so the caller must specify the possible lengths and the
-// hash of the data, and we'll do the load expecting to find one of
-// those hashes.
-int LoadMTDContents(const char* filename, FileContents* file) {
+// 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, ":");
-    if (strcmp(magic, "MTD") != 0) {
-        printf("LoadMTDContents called with bad filename (%s)\n",
+
+    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;
     }
@@ -131,7 +141,7 @@
         }
     }
     if (colons < 3 || colons%2 == 0) {
-        printf("LoadMTDContents called with bad filename (%s)\n",
+        printf("LoadPartitionContents called with bad filename (%s)\n",
                filename);
     }
 
@@ -144,7 +154,7 @@
         const char* size_str = strtok(NULL, ":");
         size[i] = strtol(size_str, NULL, 10);
         if (size[i] == 0) {
-            printf("LoadMTDContents called with bad size (%s)\n", filename);
+            printf("LoadPartitionContents called with bad size (%s)\n", filename);
             return -1;
         }
         sha1sum[i] = strtok(NULL, ":");
@@ -156,23 +166,38 @@
     size_array = size;
     qsort(index, pairs, sizeof(int), compare_size_indices);
 
-    if (!mtd_partitions_scanned) {
-        mtd_scan_partitions();
-        mtd_partitions_scanned = 1;
-    }
+    MtdReadContext* ctx = NULL;
+    FILE* dev = NULL;
 
-    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;
-    }
+    switch (type) {
+        case MTD:
+            if (!mtd_partitions_scanned) {
+                mtd_scan_partitions();
+                mtd_partitions_scanned = 1;
+            }
 
-    MtdReadContext* ctx = mtd_read_partition(mtd);
-    if (ctx == NULL) {
-        printf("failed to initialize read of mtd partition \"%s\"\n",
-               partition);
-        return -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;
@@ -191,7 +216,15 @@
         size_t next = size[index[i]] - file->size;
         size_t read = 0;
         if (next > 0) {
-            read = mtd_read_data(ctx, p, next);
+            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);
@@ -220,7 +253,7 @@
         if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_SIZE) == 0) {
             // we have a match.  stop reading the partition; we'll return
             // the data we've read so far.
-            printf("mtd read matched size %d sha %s\n",
+            printf("partition read matched size %d sha %s\n",
                    size[index[i]], sha1sum[index[i]]);
             break;
         }
@@ -228,12 +261,21 @@
         p += read;
     }
 
-    mtd_read_close(ctx);
+    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 MTD partition \"%s\" didn't match %s\n",
+        printf("contents of partition \"%s\" didn't match %s\n",
                partition, filename);
         free(file->data);
         file->data = NULL;
@@ -261,7 +303,7 @@
 
 // Save the contents of the given FileContents object under the given
 // filename.  Return 0 on success.
-int SaveFileContents(const char* filename, FileContents file) {
+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",
@@ -292,61 +334,87 @@
     return 0;
 }
 
-// Write a memory buffer to target_mtd partition, a string of the form
-// "MTD:<partition>[:...]".  Return 0 on success.
-int WriteToMTDPartition(unsigned char* data, size_t len,
-                        const char* target_mtd) {
-    char* partition = strchr(target_mtd, ':');
+// 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 MTD target name \"%s\"\n", target_mtd);
-        return -1;
-    }
-    ++partition;
-    // Trim off anything after a colon, eg "MTD:boot:blah:blah:blah...".
-    // We want just the partition name "boot".
-    partition = strdup(partition);
-    char* end = strchr(partition, ':');
-    if (end != NULL)
-        *end = '\0';
-
-    if (!mtd_partitions_scanned) {
-        mtd_scan_partitions();
-        mtd_partitions_scanned = 1;
-    }
-
-    const MtdPartition* mtd = mtd_find_partition_by_name(partition);
-    if (mtd == NULL) {
-        printf("mtd partition \"%s\" not found for writing\n", partition);
+        printf("bad partition target name \"%s\"\n", target);
         return -1;
     }
 
-    MtdWriteContext* ctx = mtd_write_partition(mtd);
-    if (ctx == NULL) {
-        printf("failed to init mtd partition \"%s\" for writing\n",
-               partition);
-        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;
     }
 
-    size_t written = mtd_write_data(ctx, (char*)data, len);
-    if (written != len) {
-        printf("only wrote %d of %d bytes to MTD %s\n",
-               written, len, partition);
-        mtd_write_close(ctx);
-        return -1;
-    }
-
-    if (mtd_erase_blocks(ctx, -1) < 0) {
-        printf("error finishing mtd write of %s\n", partition);
-        mtd_write_close(ctx);
-        return -1;
-    }
-
-    if (mtd_write_close(ctx)) {
-        printf("error closing mtd write of %s\n", partition);
-        return -1;
-    }
-
-    free(partition);
+    free(copy);
     return 0;
 }
 
@@ -406,7 +474,7 @@
     file.data = NULL;
 
     // It's okay to specify no sha1s; the check will pass if the
-    // LoadFileContents is successful.  (Useful for reading MTD
+    // LoadFileContents is successful.  (Useful for reading
     // partitions, where the filename encodes the sha1s; no need to
     // check them twice.)
     if (LoadFileContents(filename, &file) != 0 ||
@@ -518,8 +586,8 @@
 // - otherwise, or if any error is encountered, exits with non-zero
 //   status.
 //
-// <source_filename> may refer to an MTD partition to read the source
-// data.  See the comments for the LoadMTDContents() function above
+// <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,
@@ -623,14 +691,16 @@
         // Is there enough room in the target filesystem to hold the patched
         // file?
 
-        if (strncmp(target_filename, "MTD:", 4) == 0) {
-            // If the target is an MTD partition, we're actually going to
-            // write the output to /tmp and then copy it to the partition.
-            // statfs() always returns 0 blocks free for /tmp, so instead
-            // we'll just assume that /tmp has enough space to hold the file.
+        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 MTD
-            // write is interrupted.
+            // 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;
@@ -660,11 +730,13 @@
                 // copy the source file to cache, then delete it from the original
                 // location.
 
-                if (strncmp(source_filename, "MTD:", 4) == 0) {
+                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 an MTD partition.  If
+                    // 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 MTD\n");
+                    printf("not enough free space for target but source "
+                           "is partition\n");
                     return 1;
                 }
 
@@ -703,7 +775,8 @@
         void* token = NULL;
         output = -1;
         outname = NULL;
-        if (strncmp(target_filename, "MTD:", 4) == 0) {
+        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) {
@@ -779,8 +852,8 @@
     }
 
     if (output < 0) {
-        // Copy the temp file to the MTD partition.
-        if (WriteToMTDPartition(msi.buffer, msi.pos, target_filename) != 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;
         }
diff --git a/common.h b/common.h
index 1182d77..333417f 100644
--- a/common.h
+++ b/common.h
@@ -36,7 +36,7 @@
 // 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).
-void ui_start_menu(char** headers, char** items);
+void 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);
@@ -72,12 +72,12 @@
 void ui_reset_progress();
 
 #define LOGE(...) ui_print("E:" __VA_ARGS__)
-#define LOGW(...) fprintf(stderr, "W:" __VA_ARGS__)
-#define LOGI(...) fprintf(stderr, "I:" __VA_ARGS__)
+#define LOGW(...) fprintf(stdout, "W:" __VA_ARGS__)
+#define LOGI(...) fprintf(stdout, "I:" __VA_ARGS__)
 
 #if 0
-#define LOGV(...) fprintf(stderr, "V:" __VA_ARGS__)
-#define LOGD(...) fprintf(stderr, "D:" __VA_ARGS__)
+#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)
diff --git a/default_recovery_ui.c b/default_recovery_ui.c
index 409d679..ce12787 100644
--- a/default_recovery_ui.c
+++ b/default_recovery_ui.c
@@ -24,7 +24,7 @@
                          NULL };
 
 char* MENU_ITEMS[] = { "reboot system now",
-                       "apply sdcard:update.zip",
+                       "apply update from sdcard",
                        "wipe data/factory reset",
                        "wipe cache partition",
                        NULL };
diff --git a/install.c b/install.c
index 35ba6ca..20e8998 100644
--- a/install.c
+++ b/install.c
@@ -109,7 +109,7 @@
     if (pid == 0) {
         close(pipefd[0]);
         execv(binary, args);
-        fprintf(stderr, "E:Can't run %s (%s)\n", binary, strerror(errno));
+        fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
         _exit(-1);
     }
     close(pipefd[1]);
diff --git a/recovery.c b/recovery.c
index 04bf657..6c4507f 100644
--- a/recovery.c
+++ b/recovery.c
@@ -28,6 +28,7 @@
 #include <sys/types.h>
 #include <time.h>
 #include <unistd.h>
+#include <dirent.h>
 
 #include "bootloader.h"
 #include "common.h"
@@ -51,7 +52,7 @@
 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 *SDCARD_PACKAGE_FILE = "SDCARD:update.zip";
+static const char *SDCARD_ROOT = "SDCARD:";
 static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log";
 static const char *SIDELOAD_TEMP_DIR = "TMP:sideload";
 
@@ -405,7 +406,7 @@
 }
 
 static char**
-prepend_title(char** headers) {
+prepend_title(const char** headers) {
     char* title[] = { "Android system recovery <"
                           EXPAND(RECOVERY_API_VERSION) "e>",
                       "",
@@ -428,13 +429,14 @@
 }
 
 static int
-get_menu_selection(char** headers, char** items, int menu_only) {
+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();
 
-    ui_start_menu(headers, items);
-    int selected = 0;
+    ui_start_menu(headers, items, initial_selection);
+    int selected = initial_selection;
     int chosen_item = -1;
 
     while (chosen_item < 0) {
@@ -468,6 +470,127 @@
     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* root_path) {
+    // Mount the sdcard when the package selection menu is enabled so
+    // you can "adb push" packages to the sdcard and immediately
+    // install them.
+    ensure_root_path_mounted(SDCARD_ROOT);
+
+    const char* MENU_HEADERS[] = { "Choose a package to install:",
+                                   root_path,
+                                   "",
+                                   NULL };
+    DIR* d;
+    struct dirent* de;
+    char path[PATH_MAX];
+    d = opendir(translate_root_path(root_path, path, sizeof(path)));
+    if (d == NULL) {
+        LOGE("error opening %s: %s\n", path, strerror(errno));
+        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 = INSTALL_CORRUPT;
+    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, root_path, PATH_MAX);
+            strlcat(new_path, item, PATH_MAX);
+            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, root_path, PATH_MAX);
+            strlcat(new_path, item, PATH_MAX);
+
+            ui_print("\n-- Install %s ...\n", new_path);
+            char* copy = copy_sideloaded_package(new_path);
+            if (copy != NULL) {
+                set_sdcard_update_bootloader_message();
+                result = install_package(copy);
+                free(copy);
+            }
+            break;
+        }
+    } while (true);
+
+    int i;
+    for (i = 0; i < z_size; ++i) free(zips[i]);
+    free(zips);
+    free(headers);
+
+    return result;
+}
+
 static void
 wipe_data(int confirm) {
     if (confirm) {
@@ -478,7 +601,7 @@
                                 "  THIS CAN NOT BE UNDONE.",
                                 "",
                                 NULL };
-            title_headers = prepend_title(headers);
+            title_headers = prepend_title((const char**)headers);
         }
 
         char* items[] = { " No",
@@ -494,7 +617,7 @@
                           " No",
                           NULL };
 
-        int chosen_item = get_menu_selection(title_headers, items, 1);
+        int chosen_item = get_menu_selection(title_headers, items, 1, 0);
         if (chosen_item != 7) {
             return;
         }
@@ -509,13 +632,13 @@
 
 static void
 prompt_and_wait() {
-    char** headers = prepend_title(MENU_HEADERS);
+    char** headers = prepend_title((const char**)MENU_HEADERS);
 
     for (;;) {
         finish_recovery(NULL);
         ui_reset_progress();
 
-        int chosen_item = get_menu_selection(headers, MENU_ITEMS, 0);
+        int chosen_item = get_menu_selection(headers, MENU_ITEMS, 0, 0);
 
         // device-specific code may take some action here.  It may
         // return one of the core actions handled in the switch
@@ -539,21 +662,17 @@
                 break;
 
             case ITEM_APPLY_SDCARD:
-                ui_print("\n-- Install from sdcard...\n");
-                int status = INSTALL_CORRUPT;
-                char* copy = copy_sideloaded_package(SDCARD_PACKAGE_FILE);
-                if (copy != NULL) {
-                  set_sdcard_update_bootloader_message();
-                  status = install_package(copy);
-                  free(copy);
-                }
-                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");
+                ;
+                int status = sdcard_directory(SDCARD_ROOT);
+                if (status >= 0) {
+                    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;
         }
@@ -562,7 +681,7 @@
 
 static void
 print_property(const char *key, const char *name, void *cookie) {
-    fprintf(stderr, "%s=%s\n", key, name);
+    printf("%s=%s\n", key, name);
 }
 
 int
@@ -572,7 +691,7 @@
     // 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);
-    fprintf(stderr, "Starting recovery on %s", ctime(&start));
+    printf("Starting recovery on %s", ctime(&start));
 
     ui_init();
     get_args(&argc, &argv);
@@ -602,14 +721,14 @@
 
     device_recovery_start();
 
-    fprintf(stderr, "Command:");
+    printf("Command:");
     for (arg = 0; arg < argc; arg++) {
-        fprintf(stderr, " \"%s\"", argv[arg]);
+        printf(" \"%s\"", argv[arg]);
     }
-    fprintf(stderr, "\n\n");
+    printf("\n\n");
 
     property_list(print_property, NULL);
-    fprintf(stderr, "\n");
+    printf("\n");
 
     int status = INSTALL_SUCCESS;
 
@@ -665,7 +784,9 @@
     }
 
     if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR);
-    if (status != INSTALL_SUCCESS || ui_text_visible()) prompt_and_wait();
+    if (status != INSTALL_SUCCESS || ui_text_visible()) {
+        prompt_and_wait();
+    }
 
     // Otherwise, get ready to boot the main system...
     finish_recovery(send_intent);
diff --git a/roots.c b/roots.c
index d5754db..762bdf3 100644
--- a/roots.c
+++ b/roots.c
@@ -23,6 +23,11 @@
 
 #include "mtdutils/mtdutils.h"
 #include "mtdutils/mounts.h"
+
+#ifdef USE_EXT4
+#include "make_ext4fs.h"
+#endif
+
 #include "minzip/Zip.h"
 #include "roots.h"
 #include "common.h"
@@ -46,8 +51,6 @@
 
 static RootInfo g_roots[] = {
     { "BOOT:", g_mtd_device, NULL, "boot", NULL, g_raw },
-    { "CACHE:", g_mtd_device, NULL, "cache", "/cache", "yaffs2" },
-    { "DATA:", g_mtd_device, NULL, "userdata", "/data", "yaffs2" },
     { "MISC:", g_mtd_device, NULL, "misc", NULL, g_raw },
     { "PACKAGE:", NULL, NULL, NULL, NULL, g_package_file },
     { "RECOVERY:", g_mtd_device, NULL, "recovery", "/", g_raw },
@@ -55,6 +58,17 @@
     { "SYSTEM:", g_mtd_device, NULL, "system", "/system", "yaffs2" },
     { "MBM:", g_mtd_device, NULL, "mbm", NULL, g_raw },
     { "TMP:", NULL, NULL, NULL, "/tmp", g_ramdisk },
+
+#ifdef USE_EXT4
+    { "CACHE:", "/dev/block/platform/sdhci-tegra.3/by-name/cache", NULL, NULL,
+      "/cache", "ext4" },
+    { "DATA:", "/dev/block/platform/sdhci-tegra.3/by-name/userdata", NULL, NULL,
+      "/data", "ext4" },
+#else
+    { "CACHE:", g_mtd_device, NULL, "cache", "/cache", "yaffs2" },
+    { "DATA:", g_mtd_device, NULL, "userdata", "/data", "yaffs2" },
+#endif
+
 };
 #define NUM_ROOTS (sizeof(g_roots) / sizeof(g_roots[0]))
 
@@ -252,7 +266,7 @@
 
     mkdir(info->mount_point, 0755);  // in case it doesn't already exist
     if (mount(info->device, info->mount_point, info->filesystem,
-            MS_NOATIME | MS_NODEV | MS_NODIRATIME, "")) {
+              MS_NOATIME | MS_NODEV | MS_NODIRATIME, "")) {
         if (info->device2 == NULL) {
             LOGE("Can't mount %s\n(%s)\n", info->device, strerror(errno));
             return -1;
@@ -368,7 +382,21 @@
             }
         }
     }
+
+#ifdef USE_EXT4
+    if (strcmp(info->filesystem, "ext4") == 0) {
+        reset_ext4fs_info();
+        int result = make_ext4fs(info->device, NULL, NULL, 0, 0);
+        if (result != 0) {
+            LOGW("make_ext4fs failed: %d\n", result);
+            return -1;
+        }
+        return 0;
+    }
+#endif
+
 //TODO: handle other device types (sdcard, etc.)
-    LOGW("format_root_device: can't handle non-mtd device \"%s\"\n", root);
+
+    LOGW("format_root_device: unknown device \"%s\"\n", root);
     return -1;
 }
diff --git a/ui.c b/ui.c
index 01a005f..ee6a0c5 100644
--- a/ui.c
+++ b/ui.c
@@ -29,7 +29,7 @@
 #include "minui/minui.h"
 #include "recovery_ui.h"
 
-#define MAX_COLS 64
+#define MAX_COLS 96
 #define MAX_ROWS 32
 
 #define CHAR_WIDTH 10
@@ -404,7 +404,7 @@
     vsnprintf(buf, 256, fmt, ap);
     va_end(ap);
 
-    fputs(buf, stderr);
+    fputs(buf, stdout);
 
     // This can get called before ui_init(), so be careful.
     pthread_mutex_lock(&gUpdateMutex);
@@ -425,7 +425,7 @@
     pthread_mutex_unlock(&gUpdateMutex);
 }
 
-void ui_start_menu(char** headers, char** items) {
+void ui_start_menu(char** headers, char** items, int initial_selection) {
     int i;
     pthread_mutex_lock(&gUpdateMutex);
     if (text_rows > 0 && text_cols > 0) {
@@ -442,7 +442,7 @@
         }
         menu_items = i - menu_top;
         show_menu = 1;
-        menu_sel = 0;
+        menu_sel = initial_selection;
         update_screen_locked();
     }
     pthread_mutex_unlock(&gUpdateMutex);
diff --git a/updater/Android.mk b/updater/Android.mk
index d4a4e33..dcc6a49 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -18,7 +18,13 @@
 
 LOCAL_SRC_FILES := $(updater_src_files)
 
-LOCAL_STATIC_LIBRARIES := $(TARGET_RECOVERY_UPDATER_LIBS) $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS)
+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 += $(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
diff --git a/updater/install.c b/updater/install.c
index e869134..167b402 100644
--- a/updater/install.c
+++ b/updater/install.c
@@ -36,24 +36,35 @@
 #include "updater.h"
 #include "applypatch/applypatch.h"
 
-// mount(type, location, mount_point)
+#ifdef USE_EXT4
+#include "make_ext4fs.h"
+#endif
+
+// mount(fs_type, partition_type, location, mount_point)
 //
-//   what:  type="MTD"   location="<partition>"            to mount a yaffs2 filesystem
-//          type="vfat"  location="/dev/block/<whatever>"  to mount a device
+//    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 != 3) {
-        return ErrorAbort(state, "%s() expects 3 args, got %d", name, argc);
+    if (argc != 4) {
+        return ErrorAbort(state, "%s() expects 4 args, got %d", name, argc);
     }
-    char* type;
+    char* fs_type;
+    char* partition_type;
     char* location;
     char* mount_point;
-    if (ReadArgs(state, argv, 3, &type, &location, &mount_point) < 0) {
+    if (ReadArgs(state, argv, 4, &fs_type, &partition_type,
+                 &location, &mount_point) < 0) {
         return NULL;
     }
 
-    if (strlen(type) == 0) {
-        ErrorAbort(state, "type argument to %s() can't be empty", name);
+    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) {
@@ -67,7 +78,7 @@
 
     mkdir(mount_point, 0755);
 
-    if (strcmp(type, "MTD") == 0) {
+    if (strcmp(partition_type, "MTD") == 0) {
         mtd_scan_partitions();
         const MtdPartition* mtd;
         mtd = mtd_find_partition_by_name(location);
@@ -77,7 +88,7 @@
             result = strdup("");
             goto done;
         }
-        if (mtd_mount_partition(mtd, mount_point, "yaffs2", 0 /* rw */) != 0) {
+        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("");
@@ -85,7 +96,7 @@
         }
         result = mount_point;
     } else {
-        if (mount(location, mount_point, type,
+        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));
@@ -96,7 +107,8 @@
     }
 
 done:
-    free(type);
+    free(fs_type);
+    free(partition_type);
     free(location);
     if (result != mount_point) free(mount_point);
     return StringValue(result);
@@ -162,22 +174,29 @@
 }
 
 
-// format(type, location)
+// format(fs_type, partition_type, location)
 //
-//    type="MTD"  location=partition
+//    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 != 2) {
-        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
+    if (argc != 3) {
+        return ErrorAbort(state, "%s() expects 3 args, got %d", name, argc);
     }
-    char* type;
+    char* fs_type;
+    char* partition_type;
     char* location;
-    if (ReadArgs(state, argv, 2, &type, &location) < 0) {
+    if (ReadArgs(state, argv, 3, &fs_type, &partition_type, &location) < 0) {
         return NULL;
     }
 
-    if (strlen(type) == 0) {
-        ErrorAbort(state, "type argument to %s() can't be empty", name);
+    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) {
@@ -185,7 +204,7 @@
         goto done;
     }
 
-    if (strcmp(type, "MTD") == 0) {
+    if (strcmp(partition_type, "MTD") == 0) {
         mtd_scan_partitions();
         const MtdPartition* mtd = mtd_find_partition_by_name(location);
         if (mtd == NULL) {
@@ -212,12 +231,26 @@
             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);
+        if (status != 0) {
+            fprintf(stderr, "%s: make_ext4fs failed (%d) on %s",
+                    name, status, location);
+            result = strdup("");
+            goto done;
+        }
+        result = location;
+#endif
     } else {
-        fprintf(stderr, "%s: unsupported type \"%s\"", name, type);
+        fprintf(stderr, "%s: unsupported fs_type \"%s\" partition_type \"%s\"",
+                name, fs_type, partition_type);
     }
 
 done:
-    free(type);
+    free(fs_type);
+    free(partition_type);
     if (result != location) free(location);
     return StringValue(result);
 }