am 5c4e1dac: Merge "Update libpng API usage"

* commit '5c4e1dac0bd8f31559bd50eec4343b7a17b7a976':
  Update libpng API usage
diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c
index 0dcdce0..6b8da2a 100644
--- a/applypatch/applypatch.c
+++ b/applypatch/applypatch.c
@@ -101,7 +101,7 @@
         }
     }
 
-    SHA(file->data, file->size, file->sha1);
+    SHA_hash(file->data, file->size, file->sha1);
     return 0;
 }
 
diff --git a/edify/expr.c b/edify/expr.c
index 07a8ceb..a2f1f99 100644
--- a/edify/expr.c
+++ b/edify/expr.c
@@ -287,13 +287,13 @@
 
     long l_int = strtol(left, &end, 10);
     if (left[0] == '\0' || *end != '\0') {
-        fprintf(stderr, "[%s] is not an int\n", left);
+        printf("[%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);
+        printf("[%s] is not an int\n", right);
         goto done;
     }
 
diff --git a/edify/main.c b/edify/main.c
index 8557043..9e6bab7 100644
--- a/edify/main.c
+++ b/edify/main.c
@@ -34,8 +34,8 @@
     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);
+        printf("error parsing \"%s\" (%d errors)\n",
+               expr_str, error_count);
         ++*errors;
         return 0;
     }
@@ -49,7 +49,7 @@
     free(state.errmsg);
     free(state.script);
     if (result == NULL && expected != NULL) {
-        fprintf(stderr, "error evaluating \"%s\"\n", expr_str);
+        printf("error evaluating \"%s\"\n", expr_str);
         ++*errors;
         return 0;
     }
@@ -59,8 +59,8 @@
     }
 
     if (strcmp(result, expected) != 0) {
-        fprintf(stderr, "evaluating \"%s\": expected \"%s\", got \"%s\"\n",
-                expr_str, expected, result);
+        printf("evaluating \"%s\": expected \"%s\", got \"%s\"\n",
+               expr_str, expected, result);
         ++*errors;
         free(result);
         return 0;
diff --git a/etc/init.rc b/etc/init.rc
index abc7b31..b26d2ae 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -2,6 +2,7 @@
 
 on early-init
     start ueventd
+    start healthd
 
 on init
     export PATH /sbin
@@ -40,6 +41,9 @@
 service ueventd /sbin/ueventd
     critical
 
+service healthd /sbin/healthd -n
+    critical
+
 service recovery /sbin/recovery
 
 service adbd /sbin/adbd recovery
diff --git a/install.cpp b/install.cpp
index 0f3298f..797a525 100644
--- a/install.cpp
+++ b/install.cpp
@@ -154,6 +154,7 @@
             } else {
                 ui->Print("\n");
             }
+            fflush(stdout);
         } else if (strcmp(command, "wipe_cache") == 0) {
             *wipe_cache = 1;
         } else if (strcmp(command, "clear_display") == 0) {
@@ -179,7 +180,9 @@
 {
     ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
     ui->Print("Finding update package...\n");
-    ui->SetProgressType(RecoveryUI::INDETERMINATE);
+    // Give verification half the progress bar...
+    ui->SetProgressType(RecoveryUI::DETERMINATE);
+    ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
     LOGI("Update location: %s\n", path);
 
     if (ensure_path_mounted(path) != 0) {
@@ -190,17 +193,14 @@
     ui->Print("Opening update package...\n");
 
     int numKeys;
-    RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
+    Certificate* 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->SetProgressType(RecoveryUI::DETERMINATE);
-    ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
 
     int err;
     err = verify_file(path, loadedKeys, numKeys);
@@ -236,7 +236,13 @@
     } else {
         LOGE("failed to open last_install: %s\n", strerror(errno));
     }
-    int result = really_install_package(path, wipe_cache);
+    int result;
+    if (setup_install_mounts() != 0) {
+        LOGE("failed to set up expected mounts for install; aborting\n");
+        result = INSTALL_ERROR;
+    } else {
+        result = really_install_package(path, wipe_cache);
+    }
     if (install_log) {
         fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);
         fputc('\n', install_log);
diff --git a/minui/graphics.c b/minui/graphics.c
index 4968eac..d757165 100644
--- a/minui/graphics.c
+++ b/minui/graphics.c
@@ -385,8 +385,8 @@
 
     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);
+    printf("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;
diff --git a/minzip/DirUtil.c b/minzip/DirUtil.c
index 8dd5da1..c120fa3 100644
--- a/minzip/DirUtil.c
+++ b/minzip/DirUtil.c
@@ -23,6 +23,7 @@
 #include <errno.h>
 #include <dirent.h>
 #include <limits.h>
+#include <selinux/selinux.h>
 
 #include "DirUtil.h"
 
@@ -237,7 +238,7 @@
 
 int
 dirSetHierarchyPermissions(const char *path,
-        int uid, int gid, int dirMode, int fileMode)
+        int uid, int gid, int dirMode, int fileMode, const char* secontext)
 {
     struct stat st;
     if (lstat(path, &st)) {
@@ -255,6 +256,10 @@
         return -1;
     }
 
+    if ((secontext != NULL) && lsetfilecon(path, secontext) && (errno != ENOTSUP)) {
+        return -1;
+    }
+
     /* recurse over directory components */
     if (S_ISDIR(st.st_mode)) {
         DIR *dir = opendir(path);
@@ -271,7 +276,7 @@
 
             char dn[PATH_MAX];
             snprintf(dn, sizeof(dn), "%s/%s", path, de->d_name);
-            if (!dirSetHierarchyPermissions(dn, uid, gid, dirMode, fileMode)) {
+            if (!dirSetHierarchyPermissions(dn, uid, gid, dirMode, fileMode, secontext)) {
                 errno = 0;
             } else if (errno == 0) {
                 errno = -1;
diff --git a/minzip/DirUtil.h b/minzip/DirUtil.h
index a5cfa76..3e12a0b 100644
--- a/minzip/DirUtil.h
+++ b/minzip/DirUtil.h
@@ -54,7 +54,7 @@
  * Sets directories to <dirMode> and files to <fileMode>.  Skips symlinks.
  */
 int dirSetHierarchyPermissions(const char *path,
-         int uid, int gid, int dirMode, int fileMode);
+         int uid, int gid, int dirMode, int fileMode, const char* secontext);
 
 #ifdef __cplusplus
 }
diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c
index 107cbb9..d04b26e 100644
--- a/mtdutils/mtdutils.c
+++ b/mtdutils/mtdutils.c
@@ -289,7 +289,7 @@
 {
     struct mtd_ecc_stats before, after;
     if (ioctl(fd, ECCGETSTATS, &before)) {
-        fprintf(stderr, "mtd: ECCGETSTATS error (%s)\n", strerror(errno));
+        printf("mtd: ECCGETSTATS error (%s)\n", strerror(errno));
         return -1;
     }
 
@@ -300,13 +300,13 @@
 
     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",
+            printf("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));
+            printf("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",
+            printf("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.
@@ -431,39 +431,39 @@
         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",
+                printf("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",
+                printf("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",
+                printf("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",
+                printf("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);
+                printf("mtd: wrote block after %d retries\n", retry);
             }
-            fprintf(stderr, "mtd: successfully wrote block at %lx\n", pos);
+            printf("mtd: successfully wrote block at %lx\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);
+        printf("mtd: skipping write block at 0x%08lx\n", pos);
         ioctl(fd, MEMERASE, &erase_info);
         pos += partition->erase_size;
     }
@@ -526,7 +526,7 @@
     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);
+            printf("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.
         }
@@ -535,7 +535,7 @@
         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);
+            printf("mtd: erase failure at 0x%08lx\n", pos);
         }
         pos += ctx->partition->erase_size;
     }
diff --git a/recovery.cpp b/recovery.cpp
index c82844d..654a665 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -61,6 +61,7 @@
 
 #define LAST_LOG_FILE "/cache/recovery/last_log"
 
+static const char *CACHE_LOG_DIR = "/cache/recovery";
 static const char *COMMAND_FILE = "/cache/recovery/command";
 static const char *INTENT_FILE = "/cache/recovery/intent";
 static const char *LOG_FILE = "/cache/recovery/log";
@@ -74,6 +75,7 @@
 
 RecoveryUI* ui = NULL;
 char* locale = NULL;
+char recovery_version[PROPERTY_VALUE_MAX+1];
 
 /*
  * The recovery tool communicates with the main system through /cache files.
@@ -283,6 +285,19 @@
     }
 }
 
+static void
+copy_logs() {
+    // Copy logs to cache so the system can find out what happened.
+    copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
+    copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);
+    copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
+    chmod(LOG_FILE, 0600);
+    chown(LOG_FILE, 1000, 1000);   // system user
+    chmod(LAST_LOG_FILE, 0640);
+    chmod(LAST_INSTALL_FILE, 0644);
+    sync();
+}
+
 // 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.
@@ -312,14 +327,7 @@
         check_and_fclose(fp, LOCALE_FILE);
     }
 
-    // Copy logs to cache so the system can find out what happened.
-    copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
-    copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);
-    copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
-    chmod(LOG_FILE, 0600);
-    chown(LOG_FILE, 1000, 1000);   // system user
-    chmod(LAST_LOG_FILE, 0640);
-    chmod(LAST_INSTALL_FILE, 0644);
+    copy_logs();
 
     // Reset to normal system boot so recovery won't cycle indefinitely.
     struct bootloader_message boot;
@@ -336,22 +344,95 @@
     sync();  // For good measure.
 }
 
+typedef struct _saved_log_file {
+    char* name;
+    struct stat st;
+    unsigned char* data;
+    struct _saved_log_file* next;
+} saved_log_file;
+
 static int
 erase_volume(const char *volume) {
+    bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
+
     ui->SetBackground(RecoveryUI::ERASING);
     ui->SetProgressType(RecoveryUI::INDETERMINATE);
+
+    saved_log_file* head = NULL;
+
+    if (is_cache) {
+        // If we're reformatting /cache, we load any
+        // "/cache/recovery/last*" files into memory, so we can restore
+        // them after the reformat.
+
+        ensure_path_mounted(volume);
+
+        DIR* d;
+        struct dirent* de;
+        d = opendir(CACHE_LOG_DIR);
+        if (d) {
+            char path[PATH_MAX];
+            strcpy(path, CACHE_LOG_DIR);
+            strcat(path, "/");
+            int path_len = strlen(path);
+            while ((de = readdir(d)) != NULL) {
+                if (strncmp(de->d_name, "last", 4) == 0) {
+                    saved_log_file* p = (saved_log_file*) malloc(sizeof(saved_log_file));
+                    strcpy(path+path_len, de->d_name);
+                    p->name = strdup(path);
+                    if (stat(path, &(p->st)) == 0) {
+                        // truncate files to 512kb
+                        if (p->st.st_size > (1 << 19)) {
+                            p->st.st_size = 1 << 19;
+                        }
+                        p->data = (unsigned char*) malloc(p->st.st_size);
+                        FILE* f = fopen(path, "rb");
+                        fread(p->data, 1, p->st.st_size, f);
+                        fclose(f);
+                        p->next = head;
+                        head = p;
+                    } else {
+                        free(p);
+                    }
+                }
+            }
+            closedir(d);
+        } else {
+            if (errno != ENOENT) {
+                printf("opendir failed: %s\n", strerror(errno));
+            }
+        }
+    }
+
     ui->Print("Formatting %s...\n", volume);
 
     ensure_path_unmounted(volume);
+    int result = format_volume(volume);
 
-    if (strcmp(volume, "/cache") == 0) {
+    if (is_cache) {
+        while (head) {
+            FILE* f = fopen_path(head->name, "wb");
+            if (f) {
+                fwrite(head->data, 1, head->st.st_size, f);
+                fclose(f);
+                chmod(head->name, head->st.st_mode);
+                chown(head->name, head->st.st_uid, head->st.st_gid);
+            }
+            free(head->name);
+            free(head->data);
+            saved_log_file* temp = head->next;
+            free(head);
+            head = temp;
+        }
+
         // 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;
+        copy_logs();
     }
 
-    return format_volume(volume);
+    return result;
 }
 
 static char*
@@ -446,21 +527,17 @@
 
 static const char**
 prepend_title(const char* const* headers) {
-    const char* title[] = { "Android system recovery <"
-                            EXPAND(RECOVERY_API_VERSION) "e>",
-                            "",
-                            NULL };
-
     // count the number of lines in our title, plus the
     // caller-provided headers.
-    int count = 0;
+    int count = 3;   // our title has 3 lines
     const char* const* p;
-    for (p = title; *p; ++p, ++count);
     for (p = headers; *p; ++p, ++count);
 
     const char** new_headers = (const char**)malloc((count+1) * sizeof(char*));
     const char** h = new_headers;
-    for (p = title; *p; ++p, ++h) *h = *p;
+    *(h++) = "Android system recovery <" EXPAND(RECOVERY_API_VERSION) "e>";
+    *(h++) = recovery_version;
+    *(h++) = "";
     for (p = headers; *p; ++p, ++h) *h = *p;
     *h = NULL;
 
@@ -734,10 +811,6 @@
                 break;
 
             case Device::APPLY_EXT:
-                // Some packages expect /cache to be mounted (eg,
-                // standard incremental packages expect to use /cache
-                // as scratch space).
-                ensure_path_mounted(CACHE_ROOT);
                 status = update_directory(SDCARD_ROOT, SDCARD_ROOT, &wipe_cache, device);
                 if (status == INSTALL_SUCCESS && wipe_cache) {
                     ui->Print("\n-- Wiping cache (at package request)...\n");
@@ -783,12 +856,12 @@
                 break;
 
             case Device::APPLY_ADB_SIDELOAD:
-                ensure_path_mounted(CACHE_ROOT);
                 status = apply_from_adb(ui, &wipe_cache, TEMPORARY_INSTALL_FILE);
                 if (status >= 0) {
                     if (status != INSTALL_SUCCESS) {
                         ui->SetBackground(RecoveryUI::ERROR);
                         ui->Print("Installation aborted.\n");
+                        copy_logs();
                     } else if (!ui->IsTextVisible()) {
                         return;  // reboot if logs aren't visible
                     } else {
@@ -866,7 +939,7 @@
 
     load_volume_table();
     ensure_path_mounted(LAST_LOG_FILE);
-    rotate_last_logs(5);
+    rotate_last_logs(10);
     get_args(&argc, &argv);
 
     int previous_runs = 0;
@@ -913,8 +986,7 @@
     sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);
 
     if (!sehandle) {
-        fprintf(stderr, "Warning: No file_contexts\n");
-        ui->Print("Warning:  No file_contexts\n");
+        ui->Print("Warning: No file_contexts\n");
     }
 
     device->StartRecovery();
@@ -942,6 +1014,7 @@
     printf("\n");
 
     property_list(print_property, NULL);
+    property_get("ro.build.display.id", recovery_version, "");
     printf("\n");
 
     int status = INSTALL_SUCCESS;
@@ -979,6 +1052,7 @@
     }
 
     if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
+        copy_logs();
         ui->SetBackground(RecoveryUI::ERROR);
     }
     if (status != INSTALL_SUCCESS || ui->IsTextVisible()) {
diff --git a/roots.cpp b/roots.cpp
index 0947122..113dba1 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -202,3 +202,22 @@
     LOGE("format_volume: fs_type \"%s\" unsupported\n", v->fs_type);
     return -1;
 }
+
+int setup_install_mounts() {
+    if (fstab == NULL) {
+        LOGE("can't set up install mounts: no fstab loaded\n");
+        return -1;
+    }
+    for (int i = 0; i < fstab->num_entries; ++i) {
+        Volume* v = fstab->recs + i;
+
+        if (strcmp(v->mount_point, "/tmp") == 0 ||
+            strcmp(v->mount_point, "/cache") == 0) {
+            if (ensure_path_mounted(v->mount_point) != 0) return -1;
+
+        } else {
+            if (ensure_path_unmounted(v->mount_point) != 0) return -1;
+        }
+    }
+    return 0;
+}
diff --git a/roots.h b/roots.h
index 8abe18f..230d9de 100644
--- a/roots.h
+++ b/roots.h
@@ -42,6 +42,10 @@
 // it is mounted.
 int format_volume(const char* volume);
 
+// Ensure that all and only the volumes that packages expect to find
+// mounted (/tmp and /cache) are mounted.  Returns 0 on success.
+int setup_install_mounts();
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/screen_ui.cpp b/screen_ui.cpp
index 93e2609..8376341 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -196,9 +196,29 @@
     }
 }
 
-#define C_HEADER  247,0,6
-#define C_MENU    0,106,157
-#define C_LOG     249,194,0
+void ScreenRecoveryUI::SetColor(UIElement e) {
+    switch (e) {
+        case HEADER:
+            gr_color(247, 0, 6, 255);
+            break;
+        case MENU:
+        case MENU_SEL_BG:
+            gr_color(0, 106, 157, 255);
+            break;
+        case MENU_SEL_FG:
+            gr_color(255, 255, 255, 255);
+            break;
+        case LOG:
+            gr_color(249, 194, 0, 255);
+            break;
+        case TEXT_FILL:
+            gr_color(0, 0, 0, 160);
+            break;
+        default:
+            gr_color(255, 255, 255, 255);
+            break;
+    }
+}
 
 // Redraw everything on the screen.  Does not flip pages.
 // Should only be called with updateMutex locked.
@@ -208,37 +228,38 @@
     draw_progress_locked();
 
     if (show_text) {
-        gr_color(0, 0, 0, 160);
+        SetColor(TEXT_FILL);
         gr_fill(0, 0, gr_fb_width(), gr_fb_height());
 
         int y = 0;
         int i = 0;
         if (show_menu) {
-            gr_color(C_HEADER, 255);
+            SetColor(HEADER);
 
             for (; i < menu_top + menu_items; ++i) {
-                if (i == menu_top) gr_color(C_MENU, 255);
+                if (i == menu_top) SetColor(MENU);
 
                 if (i == menu_top + menu_sel) {
                     // draw the highlight bar
+                    SetColor(MENU_SEL_BG);
                     gr_fill(0, y-2, gr_fb_width(), y+char_height+2);
                     // white text of selected item
-                    gr_color(255, 255, 255, 255);
+                    SetColor(MENU_SEL_FG);
                     if (menu[i][0]) gr_text(4, y, menu[i], 1);
-                    gr_color(C_MENU, 255);
+                    SetColor(MENU);
                 } else {
                     if (menu[i][0]) gr_text(4, y, menu[i], i < menu_top);
                 }
                 y += char_height+4;
             }
-            gr_color(C_MENU, 255);
+            SetColor(MENU);
             y += 4;
             gr_fill(0, y, gr_fb_width(), y+2);
             y += 4;
             ++i;
         }
 
-        gr_color(C_LOG, 255);
+        SetColor(LOG);
 
         // display from the bottom up, until we hit the top of the
         // screen, the bottom of the menu, or we've displayed the
@@ -446,10 +467,11 @@
     pthread_mutex_lock(&updateMutex);
     if (progressBarType != type) {
         progressBarType = type;
-        update_progress_locked();
     }
     progressScopeStart = 0;
+    progressScopeSize = 0;
     progress = 0;
+    update_progress_locked();
     pthread_mutex_unlock(&updateMutex);
 }
 
@@ -585,3 +607,10 @@
     update_screen_locked();
     pthread_mutex_unlock(&updateMutex);
 }
+
+void ScreenRecoveryUI::Redraw()
+{
+    pthread_mutex_lock(&updateMutex);
+    update_screen_locked();
+    pthread_mutex_unlock(&updateMutex);
+}
diff --git a/screen_ui.h b/screen_ui.h
index fe0de46..0bd220f 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -53,6 +53,11 @@
     int SelectMenu(int sel);
     void EndMenu();
 
+    void Redraw();
+
+    enum UIElement { HEADER, MENU, MENU_SEL_BG, MENU_SEL_FG, LOG, TEXT_FILL };
+    virtual void SetColor(UIElement e);
+
   private:
     Icon currentIcon;
     int installingFrame;
diff --git a/testdata/otasigned_f4_sha256.zip b/testdata/otasigned_f4_sha256.zip
new file mode 100644
index 0000000..3af408c
--- /dev/null
+++ b/testdata/otasigned_f4_sha256.zip
Binary files differ
diff --git a/testdata/otasigned_sha256.zip b/testdata/otasigned_sha256.zip
new file mode 100644
index 0000000..0ed4409
--- /dev/null
+++ b/testdata/otasigned_sha256.zip
Binary files differ
diff --git a/testdata/test_f4_sha256.x509.pem b/testdata/test_f4_sha256.x509.pem
new file mode 100644
index 0000000..9d5376b
--- /dev/null
+++ b/testdata/test_f4_sha256.x509.pem
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIJAKhkCO1dDYMaMA0GCSqGSIb3DQEBCwUAMG8xCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBW
+aWV3MQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMT
+B1Rlc3QxMjMwHhcNMTMwNDEwMTcyMzUyWhcNMTMwNTEwMTcyMzUyWjBvMQswCQYD
+VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
+VmlldzEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRAwDgYDVQQD
+EwdUZXN0MTIzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu8WwMN9x
+4Mz7YgkG2qy9g8/kl5ZoYrUM0ApHhaITAcL7RXLZaNipCf0w/YjYTQgj+75MK30x
+TsnPeWNOEwA62gkHrZyyWfxBRO6kBYuIuI4roGDBJOmKQ1OEaDeIRKu7q5V8v3Cs
+0wQDAQWTbhpxBZr9UYFgJUg8XWBfPrGJLVwsoiy4xrMhoTlNZKHfwOMMqVtSHkZX
+qydYrcIzyjh+TO0e/xSNQ8MMRRbtqWgCHN6Rzhog3IHZu0RaPoukariopjXM/s0V
+gTm3rHDHCOpna2pNblyiFlvbkoCs769mtNmx/yrDShO30jg/xaG8RypKDvTChzOT
+oWW/XQ5VEXjbHwIDAQABo4HUMIHRMB0GA1UdDgQWBBRlT2dEZJY1tmUM8mZ0xnhS
+GdD9TTCBoQYDVR0jBIGZMIGWgBRlT2dEZJY1tmUM8mZ0xnhSGdD9TaFzpHEwbzEL
+MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50
+YWluIFZpZXcxDzANBgNVBAoTBkdvb2dsZTEQMA4GA1UECxMHQW5kcm9pZDEQMA4G
+A1UEAxMHVGVzdDEyM4IJAKhkCO1dDYMaMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN
+AQELBQADggEBAKWWQ9S0V9wWjrMJe8exj1gklwD1Ysi0vi+h2tfixahelrpsNkWi
+EFjoUSHEkW9ThLmtui646uAlwSiWtSn1XkGGmIJ3s+gmAFUcMc0CaK0dgoq/M9zn
+fQ0Vkzc1tK4MLsf+CbPDywPycb6+T3dBkerbWn9GUpjGl1ANWlciXZZ3657m61sL
+HhwUOBxbZZ6sYP4ed2SVCf45GgMyJ0VoUg5yI2JzPAgOkGfeEIPVXE1M94edJY4G
+8eHYvXovJZwXvKFI+ZyS0KBPx8cpfw89RB9qmkxqNBIm8qWb3qBiuBEIPj+NF/7w
+sC/Fv8NNXkVquy0xa0qdyJBABzWE18zGcXs=
+-----END CERTIFICATE-----
diff --git a/testdata/testkey.pk8 b/testdata/testkey.pk8
new file mode 100644
index 0000000..586c1bd
--- /dev/null
+++ b/testdata/testkey.pk8
Binary files differ
diff --git a/testdata/testkey.x509.pem b/testdata/testkey.x509.pem
new file mode 100644
index 0000000..e242d83
--- /dev/null
+++ b/testdata/testkey.x509.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD
+VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
+VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
+AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
+Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET
+MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
+A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
+ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
+hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM
+qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4
+wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy
+4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU
+RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s
+zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw
+HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ
+AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
+CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
+QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
+CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud
+EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa
+J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y
+LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe
++ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX
+31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr
+sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0=
+-----END CERTIFICATE-----
diff --git a/testdata/testkey_sha256.x509.pem b/testdata/testkey_sha256.x509.pem
new file mode 100644
index 0000000..002ce89
--- /dev/null
+++ b/testdata/testkey_sha256.x509.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD
+VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
+VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
+AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
+Fw0xMzA0MTAxODA1MzZaFw0xMzA1MTAxODA1MzZaMIGUMQswCQYDVQQGEwJVUzET
+MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
+A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
+ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
+hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM
+qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4
+wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy
+4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU
+RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s
+zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw
+HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ
+AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
+CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
+QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
+CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud
+EwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKRVj9hOaozH1W8Wb4CNj7sCWixh
+UMMZJXkxUtvUVHZGefp6MdtYiD/ZM7YRwZphm9aNhkykbHJdZ3lPzeL2csCa+sDQ
+8sIzGu0/aD6p4zgIKQZmz0mZHqPGbHoLWOmA9EexRCFZ7vO/kO56ZbyhfFz2DI3S
+Yez65CabErOFhNX6WukSPbV3zfsHRDD5JUStb/ko6t99HXsvIO0Ax9poj60PpCC1
+SiFzHZUY9mOnUfJFs+3NWCwKtP9nho3mZ3pJ1i+SeF6JiqbE3KHl4CDBeVGcu3CK
+fiUZ8e8iXVN471Cgc5GD6Ud1pS7ifNZJsKhbETQ63KmvHCLRPi4NmP67uDE=
+-----END CERTIFICATE-----
diff --git a/ui.cpp b/ui.cpp
index 65f4028..cece02d 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -46,7 +46,8 @@
 RecoveryUI::RecoveryUI() :
     key_queue_len(0),
     key_last_down(-1),
-    key_down_time(0) {
+    key_long_press(false),
+    key_down_count(0) {
     pthread_mutex_init(&key_queue_mutex, NULL);
     pthread_cond_init(&key_queue_cond, NULL);
     self = this;
@@ -112,19 +113,22 @@
     bool register_key = false;
     bool long_press = false;
 
-    const long long_threshold = CLOCKS_PER_SEC * 750 / 1000;
-
     pthread_mutex_lock(&key_queue_mutex);
     key_pressed[key_code] = updown;
     if (updown) {
+        ++key_down_count;
         key_last_down = key_code;
-        key_down_time = clock();
+        key_long_press = false;
+        pthread_t th;
+        key_timer_t* info = new key_timer_t;
+        info->ui = this;
+        info->key_code = key_code;
+        info->count = key_down_count;
+        pthread_create(&th, NULL, &RecoveryUI::time_key_helper, info);
+        pthread_detach(th);
     } else {
         if (key_last_down == key_code) {
-            long duration = clock() - key_down_time;
-            if (duration > long_threshold) {
-                long_press = true;
-            }
+            long_press = key_long_press;
             register_key = true;
         }
         key_last_down = -1;
@@ -152,6 +156,24 @@
     }
 }
 
+void* RecoveryUI::time_key_helper(void* cookie) {
+    key_timer_t* info = (key_timer_t*) cookie;
+    info->ui->time_key(info->key_code, info->count);
+    delete info;
+    return NULL;
+}
+
+void RecoveryUI::time_key(int key_code, int count) {
+    usleep(750000);  // 750 ms == "long"
+    bool long_press = false;
+    pthread_mutex_lock(&key_queue_mutex);
+    if (key_last_down == key_code && key_down_count == count) {
+        long_press = key_long_press = true;
+    }
+    pthread_mutex_unlock(&key_queue_mutex);
+    if (long_press) KeyLongPress(key_code);
+}
+
 void RecoveryUI::EnqueueKey(int key_code) {
     pthread_mutex_lock(&key_queue_mutex);
     const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);
@@ -242,3 +264,6 @@
 
 void RecoveryUI::NextCheckKeyIsLong(bool is_long_press) {
 }
+
+void RecoveryUI::KeyLongPress(int key) {
+}
diff --git a/ui.h b/ui.h
index aca7b7b..6c8987a 100644
--- a/ui.h
+++ b/ui.h
@@ -80,8 +80,17 @@
     enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE };
     virtual KeyAction CheckKey(int key);
 
+    // Called immediately before each call to CheckKey(), tell you if
+    // the key was long-pressed.
     virtual void NextCheckKeyIsLong(bool is_long_press);
 
+    // Called when a key is held down long enough to have been a
+    // long-press (but before the key is released).  This means that
+    // if the key is eventually registered (released without any other
+    // keys being pressed in the meantime), NextCheckKeyIsLong() will
+    // be called with "true".
+    virtual void KeyLongPress(int key);
+
     // --- menu display ---
 
     // Display some header text followed by a menu of items, which appears
@@ -108,15 +117,25 @@
     int key_queue[256], key_queue_len;
     char key_pressed[KEY_MAX + 1];     // under key_queue_mutex
     int key_last_down;                 // under key_queue_mutex
-    clock_t key_down_time;             // under key_queue_mutex
+    bool key_long_press;               // under key_queue_mutex
+    int key_down_count;                // under key_queue_mutex
     int rel_sum;
 
+    typedef struct {
+        RecoveryUI* ui;
+        int key_code;
+        int count;
+    } key_timer_t;
+
     pthread_t input_t;
 
     static void* input_thread(void* cookie);
     static int input_callback(int fd, short revents, void* data);
     void process_key(int key_code, int updown);
     bool usb_connected();
+
+    static void* time_key_helper(void* cookie);
+    void time_key(int key_code, int count);
 };
 
 #endif  // RECOVERY_UI_H
diff --git a/updater/install.c b/updater/install.c
index 1905423..c81bbb5 100644
--- a/updater/install.c
+++ b/updater/install.c
@@ -27,6 +27,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <time.h>
+#include <selinux/selinux.h>
 
 #include "cutils/misc.h"
 #include "cutils/properties.h"
@@ -97,13 +98,13 @@
         const MtdPartition* mtd;
         mtd = mtd_find_partition_by_name(location);
         if (mtd == NULL) {
-            fprintf(stderr, "%s: no mtd partition named \"%s\"",
+            printf("%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",
+            printf("mtd mount of %s failed: %s\n",
                     location, strerror(errno));
             result = strdup("");
             goto done;
@@ -112,7 +113,7 @@
     } 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",
+            printf("%s: failed to mount %s at %s: %s\n",
                     name, location, mount_point, strerror(errno));
             result = strdup("");
         } else {
@@ -175,7 +176,7 @@
     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);
+        printf("unmount of %s failed; no such volume\n", mount_point);
         result = strdup("");
     } else {
         unmount_mounted_volume(vol);
@@ -233,25 +234,25 @@
         mtd_scan_partitions();
         const MtdPartition* mtd = mtd_find_partition_by_name(location);
         if (mtd == NULL) {
-            fprintf(stderr, "%s: no mtd partition named \"%s\"",
+            printf("%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);
+            printf("%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);
+            printf("%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);
+            printf("%s: failed to close \"%s\"", name, location);
             result = strdup("");
             goto done;
         }
@@ -260,7 +261,7 @@
     } else if (strcmp(fs_type, "ext4") == 0) {
         int status = make_ext4fs(location, atoll(fs_size), mount_point, sehandle);
         if (status != 0) {
-            fprintf(stderr, "%s: make_ext4fs failed (%d) on %s",
+            printf("%s: make_ext4fs failed (%d) on %s",
                     name, status, location);
             result = strdup("");
             goto done;
@@ -268,7 +269,7 @@
         result = location;
 #endif
     } else {
-        fprintf(stderr, "%s: unsupported fs_type \"%s\" partition_type \"%s\"",
+        printf("%s: unsupported fs_type \"%s\" partition_type \"%s\"",
                 name, fs_type, partition_type);
     }
 
@@ -394,13 +395,13 @@
         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);
+            printf("%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",
+            printf("%s: can't open %s for write: %s\n",
                     name, dest_path, strerror(errno));
             goto done2;
         }
@@ -426,14 +427,14 @@
         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);
+            printf("%s: no %s in package\n", name, zip_path);
             goto done1;
         }
 
         v->size = mzGetZipEntryUncompLen(entry);
         v->data = malloc(v->size);
         if (v->data == NULL) {
-            fprintf(stderr, "%s: failed to allocate %ld bytes for %s\n",
+            printf("%s: failed to allocate %ld bytes for %s\n",
                     name, (long)v->size, zip_path);
             goto done1;
         }
@@ -460,13 +461,13 @@
         *p = '\0';
         if (make_parents(name) < 0) return -1;
         int result = mkdir(name, 0700);
-        if (result == 0) fprintf(stderr, "symlink(): created [%s]\n", name);
+        if (result == 0) printf("symlink(): created [%s]\n", name);
         *p = '/';
         if (result == 0 || errno == EEXIST) {
             // successfully created or already existed; we're done
             return 0;
         } else {
-            fprintf(stderr, "failed to mkdir %s: %s\n", name, strerror(errno));
+            printf("failed to mkdir %s: %s\n", name, strerror(errno));
             return -1;
         }
     }
@@ -494,18 +495,18 @@
     for (i = 0; i < argc-1; ++i) {
         if (unlink(srcs[i]) < 0) {
             if (errno != ENOENT) {
-                fprintf(stderr, "%s: failed to remove %s: %s\n",
+                printf("%s: failed to remove %s: %s\n",
                         name, srcs[i], strerror(errno));
                 ++bad;
             }
         }
         if (make_parents(srcs[i])) {
-            fprintf(stderr, "%s: failed to symlink %s to %s: making parents failed\n",
+            printf("%s: failed to symlink %s to %s: making parents failed\n",
                     name, srcs[i], target);
             ++bad;
         }
         if (symlink(target, srcs[i]) < 0) {
-            fprintf(stderr, "%s: failed to symlink %s to %s: %s\n",
+            printf("%s: failed to symlink %s to %s: %s\n",
                     name, srcs[i], target, strerror(errno));
             ++bad;
         }
@@ -521,9 +522,10 @@
 
 Value* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
-    bool recursive = (strcmp(name, "set_perm_recursive") == 0);
+    bool recursive = (strcmp(name, "set_perm_recursive") == 0) || (strcmp(name, "set_perm2_recursive") == 0);
+    bool has_selabel = (strcmp(name, "set_perm2") == 0) || (strcmp(name, "set_perm2_recursive") == 0);
 
-    int min_args = 4 + (recursive ? 1 : 0);
+    int min_args = 4 + (has_selabel ? 1 : 0) + (recursive ? 1 : 0);
     if (argc < min_args) {
         return ErrorAbort(state, "%s() expects %d+ args, got %d",
                           name, min_args, argc);
@@ -562,8 +564,13 @@
             goto done;
         }
 
-        for (i = 4; i < argc; ++i) {
-            dirSetHierarchyPermissions(args[i], uid, gid, dir_mode, file_mode);
+        char* secontext = NULL;
+        if (has_selabel) {
+            secontext = args[4];
+        }
+
+        for (i = 4 + (has_selabel ? 1 : 0); i < argc; ++i) {
+            dirSetHierarchyPermissions(args[i], uid, gid, dir_mode, file_mode, secontext);
         }
     } else {
         int mode = strtoul(args[2], &end, 0);
@@ -572,17 +579,27 @@
             goto done;
         }
 
-        for (i = 3; i < argc; ++i) {
+        char* secontext = NULL;
+        if (has_selabel) {
+            secontext = args[3];
+        }
+
+        for (i = 3 + (has_selabel ? 1 : 0); i < argc; ++i) {
             if (chown(args[i], uid, gid) < 0) {
-                fprintf(stderr, "%s: chown of %s to %d %d failed: %s\n",
+                printf("%s: chown of %s to %d %d failed: %s\n",
                         name, args[i], uid, gid, strerror(errno));
                 ++bad;
             }
             if (chmod(args[i], mode) < 0) {
-                fprintf(stderr, "%s: chmod of %s to %o failed: %s\n",
+                printf("%s: chmod of %s to %o failed: %s\n",
                         name, args[i], mode, strerror(errno));
                 ++bad;
             }
+            if (has_selabel && lsetfilecon(args[i], secontext) && (errno != ENOTSUP)) {
+                printf("%s: lsetfilecon of %s to %s failed: %s\n",
+                        name, args[i], secontext, strerror(errno));
+                ++bad;
+            }
         }
     }
     result = strdup("");
@@ -720,7 +737,7 @@
                                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));
+    printf("%s\n", strerror(errno));
     return false;
 }
 
@@ -752,14 +769,14 @@
     mtd_scan_partitions();
     const MtdPartition* mtd = mtd_find_partition_by_name(partition);
     if (mtd == NULL) {
-        fprintf(stderr, "%s: no mtd partition named \"%s\"\n", name, partition);
+        printf("%s: no mtd partition named \"%s\"\n", name, partition);
         result = strdup("");
         goto done;
     }
 
     MtdWriteContext* ctx = mtd_write_partition(mtd);
     if (ctx == NULL) {
-        fprintf(stderr, "%s: can't write mtd partition \"%s\"\n",
+        printf("%s: can't write mtd partition \"%s\"\n",
                 name, partition);
         result = strdup("");
         goto done;
@@ -772,7 +789,7 @@
         char* filename = contents->data;
         FILE* f = fopen(filename, "rb");
         if (f == NULL) {
-            fprintf(stderr, "%s: can't open %s: %s\n",
+            printf("%s: can't open %s: %s\n",
                     name, filename, strerror(errno));
             result = strdup("");
             goto done;
@@ -793,15 +810,15 @@
         success = (wrote == contents->size);
     }
     if (!success) {
-        fprintf(stderr, "mtd_write_data to %s failed: %s\n",
+        printf("mtd_write_data to %s failed: %s\n",
                 partition, strerror(errno));
     }
 
     if (mtd_erase_blocks(ctx, -1) == -1) {
-        fprintf(stderr, "%s: error erasing blocks of %s\n", name, partition);
+        printf("%s: error erasing blocks of %s\n", name, partition);
     }
     if (mtd_write_close(ctx) != 0) {
-        fprintf(stderr, "%s: error closing write of %s\n", name, partition);
+        printf("%s: error closing write of %s\n", name, partition);
     }
 
     printf("%s %s partition\n",
@@ -988,23 +1005,23 @@
     memcpy(args2, args, sizeof(char*) * argc);
     args2[argc] = NULL;
 
-    fprintf(stderr, "about to run program [%s] with %d args\n", args2[0], argc);
+    printf("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));
+        printf("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",
+            printf("run_program: child exited with status %d\n",
                     WEXITSTATUS(status));
         }
     } else if (WIFSIGNALED(status)) {
-        fprintf(stderr, "run_program: child terminated by signal %d\n",
+        printf("run_program: child terminated by signal %d\n",
                 WTERMSIG(status));
     }
 
@@ -1053,11 +1070,11 @@
     }
 
     if (args[0]->size < 0) {
-        fprintf(stderr, "%s(): no file contents received", name);
+        printf("%s(): no file contents received", name);
         return StringValue(strdup(""));
     }
     uint8_t digest[SHA_DIGEST_SIZE];
-    SHA(args[0]->data, args[0]->size, digest);
+    SHA_hash(args[0]->data, args[0]->size, digest);
     FreeValue(args[0]);
 
     if (argc == 1) {
@@ -1068,12 +1085,12 @@
     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",
+            printf("%s(): arg %d is not a string; skipping",
                     name, i);
         } else if (ParseSha1(args[i]->data, arg_digest) != 0) {
             // Warn about bad args and skip them.
-            fprintf(stderr, "%s(): error parsing \"%s\" as sha-1; skipping",
-                    name, args[i]->data);
+            printf("%s(): error parsing \"%s\" as sha-1; skipping",
+                   name, args[i]->data);
         } else if (memcmp(digest, arg_digest, SHA_DIGEST_SIZE) == 0) {
             break;
         }
@@ -1135,6 +1152,8 @@
     RegisterFunction("symlink", SymlinkFn);
     RegisterFunction("set_perm", SetPermFn);
     RegisterFunction("set_perm_recursive", SetPermFn);
+    RegisterFunction("set_perm2", SetPermFn);
+    RegisterFunction("set_perm2_recursive", SetPermFn);
 
     RegisterFunction("getprop", GetPropFn);
     RegisterFunction("file_getprop", FileGetPropFn);
diff --git a/updater/updater.c b/updater/updater.c
index 58ac27f..c7009fe 100644
--- a/updater/updater.c
+++ b/updater/updater.c
@@ -36,13 +36,14 @@
 
 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.
+    // at random (though we've tried to standardize on stdout).  The
+    // log file makes more sense if buffering is turned off so things
+    // appear in the right order.
     setbuf(stdout, NULL);
     setbuf(stderr, NULL);
 
     if (argc != 4) {
-        fprintf(stderr, "unexpected number of arguments (%d)\n", argc);
+        printf("unexpected number of arguments (%d)\n", argc);
         return 1;
     }
 
@@ -50,7 +51,7 @@
     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; "
+        printf("wrong updater binary API; expected 1, 2, or 3; "
                         "got %s\n",
                 argv[1]);
         return 2;
@@ -69,20 +70,20 @@
     int err;
     err = mzOpenZipArchive(package_data, &za);
     if (err != 0) {
-        fprintf(stderr, "failed to open package %s: %s\n",
+        printf("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);
+        printf("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");
+        printf("failed to read script from package\n");
         return 5;
     }
     script[script_entry->uncompLen] = '\0';
@@ -101,7 +102,7 @@
     yy_scan_string(script);
     int error = yyparse(&root, &error_count);
     if (error != 0 || error_count > 0) {
-        fprintf(stderr, "%d parse errors\n", error_count);
+        printf("%d parse errors\n", error_count);
         return 6;
     }
 
@@ -112,7 +113,6 @@
     sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);
 
     if (!sehandle) {
-        fprintf(stderr, "Warning:  No file_contexts\n");
         fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
     }
 
@@ -131,10 +131,10 @@
     char* result = Evaluate(&state, root);
     if (result == NULL) {
         if (state.errmsg == NULL) {
-            fprintf(stderr, "script aborted (no error message)\n");
+            printf("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);
+            printf("script aborted: %s\n", state.errmsg);
             char* line = strtok(state.errmsg, "\n");
             while (line) {
                 fprintf(cmd_pipe, "ui_print %s\n", line);
@@ -145,7 +145,7 @@
         free(state.errmsg);
         return 7;
     } else {
-        fprintf(stderr, "script result was [%s]\n", result);
+        fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result);
         free(result);
     }
 
diff --git a/verifier.cpp b/verifier.cpp
index 5f4c981..782a838 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -20,6 +20,7 @@
 
 #include "mincrypt/rsa.h"
 #include "mincrypt/sha.h"
+#include "mincrypt/sha256.h"
 
 #include <string.h>
 #include <stdio.h>
@@ -34,7 +35,7 @@
 // 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) {
+int verify_file(const char* path, const Certificate* pKeys, unsigned int numKeys) {
     ui->SetProgress(0.0);
 
     FILE* f = fopen(path, "rb");
@@ -68,6 +69,7 @@
     }
 
     if (footer[2] != 0xff || footer[3] != 0xff) {
+        LOGE("footer is wrong\n");
         fclose(f);
         return VERIFY_FAILURE;
     }
@@ -139,8 +141,19 @@
 
 #define BUFFER_SIZE 4096
 
-    SHA_CTX ctx;
-    SHA_init(&ctx);
+    bool need_sha1 = false;
+    bool need_sha256 = false;
+    for (i = 0; i < numKeys; ++i) {
+        switch (pKeys[i].hash_len) {
+            case SHA_DIGEST_SIZE: need_sha1 = true; break;
+            case SHA256_DIGEST_SIZE: need_sha256 = true; break;
+        }
+    }
+
+    SHA_CTX sha1_ctx;
+    SHA256_CTX sha256_ctx;
+    SHA_init(&sha1_ctx);
+    SHA256_init(&sha256_ctx);
     unsigned char* buffer = (unsigned char*)malloc(BUFFER_SIZE);
     if (buffer == NULL) {
         LOGE("failed to alloc memory for sha1 buffer\n");
@@ -159,7 +172,8 @@
             fclose(f);
             return VERIFY_FAILURE;
         }
-        SHA_update(&ctx, buffer, size);
+        if (need_sha1) SHA_update(&sha1_ctx, buffer, size);
+        if (need_sha256) SHA256_update(&sha256_ctx, buffer, size);
         so_far += size;
         double f = so_far / (double)signed_len;
         if (f > frac + 0.02 || size == so_far) {
@@ -170,12 +184,21 @@
     fclose(f);
     free(buffer);
 
-    const uint8_t* sha1 = SHA_final(&ctx);
+    const uint8_t* sha1 = SHA_final(&sha1_ctx);
+    const uint8_t* sha256 = SHA256_final(&sha256_ctx);
+
     for (i = 0; i < numKeys; ++i) {
+        const uint8_t* hash;
+        switch (pKeys[i].hash_len) {
+            case SHA_DIGEST_SIZE: hash = sha1; break;
+            case SHA256_DIGEST_SIZE: hash = sha256; break;
+            default: continue;
+        }
+
         // 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)) {
+        if (RSA_verify(pKeys[i].public_key, eocd + eocd_size - 6 - RSANUMBYTES,
+                       RSANUMBYTES, hash, pKeys[i].hash_len)) {
             LOGI("whole-file signature verified against key %d\n", i);
             free(eocd);
             return VERIFY_SUCCESS;
@@ -207,10 +230,19 @@
 // The file may contain multiple keys in this format, separated by
 // commas.  The last key must not be followed by a comma.
 //
+// A Certificate is a pair of an RSAPublicKey and a particular hash
+// (we support SHA-1 and SHA-256; we store the hash length to signify
+// which is being used).  The hash used is implied by the version number.
+//
+//       1: 2048-bit RSA key with e=3 and SHA-1 hash
+//       2: 2048-bit RSA key with e=65537 and SHA-1 hash
+//       3: 2048-bit RSA key with e=3 and SHA-256 hash
+//       4: 2048-bit RSA key with e=65537 and SHA-256 hash
+//
 // Returns NULL if the file failed to parse, or if it contain zero keys.
-RSAPublicKey*
+Certificate*
 load_keys(const char* filename, int* numKeys) {
-    RSAPublicKey* out = NULL;
+    Certificate* out = NULL;
     *numKeys = 0;
 
     FILE* f = fopen(filename, "r");
@@ -224,24 +256,38 @@
         bool done = false;
         while (!done) {
             ++*numKeys;
-            out = (RSAPublicKey*)realloc(out, *numKeys * sizeof(RSAPublicKey));
-            RSAPublicKey* key = out + (*numKeys - 1);
+            out = (Certificate*)realloc(out, *numKeys * sizeof(Certificate));
+            Certificate* cert = out + (*numKeys - 1);
+            cert->public_key = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
 
             char start_char;
             if (fscanf(f, " %c", &start_char) != 1) goto exit;
             if (start_char == '{') {
                 // a version 1 key has no version specifier.
-                key->exponent = 3;
+                cert->public_key->exponent = 3;
+                cert->hash_len = SHA_DIGEST_SIZE;
             } else if (start_char == 'v') {
                 int version;
                 if (fscanf(f, "%d {", &version) != 1) goto exit;
-                if (version == 2) {
-                    key->exponent = 65537;
-                } else {
-                    goto exit;
+                switch (version) {
+                    case 2:
+                        cert->public_key->exponent = 65537;
+                        cert->hash_len = SHA_DIGEST_SIZE;
+                        break;
+                    case 3:
+                        cert->public_key->exponent = 3;
+                        cert->hash_len = SHA256_DIGEST_SIZE;
+                        break;
+                    case 4:
+                        cert->public_key->exponent = 65537;
+                        cert->hash_len = SHA256_DIGEST_SIZE;
+                        break;
+                    default:
+                        goto exit;
                 }
             }
 
+            RSAPublicKey* key = cert->public_key;
             if (fscanf(f, " %i , 0x%x , { %u",
                        &(key->len), &(key->n0inv), &(key->n[0])) != 3) {
                 goto exit;
@@ -274,7 +320,7 @@
                 goto exit;
             }
 
-            LOGI("read key e=%d\n", key->exponent);
+            LOGI("read key e=%d hash=%d\n", key->exponent, cert->hash_len);
         }
     }
 
diff --git a/verifier.h b/verifier.h
index e9ef3b7..6ce1b44 100644
--- a/verifier.h
+++ b/verifier.h
@@ -19,12 +19,17 @@
 
 #include "mincrypt/rsa.h"
 
+typedef struct Certificate {
+    int hash_len;  // SHA_DIGEST_SIZE (SHA-1) or SHA256_DIGEST_SIZE (SHA-256)
+    RSAPublicKey* public_key;
+} Certificate;
+
 /* 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);
+int verify_file(const char* path, const Certificate *pKeys, unsigned int numKeys);
 
-RSAPublicKey* load_keys(const char* filename, int* numKeys);
+Certificate* load_keys(const char* filename, int* numKeys);
 
 #define VERIFY_SUCCESS        0
 #define VERIFY_FAILURE        1
diff --git a/verifier_test.cpp b/verifier_test.cpp
index 2ef52a0..1063cba 100644
--- a/verifier_test.cpp
+++ b/verifier_test.cpp
@@ -21,80 +21,82 @@
 #include "common.h"
 #include "verifier.h"
 #include "ui.h"
+#include "mincrypt/sha.h"
+#include "mincrypt/sha256.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 },
+      { 0x6afee91fu, 0x7fa31d5bu, 0x38a0b217u, 0x99df9baeu,
+        0xfe72991du, 0x727d3c04u, 0x20943f99u, 0xd08e7826u,
+        0x69e7c8a2u, 0xdeeccc8eu, 0x6b9af76fu, 0x553311c4u,
+        0x07b9e247u, 0x54c8bbcau, 0x6a540d81u, 0x48dbf567u,
+        0x98c92877u, 0x134fbfdeu, 0x01b32564u, 0x24581948u,
+        0x6cddc3b8u, 0x0cd444dau, 0xfe0381ccu, 0xf15818dfu,
+        0xc06e6d42u, 0x2e2f6412u, 0x093a6737u, 0x94d83b31u,
+        0xa466c87au, 0xb3f284a0u, 0xa694ec2cu, 0x053359e6u,
+        0x9717ee6au, 0x0732e080u, 0x220d5008u, 0xdc4af350u,
+        0x93d0a7c3u, 0xe330c9eau, 0xcac3da1eu, 0x8ebecf8fu,
+        0xc2be387fu, 0x38a14e89u, 0x211586f0u, 0x18b846f5u,
+        0x43be4c72u, 0xb578c204u, 0x1bbfb230u, 0xf1e267a8u,
+        0xa2d3e656u, 0x64b8e4feu, 0xe7e83d4bu, 0x3e77a943u,
+        0x3559ffd9u, 0x0ebb0f99u, 0x0aa76ce6u, 0xd3786ea7u,
+        0xbca8cd6bu, 0x068ca8e8u, 0xeb1de2ffu, 0x3e3ecd6cu,
+        0xe0d9d825u, 0xb1edc762u, 0xdec60b24u, 0xd6931904u},
+      { 0xccdcb989u, 0xe19281f9u, 0xa6e80accu, 0xb7f40560u,
+        0x0efb0bccu, 0x7f12b0bbu, 0x1e90531au, 0x136d95d0u,
+        0x9e660665u, 0x7d54918fu, 0xe3b93ea2u, 0x2f415d10u,
+        0x3d2df6e6u, 0x7a627ecfu, 0xa6f22d70u, 0xb995907au,
+        0x09de16b2u, 0xfeb8bd61u, 0xf24ec294u, 0x716a427fu,
+        0x2e12046fu, 0xeaf3d56au, 0xd9b873adu, 0x0ced340bu,
+        0xbc9cec09u, 0x73c65903u, 0xee39ce9bu, 0x3eede25au,
+        0x397633b7u, 0x2583c165u, 0x8514f97du, 0xe9166510u,
+        0x0b6fae99u, 0xa47139fdu, 0xdb8352f0u, 0xb2ad7f2cu,
+        0xa11552e2u, 0xd4d490a7u, 0xe11e8568u, 0xe9e484dau,
+        0xd3ef8449u, 0xa47055dau, 0x4edd9557u, 0x03a78ba1u,
+        0x770e130du, 0x16762facu, 0x0cbdfcc4u, 0xf3070540u,
+        0x008b6515u, 0x60e7e1b7u, 0xa72cf7f9u, 0xaff86e39u,
+        0x4296faadu, 0xfc90430eu, 0x6cc8f377u, 0xb398fd43u,
+        0x423c5997u, 0x991d59c4u, 0x6464bf73u, 0x96431575u,
+        0x15e3d207u, 0x30532a7au, 0x8c4be618u, 0x460a4d76u },
       3
     };
 
 RSAPublicKey test_f4_key =
     { 64, 0xc9bd1f21,
-      { 293133087u, 3210546773u, 865313125u, 250921607u,
-        3158780490u, 943703457u, 1242806226u, 2986289859u,
-        2942743769u, 2457906415u, 2719374299u, 1783459420u,
-        149579627u, 3081531591u, 3440738617u, 2788543742u,
-        2758457512u, 1146764939u, 3699497403u, 2446203424u,
-        1744968926u, 1159130537u, 2370028300u, 3978231572u,
-        3392699980u, 1487782451u, 1180150567u, 2841334302u,
-        3753960204u, 961373345u, 3333628321u, 748825784u,
-        2978557276u, 1566596926u, 1613056060u, 2600292737u,
-        1847226629u, 50398611u, 1890374404u, 2878700735u,
-        2286201787u, 1401186359u, 619285059u, 731930817u,
-        2340993166u, 1156490245u, 2992241729u, 151498140u,
-        318782170u, 3480838990u, 2100383433u, 4223552555u,
-        3628927011u, 4247846280u, 1759029513u, 4215632601u,
-        2719154626u, 3490334597u, 1751299340u, 3487864726u,
-        3668753795u, 4217506054u, 3748782284u, 3150295088u },
-      { 1772626313u, 445326068u, 3477676155u, 1758201194u,
-        2986784722u, 491035581u, 3922936562u, 702212696u,
-        2979856666u, 3324974564u, 2488428922u, 3056318590u,
-        1626954946u, 664714029u, 398585816u, 3964097931u,
-        3356701905u, 2298377729u, 2040082097u, 3025491477u,
-        539143308u, 3348777868u, 2995302452u, 3602465520u,
-        212480763u, 2691021393u, 1307177300u, 704008044u,
-        2031136606u, 1054106474u, 3838318865u, 2441343869u,
-        1477566916u, 700949900u, 2534790355u, 3353533667u,
-        336163563u, 4106790558u, 2701448228u, 1571536379u,
-        1103842411u, 3623110423u, 1635278839u, 1577828979u,
-        910322800u, 715583630u, 138128831u, 1017877531u,
-        2289162787u, 447994798u, 1897243165u, 4121561445u,
-        4150719842u, 2131821093u, 2262395396u, 3305771534u,
-        980753571u, 3256525190u, 3128121808u, 1072869975u,
-        3507939515u, 4229109952u, 118381341u, 2209831334u },
+      { 0x1178db1fu, 0xbf5d0e55u, 0x3393a165u, 0x0ef4c287u,
+        0xbc472a4au, 0x383fc5a1u, 0x4a13b7d2u, 0xb1ff2ac3u,
+        0xaf66b4d9u, 0x9280acefu, 0xa2165bdbu, 0x6a4d6e5cu,
+        0x08ea676bu, 0xb7ac70c7u, 0xcd158139u, 0xa635ccfeu,
+        0xa46ab8a8u, 0x445a3e8bu, 0xdc81d9bbu, 0x91ce1a20u,
+        0x68021cdeu, 0x4516eda9u, 0x8d43c30cu, 0xed1eff14u,
+        0xca387e4cu, 0x58adc233u, 0x4657ab27u, 0xa95b521eu,
+        0xdfc0e30cu, 0x394d64a1u, 0xc6b321a1u, 0x2ca22cb8u,
+        0xb1892d5cu, 0x5d605f3eu, 0x6025483cu, 0x9afd5181u,
+        0x6e1a7105u, 0x03010593u, 0x70acd304u, 0xab957cbfu,
+        0x8844abbbu, 0x53846837u, 0x24e98a43u, 0x2ba060c1u,
+        0x8b88b88eu, 0x44eea405u, 0xb259fc41u, 0x0907ad9cu,
+        0x13003adau, 0xcf79634eu, 0x7d314ec9u, 0xfbbe4c2bu,
+        0xd84d0823u, 0xfd30fd88u, 0x68d8a909u, 0xfb4572d9u,
+        0xa21301c2u, 0xd00a4785u, 0x6862b50cu, 0xcfe49796u,
+        0xdaacbd83u, 0xfb620906u, 0xdf71e0ccu, 0xbbc5b030u },
+      { 0x69a82189u, 0x1a8b22f4u, 0xcf49207bu, 0x68cc056au,
+        0xb206b7d2u, 0x1d449bbdu, 0xe9d342f2u, 0x29daea58u,
+        0xb19d011au, 0xc62f15e4u, 0x9452697au, 0xb62bb87eu,
+        0x60f95cc2u, 0x279ebb2du, 0x17c1efd8u, 0xec47558bu,
+        0xc81334d1u, 0x88fe7601u, 0x79992eb1u, 0xb4555615u,
+        0x2022ac8cu, 0xc79a4b8cu, 0xb288b034u, 0xd6b942f0u,
+        0x0caa32fbu, 0xa065ba51u, 0x4de9f154u, 0x29f64f6cu,
+        0x7910af5eu, 0x3ed4636au, 0xe4c81911u, 0x9183f37du,
+        0x5811e1c4u, 0x29c7a58cu, 0x9715d4d3u, 0xc7e2dce3u,
+        0x140972ebu, 0xf4c8a69eu, 0xa104d424u, 0x5dabbdfbu,
+        0x41cb4c6bu, 0xd7f44717u, 0x61785ff7u, 0x5e0bc273u,
+        0x36426c70u, 0x2aa6f08eu, 0x083badbfu, 0x3cab941bu,
+        0x8871da23u, 0x1ab3dbaeu, 0x7115a21du, 0xf5aa0965u,
+        0xf766f562u, 0x7f110225u, 0x86d96a04u, 0xc50a120eu,
+        0x3a751ca3u, 0xc21aa186u, 0xba7359d0u, 0x3ff2b257u,
+        0xd116e8bbu, 0xfc1318c0u, 0x070e5b1du, 0x83b759a6u },
       65537
     };
 
@@ -136,30 +138,37 @@
 
 int main(int argc, char **argv) {
     if (argc < 2 || argc > 4) {
-        fprintf(stderr, "Usage: %s [-f4 | -file <keys>] <package>\n", argv[0]);
+        fprintf(stderr, "Usage: %s [-sha256] [-f4 | -file <keys>] <package>\n", argv[0]);
         return 2;
     }
 
-    RSAPublicKey* key = &test_key;
+    Certificate default_cert;
+    Certificate* cert = &default_cert;
+    cert->public_key = &test_key;
+    cert->hash_len = SHA_DIGEST_SIZE;
     int num_keys = 1;
     ++argv;
+    if (strcmp(argv[0], "-sha256") == 0) {
+        ++argv;
+        cert->hash_len = SHA256_DIGEST_SIZE;
+    }
     if (strcmp(argv[0], "-f4") == 0) {
         ++argv;
-        key = &test_f4_key;
+        cert->public_key = &test_f4_key;
     } else if (strcmp(argv[0], "-file") == 0) {
         ++argv;
-        key = load_keys(argv[0], &num_keys);
+        cert = load_keys(argv[0], &num_keys);
         ++argv;
     }
 
     ui = new FakeUI();
 
-    int result = verify_file(*argv, key, num_keys);
+    int result = verify_file(*argv, cert, num_keys);
     if (result == VERIFY_SUCCESS) {
-        printf("SUCCESS\n");
+        printf("VERIFIED\n");
         return 0;
     } else if (result == VERIFY_FAILURE) {
-        printf("FAILURE\n");
+        printf("NOT VERIFIED\n");
         return 1;
     } else {
         printf("bad return value\n");
diff --git a/verifier_test.sh b/verifier_test.sh
index 378b0e5..65f77f4 100755
--- a/verifier_test.sh
+++ b/verifier_test.sh
@@ -64,33 +64,39 @@
 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
+  shift
+  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
+  shift
+  run_command $WORK_DIR/verifier_test "$@" $WORK_DIR/package.zip && fail
 }
 
-expect_succeed_f4() {
-  testname "$1 (should succeed)"
-  $ADB push $DATA_DIR/$1 $WORK_DIR/package.zip
-  run_command $WORK_DIR/verifier_test -f4 $WORK_DIR/package.zip || fail
-}
-
-expect_fail_f4() {
-  testname "$1 (should fail)"
-  $ADB push $DATA_DIR/$1 $WORK_DIR/package.zip
-  run_command $WORK_DIR/verifier_test -f4 $WORK_DIR/package.zip && fail
-}
-
+# not signed at all
 expect_fail unsigned.zip
+# signed in the pre-donut way
 expect_fail jarsigned.zip
+
+# success cases
 expect_succeed otasigned.zip
-expect_fail_f4 otasigned.zip
-expect_succeed_f4 otasigned_f4.zip
+expect_succeed otasigned_f4.zip -f4
+expect_succeed otasigned_sha256.zip -sha256
+expect_succeed otasigned_f4_sha256.zip -sha256 -f4
+
+# verified against different key
+expect_fail otasigned.zip -f4
 expect_fail otasigned_f4.zip
+
+# verified against right key but wrong hash algorithm
+expect_fail otasigned.zip -sha256
+expect_fail otasigned_f4.zip -sha256 -f4
+expect_fail otasigned_sha256.zip
+expect_fail otasigned_f4_sha256.zip -f4
+
+# various other cases
 expect_fail random.zip
 expect_fail fake-eocd.zip
 expect_fail alter-metadata.zip