Merge "start healthd in recovery"
diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c
index 259fa58..6b8da2a 100644
--- a/applypatch/applypatch.c
+++ b/applypatch/applypatch.c
@@ -421,18 +421,111 @@
             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));
+        {
+            size_t start = 0;
+            int success = 0;
+            int fd = open(partition, O_RDWR | O_SYNC);
+            if (fd < 0) {
+                printf("failed to open %s: %s\n", partition, strerror(errno));
                 return -1;
             }
-            if (fclose(f) != 0) {
+            int attempt;
+
+            for (attempt = 0; attempt < 10; ++attempt) {
+                size_t next_sync = start + (1<<20);
+                printf("raw O_SYNC write %s attempt %d start at %d\n", partition, attempt+1, start);
+                lseek(fd, start, SEEK_SET);
+                while (start < len) {
+                    size_t to_write = len - start;
+                    if (to_write > 4096) to_write = 4096;
+
+                    ssize_t written = write(fd, data+start, to_write);
+                    if (written < 0) {
+                        if (errno == EINTR) {
+                            written = 0;
+                        } else {
+                            printf("failed write writing to %s (%s)\n",
+                                   partition, strerror(errno));
+                            return -1;
+                        }
+                    }
+                    start += written;
+                    if (start >= next_sync) {
+                        fsync(fd);
+                        next_sync = start + (1<<20);
+                    }
+                }
+                fsync(fd);
+
+                // drop caches so our subsequent verification read
+                // won't just be reading the cache.
+                sync();
+                int dc = open("/proc/sys/vm/drop_caches", O_WRONLY);
+                write(dc, "3\n", 2);
+                close(dc);
+                sleep(1);
+                printf("  caches dropped\n");
+
+                // verify
+                lseek(fd, 0, SEEK_SET);
+                unsigned char buffer[4096];
+                start = len;
+                size_t p;
+                for (p = 0; p < len; p += sizeof(buffer)) {
+                    size_t to_read = len - p;
+                    if (to_read > sizeof(buffer)) to_read = sizeof(buffer);
+
+                    size_t so_far = 0;
+                    while (so_far < to_read) {
+                        ssize_t read_count = read(fd, buffer+so_far, to_read-so_far);
+                        if (read_count < 0) {
+                            if (errno == EINTR) {
+                                read_count = 0;
+                            } else {
+                                printf("verify read error %s at %d: %s\n",
+                                       partition, p, strerror(errno));
+                                return -1;
+                            }
+                        }
+                        if ((size_t)read_count < to_read) {
+                            printf("short verify read %s at %d: %d %d %s\n",
+                                   partition, p, read_count, to_read, strerror(errno));
+                        }
+                        so_far += read_count;
+                    }
+
+                    if (memcmp(buffer, data+p, to_read)) {
+                        printf("verification failed starting at %d\n", p);
+                        start = p;
+                        break;
+                    }
+                }
+
+                if (start == len) {
+                    printf("verification read succeeded (attempt %d)\n", attempt+1);
+                    success = true;
+                    break;
+                }
+
+                sleep(2);
+            }
+
+            if (!success) {
+                printf("failed to verify after all attempts\n");
+                return -1;
+            }
+
+            if (close(fd) != 0) {
                 printf("error closing %s (%s)\n", partition, strerror(errno));
                 return -1;
             }
+            // hack: sync and sleep after closing in hopes of getting
+            // the data actually onto flash.
+            printf("sleeping after close\n");
+            sync();
+            sleep(5);
             break;
+        }
     }
 
     free(copy);
@@ -473,7 +566,7 @@
 // Search an array of sha1 strings for one matching the given sha1.
 // Return the index of the match on success, or -1 if no match is
 // found.
-int FindMatchingPatch(uint8_t* sha1, const char** patch_sha1_str,
+int FindMatchingPatch(uint8_t* sha1, char* const * const patch_sha1_str,
                       int num_patches) {
     int i;
     uint8_t patch_sha1[SHA_DIGEST_SIZE];
diff --git a/applypatch/applypatch.h b/applypatch/applypatch.h
index d1a0232..f1f13a1 100644
--- a/applypatch/applypatch.h
+++ b/applypatch/applypatch.h
@@ -65,7 +65,7 @@
                      int retouch_flag);
 int SaveFileContents(const char* filename, const FileContents* file);
 void FreeFileContents(FileContents* file);
-int FindMatchingPatch(uint8_t* sha1, const char** patch_sha1_str,
+int FindMatchingPatch(uint8_t* sha1, char* const * const patch_sha1_str,
                       int num_patches);
 
 // bsdiff.c
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/install.cpp b/install.cpp
index 0cb5cc7..e1ab848 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) {
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..c5a589c 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";
@@ -283,6 +284,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 +326,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 +343,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*
@@ -789,6 +869,7 @@
                     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 +947,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 +994,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();
@@ -979,6 +1059,7 @@
     }
 
     if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
+        copy_logs();
         ui->SetBackground(RecoveryUI::ERROR);
     }
     if (status != INSTALL_SUCCESS || ui->IsTextVisible()) {
diff --git a/screen_ui.cpp b/screen_ui.cpp
index 222de00..93e2609 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -82,6 +82,10 @@
     install_overlay_offset_y(190),
     overlay_offset_x(-1),
     overlay_offset_y(-1) {
+
+    for (int i = 0; i < 5; i++)
+        backgroundIcon[i] = NULL;
+
     pthread_mutex_init(&updateMutex, NULL);
     self = this;
 }
diff --git a/updater/install.c b/updater/install.c
index 1fc4fd3..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,7 +1070,7 @@
     }
 
     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];
@@ -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);
     }