Merge changes Ia6d6821e,I1e1a55d1,Ic26e9dba,Iedc55c13

* changes:
  liblog: Add liblog test suite
  logcat: Incorporate liblog reading API
  debuggerd: Incorporate liblog reading API
  liblog: Interface to support abstracting log read
diff --git a/debuggerd/tombstone.c b/debuggerd/tombstone.c
index aa63547..2379bd2 100644
--- a/debuggerd/tombstone.c
+++ b/debuggerd/tombstone.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2012-2013 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.
@@ -29,6 +29,7 @@
 
 #include <private/android_filesystem_config.h>
 
+#include <log/log.h>
 #include <log/logger.h>
 #include <cutils/properties.h>
 
@@ -468,43 +469,38 @@
  * Reads the contents of the specified log device, filters out the entries
  * that don't match the specified pid, and writes them to the tombstone file.
  *
- * If "tailOnly" is set, we only print the last few lines.
+ * If "tail" is set, we only print the last few lines.
  */
 static void dump_log_file(log_t* log, pid_t pid, const char* filename,
-    bool tailOnly)
+    unsigned int tail)
 {
     bool first = true;
+    struct logger_list *logger_list;
 
-    /* circular buffer, for "tailOnly" mode */
-    const int kShortLogMaxLines = 5;
-    const int kShortLogLineLen = 256;
-    char shortLog[kShortLogMaxLines][kShortLogLineLen];
-    int shortLogCount = 0;
-    int shortLogNext = 0;
+    logger_list = android_logger_list_open(
+        android_name_to_log_id(filename), O_RDONLY | O_NONBLOCK, tail, pid);
 
-    int logfd = open(filename, O_RDONLY | O_NONBLOCK);
-    if (logfd < 0) {
+    if (!logger_list) {
         XLOG("Unable to open %s: %s\n", filename, strerror(errno));
         return;
     }
 
-    union {
-        unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1];
-        struct logger_entry entry;
-    } log_entry;
+    struct log_msg log_entry;
 
     while (true) {
-        ssize_t actual = read(logfd, log_entry.buf, LOGGER_ENTRY_MAX_LEN);
+        ssize_t actual = android_logger_list_read(logger_list, &log_entry);
+        struct logger_entry* entry;
+
         if (actual < 0) {
-            if (errno == EINTR) {
+            if (actual == -EINTR) {
                 /* interrupted by signal, retry */
                 continue;
-            } else if (errno == EAGAIN) {
+            } else if (actual == -EAGAIN) {
                 /* non-blocking EOF; we're done */
                 break;
             } else {
                 _LOG(log, 0, "Error while reading log: %s\n",
-                    strerror(errno));
+                    strerror(-actual));
                 break;
             }
         } else if (actual == 0) {
@@ -520,16 +516,11 @@
          * the tombstone file.
          */
 
-        struct logger_entry* entry = &log_entry.entry;
-
-        if (entry->pid != (int32_t) pid) {
-            /* wrong pid, ignore */
-            continue;
-        }
+        entry = &log_entry.entry_v1;
 
         if (first) {
             _LOG(log, 0, "--------- %slog %s\n",
-                tailOnly ? "tail end of " : "", filename);
+                tail ? "tail end of " : "", filename);
             first = false;
         }
 
@@ -543,9 +534,14 @@
          * on a separate line, prefixed with the header, like logcat does.
          */
         static const char* kPrioChars = "!.VDIWEFS";
-        unsigned char prio = entry->msg[0];
-        char* tag = entry->msg + 1;
-        char* msg = tag + strlen(tag) + 1;
+        unsigned hdr_size = log_entry.entry.hdr_size;
+        if (!hdr_size) {
+            hdr_size = sizeof(log_entry.entry_v1);
+        }
+        char* msg = (char *)log_entry.buf + hdr_size;
+        unsigned char prio = msg[0];
+        char* tag = msg + 1;
+        msg = tag + strlen(tag) + 1;
 
         /* consume any trailing newlines */
         char* eatnl = msg + strlen(msg) - 1;
@@ -562,50 +558,22 @@
         ptm = localtime_r(&sec, &tmBuf);
         strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
 
-        if (tailOnly) {
-            snprintf(shortLog[shortLogNext], kShortLogLineLen,
-                "%s.%03d %5d %5d %c %-8s: %s",
-                timeBuf, entry->nsec / 1000000, entry->pid, entry->tid,
-                prioChar, tag, msg);
-            shortLogNext = (shortLogNext + 1) % kShortLogMaxLines;
-            shortLogCount++;
-        } else {
-            _LOG(log, 0, "%s.%03d %5d %5d %c %-8s: %s\n",
-                timeBuf, entry->nsec / 1000000, entry->pid, entry->tid,
-                prioChar, tag, msg);
-        }
+        _LOG(log, 0, "%s.%03d %5d %5d %c %-8s: %s\n",
+             timeBuf, entry->nsec / 1000000, entry->pid, entry->tid,
+             prioChar, tag, msg);
     }
 
-    if (tailOnly) {
-        int i;
-
-        /*
-         * If we filled the buffer, we want to start at "next", which has
-         * the oldest entry.  If we didn't, we want to start at zero.
-         */
-        if (shortLogCount < kShortLogMaxLines) {
-            shortLogNext = 0;
-        } else {
-            shortLogCount = kShortLogMaxLines;  /* cap at window size */
-        }
-
-        for (i = 0; i < shortLogCount; i++) {
-            _LOG(log, 0, "%s\n", shortLog[shortLogNext]);
-            shortLogNext = (shortLogNext + 1) % kShortLogMaxLines;
-        }
-    }
-
-    close(logfd);
+    android_logger_list_free(logger_list);
 }
 
 /*
  * Dumps the logs generated by the specified pid to the tombstone, from both
  * "system" and "main" log devices.  Ideally we'd interleave the output.
  */
-static void dump_logs(log_t* log, pid_t pid, bool tailOnly)
+static void dump_logs(log_t* log, pid_t pid, unsigned tail)
 {
-    dump_log_file(log, pid, "/dev/log/system", tailOnly);
-    dump_log_file(log, pid, "/dev/log/main", tailOnly);
+    dump_log_file(log, pid, "system", tail);
+    dump_log_file(log, pid, "main", tail);
 }
 
 static void dump_abort_message(const backtrace_context_t* context, log_t* log, uintptr_t address) {
@@ -683,7 +651,7 @@
     }
 
     if (want_logs) {
-        dump_logs(log, pid, true);
+        dump_logs(log, pid, 5);
     }
 
     bool detach_failed = false;
@@ -692,7 +660,7 @@
     }
 
     if (want_logs) {
-        dump_logs(log, pid, false);
+        dump_logs(log, pid, 0);
     }
 
     /* send EOD to the Activity Manager, then wait for its ack to avoid racing ahead
diff --git a/include/log/log.h b/include/log/log.h
index 7faddea..1b79caa 100644
--- a/include/log/log.h
+++ b/include/log/log.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2005 The Android Open Source Project
+ * Copyright (C) 2005-2013 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.
@@ -28,21 +28,19 @@
 #ifndef _LIBS_LOG_LOG_H
 #define _LIBS_LOG_LOG_H
 
-#include <stdio.h>
-#include <time.h>
+#include <sys/cdefs.h>
 #include <sys/types.h>
-#include <unistd.h>
 #ifdef HAVE_PTHREADS
 #include <pthread.h>
 #endif
 #include <stdarg.h>
-
-#include <log/uio.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
 #include <log/logd.h>
+#include <log/uio.h>
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+__BEGIN_DECLS
 
 // ---------------------------------------------------------------------
 
@@ -470,7 +468,8 @@
     EVENT_TYPE_STRING   = 2,
     EVENT_TYPE_LIST     = 3,
 } AndroidEventLogType;
-
+#define sizeof_AndroidEventLogType sizeof(typeof_AndroidEventLogType)
+#define typeof_AndroidEventLogType unsigned char
 
 #ifndef LOG_EVENT_INT
 #define LOG_EVENT_INT(_tag, _value) {                                       \
@@ -540,7 +539,9 @@
 #define android_logToFile(tag, file) (0)
 #define android_logToFd(tag, fd) (0)
 
-typedef enum {
+typedef enum log_id {
+    LOG_ID_MIN = 0,
+
     LOG_ID_MAIN = 0,
     LOG_ID_RADIO = 1,
     LOG_ID_EVENTS = 2,
@@ -548,6 +549,57 @@
 
     LOG_ID_MAX
 } log_id_t;
+#define sizeof_log_id_t sizeof(typeof_log_id_t)
+#define typeof_log_id_t unsigned char
+
+/* Currently helper to liblog unit tests, expect wider use. */
+#define NS_PER_SEC 1000000000ULL
+#ifdef __cplusplus
+typedef struct log_time : public timespec {
+public:
+    log_time(void)
+    {
+    }
+    log_time(const char *T)
+    {
+        const uint8_t *c = (const uint8_t *) T;
+        tv_sec = c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24);
+        tv_nsec = c[4] | (c[5] << 8) | (c[6] << 16) | (c[7] << 24);
+    }
+    bool operator== (log_time &T)
+    {
+        return (tv_sec == T.tv_sec) && (tv_nsec == T.tv_nsec);
+    }
+    bool operator!= (log_time &T)
+    {
+        return !(*this == T);
+    }
+    bool operator< (log_time &T)
+    {
+        return (tv_sec < T.tv_sec)
+            || ((tv_sec == T.tv_sec) && (tv_nsec < T.tv_nsec));
+    }
+    bool operator>= (log_time &T)
+    {
+        return !(*this < T);
+    }
+    bool operator> (log_time &T)
+    {
+        return (tv_sec > T.tv_sec)
+            || ((tv_sec == T.tv_sec) && (tv_nsec > T.tv_nsec));
+    }
+    bool operator<= (log_time &T)
+    {
+        return !(*this > T);
+    }
+    uint64_t nsec(void)
+    {
+        return static_cast<uint64_t>(tv_sec) * NS_PER_SEC + tv_nsec;
+    }
+} log_time_t;
+#else
+typedef struct timespec log_time_t;
+#endif
 
 /*
  * Send a simple string to the log.
@@ -555,9 +607,6 @@
 int __android_log_buf_write(int bufID, int prio, const char *tag, const char *text);
 int __android_log_buf_print(int bufID, int prio, const char *tag, const char *fmt, ...);
 
-
-#ifdef __cplusplus
-}
-#endif
+__END_DECLS
 
 #endif // _LIBS_CUTILS_LOG_H
diff --git a/include/log/logger.h b/include/log/logger.h
index 04f3fb0..9ce37c9 100644
--- a/include/log/logger.h
+++ b/include/log/logger.h
@@ -1,5 +1,5 @@
-/* utils/logger.h
-** 
+/*
+**
 ** Copyright 2007, The Android Open Source Project
 **
 ** This file is dual licensed.  It may be redistributed and/or modified
@@ -10,7 +10,11 @@
 #ifndef _UTILS_LOGGER_H
 #define _UTILS_LOGGER_H
 
+#include <sys/cdefs.h>
 #include <stdint.h>
+#include <log/log.h>
+
+__BEGIN_DECLS
 
 /*
  * The userspace structure for version 1 of the logger_entry ABI.
@@ -63,6 +67,106 @@
  */
 #define LOGGER_ENTRY_MAX_LEN		(5*1024)
 
+#define NS_PER_SEC 1000000000ULL
+
+struct log_msg {
+    union {
+        unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1];
+        struct logger_entry_v2 entry;
+        struct logger_entry_v2 entry_v2;
+        struct logger_entry    entry_v1;
+        struct {
+            unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1];
+            log_id_t id;
+        } extra;
+    } __attribute__((aligned(4)));
+#ifdef __cplusplus
+    /* Matching log_time_t operators */
+    bool operator== (log_msg &T)
+    {
+        return (entry.sec == T.entry.sec) && (entry.nsec == T.entry.nsec);
+    }
+    bool operator!= (log_msg &T)
+    {
+        return !(*this == T);
+    }
+    bool operator< (log_msg &T)
+    {
+        return (entry.sec < T.entry.sec)
+            || ((entry.sec == T.entry.sec)
+             && (entry.nsec < T.entry.nsec));
+    }
+    bool operator>= (log_msg &T)
+    {
+        return !(*this < T);
+    }
+    bool operator> (log_msg &T)
+    {
+        return (entry.sec > T.entry.sec)
+            || ((entry.sec == T.entry.sec)
+             && (entry.nsec > T.entry.nsec));
+    }
+    bool operator<= (log_msg &T)
+    {
+        return !(*this > T);
+    }
+    uint64_t nsec(void)
+    {
+        return static_cast<uint64_t>(entry.sec) * NS_PER_SEC + entry.nsec;
+    }
+
+    /* packet methods */
+    log_id_t id(void)
+    {
+        return extra.id;
+    }
+    char *msg(void)
+    {
+        return entry.hdr_size ? (char *) buf + entry.hdr_size : entry_v1.msg;
+    }
+    unsigned int len(void)
+    {
+        return (entry.hdr_size ? entry.hdr_size : sizeof(entry_v1)) + entry.len;
+    }
+#endif
+};
+
+struct logger;
+
+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);
+int android_logger_get_log_version(struct logger *logger);
+
+struct logger_list;
+
+struct logger_list *android_logger_list_alloc(int mode,
+                                              unsigned int tail,
+                                              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,
+                             struct log_msg *log_msg);
+
+/* Multiple log_id_t opens */
+struct logger *android_logger_open(struct logger_list *logger_list,
+                                   log_id_t id);
+#define android_logger_close android_logger_free
+/* Single log_id_t open */
+struct logger_list *android_logger_list_open(log_id_t id,
+                                             int mode,
+                                             unsigned int tail,
+                                             pid_t pid);
+#define android_logger_list_close android_logger_list_free
+
+/*
+ * log_id_t helpers
+ */
+log_id_t android_name_to_log_id(const char *logName);
+const char *android_log_id_to_name(log_id_t log_id);
+
 #ifdef HAVE_IOCTL
 
 #include <sys/ioctl.h>
@@ -78,4 +182,6 @@
 
 #endif // HAVE_IOCTL
 
+__END_DECLS
+
 #endif /* _UTILS_LOGGER_H */
diff --git a/liblog/Android.mk b/liblog/Android.mk
index 6bfb119..cc8df83 100644
--- a/liblog/Android.mk
+++ b/liblog/Android.mk
@@ -35,6 +35,7 @@
 ifndef WITH_MINGW
     liblog_sources += \
         logprint.c \
+        log_read.c \
         event_tag_map.c
 else
     liblog_sources += \
@@ -79,3 +80,5 @@
 LOCAL_MODULE := liblog
 LOCAL_WHOLE_STATIC_LIBRARIES := liblog
 include $(BUILD_SHARED_LIBRARY)
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/liblog/log_read.c b/liblog/log_read.c
new file mode 100644
index 0000000..11464ce
--- /dev/null
+++ b/liblog/log_read.c
@@ -0,0 +1,652 @@
+/*
+** Copyright 2013, 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.
+*/
+
+#define _GNU_SOURCE /* asprintf for x86 host */
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <cutils/list.h>
+#include <log/log.h>
+#include <log/logger.h>
+
+typedef char bool;
+#define false (const bool)0
+#define true (const bool)1
+
+#define LOG_FILE_DIR "/dev/log/"
+
+#define LOG_TIMEOUT_MS 5
+
+#define logger_for_each(logger, logger_list) \
+    for (logger = node_to_item((logger_list)->node.next, struct logger, node); \
+         logger != node_to_item(&(logger_list)->node, struct logger, node); \
+         logger = node_to_item((logger)->node.next, struct logger, node))
+
+/* In the future, we would like to make this list extensible */
+static const char *LOG_NAME[LOG_ID_MAX] = {
+    [LOG_ID_MAIN] = "main",
+    [LOG_ID_RADIO] = "radio",
+    [LOG_ID_EVENTS] = "events",
+    [LOG_ID_SYSTEM] = "system"
+};
+
+const char *android_log_id_to_name(log_id_t log_id) {
+    if (log_id >= LOG_ID_MAX) {
+        log_id = LOG_ID_MAIN;
+    }
+    return LOG_NAME[log_id];
+}
+
+static int accessmode(int mode)
+{
+    if ((mode & O_ACCMODE) == O_WRONLY) {
+        return W_OK;
+    }
+    if ((mode & O_ACCMODE) == O_RDWR) {
+        return R_OK | W_OK;
+    }
+    return R_OK;
+}
+
+/* repeated fragment */
+static int check_allocate_accessible(char **n, const char *b, int mode)
+{
+    *n = NULL;
+
+    if (!b) {
+        return -EINVAL;
+    }
+
+    asprintf(n, LOG_FILE_DIR "%s", b);
+    if (!*n) {
+        return -1;
+    }
+
+    return access(*n, accessmode(mode));
+}
+
+log_id_t android_name_to_log_id(const char *logName) {
+    const char *b;
+    char *n;
+    int ret;
+
+    if (!logName) {
+        return -1; /* NB: log_id_t is unsigned */
+    }
+    b = strrchr(logName, '/');
+    if (!b) {
+        b = logName;
+    } else {
+        ++b;
+    }
+
+    ret = check_allocate_accessible(&n, b, O_RDONLY);
+    free(n);
+    if (ret) {
+        return ret;
+    }
+
+    for(ret = LOG_ID_MIN; ret < LOG_ID_MAX; ++ret) {
+        const char *l = LOG_NAME[ret];
+        if (l && !strcmp(b, l)) {
+            return ret;
+        }
+    }
+    return -1;   /* should never happen */
+}
+
+struct logger_list {
+    struct listnode node;
+    int mode;
+    unsigned int tail;
+    pid_t pid;
+    unsigned int queued_lines;
+    int timeout_ms;
+    int error;
+    bool flush;
+    bool valid_entry; /* valiant(?) effort to deal with memory starvation */
+    struct log_msg entry;
+};
+
+struct log_list {
+    struct listnode node;
+    struct log_msg entry; /* Truncated to event->len() + 1 to save space */
+};
+
+struct logger {
+    struct listnode node;
+    struct logger_list *top;
+    int fd;
+    log_id_t id;
+    short *revents;
+    struct listnode log_list;
+};
+
+/* android_logger_alloc unimplemented, no use case */
+/* android_logger_free not exported */
+static void android_logger_free(struct logger *logger)
+{
+    if (!logger) {
+        return;
+    }
+
+    while (!list_empty(&logger->log_list)) {
+        struct log_list *entry = node_to_item(
+            list_head(&logger->log_list), struct log_list, node);
+        list_remove(&entry->node);
+        free(entry);
+        if (logger->top->queued_lines) {
+            logger->top->queued_lines--;
+        }
+    }
+
+    if (logger->fd >= 0) {
+        close(logger->fd);
+    }
+
+    list_remove(&logger->node);
+
+    free(logger);
+}
+
+log_id_t android_logger_get_id(struct logger *logger)
+{
+    return logger->id;
+}
+
+/* worker for sending the command to the logger */
+static int logger_ioctl(struct logger *logger, int cmd, int mode)
+{
+    char *n;
+    int  f, ret;
+
+    if (!logger || !logger->top) {
+        return -EFAULT;
+    }
+
+    if (((mode & O_ACCMODE) == O_RDWR)
+     || (((mode ^ logger->top->mode) & O_ACCMODE) == 0)) {
+        return ioctl(logger->fd, cmd);
+    }
+
+    /* We go here if android_logger_list_open got mode wrong for this ioctl */
+    ret = check_allocate_accessible(&n, android_log_id_to_name(logger->id), mode);
+    if (ret) {
+        free(n);
+        return ret;
+    }
+
+    f = open(n, mode);
+    free(n);
+    if (f < 0) {
+        return f;
+    }
+
+    ret = ioctl(f, cmd);
+    close (f);
+
+    return ret;
+}
+
+int android_logger_clear(struct logger *logger)
+{
+    return logger_ioctl(logger, LOGGER_FLUSH_LOG, O_WRONLY);
+}
+
+/* returns the total size of the log's ring buffer */
+int android_logger_get_log_size(struct logger *logger)
+{
+    return logger_ioctl(logger, LOGGER_GET_LOG_BUF_SIZE, O_RDWR);
+}
+
+/*
+ * 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)
+{
+    return logger_ioctl(logger, LOGGER_GET_LOG_LEN, O_RDONLY);
+}
+
+/*
+ * returns the logger version
+ */
+int android_logger_get_log_version(struct logger *logger)
+{
+    int ret = logger_ioctl(logger, LOGGER_GET_VERSION, O_RDWR);
+    return (ret < 0) ? 1 : ret;
+}
+
+struct logger_list *android_logger_list_alloc(int mode,
+                                              unsigned int tail,
+                                              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->tail = tail;
+    logger_list->pid = pid;
+    return logger_list;
+}
+
+/* android_logger_list_register unimplemented, no use case */
+/* android_logger_list_unregister unimplemented, no use case */
+
+/* Open the named log and add it to the logger list */
+struct logger *android_logger_open(struct logger_list *logger_list,
+                                   log_id_t id)
+{
+    struct listnode *node;
+    struct logger *logger;
+    char *n;
+
+    if (!logger_list || (id >= LOG_ID_MAX)) {
+        goto err;
+    }
+
+    logger_for_each(logger, logger_list) {
+        if (logger->id == id) {
+            goto ok;
+        }
+    }
+
+    logger = calloc(1, sizeof(*logger));
+    if (!logger) {
+        goto err;
+    }
+
+    if (check_allocate_accessible(&n, android_log_id_to_name(id),
+                                  logger_list->mode)) {
+        goto err_name;
+    }
+
+    logger->fd = open(n, logger_list->mode);
+    if (logger->fd < 0) {
+        goto err_name;
+    }
+
+    free(n);
+    logger->id = id;
+    list_init(&logger->log_list);
+    list_add_tail(&logger_list->node, &logger->node);
+    logger->top = logger_list;
+    logger_list->timeout_ms = LOG_TIMEOUT_MS;
+    goto ok;
+
+err_name:
+    free(n);
+err_logger:
+    free(logger);
+err:
+    logger = NULL;
+ok:
+    return logger;
+}
+
+/* Open the single named log and make it part of a new logger list */
+struct logger_list *android_logger_list_open(log_id_t id,
+                                             int mode,
+                                             unsigned int tail,
+                                             pid_t pid)
+{
+    struct logger_list *logger_list = android_logger_list_alloc(mode, tail, pid);
+    if (!logger_list) {
+        return NULL;
+    }
+
+    if (!android_logger_open(logger_list, id)) {
+        android_logger_list_free(logger_list);
+        return NULL;
+    }
+
+    return logger_list;
+}
+
+/* prevent memory starvation when backfilling */
+static unsigned int queue_threshold(struct logger_list *logger_list)
+{
+    return (logger_list->tail < 64) ? 64 : logger_list->tail;
+}
+
+static bool low_queue(struct listnode *node)
+{
+    /* low is considered less than 2 */
+    return list_head(node) == list_tail(node);
+}
+
+/* Flush queues in sequential order, one at a time */
+static int android_logger_list_flush(struct logger_list *logger_list,
+                                     struct log_msg *log_msg)
+{
+    int ret = 0;
+    struct log_list *firstentry = NULL;
+
+    while ((ret == 0)
+     && (logger_list->flush
+      || (logger_list->queued_lines > logger_list->tail))) {
+        struct logger *logger;
+
+        /* Merge sort */
+        bool at_least_one_is_low = false;
+        struct logger *firstlogger = NULL;
+        firstentry = NULL;
+
+        logger_for_each(logger, logger_list) {
+            struct listnode *node;
+            struct log_list *oldest = NULL;
+
+            /* kernel logger channels not necessarily time-sort order */
+            list_for_each(node, &logger->log_list) {
+                struct log_list *entry = node_to_item(node,
+                                                      struct log_list, node);
+                if (!oldest
+                 || (entry->entry.entry.sec < oldest->entry.entry.sec)
+                 || ((entry->entry.entry.sec == oldest->entry.entry.sec)
+                  && (entry->entry.entry.nsec < oldest->entry.entry.nsec))) {
+                     oldest = entry;
+                }
+            }
+
+            if (!oldest) {
+                at_least_one_is_low = true;
+                continue;
+            } else if (low_queue(&logger->log_list)) {
+                at_least_one_is_low = true;
+            }
+
+            if (!firstentry
+             || (oldest->entry.entry.sec < firstentry->entry.entry.sec)
+             || ((oldest->entry.entry.sec == firstentry->entry.entry.sec)
+              && (oldest->entry.entry.nsec < firstentry->entry.entry.nsec))) {
+                firstentry = oldest;
+                firstlogger = logger;
+            }
+        }
+
+        if (!firstentry) {
+            break;
+        }
+
+        /* when trimming list, tries to keep one entry behind in each bucket */
+        if (!logger_list->flush
+         && at_least_one_is_low
+         && (logger_list->queued_lines < queue_threshold(logger_list))) {
+            break;
+        }
+
+        /* within tail?, send! */
+        if ((logger_list->tail == 0)
+         || (logger_list->queued_lines <= logger_list->tail)) {
+            ret = firstentry->entry.entry.hdr_size;
+            if (!ret) {
+                ret = sizeof(firstentry->entry.entry_v1);
+            }
+            ret += firstentry->entry.entry.len;
+
+            memcpy(log_msg->buf, firstentry->entry.buf, ret + 1);
+            log_msg->extra.id = firstlogger->id;
+        }
+
+        /* next entry */
+        list_remove(&firstentry->node);
+        free(firstentry);
+        if (logger_list->queued_lines) {
+            logger_list->queued_lines--;
+        }
+    }
+
+    /* Flushed the list, no longer in tail mode for continuing content */
+    if (logger_list->flush && !firstentry) {
+        logger_list->tail = 0;
+    }
+    return ret;
+}
+
+/* Read from the selected logs */
+int android_logger_list_read(struct logger_list *logger_list,
+                             struct log_msg *log_msg)
+{
+    struct logger *logger;
+    nfds_t nfds;
+    struct pollfd *p, *pollfds = NULL;
+    int error = 0, ret = 0;
+
+    memset(log_msg, 0, sizeof(struct log_msg));
+
+    if (!logger_list) {
+        return -ENODEV;
+    }
+
+    if (!(accessmode(logger_list->mode) & R_OK)) {
+        logger_list->error = EPERM;
+        goto done;
+    }
+
+    nfds = 0;
+    logger_for_each(logger, logger_list) {
+        ++nfds;
+    }
+    if (nfds <= 0) {
+        error = ENODEV;
+        goto done;
+    }
+
+    /* Do we have anything to offer from the buffer or state? */
+    if (logger_list->valid_entry) { /* implies we are also in a flush state */
+        goto flush;
+    }
+
+    ret = android_logger_list_flush(logger_list, log_msg);
+    if (ret) {
+        goto done;
+    }
+
+    if (logger_list->error) { /* implies we are also in a flush state */
+        goto done;
+    }
+
+    /* Lets start grinding on metal */
+    pollfds = calloc(nfds, sizeof(struct pollfd));
+    if (!pollfds) {
+        error = ENOMEM;
+        goto flush;
+    }
+
+    p = pollfds;
+    logger_for_each(logger, logger_list) {
+        p->fd = logger->fd;
+        p->events = POLLIN;
+        logger->revents = &p->revents;
+        ++p;
+    }
+
+    while (!ret && !error) {
+        int result;
+
+        /* If we oversleep it's ok, i.e. ignore EINTR. */
+        result = TEMP_FAILURE_RETRY(
+            poll(pollfds, nfds, logger_list->timeout_ms));
+
+        if (result <= 0) {
+            if (result) {
+                error = errno;
+            } else if (logger_list->mode & O_NDELAY) {
+                error = EAGAIN;
+            } else {
+                logger_list->timeout_ms = 0;
+            }
+
+            logger_list->flush = true;
+            goto try_flush;
+        }
+
+        logger_list->timeout_ms = LOG_TIMEOUT_MS;
+
+        /* Anti starvation */
+        if (!logger_list->flush
+         && (logger_list->queued_lines > (queue_threshold(logger_list) / 2))) {
+            /* Any queues with input pending that is low? */
+            bool starving = false;
+            logger_for_each(logger, logger_list) {
+                if ((*(logger->revents) & POLLIN)
+                 && low_queue(&logger->log_list)) {
+                    starving = true;
+                    break;
+                }
+            }
+
+            /* pushback on any queues that are not low */
+            if (starving) {
+                logger_for_each(logger, logger_list) {
+                    if ((*(logger->revents) & POLLIN)
+                     && !low_queue(&logger->log_list)) {
+                        *(logger->revents) &= ~POLLIN;
+                    }
+                }
+            }
+        }
+
+        logger_for_each(logger, logger_list) {
+            unsigned int hdr_size;
+            struct log_list *entry;
+
+            if (!(*(logger->revents) & POLLIN)) {
+                continue;
+            }
+
+            memset(logger_list->entry.buf, 0, sizeof(struct log_msg));
+            /* NOTE: driver guarantees we read exactly one full entry */
+            result = read(logger->fd, logger_list->entry.buf,
+                          LOGGER_ENTRY_MAX_LEN);
+            if (result <= 0) {
+                if (!result) {
+                    error = EIO;
+                } else if (errno != EINTR) {
+                    error = errno;
+                }
+                continue;
+            }
+
+            if (logger_list->pid
+             && (logger_list->pid != logger_list->entry.entry.pid)) {
+                continue;
+            }
+
+            hdr_size = logger_list->entry.entry.hdr_size;
+            if (!hdr_size) {
+                hdr_size = sizeof(logger_list->entry.entry_v1);
+            }
+
+            if ((hdr_size > sizeof(struct log_msg))
+             || (logger_list->entry.entry.len
+               > sizeof(logger_list->entry.buf) - hdr_size)
+             || (logger_list->entry.entry.len != result - hdr_size)) {
+                error = EINVAL;
+                continue;
+            }
+
+            logger_list->entry.extra.id = logger->id;
+
+            /* speedup: If not tail, and only one list, send directly */
+            if (!logger_list->tail
+             && (list_head(&logger_list->node)
+              == list_tail(&logger_list->node))) {
+                ret = result;
+                memcpy(log_msg->buf, logger_list->entry.buf, result + 1);
+                log_msg->extra.id = logger->id;
+                break;
+            }
+
+            entry = malloc(sizeof(*entry) - sizeof(entry->entry) + result + 1);
+
+            if (!entry) {
+                logger_list->valid_entry = true;
+                error = ENOMEM;
+                break;
+            }
+
+            logger_list->queued_lines++;
+
+            memcpy(entry->entry.buf, logger_list->entry.buf, result);
+            entry->entry.buf[result] = '\0';
+            list_add_tail(&logger->log_list, &entry->node);
+        }
+
+        if (ret <= 0) {
+try_flush:
+            ret = android_logger_list_flush(logger_list, log_msg);
+        }
+    }
+
+    free(pollfds);
+
+flush:
+    if (error) {
+        logger_list->flush = true;
+    }
+
+    if (ret <= 0) {
+        ret = android_logger_list_flush(logger_list, log_msg);
+
+        if (!ret && logger_list->valid_entry) {
+            ret = logger_list->entry.entry.hdr_size;
+            if (!ret) {
+                ret = sizeof(logger_list->entry.entry_v1);
+            }
+            ret += logger_list->entry.entry.len;
+
+            memcpy(log_msg->buf, logger_list->entry.buf,
+                   sizeof(struct log_msg));
+            logger_list->valid_entry = false;
+        }
+    }
+
+done:
+    if (logger_list->error) {
+        error = logger_list->error;
+    }
+    if (error) {
+        logger_list->error = error;
+        if (!ret) {
+            ret = -error;
+        }
+    }
+    return ret;
+}
+
+/* Close all the logs */
+void android_logger_list_free(struct logger_list *logger_list)
+{
+    if (logger_list == NULL) {
+        return;
+    }
+
+    while (!list_empty(&logger_list->node)) {
+        struct listnode *node = list_head(&logger_list->node);
+        struct logger *logger = node_to_item(node, struct logger, node);
+        android_logger_free(logger);
+    }
+
+    free(logger_list);
+}
diff --git a/liblog/tests/Android.mk b/liblog/tests/Android.mk
new file mode 100644
index 0000000..a234a97
--- /dev/null
+++ b/liblog/tests/Android.mk
@@ -0,0 +1,77 @@
+#
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+# -----------------------------------------------------------------------------
+# Benchmarks.
+# -----------------------------------------------------------------------------
+
+test_module_prefix := liblog-
+test_tags := tests
+
+benchmark_c_flags := \
+    -Ibionic/tests \
+    -Wall -Wextra \
+    -Werror \
+    -fno-builtin \
+    -std=gnu++11
+
+benchmark_src_files := \
+    benchmark_main.cpp \
+    liblog_benchmark.cpp \
+
+# Build benchmarks for the device. Run with:
+#   adb shell liblog-benchmarks
+include $(CLEAR_VARS)
+LOCAL_MODULE := $(test_module_prefix)benchmarks
+LOCAL_MODULE_TAGS := $(test_tags)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+LOCAL_CFLAGS += $(benchmark_c_flags)
+LOCAL_SHARED_LIBRARIES += liblog libm
+LOCAL_SRC_FILES := $(benchmark_src_files)
+ifndef LOCAL_SDK_VERSION
+LOCAL_C_INCLUDES += bionic bionic/libstdc++/include external/stlport/stlport
+LOCAL_SHARED_LIBRARIES += libstlport
+endif
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_NATIVE_TESTS)/$(LOCAL_MODULE)
+include $(BUILD_EXECUTABLE)
+
+# -----------------------------------------------------------------------------
+# Unit tests.
+# -----------------------------------------------------------------------------
+
+test_c_flags := \
+    -fstack-protector-all \
+    -g \
+    -Wall -Wextra \
+    -Werror \
+    -fno-builtin \
+
+test_src_files := \
+    liblog_test.cpp \
+
+# Build tests for the device (with .so). Run with:
+#   adb shell /data/nativetest/liblog-unit-tests/liblog-unit-tests
+include $(CLEAR_VARS)
+LOCAL_MODULE := $(test_module_prefix)unit-tests
+LOCAL_MODULE_TAGS := $(test_tags)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+LOCAL_CFLAGS += $(test_c_flags)
+LOCAL_LDLIBS := -lpthread
+LOCAL_SHARED_LIBRARIES := liblog
+LOCAL_SRC_FILES := $(test_src_files)
+include $(BUILD_NATIVE_TEST)
diff --git a/liblog/tests/benchmark.h b/liblog/tests/benchmark.h
new file mode 100644
index 0000000..ec72b80
--- /dev/null
+++ b/liblog/tests/benchmark.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2012-2013 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 <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <vector>
+
+#ifndef BIONIC_BENCHMARK_H_
+#define BIONIC_BENCHMARK_H_
+
+namespace testing {
+
+class Benchmark;
+template <typename T> class BenchmarkWantsArg;
+template <typename T> class BenchmarkWithArg;
+
+void BenchmarkRegister(Benchmark* bm);
+int PrettyPrintInt(char* str, int len, unsigned int arg);
+
+class Benchmark {
+ public:
+  Benchmark(const char* name, void (*fn)(int)) : name_(strdup(name)), fn_(fn) {
+    BenchmarkRegister(this);
+  }
+  Benchmark(const char* name) : name_(strdup(name)), fn_(NULL) {}
+
+  virtual ~Benchmark() {
+    free(name_);
+  }
+
+  const char* Name() { return name_; }
+  virtual const char* ArgName() { return NULL; }
+  virtual void RunFn(int iterations) { fn_(iterations); }
+
+ protected:
+  char* name_;
+
+ private:
+  void (*fn_)(int);
+};
+
+template <typename T>
+class BenchmarkWantsArgBase : public Benchmark {
+ public:
+  BenchmarkWantsArgBase(const char* name, void (*fn)(int, T)) : Benchmark(name) {
+    fn_arg_ = fn;
+  }
+
+  BenchmarkWantsArgBase<T>* Arg(const char* arg_name, T arg) {
+    BenchmarkRegister(new BenchmarkWithArg<T>(name_, fn_arg_, arg_name, arg));
+    return this;
+  }
+
+ protected:
+  virtual void RunFn(int) { printf("can't run arg benchmark %s without arg\n", Name()); }
+  void (*fn_arg_)(int, T);
+};
+
+template <typename T>
+class BenchmarkWithArg : public BenchmarkWantsArg<T> {
+ public:
+  BenchmarkWithArg(const char* name, void (*fn)(int, T), const char* arg_name, T arg) :
+      BenchmarkWantsArg<T>(name, fn), arg_(arg) {
+    arg_name_ = strdup(arg_name);
+  }
+
+  virtual ~BenchmarkWithArg() {
+    free(arg_name_);
+  }
+
+  virtual const char* ArgName() { return arg_name_; }
+
+ protected:
+  virtual void RunFn(int iterations) { BenchmarkWantsArg<T>::fn_arg_(iterations, arg_); }
+
+ private:
+  T arg_;
+  char* arg_name_;
+};
+
+template <typename T>
+class BenchmarkWantsArg : public BenchmarkWantsArgBase<T> {
+ public:
+  BenchmarkWantsArg<T>(const char* name, void (*fn)(int, T)) :
+    BenchmarkWantsArgBase<T>(name, fn) { }
+};
+
+template <>
+class BenchmarkWantsArg<int> : public BenchmarkWantsArgBase<int> {
+ public:
+  BenchmarkWantsArg<int>(const char* name, void (*fn)(int, int)) :
+    BenchmarkWantsArgBase<int>(name, fn) { }
+
+  BenchmarkWantsArg<int>* Arg(int arg) {
+    char arg_name[100];
+    PrettyPrintInt(arg_name, sizeof(arg_name), arg);
+    BenchmarkRegister(new BenchmarkWithArg<int>(name_, fn_arg_, arg_name, arg));
+    return this;
+  }
+};
+
+static inline Benchmark* BenchmarkFactory(const char* name, void (*fn)(int)) {
+  return new Benchmark(name, fn);
+}
+
+template <typename T>
+static inline BenchmarkWantsArg<T>* BenchmarkFactory(const char* name, void (*fn)(int, T)) {
+  return new BenchmarkWantsArg<T>(name, fn);
+}
+
+}  // namespace testing
+
+template <typename T>
+static inline void BenchmarkAddArg(::testing::Benchmark* b, const char* name, T arg) {
+  ::testing::BenchmarkWantsArg<T>* ba;
+  ba = static_cast< ::testing::BenchmarkWantsArg<T>* >(b);
+  ba->Arg(name, arg);
+}
+
+void SetBenchmarkBytesProcessed(uint64_t);
+void ResetBenchmarkTiming(void);
+void StopBenchmarkTiming(void);
+void StartBenchmarkTiming(void);
+void StartBenchmarkTiming(uint64_t);
+void StopBenchmarkTiming(uint64_t);
+
+#define BENCHMARK(f) \
+    static ::testing::Benchmark* _benchmark_##f __attribute__((unused)) = \
+        (::testing::Benchmark*)::testing::BenchmarkFactory(#f, f)
+
+#endif // BIONIC_BENCHMARK_H_
diff --git a/liblog/tests/benchmark_main.cpp b/liblog/tests/benchmark_main.cpp
new file mode 100644
index 0000000..942e12c
--- /dev/null
+++ b/liblog/tests/benchmark_main.cpp
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2012-2013 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 <benchmark.h>
+
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <string>
+#include <map>
+#include <vector>
+
+static uint64_t gBytesProcessed;
+static uint64_t gBenchmarkTotalTimeNs;
+static uint64_t gBenchmarkTotalTimeNsSquared;
+static uint64_t gBenchmarkNum;
+static uint64_t gBenchmarkStartTimeNs;
+
+typedef std::vector< ::testing::Benchmark* > BenchmarkList;
+static BenchmarkList* gBenchmarks;
+
+static int Round(int n) {
+  int base = 1;
+  while (base*10 < n) {
+    base *= 10;
+  }
+  if (n < 2*base) {
+    return 2*base;
+  }
+  if (n < 5*base) {
+    return 5*base;
+  }
+  return 10*base;
+}
+
+static uint64_t NanoTime() {
+  struct timespec t;
+  t.tv_sec = t.tv_nsec = 0;
+  clock_gettime(CLOCK_MONOTONIC, &t);
+  return static_cast<uint64_t>(t.tv_sec) * 1000000000ULL + t.tv_nsec;
+}
+
+namespace testing {
+
+int PrettyPrintInt(char* str, int len, unsigned int arg)
+{
+  if (arg >= (1<<30) && arg % (1<<30) == 0) {
+    return snprintf(str, len, "%uGi", arg/(1<<30));
+  } else if (arg >= (1<<20) && arg % (1<<20) == 0) {
+    return snprintf(str, len, "%uMi", arg/(1<<20));
+  } else if (arg >= (1<<10) && arg % (1<<10) == 0) {
+    return snprintf(str, len, "%uKi", arg/(1<<10));
+  } else if (arg >= 1000000000 && arg % 1000000000 == 0) {
+    return snprintf(str, len, "%uG", arg/1000000000);
+  } else if (arg >= 1000000 && arg % 1000000 == 0) {
+    return snprintf(str, len, "%uM", arg/1000000);
+  } else if (arg >= 1000 && arg % 1000 == 0) {
+    return snprintf(str, len, "%uK", arg/1000);
+  } else {
+    return snprintf(str, len, "%u", arg);
+  }
+}
+
+bool ShouldRun(Benchmark* b, int argc, char* argv[]) {
+  if (argc == 1) {
+    return true;  // With no arguments, we run all benchmarks.
+  }
+  // Otherwise, we interpret each argument as a regular expression and
+  // see if any of our benchmarks match.
+  for (int i = 1; i < argc; i++) {
+    regex_t re;
+    if (regcomp(&re, argv[i], 0) != 0) {
+      fprintf(stderr, "couldn't compile \"%s\" as a regular expression!\n", argv[i]);
+      exit(EXIT_FAILURE);
+    }
+    int match = regexec(&re, b->Name(), 0, NULL, 0);
+    regfree(&re);
+    if (match != REG_NOMATCH) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void BenchmarkRegister(Benchmark* b) {
+  if (gBenchmarks == NULL) {
+    gBenchmarks = new BenchmarkList;
+  }
+  gBenchmarks->push_back(b);
+}
+
+void RunRepeatedly(Benchmark* b, int iterations) {
+  gBytesProcessed = 0;
+  ResetBenchmarkTiming();
+  uint64_t StartTimeNs = NanoTime();
+  b->RunFn(iterations);
+  // Catch us if we fail to log anything.
+  if ((gBenchmarkTotalTimeNs == 0)
+   && (StartTimeNs != 0)
+   && (gBenchmarkStartTimeNs == 0)) {
+    gBenchmarkTotalTimeNs = NanoTime() - StartTimeNs;
+  }
+}
+
+void Run(Benchmark* b) {
+  // run once in case it's expensive
+  unsigned iterations = 1;
+  uint64_t s = NanoTime();
+  RunRepeatedly(b, iterations);
+  s = NanoTime() - s;
+  while (s < 2e9 && gBenchmarkTotalTimeNs < 1e9 && iterations < 1e9) {
+    unsigned last = iterations;
+    if (gBenchmarkTotalTimeNs/iterations == 0) {
+      iterations = 1e9;
+    } else {
+      iterations = 1e9 / (gBenchmarkTotalTimeNs/iterations);
+    }
+    iterations = std::max(last + 1, std::min(iterations + iterations/2, 100*last));
+    iterations = Round(iterations);
+    s = NanoTime();
+    RunRepeatedly(b, iterations);
+    s = NanoTime() - s;
+  }
+
+  char throughput[100];
+  throughput[0] = '\0';
+  if (gBenchmarkTotalTimeNs > 0 && gBytesProcessed > 0) {
+    double mib_processed = static_cast<double>(gBytesProcessed)/1e6;
+    double seconds = static_cast<double>(gBenchmarkTotalTimeNs)/1e9;
+    snprintf(throughput, sizeof(throughput), " %8.2f MiB/s", mib_processed/seconds);
+  }
+
+  char full_name[100];
+  snprintf(full_name, sizeof(full_name), "%s%s%s", b->Name(),
+           b->ArgName() ? "/" : "",
+           b->ArgName() ? b->ArgName() : "");
+
+  uint64_t mean = gBenchmarkTotalTimeNs / iterations;
+  uint64_t sdev = 0;
+  if (gBenchmarkNum == iterations) {
+    mean = gBenchmarkTotalTimeNs / gBenchmarkNum;
+    uint64_t nXvariance = gBenchmarkTotalTimeNsSquared * gBenchmarkNum
+                        - (gBenchmarkTotalTimeNs * gBenchmarkTotalTimeNs);
+    sdev = (sqrt((double)nXvariance) / gBenchmarkNum / gBenchmarkNum) + 0.5;
+  }
+  if (mean > (10000 * sdev)) {
+    printf("%-25s %10llu %10llu%s\n", full_name,
+            static_cast<uint64_t>(iterations), mean, throughput);
+  } else {
+    printf("%-25s %10llu %10llu(\317\203%llu)%s\n", full_name,
+           static_cast<uint64_t>(iterations), mean, sdev, throughput);
+  }
+  fflush(stdout);
+}
+
+}  // namespace testing
+
+void SetBenchmarkBytesProcessed(uint64_t x) {
+  gBytesProcessed = x;
+}
+
+void ResetBenchmarkTiming() {
+  gBenchmarkStartTimeNs = 0;
+  gBenchmarkTotalTimeNs = 0;
+  gBenchmarkTotalTimeNsSquared = 0;
+  gBenchmarkNum = 0;
+}
+
+void StopBenchmarkTiming(void) {
+  if (gBenchmarkStartTimeNs != 0) {
+    int64_t diff = NanoTime() - gBenchmarkStartTimeNs;
+    gBenchmarkTotalTimeNs += diff;
+    gBenchmarkTotalTimeNsSquared += diff * diff;
+    ++gBenchmarkNum;
+  }
+  gBenchmarkStartTimeNs = 0;
+}
+
+void StartBenchmarkTiming(void) {
+  if (gBenchmarkStartTimeNs == 0) {
+    gBenchmarkStartTimeNs = NanoTime();
+  }
+}
+
+void StopBenchmarkTiming(uint64_t NanoTime) {
+  if (gBenchmarkStartTimeNs != 0) {
+    int64_t diff = NanoTime - gBenchmarkStartTimeNs;
+    gBenchmarkTotalTimeNs += diff;
+    gBenchmarkTotalTimeNsSquared += diff * diff;
+    if (NanoTime != 0) {
+      ++gBenchmarkNum;
+    }
+  }
+  gBenchmarkStartTimeNs = 0;
+}
+
+void StartBenchmarkTiming(uint64_t NanoTime) {
+  if (gBenchmarkStartTimeNs == 0) {
+    gBenchmarkStartTimeNs = NanoTime;
+  }
+}
+
+int main(int argc, char* argv[]) {
+  if (gBenchmarks->empty()) {
+    fprintf(stderr, "No benchmarks registered!\n");
+    exit(EXIT_FAILURE);
+  }
+
+  bool need_header = true;
+  for (auto b : *gBenchmarks) {
+    if (ShouldRun(b, argc, argv)) {
+      if (need_header) {
+        printf("%-25s %10s %10s\n", "", "iterations", "ns/op");
+        fflush(stdout);
+        need_header = false;
+      }
+      Run(b);
+    }
+  }
+
+  if (need_header) {
+    fprintf(stderr, "No matching benchmarks!\n");
+    fprintf(stderr, "Available benchmarks:\n");
+    for (auto b : *gBenchmarks) {
+      fprintf(stderr, "  %s\n", b->Name());
+    }
+    exit(EXIT_FAILURE);
+  }
+
+  return 0;
+}
diff --git a/liblog/tests/liblog_benchmark.cpp b/liblog/tests/liblog_benchmark.cpp
new file mode 100644
index 0000000..c06ef91
--- /dev/null
+++ b/liblog/tests/liblog_benchmark.cpp
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2013 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 "benchmark.h"
+
+#include <sys/socket.h>
+#include <cutils/sockets.h>
+#include <log/log.h>
+#include <log/logger.h>
+
+/*
+ *	Measure the fastest rate we can reliabley stuff print messages into
+ * the log at high pressure. Expect this to be less than double the process
+ * wakeup time (2ms?)
+ */
+static void BM_log_maximum_retry(int iters) {
+    StartBenchmarkTiming();
+
+    for (int i = 0; i < iters; ++i) {
+        TEMP_FAILURE_RETRY(
+            __android_log_print(ANDROID_LOG_INFO,
+                                "BM_log_maximum_retry", "%d", i));
+    }
+
+    StopBenchmarkTiming();
+}
+BENCHMARK(BM_log_maximum_retry);
+
+/*
+ *	Measure the fastest rate we can stuff print messages into the log
+ * at high pressure. Expect this to be less than double the process wakeup
+ * time (2ms?)
+ */
+static void BM_log_maximum(int iters) {
+    StartBenchmarkTiming();
+
+    for (int i = 0; i < iters; ++i) {
+        __android_log_print(ANDROID_LOG_INFO, "BM_log_maximum", "%d", i);
+    }
+
+    StopBenchmarkTiming();
+}
+BENCHMARK(BM_log_maximum);
+
+/*
+ *	Measure the time it takes to submit the android logging call using
+ * discrete acquisition under light load. Expect this to be a pair of
+ * syscall periods (2us).
+ */
+static void BM_clock_overhead(int iters) {
+    for (int i = 0; i < iters; ++i) {
+       StartBenchmarkTiming();
+       StopBenchmarkTiming();
+    }
+}
+BENCHMARK(BM_clock_overhead);
+
+/*
+ *	Measure the time it takes to submit the android logging call using
+ * discrete acquisition under light load. Expect this to be a dozen or so
+ * syscall periods (40us).
+ */
+static void BM_log_overhead(int iters) {
+    for (int i = 0; i < iters; ++i) {
+       StartBenchmarkTiming();
+       __android_log_print(ANDROID_LOG_INFO, "BM_log_overhead", "%d", i);
+       StopBenchmarkTiming();
+       usleep(1000);
+    }
+}
+BENCHMARK(BM_log_overhead);
+
+/*
+ *	Measure the time it takes for the logd posting call to acquire the
+ * timestamp to place into the internal record. Expect this to be less than
+ * 4 syscalls (3us).
+ */
+static void BM_log_latency(int iters) {
+    pid_t pid = getpid();
+
+    struct logger_list * logger_list = android_logger_list_open(LOG_ID_EVENTS,
+        O_RDONLY, 0, pid);
+
+    if (!logger_list) {
+        fprintf(stderr, "Unable to open events log: %s\n", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+
+    for (int j = 0, i = 0; i < iters && j < 10*iters; ++i, ++j) {
+        log_time_t ts;
+        TEMP_FAILURE_RETRY((
+            clock_gettime(CLOCK_REALTIME, &ts),
+            android_btWriteLog(0, EVENT_TYPE_LONG, &ts, sizeof(ts))));
+
+        for (;;) {
+            log_msg log_msg;
+            int ret = android_logger_list_read(logger_list, &log_msg);
+
+            if (ret <= 0) {
+                iters = i;
+                break;
+            }
+            if ((log_msg.entry.len != (4 + 1 + 8))
+             || (log_msg.id() != LOG_ID_EVENTS)) {
+                continue;
+            }
+
+            char* eventData = log_msg.msg();
+
+            if (eventData[4] != EVENT_TYPE_LONG) {
+                continue;
+            }
+            log_time_t tx(eventData + 4 + 1);
+            if (ts != tx) {
+                continue;
+            }
+
+            uint64_t start = ts.nsec();
+            uint64_t end = log_msg.nsec();
+            if (end >= start) {
+                StartBenchmarkTiming(start);
+                StopBenchmarkTiming(end);
+            } else {
+                --i;
+            }
+            break;
+        }
+    }
+
+    android_logger_list_free(logger_list);
+}
+BENCHMARK(BM_log_latency);
+
+/*
+ *	Measure the time it takes for the logd posting call to make it into
+ * the logs. Expect this to be less than double the process wakeup time (2ms).
+ */
+static void BM_log_delay(int iters) {
+    pid_t pid = getpid();
+
+    struct logger_list * logger_list = android_logger_list_open(LOG_ID_EVENTS,
+        O_RDONLY, 0, pid);
+
+    if (!logger_list) {
+        fprintf(stderr, "Unable to open events log: %s\n", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+
+    StartBenchmarkTiming();
+
+    for (int i = 0; i < iters; ++i) {
+        log_time_t ts;
+
+        clock_gettime(CLOCK_REALTIME, &ts);
+        TEMP_FAILURE_RETRY(
+            android_btWriteLog(0, EVENT_TYPE_LONG, &ts, sizeof(ts)));
+
+        for (;;) {
+            log_msg log_msg;
+            int ret = android_logger_list_read(logger_list, &log_msg);
+
+            if (ret <= 0) {
+                iters = i;
+                break;
+            }
+            if ((log_msg.entry.len != (4 + 1 + 8))
+             || (log_msg.id() != LOG_ID_EVENTS)) {
+                continue;
+            }
+
+            char* eventData = log_msg.msg();
+
+            if (eventData[4] != EVENT_TYPE_LONG) {
+                continue;
+            }
+            log_time_t tx(eventData + 4 + 1);
+            if (ts != tx) {
+                continue;
+            }
+
+            break;
+        }
+    }
+
+    StopBenchmarkTiming();
+
+    android_logger_list_free(logger_list);
+}
+BENCHMARK(BM_log_delay);
diff --git a/liblog/tests/liblog_test.cpp b/liblog/tests/liblog_test.cpp
new file mode 100644
index 0000000..dfc08f0
--- /dev/null
+++ b/liblog/tests/liblog_test.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2013 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 <fcntl.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+#include <log/logger.h>
+
+TEST(liblog, __android_log_buf_print) {
+    ASSERT_LT(0, __android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO,
+                                         "TEST__android_log_buf_print",
+                                         "radio"));
+    usleep(1000);
+    ASSERT_LT(0, __android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO,
+                                         "TEST__android_log_buf_print",
+                                         "system"));
+    usleep(1000);
+    ASSERT_LT(0, __android_log_buf_print(LOG_ID_MAIN, ANDROID_LOG_INFO,
+                                         "TEST__android_log_buf_print",
+                                         "main"));
+    usleep(1000);
+}
+
+TEST(liblog, __android_log_buf_write) {
+    ASSERT_LT(0, __android_log_buf_write(LOG_ID_RADIO, ANDROID_LOG_INFO,
+                                         "TEST__android_log_buf_write",
+                                         "radio"));
+    usleep(1000);
+    ASSERT_LT(0, __android_log_buf_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO,
+                                         "TEST__android_log_buf_write",
+                                         "system"));
+    usleep(1000);
+    ASSERT_LT(0, __android_log_buf_write(LOG_ID_MAIN, ANDROID_LOG_INFO,
+                                         "TEST__android_log_buf_write",
+                                         "main"));
+    usleep(1000);
+}
+
+TEST(liblog, __android_log_btwrite) {
+    int intBuf = 0xDEADBEEF;
+    ASSERT_LT(0, __android_log_btwrite(0,
+                                      EVENT_TYPE_INT,
+                                      &intBuf, sizeof(intBuf)));
+    long long longBuf = 0xDEADBEEFA55A5AA5;
+    ASSERT_LT(0, __android_log_btwrite(0,
+                                      EVENT_TYPE_LONG,
+                                      &longBuf, sizeof(longBuf)));
+    usleep(1000);
+    char Buf[] = "\20\0\0\0DeAdBeEfA55a5aA5";
+    ASSERT_LT(0, __android_log_btwrite(0,
+                                      EVENT_TYPE_STRING,
+                                      Buf, sizeof(Buf) - 1));
+    usleep(1000);
+}
+
+static void* ConcurrentPrintFn(void *arg) {
+    int ret = __android_log_buf_print(LOG_ID_MAIN, ANDROID_LOG_INFO,
+                                  "TEST__android_log_print", "Concurrent %d",
+                                  reinterpret_cast<int>(arg));
+    return reinterpret_cast<void*>(ret);
+}
+
+#define NUM_CONCURRENT 64
+#define _concurrent_name(a,n) a##__concurrent##n
+#define concurrent_name(a,n) _concurrent_name(a,n)
+
+TEST(liblog, concurrent_name(__android_log_buf_print, NUM_CONCURRENT)) {
+    pthread_t t[NUM_CONCURRENT];
+    int i;
+    for (i=0; i < NUM_CONCURRENT; i++) {
+        ASSERT_EQ(0, pthread_create(&t[i], NULL,
+                                    ConcurrentPrintFn,
+                                    reinterpret_cast<void *>(i)));
+    }
+    int ret = 0;
+    for (i=0; i < NUM_CONCURRENT; i++) {
+        void* result;
+        ASSERT_EQ(0, pthread_join(t[i], &result));
+        if ((0 == ret) && (0 != reinterpret_cast<int>(result))) {
+            ret = reinterpret_cast<int>(result);
+        }
+    }
+    ASSERT_LT(0, ret);
+}
+
+TEST(liblog, __android_log_btwrite__android_logger_list_read) {
+    pid_t pid;
+    struct logger_list *logger_list;
+    log_time_t ts;
+
+    pid = getpid();
+
+    ASSERT_EQ(0, NULL == (logger_list = android_logger_list_open(
+        LOG_ID_EVENTS, O_RDONLY | O_NDELAY, 1000, pid)));
+
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+
+    ASSERT_LT(0, __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)));
+    usleep(1000000);
+
+    int count = 0;
+
+    for (;;) {
+        log_msg log_msg;
+        if (android_logger_list_read(logger_list, &log_msg) <= 0) {
+            break;
+        }
+
+        ASSERT_EQ(log_msg.entry.pid, pid);
+
+        if ((log_msg.entry.len != (4 + 1 + 8))
+         || (log_msg.id() != LOG_ID_EVENTS)) {
+            continue;
+        }
+
+        char *eventData = log_msg.msg();
+
+        if (eventData[4] != EVENT_TYPE_LONG) {
+            continue;
+        }
+
+        log_time_t tx(eventData + 4 + 1);
+        if (ts == tx) {
+            ++count;
+        }
+    }
+
+    ASSERT_EQ(1, count);
+
+    android_logger_list_close(logger_list);
+}
+
+TEST(liblog, android_logger_get_) {
+    struct logger_list * logger_list = android_logger_list_alloc(O_WRONLY, 0, 0);
+
+    for(int i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) {
+        log_id_t id = static_cast<log_id_t>(i);
+        const char *name = android_log_id_to_name(id);
+        if (id != android_name_to_log_id(name)) {
+            continue;
+        }
+        struct logger * logger;
+        ASSERT_EQ(0, NULL == (logger = android_logger_open(logger_list, id)));
+        ASSERT_EQ(id, android_logger_get_id(logger));
+        ASSERT_LT(0, android_logger_get_log_size(logger));
+        ASSERT_LT(0, android_logger_get_log_readable_size(logger));
+        ASSERT_LT(0, android_logger_get_log_version(logger));
+    }
+
+    android_logger_list_close(logger_list);
+}
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index d44c679..b6ca171 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -1,5 +1,6 @@
-// Copyright 2006 The Android Open Source Project
+// Copyright 2006-2013 The Android Open Source Project
 
+#include <log/log.h>
 #include <log/logger.h>
 #include <log/logd.h>
 #include <log/logprint.h>
@@ -24,65 +25,27 @@
 #define DEFAULT_MAX_ROTATED_LOGS 4
 
 static AndroidLogFormat * g_logformat;
-static bool g_nonblock = false;
-static int g_tail_lines = 0;
 
 /* logd prefixes records with a length field */
 #define RECORD_LENGTH_FIELD_SIZE_BYTES sizeof(uint32_t)
 
-#define LOG_FILE_DIR    "/dev/log/"
-
-struct queued_entry_t {
-    union {
-        unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1] __attribute__((aligned(4)));
-        struct logger_entry entry __attribute__((aligned(4)));
-    };
-    queued_entry_t* next;
-
-    queued_entry_t() {
-        next = NULL;
-    }
-};
-
-static int cmp(queued_entry_t* a, queued_entry_t* b) {
-    int n = a->entry.sec - b->entry.sec;
-    if (n != 0) {
-        return n;
-    }
-    return a->entry.nsec - b->entry.nsec;
-}
-
 struct log_device_t {
-    char* device;
+    const char* device;
     bool binary;
-    int fd;
+    struct logger *logger;
+    struct logger_list *logger_list;
     bool printed;
     char label;
 
-    queued_entry_t* queue;
     log_device_t* next;
 
-    log_device_t(char* d, bool b, char l) {
+    log_device_t(const char* d, bool b, char l) {
         device = d;
         binary = b;
         label = l;
-        queue = NULL;
         next = NULL;
         printed = false;
     }
-
-    void enqueue(queued_entry_t* entry) {
-        if (this->queue == NULL) {
-            this->queue = entry;
-        } else {
-            queued_entry_t** e = &this->queue;
-            while (*e && cmp(entry, *e) >= 0) {
-                e = &((*e)->next);
-            }
-            entry->next = *e;
-            *e = entry;
-        }
-    }
 };
 
 namespace android {
@@ -147,17 +110,14 @@
 
 }
 
-void printBinary(struct logger_entry *buf)
+void printBinary(struct log_msg *buf)
 {
-    size_t size = sizeof(logger_entry) + buf->len;
-    int ret;
-    
-    do {
-        ret = write(g_outFD, buf, size);
-    } while (ret < 0 && errno == EINTR);
+    size_t size = buf->len();
+
+    TEMP_FAILURE_RETRY(write(g_outFD, buf, size));
 }
 
-static void processBuffer(log_device_t* dev, struct logger_entry *buf)
+static void processBuffer(log_device_t* dev, struct log_msg *buf)
 {
     int bytesWritten = 0;
     int err;
@@ -165,12 +125,14 @@
     char binaryMsgBuf[1024];
 
     if (dev->binary) {
-        err = android_log_processBinaryLogBuffer(buf, &entry, g_eventTagMap,
-                binaryMsgBuf, sizeof(binaryMsgBuf));
+        err = android_log_processBinaryLogBuffer(&buf->entry_v1, &entry,
+                                                 g_eventTagMap,
+                                                 binaryMsgBuf,
+                                                 sizeof(binaryMsgBuf));
         //printf(">>> pri=%d len=%d msg='%s'\n",
         //    entry.priority, entry.messageLen, entry.message);
     } else {
-        err = android_log_processLogBuffer(buf, &entry);
+        err = android_log_processLogBuffer(&buf->entry_v1, &entry);
     }
     if (err < 0) {
         goto error;
@@ -197,7 +159,7 @@
 
     g_outByteCount += bytesWritten;
 
-    if (g_logRotateSizeKBytes > 0 
+    if (g_logRotateSizeKBytes > 0
         && (g_outByteCount / 1024) >= g_logRotateSizeKBytes
     ) {
         rotateLogs();
@@ -208,20 +170,13 @@
     return;
 }
 
-static void chooseFirst(log_device_t* dev, log_device_t** firstdev) {
-    for (*firstdev = NULL; dev != NULL; dev = dev->next) {
-        if (dev->queue != NULL && (*firstdev == NULL || cmp(dev->queue, (*firstdev)->queue) < 0)) {
-            *firstdev = dev;
-        }
-    }
-}
-
 static void maybePrintStart(log_device_t* dev) {
     if (!dev->printed) {
         dev->printed = true;
         if (g_devCount > 1 && !g_printBinary) {
             char buf[1024];
-            snprintf(buf, sizeof(buf), "--------- beginning of %s\n", dev->device);
+            snprintf(buf, sizeof(buf), "--------- beginning of %s\n",
+                     dev->device);
             if (write(g_outFD, buf, strlen(buf)) < 0) {
                 perror("output error");
                 exit(-1);
@@ -230,145 +185,6 @@
     }
 }
 
-static void skipNextEntry(log_device_t* dev) {
-    maybePrintStart(dev);
-    queued_entry_t* entry = dev->queue;
-    dev->queue = entry->next;
-    delete entry;
-}
-
-static void printNextEntry(log_device_t* dev) {
-    maybePrintStart(dev);
-    if (g_printBinary) {
-        printBinary(&dev->queue->entry);
-    } else {
-        processBuffer(dev, &dev->queue->entry);
-    }
-    skipNextEntry(dev);
-}
-
-static void readLogLines(log_device_t* devices)
-{
-    log_device_t* dev;
-    int max = 0;
-    int ret;
-    int queued_lines = 0;
-    bool sleep = false;
-
-    int result;
-    fd_set readset;
-
-    for (dev=devices; dev; dev = dev->next) {
-        if (dev->fd > max) {
-            max = dev->fd;
-        }
-    }
-
-    while (1) {
-        do {
-            timeval timeout = { 0, 5000 /* 5ms */ }; // If we oversleep it's ok, i.e. ignore EINTR.
-            FD_ZERO(&readset);
-            for (dev=devices; dev; dev = dev->next) {
-                FD_SET(dev->fd, &readset);
-            }
-            result = select(max + 1, &readset, NULL, NULL, sleep ? NULL : &timeout);
-        } while (result == -1 && errno == EINTR);
-
-        if (result >= 0) {
-            for (dev=devices; dev; dev = dev->next) {
-                if (FD_ISSET(dev->fd, &readset)) {
-                    queued_entry_t* entry = new queued_entry_t();
-                    /* NOTE: driver guarantees we read exactly one full entry */
-                    ret = read(dev->fd, entry->buf, LOGGER_ENTRY_MAX_LEN);
-                    if (ret < 0) {
-                        if (errno == EINTR) {
-                            delete entry;
-                            goto next;
-                        }
-                        if (errno == EAGAIN) {
-                            delete entry;
-                            break;
-                        }
-                        perror("logcat read");
-                        exit(EXIT_FAILURE);
-                    }
-                    else if (!ret) {
-                        fprintf(stderr, "read: Unexpected EOF!\n");
-                        exit(EXIT_FAILURE);
-                    }
-                    else if (entry->entry.len != ret - sizeof(struct logger_entry)) {
-                        fprintf(stderr, "read: unexpected length. Expected %d, got %d\n",
-                                entry->entry.len, ret - sizeof(struct logger_entry));
-                        exit(EXIT_FAILURE);
-                    }
-
-                    entry->entry.msg[entry->entry.len] = '\0';
-
-                    dev->enqueue(entry);
-                    ++queued_lines;
-                }
-            }
-
-            if (result == 0) {
-                // we did our short timeout trick and there's nothing new
-                // print everything we have and wait for more data
-                sleep = true;
-                while (true) {
-                    chooseFirst(devices, &dev);
-                    if (dev == NULL) {
-                        break;
-                    }
-                    if (g_tail_lines == 0 || queued_lines <= g_tail_lines) {
-                        printNextEntry(dev);
-                    } else {
-                        skipNextEntry(dev);
-                    }
-                    --queued_lines;
-                }
-
-                // the caller requested to just dump the log and exit
-                if (g_nonblock) {
-                    return;
-                }
-            } else {
-                // print all that aren't the last in their list
-                sleep = false;
-                while (g_tail_lines == 0 || queued_lines > g_tail_lines) {
-                    chooseFirst(devices, &dev);
-                    if (dev == NULL || dev->queue->next == NULL) {
-                        break;
-                    }
-                    if (g_tail_lines == 0) {
-                        printNextEntry(dev);
-                    } else {
-                        skipNextEntry(dev);
-                    }
-                    --queued_lines;
-                }
-            }
-        }
-next:
-        ;
-    }
-}
-
-static int clearLog(int logfd)
-{
-    return ioctl(logfd, LOGGER_FLUSH_LOG);
-}
-
-/* returns the total size of the log's ring buffer */
-static int getLogSize(int logfd)
-{
-    return ioctl(logfd, LOGGER_GET_LOG_BUF_SIZE);
-}
-
-/* returns the readable size of the log's ring buffer (that is, amount of the log consumed) */
-static int getLogReadableSize(int logfd)
-{
-    return ioctl(logfd, LOGGER_GET_LOG_LEN);
-}
-
 static void setupOutput()
 {
 
@@ -465,6 +281,8 @@
     log_device_t* devices = NULL;
     log_device_t* dev;
     bool needBinary = false;
+    struct logger_list *logger_list;
+    int tail_lines = 0;
 
     g_logformat = android_log_format_new();
 
@@ -488,7 +306,7 @@
         }
 
         switch(ret) {
-            case 's': 
+            case 's':
                 // default to all silent
                 android_log_addFilterRule(g_logformat, "*:s");
             break;
@@ -499,12 +317,12 @@
             break;
 
             case 'd':
-                g_nonblock = true;
+                mode = O_RDONLY | O_NDELAY;
             break;
 
             case 't':
-                g_nonblock = true;
-                g_tail_lines = atoi(optarg);
+                mode = O_RDONLY | O_NDELAY;
+                tail_lines = atoi(optarg);
             break;
 
             case 'g':
@@ -512,10 +330,6 @@
             break;
 
             case 'b': {
-                char* buf = (char*) malloc(strlen(LOG_FILE_DIR) + strlen(optarg) + 1);
-                strcpy(buf, LOG_FILE_DIR);
-                strcat(buf, optarg);
-
                 bool binary = strcmp(optarg, "events") == 0;
                 if (binary) {
                     needBinary = true;
@@ -526,9 +340,9 @@
                     while (dev->next) {
                         dev = dev->next;
                     }
-                    dev->next = new log_device_t(buf, binary, optarg[0]);
+                    dev->next = new log_device_t(optarg, binary, optarg[0]);
                 } else {
-                    devices = new log_device_t(buf, binary, optarg[0]);
+                    devices = new log_device_t(optarg, binary, optarg[0]);
                 }
                 android::g_devCount++;
             }
@@ -546,8 +360,8 @@
             break;
 
             case 'r':
-                if (optarg == NULL) {                
-                    android::g_logRotateSizeKBytes 
+                if (optarg == NULL) {
+                    android::g_logRotateSizeKBytes
                                 = DEFAULT_LOG_ROTATE_SIZE_KBYTES;
                 } else {
                     long logRotateSize;
@@ -659,19 +473,15 @@
     }
 
     if (!devices) {
-        devices = new log_device_t(strdup("/dev/"LOGGER_LOG_MAIN), false, 'm');
+        devices = new log_device_t("main", false, 'm');
         android::g_devCount = 1;
-        int accessmode =
-                  (mode & O_RDONLY) ? R_OK : 0
-                | (mode & O_WRONLY) ? W_OK : 0;
-        // only add this if it's available
-        if (0 == access("/dev/"LOGGER_LOG_SYSTEM, accessmode)) {
-            devices->next = new log_device_t(strdup("/dev/"LOGGER_LOG_SYSTEM), false, 's');
+        if (android_name_to_log_id("system") == LOG_ID_SYSTEM) {
+            devices->next = new log_device_t("system", false, 's');
             android::g_devCount++;
         }
     }
 
-    if (android::g_logRotateSizeKBytes != 0 
+    if (android::g_logRotateSizeKBytes != 0
         && android::g_outputFileName == NULL
     ) {
         fprintf(stderr,"-r requires -f as well\n");
@@ -688,7 +498,7 @@
             err = setLogFormat(logFormat);
 
             if (err < 0) {
-                fprintf(stderr, "invalid format in ANDROID_PRINTF_LOG '%s'\n", 
+                fprintf(stderr, "invalid format in ANDROID_PRINTF_LOG '%s'\n",
                                     logFormat);
             }
         }
@@ -707,8 +517,8 @@
         if (env_tags_orig != NULL) {
             err = android_log_addFilterString(g_logformat, env_tags_orig);
 
-            if (err < 0) { 
-                fprintf(stderr, "Invalid filter expression in" 
+            if (err < 0) {
+                fprintf(stderr, "Invalid filter expression in"
                                     " ANDROID_LOG_TAGS\n");
                 android::show_help(argv[0]);
                 exit(-1);
@@ -719,7 +529,7 @@
         for (int i = optind ; i < argc ; i++) {
             err = android_log_addFilterString(g_logformat, argv[i]);
 
-            if (err < 0) { 
+            if (err < 0) {
                 fprintf (stderr, "Invalid filter expression '%s'\n", argv[i]);
                 android::show_help(argv[0]);
                 exit(-1);
@@ -728,19 +538,21 @@
     }
 
     dev = devices;
+    logger_list = android_logger_list_alloc(mode, tail_lines, 0);
     while (dev) {
-        dev->fd = open(dev->device, mode);
-        if (dev->fd < 0) {
-            fprintf(stderr, "Unable to open log device '%s': %s\n",
-                dev->device, strerror(errno));
+        dev->logger_list = logger_list;
+        dev->logger = android_logger_open(logger_list,
+                                          android_name_to_log_id(dev->device));
+        if (!dev->logger) {
+            fprintf(stderr, "Unable to open log device '%s'\n", dev->device);
             exit(EXIT_FAILURE);
         }
 
         if (clearLog) {
             int ret;
-            ret = android::clearLog(dev->fd);
+            ret = android_logger_clear(dev->logger);
             if (ret) {
-                perror("ioctl");
+                perror("clearLog");
                 exit(EXIT_FAILURE);
             }
         }
@@ -748,15 +560,15 @@
         if (getLogSize) {
             int size, readable;
 
-            size = android::getLogSize(dev->fd);
+            size = android_logger_get_log_size(dev->logger);
             if (size < 0) {
-                perror("ioctl");
+                perror("getLogSize");
                 exit(EXIT_FAILURE);
             }
 
-            readable = android::getLogReadableSize(dev->fd);
+            readable = android_logger_get_log_readable_size(dev->logger);
             if (readable < 0) {
-                perror("ioctl");
+                perror("getLogReadableSize");
                 exit(EXIT_FAILURE);
             }
 
@@ -783,7 +595,51 @@
     if (needBinary)
         android::g_eventTagMap = android_openEventTagMap(EVENT_TAG_MAP_FILE);
 
-    android::readLogLines(devices);
+    while (1) {
+        struct log_msg log_msg;
+        int ret = android_logger_list_read(logger_list, &log_msg);
+
+        if (ret == 0) {
+            fprintf(stderr, "read: Unexpected EOF!\n");
+            exit(EXIT_FAILURE);
+        }
+
+        if (ret < 0) {
+            if (ret == -EAGAIN) {
+                break;
+            }
+
+            if (ret == -EIO) {
+                fprintf(stderr, "read: Unexpected EOF!\n");
+                exit(EXIT_FAILURE);
+            }
+            if (ret == -EINVAL) {
+                fprintf(stderr, "read: unexpected length.\n");
+                exit(EXIT_FAILURE);
+            }
+            perror("logcat read");
+            exit(EXIT_FAILURE);
+        }
+
+        for(dev = devices; dev; dev = dev->next) {
+            if (android_name_to_log_id(dev->device) == log_msg.id()) {
+                break;
+            }
+        }
+        if (!dev) {
+            fprintf(stderr, "read: Unexpected log ID!\n");
+            exit(EXIT_FAILURE);
+        }
+
+        android::maybePrintStart(dev);
+        if (android::g_printBinary) {
+            android::printBinary(&log_msg);
+        } else {
+            android::processBuffer(dev, &log_msg);
+        }
+    }
+
+    android_logger_list_free(logger_list);
 
     return 0;
 }