Merge changes Ibea4c13a,I27215a3d

* changes:
  init: Add "partition.*.verified" properties to the property service.
  fs_mgr: Set the 'partition.*.verified' property for verified partitions.
diff --git a/adb/transport_local.c b/adb/transport_local.c
index d2dbca6..948cc15 100644
--- a/adb/transport_local.c
+++ b/adb/transport_local.c
@@ -48,7 +48,7 @@
  * local transport it is connected. The list is used to detect when we're
  * trying to connect twice to a given local transport.
  */
-#define  ADB_LOCAL_TRANSPORT_MAX  16
+#define  ADB_LOCAL_TRANSPORT_MAX  64
 
 ADB_MUTEX_DEFINE( local_transports_lock );
 
diff --git a/fastboot/Android.mk b/fastboot/Android.mk
index b9b3c92..05ddf2a 100644
--- a/fastboot/Android.mk
+++ b/fastboot/Android.mk
@@ -18,7 +18,7 @@
 
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/../mkbootimg \
   $(LOCAL_PATH)/../../extras/ext4_utils
-LOCAL_SRC_FILES := protocol.c engine.c bootimg.c fastboot.c util.c
+LOCAL_SRC_FILES := protocol.c engine.c bootimg.c fastboot.c util.c fs.c
 LOCAL_MODULE := fastboot
 LOCAL_MODULE_TAGS := debug
 LOCAL_CFLAGS += -std=gnu99
diff --git a/fastboot/engine.c b/fastboot/engine.c
index 972c4ed..0fab703 100644
--- a/fastboot/engine.c
+++ b/fastboot/engine.c
@@ -27,7 +27,7 @@
  */
 
 #include "fastboot.h"
-#include "make_ext4fs.h"
+#include "fs.h"
 
 #include <errno.h>
 #include <stdio.h>
@@ -51,9 +51,8 @@
 #define OP_COMMAND    2
 #define OP_QUERY      3
 #define OP_NOTICE     4
-#define OP_FORMAT     5
-#define OP_DOWNLOAD_SPARSE 6
-#define OP_WAIT_FOR_DISCONNECT 7
+#define OP_DOWNLOAD_SPARSE 5
+#define OP_WAIT_FOR_DISCONNECT 6
 
 typedef struct Action Action;
 
@@ -79,14 +78,7 @@
 static Action *action_last = 0;
 
 
-struct image_data {
-    long long partition_size;
-    long long image_size; // real size of image file
-    void *buffer;
-};
 
-void generate_ext4_image(struct image_data *image);
-void cleanup_image(struct image_data *image);
 
 int fb_getvar(struct usb_handle *usb, char *response, const char *fmt, ...)
 {
@@ -102,24 +94,6 @@
     return fb_command_response(usb, cmd, response);
 }
 
-struct generator {
-    char *fs_type;
-
-    /* generate image and return it as image->buffer.
-     * size of the buffer returned as image->image_size.
-     *
-     * image->partition_size specifies what is the size of the
-     * file partition we generate image for.
-     */
-    void (*generate)(struct image_data *image);
-
-    /* it cleans the buffer allocated during image creation.
-     * this function probably does free() or munmap().
-     */
-    void (*cleanup)(struct image_data *image);
-} generators[] = {
-    { "ext4", generate_ext4_image, cleanup_image }
-};
 
 /* Return true if this partition is supported by the fastboot format command.
  * It is also used to determine if we should first erase a partition before
@@ -130,8 +104,7 @@
  */
 int fb_format_supported(usb_handle *usb, const char *partition)
 {
-    char response[FB_RESPONSE_SZ+1];
-    struct generator *generator = NULL;
+    char response[FB_RESPONSE_SZ + 1] = {0,};
     int status;
     unsigned int i;
 
@@ -140,18 +113,7 @@
         return 0;
     }
 
-    for (i = 0; i < ARRAY_SIZE(generators); i++) {
-        if (!strncmp(generators[i].fs_type, response, FB_RESPONSE_SZ)) {
-            generator = &generators[i];
-            break;
-        }
-    }
-
-    if (generator) {
-        return 1;
-    }
-
-    return 0;
+    return !!fs_get_generator(response);
 }
 
 static int cb_default(Action *a, int status, char *resp)
@@ -205,163 +167,6 @@
     a->msg = mkmsg("erasing '%s'", ptn);
 }
 
-/* Loads file content into buffer. Returns NULL on error. */
-static void *load_buffer(int fd, off_t size)
-{
-    void *buffer;
-
-#ifdef USE_MINGW
-    ssize_t count = 0;
-
-    // mmap is more efficient but mingw does not support it.
-    // In this case we read whole image into memory buffer.
-    buffer = malloc(size);
-    if (!buffer) {
-        perror("malloc");
-        return NULL;
-    }
-
-    lseek(fd, 0, SEEK_SET);
-    while(count < size) {
-        ssize_t actually_read = read(fd, (char*)buffer+count, size-count);
-
-        if (actually_read == 0) {
-            break;
-        }
-        if (actually_read < 0) {
-            if (errno == EINTR) {
-                continue;
-            }
-            perror("read");
-            free(buffer);
-            return NULL;
-        }
-
-        count += actually_read;
-    }
-#else
-    buffer = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
-    if (buffer == MAP_FAILED) {
-        perror("mmap");
-        return NULL;
-    }
-#endif
-
-    return buffer;
-}
-
-void cleanup_image(struct image_data *image)
-{
-#ifdef USE_MINGW
-    free(image->buffer);
-#else
-    munmap(image->buffer, image->image_size);
-#endif
-}
-
-void generate_ext4_image(struct image_data *image)
-{
-    int fd;
-    struct stat st;
-
-    fd = fileno(tmpfile());
-    make_ext4fs_sparse_fd(fd, image->partition_size, NULL, NULL);
-
-    fstat(fd, &st);
-    image->image_size = st.st_size;
-    image->buffer = load_buffer(fd, st.st_size);
-
-    close(fd);
-}
-
-int fb_format(Action *a, usb_handle *usb, int skip_if_not_supported)
-{
-    const char *partition = a->cmd;
-    char response[FB_RESPONSE_SZ+1];
-    int status = 0;
-    struct image_data image;
-    struct generator *generator = NULL;
-    int fd;
-    unsigned i;
-    char cmd[CMD_SIZE];
-
-    status = fb_getvar(usb, response, "partition-type:%s", partition);
-    if (status) {
-        if (skip_if_not_supported) {
-            fprintf(stderr,
-                    "Erase successful, but not automatically formatting.\n");
-            fprintf(stderr,
-                    "Can't determine partition type.\n");
-            return 0;
-        }
-        fprintf(stderr,"FAILED (%s)\n", fb_get_error());
-        return status;
-    }
-
-    for (i = 0; i < ARRAY_SIZE(generators); i++) {
-        if (!strncmp(generators[i].fs_type, response, FB_RESPONSE_SZ)) {
-            generator = &generators[i];
-            break;
-        }
-    }
-    if (!generator) {
-        if (skip_if_not_supported) {
-            fprintf(stderr,
-                    "Erase successful, but not automatically formatting.\n");
-            fprintf(stderr,
-                    "File system type %s not supported.\n", response);
-            return 0;
-        }
-        fprintf(stderr,"Formatting is not supported for filesystem with type '%s'.\n",
-                response);
-        return -1;
-    }
-
-    status = fb_getvar(usb, response, "partition-size:%s", partition);
-    if (status) {
-        if (skip_if_not_supported) {
-            fprintf(stderr,
-                    "Erase successful, but not automatically formatting.\n");
-            fprintf(stderr, "Unable to get partition size\n.");
-            return 0;
-        }
-        fprintf(stderr,"FAILED (%s)\n", fb_get_error());
-        return status;
-    }
-    image.partition_size = strtoll(response, (char **)NULL, 16);
-
-    generator->generate(&image);
-    if (!image.buffer) {
-        fprintf(stderr,"Cannot generate image.\n");
-        return -1;
-    }
-
-    // Following piece of code is similar to fb_queue_flash() but executes
-    // actions directly without queuing
-    fprintf(stderr, "sending '%s' (%lli KB)...\n", partition, image.image_size/1024);
-    status = fb_download_data(usb, image.buffer, image.image_size);
-    if (status) goto cleanup;
-
-    fprintf(stderr, "writing '%s'...\n", partition);
-    snprintf(cmd, sizeof(cmd), "flash:%s", partition);
-    status = fb_command(usb, cmd);
-    if (status) goto cleanup;
-
-cleanup:
-    generator->cleanup(&image);
-
-    return status;
-}
-
-void fb_queue_format(const char *partition, int skip_if_not_supported)
-{
-    Action *a;
-
-    a = queue_action(OP_FORMAT, partition);
-    a->data = (void*)skip_if_not_supported;
-    a->msg = mkmsg("formatting '%s' partition", partition);
-}
-
 void fb_queue_flash(const char *ptn, void *data, unsigned sz)
 {
     Action *a;
@@ -589,10 +394,6 @@
             if (status) break;
         } else if (a->op == OP_NOTICE) {
             fprintf(stderr,"%s\n",(char*)a->data);
-        } else if (a->op == OP_FORMAT) {
-            status = fb_format(a, usb, (int)a->data);
-            status = a->func(a, status, status ? fb_get_error() : "");
-            if (status) break;
         } else if (a->op == OP_DOWNLOAD_SPARSE) {
             status = fb_download_data_sparse(usb, a->data);
             status = a->func(a, status, status ? fb_get_error() : "");
diff --git a/fastboot/fastboot.c b/fastboot/fastboot.c
index 7d26c6f..7f49ae9 100644
--- a/fastboot/fastboot.c
+++ b/fastboot/fastboot.c
@@ -49,6 +49,7 @@
 #include <zipfile/zipfile.h>
 
 #include "fastboot.h"
+#include "fs.h"
 
 #ifndef O_BINARY
 #define O_BINARY 0
@@ -622,10 +623,13 @@
     void *data;
     int64_t limit;
 
+
     sz64 = file_size(fd);
     if (sz64 < 0) {
         return -1;
     }
+
+    lseek(fd, 0, SEEK_SET);
     limit = get_sparse_limit(usb, sz64);
     if (limit) {
         struct sparse_file **s = load_sparse_files(fd, limit);
@@ -872,6 +876,73 @@
     return num;
 }
 
+void fb_perform_format(const char *partition, int skip_if_not_supported)
+{
+    char pType[FB_RESPONSE_SZ + 1], pSize[FB_RESPONSE_SZ + 1];
+    unsigned int limit = INT_MAX;
+    struct fastboot_buffer buf;
+    const char *errMsg = NULL;
+    const struct fs_generator *gen;
+    uint64_t pSz;
+    int status;
+    int fd;
+
+    if (target_sparse_limit > 0 && target_sparse_limit < limit)
+        limit = target_sparse_limit;
+    if (sparse_limit > 0 && sparse_limit < limit)
+        limit = sparse_limit;
+
+    status = fb_getvar(usb, pType, "partition-type:%s", partition);
+    if (status) {
+        errMsg = "Can't determine partition type.\n";
+        goto failed;
+    }
+
+    status = fb_getvar(usb, pSize, "partition-size:%s", partition);
+    if (status) {
+        errMsg = "Unable to get partition size\n";
+        goto failed;
+    }
+
+    gen = fs_get_generator(pType);
+    if (!gen) {
+        if (skip_if_not_supported) {
+            fprintf(stderr, "Erase successful, but not automatically formatting.\n");
+            fprintf(stderr, "File system type %s not supported.\n", pType);
+            return;
+        }
+        fprintf(stderr, "Formatting is not supported for filesystem with type '%s'.\n", pType);
+        return;
+    }
+
+    pSz = strtoll(pSize, (char **)NULL, 16);
+
+    fd = fileno(tmpfile());
+    if (fs_generator_generate(gen, fd, pSz)) {
+        close(fd);
+        fprintf(stderr, "Cannot generate image.\n");
+        return;
+    }
+
+    if (load_buf_fd(usb, fd, &buf)) {
+        fprintf(stderr, "Cannot read image.\n");
+        close(fd);
+        return;
+    }
+    flash_buf(partition, &buf);
+
+    return;
+
+
+failed:
+    if (skip_if_not_supported) {
+        fprintf(stderr, "Erase successful, but not automatically formatting.\n");
+        if (errMsg)
+            fprintf(stderr, "%s", errMsg);
+    }
+    fprintf(stderr,"FAILED (%s)\n", fb_get_error());
+}
+
 int main(int argc, char **argv)
 {
     int wants_wipe = 0;
@@ -1004,7 +1075,7 @@
             if (erase_first && needs_erase(argv[1])) {
                 fb_queue_erase(argv[1]);
             }
-            fb_queue_format(argv[1], 0);
+            fb_perform_format(argv[1], 0);
             skip(2);
         } else if(!strcmp(*argv, "signature")) {
             require(2);
@@ -1092,9 +1163,9 @@
 
     if (wants_wipe) {
         fb_queue_erase("userdata");
-        fb_queue_format("userdata", 1);
+        fb_perform_format("userdata", 1);
         fb_queue_erase("cache");
-        fb_queue_format("cache", 1);
+        fb_perform_format("cache", 1);
     }
     if (wants_reboot) {
         fb_queue_reboot();
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index 976c397..c510a36 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -49,7 +49,7 @@
 void fb_queue_flash(const char *ptn, void *data, unsigned sz);
 void fb_queue_flash_sparse(const char *ptn, struct sparse_file *s, unsigned sz);
 void fb_queue_erase(const char *ptn);
-void fb_queue_format(const char *ptn, int skip_if_not_supported);
+void fb_queue_format(const char *ptn, int skip_if_not_supported, unsigned int max_chunk_sz);
 void fb_queue_require(const char *prod, const char *var, int invert,
         unsigned nvalues, const char **value);
 void fb_queue_display(const char *var, const char *prettyname);
diff --git a/fastboot/fs.c b/fastboot/fs.c
new file mode 100644
index 0000000..6a1f9e6
--- /dev/null
+++ b/fastboot/fs.c
@@ -0,0 +1,56 @@
+#include "fastboot.h"
+#include "make_ext4fs.h"
+#include "fs.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sparse/sparse.h>
+#include <unistd.h>
+
+#ifdef USE_MINGW
+#include <fcntl.h>
+#else
+#include <sys/mman.h>
+#endif
+
+
+
+static int generate_ext4_image(int fd, long long partSize)
+{
+    make_ext4fs_sparse_fd(fd, partSize, NULL, NULL);
+
+    return 0;
+}
+
+static const struct fs_generator {
+
+    char *fs_type;  //must match what fastboot reports for partition type
+    int (*generate)(int fd, long long partSize); //returns 0 or error value
+
+} generators[] = {
+
+    { "ext4", generate_ext4_image}
+
+};
+
+const struct fs_generator* fs_get_generator(const char* name)
+{
+    unsigned i;
+
+    for (i = 0; i < sizeof(generators) / sizeof(*generators); i++)
+        if (!strcmp(generators[i].fs_type, name))
+            return generators + i;
+
+    return NULL;
+}
+
+int fs_generator_generate(const struct fs_generator* gen, int tmpFileNo, long long partSize)
+{
+    return gen->generate(tmpFileNo, partSize);
+}
diff --git a/fastboot/fs.h b/fastboot/fs.h
new file mode 100644
index 0000000..65b9555
--- /dev/null
+++ b/fastboot/fs.h
@@ -0,0 +1,12 @@
+#ifndef _FS_H_
+#define _FH_H_
+
+#include <stdint.h>
+
+struct fs_generator;
+
+const struct fs_generator* fs_get_generator(const char* name);
+int fs_generator_generate(const struct fs_generator* gen, int tmpFileNo, long long partSize);
+
+#endif
+
diff --git a/fs_mgr/fs_mgr.c b/fs_mgr/fs_mgr.c
index 0a0a08a..c4f27a0 100644
--- a/fs_mgr/fs_mgr.c
+++ b/fs_mgr/fs_mgr.c
@@ -260,9 +260,9 @@
 
         /* back up errno as partition_wipe clobbers the value */
         mount_errno = errno;
-
         /* mount(2) returned an error, check if it's encryptable and deal with it */
-        if ((fstab->recs[i].fs_mgr_flags & MF_CRYPT) &&
+        if (mount_errno != EBUSY && mount_errno != EACCES &&
+            (fstab->recs[i].fs_mgr_flags & MF_CRYPT) &&
             !partition_wiped(fstab->recs[i].blk_device)) {
             /* Need to mount a tmpfs at this mountpoint for now, and set
              * properties that vold will query later for decrypting
diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
index fc3ac67..710f5b6 100644
--- a/healthd/healthd_mode_charger.cpp
+++ b/healthd/healthd_mode_charger.cpp
@@ -646,7 +646,7 @@
         healthd_register_event(epollfd, charger_event_handler);
     }
 
-    ret = res_create_surface("charger/battery_fail", &charger->surf_unknown);
+    ret = res_create_display_surface("charger/battery_fail", &charger->surf_unknown);
     if (ret < 0) {
         LOGE("Cannot load battery_fail image\n");
         charger->surf_unknown = NULL;
@@ -656,7 +656,7 @@
 
     gr_surface* scale_frames;
     int scale_count;
-    ret = res_create_multi_surface("charger/battery_scale", &scale_count, &scale_frames);
+    ret = res_create_multi_display_surface("charger/battery_scale", &scale_count, &scale_frames);
     if (ret < 0) {
         LOGE("Cannot load battery_scale image\n");
         charger->batt_anim->num_frames = 0;
diff --git a/include/log/log_read.h b/include/log/log_read.h
index 7edfe3c..bd9de12 100644
--- a/include/log/log_read.h
+++ b/include/log/log_read.h
@@ -17,11 +17,17 @@
 #ifndef _LIBS_LOG_LOG_READ_H
 #define _LIBS_LOG_LOG_READ_H
 
+#include <stdint.h>
 #include <time.h>
 
 /* struct log_time is a wire-format variant of struct timespec */
 #define NS_PER_SEC 1000000000ULL
+
 #ifdef __cplusplus
+
+// NB: do NOT define a copy constructor. This will result in structure
+// no longer being compatible with pass-by-value which is desired
+// efficient behavior. Also, pass-by-reference breaks C/C++ ABI.
 struct log_time {
 public:
     uint32_t tv_sec; // good to Feb 5 2106
@@ -32,16 +38,12 @@
         tv_sec = T.tv_sec;
         tv_nsec = T.tv_nsec;
     }
-    log_time(const log_time &T)
-    {
-        tv_sec = T.tv_sec;
-        tv_nsec = T.tv_nsec;
-    }
     log_time(uint32_t sec, uint32_t nsec)
     {
         tv_sec = sec;
         tv_nsec = nsec;
     }
+    static const timespec EPOCH;
     log_time()
     {
     }
@@ -86,6 +88,12 @@
     {
         return !(*this > T);
     }
+    log_time operator-= (const timespec &T);
+    log_time operator- (const timespec &T) const
+    {
+        log_time local(*this);
+        return local -= T;
+    }
 
     // log_time
     bool operator== (const log_time &T) const
@@ -114,17 +122,31 @@
     {
         return !(*this > T);
     }
+    log_time operator-= (const log_time &T);
+    log_time operator- (const log_time &T) const
+    {
+        log_time local(*this);
+        return local -= T;
+    }
 
     uint64_t nsec() const
     {
         return static_cast<uint64_t>(tv_sec) * NS_PER_SEC + tv_nsec;
     }
+
+    static const char default_format[];
+
+    // Add %#q for the fraction of a second to the standard library functions
+    char *strptime(const char *s, const char *format = default_format);
 } __attribute__((__packed__));
+
 #else
+
 typedef struct log_time {
     uint32_t tv_sec;
     uint32_t tv_nsec;
 } __attribute__((__packed__)) log_time;
+
 #endif
 
 #endif /* define _LIBS_LOG_LOG_READ_H */
diff --git a/include/log/logger.h b/include/log/logger.h
index 8810615..3c6ea30 100644
--- a/include/log/logger.h
+++ b/include/log/logger.h
@@ -12,6 +12,7 @@
 
 #include <stdint.h>
 #include <log/log.h>
+#include <log/log_read.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -140,15 +141,30 @@
 log_id_t android_logger_get_id(struct logger *logger);
 
 int android_logger_clear(struct logger *logger);
-int android_logger_get_log_size(struct logger *logger);
-int android_logger_get_log_readable_size(struct logger *logger);
+long android_logger_get_log_size(struct logger *logger);
+#ifdef USERDEBUG_BUILD
+int android_logger_set_log_size(struct logger *logger, unsigned long size);
+#endif
+long android_logger_get_log_readable_size(struct logger *logger);
 int android_logger_get_log_version(struct logger *logger);
 
 struct logger_list;
 
+ssize_t android_logger_get_statistics(struct logger_list *logger_list,
+                                      char *buf, size_t len);
+#ifdef USERDEBUG_BUILD
+ssize_t android_logger_get_prune_list(struct logger_list *logger_list,
+                                      char *buf, size_t len);
+int android_logger_set_prune_list(struct logger_list *logger_list,
+                                  char *buf, size_t len);
+#endif
+
 struct logger_list *android_logger_list_alloc(int mode,
                                               unsigned int tail,
                                               pid_t pid);
+struct logger_list *android_logger_list_alloc_time(int mode,
+                                                   log_time start,
+                                                   pid_t pid);
 void android_logger_list_free(struct logger_list *logger_list);
 /* In the purest sense, the following two are orthogonal interfaces */
 int android_logger_list_read(struct logger_list *logger_list,
diff --git a/include/utils/BitSet.h b/include/utils/BitSet.h
index 19c03d1..75abe6c 100644
--- a/include/utils/BitSet.h
+++ b/include/utils/BitSet.h
@@ -40,7 +40,7 @@
     inline void clear() { value = 0; }
 
     // Returns the number of marked bits in the set.
-    inline uint32_t count() const { return __builtin_popcount(value); }
+    inline uint32_t count() const { return __builtin_popcountl(value); }
 
     // Returns true if the bit set does not contain any marked bits.
     inline bool isEmpty() const { return ! value; }
@@ -59,15 +59,15 @@
 
     // Finds the first marked bit in the set.
     // Result is undefined if all bits are unmarked.
-    inline uint32_t firstMarkedBit() const { return __builtin_clz(value); }
+    inline uint32_t firstMarkedBit() const { return __builtin_clzl(value); }
 
     // Finds the first unmarked bit in the set.
     // Result is undefined if all bits are marked.
-    inline uint32_t firstUnmarkedBit() const { return __builtin_clz(~ value); }
+    inline uint32_t firstUnmarkedBit() const { return __builtin_clzl(~ value); }
 
     // Finds the last marked bit in the set.
     // Result is undefined if all bits are unmarked.
-    inline uint32_t lastMarkedBit() const { return 31 - __builtin_ctz(value); }
+    inline uint32_t lastMarkedBit() const { return 31 - __builtin_ctzl(value); }
 
     // Finds the first marked bit in the set and clears it.  Returns the bit index.
     // Result is undefined if all bits are unmarked.
@@ -96,7 +96,7 @@
     // Gets the index of the specified bit in the set, which is the number of
     // marked bits that appear before the specified bit.
     inline uint32_t getIndexOfBit(uint32_t n) const {
-        return __builtin_popcount(value & ~(0xffffffffUL >> n));
+        return __builtin_popcountl(value & ~(0xffffffffUL >> n));
     }
 
     inline bool operator== (const BitSet32& other) const { return value == other.value; }
@@ -119,6 +119,99 @@
 
 ANDROID_BASIC_TYPES_TRAITS(BitSet32)
 
+// A simple set of 64 bits that can be individually marked or cleared.
+struct BitSet64 {
+    uint64_t value;
+
+    inline BitSet64() : value(0ULL) { }
+    explicit inline BitSet64(uint64_t value) : value(value) { }
+
+    // Gets the value associated with a particular bit index.
+    static inline uint64_t valueForBit(uint32_t n) { return 0x8000000000000000ULL >> n; }
+
+    // Clears the bit set.
+    inline void clear() { value = 0ULL; }
+
+    // Returns the number of marked bits in the set.
+    inline uint32_t count() const { return __builtin_popcountll(value); }
+
+    // Returns true if the bit set does not contain any marked bits.
+    inline bool isEmpty() const { return ! value; }
+
+    // Returns true if the bit set does not contain any unmarked bits.
+    inline bool isFull() const { return value == 0xffffffffffffffffULL; }
+
+    // Returns true if the specified bit is marked.
+    inline bool hasBit(uint32_t n) const { return value & valueForBit(n); }
+
+    // Marks the specified bit.
+    inline void markBit(uint32_t n) { value |= valueForBit(n); }
+
+    // Clears the specified bit.
+    inline void clearBit(uint32_t n) { value &= ~ valueForBit(n); }
+
+    // Finds the first marked bit in the set.
+    // Result is undefined if all bits are unmarked.
+    inline uint32_t firstMarkedBit() const { return __builtin_clzll(value); }
+
+    // Finds the first unmarked bit in the set.
+    // Result is undefined if all bits are marked.
+    inline uint32_t firstUnmarkedBit() const { return __builtin_clzll(~ value); }
+
+    // Finds the last marked bit in the set.
+    // Result is undefined if all bits are unmarked.
+    inline uint32_t lastMarkedBit() const { return 63 - __builtin_ctzll(value); }
+
+    // Finds the first marked bit in the set and clears it.  Returns the bit index.
+    // Result is undefined if all bits are unmarked.
+    inline uint32_t clearFirstMarkedBit() {
+        uint64_t n = firstMarkedBit();
+        clearBit(n);
+        return n;
+    }
+
+    // Finds the first unmarked bit in the set and marks it.  Returns the bit index.
+    // Result is undefined if all bits are marked.
+    inline uint32_t markFirstUnmarkedBit() {
+        uint64_t n = firstUnmarkedBit();
+        markBit(n);
+        return n;
+    }
+
+    // Finds the last marked bit in the set and clears it.  Returns the bit index.
+    // Result is undefined if all bits are unmarked.
+    inline uint32_t clearLastMarkedBit() {
+        uint64_t n = lastMarkedBit();
+        clearBit(n);
+        return n;
+    }
+
+    // Gets the index of the specified bit in the set, which is the number of
+    // marked bits that appear before the specified bit.
+    inline uint32_t getIndexOfBit(uint32_t n) const {
+        return __builtin_popcountll(value & ~(0xffffffffffffffffULL >> n));
+    }
+
+    inline bool operator== (const BitSet64& other) const { return value == other.value; }
+    inline bool operator!= (const BitSet64& other) const { return value != other.value; }
+    inline BitSet64 operator& (const BitSet64& other) const {
+        return BitSet64(value & other.value);
+    }
+    inline BitSet64& operator&= (const BitSet64& other) {
+        value &= other.value;
+        return *this;
+    }
+    inline BitSet64 operator| (const BitSet64& other) const {
+        return BitSet64(value | other.value);
+    }
+    inline BitSet64& operator|= (const BitSet64& other) {
+        value |= other.value;
+        return *this;
+    }
+};
+
+ANDROID_BASIC_TYPES_TRAITS(BitSet64)
+
 } // namespace android
 
 #endif // UTILS_BITSET_H
diff --git a/init/property_service.c b/init/property_service.c
index 987e1ee..cb3f082 100644
--- a/init/property_service.c
+++ b/init/property_service.c
@@ -540,8 +540,9 @@
                     || (sb.st_uid != 0)
                     || (sb.st_gid != 0)
                     || (sb.st_nlink != 1)) {
-                ERROR("skipping insecure property file %s (uid=%lu gid=%lu nlink=%d mode=%o)\n",
-                      entry->d_name, sb.st_uid, sb.st_gid, sb.st_nlink, sb.st_mode);
+                ERROR("skipping insecure property file %s (uid=%u gid=%u nlink=%d mode=%o)\n",
+                      entry->d_name, (unsigned int)sb.st_uid, (unsigned int)sb.st_gid,
+                      sb.st_nlink, sb.st_mode);
                 close(fd);
                 continue;
             }
diff --git a/libdiskconfig/diskconfig.c b/libdiskconfig/diskconfig.c
index 6fd81b7..1167d4b 100644
--- a/libdiskconfig/diskconfig.c
+++ b/libdiskconfig/diskconfig.c
@@ -30,7 +30,7 @@
 #include <linux/fs.h>
 
 #include <cutils/config_utils.h>
-#include <cutils/log.h>
+#include <log/log.h>
 
 #include <diskconfig/diskconfig.h>
 
@@ -337,7 +337,7 @@
     }
 
 #if 1
-    ALOGV("Device/file %s: size=%llu bytes, num_lba=%u, sect_size=%d",
+    ALOGV("Device/file %s: size=%" PRIu64 " bytes, num_lba=%u, sect_size=%d",
          dinfo->device, disk_size, dinfo->num_lba, dinfo->sect_size);
 #endif
 
diff --git a/libdiskconfig/diskutils.c b/libdiskconfig/diskutils.c
index e325735..7659632 100644
--- a/libdiskconfig/diskutils.c
+++ b/libdiskconfig/diskutils.c
@@ -19,13 +19,14 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <inttypes.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <sys/stat.h>
 
-#include <cutils/log.h>
+#include <log/log.h>
 
 #include <diskconfig/diskconfig.h>
 
@@ -35,8 +36,8 @@
     int dst_fd = -1;
     int src_fd = -1;
     uint8_t buffer[2048];
-    int nr_bytes;
-    int tmp;
+    ssize_t nr_bytes;
+    ssize_t tmp;
     int done = 0;
     uint64_t total = 0;
 
@@ -101,7 +102,7 @@
     if (dst_fd >= 0)
         fsync(dst_fd);
 
-    ALOGI("Wrote %llu bytes to %s @ %lld", total, dst, offset);
+    ALOGI("Wrote %" PRIu64 " bytes to %s @ %lld", total, dst, offset);
 
     close(src_fd);
     if (dst_fd >= 0)
diff --git a/liblog/Android.mk b/liblog/Android.mk
index 174ca55..a23de2d 100644
--- a/liblog/Android.mk
+++ b/liblog/Android.mk
@@ -16,12 +16,16 @@
 LOCAL_PATH := $(my-dir)
 include $(CLEAR_VARS)
 
-ifeq ($(TARGET_USES_LOGD),true)
+ifneq ($(TARGET_USES_LOGD),false)
 liblog_sources := logd_write.c
 else
 liblog_sources := logd_write_kern.c
 endif
 
+ifneq ($(filter userdebug eng,$(TARGET_BUILD_VARIANT)),)
+liblog_cflags := -DUSERDEBUG_BUILD=1
+endif
+
 # some files must not be compiled when building against Mingw
 # they correspond to features not used by our host development tools
 # which are also hard or even impossible to port to native Win32
@@ -46,10 +50,11 @@
 endif
 
 liblog_host_sources := $(liblog_sources) fake_log_device.c
-ifeq ($(TARGET_USES_LOGD),true)
-liblog_target_sources = $(liblog_sources) log_read.c
+liblog_target_sources := $(liblog_sources) log_time.cpp
+ifneq ($(TARGET_USES_LOGD),false)
+liblog_target_sources += log_read.c
 else
-liblog_target_sources = $(liblog_sources) log_read_kern.c
+liblog_target_sources += log_read_kern.c
 endif
 
 # Shared and static library for host
@@ -57,12 +62,18 @@
 LOCAL_MODULE := liblog
 LOCAL_SRC_FILES := $(liblog_host_sources)
 LOCAL_LDLIBS := -lpthread
+ifeq ($(strip $(HOST_OS)),linux)
+LOCAL_LDLIBS += -lrt
+endif
 LOCAL_CFLAGS := -DFAKE_LOG_DEVICE=1
 include $(BUILD_HOST_STATIC_LIBRARY)
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := liblog
 LOCAL_WHOLE_STATIC_LIBRARIES := liblog
+ifeq ($(strip $(HOST_OS)),linux)
+LOCAL_LDLIBS := -lrt
+endif
 include $(BUILD_HOST_SHARED_LIBRARY)
 
 
@@ -72,6 +83,9 @@
 LOCAL_MODULE := lib64log
 LOCAL_SRC_FILES := $(liblog_host_sources)
 LOCAL_LDLIBS := -lpthread
+ifeq ($(strip $(HOST_OS)),linux)
+LOCAL_LDLIBS += -lrt
+endif
 LOCAL_CFLAGS := -DFAKE_LOG_DEVICE=1 -m64
 include $(BUILD_HOST_STATIC_LIBRARY)
 
@@ -80,11 +94,13 @@
 include $(CLEAR_VARS)
 LOCAL_MODULE := liblog
 LOCAL_SRC_FILES := $(liblog_target_sources)
+LOCAL_CFLAGS := $(liblog_cflags)
 include $(BUILD_STATIC_LIBRARY)
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := liblog
 LOCAL_WHOLE_STATIC_LIBRARIES := liblog
+LOCAL_CFLAGS := $(liblog_cflags)
 include $(BUILD_SHARED_LIBRARY)
 
 include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/liblog/log_read.c b/liblog/log_read.c
index 889442d..2dd07e6 100644
--- a/liblog/log_read.c
+++ b/liblog/log_read.c
@@ -16,6 +16,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <inttypes.h>
 #include <signal.h>
 #include <stddef.h>
 #define NOMINMAX /* for windows to suppress definition of min in stdlib.h */
@@ -234,6 +235,7 @@
     struct listnode node;
     int mode;
     unsigned int tail;
+    log_time start;
     pid_t pid;
     int sock;
 };
@@ -270,11 +272,11 @@
                             const char *msg, char *buf, size_t buf_size)
 {
     ssize_t ret;
+    int errno_save = 0;
     int sock = socket_local_client("logd", ANDROID_SOCKET_NAMESPACE_RESERVED,
                                    SOCK_STREAM);
     if (sock < 0) {
-        ret = sock;
-        goto done;
+        return sock;
     }
 
     if (msg) {
@@ -290,30 +292,39 @@
 
 done:
     if ((ret == -1) && errno) {
-        ret = -errno;
+        errno_save = errno;
     }
     close(sock);
+    if (errno_save) {
+        errno = errno_save;
+    }
     return ret;
 }
 
-int android_logger_clear(struct logger *logger)
+static int check_log_success(char *buf, ssize_t ret)
 {
-    char buf[512];
-
-    ssize_t ret = send_log_msg(logger, "clear %d", buf, sizeof(buf));
     if (ret < 0) {
         return ret;
     }
 
     if (strncmp(buf, "success", 7)) {
+        errno = EINVAL;
         return -1;
     }
 
     return 0;
 }
 
+int android_logger_clear(struct logger *logger)
+{
+    char buf[512];
+
+    return check_log_success(buf,
+        send_log_msg(logger, "clear %d", buf, sizeof(buf)));
+}
+
 /* returns the total size of the log's ring buffer */
-int android_logger_get_log_size(struct logger *logger)
+long android_logger_get_log_size(struct logger *logger)
 {
     char buf[512];
 
@@ -326,14 +337,28 @@
         return -1;
     }
 
-    return atoi(buf);
+    return atol(buf);
 }
 
+#ifdef USERDEBUG_BUILD
+
+int android_logger_set_log_size(struct logger *logger, unsigned long size)
+{
+    char buf[512];
+
+    snprintf(buf, sizeof(buf), "setLogSize %d %lu",
+        logger ? logger->id : (unsigned) -1, size);
+
+    return check_log_success(buf, send_log_msg(NULL, NULL, buf, sizeof(buf)));
+}
+
+#endif /* USERDEBUG_BUILD */
+
 /*
  * returns the readable size of the log's ring buffer (that is, amount of the
  * log consumed)
  */
-int android_logger_get_log_readable_size(struct logger *logger)
+long android_logger_get_log_readable_size(struct logger *logger)
 {
     char buf[512];
 
@@ -346,7 +371,7 @@
         return -1;
     }
 
-    return atoi(buf);
+    return atol(buf);
 }
 
 /*
@@ -357,6 +382,58 @@
     return 3;
 }
 
+/*
+ * returns statistics
+ */
+ssize_t android_logger_get_statistics(struct logger_list *logger_list,
+                                      char *buf, size_t len)
+{
+    struct listnode *node;
+    struct logger *logger;
+    char *cp = buf;
+    size_t remaining = len;
+    size_t n;
+
+    n = snprintf(cp, remaining, "getStatistics");
+    n = min(n, remaining);
+    remaining -= n;
+    cp += n;
+
+    logger_for_each(logger, logger_list) {
+        n = snprintf(cp, remaining, " %d", logger->id);
+        n = min(n, remaining);
+        remaining -= n;
+        cp += n;
+    }
+    return send_log_msg(NULL, NULL, buf, len);
+}
+
+#ifdef USERDEBUG_BUILD
+
+ssize_t android_logger_get_prune_list(struct logger_list *logger_list UNUSED,
+                                      char *buf, size_t len)
+{
+    return send_log_msg(NULL, "getPruneList", buf, len);
+}
+
+int android_logger_set_prune_list(struct logger_list *logger_list UNUSED,
+                                  char *buf, size_t len)
+{
+    const char cmd[] = "setPruneList ";
+    const size_t cmdlen = sizeof(cmd) - 1;
+
+    if (strlen(buf) > (len - cmdlen)) {
+        return -ENOMEM; /* KISS */
+    }
+    memmove(buf + cmdlen, buf, len - cmdlen);
+    buf[len - 1] = '\0';
+    memcpy(buf, cmd, cmdlen);
+
+    return check_log_success(buf, send_log_msg(NULL, NULL, buf, len));
+}
+
+#endif /* USERDEBUG_BUILD */
+
 struct logger_list *android_logger_list_alloc(int mode,
                                               unsigned int tail,
                                               pid_t pid)
@@ -370,6 +447,8 @@
 
     list_init(&logger_list->node);
     logger_list->mode = mode;
+    logger_list->start.tv_sec = 0;
+    logger_list->start.tv_nsec = 0;
     logger_list->tail = tail;
     logger_list->pid = pid;
     logger_list->sock = -1;
@@ -377,6 +456,27 @@
     return logger_list;
 }
 
+struct logger_list *android_logger_list_alloc_time(int mode,
+                                                   log_time start,
+                                                   pid_t pid)
+{
+    struct logger_list *logger_list;
+
+    logger_list = calloc(1, sizeof(*logger_list));
+    if (!logger_list) {
+        return NULL;
+    }
+
+    list_init(&logger_list->node);
+    logger_list->mode = mode;
+    logger_list->start = start;
+    logger_list->tail = 0;
+    logger_list->pid = pid;
+    logger_list->sock = -1;
+
+    return logger_list;
+}
+
 /* android_logger_list_register unimplemented, no use case */
 /* android_logger_list_unregister unimplemented, no use case */
 
@@ -493,6 +593,15 @@
             cp += ret;
         }
 
+        if (logger_list->start.tv_sec || logger_list->start.tv_nsec) {
+            ret = snprintf(cp, remaining, " start=%" PRIu32 ".%09" PRIu32,
+                           logger_list->start.tv_sec,
+                           logger_list->start.tv_nsec);
+            ret = min(ret, remaining);
+            remaining -= ret;
+            cp += ret;
+        }
+
         if (logger_list->pid) {
             ret = snprintf(cp, remaining, " pid=%u", logger_list->pid);
             ret = min(ret, remaining);
diff --git a/liblog/log_read_kern.c b/liblog/log_read_kern.c
index b454729..9cccb1d 100644
--- a/liblog/log_read_kern.c
+++ b/liblog/log_read_kern.c
@@ -51,6 +51,8 @@
          logger != node_to_item(&(logger_list)->node, struct logger, node); \
          logger = node_to_item((logger)->node.next, struct logger, node))
 
+#define UNUSED __attribute__((unused))
+
 /* In the future, we would like to make this list extensible */
 static const char *LOG_NAME[LOG_ID_MAX] = {
     [LOG_ID_MAIN] = "main",
@@ -225,16 +227,26 @@
 }
 
 /* returns the total size of the log's ring buffer */
-int android_logger_get_log_size(struct logger *logger)
+long android_logger_get_log_size(struct logger *logger)
 {
     return logger_ioctl(logger, LOGGER_GET_LOG_BUF_SIZE, O_RDWR);
 }
 
+#ifdef USERDEBUG_BUILD
+
+int android_logger_set_log_size(struct logger *logger UNUSED,
+                                unsigned long size UNUSED)
+{
+    return -ENOTSUP;
+}
+
+#endif /* USERDEBUG_BUILD */
+
 /*
  * returns the readable size of the log's ring buffer (that is, amount of the
  * log consumed)
  */
-int android_logger_get_log_readable_size(struct logger *logger)
+long android_logger_get_log_readable_size(struct logger *logger)
 {
     return logger_ioctl(logger, LOGGER_GET_LOG_LEN, O_RDONLY);
 }
@@ -248,6 +260,37 @@
     return (ret < 0) ? 1 : ret;
 }
 
+/*
+ * returns statistics
+ */
+static const char unsupported[] = "18\nNot Supported\n\f";
+
+ssize_t android_logger_get_statistics(struct logger_list *logger_list UNUSED,
+                                      char *buf, size_t len)
+{
+    strncpy(buf, unsupported, len);
+    return -ENOTSUP;
+}
+
+#ifdef USERDEBUG_BUILD
+
+ssize_t android_logger_get_prune_list(struct logger_list *logger_list UNUSED,
+                                      char *buf, size_t len)
+{
+    strncpy(buf, unsupported, len);
+    return -ENOTSUP;
+}
+
+int android_logger_set_prune_list(struct logger_list *logger_list UNUSED,
+                                  char *buf, size_t len)
+{
+    static const char unsupported_error[] = "Unsupported";
+    strncpy(buf, unsupported, len);
+    return -ENOTSUP;
+}
+
+#endif /* USERDEBUG_BUILD */
+
 struct logger_list *android_logger_list_alloc(int mode,
                                               unsigned int tail,
                                               pid_t pid)
@@ -265,6 +308,13 @@
     return logger_list;
 }
 
+struct logger_list *android_logger_list_alloc_time(int mode,
+                                                   log_time start UNUSED,
+                                                   pid_t pid)
+{
+    return android_logger_list_alloc(mode, 0, pid);
+}
+
 /* android_logger_list_register unimplemented, no use case */
 /* android_logger_list_unregister unimplemented, no use case */
 
diff --git a/liblog/log_time.cpp b/liblog/log_time.cpp
new file mode 100644
index 0000000..755c2d9
--- /dev/null
+++ b/liblog/log_time.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/cdefs.h>
+
+#include <log/log_read.h>
+
+const char log_time::default_format[] = "%m-%d %H:%M:%S.%3q";
+const timespec log_time::EPOCH = { 0, 0 };
+
+// Add %#q for fractional seconds to standard strptime function
+
+char *log_time::strptime(const char *s, const char *format) {
+    time_t now;
+#ifdef __linux__
+    *this = log_time(CLOCK_REALTIME);
+    now = tv_sec;
+#else
+    time(&now);
+    tv_sec = now;
+    tv_nsec = 0;
+#endif
+
+    struct tm *ptm;
+#if (defined(HAVE_LOCALTIME_R))
+    struct tm tmBuf;
+    ptm = localtime_r(&now, &tmBuf);
+#else
+    ptm = localtime(&now);
+#endif
+
+    char fmt[strlen(format) + 1];
+    strcpy(fmt, format);
+
+    char *ret = const_cast<char *> (s);
+    char *cp;
+    for (char *f = cp = fmt; ; ++cp) {
+        if (!*cp) {
+            if (f != cp) {
+                ret = ::strptime(ret, f, ptm);
+            }
+            break;
+        }
+        if (*cp != '%') {
+            continue;
+        }
+        char *e = cp;
+        ++e;
+#if (defined(__BIONIC__))
+        if (*e == 's') {
+            *cp = '\0';
+            if (*f) {
+                ret = ::strptime(ret, f, ptm);
+                if (!ret) {
+                    break;
+                }
+            }
+            tv_sec = 0;
+            while (isdigit(*ret)) {
+                tv_sec = tv_sec * 10 + *ret - '0';
+                ++ret;
+            }
+            now = tv_sec;
+#if (defined(HAVE_LOCALTIME_R))
+            ptm = localtime_r(&now, &tmBuf);
+#else
+            ptm = localtime(&now);
+#endif
+        } else
+#endif
+        {
+            unsigned num = 0;
+            while (isdigit(*e)) {
+                num = num * 10 + *e - '0';
+                ++e;
+            }
+            if (*e != 'q') {
+                continue;
+            }
+            *cp = '\0';
+            if (*f) {
+                ret = ::strptime(ret, f, ptm);
+                if (!ret) {
+                    break;
+                }
+            }
+            unsigned long mul = NS_PER_SEC;
+            if (num == 0) {
+                num = INT_MAX;
+            }
+            tv_nsec = 0;
+            while (isdigit(*ret) && num && (mul > 1)) {
+                --num;
+                mul /= 10;
+                tv_nsec = tv_nsec + (*ret - '0') * mul;
+                ++ret;
+            }
+        }
+        f = cp = e;
+        ++f;
+    }
+
+    if (ret) {
+        tv_sec = mktime(ptm);
+        return ret;
+    }
+
+    // Upon error, place a known value into the class, the current time.
+#ifdef __linux__
+    *this = log_time(CLOCK_REALTIME);
+#else
+    time(&now);
+    tv_sec = now;
+    tv_nsec = 0;
+#endif
+    return ret;
+}
+
+log_time log_time::operator-= (const timespec &T) {
+    // No concept of negative time, clamp to EPOCH
+    if (*this <= T) {
+        return *this = EPOCH;
+    }
+
+    if (this->tv_nsec < (unsigned long int)T.tv_nsec) {
+        --this->tv_sec;
+        this->tv_nsec = NS_PER_SEC + this->tv_nsec - T.tv_nsec;
+    } else {
+        this->tv_nsec -= T.tv_nsec;
+    }
+    this->tv_sec -= T.tv_sec;
+
+    return *this;
+}
+
+log_time log_time::operator-= (const log_time &T) {
+    // No concept of negative time, clamp to EPOCH
+    if (*this <= T) {
+        return *this = EPOCH;
+    }
+
+    if (this->tv_nsec < T.tv_nsec) {
+        --this->tv_sec;
+        this->tv_nsec = NS_PER_SEC + this->tv_nsec - T.tv_nsec;
+    } else {
+        this->tv_nsec -= T.tv_nsec;
+    }
+    this->tv_sec -= T.tv_sec;
+
+    return *this;
+}
diff --git a/libutils/tests/BitSet_test.cpp b/libutils/tests/BitSet_test.cpp
index 752e56d..1f4917a 100644
--- a/libutils/tests/BitSet_test.cpp
+++ b/libutils/tests/BitSet_test.cpp
@@ -23,7 +23,7 @@
 
 namespace android {
 
-class BitSetTest : public testing::Test {
+class BitSet32Test : public testing::Test {
 protected:
     BitSet32 b1;
     BitSet32 b2;
@@ -34,7 +34,7 @@
 };
 
 
-TEST_F(BitSetTest, BitWiseOr) {
+TEST_F(BitSet32Test, BitWiseOr) {
     b1.markBit(2);
     b2.markBit(4);
 
@@ -49,7 +49,7 @@
     EXPECT_TRUE(b1.hasBit(2) && b1.hasBit(4));
     EXPECT_TRUE(b2.hasBit(4) && b2.count() == 1u);
 }
-TEST_F(BitSetTest, BitWiseAnd_Disjoint) {
+TEST_F(BitSet32Test, BitWiseAnd_Disjoint) {
     b1.markBit(2);
     b1.markBit(4);
     b1.markBit(6);
@@ -65,7 +65,7 @@
     EXPECT_TRUE(b1.hasBit(2) && b1.hasBit(4) && b1.hasBit(6));
 }
 
-TEST_F(BitSetTest, BitWiseAnd_NonDisjoint) {
+TEST_F(BitSet32Test, BitWiseAnd_NonDisjoint) {
     b1.markBit(2);
     b1.markBit(4);
     b1.markBit(6);
@@ -84,4 +84,66 @@
     EXPECT_EQ(b2.count(), 3u);
     EXPECT_TRUE(b2.hasBit(3) && b2.hasBit(6) && b2.hasBit(9));
 }
+
+class BitSet64Test : public testing::Test {
+protected:
+    BitSet64 b1;
+    BitSet64 b2;
+    virtual void TearDown() {
+        b1.clear();
+        b2.clear();
+    }
+};
+
+
+TEST_F(BitSet64Test, BitWiseOr) {
+    b1.markBit(20);
+    b2.markBit(40);
+
+    BitSet64 tmp = b1 | b2;
+    EXPECT_EQ(tmp.count(), 2u);
+    EXPECT_TRUE(tmp.hasBit(20) && tmp.hasBit(40));
+    // Check that the operator is symmetric
+    EXPECT_TRUE((b2 | b1) == (b1 | b2));
+
+    b1 |= b2;
+    EXPECT_EQ(b1.count(), 2u);
+    EXPECT_TRUE(b1.hasBit(20) && b1.hasBit(40));
+    EXPECT_TRUE(b2.hasBit(40) && b2.count() == 1u);
+}
+TEST_F(BitSet64Test, BitWiseAnd_Disjoint) {
+    b1.markBit(20);
+    b1.markBit(40);
+    b1.markBit(60);
+
+    BitSet64 tmp = b1 & b2;
+    EXPECT_TRUE(tmp.isEmpty());
+    // Check that the operator is symmetric
+    EXPECT_TRUE((b2 & b1) == (b1 & b2));
+
+    b2 &= b1;
+    EXPECT_TRUE(b2.isEmpty());
+    EXPECT_EQ(b1.count(), 3u);
+    EXPECT_TRUE(b1.hasBit(20) && b1.hasBit(40) && b1.hasBit(60));
+}
+
+TEST_F(BitSet64Test, BitWiseAnd_NonDisjoint) {
+    b1.markBit(20);
+    b1.markBit(40);
+    b1.markBit(60);
+    b2.markBit(30);
+    b2.markBit(60);
+    b2.markBit(63);
+
+    BitSet64 tmp = b1 & b2;
+    EXPECT_EQ(tmp.count(), 1u);
+    EXPECT_TRUE(tmp.hasBit(60));
+    // Check that the operator is symmetric
+    EXPECT_TRUE((b2 & b1) == (b1 & b2));
+
+    b1 &= b2;
+    EXPECT_EQ(b1.count(), 1u);
+    EXPECT_EQ(b2.count(), 3u);
+    EXPECT_TRUE(b2.hasBit(30) && b2.hasBit(60) && b2.hasBit(63));
+}
 } // namespace android
diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc
index 01f9249..aebddc8 100644
--- a/libziparchive/zip_archive.cc
+++ b/libziparchive/zip_archive.cc
@@ -17,22 +17,23 @@
 /*
  * Read-only access to Zip archives, with minimal heap allocation.
  */
-#include "ziparchive/zip_archive.h"
-
-#include <zlib.h>
 
 #include <assert.h>
 #include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
 #include <limits.h>
 #include <log/log.h>
-#include <fcntl.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <utils/FileMap.h>
+#include <zlib.h>
 
 #include <JNIHelp.h>  // TEMP_FAILURE_RETRY may or may not be in unistd
 
+#include "ziparchive/zip_archive.h"
+
 // This is for windows. If we don't open a file in binary mode, weirds
 // things will happen.
 #ifndef O_BINARY
@@ -217,8 +218,7 @@
     ssize_t actual = TEMP_FAILURE_RETRY(read(fd, buf, get_size));
 
     if (actual != get_size) {
-      ALOGW("CopyFileToFile: copy read failed (%d vs %zd)",
-          (int) actual, get_size);
+      ALOGW("CopyFileToFile: copy read failed (%zd vs %zd)", actual, get_size);
       return kIoError;
     }
 
@@ -337,12 +337,12 @@
   const off64_t search_start = file_length - read_amount;
 
   if (lseek64(fd, search_start, SEEK_SET) != search_start) {
-    ALOGW("Zip: seek %lld failed: %s", search_start, strerror(errno));
+    ALOGW("Zip: seek %" PRId64 " failed: %s", search_start, strerror(errno));
     return kIoError;
   }
   ssize_t actual = TEMP_FAILURE_RETRY(read(fd, scan_buffer, read_amount));
   if (actual != (ssize_t) read_amount) {
-    ALOGW("Zip: read %zd failed: %s", read_amount, strerror(errno));
+    ALOGW("Zip: read %u failed: %s", read_amount, strerror(errno));
     return kIoError;
   }
 
@@ -379,7 +379,7 @@
   const off64_t dir_offset = get4LE(eocd_ptr + kEOCDFileOffset);
 
   if (dir_offset + dir_size > eocd_offset) {
-    ALOGW("Zip: bad offsets (dir %lld, size %lld, eocd %lld)",
+    ALOGW("Zip: bad offsets (dir %" PRId64 ", size %" PRId64 ", eocd %" PRId64 ")",
         dir_offset, dir_size, eocd_offset);
     return kInvalidOffset;
   }
@@ -388,8 +388,8 @@
     return kEmptyArchive;
   }
 
-  ALOGV("+++ num_entries=%d dir_size=%d dir_offset=%d", num_entries, dir_size,
-      dir_offset);
+  ALOGV("+++ num_entries=%d dir_size=%" PRId64 " dir_offset=%" PRId64,
+        num_entries, dir_size, dir_offset);
 
   /*
    * It all looks good.  Create a mapping for the CD, and set the fields
@@ -430,12 +430,12 @@
   }
 
   if (file_length > (off64_t) 0xffffffff) {
-    ALOGV("Zip: zip file too long %d", file_length);
+    ALOGV("Zip: zip file too long %" PRId64, file_length);
     return kInvalidFile;
   }
 
   if (file_length < (int64_t) kEOCDLen) {
-    ALOGV("Zip: length %ld is too small to be zip", file_length);
+    ALOGV("Zip: length %" PRId64 " is too small to be zip", file_length);
     return kInvalidFile;
   }
 
@@ -503,7 +503,7 @@
 
     const off64_t local_header_offset = get4LE(ptr + kCDELocalOffset);
     if (local_header_offset >= archive->directory_offset) {
-      ALOGW("Zip: bad LFH offset %lld at entry %d", local_header_offset, i);
+      ALOGW("Zip: bad LFH offset %" PRId64 " at entry %d", local_header_offset, i);
       goto bail;
     }
 
@@ -522,8 +522,8 @@
 
     ptr += kCDELen + file_name_length + extra_length + comment_length;
     if ((size_t)(ptr - cd_ptr) > cd_length) {
-      ALOGW("Zip: bad CD advance (%d vs %zd) at entry %d",
-        (int) (ptr - cd_ptr), cd_length, i);
+      ALOGW("Zip: bad CD advance (%zu vs %zu) at entry %d",
+        (size_t) (ptr - cd_ptr), cd_length, i);
       goto bail;
     }
   }
@@ -630,7 +630,7 @@
   // is Windows. Only recent versions of windows support unix like forks,
   // and even there the semantics are quite different.
   if (lseek64(fd, off, SEEK_SET) != off) {
-    ALOGW("Zip: failed seek to offset %lld", off);
+    ALOGW("Zip: failed seek to offset %" PRId64, off);
     return kIoError;
   }
 
@@ -686,12 +686,12 @@
   ssize_t actual = ReadAtOffset(archive->fd, lfh_buf, sizeof(lfh_buf),
                                  local_header_offset);
   if (actual != sizeof(lfh_buf)) {
-    ALOGW("Zip: failed reading lfh name from offset %lld", local_header_offset);
+    ALOGW("Zip: failed reading lfh name from offset %" PRId64, local_header_offset);
     return kIoError;
   }
 
   if (get4LE(lfh_buf) != kLFHSignature) {
-    ALOGW("Zip: didn't find signature at start of lfh, offset=%lld",
+    ALOGW("Zip: didn't find signature at start of lfh, offset=%" PRId64,
         local_header_offset);
     return kInvalidOffset;
   }
@@ -733,7 +733,7 @@
                                   name_offset);
 
     if (actual != nameLen) {
-      ALOGW("Zip: failed reading lfh name from offset %lld", name_offset);
+      ALOGW("Zip: failed reading lfh name from offset %" PRId64, name_offset);
       free(name_buf);
       return kIoError;
     }
@@ -751,19 +751,19 @@
 
   const off64_t data_offset = local_header_offset + kLFHLen + lfhNameLen + lfhExtraLen;
   if (data_offset > cd_offset) {
-    ALOGW("Zip: bad data offset %lld in zip", (off64_t) data_offset);
+    ALOGW("Zip: bad data offset %" PRId64 " in zip", data_offset);
     return kInvalidOffset;
   }
 
   if ((off64_t)(data_offset + data->compressed_length) > cd_offset) {
-    ALOGW("Zip: bad compressed length in zip (%lld + %zd > %lld)",
+    ALOGW("Zip: bad compressed length in zip (%" PRId64 " + %zd > %" PRId64 ")",
       data_offset, data->compressed_length, cd_offset);
     return kInvalidOffset;
   }
 
   if (data->method == kCompressStored &&
     (off64_t)(data_offset + data->uncompressed_length) > cd_offset) {
-     ALOGW("Zip: bad uncompressed length in zip (%lld + %zd > %lld)",
+     ALOGW("Zip: bad uncompressed length in zip (%" PRId64 " + %d > %" PRId64 ")",
        data_offset, data->uncompressed_length, cd_offset);
      return kInvalidOffset;
   }
@@ -903,7 +903,7 @@
       const ssize_t getSize = (compressed_length > kBufSize) ? kBufSize : compressed_length;
       const ssize_t actual = TEMP_FAILURE_RETRY(read(fd, read_buf, getSize));
       if (actual != getSize) {
-        ALOGW("Zip: inflate read failed (%d vs %zd)", actual, getSize);
+        ALOGW("Zip: inflate read failed (%zd vs %zd)", actual, getSize);
         result = kIoError;
         goto z_bail;
       }
@@ -946,7 +946,7 @@
   *crc_out = zstream.adler;
 
   if (zstream.total_out != uncompressed_length || compressed_length != 0) {
-    ALOGW("Zip: size mismatch on inflated file (%ld vs %zd)",
+    ALOGW("Zip: size mismatch on inflated file (%ld vs %u)",
         zstream.total_out, uncompressed_length);
     result = kInconsistentInformation;
     goto z_bail;
@@ -967,7 +967,7 @@
   off64_t data_offset = entry->offset;
 
   if (lseek64(archive->fd, data_offset, SEEK_SET) != data_offset) {
-    ALOGW("Zip: lseek to data at %lld failed", (off64_t) data_offset);
+    ALOGW("Zip: lseek to data at %" PRId64 " failed", data_offset);
     return kIoError;
   }
 
@@ -990,7 +990,7 @@
   // TODO: Fix this check by passing the right flags to inflate2 so that
   // it calculates the CRC for us.
   if (entry->crc32 != crc && false) {
-    ALOGW("Zip: crc mismatch: expected %u, was %llu", entry->crc32, crc);
+    ALOGW("Zip: crc mismatch: expected %u, was %" PRIu64, entry->crc32, crc);
     return kInconsistentInformation;
   }
 
@@ -1010,8 +1010,8 @@
 
   int result = TEMP_FAILURE_RETRY(ftruncate(fd, declared_length + current_offset));
   if (result == -1) {
-    ALOGW("Zip: unable to truncate file to %lld: %s", declared_length + current_offset,
-          strerror(errno));
+    ALOGW("Zip: unable to truncate file to %" PRId64 ": %s",
+          declared_length + current_offset, strerror(errno));
     return kIoError;
   }
 
diff --git a/logcat/Android.mk b/logcat/Android.mk
index b5e27eb..dd15cb3 100644
--- a/logcat/Android.mk
+++ b/logcat/Android.mk
@@ -3,6 +3,10 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
+ifneq ($(filter userdebug eng,$(TARGET_BUILD_VARIANT)),)
+LOCAL_CFLAGS += -DUSERDEBUG_BUILD=1
+endif
+
 LOCAL_SRC_FILES:= logcat.cpp event.logtags
 
 LOCAL_SHARED_LIBRARIES := liblog
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index 3c33938..5960609 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -17,6 +17,7 @@
 
 #include <cutils/sockets.h>
 #include <log/log.h>
+#include <log/log_read.h>
 #include <log/logger.h>
 #include <log/logd.h>
 #include <log/logprint.h>
@@ -225,11 +226,23 @@
                     "  -t <count>      print only the most recent <count> lines (implies -d)\n"
                     "  -T <count>      print only the most recent <count> lines (does not imply -d)\n"
                     "  -g              get the size of the log's ring buffer and exit\n"
-                    "  -b <buffer>     Request alternate ring buffer, 'main', 'system', 'radio'\n"
-                    "                  or 'events'. Multiple -b parameters are allowed and the\n"
+                    "  -b <buffer>     Request alternate ring buffer, 'main', 'system', 'radio',\n"
+                    "                  'events' or 'all'. Multiple -b parameters are allowed and\n"
                     "                  results are interleaved. The default is -b main -b system.\n"
-                    "  -B              output the log in binary");
+                    "  -B              output the log in binary.\n"
+                    "  -S              output statistics.\n");
 
+#ifdef USERDEBUG_BUILD
+
+    fprintf(stderr, "--------------------- eng & userdebug builds only ---------------------------\n"
+                    "  -G <count>      set size of log's ring buffer and exit\n"
+                    "  -p              output prune white and ~black list\n"
+                    "  -P '<list> ...' set prune white and ~black list; UID, /PID or !(worst UID)\n"
+                    "                  default is ~!, prune worst UID.\n"
+                    "-----------------------------------------------------------------------------\n"
+    );
+
+#endif
 
     fprintf(stderr,"\nfilterspecs are a series of \n"
                    "  <tag>[:priority]\n\n"
@@ -278,13 +291,20 @@
     int hasSetLogFormat = 0;
     int clearLog = 0;
     int getLogSize = 0;
+#ifdef USERDEBUG_BUILD
+    unsigned long setLogSize = 0;
+    int getPruneList = 0;
+    char *setPruneList = NULL;
+#endif
+    int printStatistics = 0;
     int mode = O_RDONLY;
     const char *forceFilters = NULL;
     log_device_t* devices = NULL;
     log_device_t* dev;
     bool needBinary = false;
     struct logger_list *logger_list;
-    int tail_lines = 0;
+    unsigned int tail_lines = 0;
+    log_time tail_time(log_time::EPOCH);
 
     signal(SIGPIPE, exit);
 
@@ -303,7 +323,13 @@
     for (;;) {
         int ret;
 
-        ret = getopt(argc, argv, "cdt:T:gsQf:r::n:v:b:B");
+        ret = getopt(argc, argv,
+#ifdef USERDEBUG_BUILD
+            "cdt:T:gG:sQf:r::n:v:b:BSpP:"
+#else
+            "cdt:T:gsQf:r::n:v:b:BS"
+#endif
+        );
 
         if (ret < 0) {
             break;
@@ -328,14 +354,121 @@
                 mode = O_RDONLY | O_NDELAY;
                 /* FALLTHRU */
             case 'T':
-                tail_lines = atoi(optarg);
+                if (strspn(optarg, "0123456789") != strlen(optarg)) {
+                    char *cp = tail_time.strptime(optarg,
+                                                  log_time::default_format);
+                    if (!cp) {
+                        fprintf(stderr,
+                                "ERROR: -%c \"%s\" not in \"%s\" time format\n",
+                                ret, optarg, log_time::default_format);
+                        exit(1);
+                    }
+                    if (*cp) {
+                        char c = *cp;
+                        *cp = '\0';
+                        fprintf(stderr,
+                                "WARNING: -%c \"%s\"\"%c%s\" time truncated\n",
+                                ret, optarg, c, cp + 1);
+                        *cp = c;
+                    }
+                } else {
+                    tail_lines = atoi(optarg);
+                    if (!tail_lines) {
+                        fprintf(stderr,
+                                "WARNING: -%c %s invalid, setting to 1\n",
+                                ret, optarg);
+                        tail_lines = 1;
+                    }
+                }
             break;
 
             case 'g':
                 getLogSize = 1;
             break;
 
+#ifdef USERDEBUG_BUILD
+
+            case 'G': {
+                // would use atol if not for the multiplier
+                char *cp = optarg;
+                setLogSize = 0;
+                while (('0' <= *cp) && (*cp <= '9')) {
+                    setLogSize *= 10;
+                    setLogSize += *cp - '0';
+                    ++cp;
+                }
+
+                switch(*cp) {
+                case 'g':
+                case 'G':
+                    setLogSize *= 1024;
+                /* FALLTHRU */
+                case 'm':
+                case 'M':
+                    setLogSize *= 1024;
+                /* FALLTHRU */
+                case 'k':
+                case 'K':
+                    setLogSize *= 1024;
+                /* FALLTHRU */
+                case '\0':
+                break;
+
+                default:
+                    setLogSize = 0;
+                }
+
+                if (!setLogSize) {
+                    fprintf(stderr, "ERROR: -G <num><multiplier>\n");
+                    exit(1);
+                }
+            }
+            break;
+
+            case 'p':
+                getPruneList = 1;
+            break;
+
+            case 'P':
+                setPruneList = optarg;
+            break;
+
+#endif
+
             case 'b': {
+                if (strcmp(optarg, "all") == 0) {
+                    while (devices) {
+                        dev = devices;
+                        devices = dev->next;
+                        delete dev;
+                    }
+
+                    dev = devices = new log_device_t("main", false, 'm');
+                    android::g_devCount = 1;
+                    if (android_name_to_log_id("system") == LOG_ID_SYSTEM) {
+                        dev->next = new log_device_t("system", false, 's');
+                        if (dev->next) {
+                            dev = dev->next;
+                            android::g_devCount++;
+                        }
+                    }
+                    if (android_name_to_log_id("radio") == LOG_ID_RADIO) {
+                        dev->next = new log_device_t("radio", false, 'r');
+                        if (dev->next) {
+                            dev = dev->next;
+                            android::g_devCount++;
+                        }
+                    }
+                    if (android_name_to_log_id("events") == LOG_ID_EVENTS) {
+                        dev->next = new log_device_t("events", true, 'e');
+                        if (dev->next) {
+                            android::g_devCount++;
+                            needBinary = true;
+                        }
+                    }
+                    break;
+                }
+
                 bool binary = strcmp(optarg, "events") == 0;
                 if (binary) {
                     needBinary = true;
@@ -470,6 +603,10 @@
                 }
                 break;
 
+            case 'S':
+                printStatistics = 1;
+                break;
+
             default:
                 fprintf(stderr,"Unrecognized Option\n");
                 android::show_help(argv[0]);
@@ -544,7 +681,11 @@
     }
 
     dev = devices;
-    logger_list = android_logger_list_alloc(mode, tail_lines, 0);
+    if (tail_time != log_time::EPOCH) {
+        logger_list = android_logger_list_alloc_time(mode, tail_time, 0);
+    } else {
+        logger_list = android_logger_list_alloc(mode, tail_lines, 0);
+    }
     while (dev) {
         dev->logger_list = logger_list;
         dev->logger = android_logger_open(logger_list,
@@ -558,27 +699,36 @@
             int ret;
             ret = android_logger_clear(dev->logger);
             if (ret) {
-                perror("clearLog");
+                perror("failed to clear the log");
                 exit(EXIT_FAILURE);
             }
         }
 
+#ifdef USERDEBUG_BUILD
+
+        if (setLogSize && android_logger_set_log_size(dev->logger, setLogSize)) {
+            perror("failed to set the log size");
+            exit(EXIT_FAILURE);
+        }
+
+#endif
+
         if (getLogSize) {
-            int size, readable;
+            long size, readable;
 
             size = android_logger_get_log_size(dev->logger);
             if (size < 0) {
-                perror("getLogSize");
+                perror("failed to get the log size");
                 exit(EXIT_FAILURE);
             }
 
             readable = android_logger_get_log_readable_size(dev->logger);
             if (readable < 0) {
-                perror("getLogReadableSize");
+                perror("failed to get the readable log size");
                 exit(EXIT_FAILURE);
             }
 
-            printf("%s: ring buffer is %dKb (%dKb consumed), "
+            printf("%s: ring buffer is %ldKb (%ldKb consumed), "
                    "max entry is %db, max payload is %db\n", dev->device,
                    size / 1024, readable / 1024,
                    (int) LOGGER_ENTRY_MAX_LEN, (int) LOGGER_ENTRY_MAX_PAYLOAD);
@@ -587,9 +737,95 @@
         dev = dev->next;
     }
 
+#ifdef USERDEBUG_BUILD
+
+    if (setPruneList) {
+        size_t len = strlen(setPruneList) + 32; // margin to allow rc
+        char *buf = (char *) malloc(len);
+
+        strcpy(buf, setPruneList);
+        int ret = android_logger_set_prune_list(logger_list, buf, len);
+        free(buf);
+
+        if (ret) {
+            perror("failed to set the prune list");
+            exit(EXIT_FAILURE);
+        }
+    }
+
+#endif
+
+    if (
+#ifdef USERDEBUG_BUILD
+        printStatistics || getPruneList
+#else
+        printStatistics
+#endif
+    ) {
+        size_t len = 8192;
+        char *buf;
+
+        for(int retry = 32;
+                (retry >= 0) && ((buf = new char [len]));
+                delete [] buf, --retry) {
+#ifdef USERDEBUG_BUILD
+            if (getPruneList) {
+                android_logger_get_prune_list(logger_list, buf, len);
+            } else {
+                android_logger_get_statistics(logger_list, buf, len);
+            }
+#else
+            android_logger_get_statistics(logger_list, buf, len);
+#endif
+            buf[len-1] = '\0';
+            size_t ret = atol(buf) + 1;
+            if (ret < 4) {
+                delete [] buf;
+                buf = NULL;
+                break;
+            }
+            bool check = ret <= len;
+            len = ret;
+            if (check) {
+                break;
+            }
+        }
+
+        if (!buf) {
+            perror("failed to read data");
+            exit(EXIT_FAILURE);
+        }
+
+        // remove trailing FF
+        char *cp = buf + len - 1;
+        *cp = '\0';
+        bool truncated = *--cp != '\f';
+        if (!truncated) {
+            *cp = '\0';
+        }
+
+        // squash out the byte count
+        cp = buf;
+        if (!truncated) {
+            while (isdigit(*cp) || (*cp == '\n')) {
+                ++cp;
+            }
+        }
+
+        printf("%s", cp);
+        delete [] buf;
+        exit(0);
+    }
+
+
     if (getLogSize) {
         exit(0);
     }
+#ifdef USERDEBUG_BUILD
+    if (setLogSize || setPruneList) {
+        exit(0);
+    }
+#endif
     if (clearLog) {
         exit(0);
     }
@@ -623,7 +859,7 @@
                 fprintf(stderr, "read: unexpected length.\n");
                 exit(EXIT_FAILURE);
             }
-            perror("logcat read");
+            perror("logcat read failure");
             exit(EXIT_FAILURE);
         }
 
diff --git a/logcat/tests/Android.mk b/logcat/tests/Android.mk
index bdaec14..733af31 100644
--- a/logcat/tests/Android.mk
+++ b/logcat/tests/Android.mk
@@ -28,7 +28,11 @@
     -g \
     -Wall -Wextra \
     -Werror \
-    -fno-builtin \
+    -fno-builtin
+
+ifneq ($(filter userdebug eng,$(TARGET_BUILD_VARIANT)),)
+test_c_flags += -DUSERDEBUG_BUILD=1
+endif
 
 test_src_files := \
     logcat_test.cpp \
diff --git a/logcat/tests/logcat_test.cpp b/logcat/tests/logcat_test.cpp
index fc696bb..818a978 100644
--- a/logcat/tests/logcat_test.cpp
+++ b/logcat/tests/logcat_test.cpp
@@ -527,3 +527,100 @@
 
     ASSERT_EQ(1, signals);
 }
+
+#ifdef USERDEBUG_BUILD
+static bool get_white_black(char **list) {
+    FILE *fp;
+
+    fp = popen("logcat -p 2>/dev/null", "r");
+    if (fp == NULL) {
+        fprintf(stderr, "ERROR: logcat -p 2>/dev/null\n");
+        return false;
+    }
+
+    char buffer[5120];
+
+    while (fgets(buffer, sizeof(buffer), fp)) {
+        char *hold = *list;
+        char *buf = buffer;
+	while (isspace(*buf)) {
+            ++buf;
+        }
+        char *end = buf + strlen(buf);
+        while (isspace(*--end) && (end >= buf)) {
+            *end = '\0';
+        }
+        if (end < buf) {
+            continue;
+        }
+        if (hold) {
+            asprintf(list, "%s %s", hold, buf);
+            free(hold);
+        } else {
+            asprintf(list, "%s", buf);
+        }
+    }
+    pclose(fp);
+    return *list != NULL;
+}
+
+static bool set_white_black(const char *list) {
+    FILE *fp;
+
+    char buffer[5120];
+
+    snprintf(buffer, sizeof(buffer), "logcat -P '%s' 2>&1", list);
+    fp = popen(buffer, "r");
+    if (fp == NULL) {
+        fprintf(stderr, "ERROR: %s\n", buffer);
+        return false;
+    }
+
+    while (fgets(buffer, sizeof(buffer), fp)) {
+        char *buf = buffer;
+	while (isspace(*buf)) {
+            ++buf;
+        }
+        char *end = buf + strlen(buf);
+        while (isspace(*--end) && (end >= buf)) {
+            *end = '\0';
+        }
+        if (end < buf) {
+            continue;
+        }
+        fprintf(stderr, "%s\n", buf);
+        pclose(fp);
+        return false;
+    }
+    return pclose(fp) == 0;
+}
+
+TEST(logcat, white_black_adjust) {
+    char *list = NULL;
+    char *adjust = NULL;
+
+    ASSERT_EQ(true, get_white_black(&list));
+
+    static const char adjustment[] = "~! ~1000";
+    ASSERT_EQ(true, set_white_black(adjustment));
+    ASSERT_EQ(true, get_white_black(&adjust));
+    if (strcmp(adjustment, adjust)) {
+        fprintf(stderr, "ERROR: '%s' != '%s'\n", adjustment, adjust);
+    }
+    ASSERT_STREQ(adjustment, adjust);
+    free(adjust);
+    adjust = NULL;
+
+    ASSERT_EQ(true, set_white_black(list));
+    ASSERT_EQ(true, get_white_black(&adjust));
+    if (strcmp(list, adjust)) {
+        fprintf(stderr, "ERROR: '%s' != '%s'\n", list, adjust);
+    }
+    ASSERT_STREQ(list, adjust);
+    free(adjust);
+    adjust = NULL;
+
+    free(list);
+    list = NULL;
+}
+#endif // USERDEBUG_BUILD
diff --git a/logd/Android.mk b/logd/Android.mk
index 744c9d3..b0bc746 100644
--- a/logd/Android.mk
+++ b/logd/Android.mk
@@ -4,6 +4,10 @@
 
 LOCAL_MODULE:= logd
 
+ifneq ($(filter userdebug eng,$(TARGET_BUILD_VARIANT)),)
+LOCAL_CFLAGS += -DUSERDEBUG_BUILD=1
+endif
+
 LOCAL_SRC_FILES := \
     main.cpp \
     LogCommand.cpp \
@@ -13,12 +17,15 @@
     FlushCommand.cpp \
     LogBuffer.cpp \
     LogBufferElement.cpp \
-    LogTimes.cpp
+    LogTimes.cpp \
+    LogStatistics.cpp \
+    LogWhiteBlackList.cpp
 
 LOCAL_SHARED_LIBRARIES := \
     libsysutils \
     liblog \
-    libcutils
+    libcutils \
+    libutils
 
 LOCAL_MODULE_TAGS := optional
 
diff --git a/logd/CommandListener.cpp b/logd/CommandListener.cpp
index 202d542..12b10ca 100644
--- a/logd/CommandListener.cpp
+++ b/logd/CommandListener.cpp
@@ -37,7 +37,15 @@
     // registerCmd(new ShutdownCmd(buf, writer, swl));
     registerCmd(new ClearCmd(buf));
     registerCmd(new GetBufSizeCmd(buf));
+#ifdef USERDEBUG_BUILD
+    registerCmd(new SetBufSizeCmd(buf));
+#endif
     registerCmd(new GetBufSizeUsedCmd(buf));
+    registerCmd(new GetStatisticsCmd(buf));
+#ifdef USERDEBUG_BUILD
+    registerCmd(new SetPruneListCmd(buf));
+    registerCmd(new GetPruneListCmd(buf));
+#endif
 }
 
 CommandListener::ShutdownCmd::ShutdownCmd(LogBuffer *buf, LogReader *reader,
@@ -109,6 +117,43 @@
     return 0;
 }
 
+#ifdef USERDEBUG_BUILD
+
+CommandListener::SetBufSizeCmd::SetBufSizeCmd(LogBuffer *buf)
+        : LogCommand("setLogSize")
+        , mBuf(*buf)
+{ }
+
+int CommandListener::SetBufSizeCmd::runCommand(SocketClient *cli,
+                                         int argc, char **argv) {
+    if (!clientHasLogCredentials(cli)) {
+        cli->sendMsg("Permission Denied");
+        return 0;
+    }
+
+    if (argc < 3) {
+        cli->sendMsg("Missing Argument");
+        return 0;
+    }
+
+    int id = atoi(argv[1]);
+    if ((id < LOG_ID_MIN) || (LOG_ID_MAX <= id)) {
+        cli->sendMsg("Range Error");
+        return 0;
+    }
+
+    unsigned long size = atol(argv[2]);
+    if (mBuf.setSize((log_id_t) id, size)) {
+        cli->sendMsg("Range Error");
+        return 0;
+    }
+
+    cli->sendMsg("success");
+    return 0;
+}
+
+#endif // USERDEBUG_BUILD
+
 CommandListener::GetBufSizeUsedCmd::GetBufSizeUsedCmd(LogBuffer *buf)
         : LogCommand("getLogSizeUsed")
         , mBuf(*buf)
@@ -133,3 +178,120 @@
     cli->sendMsg(buf);
     return 0;
 }
+
+CommandListener::GetStatisticsCmd::GetStatisticsCmd(LogBuffer *buf)
+        : LogCommand("getStatistics")
+        , mBuf(*buf)
+{ }
+
+static void package_string(char **strp) {
+    const char *a = *strp;
+    if (!a) {
+        a = "";
+    }
+
+    // Calculate total buffer size prefix, count is the string length w/o nul
+    char fmt[32];
+    for(size_t l = strlen(a), y = 0, x = 6; y != x; y = x, x = strlen(fmt) - 2) {
+        snprintf(fmt, sizeof(fmt), "%zu\n%%s\n\f", l + x);
+    }
+
+    char *b = *strp;
+    *strp = NULL;
+    asprintf(strp, fmt, a);
+    free(b);
+}
+
+int CommandListener::GetStatisticsCmd::runCommand(SocketClient *cli,
+                                         int argc, char **argv) {
+    uid_t uid = cli->getUid();
+    gid_t gid = cli->getGid();
+    if (clientHasLogCredentials(cli)) {
+        uid = AID_ROOT;
+    }
+
+    unsigned int logMask = -1;
+    if (argc > 1) {
+        logMask = 0;
+        for (int i = 1; i < argc; ++i) {
+            int id = atoi(argv[i]);
+            if ((id < LOG_ID_MIN) || (LOG_ID_MAX <= id)) {
+                cli->sendMsg("Range Error");
+                return 0;
+            }
+            logMask |= 1 << id;
+        }
+    }
+
+    char *buf = NULL;
+
+    mBuf.formatStatistics(&buf, uid, logMask);
+    if (!buf) {
+        cli->sendMsg("Failed");
+    } else {
+        package_string(&buf);
+        cli->sendMsg(buf);
+        free(buf);
+    }
+    return 0;
+}
+
+#ifdef USERDEBUG_BUILD
+
+CommandListener::GetPruneListCmd::GetPruneListCmd(LogBuffer *buf)
+        : LogCommand("getPruneList")
+        , mBuf(*buf)
+{ }
+
+int CommandListener::GetPruneListCmd::runCommand(SocketClient *cli,
+                                         int /*argc*/, char ** /*argv*/) {
+    char *buf = NULL;
+    mBuf.formatPrune(&buf);
+    if (!buf) {
+        cli->sendMsg("Failed");
+    } else {
+        package_string(&buf);
+        cli->sendMsg(buf);
+        free(buf);
+    }
+    return 0;
+}
+
+CommandListener::SetPruneListCmd::SetPruneListCmd(LogBuffer *buf)
+        : LogCommand("setPruneList")
+        , mBuf(*buf)
+{ }
+
+int CommandListener::SetPruneListCmd::runCommand(SocketClient *cli,
+                                         int argc, char **argv) {
+    if (!clientHasLogCredentials(cli)) {
+        cli->sendMsg("Permission Denied");
+        return 0;
+    }
+
+    char *cp = NULL;
+    for (int i = 1; i < argc; ++i) {
+        char *p = cp;
+        if (p) {
+            cp = NULL;
+            asprintf(&cp, "%s %s", p, argv[i]);
+            free(p);
+        } else {
+            asprintf(&cp, "%s", argv[i]);
+        }
+    }
+
+    int ret = mBuf.initPrune(cp);
+    free(cp);
+
+    if (ret) {
+        cli->sendMsg("Invalid");
+        return 0;
+    }
+
+    cli->sendMsg("success");
+
+    return 0;
+}
+
+#endif // USERDEBUG_BUILD
diff --git a/logd/CommandListener.h b/logd/CommandListener.h
index 861abbf..de1dcb9 100644
--- a/logd/CommandListener.h
+++ b/logd/CommandListener.h
@@ -53,7 +53,15 @@
 
     LogBufferCmd(Clear)
     LogBufferCmd(GetBufSize)
+#ifdef USERDEBUG_BUILD
+    LogBufferCmd(SetBufSize)
+#endif
     LogBufferCmd(GetBufSizeUsed)
+    LogBufferCmd(GetStatistics)
+#ifdef USERDEBUG_BUILD
+    LogBufferCmd(GetPruneList)
+    LogBufferCmd(SetPruneList)
+#endif
 };
 
 #endif
diff --git a/logd/FlushCommand.cpp b/logd/FlushCommand.cpp
index 0b8c31b..3be07c0 100644
--- a/logd/FlushCommand.cpp
+++ b/logd/FlushCommand.cpp
@@ -26,12 +26,14 @@
                            bool nonBlock,
                            unsigned long tail,
                            unsigned int logMask,
-                           pid_t pid)
+                           pid_t pid,
+                           log_time start)
         : mReader(reader)
         , mNonBlock(nonBlock)
         , mTail(tail)
         , mLogMask(logMask)
         , mPid(pid)
+        , mStart(start)
 { }
 
 // runSocketCommand is called once for every open client on the
@@ -69,7 +71,7 @@
             LogTimeEntry::unlock();
             return;
         }
-        entry = new LogTimeEntry(mReader, client, mNonBlock, mTail, mLogMask, mPid);
+        entry = new LogTimeEntry(mReader, client, mNonBlock, mTail, mLogMask, mPid, mStart);
         times.push_back(entry);
     }
 
diff --git a/logd/FlushCommand.h b/logd/FlushCommand.h
index 715daac..f34c06a 100644
--- a/logd/FlushCommand.h
+++ b/logd/FlushCommand.h
@@ -16,8 +16,13 @@
 #ifndef _FLUSH_COMMAND_H
 #define _FLUSH_COMMAND_H
 
+#include <log/log_read.h>
 #include <sysutils/SocketClientCommand.h>
 
+class LogBufferElement;
+
+#include "LogTimes.h"
+
 class LogReader;
 
 class FlushCommand : public SocketClientCommand {
@@ -26,13 +31,15 @@
     unsigned long mTail;
     unsigned int mLogMask;
     pid_t mPid;
+    log_time mStart;
 
 public:
     FlushCommand(LogReader &mReader,
                  bool nonBlock = false,
                  unsigned long tail = -1,
                  unsigned int logMask = -1,
-                 pid_t pid = 0);
+                 pid_t pid = 0,
+                 log_time start = LogTimeEntry::EPOCH);
     virtual void runSocketCommand(SocketClient *client);
 
     static bool hasReadLogs(SocketClient *client);
diff --git a/logd/LogBuffer.cpp b/logd/LogBuffer.cpp
index c5760f7..197b7e8 100644
--- a/logd/LogBuffer.cpp
+++ b/logd/LogBuffer.cpp
@@ -22,18 +22,26 @@
 #include <log/logger.h>
 
 #include "LogBuffer.h"
+#include "LogStatistics.h"
+#include "LogWhiteBlackList.h"
 #include "LogReader.h"
 
+// Default
 #define LOG_BUFFER_SIZE (256 * 1024) // Tuned on a per-platform basis here?
+#ifdef USERDEBUG_BUILD
+#define log_buffer_size(id) mMaxSize[id]
+#else
+#define log_buffer_size(id) LOG_BUFFER_SIZE
+#endif
 
 LogBuffer::LogBuffer(LastLogTimes *times)
         : mTimes(*times) {
-    int i;
-    for (i = 0; i < LOG_ID_MAX; i++) {
-        mSizes[i] = 0;
-        mElements[i] = 0;
-    }
     pthread_mutex_init(&mLogElementsLock, NULL);
+#ifdef USERDEBUG_BUILD
+    log_id_for_each(i) {
+        mMaxSize[i] = LOG_BUFFER_SIZE;
+    }
+#endif
 }
 
 void LogBuffer::log(log_id_t log_id, log_time realtime,
@@ -93,8 +101,7 @@
         LogTimeEntry::unlock();
     }
 
-    mSizes[log_id] += len;
-    mElements[log_id]++;
+    stats.add(len, log_id, uid, pid);
     maybePrune(log_id);
     pthread_mutex_unlock(&mLogElementsLock);
 }
@@ -104,10 +111,10 @@
 //
 // mLogElementsLock must be held when this function is called.
 void LogBuffer::maybePrune(log_id_t id) {
-    unsigned long sizes = mSizes[id];
-    if (sizes > LOG_BUFFER_SIZE) {
-        unsigned long sizeOver90Percent = sizes - ((LOG_BUFFER_SIZE * 9) / 10);
-        unsigned long elements = mElements[id];
+    size_t sizes = stats.sizes(id);
+    if (sizes > log_buffer_size(id)) {
+        size_t sizeOver90Percent = sizes - ((log_buffer_size(id) * 9) / 10);
+        size_t elements = stats.elements(id);
         unsigned long pruneRows = elements * sizeOver90Percent / sizes;
         elements /= 10;
         if (pruneRows <= elements) {
@@ -136,22 +143,117 @@
         t++;
     }
 
-    LogBufferElementCollection::iterator it = mLogElements.begin();
+    LogBufferElementCollection::iterator it;
+
+    // prune by worst offender by uid
+    while (pruneRows > 0) {
+        // recalculate the worst offender on every batched pass
+        uid_t worst = (uid_t) -1;
+        size_t worst_sizes = 0;
+        size_t second_worst_sizes = 0;
+
+#ifdef USERDEBUG_BUILD
+        if (mPrune.worstUidEnabled())
+#endif
+        {
+            LidStatistics &l = stats.id(id);
+            UidStatisticsCollection::iterator iu;
+            for (iu = l.begin(); iu != l.end(); ++iu) {
+                UidStatistics *u = (*iu);
+                size_t sizes = u->sizes();
+                if (worst_sizes < sizes) {
+                    second_worst_sizes = worst_sizes;
+                    worst_sizes = sizes;
+                    worst = u->getUid();
+                }
+                if ((second_worst_sizes < sizes) && (sizes < worst_sizes)) {
+                    second_worst_sizes = sizes;
+                }
+            }
+        }
+
+        bool kick = false;
+        for(it = mLogElements.begin(); it != mLogElements.end();) {
+            LogBufferElement *e = *it;
+
+            if (oldest && (oldest->mStart <= e->getMonotonicTime())) {
+                break;
+            }
+
+            if (e->getLogId() != id) {
+                ++it;
+                continue;
+            }
+
+            uid_t uid = e->getUid();
+
+            if (uid == worst) {
+                it = mLogElements.erase(it);
+                unsigned short len = e->getMsgLen();
+                stats.subtract(len, id, worst, e->getPid());
+                delete e;
+                kick = true;
+                pruneRows--;
+                if ((pruneRows == 0) || (worst_sizes < second_worst_sizes)) {
+                    break;
+                }
+                worst_sizes -= len;
+            }
+#ifdef USERDEBUG_BUILD
+            else if (mPrune.naughty(e)) { // BlackListed
+                it = mLogElements.erase(it);
+                stats.subtract(e->getMsgLen(), id, uid, e->getPid());
+                delete e;
+                pruneRows--;
+                if (pruneRows == 0) {
+                    break;
+                }
+            }
+#endif
+            else {
+                ++it;
+            }
+        }
+
+        if (!kick
+#ifdef USERDEBUG_BUILD
+                || !mPrune.worstUidEnabled()
+#endif
+        ) {
+            break; // the following loop will ask bad clients to skip/drop
+        }
+    }
+
+#ifdef USERDEBUG_BUILD
+    bool whitelist = false;
+#endif
+    it = mLogElements.begin();
     while((pruneRows > 0) && (it != mLogElements.end())) {
         LogBufferElement *e = *it;
         if (e->getLogId() == id) {
             if (oldest && (oldest->mStart <= e->getMonotonicTime())) {
-                if (mSizes[id] > (2 * LOG_BUFFER_SIZE)) {
-                    // kick a misbehaving log reader client off the island
-                    oldest->release_Locked();
-                } else {
-                    oldest->triggerSkip_Locked(pruneRows);
+#ifdef USERDEBUG_BUILD
+                if (!whitelist)
+#endif
+                {
+                    if (stats.sizes(id) > (2 * log_buffer_size(id))) {
+                        // kick a misbehaving log reader client off the island
+                        oldest->release_Locked();
+                    } else {
+                        oldest->triggerSkip_Locked(pruneRows);
+                    }
                 }
                 break;
             }
+#ifdef USERDEBUG_BUILD
+            if (mPrune.nice(e)) { // WhiteListed
+                whitelist = true;
+                it++;
+                continue;
+            }
+#endif
             it = mLogElements.erase(it);
-            mSizes[id] -= e->getMsgLen();
-            mElements[id]--;
+            stats.subtract(e->getMsgLen(), id, e->getUid(), e->getPid());
             delete e;
             pruneRows--;
         } else {
@@ -159,6 +261,32 @@
         }
     }
 
+#ifdef USERDEBUG_BUILD
+    if (whitelist && (pruneRows > 0)) {
+        it = mLogElements.begin();
+        while((it != mLogElements.end()) && (pruneRows > 0)) {
+            LogBufferElement *e = *it;
+            if (e->getLogId() == id) {
+                if (oldest && (oldest->mStart <= e->getMonotonicTime())) {
+                    if (stats.sizes(id) > (2 * log_buffer_size(id))) {
+                        // kick a misbehaving log reader client off the island
+                        oldest->release_Locked();
+                    } else {
+                        oldest->triggerSkip_Locked(pruneRows);
+                    }
+                    break;
+                }
+                it = mLogElements.erase(it);
+                stats.subtract(e->getMsgLen(), id, e->getUid(), e->getPid());
+                delete e;
+                pruneRows--;
+            } else {
+                it++;
+            }
+        }
+    }
+#endif
+
     LogTimeEntry::unlock();
 }
 
@@ -172,16 +300,42 @@
 // get the used space associated with "id".
 unsigned long LogBuffer::getSizeUsed(log_id_t id) {
     pthread_mutex_lock(&mLogElementsLock);
-    unsigned long retval = mSizes[id];
+    size_t retval = stats.sizes(id);
     pthread_mutex_unlock(&mLogElementsLock);
     return retval;
 }
 
+#ifdef USERDEBUG_BUILD
+
+// set the total space allocated to "id"
+int LogBuffer::setSize(log_id_t id, unsigned long size) {
+    // Reasonable limits ...
+    if ((size < (64 * 1024)) || ((256 * 1024 * 1024) < size)) {
+        return -1;
+    }
+    pthread_mutex_lock(&mLogElementsLock);
+    log_buffer_size(id) = size;
+    pthread_mutex_unlock(&mLogElementsLock);
+    return 0;
+}
+
+// get the total space allocated to "id"
+unsigned long LogBuffer::getSize(log_id_t id) {
+    pthread_mutex_lock(&mLogElementsLock);
+    size_t retval = log_buffer_size(id);
+    pthread_mutex_unlock(&mLogElementsLock);
+    return retval;
+}
+
+#else // ! USERDEBUG_BUILD
+
 // get the total space allocated to "id"
 unsigned long LogBuffer::getSize(log_id_t /*id*/) {
-    return LOG_BUFFER_SIZE;
+    return log_buffer_size(id);
 }
 
+#endif
+
 log_time LogBuffer::flushTo(
         SocketClient *reader, const log_time start, bool privileged,
         bool (*filter)(const LogBufferElement *element, void *arg), void *arg) {
@@ -221,3 +375,24 @@
 
     return max;
 }
+
+void LogBuffer::formatStatistics(char **strp, uid_t uid, unsigned int logMask) {
+    log_time oldest(CLOCK_MONOTONIC);
+
+    pthread_mutex_lock(&mLogElementsLock);
+
+    // Find oldest element in the log(s)
+    LogBufferElementCollection::iterator it;
+    for (it = mLogElements.begin(); it != mLogElements.end(); ++it) {
+        LogBufferElement *element = *it;
+
+        if ((logMask & (1 << element->getLogId()))) {
+            oldest = element->getMonotonicTime();
+            break;
+        }
+    }
+
+    stats.format(strp, uid, logMask, oldest);
+
+    pthread_mutex_unlock(&mLogElementsLock);
+}
diff --git a/logd/LogBuffer.h b/logd/LogBuffer.h
index 1b50a8f..0745e56 100644
--- a/logd/LogBuffer.h
+++ b/logd/LogBuffer.h
@@ -25,6 +25,8 @@
 
 #include "LogBufferElement.h"
 #include "LogTimes.h"
+#include "LogStatistics.h"
+#include "LogWhiteBlackList.h"
 
 typedef android::List<LogBufferElement *> LogBufferElementCollection;
 
@@ -32,8 +34,13 @@
     LogBufferElementCollection mLogElements;
     pthread_mutex_t mLogElementsLock;
 
-    unsigned long mSizes[LOG_ID_MAX];
-    unsigned long mElements[LOG_ID_MAX];
+    LogStatistics stats;
+
+#ifdef USERDEBUG_BUILD
+    PruneList mPrune;
+
+    unsigned long mMaxSize[LOG_ID_MAX];
+#endif
 
 public:
     LastLogTimes &mTimes;
@@ -49,7 +56,18 @@
 
     void clear(log_id_t id);
     unsigned long getSize(log_id_t id);
+#ifdef USERDEBUG_BUILD
+    int setSize(log_id_t id, unsigned long size);
+#endif
     unsigned long getSizeUsed(log_id_t id);
+    // *strp uses malloc, use free to release.
+    void formatStatistics(char **strp, uid_t uid, unsigned int logMask);
+
+#ifdef USERDEBUG_BUILD
+    int initPrune(char *cp) { return mPrune.init(cp); }
+    // *strp uses malloc, use free to release.
+    void formatPrune(char **strp) { mPrune.format(strp); }
+#endif
 
 private:
     void maybePrune(log_id_t id);
@@ -57,4 +75,4 @@
 
 };
 
-#endif
+#endif // _LOGD_LOG_BUFFER_H__
diff --git a/logd/LogBufferElement.cpp b/logd/LogBufferElement.cpp
index 01cc9de..8d45f34 100644
--- a/logd/LogBufferElement.cpp
+++ b/logd/LogBufferElement.cpp
@@ -24,7 +24,7 @@
 #include "LogBufferElement.h"
 #include "LogReader.h"
 
-const log_time LogBufferElement::FLUSH_ERROR(0, 0);
+const log_time LogBufferElement::FLUSH_ERROR((uint32_t)0, (uint32_t)0);
 
 LogBufferElement::LogBufferElement(log_id_t log_id, log_time realtime,
                                    uid_t uid, pid_t pid, const char *msg,
diff --git a/logd/LogListener.cpp b/logd/LogListener.cpp
index c6b248b..2aa2ebb 100644
--- a/logd/LogListener.cpp
+++ b/logd/LogListener.cpp
@@ -31,7 +31,8 @@
 {  }
 
 bool LogListener::onDataAvailable(SocketClient *cli) {
-    char buffer[1024];
+    char buffer[sizeof_log_id_t + sizeof(log_time) + sizeof(char)
+        + LOGGER_ENTRY_MAX_PAYLOAD];
     struct iovec iov = { buffer, sizeof(buffer) };
     memset(buffer, 0, sizeof(buffer));
 
diff --git a/logd/LogReader.cpp b/logd/LogReader.cpp
index 5b540bf..60a3507 100644
--- a/logd/LogReader.cpp
+++ b/logd/LogReader.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <ctype.h>
 #include <poll.h>
 #include <sys/socket.h>
 #include <cutils/sockets.h>
@@ -50,6 +51,14 @@
         tail = atol(cp + sizeof(_tail) - 1);
     }
 
+    log_time start(log_time::EPOCH);
+    static const char _start[] = " start=";
+    cp = strstr(buffer, _start);
+    if (cp) {
+        // Parse errors will result in current time
+        start.strptime(cp + sizeof(_start) - 1, "%s.%q");
+    }
+
     unsigned int logMask = -1;
     static const char _logIds[] = " lids=";
     cp = strstr(buffer, _logIds);
@@ -58,9 +67,8 @@
         cp += sizeof(_logIds) - 1;
         while (*cp && *cp != '\0') {
             int val = 0;
-            while (('0' <= *cp) && (*cp <= '9')) {
-                val *= 10;
-                val += *cp - '0';
+            while (isdigit(*cp)) {
+                val = val * 10 + *cp - '0';
                 ++cp;
             }
             logMask |= 1 << val;
@@ -83,7 +91,63 @@
         nonBlock = true;
     }
 
-    FlushCommand command(*this, nonBlock, tail, logMask, pid);
+    // Convert realtime to monotonic time
+    if (start == log_time::EPOCH) {
+        start = LogTimeEntry::EPOCH;
+    } else {
+        class LogFindStart {
+            const pid_t mPid;
+            const unsigned mLogMask;
+            bool startTimeSet;
+            log_time &start;
+            log_time last;
+
+        public:
+            LogFindStart(unsigned logMask, pid_t pid, log_time &start)
+                    : mPid(pid)
+                    , mLogMask(logMask)
+                    , startTimeSet(false)
+                    , start(start)
+                    , last(LogTimeEntry::EPOCH)
+            { }
+
+            static bool callback(const LogBufferElement *element, void *obj) {
+                LogFindStart *me = reinterpret_cast<LogFindStart *>(obj);
+                if (!me->startTimeSet
+                        && (!me->mPid || (me->mPid == element->getPid()))
+                        && (me->mLogMask & (1 << element->getLogId()))) {
+                    if (me->start == element->getRealTime()) {
+                        me->start = element->getMonotonicTime();
+                        me->startTimeSet = true;
+                    } else {
+                        if (me->start < element->getRealTime()) {
+                            me->start = me->last;
+                            me->startTimeSet = true;
+                        }
+                        me->last = element->getMonotonicTime();
+                    }
+                }
+                return false;
+            }
+
+            bool found() { return startTimeSet; }
+        } logFindStart(logMask, pid, start);
+
+        logbuf().flushTo(cli, LogTimeEntry::EPOCH,
+                         FlushCommand::hasReadLogs(cli),
+                         logFindStart.callback, &logFindStart);
+
+        if (!logFindStart.found()) {
+            if (nonBlock) {
+                doSocketDelete(cli);
+                return false;
+            }
+            log_time now(CLOCK_MONOTONIC);
+            start = now;
+        }
+    }
+
+    FlushCommand command(*this, nonBlock, tail, logMask, pid, start);
     command.runSocketCommand(cli);
     return true;
 }
diff --git a/logd/LogStatistics.cpp b/logd/LogStatistics.cpp
new file mode 100644
index 0000000..49ee50d
--- /dev/null
+++ b/logd/LogStatistics.cpp
@@ -0,0 +1,555 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdarg.h>
+#include <time.h>
+
+#include <log/logger.h>
+#include <private/android_filesystem_config.h>
+#include <utils/String8.h>
+
+#include "LogStatistics.h"
+
+PidStatistics::PidStatistics(pid_t pid)
+        : pid(pid)
+        , mSizesTotal(0)
+        , mElementsTotal(0)
+        , mSizes(0)
+        , mElements(0) { }
+
+void PidStatistics::add(unsigned short size) {
+    mSizesTotal += size;
+    ++mElementsTotal;
+    mSizes += size;
+    ++mElements;
+}
+
+bool PidStatistics::subtract(unsigned short size) {
+    mSizes -= size;
+    --mElements;
+    return mElements == 0 && kill(pid, 0);
+}
+
+void PidStatistics::addTotal(size_t size, size_t element) {
+    if (pid == gone) {
+        mSizesTotal += size;
+        mElementsTotal += element;
+    }
+}
+
+UidStatistics::UidStatistics(uid_t uid)
+        : uid(uid) {
+    Pids.clear();
+}
+
+UidStatistics::~UidStatistics() {
+    PidStatisticsCollection::iterator it;
+    for (it = begin(); it != end();) {
+        delete (*it);
+        it = Pids.erase(it);
+    }
+}
+
+void UidStatistics::add(unsigned short size, pid_t pid) {
+    PidStatistics *p;
+    PidStatisticsCollection::iterator last;
+    PidStatisticsCollection::iterator it;
+    for (last = it = begin(); it != end(); last = it, ++it) {
+        p = *it;
+        if (pid == p->getPid()) {
+            p->add(size);
+            // poor-man sort, bubble upwards if bigger than last
+            if ((last != it) && ((*last)->sizesTotal() < p->sizesTotal())) {
+                Pids.erase(it);
+                Pids.insert(last, p);
+            }
+            return;
+        }
+    }
+    // poor-man sort, insert if bigger than last or last is the gone entry.
+    bool insert = (last != it)
+        && ((p->getPid() == p->gone)
+            || ((*last)->sizesTotal() < (size_t) size));
+    p = new PidStatistics(pid);
+    if (insert) {
+        Pids.insert(last, p);
+    } else {
+        Pids.push_back(p);
+    }
+    p->add(size);
+}
+
+void UidStatistics::subtract(unsigned short size, pid_t pid) {
+    PidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        PidStatistics *p = *it;
+        if (pid == p->getPid()) {
+            if (p->subtract(size)) {
+                size_t szsTotal = p->sizesTotal();
+                size_t elsTotal = p->elementsTotal();
+                delete p;
+                Pids.erase(it);
+                it = end();
+                --it;
+                if (it == end()) {
+                    p = new PidStatistics(p->gone);
+                    Pids.push_back(p);
+                } else {
+                    p = *it;
+                    if (p->getPid() != p->gone) {
+                        p = new PidStatistics(p->gone);
+                        Pids.push_back(p);
+                    }
+                }
+                p->addTotal(szsTotal, elsTotal);
+            }
+            return;
+        }
+    }
+}
+
+size_t UidStatistics::sizes(pid_t pid) {
+    size_t sizes = 0;
+    PidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        PidStatistics *p = *it;
+        if ((pid == pid_all) || (pid == p->getPid())) {
+            sizes += p->sizes();
+        }
+    }
+    return sizes;
+}
+
+size_t UidStatistics::elements(pid_t pid) {
+    size_t elements = 0;
+    PidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        PidStatistics *p = *it;
+        if ((pid == pid_all) || (pid == p->getPid())) {
+            elements += p->elements();
+        }
+    }
+    return elements;
+}
+
+size_t UidStatistics::sizesTotal(pid_t pid) {
+    size_t sizes = 0;
+    PidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        PidStatistics *p = *it;
+        if ((pid == pid_all) || (pid == p->getPid())) {
+            sizes += p->sizesTotal();
+        }
+    }
+    return sizes;
+}
+
+size_t UidStatistics::elementsTotal(pid_t pid) {
+    size_t elements = 0;
+    PidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        PidStatistics *p = *it;
+        if ((pid == pid_all) || (pid == p->getPid())) {
+            elements += p->elementsTotal();
+        }
+    }
+    return elements;
+}
+
+LidStatistics::LidStatistics() {
+    Uids.clear();
+}
+
+LidStatistics::~LidStatistics() {
+    UidStatisticsCollection::iterator it;
+    for (it = begin(); it != end();) {
+        delete (*it);
+        it = Uids.erase(it);
+    }
+}
+
+void LidStatistics::add(unsigned short size, uid_t uid, pid_t pid) {
+    UidStatistics *u;
+    UidStatisticsCollection::iterator it;
+    UidStatisticsCollection::iterator last;
+
+    if (uid == (uid_t) -1) { // init
+        uid = (uid_t) AID_ROOT;
+    }
+
+    for (last = it = begin(); it != end(); last = it, ++it) {
+        u = *it;
+        if (uid == u->getUid()) {
+            u->add(size, pid);
+            if ((last != it) && ((*last)->sizesTotal() < u->sizesTotal())) {
+                Uids.erase(it);
+                Uids.insert(last, u);
+            }
+            return;
+        }
+    }
+    u = new UidStatistics(uid);
+    if ((last != it) && ((*last)->sizesTotal() < (size_t) size)) {
+        Uids.insert(last, u);
+    } else {
+        Uids.push_back(u);
+    }
+    u->add(size, pid);
+}
+
+void LidStatistics::subtract(unsigned short size, uid_t uid, pid_t pid) {
+    UidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        UidStatistics *u = *it;
+        if (uid == u->getUid()) {
+            u->subtract(size, pid);
+            return;
+        }
+    }
+}
+
+size_t LidStatistics::sizes(uid_t uid, pid_t pid) {
+    size_t sizes = 0;
+    UidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        UidStatistics *u = *it;
+        if ((uid == uid_all) || (uid == u->getUid())) {
+            sizes += u->sizes(pid);
+        }
+    }
+    return sizes;
+}
+
+size_t LidStatistics::elements(uid_t uid, pid_t pid) {
+    size_t elements = 0;
+    UidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        UidStatistics *u = *it;
+        if ((uid == uid_all) || (uid == u->getUid())) {
+            elements += u->elements(pid);
+        }
+    }
+    return elements;
+}
+
+size_t LidStatistics::sizesTotal(uid_t uid, pid_t pid) {
+    size_t sizes = 0;
+    UidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        UidStatistics *u = *it;
+        if ((uid == uid_all) || (uid == u->getUid())) {
+            sizes += u->sizesTotal(pid);
+        }
+    }
+    return sizes;
+}
+
+size_t LidStatistics::elementsTotal(uid_t uid, pid_t pid) {
+    size_t elements = 0;
+    UidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        UidStatistics *u = *it;
+        if ((uid == uid_all) || (uid == u->getUid())) {
+            elements += u->elementsTotal(pid);
+        }
+    }
+    return elements;
+}
+
+LogStatistics::LogStatistics()
+        : start(CLOCK_MONOTONIC) {
+    log_id_for_each(i) {
+        mSizes[i] = 0;
+        mElements[i] = 0;
+    }
+}
+
+void LogStatistics::add(unsigned short size,
+                        log_id_t log_id, uid_t uid, pid_t pid) {
+    mSizes[log_id] += size;
+    ++mElements[log_id];
+    id(log_id).add(size, uid, pid);
+}
+
+void LogStatistics::subtract(unsigned short size,
+                             log_id_t log_id, uid_t uid, pid_t pid) {
+    mSizes[log_id] -= size;
+    --mElements[log_id];
+    id(log_id).subtract(size, uid, pid);
+}
+
+size_t LogStatistics::sizes(log_id_t log_id, uid_t uid, pid_t pid) {
+    if (log_id != log_id_all) {
+        return id(log_id).sizes(uid, pid);
+    }
+    size_t sizes = 0;
+    log_id_for_each(i) {
+        sizes += id(i).sizes(uid, pid);
+    }
+    return sizes;
+}
+
+size_t LogStatistics::elements(log_id_t log_id, uid_t uid, pid_t pid) {
+    if (log_id != log_id_all) {
+        return id(log_id).elements(uid, pid);
+    }
+    size_t elements = 0;
+    log_id_for_each(i) {
+        elements += id(i).elements(uid, pid);
+    }
+    return elements;
+}
+
+size_t LogStatistics::sizesTotal(log_id_t log_id, uid_t uid, pid_t pid) {
+    if (log_id != log_id_all) {
+        return id(log_id).sizesTotal(uid, pid);
+    }
+    size_t sizes = 0;
+    log_id_for_each(i) {
+        sizes += id(i).sizesTotal(uid, pid);
+    }
+    return sizes;
+}
+
+size_t LogStatistics::elementsTotal(log_id_t log_id, uid_t uid, pid_t pid) {
+    if (log_id != log_id_all) {
+        return id(log_id).elementsTotal(uid, pid);
+    }
+    size_t elements = 0;
+    log_id_for_each(i) {
+        elements += id(i).elementsTotal(uid, pid);
+    }
+    return elements;
+}
+
+void LogStatistics::format(char **buf,
+                           uid_t uid, unsigned int logMask, log_time oldest) {
+    const unsigned short spaces_current = 13;
+    const unsigned short spaces_total = 19;
+
+    if (*buf) {
+        free(buf);
+        *buf = NULL;
+    }
+
+    android::String8 string("        span -> size/num");
+    size_t oldLength;
+    short spaces = 2;
+
+    log_id_for_each(i) {
+        if (logMask & (1 << i)) {
+            oldLength = string.length();
+            string.appendFormat("%*s%s", spaces, "", android_log_id_to_name(i));
+            spaces += spaces_total + oldLength - string.length();
+        }
+    }
+
+    spaces = 1;
+    log_time t(CLOCK_MONOTONIC);
+    unsigned long long d = t.nsec() - start.nsec();
+    string.appendFormat("\nTotal%4llu:%02llu:%02llu.%09llu",
+                  d / NS_PER_SEC / 60 / 60, (d / NS_PER_SEC / 60) % 60,
+                  (d / NS_PER_SEC) % 60, d % NS_PER_SEC);
+
+    log_id_for_each(i) {
+        if (!(logMask & (1 << i))) {
+            continue;
+        }
+        oldLength = string.length();
+        string.appendFormat("%*s%zu/%zu", spaces, "",
+                            sizesTotal(i), elementsTotal(i));
+        spaces += spaces_total + oldLength - string.length();
+    }
+
+    spaces = 1;
+    d = t.nsec() - oldest.nsec();
+    string.appendFormat("\nNow%6llu:%02llu:%02llu.%09llu",
+                  d / NS_PER_SEC / 60 / 60, (d / NS_PER_SEC / 60) % 60,
+                  (d / NS_PER_SEC) % 60, d % NS_PER_SEC);
+
+    log_id_for_each(i) {
+        if (!(logMask & (1 << i))) {
+            continue;
+        }
+
+        size_t els = elements(i);
+        if (els) {
+            oldLength = string.length();
+            string.appendFormat("%*s%zu/%zu", spaces, "", sizes(i), els);
+            spaces -= string.length() - oldLength;
+        }
+        spaces += spaces_total;
+    }
+
+    log_id_for_each(i) {
+        if (!(logMask & (1 << i))) {
+            continue;
+        }
+
+        bool header = false;
+        bool first = true;
+
+        UidStatisticsCollection::iterator ut;
+        for(ut = id(i).begin(); ut != id(i).end(); ++ut) {
+            UidStatistics *up = *ut;
+            if ((uid != AID_ROOT) && (uid != up->getUid())) {
+                continue;
+            }
+
+            PidStatisticsCollection::iterator pt = up->begin();
+            if (pt == up->end()) {
+                continue;
+            }
+
+            android::String8 intermediate;
+
+            if (!header) {
+                // header below tuned to match spaces_total and spaces_current
+                spaces = 0;
+                intermediate = string.format("%s: UID/PID Total size/num",
+                                             android_log_id_to_name(i));
+                string.appendFormat("\n\n%-31sNow          "
+                                         "UID/PID[?]  Total              Now",
+                                    intermediate.string());
+                intermediate.clear();
+                header = true;
+            }
+
+            bool oneline = ++pt == up->end();
+            --pt;
+
+            if (!oneline) {
+                first = true;
+            } else if (!first && spaces) {
+                string.appendFormat("%*s", spaces, "");
+            }
+            spaces = 0;
+
+            uid_t u = up->getUid();
+            pid_t p = (*pt)->getPid();
+
+            intermediate = string.format(oneline
+                                             ? ((p == PidStatistics::gone)
+                                                 ? "%d/?"
+                                                 : "%d/%d")
+                                             : "%d",
+                                         u, p);
+            string.appendFormat((first) ? "\n%-12s" : "%-12s",
+                                intermediate.string());
+            intermediate.clear();
+
+            size_t elsTotal = up->elementsTotal();
+            oldLength = string.length();
+            string.appendFormat("%zu/%zu", up->sizesTotal(), elsTotal);
+            spaces += spaces_total + oldLength - string.length();
+
+            size_t els = up->elements();
+            if (els == elsTotal) {
+                string.appendFormat("%*s=", spaces, "");
+                spaces = -1;
+            } else if (els) {
+                oldLength = string.length();
+                string.appendFormat("%*s%zu/%zu", spaces, "", up->sizes(), els);
+                spaces -= string.length() - oldLength;
+            }
+            spaces += spaces_current;
+
+            first = !first;
+
+            if (oneline) {
+                continue;
+            }
+
+            size_t gone_szs = 0;
+            size_t gone_els = 0;
+
+            for(; pt != up->end(); ++pt) {
+                PidStatistics *pp = *pt;
+                pid_t p = pp->getPid();
+
+                // If a PID no longer has any current logs, and is not
+                // active anymore, skip & report totals for gone.
+                elsTotal = pp->elementsTotal();
+                size_t szsTotal = pp->sizesTotal();
+                if (p == pp->gone) {
+                    gone_szs += szsTotal;
+                    gone_els += elsTotal;
+                    continue;
+                }
+                els = pp->elements();
+                bool gone = kill(p, 0);
+                if (gone && (els == 0)) {
+                    // ToDo: garbage collection: move this statistical bucket
+                    //       from its current UID/PID to UID/? (races and
+                    //       wrap around are our achilles heel). Below is
+                    //       merely lipservice to catch PIDs that were still
+                    //       around when the stats were pruned to zero.
+                    gone_szs += szsTotal;
+                    gone_els += elsTotal;
+                    continue;
+                }
+
+                if (!first && spaces) {
+                    string.appendFormat("%*s", spaces, "");
+                }
+                spaces = 0;
+
+                intermediate = string.format((gone) ? "%d/%d?" : "%d/%d", u, p);
+                string.appendFormat((first) ? "\n%-12s" : "%-12s",
+                                    intermediate.string());
+                intermediate.clear();
+
+                oldLength = string.length();
+                string.appendFormat("%zu/%zu", szsTotal, elsTotal);
+                spaces += spaces_total + oldLength - string.length();
+
+                if (els == elsTotal) {
+                    string.appendFormat("%*s=", spaces, "");
+                    spaces = -1;
+                } else if (els) {
+                    oldLength = string.length();
+                    string.appendFormat("%*s%zu/%zu", spaces, "",
+                                        pp->sizes(), els);
+                    spaces -= string.length() - oldLength;
+                }
+                spaces += spaces_current;
+
+                first = !first;
+            }
+
+            if (gone_els) {
+                if (!first && spaces) {
+                    string.appendFormat("%*s", spaces, "");
+                }
+
+                intermediate = string.format("%d/?", u);
+                string.appendFormat((first) ? "\n%-12s" : "%-12s",
+                                    intermediate.string());
+                intermediate.clear();
+
+                spaces = spaces_total + spaces_current;
+
+                oldLength = string.length();
+                string.appendFormat("%zu/%zu", gone_szs, gone_els);
+                spaces -= string.length() - oldLength;
+
+                first = !first;
+            }
+        }
+    }
+
+    *buf = strdup(string.string());
+}
diff --git a/logd/LogStatistics.h b/logd/LogStatistics.h
new file mode 100644
index 0000000..d44afa2
--- /dev/null
+++ b/logd/LogStatistics.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _LOGD_LOG_STATISTICS_H__
+#define _LOGD_LOG_STATISTICS_H__
+
+#include <sys/types.h>
+
+#include <log/log.h>
+#include <log/log_read.h>
+#include <utils/List.h>
+
+#define log_id_for_each(i) \
+    for (log_id_t i = LOG_ID_MIN; i < LOG_ID_MAX; i = (log_id_t) (i + 1))
+
+class PidStatistics {
+    const pid_t pid;
+
+    // Total
+    size_t mSizesTotal;
+    size_t mElementsTotal;
+    // Current
+    size_t mSizes;
+    size_t mElements;
+
+public:
+    static const pid_t gone = (pid_t) -1;
+
+    PidStatistics(pid_t pid);
+
+    pid_t getPid() const { return pid; }
+
+    void add(unsigned short size);
+    bool subtract(unsigned short size); // returns true if stats and PID gone
+    void addTotal(size_t size, size_t element);
+
+    size_t sizes() const { return mSizes; }
+    size_t elements() const { return mElements; }
+
+    size_t sizesTotal() const { return mSizesTotal; }
+    size_t elementsTotal() const { return mElementsTotal; }
+};
+
+typedef android::List<PidStatistics *> PidStatisticsCollection;
+
+class UidStatistics {
+    const uid_t uid;
+
+    PidStatisticsCollection Pids;
+
+public:
+    UidStatistics(uid_t uid);
+    ~UidStatistics();
+
+    PidStatisticsCollection::iterator begin() { return Pids.begin(); }
+    PidStatisticsCollection::iterator end() { return Pids.end(); }
+
+    uid_t getUid() { return uid; }
+
+    void add(unsigned short size, pid_t pid);
+    void subtract(unsigned short size, pid_t pid);
+
+    static const pid_t pid_all = (pid_t) -1;
+
+    size_t sizes(pid_t pid = pid_all);
+    size_t elements(pid_t pid = pid_all);
+
+    size_t sizesTotal(pid_t pid = pid_all);
+    size_t elementsTotal(pid_t pid = pid_all);
+};
+
+typedef android::List<UidStatistics *> UidStatisticsCollection;
+
+class LidStatistics {
+    UidStatisticsCollection Uids;
+
+public:
+    LidStatistics();
+    ~LidStatistics();
+
+    UidStatisticsCollection::iterator begin() { return Uids.begin(); }
+    UidStatisticsCollection::iterator end() { return Uids.end(); }
+
+    void add(unsigned short size, uid_t uid, pid_t pid);
+    void subtract(unsigned short size, uid_t uid, pid_t pid);
+
+    static const pid_t pid_all = (pid_t) -1;
+    static const uid_t uid_all = (uid_t) -1;
+
+    size_t sizes(uid_t uid = uid_all, pid_t pid = pid_all);
+    size_t elements(uid_t uid = uid_all, pid_t pid = pid_all);
+
+    size_t sizesTotal(uid_t uid = uid_all, pid_t pid = pid_all);
+    size_t elementsTotal(uid_t uid = uid_all, pid_t pid = pid_all);
+};
+
+// Log Statistics
+class LogStatistics {
+    LidStatistics LogIds[LOG_ID_MAX];
+
+    size_t mSizes[LOG_ID_MAX];
+    size_t mElements[LOG_ID_MAX];
+
+public:
+    const log_time start;
+
+    LogStatistics();
+
+    LidStatistics &id(log_id_t log_id) { return LogIds[log_id]; }
+
+    void add(unsigned short size, log_id_t log_id, uid_t uid, pid_t pid);
+    void subtract(unsigned short size, log_id_t log_id, uid_t uid, pid_t pid);
+
+    // fast track current value by id only
+    size_t sizes(log_id_t id) const { return mSizes[id]; }
+    size_t elements(log_id_t id) const { return mElements[id]; }
+
+    // statistical track
+    static const log_id_t log_id_all = (log_id_t) -1;
+    static const uid_t uid_all = (uid_t) -1;
+    static const pid_t pid_all = (pid_t) -1;
+
+    size_t sizes(log_id_t id, uid_t uid, pid_t pid = pid_all);
+    size_t elements(log_id_t id, uid_t uid, pid_t pid = pid_all);
+    size_t sizes() { return sizes(log_id_all, uid_all); }
+    size_t elements() { return elements(log_id_all, uid_all); }
+
+    size_t sizesTotal(log_id_t id = log_id_all,
+                      uid_t uid = uid_all,
+                      pid_t pid = pid_all);
+    size_t elementsTotal(log_id_t id = log_id_all,
+                         uid_t uid = uid_all,
+                         pid_t pid = pid_all);
+
+    // *strp = malloc, balance with free
+    void format(char **strp, uid_t uid, unsigned int logMask, log_time oldest);
+};
+
+#endif // _LOGD_LOG_STATISTICS_H__
diff --git a/logd/LogTimes.cpp b/logd/LogTimes.cpp
index 67cc65e..8cb015c 100644
--- a/logd/LogTimes.cpp
+++ b/logd/LogTimes.cpp
@@ -25,7 +25,8 @@
 
 LogTimeEntry::LogTimeEntry(LogReader &reader, SocketClient *client,
                            bool nonBlock, unsigned long tail,
-                           unsigned int logMask, pid_t pid)
+                           unsigned int logMask, pid_t pid,
+                           log_time start)
         : mRefCount(1)
         , mRelease(false)
         , mError(false)
@@ -39,7 +40,7 @@
         , mTail(tail)
         , mIndex(0)
         , mClient(client)
-        , mStart(EPOCH)
+        , mStart(start)
         , mNonBlock(nonBlock)
         , mEnd(CLOCK_MONOTONIC)
 { }
diff --git a/logd/LogTimes.h b/logd/LogTimes.h
index cb6f566..beaf646 100644
--- a/logd/LogTimes.h
+++ b/logd/LogTimes.h
@@ -45,7 +45,8 @@
 
 public:
     LogTimeEntry(LogReader &reader, SocketClient *client, bool nonBlock,
-                 unsigned long tail, unsigned int logMask, pid_t pid);
+                 unsigned long tail, unsigned int logMask, pid_t pid,
+                 log_time start);
 
     SocketClient *mClient;
     static const struct timespec EPOCH;
diff --git a/logd/LogWhiteBlackList.cpp b/logd/LogWhiteBlackList.cpp
new file mode 100644
index 0000000..d0ceb9f
--- /dev/null
+++ b/logd/LogWhiteBlackList.cpp
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef USERDEBUG_BUILD
+
+#include <ctype.h>
+
+#include <utils/String8.h>
+
+#include "LogWhiteBlackList.h"
+
+// White and Black list
+
+Prune::Prune(uid_t uid, pid_t pid)
+        : mUid(uid)
+        , mPid(pid)
+{ }
+
+int Prune::cmp(uid_t uid, pid_t pid) const {
+    if ((mUid == uid_all) || (mUid == uid)) {
+        if (mPid == pid_all) {
+            return 0;
+        }
+        return pid - mPid;
+    }
+    return uid - mUid;
+}
+
+void Prune::format(char **strp) {
+    if (mUid != uid_all) {
+        asprintf(strp, (mPid != pid_all) ? "%u/%u" : "%u", mUid, mPid);
+    } else {
+        // NB: mPid == pid_all can not happen if mUid == uid_all
+        asprintf(strp, (mPid != pid_all) ? "/%u" : "/", mPid);
+    }
+}
+
+PruneList::PruneList()
+        : mWorstUidEnabled(true) {
+    mNaughty.clear();
+    mNice.clear();
+}
+
+PruneList::~PruneList() {
+    PruneCollection::iterator it;
+    for (it = mNice.begin(); it != mNice.end();) {
+        delete (*it);
+        it = mNice.erase(it);
+    }
+    for (it = mNaughty.begin(); it != mNaughty.end();) {
+        delete (*it);
+        it = mNaughty.erase(it);
+    }
+}
+
+int PruneList::init(char *str) {
+    mWorstUidEnabled = true;
+    PruneCollection::iterator it;
+    for (it = mNice.begin(); it != mNice.end();) {
+        delete (*it);
+        it = mNice.erase(it);
+    }
+    for (it = mNaughty.begin(); it != mNaughty.end();) {
+        delete (*it);
+        it = mNaughty.erase(it);
+    }
+
+    if (!str) {
+        return 0;
+    }
+
+    mWorstUidEnabled = false;
+
+    for(; *str; ++str) {
+        if (isspace(*str)) {
+            continue;
+        }
+
+        PruneCollection *list;
+        if ((*str == '~') || (*str == '!')) { // ~ supported, ! undocumented
+            ++str;
+            // special case, translates to worst UID at priority in blacklist
+            if (*str == '!') {
+                mWorstUidEnabled = true;
+                ++str;
+                if (!*str) {
+                    break;
+                }
+                if (!isspace(*str)) {
+                    return 1;
+                }
+                continue;
+            }
+            if (!*str) {
+                return 1;
+            }
+            list = &mNaughty;
+        } else {
+            list = &mNice;
+        }
+
+        uid_t uid = Prune::uid_all;
+        if (isdigit(*str)) {
+            uid = 0;
+            do {
+                uid = uid * 10 + *str++ - '0';
+            } while (isdigit(*str));
+        }
+
+        pid_t pid = Prune::pid_all;
+        if (*str == '/') {
+            ++str;
+            if (isdigit(*str)) {
+                pid = 0;
+                do {
+                    pid = pid * 10 + *str++ - '0';
+                } while (isdigit(*str));
+            }
+        }
+
+        if ((uid == Prune::uid_all) && (pid == Prune::pid_all)) {
+            return 1;
+        }
+
+        if (*str && !isspace(*str)) {
+            return 1;
+        }
+
+        // insert sequentially into list
+        PruneCollection::iterator it = list->begin();
+        while (it != list->end()) {
+            Prune *p = *it;
+            int m = uid - p->mUid;
+            if (m == 0) {
+                if (p->mPid == p->pid_all) {
+                    break;
+                }
+                if ((pid == p->pid_all) && (p->mPid != p->pid_all)) {
+                    it = list->erase(it);
+                    continue;
+                }
+                m = pid - p->mPid;
+            }
+            if (m >= 0) {
+                if (m > 0) {
+                    list->insert(it, new Prune(uid,pid));
+                }
+                break;
+            }
+            ++it;
+        }
+        if (it == list->end()) {
+            list->push_back(new Prune(uid,pid));
+        }
+        if (!*str) {
+            break;
+        }
+    }
+
+    return 0;
+}
+
+void PruneList::format(char **strp) {
+    if (*strp) {
+        free(*strp);
+        *strp = NULL;
+    }
+
+    static const char nice_format[] = " %s";
+    const char *fmt = nice_format + 1;
+
+    android::String8 string;
+
+    if (mWorstUidEnabled) {
+        string.setTo("~!");
+        fmt = nice_format;
+    }
+
+    PruneCollection::iterator it;
+
+    for (it = mNice.begin(); it != mNice.end(); ++it) {
+        char *a = NULL;
+        (*it)->format(&a);
+
+        string.appendFormat(fmt, a);
+        fmt = nice_format;
+
+        free(a);
+    }
+
+    static const char naughty_format[] = " ~%s";
+    fmt = naughty_format + (*fmt != ' ');
+    for (it = mNaughty.begin(); it != mNaughty.end(); ++it) {
+        char *a = NULL;
+        (*it)->format(&a);
+
+        string.appendFormat(fmt, a);
+        fmt = naughty_format;
+
+        free(a);
+    }
+
+    *strp = strdup(string.string());
+}
+
+// ToDo: Lists are in sorted order, Prune->cmp() returns + or -
+// If there is scaling issues, resort to a better algorithm than linear
+// based on these assumptions.
+
+bool PruneList::naughty(LogBufferElement *element) {
+    PruneCollection::iterator it;
+    for (it = mNaughty.begin(); it != mNaughty.end(); ++it) {
+        if (!(*it)->cmp(element)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool PruneList::nice(LogBufferElement *element) {
+    PruneCollection::iterator it;
+    for (it = mNice.begin(); it != mNice.end(); ++it) {
+        if (!(*it)->cmp(element)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+#endif // USERDEBUG_BUILD
diff --git a/logd/LogWhiteBlackList.h b/logd/LogWhiteBlackList.h
new file mode 100644
index 0000000..769d651
--- /dev/null
+++ b/logd/LogWhiteBlackList.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _LOGD_LOG_WHITE_BLACK_LIST_H__
+#define _LOGD_LOG_WHITE_BLACK_LIST_H__
+
+#include <sys/types.h>
+
+#include <utils/List.h>
+
+#include <LogBufferElement.h>
+
+// White and Blacklist
+
+class Prune {
+    friend class PruneList;
+
+    const uid_t mUid;
+    const pid_t mPid;
+    int cmp(uid_t uid, pid_t pid) const;
+
+public:
+    static const uid_t uid_all = (uid_t) -1;
+    static const pid_t pid_all = (pid_t) -1;
+
+    Prune(uid_t uid, pid_t pid);
+
+    uid_t getUid() const { return mUid; }
+    pid_t getPid() const { return mPid; }
+
+    int cmp(LogBufferElement *e) const { return cmp(e->getUid(), e->getPid()); }
+
+    // *strp is malloc'd, use free to release
+    void format(char **strp);
+};
+
+typedef android::List<Prune *> PruneCollection;
+
+class PruneList {
+    PruneCollection mNaughty;
+    PruneCollection mNice;
+    bool mWorstUidEnabled;
+
+public:
+    PruneList();
+    ~PruneList();
+
+    int init(char *str);
+
+    bool naughty(LogBufferElement *element);
+    bool nice(LogBufferElement *element);
+    bool worstUidEnabled() const { return mWorstUidEnabled; }
+
+    // *strp is malloc'd, use free to release
+    void format(char **strp);
+};
+
+#endif // _LOGD_LOG_WHITE_BLACK_LIST_H__
diff --git a/toolbox/ls.c b/toolbox/ls.c
index 3cc5bb2..06910ee 100644
--- a/toolbox/ls.c
+++ b/toolbox/ls.c
@@ -137,7 +137,7 @@
 
     /* blocks are 512 bytes, we want output to be KB */
     if ((flags & LIST_SIZE) != 0) {
-        printf("%lld ", s->st_blocks / 2);
+        printf("%lld ", (long long)s->st_blocks / 2);
     }
 
     if ((flags & LIST_CLASSIFY) != 0) {
@@ -205,7 +205,7 @@
         break;
     case S_IFREG:
         printf("%s %-8s %-8s %8lld %s %s\n",
-               mode, user, group, s->st_size, date, name);
+               mode, user, group, (long long)s->st_size, date, name);
         break;
     case S_IFLNK: {
         char linkto[256];
@@ -321,7 +321,7 @@
     }
 
     if(flags & LIST_INODE) {
-        printf("%8llu ", s.st_ino);
+        printf("%8llu ", (unsigned long long)s.st_ino);
     }
 
     if ((flags & LIST_MACLABEL) != 0) {
diff --git a/toolbox/schedtop.c b/toolbox/schedtop.c
index a76f968..0c85e76 100644
--- a/toolbox/schedtop.c
+++ b/toolbox/schedtop.c
@@ -1,26 +1,23 @@
-#include <stdio.h>
-#include <stdlib.h>
 #include <ctype.h>
+#include <dirent.h>
 #include <fcntl.h>
-#include <unistd.h>
-
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
 #include <stdint.h>
+#include <stdlib.h>
 #include <string.h>
-
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <dirent.h>
-#include <signal.h>
-
-#include <pwd.h>
+#include <unistd.h>
 
 struct thread_info {
     int pid;
     int tid;
     char name[64];
-    uint64_t exec_time;
-    uint64_t delay_time;
-    uint32_t run_count;
+    unsigned long long exec_time;
+    unsigned long long delay_time;
+    unsigned long long run_count;
 };
 
 struct thread_table {
@@ -110,7 +107,8 @@
         sprintf(line, "/proc/%d/schedstat", pid);
     if (read_line(line, sizeof(line)))
         return;
-    if(sscanf(line, "%llu %llu %u", &info->exec_time, &info->delay_time, &info->run_count) != 3)
+    if(sscanf(line, "%llu %llu %llu",
+              &info->exec_time, &info->delay_time, &info->run_count) != 3)
         return;
     if (proc_info) {
         proc_info->exec_time += info->exec_time;
@@ -183,7 +181,7 @@
         if (j == threads.active)
             printf(" %5u died\n", tid);
         else if (!(flags & FLAG_HIDE_IDLE) || threads.data[j].run_count - last_threads.data[i].run_count)
-            printf(" %5u %2u.%0*u %2u.%0*u %5u %5u.%0*u %5u.%0*u %7u  %s\n", tid,
+            printf(" %5u %2u.%0*u %2u.%0*u %5llu %5u.%0*u %5u.%0*u %7llu  %s\n", tid,
                 NS_TO_S_D(threads.data[j].exec_time - last_threads.data[i].exec_time),
                 NS_TO_S_D(threads.data[j].delay_time - last_threads.data[i].delay_time),
                 threads.data[j].run_count - last_threads.data[i].run_count,
@@ -236,7 +234,7 @@
         if (j == processes.active)
             printf("%5u died\n", pid);
         else if (!(flags & FLAG_HIDE_IDLE) || processes.data[j].run_count - last_processes.data[i].run_count) {
-            printf("%5u  %2u.%0*u %2u.%0*u %5u %5u.%0*u %5u.%0*u %7u %s\n", pid,
+            printf("%5u  %2u.%0*u %2u.%0*u %5llu %5u.%0*u %5u.%0*u %7llu %s\n", pid,
                 NS_TO_S_D(processes.data[j].exec_time - last_processes.data[i].exec_time),
                 NS_TO_S_D(processes.data[j].delay_time - last_processes.data[i].delay_time),
                 processes.data[j].run_count - last_processes.data[i].run_count,