[automerger skipped] am 0c61a650: am b6762cf3: Merge "Logd: Handle unused variable and fields" am: 10be82c110 -s ours

am skip reason: skipped by user salyzyn

Original change: undetermined

Change-Id: I51ba3f58f6bbbfd73c27cb440a2959c1ece9842d
diff --git a/liblog/.clang-format b/liblog/.clang-format
new file mode 120000
index 0000000..fd0645f
--- /dev/null
+++ b/liblog/.clang-format
@@ -0,0 +1 @@
+../.clang-format-2
\ No newline at end of file
diff --git a/liblog/Android.bp b/liblog/Android.bp
new file mode 100644
index 0000000..8f15541
--- /dev/null
+++ b/liblog/Android.bp
@@ -0,0 +1,156 @@
+//
+// Copyright (C) 2008-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.
+//
+
+liblog_sources = [
+    "log_event_list.cpp",
+    "log_event_write.cpp",
+    "logger_name.cpp",
+    "logger_read.cpp",
+    "logger_write.cpp",
+    "logprint.cpp",
+    "properties.cpp",
+]
+liblog_target_sources = [
+    "event_tag_map.cpp",
+    "log_time.cpp",
+    "pmsg_reader.cpp",
+    "pmsg_writer.cpp",
+    "logd_reader.cpp",
+    "logd_writer.cpp",
+]
+
+cc_library_headers {
+    name: "liblog_headers",
+    host_supported: true,
+    vendor_available: true,
+    ramdisk_available: true,
+    recovery_available: true,
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
+    min_sdk_version: "29",
+    sdk_version: "minimum",
+    native_bridge_supported: true,
+    export_include_dirs: ["include"],
+    system_shared_libs: [],
+    stl: "none",
+    target: {
+        windows: {
+            enabled: true,
+        },
+        linux_bionic: {
+            enabled: true,
+        },
+        vendor: {
+            override_export_include_dirs: ["include_vndk"],
+        },
+    },
+}
+
+// Shared and static library for host and device
+// ========================================================
+cc_library {
+    name: "liblog",
+    host_supported: true,
+    ramdisk_available: true,
+    recovery_available: true,
+    native_bridge_supported: true,
+    srcs: liblog_sources,
+
+    target: {
+        android: {
+            version_script: "liblog.map.txt",
+            srcs: liblog_target_sources,
+            // AddressSanitizer runtime library depends on liblog.
+            sanitize: {
+                address: false,
+            },
+        },
+        android_arm: {
+            // TODO: This is to work around b/24465209. Remove after root cause is fixed
+            pack_relocations: false,
+            ldflags: ["-Wl,--hash-style=both"],
+        },
+        windows: {
+            enabled: true,
+        },
+        not_windows: {
+            srcs: ["event_tag_map.cpp"],
+        },
+        linux_bionic: {
+            enabled: true,
+        },
+    },
+
+    header_libs: [
+        "libbase_headers",
+        "libcutils_headers",
+        "liblog_headers",
+    ],
+    export_header_lib_headers: ["liblog_headers"],
+
+    stubs: {
+        symbol_file: "liblog.map.txt",
+        versions: ["29", "30"],
+    },
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wextra",
+        // This is what we want to do:
+        //  liblog_cflags := $(shell \
+        //   sed -n \
+        //       's/^\([0-9]*\)[ \t]*liblog[ \t].*/-DLIBLOG_LOG_TAG=\1/p' \
+        //       $(LOCAL_PATH)/event.logtags)
+        // so make sure we do not regret hard-coding it as follows:
+        "-DLIBLOG_LOG_TAG=1006",
+        "-DSNET_EVENT_LOG_TAG=1397638484",
+    ],
+    logtags: ["event.logtags"],
+    compile_multilib: "both",
+    apex_available: [
+        "//apex_available:platform",
+        // liblog is exceptionally available to the runtime APEX
+        // because the dynamic linker has to use it statically.
+        // See b/151051671
+        "com.android.runtime",
+        // DO NOT add more apex names here
+    ],
+}
+
+ndk_headers {
+    name: "liblog_ndk_headers",
+    from: "include/android",
+    to: "android",
+    srcs: ["include/android/log.h"],
+    license: "NOTICE",
+}
+
+ndk_library {
+    name: "liblog",
+    symbol_file: "liblog.map.txt",
+    first_version: "9",
+    unversioned_until: "current",
+}
+
+llndk_library {
+    name: "liblog",
+    native_bridge_supported: true,
+    symbol_file: "liblog.map.txt",
+    export_include_dirs: ["include_vndk"],
+}
diff --git a/liblog/Android.mk b/liblog/Android.mk
deleted file mode 100644
index ce282bd..0000000
--- a/liblog/Android.mk
+++ /dev/null
@@ -1,98 +0,0 @@
-#
-# Copyright (C) 2008-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.
-#
-LOCAL_PATH := $(my-dir)
-include $(CLEAR_VARS)
-
-# This is what we want to do:
-#  liblog_cflags := $(shell \
-#   sed -n \
-#       's/^\([0-9]*\)[ \t]*liblog[ \t].*/-DLIBLOG_LOG_TAG=\1/p' \
-#       $(LOCAL_PATH)/event.logtags)
-# so make sure we do not regret hard-coding it as follows:
-liblog_cflags := -DLIBLOG_LOG_TAG=1005
-
-ifneq ($(TARGET_USES_LOGD),false)
-liblog_sources := logd_write.c
-else
-liblog_sources := logd_write_kern.c
-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
-
-ifeq ($(strip $(USE_MINGW)),)
-    liblog_sources += \
-        event_tag_map.c
-else
-    liblog_sources += \
-        uio.c
-endif
-
-liblog_host_sources := $(liblog_sources) fake_log_device.c event.logtags
-liblog_target_sources := $(liblog_sources) log_time.cpp log_is_loggable.c
-ifeq ($(strip $(USE_MINGW)),)
-liblog_target_sources += logprint.c
-endif
-ifneq ($(TARGET_USES_LOGD),false)
-liblog_target_sources += log_read.c
-else
-liblog_target_sources += log_read_kern.c
-endif
-
-# Shared and static library for host
-# ========================================================
-LOCAL_MODULE := liblog
-LOCAL_SRC_FILES := $(liblog_host_sources)
-LOCAL_CFLAGS := -DFAKE_LOG_DEVICE=1 -Werror $(liblog_cflags)
-LOCAL_MULTILIB := both
-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
-LOCAL_MULTILIB := both
-LOCAL_CXX_STL := none
-include $(BUILD_HOST_SHARED_LIBRARY)
-
-
-# Shared and static library for target
-# ========================================================
-include $(CLEAR_VARS)
-LOCAL_MODULE := liblog
-LOCAL_SRC_FILES := $(liblog_target_sources)
-LOCAL_CFLAGS := -Werror $(liblog_cflags)
-# AddressSanitizer runtime library depends on liblog.
-LOCAL_SANITIZE := never
-include $(BUILD_STATIC_LIBRARY)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := liblog
-LOCAL_WHOLE_STATIC_LIBRARIES := liblog
-LOCAL_CFLAGS := -Werror $(liblog_cflags)
-
-# TODO: This is to work around b/19059885. Remove after root cause is fixed
-LOCAL_LDFLAGS_arm := -Wl,--hash-style=both
-
-LOCAL_SANITIZE := never
-LOCAL_CXX_STL := none
-
-include $(BUILD_SHARED_LIBRARY)
-
-include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/liblog/OWNERS b/liblog/OWNERS
new file mode 100644
index 0000000..babbe4d
--- /dev/null
+++ b/liblog/OWNERS
@@ -0,0 +1 @@
+tomcherry@google.com
diff --git a/liblog/README b/liblog/README
deleted file mode 100644
index f29ac04..0000000
--- a/liblog/README
+++ /dev/null
@@ -1,166 +0,0 @@
-LIBLOG(3)               Android NDK Programming Manual               LIBLOG(3)
-
-
-
-NAME
-       liblog - Android NDK logger interfaces
-
-SYNOPSIS
-       #include <log/log.h>
-
-       ALOG(android_priority, tag, format, ...)
-       IF_ALOG(android_priority, tag)
-       LOG_PRI(priority, tag, format, ...)
-       LOG_PRI_VA(priority, tag, format, args)
-       #define LOG_TAG NULL
-       ALOGV(format, ...)
-       SLOGV(format, ...)
-       RLOGV(format, ...)
-       ALOGV_IF(cond, format, ...)
-       SLOGV_IF(cond, format, ...)
-       RLOGV_IF(cond, format, ...)
-       IF_ALOGC()
-       ALOGD(format, ...)
-       SLOGD(format, ...)
-       RLOGD(format, ...)
-       ALOGD_IF(cond, format, ...)
-       SLOGD_IF(cond, format, ...)
-       RLOGD_IF(cond, format, ...)
-       IF_ALOGD()
-       ALOGI(format, ...)
-       SLOGI(format, ...)
-       RLOGI(format, ...)
-       ALOGI_IF(cond, format, ...)
-       SLOGI_IF(cond, format, ...)
-       RLOGI_IF(cond, format, ...)
-       IF_ALOGI()
-       ALOGW(format, ...)
-       SLOGW(format, ...)
-       RLOGW(format, ...)
-       ALOGW_IF(cond, format, ...)
-       SLOGW_IF(cond, format, ...)
-       RLOGW_IF(cond, format, ...)
-       IF_ALOGW()
-       ALOGE(format, ...)
-       SLOGE(format, ...)
-       RLOGE(format, ...)
-       ALOGE_IF(cond, format, ...)
-       SLOGE_IF(cond, format, ...)
-       RLOGE_IF(cond, format, ...)
-       IF_ALOGE()
-       LOG_FATAL(format, ...)
-       LOG_ALWAYS_FATAL(format, ...)
-       LOG_FATAL_IF(cond, format, ...)
-       LOG_ALWAYS_FATAL_IF(cond, format, ...)
-       ALOG_ASSERT(cond, format, ...)
-       LOG_EVENT_INT(tag, value)
-       LOG_EVENT_LONG(tag, value)
-
-       Link with -llog
-
-       #include <log/logger.h>
-
-       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  *android_logger_list_alloc(int  mode, unsigned int
-       tail, pid_t pid)
-       struct  logger  *android_logger_open(struct  logger_list  *logger_list,
-       log_id_t id)
-       struct  logger_list  *android_logger_list_open(log_id_t  id,  int mode,
-       unsigned int tail, pid_t pid)
-
-       int android_logger_list_read(struct  logger_list  *logger_list,  struct
-       log_msg *log_msg
-
-       void android_logger_list_free(struct logger_list *logger_list)
-
-       log_id_t android_name_to_log_id(const char *logName)
-       const char *android_log_id_to_name(log_id_t log_id)
-
-       Link with -llog
-
-DESCRIPTION
-       liblog  represents  an interface to the volatile Android Logging system
-       for NDK (Native) applications  and  libraries.  Interfaces  for  either
-       writing  or reading logs.  The log buffers are divided up in Main, Sys‐
-       tem, Radio and Events sub-logs.
-
-       The logging interfaces are a series of macros,  all  of  which  can  be
-       overridden individually in order to control the verbosity of the appli‐
-       cation or library.  [ASR]LOG[VDIWE] calls are used  to  log  to  BAsic,
-       System or Radio sub-logs in either the Verbose, Debug, Info, Warning or
-       Error priorities.  [ASR]LOG[VDIWE]_IF calls are used  to  perform  thus
-       based  on a condition being true.  IF_ALOG[VDIWE] calls are true if the
-       current LOG_TAG is enabled at the specified priority.  LOG_ALWAYS_FATAL
-       is  used to ALOG a message, then kill the process.  LOG_FATAL call is a
-       variant of LOG_ALWAYS_FATAL,  only  enabled  in  engineering,  and  not
-       release builds.  ALOG_ASSERT is used to ALOG a message if the condition
-       is  false;   the   condition   is   part   of   the   logged   message.
-       LOG_EVENT_(INT|LONG)  is  used  to  drop binary content into the Events
-       sub-log.
-
-       The log reading interfaces permit opening the  logs  either  singly  or
-       multiply,  retrieving  a  log  entry  at  a  time in time sorted order,
-       optionally limited to a specific pid and tail of the log(s) and finally
-       a  call closing the logs.  A single log can be opened with android_log‐
-       ger_list_open;  or  multiple  logs  can  be  opened  with  android_log‐
-       ger_list_alloc,  calling  in  turn the android_logger_open for each log
-       id.  Each entry can be retrieved  with  android_logger_list_read.   The
-       log(s) can be closed with android_logger_list_free.  The logs should be
-       opened  with an  ANDROID_LOG_RDONLY  mode.   ANDROID_LOG_NONBLOCK  mode
-       will report when the  log reading is done with an  EAGAIN  error return
-       code,  otherwise the  android_logger_list_read  call will block for new
-       entries.
-
-       The  ANDROID_LOG_PSTORE mode flag to the android_logger_open is used to
-       switch from the active logs to the persistent logs from before the last
-       reboot.
-
-       The value returned by android_logger_open can be used as a parameter to
-       the  android_logger_clear  function to empty the sub-log.  It is recom‐
-       mended to only open log ANDROID_LOG_WRONLY in that case.
-
-       The value returned by android_logger_open can be used as a parameter to
-       the android_logger_get_log_(size|readable_size|version) to retrieve the
-       sub-log maximum size, readable size and log buffer format protocol ver‐
-       sion  respectively.  android_logger_get_id returns the id that was used
-       when  opening  the  sub-log.    It  is  recommended  to  open  the  log
-       ANDROID_LOG_RDONLY in these cases.
-
-ERRORS
-       If messages fail, a negative error code will be returned to the caller.
-
-       The -ENOTCONN return code indicates that the logger daemon is stopped.
-
-       The  -EBADF return code indicates that the log access point can not be
-       opened, or the log buffer id is out of range.
-
-       For the  -EAGAIN  return code,  this means that the logging message was
-       temporarily backed-up either because of Denial Of Service (DOS) logging
-       pressure from some chatty application or service in the Android system,
-       or if too small of a value is set in /proc/sys/net/unix/max_dgram_qlen.
-       To aid in diagnosing the occurence of this,  a binary event from liblog
-       will be sent to the  log  daemon  once a  new  message  can get through
-       indicating how many  messages were  dropped  as a result.   Please take
-       action to resolve the structural problems at the source.
-
-       It is generally not advised for the caller to retry the  -EAGAIN return
-       code as  this  will  only  make the  problem(s)  worse  and  cause your
-       application to temporarily drop to the  logger daemon  priority,  BATCH
-       scheduling policy and background task cgroup. If you require a group of
-       messages to be passed atomically,  merge  them  into  one  message with
-       embedded newlines to the maximum length LOGGER_ENTRY_MAX_PAYLOAD.
-
-       Other return codes  from  writing operation can be returned.  Since the
-       library retries on EINTR, -EINTR should never be returned.
-
-SEE ALSO
-       syslogd(8)
-
-
-
-                                  24 Jan 2014                        LIBLOG(3)
diff --git a/liblog/README.md b/liblog/README.md
new file mode 100644
index 0000000..74a2cd7
--- /dev/null
+++ b/liblog/README.md
@@ -0,0 +1,161 @@
+Android liblog
+--------------
+
+Public Functions and Macros
+---------------------------
+
+    /*
+     * Please limit to 24 characters for runtime is loggable,
+     * 16 characters for persist is loggable, and logcat pretty
+     * alignment with limit of 7 characters.
+    */
+    #define LOG_TAG "yourtag"
+    #include <log/log.h>
+
+    ALOG(android_priority, tag, format, ...)
+    IF_ALOG(android_priority, tag)
+    LOG_PRI(priority, tag, format, ...)
+    LOG_PRI_VA(priority, tag, format, args)
+    #define LOG_TAG NULL
+    ALOGV(format, ...)
+    SLOGV(format, ...)
+    RLOGV(format, ...)
+    ALOGV_IF(cond, format, ...)
+    SLOGV_IF(cond, format, ...)
+    RLOGV_IF(cond, format, ...)
+    IF_ALOGC()
+    ALOGD(format, ...)
+    SLOGD(format, ...)
+    RLOGD(format, ...)
+    ALOGD_IF(cond, format, ...)
+    SLOGD_IF(cond, format, ...)
+    RLOGD_IF(cond, format, ...)
+    IF_ALOGD()
+    ALOGI(format, ...)
+    SLOGI(format, ...)
+    RLOGI(format, ...)
+    ALOGI_IF(cond, format, ...)
+    SLOGI_IF(cond, format, ...)
+    RLOGI_IF(cond, format, ...)
+    IF_ALOGI()
+    ALOGW(format, ...)
+    SLOGW(format, ...)
+    RLOGW(format, ...)
+    ALOGW_IF(cond, format, ...)
+    SLOGW_IF(cond, format, ...)
+    RLOGW_IF(cond, format, ...)
+    IF_ALOGW()
+    ALOGE(format, ...)
+    SLOGE(format, ...)
+    RLOGE(format, ...)
+    ALOGE_IF(cond, format, ...)
+    SLOGE_IF(cond, format, ...)
+    RLOGE_IF(cond, format, ...)
+    IF_ALOGE()
+    LOG_FATAL(format, ...)
+    LOG_ALWAYS_FATAL(format, ...)
+    LOG_FATAL_IF(cond, format, ...)
+    LOG_ALWAYS_FATAL_IF(cond, format, ...)
+    ALOG_ASSERT(cond, format, ...)
+    LOG_EVENT_INT(tag, value)
+    LOG_EVENT_LONG(tag, value)
+
+    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 *android_logger_list_alloc(int mode, unsigned int tail, pid_t pid)
+    struct logger *android_logger_open(struct logger_list *logger_list, log_id_t id)
+    struct logger_list *android_logger_list_open(log_id_t id, int mode, unsigned int tail, pid_t pid)
+    int android_logger_list_read(struct logger_list *logger_list, struct log_msg *log_msg)
+    void android_logger_list_free(struct logger_list *logger_list)
+
+    log_id_t android_name_to_log_id(const char *logName)
+    const char *android_log_id_to_name(log_id_t log_id)
+
+    android_log_context create_android_logger(uint32_t tag)
+
+    int android_log_write_list_begin(android_log_context ctx)
+    int android_log_write_list_end(android_log_context ctx)
+
+    int android_log_write_int32(android_log_context ctx, int32_t value)
+    int android_log_write_int64(android_log_context ctx, int64_t value)
+    int android_log_write_string8(android_log_context ctx, const char *value)
+    int android_log_write_string8_len(android_log_context ctx, const char *value, size_t maxlen)
+    int android_log_write_float32(android_log_context ctx, float value)
+
+    int android_log_write_list(android_log_context ctx, log_id_t id = LOG_ID_EVENTS)
+
+    android_log_context create_android_log_parser(const char *msg, size_t len)
+    android_log_list_element android_log_read_next(android_log_context ctx)
+    android_log_list_element android_log_peek_next(android_log_context ctx)
+
+    int android_log_destroy(android_log_context *ctx)
+
+Description
+-----------
+
+liblog represents an interface to the volatile Android Logging system for NDK (Native) applications
+and libraries.  Interfaces for either writing or reading logs.  The log buffers are divided up in
+Main, System, Radio and Events sub-logs.
+
+The logging interfaces are a series of macros, all of which can be overridden individually in order
+to control the verbosity of the application or library.  `[ASR]LOG[VDIWE]` calls are used to log to
+BAsic, System or Radio sub-logs in either the Verbose, Debug, Info, Warning or Error priorities.
+`[ASR]LOG[VDIWE]_IF` calls are used to perform thus based on a condition being true.
+`IF_ALOG[VDIWE]` calls are true if the current `LOG_TAG` is enabled at the specified priority.
+`LOG_ALWAYS_FATAL` is used to `ALOG` a message, then kill the process.  `LOG_FATAL` call is a
+variant of `LOG_ALWAYS_FATAL`, only enabled in engineering, and not release builds.  `ALOG_ASSERT`
+is used to `ALOG` a message if the condition is false; the condition is part of the logged message.
+`LOG_EVENT_(INT|LONG)` is used to drop binary content into the Events sub-log.
+
+The log reading interfaces permit opening the logs either singly or multiply, retrieving a log entry
+at a time in time sorted order, optionally limited to a specific pid and tail of the log(s) and
+finally a call closing the logs.  A single log can be opened with `android_logger_list_open()`; or
+multiple logs can be opened with `android_logger_list_alloc()`, calling in turn the
+`android_logger_open()` for each log id.  Each entry can be retrieved with
+`android_logger_list_read()`.  The log(s) can be closed with `android_logger_list_free()`.
+`ANDROID_LOG_NONBLOCK` mode will report when the log reading is done with an `EAGAIN` error return
+code, otherwise the `android_logger_list_read()` call will block for new entries.
+
+The `ANDROID_LOG_WRAP` mode flag to the `android_logger_list_alloc_time()` signals logd to quiesce
+the reader until the buffer is about to prune at the start time then proceed to dumping content.
+
+The `ANDROID_LOG_PSTORE` mode flag to the `android_logger_open()` is used to switch from the active
+logs to the persistent logs from before the last reboot.
+
+The value returned by `android_logger_open()` can be used as a parameter to the
+`android_logger_clear()` function to empty the sub-log.
+
+The value returned by `android_logger_open()` can be used as a parameter to the
+`android_logger_get_log_(size|readable_size|version)` to retrieve the sub-log maximum size, readable
+size and log buffer format protocol version respectively.  `android_logger_get_id()` returns the id
+that was used when opening the sub-log.
+
+Errors
+------
+
+If messages fail, a negative error code will be returned to the caller.
+
+The `-ENOTCONN` return code indicates that the logger daemon is stopped.
+
+The `-EBADF` return code indicates that the log access point can not be opened, or the log buffer id
+is out of range.
+
+For the `-EAGAIN` return code, this means that the logging message was temporarily backed-up either
+because of Denial Of Service (DOS) logging pressure from some chatty application or service in the
+Android system, or if too small of a value is set in /proc/sys/net/unix/max_dgram_qlen.  To aid in
+diagnosing the occurence of this, a binary event from liblog will be sent to the log daemon once a
+new message can get through indicating how many messages were dropped as a result.  Please take
+action to resolve the structural problems at the source.
+
+It is generally not advised for the caller to retry the `-EAGAIN` return code as this will only make
+the problem(s) worse and cause your application to temporarily drop to the logger daemon priority,
+BATCH scheduling policy and background task cgroup. If you require a group of messages to be passed
+atomically, merge them into one message with embedded newlines to the maximum length
+`LOGGER_ENTRY_MAX_PAYLOAD`.
+
+Other return codes from writing operation can be returned.  Since the library retries on `EINTR`,
+`-EINTR` should never be returned.
diff --git a/liblog/README.protocol.md b/liblog/README.protocol.md
new file mode 100644
index 0000000..f247b28
--- /dev/null
+++ b/liblog/README.protocol.md
@@ -0,0 +1,92 @@
+# liblog -> logd
+
+The data that liblog sends to logd is represented below.
+
+    struct {
+        android_log_header_t header;
+        union {
+           struct {
+                char     prio;
+                char     tag[...];
+                char     message[...];
+            } string;
+            struct {
+                android_event_header_t event_header;
+                android_event_*_t      payload[...];
+            } binary;
+        };
+    };
+
+where the embedded structs are defined as:
+
+    struct android_log_header_t {
+        uint8_t id;
+        uint16_t tid;
+        log_time realtime;
+    };
+
+    struct log_time {
+        uint32_t tv_sec = 0;
+        uint32_t tv_nsec = 0;
+    }
+
+    struct android_event_header_t {
+        int32_t tag;
+    };
+
+    struct android_event_list_t {
+        int8_t type;  // EVENT_TYPE_LIST
+        int8_t element_count;
+    };
+
+    struct android_event_float_t {
+        int8_t type;  // EVENT_TYPE_FLOAT
+        float data;
+    };
+
+    struct android_event_int_t {
+        int8_t type;   // EVENT_TYPE_INT
+        int32_t data;
+    } android_event_int_t;
+
+    struct android_event_long_t {
+        int8_t type;   // EVENT_TYPE_LONG
+        int64_t data;
+    };
+
+    struct android_event_string_t {
+        int8_t type;     // EVENT_TYPE_STRING;
+        int32_t length;
+        char data[];
+    };
+
+The payload, excluding the header, has a max size of LOGGER_ENTRY_MAX_PAYLOAD.
+
+## header
+
+The header is added immediately before sending the log message to logd.
+
+## `string` payload
+
+The `string` part of the union is for normal buffers (main, system, radio, etc) and consists of a
+single character priority, followed by a variable length null terminated string for the tag, and
+finally a variable length null terminated string for the message.
+
+This payload is used for the `__android_log_buf_write()` family of functions.
+
+## `binary` payload
+
+The `binary` part of the union is for binary buffers (events, security, etc) and consists of an
+android_event_header_t struct followed by a variable number of android_event_*_t
+(android_event_list_t, android_event_int_t, etc) structs.
+
+If multiple android_event_*_t elements are present, then they must be in a list and the first
+element in payload must be an android_event_list_t.
+
+This payload is used for the `__android_log_bwrite()` family of functions. It is additionally used
+for `android_log_write_list()` and the related functions that manipulate event lists.
+
+# logd -> liblog
+
+logd sends a `logger_entry` struct to liblog followed by the payload. The payload is identical to
+the payloads defined above. The max size of the entire message from logd is LOGGER_ENTRY_MAX_LEN.
diff --git a/liblog/event.logtags b/liblog/event.logtags
index 72ecab1..0a3b650 100644
--- a/liblog/event.logtags
+++ b/liblog/event.logtags
@@ -29,8 +29,9 @@
 # 4: Number of allocations
 # 5: Id
 # 6: Percent
+# s: Number of seconds (monotonic time)
 # Default value for data of type int/long is 2 (bytes).
 #
 # TODO: generate ".java" and ".h" files with integer constants from this file.
 
-1005  liblog (dropped|1)
+1006  liblog (dropped|1)
diff --git a/liblog/event_tag_map.c b/liblog/event_tag_map.c
deleted file mode 100644
index bea99aa..0000000
--- a/liblog/event_tag_map.c
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- * Copyright (C) 2007 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 <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mman.h>
-
-#include <log/event_tag_map.h>
-#include <log/log.h>
-
-#define OUT_TAG "EventTagMap"
-
-/*
- * Single entry.
- */
-typedef struct EventTag {
-    unsigned int    tagIndex;
-    const char*     tagStr;
-} EventTag;
-
-/*
- * Map.
- */
-struct EventTagMap {
-    /* memory-mapped source file; we get strings from here */
-    void*           mapAddr;
-    size_t          mapLen;
-
-    /* array of event tags, sorted numerically by tag index */
-    EventTag*       tagArray;
-    int             numTags;
-};
-
-/* fwd */
-static int processFile(EventTagMap* map);
-static int countMapLines(const EventTagMap* map);
-static int parseMapLines(EventTagMap* map);
-static int scanTagLine(char** pData, EventTag* tag, int lineNum);
-static int sortTags(EventTagMap* map);
-
-
-/*
- * Open the map file and allocate a structure to manage it.
- *
- * We create a private mapping because we want to terminate the log tag
- * strings with '\0'.
- */
-EventTagMap* android_openEventTagMap(const char* fileName)
-{
-    EventTagMap* newTagMap;
-    off_t end;
-    int fd = -1;
-
-    newTagMap = calloc(1, sizeof(EventTagMap));
-    if (newTagMap == NULL)
-        return NULL;
-
-    fd = open(fileName, O_RDONLY);
-    if (fd < 0) {
-        fprintf(stderr, "%s: unable to open map '%s': %s\n",
-            OUT_TAG, fileName, strerror(errno));
-        goto fail;
-    }
-
-    end = lseek(fd, 0L, SEEK_END);
-    (void) lseek(fd, 0L, SEEK_SET);
-    if (end < 0) {
-        fprintf(stderr, "%s: unable to seek map '%s'\n", OUT_TAG, fileName);
-        goto fail;
-    }
-
-    newTagMap->mapAddr = mmap(NULL, end, PROT_READ | PROT_WRITE, MAP_PRIVATE,
-                                fd, 0);
-    if (newTagMap->mapAddr == MAP_FAILED) {
-        fprintf(stderr, "%s: mmap(%s) failed: %s\n",
-            OUT_TAG, fileName, strerror(errno));
-        goto fail;
-    }
-    newTagMap->mapLen = end;
-
-    if (processFile(newTagMap) != 0)
-        goto fail;
-
-    return newTagMap;
-
-fail:
-    android_closeEventTagMap(newTagMap);
-    if (fd >= 0)
-        close(fd);
-    return NULL;
-}
-
-/*
- * Close the map.
- */
-void android_closeEventTagMap(EventTagMap* map)
-{
-    if (map == NULL)
-        return;
-
-    munmap(map->mapAddr, map->mapLen);
-    free(map);
-}
-
-/*
- * Look up an entry in the map.
- *
- * The entries are sorted by tag number, so we can do a binary search.
- */
-const char* android_lookupEventTag(const EventTagMap* map, int tag)
-{
-    int hi, lo, mid;
-
-    lo = 0;
-    hi = map->numTags-1;
-
-    while (lo <= hi) {
-        int cmp;
-
-        mid = (lo+hi)/2;
-        cmp = map->tagArray[mid].tagIndex - tag;
-        if (cmp < 0) {
-            /* tag is bigger */
-            lo = mid + 1;
-        } else if (cmp > 0) {
-            /* tag is smaller */
-            hi = mid - 1;
-        } else {
-            /* found */
-            return map->tagArray[mid].tagStr;
-        }
-    }
-
-    return NULL;
-}
-
-
-
-/*
- * Determine whether "c" is a whitespace char.
- */
-static inline int isCharWhitespace(char c)
-{
-    return (c == ' ' || c == '\n' || c == '\r' || c == '\t');
-}
-
-/*
- * Determine whether "c" is a valid tag char.
- */
-static inline int isCharValidTag(char c)
-{
-    return ((c >= 'A' && c <= 'Z') ||
-            (c >= 'a' && c <= 'z') ||
-            (c >= '0' && c <= '9') ||
-            (c == '_'));
-}
-
-/*
- * Determine whether "c" is a valid decimal digit.
- */
-static inline int isCharDigit(char c)
-{
-    return (c >= '0' && c <= '9');
-}
-
-
-/*
- * Crunch through the file, parsing the contents and creating a tag index.
- */
-static int processFile(EventTagMap* map)
-{
-    /* get a tag count */
-    map->numTags = countMapLines(map);
-    if (map->numTags < 0)
-        return -1;
-
-    //printf("+++ found %d tags\n", map->numTags);
-
-    /* allocate storage for the tag index array */
-    map->tagArray = calloc(1, sizeof(EventTag) * map->numTags);
-    if (map->tagArray == NULL)
-        return -1;
-
-    /* parse the file, null-terminating tag strings */
-    if (parseMapLines(map) != 0) {
-        fprintf(stderr, "%s: file parse failed\n", OUT_TAG);
-        return -1;
-    }
-
-    /* sort the tags and check for duplicates */
-    if (sortTags(map) != 0)
-        return -1;
-
-    return 0;
-}
-
-/*
- * Run through all lines in the file, determining whether they're blank,
- * comments, or possibly have a tag entry.
- *
- * This is a very "loose" scan.  We don't try to detect syntax errors here.
- * The later pass is more careful, but the number of tags found there must
- * match the number of tags found here.
- *
- * Returns the number of potential tag entries found.
- */
-static int countMapLines(const EventTagMap* map)
-{
-    int numTags, unknown;
-    const char* cp;
-    const char* endp;
-
-    cp = (const char*) map->mapAddr;
-    endp = cp + map->mapLen;
-
-    numTags = 0;
-    unknown = 1;
-    while (cp < endp) {
-        if (*cp == '\n') {
-            unknown = 1;
-        } else if (unknown) {
-            if (isCharDigit(*cp)) {
-                /* looks like a tag to me */
-                numTags++;
-                unknown = 0;
-            } else if (isCharWhitespace(*cp)) {
-                /* might be leading whitespace before tag num, keep going */
-            } else {
-                /* assume comment; second pass can complain in detail */
-                unknown = 0;
-            }
-        } else {
-            /* we've made up our mind; just scan to end of line */
-        }
-        cp++;
-    }
-
-    return numTags;
-}
-
-/*
- * Parse the tags out of the file.
- */
-static int parseMapLines(EventTagMap* map)
-{
-    int tagNum, lineStart, lineNum;
-    char* cp;
-    char* endp;
-
-    cp = (char*) map->mapAddr;
-    endp = cp + map->mapLen;
-
-    /* insist on EOL at EOF; simplifies parsing and null-termination */
-    if (*(endp-1) != '\n') {
-        fprintf(stderr, "%s: map file missing EOL on last line\n", OUT_TAG);
-        return -1;
-    }
-
-    tagNum = 0;
-    lineStart = 1;
-    lineNum = 1;
-    while (cp < endp) {
-        //printf("{%02x}", *cp); fflush(stdout);
-        if (*cp == '\n') {
-            lineStart = 1;
-            lineNum++;
-        } else if (lineStart) {
-            if (*cp == '#') {
-                /* comment; just scan to end */
-                lineStart = 0;
-            } else if (isCharDigit(*cp)) {
-                /* looks like a tag; scan it out */
-                if (tagNum >= map->numTags) {
-                    fprintf(stderr,
-                        "%s: more tags than expected (%d)\n", OUT_TAG, tagNum);
-                    return -1;
-                }
-                if (scanTagLine(&cp, &map->tagArray[tagNum], lineNum) != 0)
-                    return -1;
-                tagNum++;
-                lineNum++;      // we eat the '\n'
-                /* leave lineStart==1 */
-            } else if (isCharWhitespace(*cp)) {
-                /* looks like leading whitespace; keep scanning */
-            } else {
-                fprintf(stderr,
-                    "%s: unexpected chars (0x%02x) in tag number on line %d\n",
-                    OUT_TAG, *cp, lineNum);
-                return -1;
-            }
-        } else {
-            /* this is a blank or comment line */
-        }
-        cp++;
-    }
-
-    if (tagNum != map->numTags) {
-        fprintf(stderr, "%s: parsed %d tags, expected %d\n",
-            OUT_TAG, tagNum, map->numTags);
-        return -1;
-    }
-
-    return 0;
-}
-
-/*
- * Scan one tag line.
- *
- * "*pData" should be pointing to the first digit in the tag number.  On
- * successful return, it will be pointing to the last character in the
- * tag line (i.e. the character before the start of the next line).
- *
- * Returns 0 on success, nonzero on failure.
- */
-static int scanTagLine(char** pData, EventTag* tag, int lineNum)
-{
-    char* cp = *pData;
-    char* startp;
-    char* endp;
-    unsigned long val;
-
-    startp = cp;
-    while (isCharDigit(*++cp))
-        ;
-    *cp = '\0';
-
-    val = strtoul(startp, &endp, 10);
-    assert(endp == cp);
-    if (endp != cp)
-        fprintf(stderr, "ARRRRGH\n");
-
-    tag->tagIndex = val;
-
-    while (*++cp != '\n' && isCharWhitespace(*cp))
-        ;
-
-    if (*cp == '\n') {
-        fprintf(stderr,
-            "%s: missing tag string on line %d\n", OUT_TAG, lineNum);
-        return -1;
-    }
-
-    tag->tagStr = cp;
-
-    while (isCharValidTag(*++cp))
-        ;
-
-    if (*cp == '\n') {
-        /* null terminate and return */
-        *cp = '\0';
-    } else if (isCharWhitespace(*cp)) {
-        /* CRLF or trailin spaces; zap this char, then scan for the '\n' */
-        *cp = '\0';
-
-        /* just ignore the rest of the line till \n
-        TODO: read the tag description that follows the tag name
-        */
-        while (*++cp != '\n') {
-        }
-    } else {
-        fprintf(stderr,
-            "%s: invalid tag chars on line %d\n", OUT_TAG, lineNum);
-        return -1;
-    }
-
-    *pData = cp;
-
-    //printf("+++ Line %d: got %d '%s'\n", lineNum, tag->tagIndex, tag->tagStr);
-    return 0;
-}
-
-/*
- * Compare two EventTags.
- */
-static int compareEventTags(const void* v1, const void* v2)
-{
-    const EventTag* tag1 = (const EventTag*) v1;
-    const EventTag* tag2 = (const EventTag*) v2;
-
-    return tag1->tagIndex - tag2->tagIndex;
-}
-
-/*
- * Sort the EventTag array so we can do fast lookups by tag index.  After
- * the sort we do a quick check for duplicate tag indices.
- *
- * Returns 0 on success.
- */
-static int sortTags(EventTagMap* map)
-{
-    int i;
-
-    qsort(map->tagArray, map->numTags, sizeof(EventTag), compareEventTags);
-
-    for (i = 1; i < map->numTags; i++) {
-        if (map->tagArray[i].tagIndex == map->tagArray[i-1].tagIndex) {
-            fprintf(stderr, "%s: duplicate tag entries (%d:%s and %d:%s)\n",
-                OUT_TAG,
-                map->tagArray[i].tagIndex, map->tagArray[i].tagStr,
-                map->tagArray[i-1].tagIndex, map->tagArray[i-1].tagStr);
-            return -1;
-        }
-    }
-
-    return 0;
-}
diff --git a/liblog/event_tag_map.cpp b/liblog/event_tag_map.cpp
new file mode 100644
index 0000000..85556e8
--- /dev/null
+++ b/liblog/event_tag_map.cpp
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2007-2016 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 <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+
+#include <functional>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+
+#include <log/event_tag_map.h>
+#include <private/android_logger.h>
+#include <utils/FastStrcmp.h>
+#include <utils/RWLock.h>
+
+#define OUT_TAG "EventTagMap"
+
+typedef std::pair<std::string_view, std::string_view> TagFmt;
+
+// Map
+struct EventTagMap {
+#define NUM_MAPS 2
+  // memory-mapped source file; we get strings from here
+  void* mapAddr[NUM_MAPS];
+  size_t mapLen[NUM_MAPS];
+
+ private:
+  std::unordered_map<uint32_t, TagFmt> Idx2TagFmt;
+  std::unordered_map<std::string_view, uint32_t> Tag2Idx;
+  // protect unordered sets
+  android::RWLock rwlock;
+
+ public:
+  EventTagMap() {
+    memset(mapAddr, 0, sizeof(mapAddr));
+    memset(mapLen, 0, sizeof(mapLen));
+  }
+
+  ~EventTagMap() {
+    Idx2TagFmt.clear();
+    Tag2Idx.clear();
+    for (size_t which = 0; which < NUM_MAPS; ++which) {
+      if (mapAddr[which]) {
+        munmap(mapAddr[which], mapLen[which]);
+        mapAddr[which] = 0;
+      }
+    }
+  }
+
+  bool emplaceUnique(uint32_t tag, const TagFmt& tagfmt, bool verbose = false);
+  const TagFmt* find(uint32_t tag) const;
+  int find(std::string_view tag) const;
+};
+
+bool EventTagMap::emplaceUnique(uint32_t tag, const TagFmt& tagfmt,
+                                bool verbose) {
+  bool ret = true;
+  static const char errorFormat[] =
+      OUT_TAG ": duplicate tag entries %" PRIu32 ":%.*s:%.*s and %" PRIu32
+              ":%.*s:%.*s)\n";
+  android::RWLock::AutoWLock writeLock(rwlock);
+  {
+    auto it = Idx2TagFmt.find(tag);
+    if (it != Idx2TagFmt.end()) {
+      if (verbose) {
+        fprintf(stderr, errorFormat, it->first, (int)it->second.first.length(),
+                it->second.first.data(), (int)it->second.second.length(),
+                it->second.second.data(), tag, (int)tagfmt.first.length(),
+                tagfmt.first.data(), (int)tagfmt.second.length(),
+                tagfmt.second.data());
+      }
+      ret = false;
+    } else {
+      Idx2TagFmt.emplace(std::make_pair(tag, tagfmt));
+    }
+  }
+
+  {
+    auto it = Tag2Idx.find(tagfmt.first);
+    if (!tagfmt.second.length() && (it != Tag2Idx.end())) {
+      Tag2Idx.erase(it);
+      it = Tag2Idx.end();
+    }
+    if (it == Tag2Idx.end()) {
+      Tag2Idx.emplace(std::make_pair(tagfmt.first, tag));
+    }
+  }
+
+  return ret;
+}
+
+const TagFmt* EventTagMap::find(uint32_t tag) const {
+  android::RWLock::AutoRLock readLock(const_cast<android::RWLock&>(rwlock));
+  auto it = Idx2TagFmt.find(tag);
+  if (it == Idx2TagFmt.end()) return NULL;
+  return &(it->second);
+}
+
+int EventTagMap::find(std::string_view tag) const {
+  android::RWLock::AutoRLock readLock(const_cast<android::RWLock&>(rwlock));
+  auto it = Tag2Idx.find(std::move(tag));
+  if (it == Tag2Idx.end()) return -1;
+  return it->second;
+}
+
+// The position after the end of a valid section of the tag string,
+// caller makes sure delimited appropriately.
+static const char* endOfTag(const char* cp) {
+  while (*cp && (isalnum(*cp) || strchr("_.-@,", *cp))) ++cp;
+  return cp;
+}
+
+// Scan one tag line.
+//
+// "pData" should be pointing to the first digit in the tag number.  On
+// successful return, it will be pointing to the last character in the
+// tag line (i.e. the character before the start of the next line).
+//
+// Returns 0 on success, nonzero on failure.
+static int scanTagLine(EventTagMap* map, const char*& pData, int line_num) {
+  char* ep;
+  unsigned long val = strtoul(pData, &ep, 10);
+  const char* cp = ep;
+  if (cp == pData) {
+    fprintf(stderr, OUT_TAG ": malformed tag number on line %d\n", line_num);
+    errno = EINVAL;
+    return -1;
+  }
+
+  uint32_t tagIndex = val;
+  if (tagIndex != val) {
+    fprintf(stderr, OUT_TAG ": tag number too large on line %d\n", line_num);
+    errno = ERANGE;
+    return -1;
+  }
+
+  while ((*++cp != '\n') && isspace(*cp)) {
+  }
+
+  if (*cp == '\n') {
+    fprintf(stderr, OUT_TAG ": missing tag string on line %d\n", line_num);
+    errno = EINVAL;
+    return -1;
+  }
+
+  const char* tag = cp;
+  cp = endOfTag(cp);
+  size_t tagLen = cp - tag;
+
+  if (!isspace(*cp)) {
+    fprintf(stderr, OUT_TAG ": invalid tag char %c on line %d\n", *cp, line_num);
+    errno = EINVAL;
+    return -1;
+  }
+
+  while (isspace(*cp) && (*cp != '\n')) ++cp;
+  const char* fmt = NULL;
+  size_t fmtLen = 0;
+  if (*cp && (*cp != '#')) {
+    fmt = cp;
+    while (*cp && (*cp != '\n') && (*cp != '#')) ++cp;
+    while ((cp > fmt) && isspace(*(cp - 1))) --cp;
+    fmtLen = cp - fmt;
+  }
+
+  // KISS Only report identicals if they are global
+  // Ideally we want to check if there are identicals
+  // recorded for the same uid, but recording that
+  // unused detail in our database is too burdensome.
+  bool verbose = true;
+  while (*cp && (*cp != '#') && (*cp != '\n')) ++cp;
+  if (*cp == '#') {
+    do {
+      ++cp;
+    } while (isspace(*cp) && (*cp != '\n'));
+    verbose = !!fastcmp<strncmp>(cp, "uid=", strlen("uid="));
+  }
+
+  while (*cp && (*cp != '\n')) ++cp;
+#ifdef DEBUG
+  fprintf(stderr, "%d: %p: %.*s\n", line_num, tag, (int)(cp - pData), pData);
+#endif
+  pData = cp;
+
+  if (map->emplaceUnique(
+          tagIndex,
+          TagFmt(std::make_pair(std::string_view(tag, tagLen), std::string_view(fmt, fmtLen))),
+          verbose)) {
+    return 0;
+  }
+  errno = EMLINK;
+  return -1;
+}
+
+static const char* eventTagFiles[NUM_MAPS] = {
+  EVENT_TAG_MAP_FILE, "/dev/event-log-tags",
+};
+
+// Parse the tags out of the file.
+static int parseMapLines(EventTagMap* map, size_t which) {
+  const char* cp = static_cast<char*>(map->mapAddr[which]);
+  size_t len = map->mapLen[which];
+  const char* endp = cp + len;
+
+  // insist on EOL at EOF; simplifies parsing and null-termination
+  if (!len || (*(endp - 1) != '\n')) {
+#ifdef DEBUG
+    fprintf(stderr, OUT_TAG ": map file %zu[%zu] missing EOL on last line\n",
+            which, len);
+#endif
+    if (which) {  // do not propagate errors for other files
+      return 0;
+    }
+    errno = EINVAL;
+    return -1;
+  }
+
+  bool lineStart = true;
+  int lineNum = 1;
+  while (cp < endp) {
+    if (*cp == '\n') {
+      lineStart = true;
+      lineNum++;
+    } else if (lineStart) {
+      if (*cp == '#') {
+        // comment; just scan to end
+        lineStart = false;
+      } else if (isdigit(*cp)) {
+        // looks like a tag; scan it out
+        if (scanTagLine(map, cp, lineNum) != 0) {
+          if (!which || (errno != EMLINK)) {
+            return -1;
+          }
+        }
+        lineNum++;  // we eat the '\n'
+                    // leave lineStart==true
+      } else if (isspace(*cp)) {
+        // looks like leading whitespace; keep scanning
+      } else {
+        fprintf(stderr,
+                OUT_TAG
+                ": unexpected chars (0x%02x) in tag number on line %d\n",
+                *cp, lineNum);
+        errno = EINVAL;
+        return -1;
+      }
+    } else {
+      // this is a blank or comment line
+    }
+    cp++;
+  }
+
+  return 0;
+}
+
+// Open the map file and allocate a structure to manage it.
+//
+// We create a private mapping because we want to terminate the log tag
+// strings with '\0'.
+EventTagMap* android_openEventTagMap(const char* fileName) {
+  EventTagMap* newTagMap;
+  off_t end[NUM_MAPS];
+  int save_errno, fd[NUM_MAPS];
+  size_t which;
+
+  memset(fd, -1, sizeof(fd));
+  memset(end, 0, sizeof(end));
+
+  for (which = 0; which < NUM_MAPS; ++which) {
+    const char* tagfile = fileName ? fileName : eventTagFiles[which];
+
+    fd[which] = open(tagfile, O_RDONLY | O_CLOEXEC);
+    if (fd[which] < 0) {
+      if (!which) {
+        save_errno = errno;
+        fprintf(stderr, OUT_TAG ": unable to open map '%s': %s\n", tagfile,
+                strerror(save_errno));
+        goto fail_errno;
+      }
+      continue;
+    }
+    end[which] = lseek(fd[which], 0L, SEEK_END);
+    save_errno = errno;
+    (void)lseek(fd[which], 0L, SEEK_SET);
+    if (!which && (end[0] < 0)) {
+      fprintf(stderr, OUT_TAG ": unable to seek map '%s' %s\n", tagfile,
+              strerror(save_errno));
+      goto fail_close;
+    }
+    if (fileName) break;  // Only allow one as specified
+  }
+
+  newTagMap = new EventTagMap;
+  if (newTagMap == NULL) {
+    save_errno = errno;
+    goto fail_close;
+  }
+
+  for (which = 0; which < NUM_MAPS; ++which) {
+    if (fd[which] >= 0) {
+      newTagMap->mapAddr[which] =
+          mmap(NULL, end[which], which ? PROT_READ : PROT_READ | PROT_WRITE,
+               which ? MAP_SHARED : MAP_PRIVATE, fd[which], 0);
+      save_errno = errno;
+      close(fd[which]); /* fd DONE */
+      fd[which] = -1;
+      if ((newTagMap->mapAddr[which] != MAP_FAILED) &&
+          (newTagMap->mapAddr[which] != NULL)) {
+        newTagMap->mapLen[which] = end[which];
+      } else if (!which) {
+        const char* tagfile = fileName ? fileName : eventTagFiles[which];
+
+        fprintf(stderr, OUT_TAG ": mmap(%s) failed: %s\n", tagfile,
+                strerror(save_errno));
+        goto fail_unmap;
+      }
+    }
+  }
+
+  for (which = 0; which < NUM_MAPS; ++which) {
+    if (parseMapLines(newTagMap, which) != 0) {
+      delete newTagMap;
+      return NULL;
+    }
+    /* See 'fd DONE' comments above and below, no need to clean up here */
+  }
+
+  return newTagMap;
+
+fail_unmap:
+  save_errno = EINVAL;
+  delete newTagMap;
+fail_close:
+  for (which = 0; which < NUM_MAPS; ++which) close(fd[which]); /* fd DONE */
+fail_errno:
+  errno = save_errno;
+  return NULL;
+}
+
+// Close the map.
+void android_closeEventTagMap(EventTagMap* map) {
+  if (map) delete map;
+}
+
+// Look up an entry in the map.
+const char* android_lookupEventTag_len(const EventTagMap* map, size_t* len, unsigned int tag) {
+  if (len) *len = 0;
+  const TagFmt* str = map->find(tag);
+  if (!str) return NULL;
+  if (len) *len = str->first.length();
+  return str->first.data();
+}
+
+// Look up an entry in the map.
+const char* android_lookupEventFormat_len(const EventTagMap* map, size_t* len, unsigned int tag) {
+  if (len) *len = 0;
+  const TagFmt* str = map->find(tag);
+  if (!str) return NULL;
+  if (len) *len = str->second.length();
+  return str->second.data();
+}
+
diff --git a/liblog/fake_log_device.c b/liblog/fake_log_device.c
deleted file mode 100644
index 8a8ece2..0000000
--- a/liblog/fake_log_device.c
+++ /dev/null
@@ -1,697 +0,0 @@
-/*
- * Copyright (C) 2008-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.
- */
-/*
- * Intercepts log messages intended for the Android log device.
- * When running in the context of the simulator, the messages are
- * passed on to the underlying (fake) log device.  When not in the
- * simulator, messages are printed to stderr.
- */
-#include "fake_log_device.h"
-
-#include <ctype.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <log/logd.h>
-
-#if !defined(_WIN32)
-#include <pthread.h>
-#endif
-
-#ifndef __unused
-#define __unused __attribute__((__unused__))
-#endif
-
-#define kMaxTagLen  16      /* from the long-dead utils/Log.cpp */
-
-#define kTagSetSize 16      /* arbitrary */
-
-#if 0
-#define TRACE(...) printf("fake_log_device: " __VA_ARGS__)
-#else
-#define TRACE(...) ((void)0)
-#endif
-
-/* from the long-dead utils/Log.cpp */
-typedef enum {
-    FORMAT_OFF = 0,
-    FORMAT_BRIEF,
-    FORMAT_PROCESS,
-    FORMAT_TAG,
-    FORMAT_THREAD,
-    FORMAT_RAW,
-    FORMAT_TIME,
-    FORMAT_THREADTIME,
-    FORMAT_LONG
-} LogFormat;
-
-
-/*
- * Log driver state.
- */
-typedef struct LogState {
-    /* the fake fd that's seen by the user */
-    int     fakeFd;
-
-    /* a printable name for this fake device */
-    char   *debugName;
-
-    /* nonzero if this is a binary log */
-    int     isBinary;
-
-    /* global minimum priority */
-    int     globalMinPriority;
-
-    /* output format */
-    LogFormat outputFormat;
-
-    /* tags and priorities */
-    struct {
-        char    tag[kMaxTagLen];
-        int     minPriority;
-    } tagSet[kTagSetSize];
-} LogState;
-
-
-#if !defined(_WIN32)
-/*
- * Locking.  Since we're emulating a device, we need to be prepared
- * to have multiple callers at the same time.  This lock is used
- * to both protect the fd list and to prevent LogStates from being
- * freed out from under a user.
- */
-static pthread_mutex_t fakeLogDeviceLock = PTHREAD_MUTEX_INITIALIZER;
-
-static void lock()
-{
-    pthread_mutex_lock(&fakeLogDeviceLock);
-}
-
-static void unlock()
-{
-    pthread_mutex_unlock(&fakeLogDeviceLock);
-}
-#else   // !defined(_WIN32)
-#define lock() ((void)0)
-#define unlock() ((void)0)
-#endif  // !defined(_WIN32)
-
-
-/*
- * File descriptor management.
- */
-#define FAKE_FD_BASE 10000
-#define MAX_OPEN_LOGS 16
-static LogState *openLogTable[MAX_OPEN_LOGS];
-
-/*
- * Allocate an fd and associate a new LogState with it.
- * The fd is available via the fakeFd field of the return value.
- */
-static LogState *createLogState()
-{
-    size_t i;
-
-    for (i = 0; i < sizeof(openLogTable); i++) {
-        if (openLogTable[i] == NULL) {
-            openLogTable[i] = calloc(1, sizeof(LogState));
-            openLogTable[i]->fakeFd = FAKE_FD_BASE + i;
-            return openLogTable[i];
-        }
-    }
-    return NULL;
-}
-
-/*
- * Translate an fd to a LogState.
- */
-static LogState *fdToLogState(int fd)
-{
-    if (fd >= FAKE_FD_BASE && fd < FAKE_FD_BASE + MAX_OPEN_LOGS) {
-        return openLogTable[fd - FAKE_FD_BASE];
-    }
-    return NULL;
-}
-
-/*
- * Unregister the fake fd and free the memory it pointed to.
- */
-static void deleteFakeFd(int fd)
-{
-    LogState *ls;
-
-    lock();
-
-    ls = fdToLogState(fd);
-    if (ls != NULL) {
-        openLogTable[fd - FAKE_FD_BASE] = NULL;
-        free(ls->debugName);
-        free(ls);
-    }
-
-    unlock();
-}
-
-/*
- * Configure logging based on ANDROID_LOG_TAGS environment variable.  We
- * need to parse a string that looks like
- *
- *   *:v jdwp:d dalvikvm:d dalvikvm-gc:i dalvikvmi:i
- *
- * The tag (or '*' for the global level) comes first, followed by a colon
- * and a letter indicating the minimum priority level we're expected to log.
- * This can be used to reveal or conceal logs with specific tags.
- *
- * We also want to check ANDROID_PRINTF_LOG to determine how the output
- * will look.
- */
-static void configureInitialState(const char* pathName, LogState* logState)
-{
-    static const int kDevLogLen = sizeof("/dev/log/") - 1;
-
-    logState->debugName = strdup(pathName);
-
-    /* identify binary logs */
-    if (strcmp(pathName + kDevLogLen, "events") == 0) {
-        logState->isBinary = 1;
-    }
-
-    /* global min priority defaults to "info" level */
-    logState->globalMinPriority = ANDROID_LOG_INFO;
-
-    /*
-     * This is based on the the long-dead utils/Log.cpp code.
-     */
-    const char* tags = getenv("ANDROID_LOG_TAGS");
-    TRACE("Found ANDROID_LOG_TAGS='%s'\n", tags);
-    if (tags != NULL) {
-        int entry = 0;
-
-        while (*tags != '\0') {
-            char tagName[kMaxTagLen];
-            int i, minPrio;
-
-            while (isspace(*tags))
-                tags++;
-
-            i = 0;
-            while (*tags != '\0' && !isspace(*tags) && *tags != ':' &&
-                i < kMaxTagLen)
-            {
-                tagName[i++] = *tags++;
-            }
-            if (i == kMaxTagLen) {
-                TRACE("ERROR: env tag too long (%d chars max)\n", kMaxTagLen-1);
-                return;
-            }
-            tagName[i] = '\0';
-
-            /* default priority, if there's no ":" part; also zero out '*' */
-            minPrio = ANDROID_LOG_VERBOSE;
-            if (tagName[0] == '*' && tagName[1] == '\0') {
-                minPrio = ANDROID_LOG_DEBUG;
-                tagName[0] = '\0';
-            }
-
-            if (*tags == ':') {
-                tags++;
-                if (*tags >= '0' && *tags <= '9') {
-                    if (*tags >= ('0' + ANDROID_LOG_SILENT))
-                        minPrio = ANDROID_LOG_VERBOSE;
-                    else
-                        minPrio = *tags - '\0';
-                } else {
-                    switch (*tags) {
-                    case 'v':   minPrio = ANDROID_LOG_VERBOSE;  break;
-                    case 'd':   minPrio = ANDROID_LOG_DEBUG;    break;
-                    case 'i':   minPrio = ANDROID_LOG_INFO;     break;
-                    case 'w':   minPrio = ANDROID_LOG_WARN;     break;
-                    case 'e':   minPrio = ANDROID_LOG_ERROR;    break;
-                    case 'f':   minPrio = ANDROID_LOG_FATAL;    break;
-                    case 's':   minPrio = ANDROID_LOG_SILENT;   break;
-                    default:    minPrio = ANDROID_LOG_DEFAULT;  break;
-                    }
-                }
-
-                tags++;
-                if (*tags != '\0' && !isspace(*tags)) {
-                    TRACE("ERROR: garbage in tag env; expected whitespace\n");
-                    TRACE("       env='%s'\n", tags);
-                    return;
-                }
-            }
-
-            if (tagName[0] == 0) {
-                logState->globalMinPriority = minPrio;
-                TRACE("+++ global min prio %d\n", logState->globalMinPriority);
-            } else {
-                logState->tagSet[entry].minPriority = minPrio;
-                strcpy(logState->tagSet[entry].tag, tagName);
-                TRACE("+++ entry %d: %s:%d\n",
-                    entry,
-                    logState->tagSet[entry].tag,
-                    logState->tagSet[entry].minPriority);
-                entry++;
-            }
-        }
-    }
-
-
-    /*
-     * Taken from the long-dead utils/Log.cpp
-     */
-    const char* fstr = getenv("ANDROID_PRINTF_LOG");
-    LogFormat format;
-    if (fstr == NULL) {
-        format = FORMAT_BRIEF;
-    } else {
-        if (strcmp(fstr, "brief") == 0)
-            format = FORMAT_BRIEF;
-        else if (strcmp(fstr, "process") == 0)
-            format = FORMAT_PROCESS;
-        else if (strcmp(fstr, "tag") == 0)
-            format = FORMAT_PROCESS;
-        else if (strcmp(fstr, "thread") == 0)
-            format = FORMAT_PROCESS;
-        else if (strcmp(fstr, "raw") == 0)
-            format = FORMAT_PROCESS;
-        else if (strcmp(fstr, "time") == 0)
-            format = FORMAT_PROCESS;
-        else if (strcmp(fstr, "long") == 0)
-            format = FORMAT_PROCESS;
-        else
-            format = (LogFormat) atoi(fstr);        // really?!
-    }
-
-    logState->outputFormat = format;
-}
-
-/*
- * Return a human-readable string for the priority level.  Always returns
- * a valid string.
- */
-static const char* getPriorityString(int priority)
-{
-    /* the first character of each string should be unique */
-    static const char* priorityStrings[] = {
-        "Verbose", "Debug", "Info", "Warn", "Error", "Assert"
-    };
-    int idx;
-
-    idx = (int) priority - (int) ANDROID_LOG_VERBOSE;
-    if (idx < 0 ||
-        idx >= (int) (sizeof(priorityStrings) / sizeof(priorityStrings[0])))
-        return "?unknown?";
-    return priorityStrings[idx];
-}
-
-#if defined(_WIN32)
-/*
- * WIN32 does not have writev().
- * Make up something to replace it.
- */
-static ssize_t fake_writev(int fd, const struct iovec *iov, int iovcnt) {
-    ssize_t result = 0;
-    const struct iovec* end = iov + iovcnt;
-    for (; iov < end; iov++) {
-        ssize_t w = write(fd, iov->iov_base, iov->iov_len);
-        if (w != (ssize_t) iov->iov_len) {
-            if (w < 0)
-                return w;
-            return result + w;
-        }
-        result += w;
-    }
-    return result;
-}
-
-#define writev fake_writev
-#endif
-
-
-/*
- * Write a filtered log message to stderr.
- *
- * Log format parsing taken from the long-dead utils/Log.cpp.
- */
-static void showLog(LogState *state,
-        int logPrio, const char* tag, const char* msg)
-{
-#if !defined(_WIN32)
-    struct tm tmBuf;
-#endif
-    struct tm* ptm;
-    char timeBuf[32];
-    char prefixBuf[128], suffixBuf[128];
-    char priChar;
-    time_t when;
-    pid_t pid, tid;
-
-    TRACE("LOG %d: %s %s", logPrio, tag, msg);
-
-    priChar = getPriorityString(logPrio)[0];
-    when = time(NULL);
-    pid = tid = getpid();       // find gettid()?
-
-    /*
-     * Get the current date/time in pretty form
-     *
-     * It's often useful when examining a log with "less" to jump to
-     * a specific point in the file by searching for the date/time stamp.
-     * For this reason it's very annoying to have regexp meta characters
-     * in the time stamp.  Don't use forward slashes, parenthesis,
-     * brackets, asterisks, or other special chars here.
-     */
-#if !defined(_WIN32)
-    ptm = localtime_r(&when, &tmBuf);
-#else
-    ptm = localtime(&when);
-#endif
-    //strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", ptm);
-    strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
-
-    /*
-     * Construct a buffer containing the log header and log message.
-     */
-    size_t prefixLen, suffixLen;
-
-    switch (state->outputFormat) {
-    case FORMAT_TAG:
-        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
-            "%c/%-8s: ", priChar, tag);
-        strcpy(suffixBuf, "\n"); suffixLen = 1;
-        break;
-    case FORMAT_PROCESS:
-        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
-            "%c(%5d) ", priChar, pid);
-        suffixLen = snprintf(suffixBuf, sizeof(suffixBuf),
-            "  (%s)\n", tag);
-        break;
-    case FORMAT_THREAD:
-        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
-            "%c(%5d:%5d) ", priChar, pid, tid);
-        strcpy(suffixBuf, "\n"); suffixLen = 1;
-        break;
-    case FORMAT_RAW:
-        prefixBuf[0] = 0; prefixLen = 0;
-        strcpy(suffixBuf, "\n"); suffixLen = 1;
-        break;
-    case FORMAT_TIME:
-        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
-            "%s %-8s\n\t", timeBuf, tag);
-        strcpy(suffixBuf, "\n"); suffixLen = 1;
-        break;
-    case FORMAT_THREADTIME:
-        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
-            "%s %5d %5d %c %-8s \n\t", timeBuf, pid, tid, priChar, tag);
-        strcpy(suffixBuf, "\n"); suffixLen = 1;
-        break;
-    case FORMAT_LONG:
-        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
-            "[ %s %5d:%5d %c/%-8s ]\n",
-            timeBuf, pid, tid, priChar, tag);
-        strcpy(suffixBuf, "\n\n"); suffixLen = 2;
-        break;
-    default:
-        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
-            "%c/%-8s(%5d): ", priChar, tag, pid);
-        strcpy(suffixBuf, "\n"); suffixLen = 1;
-        break;
-     }
-
-    /*
-     * Figure out how many lines there will be.
-     */
-    const char* end = msg + strlen(msg);
-    size_t numLines = 0;
-    const char* p = msg;
-    while (p < end) {
-        if (*p++ == '\n') numLines++;
-    }
-    if (p > msg && *(p-1) != '\n') numLines++;
-
-    /*
-     * Create an array of iovecs large enough to write all of
-     * the lines with a prefix and a suffix.
-     */
-    const size_t INLINE_VECS = 6;
-    const size_t MAX_LINES   = ((size_t)~0)/(3*sizeof(struct iovec*));
-    struct iovec stackVec[INLINE_VECS];
-    struct iovec* vec = stackVec;
-    size_t numVecs;
-
-    if (numLines > MAX_LINES)
-        numLines = MAX_LINES;
-
-    numVecs = numLines*3;  // 3 iovecs per line.
-    if (numVecs > INLINE_VECS) {
-        vec = (struct iovec*)malloc(sizeof(struct iovec)*numVecs);
-        if (vec == NULL) {
-            msg = "LOG: write failed, no memory";
-            numVecs = 3;
-            numLines = 1;
-            vec = stackVec;
-        }
-    }
-
-    /*
-     * Fill in the iovec pointers.
-     */
-    p = msg;
-    struct iovec* v = vec;
-    int totalLen = 0;
-    while (numLines > 0 && p < end) {
-        if (prefixLen > 0) {
-            v->iov_base = prefixBuf;
-            v->iov_len = prefixLen;
-            totalLen += prefixLen;
-            v++;
-        }
-        const char* start = p;
-        while (p < end && *p != '\n') p++;
-        if ((p-start) > 0) {
-            v->iov_base = (void*)start;
-            v->iov_len = p-start;
-            totalLen += p-start;
-            v++;
-        }
-        if (*p == '\n') p++;
-        if (suffixLen > 0) {
-            v->iov_base = suffixBuf;
-            v->iov_len = suffixLen;
-            totalLen += suffixLen;
-            v++;
-        }
-        numLines -= 1;
-    }
-    
-    /*
-     * Write the entire message to the log file with a single writev() call.
-     * We need to use this rather than a collection of printf()s on a FILE*
-     * because of multi-threading and multi-process issues.
-     *
-     * If the file was not opened with O_APPEND, this will produce interleaved
-     * output when called on the same file from multiple processes.
-     *
-     * If the file descriptor is actually a network socket, the writev()
-     * call may return with a partial write.  Putting the writev() call in
-     * a loop can result in interleaved data.  This can be alleviated
-     * somewhat by wrapping the writev call in the Mutex.
-     */
-
-    for(;;) {
-        int cc = writev(fileno(stderr), vec, v-vec);
-
-        if (cc == totalLen) break;
-        
-        if (cc < 0) {
-            if(errno == EINTR) continue;
-            
-                /* can't really log the failure; for now, throw out a stderr */
-            fprintf(stderr, "+++ LOG: write failed (errno=%d)\n", errno);
-            break;
-        } else {
-                /* shouldn't happen when writing to file or tty */
-            fprintf(stderr, "+++ LOG: write partial (%d of %d)\n", cc, totalLen);
-            break;
-        }
-    }
-
-    /* if we allocated storage for the iovecs, free it */
-    if (vec != stackVec)
-        free(vec);
-}
-
-
-/*
- * Receive a log message.  We happen to know that "vector" has three parts:
- *
- *  priority (1 byte)
- *  tag (N bytes -- null-terminated ASCII string)
- *  message (N bytes -- null-terminated ASCII string)
- */
-static ssize_t logWritev(int fd, const struct iovec* vector, int count)
-{
-    LogState* state;
-
-    /* Make sure that no-one frees the LogState while we're using it.
-     * Also guarantees that only one thread is in showLog() at a given
-     * time (if it matters).
-     */
-    lock();
-
-    state = fdToLogState(fd);
-    if (state == NULL) {
-        errno = EBADF;
-        goto error;
-    }
-
-    if (state->isBinary) {
-        TRACE("%s: ignoring binary log\n", state->debugName);
-        goto bail;
-    }
-
-    if (count != 3) {
-        TRACE("%s: writevLog with count=%d not expected\n",
-            state->debugName, count);
-        goto error;
-    }
-
-    /* pull out the three fields */
-    int logPrio = *(const char*)vector[0].iov_base;
-    const char* tag = (const char*) vector[1].iov_base;
-    const char* msg = (const char*) vector[2].iov_base;
-
-    /* see if this log tag is configured */
-    int i;
-    int minPrio = state->globalMinPriority;
-    for (i = 0; i < kTagSetSize; i++) {
-        if (state->tagSet[i].minPriority == ANDROID_LOG_UNKNOWN)
-            break;      /* reached end of configured values */
-
-        if (strcmp(state->tagSet[i].tag, tag) == 0) {
-            //TRACE("MATCH tag '%s'\n", tag);
-            minPrio = state->tagSet[i].minPriority;
-            break;
-        }
-    }
-
-    if (logPrio >= minPrio) {
-        showLog(state, logPrio, tag, msg);
-    } else {
-        //TRACE("+++ NOLOG(%d): %s %s", logPrio, tag, msg);
-    }
-
-bail:
-    unlock();
-    return vector[0].iov_len + vector[1].iov_len + vector[2].iov_len;
-error:
-    unlock();
-    return -1;
-}
-
-/*
- * Free up our state and close the fake descriptor.
- */
-static int logClose(int fd)
-{
-    deleteFakeFd(fd);
-    return 0;
-}
-
-/*
- * Open a log output device and return a fake fd.
- */
-static int logOpen(const char* pathName, int flags __unused)
-{
-    LogState *logState;
-    int fd = -1;
-
-    lock();
-
-    logState = createLogState();
-    if (logState != NULL) {
-        configureInitialState(pathName, logState);
-        fd = logState->fakeFd;
-    } else  {
-        errno = ENFILE;
-    }
-
-    unlock();
-
-    return fd;
-}
-
-
-/*
- * Runtime redirection.  If this binary is running in the simulator,
- * just pass log messages to the emulated device.  If it's running
- * outside of the simulator, write the log messages to stderr.
- */
-
-static int (*redirectOpen)(const char *pathName, int flags) = NULL;
-static int (*redirectClose)(int fd) = NULL;
-static ssize_t (*redirectWritev)(int fd, const struct iovec* vector, int count)
-        = NULL;
-
-static void setRedirects()
-{
-    const char *ws;
-
-    /* Wrapsim sets this environment variable on children that it's
-     * created using its LD_PRELOAD wrapper.
-     */
-    ws = getenv("ANDROID_WRAPSIM");
-    if (ws != NULL && strcmp(ws, "1") == 0) {
-        /* We're running inside wrapsim, so we can just write to the device. */
-        redirectOpen = (int (*)(const char *pathName, int flags))open;
-        redirectClose = close;
-        redirectWritev = writev;
-    } else {
-        /* There's no device to delegate to; handle the logging ourselves. */
-        redirectOpen = logOpen;
-        redirectClose = logClose;
-        redirectWritev = logWritev;
-    }
-}
-
-int fakeLogOpen(const char *pathName, int flags)
-{
-    if (redirectOpen == NULL) {
-        setRedirects();
-    }
-    return redirectOpen(pathName, flags);
-}
-
-int fakeLogClose(int fd)
-{
-    /* Assume that open() was called first. */
-    return redirectClose(fd);
-}
-
-ssize_t fakeLogWritev(int fd, const struct iovec* vector, int count)
-{
-    /* Assume that open() was called first. */
-    return redirectWritev(fd, vector, count);
-}
-
-int __android_log_is_loggable(int prio, const char *tag __unused, int def)
-{
-    int logLevel = def;
-    return logLevel >= 0 && prio >= logLevel;
-}
diff --git a/liblog/include/android/log.h b/liblog/include/android/log.h
new file mode 100644
index 0000000..8a0ebf2
--- /dev/null
+++ b/liblog/include/android/log.h
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+#pragma once
+
+/**
+ * @addtogroup Logging
+ * @{
+ */
+
+/**
+ * \file
+ *
+ * Support routines to send messages to the Android log buffer,
+ * which can later be accessed through the `logcat` utility.
+ *
+ * Each log message must have
+ *   - a priority
+ *   - a log tag
+ *   - some text
+ *
+ * The tag normally corresponds to the component that emits the log message,
+ * and should be reasonably small.
+ *
+ * Log message text may be truncated to less than an implementation-specific
+ * limit (1023 bytes).
+ *
+ * Note that a newline character ("\n") will be appended automatically to your
+ * log message, if not already there. It is not possible to send several
+ * messages and have them appear on a single line in logcat.
+ *
+ * Please use logging in moderation:
+ *
+ *  - Sending log messages eats CPU and slow down your application and the
+ *    system.
+ *
+ *  - The circular log buffer is pretty small, so sending many messages
+ *    will hide other important log messages.
+ *
+ *  - In release builds, only send log messages to account for exceptional
+ *    conditions.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/cdefs.h>
+
+#if !defined(__BIONIC__) && !defined(__INTRODUCED_IN)
+#define __INTRODUCED_IN(x)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Android log priority values, in increasing order of priority.
+ */
+typedef enum android_LogPriority {
+  /** For internal use only.  */
+  ANDROID_LOG_UNKNOWN = 0,
+  /** The default priority, for internal use only.  */
+  ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
+  /** Verbose logging. Should typically be disabled for a release apk. */
+  ANDROID_LOG_VERBOSE,
+  /** Debug logging. Should typically be disabled for a release apk. */
+  ANDROID_LOG_DEBUG,
+  /** Informational logging. Should typically be disabled for a release apk. */
+  ANDROID_LOG_INFO,
+  /** Warning logging. For use with recoverable failures. */
+  ANDROID_LOG_WARN,
+  /** Error logging. For use with unrecoverable failures. */
+  ANDROID_LOG_ERROR,
+  /** Fatal logging. For use when aborting. */
+  ANDROID_LOG_FATAL,
+  /** For internal use only.  */
+  ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
+} android_LogPriority;
+
+/**
+ * Writes the constant string `text` to the log, with priority `prio` and tag
+ * `tag`.
+ */
+int __android_log_write(int prio, const char* tag, const char* text);
+
+/**
+ * Writes a formatted string to the log, with priority `prio` and tag `tag`.
+ * The details of formatting are the same as for
+ * [printf(3)](http://man7.org/linux/man-pages/man3/printf.3.html).
+ */
+int __android_log_print(int prio, const char* tag, const char* fmt, ...)
+    __attribute__((__format__(printf, 3, 4)));
+
+/**
+ * Equivalent to `__android_log_print`, but taking a `va_list`.
+ * (If `__android_log_print` is like `printf`, this is like `vprintf`.)
+ */
+int __android_log_vprint(int prio, const char* tag, const char* fmt, va_list ap)
+    __attribute__((__format__(printf, 3, 0)));
+
+/**
+ * Writes an assertion failure to the log (as `ANDROID_LOG_FATAL`) and to
+ * stderr, before calling
+ * [abort(3)](http://man7.org/linux/man-pages/man3/abort.3.html).
+ *
+ * If `fmt` is non-null, `cond` is unused. If `fmt` is null, the string
+ * `Assertion failed: %s` is used with `cond` as the string argument.
+ * If both `fmt` and `cond` are null, a default string is provided.
+ *
+ * Most callers should use
+ * [assert(3)](http://man7.org/linux/man-pages/man3/assert.3.html) from
+ * `&lt;assert.h&gt;` instead, or the `__assert` and `__assert2` functions
+ * provided by bionic if more control is needed. They support automatically
+ * including the source filename and line number more conveniently than this
+ * function.
+ */
+void __android_log_assert(const char* cond, const char* tag, const char* fmt, ...)
+    __attribute__((__noreturn__)) __attribute__((__format__(printf, 3, 4)));
+
+/**
+ * Identifies a specific log buffer for __android_log_buf_write()
+ * and __android_log_buf_print().
+ */
+typedef enum log_id {
+  LOG_ID_MIN = 0,
+
+  /** The main log buffer. This is the only log buffer available to apps. */
+  LOG_ID_MAIN = 0,
+  /** The radio log buffer. */
+  LOG_ID_RADIO = 1,
+  /** The event log buffer. */
+  LOG_ID_EVENTS = 2,
+  /** The system log buffer. */
+  LOG_ID_SYSTEM = 3,
+  /** The crash log buffer. */
+  LOG_ID_CRASH = 4,
+  /** The statistics log buffer. */
+  LOG_ID_STATS = 5,
+  /** The security log buffer. */
+  LOG_ID_SECURITY = 6,
+  /** The kernel log buffer. */
+  LOG_ID_KERNEL = 7,
+
+  LOG_ID_MAX,
+
+  /** Let the logging function choose the best log target. */
+  LOG_ID_DEFAULT = 0x7FFFFFFF
+} log_id_t;
+
+/**
+ * Writes the constant string `text` to the log buffer `id`,
+ * with priority `prio` and tag `tag`.
+ *
+ * Apps should use __android_log_write() instead.
+ */
+int __android_log_buf_write(int bufID, int prio, const char* tag, const char* text);
+
+/**
+ * Writes a formatted string to log buffer `id`,
+ * with priority `prio` and tag `tag`.
+ * The details of formatting are the same as for
+ * [printf(3)](http://man7.org/linux/man-pages/man3/printf.3.html).
+ *
+ * Apps should use __android_log_print() instead.
+ */
+int __android_log_buf_print(int bufID, int prio, const char* tag, const char* fmt, ...)
+    __attribute__((__format__(printf, 4, 5)));
+
+/**
+ * Logger data struct used for writing log messages to liblog via __android_log_write_logger_data()
+ * and sending log messages to user defined loggers specified in __android_log_set_logger().
+ */
+struct __android_log_message {
+  /** Must be set to sizeof(__android_log_message) and is used for versioning. */
+  size_t struct_size;
+
+  /** {@link log_id_t} values. */
+  int32_t buffer_id;
+
+  /** {@link android_LogPriority} values. */
+  int32_t priority;
+
+  /** The tag for the log message. */
+  const char* tag;
+
+  /** Optional file name, may be set to nullptr. */
+  const char* file;
+
+  /** Optional line number, ignore if file is nullptr. */
+  uint32_t line;
+
+  /** The log message itself. */
+  const char* message;
+};
+
+/**
+ * Prototype for the 'logger' function that is called for every log message.
+ */
+typedef void (*__android_logger_function)(const struct __android_log_message* log_message);
+/**
+ * Prototype for the 'abort' function that is called when liblog will abort due to
+ * __android_log_assert() failures.
+ */
+typedef void (*__android_aborter_function)(const char* abort_message);
+
+#if !defined(__ANDROID__) || __ANDROID_API__ >= 30
+/**
+ * Writes the log message specified by log_message.  log_message includes additional file name and
+ * line number information that a logger may use.  log_message is versioned for backwards
+ * compatibility.
+ * This assumes that loggability has already been checked through __android_log_is_loggable().
+ * Higher level logging libraries, such as libbase, first check loggability, then format their
+ * buffers, then pass the message to liblog via this function, and therefore we do not want to
+ * duplicate the loggability check here.
+ *
+ * @param log_message the log message itself, see __android_log_message.
+ *
+ * Available since API level 30.
+ */
+void __android_log_write_log_message(struct __android_log_message* log_message) __INTRODUCED_IN(30);
+
+/**
+ * Sets a user defined logger function.  All log messages sent to liblog will be set to the
+ * function pointer specified by logger for processing.  It is not expected that log messages are
+ * already terminated with a new line.  This function should add new lines if required for line
+ * separation.
+ *
+ * @param logger the new function that will handle log messages.
+ *
+ * Available since API level 30.
+ */
+void __android_log_set_logger(__android_logger_function logger) __INTRODUCED_IN(30);
+
+/**
+ * Writes the log message to logd.  This is an __android_logger_function and can be provided to
+ * __android_log_set_logger().  It is the default logger when running liblog on a device.
+ *
+ * @param log_message the log message to write, see __android_log_message.
+ *
+ * Available since API level 30.
+ */
+void __android_log_logd_logger(const struct __android_log_message* log_message) __INTRODUCED_IN(30);
+
+/**
+ * Writes the log message to stderr.  This is an __android_logger_function and can be provided to
+ * __android_log_set_logger().  It is the default logger when running liblog on host.
+ *
+ * @param log_message the log message to write, see __android_log_message.
+ *
+ * Available since API level 30.
+ */
+void __android_log_stderr_logger(const struct __android_log_message* log_message)
+    __INTRODUCED_IN(30);
+
+/**
+ * Sets a user defined aborter function that is called for __android_log_assert() failures.  This
+ * user defined aborter function is highly recommended to abort and be noreturn, but is not strictly
+ * required to.
+ *
+ * @param aborter the new aborter function, see __android_aborter_function.
+ *
+ * Available since API level 30.
+ */
+void __android_log_set_aborter(__android_aborter_function aborter) __INTRODUCED_IN(30);
+
+/**
+ * Calls the stored aborter function.  This allows for other logging libraries to use the same
+ * aborter function by calling this function in liblog.
+ *
+ * @param abort_message an additional message supplied when aborting, for example this is used to
+ *                      call android_set_abort_message() in __android_log_default_aborter().
+ *
+ * Available since API level 30.
+ */
+void __android_log_call_aborter(const char* abort_message) __INTRODUCED_IN(30);
+
+/**
+ * Sets android_set_abort_message() on device then aborts().  This is the default aborter.
+ *
+ * @param abort_message an additional message supplied when aborting.  This functions calls
+ *                      android_set_abort_message() with its contents.
+ *
+ * Available since API level 30.
+ */
+void __android_log_default_aborter(const char* abort_message) __attribute__((noreturn))
+__INTRODUCED_IN(30);
+
+/**
+ * Use the per-tag properties "log.tag.<tagname>" along with the minimum priority from
+ * __android_log_set_minimum_priority() to determine if a log message with a given prio and tag will
+ * be printed.  A non-zero result indicates yes, zero indicates false.
+ *
+ * If both a priority for a tag and a minimum priority are set by
+ * __android_log_set_minimum_priority(), then the lowest of the two values are to determine the
+ * minimum priority needed to log.  If only one is set, then that value is used to determine the
+ * minimum priority needed.  If none are set, then default_priority is used.
+ *
+ * @param prio         the priority to test, takes android_LogPriority values.
+ * @param tag          the tag to test.
+ * @param default_prio the default priority to use if no properties or minimum priority are set.
+ * @return an integer where 1 indicates that the message is loggable and 0 indicates that it is not.
+ *
+ * Available since API level 30.
+ */
+int __android_log_is_loggable(int prio, const char* tag, int default_prio) __INTRODUCED_IN(30);
+
+/**
+ * Use the per-tag properties "log.tag.<tagname>" along with the minimum priority from
+ * __android_log_set_minimum_priority() to determine if a log message with a given prio and tag will
+ * be printed.  A non-zero result indicates yes, zero indicates false.
+ *
+ * If both a priority for a tag and a minimum priority are set by
+ * __android_log_set_minimum_priority(), then the lowest of the two values are to determine the
+ * minimum priority needed to log.  If only one is set, then that value is used to determine the
+ * minimum priority needed.  If none are set, then default_priority is used.
+ *
+ * @param prio         the priority to test, takes android_LogPriority values.
+ * @param tag          the tag to test.
+ * @param len          the length of the tag.
+ * @param default_prio the default priority to use if no properties or minimum priority are set.
+ * @return an integer where 1 indicates that the message is loggable and 0 indicates that it is not.
+ *
+ * Available since API level 30.
+ */
+int __android_log_is_loggable_len(int prio, const char* tag, size_t len, int default_prio)
+    __INTRODUCED_IN(30);
+
+/**
+ * Sets the minimum priority that will be logged for this process.
+ *
+ * @param priority the new minimum priority to set, takes android_LogPriority values.
+ * @return the previous set minimum priority as android_LogPriority values, or
+ *         ANDROID_LOG_DEFAULT if none was set.
+ *
+ * Available since API level 30.
+ */
+int32_t __android_log_set_minimum_priority(int32_t priority) __INTRODUCED_IN(30);
+
+/**
+ * Gets the minimum priority that will be logged for this process.  If none has been set by a
+ * previous __android_log_set_minimum_priority() call, this returns ANDROID_LOG_DEFAULT.
+ *
+ * @return the current minimum priority as android_LogPriority values, or
+ *         ANDROID_LOG_DEFAULT if none is set.
+ *
+ * Available since API level 30.
+ */
+int32_t __android_log_get_minimum_priority(void) __INTRODUCED_IN(30);
+
+/**
+ * Sets the default tag if no tag is provided when writing a log message.  Defaults to
+ * getprogname().  This truncates tag to the maximum log message size, though appropriate tags
+ * should be much smaller.
+ *
+ * @param tag the new log tag.
+ *
+ * Available since API level 30.
+ */
+void __android_log_set_default_tag(const char* tag) __INTRODUCED_IN(30);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+/** @} */
diff --git a/liblog/include/log/event_tag_map.h b/liblog/include/log/event_tag_map.h
new file mode 100644
index 0000000..de49fbf
--- /dev/null
+++ b/liblog/include/log/event_tag_map.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+#pragma once
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define EVENT_TAG_MAP_FILE "/system/etc/event-log-tags"
+
+struct EventTagMap;
+typedef struct EventTagMap EventTagMap;
+
+/*
+ * Open the specified file as an event log tag map.
+ *
+ * Returns NULL on failure.
+ */
+EventTagMap* android_openEventTagMap(const char* fileName);
+
+/*
+ * Close the map.
+ */
+void android_closeEventTagMap(EventTagMap* map);
+
+/*
+ * Look up a tag by index.  Returns the tag string & string length, or NULL if
+ * not found.  Returned string is not guaranteed to be nul terminated.
+ */
+const char* android_lookupEventTag_len(const EventTagMap* map, size_t* len,
+                                       unsigned int tag);
+
+/*
+ * Look up a format by index. Returns the format string & string length,
+ * or NULL if not found. Returned string is not guaranteed to be nul terminated.
+ */
+const char* android_lookupEventFormat_len(const EventTagMap* map, size_t* len,
+                                          unsigned int tag);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/liblog/include/log/log.h b/liblog/include/log/log.h
new file mode 100644
index 0000000..d7e9b7d
--- /dev/null
+++ b/liblog/include/log/log.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2005-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.
+ */
+
+#pragma once
+
+/* Too many in the ecosystem assume these are included */
+#if !defined(_WIN32)
+#include <pthread.h>
+#endif
+#include <stdint.h> /* uint16_t, int32_t */
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <android/log.h>
+#include <log/log_id.h>
+#include <log/log_main.h>
+#include <log/log_radio.h>
+#include <log/log_safetynet.h>
+#include <log/log_system.h>
+#include <log/log_time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * LOG_TAG is the local tag used for the following simplified
+ * logging macros.  You can change this preprocessor definition
+ * before using the other macros to change the tag.
+ */
+
+#ifndef LOG_TAG
+#define LOG_TAG NULL
+#endif
+
+/*
+ * Normally we strip the effects of ALOGV (VERBOSE messages),
+ * LOG_FATAL and LOG_FATAL_IF (FATAL assert messages) from the
+ * release builds be defining NDEBUG.  You can modify this (for
+ * example with "#define LOG_NDEBUG 0" at the top of your source
+ * file) to change that behavior.
+ */
+
+#ifndef LOG_NDEBUG
+#ifdef NDEBUG
+#define LOG_NDEBUG 1
+#else
+#define LOG_NDEBUG 0
+#endif
+#endif
+
+/*
+ * The maximum size of the log entry payload that can be
+ * written to the logger. An attempt to write more than
+ * this amount will result in a truncated log entry.
+ */
+#define LOGGER_ENTRY_MAX_PAYLOAD 4068
+
+/*
+ * Event logging.
+ */
+
+/*
+ * The following should not be used directly.
+ */
+
+int __android_log_bwrite(int32_t tag, const void* payload, size_t len);
+int __android_log_btwrite(int32_t tag, char type, const void* payload,
+                          size_t len);
+int __android_log_bswrite(int32_t tag, const char* payload);
+
+int __android_log_stats_bwrite(int32_t tag, const void* payload, size_t len);
+
+#define android_bWriteLog(tag, payload, len) \
+  __android_log_bwrite(tag, payload, len)
+#define android_btWriteLog(tag, type, payload, len) \
+  __android_log_btwrite(tag, type, payload, len)
+
+/*
+ * Event log entry types.
+ */
+typedef enum {
+  /* Special markers for android_log_list_element type */
+  EVENT_TYPE_LIST_STOP = '\n', /* declare end of list  */
+  EVENT_TYPE_UNKNOWN = '?',    /* protocol error       */
+
+  /* must match with declaration in java/android/android/util/EventLog.java */
+  EVENT_TYPE_INT = 0,  /* int32_t */
+  EVENT_TYPE_LONG = 1, /* int64_t */
+  EVENT_TYPE_STRING = 2,
+  EVENT_TYPE_LIST = 3,
+  EVENT_TYPE_FLOAT = 4,
+} AndroidEventLogType;
+
+#ifndef LOG_EVENT_INT
+#define LOG_EVENT_INT(_tag, _value)                                          \
+  {                                                                          \
+    int intBuf = _value;                                                     \
+    (void)android_btWriteLog(_tag, EVENT_TYPE_INT, &intBuf, sizeof(intBuf)); \
+  }
+#endif
+#ifndef LOG_EVENT_LONG
+#define LOG_EVENT_LONG(_tag, _value)                                            \
+  {                                                                             \
+    long long longBuf = _value;                                                 \
+    (void)android_btWriteLog(_tag, EVENT_TYPE_LONG, &longBuf, sizeof(longBuf)); \
+  }
+#endif
+#ifndef LOG_EVENT_FLOAT
+#define LOG_EVENT_FLOAT(_tag, _value)                           \
+  {                                                             \
+    float floatBuf = _value;                                    \
+    (void)android_btWriteLog(_tag, EVENT_TYPE_FLOAT, &floatBuf, \
+                             sizeof(floatBuf));                 \
+  }
+#endif
+#ifndef LOG_EVENT_STRING
+#define LOG_EVENT_STRING(_tag, _value) \
+  (void)__android_log_bswrite(_tag, _value);
+#endif
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * Release any logger resources (a new log write will immediately re-acquire)
+ *
+ * This is specifically meant to be used by Zygote to close open file descriptors after fork()
+ * and before specialization.  O_CLOEXEC is used on file descriptors, so they will be closed upon
+ * exec() in normal use cases.
+ *
+ * Note that this is not safe to call from a multi-threaded program.
+ */
+void __android_log_close(void);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/liblog/include/log/log_event_list.h b/liblog/include/log/log_event_list.h
new file mode 100644
index 0000000..deadf20
--- /dev/null
+++ b/liblog/include/log/log_event_list.h
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2005-2016 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.
+ */
+
+#pragma once
+
+#include <errno.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+#include <string>
+#endif
+
+#include <log/log.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* For manipulating lists of events. */
+
+#define ANDROID_MAX_LIST_NEST_DEPTH 8
+
+/*
+ * The opaque context used to manipulate lists of events.
+ */
+typedef struct android_log_context_internal* android_log_context;
+
+/*
+ * Elements returned when reading a list of events.
+ */
+typedef struct {
+  AndroidEventLogType type;
+  uint16_t complete;
+  uint16_t len;
+  union {
+    int32_t int32;
+    int64_t int64;
+    char* string;
+    float float32;
+  } data;
+} android_log_list_element;
+
+/*
+ * Creates a context associated with an event tag to write elements to
+ * the list of events.
+ */
+android_log_context create_android_logger(uint32_t tag);
+
+/* All lists must be braced by a begin and end call */
+/*
+ * NB: If the first level braces are missing when specifying multiple
+ *     elements, we will manufacturer a list to embrace it for your API
+ *     convenience. For a single element, it will remain solitary.
+ */
+int android_log_write_list_begin(android_log_context ctx);
+int android_log_write_list_end(android_log_context ctx);
+
+int android_log_write_int32(android_log_context ctx, int32_t value);
+int android_log_write_int64(android_log_context ctx, int64_t value);
+int android_log_write_string8(android_log_context ctx, const char* value);
+int android_log_write_string8_len(android_log_context ctx, const char* value,
+                                  size_t maxlen);
+int android_log_write_float32(android_log_context ctx, float value);
+
+/* Submit the composed list context to the specified logger id */
+/* NB: LOG_ID_EVENTS and LOG_ID_SECURITY only valid binary buffers */
+int android_log_write_list(android_log_context ctx, log_id_t id);
+
+/*
+ * Creates a context from a raw buffer representing a list of events to be read.
+ */
+android_log_context create_android_log_parser(const char* msg, size_t len);
+
+android_log_list_element android_log_read_next(android_log_context ctx);
+android_log_list_element android_log_peek_next(android_log_context ctx);
+
+/* Reset writer context */
+int android_log_reset(android_log_context ctx);
+
+/* Reset reader context */
+int android_log_parser_reset(android_log_context ctx,
+                             const char* msg, size_t len);
+
+/* Finished with reader or writer context */
+int android_log_destroy(android_log_context* ctx);
+
+#ifdef __cplusplus
+/* android_log_list C++ helpers */
+extern "C++" {
+class android_log_event_list {
+ private:
+  android_log_context ctx;
+  int ret;
+
+  android_log_event_list(const android_log_event_list&) = delete;
+  void operator=(const android_log_event_list&) = delete;
+
+ public:
+  explicit android_log_event_list(int tag) : ret(0) {
+    ctx = create_android_logger(static_cast<uint32_t>(tag));
+  }
+  ~android_log_event_list() {
+    android_log_destroy(&ctx);
+  }
+
+  int close() {
+    int retval = android_log_destroy(&ctx);
+    if (retval < 0) ret = retval;
+    return retval;
+  }
+
+  /* To allow above C calls to use this class as parameter */
+  operator android_log_context() const {
+    return ctx;
+  }
+
+  /* return errors or transmit status */
+  int status() const {
+    return ret;
+  }
+
+  int begin() {
+    int retval = android_log_write_list_begin(ctx);
+    if (retval < 0) ret = retval;
+    return ret;
+  }
+  int end() {
+    int retval = android_log_write_list_end(ctx);
+    if (retval < 0) ret = retval;
+    return ret;
+  }
+
+  android_log_event_list& operator<<(int32_t value) {
+    int retval = android_log_write_int32(ctx, value);
+    if (retval < 0) ret = retval;
+    return *this;
+  }
+
+  android_log_event_list& operator<<(uint32_t value) {
+    int retval = android_log_write_int32(ctx, static_cast<int32_t>(value));
+    if (retval < 0) ret = retval;
+    return *this;
+  }
+
+  android_log_event_list& operator<<(bool value) {
+    int retval = android_log_write_int32(ctx, value ? 1 : 0);
+    if (retval < 0) ret = retval;
+    return *this;
+  }
+
+  android_log_event_list& operator<<(int64_t value) {
+    int retval = android_log_write_int64(ctx, value);
+    if (retval < 0) ret = retval;
+    return *this;
+  }
+
+  android_log_event_list& operator<<(uint64_t value) {
+    int retval = android_log_write_int64(ctx, static_cast<int64_t>(value));
+    if (retval < 0) ret = retval;
+    return *this;
+  }
+
+  android_log_event_list& operator<<(const char* value) {
+    int retval = android_log_write_string8(ctx, value);
+    if (retval < 0) ret = retval;
+    return *this;
+  }
+
+  android_log_event_list& operator<<(const std::string& value) {
+    int retval =
+        android_log_write_string8_len(ctx, value.data(), value.length());
+    if (retval < 0) ret = retval;
+    return *this;
+  }
+
+  android_log_event_list& operator<<(float value) {
+    int retval = android_log_write_float32(ctx, value);
+    if (retval < 0) ret = retval;
+    return *this;
+  }
+
+  int write(log_id_t id = LOG_ID_EVENTS) {
+    /* facilitate -EBUSY retry */
+    if ((ret == -EBUSY) || (ret > 0)) ret = 0;
+    int retval = android_log_write_list(ctx, id);
+    /* existing errors trump transmission errors */
+    if (!ret) ret = retval;
+    return ret;
+  }
+
+  int operator<<(log_id_t id) {
+    write(id);
+    android_log_destroy(&ctx);
+    return ret;
+  }
+
+  /*
+   * Append<Type> methods removes any integer promotion
+   * confusion, and adds access to string with length.
+   * Append methods are also added for all types for
+   * convenience.
+   */
+
+  bool AppendInt(int32_t value) {
+    int retval = android_log_write_int32(ctx, value);
+    if (retval < 0) ret = retval;
+    return ret >= 0;
+  }
+
+  bool AppendLong(int64_t value) {
+    int retval = android_log_write_int64(ctx, value);
+    if (retval < 0) ret = retval;
+    return ret >= 0;
+  }
+
+  bool AppendString(const char* value) {
+    int retval = android_log_write_string8(ctx, value);
+    if (retval < 0) ret = retval;
+    return ret >= 0;
+  }
+
+  bool AppendString(const char* value, size_t len) {
+    int retval = android_log_write_string8_len(ctx, value, len);
+    if (retval < 0) ret = retval;
+    return ret >= 0;
+  }
+
+  bool AppendString(const std::string& value) {
+    int retval =
+        android_log_write_string8_len(ctx, value.data(), value.length());
+    if (retval < 0) ret = retval;
+    return ret;
+  }
+
+  bool Append(const std::string& value) {
+    int retval =
+        android_log_write_string8_len(ctx, value.data(), value.length());
+    if (retval < 0) ret = retval;
+    return ret;
+  }
+
+  bool AppendFloat(float value) {
+    int retval = android_log_write_float32(ctx, value);
+    if (retval < 0) ret = retval;
+    return ret >= 0;
+  }
+
+  template <typename Tvalue>
+  bool Append(Tvalue value) {
+    *this << value;
+    return ret >= 0;
+  }
+
+  bool Append(const char* value, size_t len) {
+    int retval = android_log_write_string8_len(ctx, value, len);
+    if (retval < 0) ret = retval;
+    return ret >= 0;
+  }
+};
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/liblog/fake_log_device.h b/liblog/include/log/log_id.h
similarity index 62%
copy from liblog/fake_log_device.h
copy to liblog/include/log/log_id.h
index 9d168cd..8e4faeb 100644
--- a/liblog/fake_log_device.h
+++ b/liblog/include/log/log_id.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2005-2017 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.
@@ -14,15 +14,20 @@
  * limitations under the License.
  */
 
-#ifndef _LIBLOG_FAKE_LOG_DEVICE_H
-#define _LIBLOG_FAKE_LOG_DEVICE_H
+#pragma once
 
-#include <sys/types.h>
+#include <android/log.h>
 
-struct iovec;
+#ifdef __cplusplus
+extern "C" {
+#endif
 
-int fakeLogOpen(const char *pathName, int flags);
-int fakeLogClose(int fd);
-ssize_t fakeLogWritev(int fd, const struct iovec* vector, int count);
+/*
+ * 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);
 
-#endif // _LIBLOG_FAKE_LOG_DEVICE_H
+#ifdef __cplusplus
+}
+#endif
diff --git a/liblog/include/log/log_main.h b/liblog/include/log/log_main.h
new file mode 100644
index 0000000..1bd1c8a
--- /dev/null
+++ b/liblog/include/log/log_main.h
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2005-2017 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.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#include <android/log.h>
+
+__BEGIN_DECLS
+
+/*
+ * Normally we strip the effects of ALOGV (VERBOSE messages),
+ * LOG_FATAL and LOG_FATAL_IF (FATAL assert messages) from the
+ * release builds be defining NDEBUG.  You can modify this (for
+ * example with "#define LOG_NDEBUG 0" at the top of your source
+ * file) to change that behavior.
+ */
+
+#ifndef LOG_NDEBUG
+#ifdef NDEBUG
+#define LOG_NDEBUG 1
+#else
+#define LOG_NDEBUG 0
+#endif
+#endif
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * This file uses ", ## __VA_ARGS__" zero-argument token pasting to
+ * work around issues with debug-only syntax errors in assertions
+ * that are missing format strings.  See commit
+ * 19299904343daf191267564fe32e6cd5c165cd42
+ */
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+#endif
+
+/*
+ * Use __VA_ARGS__ if running a static analyzer,
+ * to avoid warnings of unused variables in __VA_ARGS__.
+ * Use constexpr function in C++ mode, so these macros can be used
+ * in other constexpr functions without warning.
+ */
+#ifdef __clang_analyzer__
+#ifdef __cplusplus
+extern "C++" {
+template <typename... Ts>
+constexpr int __fake_use_va_args(Ts...) {
+  return 0;
+}
+}
+#else
+extern int __fake_use_va_args(int, ...);
+#endif /* __cplusplus */
+#define __FAKE_USE_VA_ARGS(...) ((void)__fake_use_va_args(0, ##__VA_ARGS__))
+#else
+#define __FAKE_USE_VA_ARGS(...) ((void)(0))
+#endif /* __clang_analyzer__ */
+
+#ifndef __predict_false
+#define __predict_false(exp) __builtin_expect((exp) != 0, 0)
+#endif
+
+#define android_writeLog(prio, tag, text) __android_log_write(prio, tag, text)
+
+#define android_printLog(prio, tag, ...) \
+  __android_log_print(prio, tag, __VA_ARGS__)
+
+#define android_vprintLog(prio, cond, tag, ...) \
+  __android_log_vprint(prio, tag, __VA_ARGS__)
+
+/*
+ * Log macro that allows you to specify a number for the priority.
+ */
+#ifndef LOG_PRI
+#define LOG_PRI(priority, tag, ...) android_printLog(priority, tag, __VA_ARGS__)
+#endif
+
+/*
+ * Log macro that allows you to pass in a varargs ("args" is a va_list).
+ */
+#ifndef LOG_PRI_VA
+#define LOG_PRI_VA(priority, tag, fmt, args) \
+  android_vprintLog(priority, NULL, tag, fmt, args)
+#endif
+
+/* --------------------------------------------------------------------- */
+
+/* XXX Macros to work around syntax errors in places where format string
+ * arg is not passed to ALOG_ASSERT, LOG_ALWAYS_FATAL or LOG_ALWAYS_FATAL_IF
+ * (happens only in debug builds).
+ */
+
+/* Returns 2nd arg.  Used to substitute default value if caller's vararg list
+ * is empty.
+ */
+#define __android_second(dummy, second, ...) second
+
+/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise
+ * returns nothing.
+ */
+#define __android_rest(first, ...) , ##__VA_ARGS__
+
+#define android_printAssert(cond, tag, ...)                     \
+  __android_log_assert(cond, tag,                               \
+                       __android_second(0, ##__VA_ARGS__, NULL) \
+                           __android_rest(__VA_ARGS__))
+
+/*
+ * Log a fatal error.  If the given condition fails, this stops program
+ * execution like a normal assertion, but also generating the given message.
+ * It is NOT stripped from release builds.  Note that the condition test
+ * is -inverted- from the normal assert() semantics.
+ */
+#ifndef LOG_ALWAYS_FATAL_IF
+#define LOG_ALWAYS_FATAL_IF(cond, ...)                                                    \
+  ((__predict_false(cond)) ? (__FAKE_USE_VA_ARGS(__VA_ARGS__),                            \
+                              ((void)android_printAssert(#cond, LOG_TAG, ##__VA_ARGS__))) \
+                           : ((void)0))
+#endif
+
+#ifndef LOG_ALWAYS_FATAL
+#define LOG_ALWAYS_FATAL(...) \
+  (((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__)))
+#endif
+
+/*
+ * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that
+ * are stripped out of release builds.
+ */
+
+#if LOG_NDEBUG
+
+#ifndef LOG_FATAL_IF
+#define LOG_FATAL_IF(cond, ...) __FAKE_USE_VA_ARGS(__VA_ARGS__)
+#endif
+#ifndef LOG_FATAL
+#define LOG_FATAL(...) __FAKE_USE_VA_ARGS(__VA_ARGS__)
+#endif
+
+#else
+
+#ifndef LOG_FATAL_IF
+#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ##__VA_ARGS__)
+#endif
+#ifndef LOG_FATAL
+#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__)
+#endif
+
+#endif
+
+/*
+ * Assertion that generates a log message when the assertion fails.
+ * Stripped out of release builds.  Uses the current LOG_TAG.
+ */
+#ifndef ALOG_ASSERT
+#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__)
+#endif
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * C/C++ logging functions.  See the logging documentation for API details.
+ *
+ * We'd like these to be available from C code (in case we import some from
+ * somewhere), so this has a C interface.
+ *
+ * The output will be correct when the log file is shared between multiple
+ * threads and/or multiple processes so long as the operating system
+ * supports O_APPEND.  These calls have mutex-protected data structures
+ * and so are NOT reentrant.  Do not use LOG in a signal handler.
+ */
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * Simplified macro to send a verbose log message using the current LOG_TAG.
+ */
+#ifndef ALOGV
+#define __ALOGV(...) ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
+#if LOG_NDEBUG
+#define ALOGV(...)                   \
+  do {                               \
+    __FAKE_USE_VA_ARGS(__VA_ARGS__); \
+    if (false) {                     \
+      __ALOGV(__VA_ARGS__);          \
+    }                                \
+  } while (false)
+#else
+#define ALOGV(...) __ALOGV(__VA_ARGS__)
+#endif
+#endif
+
+#ifndef ALOGV_IF
+#if LOG_NDEBUG
+#define ALOGV_IF(cond, ...) __FAKE_USE_VA_ARGS(__VA_ARGS__)
+#else
+#define ALOGV_IF(cond, ...)                                                               \
+  ((__predict_false(cond))                                                                \
+       ? (__FAKE_USE_VA_ARGS(__VA_ARGS__), (void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
+       : ((void)0))
+#endif
+#endif
+
+/*
+ * Simplified macro to send a debug log message using the current LOG_TAG.
+ */
+#ifndef ALOGD
+#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGD_IF
+#define ALOGD_IF(cond, ...)                                                             \
+  ((__predict_false(cond))                                                              \
+       ? (__FAKE_USE_VA_ARGS(__VA_ARGS__), (void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
+       : ((void)0))
+#endif
+
+/*
+ * Simplified macro to send an info log message using the current LOG_TAG.
+ */
+#ifndef ALOGI
+#define ALOGI(...) ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGI_IF
+#define ALOGI_IF(cond, ...)                                                            \
+  ((__predict_false(cond))                                                             \
+       ? (__FAKE_USE_VA_ARGS(__VA_ARGS__), (void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \
+       : ((void)0))
+#endif
+
+/*
+ * Simplified macro to send a warning log message using the current LOG_TAG.
+ */
+#ifndef ALOGW
+#define ALOGW(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGW_IF
+#define ALOGW_IF(cond, ...)                                                            \
+  ((__predict_false(cond))                                                             \
+       ? (__FAKE_USE_VA_ARGS(__VA_ARGS__), (void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \
+       : ((void)0))
+#endif
+
+/*
+ * Simplified macro to send an error log message using the current LOG_TAG.
+ */
+#ifndef ALOGE
+#define ALOGE(...) ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGE_IF
+#define ALOGE_IF(cond, ...)                                                             \
+  ((__predict_false(cond))                                                              \
+       ? (__FAKE_USE_VA_ARGS(__VA_ARGS__), (void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
+       : ((void)0))
+#endif
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * verbose priority.
+ */
+#ifndef IF_ALOGV
+#if LOG_NDEBUG
+#define IF_ALOGV() if (false)
+#else
+#define IF_ALOGV() IF_ALOG(LOG_VERBOSE, LOG_TAG)
+#endif
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * debug priority.
+ */
+#ifndef IF_ALOGD
+#define IF_ALOGD() IF_ALOG(LOG_DEBUG, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * info priority.
+ */
+#ifndef IF_ALOGI
+#define IF_ALOGI() IF_ALOG(LOG_INFO, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * warn priority.
+ */
+#ifndef IF_ALOGW
+#define IF_ALOGW() IF_ALOG(LOG_WARN, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * error priority.
+ */
+#ifndef IF_ALOGE
+#define IF_ALOGE() IF_ALOG(LOG_ERROR, LOG_TAG)
+#endif
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * Basic log message macro.
+ *
+ * Example:
+ *  ALOG(LOG_WARN, NULL, "Failed with error %d", errno);
+ *
+ * The second argument may be NULL or "" to indicate the "global" tag.
+ */
+#ifndef ALOG
+#define ALOG(priority, tag, ...) LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
+#endif
+
+/*
+ * Conditional given a desired logging priority and tag.
+ */
+#ifndef IF_ALOG
+#define IF_ALOG(priority, tag) if (android_testLog(ANDROID_##priority, tag))
+#endif
+
+/* --------------------------------------------------------------------- */
+
+/*
+ *    IF_ALOG uses android_testLog, but IF_ALOG can be overridden.
+ *    android_testLog will remain constant in its purpose as a wrapper
+ *        for Android logging filter policy, and can be subject to
+ *        change. It can be reused by the developers that override
+ *        IF_ALOG as a convenient means to reimplement their policy
+ *        over Android.
+ */
+
+/*
+ * Use the per-tag properties "log.tag.<tagname>" to generate a runtime
+ * result of non-zero to expose a log. prio is ANDROID_LOG_VERBOSE to
+ * ANDROID_LOG_FATAL. default_prio if no property. Undefined behavior if
+ * any other value.
+ */
+int __android_log_is_loggable(int prio, const char* tag, int default_prio);
+int __android_log_is_loggable_len(int prio, const char* tag, size_t len, int default_prio);
+
+#if LOG_NDEBUG /* Production */
+#define android_testLog(prio, tag)                                           \
+  (__android_log_is_loggable_len(prio, tag, ((tag) && *(tag)) ? strlen(tag) : 0, \
+                                 ANDROID_LOG_DEBUG) != 0)
+#else
+#define android_testLog(prio, tag)                                           \
+  (__android_log_is_loggable_len(prio, tag, ((tag) && *(tag)) ? strlen(tag) : 0, \
+                                 ANDROID_LOG_VERBOSE) != 0)
+#endif
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+__END_DECLS
diff --git a/liblog/fake_log_device.h b/liblog/include/log/log_properties.h
similarity index 62%
copy from liblog/fake_log_device.h
copy to liblog/include/log/log_properties.h
index 9d168cd..2a0230f 100644
--- a/liblog/fake_log_device.h
+++ b/liblog/include/log/log_properties.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-#ifndef _LIBLOG_FAKE_LOG_DEVICE_H
-#define _LIBLOG_FAKE_LOG_DEVICE_H
+#pragma once
 
-#include <sys/types.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
 
-struct iovec;
+/* Returns `1` if the device is debuggable or `0` if not. */
+int __android_log_is_debuggable();
 
-int fakeLogOpen(const char *pathName, int flags);
-int fakeLogClose(int fd);
-ssize_t fakeLogWritev(int fd, const struct iovec* vector, int count);
-
-#endif // _LIBLOG_FAKE_LOG_DEVICE_H
+#ifdef __cplusplus
+}
+#endif
diff --git a/liblog/include/log/log_radio.h b/liblog/include/log/log_radio.h
new file mode 100644
index 0000000..f5525c1
--- /dev/null
+++ b/liblog/include/log/log_radio.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2005-2017 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.
+ */
+
+#pragma once
+
+#include <android/log.h>
+
+/*
+ * Normally we strip the effects of ALOGV (VERBOSE messages),
+ * LOG_FATAL and LOG_FATAL_IF (FATAL assert messages) from the
+ * release builds be defining NDEBUG.  You can modify this (for
+ * example with "#define LOG_NDEBUG 0" at the top of your source
+ * file) to change that behavior.
+ */
+
+#ifndef LOG_NDEBUG
+#ifdef NDEBUG
+#define LOG_NDEBUG 1
+#else
+#define LOG_NDEBUG 0
+#endif
+#endif
+
+/* --------------------------------------------------------------------- */
+
+#ifndef __predict_false
+#define __predict_false(exp) __builtin_expect((exp) != 0, 0)
+#endif
+
+/*
+ * Simplified macro to send a verbose radio log message using current LOG_TAG.
+ */
+#ifndef RLOGV
+#define __RLOGV(...)                                                         \
+  ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_VERBOSE, LOG_TAG, \
+                                 __VA_ARGS__))
+#if LOG_NDEBUG
+#define RLOGV(...)          \
+  do {                      \
+    if (0) {                \
+      __RLOGV(__VA_ARGS__); \
+    }                       \
+  } while (0)
+#else
+#define RLOGV(...) __RLOGV(__VA_ARGS__)
+#endif
+#endif
+
+#ifndef RLOGV_IF
+#if LOG_NDEBUG
+#define RLOGV_IF(cond, ...) ((void)0)
+#else
+#define RLOGV_IF(cond, ...)                                                \
+  ((__predict_false(cond))                                                 \
+       ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_VERBOSE, \
+                                        LOG_TAG, __VA_ARGS__))             \
+       : (void)0)
+#endif
+#endif
+
+/*
+ * Simplified macro to send a debug radio log message using  current LOG_TAG.
+ */
+#ifndef RLOGD
+#define RLOGD(...)                                                         \
+  ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_DEBUG, LOG_TAG, \
+                                 __VA_ARGS__))
+#endif
+
+#ifndef RLOGD_IF
+#define RLOGD_IF(cond, ...)                                              \
+  ((__predict_false(cond))                                               \
+       ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_DEBUG, \
+                                        LOG_TAG, __VA_ARGS__))           \
+       : (void)0)
+#endif
+
+/*
+ * Simplified macro to send an info radio log message using  current LOG_TAG.
+ */
+#ifndef RLOGI
+#define RLOGI(...)                                                        \
+  ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO, LOG_TAG, \
+                                 __VA_ARGS__))
+#endif
+
+#ifndef RLOGI_IF
+#define RLOGI_IF(cond, ...)                                             \
+  ((__predict_false(cond))                                              \
+       ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO, \
+                                        LOG_TAG, __VA_ARGS__))          \
+       : (void)0)
+#endif
+
+/*
+ * Simplified macro to send a warning radio log message using current LOG_TAG.
+ */
+#ifndef RLOGW
+#define RLOGW(...)                                                        \
+  ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_WARN, LOG_TAG, \
+                                 __VA_ARGS__))
+#endif
+
+#ifndef RLOGW_IF
+#define RLOGW_IF(cond, ...)                                             \
+  ((__predict_false(cond))                                              \
+       ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_WARN, \
+                                        LOG_TAG, __VA_ARGS__))          \
+       : (void)0)
+#endif
+
+/*
+ * Simplified macro to send an error radio log message using current LOG_TAG.
+ */
+#ifndef RLOGE
+#define RLOGE(...)                                                         \
+  ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_ERROR, LOG_TAG, \
+                                 __VA_ARGS__))
+#endif
+
+#ifndef RLOGE_IF
+#define RLOGE_IF(cond, ...)                                              \
+  ((__predict_false(cond))                                               \
+       ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_ERROR, \
+                                        LOG_TAG, __VA_ARGS__))           \
+       : (void)0)
+#endif
diff --git a/liblog/include/log/log_read.h b/liblog/include/log/log_read.h
new file mode 100644
index 0000000..1736934
--- /dev/null
+++ b/liblog/include/log/log_read.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2005-2017 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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <android/log.h>
+#include <log/log_time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ANDROID_LOG_WRAP_DEFAULT_TIMEOUT 7200 /* 2 hour default */
+
+/*
+ * Native log reading interface section. See logcat for sample code.
+ *
+ * The preferred API is an exec of logcat. Likely uses of this interface
+ * are if native code suffers from exec or filtration being too costly,
+ * access to raw information, or parsing is an issue.
+ */
+
+struct logger_entry {
+  uint16_t len;      /* length of the payload */
+  uint16_t hdr_size; /* sizeof(struct logger_entry) */
+  int32_t pid;       /* generating process's pid */
+  uint32_t tid;      /* generating process's tid */
+  uint32_t sec;      /* seconds since Epoch */
+  uint32_t nsec;     /* nanoseconds */
+  uint32_t lid;      /* log id of the payload, bottom 4 bits currently */
+  uint32_t uid;      /* generating process's uid */
+};
+
+/*
+ * The maximum size of a log entry which can be read.
+ * An attempt to read less than this amount may result
+ * in read() returning EINVAL.
+ */
+#define LOGGER_ENTRY_MAX_LEN (5 * 1024)
+
+struct log_msg {
+  union {
+    unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1];
+    struct logger_entry entry;
+  } __attribute__((aligned(4)));
+#ifdef __cplusplus
+  uint64_t nsec() const {
+    return static_cast<uint64_t>(entry.sec) * NS_PER_SEC + entry.nsec;
+  }
+  log_id_t id() {
+    return static_cast<log_id_t>(entry.lid);
+  }
+  char* msg() {
+    unsigned short hdr_size = entry.hdr_size;
+    if (hdr_size >= sizeof(struct log_msg) - sizeof(entry)) {
+      return nullptr;
+    }
+    return reinterpret_cast<char*>(buf) + hdr_size;
+  }
+  unsigned int len() { return entry.hdr_size + entry.len; }
+#endif
+};
+
+struct logger;
+
+log_id_t android_logger_get_id(struct logger* logger);
+
+/* Clears the given log buffer. */
+int android_logger_clear(struct logger* logger);
+/* Return the allotted size for the given log buffer. */
+long android_logger_get_log_size(struct logger* logger);
+/* Set the allotted size for the given log buffer. */
+int android_logger_set_log_size(struct logger* logger, unsigned long size);
+/* Return the actual, uncompressed size that can be read from the given log buffer. */
+long android_logger_get_log_readable_size(struct logger* logger);
+/* Return the actual, compressed size that the given log buffer is consuming. */
+long android_logger_get_log_consumed_size(struct logger* logger);
+/* Deprecated.  Always returns '4' regardless of input. */
+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);
+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, const char* buf, size_t len);
+
+/* The below values are used for the `mode` argument of the below functions. */
+/* Note that 0x00000003 were previously used and should be considered reserved. */
+#define ANDROID_LOG_NONBLOCK 0x00000800
+#define ANDROID_LOG_WRAP 0x40000000 /* Block until buffer about to wrap */
+#define ANDROID_LOG_PSTORE 0x80000000
+
+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,
+                             struct log_msg* log_msg);
+
+/* Multiple log_id_t opens */
+struct logger* android_logger_open(struct logger_list* logger_list, log_id_t id);
+/* 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
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/liblog/include/log/log_safetynet.h b/liblog/include/log/log_safetynet.h
new file mode 100644
index 0000000..b2604b5
--- /dev/null
+++ b/liblog/include/log/log_safetynet.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define android_errorWriteLog(tag, subTag) \
+  __android_log_error_write(tag, subTag, -1, NULL, 0)
+
+#define android_errorWriteWithInfoLog(tag, subTag, uid, data, dataLen) \
+  __android_log_error_write(tag, subTag, uid, data, dataLen)
+
+int __android_log_error_write(int tag, const char* subTag, int32_t uid,
+                              const char* data, uint32_t dataLen);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/liblog/include/log/log_system.h b/liblog/include/log/log_system.h
new file mode 100644
index 0000000..6f40515
--- /dev/null
+++ b/liblog/include/log/log_system.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2005-2017 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.
+ */
+
+#pragma once
+
+#include <android/log.h>
+
+/*
+ * Normally we strip the effects of ALOGV (VERBOSE messages),
+ * LOG_FATAL and LOG_FATAL_IF (FATAL assert messages) from the
+ * release builds be defining NDEBUG.  You can modify this (for
+ * example with "#define LOG_NDEBUG 0" at the top of your source
+ * file) to change that behavior.
+ */
+
+#ifndef LOG_NDEBUG
+#ifdef NDEBUG
+#define LOG_NDEBUG 1
+#else
+#define LOG_NDEBUG 0
+#endif
+#endif
+
+#ifndef __predict_false
+#define __predict_false(exp) __builtin_expect((exp) != 0, 0)
+#endif
+
+/*
+ * Simplified macro to send a verbose system log message using current LOG_TAG.
+ */
+#ifndef SLOGV
+#define __SLOGV(...)                                                          \
+  ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, LOG_TAG, \
+                                 __VA_ARGS__))
+#if LOG_NDEBUG
+#define SLOGV(...)          \
+  do {                      \
+    if (0) {                \
+      __SLOGV(__VA_ARGS__); \
+    }                       \
+  } while (0)
+#else
+#define SLOGV(...) __SLOGV(__VA_ARGS__)
+#endif
+#endif
+
+#ifndef SLOGV_IF
+#if LOG_NDEBUG
+#define SLOGV_IF(cond, ...) ((void)0)
+#else
+#define SLOGV_IF(cond, ...)                                                 \
+  ((__predict_false(cond))                                                  \
+       ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, \
+                                        LOG_TAG, __VA_ARGS__))              \
+       : (void)0)
+#endif
+#endif
+
+/*
+ * Simplified macro to send a debug system log message using current LOG_TAG.
+ */
+#ifndef SLOGD
+#define SLOGD(...)                                                          \
+  ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, \
+                                 __VA_ARGS__))
+#endif
+
+#ifndef SLOGD_IF
+#define SLOGD_IF(cond, ...)                                               \
+  ((__predict_false(cond))                                                \
+       ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, \
+                                        LOG_TAG, __VA_ARGS__))            \
+       : (void)0)
+#endif
+
+/*
+ * Simplified macro to send an info system log message using current LOG_TAG.
+ */
+#ifndef SLOGI
+#define SLOGI(...)                                                         \
+  ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, LOG_TAG, \
+                                 __VA_ARGS__))
+#endif
+
+#ifndef SLOGI_IF
+#define SLOGI_IF(cond, ...)                                              \
+  ((__predict_false(cond))                                               \
+       ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, \
+                                        LOG_TAG, __VA_ARGS__))           \
+       : (void)0)
+#endif
+
+/*
+ * Simplified macro to send a warning system log message using current LOG_TAG.
+ */
+#ifndef SLOGW
+#define SLOGW(...)                                                         \
+  ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_WARN, LOG_TAG, \
+                                 __VA_ARGS__))
+#endif
+
+#ifndef SLOGW_IF
+#define SLOGW_IF(cond, ...)                                              \
+  ((__predict_false(cond))                                               \
+       ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_WARN, \
+                                        LOG_TAG, __VA_ARGS__))           \
+       : (void)0)
+#endif
+
+/*
+ * Simplified macro to send an error system log message using current LOG_TAG.
+ */
+#ifndef SLOGE
+#define SLOGE(...)                                                          \
+  ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, LOG_TAG, \
+                                 __VA_ARGS__))
+#endif
+
+#ifndef SLOGE_IF
+#define SLOGE_IF(cond, ...)                                               \
+  ((__predict_false(cond))                                                \
+       ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, \
+                                        LOG_TAG, __VA_ARGS__))            \
+       : (void)0)
+#endif
diff --git a/liblog/include/log/log_time.h b/liblog/include/log/log_time.h
new file mode 100644
index 0000000..f50764d
--- /dev/null
+++ b/liblog/include/log/log_time.h
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2005-2017 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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <time.h>
+
+/* struct log_time is a wire-format variant of struct timespec */
+#define NS_PER_SEC 1000000000ULL
+#define US_PER_SEC 1000000ULL
+#define MS_PER_SEC 1000ULL
+
+#define LOG_TIME_SEC(t) ((t)->tv_sec)
+/* next power of two after NS_PER_SEC */
+#define LOG_TIME_NSEC(t) ((t)->tv_nsec & (UINT32_MAX >> 2))
+
+#ifdef __cplusplus
+
+extern "C" {
+
+struct log_time {
+ public:
+  uint32_t tv_sec = 0; /* good to Feb 5 2106 */
+  uint32_t tv_nsec = 0;
+
+  static constexpr timespec EPOCH = {0, 0};
+
+  log_time() {}
+  explicit log_time(const timespec& T)
+      : tv_sec(static_cast<uint32_t>(T.tv_sec)), tv_nsec(static_cast<uint32_t>(T.tv_nsec)) {}
+  explicit log_time(uint32_t sec, uint32_t nsec = 0)
+      : tv_sec(sec), tv_nsec(nsec) {
+  }
+#ifdef __linux__
+  explicit log_time(clockid_t id) {
+    timespec T;
+    clock_gettime(id, &T);
+    tv_sec = static_cast<uint32_t>(T.tv_sec);
+    tv_nsec = static_cast<uint32_t>(T.tv_nsec);
+  }
+#endif
+  /* timespec */
+  bool operator==(const timespec& T) const {
+    return (tv_sec == static_cast<uint32_t>(T.tv_sec)) &&
+           (tv_nsec == static_cast<uint32_t>(T.tv_nsec));
+  }
+  bool operator!=(const timespec& T) const {
+    return !(*this == T);
+  }
+  bool operator<(const timespec& T) const {
+    return (tv_sec < static_cast<uint32_t>(T.tv_sec)) ||
+           ((tv_sec == static_cast<uint32_t>(T.tv_sec)) &&
+            (tv_nsec < static_cast<uint32_t>(T.tv_nsec)));
+  }
+  bool operator>=(const timespec& T) const {
+    return !(*this < T);
+  }
+  bool operator>(const timespec& T) const {
+    return (tv_sec > static_cast<uint32_t>(T.tv_sec)) ||
+           ((tv_sec == static_cast<uint32_t>(T.tv_sec)) &&
+            (tv_nsec > static_cast<uint32_t>(T.tv_nsec)));
+  }
+  bool operator<=(const timespec& T) const {
+    return !(*this > T);
+  }
+
+  /* log_time */
+  bool operator==(const log_time& T) const {
+    return (tv_sec == T.tv_sec) && (tv_nsec == T.tv_nsec);
+  }
+  bool operator!=(const log_time& T) const {
+    return !(*this == T);
+  }
+  bool operator<(const log_time& T) const {
+    return (tv_sec < T.tv_sec) ||
+           ((tv_sec == T.tv_sec) && (tv_nsec < T.tv_nsec));
+  }
+  bool operator>=(const log_time& T) const {
+    return !(*this < T);
+  }
+  bool operator>(const log_time& T) const {
+    return (tv_sec > T.tv_sec) ||
+           ((tv_sec == T.tv_sec) && (tv_nsec > T.tv_nsec));
+  }
+  bool operator<=(const log_time& T) const {
+    return !(*this > T);
+  }
+
+  log_time operator-=(const log_time& T) {
+    // No concept of negative time, clamp to EPOCH
+    if (*this <= T) {
+      return *this = log_time(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;
+  }
+  log_time operator-(const log_time& T) const {
+    log_time local(*this);
+    return local -= T;
+  }
+  log_time operator+=(const log_time& T) {
+    this->tv_nsec += T.tv_nsec;
+    if (this->tv_nsec >= NS_PER_SEC) {
+      this->tv_nsec -= NS_PER_SEC;
+      ++this->tv_sec;
+    }
+    this->tv_sec += T.tv_sec;
+
+    return *this;
+  }
+  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;
+  }
+  uint64_t usec() const {
+    return static_cast<uint64_t>(tv_sec) * US_PER_SEC +
+           tv_nsec / (NS_PER_SEC / US_PER_SEC);
+  }
+  uint64_t msec() const {
+    return static_cast<uint64_t>(tv_sec) * MS_PER_SEC +
+           tv_nsec / (NS_PER_SEC / MS_PER_SEC);
+  }
+
+  /* Add %#q for the fraction of a second to the standard library functions */
+  char* strptime(const char* s, const char* format);
+} __attribute__((__packed__));
+}
+
+#else /* __cplusplus */
+
+typedef struct log_time {
+  uint32_t tv_sec;
+  uint32_t tv_nsec;
+} __attribute__((__packed__)) log_time;
+
+#endif /* __cplusplus */
diff --git a/liblog/include/log/logprint.h b/liblog/include/log/logprint.h
new file mode 100644
index 0000000..7dfd914
--- /dev/null
+++ b/liblog/include/log/logprint.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <android/log.h>
+#include <log/event_tag_map.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+  /* Verbs */
+  FORMAT_OFF = 0,
+  FORMAT_BRIEF,
+  FORMAT_PROCESS,
+  FORMAT_TAG,
+  FORMAT_THREAD,
+  FORMAT_RAW,
+  FORMAT_TIME,
+  FORMAT_THREADTIME,
+  FORMAT_LONG,
+  /* Adverbs. The following are modifiers to above format verbs */
+  FORMAT_MODIFIER_COLOR,     /* converts priority to color */
+  FORMAT_MODIFIER_TIME_USEC, /* switches from msec to usec time precision */
+  FORMAT_MODIFIER_PRINTABLE, /* converts non-printable to printable escapes */
+  FORMAT_MODIFIER_YEAR,      /* Adds year to date */
+  FORMAT_MODIFIER_ZONE,      /* Adds zone to date, + UTC */
+  FORMAT_MODIFIER_EPOCH,     /* Print time as seconds since Jan 1 1970 */
+  FORMAT_MODIFIER_MONOTONIC, /* Print cpu time as seconds since start */
+  FORMAT_MODIFIER_UID,       /* Adds uid */
+  FORMAT_MODIFIER_DESCRIPT,  /* Adds descriptive */
+  /* private, undocumented */
+  FORMAT_MODIFIER_TIME_NSEC, /* switches from msec to nsec time precision */
+} AndroidLogPrintFormat;
+
+typedef struct AndroidLogFormat_t AndroidLogFormat;
+
+typedef struct AndroidLogEntry_t {
+  time_t tv_sec;
+  long tv_nsec;
+  android_LogPriority priority;
+  int32_t uid;
+  int32_t pid;
+  int32_t tid;
+  const char* tag;
+  size_t tagLen;
+  size_t messageLen;
+  const char* message;
+} AndroidLogEntry;
+
+AndroidLogFormat* android_log_format_new();
+
+void android_log_format_free(AndroidLogFormat* p_format);
+
+/* currently returns 0 if format is a modifier, 1 if not */
+int android_log_setPrintFormat(AndroidLogFormat* p_format,
+                               AndroidLogPrintFormat format);
+
+/**
+ * Returns FORMAT_OFF on invalid string
+ */
+AndroidLogPrintFormat android_log_formatFromString(const char* s);
+
+/**
+ * filterExpression: a single filter expression
+ * eg "AT:d"
+ *
+ * returns 0 on success and -1 on invalid expression
+ *
+ * Assumes single threaded execution
+ *
+ */
+
+int android_log_addFilterRule(AndroidLogFormat* p_format,
+                              const char* filterExpression);
+
+/**
+ * filterString: a whitespace-separated set of filter expressions
+ * eg "AT:d *:i"
+ *
+ * returns 0 on success and -1 on invalid expression
+ *
+ * Assumes single threaded execution
+ *
+ */
+
+int android_log_addFilterString(AndroidLogFormat* p_format,
+                                const char* filterString);
+
+/**
+ * returns 1 if this log line should be printed based on its priority
+ * and tag, and 0 if it should not
+ */
+int android_log_shouldPrintLine(AndroidLogFormat* p_format, const char* tag,
+                                android_LogPriority pri);
+
+/**
+ * Splits a wire-format buffer into an AndroidLogEntry
+ * entry allocated by caller. Pointers will point directly into buf
+ *
+ * Returns 0 on success and -1 on invalid wire format (entry will be
+ * in unspecified state)
+ */
+int android_log_processLogBuffer(struct logger_entry* buf,
+                                 AndroidLogEntry* entry);
+
+/**
+ * Like android_log_processLogBuffer, but for binary logs.
+ *
+ * If "map" is non-NULL, it will be used to convert the log tag number
+ * into a string.
+ */
+int android_log_processBinaryLogBuffer(struct logger_entry* buf,
+                                       AndroidLogEntry* entry,
+                                       const EventTagMap* map, char* messageBuf,
+                                       int messageBufLen);
+
+/**
+ * Formats a log message into a buffer
+ *
+ * Uses defaultBuffer if it can, otherwise malloc()'s a new buffer
+ * If return value != defaultBuffer, caller must call free()
+ * Returns NULL on malloc error
+ */
+
+char* android_log_formatLogLine(AndroidLogFormat* p_format, char* defaultBuffer,
+                                size_t defaultBufferSize,
+                                const AndroidLogEntry* p_line,
+                                size_t* p_outLength);
+
+/**
+ * Either print or do not print log line, based on filter
+ *
+ * Assumes single threaded execution
+ *
+ */
+int android_log_printLogLine(AndroidLogFormat* p_format, int fd,
+                             const AndroidLogEntry* entry);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/liblog/include/private/android_logger.h b/liblog/include/private/android_logger.h
new file mode 100644
index 0000000..166f387
--- /dev/null
+++ b/liblog/include/private/android_logger.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+/* This file is used to define the internal protocol for the Android Logger */
+
+#pragma once
+
+/* Android private interfaces */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#ifdef __cplusplus
+#include <string>
+#endif
+
+#include <log/log.h>
+#include <log/log_event_list.h>
+
+#define LOGGER_MAGIC 'l'
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/* Header Structure to pstore */
+typedef struct __attribute__((__packed__)) {
+  uint8_t magic;
+  uint16_t len;
+  uint16_t uid;
+  uint16_t pid;
+} android_pmsg_log_header_t;
+
+/* Header Structure to logd, and second header for pstore */
+typedef struct __attribute__((__packed__)) {
+  uint8_t id;
+  uint16_t tid;
+  log_time realtime;
+} android_log_header_t;
+
+/* Event Header Structure to logd */
+typedef struct __attribute__((__packed__)) {
+  int32_t tag;  // Little Endian Order
+} android_event_header_t;
+
+// Event payload EVENT_TYPE_LIST
+typedef struct __attribute__((__packed__)) {
+  int8_t type;  // EVENT_TYPE_LIST
+  int8_t element_count;
+} android_event_list_t;
+
+// Event payload EVENT_TYPE_FLOAT
+typedef struct __attribute__((__packed__)) {
+  int8_t type;  // EVENT_TYPE_FLOAT
+  float data;
+} android_event_float_t;
+
+/* Event payload EVENT_TYPE_INT */
+typedef struct __attribute__((__packed__)) {
+  int8_t type;   // EVENT_TYPE_INT
+  int32_t data;  // Little Endian Order
+} android_event_int_t;
+
+/* Event with single EVENT_TYPE_INT */
+typedef struct __attribute__((__packed__)) {
+  android_event_header_t header;
+  android_event_int_t payload;
+} android_log_event_int_t;
+
+/* Event payload EVENT_TYPE_LONG */
+typedef struct __attribute__((__packed__)) {
+  int8_t type;   // EVENT_TYPE_LONG
+  int64_t data;  // Little Endian Order
+} android_event_long_t;
+
+/* Event with single EVENT_TYPE_LONG */
+typedef struct __attribute__((__packed__)) {
+  android_event_header_t header;
+  android_event_long_t payload;
+} android_log_event_long_t;
+
+/*
+ * Event payload EVENT_TYPE_STRING
+ *
+ * Danger: do not embed this structure into another structure.
+ * This structure uses a flexible array member, and when
+ * compiled using g++, __builtin_object_size(data, 1) returns
+ * a bad value. This is possibly a g++ bug, or a bug due to
+ * the fact that flexible array members are not supported
+ * in C++.
+ * http://stackoverflow.com/questions/4412749/are-flexible-array-members-valid-in-c
+ */
+
+typedef struct __attribute__((__packed__)) {
+  int8_t type;     // EVENT_TYPE_STRING;
+  int32_t length;  // Little Endian Order
+  char data[];
+} android_event_string_t;
+
+/* Event with single EVENT_TYPE_STRING */
+typedef struct __attribute__((__packed__)) {
+  android_event_header_t header;
+  int8_t type;     // EVENT_TYPE_STRING;
+  int32_t length;  // Little Endian Order
+  char data[];
+} android_log_event_string_t;
+
+#define ANDROID_LOG_PMSG_FILE_MAX_SEQUENCE 256 /* 1MB file */
+#define ANDROID_LOG_PMSG_FILE_SEQUENCE 1000
+
+ssize_t __android_log_pmsg_file_write(log_id_t logId, char prio,
+                                      const char* filename, const char* buf,
+                                      size_t len);
+
+#define LOG_ID_ANY ((log_id_t)-1)
+#define ANDROID_LOG_ANY ANDROID_LOG_UNKNOWN
+
+/* first 5 arguments match __android_log_msg_file_write, a cast is safe */
+typedef ssize_t (*__android_log_pmsg_file_read_fn)(log_id_t logId, char prio,
+                                                   const char* filename,
+                                                   const char* buf, size_t len,
+                                                   void* arg);
+
+ssize_t __android_log_pmsg_file_read(log_id_t logId, char prio,
+                                     const char* prefix,
+                                     __android_log_pmsg_file_read_fn fn,
+                                     void* arg);
+
+int __android_log_security_bwrite(int32_t tag, const void* payload, size_t len);
+int __android_log_security_bswrite(int32_t tag, const char* payload);
+int __android_log_security(); /* Device Owner is present */
+
+/* Retrieve the composed event buffer */
+int android_log_write_list_buffer(android_log_context ctx, const char** msg);
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/liblog/include_vndk/android b/liblog/include_vndk/android
new file mode 120000
index 0000000..a3c0320
--- /dev/null
+++ b/liblog/include_vndk/android
@@ -0,0 +1 @@
+../include/android
\ No newline at end of file
diff --git a/liblog/include_vndk/log/log.h b/liblog/include_vndk/log/log.h
new file mode 100644
index 0000000..fee18c6
--- /dev/null
+++ b/liblog/include_vndk/log/log.h
@@ -0,0 +1,27 @@
+/*Special log.h file for VNDK linking modules*/
+
+#pragma once
+
+/* Historically vendors have depended on these headers being included. */
+#include <fcntl.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#include <android/log.h>
+#include <log/log_id.h>
+#include <log/log_main.h>
+#include <log/log_radio.h>
+#include <log/log_read.h>
+#include <log/log_safetynet.h>
+#include <log/log_system.h>
+#include <log/log_time.h>
+
+/*
+ * LOG_TAG is the local tag used for the following simplified
+ * logging macros.  You can change this preprocessor definition
+ * before using the other macros to change the tag.
+ */
+
+#ifndef LOG_TAG
+#define LOG_TAG NULL
+#endif
diff --git a/liblog/include_vndk/log/log_event_list.h b/liblog/include_vndk/log/log_event_list.h
new file mode 100644
index 0000000..1f3dd37
--- /dev/null
+++ b/liblog/include_vndk/log/log_event_list.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2005-2016 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.
+ */
+
+/* Special log_event_list.h file for VNDK linking modules */
+
+#ifndef _LIBS_LOG_EVENT_LIST_H
+#define _LIBS_LOG_EVENT_LIST_H
+
+#include <stdint.h>
+
+#include <log/log_id.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * The opaque context used to manipulate lists of events.
+ */
+#ifndef __android_log_context_defined
+#define __android_log_context_defined
+typedef struct android_log_context_internal* android_log_context;
+#endif
+
+/*
+ * Creates a context associated with an event tag to write elements to
+ * the list of events.
+ */
+android_log_context create_android_logger(uint32_t tag);
+
+/* All lists must be braced by a begin and end call */
+/*
+ * NB: If the first level braces are missing when specifying multiple
+ *     elements, we will manufacturer a list to embrace it for your API
+ *     convenience. For a single element, it will remain solitary.
+ */
+int android_log_write_list_begin(android_log_context ctx);
+int android_log_write_list_end(android_log_context ctx);
+
+int android_log_write_int32(android_log_context ctx, int32_t value);
+int android_log_write_int64(android_log_context ctx, int64_t value);
+int android_log_write_string8(android_log_context ctx, const char* value);
+int android_log_write_string8_len(android_log_context ctx, const char* value,
+                                  size_t maxlen);
+int android_log_write_float32(android_log_context ctx, float value);
+
+/* Submit the composed list context to the specified logger id */
+/* NB: LOG_ID_EVENTS and LOG_ID_SECURITY only valid binary buffers */
+int android_log_write_list(android_log_context ctx, log_id_t id);
+
+/* Reset writer context */
+int android_log_reset(android_log_context ctx);
+
+/* Reset reader context */
+int android_log_parser_reset(android_log_context ctx,
+                             const char* msg, size_t len);
+
+/* Finished with reader or writer context */
+int android_log_destroy(android_log_context* ctx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBS_LOG_EVENT_LIST_H */
diff --git a/liblog/include_vndk/log/log_id.h b/liblog/include_vndk/log/log_id.h
new file mode 120000
index 0000000..dce92b9
--- /dev/null
+++ b/liblog/include_vndk/log/log_id.h
@@ -0,0 +1 @@
+../../include/log/log_id.h
\ No newline at end of file
diff --git a/liblog/include_vndk/log/log_main.h b/liblog/include_vndk/log/log_main.h
new file mode 120000
index 0000000..f2ec018
--- /dev/null
+++ b/liblog/include_vndk/log/log_main.h
@@ -0,0 +1 @@
+../../include/log/log_main.h
\ No newline at end of file
diff --git a/liblog/include_vndk/log/log_properties.h b/liblog/include_vndk/log/log_properties.h
new file mode 120000
index 0000000..bbec426
--- /dev/null
+++ b/liblog/include_vndk/log/log_properties.h
@@ -0,0 +1 @@
+../../include/log/log_properties.h
\ No newline at end of file
diff --git a/liblog/include_vndk/log/log_radio.h b/liblog/include_vndk/log/log_radio.h
new file mode 120000
index 0000000..1e12b32
--- /dev/null
+++ b/liblog/include_vndk/log/log_radio.h
@@ -0,0 +1 @@
+../../include/log/log_radio.h
\ No newline at end of file
diff --git a/liblog/include_vndk/log/log_read.h b/liblog/include_vndk/log/log_read.h
new file mode 120000
index 0000000..01de8b9
--- /dev/null
+++ b/liblog/include_vndk/log/log_read.h
@@ -0,0 +1 @@
+../../include/log/log_read.h
\ No newline at end of file
diff --git a/liblog/include_vndk/log/log_safetynet.h b/liblog/include_vndk/log/log_safetynet.h
new file mode 120000
index 0000000..a4614e7
--- /dev/null
+++ b/liblog/include_vndk/log/log_safetynet.h
@@ -0,0 +1 @@
+../../include/log/log_safetynet.h
\ No newline at end of file
diff --git a/liblog/include_vndk/log/log_system.h b/liblog/include_vndk/log/log_system.h
new file mode 120000
index 0000000..d0d3904
--- /dev/null
+++ b/liblog/include_vndk/log/log_system.h
@@ -0,0 +1 @@
+../../include/log/log_system.h
\ No newline at end of file
diff --git a/liblog/include_vndk/log/log_time.h b/liblog/include_vndk/log/log_time.h
new file mode 100644
index 0000000..5a09959
--- /dev/null
+++ b/liblog/include_vndk/log/log_time.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2005-2017 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 _LIBS_LOG_LOG_TIME_H
+#define _LIBS_LOG_LOG_TIME_H
+
+#include <stdint.h>
+
+/* struct log_time is a wire-format variant of struct timespec */
+#ifndef NS_PER_SEC
+#define NS_PER_SEC 1000000000ULL
+#endif
+#ifndef US_PER_SEC
+#define US_PER_SEC 1000000ULL
+#endif
+#ifndef MS_PER_SEC
+#define MS_PER_SEC 1000ULL
+#endif
+
+#ifndef __struct_log_time_defined
+#define __struct_log_time_defined
+
+#define LOG_TIME_SEC(t) ((t)->tv_sec)
+/* next power of two after NS_PER_SEC */
+#define LOG_TIME_NSEC(t) ((t)->tv_nsec & (UINT32_MAX >> 2))
+
+typedef struct log_time {
+  uint32_t tv_sec;
+  uint32_t tv_nsec;
+} __attribute__((__packed__)) log_time;
+
+#endif
+
+#endif /* _LIBS_LOG_LOG_TIME_H */
diff --git a/liblog/liblog.map.txt b/liblog/liblog.map.txt
new file mode 100644
index 0000000..f8d5ef0
--- /dev/null
+++ b/liblog/liblog.map.txt
@@ -0,0 +1,93 @@
+LIBLOG {
+  global:
+    android_name_to_log_id; # apex llndk
+    android_log_id_to_name; # llndk
+    __android_log_assert;
+    __android_log_buf_print;
+    __android_log_buf_write;
+    __android_log_print;
+    __android_log_vprint;
+    __android_log_write;
+  local:
+    *;
+};
+
+LIBLOG_L {
+  global:
+    android_logger_clear; # llndk
+    android_logger_get_id; # llndk
+    android_logger_get_log_readable_size; # llndk
+    android_logger_get_log_version; # llndk
+    android_logger_get_log_size; # llndk
+    android_logger_list_alloc; # apex llndk
+    android_logger_list_alloc_time; # apex llndk
+    android_logger_list_free; # apex llndk
+    android_logger_list_open; # apex llndk
+    android_logger_list_read; # apex llndk
+    android_logger_open; # apex llndk
+    android_logger_set_log_size; # llndk
+};
+
+LIBLOG_M {
+  global:
+    android_logger_get_prune_list; # llndk
+    android_logger_set_prune_list; # llndk
+    android_logger_get_statistics; # llndk
+    __android_log_error_write; # apex llndk
+    __android_log_is_loggable;
+    create_android_logger; # apex llndk
+    android_log_destroy; # apex llndk
+    android_log_write_list_begin; # apex llndk
+    android_log_write_list_end; # apex llndk
+    android_log_write_int32; # apex llndk
+    android_log_write_int64; # apex llndk
+    android_log_write_string8; # apex llndk
+    android_log_write_string8_len; # apex llndk
+    android_log_write_float32; # apex llndk
+    android_log_write_list; # apex llndk
+
+};
+
+LIBLOG_O {
+  global:
+    __android_log_is_loggable_len;
+    __android_log_is_debuggable; # apex llndk
+};
+
+LIBLOG_Q { # introduced=29
+  global:
+    __android_log_bswrite; # apex
+    __android_log_btwrite; # apex
+    __android_log_bwrite; # apex
+    __android_log_close; # apex
+    __android_log_security; # apex
+    android_log_reset; # llndk
+    android_log_parser_reset; # llndk
+};
+
+LIBLOG_R { # introduced=30
+  global:
+    __android_log_call_aborter;
+    __android_log_default_aborter;
+    __android_log_get_minimum_priority;
+    __android_log_logd_logger;
+    __android_log_security_bswrite; # apex
+    __android_log_set_aborter;
+    __android_log_set_default_tag;
+    __android_log_set_logger;
+    __android_log_set_minimum_priority;
+    __android_log_stderr_logger;
+    __android_log_write_log_message;
+};
+
+LIBLOG_PRIVATE {
+  global:
+    __android_log_pmsg_file_read;
+    __android_log_pmsg_file_write;
+    android_openEventTagMap;
+    android_log_processBinaryLogBuffer;
+    android_log_processLogBuffer;
+    android_log_read_next;
+    android_log_write_list_buffer;
+    create_android_log_parser;
+};
diff --git a/liblog/log_event_list.cpp b/liblog/log_event_list.cpp
new file mode 100644
index 0000000..cb70d48
--- /dev/null
+++ b/liblog/log_event_list.cpp
@@ -0,0 +1,543 @@
+/*
+ * Copyright (C) 2016 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 <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <log/log_event_list.h>
+#include <private/android_logger.h>
+
+#define MAX_EVENT_PAYLOAD (LOGGER_ENTRY_MAX_PAYLOAD - sizeof(int32_t))
+
+enum ReadWriteFlag {
+  kAndroidLoggerRead = 1,
+  kAndroidLoggerWrite = 2,
+};
+
+struct android_log_context_internal {
+  uint32_t tag;
+  unsigned pos;                                    /* Read/write position into buffer */
+  unsigned count[ANDROID_MAX_LIST_NEST_DEPTH + 1]; /* Number of elements   */
+  unsigned list[ANDROID_MAX_LIST_NEST_DEPTH + 1];  /* pos for list counter */
+  unsigned list_nest_depth;
+  unsigned len; /* Length or raw buffer. */
+  bool overflow;
+  bool list_stop; /* next call decrement list_nest_depth and issue a stop */
+  ReadWriteFlag read_write_flag;
+  uint8_t storage[LOGGER_ENTRY_MAX_PAYLOAD];
+};
+
+static void init_context(android_log_context_internal* context, uint32_t tag) {
+  context->tag = tag;
+  context->read_write_flag = kAndroidLoggerWrite;
+  size_t needed = sizeof(android_event_list_t);
+  if ((context->pos + needed) > MAX_EVENT_PAYLOAD) {
+    context->overflow = true;
+  }
+  /* Everything is a list */
+  context->storage[context->pos + 0] = EVENT_TYPE_LIST;
+  context->list[0] = context->pos + 1;
+  context->pos += needed;
+}
+
+static void init_parser_context(android_log_context_internal* context, const char* msg,
+                                size_t len) {
+  len = (len <= MAX_EVENT_PAYLOAD) ? len : MAX_EVENT_PAYLOAD;
+  context->len = len;
+  memcpy(context->storage, msg, len);
+  context->read_write_flag = kAndroidLoggerRead;
+}
+
+android_log_context create_android_logger(uint32_t tag) {
+  android_log_context_internal* context;
+
+  context =
+      static_cast<android_log_context_internal*>(calloc(1, sizeof(android_log_context_internal)));
+  if (!context) {
+    return NULL;
+  }
+  init_context(context, tag);
+
+  return (android_log_context)context;
+}
+
+android_log_context create_android_log_parser(const char* msg, size_t len) {
+  android_log_context_internal* context;
+
+  context =
+      static_cast<android_log_context_internal*>(calloc(1, sizeof(android_log_context_internal)));
+  if (!context) {
+    return NULL;
+  }
+  init_parser_context(context, msg, len);
+
+  return (android_log_context)context;
+}
+
+int android_log_destroy(android_log_context* ctx) {
+  android_log_context_internal* context;
+
+  context = (android_log_context_internal*)*ctx;
+  if (!context) {
+    return -EBADF;
+  }
+  memset(context, 0, sizeof(*context));
+  free(context);
+  *ctx = NULL;
+  return 0;
+}
+
+int android_log_reset(android_log_context context) {
+  uint32_t tag;
+
+  if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+    return -EBADF;
+  }
+
+  tag = context->tag;
+  memset(context, 0, sizeof(*context));
+  init_context(context, tag);
+
+  return 0;
+}
+
+int android_log_parser_reset(android_log_context context, const char* msg, size_t len) {
+  if (!context || (kAndroidLoggerRead != context->read_write_flag)) {
+    return -EBADF;
+  }
+
+  memset(context, 0, sizeof(*context));
+  init_parser_context(context, msg, len);
+
+  return 0;
+}
+
+int android_log_write_list_begin(android_log_context context) {
+  if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+    return -EBADF;
+  }
+  if (context->list_nest_depth > ANDROID_MAX_LIST_NEST_DEPTH) {
+    context->overflow = true;
+    return -EOVERFLOW;
+  }
+  size_t needed = sizeof(android_event_list_t);
+  if ((context->pos + needed) > MAX_EVENT_PAYLOAD) {
+    context->overflow = true;
+    return -EIO;
+  }
+  context->count[context->list_nest_depth]++;
+  context->list_nest_depth++;
+  if (context->list_nest_depth > ANDROID_MAX_LIST_NEST_DEPTH) {
+    context->overflow = true;
+    return -EOVERFLOW;
+  }
+  if (context->overflow) {
+    return -EIO;
+  }
+  auto* event_list = reinterpret_cast<android_event_list_t*>(&context->storage[context->pos]);
+  event_list->type = EVENT_TYPE_LIST;
+  event_list->element_count = 0;
+  context->list[context->list_nest_depth] = context->pos + 1;
+  context->count[context->list_nest_depth] = 0;
+  context->pos += needed;
+  return 0;
+}
+
+int android_log_write_int32(android_log_context context, int32_t value) {
+  if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+    return -EBADF;
+  }
+  if (context->overflow) {
+    return -EIO;
+  }
+  size_t needed = sizeof(android_event_int_t);
+  if ((context->pos + needed) > MAX_EVENT_PAYLOAD) {
+    context->overflow = true;
+    return -EIO;
+  }
+  context->count[context->list_nest_depth]++;
+  auto* event_int = reinterpret_cast<android_event_int_t*>(&context->storage[context->pos]);
+  event_int->type = EVENT_TYPE_INT;
+  event_int->data = value;
+  context->pos += needed;
+  return 0;
+}
+
+int android_log_write_int64(android_log_context context, int64_t value) {
+  if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+    return -EBADF;
+  }
+  if (context->overflow) {
+    return -EIO;
+  }
+  size_t needed = sizeof(android_event_long_t);
+  if ((context->pos + needed) > MAX_EVENT_PAYLOAD) {
+    context->overflow = true;
+    return -EIO;
+  }
+  context->count[context->list_nest_depth]++;
+  auto* event_long = reinterpret_cast<android_event_long_t*>(&context->storage[context->pos]);
+  event_long->type = EVENT_TYPE_LONG;
+  event_long->data = value;
+  context->pos += needed;
+  return 0;
+}
+
+int android_log_write_string8_len(android_log_context context, const char* value, size_t maxlen) {
+  if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+    return -EBADF;
+  }
+  if (context->overflow) {
+    return -EIO;
+  }
+  if (!value) {
+    value = "";
+  }
+  int32_t len = strnlen(value, maxlen);
+  size_t needed = sizeof(android_event_string_t) + len;
+  if ((context->pos + needed) > MAX_EVENT_PAYLOAD) {
+    /* Truncate string for delivery */
+    len = MAX_EVENT_PAYLOAD - context->pos - 1 - sizeof(int32_t);
+    if (len <= 0) {
+      context->overflow = true;
+      return -EIO;
+    }
+  }
+  context->count[context->list_nest_depth]++;
+  auto* event_string = reinterpret_cast<android_event_string_t*>(&context->storage[context->pos]);
+  event_string->type = EVENT_TYPE_STRING;
+  event_string->length = len;
+  if (len) {
+    memcpy(&event_string->data, value, len);
+  }
+  context->pos += needed;
+  return len;
+}
+
+int android_log_write_string8(android_log_context ctx, const char* value) {
+  return android_log_write_string8_len(ctx, value, MAX_EVENT_PAYLOAD);
+}
+
+int android_log_write_float32(android_log_context context, float value) {
+  if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+    return -EBADF;
+  }
+  if (context->overflow) {
+    return -EIO;
+  }
+  size_t needed = sizeof(android_event_float_t);
+  if ((context->pos + needed) > MAX_EVENT_PAYLOAD) {
+    context->overflow = true;
+    return -EIO;
+  }
+  context->count[context->list_nest_depth]++;
+  auto* event_float = reinterpret_cast<android_event_float_t*>(&context->storage[context->pos]);
+  event_float->type = EVENT_TYPE_FLOAT;
+  event_float->data = value;
+  context->pos += needed;
+  return 0;
+}
+
+int android_log_write_list_end(android_log_context context) {
+  if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+    return -EBADF;
+  }
+  if (context->list_nest_depth > ANDROID_MAX_LIST_NEST_DEPTH) {
+    context->overflow = true;
+    context->list_nest_depth--;
+    return -EOVERFLOW;
+  }
+  if (!context->list_nest_depth) {
+    context->overflow = true;
+    return -EOVERFLOW;
+  }
+  if (context->list[context->list_nest_depth] <= 0) {
+    context->list_nest_depth--;
+    context->overflow = true;
+    return -EOVERFLOW;
+  }
+  context->storage[context->list[context->list_nest_depth]] =
+      context->count[context->list_nest_depth];
+  context->list_nest_depth--;
+  return 0;
+}
+
+/*
+ * Logs the list of elements to the event log.
+ */
+int android_log_write_list(android_log_context context, log_id_t id) {
+  const char* msg;
+  ssize_t len;
+
+  if ((id != LOG_ID_EVENTS) && (id != LOG_ID_SECURITY) && (id != LOG_ID_STATS)) {
+    return -EINVAL;
+  }
+
+  if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+    return -EBADF;
+  }
+  if (context->list_nest_depth) {
+    return -EIO;
+  }
+  /* NB: if there was overflow, then log is truncated. Nothing reported */
+  context->storage[1] = context->count[0];
+  len = context->len = context->pos;
+  msg = (const char*)context->storage;
+  /* it's not a list */
+  if (context->count[0] <= 1) {
+    len -= sizeof(uint8_t) + sizeof(uint8_t);
+    if (len < 0) {
+      len = 0;
+    }
+    msg += sizeof(uint8_t) + sizeof(uint8_t);
+  }
+  return (id == LOG_ID_EVENTS)
+             ? __android_log_bwrite(context->tag, msg, len)
+             : ((id == LOG_ID_STATS) ? __android_log_stats_bwrite(context->tag, msg, len)
+                                     : __android_log_security_bwrite(context->tag, msg, len));
+}
+
+int android_log_write_list_buffer(android_log_context context, const char** buffer) {
+  const char* msg;
+  ssize_t len;
+
+  if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+    return -EBADF;
+  }
+  if (context->list_nest_depth) {
+    return -EIO;
+  }
+  if (buffer == NULL) {
+    return -EFAULT;
+  }
+  /* NB: if there was overflow, then log is truncated. Nothing reported */
+  context->storage[1] = context->count[0];
+  len = context->len = context->pos;
+  msg = (const char*)context->storage;
+  /* it's not a list */
+  if (context->count[0] <= 1) {
+    len -= sizeof(uint8_t) + sizeof(uint8_t);
+    if (len < 0) {
+      len = 0;
+    }
+    msg += sizeof(uint8_t) + sizeof(uint8_t);
+  }
+  *buffer = msg;
+  return len;
+}
+
+/*
+ * Gets the next element. Parsing errors result in an EVENT_TYPE_UNKNOWN type.
+ * If there is nothing to process, the complete field is set to non-zero. If
+ * an EVENT_TYPE_UNKNOWN type is returned once, and the caller does not check
+ * this and continues to call this function, the behavior is undefined
+ * (although it won't crash).
+ */
+static android_log_list_element android_log_read_next_internal(android_log_context context,
+                                                               int peek) {
+  android_log_list_element elem;
+  unsigned pos;
+
+  memset(&elem, 0, sizeof(elem));
+
+  /* Nothing to parse from this context, so return complete. */
+  if (!context || (kAndroidLoggerRead != context->read_write_flag) ||
+      (context->list_nest_depth > ANDROID_MAX_LIST_NEST_DEPTH) ||
+      (context->count[context->list_nest_depth] >=
+       (MAX_EVENT_PAYLOAD / (sizeof(uint8_t) + sizeof(uint8_t))))) {
+    elem.type = EVENT_TYPE_UNKNOWN;
+    if (context &&
+        (context->list_stop || ((context->list_nest_depth <= ANDROID_MAX_LIST_NEST_DEPTH) &&
+                                !context->count[context->list_nest_depth]))) {
+      elem.type = EVENT_TYPE_LIST_STOP;
+    }
+    elem.complete = true;
+    return elem;
+  }
+
+  /*
+   * Use a different variable to update the position in case this
+   * operation is a "peek".
+   */
+  pos = context->pos;
+  if (context->list_stop) {
+    elem.type = EVENT_TYPE_LIST_STOP;
+    elem.complete = !context->count[0] && (!context->list_nest_depth ||
+                                           ((context->list_nest_depth == 1) && !context->count[1]));
+    if (!peek) {
+      /* Suck in superfluous stop */
+      if (context->storage[pos] == EVENT_TYPE_LIST_STOP) {
+        context->pos = pos + 1;
+      }
+      if (context->list_nest_depth) {
+        --context->list_nest_depth;
+        if (context->count[context->list_nest_depth]) {
+          context->list_stop = false;
+        }
+      } else {
+        context->list_stop = false;
+      }
+    }
+    return elem;
+  }
+  if ((pos + 1) > context->len) {
+    elem.type = EVENT_TYPE_UNKNOWN;
+    elem.complete = true;
+    return elem;
+  }
+
+  elem.type = static_cast<AndroidEventLogType>(context->storage[pos]);
+  switch ((int)elem.type) {
+    case EVENT_TYPE_FLOAT:
+    /* Rely on union to translate elem.data.int32 into elem.data.float32 */
+    /* FALLTHRU */
+    case EVENT_TYPE_INT: {
+      elem.len = sizeof(int32_t);
+      if ((pos + sizeof(android_event_int_t)) > context->len) {
+        elem.type = EVENT_TYPE_UNKNOWN;
+        return elem;
+      }
+
+      auto* event_int = reinterpret_cast<android_event_int_t*>(&context->storage[pos]);
+      pos += sizeof(android_event_int_t);
+      elem.data.int32 = event_int->data;
+      /* common tangeable object suffix */
+      elem.complete = !context->list_nest_depth && !context->count[0];
+      if (!peek) {
+        if (!context->count[context->list_nest_depth] ||
+            !--(context->count[context->list_nest_depth])) {
+          context->list_stop = true;
+        }
+        context->pos = pos;
+      }
+      return elem;
+    }
+
+    case EVENT_TYPE_LONG: {
+      elem.len = sizeof(int64_t);
+      if ((pos + sizeof(android_event_long_t)) > context->len) {
+        elem.type = EVENT_TYPE_UNKNOWN;
+        return elem;
+      }
+
+      auto* event_long = reinterpret_cast<android_event_long_t*>(&context->storage[pos]);
+      pos += sizeof(android_event_long_t);
+      elem.data.int64 = event_long->data;
+      /* common tangeable object suffix */
+      elem.complete = !context->list_nest_depth && !context->count[0];
+      if (!peek) {
+        if (!context->count[context->list_nest_depth] ||
+            !--(context->count[context->list_nest_depth])) {
+          context->list_stop = true;
+        }
+        context->pos = pos;
+      }
+      return elem;
+    }
+
+    case EVENT_TYPE_STRING: {
+      if ((pos + sizeof(android_event_string_t)) > context->len) {
+        elem.type = EVENT_TYPE_UNKNOWN;
+        elem.complete = true;
+        return elem;
+      }
+      auto* event_string = reinterpret_cast<android_event_string_t*>(&context->storage[pos]);
+      pos += sizeof(android_event_string_t);
+      // Wire format is int32_t, but elem.len is uint16_t...
+      if (event_string->length >= UINT16_MAX) {
+        elem.type = EVENT_TYPE_UNKNOWN;
+        return elem;
+      }
+      elem.len = event_string->length;
+      if ((pos + elem.len) > context->len) {
+        elem.len = context->len - pos; /* truncate string */
+        elem.complete = true;
+        if (!elem.len) {
+          elem.type = EVENT_TYPE_UNKNOWN;
+          return elem;
+        }
+      }
+      elem.data.string = event_string->data;
+      /* common tangeable object suffix */
+      pos += elem.len;
+      elem.complete = !context->list_nest_depth && !context->count[0];
+      if (!peek) {
+        if (!context->count[context->list_nest_depth] ||
+            !--(context->count[context->list_nest_depth])) {
+          context->list_stop = true;
+        }
+        context->pos = pos;
+      }
+      return elem;
+    }
+
+    case EVENT_TYPE_LIST: {
+      if ((pos + sizeof(android_event_list_t)) > context->len) {
+        elem.type = EVENT_TYPE_UNKNOWN;
+        elem.complete = true;
+        return elem;
+      }
+      auto* event_list = reinterpret_cast<android_event_list_t*>(&context->storage[pos]);
+      pos += sizeof(android_event_list_t);
+      elem.complete = context->list_nest_depth >= ANDROID_MAX_LIST_NEST_DEPTH;
+      if (peek) {
+        return elem;
+      }
+      if (context->count[context->list_nest_depth]) {
+        context->count[context->list_nest_depth]--;
+      }
+      context->list_stop = event_list->element_count == 0;
+      context->list_nest_depth++;
+      if (context->list_nest_depth <= ANDROID_MAX_LIST_NEST_DEPTH) {
+        context->count[context->list_nest_depth] = event_list->element_count;
+      }
+      context->pos = pos;
+      return elem;
+    }
+
+    case EVENT_TYPE_LIST_STOP: /* Suprise Newline terminates lists. */
+      pos++;
+      if (!peek) {
+        context->pos = pos;
+      }
+      elem.type = EVENT_TYPE_UNKNOWN;
+      elem.complete = !context->list_nest_depth;
+      if (context->list_nest_depth > 0) {
+        elem.type = EVENT_TYPE_LIST_STOP;
+        if (!peek) {
+          context->list_nest_depth--;
+        }
+      }
+      return elem;
+
+    default:
+      elem.type = EVENT_TYPE_UNKNOWN;
+      return elem;
+  }
+}
+
+android_log_list_element android_log_read_next(android_log_context ctx) {
+  return android_log_read_next_internal(ctx, 0);
+}
+
+android_log_list_element android_log_peek_next(android_log_context ctx) {
+  return android_log_read_next_internal(ctx, 1);
+}
diff --git a/liblog/log_event_write.cpp b/liblog/log_event_write.cpp
new file mode 100644
index 0000000..39afd0c
--- /dev/null
+++ b/liblog/log_event_write.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 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 <errno.h>
+#include <stdint.h>
+
+#include <log/log.h>
+#include <log/log_event_list.h>
+
+#define MAX_SUBTAG_LEN 32
+
+int __android_log_error_write(int tag, const char* subTag, int32_t uid, const char* data,
+                              uint32_t dataLen) {
+  int ret = -EINVAL;
+
+  if (subTag && (data || !dataLen)) {
+    android_log_context ctx = create_android_logger(tag);
+
+    ret = -ENOMEM;
+    if (ctx) {
+      ret = android_log_write_string8_len(ctx, subTag, MAX_SUBTAG_LEN);
+      if (ret >= 0) {
+        ret = android_log_write_int32(ctx, uid);
+        if (ret >= 0) {
+          ret = android_log_write_string8_len(ctx, data, dataLen);
+          if (ret >= 0) {
+            ret = android_log_write_list(ctx, LOG_ID_EVENTS);
+          }
+        }
+      }
+      android_log_destroy(&ctx);
+    }
+  }
+  return ret;
+}
diff --git a/liblog/log_is_loggable.c b/liblog/log_is_loggable.c
deleted file mode 100644
index 7a8e33f..0000000
--- a/liblog/log_is_loggable.c
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
-** Copyright 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 <pthread.h>
-#include <stdlib.h>
-#include <string.h>
-#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
-#include <sys/_system_properties.h>
-
-#include <android/log.h>
-
-struct cache {
-    const prop_info *pinfo;
-    uint32_t serial;
-    char c;
-};
-
-static void refresh_cache(struct cache *cache, const char *key)
-{
-    uint32_t serial;
-    char buf[PROP_VALUE_MAX];
-
-    if (!cache->pinfo) {
-        cache->pinfo = __system_property_find(key);
-        if (!cache->pinfo) {
-            return;
-        }
-    }
-    serial = __system_property_serial(cache->pinfo);
-    if (serial == cache->serial) {
-        return;
-    }
-    cache->serial = serial;
-    __system_property_read(cache->pinfo, 0, buf);
-    cache->c = buf[0];
-}
-
-static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
-
-static int __android_log_level(const char *tag, int def)
-{
-    /* sizeof() is used on this array below */
-    static const char log_namespace[] = "persist.log.tag.";
-    static const size_t base_offset = 8; /* skip "persist." */
-    /* calculate the size of our key temporary buffer */
-    const size_t taglen = (tag && *tag) ? strlen(tag) : 0;
-    /* sizeof(log_namespace) = strlen(log_namespace) + 1 */
-    char key[sizeof(log_namespace) + taglen];
-    char *kp;
-    size_t i;
-    char c = 0;
-    /*
-     * Single layer cache of four properties. Priorities are:
-     *    log.tag.<tag>
-     *    persist.log.tag.<tag>
-     *    log.tag
-     *    persist.log.tag
-     * Where the missing tag matches all tags and becomes the
-     * system global default. We do not support ro.log.tag* .
-     */
-    static char *last_tag;
-    static uint32_t global_serial;
-    uint32_t current_global_serial;
-    static struct cache tag_cache[2] = {
-        { NULL, -1, 0 },
-        { NULL, -1, 0 }
-    };
-    static struct cache global_cache[2] = {
-        { NULL, -1, 0 },
-        { NULL, -1, 0 }
-    };
-
-    strcpy(key, log_namespace);
-
-    pthread_mutex_lock(&lock);
-
-    current_global_serial = __system_property_area_serial();
-
-    if (taglen) {
-        uint32_t current_local_serial = current_global_serial;
-
-        if (!last_tag || strcmp(last_tag, tag)) {
-            /* invalidate log.tag.<tag> cache */
-            for(i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) {
-                tag_cache[i].pinfo = NULL;
-                tag_cache[i].serial = -1;
-                tag_cache[i].c = '\0';
-            }
-            free(last_tag);
-            last_tag = NULL;
-            current_global_serial = -1;
-        }
-        if (!last_tag) {
-            last_tag = strdup(tag);
-        }
-        strcpy(key + sizeof(log_namespace) - 1, tag);
-
-        kp = key;
-        for(i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) {
-            if (current_local_serial != global_serial) {
-                refresh_cache(&tag_cache[i], kp);
-            }
-
-            if (tag_cache[i].c) {
-                c = tag_cache[i].c;
-                break;
-            }
-
-            kp = key + base_offset;
-        }
-    }
-
-    switch (toupper(c)) { /* if invalid, resort to global */
-    case 'V':
-    case 'D':
-    case 'I':
-    case 'W':
-    case 'E':
-    case 'F': /* Not officially supported */
-    case 'A':
-    case 'S':
-        break;
-    default:
-        /* clear '.' after log.tag */
-        key[sizeof(log_namespace) - 2] = '\0';
-
-        kp = key;
-        for(i = 0; i < (sizeof(global_cache) / sizeof(global_cache[0])); ++i) {
-            if (current_global_serial != global_serial) {
-                refresh_cache(&global_cache[i], kp);
-            }
-
-            if (global_cache[i].c) {
-                c = global_cache[i].c;
-                break;
-            }
-
-            kp = key + base_offset;
-        }
-        break;
-    }
-
-    global_serial = current_global_serial;
-
-    pthread_mutex_unlock(&lock);
-
-    switch (toupper(c)) {
-    case 'V': return ANDROID_LOG_VERBOSE;
-    case 'D': return ANDROID_LOG_DEBUG;
-    case 'I': return ANDROID_LOG_INFO;
-    case 'W': return ANDROID_LOG_WARN;
-    case 'E': return ANDROID_LOG_ERROR;
-    case 'F': /* FALLTHRU */ /* Not officially supported */
-    case 'A': return ANDROID_LOG_FATAL;
-    case 'S': return -1; /* ANDROID_LOG_SUPPRESS */
-    }
-    return def;
-}
-
-int __android_log_is_loggable(int prio, const char *tag, int def)
-{
-    int logLevel = __android_log_level(tag, def);
-    return logLevel >= 0 && prio >= logLevel;
-}
diff --git a/liblog/log_read.c b/liblog/log_read.c
deleted file mode 100644
index 9c4af30..0000000
--- a/liblog/log_read.c
+++ /dev/null
@@ -1,900 +0,0 @@
-/*
-** Copyright 2013-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 <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <poll.h>
-#include <signal.h>
-#include <stdbool.h>
-#include <stddef.h>
-#define NOMINMAX /* for windows to suppress definition of min in stdlib.h */
-#include <stdlib.h>
-#include <string.h>
-#include <sys/cdefs.h>
-#include <unistd.h>
-
-#include <cutils/list.h>
-#include <cutils/sockets.h>
-#include <log/log.h>
-#include <log/logger.h>
-#include <private/android_filesystem_config.h>
-#include <private/android_logger.h>
-
-/* branchless on many architectures. */
-#define min(x,y) ((y) ^ (((x) ^ (y)) & -((x) < (y))))
-
-#if (defined(USE_MINGW) || defined(HAVE_WINSOCK))
-#define WEAK static
-#else
-#define WEAK __attribute__((weak))
-#endif
-#ifndef __unused
-#define __unused __attribute__((unused))
-#endif
-
-/* Private copy of ../libcutils/socket_local_client.c prevent library loops */
-
-#ifdef HAVE_WINSOCK
-
-int WEAK socket_local_client(const char *name, int namespaceId, int type)
-{
-    errno = ENOSYS;
-    return -ENOSYS;
-}
-
-#else /* !HAVE_WINSOCK */
-
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <sys/select.h>
-#include <sys/types.h>
-
-/* Private copy of ../libcutils/socket_local.h prevent library loops */
-#define FILESYSTEM_SOCKET_PREFIX "/tmp/"
-#define ANDROID_RESERVED_SOCKET_PREFIX "/dev/socket/"
-/* End of ../libcutils/socket_local.h */
-
-#define LISTEN_BACKLOG 4
-
-/* Documented in header file. */
-int WEAK socket_make_sockaddr_un(const char *name, int namespaceId,
-                                 struct sockaddr_un *p_addr, socklen_t *alen)
-{
-    memset (p_addr, 0, sizeof (*p_addr));
-    size_t namelen;
-
-    switch (namespaceId) {
-    case ANDROID_SOCKET_NAMESPACE_ABSTRACT:
-#if defined(__linux__)
-        namelen  = strlen(name);
-
-        /* Test with length +1 for the *initial* '\0'. */
-        if ((namelen + 1) > sizeof(p_addr->sun_path)) {
-            goto error;
-        }
-
-        /*
-         * Note: The path in this case is *not* supposed to be
-         * '\0'-terminated. ("man 7 unix" for the gory details.)
-         */
-
-        p_addr->sun_path[0] = 0;
-        memcpy(p_addr->sun_path + 1, name, namelen);
-#else
-        /* this OS doesn't have the Linux abstract namespace */
-
-        namelen = strlen(name) + strlen(FILESYSTEM_SOCKET_PREFIX);
-        /* unix_path_max appears to be missing on linux */
-        if (namelen > sizeof(*p_addr)
-                - offsetof(struct sockaddr_un, sun_path) - 1) {
-            goto error;
-        }
-
-        strcpy(p_addr->sun_path, FILESYSTEM_SOCKET_PREFIX);
-        strcat(p_addr->sun_path, name);
-#endif
-        break;
-
-    case ANDROID_SOCKET_NAMESPACE_RESERVED:
-        namelen = strlen(name) + strlen(ANDROID_RESERVED_SOCKET_PREFIX);
-        /* unix_path_max appears to be missing on linux */
-        if (namelen > sizeof(*p_addr)
-                - offsetof(struct sockaddr_un, sun_path) - 1) {
-            goto error;
-        }
-
-        strcpy(p_addr->sun_path, ANDROID_RESERVED_SOCKET_PREFIX);
-        strcat(p_addr->sun_path, name);
-        break;
-
-    case ANDROID_SOCKET_NAMESPACE_FILESYSTEM:
-        namelen = strlen(name);
-        /* unix_path_max appears to be missing on linux */
-        if (namelen > sizeof(*p_addr)
-                - offsetof(struct sockaddr_un, sun_path) - 1) {
-            goto error;
-        }
-
-        strcpy(p_addr->sun_path, name);
-        break;
-
-    default:
-        /* invalid namespace id */
-        return -1;
-    }
-
-    p_addr->sun_family = AF_LOCAL;
-    *alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1;
-    return 0;
-error:
-    return -1;
-}
-
-/**
- * connect to peer named "name" on fd
- * returns same fd or -1 on error.
- * fd is not closed on error. that's your job.
- *
- * Used by AndroidSocketImpl
- */
-int WEAK socket_local_client_connect(int fd, const char *name, int namespaceId,
-                                     int type __unused)
-{
-    struct sockaddr_un addr;
-    socklen_t alen;
-    int err;
-
-    err = socket_make_sockaddr_un(name, namespaceId, &addr, &alen);
-
-    if (err < 0) {
-        goto error;
-    }
-
-    if(connect(fd, (struct sockaddr *) &addr, alen) < 0) {
-        goto error;
-    }
-
-    return fd;
-
-error:
-    return -1;
-}
-
-/**
- * connect to peer named "name"
- * returns fd or -1 on error
- */
-int WEAK socket_local_client(const char *name, int namespaceId, int type)
-{
-    int s;
-
-    s = socket(AF_LOCAL, type, 0);
-    if(s < 0) return -1;
-
-    if ( 0 > socket_local_client_connect(s, name, namespaceId, type)) {
-        close(s);
-        return -1;
-    }
-
-    return s;
-}
-
-#endif /* !HAVE_WINSOCK */
-/* End of ../libcutils/socket_local_client.c */
-
-#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",
-    [LOG_ID_CRASH] = "crash",
-    [LOG_ID_KERNEL] = "kernel",
-};
-
-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];
-}
-
-log_id_t android_name_to_log_id(const char *logName)
-{
-    const char *b;
-    int ret;
-
-    if (!logName) {
-        return -1; /* NB: log_id_t is unsigned */
-    }
-    b = strrchr(logName, '/');
-    if (!b) {
-        b = logName;
-    } else {
-        ++b;
-    }
-
-    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;
-    log_time start;
-    pid_t pid;
-    int sock;
-};
-
-struct logger {
-    struct listnode node;
-    struct logger_list *top;
-    log_id_t id;
-};
-
-/* android_logger_alloc unimplemented, no use case */
-/* android_logger_free not exported */
-static void android_logger_free(struct logger *logger)
-{
-    if (!logger) {
-        return;
-    }
-
-    list_remove(&logger->node);
-
-    free(logger);
-}
-
-/* android_logger_alloc unimplemented, no use case */
-
-/* method for getting the associated sublog id */
-log_id_t android_logger_get_id(struct logger *logger)
-{
-    return logger->id;
-}
-
-/* worker for sending the command to the logger */
-static ssize_t send_log_msg(struct logger *logger,
-                            const char *msg, char *buf, size_t buf_size)
-{
-    ssize_t ret;
-    size_t len;
-    char *cp;
-    int errno_save = 0;
-    int sock = socket_local_client("logd", ANDROID_SOCKET_NAMESPACE_RESERVED,
-                                   SOCK_STREAM);
-    if (sock < 0) {
-        return sock;
-    }
-
-    if (msg) {
-        snprintf(buf, buf_size, msg, logger ? logger->id : (unsigned) -1);
-    }
-
-    len = strlen(buf) + 1;
-    ret = TEMP_FAILURE_RETRY(write(sock, buf, len));
-    if (ret <= 0) {
-        goto done;
-    }
-
-    len = buf_size;
-    cp = buf;
-    while ((ret = TEMP_FAILURE_RETRY(read(sock, cp, len))) > 0) {
-        struct pollfd p;
-
-        if (((size_t)ret == len) || (buf_size < PAGE_SIZE)) {
-            break;
-        }
-
-        len -= ret;
-        cp += ret;
-
-        memset(&p, 0, sizeof(p));
-        p.fd = sock;
-        p.events = POLLIN;
-
-        /* Give other side 20ms to refill pipe */
-        ret = TEMP_FAILURE_RETRY(poll(&p, 1, 20));
-
-        if (ret <= 0) {
-            break;
-        }
-
-        if (!(p.revents & POLLIN)) {
-            ret = 0;
-            break;
-        }
-    }
-
-    if (ret >= 0) {
-        ret += buf_size - len;
-    }
-
-done:
-    if ((ret == -1) && errno) {
-        errno_save = errno;
-    }
-    close(sock);
-    if (errno_save) {
-        errno = errno_save;
-    }
-    return ret;
-}
-
-static int check_log_success(char *buf, ssize_t ret)
-{
-    if (ret < 0) {
-        return ret;
-    }
-
-    if (strncmp(buf, "success", 7)) {
-        errno = EINVAL;
-        return -1;
-    }
-
-    return 0;
-}
-
-/* Determine the credentials of the caller */
-static bool uid_has_log_permission(uid_t uid)
-{
-    return (uid == AID_SYSTEM) || (uid == AID_LOG) || (uid == AID_ROOT);
-}
-
-static uid_t get_best_effective_uid()
-{
-    uid_t euid;
-    uid_t uid;
-    gid_t gid;
-    ssize_t i;
-    static uid_t last_uid = (uid_t) -1;
-
-    if (last_uid != (uid_t) -1) {
-        return last_uid;
-    }
-    uid = getuid();
-    if (uid_has_log_permission(uid)) {
-        return last_uid = uid;
-    }
-    euid = geteuid();
-    if (uid_has_log_permission(euid)) {
-        return last_uid = euid;
-    }
-    gid = getgid();
-    if (uid_has_log_permission(gid)) {
-        return last_uid = gid;
-    }
-    gid = getegid();
-    if (uid_has_log_permission(gid)) {
-        return last_uid = gid;
-    }
-    i = getgroups((size_t) 0, NULL);
-    if (i > 0) {
-        gid_t list[i];
-
-        getgroups(i, list);
-        while (--i >= 0) {
-            if (uid_has_log_permission(list[i])) {
-                return last_uid = list[i];
-            }
-        }
-    }
-    return last_uid = uid;
-}
-
-int android_logger_clear(struct logger *logger)
-{
-    char buf[512];
-
-    if (logger->top->mode & ANDROID_LOG_PSTORE) {
-        if (uid_has_log_permission(get_best_effective_uid())) {
-            return unlink("/sys/fs/pstore/pmsg-ramoops-0");
-        }
-        errno = EPERM;
-        return -1;
-    }
-    return check_log_success(buf,
-        send_log_msg(logger, "clear %d", buf, sizeof(buf)));
-}
-
-/* returns the total size of the log's ring buffer */
-long android_logger_get_log_size(struct logger *logger)
-{
-    char buf[512];
-
-    ssize_t ret = send_log_msg(logger, "getLogSize %d", buf, sizeof(buf));
-    if (ret < 0) {
-        return ret;
-    }
-
-    if ((buf[0] < '0') || ('9' < buf[0])) {
-        return -1;
-    }
-
-    return atol(buf);
-}
-
-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)));
-}
-
-/*
- * returns the readable size of the log's ring buffer (that is, amount of the
- * log consumed)
- */
-long android_logger_get_log_readable_size(struct logger *logger)
-{
-    char buf[512];
-
-    ssize_t ret = send_log_msg(logger, "getLogSizeUsed %d", buf, sizeof(buf));
-    if (ret < 0) {
-        return ret;
-    }
-
-    if ((buf[0] < '0') || ('9' < buf[0])) {
-        return -1;
-    }
-
-    return atol(buf);
-}
-
-/*
- * returns the logger version
- */
-int android_logger_get_log_version(struct logger *logger __unused)
-{
-    return 3;
-}
-
-/*
- * returns statistics
- */
-ssize_t android_logger_get_statistics(struct logger_list *logger_list,
-                                      char *buf, size_t len)
-{
-    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);
-}
-
-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));
-}
-
-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->start.tv_sec = 0;
-    logger_list->start.tv_nsec = 0;
-    logger_list->tail = tail;
-    logger_list->pid = pid;
-    logger_list->sock = -1;
-
-    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 */
-
-/* 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 logger *logger;
-
-    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;
-    }
-
-    logger->id = id;
-    list_add_tail(&logger_list->node, &logger->node);
-    logger->top = logger_list;
-    goto ok;
-
-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;
-}
-
-static int android_logger_list_read_pstore(struct logger_list *logger_list,
-                                           struct log_msg *log_msg)
-{
-    ssize_t ret;
-    off_t current, next;
-    uid_t uid;
-    struct logger *logger;
-    struct __attribute__((__packed__)) {
-        android_pmsg_log_header_t p;
-        android_log_header_t l;
-    } buf;
-    static uint8_t preread_count;
-
-    memset(log_msg, 0, sizeof(*log_msg));
-
-    if (logger_list->sock < 0) {
-        int fd = open("/sys/fs/pstore/pmsg-ramoops-0", O_RDONLY);
-
-        if (fd < 0) {
-            return -errno;
-        }
-        logger_list->sock = fd;
-        preread_count = 0;
-    }
-
-    ret = 0;
-    while(1) {
-        if (preread_count < sizeof(buf)) {
-            ret = TEMP_FAILURE_RETRY(read(logger_list->sock,
-                                          &buf.p.magic + preread_count,
-                                          sizeof(buf) - preread_count));
-            if (ret < 0) {
-                return -errno;
-            }
-            preread_count += ret;
-        }
-        if (preread_count != sizeof(buf)) {
-            return preread_count ? -EIO : -EAGAIN;
-        }
-        if ((buf.p.magic != LOGGER_MAGIC)
-         || (buf.p.len <= sizeof(buf))
-         || (buf.p.len > (sizeof(buf) + LOGGER_ENTRY_MAX_PAYLOAD))
-         || (buf.l.id >= LOG_ID_MAX)
-         || (buf.l.realtime.tv_nsec >= NS_PER_SEC)) {
-            do {
-                memmove(&buf.p.magic, &buf.p.magic + 1, --preread_count);
-            } while (preread_count && (buf.p.magic != LOGGER_MAGIC));
-            continue;
-        }
-        preread_count = 0;
-
-        logger_for_each(logger, logger_list) {
-            if (buf.l.id != logger->id) {
-                continue;
-            }
-
-            if ((logger_list->start.tv_sec || logger_list->start.tv_nsec)
-             && ((logger_list->start.tv_sec > buf.l.realtime.tv_sec)
-              || ((logger_list->start.tv_sec == buf.l.realtime.tv_sec)
-               && (logger_list->start.tv_nsec > buf.l.realtime.tv_nsec)))) {
-                break;
-            }
-
-            if (logger_list->pid && (logger_list->pid != buf.p.pid)) {
-                break;
-            }
-
-            uid = get_best_effective_uid();
-            if (!uid_has_log_permission(uid) && (uid != buf.p.uid)) {
-                break;
-            }
-
-            ret = TEMP_FAILURE_RETRY(read(logger_list->sock,
-                                          log_msg->entry_v3.msg,
-                                          buf.p.len - sizeof(buf)));
-            if (ret < 0) {
-                return -errno;
-            }
-            if (ret != (ssize_t)(buf.p.len - sizeof(buf))) {
-                return -EIO;
-            }
-
-            log_msg->entry_v3.len = buf.p.len - sizeof(buf);
-            log_msg->entry_v3.hdr_size = sizeof(log_msg->entry_v3);
-            log_msg->entry_v3.pid = buf.p.pid;
-            log_msg->entry_v3.tid = buf.l.tid;
-            log_msg->entry_v3.sec = buf.l.realtime.tv_sec;
-            log_msg->entry_v3.nsec = buf.l.realtime.tv_nsec;
-            log_msg->entry_v3.lid = buf.l.id;
-
-            return ret;
-        }
-
-        current = TEMP_FAILURE_RETRY(lseek(logger_list->sock,
-                                           (off_t)0, SEEK_CUR));
-        if (current < 0) {
-            return -errno;
-        }
-        next = TEMP_FAILURE_RETRY(lseek(logger_list->sock,
-                                        (off_t)(buf.p.len - sizeof(buf)),
-                                        SEEK_CUR));
-        if (next < 0) {
-            return -errno;
-        }
-        if ((next - current) != (ssize_t)(buf.p.len - sizeof(buf))) {
-            return -EIO;
-        }
-    }
-}
-
-static void caught_signal(int signum __unused)
-{
-}
-
-/* Read from the selected logs */
-int android_logger_list_read(struct logger_list *logger_list,
-                             struct log_msg *log_msg)
-{
-    int ret, e;
-    struct logger *logger;
-    struct sigaction ignore;
-    struct sigaction old_sigaction;
-    unsigned int old_alarm = 0;
-
-    if (!logger_list) {
-        return -EINVAL;
-    }
-
-    if (logger_list->mode & ANDROID_LOG_PSTORE) {
-        return android_logger_list_read_pstore(logger_list, log_msg);
-    }
-
-    if (logger_list->mode & ANDROID_LOG_NONBLOCK) {
-        memset(&ignore, 0, sizeof(ignore));
-        ignore.sa_handler = caught_signal;
-        sigemptyset(&ignore.sa_mask);
-    }
-
-    if (logger_list->sock < 0) {
-        char buffer[256], *cp, c;
-
-        int sock = socket_local_client("logdr",
-                                       ANDROID_SOCKET_NAMESPACE_RESERVED,
-                                       SOCK_SEQPACKET);
-        if (sock < 0) {
-            if ((sock == -1) && errno) {
-                return -errno;
-            }
-            return sock;
-        }
-
-        strcpy(buffer,
-               (logger_list->mode & ANDROID_LOG_NONBLOCK) ? "dumpAndClose" : "stream");
-        cp = buffer + strlen(buffer);
-
-        strcpy(cp, " lids");
-        cp += 5;
-        c = '=';
-        int remaining = sizeof(buffer) - (cp - buffer);
-        logger_for_each(logger, logger_list) {
-            ret = snprintf(cp, remaining, "%c%u", c, logger->id);
-            ret = min(ret, remaining);
-            remaining -= ret;
-            cp += ret;
-            c = ',';
-        }
-
-        if (logger_list->tail) {
-            ret = snprintf(cp, remaining, " tail=%u", logger_list->tail);
-            ret = min(ret, remaining);
-            remaining -= ret;
-            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);
-            remaining -= ret;
-            cp += ret;
-        }
-
-        if (logger_list->mode & ANDROID_LOG_NONBLOCK) {
-            /* Deal with an unresponsive logd */
-            sigaction(SIGALRM, &ignore, &old_sigaction);
-            old_alarm = alarm(30);
-        }
-        ret = write(sock, buffer, cp - buffer);
-        e = errno;
-        if (logger_list->mode & ANDROID_LOG_NONBLOCK) {
-            if (e == EINTR) {
-                e = ETIMEDOUT;
-            }
-            alarm(old_alarm);
-            sigaction(SIGALRM, &old_sigaction, NULL);
-        }
-
-        if (ret <= 0) {
-            close(sock);
-            if ((ret == -1) && e) {
-                return -e;
-            }
-            if (ret == 0) {
-                return -EIO;
-            }
-            return ret;
-        }
-
-        logger_list->sock = sock;
-    }
-
-    ret = 0;
-    while(1) {
-        memset(log_msg, 0, sizeof(*log_msg));
-
-        if (logger_list->mode & ANDROID_LOG_NONBLOCK) {
-            /* particularily useful if tombstone is reporting for logd */
-            sigaction(SIGALRM, &ignore, &old_sigaction);
-            old_alarm = alarm(30);
-        }
-        /* NOTE: SOCK_SEQPACKET guarantees we read exactly one full entry */
-        ret = recv(logger_list->sock, log_msg, LOGGER_ENTRY_MAX_LEN, 0);
-        e = errno;
-        if (logger_list->mode & ANDROID_LOG_NONBLOCK) {
-            if ((ret == 0) || (e == EINTR)) {
-                e = EAGAIN;
-                ret = -1;
-            }
-            alarm(old_alarm);
-            sigaction(SIGALRM, &old_sigaction, NULL);
-        }
-
-        if (ret <= 0) {
-            if ((ret == -1) && e) {
-                return -e;
-            }
-            return ret;
-        }
-
-        logger_for_each(logger, logger_list) {
-            if (log_msg->entry.lid == logger->id) {
-                return ret;
-            }
-        }
-    }
-    /* NOTREACH */
-    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);
-    }
-
-    if (logger_list->sock >= 0) {
-        close (logger_list->sock);
-    }
-
-    free(logger_list);
-}
diff --git a/liblog/log_read_kern.c b/liblog/log_read_kern.c
deleted file mode 100644
index 69b405c..0000000
--- a/liblog/log_read_kern.c
+++ /dev/null
@@ -1,742 +0,0 @@
-/*
-** Copyright 2013-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.
-*/
-
-#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 <sys/cdefs.h>
-#include <sys/ioctl.h>
-
-#include <cutils/list.h>
-#include <log/log.h>
-#include <log/logger.h>
-
-#define __LOGGERIO     0xAE
-
-#define LOGGER_GET_LOG_BUF_SIZE    _IO(__LOGGERIO, 1) /* size of log */
-#define LOGGER_GET_LOG_LEN         _IO(__LOGGERIO, 2) /* used log len */
-#define LOGGER_GET_NEXT_ENTRY_LEN  _IO(__LOGGERIO, 3) /* next entry len */
-#define LOGGER_FLUSH_LOG           _IO(__LOGGERIO, 4) /* flush log */
-#define LOGGER_GET_VERSION         _IO(__LOGGERIO, 5) /* abi version */
-#define LOGGER_SET_VERSION         _IO(__LOGGERIO, 6) /* abi version */
-
-typedef char bool;
-#define false (const bool)0
-#define true (const bool)1
-
-#define LOG_FILE_DIR "/dev/log/"
-
-/* timeout in milliseconds */
-#define LOG_TIMEOUT_FLUSH 5
-#define LOG_TIMEOUT_NEVER -1
-
-#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))
-
-#ifndef __unused
-#define __unused __attribute__((unused))
-#endif
-
-/* 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",
-    [LOG_ID_CRASH] = "crash",
-    [LOG_ID_KERNEL] = "kernel",
-};
-
-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 & ANDROID_LOG_ACCMODE) == ANDROID_LOG_WRONLY) {
-        return W_OK;
-    }
-    if ((mode & ANDROID_LOG_ACCMODE) == ANDROID_LOG_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, ANDROID_LOG_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 & ANDROID_LOG_ACCMODE) == ANDROID_LOG_RDWR)
-            || (((mode ^ logger->top->mode) & ANDROID_LOG_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, ANDROID_LOG_WRONLY);
-}
-
-/* returns the total size of the log's ring buffer */
-long android_logger_get_log_size(struct logger *logger)
-{
-    return logger_ioctl(logger, LOGGER_GET_LOG_BUF_SIZE, ANDROID_LOG_RDWR);
-}
-
-int android_logger_set_log_size(struct logger *logger __unused,
-                                unsigned long size __unused)
-{
-    return -ENOTSUP;
-}
-
-/*
- * returns the readable size of the log's ring buffer (that is, amount of the
- * log consumed)
- */
-long android_logger_get_log_readable_size(struct logger *logger)
-{
-    return logger_ioctl(logger, LOGGER_GET_LOG_LEN, ANDROID_LOG_RDONLY);
-}
-
-/*
- * returns the logger version
- */
-int android_logger_get_log_version(struct logger *logger)
-{
-    int ret = logger_ioctl(logger, LOGGER_GET_VERSION, ANDROID_LOG_RDWR);
-    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;
-}
-
-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;
-}
-
-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;
-}
-
-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 */
-
-/* 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 & (ANDROID_LOG_ACCMODE | ANDROID_LOG_NONBLOCK));
-    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_FLUSH;
-    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)) {
-            int diff;
-            ret = firstentry->entry.entry.hdr_size;
-            if (!ret) {
-                ret = sizeof(firstentry->entry.entry_v1);
-            }
-
-            /* Promote entry to v3 format */
-            memcpy(log_msg->buf, firstentry->entry.buf, ret);
-            diff = sizeof(firstentry->entry.entry_v3) - ret;
-            if (diff < 0) {
-                diff = 0;
-            } else if (diff > 0) {
-                memset(log_msg->buf + ret, 0, diff);
-            }
-            memcpy(log_msg->buf + ret + diff, firstentry->entry.buf + ret,
-                   firstentry->entry.entry.len + 1);
-            ret += diff;
-            log_msg->entry.hdr_size = ret;
-            log_msg->entry.lid = firstlogger->id;
-
-            ret += firstentry->entry.entry.len;
-        }
-
-        /* 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 & ANDROID_LOG_NONBLOCK) {
-                error = EAGAIN;
-            } else {
-                logger_list->timeout_ms = LOG_TIMEOUT_NEVER;
-            }
-
-            logger_list->flush = true;
-            goto try_flush;
-        }
-
-        logger_list->timeout_ms = LOG_TIMEOUT_FLUSH;
-
-        /* 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;
-            int diff;
-
-            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;
-            }
-
-            /* Promote entry to v3 format */
-            diff = sizeof(logger_list->entry.entry_v3) - hdr_size;
-            if (diff > 0) {
-                if (logger_list->entry.entry.len
-                        > sizeof(logger_list->entry.buf) - hdr_size - diff) {
-                    error = EINVAL;
-                    continue;
-                }
-                result += diff;
-                memmove(logger_list->entry.buf + hdr_size + diff,
-                        logger_list->entry.buf + hdr_size,
-                        logger_list->entry.entry.len + 1);
-                memset(logger_list->entry.buf + hdr_size, 0, diff);
-                logger_list->entry.entry.hdr_size = hdr_size + diff;
-            }
-            logger_list->entry.entry.lid = 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);
-                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/log_time.cpp b/liblog/log_time.cpp
index 9d5ea0e..14c408c 100644
--- a/liblog/log_time.cpp
+++ b/liblog/log_time.cpp
@@ -18,173 +18,112 @@
 #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.%q";
-const timespec log_time::EPOCH = { 0, 0 };
+#include <private/android_logger.h>
 
 // Add %#q for fractional seconds to standard strptime function
-
-char *log_time::strptime(const char *s, const char *format) {
-    time_t now;
+char* log_time::strptime(const char* s, const char* format) {
+  time_t now;
 #ifdef __linux__
-    *this = log_time(CLOCK_REALTIME);
-    now = tv_sec;
+  *this = log_time(CLOCK_REALTIME);
+  now = tv_sec;
 #else
-    time(&now);
-    tv_sec = now;
-    tv_nsec = 0;
+  time(&now);
+  tv_sec = now;
+  tv_nsec = 0;
 #endif
 
-    struct tm *ptm;
+  struct tm* ptm;
 #if !defined(_WIN32)
-    struct tm tmBuf;
-    ptm = localtime_r(&now, &tmBuf);
+  struct tm tmBuf;
+  ptm = localtime_r(&now, &tmBuf);
 #else
-    ptm = localtime(&now);
+  ptm = localtime(&now);
 #endif
 
-    char fmt[strlen(format) + 1];
-    strcpy(fmt, format);
+  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;
+  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(_WIN32)
-            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;
-            }
+    if (*e == 's') {
+      *cp = '\0';
+      if (*f) {
+        ret = ::strptime(ret, f, ptm);
+        if (!ret) {
+          break;
         }
-        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);
+      }
+      tv_sec = 0;
+      while (isdigit(*ret)) {
+        tv_sec = tv_sec * 10 + *ret - '0';
+        ++ret;
+      }
+      now = tv_sec;
+#if !defined(_WIN32)
+      ptm = localtime_r(&now, &tmBuf);
 #else
-    time(&now);
-    tv_sec = now;
-    tv_nsec = 0;
+      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;
-}
+  }
 
-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 timespec &T) {
-    this->tv_nsec += (unsigned long int)T.tv_nsec;
-    if (this->tv_nsec >= NS_PER_SEC) {
-        this->tv_nsec -= NS_PER_SEC;
-        ++this->tv_sec;
-    }
-    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;
-}
-
-log_time log_time::operator+= (const log_time &T) {
-    this->tv_nsec += T.tv_nsec;
-    if (this->tv_nsec >= NS_PER_SEC) {
-        this->tv_nsec -= NS_PER_SEC;
-        ++this->tv_sec;
-    }
-    this->tv_sec += T.tv_sec;
-
-    return *this;
+// 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;
 }
diff --git a/liblog/logd_reader.cpp b/liblog/logd_reader.cpp
new file mode 100644
index 0000000..611caed
--- /dev/null
+++ b/liblog/logd_reader.cpp
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2007-2016 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 "logd_reader.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdatomic.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/parseint.h>
+#include <private/android_logger.h>
+
+#include "logger.h"
+
+// Connects to /dev/socket/<name> and returns the associated fd or returns -1 on error.
+// O_CLOEXEC is always set.
+static int socket_local_client(const std::string& name, int type, bool timeout) {
+  sockaddr_un addr = {.sun_family = AF_LOCAL};
+
+  std::string path = "/dev/socket/" + name;
+  if (path.size() + 1 > sizeof(addr.sun_path)) {
+    return -1;
+  }
+  strlcpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path));
+
+  int fd = socket(AF_LOCAL, type | SOCK_CLOEXEC, 0);
+  if (fd == -1) {
+    return -1;
+  }
+
+  if (timeout) {
+    // Sending and receiving messages should be instantaneous, but we don't want to wait forever if
+    // logd is hung, so we set a gracious 2s timeout.
+    struct timeval t = {2, 0};
+    if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &t, sizeof(t)) == -1) {
+      return -1;
+    }
+    if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)) == -1) {
+      return -1;
+    }
+  }
+
+  if (connect(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1) {
+    close(fd);
+    return -1;
+  }
+
+  return fd;
+}
+
+/* worker for sending the command to the logger */
+ssize_t SendLogdControlMessage(char* buf, size_t buf_size) {
+  ssize_t ret;
+  size_t len;
+  char* cp;
+  int errno_save = 0;
+  int sock = socket_local_client("logd", SOCK_STREAM, true);
+  if (sock < 0) {
+    return sock;
+  }
+
+  len = strlen(buf) + 1;
+  ret = TEMP_FAILURE_RETRY(write(sock, buf, len));
+  if (ret <= 0) {
+    goto done;
+  }
+
+  len = buf_size;
+  cp = buf;
+  while ((ret = TEMP_FAILURE_RETRY(read(sock, cp, len))) > 0) {
+    struct pollfd p;
+
+    if (((size_t)ret == len) || (buf_size < PAGE_SIZE)) {
+      break;
+    }
+
+    len -= ret;
+    cp += ret;
+
+    memset(&p, 0, sizeof(p));
+    p.fd = sock;
+    p.events = POLLIN;
+
+    /* Give other side 20ms to refill pipe */
+    ret = TEMP_FAILURE_RETRY(poll(&p, 1, 20));
+
+    if (ret <= 0) {
+      break;
+    }
+
+    if (!(p.revents & POLLIN)) {
+      ret = 0;
+      break;
+    }
+  }
+
+  if (ret >= 0) {
+    ret += buf_size - len;
+  }
+
+done:
+  if ((ret == -1) && errno) {
+    errno_save = errno;
+  }
+  close(sock);
+  if (errno_save) {
+    errno = errno_save;
+  }
+  return ret;
+}
+
+static int check_log_success(char* buf, ssize_t ret) {
+  if (ret < 0) {
+    return ret;
+  }
+
+  if (strncmp(buf, "success", 7)) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  return 0;
+}
+
+int android_logger_clear(struct logger* logger) {
+  if (!android_logger_is_logd(logger)) {
+    return -EINVAL;
+  }
+  uint32_t log_id = android_logger_get_id(logger);
+  char buf[512];
+  snprintf(buf, sizeof(buf), "clear %" PRIu32, log_id);
+
+  return check_log_success(buf, SendLogdControlMessage(buf, sizeof(buf)));
+}
+
+enum class LogSizeType : uint32_t {
+  kAllotted = 0,
+  kReadable,
+  kConsumed,
+};
+
+static long GetLogSize(struct logger* logger, LogSizeType type) {
+  if (!android_logger_is_logd(logger)) {
+    return -EINVAL;
+  }
+
+  uint32_t log_id = android_logger_get_id(logger);
+  char buf[512];
+  switch (type) {
+    case LogSizeType::kAllotted:
+      snprintf(buf, sizeof(buf), "getLogSize %" PRIu32, log_id);
+      break;
+    case LogSizeType::kReadable:
+      snprintf(buf, sizeof(buf), "getLogSizeReadable %" PRIu32, log_id);
+      break;
+    case LogSizeType::kConsumed:
+      snprintf(buf, sizeof(buf), "getLogSizeUsed %" PRIu32, log_id);
+      break;
+    default:
+      abort();
+  }
+
+  ssize_t ret = SendLogdControlMessage(buf, sizeof(buf));
+  if (ret < 0) {
+    return ret;
+  }
+
+  long size;
+  if (!android::base::ParseInt(buf, &size)) {
+    return -1;
+  }
+
+  return size;
+}
+
+long android_logger_get_log_size(struct logger* logger) {
+  return GetLogSize(logger, LogSizeType::kAllotted);
+}
+
+long android_logger_get_log_readable_size(struct logger* logger) {
+  return GetLogSize(logger, LogSizeType::kReadable);
+}
+
+long android_logger_get_log_consumed_size(struct logger* logger) {
+  return GetLogSize(logger, LogSizeType::kConsumed);
+}
+
+int android_logger_set_log_size(struct logger* logger, unsigned long size) {
+  if (!android_logger_is_logd(logger)) {
+    return -EINVAL;
+  }
+
+  uint32_t log_id = android_logger_get_id(logger);
+  char buf[512];
+  snprintf(buf, sizeof(buf), "setLogSize %" PRIu32 " %lu", log_id, size);
+
+  return check_log_success(buf, SendLogdControlMessage(buf, sizeof(buf)));
+}
+
+int android_logger_get_log_version(struct logger*) {
+  return 4;
+}
+
+ssize_t android_logger_get_statistics(struct logger_list* logger_list, char* buf, size_t len) {
+  if (logger_list->mode & ANDROID_LOG_PSTORE) {
+    return -EINVAL;
+  }
+
+  char* cp = buf;
+  size_t remaining = len;
+  size_t n;
+
+  n = snprintf(cp, remaining, "getStatistics");
+  n = MIN(n, remaining);
+  remaining -= n;
+  cp += n;
+
+  for (size_t log_id = 0; log_id < LOG_ID_MAX; ++log_id) {
+    if ((1 << log_id) & logger_list->log_mask) {
+      n = snprintf(cp, remaining, " %zu", log_id);
+      n = MIN(n, remaining);
+      remaining -= n;
+      cp += n;
+    }
+  }
+
+  if (logger_list->pid) {
+    snprintf(cp, remaining, " pid=%u", logger_list->pid);
+  }
+
+  return SendLogdControlMessage(buf, len);
+}
+ssize_t android_logger_get_prune_list(struct logger_list* logger_list, char* buf, size_t len) {
+  if (logger_list->mode & ANDROID_LOG_PSTORE) {
+    return -EINVAL;
+  }
+
+  snprintf(buf, len, "getPruneList");
+  return SendLogdControlMessage(buf, len);
+}
+
+int android_logger_set_prune_list(struct logger_list* logger_list, const char* buf, size_t len) {
+  if (logger_list->mode & ANDROID_LOG_PSTORE) {
+    return -EINVAL;
+  }
+
+  std::string cmd = "setPruneList " + std::string{buf, len};
+
+  return check_log_success(cmd.data(), SendLogdControlMessage(cmd.data(), cmd.size()));
+}
+
+static int logdOpen(struct logger_list* logger_list) {
+  char buffer[256], *cp, c;
+  int ret, remaining, sock;
+
+  sock = atomic_load(&logger_list->fd);
+  if (sock > 0) {
+    return sock;
+  }
+
+  sock = socket_local_client("logdr", SOCK_SEQPACKET, false);
+  if (sock <= 0) {
+    if ((sock == -1) && errno) {
+      return -errno;
+    }
+    return sock;
+  }
+
+  strcpy(buffer, (logger_list->mode & ANDROID_LOG_NONBLOCK) ? "dumpAndClose" : "stream");
+  cp = buffer + strlen(buffer);
+
+  strcpy(cp, " lids");
+  cp += 5;
+  c = '=';
+  remaining = sizeof(buffer) - (cp - buffer);
+
+  for (size_t log_id = 0; log_id < LOG_ID_MAX; ++log_id) {
+    if ((1 << log_id) & logger_list->log_mask) {
+      ret = snprintf(cp, remaining, "%c%zu", c, log_id);
+      ret = MIN(ret, remaining);
+      remaining -= ret;
+      cp += ret;
+      c = ',';
+    }
+  }
+
+  if (logger_list->tail) {
+    ret = snprintf(cp, remaining, " tail=%u", logger_list->tail);
+    ret = MIN(ret, remaining);
+    remaining -= ret;
+    cp += ret;
+  }
+
+  if (logger_list->start.tv_sec || logger_list->start.tv_nsec) {
+    if (logger_list->mode & ANDROID_LOG_WRAP) {
+      // ToDo: alternate API to allow timeout to be adjusted.
+      ret = snprintf(cp, remaining, " timeout=%u", ANDROID_LOG_WRAP_DEFAULT_TIMEOUT);
+      ret = MIN(ret, remaining);
+      remaining -= ret;
+      cp += ret;
+    }
+    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);
+    cp += ret;
+  }
+
+  ret = TEMP_FAILURE_RETRY(write(sock, buffer, cp - buffer));
+  int write_errno = errno;
+
+  if (ret <= 0) {
+    close(sock);
+    if (ret == -1) {
+      return -write_errno;
+    }
+    if (ret == 0) {
+      return -EIO;
+    }
+    return ret;
+  }
+
+  ret = atomic_exchange(&logger_list->fd, sock);
+  if ((ret > 0) && (ret != sock)) {
+    close(ret);
+  }
+  return sock;
+}
+
+/* Read from the selected logs */
+int LogdRead(struct logger_list* logger_list, struct log_msg* log_msg) {
+  int ret = logdOpen(logger_list);
+  if (ret < 0) {
+    return ret;
+  }
+
+  /* NOTE: SOCK_SEQPACKET guarantees we read exactly one full entry */
+  ret = TEMP_FAILURE_RETRY(recv(ret, log_msg, LOGGER_ENTRY_MAX_LEN, 0));
+  if ((logger_list->mode & ANDROID_LOG_NONBLOCK) && ret == 0) {
+    return -EAGAIN;
+  }
+
+  if (ret == -1) {
+    return -errno;
+  }
+  return ret;
+}
+
+/* Close all the logs */
+void LogdClose(struct logger_list* logger_list) {
+  int sock = atomic_exchange(&logger_list->fd, -1);
+  if (sock > 0) {
+    close(sock);
+  }
+}
diff --git a/liblog/fake_log_device.h b/liblog/logd_reader.h
similarity index 62%
copy from liblog/fake_log_device.h
copy to liblog/logd_reader.h
index 9d168cd..68eef02 100644
--- a/liblog/fake_log_device.h
+++ b/liblog/logd_reader.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2016 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.
@@ -14,15 +14,18 @@
  * limitations under the License.
  */
 
-#ifndef _LIBLOG_FAKE_LOG_DEVICE_H
-#define _LIBLOG_FAKE_LOG_DEVICE_H
+#pragma once
 
-#include <sys/types.h>
+#include <sys/cdefs.h>
+#include <unistd.h>
 
-struct iovec;
+#include "log/log_read.h"
 
-int fakeLogOpen(const char *pathName, int flags);
-int fakeLogClose(int fd);
-ssize_t fakeLogWritev(int fd, const struct iovec* vector, int count);
+__BEGIN_DECLS
 
-#endif // _LIBLOG_FAKE_LOG_DEVICE_H
+int LogdRead(struct logger_list* logger_list, struct log_msg* log_msg);
+void LogdClose(struct logger_list* logger_list);
+
+ssize_t SendLogdControlMessage(char* buf, size_t buf_size);
+
+__END_DECLS
diff --git a/liblog/logd_write.c b/liblog/logd_write.c
deleted file mode 100644
index bdee28f..0000000
--- a/liblog/logd_write.c
+++ /dev/null
@@ -1,515 +0,0 @@
-/*
- * Copyright (C) 2007-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.
- */
-#if (FAKE_LOG_DEVICE == 0)
-#include <endian.h>
-#endif
-#include <errno.h>
-#include <fcntl.h>
-#if !defined(_WIN32)
-#include <pthread.h>
-#endif
-#include <stdarg.h>
-#include <stdatomic.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#if (FAKE_LOG_DEVICE == 0)
-#include <sys/socket.h>
-#include <sys/un.h>
-#endif
-#include <time.h>
-#include <unistd.h>
-
-#ifdef __BIONIC__
-#include <android/set_abort_message.h>
-#endif
-
-#include <log/logd.h>
-#include <log/logger.h>
-#include <log/log_read.h>
-#include <private/android_filesystem_config.h>
-#include <private/android_logger.h>
-
-#define LOG_BUF_SIZE 1024
-
-#if FAKE_LOG_DEVICE
-/* This will be defined when building for the host. */
-#include "fake_log_device.h"
-#endif
-
-static int __write_to_log_init(log_id_t, struct iovec *vec, size_t nr);
-static int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) = __write_to_log_init;
-#if !defined(_WIN32)
-static pthread_mutex_t log_init_lock = PTHREAD_MUTEX_INITIALIZER;
-#endif
-
-#ifndef __unused
-#define __unused  __attribute__((__unused__))
-#endif
-
-#if FAKE_LOG_DEVICE
-static int log_fds[(int)LOG_ID_MAX] = { -1, -1, -1, -1, -1 };
-#else
-static int logd_fd = -1;
-static int pstore_fd = -1;
-#endif
-
-/*
- * This is used by the C++ code to decide if it should write logs through
- * the C code.  Basically, if /dev/socket/logd is available, we're running in
- * the simulator rather than a desktop tool and want to use the device.
- */
-static enum {
-    kLogUninitialized, kLogNotAvailable, kLogAvailable
-} g_log_status = kLogUninitialized;
-
-int __android_log_dev_available(void)
-{
-    if (g_log_status == kLogUninitialized) {
-        if (access("/dev/socket/logdw", W_OK) == 0)
-            g_log_status = kLogAvailable;
-        else
-            g_log_status = kLogNotAvailable;
-    }
-
-    return (g_log_status == kLogAvailable);
-}
-
-/* log_init_lock assumed */
-static int __write_to_log_initialize()
-{
-    int i, ret = 0;
-
-#if FAKE_LOG_DEVICE
-    for (i = 0; i < LOG_ID_MAX; i++) {
-        char buf[sizeof("/dev/log_system")];
-        snprintf(buf, sizeof(buf), "/dev/log_%s", android_log_id_to_name(i));
-        log_fds[i] = fakeLogOpen(buf, O_WRONLY);
-    }
-#else
-    if (pstore_fd < 0) {
-        pstore_fd = TEMP_FAILURE_RETRY(open("/dev/pmsg0", O_WRONLY));
-    }
-
-    if (logd_fd < 0) {
-        i = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0));
-        if (i < 0) {
-            ret = -errno;
-        } else if (TEMP_FAILURE_RETRY(fcntl(i, F_SETFL, O_NONBLOCK)) < 0) {
-            ret = -errno;
-            close(i);
-        } else {
-            struct sockaddr_un un;
-            memset(&un, 0, sizeof(struct sockaddr_un));
-            un.sun_family = AF_UNIX;
-            strcpy(un.sun_path, "/dev/socket/logdw");
-
-            if (TEMP_FAILURE_RETRY(connect(i, (struct sockaddr *)&un,
-                                           sizeof(struct sockaddr_un))) < 0) {
-                ret = -errno;
-                close(i);
-            } else {
-                logd_fd = i;
-            }
-        }
-    }
-#endif
-
-    return ret;
-}
-
-static int __write_to_log_daemon(log_id_t log_id, struct iovec *vec, size_t nr)
-{
-    ssize_t ret;
-#if FAKE_LOG_DEVICE
-    int log_fd;
-
-    if (/*(int)log_id >= 0 &&*/ (int)log_id < (int)LOG_ID_MAX) {
-        log_fd = log_fds[(int)log_id];
-    } else {
-        return -EBADF;
-    }
-    do {
-        ret = fakeLogWritev(log_fd, vec, nr);
-        if (ret < 0) {
-            ret = -errno;
-        }
-    } while (ret == -EINTR);
-#else
-    static const unsigned header_length = 2;
-    struct iovec newVec[nr + header_length];
-    android_log_header_t header;
-    android_pmsg_log_header_t pmsg_header;
-    struct timespec ts;
-    size_t i, payload_size;
-    static uid_t last_uid = AID_ROOT; /* logd *always* starts up as AID_ROOT */
-    static pid_t last_pid = (pid_t) -1;
-    static atomic_int_fast32_t dropped;
-
-    if (!nr) {
-        return -EINVAL;
-    }
-
-    if (last_uid == AID_ROOT) { /* have we called to get the UID yet? */
-        last_uid = getuid();
-    }
-    if (last_pid == (pid_t) -1) {
-        last_pid = getpid();
-    }
-    /*
-     *  struct {
-     *      // what we provide to pstore
-     *      android_pmsg_log_header_t pmsg_header;
-     *      // what we provide to socket
-     *      android_log_header_t header;
-     *      // caller provides
-     *      union {
-     *          struct {
-     *              char     prio;
-     *              char     payload[];
-     *          } string;
-     *          struct {
-     *              uint32_t tag
-     *              char     payload[];
-     *          } binary;
-     *      };
-     *  };
-     */
-
-    clock_gettime(CLOCK_REALTIME, &ts);
-
-    pmsg_header.magic = LOGGER_MAGIC;
-    pmsg_header.len = sizeof(pmsg_header) + sizeof(header);
-    pmsg_header.uid = last_uid;
-    pmsg_header.pid = last_pid;
-
-    header.tid = gettid();
-    header.realtime.tv_sec = ts.tv_sec;
-    header.realtime.tv_nsec = ts.tv_nsec;
-
-    newVec[0].iov_base   = (unsigned char *) &pmsg_header;
-    newVec[0].iov_len    = sizeof(pmsg_header);
-    newVec[1].iov_base   = (unsigned char *) &header;
-    newVec[1].iov_len    = sizeof(header);
-
-    if (logd_fd > 0) {
-        int32_t snapshot = atomic_exchange_explicit(&dropped, 0, memory_order_relaxed);
-        if (snapshot) {
-            android_log_event_int_t buffer;
-
-            header.id = LOG_ID_EVENTS;
-            buffer.header.tag = htole32(LIBLOG_LOG_TAG);
-            buffer.payload.type = EVENT_TYPE_INT;
-            buffer.payload.data = htole32(snapshot);
-
-            newVec[2].iov_base = &buffer;
-            newVec[2].iov_len  = sizeof(buffer);
-
-            ret = TEMP_FAILURE_RETRY(writev(logd_fd, newVec + 1, 2));
-            if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
-                atomic_fetch_add_explicit(&dropped, snapshot, memory_order_relaxed);
-            }
-        }
-    }
-
-    header.id = log_id;
-
-    for (payload_size = 0, i = header_length; i < nr + header_length; i++) {
-        newVec[i].iov_base = vec[i - header_length].iov_base;
-        payload_size += newVec[i].iov_len = vec[i - header_length].iov_len;
-
-        if (payload_size > LOGGER_ENTRY_MAX_PAYLOAD) {
-            newVec[i].iov_len -= payload_size - LOGGER_ENTRY_MAX_PAYLOAD;
-            if (newVec[i].iov_len) {
-                ++i;
-            }
-            payload_size = LOGGER_ENTRY_MAX_PAYLOAD;
-            break;
-        }
-    }
-    pmsg_header.len += payload_size;
-
-    if (pstore_fd >= 0) {
-        TEMP_FAILURE_RETRY(writev(pstore_fd, newVec, i));
-    }
-
-    if (last_uid == AID_LOGD) { /* logd, after initialization and priv drop */
-        /*
-         * ignore log messages we send to ourself (logd).
-         * Such log messages are often generated by libraries we depend on
-         * which use standard Android logging.
-         */
-        return 0;
-    }
-
-    if (logd_fd < 0) {
-        return -EBADF;
-    }
-
-    /*
-     * The write below could be lost, but will never block.
-     *
-     * To logd, we drop the pmsg_header
-     *
-     * ENOTCONN occurs if logd dies.
-     * EAGAIN occurs if logd is overloaded.
-     */
-    ret = TEMP_FAILURE_RETRY(writev(logd_fd, newVec + 1, i - 1));
-    if (ret < 0) {
-        ret = -errno;
-        if (ret == -ENOTCONN) {
-#if !defined(_WIN32)
-            pthread_mutex_lock(&log_init_lock);
-#endif
-            close(logd_fd);
-            logd_fd = -1;
-            ret = __write_to_log_initialize();
-#if !defined(_WIN32)
-            pthread_mutex_unlock(&log_init_lock);
-#endif
-
-            if (ret < 0) {
-                return ret;
-            }
-
-            ret = TEMP_FAILURE_RETRY(writev(logd_fd, newVec + 1, i - 1));
-            if (ret < 0) {
-                ret = -errno;
-            }
-        }
-    }
-
-    if (ret > (ssize_t)sizeof(header)) {
-        ret -= sizeof(header);
-    } else if (ret == -EAGAIN) {
-        atomic_fetch_add_explicit(&dropped, 1, memory_order_relaxed);
-    }
-#endif
-
-    return ret;
-}
-
-#if FAKE_LOG_DEVICE
-static const char *LOG_NAME[LOG_ID_MAX] = {
-    [LOG_ID_MAIN] = "main",
-    [LOG_ID_RADIO] = "radio",
-    [LOG_ID_EVENTS] = "events",
-    [LOG_ID_SYSTEM] = "system",
-    [LOG_ID_CRASH] = "crash",
-    [LOG_ID_KERNEL] = "kernel",
-};
-
-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];
-}
-#endif
-
-static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr)
-{
-#if !defined(_WIN32)
-    pthread_mutex_lock(&log_init_lock);
-#endif
-
-    if (write_to_log == __write_to_log_init) {
-        int ret;
-
-        ret = __write_to_log_initialize();
-        if (ret < 0) {
-#if !defined(_WIN32)
-            pthread_mutex_unlock(&log_init_lock);
-#endif
-#if (FAKE_LOG_DEVICE == 0)
-            if (pstore_fd >= 0) {
-                __write_to_log_daemon(log_id, vec, nr);
-            }
-#endif
-            return ret;
-        }
-
-        write_to_log = __write_to_log_daemon;
-    }
-
-#if !defined(_WIN32)
-    pthread_mutex_unlock(&log_init_lock);
-#endif
-
-    return write_to_log(log_id, vec, nr);
-}
-
-int __android_log_write(int prio, const char *tag, const char *msg)
-{
-    return __android_log_buf_write(LOG_ID_MAIN, prio, tag, msg);
-}
-
-int __android_log_buf_write(int bufID, int prio, const char *tag, const char *msg)
-{
-    struct iovec vec[3];
-    char tmp_tag[32];
-
-    if (!tag)
-        tag = "";
-
-    /* XXX: This needs to go! */
-    if ((bufID != LOG_ID_RADIO) &&
-         (!strcmp(tag, "HTC_RIL") ||
-        !strncmp(tag, "RIL", 3) || /* Any log tag with "RIL" as the prefix */
-        !strncmp(tag, "IMS", 3) || /* Any log tag with "IMS" as the prefix */
-        !strcmp(tag, "AT") ||
-        !strcmp(tag, "GSM") ||
-        !strcmp(tag, "STK") ||
-        !strcmp(tag, "CDMA") ||
-        !strcmp(tag, "PHONE") ||
-        !strcmp(tag, "SMS"))) {
-            bufID = LOG_ID_RADIO;
-            /* Inform third party apps/ril/radio.. to use Rlog or RLOG */
-            snprintf(tmp_tag, sizeof(tmp_tag), "use-Rlog/RLOG-%s", tag);
-            tag = tmp_tag;
-    }
-
-#if __BIONIC__
-    if (prio == ANDROID_LOG_FATAL) {
-        android_set_abort_message(msg);
-    }
-#endif
-
-    vec[0].iov_base   = (unsigned char *) &prio;
-    vec[0].iov_len    = 1;
-    vec[1].iov_base   = (void *) tag;
-    vec[1].iov_len    = strlen(tag) + 1;
-    vec[2].iov_base   = (void *) msg;
-    vec[2].iov_len    = strlen(msg) + 1;
-
-    return write_to_log(bufID, vec, 3);
-}
-
-int __android_log_vprint(int prio, const char *tag, const char *fmt, va_list ap)
-{
-    char buf[LOG_BUF_SIZE];
-
-    vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
-
-    return __android_log_write(prio, tag, buf);
-}
-
-int __android_log_print(int prio, const char *tag, const char *fmt, ...)
-{
-    va_list ap;
-    char buf[LOG_BUF_SIZE];
-
-    va_start(ap, fmt);
-    vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
-    va_end(ap);
-
-    return __android_log_write(prio, tag, buf);
-}
-
-int __android_log_buf_print(int bufID, int prio, const char *tag, const char *fmt, ...)
-{
-    va_list ap;
-    char buf[LOG_BUF_SIZE];
-
-    va_start(ap, fmt);
-    vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
-    va_end(ap);
-
-    return __android_log_buf_write(bufID, prio, tag, buf);
-}
-
-void __android_log_assert(const char *cond, const char *tag,
-                          const char *fmt, ...)
-{
-    char buf[LOG_BUF_SIZE];
-
-    if (fmt) {
-        va_list ap;
-        va_start(ap, fmt);
-        vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
-        va_end(ap);
-    } else {
-        /* Msg not provided, log condition.  N.B. Do not use cond directly as
-         * format string as it could contain spurious '%' syntax (e.g.
-         * "%d" in "blocks%devs == 0").
-         */
-        if (cond)
-            snprintf(buf, LOG_BUF_SIZE, "Assertion failed: %s", cond);
-        else
-            strcpy(buf, "Unspecified assertion failed");
-    }
-
-    __android_log_write(ANDROID_LOG_FATAL, tag, buf);
-    abort(); /* abort so we have a chance to debug the situation */
-    /* NOTREACHED */
-}
-
-int __android_log_bwrite(int32_t tag, const void *payload, size_t len)
-{
-    struct iovec vec[2];
-
-    vec[0].iov_base = &tag;
-    vec[0].iov_len = sizeof(tag);
-    vec[1].iov_base = (void*)payload;
-    vec[1].iov_len = len;
-
-    return write_to_log(LOG_ID_EVENTS, vec, 2);
-}
-
-/*
- * Like __android_log_bwrite, but takes the type as well.  Doesn't work
- * for the general case where we're generating lists of stuff, but very
- * handy if we just want to dump an integer into the log.
- */
-int __android_log_btwrite(int32_t tag, char type, const void *payload,
-                          size_t len)
-{
-    struct iovec vec[3];
-
-    vec[0].iov_base = &tag;
-    vec[0].iov_len = sizeof(tag);
-    vec[1].iov_base = &type;
-    vec[1].iov_len = sizeof(type);
-    vec[2].iov_base = (void*)payload;
-    vec[2].iov_len = len;
-
-    return write_to_log(LOG_ID_EVENTS, vec, 3);
-}
-
-/*
- * Like __android_log_bwrite, but used for writing strings to the
- * event log.
- */
-int __android_log_bswrite(int32_t tag, const char *payload)
-{
-    struct iovec vec[4];
-    char type = EVENT_TYPE_STRING;
-    uint32_t len = strlen(payload);
-
-    vec[0].iov_base = &tag;
-    vec[0].iov_len = sizeof(tag);
-    vec[1].iov_base = &type;
-    vec[1].iov_len = sizeof(type);
-    vec[2].iov_base = &len;
-    vec[2].iov_len = sizeof(len);
-    vec[3].iov_base = (void*)payload;
-    vec[3].iov_len = len;
-
-    return write_to_log(LOG_ID_EVENTS, vec, 4);
-}
diff --git a/liblog/logd_write_kern.c b/liblog/logd_write_kern.c
deleted file mode 100644
index 8742b34..0000000
--- a/liblog/logd_write_kern.c
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- * Copyright (C) 2007-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 <errno.h>
-#include <fcntl.h>
-#include <pthread.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <time.h>
-#include <unistd.h>
-
-#include <android/set_abort_message.h>
-
-#include <log/log.h>
-#include <log/logd.h>
-#include <log/logger.h>
-
-#define LOGGER_LOG_MAIN		"log/main"
-#define LOGGER_LOG_RADIO	"log/radio"
-#define LOGGER_LOG_EVENTS	"log/events"
-#define LOGGER_LOG_SYSTEM	"log/system"
-
-#define LOG_BUF_SIZE 1024
-
-#define log_open(pathname, flags) open(pathname, (flags) | O_CLOEXEC)
-#define log_writev(filedes, vector, count) writev(filedes, vector, count)
-#define log_close(filedes) close(filedes)
-
-static int __write_to_log_init(log_id_t, struct iovec *vec, size_t nr);
-static int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) = __write_to_log_init;
-
-static pthread_mutex_t log_init_lock = PTHREAD_MUTEX_INITIALIZER;
-
-#ifndef __unused
-#define __unused  __attribute__((__unused__))
-#endif
-
-static int log_fds[(int)LOG_ID_MAX] = { -1, -1, -1, -1 };
-
-/*
- * This is used by the C++ code to decide if it should write logs through
- * the C code.  Basically, if /dev/log/... is available, we're running in
- * the simulator rather than a desktop tool and want to use the device.
- */
-static enum {
-    kLogUninitialized, kLogNotAvailable, kLogAvailable
-} g_log_status = kLogUninitialized;
-int __android_log_dev_available(void)
-{
-    if (g_log_status == kLogUninitialized) {
-        if (access("/dev/"LOGGER_LOG_MAIN, W_OK) == 0)
-            g_log_status = kLogAvailable;
-        else
-            g_log_status = kLogNotAvailable;
-    }
-
-    return (g_log_status == kLogAvailable);
-}
-
-static int __write_to_log_null(log_id_t log_fd __unused, struct iovec *vec __unused,
-                               size_t nr __unused)
-{
-    return -1;
-}
-
-static int __write_to_log_kernel(log_id_t log_id, struct iovec *vec, size_t nr)
-{
-    ssize_t ret;
-    int log_fd;
-
-    if (/*(int)log_id >= 0 &&*/ (int)log_id < (int)LOG_ID_MAX) {
-        if (log_id == LOG_ID_CRASH) {
-            log_id = LOG_ID_MAIN;
-        }
-        log_fd = log_fds[(int)log_id];
-    } else {
-        return -EBADF;
-    }
-
-    do {
-        ret = log_writev(log_fd, vec, nr);
-        if (ret < 0) {
-            ret = -errno;
-        }
-    } while (ret == -EINTR);
-
-    return ret;
-}
-
-static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr)
-{
-    pthread_mutex_lock(&log_init_lock);
-
-    if (write_to_log == __write_to_log_init) {
-        log_fds[LOG_ID_MAIN] = log_open("/dev/"LOGGER_LOG_MAIN, O_WRONLY);
-        log_fds[LOG_ID_RADIO] = log_open("/dev/"LOGGER_LOG_RADIO, O_WRONLY);
-        log_fds[LOG_ID_EVENTS] = log_open("/dev/"LOGGER_LOG_EVENTS, O_WRONLY);
-        log_fds[LOG_ID_SYSTEM] = log_open("/dev/"LOGGER_LOG_SYSTEM, O_WRONLY);
-
-        write_to_log = __write_to_log_kernel;
-
-        if (log_fds[LOG_ID_MAIN] < 0 || log_fds[LOG_ID_RADIO] < 0 ||
-                log_fds[LOG_ID_EVENTS] < 0) {
-            log_close(log_fds[LOG_ID_MAIN]);
-            log_close(log_fds[LOG_ID_RADIO]);
-            log_close(log_fds[LOG_ID_EVENTS]);
-            log_fds[LOG_ID_MAIN] = -1;
-            log_fds[LOG_ID_RADIO] = -1;
-            log_fds[LOG_ID_EVENTS] = -1;
-            write_to_log = __write_to_log_null;
-        }
-
-        if (log_fds[LOG_ID_SYSTEM] < 0) {
-            log_fds[LOG_ID_SYSTEM] = log_fds[LOG_ID_MAIN];
-        }
-    }
-
-    pthread_mutex_unlock(&log_init_lock);
-
-    return write_to_log(log_id, vec, nr);
-}
-
-int __android_log_write(int prio, const char *tag, const char *msg)
-{
-    return __android_log_buf_write(LOG_ID_MAIN, prio, tag, msg);
-}
-
-int __android_log_buf_write(int bufID, int prio, const char *tag, const char *msg)
-{
-    struct iovec vec[3];
-    char tmp_tag[32];
-
-    if (!tag)
-        tag = "";
-
-    /* XXX: This needs to go! */
-    if ((bufID != LOG_ID_RADIO) &&
-         (!strcmp(tag, "HTC_RIL") ||
-        !strncmp(tag, "RIL", 3) || /* Any log tag with "RIL" as the prefix */
-        !strncmp(tag, "IMS", 3) || /* Any log tag with "IMS" as the prefix */
-        !strcmp(tag, "AT") ||
-        !strcmp(tag, "GSM") ||
-        !strcmp(tag, "STK") ||
-        !strcmp(tag, "CDMA") ||
-        !strcmp(tag, "PHONE") ||
-        !strcmp(tag, "SMS"))) {
-            bufID = LOG_ID_RADIO;
-            /* Inform third party apps/ril/radio.. to use Rlog or RLOG */
-            snprintf(tmp_tag, sizeof(tmp_tag), "use-Rlog/RLOG-%s", tag);
-            tag = tmp_tag;
-    }
-
-    if (prio == ANDROID_LOG_FATAL) {
-        android_set_abort_message(msg);
-    }
-
-    vec[0].iov_base   = (unsigned char *) &prio;
-    vec[0].iov_len    = 1;
-    vec[1].iov_base   = (void *) tag;
-    vec[1].iov_len    = strlen(tag) + 1;
-    vec[2].iov_base   = (void *) msg;
-    vec[2].iov_len    = strlen(msg) + 1;
-
-    return write_to_log(bufID, vec, 3);
-}
-
-int __android_log_vprint(int prio, const char *tag, const char *fmt, va_list ap)
-{
-    char buf[LOG_BUF_SIZE];
-
-    vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
-
-    return __android_log_write(prio, tag, buf);
-}
-
-int __android_log_print(int prio, const char *tag, const char *fmt, ...)
-{
-    va_list ap;
-    char buf[LOG_BUF_SIZE];
-
-    va_start(ap, fmt);
-    vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
-    va_end(ap);
-
-    return __android_log_write(prio, tag, buf);
-}
-
-int __android_log_buf_print(int bufID, int prio, const char *tag, const char *fmt, ...)
-{
-    va_list ap;
-    char buf[LOG_BUF_SIZE];
-
-    va_start(ap, fmt);
-    vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
-    va_end(ap);
-
-    return __android_log_buf_write(bufID, prio, tag, buf);
-}
-
-void __android_log_assert(const char *cond, const char *tag,
-                          const char *fmt, ...)
-{
-    char buf[LOG_BUF_SIZE];
-
-    if (fmt) {
-        va_list ap;
-        va_start(ap, fmt);
-        vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
-        va_end(ap);
-    } else {
-        /* Msg not provided, log condition.  N.B. Do not use cond directly as
-         * format string as it could contain spurious '%' syntax (e.g.
-         * "%d" in "blocks%devs == 0").
-         */
-        if (cond)
-            snprintf(buf, LOG_BUF_SIZE, "Assertion failed: %s", cond);
-        else
-            strcpy(buf, "Unspecified assertion failed");
-    }
-
-    __android_log_write(ANDROID_LOG_FATAL, tag, buf);
-    abort(); /* abort so we have a chance to debug the situation */
-    /* NOTREACHED */
-}
-
-int __android_log_bwrite(int32_t tag, const void *payload, size_t len)
-{
-    struct iovec vec[2];
-
-    vec[0].iov_base = &tag;
-    vec[0].iov_len = sizeof(tag);
-    vec[1].iov_base = (void*)payload;
-    vec[1].iov_len = len;
-
-    return write_to_log(LOG_ID_EVENTS, vec, 2);
-}
-
-/*
- * Like __android_log_bwrite, but takes the type as well.  Doesn't work
- * for the general case where we're generating lists of stuff, but very
- * handy if we just want to dump an integer into the log.
- */
-int __android_log_btwrite(int32_t tag, char type, const void *payload,
-                          size_t len)
-{
-    struct iovec vec[3];
-
-    vec[0].iov_base = &tag;
-    vec[0].iov_len = sizeof(tag);
-    vec[1].iov_base = &type;
-    vec[1].iov_len = sizeof(type);
-    vec[2].iov_base = (void*)payload;
-    vec[2].iov_len = len;
-
-    return write_to_log(LOG_ID_EVENTS, vec, 3);
-}
-
-/*
- * Like __android_log_bwrite, but used for writing strings to the
- * event log.
- */
-int __android_log_bswrite(int32_t tag, const char *payload)
-{
-    struct iovec vec[4];
-    char type = EVENT_TYPE_STRING;
-    uint32_t len = strlen(payload);
-
-    vec[0].iov_base = &tag;
-    vec[0].iov_len = sizeof(tag);
-    vec[1].iov_base = &type;
-    vec[1].iov_len = sizeof(type);
-    vec[2].iov_base = &len;
-    vec[2].iov_len = sizeof(len);
-    vec[3].iov_base = (void*)payload;
-    vec[3].iov_len = len;
-
-    return write_to_log(LOG_ID_EVENTS, vec, 4);
-}
diff --git a/liblog/logd_writer.cpp b/liblog/logd_writer.cpp
new file mode 100644
index 0000000..f5d19ca
--- /dev/null
+++ b/liblog/logd_writer.cpp
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2007-2016 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 "logd_writer.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdatomic.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <private/android_filesystem_config.h>
+#include <private/android_logger.h>
+
+#include "logger.h"
+#include "uio.h"
+
+static atomic_int logd_socket;
+
+// Note that it is safe to call connect() multiple times on DGRAM Unix domain sockets, so this
+// function is used to reconnect to logd without requiring a new socket.
+static void LogdConnect() {
+  sockaddr_un un = {};
+  un.sun_family = AF_UNIX;
+  strcpy(un.sun_path, "/dev/socket/logdw");
+  TEMP_FAILURE_RETRY(connect(logd_socket, reinterpret_cast<sockaddr*>(&un), sizeof(sockaddr_un)));
+}
+
+// logd_socket should only be opened once.  If we see that logd_socket is uninitialized, we create a
+// new socket and attempt to exchange it into the atomic logd_socket.  If the compare/exchange was
+// successful, then that will be the socket used for the duration of the program, otherwise a
+// different thread has already opened and written the socket to the atomic, so close the new socket
+// and return.
+static void GetSocket() {
+  if (logd_socket != 0) {
+    return;
+  }
+
+  int new_socket = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+  if (new_socket <= 0) {
+    return;
+  }
+
+  int uninitialized_value = 0;
+  if (!logd_socket.compare_exchange_strong(uninitialized_value, new_socket)) {
+    close(new_socket);
+    return;
+  }
+
+  LogdConnect();
+}
+
+// This is the one exception to the above.  Zygote uses this to clean up open FD's after fork() and
+// before specialization.  It is single threaded at this point and therefore this function is
+// explicitly not thread safe.  It sets logd_socket to 0, so future logs will be safely initialized
+// whenever they happen.
+void LogdClose() {
+  if (logd_socket > 0) {
+    close(logd_socket);
+  }
+  logd_socket = 0;
+}
+
+int LogdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr) {
+  ssize_t ret;
+  static const unsigned headerLength = 1;
+  struct iovec newVec[nr + headerLength];
+  android_log_header_t header;
+  size_t i, payloadSize;
+
+  GetSocket();
+
+  if (logd_socket <= 0) {
+    return -EBADF;
+  }
+
+  /* logd, after initialization and priv drop */
+  if (getuid() == AID_LOGD) {
+    /*
+     * ignore log messages we send to ourself (logd).
+     * Such log messages are often generated by libraries we depend on
+     * which use standard Android logging.
+     */
+    return 0;
+  }
+
+  header.id = logId;
+  header.tid = gettid();
+  header.realtime.tv_sec = ts->tv_sec;
+  header.realtime.tv_nsec = ts->tv_nsec;
+
+  newVec[0].iov_base = (unsigned char*)&header;
+  newVec[0].iov_len = sizeof(header);
+
+  for (payloadSize = 0, i = headerLength; i < nr + headerLength; i++) {
+    newVec[i].iov_base = vec[i - headerLength].iov_base;
+    payloadSize += newVec[i].iov_len = vec[i - headerLength].iov_len;
+
+    if (payloadSize > LOGGER_ENTRY_MAX_PAYLOAD) {
+      newVec[i].iov_len -= payloadSize - LOGGER_ENTRY_MAX_PAYLOAD;
+      if (newVec[i].iov_len) {
+        ++i;
+      }
+      break;
+    }
+  }
+
+  ret = TEMP_FAILURE_RETRY(writev(logd_socket, newVec, i));
+  if (ret < 0) {
+    LogdConnect();
+
+    ret = TEMP_FAILURE_RETRY(writev(logd_socket, newVec, i));
+  }
+
+  if (ret < 0) {
+    ret = -errno;
+  }
+
+  return ret;
+}
diff --git a/liblog/fake_log_device.h b/liblog/logd_writer.h
similarity index 62%
copy from liblog/fake_log_device.h
copy to liblog/logd_writer.h
index 9d168cd..41197b5 100644
--- a/liblog/fake_log_device.h
+++ b/liblog/logd_writer.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -14,15 +14,11 @@
  * limitations under the License.
  */
 
-#ifndef _LIBLOG_FAKE_LOG_DEVICE_H
-#define _LIBLOG_FAKE_LOG_DEVICE_H
+#pragma once
 
-#include <sys/types.h>
+#include <stddef.h>
 
-struct iovec;
+#include <android/log.h>
 
-int fakeLogOpen(const char *pathName, int flags);
-int fakeLogClose(int fd);
-ssize_t fakeLogWritev(int fd, const struct iovec* vector, int count);
-
-#endif // _LIBLOG_FAKE_LOG_DEVICE_H
+int LogdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr);
+void LogdClose();
diff --git a/liblog/logger.h b/liblog/logger.h
new file mode 100644
index 0000000..ddff19d
--- /dev/null
+++ b/liblog/logger.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include <stdatomic.h>
+#include <sys/cdefs.h>
+
+#include <log/log.h>
+
+#include "uio.h"
+
+__BEGIN_DECLS
+
+struct logger_list {
+  atomic_int fd;
+  int mode;
+  unsigned int tail;
+  log_time start;
+  pid_t pid;
+  uint32_t log_mask;
+};
+
+// Format for a 'logger' entry: uintptr_t where only the bottom 32 bits are used.
+// bit 31: Set if this 'logger' is for logd.
+// bit 30: Set if this 'logger' is for pmsg
+// bits 0-2: the decimal value of the log buffer.
+// Other bits are unused.
+
+#define LOGGER_LOGD (1U << 31)
+#define LOGGER_PMSG (1U << 30)
+#define LOGGER_LOG_ID_MASK ((1U << 3) - 1)
+
+inline bool android_logger_is_logd(struct logger* logger) {
+  return reinterpret_cast<uintptr_t>(logger) & LOGGER_LOGD;
+}
+
+__END_DECLS
diff --git a/liblog/logger_name.cpp b/liblog/logger_name.cpp
new file mode 100644
index 0000000..e72290e
--- /dev/null
+++ b/liblog/logger_name.cpp
@@ -0,0 +1,72 @@
+/*
+** Copyright 2013-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 <string.h>
+#include <type_traits>
+
+#include <log/log.h>
+
+/* In the future, we would like to make this list extensible */
+static const char* LOG_NAME[LOG_ID_MAX] = {
+    /* clang-format off */
+  [LOG_ID_MAIN] = "main",
+  [LOG_ID_RADIO] = "radio",
+  [LOG_ID_EVENTS] = "events",
+  [LOG_ID_SYSTEM] = "system",
+  [LOG_ID_CRASH] = "crash",
+  [LOG_ID_STATS] = "stats",
+  [LOG_ID_SECURITY] = "security",
+  [LOG_ID_KERNEL] = "kernel",
+    /* clang-format on */
+};
+
+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_assert(std::is_same<std::underlying_type<log_id_t>::type, uint32_t>::value,
+              "log_id_t must be an uint32_t");
+
+static_assert(std::is_same<std::underlying_type<android_LogPriority>::type, uint32_t>::value,
+              "log_id_t must be an uint32_t");
+
+log_id_t android_name_to_log_id(const char* logName) {
+  const char* b;
+  unsigned int ret;
+
+  if (!logName) {
+    return static_cast<log_id_t>(LOG_ID_MAX);
+  }
+
+  b = strrchr(logName, '/');
+  if (!b) {
+    b = logName;
+  } else {
+    ++b;
+  }
+
+  for (ret = LOG_ID_MIN; ret < LOG_ID_MAX; ++ret) {
+    const char* l = LOG_NAME[ret];
+    if (l && !strcmp(b, l)) {
+      return static_cast<log_id_t>(ret);
+    }
+  }
+
+  return static_cast<log_id_t>(LOG_ID_MAX);
+}
diff --git a/liblog/logger_read.cpp b/liblog/logger_read.cpp
new file mode 100644
index 0000000..4937042
--- /dev/null
+++ b/liblog/logger_read.cpp
@@ -0,0 +1,149 @@
+/*
+** Copyright 2013-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 "log/log_read.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <sched.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <android/log.h>
+
+#include "logd_reader.h"
+#include "logger.h"
+#include "pmsg_reader.h"
+
+/* method for getting the associated sublog id */
+log_id_t android_logger_get_id(struct logger* logger) {
+  return static_cast<log_id_t>(reinterpret_cast<uintptr_t>(logger) & LOGGER_LOG_ID_MASK);
+}
+
+static struct logger_list* android_logger_list_alloc_internal(int mode, unsigned int tail,
+                                                              log_time start, pid_t pid) {
+  auto* logger_list = static_cast<struct logger_list*>(calloc(1, sizeof(struct logger_list)));
+  if (!logger_list) {
+    return nullptr;
+  }
+
+  logger_list->mode = mode;
+  logger_list->start = start;
+  logger_list->tail = tail;
+  logger_list->pid = pid;
+
+  return logger_list;
+}
+
+struct logger_list* android_logger_list_alloc(int mode, unsigned int tail, pid_t pid) {
+  return android_logger_list_alloc_internal(mode, tail, log_time(0, 0), pid);
+}
+
+struct logger_list* android_logger_list_alloc_time(int mode, log_time start, pid_t pid) {
+  return android_logger_list_alloc_internal(mode, 0, start, pid);
+}
+
+/* Open the named log and add it to the logger list */
+struct logger* android_logger_open(struct logger_list* logger_list, log_id_t logId) {
+  if (!logger_list || (logId >= LOG_ID_MAX)) {
+    return nullptr;
+  }
+
+  logger_list->log_mask |= 1 << logId;
+
+  uintptr_t logger = logId;
+  logger |= (logger_list->mode & ANDROID_LOG_PSTORE) ? LOGGER_PMSG : LOGGER_LOGD;
+  return reinterpret_cast<struct logger*>(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 logId, 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, logId)) {
+    android_logger_list_free(logger_list);
+    return NULL;
+  }
+
+  return logger_list;
+}
+
+int android_logger_list_read(struct logger_list* logger_list, struct log_msg* log_msg) {
+  if (logger_list == nullptr || logger_list->log_mask == 0) {
+    return -EINVAL;
+  }
+
+  int ret = 0;
+
+#ifdef __ANDROID__
+  if (logger_list->mode & ANDROID_LOG_PSTORE) {
+    ret = PmsgRead(logger_list, log_msg);
+  } else {
+    ret = LogdRead(logger_list, log_msg);
+  }
+#endif
+
+  if (ret <= 0) {
+    return ret;
+  }
+
+  if (ret > LOGGER_ENTRY_MAX_LEN) {
+    ret = LOGGER_ENTRY_MAX_LEN;
+  }
+
+  if (ret < static_cast<int>(sizeof(log_msg->entry))) {
+    return -EINVAL;
+  }
+
+  if (log_msg->entry.hdr_size < sizeof(log_msg->entry) ||
+      log_msg->entry.hdr_size >= LOGGER_ENTRY_MAX_LEN - sizeof(log_msg->entry)) {
+    return -EINVAL;
+  }
+
+  if (log_msg->entry.len > ret - log_msg->entry.hdr_size) {
+    return -EINVAL;
+  }
+
+  log_msg->buf[log_msg->entry.len + log_msg->entry.hdr_size] = '\0';
+
+  return ret;
+}
+
+/* Close all the logs */
+void android_logger_list_free(struct logger_list* logger_list) {
+  if (logger_list == NULL) {
+    return;
+  }
+
+#ifdef __ANDROID__
+  if (logger_list->mode & ANDROID_LOG_PSTORE) {
+    PmsgClose(logger_list);
+  } else {
+    LogdClose(logger_list);
+  }
+#endif
+
+  free(logger_list);
+}
diff --git a/liblog/logger_write.cpp b/liblog/logger_write.cpp
new file mode 100644
index 0000000..22c7eca
--- /dev/null
+++ b/liblog/logger_write.cpp
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2007-2016 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 "logger_write.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#ifdef __BIONIC__
+#include <android/set_abort_message.h>
+#endif
+
+#include <atomic>
+
+#include <android-base/errno_restorer.h>
+#include <android-base/macros.h>
+#include <private/android_filesystem_config.h>
+#include <private/android_logger.h>
+
+#include "android/log.h"
+#include "log/log_read.h"
+#include "logger.h"
+#include "uio.h"
+
+#ifdef __ANDROID__
+#include "logd_writer.h"
+#include "pmsg_writer.h"
+#endif
+
+#if defined(__APPLE__)
+#include <pthread.h>
+#elif defined(__linux__) && !defined(__ANDROID__)
+#include <syscall.h>
+#elif defined(_WIN32)
+#include <windows.h>
+#endif
+
+using android::base::ErrnoRestorer;
+
+#define LOG_BUF_SIZE 1024
+
+#if defined(__ANDROID__)
+static int check_log_uid_permissions() {
+  uid_t uid = getuid();
+
+  /* Matches clientHasLogCredentials() in logd */
+  if ((uid != AID_SYSTEM) && (uid != AID_ROOT) && (uid != AID_LOG)) {
+    uid = geteuid();
+    if ((uid != AID_SYSTEM) && (uid != AID_ROOT) && (uid != AID_LOG)) {
+      gid_t gid = getgid();
+      if ((gid != AID_SYSTEM) && (gid != AID_ROOT) && (gid != AID_LOG)) {
+        gid = getegid();
+        if ((gid != AID_SYSTEM) && (gid != AID_ROOT) && (gid != AID_LOG)) {
+          int num_groups;
+          gid_t* groups;
+
+          num_groups = getgroups(0, NULL);
+          if (num_groups <= 0) {
+            return -EPERM;
+          }
+          groups = static_cast<gid_t*>(calloc(num_groups, sizeof(gid_t)));
+          if (!groups) {
+            return -ENOMEM;
+          }
+          num_groups = getgroups(num_groups, groups);
+          while (num_groups > 0) {
+            if (groups[num_groups - 1] == AID_LOG) {
+              break;
+            }
+            --num_groups;
+          }
+          free(groups);
+          if (num_groups <= 0) {
+            return -EPERM;
+          }
+        }
+      }
+    }
+  }
+  return 0;
+}
+#endif
+
+/*
+ * Release any logger resources. A new log write will immediately re-acquire.
+ */
+void __android_log_close() {
+#ifdef __ANDROID__
+  LogdClose();
+  PmsgClose();
+#endif
+}
+
+#if defined(__GLIBC__) || defined(_WIN32)
+static const char* getprogname() {
+#if defined(__GLIBC__)
+  return program_invocation_short_name;
+#elif defined(_WIN32)
+  static bool first = true;
+  static char progname[MAX_PATH] = {};
+
+  if (first) {
+    char path[PATH_MAX + 1];
+    DWORD result = GetModuleFileName(nullptr, path, sizeof(path) - 1);
+    if (result == 0 || result == sizeof(path) - 1) return "";
+    path[PATH_MAX - 1] = 0;
+
+    char* path_basename = basename(path);
+
+    snprintf(progname, sizeof(progname), "%s", path_basename);
+    first = false;
+  }
+
+  return progname;
+#endif
+}
+#endif
+
+// It's possible for logging to happen during static initialization before our globals are
+// initialized, so we place this std::string in a function such that it is initialized on the first
+// call.
+std::string& GetDefaultTag() {
+  static std::string default_tag = getprogname();
+  return default_tag;
+}
+
+void __android_log_set_default_tag(const char* tag) {
+  GetDefaultTag().assign(tag, 0, LOGGER_ENTRY_MAX_PAYLOAD);
+}
+
+static std::atomic_int32_t minimum_log_priority = ANDROID_LOG_DEFAULT;
+int32_t __android_log_set_minimum_priority(int32_t priority) {
+  return minimum_log_priority.exchange(priority, std::memory_order_relaxed);
+}
+
+int32_t __android_log_get_minimum_priority() {
+  return minimum_log_priority;
+}
+
+#ifdef __ANDROID__
+static __android_logger_function logger_function = __android_log_logd_logger;
+#else
+static __android_logger_function logger_function = __android_log_stderr_logger;
+#endif
+
+void __android_log_set_logger(__android_logger_function logger) {
+  logger_function = logger;
+}
+
+void __android_log_default_aborter(const char* abort_message) {
+#ifdef __ANDROID__
+  android_set_abort_message(abort_message);
+#else
+  UNUSED(abort_message);
+#endif
+  abort();
+}
+
+static __android_aborter_function aborter_function = __android_log_default_aborter;
+
+void __android_log_set_aborter(__android_aborter_function aborter) {
+  aborter_function = aborter;
+}
+
+void __android_log_call_aborter(const char* abort_message) {
+  aborter_function(abort_message);
+}
+
+#ifdef __ANDROID__
+static int write_to_log(log_id_t log_id, struct iovec* vec, size_t nr) {
+  int ret;
+  struct timespec ts;
+
+  if (log_id == LOG_ID_KERNEL) {
+    return -EINVAL;
+  }
+
+  clock_gettime(CLOCK_REALTIME, &ts);
+
+  if (log_id == LOG_ID_SECURITY) {
+    if (vec[0].iov_len < 4) {
+      return -EINVAL;
+    }
+
+    ret = check_log_uid_permissions();
+    if (ret < 0) {
+      return ret;
+    }
+    if (!__android_log_security()) {
+      /* If only we could reset downstream logd counter */
+      return -EPERM;
+    }
+  } else if (log_id == LOG_ID_EVENTS || log_id == LOG_ID_STATS) {
+    if (vec[0].iov_len < 4) {
+      return -EINVAL;
+    }
+  }
+
+  ret = LogdWrite(log_id, &ts, vec, nr);
+  PmsgWrite(log_id, &ts, vec, nr);
+
+  return ret;
+}
+#else
+static int write_to_log(log_id_t, struct iovec*, size_t) {
+  // Non-Android text logs should go to __android_log_stderr_logger, not here.
+  // Non-Android binary logs are always dropped.
+  return 1;
+}
+#endif
+
+// Copied from base/threads.cpp
+static uint64_t GetThreadId() {
+#if defined(__BIONIC__)
+  return gettid();
+#elif defined(__APPLE__)
+  uint64_t tid;
+  pthread_threadid_np(NULL, &tid);
+  return tid;
+#elif defined(__linux__)
+  return syscall(__NR_gettid);
+#elif defined(_WIN32)
+  return GetCurrentThreadId();
+#endif
+}
+
+void __android_log_stderr_logger(const struct __android_log_message* log_message) {
+  struct tm now;
+  time_t t = time(nullptr);
+
+#if defined(_WIN32)
+  localtime_s(&now, &t);
+#else
+  localtime_r(&t, &now);
+#endif
+
+  char timestamp[32];
+  strftime(timestamp, sizeof(timestamp), "%m-%d %H:%M:%S", &now);
+
+  static const char log_characters[] = "XXVDIWEF";
+  static_assert(arraysize(log_characters) - 1 == ANDROID_LOG_SILENT,
+                "Mismatch in size of log_characters and values in android_LogPriority");
+  int32_t priority =
+      log_message->priority > ANDROID_LOG_SILENT ? ANDROID_LOG_FATAL : log_message->priority;
+  char priority_char = log_characters[priority];
+  uint64_t tid = GetThreadId();
+
+  if (log_message->file != nullptr) {
+    fprintf(stderr, "%s %c %s %5d %5" PRIu64 " %s:%u] %s\n",
+            log_message->tag ? log_message->tag : "nullptr", priority_char, timestamp, getpid(),
+            tid, log_message->file, log_message->line, log_message->message);
+  } else {
+    fprintf(stderr, "%s %c %s %5d %5" PRIu64 " %s\n",
+            log_message->tag ? log_message->tag : "nullptr", priority_char, timestamp, getpid(),
+            tid, log_message->message);
+  }
+}
+
+void __android_log_logd_logger(const struct __android_log_message* log_message) {
+  int buffer_id = log_message->buffer_id == LOG_ID_DEFAULT ? LOG_ID_MAIN : log_message->buffer_id;
+
+  struct iovec vec[3];
+  vec[0].iov_base =
+      const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(&log_message->priority));
+  vec[0].iov_len = 1;
+  vec[1].iov_base = const_cast<void*>(static_cast<const void*>(log_message->tag));
+  vec[1].iov_len = strlen(log_message->tag) + 1;
+  vec[2].iov_base = const_cast<void*>(static_cast<const void*>(log_message->message));
+  vec[2].iov_len = strlen(log_message->message) + 1;
+
+  write_to_log(static_cast<log_id_t>(buffer_id), vec, 3);
+}
+
+int __android_log_write(int prio, const char* tag, const char* msg) {
+  return __android_log_buf_write(LOG_ID_MAIN, prio, tag, msg);
+}
+
+void __android_log_write_log_message(__android_log_message* log_message) {
+  ErrnoRestorer errno_restorer;
+
+  if (log_message->buffer_id != LOG_ID_DEFAULT && log_message->buffer_id != LOG_ID_MAIN &&
+      log_message->buffer_id != LOG_ID_SYSTEM && log_message->buffer_id != LOG_ID_RADIO &&
+      log_message->buffer_id != LOG_ID_CRASH) {
+    return;
+  }
+
+  if (log_message->tag == nullptr) {
+    log_message->tag = GetDefaultTag().c_str();
+  }
+
+#if __BIONIC__
+  if (log_message->priority == ANDROID_LOG_FATAL) {
+    android_set_abort_message(log_message->message);
+  }
+#endif
+
+  logger_function(log_message);
+}
+
+int __android_log_buf_write(int bufID, int prio, const char* tag, const char* msg) {
+  ErrnoRestorer errno_restorer;
+
+  if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
+    return -EPERM;
+  }
+
+  __android_log_message log_message = {
+      sizeof(__android_log_message), bufID, prio, tag, nullptr, 0, msg};
+  __android_log_write_log_message(&log_message);
+  return 1;
+}
+
+int __android_log_vprint(int prio, const char* tag, const char* fmt, va_list ap) {
+  ErrnoRestorer errno_restorer;
+
+  if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
+    return -EPERM;
+  }
+
+  __attribute__((uninitialized)) char buf[LOG_BUF_SIZE];
+
+  vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
+
+  __android_log_message log_message = {
+      sizeof(__android_log_message), LOG_ID_MAIN, prio, tag, nullptr, 0, buf};
+  __android_log_write_log_message(&log_message);
+  return 1;
+}
+
+int __android_log_print(int prio, const char* tag, const char* fmt, ...) {
+  ErrnoRestorer errno_restorer;
+
+  if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
+    return -EPERM;
+  }
+
+  va_list ap;
+  __attribute__((uninitialized)) char buf[LOG_BUF_SIZE];
+
+  va_start(ap, fmt);
+  vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
+  va_end(ap);
+
+  __android_log_message log_message = {
+      sizeof(__android_log_message), LOG_ID_MAIN, prio, tag, nullptr, 0, buf};
+  __android_log_write_log_message(&log_message);
+  return 1;
+}
+
+int __android_log_buf_print(int bufID, int prio, const char* tag, const char* fmt, ...) {
+  ErrnoRestorer errno_restorer;
+
+  if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
+    return -EPERM;
+  }
+
+  va_list ap;
+  __attribute__((uninitialized)) char buf[LOG_BUF_SIZE];
+
+  va_start(ap, fmt);
+  vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
+  va_end(ap);
+
+  __android_log_message log_message = {
+      sizeof(__android_log_message), bufID, prio, tag, nullptr, 0, buf};
+  __android_log_write_log_message(&log_message);
+  return 1;
+}
+
+void __android_log_assert(const char* cond, const char* tag, const char* fmt, ...) {
+  __attribute__((uninitialized)) char buf[LOG_BUF_SIZE];
+
+  if (fmt) {
+    va_list ap;
+    va_start(ap, fmt);
+    vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
+    va_end(ap);
+  } else {
+    /* Msg not provided, log condition.  N.B. Do not use cond directly as
+     * format string as it could contain spurious '%' syntax (e.g.
+     * "%d" in "blocks%devs == 0").
+     */
+    if (cond)
+      snprintf(buf, LOG_BUF_SIZE, "Assertion failed: %s", cond);
+    else
+      strcpy(buf, "Unspecified assertion failed");
+  }
+
+  // Log assertion failures to stderr for the benefit of "adb shell" users
+  // and gtests (http://b/23675822).
+  TEMP_FAILURE_RETRY(write(2, buf, strlen(buf)));
+  TEMP_FAILURE_RETRY(write(2, "\n", 1));
+
+  __android_log_write(ANDROID_LOG_FATAL, tag, buf);
+  __android_log_call_aborter(buf);
+  abort();
+}
+
+int __android_log_bwrite(int32_t tag, const void* payload, size_t len) {
+  ErrnoRestorer errno_restorer;
+
+  struct iovec vec[2];
+
+  vec[0].iov_base = &tag;
+  vec[0].iov_len = sizeof(tag);
+  vec[1].iov_base = (void*)payload;
+  vec[1].iov_len = len;
+
+  return write_to_log(LOG_ID_EVENTS, vec, 2);
+}
+
+int __android_log_stats_bwrite(int32_t tag, const void* payload, size_t len) {
+  ErrnoRestorer errno_restorer;
+
+  struct iovec vec[2];
+
+  vec[0].iov_base = &tag;
+  vec[0].iov_len = sizeof(tag);
+  vec[1].iov_base = (void*)payload;
+  vec[1].iov_len = len;
+
+  return write_to_log(LOG_ID_STATS, vec, 2);
+}
+
+int __android_log_security_bwrite(int32_t tag, const void* payload, size_t len) {
+  ErrnoRestorer errno_restorer;
+
+  struct iovec vec[2];
+
+  vec[0].iov_base = &tag;
+  vec[0].iov_len = sizeof(tag);
+  vec[1].iov_base = (void*)payload;
+  vec[1].iov_len = len;
+
+  return write_to_log(LOG_ID_SECURITY, vec, 2);
+}
+
+/*
+ * Like __android_log_bwrite, but takes the type as well.  Doesn't work
+ * for the general case where we're generating lists of stuff, but very
+ * handy if we just want to dump an integer into the log.
+ */
+int __android_log_btwrite(int32_t tag, char type, const void* payload, size_t len) {
+  ErrnoRestorer errno_restorer;
+
+  struct iovec vec[3];
+
+  vec[0].iov_base = &tag;
+  vec[0].iov_len = sizeof(tag);
+  vec[1].iov_base = &type;
+  vec[1].iov_len = sizeof(type);
+  vec[2].iov_base = (void*)payload;
+  vec[2].iov_len = len;
+
+  return write_to_log(LOG_ID_EVENTS, vec, 3);
+}
+
+/*
+ * Like __android_log_bwrite, but used for writing strings to the
+ * event log.
+ */
+int __android_log_bswrite(int32_t tag, const char* payload) {
+  ErrnoRestorer errno_restorer;
+
+  struct iovec vec[4];
+  char type = EVENT_TYPE_STRING;
+  uint32_t len = strlen(payload);
+
+  vec[0].iov_base = &tag;
+  vec[0].iov_len = sizeof(tag);
+  vec[1].iov_base = &type;
+  vec[1].iov_len = sizeof(type);
+  vec[2].iov_base = &len;
+  vec[2].iov_len = sizeof(len);
+  vec[3].iov_base = (void*)payload;
+  vec[3].iov_len = len;
+
+  return write_to_log(LOG_ID_EVENTS, vec, 4);
+}
+
+/*
+ * Like __android_log_security_bwrite, but used for writing strings to the
+ * security log.
+ */
+int __android_log_security_bswrite(int32_t tag, const char* payload) {
+  ErrnoRestorer errno_restorer;
+
+  struct iovec vec[4];
+  char type = EVENT_TYPE_STRING;
+  uint32_t len = strlen(payload);
+
+  vec[0].iov_base = &tag;
+  vec[0].iov_len = sizeof(tag);
+  vec[1].iov_base = &type;
+  vec[1].iov_len = sizeof(type);
+  vec[2].iov_base = &len;
+  vec[2].iov_len = sizeof(len);
+  vec[3].iov_base = (void*)payload;
+  vec[3].iov_len = len;
+
+  return write_to_log(LOG_ID_SECURITY, vec, 4);
+}
diff --git a/liblog/fake_log_device.h b/liblog/logger_write.h
similarity index 62%
copy from liblog/fake_log_device.h
copy to liblog/logger_write.h
index 9d168cd..eee2778 100644
--- a/liblog/fake_log_device.h
+++ b/liblog/logger_write.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -14,15 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef _LIBLOG_FAKE_LOG_DEVICE_H
-#define _LIBLOG_FAKE_LOG_DEVICE_H
+#pragma once
 
-#include <sys/types.h>
+#include <string>
 
-struct iovec;
-
-int fakeLogOpen(const char *pathName, int flags);
-int fakeLogClose(int fd);
-ssize_t fakeLogWritev(int fd, const struct iovec* vector, int count);
-
-#endif // _LIBLOG_FAKE_LOG_DEVICE_H
+std::string& GetDefaultTag();
diff --git a/liblog/logprint.c b/liblog/logprint.c
deleted file mode 100644
index c2f1545..0000000
--- a/liblog/logprint.c
+++ /dev/null
@@ -1,1141 +0,0 @@
-/*
-**
-** Copyright 2006-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.
-*/
-
-#define _GNU_SOURCE /* for asprintf */
-
-#include <arpa/inet.h>
-#include <assert.h>
-#include <ctype.h>
-#include <errno.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <inttypes.h>
-#include <sys/param.h>
-
-#include <log/logd.h>
-#include <log/logprint.h>
-
-/* open coded fragment, prevent circular dependencies */
-#define WEAK static
-
-typedef struct FilterInfo_t {
-    char *mTag;
-    android_LogPriority mPri;
-    struct FilterInfo_t *p_next;
-} FilterInfo;
-
-struct AndroidLogFormat_t {
-    android_LogPriority global_pri;
-    FilterInfo *filters;
-    AndroidLogPrintFormat format;
-    bool colored_output;
-    bool usec_time_output;
-    bool printable_output;
-};
-
-/*
- *  gnome-terminal color tags
- *    See http://misc.flogisoft.com/bash/tip_colors_and_formatting
- *    for ideas on how to set the forground color of the text for xterm.
- *    The color manipulation character stream is defined as:
- *      ESC [ 3 8 ; 5 ; <color#> m
- */
-#define ANDROID_COLOR_BLUE     75
-#define ANDROID_COLOR_DEFAULT 231
-#define ANDROID_COLOR_GREEN    40
-#define ANDROID_COLOR_ORANGE  166
-#define ANDROID_COLOR_RED     196
-#define ANDROID_COLOR_YELLOW  226
-
-static FilterInfo * filterinfo_new(const char * tag, android_LogPriority pri)
-{
-    FilterInfo *p_ret;
-
-    p_ret = (FilterInfo *)calloc(1, sizeof(FilterInfo));
-    p_ret->mTag = strdup(tag);
-    p_ret->mPri = pri;
-
-    return p_ret;
-}
-
-/* balance to above, filterinfo_free left unimplemented */
-
-/*
- * Note: also accepts 0-9 priorities
- * returns ANDROID_LOG_UNKNOWN if the character is unrecognized
- */
-static android_LogPriority filterCharToPri (char c)
-{
-    android_LogPriority pri;
-
-    c = tolower(c);
-
-    if (c >= '0' && c <= '9') {
-        if (c >= ('0'+ANDROID_LOG_SILENT)) {
-            pri = ANDROID_LOG_VERBOSE;
-        } else {
-            pri = (android_LogPriority)(c - '0');
-        }
-    } else if (c == 'v') {
-        pri = ANDROID_LOG_VERBOSE;
-    } else if (c == 'd') {
-        pri = ANDROID_LOG_DEBUG;
-    } else if (c == 'i') {
-        pri = ANDROID_LOG_INFO;
-    } else if (c == 'w') {
-        pri = ANDROID_LOG_WARN;
-    } else if (c == 'e') {
-        pri = ANDROID_LOG_ERROR;
-    } else if (c == 'f') {
-        pri = ANDROID_LOG_FATAL;
-    } else if (c == 's') {
-        pri = ANDROID_LOG_SILENT;
-    } else if (c == '*') {
-        pri = ANDROID_LOG_DEFAULT;
-    } else {
-        pri = ANDROID_LOG_UNKNOWN;
-    }
-
-    return pri;
-}
-
-static char filterPriToChar (android_LogPriority pri)
-{
-    switch (pri) {
-        case ANDROID_LOG_VERBOSE:       return 'V';
-        case ANDROID_LOG_DEBUG:         return 'D';
-        case ANDROID_LOG_INFO:          return 'I';
-        case ANDROID_LOG_WARN:          return 'W';
-        case ANDROID_LOG_ERROR:         return 'E';
-        case ANDROID_LOG_FATAL:         return 'F';
-        case ANDROID_LOG_SILENT:        return 'S';
-
-        case ANDROID_LOG_DEFAULT:
-        case ANDROID_LOG_UNKNOWN:
-        default:                        return '?';
-    }
-}
-
-static int colorFromPri (android_LogPriority pri)
-{
-    switch (pri) {
-        case ANDROID_LOG_VERBOSE:       return ANDROID_COLOR_DEFAULT;
-        case ANDROID_LOG_DEBUG:         return ANDROID_COLOR_BLUE;
-        case ANDROID_LOG_INFO:          return ANDROID_COLOR_GREEN;
-        case ANDROID_LOG_WARN:          return ANDROID_COLOR_ORANGE;
-        case ANDROID_LOG_ERROR:         return ANDROID_COLOR_RED;
-        case ANDROID_LOG_FATAL:         return ANDROID_COLOR_RED;
-        case ANDROID_LOG_SILENT:        return ANDROID_COLOR_DEFAULT;
-
-        case ANDROID_LOG_DEFAULT:
-        case ANDROID_LOG_UNKNOWN:
-        default:                        return ANDROID_COLOR_DEFAULT;
-    }
-}
-
-static android_LogPriority filterPriForTag(
-        AndroidLogFormat *p_format, const char *tag)
-{
-    FilterInfo *p_curFilter;
-
-    for (p_curFilter = p_format->filters
-            ; p_curFilter != NULL
-            ; p_curFilter = p_curFilter->p_next
-    ) {
-        if (0 == strcmp(tag, p_curFilter->mTag)) {
-            if (p_curFilter->mPri == ANDROID_LOG_DEFAULT) {
-                return p_format->global_pri;
-            } else {
-                return p_curFilter->mPri;
-            }
-        }
-    }
-
-    return p_format->global_pri;
-}
-
-/**
- * returns 1 if this log line should be printed based on its priority
- * and tag, and 0 if it should not
- */
-int android_log_shouldPrintLine (
-        AndroidLogFormat *p_format, const char *tag, android_LogPriority pri)
-{
-    return pri >= filterPriForTag(p_format, tag);
-}
-
-AndroidLogFormat *android_log_format_new()
-{
-    AndroidLogFormat *p_ret;
-
-    p_ret = calloc(1, sizeof(AndroidLogFormat));
-
-    p_ret->global_pri = ANDROID_LOG_VERBOSE;
-    p_ret->format = FORMAT_BRIEF;
-    p_ret->colored_output = false;
-    p_ret->usec_time_output = false;
-    p_ret->printable_output = false;
-
-    return p_ret;
-}
-
-void android_log_format_free(AndroidLogFormat *p_format)
-{
-    FilterInfo *p_info, *p_info_old;
-
-    p_info = p_format->filters;
-
-    while (p_info != NULL) {
-        p_info_old = p_info;
-        p_info = p_info->p_next;
-
-        free(p_info_old);
-    }
-
-    free(p_format);
-}
-
-
-
-int android_log_setPrintFormat(AndroidLogFormat *p_format,
-        AndroidLogPrintFormat format)
-{
-    switch (format) {
-    case FORMAT_MODIFIER_COLOR:
-        p_format->colored_output = true;
-        return 0;
-    case FORMAT_MODIFIER_TIME_USEC:
-        p_format->usec_time_output = true;
-        return 0;
-    case FORMAT_MODIFIER_PRINTABLE:
-        p_format->printable_output = true;
-        return 0;
-    default:
-        break;
-    }
-    p_format->format = format;
-    return 1;
-}
-
-/**
- * Returns FORMAT_OFF on invalid string
- */
-AndroidLogPrintFormat android_log_formatFromString(const char * formatString)
-{
-    static AndroidLogPrintFormat format;
-
-    if (strcmp(formatString, "brief") == 0) format = FORMAT_BRIEF;
-    else if (strcmp(formatString, "process") == 0) format = FORMAT_PROCESS;
-    else if (strcmp(formatString, "tag") == 0) format = FORMAT_TAG;
-    else if (strcmp(formatString, "thread") == 0) format = FORMAT_THREAD;
-    else if (strcmp(formatString, "raw") == 0) format = FORMAT_RAW;
-    else if (strcmp(formatString, "time") == 0) format = FORMAT_TIME;
-    else if (strcmp(formatString, "threadtime") == 0) format = FORMAT_THREADTIME;
-    else if (strcmp(formatString, "long") == 0) format = FORMAT_LONG;
-    else if (strcmp(formatString, "color") == 0) format = FORMAT_MODIFIER_COLOR;
-    else if (strcmp(formatString, "usec") == 0) format = FORMAT_MODIFIER_TIME_USEC;
-    else if (strcmp(formatString, "printable") == 0) format = FORMAT_MODIFIER_PRINTABLE;
-    else format = FORMAT_OFF;
-
-    return format;
-}
-
-/**
- * filterExpression: a single filter expression
- * eg "AT:d"
- *
- * returns 0 on success and -1 on invalid expression
- *
- * Assumes single threaded execution
- */
-
-int android_log_addFilterRule(AndroidLogFormat *p_format,
-        const char *filterExpression)
-{
-    size_t tagNameLength;
-    android_LogPriority pri = ANDROID_LOG_DEFAULT;
-
-    tagNameLength = strcspn(filterExpression, ":");
-
-    if (tagNameLength == 0) {
-        goto error;
-    }
-
-    if(filterExpression[tagNameLength] == ':') {
-        pri = filterCharToPri(filterExpression[tagNameLength+1]);
-
-        if (pri == ANDROID_LOG_UNKNOWN) {
-            goto error;
-        }
-    }
-
-    if(0 == strncmp("*", filterExpression, tagNameLength)) {
-        /*
-         * This filter expression refers to the global filter
-         * The default level for this is DEBUG if the priority
-         * is unspecified
-         */
-        if (pri == ANDROID_LOG_DEFAULT) {
-            pri = ANDROID_LOG_DEBUG;
-        }
-
-        p_format->global_pri = pri;
-    } else {
-        /*
-         * for filter expressions that don't refer to the global
-         * filter, the default is verbose if the priority is unspecified
-         */
-        if (pri == ANDROID_LOG_DEFAULT) {
-            pri = ANDROID_LOG_VERBOSE;
-        }
-
-        char *tagName;
-
-/*
- * Presently HAVE_STRNDUP is never defined, so the second case is always taken
- * Darwin doesn't have strnup, everything else does
- */
-#ifdef HAVE_STRNDUP
-        tagName = strndup(filterExpression, tagNameLength);
-#else
-        /* a few extra bytes copied... */
-        tagName = strdup(filterExpression);
-        tagName[tagNameLength] = '\0';
-#endif /*HAVE_STRNDUP*/
-
-        FilterInfo *p_fi = filterinfo_new(tagName, pri);
-        free(tagName);
-
-        p_fi->p_next = p_format->filters;
-        p_format->filters = p_fi;
-    }
-
-    return 0;
-error:
-    return -1;
-}
-
-
-/**
- * filterString: a comma/whitespace-separated set of filter expressions
- *
- * eg "AT:d *:i"
- *
- * returns 0 on success and -1 on invalid expression
- *
- * Assumes single threaded execution
- *
- */
-
-int android_log_addFilterString(AndroidLogFormat *p_format,
-        const char *filterString)
-{
-    char *filterStringCopy = strdup (filterString);
-    char *p_cur = filterStringCopy;
-    char *p_ret;
-    int err;
-
-    /* Yes, I'm using strsep */
-    while (NULL != (p_ret = strsep(&p_cur, " \t,"))) {
-        /* ignore whitespace-only entries */
-        if(p_ret[0] != '\0') {
-            err = android_log_addFilterRule(p_format, p_ret);
-
-            if (err < 0) {
-                goto error;
-            }
-        }
-    }
-
-    free (filterStringCopy);
-    return 0;
-error:
-    free (filterStringCopy);
-    return -1;
-}
-
-/**
- * Splits a wire-format buffer into an AndroidLogEntry
- * entry allocated by caller. Pointers will point directly into buf
- *
- * Returns 0 on success and -1 on invalid wire format (entry will be
- * in unspecified state)
- */
-int android_log_processLogBuffer(struct logger_entry *buf,
-                                 AndroidLogEntry *entry)
-{
-    entry->tv_sec = buf->sec;
-    entry->tv_nsec = buf->nsec;
-    entry->pid = buf->pid;
-    entry->tid = buf->tid;
-
-    /*
-     * format: <priority:1><tag:N>\0<message:N>\0
-     *
-     * tag str
-     *   starts at buf->msg+1
-     * msg
-     *   starts at buf->msg+1+len(tag)+1
-     *
-     * The message may have been truncated by the kernel log driver.
-     * When that happens, we must null-terminate the message ourselves.
-     */
-    if (buf->len < 3) {
-        /*
-         * An well-formed entry must consist of at least a priority
-         * and two null characters
-         */
-        fprintf(stderr, "+++ LOG: entry too small\n");
-        return -1;
-    }
-
-    int msgStart = -1;
-    int msgEnd = -1;
-
-    int i;
-    char *msg = buf->msg;
-    struct logger_entry_v2 *buf2 = (struct logger_entry_v2 *)buf;
-    if (buf2->hdr_size) {
-        msg = ((char *)buf2) + buf2->hdr_size;
-    }
-    for (i = 1; i < buf->len; i++) {
-        if (msg[i] == '\0') {
-            if (msgStart == -1) {
-                msgStart = i + 1;
-            } else {
-                msgEnd = i;
-                break;
-            }
-        }
-    }
-
-    if (msgStart == -1) {
-        fprintf(stderr, "+++ LOG: malformed log message\n");
-        return -1;
-    }
-    if (msgEnd == -1) {
-        /* incoming message not null-terminated; force it */
-        msgEnd = buf->len - 1;
-        msg[msgEnd] = '\0';
-    }
-
-    entry->priority = msg[0];
-    entry->tag = msg + 1;
-    entry->message = msg + msgStart;
-    entry->messageLen = msgEnd - msgStart;
-
-    return 0;
-}
-
-/*
- * Extract a 4-byte value from a byte stream.
- */
-static inline uint32_t get4LE(const uint8_t* src)
-{
-    return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
-}
-
-/*
- * Extract an 8-byte value from a byte stream.
- */
-static inline uint64_t get8LE(const uint8_t* src)
-{
-    uint32_t low, high;
-
-    low = src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
-    high = src[4] | (src[5] << 8) | (src[6] << 16) | (src[7] << 24);
-    return ((uint64_t) high << 32) | (uint64_t) low;
-}
-
-
-/*
- * Recursively convert binary log data to printable form.
- *
- * This needs to be recursive because you can have lists of lists.
- *
- * If we run out of room, we stop processing immediately.  It's important
- * for us to check for space on every output element to avoid producing
- * garbled output.
- *
- * Returns 0 on success, 1 on buffer full, -1 on failure.
- */
-static int android_log_printBinaryEvent(const unsigned char** pEventData,
-    size_t* pEventDataLen, char** pOutBuf, size_t* pOutBufLen)
-{
-    const unsigned char* eventData = *pEventData;
-    size_t eventDataLen = *pEventDataLen;
-    char* outBuf = *pOutBuf;
-    size_t outBufLen = *pOutBufLen;
-    unsigned char type;
-    size_t outCount;
-    int result = 0;
-
-    if (eventDataLen < 1)
-        return -1;
-    type = *eventData++;
-    eventDataLen--;
-
-    switch (type) {
-    case EVENT_TYPE_INT:
-        /* 32-bit signed int */
-        {
-            int ival;
-
-            if (eventDataLen < 4)
-                return -1;
-            ival = get4LE(eventData);
-            eventData += 4;
-            eventDataLen -= 4;
-
-            outCount = snprintf(outBuf, outBufLen, "%d", ival);
-            if (outCount < outBufLen) {
-                outBuf += outCount;
-                outBufLen -= outCount;
-            } else {
-                /* halt output */
-                goto no_room;
-            }
-        }
-        break;
-    case EVENT_TYPE_LONG:
-        /* 64-bit signed long */
-        {
-            uint64_t lval;
-
-            if (eventDataLen < 8)
-                return -1;
-            lval = get8LE(eventData);
-            eventData += 8;
-            eventDataLen -= 8;
-
-            outCount = snprintf(outBuf, outBufLen, "%" PRId64, lval);
-            if (outCount < outBufLen) {
-                outBuf += outCount;
-                outBufLen -= outCount;
-            } else {
-                /* halt output */
-                goto no_room;
-            }
-        }
-        break;
-    case EVENT_TYPE_FLOAT:
-        /* float */
-        {
-            uint32_t ival;
-            float fval;
-
-            if (eventDataLen < 4)
-                return -1;
-            ival = get4LE(eventData);
-            fval = *(float*)&ival;
-            eventData += 4;
-            eventDataLen -= 4;
-
-            outCount = snprintf(outBuf, outBufLen, "%f", fval);
-            if (outCount < outBufLen) {
-                outBuf += outCount;
-                outBufLen -= outCount;
-            } else {
-                /* halt output */
-                goto no_room;
-            }
-        }
-        break;
-    case EVENT_TYPE_STRING:
-        /* UTF-8 chars, not NULL-terminated */
-        {
-            unsigned int strLen;
-
-            if (eventDataLen < 4)
-                return -1;
-            strLen = get4LE(eventData);
-            eventData += 4;
-            eventDataLen -= 4;
-
-            if (eventDataLen < strLen)
-                return -1;
-
-            if (strLen < outBufLen) {
-                memcpy(outBuf, eventData, strLen);
-                outBuf += strLen;
-                outBufLen -= strLen;
-            } else if (outBufLen > 0) {
-                /* copy what we can */
-                memcpy(outBuf, eventData, outBufLen);
-                outBuf += outBufLen;
-                outBufLen -= outBufLen;
-                goto no_room;
-            }
-            eventData += strLen;
-            eventDataLen -= strLen;
-            break;
-        }
-    case EVENT_TYPE_LIST:
-        /* N items, all different types */
-        {
-            unsigned char count;
-            int i;
-
-            if (eventDataLen < 1)
-                return -1;
-
-            count = *eventData++;
-            eventDataLen--;
-
-            if (outBufLen > 0) {
-                *outBuf++ = '[';
-                outBufLen--;
-            } else {
-                goto no_room;
-            }
-
-            for (i = 0; i < count; i++) {
-                result = android_log_printBinaryEvent(&eventData, &eventDataLen,
-                        &outBuf, &outBufLen);
-                if (result != 0)
-                    goto bail;
-
-                if (i < count-1) {
-                    if (outBufLen > 0) {
-                        *outBuf++ = ',';
-                        outBufLen--;
-                    } else {
-                        goto no_room;
-                    }
-                }
-            }
-
-            if (outBufLen > 0) {
-                *outBuf++ = ']';
-                outBufLen--;
-            } else {
-                goto no_room;
-            }
-        }
-        break;
-    default:
-        fprintf(stderr, "Unknown binary event type %d\n", type);
-        return -1;
-    }
-
-bail:
-    *pEventData = eventData;
-    *pEventDataLen = eventDataLen;
-    *pOutBuf = outBuf;
-    *pOutBufLen = outBufLen;
-    return result;
-
-no_room:
-    result = 1;
-    goto bail;
-}
-
-/**
- * Convert a binary log entry to ASCII form.
- *
- * For convenience we mimic the processLogBuffer API.  There is no
- * pre-defined output length for the binary data, since we're free to format
- * it however we choose, which means we can't really use a fixed-size buffer
- * here.
- */
-int android_log_processBinaryLogBuffer(struct logger_entry *buf,
-    AndroidLogEntry *entry, const EventTagMap* map, char* messageBuf,
-    int messageBufLen)
-{
-    size_t inCount;
-    unsigned int tagIndex;
-    const unsigned char* eventData;
-
-    entry->tv_sec = buf->sec;
-    entry->tv_nsec = buf->nsec;
-    entry->priority = ANDROID_LOG_INFO;
-    entry->pid = buf->pid;
-    entry->tid = buf->tid;
-
-    /*
-     * Pull the tag out.
-     */
-    eventData = (const unsigned char*) buf->msg;
-    struct logger_entry_v2 *buf2 = (struct logger_entry_v2 *)buf;
-    if (buf2->hdr_size) {
-        eventData = ((unsigned char *)buf2) + buf2->hdr_size;
-    }
-    inCount = buf->len;
-    if (inCount < 4)
-        return -1;
-    tagIndex = get4LE(eventData);
-    eventData += 4;
-    inCount -= 4;
-
-    if (map != NULL) {
-        entry->tag = android_lookupEventTag(map, tagIndex);
-    } else {
-        entry->tag = NULL;
-    }
-
-    /*
-     * If we don't have a map, or didn't find the tag number in the map,
-     * stuff a generated tag value into the start of the output buffer and
-     * shift the buffer pointers down.
-     */
-    if (entry->tag == NULL) {
-        int tagLen;
-
-        tagLen = snprintf(messageBuf, messageBufLen, "[%d]", tagIndex);
-        entry->tag = messageBuf;
-        messageBuf += tagLen+1;
-        messageBufLen -= tagLen+1;
-    }
-
-    /*
-     * Format the event log data into the buffer.
-     */
-    char* outBuf = messageBuf;
-    size_t outRemaining = messageBufLen-1;      /* leave one for nul byte */
-    int result;
-    result = android_log_printBinaryEvent(&eventData, &inCount, &outBuf,
-                &outRemaining);
-    if (result < 0) {
-        fprintf(stderr, "Binary log entry conversion failed\n");
-        return -1;
-    } else if (result == 1) {
-        if (outBuf > messageBuf) {
-            /* leave an indicator */
-            *(outBuf-1) = '!';
-        } else {
-            /* no room to output anything at all */
-            *outBuf++ = '!';
-            outRemaining--;
-        }
-        /* pretend we ate all the data */
-        inCount = 0;
-    }
-
-    /* eat the silly terminating '\n' */
-    if (inCount == 1 && *eventData == '\n') {
-        eventData++;
-        inCount--;
-    }
-
-    if (inCount != 0) {
-        fprintf(stderr,
-            "Warning: leftover binary log data (%zu bytes)\n", inCount);
-    }
-
-    /*
-     * Terminate the buffer.  The NUL byte does not count as part of
-     * entry->messageLen.
-     */
-    *outBuf = '\0';
-    entry->messageLen = outBuf - messageBuf;
-    assert(entry->messageLen == (messageBufLen-1) - outRemaining);
-
-    entry->message = messageBuf;
-
-    return 0;
-}
-
-/*
- * One utf8 character at a time
- *
- * Returns the length of the utf8 character in the buffer,
- * or -1 if illegal or truncated
- *
- * Open coded from libutils/Unicode.cpp, borrowed from utf8_length(),
- * can not remove from here because of library circular dependencies.
- * Expect one-day utf8_character_length with the same signature could
- * _also_ be part of libutils/Unicode.cpp if its usefullness needs to
- * propagate globally.
- */
-WEAK ssize_t utf8_character_length(const char *src, size_t len)
-{
-    const char *cur = src;
-    const char first_char = *cur++;
-    static const uint32_t kUnicodeMaxCodepoint = 0x0010FFFF;
-    int32_t mask, to_ignore_mask;
-    size_t num_to_read;
-    uint32_t utf32;
-
-    if ((first_char & 0x80) == 0) { /* ASCII */
-        return 1;
-    }
-
-    /*
-     * (UTF-8's character must not be like 10xxxxxx,
-     *  but 110xxxxx, 1110xxxx, ... or 1111110x)
-     */
-    if ((first_char & 0x40) == 0) {
-        return -1;
-    }
-
-    for (utf32 = 1, num_to_read = 1, mask = 0x40, to_ignore_mask = 0x80;
-         num_to_read < 5 && (first_char & mask);
-         num_to_read++, to_ignore_mask |= mask, mask >>= 1) {
-        if (num_to_read > len) {
-            return -1;
-        }
-        if ((*cur & 0xC0) != 0x80) { /* can not be 10xxxxxx? */
-            return -1;
-        }
-        utf32 = (utf32 << 6) + (*cur++ & 0b00111111);
-    }
-    /* "first_char" must be (110xxxxx - 11110xxx) */
-    if (num_to_read >= 5) {
-        return -1;
-    }
-    to_ignore_mask |= mask;
-    utf32 |= ((~to_ignore_mask) & first_char) << (6 * (num_to_read - 1));
-    if (utf32 > kUnicodeMaxCodepoint) {
-        return -1;
-    }
-    return num_to_read;
-}
-
-/*
- * Convert to printable from message to p buffer, return string length. If p is
- * NULL, do not copy, but still return the expected string length.
- */
-static size_t convertPrintable(char *p, const char *message, size_t messageLen)
-{
-    char *begin = p;
-    bool print = p != NULL;
-
-    while (messageLen) {
-        char buf[6];
-        ssize_t len = sizeof(buf) - 1;
-        if ((size_t)len > messageLen) {
-            len = messageLen;
-        }
-        len = utf8_character_length(message, len);
-
-        if (len < 0) {
-            snprintf(buf, sizeof(buf),
-                     ((messageLen > 1) && isdigit(message[1]))
-                         ? "\\%03o"
-                         : "\\%o",
-                     *message & 0377);
-            len = 1;
-        } else {
-            buf[0] = '\0';
-            if (len == 1) {
-                if (*message == '\a') {
-                    strcpy(buf, "\\a");
-                } else if (*message == '\b') {
-                    strcpy(buf, "\\b");
-                } else if (*message == '\t') {
-                    strcpy(buf, "\\t");
-                } else if (*message == '\v') {
-                    strcpy(buf, "\\v");
-                } else if (*message == '\f') {
-                    strcpy(buf, "\\f");
-                } else if (*message == '\r') {
-                    strcpy(buf, "\\r");
-                } else if (*message == '\\') {
-                    strcpy(buf, "\\\\");
-                } else if ((*message < ' ') || (*message & 0x80)) {
-                    snprintf(buf, sizeof(buf), "\\%o", *message & 0377);
-                }
-            }
-            if (!buf[0]) {
-                strncpy(buf, message, len);
-                buf[len] = '\0';
-            }
-        }
-        if (print) {
-            strcpy(p, buf);
-        }
-        p += strlen(buf);
-        message += len;
-        messageLen -= len;
-    }
-    return p - begin;
-}
-
-/**
- * Formats a log message into a buffer
- *
- * Uses defaultBuffer if it can, otherwise malloc()'s a new buffer
- * If return value != defaultBuffer, caller must call free()
- * Returns NULL on malloc error
- */
-
-char *android_log_formatLogLine (
-    AndroidLogFormat *p_format,
-    char *defaultBuffer,
-    size_t defaultBufferSize,
-    const AndroidLogEntry *entry,
-    size_t *p_outLength)
-{
-#if !defined(_WIN32)
-    struct tm tmBuf;
-#endif
-    struct tm* ptm;
-    char timeBuf[32]; /* good margin, 23+nul for msec, 26+nul for usec */
-    char prefixBuf[128], suffixBuf[128];
-    char priChar;
-    int prefixSuffixIsHeaderFooter = 0;
-    char * ret = NULL;
-
-    priChar = filterPriToChar(entry->priority);
-    size_t prefixLen = 0, suffixLen = 0;
-    size_t len;
-
-    /*
-     * Get the current date/time in pretty form
-     *
-     * It's often useful when examining a log with "less" to jump to
-     * a specific point in the file by searching for the date/time stamp.
-     * For this reason it's very annoying to have regexp meta characters
-     * in the time stamp.  Don't use forward slashes, parenthesis,
-     * brackets, asterisks, or other special chars here.
-     */
-#if !defined(_WIN32)
-    ptm = localtime_r(&(entry->tv_sec), &tmBuf);
-#else
-    ptm = localtime(&(entry->tv_sec));
-#endif
-    /* strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", ptm); */
-    strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
-    len = strlen(timeBuf);
-    if (p_format->usec_time_output) {
-        snprintf(timeBuf + len, sizeof(timeBuf) - len,
-                 ".%06ld", entry->tv_nsec / 1000);
-    } else {
-        snprintf(timeBuf + len, sizeof(timeBuf) - len,
-                 ".%03ld", entry->tv_nsec / 1000000);
-    }
-
-    /*
-     * Construct a buffer containing the log header and log message.
-     */
-    if (p_format->colored_output) {
-        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), "\x1B[38;5;%dm",
-                             colorFromPri(entry->priority));
-        prefixLen = MIN(prefixLen, sizeof(prefixBuf));
-        suffixLen = snprintf(suffixBuf, sizeof(suffixBuf), "\x1B[0m");
-        suffixLen = MIN(suffixLen, sizeof(suffixBuf));
-    }
-
-    switch (p_format->format) {
-        case FORMAT_TAG:
-            len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
-                "%c/%-8s: ", priChar, entry->tag);
-            strcpy(suffixBuf + suffixLen, "\n");
-            ++suffixLen;
-            break;
-        case FORMAT_PROCESS:
-            len = snprintf(suffixBuf + suffixLen, sizeof(suffixBuf) - suffixLen,
-                "  (%s)\n", entry->tag);
-            suffixLen += MIN(len, sizeof(suffixBuf) - suffixLen);
-            len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
-                "%c(%5d) ", priChar, entry->pid);
-            break;
-        case FORMAT_THREAD:
-            len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
-                "%c(%5d:%5d) ", priChar, entry->pid, entry->tid);
-            strcpy(suffixBuf + suffixLen, "\n");
-            ++suffixLen;
-            break;
-        case FORMAT_RAW:
-            prefixBuf[prefixLen] = 0;
-            len = 0;
-            strcpy(suffixBuf + suffixLen, "\n");
-            ++suffixLen;
-            break;
-        case FORMAT_TIME:
-            len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
-                "%s %c/%-8s(%5d): ", timeBuf, priChar, entry->tag, entry->pid);
-            strcpy(suffixBuf + suffixLen, "\n");
-            ++suffixLen;
-            break;
-        case FORMAT_THREADTIME:
-            len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
-                "%s %5d %5d %c %-8s: ", timeBuf,
-                entry->pid, entry->tid, priChar, entry->tag);
-            strcpy(suffixBuf + suffixLen, "\n");
-            ++suffixLen;
-            break;
-        case FORMAT_LONG:
-            len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
-                "[ %s %5d:%5d %c/%-8s ]\n",
-                timeBuf, entry->pid, entry->tid, priChar, entry->tag);
-            strcpy(suffixBuf + suffixLen, "\n\n");
-            suffixLen += 2;
-            prefixSuffixIsHeaderFooter = 1;
-            break;
-        case FORMAT_BRIEF:
-        default:
-            len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
-                "%c/%-8s(%5d): ", priChar, entry->tag, entry->pid);
-            strcpy(suffixBuf + suffixLen, "\n");
-            ++suffixLen;
-            break;
-    }
-
-    /* snprintf has a weird return value.   It returns what would have been
-     * written given a large enough buffer.  In the case that the prefix is
-     * longer then our buffer(128), it messes up the calculations below
-     * possibly causing heap corruption.  To avoid this we double check and
-     * set the length at the maximum (size minus null byte)
-     */
-    prefixLen += MIN(len, sizeof(prefixBuf) - prefixLen);
-    suffixLen = MIN(suffixLen, sizeof(suffixBuf));
-
-    /* the following code is tragically unreadable */
-
-    size_t numLines;
-    char *p;
-    size_t bufferSize;
-    const char *pm;
-
-    if (prefixSuffixIsHeaderFooter) {
-        /* we're just wrapping message with a header/footer */
-        numLines = 1;
-    } else {
-        pm = entry->message;
-        numLines = 0;
-
-        /*
-         * The line-end finding here must match the line-end finding
-         * in for ( ... numLines...) loop below
-         */
-        while (pm < (entry->message + entry->messageLen)) {
-            if (*pm++ == '\n') numLines++;
-        }
-        /* plus one line for anything not newline-terminated at the end */
-        if (pm > entry->message && *(pm-1) != '\n') numLines++;
-    }
-
-    /*
-     * this is an upper bound--newlines in message may be counted
-     * extraneously
-     */
-    bufferSize = (numLines * (prefixLen + suffixLen)) + 1;
-    if (p_format->printable_output) {
-        /* Calculate extra length to convert non-printable to printable */
-        bufferSize += convertPrintable(NULL, entry->message, entry->messageLen);
-    } else {
-        bufferSize += entry->messageLen;
-    }
-
-    if (defaultBufferSize >= bufferSize) {
-        ret = defaultBuffer;
-    } else {
-        ret = (char *)malloc(bufferSize);
-
-        if (ret == NULL) {
-            return ret;
-        }
-    }
-
-    ret[0] = '\0';       /* to start strcat off */
-
-    p = ret;
-    pm = entry->message;
-
-    if (prefixSuffixIsHeaderFooter) {
-        strcat(p, prefixBuf);
-        p += prefixLen;
-        if (p_format->printable_output) {
-            p += convertPrintable(p, entry->message, entry->messageLen);
-        } else {
-            strncat(p, entry->message, entry->messageLen);
-            p += entry->messageLen;
-        }
-        strcat(p, suffixBuf);
-        p += suffixLen;
-    } else {
-        while(pm < (entry->message + entry->messageLen)) {
-            const char *lineStart;
-            size_t lineLen;
-            lineStart = pm;
-
-            /* Find the next end-of-line in message */
-            while (pm < (entry->message + entry->messageLen)
-                    && *pm != '\n') pm++;
-            lineLen = pm - lineStart;
-
-            strcat(p, prefixBuf);
-            p += prefixLen;
-            if (p_format->printable_output) {
-                p += convertPrintable(p, lineStart, lineLen);
-            } else {
-                strncat(p, lineStart, lineLen);
-                p += lineLen;
-            }
-            strcat(p, suffixBuf);
-            p += suffixLen;
-
-            if (*pm == '\n') pm++;
-        }
-    }
-
-    if (p_outLength != NULL) {
-        *p_outLength = p - ret;
-    }
-
-    return ret;
-}
-
-/**
- * Either print or do not print log line, based on filter
- *
- * Returns count bytes written
- */
-
-int android_log_printLogLine(
-    AndroidLogFormat *p_format,
-    int fd,
-    const AndroidLogEntry *entry)
-{
-    int ret;
-    char defaultBuffer[512];
-    char *outBuffer = NULL;
-    size_t totalLen;
-
-    outBuffer = android_log_formatLogLine(p_format, defaultBuffer,
-            sizeof(defaultBuffer), entry, &totalLen);
-
-    if (!outBuffer)
-        return -1;
-
-    do {
-        ret = write(fd, outBuffer, totalLen);
-    } while (ret < 0 && errno == EINTR);
-
-    if (ret < 0) {
-        fprintf(stderr, "+++ LOG: write failed (errno=%d)\n", errno);
-        ret = 0;
-        goto done;
-    }
-
-    if (((size_t)ret) < totalLen) {
-        fprintf(stderr, "+++ LOG: write partial (%d of %d)\n", ret,
-                (int)totalLen);
-        goto done;
-    }
-
-done:
-    if (outBuffer != defaultBuffer) {
-        free(outBuffer);
-    }
-
-    return ret;
-}
diff --git a/liblog/logprint.cpp b/liblog/logprint.cpp
new file mode 100644
index 0000000..a5c5edd
--- /dev/null
+++ b/liblog/logprint.cpp
@@ -0,0 +1,1748 @@
+/*
+**
+** Copyright 2006-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 __MINGW32__
+#define HAVE_STRSEP
+#endif
+
+#include <log/logprint.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#ifndef __MINGW32__
+#include <pwd.h>
+#endif
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <wchar.h>
+
+#include <cutils/list.h>
+
+#include <log/log.h>
+#include <log/log_read.h>
+#include <private/android_logger.h>
+
+#define MS_PER_NSEC 1000000
+#define US_PER_NSEC 1000
+
+#ifndef MIN
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+typedef struct FilterInfo_t {
+  char* mTag;
+  android_LogPriority mPri;
+  struct FilterInfo_t* p_next;
+} FilterInfo;
+
+struct AndroidLogFormat_t {
+  android_LogPriority global_pri;
+  FilterInfo* filters;
+  AndroidLogPrintFormat format;
+  bool colored_output;
+  bool usec_time_output;
+  bool nsec_time_output;
+  bool printable_output;
+  bool year_output;
+  bool zone_output;
+  bool epoch_output;
+  bool monotonic_output;
+  bool uid_output;
+  bool descriptive_output;
+};
+
+/*
+ * API issues prevent us from exposing "descriptive" in AndroidLogFormat_t
+ * during android_log_processBinaryLogBuffer(), so we break layering.
+ */
+static bool descriptive_output = false;
+
+/*
+ * 8-bit color tags. See ECMA-48 Set Graphics Rendition in
+ * [console_codes(4)](https://man7.org/linux/man-pages/man4/console_codes.4.html).
+ *
+ * The text manipulation character stream is defined as:
+ *   ESC [ <parameter #> m
+ *
+ * We use "set <color> foreground" escape sequences instead of
+ * "256/24-bit foreground color". This allows colors to render
+ * according to user preferences in terminal emulator settings
+ */
+#define ANDROID_COLOR_BLUE 34
+#define ANDROID_COLOR_DEFAULT 39
+#define ANDROID_COLOR_GREEN 32
+#define ANDROID_COLOR_RED 31
+#define ANDROID_COLOR_YELLOW 33
+
+static FilterInfo* filterinfo_new(const char* tag, android_LogPriority pri) {
+  FilterInfo* p_ret;
+
+  p_ret = (FilterInfo*)calloc(1, sizeof(FilterInfo));
+  p_ret->mTag = strdup(tag);
+  p_ret->mPri = pri;
+
+  return p_ret;
+}
+
+/* balance to above, filterinfo_free left unimplemented */
+
+/*
+ * Note: also accepts 0-9 priorities
+ * returns ANDROID_LOG_UNKNOWN if the character is unrecognized
+ */
+static android_LogPriority filterCharToPri(char c) {
+  android_LogPriority pri;
+
+  c = tolower(c);
+
+  if (c >= '0' && c <= '9') {
+    if (c >= ('0' + ANDROID_LOG_SILENT)) {
+      pri = ANDROID_LOG_VERBOSE;
+    } else {
+      pri = (android_LogPriority)(c - '0');
+    }
+  } else if (c == 'v') {
+    pri = ANDROID_LOG_VERBOSE;
+  } else if (c == 'd') {
+    pri = ANDROID_LOG_DEBUG;
+  } else if (c == 'i') {
+    pri = ANDROID_LOG_INFO;
+  } else if (c == 'w') {
+    pri = ANDROID_LOG_WARN;
+  } else if (c == 'e') {
+    pri = ANDROID_LOG_ERROR;
+  } else if (c == 'f') {
+    pri = ANDROID_LOG_FATAL;
+  } else if (c == 's') {
+    pri = ANDROID_LOG_SILENT;
+  } else if (c == '*') {
+    pri = ANDROID_LOG_DEFAULT;
+  } else {
+    pri = ANDROID_LOG_UNKNOWN;
+  }
+
+  return pri;
+}
+
+static char filterPriToChar(android_LogPriority pri) {
+  switch (pri) {
+    /* clang-format off */
+    case ANDROID_LOG_VERBOSE: return 'V';
+    case ANDROID_LOG_DEBUG:   return 'D';
+    case ANDROID_LOG_INFO:    return 'I';
+    case ANDROID_LOG_WARN:    return 'W';
+    case ANDROID_LOG_ERROR:   return 'E';
+    case ANDROID_LOG_FATAL:   return 'F';
+    case ANDROID_LOG_SILENT:  return 'S';
+
+    case ANDROID_LOG_DEFAULT:
+    case ANDROID_LOG_UNKNOWN:
+    default:                  return '?';
+      /* clang-format on */
+  }
+}
+
+static int colorFromPri(android_LogPriority pri) {
+  switch (pri) {
+    /* clang-format off */
+    case ANDROID_LOG_VERBOSE: return ANDROID_COLOR_DEFAULT;
+    case ANDROID_LOG_DEBUG:   return ANDROID_COLOR_BLUE;
+    case ANDROID_LOG_INFO:    return ANDROID_COLOR_GREEN;
+    case ANDROID_LOG_WARN:    return ANDROID_COLOR_YELLOW;
+    case ANDROID_LOG_ERROR:   return ANDROID_COLOR_RED;
+    case ANDROID_LOG_FATAL:   return ANDROID_COLOR_RED;
+    case ANDROID_LOG_SILENT:  return ANDROID_COLOR_DEFAULT;
+
+    case ANDROID_LOG_DEFAULT:
+    case ANDROID_LOG_UNKNOWN:
+    default:                  return ANDROID_COLOR_DEFAULT;
+      /* clang-format on */
+  }
+}
+
+static android_LogPriority filterPriForTag(AndroidLogFormat* p_format, const char* tag) {
+  FilterInfo* p_curFilter;
+
+  for (p_curFilter = p_format->filters; p_curFilter != NULL; p_curFilter = p_curFilter->p_next) {
+    if (0 == strcmp(tag, p_curFilter->mTag)) {
+      if (p_curFilter->mPri == ANDROID_LOG_DEFAULT) {
+        return p_format->global_pri;
+      } else {
+        return p_curFilter->mPri;
+      }
+    }
+  }
+
+  return p_format->global_pri;
+}
+
+/**
+ * returns 1 if this log line should be printed based on its priority
+ * and tag, and 0 if it should not
+ */
+int android_log_shouldPrintLine(AndroidLogFormat* p_format, const char* tag,
+                                android_LogPriority pri) {
+  return pri >= filterPriForTag(p_format, tag);
+}
+
+AndroidLogFormat* android_log_format_new() {
+  AndroidLogFormat* p_ret;
+
+  p_ret = static_cast<AndroidLogFormat*>(calloc(1, sizeof(AndroidLogFormat)));
+
+  p_ret->global_pri = ANDROID_LOG_VERBOSE;
+  p_ret->format = FORMAT_BRIEF;
+  p_ret->colored_output = false;
+  p_ret->usec_time_output = false;
+  p_ret->nsec_time_output = false;
+  p_ret->printable_output = false;
+  p_ret->year_output = false;
+  p_ret->zone_output = false;
+  p_ret->epoch_output = false;
+  p_ret->monotonic_output = false;
+  p_ret->uid_output = false;
+  p_ret->descriptive_output = false;
+  descriptive_output = false;
+
+  return p_ret;
+}
+
+static list_declare(convertHead);
+
+void android_log_format_free(AndroidLogFormat* p_format) {
+  FilterInfo *p_info, *p_info_old;
+
+  p_info = p_format->filters;
+
+  while (p_info != NULL) {
+    p_info_old = p_info;
+    p_info = p_info->p_next;
+
+    free(p_info_old);
+  }
+
+  free(p_format);
+
+  /* Free conversion resource, can always be reconstructed */
+  while (!list_empty(&convertHead)) {
+    struct listnode* node = list_head(&convertHead);
+    list_remove(node);
+    LOG_ALWAYS_FATAL_IF(node == list_head(&convertHead), "corrupted list");
+    free(node);
+  }
+}
+
+int android_log_setPrintFormat(AndroidLogFormat* p_format, AndroidLogPrintFormat format) {
+  switch (format) {
+    case FORMAT_MODIFIER_COLOR:
+      p_format->colored_output = true;
+      return 0;
+    case FORMAT_MODIFIER_TIME_USEC:
+      p_format->usec_time_output = true;
+      return 0;
+    case FORMAT_MODIFIER_TIME_NSEC:
+      p_format->nsec_time_output = true;
+      return 0;
+    case FORMAT_MODIFIER_PRINTABLE:
+      p_format->printable_output = true;
+      return 0;
+    case FORMAT_MODIFIER_YEAR:
+      p_format->year_output = true;
+      return 0;
+    case FORMAT_MODIFIER_ZONE:
+      p_format->zone_output = !p_format->zone_output;
+      return 0;
+    case FORMAT_MODIFIER_EPOCH:
+      p_format->epoch_output = true;
+      return 0;
+    case FORMAT_MODIFIER_MONOTONIC:
+      p_format->monotonic_output = true;
+      return 0;
+    case FORMAT_MODIFIER_UID:
+      p_format->uid_output = true;
+      return 0;
+    case FORMAT_MODIFIER_DESCRIPT:
+      p_format->descriptive_output = true;
+      descriptive_output = true;
+      return 0;
+    default:
+      break;
+  }
+  p_format->format = format;
+  return 1;
+}
+
+#ifndef __MINGW32__
+static const char tz[] = "TZ";
+static const char utc[] = "UTC";
+#endif
+
+/**
+ * Returns FORMAT_OFF on invalid string
+ */
+AndroidLogPrintFormat android_log_formatFromString(const char* formatString) {
+  static AndroidLogPrintFormat format;
+
+  /* clang-format off */
+  if (!strcmp(formatString, "brief")) format = FORMAT_BRIEF;
+  else if (!strcmp(formatString, "process")) format = FORMAT_PROCESS;
+  else if (!strcmp(formatString, "tag")) format = FORMAT_TAG;
+  else if (!strcmp(formatString, "thread")) format = FORMAT_THREAD;
+  else if (!strcmp(formatString, "raw")) format = FORMAT_RAW;
+  else if (!strcmp(formatString, "time")) format = FORMAT_TIME;
+  else if (!strcmp(formatString, "threadtime")) format = FORMAT_THREADTIME;
+  else if (!strcmp(formatString, "long")) format = FORMAT_LONG;
+  else if (!strcmp(formatString, "color")) format = FORMAT_MODIFIER_COLOR;
+  else if (!strcmp(formatString, "colour")) format = FORMAT_MODIFIER_COLOR;
+  else if (!strcmp(formatString, "usec")) format = FORMAT_MODIFIER_TIME_USEC;
+  else if (!strcmp(formatString, "nsec")) format = FORMAT_MODIFIER_TIME_NSEC;
+  else if (!strcmp(formatString, "printable")) format = FORMAT_MODIFIER_PRINTABLE;
+  else if (!strcmp(formatString, "year")) format = FORMAT_MODIFIER_YEAR;
+  else if (!strcmp(formatString, "zone")) format = FORMAT_MODIFIER_ZONE;
+  else if (!strcmp(formatString, "epoch")) format = FORMAT_MODIFIER_EPOCH;
+  else if (!strcmp(formatString, "monotonic")) format = FORMAT_MODIFIER_MONOTONIC;
+  else if (!strcmp(formatString, "uid")) format = FORMAT_MODIFIER_UID;
+  else if (!strcmp(formatString, "descriptive")) format = FORMAT_MODIFIER_DESCRIPT;
+    /* clang-format on */
+
+#ifndef __MINGW32__
+  else {
+    extern char* tzname[2];
+    static const char gmt[] = "GMT";
+    char* cp = getenv(tz);
+    if (cp) {
+      cp = strdup(cp);
+    }
+    setenv(tz, formatString, 1);
+    /*
+     * Run tzset here to determine if the timezone is legitimate. If the
+     * zone is GMT, check if that is what was asked for, if not then
+     * did not match any on the system; report an error to caller.
+     */
+    tzset();
+    if (!tzname[0] ||
+        ((!strcmp(tzname[0], utc) || !strcmp(tzname[0], gmt))                  /* error? */
+         && strcasecmp(formatString, utc) && strcasecmp(formatString, gmt))) { /* ok */
+      if (cp) {
+        setenv(tz, cp, 1);
+      } else {
+        unsetenv(tz);
+      }
+      tzset();
+      format = FORMAT_OFF;
+    } else {
+      format = FORMAT_MODIFIER_ZONE;
+    }
+    free(cp);
+  }
+#endif
+
+  return format;
+}
+
+/**
+ * filterExpression: a single filter expression
+ * eg "AT:d"
+ *
+ * returns 0 on success and -1 on invalid expression
+ *
+ * Assumes single threaded execution
+ */
+
+int android_log_addFilterRule(AndroidLogFormat* p_format, const char* filterExpression) {
+  size_t tagNameLength;
+  android_LogPriority pri = ANDROID_LOG_DEFAULT;
+
+  tagNameLength = strcspn(filterExpression, ":");
+
+  if (tagNameLength == 0) {
+    goto error;
+  }
+
+  if (filterExpression[tagNameLength] == ':') {
+    pri = filterCharToPri(filterExpression[tagNameLength + 1]);
+
+    if (pri == ANDROID_LOG_UNKNOWN) {
+      goto error;
+    }
+  }
+
+  if (0 == strncmp("*", filterExpression, tagNameLength)) {
+    /*
+     * This filter expression refers to the global filter
+     * The default level for this is DEBUG if the priority
+     * is unspecified
+     */
+    if (pri == ANDROID_LOG_DEFAULT) {
+      pri = ANDROID_LOG_DEBUG;
+    }
+
+    p_format->global_pri = pri;
+  } else {
+    /*
+     * for filter expressions that don't refer to the global
+     * filter, the default is verbose if the priority is unspecified
+     */
+    if (pri == ANDROID_LOG_DEFAULT) {
+      pri = ANDROID_LOG_VERBOSE;
+    }
+
+    char* tagName;
+
+/*
+ * Presently HAVE_STRNDUP is never defined, so the second case is always taken
+ * Darwin doesn't have strndup, everything else does
+ */
+#ifdef HAVE_STRNDUP
+    tagName = strndup(filterExpression, tagNameLength);
+#else
+    /* a few extra bytes copied... */
+    tagName = strdup(filterExpression);
+    tagName[tagNameLength] = '\0';
+#endif /*HAVE_STRNDUP*/
+
+    FilterInfo* p_fi = filterinfo_new(tagName, pri);
+    free(tagName);
+
+    p_fi->p_next = p_format->filters;
+    p_format->filters = p_fi;
+  }
+
+  return 0;
+error:
+  return -1;
+}
+
+#ifndef HAVE_STRSEP
+/* KISS replacement helper for below */
+static char* strsep(char** stringp, const char* delim) {
+  char* token;
+  char* ret = *stringp;
+
+  if (!ret || !*ret) {
+    return NULL;
+  }
+  token = strpbrk(ret, delim);
+  if (token) {
+    *token = '\0';
+    ++token;
+  } else {
+    token = ret + strlen(ret);
+  }
+  *stringp = token;
+  return ret;
+}
+#endif
+
+/**
+ * filterString: a comma/whitespace-separated set of filter expressions
+ *
+ * eg "AT:d *:i"
+ *
+ * returns 0 on success and -1 on invalid expression
+ *
+ * Assumes single threaded execution
+ *
+ */
+int android_log_addFilterString(AndroidLogFormat* p_format, const char* filterString) {
+  char* filterStringCopy = strdup(filterString);
+  char* p_cur = filterStringCopy;
+  char* p_ret;
+  int err;
+
+  /* Yes, I'm using strsep */
+  while (NULL != (p_ret = strsep(&p_cur, " \t,"))) {
+    /* ignore whitespace-only entries */
+    if (p_ret[0] != '\0') {
+      err = android_log_addFilterRule(p_format, p_ret);
+
+      if (err < 0) {
+        goto error;
+      }
+    }
+  }
+
+  free(filterStringCopy);
+  return 0;
+error:
+  free(filterStringCopy);
+  return -1;
+}
+
+/**
+ * Splits a wire-format buffer into an AndroidLogEntry
+ * entry allocated by caller. Pointers will point directly into buf
+ *
+ * Returns 0 on success and -1 on invalid wire format (entry will be
+ * in unspecified state)
+ */
+int android_log_processLogBuffer(struct logger_entry* buf, AndroidLogEntry* entry) {
+  entry->message = NULL;
+  entry->messageLen = 0;
+
+  entry->tv_sec = buf->sec;
+  entry->tv_nsec = buf->nsec;
+  entry->uid = -1;
+  entry->pid = buf->pid;
+  entry->tid = buf->tid;
+
+  /*
+   * format: <priority:1><tag:N>\0<message:N>\0
+   *
+   * tag str
+   *   starts at buf + buf->hdr_size + 1
+   * msg
+   *   starts at buf + buf->hdr_size + 1 + len(tag) + 1
+   *
+   * The message may have been truncated.  When that happens, we must null-terminate the message
+   * ourselves.
+   */
+  if (buf->len < 3) {
+    /*
+     * An well-formed entry must consist of at least a priority
+     * and two null characters
+     */
+    fprintf(stderr, "+++ LOG: entry too small\n");
+    return -1;
+  }
+
+  int msgStart = -1;
+  int msgEnd = -1;
+
+  int i;
+  if (buf->hdr_size < sizeof(logger_entry)) {
+    fprintf(stderr, "+++ LOG: hdr_size must be at least as big as struct logger_entry\n");
+    return -1;
+  }
+  char* msg = reinterpret_cast<char*>(buf) + buf->hdr_size;
+  entry->uid = buf->uid;
+
+  for (i = 1; i < buf->len; i++) {
+    if (msg[i] == '\0') {
+      if (msgStart == -1) {
+        msgStart = i + 1;
+      } else {
+        msgEnd = i;
+        break;
+      }
+    }
+  }
+
+  if (msgStart == -1) {
+    /* +++ LOG: malformed log message, DYB */
+    for (i = 1; i < buf->len; i++) {
+      /* odd characters in tag? */
+      if ((msg[i] <= ' ') || (msg[i] == ':') || (msg[i] >= 0x7f)) {
+        msg[i] = '\0';
+        msgStart = i + 1;
+        break;
+      }
+    }
+    if (msgStart == -1) {
+      msgStart = buf->len - 1; /* All tag, no message, print truncates */
+    }
+  }
+  if (msgEnd == -1) {
+    /* incoming message not null-terminated; force it */
+    msgEnd = buf->len - 1; /* may result in msgEnd < msgStart */
+    msg[msgEnd] = '\0';
+  }
+
+  entry->priority = static_cast<android_LogPriority>(msg[0]);
+  entry->tag = msg + 1;
+  entry->tagLen = msgStart - 1;
+  entry->message = msg + msgStart;
+  entry->messageLen = (msgEnd < msgStart) ? 0 : (msgEnd - msgStart);
+
+  return 0;
+}
+
+static bool findChar(const char** cp, size_t* len, int c) {
+  while ((*len) && isspace(*(*cp))) {
+    ++(*cp);
+    --(*len);
+  }
+  if (c == INT_MAX) return *len;
+  if ((*len) && (*(*cp) == c)) {
+    ++(*cp);
+    --(*len);
+    return true;
+  }
+  return false;
+}
+
+/*
+ * Recursively convert binary log data to printable form.
+ *
+ * This needs to be recursive because you can have lists of lists.
+ *
+ * If we run out of room, we stop processing immediately.  It's important
+ * for us to check for space on every output element to avoid producing
+ * garbled output.
+ *
+ * Returns 0 on success, 1 on buffer full, -1 on failure.
+ */
+enum objectType {
+  TYPE_OBJECTS = '1',
+  TYPE_BYTES = '2',
+  TYPE_MILLISECONDS = '3',
+  TYPE_ALLOCATIONS = '4',
+  TYPE_ID = '5',
+  TYPE_PERCENT = '6',
+  TYPE_MONOTONIC = 's'
+};
+
+static int android_log_printBinaryEvent(const unsigned char** pEventData, size_t* pEventDataLen,
+                                        char** pOutBuf, size_t* pOutBufLen, const char** fmtStr,
+                                        size_t* fmtLen) {
+  const unsigned char* eventData = *pEventData;
+  size_t eventDataLen = *pEventDataLen;
+  char* outBuf = *pOutBuf;
+  char* outBufSave = outBuf;
+  size_t outBufLen = *pOutBufLen;
+  size_t outBufLenSave = outBufLen;
+  unsigned char type;
+  size_t outCount = 0;
+  int result = 0;
+  const char* cp;
+  size_t len;
+  int64_t lval;
+
+  if (eventDataLen < 1) return -1;
+
+  type = *eventData;
+
+  cp = NULL;
+  len = 0;
+  if (fmtStr && *fmtStr && fmtLen && *fmtLen && **fmtStr) {
+    cp = *fmtStr;
+    len = *fmtLen;
+  }
+  /*
+   * event.logtag format specification:
+   *
+   * Optionally, after the tag names can be put a description for the value(s)
+   * of the tag. Description are in the format
+   *    (<name>|data type[|data unit])
+   * Multiple values are separated by commas.
+   *
+   * The data type is a number from the following values:
+   * 1: int
+   * 2: long
+   * 3: string
+   * 4: list
+   * 5: float
+   *
+   * The data unit is a number taken from the following list:
+   * 1: Number of objects
+   * 2: Number of bytes
+   * 3: Number of milliseconds
+   * 4: Number of allocations
+   * 5: Id
+   * 6: Percent
+   * s: Number of seconds (monotonic time)
+   * Default value for data of type int/long is 2 (bytes).
+   */
+  if (!cp || !findChar(&cp, &len, '(')) {
+    len = 0;
+  } else {
+    char* outBufLastSpace = NULL;
+
+    findChar(&cp, &len, INT_MAX);
+    while (len && *cp && (*cp != '|') && (*cp != ')')) {
+      if (outBufLen <= 0) {
+        /* halt output */
+        goto no_room;
+      }
+      outBufLastSpace = isspace(*cp) ? outBuf : NULL;
+      *outBuf = *cp;
+      ++outBuf;
+      ++cp;
+      --outBufLen;
+      --len;
+    }
+    if (outBufLastSpace) {
+      outBufLen += outBuf - outBufLastSpace;
+      outBuf = outBufLastSpace;
+    }
+    if (outBufLen <= 0) {
+      /* halt output */
+      goto no_room;
+    }
+    if (outBufSave != outBuf) {
+      *outBuf = '=';
+      ++outBuf;
+      --outBufLen;
+    }
+
+    if (findChar(&cp, &len, '|') && findChar(&cp, &len, INT_MAX)) {
+      static const unsigned char typeTable[] = {EVENT_TYPE_INT, EVENT_TYPE_LONG, EVENT_TYPE_STRING,
+                                                EVENT_TYPE_LIST, EVENT_TYPE_FLOAT};
+
+      if ((*cp >= '1') && (*cp < (char)('1' + (sizeof(typeTable) / sizeof(typeTable[0])))) &&
+          (type != typeTable[(size_t)(*cp - '1')]))
+        len = 0;
+
+      if (len) {
+        ++cp;
+        --len;
+      } else {
+        /* reset the format */
+        outBuf = outBufSave;
+        outBufLen = outBufLenSave;
+      }
+    }
+  }
+  outCount = 0;
+  lval = 0;
+  switch (type) {
+    case EVENT_TYPE_INT:
+      /* 32-bit signed int */
+      {
+        if (eventDataLen < sizeof(android_event_int_t)) return -1;
+        auto* event_int = reinterpret_cast<const android_event_int_t*>(eventData);
+        lval = event_int->data;
+        eventData += sizeof(android_event_int_t);
+        eventDataLen -= sizeof(android_event_int_t);
+      }
+      goto pr_lval;
+    case EVENT_TYPE_LONG:
+      /* 64-bit signed long */
+      if (eventDataLen < sizeof(android_event_long_t)) {
+        return -1;
+      }
+      {
+        auto* event_long = reinterpret_cast<const android_event_long_t*>(eventData);
+        lval = event_long->data;
+      }
+      eventData += sizeof(android_event_long_t);
+      eventDataLen -= sizeof(android_event_long_t);
+    pr_lval:
+      outCount = snprintf(outBuf, outBufLen, "%" PRId64, lval);
+      if (outCount < outBufLen) {
+        outBuf += outCount;
+        outBufLen -= outCount;
+      } else {
+        /* halt output */
+        goto no_room;
+      }
+      break;
+    case EVENT_TYPE_FLOAT:
+      /* float */
+      {
+        if (eventDataLen < sizeof(android_event_float_t)) return -1;
+        auto* event_float = reinterpret_cast<const android_event_float_t*>(eventData);
+        float fval = event_float->data;
+        eventData += sizeof(android_event_int_t);
+        eventDataLen -= sizeof(android_event_int_t);
+
+        outCount = snprintf(outBuf, outBufLen, "%f", fval);
+        if (outCount < outBufLen) {
+          outBuf += outCount;
+          outBufLen -= outCount;
+        } else {
+          /* halt output */
+          goto no_room;
+        }
+      }
+      break;
+    case EVENT_TYPE_STRING:
+      /* UTF-8 chars, not NULL-terminated */
+      {
+        if (eventDataLen < sizeof(android_event_string_t)) return -1;
+        auto* event_string = reinterpret_cast<const android_event_string_t*>(eventData);
+        unsigned int strLen = event_string->length;
+        eventData += sizeof(android_event_string_t);
+        eventDataLen -= sizeof(android_event_string_t);
+
+        if (eventDataLen < strLen) {
+          result = -1; /* mark truncated */
+          strLen = eventDataLen;
+        }
+
+        if (cp && (strLen == 0)) {
+          /* reset the format if no content */
+          outBuf = outBufSave;
+          outBufLen = outBufLenSave;
+        }
+        if (strLen < outBufLen) {
+          memcpy(outBuf, eventData, strLen);
+          outBuf += strLen;
+          outBufLen -= strLen;
+        } else {
+          if (outBufLen > 0) {
+            /* copy what we can */
+            memcpy(outBuf, eventData, outBufLen);
+            outBuf += outBufLen;
+            outBufLen -= outBufLen;
+          }
+          if (!result) result = 1; /* if not truncated, return no room */
+        }
+        eventData += strLen;
+        eventDataLen -= strLen;
+        if (result != 0) goto bail;
+        break;
+      }
+    case EVENT_TYPE_LIST:
+      /* N items, all different types */
+      {
+        if (eventDataLen < sizeof(android_event_list_t)) return -1;
+        auto* event_list = reinterpret_cast<const android_event_list_t*>(eventData);
+
+        int8_t count = event_list->element_count;
+        eventData += sizeof(android_event_list_t);
+        eventDataLen -= sizeof(android_event_list_t);
+
+        if (outBufLen <= 0) goto no_room;
+
+        *outBuf++ = '[';
+        outBufLen--;
+
+        for (int i = 0; i < count; i++) {
+          result = android_log_printBinaryEvent(&eventData, &eventDataLen, &outBuf, &outBufLen,
+                                                fmtStr, fmtLen);
+          if (result != 0) goto bail;
+
+          if (i < (count - 1)) {
+            if (outBufLen <= 0) goto no_room;
+            *outBuf++ = ',';
+            outBufLen--;
+          }
+        }
+
+        if (outBufLen <= 0) goto no_room;
+
+        *outBuf++ = ']';
+        outBufLen--;
+      }
+      break;
+    default:
+      fprintf(stderr, "Unknown binary event type %d\n", type);
+      return -1;
+  }
+  if (cp && len) {
+    if (findChar(&cp, &len, '|') && findChar(&cp, &len, INT_MAX)) {
+      switch (*cp) {
+        case TYPE_OBJECTS:
+          outCount = 0;
+          /* outCount = snprintf(outBuf, outBufLen, " objects"); */
+          break;
+        case TYPE_BYTES:
+          if ((lval != 0) && ((lval % 1024) == 0)) {
+            /* repaint with multiplier */
+            static const char suffixTable[] = {'K', 'M', 'G', 'T'};
+            size_t idx = 0;
+            outBuf -= outCount;
+            outBufLen += outCount;
+            do {
+              lval /= 1024;
+              if ((lval % 1024) != 0) break;
+            } while (++idx < ((sizeof(suffixTable) / sizeof(suffixTable[0])) - 1));
+            outCount = snprintf(outBuf, outBufLen, "%" PRId64 "%cB", lval, suffixTable[idx]);
+          } else {
+            outCount = snprintf(outBuf, outBufLen, "B");
+          }
+          break;
+        case TYPE_MILLISECONDS:
+          if (((lval <= -1000) || (1000 <= lval)) && (outBufLen || (outBuf[-1] == '0'))) {
+            /* repaint as (fractional) seconds, possibly saving space */
+            if (outBufLen) outBuf[0] = outBuf[-1];
+            outBuf[-1] = outBuf[-2];
+            outBuf[-2] = outBuf[-3];
+            outBuf[-3] = '.';
+            while ((outBufLen == 0) || (*outBuf == '0')) {
+              --outBuf;
+              ++outBufLen;
+            }
+            if (*outBuf != '.') {
+              ++outBuf;
+              --outBufLen;
+            }
+            outCount = snprintf(outBuf, outBufLen, "s");
+          } else {
+            outCount = snprintf(outBuf, outBufLen, "ms");
+          }
+          break;
+        case TYPE_MONOTONIC: {
+          static const uint64_t minute = 60;
+          static const uint64_t hour = 60 * minute;
+          static const uint64_t day = 24 * hour;
+
+          /* Repaint as unsigned seconds, minutes, hours ... */
+          outBuf -= outCount;
+          outBufLen += outCount;
+          uint64_t val = lval;
+          if (val >= day) {
+            outCount = snprintf(outBuf, outBufLen, "%" PRIu64 "d ", val / day);
+            if (outCount >= outBufLen) break;
+            outBuf += outCount;
+            outBufLen -= outCount;
+            val = (val % day) + day;
+          }
+          if (val >= minute) {
+            if (val >= hour) {
+              outCount = snprintf(outBuf, outBufLen, "%" PRIu64 ":", (val / hour) % (day / hour));
+              if (outCount >= outBufLen) break;
+              outBuf += outCount;
+              outBufLen -= outCount;
+            }
+            outCount =
+                snprintf(outBuf, outBufLen, (val >= hour) ? "%02" PRIu64 ":" : "%" PRIu64 ":",
+                         (val / minute) % (hour / minute));
+            if (outCount >= outBufLen) break;
+            outBuf += outCount;
+            outBufLen -= outCount;
+          }
+          outCount = snprintf(outBuf, outBufLen, (val >= minute) ? "%02" PRIu64 : "%" PRIu64 "s",
+                              val % minute);
+        } break;
+        case TYPE_ALLOCATIONS:
+          outCount = 0;
+          /* outCount = snprintf(outBuf, outBufLen, " allocations"); */
+          break;
+        case TYPE_ID:
+          outCount = 0;
+          break;
+        case TYPE_PERCENT:
+          outCount = snprintf(outBuf, outBufLen, "%%");
+          break;
+        default: /* ? */
+          outCount = 0;
+          break;
+      }
+      ++cp;
+      --len;
+      if (outCount < outBufLen) {
+        outBuf += outCount;
+        outBufLen -= outCount;
+      } else if (outCount) {
+        /* halt output */
+        goto no_room;
+      }
+    }
+    if (!findChar(&cp, &len, ')')) len = 0;
+    if (!findChar(&cp, &len, ',')) len = 0;
+  }
+
+bail:
+  *pEventData = eventData;
+  *pEventDataLen = eventDataLen;
+  *pOutBuf = outBuf;
+  *pOutBufLen = outBufLen;
+  if (cp) {
+    *fmtStr = cp;
+    *fmtLen = len;
+  }
+  return result;
+
+no_room:
+  result = 1;
+  goto bail;
+}
+
+/**
+ * Convert a binary log entry to ASCII form.
+ *
+ * For convenience we mimic the processLogBuffer API.  There is no
+ * pre-defined output length for the binary data, since we're free to format
+ * it however we choose, which means we can't really use a fixed-size buffer
+ * here.
+ */
+int android_log_processBinaryLogBuffer(
+    struct logger_entry* buf, AndroidLogEntry* entry,
+    [[maybe_unused]] const EventTagMap* map, /* only on !__ANDROID__ */
+    char* messageBuf, int messageBufLen) {
+  size_t inCount;
+  uint32_t tagIndex;
+  const unsigned char* eventData;
+
+  entry->message = NULL;
+  entry->messageLen = 0;
+
+  entry->tv_sec = buf->sec;
+  entry->tv_nsec = buf->nsec;
+  entry->priority = ANDROID_LOG_INFO;
+  entry->uid = -1;
+  entry->pid = buf->pid;
+  entry->tid = buf->tid;
+
+  if (buf->hdr_size < sizeof(logger_entry)) {
+    fprintf(stderr, "+++ LOG: hdr_size must be at least as big as struct logger_entry\n");
+    return -1;
+  }
+  eventData = reinterpret_cast<unsigned char*>(buf) + buf->hdr_size;
+  if (buf->lid == LOG_ID_SECURITY) {
+    entry->priority = ANDROID_LOG_WARN;
+  }
+  entry->uid = buf->uid;
+  inCount = buf->len;
+  if (inCount < sizeof(android_event_header_t)) return -1;
+  auto* event_header = reinterpret_cast<const android_event_header_t*>(eventData);
+  tagIndex = event_header->tag;
+  eventData += sizeof(android_event_header_t);
+  inCount -= sizeof(android_event_header_t);
+
+  entry->tagLen = 0;
+  entry->tag = NULL;
+#ifdef __ANDROID__
+  if (map != NULL) {
+    entry->tag = android_lookupEventTag_len(map, &entry->tagLen, tagIndex);
+  }
+#endif
+
+  /*
+   * If we don't have a map, or didn't find the tag number in the map,
+   * stuff a generated tag value into the start of the output buffer and
+   * shift the buffer pointers down.
+   */
+  if (entry->tag == NULL) {
+    size_t tagLen;
+
+    tagLen = snprintf(messageBuf, messageBufLen, "[%" PRIu32 "]", tagIndex);
+    if (tagLen >= (size_t)messageBufLen) {
+      tagLen = messageBufLen - 1;
+    }
+    entry->tag = messageBuf;
+    entry->tagLen = tagLen;
+    messageBuf += tagLen + 1;
+    messageBufLen -= tagLen + 1;
+  }
+
+  /*
+   * Format the event log data into the buffer.
+   */
+  const char* fmtStr = NULL;
+  size_t fmtLen = 0;
+#ifdef __ANDROID__
+  if (descriptive_output && map) {
+    fmtStr = android_lookupEventFormat_len(map, &fmtLen, tagIndex);
+  }
+#endif
+
+  char* outBuf = messageBuf;
+  size_t outRemaining = messageBufLen - 1; /* leave one for nul byte */
+  int result = 0;
+
+  if ((inCount > 0) || fmtLen) {
+    result = android_log_printBinaryEvent(&eventData, &inCount, &outBuf, &outRemaining, &fmtStr,
+                                          &fmtLen);
+  }
+  if ((result == 1) && fmtStr) {
+    /* We overflowed :-(, let's repaint the line w/o format dressings */
+    eventData = reinterpret_cast<unsigned char*>(buf) + buf->hdr_size;
+    eventData += 4;
+    outBuf = messageBuf;
+    outRemaining = messageBufLen - 1;
+    result = android_log_printBinaryEvent(&eventData, &inCount, &outBuf, &outRemaining, NULL, NULL);
+  }
+  if (result < 0) {
+    fprintf(stderr, "Binary log entry conversion failed\n");
+  }
+  if (result) {
+    if (!outRemaining) {
+      /* make space to leave an indicator */
+      --outBuf;
+      ++outRemaining;
+    }
+    *outBuf++ = (result < 0) ? '!' : '^'; /* Error or Truncation? */
+    outRemaining--;
+    /* pretend we ate all the data to prevent log stutter */
+    inCount = 0;
+    if (result > 0) result = 0;
+  }
+
+  /* eat the silly terminating '\n' */
+  if (inCount == 1 && *eventData == '\n') {
+    eventData++;
+    inCount--;
+  }
+
+  if (inCount != 0) {
+    fprintf(stderr, "Warning: leftover binary log data (%zu bytes)\n", inCount);
+  }
+
+  /*
+   * Terminate the buffer.  The NUL byte does not count as part of
+   * entry->messageLen.
+   */
+  *outBuf = '\0';
+  entry->messageLen = outBuf - messageBuf;
+  assert(entry->messageLen == (messageBufLen - 1) - outRemaining);
+
+  entry->message = messageBuf;
+
+  return result;
+}
+
+/*
+ * Convert to printable from message to p buffer, return string length. If p is
+ * NULL, do not copy, but still return the expected string length.
+ */
+size_t convertPrintable(char* p, const char* message, size_t messageLen) {
+  char* begin = p;
+  bool print = p != NULL;
+  mbstate_t mb_state = {};
+
+  while (messageLen) {
+    char buf[6];
+    ssize_t len = sizeof(buf) - 1;
+    if ((size_t)len > messageLen) {
+      len = messageLen;
+    }
+    len = mbrtowc(nullptr, message, len, &mb_state);
+
+    if (len < 0) {
+      snprintf(buf, sizeof(buf), "\\x%02X", static_cast<unsigned char>(*message));
+      len = 1;
+    } else {
+      buf[0] = '\0';
+      if (len == 1) {
+        if (*message == '\a') {
+          strcpy(buf, "\\a");
+        } else if (*message == '\b') {
+          strcpy(buf, "\\b");
+        } else if (*message == '\t') {
+          strcpy(buf, "\t"); /* Do not escape tabs */
+        } else if (*message == '\v') {
+          strcpy(buf, "\\v");
+        } else if (*message == '\f') {
+          strcpy(buf, "\\f");
+        } else if (*message == '\r') {
+          strcpy(buf, "\\r");
+        } else if (*message == '\\') {
+          strcpy(buf, "\\\\");
+        } else if ((*message < ' ') || (*message & 0x80)) {
+          snprintf(buf, sizeof(buf), "\\x%02X", static_cast<unsigned char>(*message));
+        }
+      }
+      if (!buf[0]) {
+        strncpy(buf, message, len);
+        buf[len] = '\0';
+      }
+    }
+    if (print) {
+      strcpy(p, buf);
+    }
+    p += strlen(buf);
+    message += len;
+    messageLen -= len;
+  }
+  return p - begin;
+}
+
+#ifdef __ANDROID__
+static char* readSeconds(char* e, struct timespec* t) {
+  unsigned long multiplier;
+  char* p;
+  t->tv_sec = strtoul(e, &p, 10);
+  if (*p != '.') {
+    return NULL;
+  }
+  t->tv_nsec = 0;
+  multiplier = NS_PER_SEC;
+  while (isdigit(*++p) && (multiplier /= 10)) {
+    t->tv_nsec += (*p - '0') * multiplier;
+  }
+  return p;
+}
+
+static struct timespec* sumTimespec(struct timespec* left, struct timespec* right) {
+  left->tv_nsec += right->tv_nsec;
+  left->tv_sec += right->tv_sec;
+  if (left->tv_nsec >= (long)NS_PER_SEC) {
+    left->tv_nsec -= NS_PER_SEC;
+    left->tv_sec += 1;
+  }
+  return left;
+}
+
+static struct timespec* subTimespec(struct timespec* result, struct timespec* left,
+                                    struct timespec* right) {
+  result->tv_nsec = left->tv_nsec - right->tv_nsec;
+  result->tv_sec = left->tv_sec - right->tv_sec;
+  if (result->tv_nsec < 0) {
+    result->tv_nsec += NS_PER_SEC;
+    result->tv_sec -= 1;
+  }
+  return result;
+}
+
+static long long nsecTimespec(struct timespec* now) {
+  return (long long)now->tv_sec * NS_PER_SEC + now->tv_nsec;
+}
+
+static void convertMonotonic(struct timespec* result, const AndroidLogEntry* entry) {
+  struct listnode* node;
+  struct conversionList {
+    struct listnode node; /* first */
+    struct timespec time;
+    struct timespec convert;
+  } * list, *next;
+  struct timespec time, convert;
+
+  /* If we do not have a conversion list, build one up */
+  if (list_empty(&convertHead)) {
+    bool suspended_pending = false;
+    struct timespec suspended_monotonic = {0, 0};
+    struct timespec suspended_diff = {0, 0};
+
+    /*
+     * Read dmesg for _some_ synchronization markers and insert
+     * Anything in the Android Logger before the dmesg logging span will
+     * be highly suspect regarding the monotonic time calculations.
+     */
+    FILE* p = popen("/system/bin/dmesg", "re");
+    if (p) {
+      char* line = NULL;
+      size_t len = 0;
+      while (getline(&line, &len, p) > 0) {
+        static const char suspend[] = "PM: suspend entry ";
+        static const char resume[] = "PM: suspend exit ";
+        static const char healthd[] = "healthd";
+        static const char battery[] = ": battery ";
+        static const char suspended[] = "Suspended for ";
+        struct timespec monotonic;
+        struct tm tm;
+        char *cp, *e = line;
+        bool add_entry = true;
+
+        if (*e == '<') {
+          while (*e && (*e != '>')) {
+            ++e;
+          }
+          if (*e != '>') {
+            continue;
+          }
+        }
+        if (*e != '[') {
+          continue;
+        }
+        while (*++e == ' ') {
+          ;
+        }
+        e = readSeconds(e, &monotonic);
+        if (!e || (*e != ']')) {
+          continue;
+        }
+
+        if ((e = strstr(e, suspend))) {
+          e += sizeof(suspend) - 1;
+        } else if ((e = strstr(line, resume))) {
+          e += sizeof(resume) - 1;
+        } else if (((e = strstr(line, healthd))) &&
+                   ((e = strstr(e + sizeof(healthd) - 1, battery)))) {
+          /* NB: healthd is roughly 150us late, worth the price to
+           * deal with ntp-induced or hardware clock drift. */
+          e += sizeof(battery) - 1;
+        } else if ((e = strstr(line, suspended))) {
+          e += sizeof(suspended) - 1;
+          e = readSeconds(e, &time);
+          if (!e) {
+            continue;
+          }
+          add_entry = false;
+          suspended_pending = true;
+          suspended_monotonic = monotonic;
+          suspended_diff = time;
+        } else {
+          continue;
+        }
+        if (add_entry) {
+          /* look for "????-??-?? ??:??:??.????????? UTC" */
+          cp = strstr(e, " UTC");
+          if (!cp || ((cp - e) < 29) || (cp[-10] != '.')) {
+            continue;
+          }
+          e = cp - 29;
+          cp = readSeconds(cp - 10, &time);
+          if (!cp) {
+            continue;
+          }
+          cp = strptime(e, "%Y-%m-%d %H:%M:%S.", &tm);
+          if (!cp) {
+            continue;
+          }
+          cp = getenv(tz);
+          if (cp) {
+            cp = strdup(cp);
+          }
+          setenv(tz, utc, 1);
+          time.tv_sec = mktime(&tm);
+          if (cp) {
+            setenv(tz, cp, 1);
+            free(cp);
+          } else {
+            unsetenv(tz);
+          }
+          list = static_cast<conversionList*>(calloc(1, sizeof(conversionList)));
+          list_init(&list->node);
+          list->time = time;
+          subTimespec(&list->convert, &time, &monotonic);
+          list_add_tail(&convertHead, &list->node);
+        }
+        if (suspended_pending && !list_empty(&convertHead)) {
+          list = node_to_item(list_tail(&convertHead), struct conversionList, node);
+          if (subTimespec(&time, subTimespec(&time, &list->time, &list->convert),
+                          &suspended_monotonic)
+                  ->tv_sec > 0) {
+            /* resume, what is convert factor before? */
+            subTimespec(&convert, &list->convert, &suspended_diff);
+          } else {
+            /* suspend */
+            convert = list->convert;
+          }
+          time = suspended_monotonic;
+          sumTimespec(&time, &convert);
+          /* breakpoint just before sleep */
+          list = static_cast<conversionList*>(calloc(1, sizeof(conversionList)));
+          list_init(&list->node);
+          list->time = time;
+          list->convert = convert;
+          list_add_tail(&convertHead, &list->node);
+          /* breakpoint just after sleep */
+          list = static_cast<conversionList*>(calloc(1, sizeof(conversionList)));
+          list_init(&list->node);
+          list->time = time;
+          sumTimespec(&list->time, &suspended_diff);
+          list->convert = convert;
+          sumTimespec(&list->convert, &suspended_diff);
+          list_add_tail(&convertHead, &list->node);
+          suspended_pending = false;
+        }
+      }
+      pclose(p);
+    }
+    /* last entry is our current time conversion */
+    list = static_cast<conversionList*>(calloc(1, sizeof(conversionList)));
+    list_init(&list->node);
+    clock_gettime(CLOCK_REALTIME, &list->time);
+    clock_gettime(CLOCK_MONOTONIC, &convert);
+    clock_gettime(CLOCK_MONOTONIC, &time);
+    /* Correct for instant clock_gettime latency (syscall or ~30ns) */
+    subTimespec(&time, &convert, subTimespec(&time, &time, &convert));
+    /* Calculate conversion factor */
+    subTimespec(&list->convert, &list->time, &time);
+    list_add_tail(&convertHead, &list->node);
+    if (suspended_pending) {
+      /* manufacture a suspend @ point before */
+      subTimespec(&convert, &list->convert, &suspended_diff);
+      time = suspended_monotonic;
+      sumTimespec(&time, &convert);
+      /* breakpoint just after sleep */
+      list = static_cast<conversionList*>(calloc(1, sizeof(conversionList)));
+      list_init(&list->node);
+      list->time = time;
+      sumTimespec(&list->time, &suspended_diff);
+      list->convert = convert;
+      sumTimespec(&list->convert, &suspended_diff);
+      list_add_head(&convertHead, &list->node);
+      /* breakpoint just before sleep */
+      list = static_cast<conversionList*>(calloc(1, sizeof(conversionList)));
+      list_init(&list->node);
+      list->time = time;
+      list->convert = convert;
+      list_add_head(&convertHead, &list->node);
+    }
+  }
+
+  /* Find the breakpoint in the conversion list */
+  list = node_to_item(list_head(&convertHead), struct conversionList, node);
+  next = NULL;
+  list_for_each(node, &convertHead) {
+    next = node_to_item(node, struct conversionList, node);
+    if (entry->tv_sec < next->time.tv_sec) {
+      break;
+    } else if (entry->tv_sec == next->time.tv_sec) {
+      if (entry->tv_nsec < next->time.tv_nsec) {
+        break;
+      }
+    }
+    list = next;
+  }
+
+  /* blend time from one breakpoint to the next */
+  convert = list->convert;
+  if (next) {
+    unsigned long long total, run;
+
+    total = nsecTimespec(subTimespec(&time, &next->time, &list->time));
+    time.tv_sec = entry->tv_sec;
+    time.tv_nsec = entry->tv_nsec;
+    run = nsecTimespec(subTimespec(&time, &time, &list->time));
+    if (run < total) {
+      long long crun;
+
+      float f = nsecTimespec(subTimespec(&time, &next->convert, &convert));
+      f *= run;
+      f /= total;
+      crun = f;
+      convert.tv_sec += crun / (long long)NS_PER_SEC;
+      if (crun < 0) {
+        convert.tv_nsec -= (-crun) % NS_PER_SEC;
+        if (convert.tv_nsec < 0) {
+          convert.tv_nsec += NS_PER_SEC;
+          convert.tv_sec -= 1;
+        }
+      } else {
+        convert.tv_nsec += crun % NS_PER_SEC;
+        if (convert.tv_nsec >= (long)NS_PER_SEC) {
+          convert.tv_nsec -= NS_PER_SEC;
+          convert.tv_sec += 1;
+        }
+      }
+    }
+  }
+
+  /* Apply the correction factor */
+  result->tv_sec = entry->tv_sec;
+  result->tv_nsec = entry->tv_nsec;
+  subTimespec(result, result, &convert);
+}
+#endif
+
+/**
+ * Formats a log message into a buffer
+ *
+ * Uses defaultBuffer if it can, otherwise malloc()'s a new buffer
+ * If return value != defaultBuffer, caller must call free()
+ * Returns NULL on malloc error
+ */
+
+char* android_log_formatLogLine(AndroidLogFormat* p_format, char* defaultBuffer,
+                                size_t defaultBufferSize, const AndroidLogEntry* entry,
+                                size_t* p_outLength) {
+#if !defined(_WIN32)
+  struct tm tmBuf;
+#endif
+  struct tm* ptm;
+  /* good margin, 23+nul for msec, 26+nul for usec, 29+nul to nsec */
+  char timeBuf[64];
+  char prefixBuf[128], suffixBuf[128];
+  char priChar;
+  int prefixSuffixIsHeaderFooter = 0;
+  char* ret;
+  time_t now;
+  unsigned long nsec;
+
+  priChar = filterPriToChar(entry->priority);
+  size_t prefixLen = 0, suffixLen = 0;
+  size_t len;
+
+  /*
+   * Get the current date/time in pretty form
+   *
+   * It's often useful when examining a log with "less" to jump to
+   * a specific point in the file by searching for the date/time stamp.
+   * For this reason it's very annoying to have regexp meta characters
+   * in the time stamp.  Don't use forward slashes, parenthesis,
+   * brackets, asterisks, or other special chars here.
+   *
+   * The caller may have affected the timezone environment, this is
+   * expected to be sensitive to that.
+   */
+  now = entry->tv_sec;
+  nsec = entry->tv_nsec;
+#if __ANDROID__
+  if (p_format->monotonic_output) {
+    struct timespec time;
+    convertMonotonic(&time, entry);
+    now = time.tv_sec;
+    nsec = time.tv_nsec;
+  }
+#endif
+  if (now < 0) {
+    nsec = NS_PER_SEC - nsec;
+  }
+  if (p_format->epoch_output || p_format->monotonic_output) {
+    ptm = NULL;
+    snprintf(timeBuf, sizeof(timeBuf), p_format->monotonic_output ? "%6lld" : "%19lld",
+             (long long)now);
+  } else {
+#if !defined(_WIN32)
+    ptm = localtime_r(&now, &tmBuf);
+#else
+    ptm = localtime(&now);
+#endif
+    strftime(timeBuf, sizeof(timeBuf), &"%Y-%m-%d %H:%M:%S"[p_format->year_output ? 0 : 3], ptm);
+  }
+  len = strlen(timeBuf);
+  if (p_format->nsec_time_output) {
+    len += snprintf(timeBuf + len, sizeof(timeBuf) - len, ".%09ld", nsec);
+  } else if (p_format->usec_time_output) {
+    len += snprintf(timeBuf + len, sizeof(timeBuf) - len, ".%06ld", nsec / US_PER_NSEC);
+  } else {
+    len += snprintf(timeBuf + len, sizeof(timeBuf) - len, ".%03ld", nsec / MS_PER_NSEC);
+  }
+  if (p_format->zone_output && ptm) {
+    strftime(timeBuf + len, sizeof(timeBuf) - len, " %z", ptm);
+  }
+
+  /*
+   * Construct a buffer containing the log header and log message.
+   */
+  if (p_format->colored_output) {
+    prefixLen =
+        snprintf(prefixBuf, sizeof(prefixBuf), "\x1B[%dm", colorFromPri(entry->priority));
+    prefixLen = MIN(prefixLen, sizeof(prefixBuf));
+
+    const char suffixContents[] = "\x1B[0m";
+    strcpy(suffixBuf, suffixContents);
+    suffixLen = strlen(suffixContents);
+  }
+
+  char uid[16];
+  uid[0] = '\0';
+  if (p_format->uid_output) {
+    if (entry->uid >= 0) {
+/*
+ * This code is Android specific, bionic guarantees that
+ * calls to non-reentrant getpwuid() are thread safe.
+ */
+#ifdef __ANDROID__
+      struct passwd* pwd = getpwuid(entry->uid);
+      if (pwd && (strlen(pwd->pw_name) <= 5)) {
+        snprintf(uid, sizeof(uid), "%5s:", pwd->pw_name);
+      } else
+#endif
+      {
+        /* Not worth parsing package list, names all longer than 5 */
+        snprintf(uid, sizeof(uid), "%5d:", entry->uid);
+      }
+    } else {
+      snprintf(uid, sizeof(uid), "      ");
+    }
+  }
+
+  switch (p_format->format) {
+    case FORMAT_TAG:
+      len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen, "%c/%-8.*s: ", priChar,
+                     (int)entry->tagLen, entry->tag);
+      strcpy(suffixBuf + suffixLen, "\n");
+      ++suffixLen;
+      break;
+    case FORMAT_PROCESS:
+      len = snprintf(suffixBuf + suffixLen, sizeof(suffixBuf) - suffixLen, "  (%.*s)\n",
+                     (int)entry->tagLen, entry->tag);
+      suffixLen += MIN(len, sizeof(suffixBuf) - suffixLen);
+      len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen, "%c(%s%5d) ", priChar,
+                     uid, entry->pid);
+      break;
+    case FORMAT_THREAD:
+      len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen, "%c(%s%5d:%5d) ",
+                     priChar, uid, entry->pid, entry->tid);
+      strcpy(suffixBuf + suffixLen, "\n");
+      ++suffixLen;
+      break;
+    case FORMAT_RAW:
+      prefixBuf[prefixLen] = 0;
+      len = 0;
+      strcpy(suffixBuf + suffixLen, "\n");
+      ++suffixLen;
+      break;
+    case FORMAT_TIME:
+      len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
+                     "%s %c/%-8.*s(%s%5d): ", timeBuf, priChar, (int)entry->tagLen, entry->tag, uid,
+                     entry->pid);
+      strcpy(suffixBuf + suffixLen, "\n");
+      ++suffixLen;
+      break;
+    case FORMAT_THREADTIME:
+      ret = strchr(uid, ':');
+      if (ret) {
+        *ret = ' ';
+      }
+      len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
+                     "%s %s%5d %5d %c %-8.*s: ", timeBuf, uid, entry->pid, entry->tid, priChar,
+                     (int)entry->tagLen, entry->tag);
+      strcpy(suffixBuf + suffixLen, "\n");
+      ++suffixLen;
+      break;
+    case FORMAT_LONG:
+      len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
+                     "[ %s %s%5d:%5d %c/%-8.*s ]\n", timeBuf, uid, entry->pid, entry->tid, priChar,
+                     (int)entry->tagLen, entry->tag);
+      strcpy(suffixBuf + suffixLen, "\n\n");
+      suffixLen += 2;
+      prefixSuffixIsHeaderFooter = 1;
+      break;
+    case FORMAT_BRIEF:
+    default:
+      len =
+          snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
+                   "%c/%-8.*s(%s%5d): ", priChar, (int)entry->tagLen, entry->tag, uid, entry->pid);
+      strcpy(suffixBuf + suffixLen, "\n");
+      ++suffixLen;
+      break;
+  }
+
+  /* snprintf has a weird return value.   It returns what would have been
+   * written given a large enough buffer.  In the case that the prefix is
+   * longer then our buffer(128), it messes up the calculations below
+   * possibly causing heap corruption.  To avoid this we double check and
+   * set the length at the maximum (size minus null byte)
+   */
+  prefixLen += len;
+  if (prefixLen >= sizeof(prefixBuf)) {
+    prefixLen = sizeof(prefixBuf) - 1;
+    prefixBuf[sizeof(prefixBuf) - 1] = '\0';
+  }
+  if (suffixLen >= sizeof(suffixBuf)) {
+    suffixLen = sizeof(suffixBuf) - 1;
+    suffixBuf[sizeof(suffixBuf) - 2] = '\n';
+    suffixBuf[sizeof(suffixBuf) - 1] = '\0';
+  }
+
+  /* the following code is tragically unreadable */
+
+  size_t numLines;
+  char* p;
+  size_t bufferSize;
+  const char* pm;
+
+  if (prefixSuffixIsHeaderFooter) {
+    /* we're just wrapping message with a header/footer */
+    numLines = 1;
+  } else {
+    pm = entry->message;
+    numLines = 0;
+
+    /*
+     * The line-end finding here must match the line-end finding
+     * in for ( ... numLines...) loop below
+     */
+    while (pm < (entry->message + entry->messageLen)) {
+      if (*pm++ == '\n') numLines++;
+    }
+    /* plus one line for anything not newline-terminated at the end */
+    if (pm > entry->message && *(pm - 1) != '\n') numLines++;
+  }
+
+  /*
+   * this is an upper bound--newlines in message may be counted
+   * extraneously
+   */
+  bufferSize = (numLines * (prefixLen + suffixLen)) + 1;
+  if (p_format->printable_output) {
+    /* Calculate extra length to convert non-printable to printable */
+    bufferSize += convertPrintable(NULL, entry->message, entry->messageLen);
+  } else {
+    bufferSize += entry->messageLen;
+  }
+
+  if (defaultBufferSize >= bufferSize) {
+    ret = defaultBuffer;
+  } else {
+    ret = (char*)malloc(bufferSize);
+
+    if (ret == NULL) {
+      return ret;
+    }
+  }
+
+  ret[0] = '\0'; /* to start strcat off */
+
+  p = ret;
+  pm = entry->message;
+
+  if (prefixSuffixIsHeaderFooter) {
+    strcat(p, prefixBuf);
+    p += prefixLen;
+    if (p_format->printable_output) {
+      p += convertPrintable(p, entry->message, entry->messageLen);
+    } else {
+      strncat(p, entry->message, entry->messageLen);
+      p += entry->messageLen;
+    }
+    strcat(p, suffixBuf);
+    p += suffixLen;
+  } else {
+    do {
+      const char* lineStart;
+      size_t lineLen;
+      lineStart = pm;
+
+      /* Find the next end-of-line in message */
+      while (pm < (entry->message + entry->messageLen) && *pm != '\n') pm++;
+      lineLen = pm - lineStart;
+
+      strcat(p, prefixBuf);
+      p += prefixLen;
+      if (p_format->printable_output) {
+        p += convertPrintable(p, lineStart, lineLen);
+      } else {
+        strncat(p, lineStart, lineLen);
+        p += lineLen;
+      }
+      strcat(p, suffixBuf);
+      p += suffixLen;
+
+      if (*pm == '\n') pm++;
+    } while (pm < (entry->message + entry->messageLen));
+  }
+
+  if (p_outLength != NULL) {
+    *p_outLength = p - ret;
+  }
+
+  return ret;
+}
+
+/**
+ * Either print or do not print log line, based on filter
+ *
+ * Returns count bytes written
+ */
+
+int android_log_printLogLine(AndroidLogFormat* p_format, int fd, const AndroidLogEntry* entry) {
+  int ret;
+  char defaultBuffer[512];
+  char* outBuffer = NULL;
+  size_t totalLen;
+
+  outBuffer =
+      android_log_formatLogLine(p_format, defaultBuffer, sizeof(defaultBuffer), entry, &totalLen);
+
+  if (!outBuffer) return -1;
+
+  do {
+    ret = write(fd, outBuffer, totalLen);
+  } while (ret < 0 && errno == EINTR);
+
+  if (ret < 0) {
+    fprintf(stderr, "+++ LOG: write failed (errno=%d)\n", errno);
+    ret = 0;
+    goto done;
+  }
+
+  if (((size_t)ret) < totalLen) {
+    fprintf(stderr, "+++ LOG: write partial (%d of %d)\n", ret, (int)totalLen);
+    goto done;
+  }
+
+done:
+  if (outBuffer != defaultBuffer) {
+    free(outBuffer);
+  }
+
+  return ret;
+}
diff --git a/liblog/pmsg_reader.cpp b/liblog/pmsg_reader.cpp
new file mode 100644
index 0000000..5640900
--- /dev/null
+++ b/liblog/pmsg_reader.cpp
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2007-2016 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 "pmsg_reader.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include <cutils/list.h>
+#include <private/android_logger.h>
+
+#include "logger.h"
+
+int PmsgRead(struct logger_list* logger_list, struct log_msg* log_msg) {
+  ssize_t ret;
+  off_t current, next;
+  struct __attribute__((__packed__)) {
+    android_pmsg_log_header_t p;
+    android_log_header_t l;
+    uint8_t prio;
+  } buf;
+  static uint8_t preread_count;
+
+  memset(log_msg, 0, sizeof(*log_msg));
+
+  if (atomic_load(&logger_list->fd) <= 0) {
+    int i, fd = open("/sys/fs/pstore/pmsg-ramoops-0", O_RDONLY | O_CLOEXEC);
+
+    if (fd < 0) {
+      return -errno;
+    }
+    if (fd == 0) { /* Argggg */
+      fd = open("/sys/fs/pstore/pmsg-ramoops-0", O_RDONLY | O_CLOEXEC);
+      close(0);
+      if (fd < 0) {
+        return -errno;
+      }
+    }
+    i = atomic_exchange(&logger_list->fd, fd);
+    if ((i > 0) && (i != fd)) {
+      close(i);
+    }
+    preread_count = 0;
+  }
+
+  while (1) {
+    int fd;
+
+    if (preread_count < sizeof(buf)) {
+      fd = atomic_load(&logger_list->fd);
+      if (fd <= 0) {
+        return -EBADF;
+      }
+      ret = TEMP_FAILURE_RETRY(read(fd, &buf.p.magic + preread_count, sizeof(buf) - preread_count));
+      if (ret < 0) {
+        return -errno;
+      }
+      preread_count += ret;
+    }
+    if (preread_count != sizeof(buf)) {
+      return preread_count ? -EIO : -EAGAIN;
+    }
+    if ((buf.p.magic != LOGGER_MAGIC) || (buf.p.len <= sizeof(buf)) ||
+        (buf.p.len > (sizeof(buf) + LOGGER_ENTRY_MAX_PAYLOAD)) || (buf.l.id >= LOG_ID_MAX) ||
+        (buf.l.realtime.tv_nsec >= NS_PER_SEC) ||
+        ((buf.l.id != LOG_ID_EVENTS) && (buf.l.id != LOG_ID_SECURITY) &&
+         ((buf.prio == ANDROID_LOG_UNKNOWN) || (buf.prio == ANDROID_LOG_DEFAULT) ||
+          (buf.prio >= ANDROID_LOG_SILENT)))) {
+      do {
+        memmove(&buf.p.magic, &buf.p.magic + 1, --preread_count);
+      } while (preread_count && (buf.p.magic != LOGGER_MAGIC));
+      continue;
+    }
+    preread_count = 0;
+
+    if ((logger_list->log_mask & (1 << buf.l.id)) &&
+        ((!logger_list->start.tv_sec && !logger_list->start.tv_nsec) ||
+         ((logger_list->start.tv_sec <= buf.l.realtime.tv_sec) &&
+          ((logger_list->start.tv_sec != buf.l.realtime.tv_sec) ||
+           (logger_list->start.tv_nsec <= buf.l.realtime.tv_nsec)))) &&
+        (!logger_list->pid || (logger_list->pid == buf.p.pid))) {
+      char* msg = reinterpret_cast<char*>(&log_msg->entry) + sizeof(log_msg->entry);
+      *msg = buf.prio;
+      fd = atomic_load(&logger_list->fd);
+      if (fd <= 0) {
+        return -EBADF;
+      }
+      ret = TEMP_FAILURE_RETRY(read(fd, msg + sizeof(buf.prio), buf.p.len - sizeof(buf)));
+      if (ret < 0) {
+        return -errno;
+      }
+      if (ret != (ssize_t)(buf.p.len - sizeof(buf))) {
+        return -EIO;
+      }
+
+      log_msg->entry.len = buf.p.len - sizeof(buf) + sizeof(buf.prio);
+      log_msg->entry.hdr_size = sizeof(log_msg->entry);
+      log_msg->entry.pid = buf.p.pid;
+      log_msg->entry.tid = buf.l.tid;
+      log_msg->entry.sec = buf.l.realtime.tv_sec;
+      log_msg->entry.nsec = buf.l.realtime.tv_nsec;
+      log_msg->entry.lid = buf.l.id;
+      log_msg->entry.uid = buf.p.uid;
+
+      return ret + sizeof(buf.prio) + log_msg->entry.hdr_size;
+    }
+
+    fd = atomic_load(&logger_list->fd);
+    if (fd <= 0) {
+      return -EBADF;
+    }
+    current = TEMP_FAILURE_RETRY(lseek(fd, (off_t)0, SEEK_CUR));
+    if (current < 0) {
+      return -errno;
+    }
+    fd = atomic_load(&logger_list->fd);
+    if (fd <= 0) {
+      return -EBADF;
+    }
+    next = TEMP_FAILURE_RETRY(lseek(fd, (off_t)(buf.p.len - sizeof(buf)), SEEK_CUR));
+    if (next < 0) {
+      return -errno;
+    }
+    if ((next - current) != (ssize_t)(buf.p.len - sizeof(buf))) {
+      return -EIO;
+    }
+  }
+}
+
+void PmsgClose(struct logger_list* logger_list) {
+  int fd = atomic_exchange(&logger_list->fd, 0);
+  if (fd > 0) {
+    close(fd);
+  }
+}
+
+static void* realloc_or_free(void* ptr, size_t new_size) {
+  void* result = realloc(ptr, new_size);
+  if (!result) {
+    free(ptr);
+  }
+  return result;
+}
+
+ssize_t __android_log_pmsg_file_read(log_id_t logId, char prio, const char* prefix,
+                                     __android_log_pmsg_file_read_fn fn, void* arg) {
+  ssize_t ret;
+  struct logger_list logger_list;
+  struct content {
+    struct listnode node;
+    struct logger_entry entry;
+  } * content;
+  struct names {
+    struct listnode node;
+    struct listnode content;
+    log_id_t id;
+    char prio;
+    char name[];
+  } * names;
+  struct listnode name_list;
+  struct listnode *node, *n;
+  size_t len, prefix_len;
+
+  if (!fn) {
+    return -EINVAL;
+  }
+
+  /* Add just enough clues in logger_list and transp to make API function */
+  memset(&logger_list, 0, sizeof(logger_list));
+
+  logger_list.mode = ANDROID_LOG_PSTORE | ANDROID_LOG_NONBLOCK;
+  logger_list.log_mask = (unsigned)-1;
+  if (logId != LOG_ID_ANY) {
+    logger_list.log_mask = (1 << logId);
+  }
+  logger_list.log_mask &= ~((1 << LOG_ID_KERNEL) | (1 << LOG_ID_EVENTS) | (1 << LOG_ID_SECURITY));
+  if (!logger_list.log_mask) {
+    return -EINVAL;
+  }
+
+  /* Initialize name list */
+  list_init(&name_list);
+
+  ret = SSIZE_MAX;
+
+  /* Validate incoming prefix, shift until it contains only 0 or 1 : or / */
+  prefix_len = 0;
+  if (prefix) {
+    const char *prev = NULL, *last = NULL, *cp = prefix;
+    while ((cp = strpbrk(cp, "/:"))) {
+      prev = last;
+      last = cp;
+      cp = cp + 1;
+    }
+    if (prev) {
+      prefix = prev + 1;
+    }
+    prefix_len = strlen(prefix);
+  }
+
+  /* Read the file content */
+  log_msg log_msg;
+  while (PmsgRead(&logger_list, &log_msg) > 0) {
+    const char* cp;
+    size_t hdr_size = log_msg.entry.hdr_size;
+
+    char* msg = (char*)&log_msg + hdr_size;
+    const char* split = NULL;
+
+    if (hdr_size != sizeof(log_msg.entry)) {
+      continue;
+    }
+    /* Check for invalid sequence number */
+    if (log_msg.entry.nsec % ANDROID_LOG_PMSG_FILE_SEQUENCE ||
+        (log_msg.entry.nsec / ANDROID_LOG_PMSG_FILE_SEQUENCE) >=
+            ANDROID_LOG_PMSG_FILE_MAX_SEQUENCE) {
+      continue;
+    }
+
+    /* Determine if it has <dirbase>:<filebase> format for tag */
+    len = log_msg.entry.len - sizeof(prio);
+    for (cp = msg + sizeof(prio); *cp && isprint(*cp) && !isspace(*cp) && --len; ++cp) {
+      if (*cp == ':') {
+        if (split) {
+          break;
+        }
+        split = cp;
+      }
+    }
+    if (*cp || !split) {
+      continue;
+    }
+
+    /* Filters */
+    if (prefix_len && strncmp(msg + sizeof(prio), prefix, prefix_len)) {
+      size_t offset;
+      /*
+       *   Allow : to be a synonym for /
+       * Things we do dealing with const char * and do not alloc
+       */
+      split = strchr(prefix, ':');
+      if (split) {
+        continue;
+      }
+      split = strchr(prefix, '/');
+      if (!split) {
+        continue;
+      }
+      offset = split - prefix;
+      if ((msg[offset + sizeof(prio)] != ':') || strncmp(msg + sizeof(prio), prefix, offset)) {
+        continue;
+      }
+      ++offset;
+      if ((prefix_len > offset) &&
+          strncmp(&msg[offset + sizeof(prio)], split + 1, prefix_len - offset)) {
+        continue;
+      }
+    }
+
+    if ((prio != ANDROID_LOG_ANY) && (*msg < prio)) {
+      continue;
+    }
+
+    /* check if there is an existing entry */
+    list_for_each(node, &name_list) {
+      names = node_to_item(node, struct names, node);
+      if (!strcmp(names->name, msg + sizeof(prio)) && names->id == log_msg.entry.lid &&
+          names->prio == *msg) {
+        break;
+      }
+    }
+
+    /* We do not have an existing entry, create and add one */
+    if (node == &name_list) {
+      static const char numbers[] = "0123456789";
+      unsigned long long nl;
+
+      len = strlen(msg + sizeof(prio)) + 1;
+      names = static_cast<struct names*>(calloc(1, sizeof(*names) + len));
+      if (!names) {
+        ret = -ENOMEM;
+        break;
+      }
+      strcpy(names->name, msg + sizeof(prio));
+      names->id = static_cast<log_id_t>(log_msg.entry.lid);
+      names->prio = *msg;
+      list_init(&names->content);
+      /*
+       * Insert in reverse numeric _then_ alpha sorted order as
+       * representative of log rotation:
+       *
+       *   log.10
+       *   klog.10
+       *   . . .
+       *   log.2
+       *   klog.2
+       *   log.1
+       *   klog.1
+       *   log
+       *   klog
+       *
+       * thus when we present the content, we are provided the oldest
+       * first, which when 'refreshed' could spill off the end of the
+       * pmsg FIFO but retaining the newest data for last with best
+       * chances to survive.
+       */
+      nl = 0;
+      cp = strpbrk(names->name, numbers);
+      if (cp) {
+        nl = strtoull(cp, NULL, 10);
+      }
+      list_for_each_reverse(node, &name_list) {
+        struct names* a_name = node_to_item(node, struct names, node);
+        const char* r = a_name->name;
+        int compare = 0;
+
+        unsigned long long nr = 0;
+        cp = strpbrk(r, numbers);
+        if (cp) {
+          nr = strtoull(cp, NULL, 10);
+        }
+        if (nr != nl) {
+          compare = (nl > nr) ? 1 : -1;
+        }
+        if (compare == 0) {
+          compare = strcmp(names->name, r);
+        }
+        if (compare <= 0) {
+          break;
+        }
+      }
+      list_add_head(node, &names->node);
+    }
+
+    /* Remove any file fragments that match our sequence number */
+    list_for_each_safe(node, n, &names->content) {
+      content = node_to_item(node, struct content, node);
+      if (log_msg.entry.nsec == content->entry.nsec) {
+        list_remove(&content->node);
+        free(content);
+      }
+    }
+
+    /* Add content */
+    content = static_cast<struct content*>(
+        calloc(1, sizeof(content->node) + hdr_size + log_msg.entry.len));
+    if (!content) {
+      ret = -ENOMEM;
+      break;
+    }
+    memcpy(&content->entry, &log_msg.entry, hdr_size + log_msg.entry.len);
+
+    /* Insert in sequence number sorted order, to ease reconstruction */
+    list_for_each_reverse(node, &names->content) {
+      if ((node_to_item(node, struct content, node))->entry.nsec < log_msg.entry.nsec) {
+        break;
+      }
+    }
+    list_add_head(node, &content->node);
+  }
+  PmsgClose(&logger_list);
+
+  /* Progress through all the collected files */
+  list_for_each_safe(node, n, &name_list) {
+    struct listnode *content_node, *m;
+    char* buf;
+    size_t sequence, tag_len;
+
+    names = node_to_item(node, struct names, node);
+
+    /* Construct content into a linear buffer */
+    buf = NULL;
+    len = 0;
+    sequence = 0;
+    tag_len = strlen(names->name) + sizeof(char); /* tag + nul */
+    list_for_each_safe(content_node, m, &names->content) {
+      ssize_t add_len;
+
+      content = node_to_item(content_node, struct content, node);
+      add_len = content->entry.len - tag_len - sizeof(prio);
+      if (add_len <= 0) {
+        list_remove(content_node);
+        free(content);
+        continue;
+      }
+
+      if (!buf) {
+        buf = static_cast<char*>(malloc(sizeof(char)));
+        if (!buf) {
+          ret = -ENOMEM;
+          list_remove(content_node);
+          free(content);
+          continue;
+        }
+        *buf = '\0';
+      }
+
+      /* Missing sequence numbers */
+      while (sequence < content->entry.nsec) {
+        /* plus space for enforced nul */
+        buf = static_cast<char*>(realloc_or_free(buf, len + sizeof(char) + sizeof(char)));
+        if (!buf) {
+          break;
+        }
+        buf[len] = '\f'; /* Mark missing content with a form feed */
+        buf[++len] = '\0';
+        sequence += ANDROID_LOG_PMSG_FILE_SEQUENCE;
+      }
+      if (!buf) {
+        ret = -ENOMEM;
+        list_remove(content_node);
+        free(content);
+        continue;
+      }
+      /* plus space for enforced nul */
+      buf = static_cast<char*>(realloc_or_free(buf, len + add_len + sizeof(char)));
+      if (!buf) {
+        ret = -ENOMEM;
+        list_remove(content_node);
+        free(content);
+        continue;
+      }
+      memcpy(buf + len, (char*)&content->entry + content->entry.hdr_size + tag_len + sizeof(prio),
+             add_len);
+      len += add_len;
+      buf[len] = '\0'; /* enforce trailing hidden nul */
+      sequence = content->entry.nsec + ANDROID_LOG_PMSG_FILE_SEQUENCE;
+
+      list_remove(content_node);
+      free(content);
+    }
+    if (buf) {
+      if (len) {
+        /* Buffer contains enforced trailing nul just beyond length */
+        ssize_t r;
+        *strchr(names->name, ':') = '/'; /* Convert back to filename */
+        r = (*fn)(names->id, names->prio, names->name, buf, len, arg);
+        if ((ret >= 0) && (r > 0)) {
+          if (ret == SSIZE_MAX) {
+            ret = r;
+          } else {
+            ret += r;
+          }
+        } else if (r < ret) {
+          ret = r;
+        }
+      }
+      free(buf);
+    }
+    list_remove(node);
+    free(names);
+  }
+  return (ret == SSIZE_MAX) ? -ENOENT : ret;
+}
diff --git a/liblog/fake_log_device.h b/liblog/pmsg_reader.h
similarity index 62%
copy from liblog/fake_log_device.h
copy to liblog/pmsg_reader.h
index 9d168cd..b784f9f 100644
--- a/liblog/fake_log_device.h
+++ b/liblog/pmsg_reader.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright 2019 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.
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-#ifndef _LIBLOG_FAKE_LOG_DEVICE_H
-#define _LIBLOG_FAKE_LOG_DEVICE_H
+#pragma once
 
-#include <sys/types.h>
+#include <sys/cdefs.h>
+#include <unistd.h>
 
-struct iovec;
+#include "log/log_read.h"
 
-int fakeLogOpen(const char *pathName, int flags);
-int fakeLogClose(int fd);
-ssize_t fakeLogWritev(int fd, const struct iovec* vector, int count);
+__BEGIN_DECLS
 
-#endif // _LIBLOG_FAKE_LOG_DEVICE_H
+int PmsgRead(struct logger_list* logger_list, struct log_msg* log_msg);
+void PmsgClose(struct logger_list* logger_list);
+
+__END_DECLS
diff --git a/liblog/pmsg_writer.cpp b/liblog/pmsg_writer.cpp
new file mode 100644
index 0000000..8e676bd
--- /dev/null
+++ b/liblog/pmsg_writer.cpp
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2007-2016 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 "pmsg_writer.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include <log/log_properties.h>
+#include <private/android_logger.h>
+
+#include "logger.h"
+#include "uio.h"
+
+static atomic_int pmsg_fd;
+
+// pmsg_fd should only beopened once.  If we see that pmsg_fd is uninitialized, we open "/dev/pmsg0"
+// then attempt to compare/exchange it into pmsg_fd.  If the compare/exchange was successful, then
+// that will be the fd used for the duration of the program, otherwise a different thread has
+// already opened and written the fd to the atomic, so close the new fd and return.
+static void GetPmsgFd() {
+  if (pmsg_fd != 0) {
+    return;
+  }
+
+  int new_fd = TEMP_FAILURE_RETRY(open("/dev/pmsg0", O_WRONLY | O_CLOEXEC));
+  if (new_fd <= 0) {
+    return;
+  }
+
+  int uninitialized_value = 0;
+  if (!pmsg_fd.compare_exchange_strong(uninitialized_value, new_fd)) {
+    close(new_fd);
+    return;
+  }
+}
+
+void PmsgClose() {
+  if (pmsg_fd > 0) {
+    close(pmsg_fd);
+  }
+  pmsg_fd = 0;
+}
+
+int PmsgWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr) {
+  static const unsigned headerLength = 2;
+  struct iovec newVec[nr + headerLength];
+  android_log_header_t header;
+  android_pmsg_log_header_t pmsgHeader;
+  size_t i, payloadSize;
+  ssize_t ret;
+
+  if (!__android_log_is_debuggable()) {
+    if (logId != LOG_ID_EVENTS && logId != LOG_ID_SECURITY) {
+      return -1;
+    }
+
+    if (logId == LOG_ID_EVENTS) {
+      if (vec[0].iov_len < 4) {
+        return -EINVAL;
+      }
+
+      if (SNET_EVENT_LOG_TAG != *static_cast<uint32_t*>(vec[0].iov_base)) {
+        return -EPERM;
+      }
+    }
+  }
+
+  GetPmsgFd();
+
+  if (pmsg_fd <= 0) {
+    return -EBADF;
+  }
+
+  /*
+   *  struct {
+   *      // what we provide to pstore
+   *      android_pmsg_log_header_t pmsgHeader;
+   *      // what we provide to file
+   *      android_log_header_t header;
+   *      // caller provides
+   *      union {
+   *          struct {
+   *              char     prio;
+   *              char     payload[];
+   *          } string;
+   *          struct {
+   *              uint32_t tag
+   *              char     payload[];
+   *          } binary;
+   *      };
+   *  };
+   */
+
+  pmsgHeader.magic = LOGGER_MAGIC;
+  pmsgHeader.len = sizeof(pmsgHeader) + sizeof(header);
+  pmsgHeader.uid = getuid();
+  pmsgHeader.pid = getpid();
+
+  header.id = logId;
+  header.tid = gettid();
+  header.realtime.tv_sec = ts->tv_sec;
+  header.realtime.tv_nsec = ts->tv_nsec;
+
+  newVec[0].iov_base = (unsigned char*)&pmsgHeader;
+  newVec[0].iov_len = sizeof(pmsgHeader);
+  newVec[1].iov_base = (unsigned char*)&header;
+  newVec[1].iov_len = sizeof(header);
+
+  for (payloadSize = 0, i = headerLength; i < nr + headerLength; i++) {
+    newVec[i].iov_base = vec[i - headerLength].iov_base;
+    payloadSize += newVec[i].iov_len = vec[i - headerLength].iov_len;
+
+    if (payloadSize > LOGGER_ENTRY_MAX_PAYLOAD) {
+      newVec[i].iov_len -= payloadSize - LOGGER_ENTRY_MAX_PAYLOAD;
+      if (newVec[i].iov_len) {
+        ++i;
+      }
+      payloadSize = LOGGER_ENTRY_MAX_PAYLOAD;
+      break;
+    }
+  }
+  pmsgHeader.len += payloadSize;
+
+  ret = TEMP_FAILURE_RETRY(writev(pmsg_fd, newVec, i));
+  if (ret < 0) {
+    ret = errno ? -errno : -ENOTCONN;
+  }
+
+  if (ret > (ssize_t)(sizeof(header) + sizeof(pmsgHeader))) {
+    ret -= sizeof(header) - sizeof(pmsgHeader);
+  }
+
+  return ret;
+}
+
+/*
+ * Virtual pmsg filesystem
+ *
+ * Payload will comprise the string "<basedir>:<basefile>\0<content>" to a
+ * maximum of LOGGER_ENTRY_MAX_PAYLOAD, but scaled to the last newline in the
+ * file.
+ *
+ * Will hijack the header.realtime.tv_nsec field for a sequence number in usec.
+ */
+
+static inline const char* strnrchr(const char* buf, size_t len, char c) {
+  const char* cp = buf + len;
+  while ((--cp > buf) && (*cp != c))
+    ;
+  if (cp <= buf) {
+    return buf + len;
+  }
+  return cp;
+}
+
+/* Write a buffer as filename references (tag = <basedir>:<basename>) */
+ssize_t __android_log_pmsg_file_write(log_id_t logId, char prio, const char* filename,
+                                      const char* buf, size_t len) {
+  size_t length, packet_len;
+  const char* tag;
+  char *cp, *slash;
+  struct timespec ts;
+  struct iovec vec[3];
+
+  /* Make sure the logId value is not a bad idea */
+  if ((logId == LOG_ID_KERNEL) ||   /* Verbotten */
+      (logId == LOG_ID_EVENTS) ||   /* Do not support binary content */
+      (logId == LOG_ID_SECURITY) || /* Bad idea to allow */
+      ((unsigned)logId >= 32)) {    /* fit within logMask on arch32 */
+    return -EINVAL;
+  }
+
+  clock_gettime(CLOCK_REALTIME, &ts);
+
+  cp = strdup(filename);
+  if (!cp) {
+    return -ENOMEM;
+  }
+
+  tag = cp;
+  slash = strrchr(cp, '/');
+  if (slash) {
+    *slash = ':';
+    slash = strrchr(cp, '/');
+    if (slash) {
+      tag = slash + 1;
+    }
+  }
+
+  length = strlen(tag) + 1;
+  packet_len = LOGGER_ENTRY_MAX_PAYLOAD - sizeof(char) - length;
+
+  vec[0].iov_base = &prio;
+  vec[0].iov_len = sizeof(char);
+  vec[1].iov_base = (unsigned char*)tag;
+  vec[1].iov_len = length;
+
+  for (ts.tv_nsec = 0, length = len; length; ts.tv_nsec += ANDROID_LOG_PMSG_FILE_SEQUENCE) {
+    ssize_t ret;
+    size_t transfer;
+
+    if ((ts.tv_nsec / ANDROID_LOG_PMSG_FILE_SEQUENCE) >= ANDROID_LOG_PMSG_FILE_MAX_SEQUENCE) {
+      len -= length;
+      break;
+    }
+
+    transfer = length;
+    if (transfer > packet_len) {
+      transfer = strnrchr(buf, packet_len - 1, '\n') - buf;
+      if ((transfer < length) && (buf[transfer] == '\n')) {
+        ++transfer;
+      }
+    }
+
+    vec[2].iov_base = (unsigned char*)buf;
+    vec[2].iov_len = transfer;
+
+    ret = PmsgWrite(logId, &ts, vec, sizeof(vec) / sizeof(vec[0]));
+
+    if (ret <= 0) {
+      free(cp);
+      return ret ? ret : (len - length);
+    }
+    length -= transfer;
+    buf += transfer;
+  }
+  free(cp);
+  return len;
+}
diff --git a/liblog/fake_log_device.h b/liblog/pmsg_writer.h
similarity index 62%
rename from liblog/fake_log_device.h
rename to liblog/pmsg_writer.h
index 9d168cd..d5e1a1c 100644
--- a/liblog/fake_log_device.h
+++ b/liblog/pmsg_writer.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -14,15 +14,11 @@
  * limitations under the License.
  */
 
-#ifndef _LIBLOG_FAKE_LOG_DEVICE_H
-#define _LIBLOG_FAKE_LOG_DEVICE_H
+#pragma once
 
-#include <sys/types.h>
+#include <stddef.h>
 
-struct iovec;
+#include <android/log.h>
 
-int fakeLogOpen(const char *pathName, int flags);
-int fakeLogClose(int fd);
-ssize_t fakeLogWritev(int fd, const struct iovec* vector, int count);
-
-#endif // _LIBLOG_FAKE_LOG_DEVICE_H
+int PmsgWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr);
+void PmsgClose();
diff --git a/liblog/properties.cpp b/liblog/properties.cpp
new file mode 100644
index 0000000..88f0bf1
--- /dev/null
+++ b/liblog/properties.cpp
@@ -0,0 +1,385 @@
+/*
+** Copyright 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 <log/log_properties.h>
+
+#include <ctype.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include <private/android_logger.h>
+
+#include "logger_write.h"
+
+#ifdef __ANDROID__
+#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
+#include <sys/_system_properties.h>
+
+static pthread_mutex_t lock_loggable = PTHREAD_MUTEX_INITIALIZER;
+
+static int lock() {
+  /*
+   * If we trigger a signal handler in the middle of locked activity and the
+   * signal handler logs a message, we could get into a deadlock state.
+   */
+  /*
+   *  Any contention, and we can turn around and use the non-cached method
+   * in less time than the system call associated with a mutex to deal with
+   * the contention.
+   */
+  return pthread_mutex_trylock(&lock_loggable);
+}
+
+static void unlock() {
+  pthread_mutex_unlock(&lock_loggable);
+}
+
+struct cache {
+  const prop_info* pinfo;
+  uint32_t serial;
+};
+
+struct cache_char {
+  struct cache cache;
+  unsigned char c;
+};
+
+static int check_cache(struct cache* cache) {
+  return cache->pinfo && __system_property_serial(cache->pinfo) != cache->serial;
+}
+
+#define BOOLEAN_TRUE 0xFF
+#define BOOLEAN_FALSE 0xFE
+
+static void refresh_cache(struct cache_char* cache, const char* key) {
+  char buf[PROP_VALUE_MAX];
+
+  if (!cache->cache.pinfo) {
+    cache->cache.pinfo = __system_property_find(key);
+    if (!cache->cache.pinfo) {
+      return;
+    }
+  }
+  cache->cache.serial = __system_property_serial(cache->cache.pinfo);
+  __system_property_read(cache->cache.pinfo, 0, buf);
+  switch (buf[0]) {
+    case 't':
+    case 'T':
+      cache->c = strcasecmp(buf + 1, "rue") ? buf[0] : BOOLEAN_TRUE;
+      break;
+    case 'f':
+    case 'F':
+      cache->c = strcasecmp(buf + 1, "alse") ? buf[0] : BOOLEAN_FALSE;
+      break;
+    default:
+      cache->c = buf[0];
+  }
+}
+
+static int __android_log_level(const char* tag, size_t len) {
+  /* sizeof() is used on this array below */
+  static const char log_namespace[] = "persist.log.tag.";
+  static const size_t base_offset = 8; /* skip "persist." */
+
+  if (tag == nullptr || len == 0) {
+    auto& tag_string = GetDefaultTag();
+    tag = tag_string.c_str();
+    len = tag_string.size();
+  }
+
+  /* sizeof(log_namespace) = strlen(log_namespace) + 1 */
+  char key[sizeof(log_namespace) + len];
+  char* kp;
+  size_t i;
+  char c = 0;
+  /*
+   * Single layer cache of four properties. Priorities are:
+   *    log.tag.<tag>
+   *    persist.log.tag.<tag>
+   *    log.tag
+   *    persist.log.tag
+   * Where the missing tag matches all tags and becomes the
+   * system global default. We do not support ro.log.tag* .
+   */
+  static char* last_tag;
+  static size_t last_tag_len;
+  static uint32_t global_serial;
+  /* some compilers erroneously see uninitialized use. !not_locked */
+  uint32_t current_global_serial = 0;
+  static struct cache_char tag_cache[2];
+  static struct cache_char global_cache[2];
+  int change_detected;
+  int global_change_detected;
+  int not_locked;
+
+  strcpy(key, log_namespace);
+
+  global_change_detected = change_detected = not_locked = lock();
+
+  if (!not_locked) {
+    /*
+     *  check all known serial numbers to changes.
+     */
+    for (i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) {
+      if (check_cache(&tag_cache[i].cache)) {
+        change_detected = 1;
+      }
+    }
+    for (i = 0; i < (sizeof(global_cache) / sizeof(global_cache[0])); ++i) {
+      if (check_cache(&global_cache[i].cache)) {
+        global_change_detected = 1;
+      }
+    }
+
+    current_global_serial = __system_property_area_serial();
+    if (current_global_serial != global_serial) {
+      change_detected = 1;
+      global_change_detected = 1;
+    }
+  }
+
+  if (len) {
+    int local_change_detected = change_detected;
+    if (!not_locked) {
+      if (!last_tag || !last_tag[0] || (last_tag[0] != tag[0]) ||
+          strncmp(last_tag + 1, tag + 1, last_tag_len - 1)) {
+        /* invalidate log.tag.<tag> cache */
+        for (i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) {
+          tag_cache[i].cache.pinfo = NULL;
+          tag_cache[i].c = '\0';
+        }
+        if (last_tag) last_tag[0] = '\0';
+        local_change_detected = 1;
+      }
+      if (!last_tag || !last_tag[0]) {
+        if (!last_tag) {
+          last_tag = static_cast<char*>(calloc(1, len + 1));
+          last_tag_len = 0;
+          if (last_tag) last_tag_len = len + 1;
+        } else if (len >= last_tag_len) {
+          last_tag = static_cast<char*>(realloc(last_tag, len + 1));
+          last_tag_len = 0;
+          if (last_tag) last_tag_len = len + 1;
+        }
+        if (last_tag) {
+          strncpy(last_tag, tag, len);
+          last_tag[len] = '\0';
+        }
+      }
+    }
+    strncpy(key + sizeof(log_namespace) - 1, tag, len);
+    key[sizeof(log_namespace) - 1 + len] = '\0';
+
+    kp = key;
+    for (i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) {
+      struct cache_char* cache = &tag_cache[i];
+      struct cache_char temp_cache;
+
+      if (not_locked) {
+        temp_cache.cache.pinfo = NULL;
+        temp_cache.c = '\0';
+        cache = &temp_cache;
+      }
+      if (local_change_detected) {
+        refresh_cache(cache, kp);
+      }
+
+      if (cache->c) {
+        c = cache->c;
+        break;
+      }
+
+      kp = key + base_offset;
+    }
+  }
+
+  switch (toupper(c)) { /* if invalid, resort to global */
+    case 'V':
+    case 'D':
+    case 'I':
+    case 'W':
+    case 'E':
+    case 'F': /* Not officially supported */
+    case 'A':
+    case 'S':
+    case BOOLEAN_FALSE: /* Not officially supported */
+      break;
+    default:
+      /* clear '.' after log.tag */
+      key[sizeof(log_namespace) - 2] = '\0';
+
+      kp = key;
+      for (i = 0; i < (sizeof(global_cache) / sizeof(global_cache[0])); ++i) {
+        struct cache_char* cache = &global_cache[i];
+        struct cache_char temp_cache;
+
+        if (not_locked) {
+          temp_cache = *cache;
+          if (temp_cache.cache.pinfo != cache->cache.pinfo) { /* check atomic */
+            temp_cache.cache.pinfo = NULL;
+            temp_cache.c = '\0';
+          }
+          cache = &temp_cache;
+        }
+        if (global_change_detected) {
+          refresh_cache(cache, kp);
+        }
+
+        if (cache->c) {
+          c = cache->c;
+          break;
+        }
+
+        kp = key + base_offset;
+      }
+      break;
+  }
+
+  if (!not_locked) {
+    global_serial = current_global_serial;
+    unlock();
+  }
+
+  switch (toupper(c)) {
+    /* clang-format off */
+    case 'V': return ANDROID_LOG_VERBOSE;
+    case 'D': return ANDROID_LOG_DEBUG;
+    case 'I': return ANDROID_LOG_INFO;
+    case 'W': return ANDROID_LOG_WARN;
+    case 'E': return ANDROID_LOG_ERROR;
+    case 'F': /* FALLTHRU */ /* Not officially supported */
+    case 'A': return ANDROID_LOG_FATAL;
+    case BOOLEAN_FALSE: /* FALLTHRU */ /* Not Officially supported */
+    case 'S': return ANDROID_LOG_SILENT;
+      /* clang-format on */
+  }
+  return -1;
+}
+
+int __android_log_is_loggable_len(int prio, const char* tag, size_t len, int default_prio) {
+  int minimum_log_priority = __android_log_get_minimum_priority();
+  int property_log_level = __android_log_level(tag, len);
+
+  if (property_log_level >= 0 && minimum_log_priority != ANDROID_LOG_DEFAULT) {
+    return prio >= std::min(property_log_level, minimum_log_priority);
+  } else if (property_log_level >= 0) {
+    return prio >= property_log_level;
+  } else if (minimum_log_priority != ANDROID_LOG_DEFAULT) {
+    return prio >= minimum_log_priority;
+  } else {
+    return prio >= default_prio;
+  }
+}
+
+int __android_log_is_loggable(int prio, const char* tag, int default_prio) {
+  auto len = tag ? strlen(tag) : 0;
+  return __android_log_is_loggable_len(prio, tag, len, default_prio);
+}
+
+int __android_log_is_debuggable() {
+  static int is_debuggable = [] {
+    char value[PROP_VALUE_MAX] = {};
+    return __system_property_get("ro.debuggable", value) > 0 && !strcmp(value, "1");
+  }();
+
+  return is_debuggable;
+}
+
+/*
+ * For properties that are read often, but generally remain constant.
+ * Since a change is rare, we will accept a trylock failure gracefully.
+ * Use a separate lock from is_loggable to keep contention down b/25563384.
+ */
+struct cache2_char {
+  pthread_mutex_t lock;
+  uint32_t serial;
+  const char* key_persist;
+  struct cache_char cache_persist;
+  const char* key_ro;
+  struct cache_char cache_ro;
+  unsigned char (*const evaluate)(const struct cache2_char* self);
+};
+
+static inline unsigned char do_cache2_char(struct cache2_char* self) {
+  uint32_t current_serial;
+  int change_detected;
+  unsigned char c;
+
+  if (pthread_mutex_trylock(&self->lock)) {
+    /* We are willing to accept some race in this context */
+    return self->evaluate(self);
+  }
+
+  change_detected = check_cache(&self->cache_persist.cache) || check_cache(&self->cache_ro.cache);
+  current_serial = __system_property_area_serial();
+  if (current_serial != self->serial) {
+    change_detected = 1;
+  }
+  if (change_detected) {
+    refresh_cache(&self->cache_persist, self->key_persist);
+    refresh_cache(&self->cache_ro, self->key_ro);
+    self->serial = current_serial;
+  }
+  c = self->evaluate(self);
+
+  pthread_mutex_unlock(&self->lock);
+
+  return c;
+}
+
+/*
+ * Security state generally remains constant, but the DO must be able
+ * to turn off logging should it become spammy after an attack is detected.
+ */
+static unsigned char evaluate_security(const struct cache2_char* self) {
+  unsigned char c = self->cache_ro.c;
+
+  return (c != BOOLEAN_FALSE) && c && (self->cache_persist.c == BOOLEAN_TRUE);
+}
+
+int __android_log_security() {
+  static struct cache2_char security = {
+      PTHREAD_MUTEX_INITIALIZER, 0,
+      "persist.logd.security",   {{NULL, 0xFFFFFFFF}, BOOLEAN_FALSE},
+      "ro.organization_owned",   {{NULL, 0xFFFFFFFF}, BOOLEAN_FALSE},
+      evaluate_security};
+
+  return do_cache2_char(&security);
+}
+
+#else
+
+int __android_log_is_loggable(int prio, const char*, int) {
+  int minimum_priority = __android_log_get_minimum_priority();
+  if (minimum_priority == ANDROID_LOG_DEFAULT) {
+    minimum_priority = ANDROID_LOG_INFO;
+  }
+  return prio >= minimum_priority;
+}
+
+int __android_log_is_loggable_len(int prio, const char*, size_t, int def) {
+  return __android_log_is_loggable(prio, nullptr, def);
+}
+
+int __android_log_is_debuggable() {
+  return 1;
+}
+
+#endif
diff --git a/liblog/tests/Android.bp b/liblog/tests/Android.bp
new file mode 100644
index 0000000..171aafd
--- /dev/null
+++ b/liblog/tests/Android.bp
@@ -0,0 +1,114 @@
+//
+// Copyright (C) 2013-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.
+//
+
+// -----------------------------------------------------------------------------
+// Benchmarks.
+// -----------------------------------------------------------------------------
+
+// Build benchmarks for the device. Run with:
+//   adb shell liblog-benchmarks
+cc_benchmark {
+    name: "liblog-benchmarks",
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-fno-builtin",
+    ],
+    shared_libs: [
+        "libm",
+        "libbase",
+        "libcutils",
+    ],
+    static_libs: ["liblog"],
+    srcs: ["liblog_benchmark.cpp"],
+}
+
+// -----------------------------------------------------------------------------
+// Unit tests.
+// -----------------------------------------------------------------------------
+
+cc_defaults {
+    name: "liblog-tests-defaults",
+
+    cflags: [
+        "-fstack-protector-all",
+        "-g",
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-fno-builtin",
+    ],
+    srcs: [
+        "libc_test.cpp",
+        "liblog_default_tag.cpp",
+        "liblog_global_state.cpp",
+        "liblog_test.cpp",
+        "log_id_test.cpp",
+        "log_radio_test.cpp",
+        "log_read_test.cpp",
+        "log_system_test.cpp",
+        "log_time_test.cpp",
+        "log_wrap_test.cpp",
+        "logd_writer_test.cpp",
+        "logprint_test.cpp",
+    ],
+    shared_libs: [
+        "libcutils",
+        "libbase",
+    ],
+    static_libs: ["liblog"],
+    isolated: true,
+    require_root: true,
+}
+
+// Build tests for the device (with .so). Run with:
+//   adb shell /data/nativetest/liblog-unit-tests/liblog-unit-tests
+cc_test {
+    name: "liblog-unit-tests",
+    defaults: ["liblog-tests-defaults"],
+}
+
+cc_test {
+    name: "CtsLiblogTestCases",
+    defaults: ["liblog-tests-defaults"],
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+
+    cflags: ["-DNO_PSTORE"],
+    test_suites: [
+        "cts",
+        "device-tests",
+    ],
+}
+
+cc_test_host {
+    name: "liblog-host-test",
+    static_libs: ["liblog"],
+    shared_libs: ["libbase"],
+    srcs: [
+        "liblog_host_test.cpp",
+        "liblog_default_tag.cpp",
+        "liblog_global_state.cpp",
+    ],
+    isolated: true,
+}
diff --git a/liblog/tests/Android.mk b/liblog/tests/Android.mk
deleted file mode 100644
index a407c50..0000000
--- a/liblog/tests/Android.mk
+++ /dev/null
@@ -1,82 +0,0 @@
-#
-# Copyright (C) 2013-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.
-#
-
-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_CFLAGS += $(benchmark_c_flags)
-LOCAL_SHARED_LIBRARIES += liblog libm
-LOCAL_SRC_FILES := $(benchmark_src_files)
-include $(BUILD_NATIVE_TEST)
-
-# -----------------------------------------------------------------------------
-# Unit tests.
-# -----------------------------------------------------------------------------
-
-test_c_flags := \
-    -fstack-protector-all \
-    -g \
-    -Wall -Wextra \
-    -Werror \
-    -fno-builtin \
-    -std=gnu++11
-
-test_src_files := \
-    liblog_test.cpp
-
-# to prevent breaking the build if bionic not relatively visible to us
-ifneq ($(wildcard $(LOCAL_PATH)/../../../../bionic/libc/bionic/libc_logging.cpp),)
-
-test_src_files += \
-    libc_test.cpp
-
-ifneq ($(TARGET_USES_LOGD),false)
-test_c_flags += -DTARGET_USES_LOGD
-endif
-
-endif
-
-# 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_CFLAGS += $(test_c_flags)
-LOCAL_SHARED_LIBRARIES := liblog libcutils
-LOCAL_SRC_FILES := $(test_src_files)
-include $(BUILD_NATIVE_TEST)
diff --git a/liblog/tests/AndroidTest.xml b/liblog/tests/AndroidTest.xml
new file mode 100644
index 0000000..fcb46b1
--- /dev/null
+++ b/liblog/tests/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<configuration description="Config for CTS Logging Library test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="systems" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsLiblogTestCases->/data/local/tmp/CtsLiblogTestCases" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="CtsLiblogTestCases" />
+        <option name="runtime-hint" value="65s" />
+    </test>
+</configuration>
diff --git a/liblog/tests/benchmark.h b/liblog/tests/benchmark.h
deleted file mode 100644
index 7f96e6d..0000000
--- a/liblog/tests/benchmark.h
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2012-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 <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
deleted file mode 100644
index e5ef970..0000000
--- a/liblog/tests/benchmark_main.cpp
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2012-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 <benchmark.h>
-
-#include <inttypes.h>
-#include <math.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 %10" PRIu64 " %10" PRIu64 "%s\n", full_name,
-            static_cast<uint64_t>(iterations), mean, throughput);
-  } else {
-    printf("%-25s %10" PRIu64 " %10" PRIu64 "(\317\203%" PRIu64 ")%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/libc_test.cpp b/liblog/tests/libc_test.cpp
index 0e84f4e..1f26263 100644
--- a/liblog/tests/libc_test.cpp
+++ b/liblog/tests/libc_test.cpp
@@ -14,134 +14,44 @@
  * limitations under the License.
  */
 
-#include <fcntl.h>
-#include <sys/cdefs.h>
-
 #include <gtest/gtest.h>
 
-// Should be in bionic test suite, *but* we are using liblog to confirm
-// end-to-end logging, so let the overly cute oedipus complex begin ...
-#include "../../../../bionic/libc/bionic/libc_logging.cpp" // not Standalone
-#define _ANDROID_LOG_H // Priorities redefined
-#define _LIBS_LOG_LOG_H // log ids redefined
-typedef unsigned char log_id_t; // log_id_t missing as a result
-#ifdef TARGET_USES_LOGD
-#define _LIBS_LOG_LOG_READ_H // log_time redefined
-#endif
-
-#include <log/log.h>
-#include <log/logger.h>
-#include <log/log_read.h>
-
-TEST(libc, __libc_android_log_event_int) {
-    struct logger_list *logger_list;
-
-    pid_t pid = getpid();
-
-    ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(
-        LOG_ID_EVENTS, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 1000, pid)));
-
-    struct timespec ts;
-    clock_gettime(CLOCK_MONOTONIC, &ts);
-    int value = ts.tv_nsec;
-
-    __libc_android_log_event_int(0, value);
-    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 + 4))
-         || ((int)log_msg.id() != LOG_ID_EVENTS)) {
-            continue;
-        }
-
-        char *eventData = log_msg.msg();
-
-        int incoming = (eventData[0] & 0xFF) |
-                      ((eventData[1] & 0xFF) << 8) |
-                      ((eventData[2] & 0xFF) << 16) |
-                      ((eventData[3] & 0xFF) << 24);
-
-        if (incoming != 0) {
-            continue;
-        }
-
-        if (eventData[4] != EVENT_TYPE_INT) {
-            continue;
-        }
-
-        incoming = (eventData[4 + 1 + 0] & 0xFF) |
-                  ((eventData[4 + 1 + 1] & 0xFF) << 8) |
-                  ((eventData[4 + 1 + 2] & 0xFF) << 16) |
-                  ((eventData[4 + 1 + 3] & 0xFF) << 24);
-
-        if (incoming == value) {
-            ++count;
-        }
-    }
-
-    EXPECT_EQ(1, count);
-
-    android_logger_list_close(logger_list);
-}
-
-TEST(libc, __libc_fatal_no_abort) {
-    struct logger_list *logger_list;
-
-    pid_t pid = getpid();
-
-    ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(
-        (log_id_t)LOG_ID_CRASH, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 1000, pid)));
-
-    char b[80];
-    struct timespec ts;
-    clock_gettime(CLOCK_MONOTONIC, &ts);
-
-    __libc_fatal_no_abort("%u.%09u", (unsigned)ts.tv_sec, (unsigned)ts.tv_nsec);
-    snprintf(b, sizeof(b),"%u.%09u", (unsigned)ts.tv_sec, (unsigned)ts.tv_nsec);
-    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 ((int)log_msg.id() != LOG_ID_CRASH) {
-            continue;
-        }
-
-        char *data = log_msg.msg();
-
-        if ((*data == ANDROID_LOG_FATAL)
-                && !strcmp(data + 1, "libc")
-                && !strcmp(data + 1 + strlen(data + 1) + 1, b)) {
-            ++count;
-        }
-    }
-
-    EXPECT_EQ(1, count);
-
-    android_logger_list_close(logger_list);
-}
+#include <errno.h>
+#include <stdio.h>
 
 TEST(libc, __pstore_append) {
-    FILE *fp;
-    ASSERT_TRUE(NULL != (fp = fopen("/dev/pmsg0", "a")));
-    static const char message[] = "libc.__pstore_append\n";
-    ASSERT_EQ((size_t)1, fwrite(message, sizeof(message), 1, fp));
-    ASSERT_EQ(0, fclose(fp));
-    fprintf(stderr, "Reboot, ensure string libc.__pstore_append is in /sys/fs/pstore/pmsg-ramoops-0\n");
+#ifdef __ANDROID__
+#ifndef NO_PSTORE
+  if (access("/dev/pmsg0", W_OK) != 0) {
+    GTEST_SKIP() << "pmsg0 not found, skipping test";
+  }
+
+  FILE* fp;
+  ASSERT_TRUE(NULL != (fp = fopen("/dev/pmsg0", "ae")));
+  static const char message[] = "libc.__pstore_append\n";
+  ASSERT_EQ((size_t)1, fwrite(message, sizeof(message), 1, fp));
+  int fflushReturn = fflush(fp);
+  int fflushErrno = fflushReturn ? errno : 0;
+  ASSERT_EQ(0, fflushReturn);
+  ASSERT_EQ(0, fflushErrno);
+  int fcloseReturn = fclose(fp);
+  int fcloseErrno = fcloseReturn ? errno : 0;
+  ASSERT_EQ(0, fcloseReturn);
+  ASSERT_EQ(0, fcloseErrno);
+  if ((fcloseErrno == ENOMEM) || (fflushErrno == ENOMEM)) {
+    fprintf(stderr,
+            "Kernel does not have space allocated to pmsg pstore driver "
+            "configured\n");
+  }
+  if (!fcloseReturn && !fcloseErrno && !fflushReturn && !fflushReturn) {
+    fprintf(stderr,
+            "Reboot, ensure string libc.__pstore_append is in "
+            "/sys/fs/pstore/pmsg-ramoops-0\n");
+  }
+#else  /* NO_PSTORE */
+  GTEST_LOG_(INFO) << "This test does nothing because of NO_PSTORE.\n";
+#endif /* NO_PSTORE */
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
 }
diff --git a/liblog/tests/liblog_benchmark.cpp b/liblog/tests/liblog_benchmark.cpp
index b594634..d2f12d6 100644
--- a/liblog/tests/liblog_benchmark.cpp
+++ b/liblog/tests/liblog_benchmark.cpp
@@ -14,44 +14,52 @@
  * limitations under the License.
  */
 
+#include <fcntl.h>
+#include <inttypes.h>
+#include <poll.h>
+#include <sched.h>
 #include <sys/socket.h>
-#include <cutils/sockets.h>
-#include <log/log.h>
-#include <log/logger.h>
-#include <log/log_read.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
 
-#include "benchmark.h"
+#include <unordered_set>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <benchmark/benchmark.h>
+#include <cutils/sockets.h>
+#include <log/event_tag_map.h>
+#include <log/log_read.h>
+#include <private/android_logger.h>
+
+BENCHMARK_MAIN();
 
 // enhanced version of LOG_FAILURE_RETRY to add support for EAGAIN and
 // non-syscall libs. Since we are benchmarking, or using this in the emergency
 // signal to stuff a terminating code, we do NOT want to introduce
 // a syscall or usleep on EAGAIN retry.
-#define LOG_FAILURE_RETRY(exp) ({  \
-    typeof (exp) _rc;              \
-    do {                           \
-        _rc = (exp);               \
-    } while (((_rc == -1)          \
-           && ((errno == EINTR)    \
-            || (errno == EAGAIN))) \
-          || (_rc == -EINTR)       \
-          || (_rc == -EAGAIN));    \
-    _rc; })
+#define LOG_FAILURE_RETRY(exp)                                           \
+  ({                                                                     \
+    typeof(exp) _rc;                                                     \
+    do {                                                                 \
+      _rc = (exp);                                                       \
+    } while (((_rc == -1) && ((errno == EINTR) || (errno == EAGAIN))) || \
+             (_rc == -EINTR) || (_rc == -EAGAIN));                       \
+    _rc;                                                                 \
+  })
 
 /*
  *	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) {
-        LOG_FAILURE_RETRY(
-            __android_log_print(ANDROID_LOG_INFO,
-                                "BM_log_maximum_retry", "%d", i));
-    }
-
-    StopBenchmarkTiming();
+static void BM_log_maximum_retry(benchmark::State& state) {
+  while (state.KeepRunning()) {
+    LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_INFO, "BM_log_maximum_retry", "%" PRIu64,
+                                          state.iterations()));
+  }
 }
 BENCHMARK(BM_log_maximum_retry);
 
@@ -60,223 +68,937 @@
  * 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();
+static void BM_log_maximum(benchmark::State& state) {
+  while (state.KeepRunning()) {
+    __android_log_print(ANDROID_LOG_INFO, "BM_log_maximum", "%" PRIu64, state.iterations());
+  }
 }
 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).
+ *	Measure the time it takes to collect the time using
+ * discrete acquisition (state.PauseTiming() to state.ResumeTiming())
+ * under light load. Expect this to be a syscall period (2us) or
+ * data read time if zero-syscall.
+ *
+ * vdso support in the kernel and the library can allow
+ * clock_gettime to be zero-syscall, but there there does remain some
+ * benchmarking overhead to pause and resume; assumptions are both are
+ * covered.
  */
-static void BM_clock_overhead(int iters) {
-    for (int i = 0; i < iters; ++i) {
-       StartBenchmarkTiming();
-       StopBenchmarkTiming();
-    }
+static void BM_clock_overhead(benchmark::State& state) {
+  while (state.KeepRunning()) {
+    state.PauseTiming();
+    state.ResumeTiming();
+  }
 }
 BENCHMARK(BM_clock_overhead);
 
+static void do_clock_overhead(benchmark::State& state, clockid_t clk_id) {
+  timespec t;
+  while (state.KeepRunning()) {
+    clock_gettime(clk_id, &t);
+  }
+}
+
+static void BM_time_clock_gettime_REALTIME(benchmark::State& state) {
+  do_clock_overhead(state, CLOCK_REALTIME);
+}
+BENCHMARK(BM_time_clock_gettime_REALTIME);
+
+static void BM_time_clock_gettime_MONOTONIC(benchmark::State& state) {
+  do_clock_overhead(state, CLOCK_MONOTONIC);
+}
+BENCHMARK(BM_time_clock_gettime_MONOTONIC);
+
+static void BM_time_clock_gettime_MONOTONIC_syscall(benchmark::State& state) {
+  timespec t;
+  while (state.KeepRunning()) {
+    syscall(__NR_clock_gettime, CLOCK_MONOTONIC, &t);
+  }
+}
+BENCHMARK(BM_time_clock_gettime_MONOTONIC_syscall);
+
+static void BM_time_clock_gettime_MONOTONIC_RAW(benchmark::State& state) {
+  do_clock_overhead(state, CLOCK_MONOTONIC_RAW);
+}
+BENCHMARK(BM_time_clock_gettime_MONOTONIC_RAW);
+
+static void BM_time_clock_gettime_BOOTTIME(benchmark::State& state) {
+  do_clock_overhead(state, CLOCK_BOOTTIME);
+}
+BENCHMARK(BM_time_clock_gettime_BOOTTIME);
+
+static void BM_time_clock_getres_MONOTONIC(benchmark::State& state) {
+  timespec t;
+  while (state.KeepRunning()) {
+    clock_getres(CLOCK_MONOTONIC, &t);
+  }
+}
+BENCHMARK(BM_time_clock_getres_MONOTONIC);
+
+static void BM_time_clock_getres_MONOTONIC_syscall(benchmark::State& state) {
+  timespec t;
+  while (state.KeepRunning()) {
+    syscall(__NR_clock_getres, CLOCK_MONOTONIC, &t);
+  }
+}
+BENCHMARK(BM_time_clock_getres_MONOTONIC_syscall);
+
+static void BM_time_time(benchmark::State& state) {
+  while (state.KeepRunning()) {
+    time_t now;
+    now = time(&now);
+  }
+}
+BENCHMARK(BM_time_time);
+
 /*
- *	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).
+ * Measure the time it takes to submit the android logging data to pstore
  */
-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);
-    }
+static void BM_pmsg_short(benchmark::State& state) {
+  int pstore_fd = TEMP_FAILURE_RETRY(open("/dev/pmsg0", O_WRONLY | O_CLOEXEC));
+  if (pstore_fd < 0) {
+    state.SkipWithError("/dev/pmsg0");
+    return;
+  }
+
+  /*
+   *  struct {
+   *      // what we provide to pstore
+   *      android_pmsg_log_header_t pmsg_header;
+   *      // what we provide to socket
+   *      android_log_header_t header;
+   *      // caller provides
+   *      union {
+   *          struct {
+   *              char     prio;
+   *              char     payload[];
+   *          } string;
+   *          struct {
+   *              uint32_t tag
+   *              char     payload[];
+   *          } binary;
+   *      };
+   *  };
+   */
+
+  struct timespec ts;
+  clock_gettime(CLOCK_REALTIME, &ts);
+
+  android_pmsg_log_header_t pmsg_header;
+  pmsg_header.magic = LOGGER_MAGIC;
+  pmsg_header.len =
+      sizeof(android_pmsg_log_header_t) + sizeof(android_log_header_t);
+  pmsg_header.uid = getuid();
+  pmsg_header.pid = getpid();
+
+  android_log_header_t header;
+  header.tid = gettid();
+  header.realtime.tv_sec = ts.tv_sec;
+  header.realtime.tv_nsec = ts.tv_nsec;
+
+  static const unsigned nr = 1;
+  static const unsigned header_length = 2;
+  struct iovec newVec[nr + header_length];
+
+  newVec[0].iov_base = (unsigned char*)&pmsg_header;
+  newVec[0].iov_len = sizeof(pmsg_header);
+  newVec[1].iov_base = (unsigned char*)&header;
+  newVec[1].iov_len = sizeof(header);
+
+  android_log_event_int_t buffer;
+
+  header.id = LOG_ID_EVENTS;
+  buffer.header.tag = 0;
+  buffer.payload.type = EVENT_TYPE_INT;
+  uint32_t snapshot = 0;
+  buffer.payload.data = snapshot;
+
+  newVec[2].iov_base = &buffer;
+  newVec[2].iov_len = sizeof(buffer);
+
+  while (state.KeepRunning()) {
+    ++snapshot;
+    buffer.payload.data = snapshot;
+    writev(pstore_fd, newVec, nr);
+  }
+  state.PauseTiming();
+  close(pstore_fd);
 }
-BENCHMARK(BM_log_overhead);
+BENCHMARK(BM_pmsg_short);
 
-static void caught_latency(int /*signum*/)
-{
-    unsigned long long v = 0xDEADBEEFA55A5AA5ULL;
+/*
+ * Measure the time it takes to submit the android logging data to pstore
+ * best case aligned single block.
+ */
+static void BM_pmsg_short_aligned(benchmark::State& state) {
+  int pstore_fd = TEMP_FAILURE_RETRY(open("/dev/pmsg0", O_WRONLY | O_CLOEXEC));
+  if (pstore_fd < 0) {
+    state.SkipWithError("/dev/pmsg0");
+    return;
+  }
 
-    LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v)));
+  /*
+   *  struct {
+   *      // what we provide to pstore
+   *      android_pmsg_log_header_t pmsg_header;
+   *      // what we provide to socket
+   *      android_log_header_t header;
+   *      // caller provides
+   *      union {
+   *          struct {
+   *              char     prio;
+   *              char     payload[];
+   *          } string;
+   *          struct {
+   *              uint32_t tag
+   *              char     payload[];
+   *          } binary;
+   *      };
+   *  };
+   */
+
+  struct timespec ts;
+  clock_gettime(CLOCK_REALTIME, &ts);
+
+  struct packet {
+    android_pmsg_log_header_t pmsg_header;
+    android_log_header_t header;
+    android_log_event_int_t payload;
+  };
+  alignas(8) char buf[sizeof(struct packet) + 8];
+  memset(buf, 0, sizeof(buf));
+  struct packet* buffer = (struct packet*)(((uintptr_t)buf + 7) & ~7);
+  if (((uintptr_t)&buffer->pmsg_header) & 7) {
+    fprintf(stderr, "&buffer=0x%p iterations=%" PRIu64 "\n", &buffer->pmsg_header,
+            state.iterations());
+  }
+
+  buffer->pmsg_header.magic = LOGGER_MAGIC;
+  buffer->pmsg_header.len =
+      sizeof(android_pmsg_log_header_t) + sizeof(android_log_header_t);
+  buffer->pmsg_header.uid = getuid();
+  buffer->pmsg_header.pid = getpid();
+
+  buffer->header.tid = gettid();
+  buffer->header.realtime.tv_sec = ts.tv_sec;
+  buffer->header.realtime.tv_nsec = ts.tv_nsec;
+
+  buffer->header.id = LOG_ID_EVENTS;
+  buffer->payload.header.tag = 0;
+  buffer->payload.payload.type = EVENT_TYPE_INT;
+  uint32_t snapshot = 0;
+  buffer->payload.payload.data = snapshot;
+
+  while (state.KeepRunning()) {
+    ++snapshot;
+    buffer->payload.payload.data = snapshot;
+    write(pstore_fd, &buffer->pmsg_header,
+          sizeof(android_pmsg_log_header_t) + sizeof(android_log_header_t) +
+              sizeof(android_log_event_int_t));
+  }
+  state.PauseTiming();
+  close(pstore_fd);
+}
+BENCHMARK(BM_pmsg_short_aligned);
+
+/*
+ * Measure the time it takes to submit the android logging data to pstore
+ * best case aligned single block.
+ */
+static void BM_pmsg_short_unaligned1(benchmark::State& state) {
+  int pstore_fd = TEMP_FAILURE_RETRY(open("/dev/pmsg0", O_WRONLY | O_CLOEXEC));
+  if (pstore_fd < 0) {
+    state.SkipWithError("/dev/pmsg0");
+    return;
+  }
+
+  /*
+   *  struct {
+   *      // what we provide to pstore
+   *      android_pmsg_log_header_t pmsg_header;
+   *      // what we provide to socket
+   *      android_log_header_t header;
+   *      // caller provides
+   *      union {
+   *          struct {
+   *              char     prio;
+   *              char     payload[];
+   *          } string;
+   *          struct {
+   *              uint32_t tag
+   *              char     payload[];
+   *          } binary;
+   *      };
+   *  };
+   */
+
+  struct timespec ts;
+  clock_gettime(CLOCK_REALTIME, &ts);
+
+  struct packet {
+    android_pmsg_log_header_t pmsg_header;
+    android_log_header_t header;
+    android_log_event_int_t payload;
+  };
+  alignas(8) char buf[sizeof(struct packet) + 8];
+  memset(buf, 0, sizeof(buf));
+  struct packet* buffer = (struct packet*)((((uintptr_t)buf + 7) & ~7) + 1);
+  if ((((uintptr_t)&buffer->pmsg_header) & 7) != 1) {
+    fprintf(stderr, "&buffer=0x%p iterations=%" PRIu64 "\n", &buffer->pmsg_header,
+            state.iterations());
+  }
+
+  buffer->pmsg_header.magic = LOGGER_MAGIC;
+  buffer->pmsg_header.len =
+      sizeof(android_pmsg_log_header_t) + sizeof(android_log_header_t);
+  buffer->pmsg_header.uid = getuid();
+  buffer->pmsg_header.pid = getpid();
+
+  buffer->header.tid = gettid();
+  buffer->header.realtime.tv_sec = ts.tv_sec;
+  buffer->header.realtime.tv_nsec = ts.tv_nsec;
+
+  buffer->header.id = LOG_ID_EVENTS;
+  buffer->payload.header.tag = 0;
+  buffer->payload.payload.type = EVENT_TYPE_INT;
+  uint32_t snapshot = 0;
+  buffer->payload.payload.data = snapshot;
+
+  while (state.KeepRunning()) {
+    ++snapshot;
+    buffer->payload.payload.data = snapshot;
+    write(pstore_fd, &buffer->pmsg_header,
+          sizeof(android_pmsg_log_header_t) + sizeof(android_log_header_t) +
+              sizeof(android_log_event_int_t));
+  }
+  state.PauseTiming();
+  close(pstore_fd);
+}
+BENCHMARK(BM_pmsg_short_unaligned1);
+
+/*
+ * Measure the time it takes to submit the android logging data to pstore
+ * best case aligned single block.
+ */
+static void BM_pmsg_long_aligned(benchmark::State& state) {
+  int pstore_fd = TEMP_FAILURE_RETRY(open("/dev/pmsg0", O_WRONLY | O_CLOEXEC));
+  if (pstore_fd < 0) {
+    state.SkipWithError("/dev/pmsg0");
+    return;
+  }
+
+  /*
+   *  struct {
+   *      // what we provide to pstore
+   *      android_pmsg_log_header_t pmsg_header;
+   *      // what we provide to socket
+   *      android_log_header_t header;
+   *      // caller provides
+   *      union {
+   *          struct {
+   *              char     prio;
+   *              char     payload[];
+   *          } string;
+   *          struct {
+   *              uint32_t tag
+   *              char     payload[];
+   *          } binary;
+   *      };
+   *  };
+   */
+
+  struct timespec ts;
+  clock_gettime(CLOCK_REALTIME, &ts);
+
+  struct packet {
+    android_pmsg_log_header_t pmsg_header;
+    android_log_header_t header;
+    android_log_event_int_t payload;
+  };
+  alignas(8) char buf[sizeof(struct packet) + 8 + LOGGER_ENTRY_MAX_PAYLOAD];
+  memset(buf, 0, sizeof(buf));
+  struct packet* buffer = (struct packet*)(((uintptr_t)buf + 7) & ~7);
+  if (((uintptr_t)&buffer->pmsg_header) & 7) {
+    fprintf(stderr, "&buffer=0x%p iterations=%" PRIu64 "\n", &buffer->pmsg_header,
+            state.iterations());
+  }
+
+  buffer->pmsg_header.magic = LOGGER_MAGIC;
+  buffer->pmsg_header.len =
+      sizeof(android_pmsg_log_header_t) + sizeof(android_log_header_t);
+  buffer->pmsg_header.uid = getuid();
+  buffer->pmsg_header.pid = getpid();
+
+  buffer->header.tid = gettid();
+  buffer->header.realtime.tv_sec = ts.tv_sec;
+  buffer->header.realtime.tv_nsec = ts.tv_nsec;
+
+  buffer->header.id = LOG_ID_EVENTS;
+  buffer->payload.header.tag = 0;
+  buffer->payload.payload.type = EVENT_TYPE_INT;
+  uint32_t snapshot = 0;
+  buffer->payload.payload.data = snapshot;
+
+  while (state.KeepRunning()) {
+    ++snapshot;
+    buffer->payload.payload.data = snapshot;
+    write(pstore_fd, &buffer->pmsg_header, LOGGER_ENTRY_MAX_PAYLOAD);
+  }
+  state.PauseTiming();
+  close(pstore_fd);
+}
+BENCHMARK(BM_pmsg_long_aligned);
+
+/*
+ * Measure the time it takes to submit the android logging data to pstore
+ * best case aligned single block.
+ */
+static void BM_pmsg_long_unaligned1(benchmark::State& state) {
+  int pstore_fd = TEMP_FAILURE_RETRY(open("/dev/pmsg0", O_WRONLY | O_CLOEXEC));
+  if (pstore_fd < 0) {
+    state.SkipWithError("/dev/pmsg0");
+    return;
+  }
+
+  /*
+   *  struct {
+   *      // what we provide to pstore
+   *      android_pmsg_log_header_t pmsg_header;
+   *      // what we provide to socket
+   *      android_log_header_t header;
+   *      // caller provides
+   *      union {
+   *          struct {
+   *              char     prio;
+   *              char     payload[];
+   *          } string;
+   *          struct {
+   *              uint32_t tag
+   *              char     payload[];
+   *          } binary;
+   *      };
+   *  };
+   */
+
+  struct timespec ts;
+  clock_gettime(CLOCK_REALTIME, &ts);
+
+  struct packet {
+    android_pmsg_log_header_t pmsg_header;
+    android_log_header_t header;
+    android_log_event_int_t payload;
+  };
+  alignas(8) char buf[sizeof(struct packet) + 8 + LOGGER_ENTRY_MAX_PAYLOAD];
+  memset(buf, 0, sizeof(buf));
+  struct packet* buffer = (struct packet*)((((uintptr_t)buf + 7) & ~7) + 1);
+  if ((((uintptr_t)&buffer->pmsg_header) & 7) != 1) {
+    fprintf(stderr, "&buffer=0x%p iterations=%" PRIu64 "\n", &buffer->pmsg_header,
+            state.iterations());
+  }
+
+  buffer->pmsg_header.magic = LOGGER_MAGIC;
+  buffer->pmsg_header.len =
+      sizeof(android_pmsg_log_header_t) + sizeof(android_log_header_t);
+  buffer->pmsg_header.uid = getuid();
+  buffer->pmsg_header.pid = getpid();
+
+  buffer->header.tid = gettid();
+  buffer->header.realtime.tv_sec = ts.tv_sec;
+  buffer->header.realtime.tv_nsec = ts.tv_nsec;
+
+  buffer->header.id = LOG_ID_EVENTS;
+  buffer->payload.header.tag = 0;
+  buffer->payload.payload.type = EVENT_TYPE_INT;
+  uint32_t snapshot = 0;
+  buffer->payload.payload.data = snapshot;
+
+  while (state.KeepRunning()) {
+    ++snapshot;
+    buffer->payload.payload.data = snapshot;
+    write(pstore_fd, &buffer->pmsg_header, LOGGER_ENTRY_MAX_PAYLOAD);
+  }
+  state.PauseTiming();
+  close(pstore_fd);
+}
+BENCHMARK(BM_pmsg_long_unaligned1);
+
+/*
+ *	Measure the time it takes to form sprintf plus time using
+ * discrete acquisition under light load. Expect this to be a syscall period
+ * (2us) or sprintf time if zero-syscall time.
+ */
+/* helper function */
+static void test_print(const char* fmt, ...) {
+  va_list ap;
+  char buf[1024];
+
+  va_start(ap, fmt);
+  vsnprintf(buf, sizeof(buf), fmt, ap);
+  va_end(ap);
 }
 
-static unsigned long long caught_convert(char *cp)
-{
-    unsigned long long l = cp[0] & 0xFF;
-    l |= (unsigned long long) (cp[1] & 0xFF) << 8;
-    l |= (unsigned long long) (cp[2] & 0xFF) << 16;
-    l |= (unsigned long long) (cp[3] & 0xFF) << 24;
-    l |= (unsigned long long) (cp[4] & 0xFF) << 32;
-    l |= (unsigned long long) (cp[5] & 0xFF) << 40;
-    l |= (unsigned long long) (cp[6] & 0xFF) << 48;
-    l |= (unsigned long long) (cp[7] & 0xFF) << 56;
-    return l;
+#define logd_yield() sched_yield()  // allow logd to catch up
+#define logd_sleep() usleep(50)     // really allow logd to catch up
+
+/* performance test */
+static void BM_sprintf_overhead(benchmark::State& state) {
+  while (state.KeepRunning()) {
+    test_print("BM_sprintf_overhead:%" PRIu64, state.iterations());
+    state.PauseTiming();
+    logd_yield();
+    state.ResumeTiming();
+  }
+}
+BENCHMARK(BM_sprintf_overhead);
+
+/*
+ *	Measure the time it takes to submit the android printing logging call
+ * using discrete acquisition discrete acquisition under light load. Expect
+ * this to be a dozen or so syscall periods (40us) plus time to run *printf
+ */
+static void BM_log_print_overhead(benchmark::State& state) {
+  while (state.KeepRunning()) {
+    __android_log_print(ANDROID_LOG_INFO, "BM_log_overhead", "%" PRIu64, state.iterations());
+    state.PauseTiming();
+    logd_yield();
+    state.ResumeTiming();
+  }
+}
+BENCHMARK(BM_log_print_overhead);
+
+/*
+ *	Measure the time it takes to submit the android event logging call
+ * using discrete acquisition under light load. Expect this to be a long path
+ * to logger to convert the unknown tag (0) into a tagname (less than 200us).
+ */
+static void BM_log_event_overhead(benchmark::State& state) {
+  for (int64_t i = 0; state.KeepRunning(); ++i) {
+    // log tag number 0 is not known, nor shall it ever be known
+    __android_log_btwrite(0, EVENT_TYPE_LONG, &i, sizeof(i));
+    state.PauseTiming();
+    logd_yield();
+    state.ResumeTiming();
+  }
+}
+BENCHMARK(BM_log_event_overhead);
+
+/*
+ *	Measure the time it takes to submit the android event logging call
+ * using discrete acquisition under light load with a known logtag.  Expect
+ * this to be a dozen or so syscall periods (less than 40us)
+ */
+static void BM_log_event_overhead_42(benchmark::State& state) {
+  for (int64_t i = 0; state.KeepRunning(); ++i) {
+    // In system/core/logcat/event.logtags:
+    // # These are used for testing, do not modify without updating
+    // # tests/framework-tests/src/android/util/EventLogFunctionalTest.java.
+    // # system/core/liblog/tests/liblog_benchmark.cpp
+    // # system/core/liblog/tests/liblog_test.cpp
+    // 42    answer (to life the universe etc|3)
+    __android_log_btwrite(42, EVENT_TYPE_LONG, &i, sizeof(i));
+    state.PauseTiming();
+    logd_yield();
+    state.ResumeTiming();
+  }
+}
+BENCHMARK(BM_log_event_overhead_42);
+
+/*
+ *	Measure the time it takes to submit the android event logging call
+ * using discrete acquisition under very-light load (<1% CPU utilization).
+ */
+static void BM_log_light_overhead(benchmark::State& state) {
+  for (int64_t i = 0; state.KeepRunning(); ++i) {
+    __android_log_btwrite(0, EVENT_TYPE_LONG, &i, sizeof(i));
+    state.PauseTiming();
+    usleep(10000);
+    state.ResumeTiming();
+  }
+}
+BENCHMARK(BM_log_light_overhead);
+
+static void caught_latency(int /*signum*/) {
+  unsigned long long v = 0xDEADBEEFA55A5AA5ULL;
+
+  LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v)));
+}
+
+static unsigned long long caught_convert(char* cp) {
+  unsigned long long l = cp[0] & 0xFF;
+  l |= (unsigned long long)(cp[1] & 0xFF) << 8;
+  l |= (unsigned long long)(cp[2] & 0xFF) << 16;
+  l |= (unsigned long long)(cp[3] & 0xFF) << 24;
+  l |= (unsigned long long)(cp[4] & 0xFF) << 32;
+  l |= (unsigned long long)(cp[5] & 0xFF) << 40;
+  l |= (unsigned long long)(cp[6] & 0xFF) << 48;
+  l |= (unsigned long long)(cp[7] & 0xFF) << 56;
+  return l;
 }
 
 static const int alarm_time = 3;
 
 /*
  *	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).
+ * timestamp to place into the internal record.  Expect this to be less than
+ * 4 syscalls (3us).  This test uses manual injection of timing because it is
+ * comparing the timestamp at send, and then picking up the corresponding log
+ * end-to-end long path from logd to see what actual timestamp was submitted.
  */
-static void BM_log_latency(int iters) {
-    pid_t pid = getpid();
+static void BM_log_latency(benchmark::State& state) {
+  pid_t pid = getpid();
 
-    struct logger_list * logger_list = android_logger_list_open(LOG_ID_EVENTS,
-        ANDROID_LOG_RDONLY, 0, pid);
+  struct logger_list* logger_list = android_logger_list_open(LOG_ID_EVENTS, 0, 0, pid);
 
-    if (!logger_list) {
-        fprintf(stderr, "Unable to open events log: %s\n", strerror(errno));
-        exit(EXIT_FAILURE);
-    }
+  if (!logger_list) {
+    fprintf(stderr, "Unable to open events log: %s\n", strerror(errno));
+    exit(EXIT_FAILURE);
+  }
 
-    signal(SIGALRM, caught_latency);
-    alarm(alarm_time);
+  signal(SIGALRM, caught_latency);
+  alarm(alarm_time);
 
-    for (int j = 0, i = 0; i < iters && j < 10*iters; ++i, ++j) {
-        log_time ts;
-        LOG_FAILURE_RETRY((
-            ts = log_time(CLOCK_REALTIME),
-            android_btWriteLog(0, EVENT_TYPE_LONG, &ts, sizeof(ts))));
+  for (size_t j = 0; state.KeepRunning() && j < 10 * state.iterations(); ++j) {
+  retry:  // We allow transitory errors (logd overloaded) to be retried.
+    log_time ts;
+    LOG_FAILURE_RETRY((ts = log_time(CLOCK_REALTIME),
+                       android_btWriteLog(0, EVENT_TYPE_LONG, &ts, sizeof(ts))));
 
-        for (;;) {
-            log_msg log_msg;
-            int ret = android_logger_list_read(logger_list, &log_msg);
-            alarm(alarm_time);
+    for (;;) {
+      log_msg log_msg;
+      int ret = android_logger_list_read(logger_list, &log_msg);
+      alarm(alarm_time);
 
-            if (ret <= 0) {
-                iters = i;
-                break;
-            }
-            if ((log_msg.entry.len != (4 + 1 + 8))
-             || (log_msg.id() != LOG_ID_EVENTS)) {
-                continue;
-            }
+      if (ret <= 0) {
+        state.SkipWithError("android_logger_list_read");
+        break;
+      }
+      if ((log_msg.entry.len != (4 + 1 + 8)) ||
+          (log_msg.id() != LOG_ID_EVENTS)) {
+        continue;
+      }
 
-            char* eventData = log_msg.msg();
+      char* eventData = log_msg.msg();
 
-            if (eventData[4] != EVENT_TYPE_LONG) {
-                continue;
-            }
-            log_time tx(eventData + 4 + 1);
-            if (ts != tx) {
-                if (0xDEADBEEFA55A5AA5ULL == caught_convert(eventData + 4 + 1)) {
-                    iters = i;
-                    break;
-                }
-                continue;
-            }
-
-            uint64_t start = ts.nsec();
-            uint64_t end = log_msg.nsec();
-            if (end >= start) {
-                StartBenchmarkTiming(start);
-                StopBenchmarkTiming(end);
-            } else {
-                --i;
-            }
-            break;
+      if (!eventData || (eventData[4] != EVENT_TYPE_LONG)) {
+        continue;
+      }
+      log_time* tx = reinterpret_cast<log_time*>(eventData + 4 + 1);
+      if (ts != *tx) {
+        if (0xDEADBEEFA55A5AA5ULL == caught_convert(eventData + 4 + 1)) {
+          state.SkipWithError("signal");
+          break;
         }
+        continue;
+      }
+
+      uint64_t start = ts.nsec();
+      uint64_t end = log_msg.nsec();
+      if (end < start) goto retry;
+      state.SetIterationTime((end - start) / (double)NS_PER_SEC);
+      break;
     }
+  }
 
-    signal(SIGALRM, SIG_DFL);
-    alarm(0);
+  signal(SIGALRM, SIG_DFL);
+  alarm(0);
 
-    android_logger_list_free(logger_list);
+  android_logger_list_free(logger_list);
 }
-BENCHMARK(BM_log_latency);
+// Default gets out of hand for this test, so we set a reasonable number of
+// iterations for a timely result.
+BENCHMARK(BM_log_latency)->UseManualTime()->Iterations(200);
 
-static void caught_delay(int /*signum*/)
-{
-    unsigned long long v = 0xDEADBEEFA55A5AA6ULL;
+static void caught_delay(int /*signum*/) {
+  unsigned long long v = 0xDEADBEEFA55A5AA6ULL;
 
-    LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v)));
+  LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v)));
 }
 
 /*
  *	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();
+static void BM_log_delay(benchmark::State& state) {
+  pid_t pid = getpid();
 
-    struct logger_list * logger_list = android_logger_list_open(LOG_ID_EVENTS,
-        ANDROID_LOG_RDONLY, 0, pid);
+  struct logger_list* logger_list = android_logger_list_open(LOG_ID_EVENTS, 0, 0, pid);
 
-    if (!logger_list) {
-        fprintf(stderr, "Unable to open events log: %s\n", strerror(errno));
-        exit(EXIT_FAILURE);
-    }
+  if (!logger_list) {
+    fprintf(stderr, "Unable to open events log: %s\n", strerror(errno));
+    exit(EXIT_FAILURE);
+  }
 
-    signal(SIGALRM, caught_delay);
-    alarm(alarm_time);
+  signal(SIGALRM, caught_delay);
+  alarm(alarm_time);
 
-    StartBenchmarkTiming();
+  while (state.KeepRunning()) {
+    log_time ts(CLOCK_REALTIME);
 
-    for (int i = 0; i < iters; ++i) {
-        log_time ts(CLOCK_REALTIME);
+    LOG_FAILURE_RETRY(android_btWriteLog(0, EVENT_TYPE_LONG, &ts, sizeof(ts)));
 
-        LOG_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);
+      alarm(alarm_time);
 
-        for (;;) {
-            log_msg log_msg;
-            int ret = android_logger_list_read(logger_list, &log_msg);
-            alarm(alarm_time);
+      if (ret <= 0) {
+        state.SkipWithError("android_logger_list_read");
+        break;
+      }
+      if ((log_msg.entry.len != (4 + 1 + 8)) ||
+          (log_msg.id() != LOG_ID_EVENTS)) {
+        continue;
+      }
 
-            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();
 
-            char* eventData = log_msg.msg();
-
-            if (eventData[4] != EVENT_TYPE_LONG) {
-                continue;
-            }
-            log_time tx(eventData + 4 + 1);
-            if (ts != tx) {
-                if (0xDEADBEEFA55A5AA6ULL == caught_convert(eventData + 4 + 1)) {
-                    iters = i;
-                    break;
-                }
-                continue;
-            }
-
-            break;
+      if (!eventData || (eventData[4] != EVENT_TYPE_LONG)) {
+        continue;
+      }
+      log_time* tx = reinterpret_cast<log_time*>(eventData + 4 + 1);
+      if (ts != *tx) {
+        if (0xDEADBEEFA55A5AA6ULL == caught_convert(eventData + 4 + 1)) {
+          state.SkipWithError("signal");
+          break;
         }
+        continue;
+      }
+
+      break;
     }
+  }
+  state.PauseTiming();
 
-    signal(SIGALRM, SIG_DFL);
-    alarm(0);
+  signal(SIGALRM, SIG_DFL);
+  alarm(0);
 
-    StopBenchmarkTiming();
-
-    android_logger_list_free(logger_list);
+  android_logger_list_free(logger_list);
 }
 BENCHMARK(BM_log_delay);
 
 /*
  *	Measure the time it takes for __android_log_is_loggable.
  */
-static void BM_is_loggable(int iters) {
-    StartBenchmarkTiming();
+static void BM_is_loggable(benchmark::State& state) {
+  static const char logd[] = "logd";
 
-    for (int i = 0; i < iters; ++i) {
-        __android_log_is_loggable(ANDROID_LOG_WARN, "logd", ANDROID_LOG_VERBOSE);
-    }
-
-    StopBenchmarkTiming();
+  while (state.KeepRunning()) {
+    __android_log_is_loggable_len(ANDROID_LOG_WARN, logd, strlen(logd),
+                                  ANDROID_LOG_VERBOSE);
+  }
 }
 BENCHMARK(BM_is_loggable);
+
+/*
+ *	Measure the time it takes for __android_log_security.
+ */
+static void BM_security(benchmark::State& state) {
+  while (state.KeepRunning()) {
+    __android_log_security();
+  }
+}
+BENCHMARK(BM_security);
+
+// Keep maps around for multiple iterations
+static std::unordered_set<uint32_t> set;
+static EventTagMap* map;
+
+static bool prechargeEventMap() {
+  if (map) return true;
+
+  fprintf(stderr, "Precharge: start\n");
+
+  map = android_openEventTagMap(NULL);
+  for (uint32_t tag = 1; tag < USHRT_MAX; ++tag) {
+    size_t len;
+    if (android_lookupEventTag_len(map, &len, tag) == NULL) continue;
+    set.insert(tag);
+  }
+
+  fprintf(stderr, "Precharge: stop %zu\n", set.size());
+
+  return true;
+}
+
+/*
+ *	Measure the time it takes for android_lookupEventTag_len
+ */
+static void BM_lookupEventTag(benchmark::State& state) {
+  prechargeEventMap();
+
+  std::unordered_set<uint32_t>::const_iterator it = set.begin();
+
+  while (state.KeepRunning()) {
+    size_t len;
+    android_lookupEventTag_len(map, &len, (*it));
+    ++it;
+    if (it == set.end()) it = set.begin();
+  }
+}
+BENCHMARK(BM_lookupEventTag);
+
+/*
+ *	Measure the time it takes for android_lookupEventTag_len
+ */
+static uint32_t notTag = 1;
+
+static void BM_lookupEventTag_NOT(benchmark::State& state) {
+  prechargeEventMap();
+
+  while (set.find(notTag) != set.end()) {
+    ++notTag;
+    if (notTag >= USHRT_MAX) notTag = 1;
+  }
+
+  while (state.KeepRunning()) {
+    size_t len;
+    android_lookupEventTag_len(map, &len, notTag);
+  }
+
+  ++notTag;
+  if (notTag >= USHRT_MAX) notTag = 1;
+}
+BENCHMARK(BM_lookupEventTag_NOT);
+
+/*
+ *	Measure the time it takes for android_lookupEventFormat_len
+ */
+static void BM_lookupEventFormat(benchmark::State& state) {
+  prechargeEventMap();
+
+  std::unordered_set<uint32_t>::const_iterator it = set.begin();
+
+  while (state.KeepRunning()) {
+    size_t len;
+    android_lookupEventFormat_len(map, &len, (*it));
+    ++it;
+    if (it == set.end()) it = set.begin();
+  }
+}
+BENCHMARK(BM_lookupEventFormat);
+
+// Must be functionally identical to liblog internal SendLogdControlMessage()
+static void send_to_control(char* buf, size_t len) {
+  int sock =
+      socket_local_client("logd", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM | SOCK_CLOEXEC);
+  if (sock < 0) return;
+  size_t writeLen = strlen(buf) + 1;
+
+  ssize_t ret = TEMP_FAILURE_RETRY(write(sock, buf, writeLen));
+  if (ret <= 0) {
+    close(sock);
+    return;
+  }
+  while ((ret = read(sock, buf, len)) > 0) {
+    if (((size_t)ret == len) || (len < PAGE_SIZE)) {
+      break;
+    }
+    len -= ret;
+    buf += ret;
+
+    struct pollfd p = {.fd = sock, .events = POLLIN, .revents = 0 };
+
+    ret = poll(&p, 1, 20);
+    if ((ret <= 0) || !(p.revents & POLLIN)) {
+      break;
+    }
+  }
+  close(sock);
+}
+
+static void BM_lookupEventTagNum_logd_new(benchmark::State& state) {
+  fprintf(stderr,
+          "WARNING: "
+          "This test can cause logd to grow in size and hit DOS limiter\n");
+  // Make copies
+  static const char empty_event_log_tags[] = "# content owned by logd\n";
+  static const char dev_event_log_tags_path[] = "/dev/event-log-tags";
+  std::string dev_event_log_tags;
+  if (android::base::ReadFileToString(dev_event_log_tags_path,
+                                      &dev_event_log_tags) &&
+      (dev_event_log_tags.length() == 0)) {
+    dev_event_log_tags = empty_event_log_tags;
+  }
+  static const char data_event_log_tags_path[] =
+      "/data/misc/logd/event-log-tags";
+  std::string data_event_log_tags;
+  if (android::base::ReadFileToString(data_event_log_tags_path,
+                                      &data_event_log_tags) &&
+      (data_event_log_tags.length() == 0)) {
+    data_event_log_tags = empty_event_log_tags;
+  }
+
+  while (state.KeepRunning()) {
+    char buffer[256];
+    memset(buffer, 0, sizeof(buffer));
+    log_time now(CLOCK_MONOTONIC);
+    char name[64];
+    snprintf(name, sizeof(name), "a%" PRIu64, now.nsec());
+    snprintf(buffer, sizeof(buffer), "getEventTag name=%s format=\"(new|1)\"",
+             name);
+    state.ResumeTiming();
+    send_to_control(buffer, sizeof(buffer));
+    state.PauseTiming();
+  }
+
+  // Restore copies (logd still know about them, until crash or reboot)
+  if (dev_event_log_tags.length() &&
+      !android::base::WriteStringToFile(dev_event_log_tags,
+                                        dev_event_log_tags_path)) {
+    fprintf(stderr,
+            "WARNING: "
+            "failed to restore %s\n",
+            dev_event_log_tags_path);
+  }
+  if (data_event_log_tags.length() &&
+      !android::base::WriteStringToFile(data_event_log_tags,
+                                        data_event_log_tags_path)) {
+    fprintf(stderr,
+            "WARNING: "
+            "failed to restore %s\n",
+            data_event_log_tags_path);
+  }
+  fprintf(stderr,
+          "WARNING: "
+          "Restarting logd to make it forget what we just did\n");
+  system("stop logd ; start logd");
+}
+BENCHMARK(BM_lookupEventTagNum_logd_new);
+
+static void BM_lookupEventTagNum_logd_existing(benchmark::State& state) {
+  prechargeEventMap();
+
+  std::unordered_set<uint32_t>::const_iterator it = set.begin();
+
+  while (state.KeepRunning()) {
+    size_t len;
+    const char* name = android_lookupEventTag_len(map, &len, (*it));
+    std::string Name(name, len);
+    const char* format = android_lookupEventFormat_len(map, &len, (*it));
+    std::string Format(format, len);
+
+    char buffer[256];
+    snprintf(buffer, sizeof(buffer), "getEventTag name=%s format=\"%s\"",
+             Name.c_str(), Format.c_str());
+
+    state.ResumeTiming();
+    send_to_control(buffer, sizeof(buffer));
+    state.PauseTiming();
+    ++it;
+    if (it == set.end()) it = set.begin();
+  }
+}
+BENCHMARK(BM_lookupEventTagNum_logd_existing);
+
+static void BM_log_verbose_overhead(benchmark::State& state) {
+  std::string test_log_tag = "liblog_verbose_tag";
+  android::base::SetProperty("log.tag." + test_log_tag, "I");
+  for (auto _ : state) {
+    __android_log_print(ANDROID_LOG_VERBOSE, test_log_tag.c_str(), "%s test log message %d %d",
+                        "test test", 123, 456);
+  }
+  android::base::SetProperty("log.tag." + test_log_tag, "");
+}
+BENCHMARK(BM_log_verbose_overhead);
diff --git a/liblog/tests/liblog_default_tag.cpp b/liblog/tests/liblog_default_tag.cpp
new file mode 100644
index 0000000..31b7467
--- /dev/null
+++ b/liblog/tests/liblog_default_tag.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+// LOG_TAG must be unset for android-base's logging to use a default tag.
+#undef LOG_TAG
+
+#include <stdlib.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/scopeguard.h>
+#include <android/log.h>
+
+#include <gtest/gtest.h>
+
+#ifndef __ANDROID__
+static const char* getprogname() {
+  return program_invocation_short_name;
+}
+#endif
+
+TEST(liblog_default_tag, no_default_tag_libbase_write_first) {
+  using namespace android::base;
+  bool message_seen = false;
+  std::string expected_tag = "";
+  SetLogger([&](LogId, LogSeverity, const char* tag, const char*, unsigned int, const char*) {
+    message_seen = true;
+    EXPECT_EQ(expected_tag, tag);
+  });
+
+  expected_tag = getprogname();
+  LOG(WARNING) << "message";
+  EXPECT_TRUE(message_seen);
+  message_seen = false;
+
+  __android_log_buf_write(LOG_ID_MAIN, ANDROID_LOG_WARN, nullptr, "message");
+  EXPECT_TRUE(message_seen);
+}
+
+TEST(liblog_default_tag, no_default_tag_liblog_write_first) {
+  using namespace android::base;
+  bool message_seen = false;
+  std::string expected_tag = "";
+  SetLogger([&](LogId, LogSeverity, const char* tag, const char*, unsigned int, const char*) {
+    message_seen = true;
+    EXPECT_EQ(expected_tag, tag);
+  });
+
+  expected_tag = getprogname();
+  __android_log_buf_write(LOG_ID_MAIN, ANDROID_LOG_WARN, nullptr, "message");
+  EXPECT_TRUE(message_seen);
+  message_seen = false;
+
+  LOG(WARNING) << "message";
+  EXPECT_TRUE(message_seen);
+}
+
+TEST(liblog_default_tag, libbase_sets_default_tag) {
+  using namespace android::base;
+  bool message_seen = false;
+  std::string expected_tag = "libbase_test_tag";
+  SetLogger([&](LogId, LogSeverity, const char* tag, const char*, unsigned int, const char*) {
+    message_seen = true;
+    EXPECT_EQ(expected_tag, tag);
+  });
+  SetDefaultTag(expected_tag);
+
+  __android_log_buf_write(LOG_ID_MAIN, ANDROID_LOG_WARN, nullptr, "message");
+  EXPECT_TRUE(message_seen);
+  message_seen = false;
+
+  LOG(WARNING) << "message";
+  EXPECT_TRUE(message_seen);
+}
+
+TEST(liblog_default_tag, liblog_sets_default_tag) {
+  using namespace android::base;
+  bool message_seen = false;
+  std::string expected_tag = "liblog_test_tag";
+  SetLogger([&](LogId, LogSeverity, const char* tag, const char*, unsigned int, const char*) {
+    message_seen = true;
+    EXPECT_EQ(expected_tag, tag);
+  });
+  __android_log_set_default_tag(expected_tag.c_str());
+
+  __android_log_buf_write(LOG_ID_MAIN, ANDROID_LOG_WARN, nullptr, "message");
+  EXPECT_TRUE(message_seen);
+  message_seen = false;
+
+  LOG(WARNING) << "message";
+  EXPECT_TRUE(message_seen);
+}
+
+TEST(liblog_default_tag, default_tag_plus_log_severity) {
+#ifdef __ANDROID__
+  using namespace android::base;
+  bool message_seen = false;
+  std::string expected_tag = "liblog_test_tag";
+  SetLogger([&](LogId, LogSeverity, const char* tag, const char*, unsigned int, const char*) {
+    message_seen = true;
+    EXPECT_EQ(expected_tag, tag);
+  });
+  __android_log_set_default_tag(expected_tag.c_str());
+
+  auto log_tag_property = "log.tag." + expected_tag;
+  SetProperty(log_tag_property, "V");
+  auto reset_tag_property_guard = make_scope_guard([=] { SetProperty(log_tag_property, ""); });
+
+  __android_log_buf_write(LOG_ID_MAIN, ANDROID_LOG_VERBOSE, nullptr, "message");
+  EXPECT_TRUE(message_seen);
+  message_seen = false;
+
+  LOG(VERBOSE) << "message";
+  EXPECT_TRUE(message_seen);
+#else
+  GTEST_SKIP() << "No log tag properties on host";
+#endif
+}
+
+TEST(liblog_default_tag, generated_default_tag_plus_log_severity) {
+#ifdef __ANDROID__
+  using namespace android::base;
+  bool message_seen = false;
+  std::string expected_tag = getprogname();
+  SetLogger([&](LogId, LogSeverity, const char* tag, const char*, unsigned int, const char*) {
+    message_seen = true;
+    EXPECT_EQ(expected_tag, tag);
+  });
+
+  // Even without any calls to SetDefaultTag(), the first message that attempts to log, will
+  // generate a default tag from getprogname() and check log.tag.<default tag> for loggability. This
+  // case checks that we can log a Verbose message when log.tag.<getprogname()> is set to 'V'.
+  auto log_tag_property = "log.tag." + expected_tag;
+  SetProperty(log_tag_property, "V");
+  auto reset_tag_property_guard = make_scope_guard([=] { SetProperty(log_tag_property, ""); });
+
+  __android_log_buf_write(LOG_ID_MAIN, ANDROID_LOG_VERBOSE, nullptr, "message");
+  EXPECT_TRUE(message_seen);
+  message_seen = false;
+
+  LOG(VERBOSE) << "message";
+  EXPECT_TRUE(message_seen);
+#else
+  GTEST_SKIP() << "No log tag properties on host";
+#endif
+}
\ No newline at end of file
diff --git a/liblog/tests/liblog_global_state.cpp b/liblog/tests/liblog_global_state.cpp
new file mode 100644
index 0000000..1d7ff9f
--- /dev/null
+++ b/liblog/tests/liblog_global_state.cpp
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2020 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 LOG_TAG "global_state_test_tag"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android/log.h>
+
+#include <gtest/gtest.h>
+
+TEST(liblog_global_state, libbase_logs_with_libbase_SetLogger) {
+  using namespace android::base;
+  bool message_seen = false;
+  LogSeverity expected_severity = WARNING;
+  std::string expected_file = Basename(__FILE__);
+  unsigned int expected_line;
+  std::string expected_message = "libbase test message";
+
+  auto LoggerFunction = [&](LogId log_id, LogSeverity severity, const char* tag, const char* file,
+                            unsigned int line, const char* message) {
+    message_seen = true;
+    EXPECT_EQ(DEFAULT, log_id);
+    EXPECT_EQ(expected_severity, severity);
+    EXPECT_STREQ(LOG_TAG, tag);
+    EXPECT_EQ(expected_file, file);
+    EXPECT_EQ(expected_line, line);
+    EXPECT_EQ(expected_message, message);
+  };
+
+  SetLogger(LoggerFunction);
+
+  expected_line = __LINE__ + 1;
+  LOG(expected_severity) << expected_message;
+  EXPECT_TRUE(message_seen);
+}
+
+TEST(liblog_global_state, libbase_logs_with_liblog_set_logger) {
+  using namespace android::base;
+  // These must be static since they're used by the liblog logger function, which only accepts
+  // lambdas without captures.  The items used by the libbase logger are explicitly not static, to
+  // ensure that lambdas with captures do work there.
+  static bool message_seen = false;
+  static std::string expected_file = Basename(__FILE__);
+  static unsigned int expected_line;
+  static std::string expected_message = "libbase test message";
+
+  auto liblog_logger_function = [](const struct __android_log_message* log_message) {
+    message_seen = true;
+    EXPECT_EQ(sizeof(__android_log_message), log_message->struct_size);
+    EXPECT_EQ(LOG_ID_DEFAULT, log_message->buffer_id);
+    EXPECT_EQ(ANDROID_LOG_WARN, log_message->priority);
+    EXPECT_STREQ(LOG_TAG, log_message->tag);
+    EXPECT_EQ(expected_file, log_message->file);
+    EXPECT_EQ(expected_line, log_message->line);
+    EXPECT_EQ(expected_message, log_message->message);
+  };
+
+  __android_log_set_logger(liblog_logger_function);
+
+  expected_line = __LINE__ + 1;
+  LOG(WARNING) << expected_message;
+  EXPECT_TRUE(message_seen);
+}
+
+TEST(liblog_global_state, liblog_logs_with_libbase_SetLogger) {
+  using namespace android::base;
+  bool message_seen = false;
+  std::string expected_message = "libbase test message";
+
+  auto LoggerFunction = [&](LogId log_id, LogSeverity severity, const char* tag, const char* file,
+                            unsigned int line, const char* message) {
+    message_seen = true;
+    EXPECT_EQ(MAIN, log_id);
+    EXPECT_EQ(WARNING, severity);
+    EXPECT_STREQ(LOG_TAG, tag);
+    EXPECT_EQ(nullptr, file);
+    EXPECT_EQ(0U, line);
+    EXPECT_EQ(expected_message, message);
+  };
+
+  SetLogger(LoggerFunction);
+
+  __android_log_buf_write(LOG_ID_MAIN, ANDROID_LOG_WARN, LOG_TAG, expected_message.c_str());
+  EXPECT_TRUE(message_seen);
+  message_seen = false;
+}
+
+TEST(liblog_global_state, liblog_logs_with_liblog_set_logger) {
+  using namespace android::base;
+  // These must be static since they're used by the liblog logger function, which only accepts
+  // lambdas without captures.  The items used by the libbase logger are explicitly not static, to
+  // ensure that lambdas with captures do work there.
+  static bool message_seen = false;
+  static int expected_buffer_id = LOG_ID_MAIN;
+  static int expected_priority = ANDROID_LOG_WARN;
+  static std::string expected_message = "libbase test message";
+
+  auto liblog_logger_function = [](const struct __android_log_message* log_message) {
+    message_seen = true;
+    EXPECT_EQ(sizeof(__android_log_message), log_message->struct_size);
+    EXPECT_EQ(expected_buffer_id, log_message->buffer_id);
+    EXPECT_EQ(expected_priority, log_message->priority);
+    EXPECT_STREQ(LOG_TAG, log_message->tag);
+    EXPECT_STREQ(nullptr, log_message->file);
+    EXPECT_EQ(0U, log_message->line);
+    EXPECT_EQ(expected_message, log_message->message);
+  };
+
+  __android_log_set_logger(liblog_logger_function);
+
+  __android_log_buf_write(expected_buffer_id, expected_priority, LOG_TAG, expected_message.c_str());
+  EXPECT_TRUE(message_seen);
+}
+
+TEST(liblog_global_state, SetAborter_with_liblog) {
+  using namespace android::base;
+
+  std::string expected_message = "libbase test message";
+  static bool message_seen = false;
+  auto aborter_function = [&](const char* message) {
+    message_seen = true;
+    EXPECT_EQ(expected_message, message);
+  };
+
+  SetAborter(aborter_function);
+  LOG(FATAL) << expected_message;
+  EXPECT_TRUE(message_seen);
+  message_seen = false;
+
+  static std::string expected_message_static = "libbase test message";
+  auto liblog_aborter_function = [](const char* message) {
+    message_seen = true;
+    EXPECT_EQ(expected_message_static, message);
+  };
+  __android_log_set_aborter(liblog_aborter_function);
+  LOG(FATAL) << expected_message_static;
+  EXPECT_TRUE(message_seen);
+  message_seen = false;
+}
+
+static std::string UniqueLogTag() {
+  std::string tag = LOG_TAG;
+  tag += "-" + std::to_string(getpid());
+  return tag;
+}
+
+TEST(liblog_global_state, is_loggable_both_default) {
+  auto tag = UniqueLogTag();
+  EXPECT_EQ(0, __android_log_is_loggable(ANDROID_LOG_DEBUG, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_INFO, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_WARN, tag.c_str(), ANDROID_LOG_INFO));
+}
+
+TEST(liblog_global_state, is_loggable_minimum_log_priority_only) {
+  auto tag = UniqueLogTag();
+  EXPECT_EQ(0, __android_log_is_loggable(ANDROID_LOG_DEBUG, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_INFO, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_WARN, tag.c_str(), ANDROID_LOG_INFO));
+
+  EXPECT_EQ(ANDROID_LOG_DEFAULT, __android_log_set_minimum_priority(ANDROID_LOG_DEBUG));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_DEBUG, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_INFO, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_WARN, tag.c_str(), ANDROID_LOG_INFO));
+
+  EXPECT_EQ(ANDROID_LOG_DEBUG, __android_log_set_minimum_priority(ANDROID_LOG_WARN));
+  EXPECT_EQ(0, __android_log_is_loggable(ANDROID_LOG_DEBUG, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(0, __android_log_is_loggable(ANDROID_LOG_INFO, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_WARN, tag.c_str(), ANDROID_LOG_INFO));
+
+  EXPECT_EQ(android::base::WARNING, android::base::SetMinimumLogSeverity(android::base::DEBUG));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_DEBUG, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_INFO, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_WARN, tag.c_str(), ANDROID_LOG_INFO));
+
+  EXPECT_EQ(android::base::DEBUG, android::base::SetMinimumLogSeverity(android::base::WARNING));
+  EXPECT_EQ(0, __android_log_is_loggable(ANDROID_LOG_DEBUG, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(0, __android_log_is_loggable(ANDROID_LOG_INFO, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_WARN, tag.c_str(), ANDROID_LOG_INFO));
+}
+
+TEST(liblog_global_state, is_loggable_tag_log_priority_only) {
+#ifdef __ANDROID__
+  auto tag = UniqueLogTag();
+  EXPECT_EQ(0, __android_log_is_loggable(ANDROID_LOG_DEBUG, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_INFO, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_WARN, tag.c_str(), ANDROID_LOG_INFO));
+
+  auto log_tag_property = std::string("log.tag.") + tag;
+  ASSERT_TRUE(android::base::SetProperty(log_tag_property, "d"));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_DEBUG, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_INFO, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_WARN, tag.c_str(), ANDROID_LOG_INFO));
+
+  ASSERT_TRUE(android::base::SetProperty(log_tag_property, "w"));
+  EXPECT_EQ(0, __android_log_is_loggable(ANDROID_LOG_DEBUG, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(0, __android_log_is_loggable(ANDROID_LOG_INFO, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_WARN, tag.c_str(), ANDROID_LOG_INFO));
+
+  ASSERT_TRUE(android::base::SetProperty(log_tag_property, ""));
+#else
+  GTEST_SKIP() << "No log tag properties on host";
+#endif
+}
+
+TEST(liblog_global_state, is_loggable_both_set) {
+#ifdef __ANDROID__
+  auto tag = UniqueLogTag();
+  EXPECT_EQ(0, __android_log_is_loggable(ANDROID_LOG_DEBUG, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_INFO, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_WARN, tag.c_str(), ANDROID_LOG_INFO));
+
+  // When both a tag and a minimum priority are set, we use the lower value of the two.
+
+  // tag = warning, minimum_priority = debug, expect 'debug'
+  auto log_tag_property = std::string("log.tag.") + tag;
+  ASSERT_TRUE(android::base::SetProperty(log_tag_property, "w"));
+  EXPECT_EQ(ANDROID_LOG_DEFAULT, __android_log_set_minimum_priority(ANDROID_LOG_DEBUG));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_DEBUG, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_INFO, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_WARN, tag.c_str(), ANDROID_LOG_INFO));
+
+  // tag = warning, minimum_priority = warning, expect 'warning'
+  EXPECT_EQ(ANDROID_LOG_DEBUG, __android_log_set_minimum_priority(ANDROID_LOG_WARN));
+  EXPECT_EQ(0, __android_log_is_loggable(ANDROID_LOG_DEBUG, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(0, __android_log_is_loggable(ANDROID_LOG_INFO, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_WARN, tag.c_str(), ANDROID_LOG_INFO));
+
+  // tag = debug, minimum_priority = warning, expect 'debug'
+  ASSERT_TRUE(android::base::SetProperty(log_tag_property, "d"));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_DEBUG, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_INFO, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_WARN, tag.c_str(), ANDROID_LOG_INFO));
+
+  // tag = debug, minimum_priority = debug, expect 'debug'
+  EXPECT_EQ(ANDROID_LOG_WARN, __android_log_set_minimum_priority(ANDROID_LOG_DEBUG));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_DEBUG, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_INFO, tag.c_str(), ANDROID_LOG_INFO));
+  EXPECT_EQ(1, __android_log_is_loggable(ANDROID_LOG_WARN, tag.c_str(), ANDROID_LOG_INFO));
+
+  ASSERT_TRUE(android::base::SetProperty(log_tag_property, ""));
+#else
+  GTEST_SKIP() << "No log tag properties on host";
+#endif
+}
diff --git a/liblog/tests/liblog_host_test.cpp b/liblog/tests/liblog_host_test.cpp
new file mode 100644
index 0000000..ec186d4
--- /dev/null
+++ b/liblog/tests/liblog_host_test.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2019 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 <log/log.h>
+#include <private/android_logger.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <regex>
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <android-base/stringprintf.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+
+using android::base::InitLogging;
+using android::base::StderrLogger;
+using android::base::StringPrintf;
+
+static std::string MakeLogPattern(int priority, const char* tag, const char* message) {
+  static const char log_characters[] = "XXVDIWEF";
+  static_assert(arraysize(log_characters) - 1 == ANDROID_LOG_SILENT,
+                "Mismatch in size of log_characters and values in android_LogPriority");
+  priority = priority > ANDROID_LOG_SILENT ? ANDROID_LOG_FATAL : priority;
+  char log_char = log_characters[priority];
+
+  return StringPrintf("%s %c \\d+-\\d+ \\d+:\\d+:\\d+ \\s*\\d+ \\s*\\d+ %s", tag, log_char,
+                      message);
+}
+
+static void CheckMessage(bool expected, const std::string& output, int priority, const char* tag,
+                         const char* message) {
+  std::regex message_regex(MakeLogPattern(priority, tag, message));
+  EXPECT_EQ(expected, std::regex_search(output, message_regex)) << message;
+}
+
+static void GenerateLogContent() {
+  __android_log_buf_print(LOG_ID_MAIN, ANDROID_LOG_VERBOSE, "tag", "verbose main");
+  __android_log_buf_print(LOG_ID_MAIN, ANDROID_LOG_INFO, "tag", "info main");
+  __android_log_buf_print(LOG_ID_MAIN, ANDROID_LOG_ERROR, "tag", "error main");
+
+  __android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_VERBOSE, "tag", "verbose radio");
+  __android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO, "tag", "info radio");
+  __android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_ERROR, "tag", "error radio");
+
+  __android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, "tag", "verbose system");
+  __android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, "tag", "info system");
+  __android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, "tag", "error system");
+
+  __android_log_buf_print(LOG_ID_CRASH, ANDROID_LOG_VERBOSE, "tag", "verbose crash");
+  __android_log_buf_print(LOG_ID_CRASH, ANDROID_LOG_INFO, "tag", "info crash");
+  __android_log_buf_print(LOG_ID_CRASH, ANDROID_LOG_ERROR, "tag", "error crash");
+}
+
+std::string GetPidString() {
+  int pid = getpid();
+  return StringPrintf("%5d", pid);
+}
+
+TEST(liblog, default_write) {
+  CapturedStderr captured_stderr;
+  InitLogging(nullptr, StderrLogger);
+
+  GenerateLogContent();
+
+  CheckMessage(false, captured_stderr.str(), ANDROID_LOG_VERBOSE, "tag", "verbose main");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_INFO, "tag", "info main");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_ERROR, "tag", "error main");
+
+  CheckMessage(false, captured_stderr.str(), ANDROID_LOG_VERBOSE, "tag", "verbose radio");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_INFO, "tag", "info radio");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_ERROR, "tag", "error radio");
+
+  CheckMessage(false, captured_stderr.str(), ANDROID_LOG_VERBOSE, "tag", "verbose system");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_INFO, "tag", "info system");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_ERROR, "tag", "error system");
+
+  CheckMessage(false, captured_stderr.str(), ANDROID_LOG_VERBOSE, "tag", "verbose crash");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_INFO, "tag", "info crash");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_ERROR, "tag", "error crash");
+}
+
+TEST(liblog, verbose_write) {
+  setenv("ANDROID_LOG_TAGS", "*:v", true);
+  CapturedStderr captured_stderr;
+  InitLogging(nullptr, StderrLogger);
+
+  GenerateLogContent();
+
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_VERBOSE, "tag", "verbose main");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_INFO, "tag", "info main");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_ERROR, "tag", "error main");
+
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_VERBOSE, "tag", "verbose radio");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_INFO, "tag", "info radio");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_ERROR, "tag", "error radio");
+
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_VERBOSE, "tag", "verbose system");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_INFO, "tag", "info system");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_ERROR, "tag", "error system");
+
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_VERBOSE, "tag", "verbose crash");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_INFO, "tag", "info crash");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_ERROR, "tag", "error crash");
+}
+
+TEST(liblog, error_write) {
+  setenv("ANDROID_LOG_TAGS", "*:e", true);
+  CapturedStderr captured_stderr;
+  InitLogging(nullptr, StderrLogger);
+
+  GenerateLogContent();
+
+  CheckMessage(false, captured_stderr.str(), ANDROID_LOG_VERBOSE, "tag", "verbose main");
+  CheckMessage(false, captured_stderr.str(), ANDROID_LOG_INFO, "tag", "info main");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_ERROR, "tag", "error main");
+
+  CheckMessage(false, captured_stderr.str(), ANDROID_LOG_VERBOSE, "tag", "verbose radio");
+  CheckMessage(false, captured_stderr.str(), ANDROID_LOG_INFO, "tag", "info radio");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_ERROR, "tag", "error radio");
+
+  CheckMessage(false, captured_stderr.str(), ANDROID_LOG_VERBOSE, "tag", "verbose system");
+  CheckMessage(false, captured_stderr.str(), ANDROID_LOG_INFO, "tag", "info system");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_ERROR, "tag", "error system");
+
+  CheckMessage(false, captured_stderr.str(), ANDROID_LOG_VERBOSE, "tag", "verbose crash");
+  CheckMessage(false, captured_stderr.str(), ANDROID_LOG_INFO, "tag", "info crash");
+  CheckMessage(true, captured_stderr.str(), ANDROID_LOG_ERROR, "tag", "error crash");
+}
+
+TEST(liblog, kernel_no_write) {
+  CapturedStderr captured_stderr;
+  InitLogging(nullptr, StderrLogger);
+  __android_log_buf_print(LOG_ID_KERNEL, ANDROID_LOG_ERROR, "tag", "kernel error");
+  EXPECT_EQ("", captured_stderr.str());
+}
+
+TEST(liblog, binary_no_write) {
+  CapturedStderr captured_stderr;
+  InitLogging(nullptr, StderrLogger);
+  __android_log_buf_print(LOG_ID_EVENTS, ANDROID_LOG_ERROR, "tag", "error events");
+  __android_log_buf_print(LOG_ID_STATS, ANDROID_LOG_ERROR, "tag", "error stats");
+  __android_log_buf_print(LOG_ID_SECURITY, ANDROID_LOG_ERROR, "tag", "error security");
+
+  __android_log_bswrite(0x12, "events");
+  __android_log_stats_bwrite(0x34, "stats", strlen("stats"));
+  __android_log_security_bswrite(0x56, "security");
+
+  EXPECT_EQ("", captured_stderr.str());
+}
diff --git a/liblog/tests/liblog_test.cpp b/liblog/tests/liblog_test.cpp
index abe0239..c49d87b 100644
--- a/liblog/tests/liblog_test.cpp
+++ b/liblog/tests/liblog_test.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013-2014 The Android Open Source Project
+ * Copyright (C) 2013-2016 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.
@@ -14,298 +14,798 @@
  * limitations under the License.
  */
 
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
+#include <pthread.h>
+#include <semaphore.h>
 #include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
 
+#include <memory>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/macros.h>
+#include <android-base/scopeguard.h>
+#include <android-base/stringprintf.h>
+#ifdef __ANDROID__  // includes sys/properties.h which does not exist outside
 #include <cutils/properties.h>
+#endif
 #include <gtest/gtest.h>
-#include <log/log.h>
-#include <log/logger.h>
+#include <log/log_event_list.h>
+#include <log/log_properties.h>
 #include <log/log_read.h>
 #include <log/logprint.h>
+#include <private/android_filesystem_config.h>
+#include <private/android_logger.h>
+
+using android::base::make_scope_guard;
+
+// #define ENABLE_FLAKY_TESTS
 
 // enhanced version of LOG_FAILURE_RETRY to add support for EAGAIN and
 // non-syscall libs. Since we are only using this in the emergency of
 // a signal to stuff a terminating code into the logs, we will spin rather
 // than try a usleep.
-#define LOG_FAILURE_RETRY(exp) ({  \
-    typeof (exp) _rc;              \
-    do {                           \
-        _rc = (exp);               \
-    } while (((_rc == -1)          \
-           && ((errno == EINTR)    \
-            || (errno == EAGAIN))) \
-          || (_rc == -EINTR)       \
-          || (_rc == -EAGAIN));    \
-    _rc; })
+#define LOG_FAILURE_RETRY(exp)                                           \
+  ({                                                                     \
+    typeof(exp) _rc;                                                     \
+    do {                                                                 \
+      _rc = (exp);                                                       \
+    } while (((_rc == -1) && ((errno == EINTR) || (errno == EAGAIN))) || \
+             (_rc == -EINTR) || (_rc == -EAGAIN));                       \
+    _rc;                                                                 \
+  })
 
-TEST(liblog, __android_log_buf_print) {
-    EXPECT_LT(0, __android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO,
-                                         "TEST__android_log_buf_print",
-                                         "radio"));
-    usleep(1000);
-    EXPECT_LT(0, __android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO,
-                                         "TEST__android_log_buf_print",
-                                         "system"));
-    usleep(1000);
-    EXPECT_LT(0, __android_log_buf_print(LOG_ID_MAIN, ANDROID_LOG_INFO,
-                                         "TEST__android_log_buf_print",
-                                         "main"));
-    usleep(1000);
-}
+// std::unique_ptr doesn't let you provide a pointer to a deleter (android_logger_list_close()) if
+// the type (struct logger_list) is an incomplete type, so we create ListCloser instead.
+struct ListCloser {
+  void operator()(struct logger_list* list) { android_logger_list_close(list); }
+};
 
-TEST(liblog, __android_log_buf_write) {
-    EXPECT_LT(0, __android_log_buf_write(LOG_ID_RADIO, ANDROID_LOG_INFO,
-                                         "TEST__android_log_buf_write",
-                                         "radio"));
-    usleep(1000);
-    EXPECT_LT(0, __android_log_buf_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO,
-                                         "TEST__android_log_buf_write",
-                                         "system"));
-    usleep(1000);
-    EXPECT_LT(0, __android_log_buf_write(LOG_ID_MAIN, ANDROID_LOG_INFO,
-                                         "TEST__android_log_buf_write",
-                                         "main"));
-    usleep(1000);
+// This function is meant to be used for most log tests, it does the following:
+// 1) Open the log_buffer with a blocking reader
+// 2) Write the messages via write_messages
+// 3) Set an alarm for 2 seconds as a timeout
+// 4) Read until check_message returns true, which should be used to indicate the target message
+//    is found
+// 5) Open log_buffer with a non_blocking reader and dump all messages
+// 6) Count the number of times check_messages returns true for these messages and assert it's
+//    only 1.
+template <typename FWrite, typename FCheck>
+static void RunLogTests(log_id_t log_buffer, FWrite write_messages, FCheck check_message) {
+  pid_t pid = getpid();
+
+  auto logger_list = std::unique_ptr<struct logger_list, ListCloser>{
+      android_logger_list_open(log_buffer, 0, 1000, pid)};
+  ASSERT_TRUE(logger_list);
+
+  write_messages();
+
+  alarm(2);
+  auto alarm_guard = android::base::make_scope_guard([] { alarm(0); });
+  bool found = false;
+  while (!found) {
+    log_msg log_msg;
+    ASSERT_GT(android_logger_list_read(logger_list.get(), &log_msg), 0);
+
+    ASSERT_EQ(log_buffer, log_msg.id());
+    ASSERT_EQ(pid, log_msg.entry.pid);
+
+    ASSERT_NE(nullptr, log_msg.msg());
+
+    check_message(log_msg, &found);
+  }
+
+  auto logger_list_non_block = std::unique_ptr<struct logger_list, ListCloser>{
+      android_logger_list_open(log_buffer, ANDROID_LOG_NONBLOCK, 1000, pid)};
+  ASSERT_TRUE(logger_list_non_block);
+
+  size_t count = 0;
+  while (true) {
+    log_msg log_msg;
+    auto ret = android_logger_list_read(logger_list_non_block.get(), &log_msg);
+    if (ret == -EAGAIN) {
+      break;
+    }
+    ASSERT_GT(ret, 0);
+
+    ASSERT_EQ(log_buffer, log_msg.id());
+    ASSERT_EQ(pid, log_msg.entry.pid);
+
+    ASSERT_NE(nullptr, log_msg.msg());
+
+    found = false;
+    check_message(log_msg, &found);
+    if (found) {
+      ++count;
+    }
+  }
+
+  EXPECT_EQ(1U, count);
 }
 
 TEST(liblog, __android_log_btwrite) {
-    int intBuf = 0xDEADBEEF;
-    EXPECT_LT(0, __android_log_btwrite(0,
-                                      EVENT_TYPE_INT,
-                                      &intBuf, sizeof(intBuf)));
-    long long longBuf = 0xDEADBEEFA55A5AA5;
-    EXPECT_LT(0, __android_log_btwrite(0,
-                                      EVENT_TYPE_LONG,
-                                      &longBuf, sizeof(longBuf)));
-    usleep(1000);
-    char Buf[] = "\20\0\0\0DeAdBeEfA55a5aA5";
-    EXPECT_LT(0, __android_log_btwrite(0,
-                                      EVENT_TYPE_STRING,
-                                      Buf, sizeof(Buf) - 1));
-    usleep(1000);
+  int intBuf = 0xDEADBEEF;
+  EXPECT_LT(0,
+            __android_log_btwrite(0, EVENT_TYPE_INT, &intBuf, sizeof(intBuf)));
+  long long longBuf = 0xDEADBEEFA55A5AA5;
+  EXPECT_LT(
+      0, __android_log_btwrite(0, EVENT_TYPE_LONG, &longBuf, sizeof(longBuf)));
+  char Buf[] = "\20\0\0\0DeAdBeEfA55a5aA5";
+  EXPECT_LT(0,
+            __android_log_btwrite(0, EVENT_TYPE_STRING, Buf, sizeof(Buf) - 1));
 }
 
-static void* ConcurrentPrintFn(void *arg) {
-    int ret = __android_log_buf_print(LOG_ID_MAIN, ANDROID_LOG_INFO,
-                                  "TEST__android_log_print", "Concurrent %" PRIuPTR,
-                                  reinterpret_cast<uintptr_t>(arg));
-    return reinterpret_cast<void*>(ret);
+#if defined(__ANDROID__)
+static std::string popenToString(const std::string& command) {
+  std::string ret;
+
+  FILE* fp = popen(command.c_str(), "re");
+  if (fp) {
+    if (!android::base::ReadFdToString(fileno(fp), &ret)) ret = "";
+    pclose(fp);
+  }
+  return ret;
 }
 
-#define NUM_CONCURRENT 64
-#define _concurrent_name(a,n) a##__concurrent##n
-#define concurrent_name(a,n) _concurrent_name(a,n)
+static bool isPmsgActive() {
+  pid_t pid = getpid();
 
-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));
-        int this_result = reinterpret_cast<uintptr_t>(result);
-        if ((0 == ret) && (0 != this_result)) {
-            ret = this_result;
-        }
-    }
-    ASSERT_LT(0, ret);
+  std::string myPidFds =
+      popenToString(android::base::StringPrintf("ls -l /proc/%d/fd", pid));
+  if (myPidFds.length() == 0) return true;  // guess it is?
+
+  return std::string::npos != myPidFds.find(" -> /dev/pmsg0");
 }
 
+static bool isLogdwActive() {
+  std::string logdwSignature =
+      popenToString("grep -a /dev/socket/logdw /proc/net/unix");
+  size_t beginning = logdwSignature.find(' ');
+  if (beginning == std::string::npos) return true;
+  beginning = logdwSignature.find(' ', beginning + 1);
+  if (beginning == std::string::npos) return true;
+  size_t end = logdwSignature.find(' ', beginning + 1);
+  if (end == std::string::npos) return true;
+  end = logdwSignature.find(' ', end + 1);
+  if (end == std::string::npos) return true;
+  end = logdwSignature.find(' ', end + 1);
+  if (end == std::string::npos) return true;
+  end = logdwSignature.find(' ', end + 1);
+  if (end == std::string::npos) return true;
+  std::string allLogdwEndpoints = popenToString(
+      "grep -a ' 00000002" + logdwSignature.substr(beginning, end - beginning) +
+      " ' /proc/net/unix | " +
+      "sed -n 's/.* \\([0-9][0-9]*\\)$/ -> socket:[\\1]/p'");
+  if (allLogdwEndpoints.length() == 0) return true;
+
+  // NB: allLogdwEndpoints has some false positives in it, but those
+  // strangers do not overlap with the simplistic activities inside this
+  // test suite.
+
+  pid_t pid = getpid();
+
+  std::string myPidFds =
+      popenToString(android::base::StringPrintf("ls -l /proc/%d/fd", pid));
+  if (myPidFds.length() == 0) return true;
+
+  // NB: fgrep with multiple strings is broken in Android
+  for (beginning = 0;
+       (end = allLogdwEndpoints.find('\n', beginning)) != std::string::npos;
+       beginning = end + 1) {
+    if (myPidFds.find(allLogdwEndpoints.substr(beginning, end - beginning)) !=
+        std::string::npos)
+      return true;
+  }
+  return false;
+}
+
+static bool tested__android_log_close;
+#endif
+
 TEST(liblog, __android_log_btwrite__android_logger_list_read) {
-    struct logger_list *logger_list;
+#ifdef __ANDROID__
+  log_time ts(CLOCK_MONOTONIC);
+  log_time ts1(ts);
 
-    pid_t pid = getpid();
+  bool has_pstore = access("/dev/pmsg0", W_OK) == 0;
 
-    ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(
-        LOG_ID_EVENTS, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 1000, pid)));
-
-    log_time ts(CLOCK_MONOTONIC);
-
-    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 tx(eventData + 4 + 1);
-        if (ts == tx) {
-            ++count;
-        }
+  auto write_function = [&] {
+    EXPECT_LT(0, __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)));
+    // Check that we can close and reopen the logger
+    bool logdwActiveAfter__android_log_btwrite;
+    if (getuid() == AID_ROOT) {
+      tested__android_log_close = true;
+      if (has_pstore) {
+        bool pmsgActiveAfter__android_log_btwrite = isPmsgActive();
+        EXPECT_TRUE(pmsgActiveAfter__android_log_btwrite);
+      }
+      logdwActiveAfter__android_log_btwrite = isLogdwActive();
+      EXPECT_TRUE(logdwActiveAfter__android_log_btwrite);
+    } else if (!tested__android_log_close) {
+      fprintf(stderr, "WARNING: can not test __android_log_close()\n");
+    }
+    __android_log_close();
+    if (getuid() == AID_ROOT) {
+      if (has_pstore) {
+        bool pmsgActiveAfter__android_log_close = isPmsgActive();
+        EXPECT_FALSE(pmsgActiveAfter__android_log_close);
+      }
+      bool logdwActiveAfter__android_log_close = isLogdwActive();
+      EXPECT_FALSE(logdwActiveAfter__android_log_close);
     }
 
-    EXPECT_EQ(1, count);
+    ts1 = log_time(CLOCK_MONOTONIC);
+    EXPECT_LT(0, __android_log_btwrite(0, EVENT_TYPE_LONG, &ts1, sizeof(ts1)));
+    if (getuid() == AID_ROOT) {
+      if (has_pstore) {
+        bool pmsgActiveAfter__android_log_btwrite = isPmsgActive();
+        EXPECT_TRUE(pmsgActiveAfter__android_log_btwrite);
+      }
+      logdwActiveAfter__android_log_btwrite = isLogdwActive();
+      EXPECT_TRUE(logdwActiveAfter__android_log_btwrite);
+    }
+  };
 
-    android_logger_list_close(logger_list);
+  int count = 0;
+  int second_count = 0;
+
+  auto check_function = [&](log_msg log_msg, bool* found) {
+    if ((log_msg.entry.len != sizeof(android_log_event_long_t)) ||
+        (log_msg.id() != LOG_ID_EVENTS)) {
+      return;
+    }
+
+    android_log_event_long_t* eventData;
+    eventData = reinterpret_cast<android_log_event_long_t*>(log_msg.msg());
+
+    if (!eventData || (eventData->payload.type != EVENT_TYPE_LONG)) {
+      return;
+    }
+
+    log_time* tx = reinterpret_cast<log_time*>(&eventData->payload.data);
+    if (ts == *tx) {
+      ++count;
+    } else if (ts1 == *tx) {
+      ++second_count;
+    }
+
+    if (count == 1 && second_count == 1) {
+      count = 0;
+      second_count = 0;
+      *found = true;
+    }
+  };
+
+  RunLogTests(LOG_ID_EVENTS, write_function, check_function);
+
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
 }
 
-static unsigned signaled;
-log_time signal_time;
+TEST(liblog, __android_log_write__android_logger_list_read) {
+#ifdef __ANDROID__
+  pid_t pid = getpid();
 
-static void caught_blocking(int /*signum*/)
-{
-    unsigned long long v = 0xDEADBEEFA55A0000ULL;
+  struct timespec ts;
+  clock_gettime(CLOCK_MONOTONIC, &ts);
+  std::string buf = android::base::StringPrintf("pid=%u ts=%ld.%09ld", pid, ts.tv_sec, ts.tv_nsec);
+  static const char tag[] = "liblog.__android_log_write__android_logger_list_read";
+  static const char prio = ANDROID_LOG_DEBUG;
 
-    v += getpid() & 0xFFFF;
+  std::string expected_message =
+      std::string(&prio, sizeof(prio)) + tag + std::string("", 1) + buf + std::string("", 1);
 
-    ++signaled;
-    if ((signal_time.tv_sec == 0) && (signal_time.tv_nsec == 0)) {
-        signal_time = log_time(CLOCK_MONOTONIC);
-        signal_time.tv_sec += 2;
+  auto write_function = [&] { ASSERT_LT(0, __android_log_write(prio, tag, buf.c_str())); };
+
+  auto check_function = [&](log_msg log_msg, bool* found) {
+    if (log_msg.entry.len != expected_message.length()) {
+      return;
     }
 
-    LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v)));
+    if (expected_message != std::string(log_msg.msg(), log_msg.entry.len)) {
+      return;
+    }
+
+    *found = true;
+  };
+
+  RunLogTests(LOG_ID_MAIN, write_function, check_function);
+
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+static void bswrite_test(const char* message) {
+#ifdef __ANDROID__
+  pid_t pid = getpid();
+
+  size_t num_lines = 1, size = 0, length = 0, total = 0;
+  const char* cp = message;
+  while (*cp) {
+    if (*cp == '\n') {
+      if (cp[1]) {
+        ++num_lines;
+      }
+    } else {
+      ++size;
+    }
+    ++cp;
+    ++total;
+    ++length;
+    if ((LOGGER_ENTRY_MAX_PAYLOAD - 4 - 1 - 4) <= length) {
+      break;
+    }
+  }
+  while (*cp) {
+    ++cp;
+    ++total;
+  }
+
+  auto write_function = [&] { EXPECT_LT(0, __android_log_bswrite(0, message)); };
+
+  auto check_function = [&](log_msg log_msg, bool* found) {
+    if ((size_t)log_msg.entry.len != (sizeof(android_log_event_string_t) + length) ||
+        log_msg.id() != LOG_ID_EVENTS) {
+      return;
+    }
+
+    android_log_event_string_t* eventData;
+    eventData = reinterpret_cast<android_log_event_string_t*>(log_msg.msg());
+
+    if (!eventData || (eventData->type != EVENT_TYPE_STRING)) {
+      return;
+    }
+
+    size_t len = eventData->length;
+    if (len == total) {
+      *found = true;
+
+      AndroidLogFormat* logformat = android_log_format_new();
+      EXPECT_TRUE(NULL != logformat);
+      AndroidLogEntry entry;
+      char msgBuf[1024];
+      if (length != total) {
+        fprintf(stderr, "Expect \"Binary log entry conversion failed\"\n");
+      }
+      int processBinaryLogBuffer = android_log_processBinaryLogBuffer(
+          &log_msg.entry, &entry, nullptr, msgBuf, sizeof(msgBuf));
+      EXPECT_EQ((length == total) ? 0 : -1, processBinaryLogBuffer);
+      if ((processBinaryLogBuffer == 0) || entry.message) {
+        size_t line_overhead = 20;
+        if (pid > 99999) ++line_overhead;
+        if (pid > 999999) ++line_overhead;
+        fflush(stderr);
+        if (processBinaryLogBuffer) {
+          EXPECT_GT((int)((line_overhead * num_lines) + size),
+                    android_log_printLogLine(logformat, fileno(stderr), &entry));
+        } else {
+          EXPECT_EQ((int)((line_overhead * num_lines) + size),
+                    android_log_printLogLine(logformat, fileno(stderr), &entry));
+        }
+      }
+      android_log_format_free(logformat);
+    }
+  };
+
+  RunLogTests(LOG_ID_EVENTS, write_function, check_function);
+
+#else
+  message = NULL;
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog, __android_log_bswrite_and_print) {
+  bswrite_test("Hello World");
+}
+
+TEST(liblog, __android_log_bswrite_and_print__empty_string) {
+  bswrite_test("");
+}
+
+TEST(liblog, __android_log_bswrite_and_print__newline_prefix) {
+  bswrite_test("\nHello World\n");
+}
+
+TEST(liblog, __android_log_bswrite_and_print__newline_space_prefix) {
+  bswrite_test("\n Hello World \n");
+}
+
+TEST(liblog, __android_log_bswrite_and_print__multiple_newline) {
+  bswrite_test("one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten");
+}
+
+static void buf_write_test(const char* message) {
+#ifdef __ANDROID__
+  pid_t pid = getpid();
+
+  static const char tag[] = "TEST__android_log_buf_write";
+
+  auto write_function = [&] {
+    EXPECT_LT(0, __android_log_buf_write(LOG_ID_MAIN, ANDROID_LOG_INFO, tag, message));
+  };
+  size_t num_lines = 1, size = 0, length = 0;
+  const char* cp = message;
+  while (*cp) {
+    if (*cp == '\n') {
+      if (cp[1]) {
+        ++num_lines;
+      }
+    } else {
+      ++size;
+    }
+    ++length;
+    if ((LOGGER_ENTRY_MAX_PAYLOAD - 2 - sizeof(tag)) <= length) {
+      break;
+    }
+    ++cp;
+  }
+
+  auto check_function = [&](log_msg log_msg, bool* found) {
+    if ((size_t)log_msg.entry.len != (sizeof(tag) + length + 2) || log_msg.id() != LOG_ID_MAIN) {
+      return;
+    }
+
+    *found = true;
+
+    AndroidLogFormat* logformat = android_log_format_new();
+    EXPECT_TRUE(NULL != logformat);
+    AndroidLogEntry entry;
+    int processLogBuffer = android_log_processLogBuffer(&log_msg.entry, &entry);
+    EXPECT_EQ(0, processLogBuffer);
+    if (processLogBuffer == 0) {
+      size_t line_overhead = 11;
+      if (pid > 99999) ++line_overhead;
+      if (pid > 999999) ++line_overhead;
+      fflush(stderr);
+      EXPECT_EQ((int)(((line_overhead + sizeof(tag)) * num_lines) + size),
+                android_log_printLogLine(logformat, fileno(stderr), &entry));
+    }
+    android_log_format_free(logformat);
+  };
+
+  RunLogTests(LOG_ID_MAIN, write_function, check_function);
+
+#else
+  message = NULL;
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog, __android_log_buf_write_and_print__empty) {
+  buf_write_test("");
+}
+
+TEST(liblog, __android_log_buf_write_and_print__newline_prefix) {
+  buf_write_test("\nHello World\n");
+}
+
+TEST(liblog, __android_log_buf_write_and_print__newline_space_prefix) {
+  buf_write_test("\n Hello World \n");
+}
+
+#ifdef ENABLE_FLAKY_TESTS
+#ifdef __ANDROID__
+static unsigned signaled;
+static log_time signal_time;
+
+/*
+ *  Strictly, we are not allowed to log messages in a signal context, but we
+ * do make an effort to keep the failure surface minimized, and this in-effect
+ * should catch any regressions in that effort. The odds of a logged message
+ * in a signal handler causing a lockup problem should be _very_ small.
+ */
+static void caught_blocking_signal(int /*signum*/) {
+  unsigned long long v = 0xDEADBEEFA55A0000ULL;
+
+  v += getpid() & 0xFFFF;
+
+  ++signaled;
+  if ((signal_time.tv_sec == 0) && (signal_time.tv_nsec == 0)) {
+    signal_time = log_time(CLOCK_MONOTONIC);
+    signal_time.tv_sec += 2;
+  }
+
+  LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v)));
 }
 
 // Fill in current process user and system time in 10ms increments
-static void get_ticks(unsigned long long *uticks, unsigned long long *sticks)
-{
+static void get_ticks(unsigned long long* uticks, unsigned long long* sticks) {
+  *uticks = *sticks = 0;
+
+  pid_t pid = getpid();
+
+  char buffer[512];
+  snprintf(buffer, sizeof(buffer), "/proc/%u/stat", pid);
+
+  FILE* fp = fopen(buffer, "re");
+  if (!fp) {
+    return;
+  }
+
+  char* cp = fgets(buffer, sizeof(buffer), fp);
+  fclose(fp);
+  if (!cp) {
+    return;
+  }
+
+  pid_t d;
+  char s[sizeof(buffer)];
+  char c;
+  long long ll;
+  unsigned long long ull;
+
+  if (15 != sscanf(buffer,
+                   "%d %s %c %lld %lld %lld %lld %lld %llu %llu %llu %llu %llu "
+                   "%llu %llu ",
+                   &d, s, &c, &ll, &ll, &ll, &ll, &ll, &ull, &ull, &ull, &ull,
+                   &ull, uticks, sticks)) {
     *uticks = *sticks = 0;
-
-    pid_t pid = getpid();
-
-    char buffer[512];
-    snprintf(buffer, sizeof(buffer), "/proc/%u/stat", pid);
-
-    FILE *fp = fopen(buffer, "r");
-    if (!fp) {
-        return;
-    }
-
-    char *cp = fgets(buffer, sizeof(buffer), fp);
-    fclose(fp);
-    if (!cp) {
-        return;
-    }
-
-    pid_t d;
-    char s[sizeof(buffer)];
-    char c;
-    long long ll;
-    unsigned long long ull;
-
-    if (15 != sscanf(buffer,
-      "%d %s %c %lld %lld %lld %lld %lld %llu %llu %llu %llu %llu %llu %llu ",
-      &d, s, &c, &ll, &ll, &ll, &ll, &ll, &ull, &ull, &ull, &ull, &ull,
-      uticks, sticks)) {
-        *uticks = *sticks = 0;
-    }
+  }
 }
+#endif
 
-TEST(liblog, android_logger_list_read__cpu) {
-    struct logger_list *logger_list;
-    unsigned long long v = 0xDEADBEEFA55A0000ULL;
+TEST(liblog, android_logger_list_read__cpu_signal) {
+#ifdef __ANDROID__
+  struct logger_list* logger_list;
+  unsigned long long v = 0xDEADBEEFA55A0000ULL;
 
-    pid_t pid = getpid();
+  pid_t pid = getpid();
 
-    v += pid & 0xFFFF;
+  v += pid & 0xFFFF;
 
-    ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(
-        LOG_ID_EVENTS, ANDROID_LOG_RDONLY, 1000, pid)));
+  ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(LOG_ID_EVENTS, 0, 1000, pid)));
 
-    int count = 0;
+  int count = 0;
 
-    int signals = 0;
+  int signals = 0;
 
-    unsigned long long uticks_start;
-    unsigned long long sticks_start;
-    get_ticks(&uticks_start, &sticks_start);
+  unsigned long long uticks_start;
+  unsigned long long sticks_start;
+  get_ticks(&uticks_start, &sticks_start);
 
-    const unsigned alarm_time = 10;
+  const unsigned alarm_time = 10;
 
-    memset(&signal_time, 0, sizeof(signal_time));
+  memset(&signal_time, 0, sizeof(signal_time));
 
-    signal(SIGALRM, caught_blocking);
+  signal(SIGALRM, caught_blocking_signal);
+  alarm(alarm_time);
+
+  signaled = 0;
+
+  do {
+    log_msg log_msg;
+    if (android_logger_list_read(logger_list, &log_msg) <= 0) {
+      break;
+    }
+
     alarm(alarm_time);
 
-    signaled = 0;
+    ++count;
 
-    do {
-        log_msg log_msg;
-        if (android_logger_list_read(logger_list, &log_msg) <= 0) {
-            break;
-        }
+    ASSERT_EQ(log_msg.entry.pid, pid);
 
-        alarm(alarm_time);
+    if ((log_msg.entry.len != sizeof(android_log_event_long_t)) ||
+        (log_msg.id() != LOG_ID_EVENTS)) {
+      continue;
+    }
 
-        ++count;
+    android_log_event_long_t* eventData;
+    eventData = reinterpret_cast<android_log_event_long_t*>(log_msg.msg());
 
-        ASSERT_EQ(log_msg.entry.pid, pid);
+    if (!eventData || (eventData->payload.type != EVENT_TYPE_LONG)) {
+      continue;
+    }
 
-        if ((log_msg.entry.len != (4 + 1 + 8))
-         || (log_msg.id() != LOG_ID_EVENTS)) {
-            continue;
-        }
+    char* cp = reinterpret_cast<char*>(&eventData->payload.data);
+    unsigned long long l = cp[0] & 0xFF;
+    l |= (unsigned long long)(cp[1] & 0xFF) << 8;
+    l |= (unsigned long long)(cp[2] & 0xFF) << 16;
+    l |= (unsigned long long)(cp[3] & 0xFF) << 24;
+    l |= (unsigned long long)(cp[4] & 0xFF) << 32;
+    l |= (unsigned long long)(cp[5] & 0xFF) << 40;
+    l |= (unsigned long long)(cp[6] & 0xFF) << 48;
+    l |= (unsigned long long)(cp[7] & 0xFF) << 56;
 
-        char *eventData = log_msg.msg();
+    if (l == v) {
+      ++signals;
+      break;
+    }
+  } while (!signaled || (log_time(CLOCK_MONOTONIC) < signal_time));
+  alarm(0);
+  signal(SIGALRM, SIG_DFL);
 
-        if (eventData[4] != EVENT_TYPE_LONG) {
-            continue;
-        }
+  EXPECT_LE(1, count);
 
-        unsigned long long l = eventData[4 + 1 + 0] & 0xFF;
-        l |= (unsigned long long) (eventData[4 + 1 + 1] & 0xFF) << 8;
-        l |= (unsigned long long) (eventData[4 + 1 + 2] & 0xFF) << 16;
-        l |= (unsigned long long) (eventData[4 + 1 + 3] & 0xFF) << 24;
-        l |= (unsigned long long) (eventData[4 + 1 + 4] & 0xFF) << 32;
-        l |= (unsigned long long) (eventData[4 + 1 + 5] & 0xFF) << 40;
-        l |= (unsigned long long) (eventData[4 + 1 + 6] & 0xFF) << 48;
-        l |= (unsigned long long) (eventData[4 + 1 + 7] & 0xFF) << 56;
+  EXPECT_EQ(1, signals);
 
-        if (l == v) {
-            ++signals;
-            break;
-        }
-    } while (!signaled || (log_time(CLOCK_MONOTONIC) < signal_time));
-    alarm(0);
-    signal(SIGALRM, SIG_DFL);
+  android_logger_list_close(logger_list);
 
-    EXPECT_LT(1, count);
+  unsigned long long uticks_end;
+  unsigned long long sticks_end;
+  get_ticks(&uticks_end, &sticks_end);
 
-    EXPECT_EQ(1, signals);
-
-    android_logger_list_close(logger_list);
-
-    unsigned long long uticks_end;
-    unsigned long long sticks_end;
-    get_ticks(&uticks_end, &sticks_end);
-
-    // Less than 1% in either user or system time, or both
-    const unsigned long long one_percent_ticks = alarm_time;
-    unsigned long long user_ticks = uticks_end - uticks_start;
-    unsigned long long system_ticks = sticks_end - sticks_start;
-    EXPECT_GT(one_percent_ticks, user_ticks);
-    EXPECT_GT(one_percent_ticks, system_ticks);
-    EXPECT_GT(one_percent_ticks, user_ticks + system_ticks);
+  // Less than 1% in either user or system time, or both
+  const unsigned long long one_percent_ticks = alarm_time;
+  unsigned long long user_ticks = uticks_end - uticks_start;
+  unsigned long long system_ticks = sticks_end - sticks_start;
+  EXPECT_GT(one_percent_ticks, user_ticks);
+  EXPECT_GT(one_percent_ticks, system_ticks);
+  EXPECT_GT(one_percent_ticks, user_ticks + system_ticks);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
 }
 
-static const char max_payload_tag[] = "TEST_max_payload_XXXX";
-static const char max_payload_buf[LOGGER_ENTRY_MAX_PAYLOAD
-    - sizeof(max_payload_tag) - 1] = "LEONATO\n\
+#ifdef __ANDROID__
+/*
+ *  Strictly, we are not allowed to log messages in a signal context, the
+ * correct way to handle this is to ensure the messages are constructed in
+ * a thread; the signal handler should only unblock the thread.
+ */
+static sem_t thread_trigger;
+
+static void caught_blocking_thread(int /*signum*/) {
+  sem_post(&thread_trigger);
+}
+
+static void* running_thread(void*) {
+  unsigned long long v = 0xDEADBEAFA55A0000ULL;
+
+  v += getpid() & 0xFFFF;
+
+  struct timespec timeout;
+  clock_gettime(CLOCK_REALTIME, &timeout);
+  timeout.tv_sec += 55;
+  sem_timedwait(&thread_trigger, &timeout);
+
+  ++signaled;
+  if ((signal_time.tv_sec == 0) && (signal_time.tv_nsec == 0)) {
+    signal_time = log_time(CLOCK_MONOTONIC);
+    signal_time.tv_sec += 2;
+  }
+
+  LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v)));
+
+  return NULL;
+}
+
+static int start_thread() {
+  sem_init(&thread_trigger, 0, 0);
+
+  pthread_attr_t attr;
+  if (pthread_attr_init(&attr)) {
+    return -1;
+  }
+
+  struct sched_param param;
+
+  memset(&param, 0, sizeof(param));
+  pthread_attr_setschedparam(&attr, &param);
+  pthread_attr_setschedpolicy(&attr, SCHED_BATCH);
+
+  if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) {
+    pthread_attr_destroy(&attr);
+    return -1;
+  }
+
+  pthread_t thread;
+  if (pthread_create(&thread, &attr, running_thread, NULL)) {
+    pthread_attr_destroy(&attr);
+    return -1;
+  }
+
+  pthread_attr_destroy(&attr);
+  return 0;
+}
+#endif
+
+TEST(liblog, android_logger_list_read__cpu_thread) {
+#ifdef __ANDROID__
+  struct logger_list* logger_list;
+  unsigned long long v = 0xDEADBEAFA55A0000ULL;
+
+  pid_t pid = getpid();
+
+  v += pid & 0xFFFF;
+
+  ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(LOG_ID_EVENTS, 0, 1000, pid)));
+
+  int count = 0;
+
+  int signals = 0;
+
+  unsigned long long uticks_start;
+  unsigned long long sticks_start;
+  get_ticks(&uticks_start, &sticks_start);
+
+  const unsigned alarm_time = 10;
+
+  memset(&signal_time, 0, sizeof(signal_time));
+
+  signaled = 0;
+  EXPECT_EQ(0, start_thread());
+
+  signal(SIGALRM, caught_blocking_thread);
+  alarm(alarm_time);
+
+  do {
+    log_msg log_msg;
+    if (LOG_FAILURE_RETRY(android_logger_list_read(logger_list, &log_msg)) <= 0) {
+      break;
+    }
+
+    alarm(alarm_time);
+
+    ++count;
+
+    ASSERT_EQ(log_msg.entry.pid, pid);
+
+    if ((log_msg.entry.len != sizeof(android_log_event_long_t)) ||
+        (log_msg.id() != LOG_ID_EVENTS)) {
+      continue;
+    }
+
+    android_log_event_long_t* eventData;
+    eventData = reinterpret_cast<android_log_event_long_t*>(log_msg.msg());
+
+    if (!eventData || (eventData->payload.type != EVENT_TYPE_LONG)) {
+      continue;
+    }
+
+    char* cp = reinterpret_cast<char*>(&eventData->payload.data);
+    unsigned long long l = cp[0] & 0xFF;
+    l |= (unsigned long long)(cp[1] & 0xFF) << 8;
+    l |= (unsigned long long)(cp[2] & 0xFF) << 16;
+    l |= (unsigned long long)(cp[3] & 0xFF) << 24;
+    l |= (unsigned long long)(cp[4] & 0xFF) << 32;
+    l |= (unsigned long long)(cp[5] & 0xFF) << 40;
+    l |= (unsigned long long)(cp[6] & 0xFF) << 48;
+    l |= (unsigned long long)(cp[7] & 0xFF) << 56;
+
+    if (l == v) {
+      ++signals;
+      break;
+    }
+  } while (!signaled || (log_time(CLOCK_MONOTONIC) < signal_time));
+  alarm(0);
+  signal(SIGALRM, SIG_DFL);
+
+  EXPECT_LE(1, count);
+
+  EXPECT_EQ(1, signals);
+
+  android_logger_list_close(logger_list);
+
+  unsigned long long uticks_end;
+  unsigned long long sticks_end;
+  get_ticks(&uticks_end, &sticks_end);
+
+  // Less than 1% in either user or system time, or both
+  const unsigned long long one_percent_ticks = alarm_time;
+  unsigned long long user_ticks = uticks_end - uticks_start;
+  unsigned long long system_ticks = sticks_end - sticks_start;
+  EXPECT_GT(one_percent_ticks, user_ticks);
+  EXPECT_GT(one_percent_ticks, system_ticks);
+  EXPECT_GT(one_percent_ticks, user_ticks + system_ticks);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+#endif  // ENABLE_FLAKY_TESTS
+
+static const char max_payload_buf[] =
+    "LEONATO\n\
 I learn in this letter that Don Peter of Arragon\n\
 comes this night to Messina\n\
 MESSENGER\n\
@@ -431,253 +931,296 @@
 trouble: the fashion of the world is to avoid\n\
 cost, and you encounter it\n\
 LEONATO\n\
-Never came trouble to my house in the likeness";
+Never came trouble to my house in the likeness of your grace,\n\
+for trouble being gone, comfort should remain, but\n\
+when you depart from me, sorrow abides and happiness\n\
+takes his leave.";
 
 TEST(liblog, max_payload) {
-    pid_t pid = getpid();
-    char tag[sizeof(max_payload_tag)];
-    memcpy(tag, max_payload_tag, sizeof(tag));
-    snprintf(tag + sizeof(tag) - 5, 5, "%04X", pid & 0xFFFF);
+#ifdef __ANDROID__
+  static const char max_payload_tag[] = "TEST_max_payload_and_longish_tag_XXXX";
+#define SIZEOF_MAX_PAYLOAD_BUF (LOGGER_ENTRY_MAX_PAYLOAD - sizeof(max_payload_tag) - 1)
 
-    LOG_FAILURE_RETRY(__android_log_buf_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO,
-                                              tag, max_payload_buf));
-    sleep(2);
+  pid_t pid = getpid();
+  char tag[sizeof(max_payload_tag)];
+  memcpy(tag, max_payload_tag, sizeof(tag));
+  snprintf(tag + sizeof(tag) - 5, 5, "%04X", pid & 0xFFFF);
 
-    struct logger_list *logger_list;
+  auto write_function = [&] {
+    LOG_FAILURE_RETRY(
+        __android_log_buf_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO, tag, max_payload_buf));
+  };
 
-    ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(
-        LOG_ID_SYSTEM, ANDROID_LOG_RDONLY, 100, 0)));
+  ssize_t max_len = 0;
+  auto check_function = [&](log_msg log_msg, bool* found) {
+    char* data = log_msg.msg();
 
-    bool matches = false;
-    ssize_t max_len = 0;
-
-    for(;;) {
-        log_msg log_msg;
-        if (android_logger_list_read(logger_list, &log_msg) <= 0) {
-            break;
-        }
-
-        if ((log_msg.entry.pid != pid) || (log_msg.id() != LOG_ID_SYSTEM)) {
-            continue;
-        }
-
-        char *data = log_msg.msg() + 1;
-
-        if (strcmp(data, tag)) {
-            continue;
-        }
-
-        data += strlen(data) + 1;
-
-        const char *left = data;
-        const char *right = max_payload_buf;
-        while (*left && *right && (*left == *right)) {
-            ++left;
-            ++right;
-        }
-
-        if (max_len <= (left - data)) {
-            max_len = left - data + 1;
-        }
-
-        if (max_len > 512) {
-            matches = true;
-            break;
-        }
+    if (!data || strcmp(++data, tag)) {
+      return;
     }
 
-    android_logger_list_close(logger_list);
+    data += strlen(data) + 1;
 
-    EXPECT_EQ(true, matches);
+    const char* left = data;
+    const char* right = max_payload_buf;
+    while (*left && *right && (*left == *right)) {
+      ++left;
+      ++right;
+    }
 
-    EXPECT_LE(sizeof(max_payload_buf), static_cast<size_t>(max_len));
+    if (max_len <= (left - data)) {
+      max_len = left - data + 1;
+    }
+
+    if (max_len > 512) {
+      *found = true;
+    }
+  };
+
+  RunLogTests(LOG_ID_SYSTEM, write_function, check_function);
+
+  EXPECT_LE(SIZEOF_MAX_PAYLOAD_BUF, static_cast<size_t>(max_len));
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
 }
 
-TEST(liblog, too_big_payload) {
-    pid_t pid = getpid();
-    static const char big_payload_tag[] = "TEST_big_payload_XXXX";
-    char tag[sizeof(big_payload_tag)];
-    memcpy(tag, big_payload_tag, sizeof(tag));
-    snprintf(tag + sizeof(tag) - 5, 5, "%04X", pid & 0xFFFF);
+TEST(liblog, __android_log_buf_print__maxtag) {
+#ifdef __ANDROID__
+  auto write_function = [&] {
+    EXPECT_LT(0, __android_log_buf_print(LOG_ID_MAIN, ANDROID_LOG_INFO, max_payload_buf,
+                                         max_payload_buf));
+  };
 
-    std::string longString(3266519, 'x');
-
-    ssize_t ret = LOG_FAILURE_RETRY(__android_log_buf_write(LOG_ID_SYSTEM,
-                                    ANDROID_LOG_INFO, tag, longString.c_str()));
-
-    struct logger_list *logger_list;
-
-    ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(
-        LOG_ID_SYSTEM, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 100, 0)));
-
-    ssize_t max_len = 0;
-
-    for(;;) {
-        log_msg log_msg;
-        if (android_logger_list_read(logger_list, &log_msg) <= 0) {
-            break;
-        }
-
-        if ((log_msg.entry.pid != pid) || (log_msg.id() != LOG_ID_SYSTEM)) {
-            continue;
-        }
-
-        char *data = log_msg.msg() + 1;
-
-        if (strcmp(data, tag)) {
-            continue;
-        }
-
-        data += strlen(data) + 1;
-
-        const char *left = data;
-        const char *right = longString.c_str();
-        while (*left && *right && (*left == *right)) {
-            ++left;
-            ++right;
-        }
-
-        if (max_len <= (left - data)) {
-            max_len = left - data + 1;
-        }
+  auto check_function = [&](log_msg log_msg, bool* found) {
+    if ((size_t)log_msg.entry.len < LOGGER_ENTRY_MAX_PAYLOAD) {
+      return;
     }
 
-    android_logger_list_close(logger_list);
+    *found = true;
 
-    EXPECT_LE(LOGGER_ENTRY_MAX_PAYLOAD - sizeof(big_payload_tag),
-              static_cast<size_t>(max_len));
+    AndroidLogFormat* logformat = android_log_format_new();
+    EXPECT_TRUE(NULL != logformat);
+    AndroidLogEntry entry;
+    int processLogBuffer = android_log_processLogBuffer(&log_msg.entry, &entry);
+    EXPECT_EQ(0, processLogBuffer);
+    if (processLogBuffer == 0) {
+      fflush(stderr);
+      int printLogLine =
+          android_log_printLogLine(logformat, fileno(stderr), &entry);
+      // Legacy tag truncation
+      EXPECT_LE(128, printLogLine);
+      // Measured maximum if we try to print part of the tag as message
+      EXPECT_GT(LOGGER_ENTRY_MAX_PAYLOAD * 13 / 8, printLogLine);
+    }
+    android_log_format_free(logformat);
+  };
 
-    EXPECT_EQ(ret, max_len + static_cast<ssize_t>(sizeof(big_payload_tag)));
+  RunLogTests(LOG_ID_MAIN, write_function, check_function);
+
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+// Note: This test is tautological. android_logger_list_read() calls recv() with
+// LOGGER_ENTRY_MAX_PAYLOAD as its size argument, so it's not possible for this test to read a
+// payload larger than that size.
+TEST(liblog, too_big_payload) {
+#ifdef __ANDROID__
+  pid_t pid = getpid();
+  static const char big_payload_tag[] = "TEST_big_payload_XXXX";
+  char tag[sizeof(big_payload_tag)];
+  memcpy(tag, big_payload_tag, sizeof(tag));
+  snprintf(tag + sizeof(tag) - 5, 5, "%04X", pid & 0xFFFF);
+
+  std::string longString(3266519, 'x');
+  ssize_t ret;
+
+  auto write_function = [&] {
+    ret = LOG_FAILURE_RETRY(
+        __android_log_buf_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO, tag, longString.c_str()));
+  };
+
+  auto check_function = [&](log_msg log_msg, bool* found) {
+    char* data = log_msg.msg();
+
+    if (!data || strcmp(++data, tag)) {
+      return;
+    }
+
+    data += strlen(data) + 1;
+
+    const char* left = data;
+    const char* right = longString.c_str();
+    while (*left && *right && (*left == *right)) {
+      ++left;
+      ++right;
+    }
+
+    ssize_t len = left - data + 1;
+    // Check that we don't see any entries larger than the max payload.
+    EXPECT_LE(static_cast<size_t>(len), LOGGER_ENTRY_MAX_PAYLOAD - sizeof(big_payload_tag));
+
+    // Once we've found our expected entry, break.
+    if (len == LOGGER_ENTRY_MAX_PAYLOAD - sizeof(big_payload_tag)) {
+      *found = true;
+    }
+  };
+
+  RunLogTests(LOG_ID_SYSTEM, write_function, check_function);
+
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
 }
 
 TEST(liblog, dual_reader) {
-    struct logger_list *logger_list1;
+#ifdef __ANDROID__
+  static const int expected_count1 = 25;
+  static const int expected_count2 = 25;
 
-    // >25 messages due to liblog.__android_log_buf_print__concurrentXX above.
-    ASSERT_TRUE(NULL != (logger_list1 = android_logger_list_open(
-        LOG_ID_MAIN, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 25, 0)));
+  pid_t pid = getpid();
 
-    struct logger_list *logger_list2;
+  auto logger_list1 = std::unique_ptr<struct logger_list, ListCloser>{
+      android_logger_list_open(LOG_ID_MAIN, 0, expected_count1, pid)};
+  ASSERT_TRUE(logger_list1);
 
-    if (NULL == (logger_list2 = android_logger_list_open(
-            LOG_ID_MAIN, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 15, 0))) {
-        android_logger_list_close(logger_list1);
-        ASSERT_TRUE(NULL != logger_list2);
+  auto logger_list2 = std::unique_ptr<struct logger_list, ListCloser>{
+      android_logger_list_open(LOG_ID_MAIN, 0, expected_count2, pid)};
+  ASSERT_TRUE(logger_list2);
+
+  for (int i = 25; i > 0; --i) {
+    static const char fmt[] = "dual_reader %02d";
+    char buffer[sizeof(fmt) + 8];
+    snprintf(buffer, sizeof(buffer), fmt, i);
+    LOG_FAILURE_RETRY(__android_log_buf_write(LOG_ID_MAIN, ANDROID_LOG_INFO,
+                                              "liblog", buffer));
+  }
+
+  alarm(2);
+  auto alarm_guard = android::base::make_scope_guard([] { alarm(0); });
+
+  // Wait until we see all messages with the blocking reader.
+  int count1 = 0;
+  int count2 = 0;
+
+  while (count1 != expected_count2 || count2 != expected_count2) {
+    log_msg log_msg;
+    if (count1 < expected_count1) {
+      ASSERT_GT(android_logger_list_read(logger_list1.get(), &log_msg), 0);
+      count1++;
+    }
+    if (count2 < expected_count2) {
+      ASSERT_GT(android_logger_list_read(logger_list2.get(), &log_msg), 0);
+      count2++;
+    }
+  }
+
+  // Test again with the nonblocking reader.
+  auto logger_list_non_block1 = std::unique_ptr<struct logger_list, ListCloser>{
+      android_logger_list_open(LOG_ID_MAIN, ANDROID_LOG_NONBLOCK, expected_count1, pid)};
+  ASSERT_TRUE(logger_list_non_block1);
+
+  auto logger_list_non_block2 = std::unique_ptr<struct logger_list, ListCloser>{
+      android_logger_list_open(LOG_ID_MAIN, ANDROID_LOG_NONBLOCK, expected_count2, pid)};
+  ASSERT_TRUE(logger_list_non_block2);
+  count1 = 0;
+  count2 = 0;
+  bool done1 = false;
+  bool done2 = false;
+
+  while (!done1 || !done2) {
+    log_msg log_msg;
+
+    if (!done1) {
+      if (android_logger_list_read(logger_list_non_block1.get(), &log_msg) <= 0) {
+        done1 = true;
+      } else {
+        ++count1;
+      }
     }
 
-    int count1 = 0;
-    bool done1 = false;
-    int count2 = 0;
-    bool done2 = false;
-
-    do {
-        log_msg log_msg;
-
-        if (!done1) {
-            if (android_logger_list_read(logger_list1, &log_msg) <= 0) {
-                done1 = true;
-            } else {
-                ++count1;
-            }
-        }
-
-        if (!done2) {
-            if (android_logger_list_read(logger_list2, &log_msg) <= 0) {
-                done2 = true;
-            } else {
-                ++count2;
-            }
-        }
-    } while ((!done1) || (!done2));
-
-    android_logger_list_close(logger_list1);
-    android_logger_list_close(logger_list2);
-
-    EXPECT_EQ(25, count1);
-    EXPECT_EQ(15, count2);
-}
-
-TEST(liblog, android_logger_get_) {
-    struct logger_list * logger_list = android_logger_list_alloc(ANDROID_LOG_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;
-        }
-        fprintf(stderr, "log buffer %s\r", name);
-        struct logger * logger;
-        EXPECT_TRUE(NULL != (logger = android_logger_open(logger_list, id)));
-        EXPECT_EQ(id, android_logger_get_id(logger));
-        /* crash buffer is allowed to be empty, that is actually healthy! */
-        if (android_logger_get_log_size(logger) || strcmp("crash", name)) {
-            EXPECT_LT(0, android_logger_get_log_size(logger));
-        }
-        EXPECT_LT(0, android_logger_get_log_readable_size(logger));
-        EXPECT_LT(0, android_logger_get_log_version(logger));
+    if (!done2) {
+      if (android_logger_list_read(logger_list_non_block2.get(), &log_msg) <= 0) {
+        done2 = true;
+      } else {
+        ++count2;
+      }
     }
+  }
 
-    android_logger_list_close(logger_list);
+  EXPECT_EQ(expected_count1, count1);
+  EXPECT_EQ(expected_count2, count2);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
 }
 
-static bool checkPriForTag(AndroidLogFormat *p_format, const char *tag, android_LogPriority pri) {
-    return android_log_shouldPrintLine(p_format, tag, pri)
-        && !android_log_shouldPrintLine(p_format, tag, (android_LogPriority)(pri - 1));
+static bool checkPriForTag(AndroidLogFormat* p_format, const char* tag,
+                           android_LogPriority pri) {
+  return android_log_shouldPrintLine(p_format, tag, pri) &&
+         !android_log_shouldPrintLine(p_format, tag,
+                                      (android_LogPriority)(pri - 1));
 }
 
 TEST(liblog, filterRule) {
-    static const char tag[] = "random";
+  static const char tag[] = "random";
 
-    AndroidLogFormat *p_format = android_log_format_new();
+  AndroidLogFormat* p_format = android_log_format_new();
 
-    android_log_addFilterRule(p_format,"*:i");
+  android_log_addFilterRule(p_format, "*:i");
 
-    EXPECT_TRUE(checkPriForTag(p_format, tag, ANDROID_LOG_INFO));
-    EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) == 0);
-    android_log_addFilterRule(p_format, "*");
-    EXPECT_TRUE (checkPriForTag(p_format, tag, ANDROID_LOG_DEBUG));
-    EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
-    android_log_addFilterRule(p_format, "*:v");
-    EXPECT_TRUE (checkPriForTag(p_format, tag, ANDROID_LOG_VERBOSE));
-    EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
-    android_log_addFilterRule(p_format, "*:i");
-    EXPECT_TRUE (checkPriForTag(p_format, tag, ANDROID_LOG_INFO));
-    EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) == 0);
+  EXPECT_TRUE(checkPriForTag(p_format, tag, ANDROID_LOG_INFO));
+  EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) ==
+              0);
+  android_log_addFilterRule(p_format, "*");
+  EXPECT_TRUE(checkPriForTag(p_format, tag, ANDROID_LOG_DEBUG));
+  EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
+  android_log_addFilterRule(p_format, "*:v");
+  EXPECT_TRUE(checkPriForTag(p_format, tag, ANDROID_LOG_VERBOSE));
+  EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
+  android_log_addFilterRule(p_format, "*:i");
+  EXPECT_TRUE(checkPriForTag(p_format, tag, ANDROID_LOG_INFO));
+  EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) ==
+              0);
 
-    android_log_addFilterRule(p_format, tag);
-    EXPECT_TRUE (checkPriForTag(p_format, tag, ANDROID_LOG_VERBOSE));
-    EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
-    android_log_addFilterRule(p_format, "random:v");
-    EXPECT_TRUE (checkPriForTag(p_format, tag, ANDROID_LOG_VERBOSE));
-    EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
-    android_log_addFilterRule(p_format, "random:d");
-    EXPECT_TRUE (checkPriForTag(p_format, tag, ANDROID_LOG_DEBUG));
-    EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
-    android_log_addFilterRule(p_format, "random:w");
-    EXPECT_TRUE (checkPriForTag(p_format, tag, ANDROID_LOG_WARN));
-    EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) == 0);
+  android_log_addFilterRule(p_format, tag);
+  EXPECT_TRUE(checkPriForTag(p_format, tag, ANDROID_LOG_VERBOSE));
+  EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
+  android_log_addFilterRule(p_format, "random:v");
+  EXPECT_TRUE(checkPriForTag(p_format, tag, ANDROID_LOG_VERBOSE));
+  EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
+  android_log_addFilterRule(p_format, "random:d");
+  EXPECT_TRUE(checkPriForTag(p_format, tag, ANDROID_LOG_DEBUG));
+  EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
+  android_log_addFilterRule(p_format, "random:w");
+  EXPECT_TRUE(checkPriForTag(p_format, tag, ANDROID_LOG_WARN));
+  EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) ==
+              0);
 
-    android_log_addFilterRule(p_format, "crap:*");
-    EXPECT_TRUE (checkPriForTag(p_format, "crap", ANDROID_LOG_VERBOSE));
-    EXPECT_TRUE(android_log_shouldPrintLine(p_format, "crap", ANDROID_LOG_VERBOSE) > 0);
+  android_log_addFilterRule(p_format, "crap:*");
+  EXPECT_TRUE(checkPriForTag(p_format, "crap", ANDROID_LOG_VERBOSE));
+  EXPECT_TRUE(
+      android_log_shouldPrintLine(p_format, "crap", ANDROID_LOG_VERBOSE) > 0);
 
-    // invalid expression
-    EXPECT_TRUE (android_log_addFilterRule(p_format, "random:z") < 0);
-    EXPECT_TRUE (checkPriForTag(p_format, tag, ANDROID_LOG_WARN));
-    EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) == 0);
+  // invalid expression
+  EXPECT_TRUE(android_log_addFilterRule(p_format, "random:z") < 0);
+  EXPECT_TRUE(checkPriForTag(p_format, tag, ANDROID_LOG_WARN));
+  EXPECT_TRUE(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) ==
+              0);
 
-    // Issue #550946
-    EXPECT_TRUE(android_log_addFilterString(p_format, " ") == 0);
-    EXPECT_TRUE(checkPriForTag(p_format, tag, ANDROID_LOG_WARN));
+  // Issue #550946
+  EXPECT_TRUE(android_log_addFilterString(p_format, " ") == 0);
+  EXPECT_TRUE(checkPriForTag(p_format, tag, ANDROID_LOG_WARN));
 
-    // note trailing space
-    EXPECT_TRUE(android_log_addFilterString(p_format, "*:s random:d ") == 0);
-    EXPECT_TRUE(checkPriForTag(p_format, tag, ANDROID_LOG_DEBUG));
+  // note trailing space
+  EXPECT_TRUE(android_log_addFilterString(p_format, "*:s random:d ") == 0);
+  EXPECT_TRUE(checkPriForTag(p_format, tag, ANDROID_LOG_DEBUG));
 
-    EXPECT_TRUE(android_log_addFilterString(p_format, "*:s random:z") < 0);
+  EXPECT_TRUE(android_log_addFilterString(p_format, "*:s random:z") < 0);
 
-#if 0 // bitrot, seek update
+#if 0  // bitrot, seek update
     char defaultBuffer[512];
 
     android_log_formatLogLine(p_format,
@@ -687,192 +1230,1541 @@
     fprintf(stderr, "%s\n", defaultBuffer);
 #endif
 
-    android_log_format_free(p_format);
+  android_log_format_free(p_format);
 }
 
+#ifdef ENABLE_FLAKY_TESTS
 TEST(liblog, is_loggable) {
-    static const char tag[] = "is_loggable";
-    static const char log_namespace[] = "persist.log.tag.";
-    static const size_t base_offset = 8; /* skip "persist." */
-    // sizeof("string") = strlen("string") + 1
-    char key[sizeof(log_namespace) + sizeof(tag) - 1];
-    char hold[4][PROP_VALUE_MAX];
-    static const struct {
-        int level;
-        char type;
-    } levels[] = {
-        { ANDROID_LOG_VERBOSE, 'v' },
-        { ANDROID_LOG_DEBUG  , 'd' },
-        { ANDROID_LOG_INFO   , 'i' },
-        { ANDROID_LOG_WARN   , 'w' },
-        { ANDROID_LOG_ERROR  , 'e' },
-        { ANDROID_LOG_FATAL  , 'a' },
-        { -1                 , 's' },
-        { -2                 , 'g' }, // Illegal value, resort to default
-    };
+#ifdef __ANDROID__
+  static const char tag[] = "is_loggable";
+  static const char log_namespace[] = "persist.log.tag.";
+  static const size_t base_offset = 8; /* skip "persist." */
+  // sizeof("string") = strlen("string") + 1
+  char key[sizeof(log_namespace) + sizeof(tag) - 1];
+  char hold[4][PROP_VALUE_MAX];
+  static const struct {
+    int level;
+    char type;
+  } levels[] = {
+      {ANDROID_LOG_VERBOSE, 'v'}, {ANDROID_LOG_DEBUG, 'd'},
+      {ANDROID_LOG_INFO, 'i'},    {ANDROID_LOG_WARN, 'w'},
+      {ANDROID_LOG_ERROR, 'e'},   {ANDROID_LOG_FATAL, 'a'},
+      {ANDROID_LOG_SILENT, 's'},  {-2, 'g'},  // Illegal value, resort to default
+  };
 
-    // Set up initial test condition
-    memset(hold, 0, sizeof(hold));
-    snprintf(key, sizeof(key), "%s%s", log_namespace, tag);
-    property_get(key, hold[0], "");
-    property_set(key, "");
-    property_get(key + base_offset, hold[1], "");
-    property_set(key + base_offset, "");
-    strcpy(key, log_namespace);
-    key[sizeof(log_namespace) - 2] = '\0';
-    property_get(key, hold[2], "");
-    property_set(key, "");
-    property_get(key, hold[3], "");
-    property_set(key + base_offset, "");
+  // Set up initial test condition
+  memset(hold, 0, sizeof(hold));
+  snprintf(key, sizeof(key), "%s%s", log_namespace, tag);
+  property_get(key, hold[0], "");
+  property_set(key, "");
+  property_get(key + base_offset, hold[1], "");
+  property_set(key + base_offset, "");
+  strcpy(key, log_namespace);
+  key[sizeof(log_namespace) - 2] = '\0';
+  property_get(key, hold[2], "");
+  property_set(key, "");
+  property_get(key, hold[3], "");
+  property_set(key + base_offset, "");
 
-    // All combinations of level and defaults
-    for(size_t i = 0; i < (sizeof(levels) / sizeof(levels[0])); ++i) {
-        if (levels[i].level == -2) {
-            continue;
-        }
-        for(size_t j = 0; j < (sizeof(levels) / sizeof(levels[0])); ++j) {
-            if (levels[j].level == -2) {
-                continue;
-            }
-            fprintf(stderr, "i=%zu j=%zu\r", i, j);
-            if ((levels[i].level < levels[j].level)
-                    || (levels[j].level == -1)) {
-                EXPECT_FALSE(__android_log_is_loggable(levels[i].level, tag,
-                                                       levels[j].level));
-            } else {
-                EXPECT_TRUE(__android_log_is_loggable(levels[i].level, tag,
-                                                      levels[j].level));
-            }
-        }
+  // All combinations of level and defaults
+  for (size_t i = 0; i < (sizeof(levels) / sizeof(levels[0])); ++i) {
+    if (levels[i].level == -2) {
+      continue;
     }
-
-    // All combinations of level and tag and global properties
-    for(size_t i = 0; i < (sizeof(levels) / sizeof(levels[0])); ++i) {
-        if (levels[i].level == -2) {
-            continue;
+    for (size_t j = 0; j < (sizeof(levels) / sizeof(levels[0])); ++j) {
+      if (levels[j].level == -2) {
+        continue;
+      }
+      fprintf(stderr, "i=%zu j=%zu\r", i, j);
+      bool android_log_is_loggable = __android_log_is_loggable_len(
+          levels[i].level, tag, strlen(tag), levels[j].level);
+      if ((levels[i].level < levels[j].level) || (levels[j].level == -1)) {
+        if (android_log_is_loggable) {
+          fprintf(stderr, "\n");
         }
-        for(size_t j = 0; j < (sizeof(levels) / sizeof(levels[0])); ++j) {
-            char buf[2];
-            buf[0] = levels[j].type;
-            buf[1] = '\0';
-
-            snprintf(key, sizeof(key), "%s%s", log_namespace, tag);
-            fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r",
-                    i, j, key, buf);
-            property_set(key, buf);
-            if ((levels[i].level < levels[j].level)
-                    || (levels[j].level == -1)
-                    || ((levels[i].level < ANDROID_LOG_DEBUG)
-                        && (levels[j].level == -2))) {
-                EXPECT_FALSE(__android_log_is_loggable(levels[i].level, tag,
-                                                       ANDROID_LOG_DEBUG));
-            } else {
-                EXPECT_TRUE(__android_log_is_loggable(levels[i].level, tag,
-                                                      ANDROID_LOG_DEBUG));
-            }
-            property_set(key, "");
-
-            fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r",
-                    i, j, key + base_offset, buf);
-            property_set(key + base_offset, buf);
-            if ((levels[i].level < levels[j].level)
-                    || (levels[j].level == -1)
-                    || ((levels[i].level < ANDROID_LOG_DEBUG)
-                        && (levels[j].level == -2))) {
-                EXPECT_FALSE(__android_log_is_loggable(levels[i].level, tag,
-                                                       ANDROID_LOG_DEBUG));
-            } else {
-                EXPECT_TRUE(__android_log_is_loggable(levels[i].level, tag,
-                                                      ANDROID_LOG_DEBUG));
-            }
-            property_set(key + base_offset, "");
-
-            strcpy(key, log_namespace);
-            key[sizeof(log_namespace) - 2] = '\0';
-            fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r",
-                    i, j, key, buf);
-            property_set(key, buf);
-            if ((levels[i].level < levels[j].level)
-                    || (levels[j].level == -1)
-                    || ((levels[i].level < ANDROID_LOG_DEBUG)
-                        && (levels[j].level == -2))) {
-                EXPECT_FALSE(__android_log_is_loggable(levels[i].level, tag,
-                                                       ANDROID_LOG_DEBUG));
-            } else {
-                EXPECT_TRUE(__android_log_is_loggable(levels[i].level, tag,
-                                                      ANDROID_LOG_DEBUG));
-            }
-            property_set(key, "");
-
-            fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r",
-                    i, j, key + base_offset, buf);
-            property_set(key + base_offset, buf);
-            if ((levels[i].level < levels[j].level)
-                    || (levels[j].level == -1)
-                    || ((levels[i].level < ANDROID_LOG_DEBUG)
-                        && (levels[j].level == -2))) {
-                EXPECT_FALSE(__android_log_is_loggable(levels[i].level, tag,
-                                                       ANDROID_LOG_DEBUG));
-            } else {
-                EXPECT_TRUE(__android_log_is_loggable(levels[i].level, tag,
-                                                      ANDROID_LOG_DEBUG));
-            }
-            property_set(key + base_offset, "");
+        EXPECT_FALSE(android_log_is_loggable);
+        for (size_t k = 10; k; --k) {
+          EXPECT_FALSE(__android_log_is_loggable_len(
+              levels[i].level, tag, strlen(tag), levels[j].level));
         }
+      } else {
+        if (!android_log_is_loggable) {
+          fprintf(stderr, "\n");
+        }
+        EXPECT_TRUE(android_log_is_loggable);
+        for (size_t k = 10; k; --k) {
+          EXPECT_TRUE(__android_log_is_loggable_len(
+              levels[i].level, tag, strlen(tag), levels[j].level));
+        }
+      }
     }
+  }
 
-    // All combinations of level and tag properties, but with global set to INFO
-    strcpy(key, log_namespace);
-    key[sizeof(log_namespace) - 2] = '\0';
-    property_set(key, "I");
-    snprintf(key, sizeof(key), "%s%s", log_namespace, tag);
-    for(size_t i = 0; i < (sizeof(levels) / sizeof(levels[0])); ++i) {
-        if (levels[i].level == -2) {
-            continue;
-        }
-        for(size_t j = 0; j < (sizeof(levels) / sizeof(levels[0])); ++j) {
-            char buf[2];
-            buf[0] = levels[j].type;
-            buf[1] = '\0';
-
-            fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r",
-                    i, j, key, buf);
-            property_set(key, buf);
-            if ((levels[i].level < levels[j].level)
-                    || (levels[j].level == -1)
-                    || ((levels[i].level < ANDROID_LOG_INFO) // Yes INFO
-                        && (levels[j].level == -2))) {
-                EXPECT_FALSE(__android_log_is_loggable(levels[i].level, tag,
-                                                       ANDROID_LOG_DEBUG));
-            } else {
-                EXPECT_TRUE(__android_log_is_loggable(levels[i].level, tag,
-                                                      ANDROID_LOG_DEBUG));
-            }
-            property_set(key, "");
-
-            fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r",
-                    i, j, key + base_offset, buf);
-            property_set(key + base_offset, buf);
-            if ((levels[i].level < levels[j].level)
-                    || (levels[j].level == -1)
-                    || ((levels[i].level < ANDROID_LOG_INFO) // Yes INFO
-                        && (levels[j].level == -2))) {
-                EXPECT_FALSE(__android_log_is_loggable(levels[i].level, tag,
-                                                       ANDROID_LOG_DEBUG));
-            } else {
-                EXPECT_TRUE(__android_log_is_loggable(levels[i].level, tag,
-                                                      ANDROID_LOG_DEBUG));
-            }
-            property_set(key + base_offset, "");
-        }
+  // All combinations of level and tag and global properties
+  for (size_t i = 0; i < (sizeof(levels) / sizeof(levels[0])); ++i) {
+    if (levels[i].level == -2) {
+      continue;
     }
+    for (size_t j = 0; j < (sizeof(levels) / sizeof(levels[0])); ++j) {
+      char buf[2];
+      buf[0] = levels[j].type;
+      buf[1] = '\0';
 
-    // reset parms
-    snprintf(key, sizeof(key), "%s%s", log_namespace, tag);
-    property_set(key, hold[0]);
-    property_set(key + base_offset, hold[1]);
-    strcpy(key, log_namespace);
-    key[sizeof(log_namespace) - 2] = '\0';
-    property_set(key, hold[2]);
-    property_set(key + base_offset, hold[3]);
+      snprintf(key, sizeof(key), "%s%s", log_namespace, tag);
+      fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r", i, j, key,
+              buf);
+      usleep(20000);
+      property_set(key, buf);
+      bool android_log_is_loggable = __android_log_is_loggable_len(
+          levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG);
+      if ((levels[i].level < levels[j].level) || (levels[j].level == -1) ||
+          ((levels[i].level < ANDROID_LOG_DEBUG) && (levels[j].level == -2))) {
+        if (android_log_is_loggable) {
+          fprintf(stderr, "\n");
+        }
+        EXPECT_FALSE(android_log_is_loggable);
+        for (size_t k = 10; k; --k) {
+          EXPECT_FALSE(__android_log_is_loggable_len(
+              levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG));
+        }
+      } else {
+        if (!android_log_is_loggable) {
+          fprintf(stderr, "\n");
+        }
+        EXPECT_TRUE(android_log_is_loggable);
+        for (size_t k = 10; k; --k) {
+          EXPECT_TRUE(__android_log_is_loggable_len(
+              levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG));
+        }
+      }
+      usleep(20000);
+      property_set(key, "");
+
+      fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r", i, j,
+              key + base_offset, buf);
+      property_set(key + base_offset, buf);
+      android_log_is_loggable = __android_log_is_loggable_len(
+          levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG);
+      if ((levels[i].level < levels[j].level) || (levels[j].level == -1) ||
+          ((levels[i].level < ANDROID_LOG_DEBUG) && (levels[j].level == -2))) {
+        if (android_log_is_loggable) {
+          fprintf(stderr, "\n");
+        }
+        EXPECT_FALSE(android_log_is_loggable);
+        for (size_t k = 10; k; --k) {
+          EXPECT_FALSE(__android_log_is_loggable_len(
+              levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG));
+        }
+      } else {
+        if (!android_log_is_loggable) {
+          fprintf(stderr, "\n");
+        }
+        EXPECT_TRUE(android_log_is_loggable);
+        for (size_t k = 10; k; --k) {
+          EXPECT_TRUE(__android_log_is_loggable_len(
+              levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG));
+        }
+      }
+      usleep(20000);
+      property_set(key + base_offset, "");
+
+      strcpy(key, log_namespace);
+      key[sizeof(log_namespace) - 2] = '\0';
+      fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r", i, j, key,
+              buf);
+      property_set(key, buf);
+      android_log_is_loggable = __android_log_is_loggable_len(
+          levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG);
+      if ((levels[i].level < levels[j].level) || (levels[j].level == -1) ||
+          ((levels[i].level < ANDROID_LOG_DEBUG) && (levels[j].level == -2))) {
+        if (android_log_is_loggable) {
+          fprintf(stderr, "\n");
+        }
+        EXPECT_FALSE(android_log_is_loggable);
+        for (size_t k = 10; k; --k) {
+          EXPECT_FALSE(__android_log_is_loggable_len(
+              levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG));
+        }
+      } else {
+        if (!android_log_is_loggable) {
+          fprintf(stderr, "\n");
+        }
+        EXPECT_TRUE(android_log_is_loggable);
+        for (size_t k = 10; k; --k) {
+          EXPECT_TRUE(__android_log_is_loggable_len(
+              levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG));
+        }
+      }
+      usleep(20000);
+      property_set(key, "");
+
+      fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r", i, j,
+              key + base_offset, buf);
+      property_set(key + base_offset, buf);
+      android_log_is_loggable = __android_log_is_loggable_len(
+          levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG);
+      if ((levels[i].level < levels[j].level) || (levels[j].level == -1) ||
+          ((levels[i].level < ANDROID_LOG_DEBUG) && (levels[j].level == -2))) {
+        if (android_log_is_loggable) {
+          fprintf(stderr, "\n");
+        }
+        EXPECT_FALSE(android_log_is_loggable);
+        for (size_t k = 10; k; --k) {
+          EXPECT_FALSE(__android_log_is_loggable_len(
+              levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG));
+        }
+      } else {
+        if (!android_log_is_loggable) {
+          fprintf(stderr, "\n");
+        }
+        EXPECT_TRUE(android_log_is_loggable);
+        for (size_t k = 10; k; --k) {
+          EXPECT_TRUE(__android_log_is_loggable_len(
+              levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG));
+        }
+      }
+      usleep(20000);
+      property_set(key + base_offset, "");
+    }
+  }
+
+  // All combinations of level and tag properties, but with global set to INFO
+  strcpy(key, log_namespace);
+  key[sizeof(log_namespace) - 2] = '\0';
+  usleep(20000);
+  property_set(key, "I");
+  snprintf(key, sizeof(key), "%s%s", log_namespace, tag);
+  for (size_t i = 0; i < (sizeof(levels) / sizeof(levels[0])); ++i) {
+    if (levels[i].level == -2) {
+      continue;
+    }
+    for (size_t j = 0; j < (sizeof(levels) / sizeof(levels[0])); ++j) {
+      char buf[2];
+      buf[0] = levels[j].type;
+      buf[1] = '\0';
+
+      fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r", i, j, key,
+              buf);
+      usleep(20000);
+      property_set(key, buf);
+      bool android_log_is_loggable = __android_log_is_loggable_len(
+          levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG);
+      if ((levels[i].level < levels[j].level) || (levels[j].level == -1) ||
+          ((levels[i].level < ANDROID_LOG_INFO)  // Yes INFO
+           && (levels[j].level == -2))) {
+        if (android_log_is_loggable) {
+          fprintf(stderr, "\n");
+        }
+        EXPECT_FALSE(android_log_is_loggable);
+        for (size_t k = 10; k; --k) {
+          EXPECT_FALSE(__android_log_is_loggable_len(
+              levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG));
+        }
+      } else {
+        if (!android_log_is_loggable) {
+          fprintf(stderr, "\n");
+        }
+        EXPECT_TRUE(android_log_is_loggable);
+        for (size_t k = 10; k; --k) {
+          EXPECT_TRUE(__android_log_is_loggable_len(
+              levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG));
+        }
+      }
+      usleep(20000);
+      property_set(key, "");
+
+      fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r", i, j,
+              key + base_offset, buf);
+      property_set(key + base_offset, buf);
+      android_log_is_loggable = __android_log_is_loggable_len(
+          levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG);
+      if ((levels[i].level < levels[j].level) || (levels[j].level == -1) ||
+          ((levels[i].level < ANDROID_LOG_INFO)  // Yes INFO
+           && (levels[j].level == -2))) {
+        if (android_log_is_loggable) {
+          fprintf(stderr, "\n");
+        }
+        EXPECT_FALSE(android_log_is_loggable);
+        for (size_t k = 10; k; --k) {
+          EXPECT_FALSE(__android_log_is_loggable_len(
+              levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG));
+        }
+      } else {
+        if (!android_log_is_loggable) {
+          fprintf(stderr, "\n");
+        }
+        EXPECT_TRUE(android_log_is_loggable);
+        for (size_t k = 10; k; --k) {
+          EXPECT_TRUE(__android_log_is_loggable_len(
+              levels[i].level, tag, strlen(tag), ANDROID_LOG_DEBUG));
+        }
+      }
+      usleep(20000);
+      property_set(key + base_offset, "");
+    }
+  }
+
+  // reset parms
+  snprintf(key, sizeof(key), "%s%s", log_namespace, tag);
+  usleep(20000);
+  property_set(key, hold[0]);
+  property_set(key + base_offset, hold[1]);
+  strcpy(key, log_namespace);
+  key[sizeof(log_namespace) - 2] = '\0';
+  property_set(key, hold[2]);
+  property_set(key + base_offset, hold[3]);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
 }
+#endif  // ENABLE_FLAKY_TESTS
+
+#ifdef ENABLE_FLAKY_TESTS
+// Following tests the specific issues surrounding error handling wrt logd.
+// Kills logd and toss all collected data, equivalent to logcat -b all -c,
+// except we also return errors to the logging callers.
+#ifdef __ANDROID__
+// helper to liblog.enoent to count end-to-end matching logging messages.
+static int count_matching_ts(log_time ts) {
+  usleep(1000000);
+
+  pid_t pid = getpid();
+
+  struct logger_list* logger_list =
+      android_logger_list_open(LOG_ID_EVENTS, ANDROID_LOG_NONBLOCK, 1000, pid);
+
+  int count = 0;
+  if (logger_list == NULL) return count;
+
+  for (;;) {
+    log_msg log_msg;
+    if (android_logger_list_read(logger_list, &log_msg) <= 0) break;
+
+    if (log_msg.entry.len != sizeof(android_log_event_long_t)) continue;
+    if (log_msg.id() != LOG_ID_EVENTS) continue;
+
+    android_log_event_long_t* eventData;
+    eventData = reinterpret_cast<android_log_event_long_t*>(log_msg.msg());
+    if (!eventData) continue;
+    if (eventData->payload.type != EVENT_TYPE_LONG) continue;
+
+    log_time tx(reinterpret_cast<char*>(&eventData->payload.data));
+    if (ts != tx) continue;
+
+    // found event message with matching timestamp signature in payload
+    ++count;
+  }
+  android_logger_list_close(logger_list);
+
+  return count;
+}
+
+TEST(liblog, enoent) {
+#ifdef __ANDROID__
+  if (getuid() != 0) {
+    GTEST_SKIP() << "Skipping test, must be run as root.";
+    return;
+  }
+
+  log_time ts(CLOCK_MONOTONIC);
+  EXPECT_LT(0, __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)));
+  EXPECT_EQ(1, count_matching_ts(ts));
+
+  // This call will fail unless we are root, beware of any
+  // test prior to this one playing with setuid and causing interference.
+  // We need to run before these tests so that they do not interfere with
+  // this test.
+  //
+  // Stopping the logger can affect some other test's expectations as they
+  // count on the log buffers filled with existing content, and this
+  // effectively does a logcat -c emptying it.  So we want this test to be
+  // as near as possible to the bottom of the file.  For example
+  // liblog.android_logger_get_ is one of those tests that has no recourse
+  // and that would be adversely affected by emptying the log if it was run
+  // right after this test.
+  system("stop logd");
+  usleep(1000000);
+
+  // A clean stop like we are testing returns -ENOENT, but in the _real_
+  // world we could get -ENOTCONN or -ECONNREFUSED depending on timing.
+  // Alas we can not test these other return values; accept that they
+  // are treated equally within the open-retry logic in liblog.
+  ts = log_time(CLOCK_MONOTONIC);
+  int ret = __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts));
+  std::string content = android::base::StringPrintf(
+      "__android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)) = %d %s\n",
+      ret, (ret <= 0) ? strerror(-ret) : "(content sent)");
+  EXPECT_TRUE(ret == -ENOENT || ret == -ENOTCONN || ret == -ECONNREFUSED) << content;
+  ret = __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts));
+  content = android::base::StringPrintf(
+      "__android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)) = %d %s\n",
+      ret, (ret <= 0) ? strerror(-ret) : "(content sent)");
+  EXPECT_TRUE(ret == -ENOENT || ret == -ENOTCONN || ret == -ECONNREFUSED) << content;
+  EXPECT_EQ(0, count_matching_ts(ts));
+
+  system("start logd");
+  usleep(1000000);
+
+  EXPECT_EQ(0, count_matching_ts(ts));
+
+  ts = log_time(CLOCK_MONOTONIC);
+  EXPECT_LT(0, __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)));
+  EXPECT_EQ(1, count_matching_ts(ts));
+
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+#endif  // __ANDROID__
+#endif  // ENABLE_FLAKY_TESTS
+
+// Below this point we run risks of setuid(AID_SYSTEM) which may affect others.
+
+#ifdef ENABLE_FLAKY_TESTS
+// Do not retest properties, and cannot log into LOG_ID_SECURITY
+TEST(liblog, __security) {
+#ifdef __ANDROID__
+  static const char persist_key[] = "persist.logd.security";
+  static const char readonly_key[] = "ro.organization_owned";
+  // A silly default value that can never be in readonly_key so
+  // that it can be determined the property is not set.
+  static const char nothing_val[] = "_NOTHING_TO_SEE_HERE_";
+  char persist[PROP_VALUE_MAX];
+  char persist_hold[PROP_VALUE_MAX];
+  char readonly[PROP_VALUE_MAX];
+
+  // First part of this test requires the test itself to have the appropriate
+  // permissions. If we do not have them, we can not override them, so we
+  // bail rather than give a failing grade.
+  property_get(persist_key, persist, "");
+  fprintf(stderr, "INFO: getprop %s -> %s\n", persist_key, persist);
+  strncpy(persist_hold, persist, PROP_VALUE_MAX);
+  property_get(readonly_key, readonly, nothing_val);
+  fprintf(stderr, "INFO: getprop %s -> %s\n", readonly_key, readonly);
+
+  if (!strcmp(readonly, nothing_val)) {
+    // Lets check if we can set the value (we should not be allowed to do so)
+    EXPECT_FALSE(__android_log_security());
+    fprintf(stderr, "WARNING: setting ro.organization_owned to a domain\n");
+    static const char domain[] = "com.google.android.SecOps.DeviceOwner";
+    EXPECT_NE(0, property_set(readonly_key, domain));
+    useconds_t total_time = 0;
+    static const useconds_t seconds = 1000000;
+    static const useconds_t max_time = 5 * seconds;  // not going to happen
+    static const useconds_t rest = 20 * 1000;
+    for (; total_time < max_time; total_time += rest) {
+      usleep(rest);  // property system does not guarantee performance.
+      property_get(readonly_key, readonly, nothing_val);
+      if (!strcmp(readonly, domain)) {
+        if (total_time > rest) {
+          fprintf(stderr, "INFO: took %u.%06u seconds to set property\n",
+                  (unsigned)(total_time / seconds),
+                  (unsigned)(total_time % seconds));
+        }
+        break;
+      }
+    }
+    EXPECT_STRNE(domain, readonly);
+  }
+
+  if (!strcasecmp(readonly, "false") || !readonly[0] ||
+      !strcmp(readonly, nothing_val)) {
+    // not enough permissions to run tests surrounding persist.logd.security
+    EXPECT_FALSE(__android_log_security());
+    return;
+  }
+
+  if (!strcasecmp(persist, "true")) {
+    EXPECT_TRUE(__android_log_security());
+  } else {
+    EXPECT_FALSE(__android_log_security());
+  }
+  property_set(persist_key, "TRUE");
+  property_get(persist_key, persist, "");
+  uid_t uid = getuid();
+  gid_t gid = getgid();
+  bool perm = (gid == AID_ROOT) || (uid == AID_ROOT);
+  EXPECT_STREQ(perm ? "TRUE" : persist_hold, persist);
+  if (!strcasecmp(persist, "true")) {
+    EXPECT_TRUE(__android_log_security());
+  } else {
+    EXPECT_FALSE(__android_log_security());
+  }
+  property_set(persist_key, "FALSE");
+  property_get(persist_key, persist, "");
+  EXPECT_STREQ(perm ? "FALSE" : persist_hold, persist);
+  if (!strcasecmp(persist, "true")) {
+    EXPECT_TRUE(__android_log_security());
+  } else {
+    EXPECT_FALSE(__android_log_security());
+  }
+  property_set(persist_key, "true");
+  property_get(persist_key, persist, "");
+  EXPECT_STREQ(perm ? "true" : persist_hold, persist);
+  if (!strcasecmp(persist, "true")) {
+    EXPECT_TRUE(__android_log_security());
+  } else {
+    EXPECT_FALSE(__android_log_security());
+  }
+  property_set(persist_key, "false");
+  property_get(persist_key, persist, "");
+  EXPECT_STREQ(perm ? "false" : persist_hold, persist);
+  if (!strcasecmp(persist, "true")) {
+    EXPECT_TRUE(__android_log_security());
+  } else {
+    EXPECT_FALSE(__android_log_security());
+  }
+  property_set(persist_key, "");
+  property_get(persist_key, persist, "");
+  EXPECT_STREQ(perm ? "" : persist_hold, persist);
+  if (!strcasecmp(persist, "true")) {
+    EXPECT_TRUE(__android_log_security());
+  } else {
+    EXPECT_FALSE(__android_log_security());
+  }
+  property_set(persist_key, persist_hold);
+  property_get(persist_key, persist, "");
+  EXPECT_STREQ(persist_hold, persist);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog, __security_buffer) {
+#ifdef __ANDROID__
+  struct logger_list* logger_list;
+  android_event_long_t buffer;
+
+  static const char persist_key[] = "persist.logd.security";
+  char persist[PROP_VALUE_MAX];
+  bool set_persist = false;
+  bool allow_security = false;
+
+  if (__android_log_security()) {
+    allow_security = true;
+  } else {
+    property_get(persist_key, persist, "");
+    if (strcasecmp(persist, "true")) {
+      property_set(persist_key, "TRUE");
+      if (__android_log_security()) {
+        allow_security = true;
+        set_persist = true;
+      } else {
+        property_set(persist_key, persist);
+      }
+    }
+  }
+
+  if (!allow_security) {
+    fprintf(stderr,
+            "WARNING: "
+            "security buffer disabled, bypassing end-to-end test\n");
+
+    log_time ts(CLOCK_MONOTONIC);
+
+    buffer.type = EVENT_TYPE_LONG;
+    buffer.data = *(static_cast<uint64_t*>((void*)&ts));
+
+    // expect failure!
+    ASSERT_GE(0, __android_log_security_bwrite(0, &buffer, sizeof(buffer)));
+
+    return;
+  }
+
+  /* Matches clientHasLogCredentials() in logd */
+  uid_t uid = getuid();
+  gid_t gid = getgid();
+  bool clientHasLogCredentials = true;
+  if ((uid != AID_SYSTEM) && (uid != AID_ROOT) && (uid != AID_LOG) &&
+      (gid != AID_SYSTEM) && (gid != AID_ROOT) && (gid != AID_LOG)) {
+    uid_t euid = geteuid();
+    if ((euid != AID_SYSTEM) && (euid != AID_ROOT) && (euid != AID_LOG)) {
+      gid_t egid = getegid();
+      if ((egid != AID_SYSTEM) && (egid != AID_ROOT) && (egid != AID_LOG)) {
+        int num_groups = getgroups(0, NULL);
+        if (num_groups > 0) {
+          gid_t groups[num_groups];
+          num_groups = getgroups(num_groups, groups);
+          while (num_groups > 0) {
+            if (groups[num_groups - 1] == AID_LOG) {
+              break;
+            }
+            --num_groups;
+          }
+        }
+        if (num_groups <= 0) {
+          clientHasLogCredentials = false;
+        }
+      }
+    }
+  }
+  if (!clientHasLogCredentials) {
+    fprintf(stderr,
+            "WARNING: "
+            "not in system context, bypassing end-to-end test\n");
+
+    log_time ts(CLOCK_MONOTONIC);
+
+    buffer.type = EVENT_TYPE_LONG;
+    buffer.data = *(static_cast<uint64_t*>((void*)&ts));
+
+    // expect failure!
+    ASSERT_GE(0, __android_log_security_bwrite(0, &buffer, sizeof(buffer)));
+
+    return;
+  }
+
+  EXPECT_EQ(0, setuid(AID_SYSTEM));  // only one that can read security buffer
+
+  uid = getuid();
+  gid = getgid();
+  pid_t pid = getpid();
+
+  ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(LOG_ID_SECURITY, ANDROID_LOG_NONBLOCK,
+                                                              1000, pid)));
+
+  log_time ts(CLOCK_MONOTONIC);
+
+  buffer.type = EVENT_TYPE_LONG;
+  buffer.data = *(static_cast<uint64_t*>((void*)&ts));
+
+  ASSERT_LT(0, __android_log_security_bwrite(0, &buffer, sizeof(buffer)));
+  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 != sizeof(android_log_event_long_t)) ||
+        (log_msg.id() != LOG_ID_SECURITY)) {
+      continue;
+    }
+
+    android_log_event_long_t* eventData;
+    eventData = reinterpret_cast<android_log_event_long_t*>(log_msg.msg());
+
+    if (!eventData || (eventData->payload.type != EVENT_TYPE_LONG)) {
+      continue;
+    }
+
+    log_time tx(reinterpret_cast<char*>(&eventData->payload.data));
+    if (ts == tx) {
+      ++count;
+    }
+  }
+
+  if (set_persist) {
+    property_set(persist_key, persist);
+  }
+
+  android_logger_list_close(logger_list);
+
+  bool clientHasSecurityCredentials = (uid == AID_SYSTEM) || (gid == AID_SYSTEM);
+  if (!clientHasSecurityCredentials) {
+    fprintf(stderr,
+            "WARNING: "
+            "not system, content submitted but can not check end-to-end\n");
+  }
+  EXPECT_EQ(clientHasSecurityCredentials ? 1 : 0, count);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+#endif  // ENABLE_FLAKY_TESTS
+
+#ifdef __ANDROID__
+static void android_errorWriteWithInfoLog_helper(int tag, const char* subtag, int uid,
+                                                 const char* payload, int data_len) {
+  auto write_function = [&] {
+    int ret = android_errorWriteWithInfoLog(tag, subtag, uid, payload, data_len);
+    ASSERT_LT(0, ret);
+  };
+
+  auto check_function = [&](log_msg log_msg, bool* found) {
+    char* event_data = log_msg.msg();
+    char* original = event_data;
+
+    // Tag
+    auto* event_header = reinterpret_cast<android_event_header_t*>(event_data);
+    event_data += sizeof(android_event_header_t);
+    if (event_header->tag != tag) {
+      return;
+    }
+
+    // List type
+    auto* event_list = reinterpret_cast<android_event_list_t*>(event_data);
+    ASSERT_EQ(EVENT_TYPE_LIST, event_list->type);
+    ASSERT_EQ(3, event_list->element_count);
+    event_data += sizeof(android_event_list_t);
+
+    // Element #1: string type for subtag
+    auto* event_string_subtag = reinterpret_cast<android_event_string_t*>(event_data);
+    ASSERT_EQ(EVENT_TYPE_STRING, event_string_subtag->type);
+    int32_t subtag_len = strlen(subtag);
+    if (subtag_len > 32) {
+      subtag_len = 32;
+    }
+    ASSERT_EQ(subtag_len, event_string_subtag->length);
+    if (memcmp(subtag, &event_string_subtag->data, subtag_len)) {
+      return;
+    }
+    event_data += sizeof(android_event_string_t) + subtag_len;
+
+    // Element #2: int type for uid
+    auto* event_int_uid = reinterpret_cast<android_event_int_t*>(event_data);
+    ASSERT_EQ(EVENT_TYPE_INT, event_int_uid->type);
+    ASSERT_EQ(uid, event_int_uid->data);
+    event_data += sizeof(android_event_int_t);
+
+    // Element #3: string type for data
+    auto* event_string_data = reinterpret_cast<android_event_string_t*>(event_data);
+    ASSERT_EQ(EVENT_TYPE_STRING, event_string_data->type);
+    int32_t message_data_len = event_string_data->length;
+    if (data_len < 512) {
+      ASSERT_EQ(data_len, message_data_len);
+    }
+    if (memcmp(payload, &event_string_data->data, message_data_len) != 0) {
+      return;
+    }
+    event_data += sizeof(android_event_string_t);
+
+    if (data_len >= 512) {
+      event_data += message_data_len;
+      // 4 bytes for the tag, and max_payload_buf should be truncated.
+      ASSERT_LE(4 + 512, event_data - original);       // worst expectations
+      ASSERT_GT(4 + data_len, event_data - original);  // must be truncated
+    }
+    *found = true;
+  };
+
+  RunLogTests(LOG_ID_EVENTS, write_function, check_function);
+}
+#endif
+
+// Make multiple tests and re-tests orthogonal to prevent falsing.
+#ifdef TEST_LOGGER
+#define UNIQUE_TAG(X) \
+  (0x12340000 + (((X) + sizeof(int) + sizeof(void*)) << 8) + TEST_LOGGER)
+#else
+#define UNIQUE_TAG(X) \
+  (0x12340000 + (((X) + sizeof(int) + sizeof(void*)) << 8) + 0xBA)
+#endif
+
+TEST(liblog, android_errorWriteWithInfoLog__android_logger_list_read__typical) {
+#ifdef __ANDROID__
+  android_errorWriteWithInfoLog_helper(UNIQUE_TAG(1), "test-subtag", -1, max_payload_buf, 200);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog,
+     android_errorWriteWithInfoLog__android_logger_list_read__data_too_large) {
+#ifdef __ANDROID__
+  android_errorWriteWithInfoLog_helper(UNIQUE_TAG(2), "test-subtag", -1, max_payload_buf,
+                                       sizeof(max_payload_buf));
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog,
+     android_errorWriteWithInfoLog__android_logger_list_read__null_data) {
+#ifdef __ANDROID__
+  int retval_android_errorWriteWithinInfoLog =
+      android_errorWriteWithInfoLog(UNIQUE_TAG(3), "test-subtag", -1, nullptr, 200);
+  ASSERT_GT(0, retval_android_errorWriteWithinInfoLog);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog,
+     android_errorWriteWithInfoLog__android_logger_list_read__subtag_too_long) {
+#ifdef __ANDROID__
+  android_errorWriteWithInfoLog_helper(
+      UNIQUE_TAG(4), "abcdefghijklmnopqrstuvwxyz now i know my abc", -1, max_payload_buf, 200);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog, __android_log_bswrite_and_print___max) {
+  bswrite_test(max_payload_buf);
+}
+
+TEST(liblog, __android_log_buf_write_and_print__max) {
+  buf_write_test(max_payload_buf);
+}
+
+TEST(liblog, android_errorWriteLog__android_logger_list_read__success) {
+#ifdef __ANDROID__
+  int kTag = UNIQUE_TAG(5);
+  const char* kSubTag = "test-subtag";
+
+  auto write_function = [&] {
+    int retval_android_errorWriteLog = android_errorWriteLog(kTag, kSubTag);
+    ASSERT_LT(0, retval_android_errorWriteLog);
+  };
+
+  auto check_function = [&](log_msg log_msg, bool* found) {
+    char* event_data = log_msg.msg();
+
+    // Tag
+    auto* event_header = reinterpret_cast<android_event_header_t*>(event_data);
+    event_data += sizeof(android_event_header_t);
+    if (event_header->tag != kTag) {
+      return;
+    }
+
+    // List type
+    auto* event_list = reinterpret_cast<android_event_list_t*>(event_data);
+    ASSERT_EQ(EVENT_TYPE_LIST, event_list->type);
+    ASSERT_EQ(3, event_list->element_count);
+    event_data += sizeof(android_event_list_t);
+
+    // Element #1: string type for subtag
+    auto* event_string_subtag = reinterpret_cast<android_event_string_t*>(event_data);
+    ASSERT_EQ(EVENT_TYPE_STRING, event_string_subtag->type);
+    int32_t subtag_len = strlen(kSubTag);
+    ASSERT_EQ(subtag_len, event_string_subtag->length);
+    if (memcmp(kSubTag, &event_string_subtag->data, subtag_len) == 0) {
+      *found = true;
+    }
+  };
+
+  RunLogTests(LOG_ID_EVENTS, write_function, check_function);
+
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog, android_errorWriteLog__android_logger_list_read__null_subtag) {
+#ifdef __ANDROID__
+  EXPECT_LT(android_errorWriteLog(UNIQUE_TAG(6), nullptr), 0);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+// Do not retest logger list handling
+#ifdef __ANDROID__
+static int is_real_element(int type) {
+  return ((type == EVENT_TYPE_INT) || (type == EVENT_TYPE_LONG) ||
+          (type == EVENT_TYPE_STRING) || (type == EVENT_TYPE_FLOAT));
+}
+
+static int android_log_buffer_to_string(const char* msg, size_t len,
+                                        char* strOut, size_t strOutLen) {
+  android_log_context context = create_android_log_parser(msg, len);
+  android_log_list_element elem;
+  bool overflow = false;
+  /* Reserve 1 byte for null terminator. */
+  size_t origStrOutLen = strOutLen--;
+
+  if (!context) {
+    return -EBADF;
+  }
+
+  memset(&elem, 0, sizeof(elem));
+
+  size_t outCount;
+
+  do {
+    elem = android_log_read_next(context);
+    switch ((int)elem.type) {
+      case EVENT_TYPE_LIST:
+        if (strOutLen == 0) {
+          overflow = true;
+        } else {
+          *strOut++ = '[';
+          strOutLen--;
+        }
+        break;
+
+      case EVENT_TYPE_LIST_STOP:
+        if (strOutLen == 0) {
+          overflow = true;
+        } else {
+          *strOut++ = ']';
+          strOutLen--;
+        }
+        break;
+
+      case EVENT_TYPE_INT:
+        /*
+         * snprintf also requires room for the null terminator, which
+         * we don't care about  but we have allocated enough room for
+         * that
+         */
+        outCount = snprintf(strOut, strOutLen + 1, "%" PRId32, elem.data.int32);
+        if (outCount <= strOutLen) {
+          strOut += outCount;
+          strOutLen -= outCount;
+        } else {
+          overflow = true;
+        }
+        break;
+
+      case EVENT_TYPE_LONG:
+        /*
+         * snprintf also requires room for the null terminator, which
+         * we don't care about but we have allocated enough room for
+         * that
+         */
+        outCount = snprintf(strOut, strOutLen + 1, "%" PRId64, elem.data.int64);
+        if (outCount <= strOutLen) {
+          strOut += outCount;
+          strOutLen -= outCount;
+        } else {
+          overflow = true;
+        }
+        break;
+
+      case EVENT_TYPE_FLOAT:
+        /*
+         * snprintf also requires room for the null terminator, which
+         * we don't care about but we have allocated enough room for
+         * that
+         */
+        outCount = snprintf(strOut, strOutLen + 1, "%f", elem.data.float32);
+        if (outCount <= strOutLen) {
+          strOut += outCount;
+          strOutLen -= outCount;
+        } else {
+          overflow = true;
+        }
+        break;
+
+      default:
+        elem.complete = true;
+        break;
+
+      case EVENT_TYPE_UNKNOWN:
+#if 0  // Ideal purity in the test, we want to complain about UNKNOWN showing up
+            if (elem.complete) {
+                break;
+            }
+#endif
+        elem.data.string = const_cast<char*>("<unknown>");
+        elem.len = strlen(elem.data.string);
+        FALLTHROUGH_INTENDED;
+      case EVENT_TYPE_STRING:
+        if (elem.len <= strOutLen) {
+          memcpy(strOut, elem.data.string, elem.len);
+          strOut += elem.len;
+          strOutLen -= elem.len;
+        } else if (strOutLen > 0) {
+          /* copy what we can */
+          memcpy(strOut, elem.data.string, strOutLen);
+          strOut += strOutLen;
+          strOutLen = 0;
+          overflow = true;
+        }
+        break;
+    }
+
+    if (elem.complete) {
+      break;
+    }
+    /* Determine whether to put a comma or not. */
+    if (!overflow &&
+        (is_real_element(elem.type) || (elem.type == EVENT_TYPE_LIST_STOP))) {
+      android_log_list_element next = android_log_peek_next(context);
+      if (!next.complete &&
+          (is_real_element(next.type) || (next.type == EVENT_TYPE_LIST))) {
+        if (strOutLen == 0) {
+          overflow = true;
+        } else {
+          *strOut++ = ',';
+          strOutLen--;
+        }
+      }
+    }
+  } while ((elem.type != EVENT_TYPE_UNKNOWN) && !overflow && !elem.complete);
+
+  android_log_destroy(&context);
+
+  if (overflow) {
+    if (strOutLen < origStrOutLen) {
+      /* leave an indicator */
+      *(strOut - 1) = '!';
+    } else {
+      /* nothing was written at all */
+      *strOut++ = '!';
+    }
+  }
+  *strOut++ = '\0';
+
+  if ((elem.type == EVENT_TYPE_UNKNOWN) && !elem.complete) {
+    fprintf(stderr, "Binary log entry conversion failed\n");
+    return -EINVAL;
+  }
+
+  return 0;
+}
+#endif  // __ANDROID__
+
+#ifdef __ANDROID__
+static const char* event_test_int32(uint32_t tag, size_t& expected_len) {
+  android_log_context ctx;
+
+  EXPECT_TRUE(NULL != (ctx = create_android_logger(tag)));
+  if (!ctx) {
+    return NULL;
+  }
+  EXPECT_LE(0, android_log_write_int32(ctx, 0x40302010));
+  EXPECT_LE(0, android_log_write_list(ctx, LOG_ID_EVENTS));
+  EXPECT_LE(0, android_log_destroy(&ctx));
+  EXPECT_TRUE(NULL == ctx);
+
+  expected_len = sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint32_t);
+
+  return "1076895760";
+}
+
+static const char* event_test_int64(uint32_t tag, size_t& expected_len) {
+  android_log_context ctx;
+
+  EXPECT_TRUE(NULL != (ctx = create_android_logger(tag)));
+  if (!ctx) {
+    return NULL;
+  }
+  EXPECT_LE(0, android_log_write_int64(ctx, 0x8070605040302010));
+  EXPECT_LE(0, android_log_write_list(ctx, LOG_ID_EVENTS));
+  EXPECT_LE(0, android_log_destroy(&ctx));
+  EXPECT_TRUE(NULL == ctx);
+
+  expected_len = sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint64_t);
+
+  return "-9191740941672636400";
+}
+
+static const char* event_test_list_int64(uint32_t tag, size_t& expected_len) {
+  android_log_context ctx;
+
+  EXPECT_TRUE(NULL != (ctx = create_android_logger(tag)));
+  if (!ctx) {
+    return NULL;
+  }
+  EXPECT_LE(0, android_log_write_list_begin(ctx));
+  EXPECT_LE(0, android_log_write_int64(ctx, 0x8070605040302010));
+  EXPECT_LE(0, android_log_write_list_end(ctx));
+  EXPECT_LE(0, android_log_write_list(ctx, LOG_ID_EVENTS));
+  EXPECT_LE(0, android_log_destroy(&ctx));
+  EXPECT_TRUE(NULL == ctx);
+
+  expected_len = sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint8_t) +
+                 sizeof(uint8_t) + sizeof(uint64_t);
+
+  return "[-9191740941672636400]";
+}
+
+static const char* event_test_simple_automagic_list(uint32_t tag,
+                                                    size_t& expected_len) {
+  android_log_context ctx;
+
+  EXPECT_TRUE(NULL != (ctx = create_android_logger(tag)));
+  if (!ctx) {
+    return NULL;
+  }
+  // The convenience API where we allow a simple list to be
+  // created without explicit begin or end calls.
+  EXPECT_LE(0, android_log_write_int32(ctx, 0x40302010));
+  EXPECT_LE(0, android_log_write_int64(ctx, 0x8070605040302010));
+  EXPECT_LE(0, android_log_write_list(ctx, LOG_ID_EVENTS));
+  EXPECT_LE(0, android_log_destroy(&ctx));
+  EXPECT_TRUE(NULL == ctx);
+
+  expected_len = sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint8_t) +
+                 sizeof(uint8_t) + sizeof(uint32_t) + sizeof(uint8_t) +
+                 sizeof(uint64_t);
+
+  return "[1076895760,-9191740941672636400]";
+}
+
+static const char* event_test_list_empty(uint32_t tag, size_t& expected_len) {
+  android_log_context ctx;
+
+  EXPECT_TRUE(NULL != (ctx = create_android_logger(tag)));
+  if (!ctx) {
+    return NULL;
+  }
+  EXPECT_LE(0, android_log_write_list_begin(ctx));
+  EXPECT_LE(0, android_log_write_list_end(ctx));
+  EXPECT_LE(0, android_log_write_list(ctx, LOG_ID_EVENTS));
+  EXPECT_LE(0, android_log_destroy(&ctx));
+  EXPECT_TRUE(NULL == ctx);
+
+  expected_len = sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint8_t);
+
+  return "[]";
+}
+
+static const char* event_test_complex_nested_list(uint32_t tag,
+                                                  size_t& expected_len) {
+  android_log_context ctx;
+
+  EXPECT_TRUE(NULL != (ctx = create_android_logger(tag)));
+  if (!ctx) {
+    return NULL;
+  }
+
+  EXPECT_LE(0, android_log_write_list_begin(ctx));  // [
+  EXPECT_LE(0, android_log_write_int32(ctx, 0x01020304));
+  EXPECT_LE(0, android_log_write_int64(ctx, 0x0102030405060708));
+  EXPECT_LE(0, android_log_write_string8(ctx, "Hello World"));
+  EXPECT_LE(0, android_log_write_list_begin(ctx));  // [
+  EXPECT_LE(0, android_log_write_int32(ctx, 1));
+  EXPECT_LE(0, android_log_write_int32(ctx, 2));
+  EXPECT_LE(0, android_log_write_int32(ctx, 3));
+  EXPECT_LE(0, android_log_write_int32(ctx, 4));
+  EXPECT_LE(0, android_log_write_list_end(ctx));  // ]
+  EXPECT_LE(0, android_log_write_float32(ctx, 1.0102030405060708));
+  EXPECT_LE(0, android_log_write_list_end(ctx));  // ]
+
+  //
+  // This one checks for the automagic list creation because a list
+  // begin and end was missing for it! This is actually an <oops> corner
+  // case, and not the behavior we morally support. The automagic API is to
+  // allow for a simple case of a series of objects in a single list. e.g.
+  //   int32,int32,int32,string -> [int32,int32,int32,string]
+  //
+  EXPECT_LE(0, android_log_write_string8(ctx, "dlroW olleH"));
+
+  EXPECT_LE(0, android_log_write_list(ctx, LOG_ID_EVENTS));
+  EXPECT_LE(0, android_log_destroy(&ctx));
+  EXPECT_TRUE(NULL == ctx);
+
+  expected_len = sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint8_t) +
+                 sizeof(uint8_t) + sizeof(uint8_t) + sizeof(uint8_t) +
+                 sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint64_t) +
+                 sizeof(uint8_t) + sizeof(uint32_t) + sizeof("Hello World") -
+                 1 + sizeof(uint8_t) + sizeof(uint8_t) +
+                 4 * (sizeof(uint8_t) + sizeof(uint32_t)) + sizeof(uint8_t) +
+                 sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint32_t) +
+                 sizeof("dlroW olleH") - 1;
+
+  return "[[16909060,72623859790382856,Hello World,[1,2,3,4],1.010203],dlroW "
+         "olleH]";
+}
+
+static const char* event_test_7_level_prefix(uint32_t tag,
+                                             size_t& expected_len) {
+  android_log_context ctx;
+
+  EXPECT_TRUE(NULL != (ctx = create_android_logger(tag)));
+  if (!ctx) {
+    return NULL;
+  }
+  EXPECT_LE(0, android_log_write_list_begin(ctx));
+  EXPECT_LE(0, android_log_write_list_begin(ctx));
+  EXPECT_LE(0, android_log_write_list_begin(ctx));
+  EXPECT_LE(0, android_log_write_list_begin(ctx));
+  EXPECT_LE(0, android_log_write_list_begin(ctx));
+  EXPECT_LE(0, android_log_write_list_begin(ctx));
+  EXPECT_LE(0, android_log_write_list_begin(ctx));
+  EXPECT_LE(0, android_log_write_int32(ctx, 1));
+  EXPECT_LE(0, android_log_write_list_end(ctx));
+  EXPECT_LE(0, android_log_write_int32(ctx, 2));
+  EXPECT_LE(0, android_log_write_list_end(ctx));
+  EXPECT_LE(0, android_log_write_int32(ctx, 3));
+  EXPECT_LE(0, android_log_write_list_end(ctx));
+  EXPECT_LE(0, android_log_write_int32(ctx, 4));
+  EXPECT_LE(0, android_log_write_list_end(ctx));
+  EXPECT_LE(0, android_log_write_int32(ctx, 5));
+  EXPECT_LE(0, android_log_write_list_end(ctx));
+  EXPECT_LE(0, android_log_write_int32(ctx, 6));
+  EXPECT_LE(0, android_log_write_list_end(ctx));
+  EXPECT_LE(0, android_log_write_int32(ctx, 7));
+  EXPECT_LE(0, android_log_write_list_end(ctx));
+  EXPECT_LE(0, android_log_write_list(ctx, LOG_ID_EVENTS));
+  EXPECT_LE(0, android_log_destroy(&ctx));
+  EXPECT_TRUE(NULL == ctx);
+
+  expected_len = sizeof(uint32_t) + 7 * (sizeof(uint8_t) + sizeof(uint8_t) +
+                                         sizeof(uint8_t) + sizeof(uint32_t));
+
+  return "[[[[[[[1],2],3],4],5],6],7]";
+}
+
+static const char* event_test_7_level_suffix(uint32_t tag,
+                                             size_t& expected_len) {
+  android_log_context ctx;
+
+  EXPECT_TRUE(NULL != (ctx = create_android_logger(tag)));
+  if (!ctx) {
+    return NULL;
+  }
+  EXPECT_LE(0, android_log_write_list_begin(ctx));
+  EXPECT_LE(0, android_log_write_int32(ctx, 1));
+  EXPECT_LE(0, android_log_write_list_begin(ctx));
+  EXPECT_LE(0, android_log_write_int32(ctx, 2));
+  EXPECT_LE(0, android_log_write_list_begin(ctx));
+  EXPECT_LE(0, android_log_write_int32(ctx, 3));
+  EXPECT_LE(0, android_log_write_list_begin(ctx));
+  EXPECT_LE(0, android_log_write_int32(ctx, 4));
+  EXPECT_LE(0, android_log_write_list_begin(ctx));
+  EXPECT_LE(0, android_log_write_int32(ctx, 5));
+  EXPECT_LE(0, android_log_write_list_begin(ctx));
+  EXPECT_LE(0, android_log_write_int32(ctx, 6));
+  EXPECT_LE(0, android_log_write_list_end(ctx));
+  EXPECT_LE(0, android_log_write_list_end(ctx));
+  EXPECT_LE(0, android_log_write_list_end(ctx));
+  EXPECT_LE(0, android_log_write_list_end(ctx));
+  EXPECT_LE(0, android_log_write_list_end(ctx));
+  EXPECT_LE(0, android_log_write_list_end(ctx));
+  EXPECT_LE(0, android_log_write_list(ctx, LOG_ID_EVENTS));
+  EXPECT_LE(0, android_log_destroy(&ctx));
+  EXPECT_TRUE(NULL == ctx);
+
+  expected_len = sizeof(uint32_t) + 6 * (sizeof(uint8_t) + sizeof(uint8_t) +
+                                         sizeof(uint8_t) + sizeof(uint32_t));
+
+  return "[1,[2,[3,[4,[5,[6]]]]]]";
+}
+
+static const char* event_test_android_log_error_write(uint32_t tag,
+                                                      size_t& expected_len) {
+  EXPECT_LE(
+      0, __android_log_error_write(tag, "Hello World", 42, "dlroW olleH", 11));
+
+  expected_len = sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint8_t) +
+                 sizeof(uint8_t) + sizeof(uint32_t) + sizeof("Hello World") -
+                 1 + sizeof(uint8_t) + sizeof(uint32_t) + sizeof(uint8_t) +
+                 sizeof(uint32_t) + sizeof("dlroW olleH") - 1;
+
+  return "[Hello World,42,dlroW olleH]";
+}
+
+static const char* event_test_android_log_error_write_null(uint32_t tag,
+                                                           size_t& expected_len) {
+  EXPECT_LE(0, __android_log_error_write(tag, "Hello World", 42, NULL, 0));
+
+  expected_len = sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint8_t) +
+                 sizeof(uint8_t) + sizeof(uint32_t) + sizeof("Hello World") -
+                 1 + sizeof(uint8_t) + sizeof(uint32_t) + sizeof(uint8_t) +
+                 sizeof(uint32_t) + sizeof("") - 1;
+
+  return "[Hello World,42,]";
+}
+
+// make sure all user buffers are flushed
+static void print_barrier() {
+  std::cout.flush();
+  fflush(stdout);
+  std::cerr.flush();
+  fflush(stderr);  // everything else is paranoia ...
+}
+
+static void create_android_logger(const char* (*fn)(uint32_t tag,
+                                                    size_t& expected_len)) {
+  size_t expected_len;
+  const char* expected_string;
+  auto write_function = [&] {
+    expected_string = (*fn)(1005, expected_len);
+    ASSERT_NE(nullptr, expected_string);
+  };
+
+  pid_t pid = getpid();
+  auto check_function = [&](log_msg log_msg, bool* found) {
+    if (static_cast<size_t>(log_msg.entry.len) != expected_len) {
+      return;
+    }
+
+    char* eventData = log_msg.msg();
+
+    AndroidLogFormat* logformat = android_log_format_new();
+    EXPECT_TRUE(NULL != logformat);
+    AndroidLogEntry entry;
+    char msgBuf[1024];
+    int processBinaryLogBuffer =
+        android_log_processBinaryLogBuffer(&log_msg.entry, &entry, nullptr, msgBuf, sizeof(msgBuf));
+    EXPECT_EQ(0, processBinaryLogBuffer);
+    if (processBinaryLogBuffer == 0) {
+      int line_overhead = 20;
+      if (pid > 99999) ++line_overhead;
+      if (pid > 999999) ++line_overhead;
+      print_barrier();
+      int printLogLine =
+          android_log_printLogLine(logformat, fileno(stderr), &entry);
+      print_barrier();
+      EXPECT_EQ(line_overhead + (int)strlen(expected_string), printLogLine);
+    }
+    android_log_format_free(logformat);
+
+    // test buffer reading API
+    int buffer_to_string = -1;
+    if (eventData) {
+      auto* event_header = reinterpret_cast<android_event_header_t*>(eventData);
+      eventData += sizeof(android_event_header_t);
+      snprintf(msgBuf, sizeof(msgBuf), "I/[%" PRId32 "]", event_header->tag);
+      print_barrier();
+      fprintf(stderr, "%-10s(%5u): ", msgBuf, pid);
+      memset(msgBuf, 0, sizeof(msgBuf));
+      buffer_to_string =
+          android_log_buffer_to_string(eventData, log_msg.entry.len, msgBuf, sizeof(msgBuf));
+      fprintf(stderr, "%s\n", msgBuf);
+      print_barrier();
+    }
+    EXPECT_EQ(0, buffer_to_string);
+    EXPECT_STREQ(expected_string, msgBuf);
+    *found = true;
+  };
+
+  RunLogTests(LOG_ID_EVENTS, write_function, check_function);
+}
+#endif
+
+TEST(liblog, create_android_logger_int32) {
+#ifdef __ANDROID__
+  create_android_logger(event_test_int32);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog, create_android_logger_int64) {
+#ifdef __ANDROID__
+  create_android_logger(event_test_int64);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog, create_android_logger_list_int64) {
+#ifdef __ANDROID__
+  create_android_logger(event_test_list_int64);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog, create_android_logger_simple_automagic_list) {
+#ifdef __ANDROID__
+  create_android_logger(event_test_simple_automagic_list);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog, create_android_logger_list_empty) {
+#ifdef __ANDROID__
+  create_android_logger(event_test_list_empty);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog, create_android_logger_complex_nested_list) {
+#ifdef __ANDROID__
+  create_android_logger(event_test_complex_nested_list);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog, create_android_logger_7_level_prefix) {
+#ifdef __ANDROID__
+  create_android_logger(event_test_7_level_prefix);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog, create_android_logger_7_level_suffix) {
+#ifdef __ANDROID__
+  create_android_logger(event_test_7_level_suffix);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog, create_android_logger_android_log_error_write) {
+#ifdef __ANDROID__
+  create_android_logger(event_test_android_log_error_write);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog, create_android_logger_android_log_error_write_null) {
+#ifdef __ANDROID__
+  create_android_logger(event_test_android_log_error_write_null);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(liblog, create_android_logger_overflow) {
+  android_log_context ctx;
+
+  EXPECT_TRUE(NULL != (ctx = create_android_logger(1005)));
+  if (ctx) {
+    for (size_t i = 0; i < ANDROID_MAX_LIST_NEST_DEPTH; ++i) {
+      EXPECT_LE(0, android_log_write_list_begin(ctx));
+    }
+    EXPECT_GT(0, android_log_write_list_begin(ctx));
+    /* One more for good measure, must be permanently unhappy */
+    EXPECT_GT(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_destroy(&ctx));
+    EXPECT_TRUE(NULL == ctx);
+  }
+
+  ASSERT_TRUE(NULL != (ctx = create_android_logger(1005)));
+  for (size_t i = 0; i < ANDROID_MAX_LIST_NEST_DEPTH; ++i) {
+    EXPECT_LE(0, android_log_write_list_begin(ctx));
+    EXPECT_LE(0, android_log_write_int32(ctx, i));
+  }
+  EXPECT_GT(0, android_log_write_list_begin(ctx));
+  /* One more for good measure, must be permanently unhappy */
+  EXPECT_GT(0, android_log_write_list_begin(ctx));
+  EXPECT_LE(0, android_log_destroy(&ctx));
+  ASSERT_TRUE(NULL == ctx);
+}
+
+#ifdef ENABLE_FLAKY_TESTS
+#ifdef __ANDROID__
+#ifndef NO_PSTORE
+static const char __pmsg_file[] =
+    "/data/william-shakespeare/MuchAdoAboutNothing.txt";
+#endif /* NO_PSTORE */
+#endif
+
+TEST(liblog, __android_log_pmsg_file_write) {
+#ifdef __ANDROID__
+#ifndef NO_PSTORE
+  __android_log_close();
+  if (getuid() == AID_ROOT) {
+    tested__android_log_close = true;
+    bool pmsgActiveAfter__android_log_close = isPmsgActive();
+    bool logdwActiveAfter__android_log_close = isLogdwActive();
+    EXPECT_FALSE(pmsgActiveAfter__android_log_close);
+    EXPECT_FALSE(logdwActiveAfter__android_log_close);
+  } else if (!tested__android_log_close) {
+    fprintf(stderr, "WARNING: can not test __android_log_close()\n");
+  }
+  int return__android_log_pmsg_file_write = __android_log_pmsg_file_write(
+      LOG_ID_CRASH, ANDROID_LOG_VERBOSE, __pmsg_file, max_payload_buf,
+      sizeof(max_payload_buf));
+  EXPECT_LT(0, return__android_log_pmsg_file_write);
+  if (return__android_log_pmsg_file_write == -ENOMEM) {
+    fprintf(stderr,
+            "Kernel does not have space allocated to pmsg pstore driver "
+            "configured\n");
+  } else if (!return__android_log_pmsg_file_write) {
+    fprintf(stderr,
+            "Reboot, ensure file %s matches\n"
+            "with liblog.__android_log_msg_file_read test\n",
+            __pmsg_file);
+  }
+  bool pmsgActiveAfter__android_pmsg_file_write;
+  bool logdwActiveAfter__android_pmsg_file_write;
+  if (getuid() == AID_ROOT) {
+    pmsgActiveAfter__android_pmsg_file_write = isPmsgActive();
+    logdwActiveAfter__android_pmsg_file_write = isLogdwActive();
+    EXPECT_FALSE(pmsgActiveAfter__android_pmsg_file_write);
+    EXPECT_FALSE(logdwActiveAfter__android_pmsg_file_write);
+  }
+  EXPECT_LT(
+      0, __android_log_buf_print(LOG_ID_MAIN, ANDROID_LOG_INFO,
+                                 "TEST__android_log_pmsg_file_write", "main"));
+  if (getuid() == AID_ROOT) {
+    bool pmsgActiveAfter__android_log_buf_print = isPmsgActive();
+    bool logdwActiveAfter__android_log_buf_print = isLogdwActive();
+    EXPECT_TRUE(pmsgActiveAfter__android_log_buf_print);
+    EXPECT_TRUE(logdwActiveAfter__android_log_buf_print);
+  }
+  EXPECT_LT(0, __android_log_pmsg_file_write(LOG_ID_CRASH, ANDROID_LOG_VERBOSE,
+                                             __pmsg_file, max_payload_buf,
+                                             sizeof(max_payload_buf)));
+  if (getuid() == AID_ROOT) {
+    pmsgActiveAfter__android_pmsg_file_write = isPmsgActive();
+    logdwActiveAfter__android_pmsg_file_write = isLogdwActive();
+    EXPECT_TRUE(pmsgActiveAfter__android_pmsg_file_write);
+    EXPECT_TRUE(logdwActiveAfter__android_pmsg_file_write);
+  }
+#else  /* NO_PSTORE */
+  GTEST_LOG_(INFO) << "This test does nothing because of NO_PSTORE.\n";
+#endif /* NO_PSTORE */
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+#ifdef __ANDROID__
+#ifndef NO_PSTORE
+static ssize_t __pmsg_fn(log_id_t logId, char prio, const char* filename,
+                         const char* buf, size_t len, void* arg) {
+  EXPECT_TRUE(NULL == arg);
+  EXPECT_EQ(LOG_ID_CRASH, logId);
+  EXPECT_EQ(ANDROID_LOG_VERBOSE, prio);
+  EXPECT_FALSE(NULL == strstr(__pmsg_file, filename));
+  EXPECT_EQ(len, sizeof(max_payload_buf));
+  EXPECT_STREQ(max_payload_buf, buf);
+
+  ++signaled;
+  if ((len != sizeof(max_payload_buf)) || strcmp(max_payload_buf, buf)) {
+    fprintf(stderr, "comparison fails on content \"%s\"\n", buf);
+  }
+  return arg || (LOG_ID_CRASH != logId) || (ANDROID_LOG_VERBOSE != prio) ||
+                 !strstr(__pmsg_file, filename) ||
+                 (len != sizeof(max_payload_buf)) ||
+                 !!strcmp(max_payload_buf, buf)
+             ? -ENOEXEC
+             : 1;
+}
+#endif /* NO_PSTORE */
+#endif
+
+TEST(liblog, __android_log_pmsg_file_read) {
+#ifdef __ANDROID__
+#ifndef NO_PSTORE
+  signaled = 0;
+
+  __android_log_close();
+  if (getuid() == AID_ROOT) {
+    tested__android_log_close = true;
+    bool pmsgActiveAfter__android_log_close = isPmsgActive();
+    bool logdwActiveAfter__android_log_close = isLogdwActive();
+    EXPECT_FALSE(pmsgActiveAfter__android_log_close);
+    EXPECT_FALSE(logdwActiveAfter__android_log_close);
+  } else if (!tested__android_log_close) {
+    fprintf(stderr, "WARNING: can not test __android_log_close()\n");
+  }
+
+  ssize_t ret = __android_log_pmsg_file_read(LOG_ID_CRASH, ANDROID_LOG_VERBOSE,
+                                             __pmsg_file, __pmsg_fn, NULL);
+
+  if (getuid() == AID_ROOT) {
+    bool pmsgActiveAfter__android_log_pmsg_file_read = isPmsgActive();
+    bool logdwActiveAfter__android_log_pmsg_file_read = isLogdwActive();
+    EXPECT_FALSE(pmsgActiveAfter__android_log_pmsg_file_read);
+    EXPECT_FALSE(logdwActiveAfter__android_log_pmsg_file_read);
+  }
+
+  if (ret == -ENOENT) {
+    fprintf(stderr,
+            "No pre-boot results of liblog.__android_log_mesg_file_write to "
+            "compare with,\n"
+            "false positive test result.\n");
+    return;
+  }
+
+  EXPECT_LT(0, ret);
+  EXPECT_EQ(1U, signaled);
+#else  /* NO_PSTORE */
+  GTEST_LOG_(INFO) << "This test does nothing because of NO_PSTORE.\n";
+#endif /* NO_PSTORE */
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+#endif  // ENABLE_FLAKY_TESTS
diff --git a/liblog/tests/log_id_test.cpp b/liblog/tests/log_id_test.cpp
new file mode 100644
index 0000000..9fb5a2c
--- /dev/null
+++ b/liblog/tests/log_id_test.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2013-2017 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 <inttypes.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+// Test the APIs in this standalone include file
+#include <log/log_id.h>
+
+// We do not want to include <android/log.h> to acquire ANDROID_LOG_INFO for
+// include file API purity.  We do however want to allow the _option_ that
+// log/log_id.h could include this file, or related content, in the future.
+#ifndef __android_LogPriority_defined
+#define ANDROID_LOG_INFO 4
+#endif
+
+TEST(liblog, log_id) {
+  int count = 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;
+    }
+    ++count;
+    fprintf(stderr, "log buffer %s\r", name);
+  }
+  ASSERT_EQ(LOG_ID_MAX, count);
+}
+
+TEST(liblog, __android_log_buf_print) {
+  EXPECT_LT(0, __android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO,
+                                       "TEST__android_log_buf_print", "radio"));
+  usleep(1000);
+  EXPECT_LT(0,
+            __android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO,
+                                    "TEST__android_log_buf_print", "system"));
+  usleep(1000);
+  EXPECT_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) {
+  EXPECT_LT(0, __android_log_buf_write(LOG_ID_RADIO, ANDROID_LOG_INFO,
+                                       "TEST__android_log_buf_write", "radio"));
+  usleep(1000);
+  EXPECT_LT(0,
+            __android_log_buf_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO,
+                                    "TEST__android_log_buf_write", "system"));
+  usleep(1000);
+  EXPECT_LT(0, __android_log_buf_write(LOG_ID_MAIN, ANDROID_LOG_INFO,
+                                       "TEST__android_log_buf_write", "main"));
+  usleep(1000);
+}
+
+static void* ConcurrentPrintFn(void* arg) {
+  int ret = __android_log_buf_print(
+      LOG_ID_MAIN, ANDROID_LOG_INFO, "TEST__android_log_print",
+      "Concurrent %" PRIuPTR, reinterpret_cast<uintptr_t>(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 = 1;
+  for (i = 0; i < NUM_CONCURRENT; i++) {
+    void* result;
+    ASSERT_EQ(0, pthread_join(t[i], &result));
+    int this_result = reinterpret_cast<uintptr_t>(result);
+    if ((0 < ret) && (ret != this_result)) {
+      ret = this_result;
+    }
+  }
+  ASSERT_LT(0, ret);
+}
diff --git a/liblog/tests/log_radio_test.cpp b/liblog/tests/log_radio_test.cpp
new file mode 100644
index 0000000..fa1255e
--- /dev/null
+++ b/liblog/tests/log_radio_test.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 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 <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <gtest/gtest.h>
+// Test the APIs in this standalone include file
+#include <log/log_radio.h>
+
+TEST(liblog, RLOG) {
+  static const char content[] = "log_radio.h";
+  static const char content_false[] = "log_radio.h false";
+
+// ratelimit content to 10/s to keep away from spam filters
+// do not send identical content together to keep away from spam filters
+
+#undef LOG_TAG
+#define LOG_TAG "TEST__RLOGV"
+  RLOGV(content);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__RLOGD"
+  RLOGD(content);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__RLOGI"
+  RLOGI(content);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__RLOGW"
+  RLOGW(content);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__RLOGE"
+  RLOGE(content);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__RLOGV"
+  RLOGV_IF(true, content);
+  usleep(100000);
+  RLOGV_IF(false, content_false);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__RLOGD"
+  RLOGD_IF(true, content);
+  usleep(100000);
+  RLOGD_IF(false, content_false);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__RLOGI"
+  RLOGI_IF(true, content);
+  usleep(100000);
+  RLOGI_IF(false, content_false);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__RLOGW"
+  RLOGW_IF(true, content);
+  usleep(100000);
+  RLOGW_IF(false, content_false);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__RLOGE"
+  RLOGE_IF(true, content);
+  usleep(100000);
+  RLOGE_IF(false, content_false);
+
+#ifdef __ANDROID__
+  // give time for content to long-path through logger
+  sleep(1);
+
+  std::string buf = android::base::StringPrintf(
+      "logcat -b radio --pid=%u -d -s"
+      " TEST__RLOGV TEST__RLOGD TEST__RLOGI TEST__RLOGW TEST__RLOGE",
+      (unsigned)getpid());
+  FILE* fp = popen(buf.c_str(), "re");
+  int count = 0;
+  int count_false = 0;
+  if (fp) {
+    if (!android::base::ReadFdToString(fileno(fp), &buf)) buf = "";
+    pclose(fp);
+    for (size_t pos = 0; (pos = buf.find(content, pos)) != std::string::npos;
+         ++pos) {
+      ++count;
+    }
+    for (size_t pos = 0;
+         (pos = buf.find(content_false, pos)) != std::string::npos; ++pos) {
+      ++count_false;
+    }
+  }
+  EXPECT_EQ(0, count_false);
+#if LOG_NDEBUG
+  ASSERT_EQ(8, count);
+#else
+  ASSERT_EQ(10, count);
+#endif
+
+#else
+  GTEST_LOG_(INFO) << "This test does not test end-to-end.\n";
+#endif
+}
diff --git a/liblog/tests/log_read_test.cpp b/liblog/tests/log_read_test.cpp
new file mode 100644
index 0000000..7acd363
--- /dev/null
+++ b/liblog/tests/log_read_test.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2013-2017 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 <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android/log.h>  // minimal logging API
+#include <gtest/gtest.h>
+#include <log/log_properties.h>
+// Test the APIs in this standalone include file
+#include <log/log_read.h>
+// Do not use anything in log/log_time.h despite side effects of the above.
+#include <private/android_logger.h>
+
+using android::base::GetBoolProperty;
+
+TEST(liblog, android_logger_get_) {
+#ifdef __ANDROID__
+  // This test assumes the log buffers are filled with noise from
+  // normal operations. It will fail if done immediately after a
+  // logcat -c.
+  struct logger_list* logger_list = android_logger_list_alloc(0, 0, 0);
+
+  for (int i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) {
+    log_id_t id = static_cast<log_id_t>(i);
+    std::string name = android_log_id_to_name(id);
+    fprintf(stderr, "log buffer %s\r", name.c_str());
+    struct logger* logger;
+    EXPECT_TRUE(NULL != (logger = android_logger_open(logger_list, id)));
+    EXPECT_EQ(id, android_logger_get_id(logger));
+    ssize_t get_log_size = android_logger_get_log_size(logger);
+    /* security buffer is allowed to be denied */
+    if (name != "security") {
+      EXPECT_GT(get_log_size, 0);
+      // crash buffer is allowed to be empty, that is actually healthy!
+      // stats buffer is no longer in use.
+      if (name == "crash" || name == "stats") {
+        continue;
+      }
+
+      // kernel buffer is empty if ro.logd.kernel is false
+      if (name == "kernel" && !GetBoolProperty("ro.logd.kernel", false)) {
+        continue;
+      }
+
+      EXPECT_LE(0, android_logger_get_log_readable_size(logger));
+    } else {
+      EXPECT_NE(0, get_log_size);
+      if (get_log_size < 0) {
+        EXPECT_GT(0, android_logger_get_log_readable_size(logger));
+      } else {
+        EXPECT_LE(0, android_logger_get_log_readable_size(logger));
+      }
+    }
+  }
+
+  android_logger_list_close(logger_list);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
diff --git a/liblog/tests/log_system_test.cpp b/liblog/tests/log_system_test.cpp
new file mode 100644
index 0000000..13f026d
--- /dev/null
+++ b/liblog/tests/log_system_test.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 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 <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <gtest/gtest.h>
+// Test the APIs in this standalone include file
+#include <log/log_system.h>
+
+TEST(liblog, SLOG) {
+  static const char content[] = "log_system.h";
+  static const char content_false[] = "log_system.h false";
+
+// ratelimit content to 10/s to keep away from spam filters
+// do not send identical content together to keep away from spam filters
+
+#undef LOG_TAG
+#define LOG_TAG "TEST__SLOGV"
+  SLOGV(content);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__SLOGD"
+  SLOGD(content);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__SLOGI"
+  SLOGI(content);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__SLOGW"
+  SLOGW(content);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__SLOGE"
+  SLOGE(content);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__SLOGV"
+  SLOGV_IF(true, content);
+  usleep(100000);
+  SLOGV_IF(false, content_false);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__SLOGD"
+  SLOGD_IF(true, content);
+  usleep(100000);
+  SLOGD_IF(false, content_false);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__SLOGI"
+  SLOGI_IF(true, content);
+  usleep(100000);
+  SLOGI_IF(false, content_false);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__SLOGW"
+  SLOGW_IF(true, content);
+  usleep(100000);
+  SLOGW_IF(false, content_false);
+  usleep(100000);
+#undef LOG_TAG
+#define LOG_TAG "TEST__SLOGE"
+  SLOGE_IF(true, content);
+  usleep(100000);
+  SLOGE_IF(false, content_false);
+
+#ifdef __ANDROID__
+  // give time for content to long-path through logger
+  sleep(1);
+
+  std::string buf = android::base::StringPrintf(
+      "logcat -b system --pid=%u -d -s"
+      " TEST__SLOGV TEST__SLOGD TEST__SLOGI TEST__SLOGW TEST__SLOGE",
+      (unsigned)getpid());
+  FILE* fp = popen(buf.c_str(), "re");
+  int count = 0;
+  int count_false = 0;
+  if (fp) {
+    if (!android::base::ReadFdToString(fileno(fp), &buf)) buf = "";
+    pclose(fp);
+    for (size_t pos = 0; (pos = buf.find(content, pos)) != std::string::npos;
+         ++pos) {
+      ++count;
+    }
+    for (size_t pos = 0;
+         (pos = buf.find(content_false, pos)) != std::string::npos; ++pos) {
+      ++count_false;
+    }
+  }
+  EXPECT_EQ(0, count_false);
+#if LOG_NDEBUG
+  ASSERT_EQ(8, count);
+#else
+  ASSERT_EQ(10, count);
+#endif
+
+#else
+  GTEST_LOG_(INFO) << "This test does not test end-to-end.\n";
+#endif
+}
diff --git a/liblog/fake_log_device.h b/liblog/tests/log_time_test.cpp
similarity index 62%
copy from liblog/fake_log_device.h
copy to liblog/tests/log_time_test.cpp
index 9d168cd..47fe594 100644
--- a/liblog/fake_log_device.h
+++ b/liblog/tests/log_time_test.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -14,15 +14,18 @@
  * limitations under the License.
  */
 
-#ifndef _LIBLOG_FAKE_LOG_DEVICE_H
-#define _LIBLOG_FAKE_LOG_DEVICE_H
+#include <time.h>
 
-#include <sys/types.h>
+#include <gtest/gtest.h>
+// Test the APIs in this standalone include file
+#include <log/log_time.h>
 
-struct iovec;
+TEST(liblog, log_time) {
+  struct timespec ts;
+  clock_gettime(CLOCK_MONOTONIC, &ts);
+  log_time tl(ts);
 
-int fakeLogOpen(const char *pathName, int flags);
-int fakeLogClose(int fd);
-ssize_t fakeLogWritev(int fd, const struct iovec* vector, int count);
-
-#endif // _LIBLOG_FAKE_LOG_DEVICE_H
+  EXPECT_EQ(tl, ts);
+  EXPECT_GE(tl, ts);
+  EXPECT_LE(tl, ts);
+}
diff --git a/liblog/tests/log_wrap_test.cpp b/liblog/tests/log_wrap_test.cpp
new file mode 100644
index 0000000..755898a
--- /dev/null
+++ b/liblog/tests/log_wrap_test.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2013-2017 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 <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/chrono_utils.h>
+#include <android-base/stringprintf.h>
+#include <android/log.h>  // minimal logging API
+#include <gtest/gtest.h>
+#include <log/log_properties.h>
+#include <log/log_read.h>
+#include <log/log_time.h>
+
+#ifdef __ANDROID__
+static void read_with_wrap() {
+  // Read the last line in the log to get a starting timestamp. We're assuming
+  // the log is not empty.
+  const int mode = ANDROID_LOG_NONBLOCK;
+  struct logger_list* logger_list =
+      android_logger_list_open(LOG_ID_MAIN, mode, 1000, 0);
+
+  ASSERT_NE(logger_list, nullptr);
+
+  log_msg log_msg;
+  int ret = android_logger_list_read(logger_list, &log_msg);
+  android_logger_list_close(logger_list);
+  ASSERT_GT(ret, 0);
+
+  log_time start(log_msg.entry.sec, log_msg.entry.nsec);
+  ASSERT_NE(start, log_time());
+
+  logger_list =
+      android_logger_list_alloc_time(mode | ANDROID_LOG_WRAP, start, 0);
+  ASSERT_NE(logger_list, nullptr);
+
+  struct logger* logger = android_logger_open(logger_list, LOG_ID_MAIN);
+  EXPECT_NE(logger, nullptr);
+  if (logger) {
+    android_logger_list_read(logger_list, &log_msg);
+  }
+
+  android_logger_list_close(logger_list);
+}
+#endif
+
+// b/64143705 confirm fixed
+TEST(liblog, wrap_mode_blocks) {
+#ifdef __ANDROID__
+  // The read call is expected to take up to 2 hours in the happy case.  There was a previous bug
+  // where it would take only 30 seconds due to an alarm() in logd_reader.cpp.  That alarm has been
+  // removed, so we check here that the read call blocks for a reasonable amount of time (5s).
+
+  struct sigaction ignore = {.sa_handler = [](int) { _exit(0); }};
+  struct sigaction old_sigaction;
+  sigaction(SIGALRM, &ignore, &old_sigaction);
+  alarm(5);
+
+  android::base::Timer timer;
+  read_with_wrap();
+
+  FAIL() << "read_with_wrap() should not return before the alarm is triggered.";
+
+  alarm(0);
+  sigaction(SIGALRM, &old_sigaction, nullptr);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
diff --git a/liblog/tests/logd_writer_test.cpp b/liblog/tests/logd_writer_test.cpp
new file mode 100644
index 0000000..b8e4726
--- /dev/null
+++ b/liblog/tests/logd_writer_test.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 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 <sys/un.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+
+using android::base::StringPrintf;
+using android::base::unique_fd;
+
+// logd_writer takes advantage of the fact that connect() can be called multiple times for a DGRAM
+// socket.  This tests for that behavior.
+TEST(liblog, multi_connect_dgram_socket) {
+#ifdef __ANDROID__
+  if (getuid() != 0) {
+    GTEST_SKIP() << "Skipping test, must be run as root.";
+    return;
+  }
+  auto temp_dir = TemporaryDir();
+  auto socket_path = StringPrintf("%s/test_socket", temp_dir.path);
+
+  unique_fd server_socket;
+
+  auto open_server_socket = [&] {
+    server_socket.reset(TEMP_FAILURE_RETRY(socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0)));
+    ASSERT_TRUE(server_socket.ok());
+
+    sockaddr_un server_sockaddr = {};
+    server_sockaddr.sun_family = AF_UNIX;
+    strlcpy(server_sockaddr.sun_path, socket_path.c_str(), sizeof(server_sockaddr.sun_path));
+    ASSERT_EQ(0,
+              TEMP_FAILURE_RETRY(bind(server_socket, reinterpret_cast<sockaddr*>(&server_sockaddr),
+                                      sizeof(server_sockaddr))));
+  };
+
+  // Open the server socket.
+  open_server_socket();
+
+  // Open the client socket.
+  auto client_socket =
+      unique_fd{TEMP_FAILURE_RETRY(socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0))};
+  ASSERT_TRUE(client_socket.ok());
+  sockaddr_un client_sockaddr = {};
+  client_sockaddr.sun_family = AF_UNIX;
+  strlcpy(client_sockaddr.sun_path, socket_path.c_str(), sizeof(client_sockaddr.sun_path));
+  ASSERT_EQ(0,
+            TEMP_FAILURE_RETRY(connect(client_socket, reinterpret_cast<sockaddr*>(&client_sockaddr),
+                                       sizeof(client_sockaddr))));
+
+  // Ensure that communication works.
+  constexpr static char kSmoke[] = "smoke test";
+  ssize_t smoke_len = sizeof(kSmoke);
+  ASSERT_EQ(smoke_len, TEMP_FAILURE_RETRY(write(client_socket, kSmoke, sizeof(kSmoke))));
+  char read_buf[512];
+  ASSERT_EQ(smoke_len, TEMP_FAILURE_RETRY(read(server_socket, read_buf, sizeof(read_buf))));
+  ASSERT_STREQ(kSmoke, read_buf);
+
+  // Close the server socket.
+  server_socket.reset();
+  ASSERT_EQ(0, unlink(socket_path.c_str())) << strerror(errno);
+
+  // Ensure that write() from the client returns an error since the server is closed.
+  ASSERT_EQ(-1, TEMP_FAILURE_RETRY(write(client_socket, kSmoke, sizeof(kSmoke))));
+  ASSERT_EQ(errno, ECONNREFUSED) << strerror(errno);
+
+  // Open the server socket again.
+  open_server_socket();
+
+  // Reconnect the same client socket.
+  ASSERT_EQ(0,
+            TEMP_FAILURE_RETRY(connect(client_socket, reinterpret_cast<sockaddr*>(&client_sockaddr),
+                                       sizeof(client_sockaddr))))
+      << strerror(errno);
+
+  // Ensure that communication works.
+  ASSERT_EQ(smoke_len, TEMP_FAILURE_RETRY(write(client_socket, kSmoke, sizeof(kSmoke))));
+  ASSERT_EQ(smoke_len, TEMP_FAILURE_RETRY(read(server_socket, read_buf, sizeof(read_buf))));
+  ASSERT_STREQ(kSmoke, read_buf);
+#else
+  GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
\ No newline at end of file
diff --git a/liblog/tests/logprint_test.cpp b/liblog/tests/logprint_test.cpp
new file mode 100644
index 0000000..72e53f9
--- /dev/null
+++ b/liblog/tests/logprint_test.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 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 <log/logprint.h>
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <log/log_read.h>
+
+size_t convertPrintable(char* p, const char* message, size_t messageLen);
+
+TEST(liblog, convertPrintable_ascii) {
+  auto input = "easy string, output same";
+  auto output_size = convertPrintable(nullptr, input, strlen(input));
+  EXPECT_EQ(output_size, strlen(input));
+
+  char output[output_size];
+
+  output_size = convertPrintable(output, input, strlen(input));
+  EXPECT_EQ(output_size, strlen(input));
+  EXPECT_STREQ(input, output);
+}
+
+TEST(liblog, convertPrintable_escapes) {
+  // Note that \t is not escaped.
+  auto input = "escape\a\b\t\v\f\r\\";
+  auto expected_output = "escape\\a\\b\t\\v\\f\\r\\\\";
+  auto output_size = convertPrintable(nullptr, input, strlen(input));
+  EXPECT_EQ(output_size, strlen(expected_output));
+
+  char output[output_size];
+
+  output_size = convertPrintable(output, input, strlen(input));
+  EXPECT_EQ(output_size, strlen(expected_output));
+  EXPECT_STREQ(expected_output, output);
+}
+
+TEST(liblog, convertPrintable_validutf8) {
+  auto input = u8"¢ह€𐍈";
+  auto output_size = convertPrintable(nullptr, input, strlen(input));
+  EXPECT_EQ(output_size, strlen(input));
+
+  char output[output_size];
+
+  output_size = convertPrintable(output, input, strlen(input));
+  EXPECT_EQ(output_size, strlen(input));
+  EXPECT_STREQ(input, output);
+}
+
+TEST(liblog, convertPrintable_invalidutf8) {
+  auto input = "\x80\xC2\x01\xE0\xA4\x06\xE0\x06\xF0\x90\x8D\x06\xF0\x90\x06\xF0\x0E";
+  auto expected_output =
+      "\\x80\\xC2\\x01\\xE0\\xA4\\x06\\xE0\\x06\\xF0\\x90\\x8D\\x06\\xF0\\x90\\x06\\xF0\\x0E";
+  auto output_size = convertPrintable(nullptr, input, strlen(input));
+  EXPECT_EQ(output_size, strlen(expected_output));
+
+  char output[output_size];
+
+  output_size = convertPrintable(output, input, strlen(input));
+  EXPECT_EQ(output_size, strlen(expected_output));
+  EXPECT_STREQ(expected_output, output);
+}
+
+TEST(liblog, convertPrintable_mixed) {
+  auto input =
+      u8"\x80\xC2¢ह€𐍈\x01\xE0\xA4\x06¢ह€𐍈\xE0\x06\a\b\xF0\x90¢ह€𐍈\x8D\x06\xF0\t\t\x90\x06\xF0\x0E";
+  auto expected_output =
+      u8"\\x80\\xC2¢ह€𐍈\\x01\\xE0\\xA4\\x06¢ह€𐍈\\xE0\\x06\\a\\b\\xF0\\x90¢ह€𐍈\\x8D\\x06\\xF0\t\t"
+      u8"\\x90\\x06\\xF0\\x0E";
+  auto output_size = convertPrintable(nullptr, input, strlen(input));
+  EXPECT_EQ(output_size, strlen(expected_output));
+
+  char output[output_size];
+
+  output_size = convertPrintable(output, input, strlen(input));
+  EXPECT_EQ(output_size, strlen(expected_output));
+  EXPECT_STREQ(expected_output, output);
+}
+
+TEST(liblog, log_print_different_header_size) {
+  constexpr int32_t kPid = 123;
+  constexpr uint32_t kTid = 456;
+  constexpr uint32_t kSec = 1000;
+  constexpr uint32_t kNsec = 999;
+  constexpr uint32_t kLid = LOG_ID_MAIN;
+  constexpr uint32_t kUid = 987;
+  constexpr char kPriority = ANDROID_LOG_ERROR;
+
+  auto create_buf = [](char* buf, size_t len, uint16_t hdr_size) {
+    memset(buf, 0, len);
+    logger_entry* header = reinterpret_cast<logger_entry*>(buf);
+    header->hdr_size = hdr_size;
+    header->pid = kPid;
+    header->tid = kTid;
+    header->sec = kSec;
+    header->nsec = kNsec;
+    header->lid = kLid;
+    header->uid = kUid;
+    char* message = buf + header->hdr_size;
+    uint16_t message_len = 0;
+    message[message_len++] = kPriority;
+    message[message_len++] = 'T';
+    message[message_len++] = 'a';
+    message[message_len++] = 'g';
+    message[message_len++] = '\0';
+    message[message_len++] = 'm';
+    message[message_len++] = 's';
+    message[message_len++] = 'g';
+    message[message_len++] = '!';
+    message[message_len++] = '\0';
+    header->len = message_len;
+  };
+
+  auto check_entry = [&](const AndroidLogEntry& entry) {
+    EXPECT_EQ(kSec, static_cast<uint32_t>(entry.tv_sec));
+    EXPECT_EQ(kNsec, static_cast<uint32_t>(entry.tv_nsec));
+    EXPECT_EQ(kPriority, entry.priority);
+    EXPECT_EQ(kUid, static_cast<uint32_t>(entry.uid));
+    EXPECT_EQ(kPid, entry.pid);
+    EXPECT_EQ(kTid, static_cast<uint32_t>(entry.tid));
+    EXPECT_STREQ("Tag", entry.tag);
+    EXPECT_EQ(4U, entry.tagLen);  // Apparently taglen includes the nullptr?
+    EXPECT_EQ(4U, entry.messageLen);
+    EXPECT_STREQ("msg!", entry.message);
+  };
+  alignas(logger_entry) char buf[LOGGER_ENTRY_MAX_LEN];
+  create_buf(buf, sizeof(buf), sizeof(logger_entry));
+
+  AndroidLogEntry entry_normal_size;
+  ASSERT_EQ(0,
+            android_log_processLogBuffer(reinterpret_cast<logger_entry*>(buf), &entry_normal_size));
+  check_entry(entry_normal_size);
+
+  create_buf(buf, sizeof(buf), sizeof(logger_entry) + 3);
+  AndroidLogEntry entry_odd_size;
+  ASSERT_EQ(0, android_log_processLogBuffer(reinterpret_cast<logger_entry*>(buf), &entry_odd_size));
+  check_entry(entry_odd_size);
+}
\ No newline at end of file
diff --git a/liblog/uio.c b/liblog/uio.c
deleted file mode 100644
index f77cc49..0000000
--- a/liblog/uio.c
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2007-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.
- */
-
-#if defined(_WIN32)
-
-#include <log/uio.h>
-#include <unistd.h>
-
-int  readv( int  fd, struct iovec*  vecs, int  count )
-{
-    int   total = 0;
-
-    for ( ; count > 0; count--, vecs++ ) {
-        char*  buf = vecs->iov_base;
-        int    len = vecs->iov_len;
-        
-        while (len > 0) {
-            int  ret = read( fd, buf, len );
-            if (ret < 0) {
-                if (total == 0)
-                    total = -1;
-                goto Exit;
-            }
-            if (ret == 0)
-                goto Exit;
-
-            total += ret;
-            buf   += ret;
-            len   -= ret;
-        }
-    }
-Exit:
-    return total;
-}
-
-int  writev( int  fd, const struct iovec*  vecs, int  count )
-{
-    int   total = 0;
-
-    for ( ; count > 0; count--, vecs++ ) {
-        const char*  buf = vecs->iov_base;
-        int          len = vecs->iov_len;
-        
-        while (len > 0) {
-            int  ret = write( fd, buf, len );
-            if (ret < 0) {
-                if (total == 0)
-                    total = -1;
-                goto Exit;
-            }
-            if (ret == 0)
-                goto Exit;
-
-            total += ret;
-            buf   += ret;
-            len   -= ret;
-        }
-    }
-Exit:    
-    return total;
-}
-
-#endif
diff --git a/liblog/fake_log_device.h b/liblog/uio.h
similarity index 62%
copy from liblog/fake_log_device.h
copy to liblog/uio.h
index 9d168cd..c85893c 100644
--- a/liblog/fake_log_device.h
+++ b/liblog/uio.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -14,15 +14,14 @@
  * limitations under the License.
  */
 
-#ifndef _LIBLOG_FAKE_LOG_DEVICE_H
-#define _LIBLOG_FAKE_LOG_DEVICE_H
+#pragma once
 
-#include <sys/types.h>
-
-struct iovec;
-
-int fakeLogOpen(const char *pathName, int flags);
-int fakeLogClose(int fd);
-ssize_t fakeLogWritev(int fd, const struct iovec* vector, int count);
-
-#endif // _LIBLOG_FAKE_LOG_DEVICE_H
+#if defined(_WIN32)
+#include <stddef.h>
+struct iovec {
+  void* iov_base;
+  size_t iov_len;
+};
+#else
+#include <sys/uio.h>
+#endif
diff --git a/logcat/Android.bp b/logcat/Android.bp
new file mode 100644
index 0000000..61fba59
--- /dev/null
+++ b/logcat/Android.bp
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2006 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.
+//
+
+cc_defaults {
+    name: "logcat_defaults",
+
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION=1",
+    ],
+    shared_libs: [
+        "libbase",
+        "libprocessgroup",
+    ],
+    static_libs: ["liblog"],
+    logtags: ["event.logtags"],
+}
+
+cc_binary {
+    name: "logcat",
+
+    defaults: ["logcat_defaults"],
+    srcs: [
+        "logcat.cpp",
+    ],
+}
+
+sh_binary {
+    name: "logcatd",
+    src: "logcatd",
+}
+
+sh_binary {
+    name: "logpersist.start",
+    src: "logpersist",
+    init_rc: ["logcatd.rc"],
+    required: ["logcatd"],
+    symlinks: [
+        "logpersist.stop",
+        "logpersist.cat",
+    ],
+}
diff --git a/logcat/Android.mk b/logcat/Android.mk
deleted file mode 100644
index 7115f9b..0000000
--- a/logcat/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2006-2014 The Android Open Source Project
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= logcat.cpp event.logtags
-
-LOCAL_SHARED_LIBRARIES := liblog libbase libcutils
-
-LOCAL_MODULE := logcat
-
-LOCAL_CFLAGS := -Werror
-
-include $(BUILD_EXECUTABLE)
-
-include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/logcat/OWNERS b/logcat/OWNERS
new file mode 100644
index 0000000..babbe4d
--- /dev/null
+++ b/logcat/OWNERS
@@ -0,0 +1 @@
+tomcherry@google.com
diff --git a/logcat/event.logtags b/logcat/event.logtags
index 909f8e2..93c3d6d 100644
--- a/logcat/event.logtags
+++ b/logcat/event.logtags
@@ -30,12 +30,15 @@
 # 4: Number of allocations
 # 5: Id
 # 6: Percent
+# s: Number of seconds (monotonic time)
 # Default value for data of type int/long is 2 (bytes).
 #
 # TODO: generate ".java" and ".h" files with integer constants from this file.
 
 # These are used for testing, do not modify without updating
 # tests/framework-tests/src/android/util/EventLogFunctionalTest.java.
+# system/core/liblog/tests/liblog_benchmark.cpp
+# system/core/liblog/tests/liblog_test.cpp
 42    answer (to life the universe etc|3)
 314   pi
 2718  e
@@ -64,8 +67,9 @@
 # ZygoteInit class preloading ends:
 3030 boot_progress_preload_end (time|2|3)
 
-# Dalvik VM
+# Dalvik VM / ART
 20003 dvm_lock_sample (process|3),(main|1|5),(thread|3),(time|1|3),(file|3),(line|1|5),(ownerfile|3),(ownerline|1|5),(sample_percent|1|6)
+20004 art_hidden_api_access (access_method|1),(flags|1),(class|3),(member|3),(type_signature|3)
 
 75000 sqlite_mem_alarm_current (current|1|2)
 75001 sqlite_mem_alarm_max (max|1|2)
@@ -109,13 +113,22 @@
 # graphics timestamp
 # 60100 - 60199 reserved for surfaceflinger
 
-# 0 for screen off, 1 for screen on, 2 for key-guard done
-70000 screen_toggled (screen_state|1|5)
+# audio
+# 61000 - 61199 reserved for audioserver
+
+# input
+# 62000 - 62199 reserved for inputflinger
+
+# com.android.server.policy
+# 70000 - 70199 reserved for PhoneWindowManager and other policies
 
 # aggregation service
 70200 aggregation (aggregation time|2|3)
 70201 aggregation_test (field1|1|2),(field2|1|2),(field3|1|2),(field4|1|2),(field5|1|2)
 
+# gms refuses to register this log tag, b/30156345
+70220 gms_unknown
+
 # libc failure logging
 80100 bionic_event_memcpy_buffer_overflow (uid|1)
 80105 bionic_event_strcat_buffer_overflow (uid|1)
@@ -135,7 +148,12 @@
 # libcore failure logging
 90100 exp_det_cert_pin_failure (certs|4)
 
+# 150000 - 160000 reserved for Android Automotive builds
+
 1397638484 snet_event_log (subtag|3) (uid|1) (message|3)
 
+# for events that go to stats log buffer
+1937006964 stats_log (atom_id|1|5),(data|4)
+
 # NOTE - the range 1000000-2000000 is reserved for partners and others who
 # want to define their own log tags without conflicting with the core platform.
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index e598bb8..6b7e016 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -1,293 +1,372 @@
-// Copyright 2006-2015 The Android Open Source Project
+/*
+ * Copyright (C) 2006-2017 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 <arpa/inet.h>
-#include <assert.h>
 #include <ctype.h>
 #include <dirent.h>
 #include <errno.h>
+#include <error.h>
 #include <fcntl.h>
+#include <getopt.h>
 #include <math.h>
 #include <sched.h>
-#include <signal.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/cdefs.h>
+#include <sys/ioctl.h>
 #include <sys/resource.h>
-#include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <time.h>
 #include <unistd.h>
 
 #include <memory>
+#include <regex>
+#include <set>
 #include <string>
+#include <utility>
+#include <vector>
 
-#include <base/file.h>
-#include <base/strings.h>
-#include <cutils/sched_policy.h>
-#include <cutils/sockets.h>
+#include <android-base/file.h>
+#include <android-base/macros.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <android/log.h>
 #include <log/event_tag_map.h>
-#include <log/log.h>
+#include <log/log_id.h>
 #include <log/log_read.h>
-#include <log/logd.h>
-#include <log/logger.h>
 #include <log/logprint.h>
-#include <utils/threads.h>
+#include <private/android_logger.h>
+#include <processgroup/sched_policy.h>
+#include <system/thread_defs.h>
 
 #define DEFAULT_MAX_ROTATED_LOGS 4
 
-static AndroidLogFormat * g_logformat;
+using android::base::Join;
+using android::base::ParseByteCount;
+using android::base::ParseUint;
+using android::base::Split;
+using android::base::StringPrintf;
+using android::base::WriteFully;
 
-/* logd prefixes records with a length field */
-#define RECORD_LENGTH_FIELD_SIZE_BYTES sizeof(uint32_t)
+class Logcat {
+  public:
+    int Run(int argc, char** argv);
 
-struct log_device_t {
-    const char* device;
-    bool binary;
-    struct logger *logger;
-    struct logger_list *logger_list;
-    bool printed;
+  private:
+    void RotateLogs();
+    void ProcessBuffer(struct log_msg* buf);
+    void PrintDividers(log_id_t log_id, bool print_dividers);
+    void SetupOutputAndSchedulingPolicy(bool blocking);
+    int SetLogFormat(const char* format_string);
 
-    log_device_t* next;
+    // Used for all options
+    android::base::unique_fd output_fd_{dup(STDOUT_FILENO)};
+    std::unique_ptr<AndroidLogFormat, decltype(&android_log_format_free)> logformat_{
+            android_log_format_new(), &android_log_format_free};
 
-    log_device_t(const char* d, bool b) {
-        device = d;
-        binary = b;
-        next = NULL;
-        printed = false;
-        logger = NULL;
-        logger_list = NULL;
-    }
+    // For logging to a file and log rotation
+    const char* output_file_name_ = nullptr;
+    size_t log_rotate_size_kb_ = 0;                       // 0 means "no log rotation"
+    size_t max_rotated_logs_ = DEFAULT_MAX_ROTATED_LOGS;  // 0 means "unbounded"
+    size_t out_byte_count_ = 0;
+
+    // For binary log buffers
+    int print_binary_ = 0;
+    std::unique_ptr<EventTagMap, decltype(&android_closeEventTagMap)> event_tag_map_{
+            nullptr, &android_closeEventTagMap};
+    bool has_opened_event_tag_map_ = false;
+
+    // For the related --regex, --max-count, --print
+    std::unique_ptr<std::regex> regex_;
+    size_t max_count_ = 0;  // 0 means "infinite"
+    size_t print_count_ = 0;
+    bool print_it_anyways_ = false;
+
+    // For PrintDividers()
+    log_id_t last_printed_id_ = LOG_ID_MAX;
+    bool printed_start_[LOG_ID_MAX] = {};
+
+    bool debug_ = false;
 };
 
-namespace android {
+#ifndef F2FS_IOC_SET_PIN_FILE
+#define F2FS_IOCTL_MAGIC       0xf5
+#define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32)
+#endif
 
-/* Global Variables */
+static int openLogFile(const char* pathname, size_t sizeKB) {
+    int fd = open(pathname, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP);
+    if (fd < 0) {
+        return fd;
+    }
 
-static const char * g_outputFileName = NULL;
-// 0 means "no log rotation"
-static size_t g_logRotateSizeKBytes = 0;
-// 0 means "unbounded"
-static size_t g_maxRotatedLogs = DEFAULT_MAX_ROTATED_LOGS;
-static int g_outFD = -1;
-static size_t g_outByteCount = 0;
-static int g_printBinary = 0;
-static int g_devCount = 0;                              // >1 means multiple
-
-__noreturn static void logcat_panic(bool showHelp, const char *fmt, ...) __printflike(2,3);
-
-static int openLogFile (const char *pathname)
-{
-    return open(pathname, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
+    // no need to check errors
+    __u32 set = 1;
+    ioctl(fd, F2FS_IOC_SET_PIN_FILE, &set);
+    fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, (sizeKB << 10));
+    return fd;
 }
 
-static void rotateLogs()
-{
-    int err;
-
-    // Can't rotate logs if we're not outputting to a file
-    if (g_outputFileName == NULL) {
+static void closeLogFile(const char* pathname) {
+    int fd = open(pathname, O_WRONLY | O_CLOEXEC);
+    if (fd == -1) {
         return;
     }
 
-    close(g_outFD);
+    // no need to check errors
+    __u32 set = 0;
+    ioctl(fd, F2FS_IOC_SET_PIN_FILE, &set);
+    close(fd);
+}
 
-    // Compute the maximum number of digits needed to count up to g_maxRotatedLogs in decimal.
-    // eg: g_maxRotatedLogs == 30 -> log10(30) == 1.477 -> maxRotationCountDigits == 2
-    int maxRotationCountDigits =
-            (g_maxRotatedLogs > 0) ? (int) (floor(log10(g_maxRotatedLogs) + 1)) : 0;
+void Logcat::RotateLogs() {
+    // Can't rotate logs if we're not outputting to a file
+    if (!output_file_name_) return;
 
-    for (int i = g_maxRotatedLogs ; i > 0 ; i--) {
-        char *file0, *file1;
+    output_fd_.reset();
 
-        asprintf(&file1, "%s.%.*d", g_outputFileName, maxRotationCountDigits, i);
+    // Compute the maximum number of digits needed to count up to
+    // maxRotatedLogs in decimal.  eg:
+    // maxRotatedLogs == 30
+    //   -> log10(30) == 1.477
+    //   -> maxRotationCountDigits == 2
+    int max_rotation_count_digits =
+            max_rotated_logs_ > 0 ? (int)(floor(log10(max_rotated_logs_) + 1)) : 0;
 
-        if (i - 1 == 0) {
-            asprintf(&file0, "%s", g_outputFileName);
+    for (int i = max_rotated_logs_; i > 0; i--) {
+        std::string file1 =
+                StringPrintf("%s.%.*d", output_file_name_, max_rotation_count_digits, i);
+
+        std::string file0;
+        if (!(i - 1)) {
+            file0 = output_file_name_;
         } else {
-            asprintf(&file0, "%s.%.*d", g_outputFileName, maxRotationCountDigits, i - 1);
+            file0 = StringPrintf("%s.%.*d", output_file_name_, max_rotation_count_digits, i - 1);
         }
 
-        if (!file0 || !file1) {
+        if (!file0.length() || !file1.length()) {
             perror("while rotating log files");
             break;
         }
 
-        err = rename(file0, file1);
+        closeLogFile(file0.c_str());
+
+        int err = rename(file0.c_str(), file1.c_str());
 
         if (err < 0 && errno != ENOENT) {
             perror("while rotating log files");
         }
-
-        free(file1);
-        free(file0);
     }
 
-    g_outFD = openLogFile(g_outputFileName);
+    output_fd_.reset(openLogFile(output_file_name_, log_rotate_size_kb_));
 
-    if (g_outFD < 0) {
-        logcat_panic(false, "couldn't open output file");
+    if (!output_fd_.ok()) {
+        error(EXIT_FAILURE, errno, "Couldn't open output file");
     }
 
-    g_outByteCount = 0;
-
+    out_byte_count_ = 0;
 }
 
-void printBinary(struct log_msg *buf)
-{
-    size_t size = buf->len();
-
-    TEMP_FAILURE_RETRY(write(g_outFD, buf, size));
-}
-
-static void processBuffer(log_device_t* dev, struct log_msg *buf)
-{
+void Logcat::ProcessBuffer(struct log_msg* buf) {
     int bytesWritten = 0;
     int err;
     AndroidLogEntry entry;
     char binaryMsgBuf[1024];
 
-    if (dev->binary) {
-        static bool hasOpenedEventTagMap = false;
-        static EventTagMap *eventTagMap = NULL;
+    bool is_binary =
+            buf->id() == LOG_ID_EVENTS || buf->id() == LOG_ID_STATS || buf->id() == LOG_ID_SECURITY;
 
-        if (!eventTagMap && !hasOpenedEventTagMap) {
-            eventTagMap = android_openEventTagMap(EVENT_TAG_MAP_FILE);
-            hasOpenedEventTagMap = true;
+    if (is_binary) {
+        if (!event_tag_map_ && !has_opened_event_tag_map_) {
+            event_tag_map_.reset(android_openEventTagMap(nullptr));
+            has_opened_event_tag_map_ = true;
         }
-        err = android_log_processBinaryLogBuffer(&buf->entry_v1, &entry,
-                                                 eventTagMap,
-                                                 binaryMsgBuf,
-                                                 sizeof(binaryMsgBuf));
-        //printf(">>> pri=%d len=%d msg='%s'\n",
+        err = android_log_processBinaryLogBuffer(&buf->entry, &entry, event_tag_map_.get(),
+                                                 binaryMsgBuf, sizeof(binaryMsgBuf));
+        // printf(">>> pri=%d len=%d msg='%s'\n",
         //    entry.priority, entry.messageLen, entry.message);
     } else {
-        err = android_log_processLogBuffer(&buf->entry_v1, &entry);
+        err = android_log_processLogBuffer(&buf->entry, &entry);
     }
-    if (err < 0) {
-        goto error;
-    }
+    if (err < 0 && !debug_) return;
 
-    if (android_log_shouldPrintLine(g_logformat, entry.tag, entry.priority)) {
-        bytesWritten = android_log_printLogLine(g_logformat, g_outFD, &entry);
+    if (android_log_shouldPrintLine(logformat_.get(), std::string(entry.tag, entry.tagLen).c_str(),
+                                    entry.priority)) {
+        bool match = !regex_ ||
+                     std::regex_search(entry.message, entry.message + entry.messageLen, *regex_);
 
-        if (bytesWritten < 0) {
-            logcat_panic(false, "output error");
-        }
-    }
+        print_count_ += match;
+        if (match || print_it_anyways_) {
+            bytesWritten = android_log_printLogLine(logformat_.get(), output_fd_.get(), &entry);
 
-    g_outByteCount += bytesWritten;
-
-    if (g_logRotateSizeKBytes > 0
-        && (g_outByteCount / 1024) >= g_logRotateSizeKBytes
-    ) {
-        rotateLogs();
-    }
-
-error:
-    //fprintf (stderr, "Error processing record\n");
-    return;
-}
-
-static void maybePrintStart(log_device_t* dev, bool printDividers) {
-    if (!dev->printed || printDividers) {
-        if (g_devCount > 1 && !g_printBinary) {
-            char buf[1024];
-            snprintf(buf, sizeof(buf), "--------- %s %s\n",
-                     dev->printed ? "switch to" : "beginning of",
-                     dev->device);
-            if (write(g_outFD, buf, strlen(buf)) < 0) {
-                logcat_panic(false, "output error");
+            if (bytesWritten < 0) {
+                error(EXIT_FAILURE, 0, "Output error.");
             }
         }
-        dev->printed = true;
+    }
+
+    out_byte_count_ += bytesWritten;
+
+    if (log_rotate_size_kb_ > 0 && (out_byte_count_ / 1024) >= log_rotate_size_kb_) {
+        RotateLogs();
     }
 }
 
-static void setupOutput()
-{
+void Logcat::PrintDividers(log_id_t log_id, bool print_dividers) {
+    if (log_id == last_printed_id_ || print_binary_) {
+        return;
+    }
+    if (!printed_start_[log_id] || print_dividers) {
+        if (dprintf(output_fd_.get(), "--------- %s %s\n",
+                    printed_start_[log_id] ? "switch to" : "beginning of",
+                    android_log_id_to_name(log_id)) < 0) {
+            error(EXIT_FAILURE, errno, "Output error");
+        }
+    }
+    last_printed_id_ = log_id;
+    printed_start_[log_id] = true;
+}
 
-    if (g_outputFileName == NULL) {
-        g_outFD = STDOUT_FILENO;
+void Logcat::SetupOutputAndSchedulingPolicy(bool blocking) {
+    if (!output_file_name_) return;
 
-    } else {
+    if (blocking) {
+        // Lower priority and set to batch scheduling if we are saving
+        // the logs into files and taking continuous content.
         if (set_sched_policy(0, SP_BACKGROUND) < 0) {
             fprintf(stderr, "failed to set background scheduling policy\n");
         }
 
-        struct sched_param param;
-        memset(&param, 0, sizeof(param));
-        if (sched_setscheduler((pid_t) 0, SCHED_BATCH, &param) < 0) {
+        struct sched_param param = {};
+        if (sched_setscheduler((pid_t)0, SCHED_BATCH, &param) < 0) {
             fprintf(stderr, "failed to set to batch scheduler\n");
         }
 
         if (setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND) < 0) {
             fprintf(stderr, "failed set to priority\n");
         }
-
-        g_outFD = openLogFile (g_outputFileName);
-
-        if (g_outFD < 0) {
-            logcat_panic(false, "couldn't open output file");
-        }
-
-        struct stat statbuf;
-        if (fstat(g_outFD, &statbuf) == -1) {
-            close(g_outFD);
-            logcat_panic(false, "couldn't get output file stat\n");
-        }
-
-        if ((size_t) statbuf.st_size > SIZE_MAX || statbuf.st_size < 0) {
-            close(g_outFD);
-            logcat_panic(false, "invalid output file stat\n");
-        }
-
-        g_outByteCount = statbuf.st_size;
     }
+
+    output_fd_.reset(openLogFile(output_file_name_, log_rotate_size_kb_));
+
+    if (!output_fd_.ok()) {
+        error(EXIT_FAILURE, errno, "Couldn't open output file");
+    }
+
+    struct stat statbuf;
+    if (fstat(output_fd_.get(), &statbuf) == -1) {
+        error(EXIT_FAILURE, errno, "Couldn't get output file stat");
+    }
+
+    if ((size_t)statbuf.st_size > SIZE_MAX || statbuf.st_size < 0) {
+        error(EXIT_FAILURE, 0, "Invalid output file stat.");
+    }
+
+    out_byte_count_ = statbuf.st_size;
 }
 
-static void show_help(const char *cmd)
-{
-    fprintf(stderr,"Usage: %s [options] [filterspecs]\n", cmd);
+// clang-format off
+static void show_help() {
+    const char* cmd = getprogname();
 
-    fprintf(stderr, "options include:\n"
-                    "  -s              Set default filter to silent.\n"
-                    "                  Like specifying filterspec '*:S'\n"
-                    "  -f <filename>   Log to file. Default is stdout\n"
-                    "  -r <kbytes>     Rotate log every kbytes. Requires -f\n"
-                    "  -n <count>      Sets max number of rotated logs to <count>, default 4\n"
-                    "  -v <format>     Sets the log print format, where <format> is:\n\n"
-                    "                      brief color long printable process raw tag thread\n"
-                    "                      threadtime time usec\n\n"
-                    "  -D              print dividers between each log buffer\n"
-                    "  -c              clear (flush) the entire log and exit\n"
-                    "  -d              dump the log and then exit (don't block)\n"
-                    "  -t <count>      print only the most recent <count> lines (implies -d)\n"
-                    "  -t '<time>'     print most recent lines since specified time (implies -d)\n"
-                    "  -T <count>      print only the most recent <count> lines (does not imply -d)\n"
-                    "  -T '<time>'     print most recent lines since specified time (not imply -d)\n"
-                    "                  count is pure numerical, time is 'MM-DD hh:mm:ss.mmm'\n"
-                    "  -g              get the size of the log's ring buffer and exit\n"
-                    "  -L              dump logs from prior to last reboot\n"
-                    "  -b <buffer>     Request alternate ring buffer, 'main', 'system', 'radio',\n"
-                    "                  'events', 'crash' or 'all'. Multiple -b parameters are\n"
-                    "                  allowed and results are interleaved. The default is\n"
-                    "                  -b main -b system -b crash.\n"
-                    "  -B              output the log in binary.\n"
-                    "  -S              output statistics.\n"
-                    "  -G <size>       set size of log ring buffer, may suffix with K or M.\n"
-                    "  -p              print prune white and ~black list. Service is specified as\n"
-                    "                  UID, UID/PID or /PID. Weighed for quicker pruning if prefix\n"
-                    "                  with ~, otherwise weighed for longevity if unadorned. All\n"
-                    "                  other pruning activity is oldest first. Special case ~!\n"
-                    "                  represents an automatic quicker pruning for the noisiest\n"
-                    "                  UID as determined by the current statistics.\n"
-                    "  -P '<list> ...' set prune white and ~black list, using same format as\n"
-                    "                  printed above. Must be quoted.\n");
+    fprintf(stderr, "Usage: %s [options] [filterspecs]\n", cmd);
 
-    fprintf(stderr,"\nfilterspecs are a series of \n"
+    fprintf(stderr, R"init(
+General options:
+  -b, --buffer=<buffer>       Request alternate ring buffer(s):
+                                main system radio events crash default all
+                              Additionally, 'kernel' for userdebug and eng builds, and
+                              'security' for Device Owner installations.
+                              Multiple -b parameters or comma separated list of buffers are
+                              allowed. Buffers are interleaved.
+                              Default -b main,system,crash,kernel.
+  -L, --last                  Dump logs from prior to last reboot from pstore.
+  -c, --clear                 Clear (flush) the entire log and exit.
+                              if -f is specified, clear the specified file and its related rotated
+                              log files instead.
+                              if -L is specified, clear pstore log instead.
+  -d                          Dump the log and then exit (don't block).
+  --pid=<pid>                 Only print logs from the given pid.
+  --wrap                      Sleep for 2 hours or when buffer about to wrap whichever
+                              comes first. Improves efficiency of polling by providing
+                              an about-to-wrap wakeup.
+
+Formatting:
+  -v, --format=<format>       Sets log print format verb and adverbs, where <format> is one of:
+                                brief help long process raw tag thread threadtime time
+                              Modifying adverbs can be added:
+                                color descriptive epoch monotonic printable uid usec UTC year zone
+                              Multiple -v parameters or comma separated list of format and format
+                              modifiers are allowed.
+  -D, --dividers              Print dividers between each log buffer.
+  -B, --binary                Output the log in binary.
+
+Outfile files:
+  -f, --file=<file>           Log to file instead of stdout.
+  -r, --rotate-kbytes=<n>     Rotate log every <n> kbytes. Requires -f option.
+  -n, --rotate-count=<count>  Sets max number of rotated logs to <count>, default 4.
+  --id=<id>                   If the signature <id> for logging to file changes, then clear the
+                              associated files and continue.
+
+Logd control:
+ These options send a control message to the logd daemon on device, print its return message if
+ applicable, then exit. They are incompatible with -L, as these attributes do not apply to pstore.
+  -g, --buffer-size           Get the size of the ring buffers within logd.
+  -G, --buffer-size=<size>    Set size of a ring buffer in logd. May suffix with K or M.
+                              This can individually control each buffer's size with -b.
+  -S, --statistics            Output statistics.
+                              --pid can be used to provide pid specific stats.
+  -p, --prune                 Print prune rules. Each rule is specified as UID, UID/PID or /PID. A
+                              '~' prefix indicates that elements matching the rule should be pruned
+                              with higher priority otherwise they're pruned with lower priority. All
+                              other pruning activity is oldest first. Special case ~! represents an
+                              automatic pruning for the noisiest UID as determined by the current
+                              statistics.  Special case ~1000/! represents pruning of the worst PID
+                              within AID_SYSTEM when AID_SYSTEM is the noisiest UID.
+  -P, --prune='<list> ...'    Set prune rules, using same format as listed above. Must be quoted.
+
+Filtering:
+  -s                          Set default filter to silent. Equivalent to filterspec '*:S'
+  -e, --regex=<expr>          Only print lines where the log message matches <expr> where <expr> is
+                              an ECMAScript regular expression.
+  -m, --max-count=<count>     Quit after printing <count> lines. This is meant to be paired with
+                              --regex, but will work on its own.
+  --print                     This option is only applicable when --regex is set and only useful if
+                              --max-count is also provided.
+                              With --print, logcat will print all messages even if they do not
+                              match the regex. Logcat will quit after printing the max-count number
+                              of lines that match the regex.
+  -t <count>                  Print only the most recent <count> lines (implies -d).
+  -t '<time>'                 Print the lines since specified time (implies -d).
+  -T <count>                  Print only the most recent <count> lines (does not imply -d).
+  -T '<time>'                 Print the lines since specified time (not imply -d).
+                              count is pure numerical, time is 'MM-DD hh:mm:ss.mmm...'
+                              'YYYY-MM-DD hh:mm:ss.mmm...' or 'sssss.mmm...' format.
+  --uid=<uids>                Only display log messages from UIDs present in the comma separate list
+                              <uids>. No name look-up is performed, so UIDs must be provided as
+                              numeric values. This option is only useful for the 'root', 'log', and
+                              'system' users since only those users can view logs from other users.
+)init");
+
+    fprintf(stderr, "\nfilterspecs are a series of \n"
                    "  <tag>[:priority]\n\n"
                    "where <tag> is a log component tag (or * for all) and priority is:\n"
                    "  V    Verbose (default for <tag>)\n"
@@ -305,110 +384,97 @@
                    "or defaults to \"threadtime\"\n\n");
 }
 
-static int setLogFormat(const char * formatString)
-{
-    static AndroidLogPrintFormat format;
+static void show_format_help() {
+    fprintf(stderr,
+        "-v <format>, --format=<format> options:\n"
+        "  Sets log print format verb and adverbs, where <format> is:\n"
+        "    brief long process raw tag thread threadtime time\n"
+        "  and individually flagged modifying adverbs can be added:\n"
+        "    color descriptive epoch monotonic printable uid usec UTC year zone\n"
+        "\nSingle format verbs:\n"
+        "  brief      — Display priority/tag and PID of the process issuing the message.\n"
+        "  long       — Display all metadata fields, separate messages with blank lines.\n"
+        "  process    — Display PID only.\n"
+        "  raw        — Display the raw log message, with no other metadata fields.\n"
+        "  tag        — Display the priority/tag only.\n"
+        "  thread     — Display priority, PID and TID of process issuing the message.\n"
+        "  threadtime — Display the date, invocation time, priority, tag, and the PID\n"
+        "               and TID of the thread issuing the message. (the default format).\n"
+        "  time       — Display the date, invocation time, priority/tag, and PID of the\n"
+        "             process issuing the message.\n"
+        "\nAdverb modifiers can be used in combination:\n"
+        "  color       — Display in highlighted color to match priority. i.e. \x1B[39mVERBOSE\n"
+        "                \x1B[34mDEBUG \x1B[32mINFO \x1B[33mWARNING \x1B[31mERROR FATAL\x1B[0m\n"
+        "  descriptive — events logs only, descriptions from event-log-tags database.\n"
+        "  epoch       — Display time as seconds since Jan 1 1970.\n"
+        "  monotonic   — Display time as cpu seconds since last boot.\n"
+        "  printable   — Ensure that any binary logging content is escaped.\n"
+        "  uid         — If permitted, display the UID or Android ID of logged process.\n"
+        "  usec        — Display time down the microsecond precision.\n"
+        "  UTC         — Display time as UTC.\n"
+        "  year        — Add the year to the displayed time.\n"
+        "  zone        — Add the local timezone to the displayed time.\n"
+        "  \"<zone>\"    — Print using this public named timezone (experimental).\n\n"
+    );
+}
+// clang-format on
 
-    format = android_log_formatFromString(formatString);
+int Logcat::SetLogFormat(const char* format_string) {
+    AndroidLogPrintFormat format = android_log_formatFromString(format_string);
 
-    if (format == FORMAT_OFF) {
-        // FORMAT_OFF means invalid string
-        return -1;
-    }
+    // invalid string?
+    if (format == FORMAT_OFF) return -1;
 
-    return android_log_setPrintFormat(g_logformat, format);
+    return android_log_setPrintFormat(logformat_.get(), format);
 }
 
-static const char multipliers[][2] = {
-    { "" },
-    { "K" },
-    { "M" },
-    { "G" }
-};
-
-static unsigned long value_of_size(unsigned long value)
-{
-    for (unsigned i = 0;
-            (i < sizeof(multipliers)/sizeof(multipliers[0])) && (value >= 1024);
-            value /= 1024, ++i) ;
-    return value;
-}
-
-static const char *multiplier_of_size(unsigned long value)
-{
-    unsigned i;
+static std::pair<unsigned long, const char*> format_of_size(unsigned long value) {
+    static const char multipliers[][3] = {{""}, {"Ki"}, {"Mi"}, {"Gi"}};
+    size_t i;
     for (i = 0;
-            (i < sizeof(multipliers)/sizeof(multipliers[0])) && (value >= 1024);
-            value /= 1024, ++i) ;
-    return multipliers[i];
+         (i < sizeof(multipliers) / sizeof(multipliers[0])) && (value >= 1024);
+         value /= 1024, ++i)
+        ;
+    return std::make_pair(value, multipliers[i]);
 }
 
-/*String to unsigned int, returns -1 if it fails*/
-static bool getSizeTArg(char *ptr, size_t *val, size_t min = 0,
-                        size_t max = SIZE_MAX)
-{
-    char *endp;
-    errno = 0;
-    size_t ret = (size_t) strtoll(ptr, &endp, 0);
-
-    if (endp[0] != '\0' || errno != 0 ) {
-        return false;
-    }
-
-    if (ret >  max || ret <  min) {
-        return false;
-    }
-
-    *val = ret;
-    return true;
+static char* parseTime(log_time& t, const char* cp) {
+    char* ep = t.strptime(cp, "%m-%d %H:%M:%S.%q");
+    if (ep) return ep;
+    ep = t.strptime(cp, "%Y-%m-%d %H:%M:%S.%q");
+    if (ep) return ep;
+    return t.strptime(cp, "%s.%q");
 }
 
-static void logcat_panic(bool showHelp, const char *fmt, ...)
-{
-    va_list  args;
-    va_start(args, fmt);
-    vfprintf(stderr, fmt,  args);
-    va_end(args);
-
-    if (showHelp) {
-       show_help(getprogname());
-    }
-
-    exit(EXIT_FAILURE);
-}
-
-static const char g_defaultTimeFormat[] = "%m-%d %H:%M:%S.%q";
-
-// Find last logged line in gestalt of all matching existing output files
-static log_time lastLogTime(char *outputFileName) {
+// Find last logged line in <outputFileName>, or <outputFileName>.1
+static log_time lastLogTime(const char* outputFileName) {
     log_time retval(log_time::EPOCH);
-    if (!outputFileName) {
-        return retval;
-    }
-
-    log_time now(CLOCK_REALTIME);
+    if (!outputFileName) return retval;
 
     std::string directory;
-    char *file = strrchr(outputFileName, '/');
+    const char* file = strrchr(outputFileName, '/');
     if (!file) {
         directory = ".";
         file = outputFileName;
     } else {
-        *file = '\0';
-        directory = outputFileName;
-        *file = '/';
+        directory = std::string(outputFileName, file - outputFileName);
         ++file;
     }
+
+    std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(directory.c_str()),
+                                            closedir);
+    if (!dir.get()) return retval;
+
+    log_time now(CLOCK_REALTIME);
+
     size_t len = strlen(file);
     log_time modulo(0, NS_PER_SEC);
-    std::unique_ptr<DIR, int(*)(DIR*)>dir(opendir(directory.c_str()), closedir);
-    struct dirent *dp;
-    while ((dp = readdir(dir.get())) != NULL) {
-        if ((dp->d_type != DT_REG)
-                || strncmp(dp->d_name, file, len)
-                || (dp->d_name[len]
-                    && ((dp->d_name[len] != '.')
-                        || !isdigit(dp->d_name[len+1])))) {
+    struct dirent* dp;
+
+    while (!!(dp = readdir(dir.get()))) {
+        if ((dp->d_type != DT_REG) || !!strncmp(dp->d_name, file, len) ||
+            (dp->d_name[len] && ((dp->d_name[len] != '.') ||
+                                 (strtoll(dp->d_name + 1, nullptr, 10) != 1)))) {
             continue;
         }
 
@@ -416,17 +482,13 @@
         file_name += "/";
         file_name += dp->d_name;
         std::string file;
-        if (!android::base::ReadFileToString(file_name, &file)) {
-            continue;
-        }
+        if (!android::base::ReadFileToString(file_name, &file)) continue;
 
         bool found = false;
         for (const auto& line : android::base::Split(file, "\n")) {
             log_time t(log_time::EPOCH);
-            char *ep = t.strptime(line.c_str(), g_defaultTimeFormat);
-            if (!ep || (*ep != ' ')) {
-                continue;
-            }
+            char* ep = parseTime(t, line.c_str());
+            if (!ep || (*ep != ' ')) continue;
             // determine the time precision of the logs (eg: msec or usec)
             for (unsigned long mod = 1UL; mod < modulo.tv_nsec; mod *= 10) {
                 if (t.tv_nsec % (mod * 10)) {
@@ -444,564 +506,707 @@
             }
         }
         // We count on the basename file to be the definitive end, so stop here.
-        if (!dp->d_name[len] && found) {
-            break;
-        }
+        if (!dp->d_name[len] && found) break;
     }
-    if (retval == log_time::EPOCH) {
-        return retval;
-    }
+    if (retval == log_time::EPOCH) return retval;
     // tail_time prints matching or higher, round up by the modulo to prevent
     // a replay of the last entry we have just checked.
     retval += modulo;
     return retval;
 }
 
-} /* namespace android */
+void ReportErrorName(const std::string& name, bool allow_security,
+                     std::vector<std::string>* errors) {
+    if (allow_security || name != "security") {
+        errors->emplace_back(name);
+    }
+}
 
-
-int main(int argc, char **argv)
-{
-    using namespace android;
-    int err;
-    int hasSetLogFormat = 0;
-    int clearLog = 0;
-    int getLogSize = 0;
-    unsigned long setLogSize = 0;
-    int getPruneList = 0;
-    char *setPruneList = NULL;
-    int printStatistics = 0;
-    int mode = ANDROID_LOG_RDONLY;
-    const char *forceFilters = NULL;
-    log_device_t* devices = NULL;
-    log_device_t* dev;
+int Logcat::Run(int argc, char** argv) {
+    bool hasSetLogFormat = false;
+    bool clearLog = false;
+    bool security_buffer_selected =
+            false;  // Do not report errors on the security buffer unless it is explicitly named.
+    bool getLogSize = false;
+    bool getPruneList = false;
+    bool printStatistics = false;
     bool printDividers = false;
-    struct logger_list *logger_list;
+    unsigned long setLogSize = 0;
+    const char* setPruneList = nullptr;
+    const char* setId = nullptr;
+    int mode = 0;
+    std::string forceFilters;
     size_t tail_lines = 0;
     log_time tail_time(log_time::EPOCH);
+    size_t pid = 0;
+    bool got_t = false;
+    unsigned id_mask = 0;
+    std::set<uid_t> uids;
 
-    signal(SIGPIPE, exit);
-
-    g_logformat = android_log_format_new();
-
-    if (argc == 2 && 0 == strcmp(argv[1], "--help")) {
-        show_help(argv[0]);
+    if (argc == 2 && !strcmp(argv[1], "--help")) {
+        show_help();
         return EXIT_SUCCESS;
     }
 
-    for (;;) {
-        int ret;
+    // meant to catch comma-delimited values, but cast a wider
+    // net for stability dealing with possible mistaken inputs.
+    static const char delimiters[] = ",:; \t\n\r\f";
 
-        ret = getopt(argc, argv, ":cdDLt:T:gG:sQf:r:n:v:b:BSpP:");
+    optind = 0;
+    while (true) {
+        int option_index = 0;
+        // list of long-argument only strings for later comparison
+        static const char pid_str[] = "pid";
+        static const char debug_str[] = "debug";
+        static const char id_str[] = "id";
+        static const char wrap_str[] = "wrap";
+        static const char print_str[] = "print";
+        static const char uid_str[] = "uid";
+        // clang-format off
+        static const struct option long_options[] = {
+          { "binary",        no_argument,       nullptr, 'B' },
+          { "buffer",        required_argument, nullptr, 'b' },
+          { "buffer-size",   optional_argument, nullptr, 'g' },
+          { "clear",         no_argument,       nullptr, 'c' },
+          { debug_str,       no_argument,       nullptr, 0 },
+          { "dividers",      no_argument,       nullptr, 'D' },
+          { "file",          required_argument, nullptr, 'f' },
+          { "format",        required_argument, nullptr, 'v' },
+          // hidden and undocumented reserved alias for --regex
+          { "grep",          required_argument, nullptr, 'e' },
+          // hidden and undocumented reserved alias for --max-count
+          { "head",          required_argument, nullptr, 'm' },
+          { "help",          no_argument,       nullptr, 'h' },
+          { id_str,          required_argument, nullptr, 0 },
+          { "last",          no_argument,       nullptr, 'L' },
+          { "max-count",     required_argument, nullptr, 'm' },
+          { pid_str,         required_argument, nullptr, 0 },
+          { print_str,       no_argument,       nullptr, 0 },
+          { "prune",         optional_argument, nullptr, 'p' },
+          { "regex",         required_argument, nullptr, 'e' },
+          { "rotate-count",  required_argument, nullptr, 'n' },
+          { "rotate-kbytes", required_argument, nullptr, 'r' },
+          { "statistics",    no_argument,       nullptr, 'S' },
+          // hidden and undocumented reserved alias for -t
+          { "tail",          required_argument, nullptr, 't' },
+          { uid_str,         required_argument, nullptr, 0 },
+          // support, but ignore and do not document, the optional argument
+          { wrap_str,        optional_argument, nullptr, 0 },
+          { nullptr,         0,                 nullptr, 0 }
+        };
+        // clang-format on
 
-        if (ret < 0) {
-            break;
-        }
+        int c = getopt_long(argc, argv, ":cdDhLt:T:gG:sQf:r:n:v:b:BSpP:m:e:", long_options,
+                            &option_index);
+        if (c == -1) break;
 
-        switch(ret) {
-            case 's':
-                // default to all silent
-                android_log_addFilterRule(g_logformat, "*:s");
-            break;
-
-            case 'c':
-                clearLog = 1;
-                mode |= ANDROID_LOG_WRONLY;
-            break;
-
-            case 'L':
-                mode |= ANDROID_LOG_PSTORE;
-            break;
-
-            case 'd':
-                mode |= ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK;
-            break;
-
-            case 't':
-                mode |= ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK;
-                /* FALLTHRU */
-            case 'T':
-                if (strspn(optarg, "0123456789") != strlen(optarg)) {
-                    char *cp = tail_time.strptime(optarg, g_defaultTimeFormat);
-                    if (!cp) {
-                        logcat_panic(false,
-                                    "-%c \"%s\" not in \"%s\" time format\n",
-                                    ret, optarg, g_defaultTimeFormat);
-                    }
-                    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 {
-                    if (!getSizeTArg(optarg, &tail_lines, 1)) {
-                        fprintf(stderr,
-                                "WARNING: -%c %s invalid, setting to 1\n",
-                                ret, optarg);
-                        tail_lines = 1;
-                    }
-                }
-            break;
-
-            case 'D':
-                printDividers = true;
-            break;
-
-            case 'g':
-                getLogSize = 1;
-            break;
-
-            case 'G': {
-                char *cp;
-                if (strtoll(optarg, &cp, 0) > 0) {
-                    setLogSize = strtoll(optarg, &cp, 0);
-                } else {
-                    setLogSize = 0;
-                }
-
-                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");
-                    return EXIT_FAILURE;
-                }
-            }
-            break;
-
-            case 'p':
-                getPruneList = 1;
-            break;
-
-            case 'P':
-                setPruneList = optarg;
-            break;
-
-            case 'b': {
-                if (strcmp(optarg, "all") == 0) {
-                    while (devices) {
-                        dev = devices;
-                        devices = dev->next;
-                        delete dev;
+        switch (c) {
+            case 0:
+                // only long options
+                if (long_options[option_index].name == pid_str) {
+                    if (pid != 0) {
+                        error(EXIT_FAILURE, 0, "Only one --pid argument can be provided.");
                     }
 
-                    devices = dev = NULL;
-                    g_devCount = 0;
-                    for(int i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) {
-                        const char *name = android_log_id_to_name((log_id_t)i);
-                        log_id_t log_id = android_name_to_log_id(name);
-
-                        if (log_id != (log_id_t)i) {
-                            continue;
-                        }
-
-                        bool binary = strcmp(name, "events") == 0;
-                        log_device_t* d = new log_device_t(name, binary);
-
-                        if (dev) {
-                            dev->next = d;
-                            dev = d;
-                        } else {
-                            devices = dev = d;
-                        }
-                        g_devCount++;
+                    if (!ParseUint(optarg, &pid) || pid < 1) {
+                        error(EXIT_FAILURE, 0, "%s %s out of range.",
+                              long_options[option_index].name, optarg);
                     }
                     break;
                 }
-
-                bool binary = strcmp(optarg, "events") == 0;
-
-                if (devices) {
-                    dev = devices;
-                    while (dev->next) {
-                        dev = dev->next;
+                if (long_options[option_index].name == wrap_str) {
+                    mode |= ANDROID_LOG_WRAP | ANDROID_LOG_NONBLOCK;
+                    // ToDo: implement API that supports setting a wrap timeout
+                    size_t timeout = ANDROID_LOG_WRAP_DEFAULT_TIMEOUT;
+                    if (optarg && (!ParseUint(optarg, &timeout) || timeout < 1)) {
+                        error(EXIT_FAILURE, 0, "%s %s out of range.",
+                              long_options[option_index].name, optarg);
                     }
-                    dev->next = new log_device_t(optarg, binary);
-                } else {
-                    devices = new log_device_t(optarg, binary);
+                    if (timeout != ANDROID_LOG_WRAP_DEFAULT_TIMEOUT) {
+                        fprintf(stderr, "WARNING: %s %u seconds, ignoring %zu\n",
+                                long_options[option_index].name, ANDROID_LOG_WRAP_DEFAULT_TIMEOUT,
+                                timeout);
+                    }
+                    break;
                 }
-                g_devCount++;
-            }
-            break;
+                if (long_options[option_index].name == print_str) {
+                    print_it_anyways_ = true;
+                    break;
+                }
+                if (long_options[option_index].name == debug_str) {
+                    debug_ = true;
+                    break;
+                }
+                if (long_options[option_index].name == id_str) {
+                    setId = (optarg && optarg[0]) ? optarg : nullptr;
+                }
+                if (long_options[option_index].name == uid_str) {
+                    auto uid_strings = Split(optarg, delimiters);
+                    for (const auto& uid_string : uid_strings) {
+                        uid_t uid;
+                        if (!ParseUint(uid_string, &uid)) {
+                            error(EXIT_FAILURE, 0, "Unable to parse UID '%s'", uid_string.c_str());
+                        }
+                        uids.emplace(uid);
+                    }
+                    break;
+                }
+                break;
+
+            case 's':
+                // default to all silent
+                android_log_addFilterRule(logformat_.get(), "*:s");
+                break;
+
+            case 'c':
+                clearLog = true;
+                break;
+
+            case 'L':
+                mode |= ANDROID_LOG_PSTORE | ANDROID_LOG_NONBLOCK;
+                break;
+
+            case 'd':
+                mode |= ANDROID_LOG_NONBLOCK;
+                break;
+
+            case 't':
+                got_t = true;
+                mode |= ANDROID_LOG_NONBLOCK;
+                FALLTHROUGH_INTENDED;
+            case 'T':
+                if (strspn(optarg, "0123456789") != strlen(optarg)) {
+                    char* cp = parseTime(tail_time, optarg);
+                    if (!cp) {
+                        error(EXIT_FAILURE, 0, "-%c '%s' not in time format.", c, optarg);
+                    }
+                    if (*cp) {
+                        char ch = *cp;
+                        *cp = '\0';
+                        fprintf(stderr, "WARNING: -%c '%s' '%c%s' time truncated\n", c, optarg, ch,
+                                cp + 1);
+                        *cp = ch;
+                    }
+                } else {
+                    if (!ParseUint(optarg, &tail_lines) || tail_lines < 1) {
+                        fprintf(stderr, "WARNING: -%c %s invalid, setting to 1\n", c, optarg);
+                        tail_lines = 1;
+                    }
+                }
+                break;
+
+            case 'D':
+                printDividers = true;
+                break;
+
+            case 'e':
+                regex_.reset(new std::regex(optarg));
+                break;
+
+            case 'm': {
+                if (!ParseUint(optarg, &max_count_) || max_count_ < 1) {
+                    error(EXIT_FAILURE, 0, "-%c '%s' isn't an integer greater than zero.", c,
+                          optarg);
+                }
+            } break;
+
+            case 'g':
+                if (!optarg) {
+                    getLogSize = true;
+                    break;
+                }
+                FALLTHROUGH_INTENDED;
+
+            case 'G': {
+                if (!ParseByteCount(optarg, &setLogSize) || setLogSize < 1) {
+                    error(EXIT_FAILURE, 0, "-G must be specified as <num><multiplier>.");
+                }
+            } break;
+
+            case 'p':
+                if (!optarg) {
+                    getPruneList = true;
+                    break;
+                }
+                FALLTHROUGH_INTENDED;
+
+            case 'P':
+                setPruneList = optarg;
+                break;
+
+            case 'b':
+                for (const auto& buffer : Split(optarg, delimiters)) {
+                    if (buffer == "default") {
+                        id_mask |= (1 << LOG_ID_MAIN) | (1 << LOG_ID_SYSTEM) | (1 << LOG_ID_CRASH);
+                    } else if (buffer == "all") {
+                        id_mask = -1;
+                    } else {
+                        log_id_t log_id = android_name_to_log_id(buffer.c_str());
+                        if (log_id >= LOG_ID_MAX) {
+                            error(EXIT_FAILURE, 0, "Unknown buffer '%s' listed for -b.",
+                                  buffer.c_str());
+                        }
+                        if (log_id == LOG_ID_SECURITY) {
+                            security_buffer_selected = true;
+                        }
+                        id_mask |= (1 << log_id);
+                    }
+                }
+                break;
 
             case 'B':
-                g_printBinary = 1;
-            break;
+                print_binary_ = 1;
+                break;
 
             case 'f':
-                if ((tail_time == log_time::EPOCH) && (tail_lines != 0)) {
+                if ((tail_time == log_time::EPOCH) && !tail_lines) {
                     tail_time = lastLogTime(optarg);
                 }
                 // redirect output to a file
-                g_outputFileName = optarg;
-            break;
+                output_file_name_ = optarg;
+                break;
 
             case 'r':
-                if (!getSizeTArg(optarg, &g_logRotateSizeKBytes, 1)) {
-                    logcat_panic(true, "Invalid parameter %s to -r\n", optarg);
+                if (!ParseUint(optarg, &log_rotate_size_kb_) || log_rotate_size_kb_ < 1) {
+                    error(EXIT_FAILURE, 0, "Invalid parameter '%s' to -r.", optarg);
                 }
-            break;
+                break;
 
             case 'n':
-                if (!getSizeTArg(optarg, &g_maxRotatedLogs, 1)) {
-                    logcat_panic(true, "Invalid parameter %s to -n\n", optarg);
+                if (!ParseUint(optarg, &max_rotated_logs_) || max_rotated_logs_ < 1) {
+                    error(EXIT_FAILURE, 0, "Invalid parameter '%s' to -n.", optarg);
                 }
-            break;
+                break;
 
             case 'v':
-                err = setLogFormat (optarg);
-                if (err < 0) {
-                    logcat_panic(true, "Invalid parameter %s to -v\n", optarg);
+                if (!strcmp(optarg, "help") || !strcmp(optarg, "--help")) {
+                    show_format_help();
+                    return EXIT_SUCCESS;
                 }
-                hasSetLogFormat |= err;
-            break;
+                for (const auto& arg : Split(optarg, delimiters)) {
+                    int err = SetLogFormat(arg.c_str());
+                    if (err < 0) {
+                        error(EXIT_FAILURE, 0, "Invalid parameter '%s' to -v.", arg.c_str());
+                    }
+                    if (err) hasSetLogFormat = true;
+                }
+                break;
 
             case 'Q':
-                /* this is a *hidden* option used to start a version of logcat                 */
-                /* in an emulated device only. it basically looks for androidboot.logcat=      */
-                /* on the kernel command line. If something is found, it extracts a log filter */
-                /* and uses it to run the program. If nothing is found, the program should     */
-                /* quit immediately                                                            */
-#define  KERNEL_OPTION  "androidboot.logcat="
-#define  CONSOLE_OPTION "androidboot.console="
+#define LOGCAT_FILTER "androidboot.logcat="
+#define CONSOLE_PIPE_OPTION "androidboot.consolepipe="
+#define CONSOLE_OPTION "androidboot.console="
+#define QEMU_PROPERTY "ro.kernel.qemu"
+#define QEMU_CMDLINE "qemu.cmdline"
+                // This is a *hidden* option used to start a version of logcat
+                // in an emulated device only.  It basically looks for
+                // androidboot.logcat= on the kernel command line.  If
+                // something is found, it extracts a log filter and uses it to
+                // run the program. The logcat output will go to consolepipe if
+                // androiboot.consolepipe (e.g. qemu_pipe) is given, otherwise,
+                // it goes to androidboot.console (e.g. tty)
                 {
-                    int          fd;
-                    char*        logcat;
-                    char*        console;
-                    int          force_exit = 1;
-                    static char  cmdline[1024];
-
-                    fd = open("/proc/cmdline", O_RDONLY);
-                    if (fd >= 0) {
-                        int  n = read(fd, cmdline, sizeof(cmdline)-1 );
-                        if (n < 0) n = 0;
-                        cmdline[n] = 0;
-                        close(fd);
-                    } else {
-                        cmdline[0] = 0;
-                    }
-
-                    logcat  = strstr( cmdline, KERNEL_OPTION );
-                    console = strstr( cmdline, CONSOLE_OPTION );
-                    if (logcat != NULL) {
-                        char*  p = logcat + sizeof(KERNEL_OPTION)-1;;
-                        char*  q = strpbrk( p, " \t\n\r" );;
-
-                        if (q != NULL)
-                            *q = 0;
-
-                        forceFilters = p;
-                        force_exit   = 0;
-                    }
-                    /* if nothing found or invalid filters, exit quietly */
-                    if (force_exit) {
+                    // if not in emulator, exit quietly
+                    if (false == android::base::GetBoolProperty(QEMU_PROPERTY, false)) {
                         return EXIT_SUCCESS;
                     }
 
-                    /* redirect our output to the emulator console */
-                    if (console) {
-                        char*  p = console + sizeof(CONSOLE_OPTION)-1;
-                        char*  q = strpbrk( p, " \t\n\r" );
-                        char   devname[64];
-                        int    len;
+                    std::string cmdline = android::base::GetProperty(QEMU_CMDLINE, "");
+                    if (cmdline.empty()) {
+                        android::base::ReadFileToString("/proc/cmdline", &cmdline);
+                    }
 
-                        if (q != NULL) {
-                            len = q - p;
-                        } else
-                            len = strlen(p);
+                    const char* logcatFilter = strstr(cmdline.c_str(), LOGCAT_FILTER);
+                    // if nothing found or invalid filters, exit quietly
+                    if (!logcatFilter) {
+                        return EXIT_SUCCESS;
+                    }
 
-                        len = snprintf( devname, sizeof(devname), "/dev/%.*s", len, p );
-                        fprintf(stderr, "logcat using %s (%d)\n", devname, len);
-                        if (len < (int)sizeof(devname)) {
-                            fd = open( devname, O_WRONLY );
-                            if (fd >= 0) {
-                                dup2(fd, 1);
-                                dup2(fd, 2);
-                                close(fd);
-                            }
+                    const char* p = logcatFilter + strlen(LOGCAT_FILTER);
+                    const char* q = strpbrk(p, " \t\n\r");
+                    if (!q) q = p + strlen(p);
+                    forceFilters = std::string(p, q);
+
+                    // redirect our output to the emulator console pipe or console
+                    const char* consolePipe =
+                        strstr(cmdline.c_str(), CONSOLE_PIPE_OPTION);
+                    const char* console =
+                        strstr(cmdline.c_str(), CONSOLE_OPTION);
+
+                    if (consolePipe) {
+                        p = consolePipe + strlen(CONSOLE_PIPE_OPTION);
+                    } else if (console) {
+                        p = console + strlen(CONSOLE_OPTION);
+                    } else {
+                        return EXIT_FAILURE;
+                    }
+
+                    q = strpbrk(p, " \t\n\r");
+                    int len = q ? q - p : strlen(p);
+                    std::string devname = "/dev/" + std::string(p, len);
+                    std::string pipePurpose("pipe:logcat");
+                    if (consolePipe) {
+                        // example: "qemu_pipe,pipe:logcat"
+                        // upon opening of /dev/qemu_pipe, the "pipe:logcat"
+                        // string with trailing '\0' should be written to the fd
+                        size_t pos = devname.find(',');
+                        if (pos != std::string::npos) {
+                            pipePurpose = devname.substr(pos + 1);
+                            devname = devname.substr(0, pos);
                         }
                     }
+
+                    fprintf(stderr, "logcat using %s\n", devname.c_str());
+
+                    int fd = open(devname.c_str(), O_WRONLY | O_CLOEXEC);
+                    if (fd < 0) {
+                        break;
+                    }
+
+                    if (consolePipe) {
+                        // need the trailing '\0'
+                        if (!WriteFully(fd, pipePurpose.c_str(), pipePurpose.size() + 1)) {
+                            close(fd);
+                            return EXIT_FAILURE;
+                        }
+                    }
+                    // close output and error channels, replace with console
+                    dup2(fd, output_fd_.get());
+                    dup2(fd, STDERR_FILENO);
+                    close(fd);
                 }
                 break;
 
             case 'S':
-                printStatistics = 1;
+                printStatistics = true;
                 break;
 
             case ':':
-                logcat_panic(true, "Option -%c needs an argument\n", optopt);
+                error(EXIT_FAILURE, 0, "Option '%s' needs an argument.", argv[optind - 1]);
+                break;
+
+            case 'h':
+                show_help();
+                show_format_help();
+                return EXIT_SUCCESS;
+
+            case '?':
+                error(EXIT_FAILURE, 0, "Unknown option '%s'.", argv[optind - 1]);
                 break;
 
             default:
-                logcat_panic(true, "Unrecognized Option %c\n", optopt);
-                break;
+                error(EXIT_FAILURE, 0, "Unknown getopt_long() result '%c'.", c);
         }
     }
 
-    if (!devices) {
-        dev = devices = new log_device_t("main", false);
-        g_devCount = 1;
-        if (android_name_to_log_id("system") == LOG_ID_SYSTEM) {
-            dev = dev->next = new log_device_t("system", false);
-            g_devCount++;
-        }
-        if (android_name_to_log_id("crash") == LOG_ID_CRASH) {
-            dev = dev->next = new log_device_t("crash", false);
-            g_devCount++;
-        }
+    if (max_count_ && got_t) {
+        error(EXIT_FAILURE, 0, "Cannot use -m (--max-count) and -t together.");
+    }
+    if (print_it_anyways_ && (!regex_ || !max_count_)) {
+        // One day it would be nice if --print -v color and --regex <expr>
+        // could play with each other and show regex highlighted content.
+        fprintf(stderr,
+                "WARNING: "
+                "--print ignored, to be used in combination with\n"
+                "         "
+                "--regex <expr> and --max-count <N>\n");
+        print_it_anyways_ = false;
     }
 
-    if (g_logRotateSizeKBytes != 0 && g_outputFileName == NULL) {
-        logcat_panic(true, "-r requires -f as well\n");
+    // If no buffers are specified, default to using these buffers.
+    if (id_mask == 0) {
+        id_mask = (1 << LOG_ID_MAIN) | (1 << LOG_ID_SYSTEM) | (1 << LOG_ID_CRASH) |
+                  (1 << LOG_ID_KERNEL);
     }
 
-    setupOutput();
+    if (log_rotate_size_kb_ != 0 && !output_file_name_) {
+        error(EXIT_FAILURE, 0, "-r requires -f as well.");
+    }
 
-    if (hasSetLogFormat == 0) {
+    if (setId != 0) {
+        if (!output_file_name_) {
+            error(EXIT_FAILURE, 0, "--id='%s' requires -f as well.", setId);
+        }
+
+        std::string file_name = StringPrintf("%s.id", output_file_name_);
+        std::string file;
+        bool file_ok = android::base::ReadFileToString(file_name, &file);
+        android::base::WriteStringToFile(setId, file_name, S_IRUSR | S_IWUSR,
+                                         getuid(), getgid());
+        if (!file_ok || !file.compare(setId)) setId = nullptr;
+    }
+
+    if (!hasSetLogFormat) {
         const char* logFormat = getenv("ANDROID_PRINTF_LOG");
 
-        if (logFormat != NULL) {
-            err = setLogFormat(logFormat);
-            if (err < 0) {
-                fprintf(stderr, "invalid format in ANDROID_PRINTF_LOG '%s'\n",
-                                    logFormat);
+        if (!!logFormat) {
+            for (const auto& arg : Split(logFormat, delimiters)) {
+                int err = SetLogFormat(arg.c_str());
+                // environment should not cause crash of logcat
+                if (err < 0) {
+                    fprintf(stderr, "invalid format in ANDROID_PRINTF_LOG '%s'\n", arg.c_str());
+                }
+                if (err > 0) hasSetLogFormat = true;
             }
-        } else {
-            setLogFormat("threadtime");
+        }
+        if (!hasSetLogFormat) {
+            SetLogFormat("threadtime");
         }
     }
 
-    if (forceFilters) {
-        err = android_log_addFilterString(g_logformat, forceFilters);
+    if (forceFilters.size()) {
+        int err = android_log_addFilterString(logformat_.get(), forceFilters.c_str());
         if (err < 0) {
-            logcat_panic(false, "Invalid filter expression in logcat args\n");
+            error(EXIT_FAILURE, 0, "Invalid filter expression in logcat args.");
         }
     } else if (argc == optind) {
         // Add from environment variable
-        char *env_tags_orig = getenv("ANDROID_LOG_TAGS");
+        const char* env_tags_orig = getenv("ANDROID_LOG_TAGS");
 
-        if (env_tags_orig != NULL) {
-            err = android_log_addFilterString(g_logformat, env_tags_orig);
+        if (!!env_tags_orig) {
+            int err = android_log_addFilterString(logformat_.get(), env_tags_orig);
 
             if (err < 0) {
-                logcat_panic(true,
-                            "Invalid filter expression in ANDROID_LOG_TAGS\n");
+                error(EXIT_FAILURE, 0, "Invalid filter expression in ANDROID_LOG_TAGS.");
             }
         }
     } else {
         // Add from commandline
         for (int i = optind ; i < argc ; i++) {
-            err = android_log_addFilterString(g_logformat, argv[i]);
-
+            int err = android_log_addFilterString(logformat_.get(), argv[i]);
             if (err < 0) {
-                logcat_panic(true, "Invalid filter expression '%s'\n", argv[i]);
+                error(EXIT_FAILURE, 0, "Invalid filter expression '%s'.", argv[i]);
             }
         }
     }
 
-    dev = devices;
-    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);
+    if (mode & ANDROID_LOG_PSTORE) {
+        if (output_file_name_) {
+            error(EXIT_FAILURE, 0, "-c is ambiguous with both -f and -L specified.");
+        }
+        if (setLogSize || getLogSize || printStatistics || getPruneList || setPruneList) {
+            error(EXIT_FAILURE, 0, "-L is incompatible with -g/-G, -S, and -p/-P.");
+        }
+        if (clearLog) {
+            unlink("/sys/fs/pstore/pmsg-ramoops-0");
+            return EXIT_SUCCESS;
+        }
     }
-    while (dev) {
-        dev->logger_list = logger_list;
-        dev->logger = android_logger_open(logger_list,
-                                          android_name_to_log_id(dev->device));
-        if (!dev->logger) {
-            logcat_panic(false, "Unable to open log device '%s'\n",
-                         dev->device);
+
+    if (output_file_name_) {
+        if (setLogSize || getLogSize || printStatistics || getPruneList || setPruneList) {
+            error(EXIT_FAILURE, 0, "-f is incompatible with -g/-G, -S, and -p/-P.");
+        }
+
+        if (clearLog || setId) {
+            int max_rotation_count_digits =
+                    max_rotated_logs_ > 0 ? (int)(floor(log10(max_rotated_logs_) + 1)) : 0;
+
+            for (int i = max_rotated_logs_; i >= 0; --i) {
+                std::string file;
+
+                if (!i) {
+                    file = output_file_name_;
+                } else {
+                    file = StringPrintf("%s.%.*d", output_file_name_, max_rotation_count_digits, i);
+                }
+
+                int err = unlink(file.c_str());
+
+                if (err < 0 && errno != ENOENT) {
+                    fprintf(stderr, "failed to delete log file '%s': %s\n", file.c_str(),
+                            strerror(errno));
+                }
+            }
         }
 
         if (clearLog) {
-            int ret;
-            ret = android_logger_clear(dev->logger);
-            if (ret) {
-                logcat_panic(false, "failed to clear the log");
+            return EXIT_SUCCESS;
+        }
+    }
+
+    std::unique_ptr<logger_list, decltype(&android_logger_list_free)> logger_list{
+            nullptr, &android_logger_list_free};
+    if (tail_time != log_time::EPOCH) {
+        logger_list.reset(android_logger_list_alloc_time(mode, tail_time, pid));
+    } else {
+        logger_list.reset(android_logger_list_alloc(mode, tail_lines, pid));
+    }
+    // We have three orthogonal actions below to clear, set log size and
+    // get log size. All sharing the same iteration loop.
+    std::vector<std::string> open_device_failures;
+    std::vector<std::string> clear_failures;
+    std::vector<std::string> set_size_failures;
+    std::vector<std::string> get_size_failures;
+
+    for (int i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) {
+        if (!(id_mask & (1 << i))) continue;
+        const char* buffer_name = android_log_id_to_name(static_cast<log_id_t>(i));
+
+        auto logger = android_logger_open(logger_list.get(), static_cast<log_id_t>(i));
+        if (logger == nullptr) {
+            ReportErrorName(buffer_name, security_buffer_selected, &open_device_failures);
+            continue;
+        }
+
+        if (clearLog) {
+            if (android_logger_clear(logger)) {
+                ReportErrorName(buffer_name, security_buffer_selected, &clear_failures);
             }
         }
 
-        if (setLogSize && android_logger_set_log_size(dev->logger, setLogSize)) {
-            logcat_panic(false, "failed to set the log size");
+        if (setLogSize) {
+            if (android_logger_set_log_size(logger, setLogSize)) {
+                ReportErrorName(buffer_name, security_buffer_selected, &set_size_failures);
+            }
         }
 
         if (getLogSize) {
-            long size, readable;
+            long size = android_logger_get_log_size(logger);
+            long readable = android_logger_get_log_readable_size(logger);
+            long consumed = android_logger_get_log_consumed_size(logger);
 
-            size = android_logger_get_log_size(dev->logger);
-            if (size < 0) {
-                logcat_panic(false, "failed to get the log size");
+            if (size < 0 || readable < 0) {
+                ReportErrorName(buffer_name, security_buffer_selected, &get_size_failures);
+            } else {
+                auto size_format = format_of_size(size);
+                auto readable_format = format_of_size(readable);
+                auto consumed_format = format_of_size(consumed);
+                std::string str = android::base::StringPrintf(
+                        "%s: ring buffer is %lu %sB (%lu %sB consumed, %lu %sB readable),"
+                        " max entry is %d B, max payload is %d B\n",
+                        buffer_name, size_format.first, size_format.second, consumed_format.first,
+                        consumed_format.second, readable_format.first, readable_format.second,
+                        (int)LOGGER_ENTRY_MAX_LEN, (int)LOGGER_ENTRY_MAX_PAYLOAD);
+                if (!WriteFully(output_fd_, str.data(), str.length())) {
+                    error(EXIT_FAILURE, errno, "Failed to write to output fd");
+                }
             }
-
-            readable = android_logger_get_log_readable_size(dev->logger);
-            if (readable < 0) {
-                logcat_panic(false, "failed to get the readable log size");
-            }
-
-            printf("%s: ring buffer is %ld%sb (%ld%sb consumed), "
-                   "max entry is %db, max payload is %db\n", dev->device,
-                   value_of_size(size), multiplier_of_size(size),
-                   value_of_size(readable), multiplier_of_size(readable),
-                   (int) LOGGER_ENTRY_MAX_LEN, (int) LOGGER_ENTRY_MAX_PAYLOAD);
         }
+    }
 
-        dev = dev->next;
+    // report any errors in the above loop and exit
+    if (!open_device_failures.empty()) {
+        error(EXIT_FAILURE, 0, "Unable to open log device%s '%s'.",
+              open_device_failures.size() > 1 ? "s" : "", Join(open_device_failures, ",").c_str());
+    }
+    if (!clear_failures.empty()) {
+        error(EXIT_FAILURE, 0, "failed to clear the '%s' log%s.", Join(clear_failures, ",").c_str(),
+              clear_failures.size() > 1 ? "s" : "");
+    }
+    if (!set_size_failures.empty()) {
+        error(EXIT_FAILURE, 0, "failed to set the '%s' log size%s.",
+              Join(set_size_failures, ",").c_str(), set_size_failures.size() > 1 ? "s" : "");
+    }
+    if (!get_size_failures.empty()) {
+        error(EXIT_FAILURE, 0, "failed to get the readable '%s' log size%s.",
+              Join(get_size_failures, ",").c_str(), get_size_failures.size() > 1 ? "s" : "");
     }
 
     if (setPruneList) {
         size_t len = strlen(setPruneList);
-        /*extra 32 bytes are needed by  android_logger_set_prune_list */
-        size_t bLen = len + 32;
-        char *buf = NULL;
-        if (asprintf(&buf, "%-*s", (int)(bLen - 1), setPruneList) > 0) {
-            buf[len] = '\0';
-            if (android_logger_set_prune_list(logger_list, buf, bLen)) {
-                logcat_panic(false, "failed to set the prune list");
-            }
-            free(buf);
-        } else {
-            logcat_panic(false, "failed to set the prune list (alloc)");
+        if (android_logger_set_prune_list(logger_list.get(), setPruneList, len)) {
+            error(EXIT_FAILURE, 0, "Failed to set the prune list.");
         }
+        return EXIT_SUCCESS;
     }
 
     if (printStatistics || getPruneList) {
-        size_t len = 8192;
-        char *buf;
+        std::string buf(8192, '\0');
+        size_t ret_length = 0;
+        int retry = 32;
 
-        for(int retry = 32;
-                (retry >= 0) && ((buf = new char [len]));
-                delete [] buf, buf = NULL, --retry) {
+        for (; retry >= 0; --retry) {
             if (getPruneList) {
-                android_logger_get_prune_list(logger_list, buf, len);
+                android_logger_get_prune_list(logger_list.get(), buf.data(), buf.size());
             } else {
-                android_logger_get_statistics(logger_list, buf, len);
+                android_logger_get_statistics(logger_list.get(), buf.data(), buf.size());
             }
-            buf[len-1] = '\0';
-            if (atol(buf) < 3) {
-                delete [] buf;
-                buf = NULL;
+
+            ret_length = atol(buf.c_str());
+            if (ret_length < 3) {
+                error(EXIT_FAILURE, 0, "Failed to read data.");
+            }
+
+            if (ret_length < buf.size()) {
                 break;
             }
-            size_t ret = atol(buf) + 1;
-            if (ret <= len) {
-                len = ret;
-                break;
-            }
-            len = ret;
+
+            buf.resize(ret_length + 1);
         }
 
-        if (!buf) {
-            logcat_panic(false, "failed to read data");
+        if (retry < 0) {
+            error(EXIT_FAILURE, 0, "Failed to read data.");
         }
 
-        // remove trailing FF
-        char *cp = buf + len - 1;
-        *cp = '\0';
-        bool truncated = *--cp != '\f';
-        if (!truncated) {
-            *cp = '\0';
+        buf.resize(ret_length);
+        if (buf.back() == '\f') {
+            buf.pop_back();
         }
 
-        // squash out the byte count
-        cp = buf;
-        if (!truncated) {
-            while (isdigit(*cp)) {
-                ++cp;
-            }
-            if (*cp == '\n') {
-                ++cp;
-            }
-        }
+        // Remove the byte count prefix
+        const char* cp = buf.c_str();
+        while (isdigit(*cp)) ++cp;
+        if (*cp == '\n') ++cp;
 
-        printf("%s", cp);
-        delete [] buf;
+        size_t len = strlen(cp);
+        if (!WriteFully(output_fd_, cp, len)) {
+            error(EXIT_FAILURE, errno, "Failed to write to output fd");
+        }
         return EXIT_SUCCESS;
     }
 
+    if (getLogSize || setLogSize || clearLog) return EXIT_SUCCESS;
 
-    if (getLogSize) {
-        return EXIT_SUCCESS;
-    }
-    if (setLogSize || setPruneList) {
-        return EXIT_SUCCESS;
-    }
-    if (clearLog) {
-        return EXIT_SUCCESS;
-    }
+    SetupOutputAndSchedulingPolicy(!(mode & ANDROID_LOG_NONBLOCK));
 
-    //LOG_EVENT_INT(10, 12345);
-    //LOG_EVENT_LONG(11, 0x1122334455667788LL);
-    //LOG_EVENT_STRING(0, "whassup, doc?");
-
-    dev = NULL;
-    log_device_t unexpected("unexpected", false);
-    while (1) {
+    while (!max_count_ || print_count_ < max_count_) {
         struct log_msg log_msg;
-        log_device_t* d;
-        int ret = android_logger_list_read(logger_list, &log_msg);
+        int ret = android_logger_list_read(logger_list.get(), &log_msg);
+        if (!ret) {
+            error(EXIT_FAILURE, 0, R"init(Unexpected EOF!
 
-        if (ret == 0) {
-            logcat_panic(false, "read: unexpected EOF!\n");
+This means that either the device shut down, logd crashed, or this instance of logcat was unable to read log
+messages as quickly as they were being produced.
+
+If you have enabled significant logging, look into using the -G option to increase log buffer sizes.)init");
         }
 
         if (ret < 0) {
-            if (ret == -EAGAIN) {
-                break;
-            }
+            if (ret == -EAGAIN) break;
 
             if (ret == -EIO) {
-                logcat_panic(false, "read: unexpected EOF!\n");
+                error(EXIT_FAILURE, 0, "Unexpected EOF!");
             }
             if (ret == -EINVAL) {
-                logcat_panic(false, "read: unexpected length.\n");
+                error(EXIT_FAILURE, 0, "Unexpected length.");
             }
-            logcat_panic(false, "logcat read failure");
+            error(EXIT_FAILURE, errno, "Logcat read failure");
         }
 
-        for(d = devices; d; d = d->next) {
-            if (android_name_to_log_id(d->device) == log_msg.id()) {
-                break;
-            }
-        }
-        if (!d) {
-            g_devCount = 2; // set to Multiple
-            d = &unexpected;
-            d->binary = log_msg.id() == LOG_ID_EVENTS;
+        if (log_msg.id() > LOG_ID_MAX) {
+            error(EXIT_FAILURE, 0, "Unexpected log id (%d) over LOG_ID_MAX (%d).", log_msg.id(),
+                  LOG_ID_MAX);
         }
 
-        if (dev != d) {
-            dev = d;
-            maybePrintStart(dev, printDividers);
+        if (!uids.empty() && uids.count(log_msg.entry.uid) == 0) {
+            continue;
         }
-        if (g_printBinary) {
-            printBinary(&log_msg);
+
+        PrintDividers(log_msg.id(), printDividers);
+
+        if (print_binary_) {
+            if (!WriteFully(output_fd_, &log_msg, log_msg.len())) {
+                error(EXIT_FAILURE, errno, "Failed to write to output fd");
+            }
         } else {
-            processBuffer(dev, &log_msg);
+            ProcessBuffer(&log_msg);
         }
     }
-
-    android_logger_list_free(logger_list);
-
     return EXIT_SUCCESS;
 }
+
+int main(int argc, char** argv) {
+    Logcat logcat;
+    return logcat.Run(argc, argv);
+}
diff --git a/logcat/logcatd b/logcat/logcatd
new file mode 100755
index 0000000..5a1415d
--- /dev/null
+++ b/logcat/logcatd
@@ -0,0 +1,29 @@
+#! /system/bin/sh
+
+# This is primarily meant to be used by logpersist.  This script is run as an init service, which
+# first reads the 'last' logcat to persistent storage with `-L` then run logcat again without
+# `-L` to read the current logcat buffers to persistent storage.
+
+# init sets the umask to 077 for forked processes. logpersist needs to create files that are group
+# readable. So relax the umask to only disallow group wx and world rwx.
+umask 037
+
+has_last="false"
+for arg in "$@"; do
+  if [ "$arg" == "-L" -o "$arg" == "--last" ]; then
+    has_last="true"
+  fi
+done
+
+if [ "$has_last" == "true" ]; then
+  logcat "$@"
+fi
+
+args_without_last=()
+for arg in "$@"; do
+  if [ "$arg" != "-L" -a "$arg" != "--last" ]; then
+    ARGS+=("$arg")
+  fi
+done
+
+exec logcat "${ARGS[@]}"
diff --git a/logcat/logcatd.rc b/logcat/logcatd.rc
new file mode 100644
index 0000000..64d5500
--- /dev/null
+++ b/logcat/logcatd.rc
@@ -0,0 +1,62 @@
+#
+# init scriptures for logcatd persistent logging.
+#
+# Make sure any property changes are only performed with /data mounted, after
+# post-fs-data state because otherwise behavior is undefined. The exceptions
+# are device adjustments for logcatd service properties (persist.* overrides
+# notwithstanding) for logd.logpersistd.size logd.logpersistd.rotate_kbytes and
+# logd.logpersistd.buffer.
+
+# persist to non-persistent trampolines to permit device properties can be
+# overridden when /data mounts, or during runtime.
+on property:persist.logd.logpersistd.count=*
+    # expect /init to report failure if property empty (default)
+    setprop persist.logd.logpersistd.size ${persist.logd.logpersistd.count}
+
+on property:persist.logd.logpersistd.size=*
+    setprop logd.logpersistd.size ${persist.logd.logpersistd.size}
+
+on property:persist.logd.logpersistd.rotate_kbytes=*
+    setprop logd.logpersistd.rotate_kbytes ${persist.logd.logpersistd.rotate_kbytes}
+
+on property:persist.logd.logpersistd.buffer=*
+    setprop logd.logpersistd.buffer ${persist.logd.logpersistd.buffer}
+
+on property:persist.logd.logpersistd=logcatd
+    setprop logd.logpersistd logcatd
+
+# enable, prep and start logcatd service
+on load_persist_props_action
+    setprop logd.logpersistd.enable true
+
+on property:logd.logpersistd.enable=true && property:logd.logpersistd=logcatd
+    # log group should be able to read persisted logs
+    mkdir /data/misc/logd 0750 logd log
+    start logcatd
+
+# stop logcatd service and clear data
+on property:logd.logpersistd.enable=true && property:logd.logpersistd=clear
+    setprop persist.logd.logpersistd ""
+    stop logcatd
+    # logd for clear of only our files in /data/misc/logd
+    exec - logd log -- /system/bin/logcat -c -f /data/misc/logd/logcat -n ${logd.logpersistd.size:-256}
+    setprop logd.logpersistd ""
+
+# stop logcatd service
+on property:logd.logpersistd=stop
+    setprop persist.logd.logpersistd ""
+    stop logcatd
+    setprop logd.logpersistd ""
+
+on property:logd.logpersistd.enable=false
+    stop logcatd
+
+# logcatd service
+service logcatd /system/bin/logcatd -L -b ${logd.logpersistd.buffer:-all} -v threadtime -v usec -v printable -D -f /data/misc/logd/logcat -r ${logd.logpersistd.rotate_kbytes:-2048} -n ${logd.logpersistd.size:-256} --id=${ro.build.id}
+    class late_start
+    disabled
+    # logd for write to /data/misc/logd, log group for read from log daemon
+    user logd
+    group log
+    writepid /dev/cpuset/system-background/tasks
+    oom_score_adjust -600
diff --git a/logcat/logpersist b/logcat/logpersist
new file mode 100755
index 0000000..05b46f0
--- /dev/null
+++ b/logcat/logpersist
@@ -0,0 +1,178 @@
+#! /system/bin/sh
+# logpersist cat, start and stop handlers
+progname="${0##*/}"
+case `getprop ro.debuggable` in
+1) ;;
+*) echo "${progname} - Permission denied"
+   exit 1
+   ;;
+esac
+
+property=persist.logd.logpersistd
+
+case `getprop ${property#persist.}.enable` in
+true) ;;
+*) echo "${progname} - Disabled"
+   exit 1
+   ;;
+esac
+
+log_uid=logd
+log_tag_property=persist.log.tag
+data=/data/misc/logd/logcat
+service=logcatd
+size_default=256
+buffer_default=all
+args="${@}"
+
+size=${size_default}
+buffer=${buffer_default}
+clear=false
+while [ ${#} -gt 0 ]; do
+  case ${1} in
+    -c|--clear) clear=true ;;
+    --size=*) size="${1#--size=}" ;;
+    --rotate-count=*) size="${1#--rotate-count=}" ;;
+    -n|--size|--rotate-count) size="${2}" ; shift ;;
+    --buffer=*) buffer="${1#--buffer=}" ;;
+    -b|--buffer) buffer="${2}" ; shift ;;
+    -h|--help|*)
+      LEAD_SPACE_="`echo ${progname%.*} | tr '[ -~]' ' '`"
+      echo "${progname%.*}.cat             - dump current ${service} logs"
+      echo "${progname%.*}.start [--size=<size_in_kb>] [--buffer=<buffers>] [--clear]"
+      echo "${LEAD_SPACE_}                 - start ${service} service"
+      echo "${progname%.*}.stop [--clear]  - stop ${service} service"
+      case ${1} in
+        -h|--help) exit 0 ;;
+        *) echo ERROR: bad argument ${@} >&2 ; exit 1 ;;
+      esac
+      ;;
+  esac
+  shift
+done
+
+if [ -z "${size}" -o "${size_default}" = "${size}" ]; then
+  unset size
+fi
+if [ -n "${size}" ] &&
+  ! ( [ 0 -lt "${size}" ] && [ 2048 -ge "${size}" ] ) >/dev/null 2>&1; then
+  echo ERROR: Invalid --size ${size} >&2
+  exit 1
+fi
+if [ -z "${buffer}" -o "${buffer_default}" = "${buffer}" ]; then
+  unset buffer
+fi
+if [ -n "${buffer}" ] && ! logcat -b ${buffer} -g >/dev/null 2>&1; then
+  echo ERROR: Invalid --buffer ${buffer} >&2
+  exit 1
+fi
+
+log_tag="`getprop ${log_tag_property}`"
+logd_logpersistd="`getprop ${property}`"
+
+case ${progname} in
+*.cat)
+  if [ -n "${size}${buffer}" -o "true" = "${clear}" ]; then
+    echo WARNING: Can not use --clear, --size or --buffer with ${progname%.*}.cat >&2
+  fi
+  su ${log_uid} ls "${data%/*}" |
+  tr -d '\r' |
+  sort -ru |
+  sed "s#^#${data%/*}/#" |
+  grep "${data}[.]*[0-9]*\$" |
+  su ${log_uid} xargs cat
+  ;;
+*.start)
+  current_buffer="`getprop ${property#persist.}.buffer`"
+  current_size="`getprop ${property#persist.}.size`"
+  if [ "${service}" = "`getprop ${property#persist.}`" ]; then
+    if [ "true" = "${clear}" ]; then
+      setprop ${property#persist.} "clear"
+    elif [ "${buffer}|${size}" != "${current_buffer}|${current_size}" ]; then
+      echo   "ERROR: Changing existing collection parameters from" >&2
+      if [ "${buffer}" != "${current_buffer}" ]; then
+        a=${current_buffer}
+        b=${buffer}
+        if [ -z "${a}" ]; then a="${default_buffer}"; fi
+        if [ -z "${b}" ]; then b="${default_buffer}"; fi
+        echo "           --buffer ${a} to ${b}" >&2
+      fi
+      if [ "${size}" != "${current_size}" ]; then
+        a=${current_size}
+        b=${size}
+        if [ -z "${a}" ]; then a="${default_size}"; fi
+        if [ -z "${b}" ]; then b="${default_size}"; fi
+        echo "           --size ${a} to ${b}" >&2
+      fi
+      echo   "       Are you sure you want to do this?" >&2
+      echo   "       Suggest add --clear to erase data and restart with new settings." >&2
+      echo   "       To blindly override and retain data, ${progname%.*}.stop first." >&2
+      exit 1
+    fi
+  elif [ "true" = "${clear}" ]; then
+    setprop ${property#persist.} "clear"
+  fi
+  if [ -n "${buffer}${current_buffer}" ]; then
+    setprop ${property}.buffer "${buffer}"
+    if [ -z "${buffer}" ]; then
+      # deal with trampoline for empty properties
+      setprop ${property#persist.}.buffer ""
+    fi
+  fi
+  if [ -n "${size}${current_size}" ]; then
+    setprop ${property}.size "${size}"
+    if [ -z "${size}" ]; then
+      # deal with trampoline for empty properties
+      setprop ${property#persist.}.size ""
+    fi
+  fi
+  while [ "clear" = "`getprop ${property#persist.}`" ]; do
+    continue
+  done
+  # Tell Settings that we are back on again if we turned logging off
+  tag="${log_tag#Settings}"
+  if [ X"${log_tag}" != X"${tag}" ]; then
+    echo "WARNING: enabling logd service" >&2
+    setprop ${log_tag_property} "${tag#,}"
+  fi
+  # ${service}.rc does the heavy lifting with the following trigger
+  setprop ${property} ${service}
+  # 20ms done, to permit process feedback check
+  sleep 1
+  getprop ${property#persist.}
+  # also generate an error return code if not found running
+  pgrep -u ${log_uid} ${service%d}
+  ;;
+*.stop)
+  if [ -n "${size}${buffer}" ]; then
+    echo "WARNING: Can not use --size or --buffer with ${progname%.*}.stop" >&2
+  fi
+  if [ "true" = "${clear}" ]; then
+    setprop ${property#persist.} "clear"
+  else
+    setprop ${property#persist.} "stop"
+  fi
+  if [ -n "`getprop ${property#persist.}.buffer`" ]; then
+    setprop ${property}.buffer ""
+    # deal with trampoline for empty properties
+    setprop ${property#persist.}.buffer ""
+  fi
+  if [ -n "`getprop ${property#persist.}.size`" ]; then
+    setprop ${property}.size ""
+    # deal with trampoline for empty properties
+    setprop ${property#persist.}.size ""
+  fi
+  while [ "clear" = "`getprop ${property#persist.}`" ]; do
+    continue
+  done
+  ;;
+*)
+  echo "ERROR: Unexpected command ${0##*/} ${args}" >&2
+  exit 1
+esac
+
+if [ X"${log_tag}" != X"`getprop ${log_tag_property}`" ] ||
+   [ X"${logd_logpersistd}" != X"`getprop ${property}`" ]; then
+  echo "WARNING: killing Settings application to pull in new values" >&2
+  am force-stop com.android.settings
+fi
diff --git a/logcat/tests/Android.bp b/logcat/tests/Android.bp
new file mode 100644
index 0000000..ab84150
--- /dev/null
+++ b/logcat/tests/Android.bp
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2013-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.
+//
+
+cc_defaults {
+    name: "logcat-tests-defaults",
+    cflags: [
+        "-fstack-protector-all",
+        "-g",
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-fno-builtin",
+    ],
+}
+
+// -----------------------------------------------------------------------------
+// Benchmarks
+// ----------------------------------------------------------------------------
+
+// Build benchmarks for the device. Run with:
+//   adb shell /data/nativetest/logcat-benchmarks/logcat-benchmarks
+cc_benchmark {
+    name: "logcat-benchmarks",
+    defaults: ["logcat-tests-defaults"],
+    srcs: ["logcat_benchmark.cpp"],
+    shared_libs: ["libbase"],
+}
+
+// -----------------------------------------------------------------------------
+// Unit tests.
+// -----------------------------------------------------------------------------
+
+// Build tests for the device (with .so). Run with:
+//   adb shell /data/nativetest/logcat-unit-tests/logcat-unit-tests
+cc_test {
+    name: "logcat-unit-tests",
+    defaults: ["logcat-tests-defaults"],
+    shared_libs: ["libbase"],
+    static_libs: ["liblog"],
+    srcs: [
+        "logcat_test.cpp",
+        "logcatd_test.cpp",
+    ],
+}
diff --git a/logcat/tests/Android.mk b/logcat/tests/Android.mk
deleted file mode 100644
index a28664e..0000000
--- a/logcat/tests/Android.mk
+++ /dev/null
@@ -1,61 +0,0 @@
-#
-# Copyright (C) 2013-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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-test_module_prefix := logcat-
-test_tags := tests
-
-test_c_flags := \
-    -fstack-protector-all \
-    -g \
-    -Wall -Wextra \
-    -Werror \
-    -fno-builtin \
-    -std=gnu++11
-
-# -----------------------------------------------------------------------------
-# Benchmarks (actually a gTest where the result code does not matter)
-# ----------------------------------------------------------------------------
-
-benchmark_src_files := \
-    logcat_benchmark.cpp
-
-# Build benchmarks for the device. Run with:
-#   adb shell /data/nativetest/logcat-benchmarks/logcat-benchmarks
-include $(CLEAR_VARS)
-LOCAL_MODULE := $(test_module_prefix)benchmarks
-LOCAL_MODULE_TAGS := $(test_tags)
-LOCAL_CFLAGS += $(test_c_flags)
-LOCAL_SRC_FILES := $(benchmark_src_files)
-include $(BUILD_NATIVE_TEST)
-
-# -----------------------------------------------------------------------------
-# Unit tests.
-# -----------------------------------------------------------------------------
-
-test_src_files := \
-    logcat_test.cpp \
-
-# Build tests for the device (with .so). Run with:
-#   adb shell /data/nativetest/logcat-unit-tests/logcat-unit-tests
-include $(CLEAR_VARS)
-LOCAL_MODULE := $(test_module_prefix)unit-tests
-LOCAL_MODULE_TAGS := $(test_tags)
-LOCAL_CFLAGS += $(test_c_flags)
-LOCAL_SHARED_LIBRARIES := liblog
-LOCAL_SRC_FILES := $(test_src_files)
-include $(BUILD_NATIVE_TEST)
diff --git a/logcat/tests/logcat_benchmark.cpp b/logcat/tests/logcat_benchmark.cpp
index be815be..8d88628 100644
--- a/logcat/tests/logcat_benchmark.cpp
+++ b/logcat/tests/logcat_benchmark.cpp
@@ -18,19 +18,22 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include <gtest/gtest.h>
+#include <benchmark/benchmark.h>
 
 static const char begin[] = "--------- beginning of ";
 
-TEST(logcat, sorted_order) {
-    FILE *fp;
+static void BM_logcat_sorted_order(benchmark::State& state) {
+    FILE* fp;
 
-    ASSERT_TRUE(NULL != (fp = popen(
-      "logcat -v time -b radio -b events -b system -b main -d 2>/dev/null",
-      "r")));
+    if (!state.KeepRunning()) return;
+
+    fp = popen(
+        "logcat -v time -b radio -b events -b system -b main -d 2>/dev/null",
+        "r");
+    if (!fp) return;
 
     class timestamp {
-    private:
+       private:
         int month;
         int day;
         int hour;
@@ -39,44 +42,39 @@
         int millisecond;
         bool ok;
 
-    public:
-        void init(const char *buffer)
-        {
+       public:
+        void init(const char* buffer) {
             ok = false;
             if (buffer != NULL) {
-                ok = sscanf(buffer, "%d-%d %d:%d:%d.%d ",
-                    &month, &day, &hour, &minute, &second, &millisecond) == 6;
+                ok = sscanf(buffer, "%d-%d %d:%d:%d.%d ", &month, &day, &hour,
+                            &minute, &second, &millisecond) == 6;
             }
         }
 
-        timestamp(const char *buffer)
-        {
+        explicit timestamp(const char* buffer) {
             init(buffer);
         }
 
-        bool operator< (timestamp &T)
-        {
-            return !ok || !T.ok
-             || (month < T.month)
-             || ((month == T.month)
-              && ((day < T.day)
-               || ((day == T.day)
-                && ((hour < T.hour)
-                 || ((hour == T.hour)
-                  && ((minute < T.minute)
-                   || ((minute == T.minute)
-                    && ((second < T.second)
-                     || ((second == T.second)
-                      && (millisecond < T.millisecond))))))))));
+        bool operator<(timestamp& T) {
+            return !ok || !T.ok || (month < T.month) ||
+                   ((month == T.month) &&
+                    ((day < T.day) ||
+                     ((day == T.day) &&
+                      ((hour < T.hour) ||
+                       ((hour == T.hour) &&
+                        ((minute < T.minute) ||
+                         ((minute == T.minute) &&
+                          ((second < T.second) ||
+                           ((second == T.second) &&
+                            (millisecond < T.millisecond))))))))));
         }
 
-        bool valid(void)
-        {
+        bool valid(void) {
             return ok;
         }
     } last(NULL);
 
-    char *last_buffer = NULL;
+    char* last_buffer = NULL;
     char buffer[5120];
 
     int count = 0;
@@ -114,15 +112,22 @@
 
     // Allow few fails, happens with readers active
     fprintf(stderr, "%s: %d/%d out of order entries\n",
-            (next_lt_last)
-                ? ((next_lt_last <= max_ok)
-                    ? "WARNING"
-                    : "ERROR")
-                : "INFO",
+            (next_lt_last) ? ((next_lt_last <= max_ok) ? "WARNING" : "ERROR")
+                           : "INFO",
             next_lt_last, count);
 
-    EXPECT_GE(max_ok, next_lt_last);
+    if (next_lt_last > max_ok) {
+        fprintf(stderr, "EXPECT_GE(max_ok=%d, next_lt_last=%d)\n", max_ok,
+                next_lt_last);
+    }
 
     // sample statistically too small
-    EXPECT_LT(100, count);
+    if (count < 100) {
+        fprintf(stderr, "EXPECT_LT(100, count=%d)\n", count);
+    }
+
+    state.KeepRunning();
 }
+BENCHMARK(BM_logcat_sorted_order);
+
+BENCHMARK_MAIN();
diff --git a/logcat/tests/logcat_test.cpp b/logcat/tests/logcat_test.cpp
index de2db67..735fd94 100644
--- a/logcat/tests/logcat_test.cpp
+++ b/logcat/tests/logcat_test.cpp
@@ -15,48 +15,91 @@
  */
 
 #include <ctype.h>
+#include <dirent.h>
+#include <pwd.h>
 #include <signal.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/cdefs.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
 
+#include <memory>
+#include <regex>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/macros.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <gtest/gtest.h>
+#include <log/event_tag_map.h>
 #include <log/log.h>
-#include <log/logger.h>
-#include <log/log_read.h>
+#include <log/log_event_list.h>
+
+#ifndef logcat_executable
+#define USING_LOGCAT_EXECUTABLE_DEFAULT
+#define logcat_executable "logcat"
+#endif
+
+#define BIG_BUFFER (5 * 1024)
+
+// rest(), let the logs settle.
+//
+// logd is in a background cgroup and under extreme load can take up to
+// 3 seconds to land a log entry. Under moderate load we can do with 200ms.
+static void rest() {
+    static const useconds_t restPeriod = 200000;
+
+    usleep(restPeriod);
+}
 
 // enhanced version of LOG_FAILURE_RETRY to add support for EAGAIN and
 // non-syscall libs. Since we are only using this in the emergency of
 // a signal to stuff a terminating code into the logs, we will spin rather
 // than try a usleep.
-#define LOG_FAILURE_RETRY(exp) ({  \
-    typeof (exp) _rc;              \
-    do {                           \
-        _rc = (exp);               \
-    } while (((_rc == -1)          \
-           && ((errno == EINTR)    \
-            || (errno == EAGAIN))) \
-          || (_rc == -EINTR)       \
-          || (_rc == -EAGAIN));    \
-    _rc; })
+#define LOG_FAILURE_RETRY(exp)                                               \
+    ({                                                                       \
+        typeof(exp) _rc;                                                     \
+        do {                                                                 \
+            _rc = (exp);                                                     \
+        } while (((_rc == -1) && ((errno == EINTR) || (errno == EAGAIN))) || \
+                 (_rc == -EINTR) || (_rc == -EAGAIN));                       \
+        _rc;                                                                 \
+    })
 
 static const char begin[] = "--------- beginning of ";
 
 TEST(logcat, buckets) {
-    FILE *fp;
+    FILE* fp;
 
-    ASSERT_TRUE(NULL != (fp = popen(
-      "logcat -b radio -b events -b system -b main -d 2>/dev/null",
-      "r")));
+#undef LOG_TAG
+#define LOG_TAG "inject.buckets"
+    // inject messages into radio, system, main and events buffers to
+    // ensure that we see all the begin[] bucket messages.
+    RLOGE(logcat_executable);
+    SLOGE(logcat_executable);
+    ALOGE(logcat_executable);
+    __android_log_bswrite(0, logcat_executable ".inject.buckets");
+    rest();
 
-    char buffer[5120];
+    ASSERT_TRUE(NULL != (fp = popen(logcat_executable
+                                    " -b radio -b events -b system -b main -d 2>/dev/null",
+                                    "r")));
+
+    char buffer[BIG_BUFFER];
 
     int ids = 0;
     int count = 0;
 
     while (fgets(buffer, sizeof(buffer), fp)) {
         if (!strncmp(begin, buffer, sizeof(begin) - 1)) {
-            while (char *cp = strrchr(buffer, '\n')) {
+            while (char* cp = strrchr(buffer, '\n')) {
                 *cp = '\0';
             }
             log_id_t id = android_name_to_log_id(buffer + sizeof(begin) - 1);
@@ -67,173 +110,350 @@
 
     pclose(fp);
 
-    EXPECT_EQ(15, ids);
+    EXPECT_EQ(ids, 15);
 
-    EXPECT_EQ(4, count);
+    EXPECT_EQ(count, 4);
 }
 
-TEST(logcat, tail_3) {
-    FILE *fp;
+TEST(logcat, event_tag_filter) {
+    FILE* fp;
 
-    ASSERT_TRUE(NULL != (fp = popen(
-      "logcat -v long -b radio -b events -b system -b main -t 3 2>/dev/null",
-      "r")));
+#undef LOG_TAG
+#define LOG_TAG "inject.filter"
+    // inject messages into radio, system and main buffers
+    // with our unique log tag to test logcat filter.
+    RLOGE(logcat_executable);
+    SLOGE(logcat_executable);
+    ALOGE(logcat_executable);
+    rest();
 
-    char buffer[5120];
+    std::string command = android::base::StringPrintf(
+        logcat_executable
+        " -b radio -b system -b main --pid=%d -d -s inject.filter 2>/dev/null",
+        getpid());
+    ASSERT_TRUE(NULL != (fp = popen(command.c_str(), "r")));
+
+    char buffer[BIG_BUFFER];
 
     int count = 0;
 
     while (fgets(buffer, sizeof(buffer), fp)) {
-        if ((buffer[0] == '[') && (buffer[1] == ' ')
-         && isdigit(buffer[2]) && isdigit(buffer[3])
-         && (buffer[4] == '-')) {
-            ++count;
-        }
+        if (strncmp(begin, buffer, sizeof(begin) - 1)) ++count;
     }
 
     pclose(fp);
 
+    // logcat, liblogcat and logcatd test instances result in the progression
+    // of 3, 6 and 9 for our counts as each round is performed.
+    EXPECT_GE(count, 3);
+    EXPECT_LE(count, 9);
+    EXPECT_EQ(count % 3, 0);
+}
+
+// If there is not enough background noise in the logs, then spam the logs to
+// permit tail checking so that the tests can progress.
+static size_t inject(ssize_t count) {
+    if (count <= 0) return 0;
+
+    static const size_t retry = 4;
+    size_t errors = retry;
+    size_t num = 0;
+    for (;;) {
+        log_time ts(CLOCK_MONOTONIC);
+        if (__android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)) >= 0) {
+            if (++num >= (size_t)count) {
+                // let data settle end-to-end
+                sleep(3);
+                return num;
+            }
+            errors = retry;
+            usleep(100);  // ~32 per timer tick, we are a spammer regardless
+        } else if (--errors <= 0) {
+            return num;
+        }
+    }
+    // NOTREACH
+    return num;
+}
+
+TEST(logcat, year) {
+    int count;
+    int tries = 3;  // in case run too soon after system start or buffer clear
+
+    do {
+        FILE* fp;
+
+        char needle[32];
+        time_t now;
+        time(&now);
+        struct tm* ptm;
+#if !defined(_WIN32)
+        struct tm tmBuf;
+        ptm = localtime_r(&now, &tmBuf);
+#else
+        ptm = localtime(&&now);
+#endif
+        strftime(needle, sizeof(needle), "[ %Y-", ptm);
+
+        ASSERT_TRUE(NULL !=
+                    (fp = popen(logcat_executable " -v long -v year -b all -t 3 2>/dev/null", "r")));
+
+        char buffer[BIG_BUFFER];
+
+        count = 0;
+
+        while (fgets(buffer, sizeof(buffer), fp)) {
+            if (!strncmp(buffer, needle, strlen(needle))) {
+                ++count;
+            }
+        }
+        pclose(fp);
+
+    } while ((count < 3) && --tries && inject(3 - count));
+
     ASSERT_EQ(3, count);
 }
 
-TEST(logcat, tail_10) {
-    FILE *fp;
+// Return a pointer to each null terminated -v long time field.
+static char* fgetLongTime(char* buffer, size_t buflen, FILE* fp) {
+    while (fgets(buffer, buflen, fp)) {
+        char* cp = buffer;
+        if (*cp != '[') {
+            continue;
+        }
+        while (*++cp == ' ') {
+            ;
+        }
+        char* ep = cp;
+        while (isdigit(*ep)) {
+            ++ep;
+        }
+        if ((*ep != '-') && (*ep != '.')) {
+            continue;
+        }
+        // Find PID field.  Look for ': ' or ':[0-9][0-9][0-9]'
+        while (((ep = strchr(ep, ':'))) && (*++ep != ' ')) {
+            if (isdigit(ep[0]) && isdigit(ep[1]) && isdigit(ep[2])) break;
+        }
+        if (!ep) {
+            continue;
+        }
+        static const size_t pid_field_width = 7;
+        ep -= pid_field_width;
+        *ep = '\0';
+        return cp;
+    }
+    return NULL;
+}
 
-    ASSERT_TRUE(NULL != (fp = popen(
-      "logcat -v long -b radio -b events -b system -b main -t 10 2>/dev/null",
-      "r")));
+TEST(logcat, tz) {
+    int tries = 4;  // in case run too soon after system start or buffer clear
+    int count;
 
-    char buffer[5120];
+    do {
+        FILE* fp;
+
+        ASSERT_TRUE(NULL != (fp = popen(logcat_executable
+                                        " -v long -v America/Los_Angeles -b all -t 3 2>/dev/null",
+                                        "r")));
+
+        char buffer[BIG_BUFFER];
+
+        count = 0;
+
+        while (fgetLongTime(buffer, sizeof(buffer), fp)) {
+            if (strstr(buffer, " -0700") || strstr(buffer, " -0800")) {
+                ++count;
+            } else {
+                fprintf(stderr, "ts=\"%s\"\n", buffer + 2);
+            }
+        }
+
+        pclose(fp);
+
+    } while ((count < 3) && --tries && inject(3 - count));
+
+    ASSERT_EQ(3, count);
+}
+
+TEST(logcat, ntz) {
+    FILE* fp;
+
+    ASSERT_TRUE(NULL !=
+                (fp = popen(logcat_executable
+                            " -v long -v America/Los_Angeles -v zone -b all -t 3 2>/dev/null",
+                            "r")));
+
+    char buffer[BIG_BUFFER];
 
     int count = 0;
 
-    while (fgets(buffer, sizeof(buffer), fp)) {
-        if ((buffer[0] == '[') && (buffer[1] == ' ')
-         && isdigit(buffer[2]) && isdigit(buffer[3])
-         && (buffer[4] == '-')) {
+    while (fgetLongTime(buffer, sizeof(buffer), fp)) {
+        if (strstr(buffer, " -0700") || strstr(buffer, " -0800")) {
             ++count;
         }
     }
 
     pclose(fp);
 
-    ASSERT_EQ(10, count);
+    ASSERT_EQ(0, count);
+}
+
+static void do_tail(int num) {
+    int tries = 4;  // in case run too soon after system start or buffer clear
+    int count;
+
+    if (num > 10) ++tries;
+    if (num > 100) ++tries;
+    do {
+        char buffer[BIG_BUFFER];
+
+        snprintf(buffer, sizeof(buffer),
+                 "ANDROID_PRINTF_LOG=long logcat -b all -t %d 2>/dev/null", num);
+
+        FILE* fp;
+        ASSERT_TRUE(NULL != (fp = popen(buffer, "r")));
+
+        count = 0;
+
+        while (fgetLongTime(buffer, sizeof(buffer), fp)) {
+            ++count;
+        }
+
+        pclose(fp);
+
+    } while ((count < num) && --tries && inject(num - count));
+
+    ASSERT_EQ(num, count);
+}
+
+TEST(logcat, tail_3) {
+    do_tail(3);
+}
+
+TEST(logcat, tail_10) {
+    do_tail(10);
 }
 
 TEST(logcat, tail_100) {
-    FILE *fp;
-
-    ASSERT_TRUE(NULL != (fp = popen(
-      "logcat -v long -b radio -b events -b system -b main -t 100 2>/dev/null",
-      "r")));
-
-    char buffer[5120];
-
-    int count = 0;
-
-    while (fgets(buffer, sizeof(buffer), fp)) {
-        if ((buffer[0] == '[') && (buffer[1] == ' ')
-         && isdigit(buffer[2]) && isdigit(buffer[3])
-         && (buffer[4] == '-')) {
-            ++count;
-        }
-    }
-
-    pclose(fp);
-
-    ASSERT_EQ(100, count);
+    do_tail(100);
 }
 
 TEST(logcat, tail_1000) {
-    FILE *fp;
-
-    ASSERT_TRUE(NULL != (fp = popen(
-      "logcat -v long -b radio -b events -b system -b main -t 1000 2>/dev/null",
-      "r")));
-
-    char buffer[5120];
-
-    int count = 0;
-
-    while (fgets(buffer, sizeof(buffer), fp)) {
-        if ((buffer[0] == '[') && (buffer[1] == ' ')
-         && isdigit(buffer[2]) && isdigit(buffer[3])
-         && (buffer[4] == '-')) {
-            ++count;
-        }
-    }
-
-    pclose(fp);
-
-    ASSERT_EQ(1000, count);
+    do_tail(1000);
 }
 
-TEST(logcat, tail_time) {
-    FILE *fp;
+static void do_tail_time(const char* cmd) {
+    FILE* fp;
+    int count;
+    char buffer[BIG_BUFFER];
+    char* last_timestamp = NULL;
+    // Hard to predict 100% if first (overlap) or second line will match.
+    // -v nsec will in a substantial majority be the second line.
+    char* first_timestamp = NULL;
+    char* second_timestamp = NULL;
+    char* input;
 
-    ASSERT_TRUE(NULL != (fp = popen("logcat -v long -b all -t 10 2>&1", "r")));
+    int tries = 4;  // in case run too soon after system start or buffer clear
 
-    char buffer[5120];
-    char *last_timestamp = NULL;
-    char *first_timestamp = NULL;
-    int count = 0;
-    const unsigned int time_length = 18;
-    const unsigned int time_offset = 2;
+    do {
+        snprintf(buffer, sizeof(buffer), "%s -t 10 2>&1", cmd);
+        ASSERT_TRUE(NULL != (fp = popen(buffer, "r")));
+        count = 0;
 
-    while (fgets(buffer, sizeof(buffer), fp)) {
-        if ((buffer[0] == '[') && (buffer[1] == ' ')
-         && isdigit(buffer[time_offset]) && isdigit(buffer[time_offset + 1])
-         && (buffer[time_offset + 2] == '-')) {
+        while ((input = fgetLongTime(buffer, sizeof(buffer), fp))) {
             ++count;
-            buffer[time_length + time_offset] = '\0';
             if (!first_timestamp) {
-                first_timestamp = strdup(buffer + time_offset);
+                first_timestamp = strdup(input);
+            } else if (!second_timestamp) {
+                second_timestamp = strdup(input);
             }
             free(last_timestamp);
-            last_timestamp = strdup(buffer + time_offset);
+            last_timestamp = strdup(input);
         }
-    }
-    pclose(fp);
+        pclose(fp);
 
-    EXPECT_EQ(10, count);
+    } while ((count < 10) && --tries && inject(10 - count));
+
+    EXPECT_EQ(count, 10);  // We want _some_ history, too small, falses below
     EXPECT_TRUE(last_timestamp != NULL);
     EXPECT_TRUE(first_timestamp != NULL);
+    EXPECT_TRUE(second_timestamp != NULL);
 
-    snprintf(buffer, sizeof(buffer), "logcat -v long -b all -t '%s' 2>&1",
-             first_timestamp);
+    snprintf(buffer, sizeof(buffer), "%s -t '%s' 2>&1", cmd, first_timestamp);
     ASSERT_TRUE(NULL != (fp = popen(buffer, "r")));
 
     int second_count = 0;
     int last_timestamp_count = -1;
 
-    while (fgets(buffer, sizeof(buffer), fp)) {
-        if ((buffer[0] == '[') && (buffer[1] == ' ')
-         && isdigit(buffer[time_offset]) && isdigit(buffer[time_offset + 1])
-         && (buffer[time_offset + 2] == '-')) {
-            ++second_count;
-            buffer[time_length + time_offset] = '\0';
-            if (first_timestamp) {
-                // we can get a transitory *extremely* rare failure if hidden
-                // underneath the time is *exactly* XX-XX XX:XX:XX.XXX000000
-                EXPECT_STREQ(buffer + time_offset, first_timestamp);
-                free(first_timestamp);
-                first_timestamp = NULL;
+    --count;  // One less unless we match the first_timestamp
+    bool found = false;
+    while ((input = fgetLongTime(buffer, sizeof(buffer), fp))) {
+        ++second_count;
+        // We want to highlight if we skip to the next entry.
+        // WAI, if the time in logd is *exactly*
+        // XX-XX XX:XX:XX.XXXXXX000 (usec) or XX-XX XX:XX:XX.XXX000000
+        // this can happen, but it should not happen with nsec.
+        // We can make this WAI behavior happen 1000 times less
+        // frequently if the caller does not use the -v usec flag,
+        // but always the second (always skip) if they use the
+        // (undocumented) -v nsec flag.
+        if (first_timestamp) {
+            found = !strcmp(input, first_timestamp);
+            if (found) {
+                ++count;
+                GTEST_LOG_(INFO)
+                    << "input = first(" << first_timestamp << ")\n";
             }
-            if (!strcmp(buffer + time_offset, last_timestamp)) {
-                last_timestamp_count = second_count;
+            free(first_timestamp);
+            first_timestamp = NULL;
+        }
+        if (second_timestamp) {
+            found = found || !strcmp(input, second_timestamp);
+            if (!found) {
+                GTEST_LOG_(INFO) << "input(" << input << ") != second("
+                                 << second_timestamp << ")\n";
             }
+            free(second_timestamp);
+            second_timestamp = NULL;
+        }
+        if (!strcmp(input, last_timestamp)) {
+            last_timestamp_count = second_count;
         }
     }
     pclose(fp);
 
+    EXPECT_TRUE(found);
+    if (!found) {
+        if (first_timestamp) {
+            GTEST_LOG_(INFO) << "first = " << first_timestamp << "\n";
+        }
+        if (second_timestamp) {
+            GTEST_LOG_(INFO) << "second = " << second_timestamp << "\n";
+        }
+        if (last_timestamp) {
+            GTEST_LOG_(INFO) << "last = " << last_timestamp << "\n";
+        }
+    }
     free(last_timestamp);
     last_timestamp = NULL;
+    free(first_timestamp);
+    free(second_timestamp);
 
     EXPECT_TRUE(first_timestamp == NULL);
+    EXPECT_TRUE(second_timestamp == NULL);
     EXPECT_LE(count, second_count);
     EXPECT_LE(count, last_timestamp_count);
 }
 
+TEST(logcat, tail_time) {
+    do_tail_time(logcat_executable " -v long -v nsec -b all");
+}
+
+TEST(logcat, tail_time_epoch) {
+    do_tail_time(logcat_executable " -v long -v nsec -v epoch -b all");
+}
+
 TEST(logcat, End_to_End) {
     pid_t pid = getpid();
 
@@ -241,12 +461,11 @@
 
     ASSERT_LT(0, __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)));
 
-    FILE *fp;
-    ASSERT_TRUE(NULL != (fp = popen(
-      "logcat -v brief -b events -t 100 2>/dev/null",
-      "r")));
+    FILE* fp;
+    ASSERT_TRUE(NULL !=
+                (fp = popen(logcat_executable " -v brief -b events -t 100 2>/dev/null", "r")));
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
 
     int count = 0;
 
@@ -254,13 +473,13 @@
         int p;
         unsigned long long t;
 
-        if ((2 != sscanf(buffer, "I/[0]     ( %d): %llu", &p, &t))
-         || (p != pid)) {
+        if ((2 != sscanf(buffer, "I/[0]     ( %d): %llu", &p, &t)) ||
+            (p != pid)) {
             continue;
         }
 
-        log_time tx((const char *) &t);
-        if (ts == tx) {
+        log_time* tx = reinterpret_cast<log_time*>(&t);
+        if (ts == *tx) {
             ++count;
         }
     }
@@ -270,88 +489,159 @@
     ASSERT_EQ(1, count);
 }
 
-TEST(logcat, get_size) {
-    FILE *fp;
+TEST(logcat, End_to_End_multitude) {
+    pid_t pid = getpid();
 
-    // NB: crash log only available in user space
-    ASSERT_TRUE(NULL != (fp = popen(
-      "logcat -v brief -b radio -b events -b system -b main -g 2>/dev/null",
-      "r")));
+    log_time ts(CLOCK_MONOTONIC);
 
-    char buffer[5120];
+    ASSERT_LT(0, __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)));
+
+    FILE* fp[256];  // does this count as a multitude!
+    memset(fp, 0, sizeof(fp));
+    size_t num = 0;
+    do {
+        EXPECT_TRUE(NULL != (fp[num] = popen(logcat_executable " -v brief -b events -t 100", "r")));
+        if (!fp[num]) {
+            fprintf(stderr,
+                    "WARNING: limiting to %zu simultaneous logcat operations\n",
+                    num);
+            break;
+        }
+    } while (++num < sizeof(fp) / sizeof(fp[0]));
+
+    char buffer[BIG_BUFFER];
+
+    size_t count = 0;
+
+    for (size_t idx = 0; idx < sizeof(fp) / sizeof(fp[0]); ++idx) {
+        if (!fp[idx]) break;
+        while (fgets(buffer, sizeof(buffer), fp[idx])) {
+            int p;
+            unsigned long long t;
+
+            if ((2 != sscanf(buffer, "I/[0]     ( %d): %llu", &p, &t)) ||
+                (p != pid)) {
+                continue;
+            }
+
+            log_time* tx = reinterpret_cast<log_time*>(&t);
+            if (ts == *tx) {
+                ++count;
+            }
+        }
+
+        pclose(fp[idx]);
+    }
+
+    ASSERT_EQ(num, count);
+}
+
+static int get_groups(const char* cmd) {
+    FILE* fp;
+
+    EXPECT_TRUE(NULL != (fp = popen(cmd, "r")));
+
+    if (fp == NULL) {
+        return 0;
+    }
+
+    char buffer[BIG_BUFFER];
 
     int count = 0;
 
     while (fgets(buffer, sizeof(buffer), fp)) {
-        int size, consumed, max, payload;
-        char size_mult[2], consumed_mult[2];
+        int size, consumed, readable, max, payload;
+        char size_mult[4], consumed_mult[4], readable_mult[4];
         long full_size, full_consumed;
 
         size = consumed = max = payload = 0;
         // NB: crash log can be very small, not hit a Kb of consumed space
         //     doubly lucky we are not including it.
-        if (6 != sscanf(buffer, "%*s ring buffer is %d%2s (%d%2s consumed),"
-                                " max entry is %db, max payload is %db",
-                                &size, size_mult, &consumed, consumed_mult,
-                                &max, &payload)) {
-            fprintf(stderr, "WARNING: Parse error: %s", buffer);
-            continue;
-        }
+        EXPECT_EQ(8, sscanf(buffer,
+                            "%*s ring buffer is %d %3s (%d %3s consumed, %d %3s readable),"
+                            " max entry is %d B, max payload is %d B",
+                            &size, size_mult, &consumed, consumed_mult, &readable, readable_mult,
+                            &max, &payload))
+                << "Parse error on: " << buffer;
         full_size = size;
-        switch(size_mult[0]) {
-        case 'G':
-            full_size *= 1024;
-            /* FALLTHRU */
-        case 'M':
-            full_size *= 1024;
-            /* FALLTHRU */
-        case 'K':
-            full_size *= 1024;
-            /* FALLTHRU */
-        case 'b':
-            break;
+        switch (size_mult[0]) {
+            case 'G':
+                full_size *= 1024;
+                FALLTHROUGH_INTENDED;
+            case 'M':
+                full_size *= 1024;
+                FALLTHROUGH_INTENDED;
+            case 'K':
+                full_size *= 1024;
+                FALLTHROUGH_INTENDED;
+            case 'B':
+                break;
+            default:
+                ADD_FAILURE() << "Parse error on multiplier: " << size_mult;
         }
         full_consumed = consumed;
-        switch(consumed_mult[0]) {
-        case 'G':
-            full_consumed *= 1024;
-            /* FALLTHRU */
-        case 'M':
-            full_consumed *= 1024;
-            /* FALLTHRU */
-        case 'K':
-            full_consumed *= 1024;
-            /* FALLTHRU */
-        case 'b':
-            break;
+        switch (consumed_mult[0]) {
+            case 'G':
+                full_consumed *= 1024;
+                FALLTHROUGH_INTENDED;
+            case 'M':
+                full_consumed *= 1024;
+                FALLTHROUGH_INTENDED;
+            case 'K':
+                full_consumed *= 1024;
+                FALLTHROUGH_INTENDED;
+            case 'B':
+                break;
+            default:
+                ADD_FAILURE() << "Parse error on multiplier: " << consumed_mult;
         }
         EXPECT_GT((full_size * 9) / 4, full_consumed);
         EXPECT_GT(full_size, max);
         EXPECT_GT(max, payload);
 
-        if ((((full_size * 9) / 4) >= full_consumed)
-         && (full_size > max)
-         && (max > payload)) {
+        if ((((full_size * 9) / 4) >= full_consumed) && (full_size > max) &&
+            (max > payload)) {
             ++count;
         }
     }
 
     pclose(fp);
 
-    ASSERT_EQ(4, count);
+    return count;
 }
 
-static void caught_blocking(int /*signum*/)
-{
+TEST(logcat, get_size) {
+    ASSERT_EQ(4, get_groups(logcat_executable
+                            " -v brief -b radio -b events -b system -b "
+                            "main -g 2>/dev/null"));
+}
+
+// duplicate test for get_size, but use comma-separated list of buffers
+TEST(logcat, multiple_buffer) {
+    ASSERT_EQ(
+        4, get_groups(logcat_executable
+                      " -v brief -b radio,events,system,main -g 2>/dev/null"));
+}
+
+TEST(logcat, bad_buffer) {
+    ASSERT_EQ(0,
+              get_groups(
+                  logcat_executable
+                  " -v brief -b radio,events,bogo,system,main -g 2>/dev/null"));
+}
+
+#ifndef logcat
+static void caught_blocking(int signum) {
     unsigned long long v = 0xDEADBEEFA55A0000ULL;
 
     v += getpid() & 0xFFFF;
+    if (signum == 0) ++v;
 
     LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v)));
 }
 
 TEST(logcat, blocking) {
-    FILE *fp;
+    FILE* fp;
     unsigned long long v = 0xDEADBEEFA55F0000ULL;
 
     pid_t pid = getpid();
@@ -362,12 +652,13 @@
 
     v &= 0xFFFFFFFFFFFAFFFFULL;
 
-    ASSERT_TRUE(NULL != (fp = popen(
-      "( trap exit HUP QUIT INT PIPE KILL ; sleep 6; echo DONE )&"
-      " logcat -v brief -b events 2>&1",
-      "r")));
+    ASSERT_TRUE(
+        NULL !=
+        (fp = popen("( trap exit HUP QUIT INT PIPE KILL ; sleep 6; echo DONE )&"
+                    " logcat -v brief -b events 2>&1",
+                    "r")));
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
 
     int count = 0;
 
@@ -376,7 +667,6 @@
     signal(SIGALRM, caught_blocking);
     alarm(2);
     while (fgets(buffer, sizeof(buffer), fp)) {
-
         if (!strncmp(buffer, "DONE", 4)) {
             break;
         }
@@ -386,8 +676,7 @@
         int p;
         unsigned long long l;
 
-        if ((2 != sscanf(buffer, "I/[0] ( %u): %lld", &p, &l))
-         || (p != pid)) {
+        if ((2 != sscanf(buffer, "I/[0] ( %u): %lld", &p, &l)) || (p != pid)) {
             continue;
         }
 
@@ -405,22 +694,22 @@
 
     pclose(fp);
 
-    EXPECT_LE(2, count);
+    EXPECT_GE(count, 2);
 
-    EXPECT_EQ(1, signals);
+    EXPECT_EQ(signals, 1);
 }
 
-static void caught_blocking_tail(int /*signum*/)
-{
+static void caught_blocking_tail(int signum) {
     unsigned long long v = 0xA55ADEADBEEF0000ULL;
 
     v += getpid() & 0xFFFF;
+    if (signum == 0) ++v;
 
     LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v)));
 }
 
 TEST(logcat, blocking_tail) {
-    FILE *fp;
+    FILE* fp;
     unsigned long long v = 0xA55FDEADBEEF0000ULL;
 
     pid_t pid = getpid();
@@ -431,12 +720,13 @@
 
     v &= 0xFFFAFFFFFFFFFFFFULL;
 
-    ASSERT_TRUE(NULL != (fp = popen(
-      "( trap exit HUP QUIT INT PIPE KILL ; sleep 6; echo DONE )&"
-      " logcat -v brief -b events -T 5 2>&1",
-      "r")));
+    ASSERT_TRUE(
+        NULL !=
+        (fp = popen("( trap exit HUP QUIT INT PIPE KILL ; sleep 6; echo DONE )&"
+                    " logcat -v brief -b events -T 5 2>&1",
+                    "r")));
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
 
     int count = 0;
 
@@ -445,7 +735,6 @@
     signal(SIGALRM, caught_blocking_tail);
     alarm(2);
     while (fgets(buffer, sizeof(buffer), fp)) {
-
         if (!strncmp(buffer, "DONE", 4)) {
             break;
         }
@@ -455,8 +744,7 @@
         int p;
         unsigned long long l;
 
-        if ((2 != sscanf(buffer, "I/[0] ( %u): %lld", &p, &l))
-         || (p != pid)) {
+        if ((2 != sscanf(buffer, "I/[0] ( %u): %lld", &p, &l)) || (p != pid)) {
             continue;
         }
 
@@ -476,9 +764,17 @@
 
     pclose(fp);
 
-    EXPECT_LE(2, count);
+    EXPECT_GE(count, 2);
 
-    EXPECT_EQ(1, signals);
+    EXPECT_EQ(signals, 1);
+}
+#endif
+
+// meant to be handed to ASSERT_FALSE / EXPECT_FALSE to expand the message
+static testing::AssertionResult IsFalse(int ret, const char* command) {
+    return ret ? (testing::AssertionSuccess()
+                  << "ret=" << ret << " command=\"" << command << "\"")
+               : testing::AssertionFailure();
 }
 
 TEST(logcat, logrotate) {
@@ -486,64 +782,66 @@
     char buf[sizeof(form)];
     ASSERT_TRUE(NULL != mkdtemp(strcpy(buf, form)));
 
-    static const char comm[] = "logcat -b radio -b events -b system -b main"
-                                     " -d -f %s/log.txt -n 7 -r 1";
+    static const char comm[] = logcat_executable
+        " -b radio -b events -b system -b main"
+        " -d -f %s/log.txt -n 7 -r 1";
     char command[sizeof(buf) + sizeof(comm)];
-    sprintf(command, comm, buf);
+    snprintf(command, sizeof(command), comm, buf);
 
     int ret;
-    EXPECT_FALSE((ret = system(command)));
+    EXPECT_FALSE(IsFalse(ret = system(command), command));
     if (!ret) {
-        sprintf(command, "ls -s %s 2>/dev/null", buf);
+        snprintf(command, sizeof(command), "ls -s %s 2>/dev/null", buf);
 
-        FILE *fp;
+        FILE* fp;
         EXPECT_TRUE(NULL != (fp = popen(command, "r")));
         if (fp) {
-            char buffer[5120];
+            char buffer[BIG_BUFFER];
             int count = 0;
 
             while (fgets(buffer, sizeof(buffer), fp)) {
-                static const char match_1[] = "4 log.txt";
-                static const char match_2[] = "8 log.txt";
-                static const char match_3[] = "12 log.txt";
-                static const char match_4[] = "16 log.txt";
                 static const char total[] = "total ";
+                int num;
+                char c;
 
-                if (!strncmp(buffer, match_1, sizeof(match_1) - 1)
-                 || !strncmp(buffer, match_2, sizeof(match_2) - 1)
-                 || !strncmp(buffer, match_3, sizeof(match_3) - 1)
-                 || !strncmp(buffer, match_4, sizeof(match_4) - 1)) {
+                if ((2 == sscanf(buffer, "%d log.tx%c", &num, &c)) &&
+                    (num <= 40)) {
                     ++count;
                 } else if (strncmp(buffer, total, sizeof(total) - 1)) {
                     fprintf(stderr, "WARNING: Parse error: %s", buffer);
                 }
             }
             pclose(fp);
+            if ((count != 7) && (count != 8)) {
+                fprintf(stderr, "count=%d\n", count);
+            }
             EXPECT_TRUE(count == 7 || count == 8);
         }
     }
-    sprintf(command, "rm -rf %s", buf);
-    EXPECT_FALSE(system(command));
+    snprintf(command, sizeof(command), "rm -rf %s", buf);
+    EXPECT_FALSE(IsFalse(system(command), command));
 }
 
 TEST(logcat, logrotate_suffix) {
-    static const char tmp_out_dir_form[] = "/data/local/tmp/logcat.logrotate.XXXXXX";
+    static const char tmp_out_dir_form[] =
+        "/data/local/tmp/logcat.logrotate.XXXXXX";
     char tmp_out_dir[sizeof(tmp_out_dir_form)];
     ASSERT_TRUE(NULL != mkdtemp(strcpy(tmp_out_dir, tmp_out_dir_form)));
 
-    static const char logcat_cmd[] = "logcat -b radio -b events -b system -b main"
-                                     " -d -f %s/log.txt -n 10 -r 1";
+    static const char logcat_cmd[] = logcat_executable
+        " -b radio -b events -b system -b main"
+        " -d -f %s/log.txt -n 10 -r 1";
     char command[sizeof(tmp_out_dir) + sizeof(logcat_cmd)];
-    sprintf(command, logcat_cmd, tmp_out_dir);
+    snprintf(command, sizeof(command), logcat_cmd, tmp_out_dir);
 
     int ret;
-    EXPECT_FALSE((ret = system(command)));
+    EXPECT_FALSE(IsFalse(ret = system(command), command));
     if (!ret) {
-        sprintf(command, "ls %s 2>/dev/null", tmp_out_dir);
+        snprintf(command, sizeof(command), "ls %s 2>/dev/null", tmp_out_dir);
 
-        FILE *fp;
+        FILE* fp;
         EXPECT_TRUE(NULL != (fp = popen(command, "r")));
-        char buffer[5120];
+        char buffer[BIG_BUFFER];
         int log_file_count = 0;
 
         while (fgets(buffer, sizeof(buffer), fp)) {
@@ -552,44 +850,339 @@
                 strlen(rotated_log_filename_prefix);
             static const char log_filename[] = "log.txt";
 
-            if (!strncmp(buffer, rotated_log_filename_prefix, rotated_log_filename_prefix_len)) {
-              // Rotated file should have form log.txt.##
-              char* rotated_log_filename_suffix = buffer + rotated_log_filename_prefix_len;
-              char* endptr;
-              const long int suffix_value = strtol(rotated_log_filename_suffix, &endptr, 10);
-              EXPECT_EQ(rotated_log_filename_suffix + 2, endptr);
-              EXPECT_LE(suffix_value, 10);
-              EXPECT_GT(suffix_value, 0);
-              ++log_file_count;
-              continue;
+            if (!strncmp(buffer, rotated_log_filename_prefix,
+                         rotated_log_filename_prefix_len)) {
+                // Rotated file should have form log.txt.##
+                char* rotated_log_filename_suffix =
+                    buffer + rotated_log_filename_prefix_len;
+                char* endptr;
+                const long int suffix_value =
+                    strtol(rotated_log_filename_suffix, &endptr, 10);
+                EXPECT_EQ(rotated_log_filename_suffix + 2, endptr);
+                EXPECT_LE(suffix_value, 10);
+                EXPECT_GT(suffix_value, 0);
+                ++log_file_count;
+                continue;
             }
 
             if (!strncmp(buffer, log_filename, strlen(log_filename))) {
-              ++log_file_count;
-              continue;
+                ++log_file_count;
+                continue;
             }
 
             fprintf(stderr, "ERROR: Unexpected file: %s", buffer);
             ADD_FAILURE();
         }
         pclose(fp);
-        EXPECT_EQ(11, log_file_count);
+        EXPECT_EQ(log_file_count, 11);
     }
-    sprintf(command, "rm -rf %s", tmp_out_dir);
-    EXPECT_FALSE(system(command));
+    snprintf(command, sizeof(command), "rm -rf %s", tmp_out_dir);
+    EXPECT_FALSE(IsFalse(system(command), command));
 }
 
-static void caught_blocking_clear(int /*signum*/)
-{
+TEST(logcat, logrotate_continue) {
+    static const char tmp_out_dir_form[] =
+        "/data/local/tmp/logcat.logrotate.XXXXXX";
+    char tmp_out_dir[sizeof(tmp_out_dir_form)];
+    ASSERT_TRUE(NULL != mkdtemp(strcpy(tmp_out_dir, tmp_out_dir_form)));
+
+    static const char log_filename[] = "log.txt";
+    static const char logcat_cmd[] =
+        logcat_executable " -b all -v nsec -d -f %s/%s -n 256 -r 1024";
+    static const char cleanup_cmd[] = "rm -rf %s";
+    char command[sizeof(tmp_out_dir) + sizeof(logcat_cmd) + sizeof(log_filename)];
+    snprintf(command, sizeof(command), logcat_cmd, tmp_out_dir, log_filename);
+
+    int ret;
+    EXPECT_FALSE(IsFalse(ret = system(command), command));
+    if (ret) {
+        snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
+        EXPECT_FALSE(IsFalse(system(command), command));
+        return;
+    }
+    FILE* fp;
+    snprintf(command, sizeof(command), "%s/%s", tmp_out_dir, log_filename);
+    EXPECT_TRUE(NULL != ((fp = fopen(command, "r"))));
+    if (!fp) {
+        snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
+        EXPECT_FALSE(IsFalse(system(command), command));
+        return;
+    }
+    char* line = NULL;
+    char* last_line =
+        NULL;  // this line is allowed to stutter, one-line overlap
+    char* second_last_line = NULL;  // should never stutter
+    char* first_line = NULL;        // help diagnose failure?
+    size_t len = 0;
+    while (getline(&line, &len, fp) != -1) {
+        if (!first_line) {
+            first_line = line;
+            line = NULL;
+            continue;
+        }
+        free(second_last_line);
+        second_last_line = last_line;
+        last_line = line;
+        line = NULL;
+    }
+    fclose(fp);
+    free(line);
+    if (second_last_line == NULL) {
+        fprintf(stderr, "No second to last line, using last, test may fail\n");
+        second_last_line = last_line;
+        last_line = NULL;
+    }
+    free(last_line);
+    EXPECT_TRUE(NULL != second_last_line);
+    if (!second_last_line) {
+        snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
+        EXPECT_FALSE(IsFalse(system(command), command));
+        free(first_line);
+        return;
+    }
+    // re-run the command, it should only add a few lines more content if it
+    // continues where it left off.
+    snprintf(command, sizeof(command), logcat_cmd, tmp_out_dir, log_filename);
+    EXPECT_FALSE(IsFalse(ret = system(command), command));
+    if (ret) {
+        snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
+        EXPECT_FALSE(IsFalse(system(command), command));
+        free(second_last_line);
+        free(first_line);
+        return;
+    }
+    std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(tmp_out_dir),
+                                                  closedir);
+    EXPECT_NE(nullptr, dir);
+    if (!dir) {
+        snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
+        EXPECT_FALSE(IsFalse(system(command), command));
+        free(second_last_line);
+        free(first_line);
+        return;
+    }
+    struct dirent* entry;
+    unsigned count = 0;
+    while ((entry = readdir(dir.get()))) {
+        if (strncmp(entry->d_name, log_filename, sizeof(log_filename) - 1)) {
+            continue;
+        }
+        snprintf(command, sizeof(command), "%s/%s", tmp_out_dir, entry->d_name);
+        EXPECT_TRUE(NULL != ((fp = fopen(command, "r"))));
+        if (!fp) {
+            fprintf(stderr, "%s ?\n", command);
+            continue;
+        }
+        line = NULL;
+        size_t number = 0;
+        while (getline(&line, &len, fp) != -1) {
+            ++number;
+            if (!strcmp(line, second_last_line)) {
+                EXPECT_TRUE(++count <= 1);
+                fprintf(stderr, "%s(%zu):\n", entry->d_name, number);
+            }
+        }
+        fclose(fp);
+        free(line);
+        unlink(command);
+    }
+    if (count > 1) {
+        char* brk = strpbrk(second_last_line, "\r\n");
+        if (!brk) brk = second_last_line + strlen(second_last_line);
+        fprintf(stderr, "\"%.*s\" occurred %u times\n",
+                (int)(brk - second_last_line), second_last_line, count);
+        if (first_line) {
+            brk = strpbrk(first_line, "\r\n");
+            if (!brk) brk = first_line + strlen(first_line);
+            fprintf(stderr, "\"%.*s\" was first line, fault?\n",
+                    (int)(brk - first_line), first_line);
+        }
+    }
+    free(second_last_line);
+    free(first_line);
+
+    snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
+    EXPECT_FALSE(IsFalse(system(command), command));
+}
+
+TEST(logcat, logrotate_clear) {
+    static const char tmp_out_dir_form[] =
+        "/data/local/tmp/logcat.logrotate.XXXXXX";
+    char tmp_out_dir[sizeof(tmp_out_dir_form)];
+    ASSERT_TRUE(NULL != mkdtemp(strcpy(tmp_out_dir, tmp_out_dir_form)));
+
+    static const char log_filename[] = "log.txt";
+    static const unsigned num_val = 32;
+    static const char logcat_cmd[] =
+        logcat_executable " -b all -d -f %s/%s -n %d -r 1";
+    static const char clear_cmd[] = " -c";
+    static const char cleanup_cmd[] = "rm -rf %s";
+    char command[sizeof(tmp_out_dir) + sizeof(logcat_cmd) +
+                 sizeof(log_filename) + sizeof(clear_cmd) + 32];
+
+    // Run command with all data
+    {
+        snprintf(command, sizeof(command) - sizeof(clear_cmd), logcat_cmd,
+                 tmp_out_dir, log_filename, num_val);
+
+        int ret;
+        EXPECT_FALSE(IsFalse(ret = system(command), command));
+        if (ret) {
+            snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
+            EXPECT_FALSE(IsFalse(system(command), command));
+            return;
+        }
+        std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(tmp_out_dir),
+                                                      closedir);
+        EXPECT_NE(nullptr, dir);
+        if (!dir) {
+            snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
+            EXPECT_FALSE(IsFalse(system(command), command));
+            return;
+        }
+        struct dirent* entry;
+        unsigned count = 0;
+        while ((entry = readdir(dir.get()))) {
+            if (strncmp(entry->d_name, log_filename, sizeof(log_filename) - 1)) {
+                continue;
+            }
+            ++count;
+        }
+        EXPECT_EQ(count, num_val + 1);
+    }
+
+    {
+        // Now with -c option tacked onto the end
+        strcat(command, clear_cmd);
+
+        int ret;
+        EXPECT_FALSE(IsFalse(ret = system(command), command));
+        if (ret) {
+            snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
+            EXPECT_FALSE(system(command));
+            EXPECT_FALSE(IsFalse(system(command), command));
+            return;
+        }
+        std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(tmp_out_dir),
+                                                      closedir);
+        EXPECT_NE(nullptr, dir);
+        if (!dir) {
+            snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
+            EXPECT_FALSE(IsFalse(system(command), command));
+            return;
+        }
+        struct dirent* entry;
+        unsigned count = 0;
+        while ((entry = readdir(dir.get()))) {
+            if (strncmp(entry->d_name, log_filename, sizeof(log_filename) - 1)) {
+                continue;
+            }
+            fprintf(stderr, "Found %s/%s!!!\n", tmp_out_dir, entry->d_name);
+            ++count;
+        }
+        EXPECT_EQ(count, 0U);
+    }
+
+    snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
+    EXPECT_FALSE(IsFalse(system(command), command));
+}
+
+static int logrotate_count_id(const char* logcat_cmd, const char* tmp_out_dir) {
+    static const char log_filename[] = "log.txt";
+    char command[strlen(tmp_out_dir) + strlen(logcat_cmd) +
+                 strlen(log_filename) + 32];
+
+    snprintf(command, sizeof(command), logcat_cmd, tmp_out_dir, log_filename);
+
+    int ret = system(command);
+    if (ret) {
+        fprintf(stderr, "system(\"%s\")=%d", command, ret);
+        return -1;
+    }
+    std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(tmp_out_dir),
+                                                  closedir);
+    if (!dir) {
+        fprintf(stderr, "opendir(\"%s\") failed", tmp_out_dir);
+        return -1;
+    }
+    struct dirent* entry;
+    int count = 0;
+    while ((entry = readdir(dir.get()))) {
+        if (strncmp(entry->d_name, log_filename, sizeof(log_filename) - 1)) {
+            continue;
+        }
+        ++count;
+    }
+    return count;
+}
+
+TEST(logcat, logrotate_id) {
+    static const char logcat_cmd[] =
+        logcat_executable " -b all -d -f %s/%s -n 32 -r 1 --id=test";
+    static const char logcat_short_cmd[] =
+        logcat_executable " -b all -t 10 -f %s/%s -n 32 -r 1 --id=test";
+    static const char tmp_out_dir_form[] =
+        "/data/local/tmp/logcat.logrotate.XXXXXX";
+    static const char log_filename[] = "log.txt";
+    char tmp_out_dir[strlen(tmp_out_dir_form) + 1];
+    ASSERT_TRUE(NULL != mkdtemp(strcpy(tmp_out_dir, tmp_out_dir_form)));
+
+    EXPECT_EQ(logrotate_count_id(logcat_cmd, tmp_out_dir), 34);
+    EXPECT_EQ(logrotate_count_id(logcat_short_cmd, tmp_out_dir), 34);
+
+    char id_file[strlen(tmp_out_dir_form) + strlen(log_filename) + 5];
+    snprintf(id_file, sizeof(id_file), "%s/%s.id", tmp_out_dir, log_filename);
+    if (getuid() != 0) {
+        chmod(id_file, 0);
+        EXPECT_EQ(logrotate_count_id(logcat_short_cmd, tmp_out_dir), 34);
+    }
+    unlink(id_file);
+    EXPECT_EQ(logrotate_count_id(logcat_short_cmd, tmp_out_dir), 34);
+
+    FILE* fp = fopen(id_file, "w");
+    if (fp) {
+        fprintf(fp, "not_a_test");
+        fclose(fp);
+    }
+    if (getuid() != 0) {
+        chmod(id_file,
+              0);  // API to preserve content even with signature change
+        ASSERT_EQ(34, logrotate_count_id(logcat_short_cmd, tmp_out_dir));
+        chmod(id_file, 0600);
+    }
+
+    int new_signature;
+    EXPECT_GE(
+        (new_signature = logrotate_count_id(logcat_short_cmd, tmp_out_dir)), 2);
+    EXPECT_LT(new_signature, 34);
+
+    static const char cleanup_cmd[] = "rm -rf %s";
+    char command[strlen(cleanup_cmd) + strlen(tmp_out_dir_form)];
+    snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
+    EXPECT_FALSE(IsFalse(system(command), command));
+}
+
+TEST(logcat, logrotate_nodir) {
+    // expect logcat to error out on writing content and not exit(0) for nodir
+    static const char command[] = logcat_executable
+        " -b all -d"
+        " -f /das/nein/gerfingerpoken/logcat/log.txt"
+        " -n 256 -r 1024";
+    EXPECT_FALSE(IsFalse(0 == system(command), command));
+}
+
+#ifndef logcat
+static void caught_blocking_clear(int signum) {
     unsigned long long v = 0xDEADBEEFA55C0000ULL;
 
     v += getpid() & 0xFFFF;
+    if (signum == 0) ++v;
 
     LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v)));
 }
 
 TEST(logcat, blocking_clear) {
-    FILE *fp;
+    FILE* fp;
     unsigned long long v = 0xDEADBEEFA55C0000ULL;
 
     pid_t pid = getpid();
@@ -598,23 +1191,30 @@
 
     // This test is racey; an event occurs between clear and dump.
     // We accept that we will get a false positive, but never a false negative.
-    ASSERT_TRUE(NULL != (fp = popen(
-      "( trap exit HUP QUIT INT PIPE KILL ; sleep 6; echo DONE )&"
-      " logcat -b events -c 2>&1 ;"
-      " logcat -v brief -b events 2>&1",
-      "r")));
+    ASSERT_TRUE(
+        NULL !=
+        (fp = popen("( trap exit HUP QUIT INT PIPE KILL ; sleep 6; echo DONE )&"
+                    " logcat -b events -c 2>&1 ;"
+                    " logcat -b events -g 2>&1 ;"
+                    " logcat -v brief -b events 2>&1",
+                    "r")));
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
 
     int count = 0;
+    int minus_g = 0;
 
     int signals = 0;
 
     signal(SIGALRM, caught_blocking_clear);
     alarm(2);
     while (fgets(buffer, sizeof(buffer), fp)) {
-
-        if (!strncmp(buffer, "clearLog: ", 10)) {
+        if (!strncmp(buffer, "clearLog: ", strlen("clearLog: "))) {
+            fprintf(stderr, "WARNING: Test lacks permission to run :-(\n");
+            count = signals = 1;
+            break;
+        }
+        if (!strncmp(buffer, "failed to clear", strlen("failed to clear"))) {
             fprintf(stderr, "WARNING: Test lacks permission to run :-(\n");
             count = signals = 1;
             break;
@@ -624,13 +1224,57 @@
             break;
         }
 
+        int size, consumed, readable, max, payload;
+        char size_mult[4], consumed_mult[4], readable_mult[4];
+        size = consumed = max = payload = 0;
+        if (8 == sscanf(buffer,
+                        "events: ring buffer is %d %3s (%d %3s consumed, %d %3s readable),"
+                        " max entry is %d B, max payload is %d B",
+                        &size, size_mult, &consumed, consumed_mult, &readable, readable_mult, &max,
+                        &payload)) {
+            long full_size = size, full_consumed = consumed;
+
+            switch (size_mult[0]) {
+                case 'G':
+                    full_size *= 1024;
+                    FALLTHROUGH_INTENDED;
+                case 'M':
+                    full_size *= 1024;
+                    FALLTHROUGH_INTENDED;
+                case 'K':
+                    full_size *= 1024;
+                    FALLTHROUGH_INTENDED;
+                case 'B':
+                    break;
+            }
+            switch (consumed_mult[0]) {
+                case 'G':
+                    full_consumed *= 1024;
+                    FALLTHROUGH_INTENDED;
+                case 'M':
+                    full_consumed *= 1024;
+                    FALLTHROUGH_INTENDED;
+                case 'K':
+                    full_consumed *= 1024;
+                    FALLTHROUGH_INTENDED;
+                case 'B':
+                    break;
+            }
+            EXPECT_GT(full_size, full_consumed);
+            EXPECT_GT(full_size, max);
+            EXPECT_GT(max, payload);
+            EXPECT_GT(max, full_consumed);
+
+            ++minus_g;
+            continue;
+        }
+
         ++count;
 
         int p;
         unsigned long long l;
 
-        if ((2 != sscanf(buffer, "I/[0] ( %u): %lld", &p, &l))
-         || (p != pid)) {
+        if ((2 != sscanf(buffer, "I/[0] ( %u): %lld", &p, &l)) || (p != pid)) {
             continue;
         }
 
@@ -651,29 +1295,29 @@
 
     pclose(fp);
 
-    EXPECT_LE(1, count);
+    EXPECT_GE(count, 1);
+    EXPECT_EQ(minus_g, 1);
 
-    EXPECT_EQ(1, signals);
+    EXPECT_EQ(signals, 1);
 }
+#endif
 
-static bool get_white_black(char **list) {
-    FILE *fp;
-
-    fp = popen("logcat -p 2>/dev/null", "r");
+static bool get_prune_rules(char** list) {
+    FILE* fp = popen(logcat_executable " -p 2>/dev/null", "r");
     if (fp == NULL) {
         fprintf(stderr, "ERROR: logcat -p 2>/dev/null\n");
         return false;
     }
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
 
     while (fgets(buffer, sizeof(buffer), fp)) {
-        char *hold = *list;
-        char *buf = buffer;
-	while (isspace(*buf)) {
+        char* hold = *list;
+        char* buf = buffer;
+        while (isspace(*buf)) {
             ++buf;
         }
-        char *end = buf + strlen(buf);
+        char* end = buf + strlen(buf);
         while (isspace(*--end) && (end >= buf)) {
             *end = '\0';
         }
@@ -691,24 +1335,22 @@
     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 ? list : "");
-    fp = popen(buffer, "r");
+static bool set_prune_rules(const char* list) {
+    char buffer[BIG_BUFFER];
+    snprintf(buffer, sizeof(buffer), logcat_executable " -P '%s' 2>&1",
+             list ? list : "");
+    FILE* 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)) {
+        char* buf = buffer;
+        while (isspace(*buf)) {
             ++buf;
         }
-        char *end = buf + strlen(buf);
+        char* end = buf + strlen(buf);
         while ((end > buf) && isspace(*--end)) {
             *end = '\0';
         }
@@ -722,28 +1364,28 @@
     return pclose(fp) == 0;
 }
 
-TEST(logcat, white_black_adjust) {
-    char *list = NULL;
-    char *adjust = NULL;
+TEST(logcat, prune_rules_adjust) {
+    char* list = NULL;
+    char* adjust = NULL;
 
-    get_white_black(&list);
+    get_prune_rules(&list);
 
     static const char adjustment[] = "~! 300/20 300/25 2000 ~1000/5 ~1000/30";
-    ASSERT_EQ(true, set_white_black(adjustment));
-    ASSERT_EQ(true, get_white_black(&adjust));
+    ASSERT_EQ(true, set_prune_rules(adjustment));
+    ASSERT_EQ(true, get_prune_rules(&adjust));
     EXPECT_STREQ(adjustment, adjust);
     free(adjust);
     adjust = NULL;
 
     static const char adjustment2[] = "300/20 300/21 2000 ~1000";
-    ASSERT_EQ(true, set_white_black(adjustment2));
-    ASSERT_EQ(true, get_white_black(&adjust));
+    ASSERT_EQ(true, set_prune_rules(adjustment2));
+    ASSERT_EQ(true, get_prune_rules(&adjust));
     EXPECT_STREQ(adjustment2, adjust);
     free(adjust);
     adjust = NULL;
 
-    ASSERT_EQ(true, set_white_black(list));
-    get_white_black(&adjust);
+    ASSERT_EQ(true, set_prune_rules(list));
+    get_prune_rules(&adjust);
     EXPECT_STREQ(list ? list : "", adjust ? adjust : "");
     free(adjust);
     adjust = NULL;
@@ -751,3 +1393,380 @@
     free(list);
     list = NULL;
 }
+
+TEST(logcat, regex) {
+    FILE* fp;
+    int count = 0;
+
+    char buffer[BIG_BUFFER];
+#define logcat_regex_prefix logcat_executable "_test"
+
+    snprintf(buffer, sizeof(buffer),
+             logcat_executable " --pid %d -d -e " logcat_regex_prefix "_a+b",
+             getpid());
+
+    LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_WARN, logcat_regex_prefix,
+                                          logcat_regex_prefix "_ab"));
+    LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_WARN, logcat_regex_prefix,
+                                          logcat_regex_prefix "_b"));
+    LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_WARN, logcat_regex_prefix,
+                                          logcat_regex_prefix "_aaaab"));
+    LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_WARN, logcat_regex_prefix,
+                                          logcat_regex_prefix "_aaaa"));
+    // Let the logs settle
+    rest();
+
+    ASSERT_TRUE(NULL != (fp = popen(buffer, "r")));
+
+    while (fgets(buffer, sizeof(buffer), fp)) {
+        if (!strncmp(begin, buffer, sizeof(begin) - 1)) {
+            continue;
+        }
+
+        EXPECT_TRUE(strstr(buffer, logcat_regex_prefix "_") != NULL);
+
+        count++;
+    }
+
+    pclose(fp);
+
+    ASSERT_EQ(2, count);
+}
+
+TEST(logcat, maxcount) {
+    FILE* fp;
+    int count = 0;
+
+    char buffer[BIG_BUFFER];
+
+    snprintf(buffer, sizeof(buffer),
+             logcat_executable " --pid %d -d --max-count 3", getpid());
+
+    LOG_FAILURE_RETRY(
+        __android_log_print(ANDROID_LOG_WARN, "logcat_test", "logcat_test"));
+    LOG_FAILURE_RETRY(
+        __android_log_print(ANDROID_LOG_WARN, "logcat_test", "logcat_test"));
+    LOG_FAILURE_RETRY(
+        __android_log_print(ANDROID_LOG_WARN, "logcat_test", "logcat_test"));
+    LOG_FAILURE_RETRY(
+        __android_log_print(ANDROID_LOG_WARN, "logcat_test", "logcat_test"));
+
+    rest();
+
+    ASSERT_TRUE(NULL != (fp = popen(buffer, "r")));
+
+    while (fgets(buffer, sizeof(buffer), fp)) {
+        if (!strncmp(begin, buffer, sizeof(begin) - 1)) {
+            continue;
+        }
+
+        count++;
+    }
+
+    pclose(fp);
+
+    ASSERT_EQ(3, count);
+}
+
+static bool End_to_End(const char* tag, const char* fmt, ...)
+#if defined(__GNUC__)
+    __attribute__((__format__(printf, 2, 3)))
+#endif
+    ;
+
+static bool End_to_End(const char* tag, const char* fmt, ...) {
+    FILE* fp = popen(logcat_executable " -v brief -b events -v descriptive -t 100 2>/dev/null", "r");
+    if (!fp) {
+        fprintf(stderr, "End_to_End: popen failed");
+        return false;
+    }
+
+    char buffer[BIG_BUFFER];
+    va_list ap;
+
+    va_start(ap, fmt);
+    vsnprintf(buffer, sizeof(buffer), fmt, ap);
+    va_end(ap);
+
+    char* str = NULL;
+    asprintf(&str, "I/%s ( %%d):%%c%s%%c", tag, buffer);
+    std::string expect(str);
+    free(str);
+
+    int count = 0;
+    pid_t pid = getpid();
+    std::string lastMatch;
+    int maxMatch = 1;
+    while (fgets(buffer, sizeof(buffer), fp)) {
+        char space;
+        char newline;
+        int p;
+        int ret = sscanf(buffer, expect.c_str(), &p, &space, &newline);
+        if ((ret == 3) && (p == pid) && (space == ' ') && (newline == '\n')) {
+            ++count;
+        } else if ((ret >= maxMatch) && (p == pid) && (count == 0)) {
+            lastMatch = buffer;
+            maxMatch = ret;
+        }
+    }
+
+    pclose(fp);
+
+    if ((count == 0) && (lastMatch.length() > 0)) {
+        // Help us pinpoint where things went wrong ...
+        fprintf(stderr, "Closest match for\n    %s\n  is\n    %s",
+                expect.c_str(), lastMatch.c_str());
+    } else if (count > 3) {
+        fprintf(stderr, "Too many matches (%d) for %s\n", count, expect.c_str());
+    }
+
+    // Three different known tests, we can see pollution from the others
+    return count && (count <= 3);
+}
+
+TEST(logcat, descriptive) {
+    struct tag {
+        uint32_t tagNo;
+        const char* tagStr;
+    };
+    int ret;
+
+    {
+        static const struct tag hhgtg = { 42, "answer" };
+        android_log_event_list ctx(hhgtg.tagNo);
+        static const char theAnswer[] = "what is five by seven";
+        ctx << theAnswer;
+        // crafted to rest at least once after, and rest between retries.
+        for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write();
+        EXPECT_GE(ret, 0);
+        EXPECT_TRUE(
+            End_to_End(hhgtg.tagStr, "to life the universe etc=%s", theAnswer));
+    }
+
+    {
+        static const struct tag sync = { 2720, "sync" };
+        static const char id[] = logcat_executable ".descriptive-sync";
+        {
+            android_log_event_list ctx(sync.tagNo);
+            ctx << id << (int32_t)42 << (int32_t)-1 << (int32_t)0;
+            for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write();
+            EXPECT_GE(ret, 0);
+            EXPECT_TRUE(End_to_End(sync.tagStr,
+                                   "[id=%s,event=42,source=-1,account=0]", id));
+        }
+
+        // Partial match to description
+        {
+            android_log_event_list ctx(sync.tagNo);
+            ctx << id << (int32_t)43 << (int64_t)-1 << (int32_t)0;
+            for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write();
+            EXPECT_GE(ret, 0);
+            EXPECT_TRUE(End_to_End(sync.tagStr, "[id=%s,event=43,-1,0]", id));
+        }
+
+        // Negative Test of End_to_End, ensure it is working
+        {
+            android_log_event_list ctx(sync.tagNo);
+            ctx << id << (int32_t)44 << (int32_t)-1 << (int64_t)0;
+            for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write();
+            EXPECT_GE(ret, 0);
+            fprintf(stderr, "Expect a \"Closest match\" message\n");
+            EXPECT_FALSE(End_to_End(
+                sync.tagStr, "[id=%s,event=44,source=-1,account=0]", id));
+        }
+    }
+
+    {
+        static const struct tag sync = { 2747, "contacts_aggregation" };
+        {
+            android_log_event_list ctx(sync.tagNo);
+            ctx << (uint64_t)30 << (int32_t)2;
+            for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write();
+            EXPECT_GE(ret, 0);
+            EXPECT_TRUE(
+                End_to_End(sync.tagStr, "[aggregation time=30ms,count=2]"));
+        }
+
+        {
+            android_log_event_list ctx(sync.tagNo);
+            ctx << (uint64_t)31570 << (int32_t)911;
+            for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write();
+            EXPECT_GE(ret, 0);
+            EXPECT_TRUE(
+                End_to_End(sync.tagStr, "[aggregation time=31.57s,count=911]"));
+        }
+    }
+
+    {
+        static const struct tag sync = { 75000, "sqlite_mem_alarm_current" };
+        {
+            android_log_event_list ctx(sync.tagNo);
+            ctx << (uint32_t)512;
+            for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write();
+            EXPECT_GE(ret, 0);
+            EXPECT_TRUE(End_to_End(sync.tagStr, "current=512B"));
+        }
+
+        {
+            android_log_event_list ctx(sync.tagNo);
+            ctx << (uint32_t)3072;
+            for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write();
+            EXPECT_GE(ret, 0);
+            EXPECT_TRUE(End_to_End(sync.tagStr, "current=3KB"));
+        }
+
+        {
+            android_log_event_list ctx(sync.tagNo);
+            ctx << (uint32_t)2097152;
+            for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write();
+            EXPECT_GE(ret, 0);
+            EXPECT_TRUE(End_to_End(sync.tagStr, "current=2MB"));
+        }
+
+        {
+            android_log_event_list ctx(sync.tagNo);
+            ctx << (uint32_t)2097153;
+            for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write();
+            EXPECT_GE(ret, 0);
+            EXPECT_TRUE(End_to_End(sync.tagStr, "current=2097153B"));
+        }
+
+        {
+            android_log_event_list ctx(sync.tagNo);
+            ctx << (uint32_t)1073741824;
+            for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write();
+            EXPECT_GE(ret, 0);
+            EXPECT_TRUE(End_to_End(sync.tagStr, "current=1GB"));
+        }
+
+        {
+            android_log_event_list ctx(sync.tagNo);
+            ctx << (uint32_t)3221225472;  // 3MB, but on purpose overflowed
+            for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write();
+            EXPECT_GE(ret, 0);
+            EXPECT_TRUE(End_to_End(sync.tagStr, "current=-1GB"));
+        }
+    }
+
+    {
+        static const struct tag sync = { 27501, "notification_panel_hidden" };
+        android_log_event_list ctx(sync.tagNo);
+        for (ret = -EBUSY; ret == -EBUSY; rest()) ret = ctx.write();
+        EXPECT_GE(ret, 0);
+        EXPECT_TRUE(End_to_End(sync.tagStr, ""));
+    }
+}
+
+static size_t commandOutputSize(const char* command) {
+    FILE* fp = popen(command, "r");
+    if (!fp) return 0;
+
+    std::string ret;
+    if (!android::base::ReadFdToString(fileno(fp), &ret)) return 0;
+    if (pclose(fp) != 0) return 0;
+
+    return ret.size();
+}
+
+TEST(logcat, help) {
+    size_t logcatHelpTextSize = commandOutputSize(logcat_executable " -h 2>&1");
+    EXPECT_GT(logcatHelpTextSize, 4096UL);
+    size_t logcatLastHelpTextSize =
+        commandOutputSize(logcat_executable " -L -h 2>&1");
+#ifdef USING_LOGCAT_EXECUTABLE_DEFAULT  // logcat and liblogcat
+    EXPECT_EQ(logcatHelpTextSize, logcatLastHelpTextSize);
+#else
+    // logcatd -L -h prints the help twice, as designed.
+    EXPECT_EQ(logcatHelpTextSize * 2, logcatLastHelpTextSize);
+#endif
+}
+
+TEST(logcat, invalid_buffer) {
+  FILE* fp = popen("logcat -b foo 2>&1", "r");
+  ASSERT_NE(nullptr, fp);
+  std::string output;
+  ASSERT_TRUE(android::base::ReadFdToString(fileno(fp), &output));
+  pclose(fp);
+
+  EXPECT_NE(std::string::npos, output.find("Unknown buffer 'foo'"));
+}
+
+static void SniffUid(const std::string& line, uid_t& uid) {
+    auto uid_regex = std::regex{"\\S+\\s+\\S+\\s+(\\S+).*"};
+
+    auto trimmed_line = android::base::Trim(line);
+
+    std::smatch match_results;
+    ASSERT_TRUE(std::regex_match(trimmed_line, match_results, uid_regex))
+            << "Unable to find UID in line '" << trimmed_line << "'";
+    auto uid_string = match_results[1];
+    if (!android::base::ParseUint(uid_string, &uid)) {
+        auto pwd = getpwnam(uid_string.str().c_str());
+        ASSERT_NE(nullptr, pwd) << "uid '" << uid_string << "' in line '" << trimmed_line << "'";
+        uid = pwd->pw_uid;
+    }
+}
+
+static void UidsInLog(std::optional<std::vector<uid_t>> filter_uid, std::map<uid_t, size_t>& uids) {
+    std::string command;
+    if (filter_uid) {
+        std::vector<std::string> uid_strings;
+        for (const auto& uid : *filter_uid) {
+            uid_strings.emplace_back(std::to_string(uid));
+        }
+        command = android::base::StringPrintf(logcat_executable
+                                              " -v uid -b all -d 2>/dev/null --uid=%s",
+                                              android::base::Join(uid_strings, ",").c_str());
+    } else {
+        command = logcat_executable " -v uid -b all -d 2>/dev/null";
+    }
+    auto fp = std::unique_ptr<FILE, decltype(&pclose)>(popen(command.c_str(), "r"), pclose);
+    ASSERT_NE(nullptr, fp);
+
+    char buffer[BIG_BUFFER];
+    while (fgets(buffer, sizeof(buffer), fp.get())) {
+        // Ignore dividers, e.g. '--------- beginning of radio'
+        if (android::base::StartsWith(buffer, "---------")) {
+            continue;
+        }
+        uid_t uid;
+        SniffUid(buffer, uid);
+        uids[uid]++;
+    }
+}
+
+static std::vector<uid_t> TopTwoInMap(const std::map<uid_t, size_t>& uids) {
+    std::pair<uid_t, size_t> top = {0, 0};
+    std::pair<uid_t, size_t> second = {0, 0};
+    for (const auto& [uid, count] : uids) {
+        if (count > top.second) {
+            top = second;
+            top = {uid, count};
+        } else if (count > second.second) {
+            second = {uid, count};
+        }
+    }
+    return {top.first, second.first};
+}
+
+TEST(logcat, uid_filter) {
+    std::map<uid_t, size_t> uids;
+    UidsInLog({}, uids);
+
+    ASSERT_GT(uids.size(), 2U);
+    auto top_uids = TopTwoInMap(uids);
+
+    // Test filtering with --uid=<top uid>
+    std::map<uid_t, size_t> uids_only_top;
+    std::vector<uid_t> top_uid = {top_uids[0]};
+    UidsInLog(top_uid, uids_only_top);
+
+    EXPECT_EQ(1U, uids_only_top.size());
+
+    // Test filtering with --uid=<top uid>,<2nd top uid>
+    std::map<uid_t, size_t> uids_only_top2;
+    std::vector<uid_t> top2_uids = {top_uids[0], top_uids[1]};
+    UidsInLog(top2_uids, uids_only_top2);
+
+    EXPECT_EQ(2U, uids_only_top2.size());
+}
diff --git a/liblog/fake_log_device.h b/logcat/tests/logcatd_test.cpp
similarity index 61%
copy from liblog/fake_log_device.h
copy to logcat/tests/logcatd_test.cpp
index 9d168cd..bb7534e 100644
--- a/liblog/fake_log_device.h
+++ b/logcat/tests/logcatd_test.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -14,15 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef _LIBLOG_FAKE_LOG_DEVICE_H
-#define _LIBLOG_FAKE_LOG_DEVICE_H
+#define logcat logcatd
+#define logcat_executable "logcatd"
 
-#include <sys/types.h>
-
-struct iovec;
-
-int fakeLogOpen(const char *pathName, int flags);
-int fakeLogClose(int fd);
-ssize_t fakeLogWritev(int fd, const struct iovec* vector, int count);
-
-#endif // _LIBLOG_FAKE_LOG_DEVICE_H
+#include "logcat_test.cpp"
diff --git a/logd/.clang-format b/logd/.clang-format
new file mode 120000
index 0000000..1af4f51
--- /dev/null
+++ b/logd/.clang-format
@@ -0,0 +1 @@
+../.clang-format-4
\ No newline at end of file
diff --git a/logd/Android.bp b/logd/Android.bp
new file mode 100644
index 0000000..829c95f
--- /dev/null
+++ b/logd/Android.bp
@@ -0,0 +1,212 @@
+// Copyright (C) 2017 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.
+
+// This is what we want to do:
+//  event_logtags = $(shell
+//    sed -n
+//        "s/^\([0-9]*\)[ \t]*$1[ \t].*/-D`echo $1 | tr a-z A-Z`_LOG_TAG=\1/p"
+//        $(LOCAL_PATH)/$2/event.logtags)
+//  event_flag := $(call event_logtags,auditd)
+//  event_flag += $(call event_logtags,logd)
+//  event_flag += $(call event_logtags,tag_def)
+// so make sure we do not regret hard-coding it as follows:
+event_flag = [
+    "-DAUDITD_LOG_TAG=1003",
+    "-DCHATTY_LOG_TAG=1004",
+    "-DTAG_DEF_LOG_TAG=1005",
+    "-DLIBLOG_LOG_TAG=1006",
+]
+
+cc_defaults {
+    name: "logd_defaults",
+
+    shared_libs: [
+        "libbase",
+        "libz",
+    ],
+    static_libs: ["libzstd"],
+    header_libs: ["libcutils_headers"],
+    cflags: [
+        "-Wextra",
+        "-Wthread-safety",
+    ] + event_flag,
+
+    lto: {
+        thin: true,
+    },
+    sanitize: {
+        cfi: true,
+    },
+    cpp_std: "experimental",
+}
+
+cc_library_static {
+    name: "liblogd",
+    defaults: ["logd_defaults"],
+    host_supported: true,
+    srcs: [
+        "ChattyLogBuffer.cpp",
+        "CompressionEngine.cpp",
+        "LogBufferElement.cpp",
+        "LogReaderList.cpp",
+        "LogReaderThread.cpp",
+        "LogSize.cpp",
+        "LogStatistics.cpp",
+        "LogTags.cpp",
+        "LogdLock.cpp",
+        "PruneList.cpp",
+        "SerializedFlushToState.cpp",
+        "SerializedLogBuffer.cpp",
+        "SerializedLogChunk.cpp",
+        "SimpleLogBuffer.cpp",
+    ],
+    static_libs: ["liblog"],
+    logtags: ["event.logtags"],
+
+    export_include_dirs: ["."],
+}
+
+cc_binary {
+    name: "logd",
+    defaults: ["logd_defaults"],
+    init_rc: ["logd.rc"],
+
+    srcs: [
+        "main.cpp",
+        "LogPermissions.cpp",
+        "CommandListener.cpp",
+        "LogListener.cpp",
+        "LogReader.cpp",
+        "LogAudit.cpp",
+        "LogKlog.cpp",
+        "libaudit.cpp",
+    ],
+
+    static_libs: [
+        "liblog",
+        "liblogd",
+    ],
+
+    shared_libs: [
+        "libsysutils",
+        "libcutils",
+        "libpackagelistparser",
+        "libprocessgroup",
+        "libcap",
+    ],
+}
+
+cc_binary {
+    name: "auditctl",
+
+    srcs: [
+        "auditctl.cpp",
+        "libaudit.cpp",
+    ],
+
+    shared_libs: ["libbase"],
+
+    cflags: [
+        "-Wextra",
+    ],
+}
+
+prebuilt_etc {
+    name: "logtagd.rc",
+    src: "logtagd.rc",
+    sub_dir: "init",
+}
+
+// -----------------------------------------------------------------------------
+// Unit tests.
+// -----------------------------------------------------------------------------
+
+cc_defaults {
+    name: "logd-unit-test-defaults",
+
+    cflags: [
+        "-fstack-protector-all",
+        "-g",
+        "-Wall",
+        "-Wthread-safety",
+        "-Wextra",
+        "-Werror",
+        "-fno-builtin",
+    ] + event_flag,
+
+    srcs: [
+        "ChattyLogBufferTest.cpp",
+        "logd_test.cpp",
+        "LogBufferTest.cpp",
+        "SerializedLogChunkTest.cpp",
+        "SerializedFlushToStateTest.cpp",
+    ],
+    sanitize: {
+        cfi: true,
+    },
+    static_libs: [
+        "libbase",
+        "libcutils",
+        "liblog",
+        "liblogd",
+        "libselinux",
+        "libz",
+        "libzstd",
+    ],
+}
+
+// Build tests for the logger. Run with:
+//   adb shell /data/nativetest/logd-unit-tests/logd-unit-tests
+cc_test {
+    name: "logd-unit-tests",
+    host_supported: true,
+    defaults: ["logd-unit-test-defaults"],
+}
+
+cc_test {
+    name: "CtsLogdTestCases",
+    defaults: ["logd-unit-test-defaults"],
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    test_suites: [
+        "cts",
+        "device-tests",
+    ],
+}
+
+cc_binary {
+    name: "replay_messages",
+    defaults: ["logd_defaults"],
+    host_supported: true,
+
+    srcs: [
+        "ReplayMessages.cpp",
+    ],
+
+    static_libs: [
+        "libbase",
+        "libcutils",
+        "liblog",
+        "liblogd",
+        "libselinux",
+        "libz",
+        "libzstd",
+    ],
+}
diff --git a/logd/Android.mk b/logd/Android.mk
deleted file mode 100644
index 615d030..0000000
--- a/logd/Android.mk
+++ /dev/null
@@ -1,55 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE:= logd
-
-LOCAL_SRC_FILES := \
-    main.cpp \
-    LogCommand.cpp \
-    CommandListener.cpp \
-    LogListener.cpp \
-    LogReader.cpp \
-    FlushCommand.cpp \
-    LogBuffer.cpp \
-    LogBufferElement.cpp \
-    LogTimes.cpp \
-    LogStatistics.cpp \
-    LogWhiteBlackList.cpp \
-    libaudit.c \
-    LogAudit.cpp \
-    LogKlog.cpp \
-    event.logtags
-
-LOCAL_SHARED_LIBRARIES := \
-    libsysutils \
-    liblog \
-    libcutils \
-    libutils
-
-# This is what we want to do:
-#  event_logtags = $(shell \
-#    sed -n \
-#        "s/^\([0-9]*\)[ \t]*$1[ \t].*/-D`echo $1 | tr a-z A-Z`_LOG_TAG=\1/p" \
-#        $(LOCAL_PATH)/$2/event.logtags)
-#  event_flag := $(call event_logtags,auditd)
-#  event_flag += $(call event_logtags,logd)
-# so make sure we do not regret hard-coding it as follows:
-event_flag := -DAUDITD_LOG_TAG=1003 -DLOGD_LOG_TAG=1004
-
-LOCAL_CFLAGS := -Werror $(event_flag)
-
-include $(BUILD_EXECUTABLE)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := logpersist.start
-LOCAL_MODULE_TAGS := debug
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_MODULE_PATH := $(bin_dir)
-LOCAL_SRC_FILES := logpersist
-ALL_TOOLS := logpersist.start logpersist.stop logpersist.cat
-LOCAL_POST_INSTALL_CMD := $(hide) $(foreach t,$(filter-out $(LOCAL_MODULE),$(ALL_TOOLS)),ln -sf $(LOCAL_MODULE) $(TARGET_OUT)/bin/$(t);)
-include $(BUILD_PREBUILT)
-
-include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/logd/AndroidTest.xml b/logd/AndroidTest.xml
new file mode 100644
index 0000000..a25dc44
--- /dev/null
+++ b/logd/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<configuration description="Config for CTS Logging Daemon test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="systems" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsLogdTestCases->/data/local/tmp/CtsLogdTestCases" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="CtsLogdTestCases" />
+        <option name="runtime-hint" value="65s" />
+    </test>
+</configuration>
diff --git a/logd/ChattyLogBuffer.cpp b/logd/ChattyLogBuffer.cpp
new file mode 100644
index 0000000..19dad17
--- /dev/null
+++ b/logd/ChattyLogBuffer.cpp
@@ -0,0 +1,617 @@
+/*
+ * Copyright (C) 2012-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.
+ */
+// for manual checking of stale entries during ChattyLogBuffer::erase()
+//#define DEBUG_CHECK_FOR_STALE_ENTRIES
+
+#include "ChattyLogBuffer.h"
+
+#include <ctype.h>
+#include <endian.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/cdefs.h>
+#include <sys/user.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <limits>
+#include <unordered_map>
+#include <utility>
+
+#include <private/android_logger.h>
+
+#include "LogUtils.h"
+
+#ifndef __predict_false
+#define __predict_false(exp) __builtin_expect((exp) != 0, 0)
+#endif
+
+ChattyLogBuffer::ChattyLogBuffer(LogReaderList* reader_list, LogTags* tags, PruneList* prune,
+                                 LogStatistics* stats)
+    : SimpleLogBuffer(reader_list, tags, stats), prune_(prune) {}
+
+ChattyLogBuffer::~ChattyLogBuffer() {}
+
+enum match_type { DIFFERENT, SAME, SAME_LIBLOG };
+
+static enum match_type Identical(const LogBufferElement& elem, const LogBufferElement& last) {
+    ssize_t lenl = elem.msg_len();
+    if (lenl <= 0) return DIFFERENT;  // value if this represents a chatty elem
+    ssize_t lenr = last.msg_len();
+    if (lenr <= 0) return DIFFERENT;  // value if this represents a chatty elem
+    if (elem.uid() != last.uid()) return DIFFERENT;
+    if (elem.pid() != last.pid()) return DIFFERENT;
+    if (elem.tid() != last.tid()) return DIFFERENT;
+
+    // last is more than a minute old, stop squashing identical messages
+    if (elem.realtime().nsec() > (last.realtime().nsec() + 60 * NS_PER_SEC)) return DIFFERENT;
+
+    // Identical message
+    const char* msgl = elem.msg();
+    const char* msgr = last.msg();
+    if (lenl == lenr) {
+        if (!fastcmp<memcmp>(msgl, msgr, lenl)) return SAME;
+        // liblog tagged messages (content gets summed)
+        if (elem.log_id() == LOG_ID_EVENTS && lenl == sizeof(android_log_event_int_t) &&
+            !fastcmp<memcmp>(msgl, msgr, sizeof(android_log_event_int_t) - sizeof(int32_t)) &&
+            elem.GetTag() == LIBLOG_LOG_TAG) {
+            return SAME_LIBLOG;
+        }
+    }
+
+    // audit message (except sequence number) identical?
+    if (IsBinary(last.log_id()) &&
+        lenl > static_cast<ssize_t>(sizeof(android_log_event_string_t)) &&
+        lenr > static_cast<ssize_t>(sizeof(android_log_event_string_t))) {
+        if (fastcmp<memcmp>(msgl, msgr, sizeof(android_log_event_string_t) - sizeof(int32_t))) {
+            return DIFFERENT;
+        }
+        msgl += sizeof(android_log_event_string_t);
+        lenl -= sizeof(android_log_event_string_t);
+        msgr += sizeof(android_log_event_string_t);
+        lenr -= sizeof(android_log_event_string_t);
+    }
+    static const char avc[] = "): avc: ";
+    const char* avcl = android::strnstr(msgl, lenl, avc);
+    if (!avcl) return DIFFERENT;
+    lenl -= avcl - msgl;
+    const char* avcr = android::strnstr(msgr, lenr, avc);
+    if (!avcr) return DIFFERENT;
+    lenr -= avcr - msgr;
+    if (lenl != lenr) return DIFFERENT;
+    if (fastcmp<memcmp>(avcl + strlen(avc), avcr + strlen(avc), lenl - strlen(avc))) {
+        return DIFFERENT;
+    }
+    return SAME;
+}
+
+void ChattyLogBuffer::LogInternal(LogBufferElement&& elem) {
+    // b/137093665: don't coalesce security messages.
+    if (elem.log_id() == LOG_ID_SECURITY) {
+        SimpleLogBuffer::LogInternal(std::move(elem));
+        return;
+    }
+    int log_id = elem.log_id();
+
+    // Initialize last_logged_elements_ to a copy of elem if logging the first element for a log_id.
+    if (!last_logged_elements_[log_id]) {
+        last_logged_elements_[log_id].emplace(elem);
+        SimpleLogBuffer::LogInternal(std::move(elem));
+        return;
+    }
+
+    LogBufferElement& current_last = *last_logged_elements_[log_id];
+    enum match_type match = Identical(elem, current_last);
+
+    if (match == DIFFERENT) {
+        if (duplicate_elements_[log_id]) {
+            // If we previously had 3+ identical messages, log the chatty message.
+            if (duplicate_elements_[log_id]->dropped_count() > 0) {
+                SimpleLogBuffer::LogInternal(std::move(*duplicate_elements_[log_id]));
+            }
+            duplicate_elements_[log_id].reset();
+            // Log the saved copy of the last identical message seen.
+            SimpleLogBuffer::LogInternal(std::move(current_last));
+        }
+        last_logged_elements_[log_id].emplace(elem);
+        SimpleLogBuffer::LogInternal(std::move(elem));
+        return;
+    }
+
+    // 2 identical message: set duplicate_elements_ appropriately.
+    if (!duplicate_elements_[log_id]) {
+        duplicate_elements_[log_id].emplace(std::move(current_last));
+        last_logged_elements_[log_id].emplace(std::move(elem));
+        return;
+    }
+
+    // 3+ identical LIBLOG event messages: coalesce them into last_logged_elements_.
+    if (match == SAME_LIBLOG) {
+        const android_log_event_int_t* current_last_event =
+                reinterpret_cast<const android_log_event_int_t*>(current_last.msg());
+        int64_t current_last_count = current_last_event->payload.data;
+        android_log_event_int_t* elem_event =
+                reinterpret_cast<android_log_event_int_t*>(const_cast<char*>(elem.msg()));
+        int64_t elem_count = elem_event->payload.data;
+
+        int64_t total = current_last_count + elem_count;
+        if (total > std::numeric_limits<int32_t>::max()) {
+            SimpleLogBuffer::LogInternal(std::move(current_last));
+            last_logged_elements_[log_id].emplace(std::move(elem));
+            return;
+        }
+        stats()->AddTotal(current_last.log_id(), current_last.msg_len());
+        elem_event->payload.data = total;
+        last_logged_elements_[log_id].emplace(std::move(elem));
+        return;
+    }
+
+    // 3+ identical messages (not LIBLOG) messages: increase the drop count.
+    uint16_t dropped_count = duplicate_elements_[log_id]->dropped_count();
+    if (dropped_count == std::numeric_limits<uint16_t>::max()) {
+        SimpleLogBuffer::LogInternal(std::move(*duplicate_elements_[log_id]));
+        dropped_count = 0;
+    }
+    // We're dropping the current_last log so add its stats to the total.
+    stats()->AddTotal(current_last.log_id(), current_last.msg_len());
+    // Use current_last for tracking the dropped count to always use the latest timestamp.
+    current_last.SetDropped(dropped_count + 1);
+    duplicate_elements_[log_id].emplace(std::move(current_last));
+    last_logged_elements_[log_id].emplace(std::move(elem));
+}
+
+LogBufferElementCollection::iterator ChattyLogBuffer::Erase(LogBufferElementCollection::iterator it,
+                                                            bool coalesce) {
+    LogBufferElement& element = *it;
+    log_id_t id = element.log_id();
+
+    // Remove iterator references in the various lists that will become stale
+    // after the element is erased from the main logging list.
+
+    {  // start of scope for found iterator
+        int key = (id == LOG_ID_EVENTS || id == LOG_ID_SECURITY) ? element.GetTag() : element.uid();
+        LogBufferIteratorMap::iterator found = mLastWorst[id].find(key);
+        if ((found != mLastWorst[id].end()) && (it == found->second)) {
+            mLastWorst[id].erase(found);
+        }
+    }
+
+    {  // start of scope for pid found iterator
+        // element->uid() may not be AID_SYSTEM for next-best-watermark.
+        // will not assume id != LOG_ID_EVENTS or LOG_ID_SECURITY for KISS and
+        // long term code stability, find() check should be fast for those ids.
+        LogBufferPidIteratorMap::iterator found = mLastWorstPidOfSystem[id].find(element.pid());
+        if (found != mLastWorstPidOfSystem[id].end() && it == found->second) {
+            mLastWorstPidOfSystem[id].erase(found);
+        }
+    }
+
+#ifdef DEBUG_CHECK_FOR_STALE_ENTRIES
+    LogBufferElementCollection::iterator bad = it;
+    int key = (id == LOG_ID_EVENTS || id == LOG_ID_SECURITY) ? element->GetTag() : element->uid();
+#endif
+
+    if (coalesce) {
+        stats()->Erase(element.ToLogStatisticsElement());
+    } else {
+        stats()->Subtract(element.ToLogStatisticsElement());
+    }
+
+    it = SimpleLogBuffer::Erase(it);
+
+#ifdef DEBUG_CHECK_FOR_STALE_ENTRIES
+    log_id_for_each(i) {
+        for (auto b : mLastWorst[i]) {
+            if (bad == b.second) {
+                LOG(ERROR) << StringPrintf("stale mLastWorst[%d] key=%d mykey=%d", i, b.first, key);
+            }
+        }
+        for (auto b : mLastWorstPidOfSystem[i]) {
+            if (bad == b.second) {
+                LOG(ERROR) << StringPrintf("stale mLastWorstPidOfSystem[%d] pid=%d", i, b.first);
+            }
+        }
+    }
+#endif
+    return it;
+}
+
+// Define a temporary mechanism to report the last LogBufferElement pointer
+// for the specified uid, pid and tid. Used below to help merge-sort when
+// pruning for worst UID.
+class LogBufferElementLast {
+    typedef std::unordered_map<uint64_t, LogBufferElement*> LogBufferElementMap;
+    LogBufferElementMap map;
+
+  public:
+    bool coalesce(LogBufferElement* element, uint16_t dropped) {
+        uint64_t key = LogBufferElementKey(element->uid(), element->pid(), element->tid());
+        LogBufferElementMap::iterator it = map.find(key);
+        if (it != map.end()) {
+            LogBufferElement* found = it->second;
+            uint16_t moreDropped = found->dropped_count();
+            if ((dropped + moreDropped) > USHRT_MAX) {
+                map.erase(it);
+            } else {
+                found->SetDropped(dropped + moreDropped);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void add(LogBufferElement* element) {
+        uint64_t key = LogBufferElementKey(element->uid(), element->pid(), element->tid());
+        map[key] = element;
+    }
+
+    void clear() { map.clear(); }
+
+    void clear(LogBufferElement* element) {
+        uint64_t current = element->realtime().nsec() - (EXPIRE_RATELIMIT * NS_PER_SEC);
+        for (LogBufferElementMap::iterator it = map.begin(); it != map.end();) {
+            LogBufferElement* mapElement = it->second;
+            if (mapElement->dropped_count() >= EXPIRE_THRESHOLD &&
+                current > mapElement->realtime().nsec()) {
+                it = map.erase(it);
+            } else {
+                ++it;
+            }
+        }
+    }
+
+  private:
+    uint64_t LogBufferElementKey(uid_t uid, pid_t pid, pid_t tid) {
+        return uint64_t(uid) << 32 | uint64_t(pid) << 16 | uint64_t(tid);
+    }
+};
+
+// prune "pruneRows" of type "id" from the buffer.
+//
+// This garbage collection task is used to expire log entries. It is called to
+// remove all logs (clear), all UID logs (unprivileged clear), or every
+// 256 or 10% of the total logs (whichever is less) to prune the logs.
+//
+// First there is a prep phase where we discover the reader region lock that
+// acts as a backstop to any pruning activity to stop there and go no further.
+//
+// There are three major pruning loops that follow. All expire from the oldest
+// entries. Since there are multiple log buffers, the Android logging facility
+// will appear to drop entries 'in the middle' when looking at multiple log
+// sources and buffers. This effect is slightly more prominent when we prune
+// the worst offender by logging source. Thus the logs slowly loose content
+// and value as you move back in time. This is preferred since chatty sources
+// invariably move the logs value down faster as less chatty sources would be
+// expired in the noise.
+//
+// The first pass prunes elements that match 3 possible rules:
+// 1) A high priority prune rule, for example ~100/20, which indicates elements from UID 100 and PID
+//    20 should be pruned in this first pass.
+// 2) The default chatty pruning rule, ~!.  This rule sums the total size spent on log messages for
+//    each UID this log buffer.  If the highest sum consumes more than 12.5% of the log buffer, then
+//    these elements from that UID are pruned.
+// 3) The default AID_SYSTEM pruning rule, ~1000/!.  This rule is a special case to 2), if
+//    AID_SYSTEM is the top consumer of the log buffer, then this rule sums the total size spent on
+//    log messages for each PID in AID_SYSTEM in this log buffer and prunes elements from the PID
+//    with the highest sum.
+// This pass reevaluates the sums for rules 2) and 3) for every log message pruned. It creates
+// 'chatty' entries for the elements that it prunes and merges related chatty entries together. It
+// completes when one of three conditions have been met:
+// 1) The requested element count has been pruned.
+// 2) There are no elements that match any of these rules.
+// 3) A reader is referencing the oldest element that would match these rules.
+//
+// The second pass prunes elements starting from the beginning of the log.  It skips elements that
+// match any low priority prune rules.  It completes when one of three conditions have been met:
+// 1) The requested element count has been pruned.
+// 2) All elements except those mwatching low priority prune rules have been pruned.
+// 3) A reader is referencing the oldest element that would match these rules.
+//
+// The final pass only happens if there are any low priority prune rules and if the first two passes
+// were unable to prune the requested number of elements.  It prunes elements all starting from the
+// beginning of the log, regardless of if they match any low priority prune rules.
+//
+// If the requested number of logs was unable to be pruned, KickReader() is called to mitigate the
+// situation before the next call to Prune() and the function returns false.  Otherwise, if the
+// requested number of logs or all logs present in the buffer are pruned, in the case of Clear(),
+// it returns true.
+bool ChattyLogBuffer::Prune(log_id_t id, unsigned long pruneRows, uid_t caller_uid) {
+    LogReaderThread* oldest = nullptr;
+    bool clearAll = pruneRows == ULONG_MAX;
+
+    // Region locked?
+    for (const auto& reader_thread : reader_list()->reader_threads()) {
+        if (!reader_thread->IsWatching(id)) {
+            continue;
+        }
+        if (!oldest || oldest->start() > reader_thread->start() ||
+            (oldest->start() == reader_thread->start() &&
+             reader_thread->deadline().time_since_epoch().count() != 0)) {
+            oldest = reader_thread.get();
+        }
+    }
+
+    LogBufferElementCollection::iterator it;
+
+    if (__predict_false(caller_uid != AID_ROOT)) {  // unlikely
+        // Only here if clear all request from non system source, so chatty
+        // filter logistics is not required.
+        it = GetOldest(id);
+        while (it != logs().end()) {
+            LogBufferElement& element = *it;
+
+            if (element.log_id() != id || element.uid() != caller_uid) {
+                ++it;
+                continue;
+            }
+
+            if (oldest && oldest->start() <= element.sequence()) {
+                KickReader(oldest, id, pruneRows);
+                return false;
+            }
+
+            it = Erase(it);
+            if (--pruneRows == 0) {
+                return true;
+            }
+        }
+        return true;
+    }
+
+    // First prune pass.
+    bool check_high_priority = id != LOG_ID_SECURITY && prune_->HasHighPriorityPruneRules();
+    while (!clearAll && (pruneRows > 0)) {
+        // recalculate the worst offender on every batched pass
+        int worst = -1;  // not valid for uid() or getKey()
+        size_t worst_sizes = 0;
+        size_t second_worst_sizes = 0;
+        pid_t worstPid = 0;  // POSIX guarantees PID != 0
+
+        if (worstUidEnabledForLogid(id) && prune_->worst_uid_enabled()) {
+            // Calculate threshold as 12.5% of available storage
+            size_t threshold = max_size(id) / 8;
+
+            if (id == LOG_ID_EVENTS || id == LOG_ID_SECURITY) {
+                stats()->WorstTwoTags(threshold, &worst, &worst_sizes, &second_worst_sizes);
+                // per-pid filter for AID_SYSTEM sources is too complex
+            } else {
+                stats()->WorstTwoUids(id, threshold, &worst, &worst_sizes, &second_worst_sizes);
+
+                if (worst == AID_SYSTEM && prune_->worst_pid_of_system_enabled()) {
+                    stats()->WorstTwoSystemPids(id, worst_sizes, &worstPid, &second_worst_sizes);
+                }
+            }
+        }
+
+        // skip if we have neither a worst UID or high priority prune rules
+        if (worst == -1 && !check_high_priority) {
+            break;
+        }
+
+        bool kick = false;
+        bool leading = true;  // true if starting from the oldest log entry, false if starting from
+                              // a specific chatty entry.
+        // Perform at least one mandatory garbage collection cycle in following
+        // - clear leading chatty tags
+        // - coalesce chatty tags
+        // - check age-out of preserved logs
+        bool gc = pruneRows <= 1;
+        if (!gc && (worst != -1)) {
+            {  // begin scope for worst found iterator
+                LogBufferIteratorMap::iterator found = mLastWorst[id].find(worst);
+                if (found != mLastWorst[id].end() && found->second != logs().end()) {
+                    leading = false;
+                    it = found->second;
+                }
+            }
+            if (worstPid) {  // begin scope for pid worst found iterator
+                // FYI: worstPid only set if !LOG_ID_EVENTS and
+                //      !LOG_ID_SECURITY, not going to make that assumption ...
+                LogBufferPidIteratorMap::iterator found = mLastWorstPidOfSystem[id].find(worstPid);
+                if (found != mLastWorstPidOfSystem[id].end() && found->second != logs().end()) {
+                    leading = false;
+                    it = found->second;
+                }
+            }
+        }
+        if (leading) {
+            it = GetOldest(id);
+        }
+        static const log_time too_old{EXPIRE_HOUR_THRESHOLD * 60 * 60, 0};
+        LogBufferElementCollection::iterator lastt;
+        lastt = logs().end();
+        --lastt;
+        LogBufferElementLast last;
+        while (it != logs().end()) {
+            LogBufferElement& element = *it;
+
+            if (oldest && oldest->start() <= element.sequence()) {
+                // Do not let chatty eliding trigger any reader mitigation
+                break;
+            }
+
+            if (element.log_id() != id) {
+                ++it;
+                continue;
+            }
+            // below this point element->log_id() == id
+
+            uint16_t dropped = element.dropped_count();
+
+            // remove any leading drops
+            if (leading && dropped) {
+                it = Erase(it);
+                continue;
+            }
+
+            if (dropped && last.coalesce(&element, dropped)) {
+                it = Erase(it, true);
+                continue;
+            }
+
+            int key = (id == LOG_ID_EVENTS || id == LOG_ID_SECURITY) ? element.GetTag()
+                                                                     : element.uid();
+
+            if (check_high_priority && prune_->IsHighPriority(&element)) {
+                last.clear(&element);
+                it = Erase(it);
+                if (dropped) {
+                    continue;
+                }
+
+                pruneRows--;
+                if (pruneRows == 0) {
+                    break;
+                }
+
+                if (key == worst) {
+                    kick = true;
+                    if (worst_sizes < second_worst_sizes) {
+                        break;
+                    }
+                    worst_sizes -= element.msg_len();
+                }
+                continue;
+            }
+
+            if (element.realtime() < (lastt->realtime() - too_old) ||
+                element.realtime() > lastt->realtime()) {
+                break;
+            }
+
+            if (dropped) {
+                last.add(&element);
+                if (worstPid && ((!gc && element.pid() == worstPid) ||
+                                 mLastWorstPidOfSystem[id].find(element.pid()) ==
+                                         mLastWorstPidOfSystem[id].end())) {
+                    // element->uid() may not be AID_SYSTEM, next best
+                    // watermark if current one empty. id is not LOG_ID_EVENTS
+                    // or LOG_ID_SECURITY because of worstPid check.
+                    mLastWorstPidOfSystem[id][element.pid()] = it;
+                }
+                if ((!gc && !worstPid && (key == worst)) ||
+                    (mLastWorst[id].find(key) == mLastWorst[id].end())) {
+                    mLastWorst[id][key] = it;
+                }
+                ++it;
+                continue;
+            }
+
+            if (key != worst || (worstPid && element.pid() != worstPid)) {
+                leading = false;
+                last.clear(&element);
+                ++it;
+                continue;
+            }
+            // key == worst below here
+            // If worstPid set, then element->pid() == worstPid below here
+
+            pruneRows--;
+            if (pruneRows == 0) {
+                break;
+            }
+
+            kick = true;
+
+            uint16_t len = element.msg_len();
+
+            // do not create any leading drops
+            if (leading) {
+                it = Erase(it);
+            } else {
+                stats()->Drop(element.ToLogStatisticsElement());
+                element.SetDropped(1);
+                if (last.coalesce(&element, 1)) {
+                    it = Erase(it, true);
+                } else {
+                    last.add(&element);
+                    if (worstPid && (!gc || mLastWorstPidOfSystem[id].find(worstPid) ==
+                                                    mLastWorstPidOfSystem[id].end())) {
+                        // element->uid() may not be AID_SYSTEM, next best
+                        // watermark if current one empty. id is not
+                        // LOG_ID_EVENTS or LOG_ID_SECURITY because of worstPid.
+                        mLastWorstPidOfSystem[id][worstPid] = it;
+                    }
+                    if ((!gc && !worstPid) || mLastWorst[id].find(worst) == mLastWorst[id].end()) {
+                        mLastWorst[id][worst] = it;
+                    }
+                    ++it;
+                }
+            }
+            if (worst_sizes < second_worst_sizes) {
+                break;
+            }
+            worst_sizes -= len;
+        }
+        last.clear();
+
+        if (!kick || !prune_->worst_uid_enabled()) {
+            break;  // the following loop will ask bad clients to skip/drop
+        }
+    }
+
+    // Second prune pass.
+    bool skipped_low_priority_prune = false;
+    bool check_low_priority =
+            id != LOG_ID_SECURITY && prune_->HasLowPriorityPruneRules() && !clearAll;
+    it = GetOldest(id);
+    while (pruneRows > 0 && it != logs().end()) {
+        LogBufferElement& element = *it;
+
+        if (element.log_id() != id) {
+            it++;
+            continue;
+        }
+
+        if (oldest && oldest->start() <= element.sequence()) {
+            if (!skipped_low_priority_prune) KickReader(oldest, id, pruneRows);
+            break;
+        }
+
+        if (check_low_priority && !element.dropped_count() && prune_->IsLowPriority(&element)) {
+            skipped_low_priority_prune = true;
+            it++;
+            continue;
+        }
+
+        it = Erase(it);
+        pruneRows--;
+    }
+
+    // Third prune pass.
+    if (skipped_low_priority_prune && pruneRows > 0) {
+        it = GetOldest(id);
+        while (it != logs().end() && pruneRows > 0) {
+            LogBufferElement& element = *it;
+
+            if (element.log_id() != id) {
+                ++it;
+                continue;
+            }
+
+            if (oldest && oldest->start() <= element.sequence()) {
+                KickReader(oldest, id, pruneRows);
+                break;
+            }
+
+            it = Erase(it);
+            pruneRows--;
+        }
+    }
+
+    return pruneRows == 0 || it == logs().end();
+}
diff --git a/logd/ChattyLogBuffer.h b/logd/ChattyLogBuffer.h
new file mode 100644
index 0000000..e5b8611
--- /dev/null
+++ b/logd/ChattyLogBuffer.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2012-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.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <list>
+#include <optional>
+#include <string>
+
+#include <android-base/thread_annotations.h>
+#include <android/log.h>
+#include <private/android_filesystem_config.h>
+
+#include "LogBuffer.h"
+#include "LogBufferElement.h"
+#include "LogReaderList.h"
+#include "LogReaderThread.h"
+#include "LogStatistics.h"
+#include "LogTags.h"
+#include "LogWriter.h"
+#include "LogdLock.h"
+#include "PruneList.h"
+#include "SimpleLogBuffer.h"
+
+typedef std::list<LogBufferElement> LogBufferElementCollection;
+
+class ChattyLogBuffer : public SimpleLogBuffer {
+    // watermark of any worst/chatty uid processing
+    typedef std::unordered_map<uid_t, LogBufferElementCollection::iterator> LogBufferIteratorMap;
+    LogBufferIteratorMap mLastWorst[LOG_ID_MAX] GUARDED_BY(logd_lock);
+    // watermark of any worst/chatty pid of system processing
+    typedef std::unordered_map<pid_t, LogBufferElementCollection::iterator> LogBufferPidIteratorMap;
+    LogBufferPidIteratorMap mLastWorstPidOfSystem[LOG_ID_MAX] GUARDED_BY(logd_lock);
+
+  public:
+    ChattyLogBuffer(LogReaderList* reader_list, LogTags* tags, PruneList* prune,
+                    LogStatistics* stats);
+    ~ChattyLogBuffer();
+
+  protected:
+    bool Prune(log_id_t id, unsigned long pruneRows, uid_t uid) REQUIRES(logd_lock) override;
+    void LogInternal(LogBufferElement&& elem) REQUIRES(logd_lock) override;
+
+  private:
+    LogBufferElementCollection::iterator Erase(LogBufferElementCollection::iterator it,
+                                               bool coalesce = false) REQUIRES(logd_lock);
+
+    PruneList* prune_;
+
+    // This always contains a copy of the last message logged, for deduplication.
+    std::optional<LogBufferElement> last_logged_elements_[LOG_ID_MAX] GUARDED_BY(logd_lock);
+    // This contains an element if duplicate messages are seen.
+    // Its `dropped` count is `duplicates seen - 1`.
+    std::optional<LogBufferElement> duplicate_elements_[LOG_ID_MAX] GUARDED_BY(logd_lock);
+};
diff --git a/logd/ChattyLogBufferTest.cpp b/logd/ChattyLogBufferTest.cpp
new file mode 100644
index 0000000..e273efe
--- /dev/null
+++ b/logd/ChattyLogBufferTest.cpp
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2020 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 "LogBufferTest.h"
+
+class ChattyLogBufferTest : public LogBufferTest {};
+
+TEST_P(ChattyLogBufferTest, deduplication_simple) {
+    auto make_message = [&](uint32_t sec, const char* tag, const char* msg,
+                            bool regex = false) -> LogMessage {
+        logger_entry entry = {
+                .pid = 1, .tid = 1, .sec = sec, .nsec = 1, .lid = LOG_ID_MAIN, .uid = 0};
+        std::string message;
+        message.push_back(ANDROID_LOG_INFO);
+        message.append(tag);
+        message.push_back('\0');
+        message.append(msg);
+        message.push_back('\0');
+        return {entry, message, regex};
+    };
+
+    // clang-format off
+    std::vector<LogMessage> log_messages = {
+            make_message(0, "test_tag", "duplicate"),
+            make_message(1, "test_tag", "duplicate"),
+            make_message(2, "test_tag", "not_same"),
+            make_message(3, "test_tag", "duplicate"),
+            make_message(4, "test_tag", "duplicate"),
+            make_message(5, "test_tag", "not_same"),
+            make_message(6, "test_tag", "duplicate"),
+            make_message(7, "test_tag", "duplicate"),
+            make_message(8, "test_tag", "duplicate"),
+            make_message(9, "test_tag", "not_same"),
+            make_message(10, "test_tag", "duplicate"),
+            make_message(11, "test_tag", "duplicate"),
+            make_message(12, "test_tag", "duplicate"),
+            make_message(13, "test_tag", "duplicate"),
+            make_message(14, "test_tag", "duplicate"),
+            make_message(15, "test_tag", "duplicate"),
+            make_message(16, "test_tag", "not_same"),
+            make_message(100, "test_tag", "duplicate"),
+            make_message(200, "test_tag", "duplicate"),
+            make_message(300, "test_tag", "duplicate"),
+    };
+    // clang-format on
+    FixupMessages(&log_messages);
+    LogMessages(log_messages);
+
+    std::vector<LogMessage> read_log_messages;
+    {
+        auto lock = std::lock_guard{logd_lock};
+        std::unique_ptr<LogWriter> test_writer(new TestWriter(&read_log_messages, nullptr));
+
+        std::unique_ptr<FlushToState> flush_to_state =
+                log_buffer_->CreateFlushToState(1, kLogMaskAll);
+        EXPECT_TRUE(log_buffer_->FlushTo(test_writer.get(), *flush_to_state, nullptr));
+    }
+
+    std::vector<LogMessage> expected_log_messages = {
+            make_message(0, "test_tag", "duplicate"),
+            make_message(1, "test_tag", "duplicate"),
+            make_message(2, "test_tag", "not_same"),
+            make_message(3, "test_tag", "duplicate"),
+            make_message(4, "test_tag", "duplicate"),
+            make_message(5, "test_tag", "not_same"),
+            // 3 duplicate logs together print the first, a 1 count chatty message, then the last.
+            make_message(6, "test_tag", "duplicate"),
+            make_message(7, "chatty", "uid=0\\([^\\)]+\\) [^ ]+ identical 1 line", true),
+            make_message(8, "test_tag", "duplicate"),
+            make_message(9, "test_tag", "not_same"),
+            // 6 duplicate logs together print the first, a 4 count chatty message, then the last.
+            make_message(10, "test_tag", "duplicate"),
+            make_message(14, "chatty", "uid=0\\([^\\)]+\\) [^ ]+ identical 4 lines", true),
+            make_message(15, "test_tag", "duplicate"),
+            make_message(16, "test_tag", "not_same"),
+            // duplicate logs > 1 minute apart are not deduplicated.
+            make_message(100, "test_tag", "duplicate"),
+            make_message(200, "test_tag", "duplicate"),
+            make_message(300, "test_tag", "duplicate"),
+    };
+    FixupMessages(&expected_log_messages);
+    CompareLogMessages(expected_log_messages, read_log_messages);
+};
+
+TEST_P(ChattyLogBufferTest, deduplication_overflow) {
+    auto make_message = [&](uint32_t sec, const char* tag, const char* msg,
+                            bool regex = false) -> LogMessage {
+        logger_entry entry = {
+                .pid = 1, .tid = 1, .sec = sec, .nsec = 1, .lid = LOG_ID_MAIN, .uid = 0};
+        std::string message;
+        message.push_back(ANDROID_LOG_INFO);
+        message.append(tag);
+        message.push_back('\0');
+        message.append(msg);
+        message.push_back('\0');
+        return {entry, message, regex};
+    };
+
+    uint32_t sec = 0;
+    std::vector<LogMessage> log_messages = {
+            make_message(sec++, "test_tag", "normal"),
+    };
+    size_t expired_per_chatty_message = std::numeric_limits<uint16_t>::max();
+    for (size_t i = 0; i < expired_per_chatty_message + 3; ++i) {
+        log_messages.emplace_back(make_message(sec++, "test_tag", "duplicate"));
+    }
+    log_messages.emplace_back(make_message(sec++, "test_tag", "normal"));
+    FixupMessages(&log_messages);
+    LogMessages(log_messages);
+
+    std::vector<LogMessage> read_log_messages;
+    {
+        auto lock = std::lock_guard{logd_lock};
+        std::unique_ptr<LogWriter> test_writer(new TestWriter(&read_log_messages, nullptr));
+        std::unique_ptr<FlushToState> flush_to_state =
+                log_buffer_->CreateFlushToState(1, kLogMaskAll);
+        EXPECT_TRUE(log_buffer_->FlushTo(test_writer.get(), *flush_to_state, nullptr));
+    }
+
+    std::vector<LogMessage> expected_log_messages = {
+            make_message(0, "test_tag", "normal"),
+            make_message(1, "test_tag", "duplicate"),
+            make_message(expired_per_chatty_message + 1, "chatty",
+                         "uid=0\\([^\\)]+\\) [^ ]+ identical 65535 lines", true),
+            make_message(expired_per_chatty_message + 2, "chatty",
+                         "uid=0\\([^\\)]+\\) [^ ]+ identical 1 line", true),
+            make_message(expired_per_chatty_message + 3, "test_tag", "duplicate"),
+            make_message(expired_per_chatty_message + 4, "test_tag", "normal"),
+    };
+    FixupMessages(&expected_log_messages);
+    CompareLogMessages(expected_log_messages, read_log_messages);
+}
+
+TEST_P(ChattyLogBufferTest, deduplication_liblog) {
+    auto make_message = [&](uint32_t sec, int32_t tag, int32_t count) -> LogMessage {
+        logger_entry entry = {
+                .pid = 1, .tid = 1, .sec = sec, .nsec = 1, .lid = LOG_ID_EVENTS, .uid = 0};
+        android_log_event_int_t liblog_event = {
+                .header.tag = tag, .payload.type = EVENT_TYPE_INT, .payload.data = count};
+        return {entry, std::string(reinterpret_cast<char*>(&liblog_event), sizeof(liblog_event)),
+                false};
+    };
+
+    // LIBLOG_LOG_TAG
+    std::vector<LogMessage> log_messages = {
+            make_message(0, 1234, 1),
+            make_message(1, LIBLOG_LOG_TAG, 3),
+            make_message(2, 1234, 2),
+            make_message(3, LIBLOG_LOG_TAG, 3),
+            make_message(4, LIBLOG_LOG_TAG, 4),
+            make_message(5, 1234, 223),
+            make_message(6, LIBLOG_LOG_TAG, 2),
+            make_message(7, LIBLOG_LOG_TAG, 3),
+            make_message(8, LIBLOG_LOG_TAG, 4),
+            make_message(9, 1234, 227),
+            make_message(10, LIBLOG_LOG_TAG, 1),
+            make_message(11, LIBLOG_LOG_TAG, 3),
+            make_message(12, LIBLOG_LOG_TAG, 2),
+            make_message(13, LIBLOG_LOG_TAG, 3),
+            make_message(14, LIBLOG_LOG_TAG, 5),
+            make_message(15, 1234, 227),
+            make_message(16, LIBLOG_LOG_TAG, 2),
+            make_message(17, LIBLOG_LOG_TAG, std::numeric_limits<int32_t>::max()),
+            make_message(18, LIBLOG_LOG_TAG, 3),
+            make_message(19, LIBLOG_LOG_TAG, 5),
+            make_message(20, 1234, 227),
+    };
+    FixupMessages(&log_messages);
+    LogMessages(log_messages);
+
+    std::vector<LogMessage> read_log_messages;
+    {
+        auto lock = std::lock_guard{logd_lock};
+        std::unique_ptr<LogWriter> test_writer(new TestWriter(&read_log_messages, nullptr));
+        std::unique_ptr<FlushToState> flush_to_state =
+                log_buffer_->CreateFlushToState(1, kLogMaskAll);
+        EXPECT_TRUE(log_buffer_->FlushTo(test_writer.get(), *flush_to_state, nullptr));
+    }
+
+    std::vector<LogMessage> expected_log_messages = {
+            make_message(0, 1234, 1),
+            make_message(1, LIBLOG_LOG_TAG, 3),
+            make_message(2, 1234, 2),
+            make_message(3, LIBLOG_LOG_TAG, 3),
+            make_message(4, LIBLOG_LOG_TAG, 4),
+            make_message(5, 1234, 223),
+            // More than 2 liblog events (3 here), sum their value into the third message.
+            make_message(6, LIBLOG_LOG_TAG, 2),
+            make_message(8, LIBLOG_LOG_TAG, 7),
+            make_message(9, 1234, 227),
+            // More than 2 liblog events (5 here), sum their value into the third message.
+            make_message(10, LIBLOG_LOG_TAG, 1),
+            make_message(14, LIBLOG_LOG_TAG, 13),
+            make_message(15, 1234, 227),
+            // int32_t max is the max for a chatty message, beyond that we must use new messages.
+            make_message(16, LIBLOG_LOG_TAG, 2),
+            make_message(17, LIBLOG_LOG_TAG, std::numeric_limits<int32_t>::max()),
+            make_message(19, LIBLOG_LOG_TAG, 8),
+            make_message(20, 1234, 227),
+    };
+    FixupMessages(&expected_log_messages);
+    CompareLogMessages(expected_log_messages, read_log_messages);
+};
+
+TEST_P(ChattyLogBufferTest, no_leading_chatty_simple) {
+    auto make_message = [&](uint32_t sec, int32_t pid, uint32_t uid, uint32_t lid, const char* tag,
+                            const char* msg, bool regex = false) -> LogMessage {
+        logger_entry entry = {.pid = pid, .tid = 1, .sec = sec, .nsec = 1, .lid = lid, .uid = uid};
+        std::string message;
+        message.push_back(ANDROID_LOG_INFO);
+        message.append(tag);
+        message.push_back('\0');
+        message.append(msg);
+        message.push_back('\0');
+        return {entry, message, regex};
+    };
+
+    // clang-format off
+    std::vector<LogMessage> log_messages = {
+            make_message(1, 1, 1, LOG_ID_MAIN, "test_tag", "duplicate1"),
+            make_message(2, 2, 2, LOG_ID_SYSTEM, "test_tag", "duplicate2"),
+            make_message(3, 2, 2, LOG_ID_SYSTEM, "test_tag", "duplicate2"),
+            make_message(4, 2, 2, LOG_ID_SYSTEM, "test_tag", "duplicate2"),
+            make_message(6, 2, 2, LOG_ID_SYSTEM, "test_tag", "not duplicate2"),
+            make_message(7, 1, 1, LOG_ID_MAIN, "test_tag", "duplicate1"),
+            make_message(8, 1, 1, LOG_ID_MAIN, "test_tag", "duplicate1"),
+            make_message(9, 1, 1, LOG_ID_MAIN, "test_tag", "duplicate1"),
+            make_message(10, 1, 1, LOG_ID_MAIN, "test_tag", "not duplicate1"),
+    };
+    // clang-format on
+    FixupMessages(&log_messages);
+    LogMessages(log_messages);
+
+    // After logging log_messages, the below is what should be in the buffer:
+    // PID=1, LOG_ID_MAIN duplicate1
+    // [1] PID=2, LOG_ID_SYSTEM duplicate2
+    // PID=2, LOG_ID_SYSTEM chatty drop
+    // PID=2, LOG_ID_SYSTEM duplicate2
+    // PID=2, LOG_ID_SYSTEM not duplicate2
+    // [2] PID=1, LOG_ID_MAIN chatty drop
+    // [3] PID=1, LOG_ID_MAIN duplicate1
+    // PID=1, LOG_ID_MAIN not duplicate1
+
+    // We then read from the 2nd sequence number, starting from log message [1], but filtering out
+    // everything but PID=1, which results in us starting with log message [2], which is a chatty
+    // drop.  Code prior to this test case would erroneously print it.  The intended behavior that
+    // this test checks prints logs starting from log message [3].
+
+    // clang-format off
+    std::vector<LogMessage> expected_log_messages = {
+            make_message(9, 1, 1, LOG_ID_MAIN, "test_tag", "duplicate1"),
+            make_message(10, 1, 1, LOG_ID_MAIN, "test_tag", "not duplicate1"),
+    };
+    FixupMessages(&expected_log_messages);
+    // clang-format on
+
+    std::vector<LogMessage> read_log_messages;
+    bool released = false;
+    {
+        auto lock = std::lock_guard{logd_lock};
+        std::unique_ptr<LogWriter> test_writer(new TestWriter(&read_log_messages, &released));
+        std::unique_ptr<LogReaderThread> log_reader(
+                new LogReaderThread(log_buffer_.get(), &reader_list_, std::move(test_writer), true,
+                                    0, ~0, 1, {}, 2, {}));
+        reader_list_.reader_threads().emplace_back(std::move(log_reader));
+    }
+
+    while (!released) {
+        usleep(5000);
+    }
+
+    CompareLogMessages(expected_log_messages, read_log_messages);
+}
+
+TEST_P(ChattyLogBufferTest, no_leading_chatty_tail) {
+    auto make_message = [&](uint32_t sec, const char* tag, const char* msg,
+                            bool regex = false) -> LogMessage {
+        logger_entry entry = {
+                .pid = 1, .tid = 1, .sec = sec, .nsec = 1, .lid = LOG_ID_MAIN, .uid = 0};
+        std::string message;
+        message.push_back(ANDROID_LOG_INFO);
+        message.append(tag);
+        message.push_back('\0');
+        message.append(msg);
+        message.push_back('\0');
+        return {entry, message, regex};
+    };
+
+    // clang-format off
+    std::vector<LogMessage> log_messages = {
+            make_message(1, "test_tag", "duplicate"),
+            make_message(2, "test_tag", "duplicate"),
+            make_message(3, "test_tag", "duplicate"),
+            make_message(4, "test_tag", "not_duplicate"),
+    };
+    // clang-format on
+    FixupMessages(&log_messages);
+    LogMessages(log_messages);
+
+    // After logging log_messages, the below is what should be in the buffer:
+    // "duplicate"
+    // chatty
+    // "duplicate"
+    // "not duplicate"
+
+    // We then read the tail 3 messages expecting there to not be a chatty message, meaning that we
+    // should only see the last two messages.
+
+    // clang-format off
+    std::vector<LogMessage> expected_log_messages = {
+            make_message(3, "test_tag", "duplicate"),
+            make_message(4, "test_tag", "not_duplicate"),
+    };
+    FixupMessages(&expected_log_messages);
+    // clang-format on
+
+    std::vector<LogMessage> read_log_messages;
+    bool released = false;
+    {
+        auto lock = std::lock_guard{logd_lock};
+        std::unique_ptr<LogWriter> test_writer(new TestWriter(&read_log_messages, &released));
+        std::unique_ptr<LogReaderThread> log_reader(
+                new LogReaderThread(log_buffer_.get(), &reader_list_, std::move(test_writer), true,
+                                    3, ~0, 0, {}, 1, {}));
+        reader_list_.reader_threads().emplace_back(std::move(log_reader));
+    }
+
+    while (!released) {
+        usleep(5000);
+    }
+
+    CompareLogMessages(expected_log_messages, read_log_messages);
+}
+
+INSTANTIATE_TEST_CASE_P(ChattyLogBufferTests, ChattyLogBufferTest, testing::Values("chatty"));
diff --git a/logd/CommandListener.cpp b/logd/CommandListener.cpp
index 489bea6..0ba1621 100644
--- a/logd/CommandListener.cpp
+++ b/logd/CommandListener.cpp
@@ -14,56 +14,47 @@
  * limitations under the License.
  */
 
+#include "CommandListener.h"
+
 #include <arpa/inet.h>
+#include <ctype.h>
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <math.h>
 #include <netinet/in.h>
-#include <string.h>
 #include <stdlib.h>
+#include <string.h>
 #include <sys/prctl.h>
 #include <sys/socket.h>
 #include <sys/types.h>
 
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
 #include <cutils/sockets.h>
+#include <log/log_properties.h>
 #include <private/android_filesystem_config.h>
 #include <sysutils/SocketClient.h>
 
-#include "CommandListener.h"
-#include "LogCommand.h"
+#include "LogPermissions.h"
 
-CommandListener::CommandListener(LogBuffer *buf, LogReader * /*reader*/,
-                                 LogListener * /*swl*/) :
-        FrameworkListener(getLogSocket()) {
-    // registerCmd(new ShutdownCmd(buf, writer, swl));
-    registerCmd(new ClearCmd(buf));
-    registerCmd(new GetBufSizeCmd(buf));
-    registerCmd(new SetBufSizeCmd(buf));
-    registerCmd(new GetBufSizeUsedCmd(buf));
-    registerCmd(new GetStatisticsCmd(buf));
-    registerCmd(new SetPruneListCmd(buf));
-    registerCmd(new GetPruneListCmd(buf));
-    registerCmd(new ReinitCmd());
-}
-
-CommandListener::ShutdownCmd::ShutdownCmd(LogReader *reader,
-                                          LogListener *swl) :
-        LogCommand("shutdown"),
-        mReader(*reader),
-        mSwl(*swl) {
-}
-
-int CommandListener::ShutdownCmd::runCommand(SocketClient * /*cli*/,
-                                             int /*argc*/,
-                                             char ** /*argv*/) {
-    mSwl.stopListener();
-    mReader.stopListener();
-    exit(0);
-}
-
-CommandListener::ClearCmd::ClearCmd(LogBuffer *buf) :
-        LogCommand("clear"),
-        mBuf(*buf) {
+CommandListener::CommandListener(LogBuffer* buf, LogTags* tags, PruneList* prune,
+                                 LogStatistics* stats)
+    : FrameworkListener(getLogSocket()), buf_(buf), tags_(tags), prune_(prune), stats_(stats) {
+    registerCmd(new ClearCmd(this));
+    registerCmd(new GetBufSizeCmd(this));
+    registerCmd(new SetBufSizeCmd(this));
+    registerCmd(new GetBufSizeReadableCmd(this));
+    registerCmd(new GetBufSizeUsedCmd(this));
+    registerCmd(new GetStatisticsCmd(this));
+    registerCmd(new SetPruneListCmd(this));
+    registerCmd(new GetPruneListCmd(this));
+    registerCmd(new GetEventTagCmd(this));
+    registerCmd(new ReinitCmd(this));
+    registerCmd(new ExitCmd(this));
 }
 
 static void setname() {
@@ -74,64 +65,58 @@
     }
 }
 
-int CommandListener::ClearCmd::runCommand(SocketClient *cli,
-                                         int argc, char **argv) {
+template <typename F>
+static int LogIdCommand(SocketClient* cli, int argc, char** argv, F&& function) {
     setname();
+    if (argc < 2) {
+        cli->sendMsg("Missing Argument");
+        return 0;
+    }
+
+    int log_id;
+    if (!android::base::ParseInt(argv[1], &log_id, static_cast<int>(LOG_ID_MAIN),
+                                 static_cast<int>(LOG_ID_KERNEL))) {
+        cli->sendMsg("Range Error");
+        return 0;
+    }
+
+    function(static_cast<log_id_t>(log_id));
+    return 0;
+}
+
+int CommandListener::ClearCmd::runCommand(SocketClient* cli, int argc, char** argv) {
     uid_t uid = cli->getUid();
     if (clientHasLogCredentials(cli)) {
         uid = AID_ROOT;
     }
 
-    if (argc < 2) {
-        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;
-    }
-
-    mBuf.clear((log_id_t) id, uid);
-    cli->sendMsg("success");
-    return 0;
+    return LogIdCommand(cli, argc, argv, [&](log_id_t id) {
+        cli->sendMsg(buf()->Clear(id, uid) ? "success" : "busy");
+    });
 }
 
-CommandListener::GetBufSizeCmd::GetBufSizeCmd(LogBuffer *buf) :
-        LogCommand("getLogSize"),
-        mBuf(*buf) {
+template <typename F>
+static int LogSizeCommand(SocketClient* cli, int argc, char** argv, F&& size_function) {
+    return LogIdCommand(cli, argc, argv, [&](log_id_t log_id) {
+        cli->sendMsg(std::to_string(size_function(log_id)).c_str());
+    });
 }
 
-int CommandListener::GetBufSizeCmd::runCommand(SocketClient *cli,
-                                         int argc, char **argv) {
-    setname();
-    if (argc < 2) {
-        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 = mBuf.getSize((log_id_t) id);
-    char buf[512];
-    snprintf(buf, sizeof(buf), "%lu", size);
-    cli->sendMsg(buf);
-    return 0;
+int CommandListener::GetBufSizeCmd::runCommand(SocketClient* cli, int argc, char** argv) {
+    return LogSizeCommand(cli, argc, argv, [this](log_id_t id) { return buf()->GetSize(id); });
 }
 
-CommandListener::SetBufSizeCmd::SetBufSizeCmd(LogBuffer *buf) :
-        LogCommand("setLogSize"),
-        mBuf(*buf) {
+int CommandListener::GetBufSizeReadableCmd::runCommand(SocketClient* cli, int argc, char** argv) {
+    return LogSizeCommand(cli, argc, argv,
+                          [this](log_id_t id) { return stats()->SizeReadable(id); });
 }
 
-int CommandListener::SetBufSizeCmd::runCommand(SocketClient *cli,
-                                         int argc, char **argv) {
-    setname();
+int CommandListener::GetBufSizeUsedCmd::runCommand(SocketClient* cli, int argc, char** argv) {
+    return LogSizeCommand(cli, argc, argv, [this](log_id_t id) { return stats()->Sizes(id); });
+}
+
+int CommandListener::SetBufSizeCmd::runCommand(SocketClient* cli, int argc,
+                                               char** argv) {
     if (!clientHasLogCredentials(cli)) {
         cli->sendMsg("Permission Denied");
         return 0;
@@ -141,74 +126,36 @@
         cli->sendMsg("Missing Argument");
         return 0;
     }
+    size_t size = atol(argv[2]);
 
-    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;
+    return LogIdCommand(cli, argc, argv, [&](log_id_t log_id) {
+        cli->sendMsg(buf()->SetSize(log_id, size) ? "success" : "busy");
+    });
 }
 
-CommandListener::GetBufSizeUsedCmd::GetBufSizeUsedCmd(LogBuffer *buf) :
-        LogCommand("getLogSizeUsed"),
-        mBuf(*buf) {
-}
+// This returns a string with a length prefix with the format <length>\n<data>\n\f.  The length
+// prefix includes the length of the prefix itself.
+static std::string PackageString(const std::string& str) {
+    size_t overhead_length = 3;  // \n \n \f.
 
-int CommandListener::GetBufSizeUsedCmd::runCommand(SocketClient *cli,
-                                         int argc, char **argv) {
-    setname();
-    if (argc < 2) {
-        cli->sendMsg("Missing Argument");
-        return 0;
+    // Number of digits needed to represent length(str + overhead_length).
+    size_t str_size_digits = 1 + static_cast<size_t>(log10(str.size() + overhead_length));
+    // Number of digits needed to represent the total size.
+    size_t total_size_digits =
+            1 + static_cast<size_t>(log10(str.size() + overhead_length + str_size_digits));
+
+    // If adding the size prefix causes a new digit to be required to represent the new total
+    // size, add it to the 'overhead_length'.  This can only happen once, since each new digit
+    // allows for 10x the previous size to be recorded.
+    if (total_size_digits != str_size_digits) {
+        overhead_length++;
     }
 
-    int id = atoi(argv[1]);
-    if ((id < LOG_ID_MIN) || (LOG_ID_MAX <= id)) {
-        cli->sendMsg("Range Error");
-        return 0;
-    }
-
-    unsigned long size = mBuf.getSizeUsed((log_id_t) id);
-    char buf[512];
-    snprintf(buf, sizeof(buf), "%lu", size);
-    cli->sendMsg(buf);
-    return 0;
+    size_t total_size = str.size() + overhead_length + str_size_digits;
+    return android::base::StringPrintf("%zu\n%s\n\f", total_size, str.c_str());
 }
 
-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) {
+int CommandListener::GetStatisticsCmd::runCommand(SocketClient* cli, int argc, char** argv) {
     setname();
     uid_t uid = cli->getUid();
     if (clientHasLogCredentials(cli)) {
@@ -216,9 +163,20 @@
     }
 
     unsigned int logMask = -1;
+    pid_t pid = 0;
     if (argc > 1) {
         logMask = 0;
         for (int i = 1; i < argc; ++i) {
+            static const char _pid[] = "pid=";
+            if (!strncmp(argv[i], _pid, sizeof(_pid) - 1)) {
+                pid = atol(argv[i] + sizeof(_pid) - 1);
+                if (pid == 0) {
+                    cli->sendMsg("PID Error");
+                    return 0;
+                }
+                continue;
+            }
+
             int id = atoi(argv[i]);
             if ((id < LOG_ID_MIN) || (LOG_ID_MAX <= id)) {
                 cli->sendMsg("Range Error");
@@ -228,87 +186,112 @@
         }
     }
 
-    char *buf = NULL;
-
-    mBuf.formatStatistics(&buf, uid, logMask);
-    if (!buf) {
-        cli->sendMsg("Failed");
-    } else {
-        package_string(&buf);
-        cli->sendMsg(buf);
-        free(buf);
-    }
+    cli->sendMsg(PackageString(stats()->Format(uid, pid, logMask)).c_str());
     return 0;
 }
 
-CommandListener::GetPruneListCmd::GetPruneListCmd(LogBuffer *buf) :
-        LogCommand("getPruneList"),
-        mBuf(*buf) {
-}
-
-int CommandListener::GetPruneListCmd::runCommand(SocketClient *cli,
-                                         int /*argc*/, char ** /*argv*/) {
+int CommandListener::GetPruneListCmd::runCommand(SocketClient* cli, int, char**) {
     setname();
-    char *buf = NULL;
-    mBuf.formatPrune(&buf);
-    if (!buf) {
-        cli->sendMsg("Failed");
-    } else {
-        package_string(&buf);
-        cli->sendMsg(buf);
-        free(buf);
-    }
+    cli->sendMsg(PackageString(prune()->Format()).c_str());
     return 0;
 }
 
-CommandListener::SetPruneListCmd::SetPruneListCmd(LogBuffer *buf) :
-        LogCommand("setPruneList"),
-        mBuf(*buf) {
-}
-
-int CommandListener::SetPruneListCmd::runCommand(SocketClient *cli,
-                                         int argc, char **argv) {
+int CommandListener::SetPruneListCmd::runCommand(SocketClient* cli, int argc, char** argv) {
     setname();
     if (!clientHasLogCredentials(cli)) {
         cli->sendMsg("Permission Denied");
         return 0;
     }
 
-    char *cp = NULL;
+    std::string str;
     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]);
+        if (str.length()) {
+            str += " ";
+        }
+        str += argv[i];
+    }
+
+    if (!prune()->Init(str.c_str())) {
+        cli->sendMsg("Invalid");
+        return 0;
+    }
+
+    cli->sendMsg("success");
+    return 0;
+}
+
+int CommandListener::GetEventTagCmd::runCommand(SocketClient* cli, int argc, char** argv) {
+    setname();
+    uid_t uid = cli->getUid();
+    if (clientHasLogCredentials(cli)) {
+        uid = AID_ROOT;
+    }
+
+    const char* name = nullptr;
+    const char* format = nullptr;
+    const char* id = nullptr;
+    for (int i = 1; i < argc; ++i) {
+        static const char _name[] = "name=";
+        if (!strncmp(argv[i], _name, strlen(_name))) {
+            name = argv[i] + strlen(_name);
+            continue;
+        }
+
+        static const char _format[] = "format=";
+        if (!strncmp(argv[i], _format, strlen(_format))) {
+            format = argv[i] + strlen(_format);
+            continue;
+        }
+
+        static const char _id[] = "id=";
+        if (!strncmp(argv[i], _id, strlen(_id))) {
+            id = argv[i] + strlen(_id);
+            continue;
         }
     }
 
-    int ret = mBuf.initPrune(cp);
-    free(cp);
-
-    if (ret) {
-        cli->sendMsg("Invalid");
+    if (id) {
+        if (format || name) {
+            cli->sendMsg("can not mix id= with either format= or name=");
+            return 0;
+        }
+        cli->sendMsg(PackageString(tags()->formatEntry(atoi(id), uid)).c_str());
         return 0;
     }
 
+    cli->sendMsg(PackageString(tags()->formatGetEventTag(uid, name, format)).c_str());
+
+    return 0;
+}
+
+int CommandListener::ReinitCmd::runCommand(SocketClient* cli, int, char**) {
+    setname();
+
+    LOG(INFO) << "logd reinit";
+    buf()->Init();
+    prune()->Init(nullptr);
+
+    // This only works on userdebug and eng devices to re-read the
+    // /data/misc/logd/event-log-tags file right after /data is mounted.
+    // The operation is near to boot and should only happen once.  There
+    // are races associated with its use since it can trigger a Rebuild
+    // of the file, but that is a can-not-happen since the file was not
+    // read yet.  More dangerous if called later, but if all is well it
+    // should just skip over everything and not write any new entries.
+    if (__android_log_is_debuggable()) {
+        tags()->ReadFileEventLogTags(tags()->debug_event_log_tags);
+    }
+
     cli->sendMsg("success");
 
     return 0;
 }
 
-CommandListener::ReinitCmd::ReinitCmd() : LogCommand("reinit") {
-}
-
-int CommandListener::ReinitCmd::runCommand(SocketClient *cli,
-                                         int /*argc*/, char ** /*argv*/) {
+int CommandListener::ExitCmd::runCommand(SocketClient* cli, int, char**) {
     setname();
 
-    reinit_signal_handler(SIGHUP);
-
     cli->sendMsg("success");
+    parent_->release(cli);
 
     return 0;
 }
@@ -318,9 +301,8 @@
     int sock = android_get_control_socket(socketName);
 
     if (sock < 0) {
-        sock = socket_local_server(socketName,
-                                   ANDROID_SOCKET_NAMESPACE_RESERVED,
-                                   SOCK_STREAM);
+        sock = socket_local_server(
+            socketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM);
     }
 
     return sock;
diff --git a/logd/CommandListener.h b/logd/CommandListener.h
index 3877675..8e12634 100644
--- a/logd/CommandListener.h
+++ b/logd/CommandListener.h
@@ -14,61 +14,56 @@
  * limitations under the License.
  */
 
-#ifndef _COMMANDLISTENER_H__
-#define _COMMANDLISTENER_H__
+#pragma once
 
+#include <sysutils/FrameworkCommand.h>
 #include <sysutils/FrameworkListener.h>
-#include "LogCommand.h"
-#include "LogBuffer.h"
-#include "LogReader.h"
-#include "LogListener.h"
 
-// See main.cpp for implementation
-void reinit_signal_handler(int /*signal*/);
+#include "LogBuffer.h"
+#include "LogListener.h"
+#include "LogStatistics.h"
+#include "LogTags.h"
+#include "PruneList.h"
 
 class CommandListener : public FrameworkListener {
-
-public:
-    CommandListener(LogBuffer *buf, LogReader *reader, LogListener *swl);
+  public:
+    CommandListener(LogBuffer* buf, LogTags* tags, PruneList* prune, LogStatistics* log_statistics);
     virtual ~CommandListener() {}
 
-private:
+  private:
     static int getLogSocket();
 
-    class ShutdownCmd : public LogCommand {
-        LogReader &mReader;
-        LogListener &mSwl;
+    LogBuffer* buf_;
+    LogTags* tags_;
+    PruneList* prune_;
+    LogStatistics* stats_;
 
-    public:
-        ShutdownCmd(LogReader *reader, LogListener *swl);
-        virtual ~ShutdownCmd() {}
-        int runCommand(SocketClient *c, int argc, char ** argv);
-    };
+#define LogCmd(name, command_string)                                \
+    class name##Cmd : public FrameworkCommand {                     \
+      public:                                                       \
+        explicit name##Cmd(CommandListener* parent)                 \
+            : FrameworkCommand(#command_string), parent_(parent) {} \
+        virtual ~name##Cmd() {}                                     \
+        int runCommand(SocketClient* c, int argc, char** argv);     \
+                                                                    \
+      private:                                                      \
+        LogBuffer* buf() const { return parent_->buf_; }            \
+        LogTags* tags() const { return parent_->tags_; }            \
+        PruneList* prune() const { return parent_->prune_; }        \
+        LogStatistics* stats() const { return parent_->stats_; }    \
+        CommandListener* parent_;                                   \
+    }
 
-#define LogBufferCmd(name)                                       \
-    class name##Cmd : public LogCommand {                        \
-        LogBuffer &mBuf;                                         \
-    public:                                                      \
-        name##Cmd(LogBuffer *buf);                               \
-        virtual ~name##Cmd() {}                                  \
-        int runCommand(SocketClient *c, int argc, char ** argv); \
-    };
-
-    LogBufferCmd(Clear)
-    LogBufferCmd(GetBufSize)
-    LogBufferCmd(SetBufSize)
-    LogBufferCmd(GetBufSizeUsed)
-    LogBufferCmd(GetStatistics)
-    LogBufferCmd(GetPruneList)
-    LogBufferCmd(SetPruneList)
-
-    class ReinitCmd : public LogCommand {
-    public:
-        ReinitCmd();
-        virtual ~ReinitCmd() {}
-        int runCommand(SocketClient *c, int argc, char ** argv);
-    };
-
+    LogCmd(Clear, clear);
+    LogCmd(GetBufSize, getLogSize);
+    LogCmd(SetBufSize, setLogSize);
+    LogCmd(GetBufSizeReadable, getLogSizeReadable);
+    LogCmd(GetBufSizeUsed, getLogSizeUsed);
+    LogCmd(GetStatistics, getStatistics);
+    LogCmd(GetPruneList, getPruneList);
+    LogCmd(SetPruneList, setPruneList);
+    LogCmd(GetEventTag, getEventTag);
+    LogCmd(Reinit, reinit);
+    LogCmd(Exit, EXIT);
+#undef LogCmd
 };
-
-#endif
diff --git a/logd/CompressionEngine.cpp b/logd/CompressionEngine.cpp
new file mode 100644
index 0000000..da2628c
--- /dev/null
+++ b/logd/CompressionEngine.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 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 "CompressionEngine.h"
+
+#include <limits>
+
+#include <android-base/logging.h>
+#include <zlib.h>
+#include <zstd.h>
+
+CompressionEngine& CompressionEngine::GetInstance() {
+    static CompressionEngine* engine = new ZstdCompressionEngine();
+    return *engine;
+}
+
+bool ZlibCompressionEngine::Compress(SerializedData& in, size_t data_length, SerializedData& out) {
+    z_stream strm;
+    strm.zalloc = Z_NULL;
+    strm.zfree = Z_NULL;
+    strm.opaque = Z_NULL;
+    int ret = deflateInit(&strm, Z_DEFAULT_COMPRESSION);
+    if (ret != Z_OK) {
+        LOG(FATAL) << "deflateInit() failed";
+    }
+
+    CHECK_LE(data_length, in.size());
+    CHECK_LE(in.size(), std::numeric_limits<uint32_t>::max());
+    uint32_t deflate_bound = deflateBound(&strm, in.size());
+
+    out.Resize(deflate_bound);
+
+    strm.avail_in = data_length;
+    strm.next_in = in.data();
+    strm.avail_out = out.size();
+    strm.next_out = out.data();
+    ret = deflate(&strm, Z_FINISH);
+    CHECK_EQ(ret, Z_STREAM_END);
+
+    uint32_t compressed_size = strm.total_out;
+    deflateEnd(&strm);
+
+    out.Resize(compressed_size);
+
+    return true;
+}
+
+bool ZlibCompressionEngine::Decompress(SerializedData& in, SerializedData& out) {
+    z_stream strm;
+    strm.zalloc = Z_NULL;
+    strm.zfree = Z_NULL;
+    strm.opaque = Z_NULL;
+    strm.avail_in = in.size();
+    strm.next_in = in.data();
+    strm.avail_out = out.size();
+    strm.next_out = out.data();
+
+    inflateInit(&strm);
+    int ret = inflate(&strm, Z_NO_FLUSH);
+
+    CHECK_EQ(strm.avail_in, 0U);
+    CHECK_EQ(strm.avail_out, 0U);
+    CHECK_EQ(ret, Z_STREAM_END);
+    inflateEnd(&strm);
+
+    return true;
+}
+
+bool ZstdCompressionEngine::Compress(SerializedData& in, size_t data_length, SerializedData& out) {
+    CHECK_LE(data_length, in.size());
+
+    size_t compress_bound = ZSTD_compressBound(data_length);
+    out.Resize(compress_bound);
+
+    size_t out_size = ZSTD_compress(out.data(), out.size(), in.data(), data_length, 1);
+    if (ZSTD_isError(out_size)) {
+        LOG(FATAL) << "ZSTD_compress failed: " << ZSTD_getErrorName(out_size);
+    }
+    out.Resize(out_size);
+
+    return true;
+}
+
+bool ZstdCompressionEngine::Decompress(SerializedData& in, SerializedData& out) {
+    size_t result = ZSTD_decompress(out.data(), out.size(), in.data(), in.size());
+    if (ZSTD_isError(result)) {
+        LOG(FATAL) << "ZSTD_decompress failed: " << ZSTD_getErrorName(result);
+    }
+    CHECK_EQ(result, out.size());
+    return true;
+}
diff --git a/logd/CompressionEngine.h b/logd/CompressionEngine.h
new file mode 100644
index 0000000..0f760ed
--- /dev/null
+++ b/logd/CompressionEngine.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include "SerializedData.h"
+
+class CompressionEngine {
+  public:
+    static CompressionEngine& GetInstance();
+
+    virtual ~CompressionEngine(){};
+
+    virtual bool Compress(SerializedData& in, size_t data_length, SerializedData& out) = 0;
+    // Decompress the contents of `in` into `out`.  `out.size()` must be set to the decompressed
+    // size of the contents.
+    virtual bool Decompress(SerializedData& in, SerializedData& out) = 0;
+};
+
+class ZlibCompressionEngine : public CompressionEngine {
+  public:
+    bool Compress(SerializedData& in, size_t data_length, SerializedData& out) override;
+    bool Decompress(SerializedData& in, SerializedData& out) override;
+};
+
+class ZstdCompressionEngine : public CompressionEngine {
+  public:
+    bool Compress(SerializedData& in, size_t data_length, SerializedData& out) override;
+    bool Decompress(SerializedData& in, SerializedData& out) override;
+};
\ No newline at end of file
diff --git a/logd/FlushCommand.cpp b/logd/FlushCommand.cpp
deleted file mode 100644
index d584925..0000000
--- a/logd/FlushCommand.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2012-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 <stdlib.h>
-
-#include "FlushCommand.h"
-#include "LogBufferElement.h"
-#include "LogCommand.h"
-#include "LogReader.h"
-#include "LogTimes.h"
-
-FlushCommand::FlushCommand(LogReader &reader,
-                           bool nonBlock,
-                           unsigned long tail,
-                           unsigned int logMask,
-                           pid_t pid,
-                           uint64_t start) :
-        mReader(reader),
-        mNonBlock(nonBlock),
-        mTail(tail),
-        mLogMask(logMask),
-        mPid(pid),
-        mStart(start) {
-}
-
-// runSocketCommand is called once for every open client on the
-// log reader socket. Here we manage and associated the reader
-// client tracking and log region locks LastLogTimes list of
-// LogTimeEntrys, and spawn a transitory per-client thread to
-// work at filing data to the  socket.
-//
-// global LogTimeEntry::lock() is used to protect access,
-// reference counts are used to ensure that individual
-// LogTimeEntry lifetime is managed when not protected.
-void FlushCommand::runSocketCommand(SocketClient *client) {
-    LogTimeEntry *entry = NULL;
-    LastLogTimes &times = mReader.logbuf().mTimes;
-
-    LogTimeEntry::lock();
-    LastLogTimes::iterator it = times.begin();
-    while(it != times.end()) {
-        entry = (*it);
-        if (entry->mClient == client) {
-            entry->triggerReader_Locked();
-            if (entry->runningReader_Locked()) {
-                LogTimeEntry::unlock();
-                return;
-            }
-            entry->incRef_Locked();
-            break;
-        }
-        it++;
-    }
-
-    if (it == times.end()) {
-        // Create LogTimeEntry in notifyNewLog() ?
-        if (mTail == (unsigned long) -1) {
-            LogTimeEntry::unlock();
-            return;
-        }
-        entry = new LogTimeEntry(mReader, client, mNonBlock, mTail, mLogMask, mPid, mStart);
-        times.push_back(entry);
-    }
-
-    client->incRef();
-
-    // release client and entry reference counts once done
-    entry->startReader_Locked();
-    LogTimeEntry::unlock();
-}
-
-bool FlushCommand::hasReadLogs(SocketClient *client) {
-    return clientHasLogCredentials(client);
-}
diff --git a/logd/FlushCommand.h b/logd/FlushCommand.h
deleted file mode 100644
index 61c6858..0000000
--- a/logd/FlushCommand.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.
- */
-#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 {
-    LogReader &mReader;
-    bool mNonBlock;
-    unsigned long mTail;
-    unsigned int mLogMask;
-    pid_t mPid;
-    uint64_t mStart;
-
-public:
-    FlushCommand(LogReader &mReader,
-                 bool nonBlock = false,
-                 unsigned long tail = -1,
-                 unsigned int logMask = -1,
-                 pid_t pid = 0,
-                 uint64_t start = 1);
-    virtual void runSocketCommand(SocketClient *client);
-
-    static bool hasReadLogs(SocketClient *client);
-};
-
-#endif
diff --git a/logd/LogAudit.cpp b/logd/LogAudit.cpp
index 4b3547c..0e17476 100644
--- a/logd/LogAudit.cpp
+++ b/logd/LogAudit.cpp
@@ -14,42 +14,70 @@
  * limitations under the License.
  */
 
+#include "LogAudit.h"
+
 #include <ctype.h>
 #include <endian.h>
 #include <errno.h>
 #include <limits.h>
 #include <stdarg.h>
+#include <stdint.h>
 #include <stdlib.h>
+#include <string.h>
 #include <sys/prctl.h>
 #include <sys/uio.h>
 #include <syslog.h>
 
+#include <fstream>
+#include <sstream>
+
+#include <android-base/macros.h>
+#include <android-base/properties.h>
 #include <private/android_filesystem_config.h>
 #include <private/android_logger.h>
 
-#include "libaudit.h"
-#include "LogAudit.h"
 #include "LogKlog.h"
+#include "LogUtils.h"
+#include "libaudit.h"
 
-#define KMSG_PRIORITY(PRI)                          \
-    '<',                                            \
-    '0' + LOG_MAKEPRI(LOG_AUTH, LOG_PRI(PRI)) / 10, \
-    '0' + LOG_MAKEPRI(LOG_AUTH, LOG_PRI(PRI)) % 10, \
-    '>'
+using android::base::GetBoolProperty;
 
-LogAudit::LogAudit(LogBuffer *buf, LogReader *reader, int fdDmesg) :
-        SocketListener(getLogSocket(), false),
-        logbuf(buf),
-        reader(reader),
-        fdDmesg(fdDmesg),
-        initialized(false) {
+#define KMSG_PRIORITY(PRI)                               \
+    '<', '0' + LOG_MAKEPRI(LOG_AUTH, LOG_PRI(PRI)) / 10, \
+        '0' + LOG_MAKEPRI(LOG_AUTH, LOG_PRI(PRI)) % 10, '>'
+
+LogAudit::LogAudit(LogBuffer* buf, int fdDmesg, LogStatistics* stats)
+    : SocketListener(getLogSocket(), false),
+      logbuf(buf),
+      fdDmesg(fdDmesg),
+      main(GetBoolProperty("ro.logd.auditd.main", true)),
+      events(GetBoolProperty("ro.logd.auditd.events", true)),
+      initialized(false),
+      stats_(stats) {
     static const char auditd_message[] = { KMSG_PRIORITY(LOG_INFO),
-        'l', 'o', 'g', 'd', '.', 'a', 'u', 'd', 'i', 't', 'd', ':',
-        ' ', 's', 't', 'a', 'r', 't', '\n' };
+                                           'l',
+                                           'o',
+                                           'g',
+                                           'd',
+                                           '.',
+                                           'a',
+                                           'u',
+                                           'd',
+                                           'i',
+                                           't',
+                                           'd',
+                                           ':',
+                                           ' ',
+                                           's',
+                                           't',
+                                           'a',
+                                           'r',
+                                           't',
+                                           '\n' };
     write(fdDmesg, auditd_message, sizeof(auditd_message));
 }
 
-bool LogAudit::onDataAvailable(SocketClient *cli) {
+bool LogAudit::onDataAvailable(SocketClient* cli) {
     if (!initialized) {
         prctl(PR_SET_NAME, "logd.auditd");
         initialized = true;
@@ -66,20 +94,91 @@
         return false;
     }
 
-    logPrint("type=%d %.*s",
-        rep.nlh.nlmsg_type, rep.nlh.nlmsg_len, rep.data);
+    logPrint("type=%d %.*s", rep.nlh.nlmsg_type, rep.nlh.nlmsg_len, rep.data);
 
     return true;
 }
 
-int LogAudit::logPrint(const char *fmt, ...) {
-    if (fmt == NULL) {
+static inline bool hasMetadata(char* str, int str_len) {
+    // need to check and see if str already contains bug metadata from
+    // possibility of stuttering if log audit crashes and then reloads kernel
+    // messages. Kernel denials that contain metadata will either end in
+    // "b/[0-9]+$" or "b/[0-9]+  duplicate messages suppressed$" which will put
+    // a '/' character at either 9 or 39 indices away from the end of the str.
+    return str_len >= 39 &&
+           (str[str_len - 9] == '/' || str[str_len - 39] == '/');
+}
+
+std::map<std::string, std::string> LogAudit::populateDenialMap() {
+    std::ifstream bug_file("/vendor/etc/selinux/selinux_denial_metadata");
+    std::string line;
+    // allocate a map for the static map pointer in auditParse to keep track of,
+    // this function only runs once
+    std::map<std::string, std::string> denial_to_bug;
+    if (bug_file.good()) {
+        std::string scontext;
+        std::string tcontext;
+        std::string tclass;
+        std::string bug_num;
+        while (std::getline(bug_file, line)) {
+            std::stringstream split_line(line);
+            split_line >> scontext >> tcontext >> tclass >> bug_num;
+            denial_to_bug.emplace(scontext + tcontext + tclass, bug_num);
+        }
+    }
+    return denial_to_bug;
+}
+
+std::string LogAudit::denialParse(const std::string& denial, char terminator,
+                                  const std::string& search_term) {
+    size_t start_index = denial.find(search_term);
+    if (start_index != std::string::npos) {
+        start_index += search_term.length();
+        return denial.substr(
+            start_index, denial.find(terminator, start_index) - start_index);
+    }
+    return "";
+}
+
+void LogAudit::auditParse(const std::string& string, uid_t uid,
+                          std::string* bug_num) {
+    static std::map<std::string, std::string> denial_to_bug =
+        populateDenialMap();
+    std::string scontext = denialParse(string, ':', "scontext=u:object_r:");
+    std::string tcontext = denialParse(string, ':', "tcontext=u:object_r:");
+    std::string tclass = denialParse(string, ' ', "tclass=");
+    if (scontext.empty()) {
+        scontext = denialParse(string, ':', "scontext=u:r:");
+    }
+    if (tcontext.empty()) {
+        tcontext = denialParse(string, ':', "tcontext=u:r:");
+    }
+    auto search = denial_to_bug.find(scontext + tcontext + tclass);
+    if (search != denial_to_bug.end()) {
+        bug_num->assign(" " + search->second);
+    } else {
+        bug_num->assign("");
+    }
+
+    // Ensure the uid name is not null before passing it to the bug string.
+    if (uid >= AID_APP_START && uid <= AID_APP_END) {
+        char* uidname = android::uidToName(uid);
+        if (uidname) {
+            bug_num->append(" app=");
+            bug_num->append(uidname);
+            free(uidname);
+        }
+    }
+}
+
+int LogAudit::logPrint(const char* fmt, ...) {
+    if (fmt == nullptr) {
         return -EINVAL;
     }
 
     va_list args;
 
-    char *str = NULL;
+    char* str = nullptr;
     va_start(args, fmt);
     int rc = vasprintf(&str, fmt, args);
     va_end(args);
@@ -87,56 +186,22 @@
     if (rc < 0) {
         return rc;
     }
+    char* cp;
+    // Work around kernels missing
+    // https://github.com/torvalds/linux/commit/b8f89caafeb55fba75b74bea25adc4e4cd91be67
+    // Such kernels improperly add newlines inside audit messages.
+    while ((cp = strchr(str, '\n'))) {
+        *cp = ' ';
+    }
 
-    char *cp;
     while ((cp = strstr(str, "  "))) {
         memmove(cp, cp + 1, strlen(cp + 1) + 1);
     }
-
-    bool info = strstr(str, " permissive=1") || strstr(str, " policy loaded ");
-    if ((fdDmesg >= 0) && initialized) {
-        struct iovec iov[3];
-        static const char log_info[] = { KMSG_PRIORITY(LOG_INFO) };
-        static const char log_warning[] = { KMSG_PRIORITY(LOG_WARNING) };
-
-        iov[0].iov_base = info ? const_cast<char *>(log_info)
-                               : const_cast<char *>(log_warning);
-        iov[0].iov_len = info ? sizeof(log_info) : sizeof(log_warning);
-        iov[1].iov_base = str;
-        iov[1].iov_len = strlen(str);
-        iov[2].iov_base = const_cast<char *>("\n");
-        iov[2].iov_len = 1;
-
-        writev(fdDmesg, iov, sizeof(iov) / sizeof(iov[0]));
-    }
-
     pid_t pid = getpid();
     pid_t tid = gettid();
     uid_t uid = AID_LOGD;
-    log_time now;
-
-    static const char audit_str[] = " audit(";
-    char *timeptr = strstr(str, audit_str);
-    if (timeptr
-            && ((cp = now.strptime(timeptr + sizeof(audit_str) - 1, "%s.%q")))
-            && (*cp == ':')) {
-        memcpy(timeptr + sizeof(audit_str) - 1, "0.0", 3);
-        memmove(timeptr + sizeof(audit_str) - 1 + 3, cp, strlen(cp) + 1);
-        //
-        // We are either in 1970ish (MONOTONIC) or 2015+ish (REALTIME) so to
-        // differentiate without prejudice, we use 1980 to delineate, earlier
-        // is monotonic, later is real.
-        //
-#       define EPOCH_PLUS_10_YEARS (10 * 1461 / 4 * 24 * 60 * 60)
-        if (now.tv_sec < EPOCH_PLUS_10_YEARS) {
-            LogKlog::convertMonotonicToReal(now);
-        }
-    } else {
-        now.strptime("", ""); // side effect of setting CLOCK_REALTIME
-    }
-
     static const char pid_str[] = " pid=";
-    char *pidptr = strstr(str, pid_str);
+    char* pidptr = strstr(str, pid_str);
     if (pidptr && isdigit(pidptr[sizeof(pid_str) - 1])) {
         cp = pidptr + sizeof(pid_str) - 1;
         pid = 0;
@@ -145,44 +210,86 @@
             ++cp;
         }
         tid = pid;
-        logbuf->lock();
-        uid = logbuf->pidToUid(pid);
-        logbuf->unlock();
+        uid = stats_->PidToUid(pid);
         memmove(pidptr, cp, strlen(cp) + 1);
     }
 
+    bool info = strstr(str, " permissive=1") || strstr(str, " policy loaded ");
+    static std::string denial_metadata;
+    if ((fdDmesg >= 0) && initialized) {
+        struct iovec iov[4];
+        static const char log_info[] = { KMSG_PRIORITY(LOG_INFO) };
+        static const char log_warning[] = { KMSG_PRIORITY(LOG_WARNING) };
+        static const char newline[] = "\n";
+
+        auditParse(str, uid, &denial_metadata);
+        iov[0].iov_base = info ? const_cast<char*>(log_info) : const_cast<char*>(log_warning);
+        iov[0].iov_len = info ? sizeof(log_info) : sizeof(log_warning);
+        iov[1].iov_base = str;
+        iov[1].iov_len = strlen(str);
+        iov[2].iov_base = const_cast<char*>(denial_metadata.c_str());
+        iov[2].iov_len = denial_metadata.length();
+        iov[3].iov_base = const_cast<char*>(newline);
+        iov[3].iov_len = strlen(newline);
+
+        writev(fdDmesg, iov, arraysize(iov));
+    }
+
+    if (!main && !events) {
+        free(str);
+        return 0;
+    }
+
+    log_time now(log_time::EPOCH);
+
+    static const char audit_str[] = " audit(";
+    char* timeptr = strstr(str, audit_str);
+    if (timeptr && ((cp = now.strptime(timeptr + sizeof(audit_str) - 1, "%s.%q"))) &&
+        (*cp == ':')) {
+        memcpy(timeptr + sizeof(audit_str) - 1, "0.0", 3);
+        memmove(timeptr + sizeof(audit_str) - 1 + 3, cp, strlen(cp) + 1);
+    } else {
+        now = log_time(CLOCK_REALTIME);
+    }
+
     // log to events
 
-    size_t l = strlen(str);
-    size_t n = l + sizeof(android_log_event_string_t);
+    size_t str_len = strnlen(str, LOGGER_ENTRY_MAX_PAYLOAD);
+    if (((fdDmesg < 0) || !initialized) && !hasMetadata(str, str_len))
+        auditParse(str, uid, &denial_metadata);
+    str_len = (str_len + denial_metadata.length() <= LOGGER_ENTRY_MAX_PAYLOAD)
+                  ? str_len + denial_metadata.length()
+                  : LOGGER_ENTRY_MAX_PAYLOAD;
+    size_t message_len = str_len + sizeof(android_log_event_string_t);
 
-    bool notify = false;
+    unsigned int notify = 0;
 
-    android_log_event_string_t *event = static_cast<android_log_event_string_t *>(malloc(n));
-    if (!event) {
-        rc = -ENOMEM;
-    } else {
+    if (events) {  // begin scope for event buffer
+        uint32_t buffer[(message_len + sizeof(uint32_t) - 1) / sizeof(uint32_t)];
+
+        android_log_event_string_t* event =
+            reinterpret_cast<android_log_event_string_t*>(buffer);
         event->header.tag = htole32(AUDITD_LOG_TAG);
         event->type = EVENT_TYPE_STRING;
-        event->length = htole32(l);
-        memcpy(event->data, str, l);
+        event->length = htole32(str_len);
+        memcpy(event->data, str, str_len - denial_metadata.length());
+        memcpy(event->data + str_len - denial_metadata.length(),
+               denial_metadata.c_str(), denial_metadata.length());
 
-        rc = logbuf->log(LOG_ID_EVENTS, now, uid, pid, tid,
-                         reinterpret_cast<char *>(event),
-                         (n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX);
-        free(event);
-
+        rc = logbuf->Log(LOG_ID_EVENTS, now, uid, pid, tid, reinterpret_cast<char*>(event),
+                         (message_len <= UINT16_MAX) ? (uint16_t)message_len : UINT16_MAX);
         if (rc >= 0) {
-            notify = true;
+            notify |= 1 << LOG_ID_EVENTS;
         }
+        // end scope for event buffer
     }
 
     // log to main
 
     static const char comm_str[] = " comm=\"";
-    const char *comm = strstr(str, comm_str);
-    const char *estr = str + strlen(str);
-    char *commfree = NULL;
+    const char* comm = strstr(str, comm_str);
+    const char* estr = str + strlen(str);
+    const char* commfree = nullptr;
     if (comm) {
         estr = comm;
         comm += sizeof(comm_str) - 1;
@@ -190,66 +297,70 @@
         pid = tid;
         comm = "auditd";
     } else {
-        logbuf->lock();
-        comm = commfree = logbuf->pidToName(pid);
-        logbuf->unlock();
+        comm = commfree = stats_->PidToName(pid);
         if (!comm) {
             comm = "unknown";
         }
     }
 
-    const char *ecomm = strchr(comm, '"');
+    const char* ecomm = strchr(comm, '"');
     if (ecomm) {
         ++ecomm;
-        l = ecomm - comm;
+        str_len = ecomm - comm;
     } else {
-        l = strlen(comm) + 1;
+        str_len = strlen(comm) + 1;
         ecomm = "";
     }
-    n = (estr - str) + strlen(ecomm) + l + 2;
+    size_t prefix_len = estr - str;
+    if (prefix_len > LOGGER_ENTRY_MAX_PAYLOAD) {
+        prefix_len = LOGGER_ENTRY_MAX_PAYLOAD;
+    }
+    size_t suffix_len = strnlen(ecomm, LOGGER_ENTRY_MAX_PAYLOAD - prefix_len);
+    message_len =
+        str_len + prefix_len + suffix_len + denial_metadata.length() + 2;
 
-    char *newstr = static_cast<char *>(malloc(n));
-    if (!newstr) {
-        rc = -ENOMEM;
-    } else {
+    if (main) {  // begin scope for main buffer
+        char newstr[message_len];
+
         *newstr = info ? ANDROID_LOG_INFO : ANDROID_LOG_WARN;
-        strlcpy(newstr + 1, comm, l);
-        strncpy(newstr + 1 + l, str, estr - str);
-        strcpy(newstr + 1 + l + (estr - str), ecomm);
+        strlcpy(newstr + 1, comm, str_len);
+        strncpy(newstr + 1 + str_len, str, prefix_len);
+        strncpy(newstr + 1 + str_len + prefix_len, ecomm, suffix_len);
+        strncpy(newstr + 1 + str_len + prefix_len + suffix_len,
+                denial_metadata.c_str(), denial_metadata.length());
 
-        rc = logbuf->log(LOG_ID_MAIN, now, uid, pid, tid, newstr,
-                         (n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX);
-        free(newstr);
+        rc = logbuf->Log(LOG_ID_MAIN, now, uid, pid, tid, newstr,
+                         (message_len <= UINT16_MAX) ? (uint16_t)message_len : UINT16_MAX);
 
         if (rc >= 0) {
-            notify = true;
+            notify |= 1 << LOG_ID_MAIN;
         }
+        // end scope for main buffer
     }
 
-    free(commfree);
+    free(const_cast<char*>(commfree));
     free(str);
 
     if (notify) {
-        reader->notifyNewLog();
         if (rc < 0) {
-            rc = n;
+            rc = message_len;
         }
     }
 
     return rc;
 }
 
-int LogAudit::log(char *buf) {
-    char *audit = strstr(buf, " audit(");
-    if (!audit) {
+int LogAudit::log(char* buf, size_t len) {
+    char* audit = strstr(buf, " audit(");
+    if (!audit || (audit >= &buf[len])) {
         return 0;
     }
 
     *audit = '\0';
 
     int rc;
-    char *type = strstr(buf, "type=");
-    if (type) {
+    char* type = strstr(buf, "type=");
+    if (type && (type < &buf[len])) {
         rc = logPrint("%s %s", type, audit + 1);
     } else {
         rc = logPrint("%s", audit + 1);
diff --git a/logd/LogAudit.h b/logd/LogAudit.h
index f977be9..181920e 100644
--- a/logd/LogAudit.h
+++ b/logd/LogAudit.h
@@ -14,29 +14,37 @@
  * limitations under the License.
  */
 
-#ifndef _LOGD_LOG_AUDIT_H__
-#define _LOGD_LOG_AUDIT_H__
+#pragma once
+
+#include <map>
 
 #include <sysutils/SocketListener.h>
-#include "LogReader.h"
+
+#include "LogBuffer.h"
+#include "LogStatistics.h"
 
 class LogAudit : public SocketListener {
-    LogBuffer *logbuf;
-    LogReader *reader;
-    int fdDmesg;
+    LogBuffer* logbuf;
+    int fdDmesg;  // fdDmesg >= 0 is functionally bool dmesg
+    bool main;
+    bool events;
     bool initialized;
 
-public:
-    LogAudit(LogBuffer *buf, LogReader *reader, int fdDmesg);
-    int log(char *buf);
+  public:
+    LogAudit(LogBuffer* buf, int fdDmesg, LogStatistics* stats);
+    int log(char* buf, size_t len);
 
-protected:
-    virtual bool onDataAvailable(SocketClient *cli);
+  protected:
+    virtual bool onDataAvailable(SocketClient* cli);
 
-private:
+  private:
     static int getLogSocket();
-    int logPrint(const char *fmt, ...)
-        __attribute__ ((__format__ (__printf__, 2, 3)));
-};
+    std::map<std::string, std::string> populateDenialMap();
+    std::string denialParse(const std::string& denial, char terminator,
+                            const std::string& search_term);
+    void auditParse(const std::string& string, uid_t uid, std::string* bug_num);
+    int logPrint(const char* fmt, ...)
+        __attribute__((__format__(__printf__, 2, 3)));
 
-#endif
+    LogStatistics* stats_;
+};
diff --git a/logd/LogBuffer.cpp b/logd/LogBuffer.cpp
deleted file mode 100644
index 0f5071b..0000000
--- a/logd/LogBuffer.cpp
+++ /dev/null
@@ -1,679 +0,0 @@
-/*
- * Copyright (C) 2012-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 <errno.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/user.h>
-#include <time.h>
-#include <unistd.h>
-
-#include <unordered_map>
-
-#include <cutils/properties.h>
-#include <log/logger.h>
-
-#include "LogBuffer.h"
-#include "LogReader.h"
-
-// Default
-#define LOG_BUFFER_SIZE (256 * 1024) // Tuned on a per-platform basis here?
-#define log_buffer_size(id) mMaxSize[id]
-#define LOG_BUFFER_MIN_SIZE (64 * 1024UL)
-#define LOG_BUFFER_MAX_SIZE (256 * 1024 * 1024UL)
-
-static bool valid_size(unsigned long value) {
-    if ((value < LOG_BUFFER_MIN_SIZE) || (LOG_BUFFER_MAX_SIZE < value)) {
-        return false;
-    }
-
-    long pages = sysconf(_SC_PHYS_PAGES);
-    if (pages < 1) {
-        return true;
-    }
-
-    long pagesize = sysconf(_SC_PAGESIZE);
-    if (pagesize <= 1) {
-        pagesize = PAGE_SIZE;
-    }
-
-    // maximum memory impact a somewhat arbitrary ~3%
-    pages = (pages + 31) / 32;
-    unsigned long maximum = pages * pagesize;
-
-    if ((maximum < LOG_BUFFER_MIN_SIZE) || (LOG_BUFFER_MAX_SIZE < maximum)) {
-        return true;
-    }
-
-    return value <= maximum;
-}
-
-static unsigned long property_get_size(const char *key) {
-    char property[PROPERTY_VALUE_MAX];
-    property_get(key, property, "");
-
-    char *cp;
-    unsigned long value = strtoul(property, &cp, 10);
-
-    switch(*cp) {
-    case 'm':
-    case 'M':
-        value *= 1024;
-    /* FALLTHRU */
-    case 'k':
-    case 'K':
-        value *= 1024;
-    /* FALLTHRU */
-    case '\0':
-        break;
-
-    default:
-        value = 0;
-    }
-
-    if (!valid_size(value)) {
-        value = 0;
-    }
-
-    return value;
-}
-
-void LogBuffer::init() {
-    static const char global_tuneable[] = "persist.logd.size"; // Settings App
-    static const char global_default[] = "ro.logd.size";       // BoardConfig.mk
-
-    unsigned long default_size = property_get_size(global_tuneable);
-    if (!default_size) {
-        default_size = property_get_size(global_default);
-    }
-
-    log_id_for_each(i) {
-        char key[PROP_NAME_MAX];
-
-        snprintf(key, sizeof(key), "%s.%s",
-                 global_tuneable, android_log_id_to_name(i));
-        unsigned long property_size = property_get_size(key);
-
-        if (!property_size) {
-            snprintf(key, sizeof(key), "%s.%s",
-                     global_default, android_log_id_to_name(i));
-            property_size = property_get_size(key);
-        }
-
-        if (!property_size) {
-            property_size = default_size;
-        }
-
-        if (!property_size) {
-            property_size = LOG_BUFFER_SIZE;
-        }
-
-        if (setSize(i, property_size)) {
-            setSize(i, LOG_BUFFER_MIN_SIZE);
-        }
-    }
-}
-
-LogBuffer::LogBuffer(LastLogTimes *times) : mTimes(*times) {
-    pthread_mutex_init(&mLogElementsLock, NULL);
-
-    init();
-}
-
-int LogBuffer::log(log_id_t log_id, log_time realtime,
-                   uid_t uid, pid_t pid, pid_t tid,
-                   const char *msg, unsigned short len) {
-    if ((log_id >= LOG_ID_MAX) || (log_id < 0)) {
-        return -EINVAL;
-    }
-
-    LogBufferElement *elem = new LogBufferElement(log_id, realtime,
-                                                  uid, pid, tid, msg, len);
-    int prio = ANDROID_LOG_INFO;
-    const char *tag = NULL;
-    if (log_id == LOG_ID_EVENTS) {
-        tag = android::tagToName(elem->getTag());
-    } else {
-        prio = *msg;
-        tag = msg + 1;
-    }
-    if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
-        // Log traffic received to total
-        pthread_mutex_lock(&mLogElementsLock);
-        stats.add(elem);
-        stats.subtract(elem);
-        pthread_mutex_unlock(&mLogElementsLock);
-        delete elem;
-        return -EACCES;
-    }
-
-    pthread_mutex_lock(&mLogElementsLock);
-
-    // Insert elements in time sorted order if possible
-    //  NB: if end is region locked, place element at end of list
-    LogBufferElementCollection::iterator it = mLogElements.end();
-    LogBufferElementCollection::iterator last = it;
-    while (last != mLogElements.begin()) {
-        --it;
-        if ((*it)->getRealTime() <= realtime) {
-            break;
-        }
-        last = it;
-    }
-
-    if (last == mLogElements.end()) {
-        mLogElements.push_back(elem);
-    } else {
-        uint64_t end = 1;
-        bool end_set = false;
-        bool end_always = false;
-
-        LogTimeEntry::lock();
-
-        LastLogTimes::iterator t = mTimes.begin();
-        while(t != mTimes.end()) {
-            LogTimeEntry *entry = (*t);
-            if (entry->owned_Locked()) {
-                if (!entry->mNonBlock) {
-                    end_always = true;
-                    break;
-                }
-                if (!end_set || (end <= entry->mEnd)) {
-                    end = entry->mEnd;
-                    end_set = true;
-                }
-            }
-            t++;
-        }
-
-        if (end_always
-                || (end_set && (end >= (*last)->getSequence()))) {
-            mLogElements.push_back(elem);
-        } else {
-            mLogElements.insert(last,elem);
-        }
-
-        LogTimeEntry::unlock();
-    }
-
-    stats.add(elem);
-    maybePrune(log_id);
-    pthread_mutex_unlock(&mLogElementsLock);
-
-    return len;
-}
-
-// If we're using more than 256K of memory for log entries, prune
-// at least 10% of the log entries.
-//
-// mLogElementsLock must be held when this function is called.
-void LogBuffer::maybePrune(log_id_t 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) {
-            pruneRows = elements;
-        }
-        prune(id, pruneRows);
-    }
-}
-
-LogBufferElementCollection::iterator LogBuffer::erase(LogBufferElementCollection::iterator it) {
-    LogBufferElement *e = *it;
-
-    it = mLogElements.erase(it);
-    stats.subtract(e);
-    delete e;
-
-    return it;
-}
-
-// Define a temporary mechanism to report the last LogBufferElement pointer
-// for the specified uid, pid and tid. Used below to help merge-sort when
-// pruning for worst UID.
-class LogBufferElementKey {
-    const union {
-        struct {
-            uint16_t uid;
-            uint16_t pid;
-            uint16_t tid;
-            uint16_t padding;
-        } __packed;
-        uint64_t value;
-    } __packed;
-
-public:
-    LogBufferElementKey(uid_t u, pid_t p, pid_t t):uid(u),pid(p),tid(t),padding(0) { }
-    LogBufferElementKey(uint64_t k):value(k) { }
-
-    uint64_t getKey() { return value; }
-};
-
-class LogBufferElementLast {
-
-    typedef std::unordered_map<uint64_t, LogBufferElement *> LogBufferElementMap;
-    LogBufferElementMap map;
-
-public:
-
-    bool merge(LogBufferElement *e, unsigned short dropped) {
-        LogBufferElementKey key(e->getUid(), e->getPid(), e->getTid());
-        LogBufferElementMap::iterator it = map.find(key.getKey());
-        if (it != map.end()) {
-            LogBufferElement *l = it->second;
-            unsigned short d = l->getDropped();
-            if ((dropped + d) > USHRT_MAX) {
-                map.erase(it);
-            } else {
-                l->setDropped(dropped + d);
-                return true;
-            }
-        }
-        return false;
-    }
-
-    void add(LogBufferElement *e) {
-        LogBufferElementKey key(e->getUid(), e->getPid(), e->getTid());
-        map[key.getKey()] = e;
-    }
-
-    inline void clear() {
-        map.clear();
-    }
-
-    void clear(LogBufferElement *e) {
-        uint64_t current = e->getRealTime().nsec()
-                         - (EXPIRE_RATELIMIT * NS_PER_SEC);
-        for(LogBufferElementMap::iterator it = map.begin(); it != map.end();) {
-            LogBufferElement *l = it->second;
-            if ((l->getDropped() >= EXPIRE_THRESHOLD)
-                    && (current > l->getRealTime().nsec())) {
-                it = map.erase(it);
-            } else {
-                ++it;
-            }
-        }
-    }
-
-};
-
-// prune "pruneRows" of type "id" from the buffer.
-//
-// mLogElementsLock must be held when this function is called.
-void LogBuffer::prune(log_id_t id, unsigned long pruneRows, uid_t caller_uid) {
-    LogTimeEntry *oldest = NULL;
-
-    LogTimeEntry::lock();
-
-    // Region locked?
-    LastLogTimes::iterator t = mTimes.begin();
-    while(t != mTimes.end()) {
-        LogTimeEntry *entry = (*t);
-        if (entry->owned_Locked() && entry->isWatching(id)
-                && (!oldest || (oldest->mStart > entry->mStart))) {
-            oldest = entry;
-        }
-        t++;
-    }
-
-    LogBufferElementCollection::iterator it;
-
-    if (caller_uid != AID_ROOT) {
-        for(it = mLogElements.begin(); it != mLogElements.end();) {
-            LogBufferElement *e = *it;
-
-            if (oldest && (oldest->mStart <= e->getSequence())) {
-                break;
-            }
-
-            if (e->getLogId() != id) {
-                ++it;
-                continue;
-            }
-
-            if (e->getUid() == caller_uid) {
-                it = erase(it);
-                pruneRows--;
-                if (pruneRows == 0) {
-                    break;
-                }
-            } else {
-                ++it;
-            }
-        }
-        LogTimeEntry::unlock();
-        return;
-    }
-
-    // prune by worst offender by uid
-    bool hasBlacklist = mPrune.naughty();
-    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;
-
-        if (worstUidEnabledForLogid(id) && mPrune.worstUidEnabled()) {
-            std::unique_ptr<const UidEntry *[]> sorted = stats.sort(2, id);
-
-            if (sorted.get()) {
-                if (sorted[0] && sorted[1]) {
-                    worst_sizes = sorted[0]->getSizes();
-                    // Calculate threshold as 12.5% of available storage
-                    size_t threshold = log_buffer_size(id) / 8;
-                    if (worst_sizes > threshold) {
-                        worst = sorted[0]->getKey();
-                        second_worst_sizes = sorted[1]->getSizes();
-                        if (second_worst_sizes < threshold) {
-                            second_worst_sizes = threshold;
-                        }
-                    }
-                }
-            }
-        }
-
-        // skip if we have neither worst nor naughty filters
-        if ((worst == (uid_t) -1) && !hasBlacklist) {
-            break;
-        }
-
-        bool kick = false;
-        bool leading = true;
-        LogBufferElementLast last;
-        for(it = mLogElements.begin(); it != mLogElements.end();) {
-            LogBufferElement *e = *it;
-
-            if (oldest && (oldest->mStart <= e->getSequence())) {
-                break;
-            }
-
-            if (e->getLogId() != id) {
-                ++it;
-                continue;
-            }
-
-            unsigned short dropped = e->getDropped();
-
-            // remove any leading drops
-            if (leading && dropped) {
-                it = erase(it);
-                continue;
-            }
-
-            // merge any drops
-            if (dropped && last.merge(e, dropped)) {
-                it = mLogElements.erase(it);
-                stats.erase(e);
-                delete e;
-                continue;
-            }
-
-            if (hasBlacklist && mPrune.naughty(e)) {
-                last.clear(e);
-                it = erase(it);
-                if (dropped) {
-                    continue;
-                }
-
-                pruneRows--;
-                if (pruneRows == 0) {
-                    break;
-                }
-
-                if (e->getUid() == worst) {
-                    kick = true;
-                    if (worst_sizes < second_worst_sizes) {
-                        break;
-                    }
-                    worst_sizes -= e->getMsgLen();
-                }
-                continue;
-            }
-
-            if (dropped) {
-                last.add(e);
-                ++it;
-                continue;
-            }
-
-            if (e->getUid() != worst) {
-                if (leading) {
-                    static const timespec too_old = {
-                        EXPIRE_HOUR_THRESHOLD * 60 * 60, 0
-                    };
-                    LogBufferElementCollection::iterator last;
-                    last = mLogElements.end();
-                    --last;
-                    if ((e->getRealTime() < ((*last)->getRealTime() - too_old))
-                            || (e->getRealTime() > (*last)->getRealTime())) {
-                        break;
-                    }
-                }
-                leading = false;
-                last.clear(e);
-                ++it;
-                continue;
-            }
-
-            pruneRows--;
-            if (pruneRows == 0) {
-                break;
-            }
-
-            kick = true;
-
-            unsigned short len = e->getMsgLen();
-
-            // do not create any leading drops
-            if (leading) {
-                it = erase(it);
-            } else {
-                stats.drop(e);
-                e->setDropped(1);
-                if (last.merge(e, 1)) {
-                    it = mLogElements.erase(it);
-                    stats.erase(e);
-                    delete e;
-                } else {
-                    last.add(e);
-                    ++it;
-                }
-            }
-            if (worst_sizes < second_worst_sizes) {
-                break;
-            }
-            worst_sizes -= len;
-        }
-        last.clear();
-
-        if (!kick || !mPrune.worstUidEnabled()) {
-            break; // the following loop will ask bad clients to skip/drop
-        }
-    }
-
-    bool whitelist = false;
-    bool hasWhitelist = mPrune.nice();
-    it = mLogElements.begin();
-    while((pruneRows > 0) && (it != mLogElements.end())) {
-        LogBufferElement *e = *it;
-
-        if (e->getLogId() != id) {
-            it++;
-            continue;
-        }
-
-        if (oldest && (oldest->mStart <= e->getSequence())) {
-            if (whitelist) {
-                break;
-            }
-
-            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(id, pruneRows);
-            }
-            break;
-        }
-
-        if (hasWhitelist && !e->getDropped() && mPrune.nice(e)) { // WhiteListed
-            whitelist = true;
-            it++;
-            continue;
-        }
-
-        it = erase(it);
-        pruneRows--;
-    }
-
-    // Do not save the whitelist if we are reader range limited
-    if (whitelist && (pruneRows > 0)) {
-        it = mLogElements.begin();
-        while((it != mLogElements.end()) && (pruneRows > 0)) {
-            LogBufferElement *e = *it;
-
-            if (e->getLogId() != id) {
-                ++it;
-                continue;
-            }
-
-            if (oldest && (oldest->mStart <= e->getSequence())) {
-                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(id, pruneRows);
-                }
-                break;
-            }
-
-            it = erase(it);
-            pruneRows--;
-        }
-    }
-
-    LogTimeEntry::unlock();
-}
-
-// clear all rows of type "id" from the buffer.
-void LogBuffer::clear(log_id_t id, uid_t uid) {
-    pthread_mutex_lock(&mLogElementsLock);
-    prune(id, ULONG_MAX, uid);
-    pthread_mutex_unlock(&mLogElementsLock);
-}
-
-// get the used space associated with "id".
-unsigned long LogBuffer::getSizeUsed(log_id_t id) {
-    pthread_mutex_lock(&mLogElementsLock);
-    size_t retval = stats.sizes(id);
-    pthread_mutex_unlock(&mLogElementsLock);
-    return retval;
-}
-
-// set the total space allocated to "id"
-int LogBuffer::setSize(log_id_t id, unsigned long size) {
-    // Reasonable limits ...
-    if (!valid_size(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;
-}
-
-uint64_t LogBuffer::flushTo(
-        SocketClient *reader, const uint64_t start, bool privileged,
-        int (*filter)(const LogBufferElement *element, void *arg), void *arg) {
-    LogBufferElementCollection::iterator it;
-    uint64_t max = start;
-    uid_t uid = reader->getUid();
-
-    pthread_mutex_lock(&mLogElementsLock);
-
-    if (start <= 1) {
-        // client wants to start from the beginning
-        it = mLogElements.begin();
-    } else {
-        // Client wants to start from some specified time. Chances are
-        // we are better off starting from the end of the time sorted list.
-        for (it = mLogElements.end(); it != mLogElements.begin(); /* do nothing */) {
-            --it;
-            LogBufferElement *element = *it;
-            if (element->getSequence() <= start) {
-                it++;
-                break;
-            }
-        }
-    }
-
-    for (; it != mLogElements.end(); ++it) {
-        LogBufferElement *element = *it;
-
-        if (!privileged && (element->getUid() != uid)) {
-            continue;
-        }
-
-        if (element->getSequence() <= start) {
-            continue;
-        }
-
-        // NB: calling out to another object with mLogElementsLock held (safe)
-        if (filter) {
-            int ret = (*filter)(element, arg);
-            if (ret == false) {
-                continue;
-            }
-            if (ret != true) {
-                break;
-            }
-        }
-
-        pthread_mutex_unlock(&mLogElementsLock);
-
-        // range locking in LastLogTimes looks after us
-        max = element->flushTo(reader, this);
-
-        if (max == element->FLUSH_ERROR) {
-            return max;
-        }
-
-        pthread_mutex_lock(&mLogElementsLock);
-    }
-    pthread_mutex_unlock(&mLogElementsLock);
-
-    return max;
-}
-
-void LogBuffer::formatStatistics(char **strp, uid_t uid, unsigned int logMask) {
-    pthread_mutex_lock(&mLogElementsLock);
-
-    stats.format(strp, uid, logMask);
-
-    pthread_mutex_unlock(&mLogElementsLock);
-}
diff --git a/logd/LogBuffer.h b/logd/LogBuffer.h
index a13fded..8d58523 100644
--- a/logd/LogBuffer.h
+++ b/logd/LogBuffer.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012-2014 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -14,74 +14,66 @@
  * limitations under the License.
  */
 
-#ifndef _LOGD_LOG_BUFFER_H__
-#define _LOGD_LOG_BUFFER_H__
+#pragma once
 
 #include <sys/types.h>
 
+#include <functional>
+#include <memory>
+
+#include <android-base/thread_annotations.h>
 #include <log/log.h>
-#include <sysutils/SocketClient.h>
-#include <utils/List.h>
+#include <log/log_read.h>
 
-#include <private/android_filesystem_config.h>
+#include "LogWriter.h"
+#include "LogdLock.h"
 
-#include "LogBufferElement.h"
-#include "LogTimes.h"
-#include "LogStatistics.h"
-#include "LogWhiteBlackList.h"
+// A mask to represent which log buffers a reader is watching, values are (1 << LOG_ID_MAIN), etc.
+using LogMask = uint32_t;
+constexpr uint32_t kLogMaskAll = 0xFFFFFFFF;
 
-typedef android::List<LogBufferElement *> LogBufferElementCollection;
+// State that a LogBuffer may want to persist across calls to FlushTo().
+class FlushToState {
+  public:
+    FlushToState(uint64_t start, LogMask log_mask) : start_(start), log_mask_(log_mask) {}
+    virtual ~FlushToState() {}
 
-class LogBuffer {
-    LogBufferElementCollection mLogElements;
-    pthread_mutex_t mLogElementsLock;
+    uint64_t start() const { return start_; }
+    void set_start(uint64_t start) { start_ = start; }
 
-    LogStatistics stats;
+    LogMask log_mask() const { return log_mask_; }
 
-    PruneList mPrune;
-
-    unsigned long mMaxSize[LOG_ID_MAX];
-
-public:
-    LastLogTimes &mTimes;
-
-    LogBuffer(LastLogTimes *times);
-    void init();
-
-    int log(log_id_t log_id, log_time realtime,
-            uid_t uid, pid_t pid, pid_t tid,
-            const char *msg, unsigned short len);
-    uint64_t flushTo(SocketClient *writer, const uint64_t start,
-                     bool privileged,
-                     int (*filter)(const LogBufferElement *element, void *arg) = NULL,
-                     void *arg = NULL);
-
-    void clear(log_id_t id, uid_t uid = AID_ROOT);
-    unsigned long getSize(log_id_t id);
-    int setSize(log_id_t id, unsigned long size);
-    unsigned long getSizeUsed(log_id_t id);
-    // *strp uses malloc, use free to release.
-    void formatStatistics(char **strp, uid_t uid, unsigned int logMask);
-
-    void enableStatistics() {
-        stats.enableStatistics();
-    }
-
-    int initPrune(char *cp) { return mPrune.init(cp); }
-    // *strp uses malloc, use free to release.
-    void formatPrune(char **strp) { mPrune.format(strp); }
-
-    // helper must be protected directly or implicitly by lock()/unlock()
-    char *pidToName(pid_t pid) { return stats.pidToName(pid); }
-    uid_t pidToUid(pid_t pid) { return stats.pidToUid(pid); }
-    char *uidToName(uid_t uid) { return stats.uidToName(uid); }
-    void lock() { pthread_mutex_lock(&mLogElementsLock); }
-    void unlock() { pthread_mutex_unlock(&mLogElementsLock); }
-
-private:
-    void maybePrune(log_id_t id);
-    void prune(log_id_t id, unsigned long pruneRows, uid_t uid = AID_ROOT);
-    LogBufferElementCollection::iterator erase(LogBufferElementCollection::iterator it);
+  private:
+    uint64_t start_;
+    LogMask log_mask_;
 };
 
-#endif // _LOGD_LOG_BUFFER_H__
+// Enum for the return values of the `filter` function passed to FlushTo().
+enum class FilterResult {
+    kSkip,
+    kStop,
+    kWrite,
+};
+
+class LogBuffer {
+  public:
+    virtual ~LogBuffer() {}
+
+    virtual void Init() = 0;
+
+    virtual int Log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid,
+                    const char* msg, uint16_t len) = 0;
+
+    virtual std::unique_ptr<FlushToState> CreateFlushToState(uint64_t start, LogMask log_mask)
+            REQUIRES(logd_lock) = 0;
+    virtual bool FlushTo(
+            LogWriter* writer, FlushToState& state,
+            const std::function<FilterResult(log_id_t log_id, pid_t pid, uint64_t sequence,
+                                             log_time realtime)>& filter) REQUIRES(logd_lock) = 0;
+
+    virtual bool Clear(log_id_t id, uid_t uid) = 0;
+    virtual size_t GetSize(log_id_t id) = 0;
+    virtual bool SetSize(log_id_t id, size_t size) = 0;
+
+    virtual uint64_t sequence() const = 0;
+};
diff --git a/logd/LogBufferElement.cpp b/logd/LogBufferElement.cpp
index 9fb1439..26affa8 100644
--- a/logd/LogBufferElement.cpp
+++ b/logd/LogBufferElement.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "LogBufferElement.h"
+
 #include <ctype.h>
 #include <endian.h>
 #include <fcntl.h>
@@ -22,47 +24,122 @@
 #include <time.h>
 #include <unistd.h>
 
-#include <log/logger.h>
+#include <log/log_read.h>
 #include <private/android_logger.h>
 
-#include "LogBufferElement.h"
-#include "LogCommand.h"
-#include "LogReader.h"
+#include "LogStatistics.h"
+#include "LogUtils.h"
 
-const uint64_t LogBufferElement::FLUSH_ERROR(0);
-atomic_int_fast64_t LogBufferElement::sequence(1);
+LogBufferElement::LogBufferElement(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid,
+                                   pid_t tid, uint64_t sequence, const char* msg, uint16_t len)
+    : uid_(uid),
+      pid_(pid),
+      tid_(tid),
+      sequence_(sequence),
+      realtime_(realtime),
+      msg_len_(len),
+      log_id_(log_id),
+      dropped_(false) {
+    msg_ = new char[len];
+    memcpy(msg_, msg, len);
+}
 
-LogBufferElement::LogBufferElement(log_id_t log_id, log_time realtime,
-                                   uid_t uid, pid_t pid, pid_t tid,
-                                   const char *msg, unsigned short len) :
-        mLogId(log_id),
-        mUid(uid),
-        mPid(pid),
-        mTid(tid),
-        mMsgLen(len),
-        mSequence(sequence.fetch_add(1, memory_order_relaxed)),
-        mRealTime(realtime) {
-    mMsg = new char[len];
-    memcpy(mMsg, msg, len);
+LogBufferElement::LogBufferElement(const LogBufferElement& elem)
+    : uid_(elem.uid_),
+      pid_(elem.pid_),
+      tid_(elem.tid_),
+      sequence_(elem.sequence_),
+      realtime_(elem.realtime_),
+      msg_len_(elem.msg_len_),
+      log_id_(elem.log_id_),
+      dropped_(elem.dropped_) {
+    if (dropped_) {
+        tag_ = elem.GetTag();
+    } else {
+        msg_ = new char[msg_len_];
+        memcpy(msg_, elem.msg_, msg_len_);
+    }
+}
+
+LogBufferElement::LogBufferElement(LogBufferElement&& elem) noexcept
+    : uid_(elem.uid_),
+      pid_(elem.pid_),
+      tid_(elem.tid_),
+      sequence_(elem.sequence_),
+      realtime_(elem.realtime_),
+      msg_len_(elem.msg_len_),
+      log_id_(elem.log_id_),
+      dropped_(elem.dropped_) {
+    if (dropped_) {
+        tag_ = elem.GetTag();
+    } else {
+        msg_ = elem.msg_;
+        elem.msg_ = nullptr;
+    }
 }
 
 LogBufferElement::~LogBufferElement() {
-    delete [] mMsg;
+    if (!dropped_) {
+        delete[] msg_;
+    }
 }
 
-uint32_t LogBufferElement::getTag() const {
-    if ((mLogId != LOG_ID_EVENTS) || !mMsg || (mMsgLen < sizeof(uint32_t))) {
+uint32_t LogBufferElement::GetTag() const {
+    // Binary buffers have no tag.
+    if (!IsBinary(log_id())) {
         return 0;
     }
-    return le32toh(reinterpret_cast<android_event_header_t *>(mMsg)->tag);
+
+    // Dropped messages store the tag in place of msg_.
+    if (dropped_) {
+        return tag_;
+    }
+
+    return MsgToTag(msg(), msg_len());
+}
+
+LogStatisticsElement LogBufferElement::ToLogStatisticsElement() const {
+    // Estimate the size of this element in the parent std::list<> by adding two void*'s
+    // corresponding to the next/prev pointers and aligning to 64 bit.
+    uint16_t element_in_list_size =
+            (sizeof(*this) + 2 * sizeof(void*) + sizeof(uint64_t) - 1) & -sizeof(uint64_t);
+    return LogStatisticsElement{
+            .uid = uid(),
+            .pid = pid(),
+            .tid = tid(),
+            .tag = GetTag(),
+            .realtime = realtime(),
+            .msg = msg(),
+            .msg_len = msg_len(),
+            .dropped_count = dropped_count(),
+            .log_id = log_id(),
+            .total_len = static_cast<uint16_t>(element_in_list_size + msg_len()),
+    };
+}
+
+uint16_t LogBufferElement::SetDropped(uint16_t value) {
+    if (dropped_) {
+        return dropped_count_ = value;
+    }
+
+    // The tag information is saved in msg_ data, which is in a union with tag_, used after dropped_
+    // is set to true. Therefore we save the tag value aside, delete msg_, then set tag_ to the tag
+    // value in its place.
+    auto old_tag = GetTag();
+    delete[] msg_;
+    msg_ = nullptr;
+
+    tag_ = old_tag;
+    dropped_ = true;
+    return dropped_count_ = value;
 }
 
 // caller must own and free character string
-char *android::tidToName(pid_t tid) {
-    char *retval = NULL;
+char* android::tidToName(pid_t tid) {
+    char* retval = nullptr;
     char buffer[256];
     snprintf(buffer, sizeof(buffer), "/proc/%u/comm", tid);
-    int fd = open(buffer, O_RDONLY);
+    int fd = open(buffer, O_RDONLY | O_CLOEXEC);
     if (fd >= 0) {
         ssize_t ret = read(fd, buffer, sizeof(buffer));
         if (ret >= (ssize_t)sizeof(buffer)) {
@@ -79,10 +156,10 @@
     }
 
     // if nothing for comm, check out cmdline
-    char *name = android::pidToName(tid);
+    char* name = android::pidToName(tid);
     if (!retval) {
         retval = name;
-        name = NULL;
+        name = nullptr;
     }
 
     // check if comm is truncated, see if cmdline has full representation
@@ -91,7 +168,8 @@
         size_t retval_len = strlen(retval);
         size_t name_len = strlen(name);
         // KISS: ToDo: Only checks prefix truncated, not suffix, or both
-        if ((retval_len < name_len) && !strcmp(retval, name + name_len - retval_len)) {
+        if ((retval_len < name_len) &&
+            !fastcmp<strcmp>(retval, name + name_len - retval_len)) {
             free(retval);
             retval = name;
         } else {
@@ -101,130 +179,119 @@
     return retval;
 }
 
-// assumption: mMsg == NULL
-size_t LogBufferElement::populateDroppedMessage(char *&buffer,
-        LogBuffer *parent) {
+// assumption: msg_ == NULL
+size_t LogBufferElement::PopulateDroppedMessage(char*& buffer, LogStatistics* stats,
+                                                bool lastSame) {
     static const char tag[] = "chatty";
 
-    if (!__android_log_is_loggable(ANDROID_LOG_INFO, tag, ANDROID_LOG_VERBOSE)) {
+    if (!__android_log_is_loggable_len(ANDROID_LOG_INFO, tag, strlen(tag),
+                                       ANDROID_LOG_VERBOSE)) {
         return 0;
     }
 
-    static const char format_uid[] = "uid=%u%s%s expire %u line%s";
-    parent->lock();
-    char *name = parent->uidToName(mUid);
-    parent->unlock();
-    char *commName = android::tidToName(mTid);
-    if (!commName && (mTid != mPid)) {
-        commName = android::tidToName(mPid);
+    static const char format_uid[] = "uid=%u%s%s %s %u line%s";
+    const char* name = stats->UidToName(uid_);
+    const char* commName = android::tidToName(tid_);
+    if (!commName && (tid_ != pid_)) {
+        commName = android::tidToName(pid_);
     }
     if (!commName) {
-        parent->lock();
-        commName = parent->pidToName(mPid);
-        parent->unlock();
+        commName = stats->PidToName(pid_);
     }
-    size_t len = name ? strlen(name) : 0;
-    if (len && commName && !strncmp(name, commName, len)) {
-        if (commName[len] == '\0') {
-            free(commName);
-            commName = NULL;
-        } else {
-            free(name);
-            name = NULL;
+    if (name && name[0] && commName && (name[0] == commName[0])) {
+        size_t len = strlen(name + 1);
+        if (!strncmp(name + 1, commName + 1, len)) {
+            if (commName[len + 1] == '\0') {
+                free(const_cast<char*>(commName));
+                commName = nullptr;
+            } else {
+                free(const_cast<char*>(name));
+                name = nullptr;
+            }
         }
     }
     if (name) {
-        char *p = NULL;
-        asprintf(&p, "(%s)", name);
-        if (p) {
-            free(name);
-            name = p;
+        char* buf = nullptr;
+        int result = asprintf(&buf, "(%s)", name);
+        if (result != -1) {
+            free(const_cast<char*>(name));
+            name = buf;
         }
     }
     if (commName) {
-        char *p = NULL;
-        asprintf(&p, " %s", commName);
-        if (p) {
-            free(commName);
-            commName = p;
+        char* buf = nullptr;
+        int result = asprintf(&buf, " %s", commName);
+        if (result != -1) {
+            free(const_cast<char*>(commName));
+            commName = buf;
         }
     }
     // identical to below to calculate the buffer size required
-    len = snprintf(NULL, 0, format_uid, mUid, name ? name : "",
-                   commName ? commName : "",
-                   mDropped, (mDropped > 1) ? "s" : "");
+    const char* type = lastSame ? "identical" : "expire";
+    size_t len = snprintf(nullptr, 0, format_uid, uid_, name ? name : "", commName ? commName : "",
+                          type, dropped_count(), (dropped_count() > 1) ? "s" : "");
 
     size_t hdrLen;
-    if (mLogId == LOG_ID_EVENTS) {
+    if (IsBinary(log_id())) {
         hdrLen = sizeof(android_log_event_string_t);
     } else {
         hdrLen = 1 + sizeof(tag);
     }
 
-    buffer = static_cast<char *>(calloc(1, hdrLen + len + 1));
+    buffer = static_cast<char*>(calloc(1, hdrLen + len + 1));
     if (!buffer) {
-        free(name);
-        free(commName);
+        free(const_cast<char*>(name));
+        free(const_cast<char*>(commName));
         return 0;
     }
 
     size_t retval = hdrLen + len;
-    if (mLogId == LOG_ID_EVENTS) {
-        android_log_event_string_t *e = reinterpret_cast<android_log_event_string_t *>(buffer);
+    if (IsBinary(log_id())) {
+        android_log_event_string_t* event =
+            reinterpret_cast<android_log_event_string_t*>(buffer);
 
-        e->header.tag = htole32(LOGD_LOG_TAG);
-        e->type = EVENT_TYPE_STRING;
-        e->length = htole32(len);
+        event->header.tag = htole32(CHATTY_LOG_TAG);
+        event->type = EVENT_TYPE_STRING;
+        event->length = htole32(len);
     } else {
         ++retval;
         buffer[0] = ANDROID_LOG_INFO;
         strcpy(buffer + 1, tag);
     }
 
-    snprintf(buffer + hdrLen, len + 1, format_uid, mUid, name ? name : "",
-             commName ? commName : "",
-             mDropped, (mDropped > 1) ? "s" : "");
-    free(name);
-    free(commName);
+    snprintf(buffer + hdrLen, len + 1, format_uid, uid_, name ? name : "", commName ? commName : "",
+             type, dropped_count(), (dropped_count() > 1) ? "s" : "");
+    free(const_cast<char*>(name));
+    free(const_cast<char*>(commName));
 
     return retval;
 }
 
-uint64_t LogBufferElement::flushTo(SocketClient *reader, LogBuffer *parent) {
-    struct logger_entry_v3 entry;
+bool LogBufferElement::FlushTo(LogWriter* writer, LogStatistics* stats, bool lastSame) {
+    struct logger_entry entry = {};
 
-    memset(&entry, 0, sizeof(struct logger_entry_v3));
+    entry.hdr_size = sizeof(struct logger_entry);
+    entry.lid = log_id_;
+    entry.pid = pid_;
+    entry.tid = tid_;
+    entry.uid = uid_;
+    entry.sec = realtime_.tv_sec;
+    entry.nsec = realtime_.tv_nsec;
 
-    entry.hdr_size = sizeof(struct logger_entry_v3);
-    entry.lid = mLogId;
-    entry.pid = mPid;
-    entry.tid = mTid;
-    entry.sec = mRealTime.tv_sec;
-    entry.nsec = mRealTime.tv_nsec;
-
-    struct iovec iovec[2];
-    iovec[0].iov_base = &entry;
-    iovec[0].iov_len = sizeof(struct logger_entry_v3);
-
-    char *buffer = NULL;
-
-    if (!mMsg) {
-        entry.len = populateDroppedMessage(buffer, parent);
-        if (!entry.len) {
-            return mSequence;
-        }
-        iovec[1].iov_base = buffer;
+    char* buffer = nullptr;
+    const char* msg;
+    if (dropped_) {
+        entry.len = PopulateDroppedMessage(buffer, stats, lastSame);
+        if (!entry.len) return true;
+        msg = buffer;
     } else {
-        entry.len = mMsgLen;
-        iovec[1].iov_base = mMsg;
+        msg = msg_;
+        entry.len = msg_len_;
     }
-    iovec[1].iov_len = entry.len;
 
-    uint64_t retval = reader->sendDatav(iovec, 2) ? FLUSH_ERROR : mSequence;
+    bool retval = writer->Write(entry, msg);
 
-    if (buffer) {
-        free(buffer);
-    }
+    if (buffer) free(buffer);
 
     return retval;
 }
diff --git a/logd/LogBufferElement.h b/logd/LogBufferElement.h
index ca2c3a6..b263ca5 100644
--- a/logd/LogBufferElement.h
+++ b/logd/LogBufferElement.h
@@ -14,91 +14,67 @@
  * limitations under the License.
  */
 
-#ifndef _LOGD_LOG_BUFFER_ELEMENT_H__
-#define _LOGD_LOG_BUFFER_ELEMENT_H__
+#pragma once
 
-#include <stdatomic.h>
+#include <stdint.h>
 #include <stdlib.h>
 #include <sys/types.h>
 
-#include <sysutils/SocketClient.h>
 #include <log/log.h>
-#include <log/log_read.h>
 
-// Hijack this header as a common include file used by most all sources
-// to report some utilities defined here and there.
+#include "LogWriter.h"
 
-namespace android {
+#include "LogStatistics.h"
 
-// Furnished in main.cpp. Caller must own and free returned value
-char *uidToName(uid_t uid);
+#define EXPIRE_HOUR_THRESHOLD 24  // Only expire chatty UID logs to preserve
+                                  // non-chatty UIDs less than this age in hours
+#define EXPIRE_THRESHOLD 10       // A smaller expire count is considered too
+                                  // chatty for the temporal expire messages
+#define EXPIRE_RATELIMIT 10  // maximum rate in seconds to report expiration
 
-// Furnished in LogStatistics.cpp. Caller must own and free returned value
-char *pidToName(pid_t pid);
-char *tidToName(pid_t tid);
+class __attribute__((packed)) LogBufferElement {
+  public:
+    LogBufferElement(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid,
+                     uint64_t sequence, const char* msg, uint16_t len);
+    LogBufferElement(const LogBufferElement& elem);
+    LogBufferElement(LogBufferElement&& elem) noexcept;
+    ~LogBufferElement();
 
-// Furnished in main.cpp. Thread safe.
-const char *tagToName(uint32_t tag);
+    uint32_t GetTag() const;
+    uint16_t SetDropped(uint16_t value);
 
-}
+    bool FlushTo(LogWriter* writer, LogStatistics* parent, bool lastSame);
 
-static inline bool worstUidEnabledForLogid(log_id_t id) {
-    return (id != LOG_ID_CRASH) && (id != LOG_ID_KERNEL) && (id != LOG_ID_EVENTS);
-}
+    LogStatisticsElement ToLogStatisticsElement() const;
 
-class LogBuffer;
+    log_id_t log_id() const { return static_cast<log_id_t>(log_id_); }
+    uid_t uid() const { return uid_; }
+    pid_t pid() const { return pid_; }
+    pid_t tid() const { return tid_; }
+    uint16_t msg_len() const { return dropped_ ? 0 : msg_len_; }
+    const char* msg() const { return dropped_ ? nullptr : msg_; }
+    uint64_t sequence() const { return sequence_; }
+    log_time realtime() const { return realtime_; }
+    uint16_t dropped_count() const { return dropped_ ? dropped_count_ : 0; }
 
-#define EXPIRE_HOUR_THRESHOLD 24 // Only expire chatty UID logs to preserve
-                                 // non-chatty UIDs less than this age in hours
-#define EXPIRE_THRESHOLD 10      // A smaller expire count is considered too
-                                 // chatty for the temporal expire messages
-#define EXPIRE_RATELIMIT 10      // maximum rate in seconds to report expiration
+  private:
+    // assumption: mDropped == true
+    size_t PopulateDroppedMessage(char*& buffer, LogStatistics* parent, bool lastSame);
 
-class LogBufferElement {
-    const log_id_t mLogId;
-    const uid_t mUid;
-    const pid_t mPid;
-    const pid_t mTid;
-    char *mMsg;
+    // sized to match reality of incoming log packets
+    const uint32_t uid_;
+    const uint32_t pid_;
+    const uint32_t tid_;
+    uint64_t sequence_;
+    log_time realtime_;
     union {
-        const unsigned short mMsgLen; // mMSg != NULL
-        unsigned short mDropped;      // mMsg == NULL
+        char* msg_;    // mDropped == false
+        int32_t tag_;  // mDropped == true
     };
-    const uint64_t mSequence;
-    const log_time mRealTime;
-    static atomic_int_fast64_t sequence;
-
-    // assumption: mMsg == NULL
-    size_t populateDroppedMessage(char *&buffer,
-                                  LogBuffer *parent);
-
-public:
-    LogBufferElement(log_id_t log_id, log_time realtime,
-                     uid_t uid, pid_t pid, pid_t tid,
-                     const char *msg, unsigned short len);
-    virtual ~LogBufferElement();
-
-    log_id_t getLogId() const { return mLogId; }
-    uid_t getUid(void) const { return mUid; }
-    pid_t getPid(void) const { return mPid; }
-    pid_t getTid(void) const { return mTid; }
-    unsigned short getDropped(void) const { return mMsg ? 0 : mDropped; }
-    unsigned short setDropped(unsigned short value) {
-        if (mMsg) {
-            free(mMsg);
-            mMsg = NULL;
-        }
-        return mDropped = value;
-    }
-    unsigned short getMsgLen() const { return mMsg ? mMsgLen : 0; }
-    uint64_t getSequence(void) const { return mSequence; }
-    static uint64_t getCurrentSequence(void) { return sequence.load(memory_order_relaxed); }
-    log_time getRealTime(void) const { return mRealTime; }
-
-    uint32_t getTag(void) const;
-
-    static const uint64_t FLUSH_ERROR;
-    uint64_t flushTo(SocketClient *writer, LogBuffer *parent);
+    union {
+        const uint16_t msg_len_;  // mDropped == false
+        uint16_t dropped_count_;  // mDropped == true
+    };
+    const uint8_t log_id_;
+    bool dropped_;
 };
-
-#endif
diff --git a/logd/LogBufferTest.cpp b/logd/LogBufferTest.cpp
new file mode 100644
index 0000000..cb9f428
--- /dev/null
+++ b/logd/LogBufferTest.cpp
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2020 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 "LogBufferTest.h"
+
+#include <unistd.h>
+
+#include <limits>
+#include <memory>
+#include <regex>
+#include <vector>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include "LogBuffer.h"
+#include "LogReaderThread.h"
+#include "LogWriter.h"
+
+using android::base::Join;
+using android::base::Split;
+using android::base::StringPrintf;
+
+char* android::uidToName(uid_t) {
+    return nullptr;
+}
+
+static std::vector<std::string> CompareLoggerEntries(const logger_entry& expected,
+                                                     const logger_entry& result, bool ignore_len) {
+    std::vector<std::string> errors;
+    if (!ignore_len && expected.len != result.len) {
+        errors.emplace_back(
+                StringPrintf("len: expected %" PRIu16 " vs %" PRIu16, expected.len, result.len));
+    }
+    if (expected.hdr_size != result.hdr_size) {
+        errors.emplace_back(StringPrintf("hdr_size: %" PRIu16 " vs %" PRIu16, expected.hdr_size,
+                                         result.hdr_size));
+    }
+    if (expected.pid != result.pid) {
+        errors.emplace_back(
+                StringPrintf("pid: expected %" PRIi32 " vs %" PRIi32, expected.pid, result.pid));
+    }
+    if (expected.tid != result.tid) {
+        errors.emplace_back(
+                StringPrintf("tid: expected %" PRIu32 " vs %" PRIu32, expected.tid, result.tid));
+    }
+    if (expected.sec != result.sec) {
+        errors.emplace_back(
+                StringPrintf("sec: expected %" PRIu32 " vs %" PRIu32, expected.sec, result.sec));
+    }
+    if (expected.nsec != result.nsec) {
+        errors.emplace_back(
+                StringPrintf("nsec: expected %" PRIu32 " vs %" PRIu32, expected.nsec, result.nsec));
+    }
+    if (expected.lid != result.lid) {
+        errors.emplace_back(
+                StringPrintf("lid: expected %" PRIu32 " vs %" PRIu32, expected.lid, result.lid));
+    }
+    if (expected.uid != result.uid) {
+        errors.emplace_back(
+                StringPrintf("uid: expected %" PRIu32 " vs %" PRIu32, expected.uid, result.uid));
+    }
+    return errors;
+}
+
+static std::string MakePrintable(std::string in) {
+    if (in.size() > 80) {
+        in = in.substr(0, 80) + "...";
+    }
+    std::string result;
+    for (const char c : in) {
+        if (isprint(c)) {
+            result.push_back(c);
+        } else {
+            result.append(StringPrintf("\\%02x", static_cast<int>(c) & 0xFF));
+        }
+    }
+    return result;
+}
+
+static std::string CompareMessages(const std::string& expected, const std::string& result) {
+    if (expected == result) {
+        return {};
+    }
+    size_t diff_index = 0;
+    for (; diff_index < std::min(expected.size(), result.size()); ++diff_index) {
+        if (expected[diff_index] != result[diff_index]) {
+            break;
+        }
+    }
+
+    if (diff_index < 80) {
+        auto expected_short = MakePrintable(expected);
+        auto result_short = MakePrintable(result);
+        return StringPrintf("msg: expected '%s' vs '%s'", expected_short.c_str(),
+                            result_short.c_str());
+    }
+
+    auto expected_short = MakePrintable(expected.substr(diff_index));
+    auto result_short = MakePrintable(result.substr(diff_index));
+    return StringPrintf("msg: index %zu: expected '%s' vs '%s'", diff_index, expected_short.c_str(),
+                        result_short.c_str());
+}
+
+static std::string CompareRegexMessages(const std::string& expected, const std::string& result) {
+    auto expected_pieces = Split(expected, std::string("\0", 1));
+    auto result_pieces = Split(result, std::string("\0", 1));
+
+    if (expected_pieces.size() != 3 || result_pieces.size() != 3) {
+        return StringPrintf(
+                "msg: should have 3 null delimited strings found %d in expected, %d in result: "
+                "'%s' vs '%s'",
+                static_cast<int>(expected_pieces.size()), static_cast<int>(result_pieces.size()),
+                MakePrintable(expected).c_str(), MakePrintable(result).c_str());
+    }
+    if (expected_pieces[0] != result_pieces[0]) {
+        return StringPrintf("msg: tag/priority mismatch expected '%s' vs '%s'",
+                            MakePrintable(expected_pieces[0]).c_str(),
+                            MakePrintable(result_pieces[0]).c_str());
+    }
+    std::regex expected_tag_regex(expected_pieces[1]);
+    if (!std::regex_search(result_pieces[1], expected_tag_regex)) {
+        return StringPrintf("msg: message regex mismatch expected '%s' vs '%s'",
+                            MakePrintable(expected_pieces[1]).c_str(),
+                            MakePrintable(result_pieces[1]).c_str());
+    }
+    if (expected_pieces[2] != result_pieces[2]) {
+        return StringPrintf("msg: nothing expected after final null character '%s' vs '%s'",
+                            MakePrintable(expected_pieces[2]).c_str(),
+                            MakePrintable(result_pieces[2]).c_str());
+    }
+    return {};
+}
+
+void CompareLogMessages(const std::vector<LogMessage>& expected,
+                        const std::vector<LogMessage>& result) {
+    EXPECT_EQ(expected.size(), result.size());
+    size_t end = std::min(expected.size(), result.size());
+    size_t num_errors = 0;
+    for (size_t i = 0; i < end; ++i) {
+        auto errors =
+                CompareLoggerEntries(expected[i].entry, result[i].entry, expected[i].regex_compare);
+        auto msg_error = expected[i].regex_compare
+                                 ? CompareRegexMessages(expected[i].message, result[i].message)
+                                 : CompareMessages(expected[i].message, result[i].message);
+        if (!msg_error.empty()) {
+            errors.emplace_back(msg_error);
+        }
+        if (!errors.empty()) {
+            GTEST_LOG_(ERROR) << "Mismatch log message " << i << "\n" << Join(errors, "\n");
+            ++num_errors;
+        }
+    }
+    EXPECT_EQ(0U, num_errors);
+}
+
+void FixupMessages(std::vector<LogMessage>* messages) {
+    for (auto& [entry, message, _] : *messages) {
+        entry.hdr_size = sizeof(logger_entry);
+        entry.len = message.size();
+    }
+}
+
+TEST_P(LogBufferTest, smoke) {
+    std::vector<LogMessage> log_messages = {
+            {{
+                     .pid = 1,
+                     .tid = 1,
+                     .sec = 1234,
+                     .nsec = 323001,
+                     .lid = LOG_ID_MAIN,
+                     .uid = 0,
+             },
+             "smoke test"},
+    };
+    FixupMessages(&log_messages);
+    LogMessages(log_messages);
+
+    std::vector<LogMessage> read_log_messages;
+    {
+        auto lock = std::lock_guard{logd_lock};
+        std::unique_ptr<LogWriter> test_writer(new TestWriter(&read_log_messages, nullptr));
+        std::unique_ptr<FlushToState> flush_to_state =
+                log_buffer_->CreateFlushToState(1, kLogMaskAll);
+        EXPECT_TRUE(log_buffer_->FlushTo(test_writer.get(), *flush_to_state, nullptr));
+        EXPECT_EQ(2ULL, flush_to_state->start());
+    }
+    CompareLogMessages(log_messages, read_log_messages);
+}
+
+TEST_P(LogBufferTest, smoke_with_reader_thread) {
+    std::vector<LogMessage> log_messages = {
+            {{.pid = 1, .tid = 2, .sec = 10000, .nsec = 20001, .lid = LOG_ID_MAIN, .uid = 0},
+             "first"},
+            {{.pid = 10, .tid = 2, .sec = 10000, .nsec = 20002, .lid = LOG_ID_MAIN, .uid = 0},
+             "second"},
+            {{.pid = 100, .tid = 2, .sec = 10000, .nsec = 20003, .lid = LOG_ID_KERNEL, .uid = 0},
+             "third"},
+            {{.pid = 10, .tid = 2, .sec = 10000, .nsec = 20004, .lid = LOG_ID_MAIN, .uid = 0},
+             "fourth"},
+            {{.pid = 1, .tid = 2, .sec = 10000, .nsec = 20005, .lid = LOG_ID_RADIO, .uid = 0},
+             "fifth"},
+            {{.pid = 2, .tid = 2, .sec = 10000, .nsec = 20006, .lid = LOG_ID_RADIO, .uid = 0},
+             "sixth"},
+            {{.pid = 3, .tid = 2, .sec = 10000, .nsec = 20007, .lid = LOG_ID_RADIO, .uid = 0},
+             "seventh"},
+            {{.pid = 4, .tid = 2, .sec = 10000, .nsec = 20008, .lid = LOG_ID_MAIN, .uid = 0},
+             "eighth"},
+            {{.pid = 5, .tid = 2, .sec = 10000, .nsec = 20009, .lid = LOG_ID_CRASH, .uid = 0},
+             "nineth"},
+            {{.pid = 6, .tid = 2, .sec = 10000, .nsec = 20011, .lid = LOG_ID_MAIN, .uid = 0},
+             "tenth"},
+    };
+    FixupMessages(&log_messages);
+    LogMessages(log_messages);
+
+    std::vector<LogMessage> read_log_messages;
+    bool released = false;
+
+    {
+        auto lock = std::lock_guard{logd_lock};
+        std::unique_ptr<LogWriter> test_writer(new TestWriter(&read_log_messages, &released));
+        std::unique_ptr<LogReaderThread> log_reader(
+                new LogReaderThread(log_buffer_.get(), &reader_list_, std::move(test_writer), true,
+                                    0, kLogMaskAll, 0, {}, 1, {}));
+        reader_list_.reader_threads().emplace_back(std::move(log_reader));
+    }
+
+    while (!released) {
+        usleep(5000);
+    }
+    {
+        auto lock = std::lock_guard{logd_lock};
+        EXPECT_EQ(0U, reader_list_.reader_threads().size());
+    }
+    CompareLogMessages(log_messages, read_log_messages);
+}
+
+// Generate random messages, set the 'sec' parameter explicit though, to be able to track the
+// expected order of messages.
+LogMessage GenerateRandomLogMessage(uint32_t sec) {
+    auto rand_uint32 = [](int max) -> uint32_t { return rand() % max; };
+    logger_entry entry = {
+            .hdr_size = sizeof(logger_entry),
+            .pid = rand() % 5000,
+            .tid = rand_uint32(5000),
+            .sec = sec,
+            .nsec = rand_uint32(NS_PER_SEC),
+            .lid = rand_uint32(LOG_ID_STATS),
+            .uid = rand_uint32(100000),
+    };
+
+    // See comment in ChattyLogBuffer::Log() for why this is disallowed.
+    if (entry.nsec % 1000 == 0) {
+        ++entry.nsec;
+    }
+
+    if (entry.lid == LOG_ID_EVENTS) {
+        entry.lid = LOG_ID_KERNEL;
+    }
+
+    std::string message;
+    char priority = ANDROID_LOG_INFO + rand() % 2;
+    message.push_back(priority);
+
+    int tag_length = 2 + rand() % 10;
+    for (int i = 0; i < tag_length; ++i) {
+        message.push_back('a' + rand() % 26);
+    }
+    message.push_back('\0');
+
+    int msg_length = 2 + rand() % 1000;
+    for (int i = 0; i < msg_length; ++i) {
+        message.push_back('a' + rand() % 26);
+    }
+    message.push_back('\0');
+
+    entry.len = message.size();
+
+    return {entry, message};
+}
+
+TEST_P(LogBufferTest, random_messages) {
+    srand(1);
+    std::vector<LogMessage> log_messages;
+    for (size_t i = 0; i < 1000; ++i) {
+        log_messages.emplace_back(GenerateRandomLogMessage(i));
+    }
+    LogMessages(log_messages);
+
+    std::vector<LogMessage> read_log_messages;
+    bool released = false;
+
+    {
+        auto lock = std::lock_guard{logd_lock};
+        std::unique_ptr<LogWriter> test_writer(new TestWriter(&read_log_messages, &released));
+        std::unique_ptr<LogReaderThread> log_reader(
+                new LogReaderThread(log_buffer_.get(), &reader_list_, std::move(test_writer), true,
+                                    0, kLogMaskAll, 0, {}, 1, {}));
+        reader_list_.reader_threads().emplace_back(std::move(log_reader));
+    }
+
+    while (!released) {
+        usleep(5000);
+    }
+    {
+        auto lock = std::lock_guard{logd_lock};
+        EXPECT_EQ(0U, reader_list_.reader_threads().size());
+    }
+    CompareLogMessages(log_messages, read_log_messages);
+}
+
+TEST_P(LogBufferTest, read_last_sequence) {
+    std::vector<LogMessage> log_messages = {
+            {{.pid = 1, .tid = 2, .sec = 10000, .nsec = 20001, .lid = LOG_ID_MAIN, .uid = 0},
+             "first"},
+            {{.pid = 10, .tid = 2, .sec = 10000, .nsec = 20002, .lid = LOG_ID_MAIN, .uid = 0},
+             "second"},
+            {{.pid = 100, .tid = 2, .sec = 10000, .nsec = 20003, .lid = LOG_ID_MAIN, .uid = 0},
+             "third"},
+    };
+    FixupMessages(&log_messages);
+    LogMessages(log_messages);
+
+    std::vector<LogMessage> read_log_messages;
+    bool released = false;
+
+    {
+        auto lock = std::lock_guard{logd_lock};
+        std::unique_ptr<LogWriter> test_writer(new TestWriter(&read_log_messages, &released));
+        std::unique_ptr<LogReaderThread> log_reader(
+                new LogReaderThread(log_buffer_.get(), &reader_list_, std::move(test_writer), true,
+                                    0, kLogMaskAll, 0, {}, 3, {}));
+        reader_list_.reader_threads().emplace_back(std::move(log_reader));
+    }
+
+    while (!released) {
+        usleep(5000);
+    }
+    {
+        auto lock = std::lock_guard{logd_lock};
+        EXPECT_EQ(0U, reader_list_.reader_threads().size());
+    }
+    std::vector<LogMessage> expected_log_messages = {log_messages.back()};
+    CompareLogMessages(expected_log_messages, read_log_messages);
+}
+
+TEST_P(LogBufferTest, clear_logs) {
+    // Log 3 initial logs.
+    std::vector<LogMessage> log_messages = {
+            {{.pid = 1, .tid = 2, .sec = 10000, .nsec = 20001, .lid = LOG_ID_MAIN, .uid = 0},
+             "first"},
+            {{.pid = 10, .tid = 2, .sec = 10000, .nsec = 20002, .lid = LOG_ID_MAIN, .uid = 0},
+             "second"},
+            {{.pid = 100, .tid = 2, .sec = 10000, .nsec = 20003, .lid = LOG_ID_MAIN, .uid = 0},
+             "third"},
+    };
+    FixupMessages(&log_messages);
+    LogMessages(log_messages);
+
+    std::vector<LogMessage> read_log_messages;
+    bool released = false;
+
+    // Connect a blocking reader.
+    {
+        auto lock = std::lock_guard{logd_lock};
+        std::unique_ptr<LogWriter> test_writer(new TestWriter(&read_log_messages, &released));
+        std::unique_ptr<LogReaderThread> log_reader(
+                new LogReaderThread(log_buffer_.get(), &reader_list_, std::move(test_writer), false,
+                                    0, kLogMaskAll, 0, {}, 1, {}));
+        reader_list_.reader_threads().emplace_back(std::move(log_reader));
+    }
+
+    // Wait up to 250ms for the reader to read the first 3 logs.
+    constexpr int kMaxRetryCount = 50;
+    int count = 0;
+    for (; count < kMaxRetryCount; ++count) {
+        usleep(5000);
+        auto lock = std::lock_guard{logd_lock};
+        if (reader_list_.reader_threads().back()->start() == 4) {
+            break;
+        }
+    }
+    ASSERT_LT(count, kMaxRetryCount);
+
+    // Clear the log buffer.
+    log_buffer_->Clear(LOG_ID_MAIN, 0);
+
+    // Log 3 more logs.
+    std::vector<LogMessage> after_clear_messages = {
+            {{.pid = 1, .tid = 2, .sec = 10000, .nsec = 20001, .lid = LOG_ID_MAIN, .uid = 0},
+             "4th"},
+            {{.pid = 10, .tid = 2, .sec = 10000, .nsec = 20002, .lid = LOG_ID_MAIN, .uid = 0},
+             "5th"},
+            {{.pid = 100, .tid = 2, .sec = 10000, .nsec = 20003, .lid = LOG_ID_MAIN, .uid = 0},
+             "6th"},
+    };
+    FixupMessages(&after_clear_messages);
+    LogMessages(after_clear_messages);
+
+    // Wait up to 250ms for the reader to read the 3 additional logs.
+    for (count = 0; count < kMaxRetryCount; ++count) {
+        usleep(5000);
+        auto lock = std::lock_guard{logd_lock};
+        if (reader_list_.reader_threads().back()->start() == 7) {
+            break;
+        }
+    }
+    ASSERT_LT(count, kMaxRetryCount);
+
+    // Release the reader, wait for it to get the signal then check that it has been deleted.
+    {
+        auto lock = std::lock_guard{logd_lock};
+        reader_list_.reader_threads().back()->Release();
+    }
+    while (!released) {
+        usleep(5000);
+    }
+    {
+        auto lock = std::lock_guard{logd_lock};
+        EXPECT_EQ(0U, reader_list_.reader_threads().size());
+    }
+
+    // Check that we have read all 6 messages.
+    std::vector<LogMessage> expected_log_messages = log_messages;
+    expected_log_messages.insert(expected_log_messages.end(), after_clear_messages.begin(),
+                                 after_clear_messages.end());
+    CompareLogMessages(expected_log_messages, read_log_messages);
+
+    // Finally, call FlushTo and ensure that only the 3 logs after the clear remain in the buffer.
+    std::vector<LogMessage> read_log_messages_after_clear;
+    {
+        auto lock = std::lock_guard{logd_lock};
+        std::unique_ptr<LogWriter> test_writer(
+                new TestWriter(&read_log_messages_after_clear, nullptr));
+        std::unique_ptr<FlushToState> flush_to_state =
+                log_buffer_->CreateFlushToState(1, kLogMaskAll);
+        EXPECT_TRUE(log_buffer_->FlushTo(test_writer.get(), *flush_to_state, nullptr));
+        EXPECT_EQ(7ULL, flush_to_state->start());
+    }
+    CompareLogMessages(after_clear_messages, read_log_messages_after_clear);
+}
+
+INSTANTIATE_TEST_CASE_P(LogBufferTests, LogBufferTest,
+                        testing::Values("chatty", "serialized", "simple"));
diff --git a/logd/LogBufferTest.h b/logd/LogBufferTest.h
new file mode 100644
index 0000000..eeeb980
--- /dev/null
+++ b/logd/LogBufferTest.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "ChattyLogBuffer.h"
+#include "LogReaderList.h"
+#include "LogStatistics.h"
+#include "LogTags.h"
+#include "PruneList.h"
+#include "SerializedLogBuffer.h"
+#include "SimpleLogBuffer.h"
+
+struct LogMessage {
+    logger_entry entry;
+    std::string message;
+    bool regex_compare = false;  // Only set for expected messages, when true 'message' should be
+                                 // interpretted as a regex.
+};
+
+// Compares the ordered list of expected and result, causing a test failure with appropriate
+// information on failure.
+void CompareLogMessages(const std::vector<LogMessage>& expected,
+                        const std::vector<LogMessage>& result);
+// Sets hdr_size and len parameters appropriately.
+void FixupMessages(std::vector<LogMessage>* messages);
+
+class TestWriter : public LogWriter {
+  public:
+    TestWriter(std::vector<LogMessage>* msgs, bool* released)
+        : LogWriter(0, true), msgs_(msgs), released_(released) {}
+    bool Write(const logger_entry& entry, const char* message) override {
+        msgs_->emplace_back(LogMessage{entry, std::string(message, entry.len), false});
+        return true;
+    }
+
+    void Release() {
+        if (released_) *released_ = true;
+    }
+
+    std::string name() const override { return "test_writer"; }
+
+  private:
+    std::vector<LogMessage>* msgs_;
+    bool* released_;
+};
+
+class LogBufferTest : public testing::TestWithParam<std::string> {
+  protected:
+    void SetUp() override {
+        if (GetParam() == "chatty") {
+            log_buffer_.reset(new ChattyLogBuffer(&reader_list_, &tags_, &prune_, &stats_));
+        } else if (GetParam() == "serialized") {
+            log_buffer_.reset(new SerializedLogBuffer(&reader_list_, &tags_, &stats_));
+        } else if (GetParam() == "simple") {
+            log_buffer_.reset(new SimpleLogBuffer(&reader_list_, &tags_, &stats_));
+        } else {
+            FAIL() << "Unknown buffer type selected for test";
+        }
+
+        log_id_for_each(i) { log_buffer_->SetSize(i, 1024 * 1024); }
+    }
+
+    void LogMessages(const std::vector<LogMessage>& messages) {
+        for (auto& [entry, message, _] : messages) {
+            log_buffer_->Log(static_cast<log_id_t>(entry.lid), log_time(entry.sec, entry.nsec),
+                             entry.uid, entry.pid, entry.tid, message.c_str(), message.size());
+        }
+    }
+
+    LogReaderList reader_list_;
+    LogTags tags_;
+    PruneList prune_;
+    LogStatistics stats_{false, true};
+    std::unique_ptr<LogBuffer> log_buffer_;
+};
diff --git a/logd/LogCommand.cpp b/logd/LogCommand.cpp
deleted file mode 100644
index 06d865c..0000000
--- a/logd/LogCommand.cpp
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2012-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 <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <private/android_filesystem_config.h>
-
-#include "LogCommand.h"
-
-LogCommand::LogCommand(const char *cmd) : FrameworkCommand(cmd) {
-}
-
-// gets a list of supplementary group IDs associated with
-// the socket peer.  This is implemented by opening
-// /proc/PID/status and look for the "Group:" line.
-//
-// This function introduces races especially since status
-// can change 'shape' while reading, the net result is err
-// on lack of permission.
-//
-// Race-free alternative is to introduce pairs of sockets
-// and threads for each command and reading, one each that
-// has open permissions, and one that has restricted
-// permissions.
-
-static bool groupIsLog(char *buf) {
-    char *ptr;
-    static const char ws[] = " \n";
-    bool ret = false;
-
-    for (buf = strtok_r(buf, ws, &ptr); buf; buf = strtok_r(NULL, ws, &ptr)) {
-        errno = 0;
-        gid_t Gid = strtol(buf, NULL, 10);
-        if (errno != 0) {
-            return false;
-        }
-        if (Gid == AID_LOG) {
-            ret = true;
-        }
-    }
-    return ret;
-}
-
-bool clientHasLogCredentials(SocketClient * cli) {
-    uid_t uid = cli->getUid();
-    if (uid == AID_ROOT) {
-        return true;
-    }
-
-    gid_t gid = cli->getGid();
-    if ((gid == AID_ROOT) || (gid == AID_SYSTEM) || (gid == AID_LOG)) {
-        return true;
-    }
-
-    // FYI We will typically be here for 'adb logcat'
-    bool ret = false;
-
-    char filename[1024];
-    snprintf(filename, sizeof(filename), "/proc/%d/status", cli->getPid());
-
-    FILE *file = fopen(filename, "r");
-    if (!file) {
-        return ret;
-    }
-
-    bool foundGid = false;
-    bool foundUid = false;
-
-    char line[1024];
-    while (fgets(line, sizeof(line), file)) {
-        static const char groups_string[] = "Groups:\t";
-        static const char uid_string[] = "Uid:\t";
-        static const char gid_string[] = "Gid:\t";
-
-        if (strncmp(groups_string, line, strlen(groups_string)) == 0) {
-            ret = groupIsLog(line + strlen(groups_string));
-            if (!ret) {
-                break;
-            }
-        } else if (strncmp(uid_string, line, strlen(uid_string)) == 0) {
-            uid_t u[4] = { (uid_t) -1, (uid_t) -1, (uid_t) -1, (uid_t) -1};
-
-            sscanf(line + strlen(uid_string), "%u\t%u\t%u\t%u",
-                   &u[0], &u[1], &u[2], &u[3]);
-
-            // Protect against PID reuse by checking that the UID is the same
-            if ((uid != u[0]) || (uid != u[1]) || (uid != u[2]) || (uid != u[3])) {
-                ret = false;
-                break;
-            }
-            foundUid = true;
-        } else if (strncmp(gid_string, line, strlen(gid_string)) == 0) {
-            gid_t g[4] = { (gid_t) -1, (gid_t) -1, (gid_t) -1, (gid_t) -1};
-
-            sscanf(line + strlen(gid_string), "%u\t%u\t%u\t%u",
-                   &g[0], &g[1], &g[2], &g[3]);
-
-            // Protect against PID reuse by checking that the GID is the same
-            if ((gid != g[0]) || (gid != g[1]) || (gid != g[2]) || (gid != g[3])) {
-                ret = false;
-                break;
-            }
-            foundGid = true;
-        }
-    }
-
-    fclose(file);
-
-    if (!foundGid || !foundUid) {
-        ret = false;
-    }
-
-    return ret;
-}
diff --git a/logd/LogKlog.cpp b/logd/LogKlog.cpp
index eff26f5..d6c7d25 100644
--- a/logd/LogKlog.cpp
+++ b/logd/LogKlog.cpp
@@ -14,180 +14,210 @@
  * limitations under the License.
  */
 
+#include "LogKlog.h"
+
 #include <ctype.h>
 #include <errno.h>
 #include <inttypes.h>
 #include <limits.h>
 #include <stdarg.h>
 #include <stdlib.h>
+#include <string.h>
 #include <sys/prctl.h>
 #include <sys/uio.h>
 #include <syslog.h>
 
-#include <log/logger.h>
+#include <private/android_filesystem_config.h>
+#include <private/android_logger.h>
 
-#include "LogKlog.h"
+#include "LogBuffer.h"
 
-#define KMSG_PRIORITY(PRI)           \
-    '<',                             \
-    '0' + (LOG_SYSLOG | (PRI)) / 10, \
-    '0' + (LOG_SYSLOG | (PRI)) % 10, \
-    '>'
+#define KMSG_PRIORITY(PRI) \
+    '<', '0' + (LOG_SYSLOG | (PRI)) / 10, '0' + (LOG_SYSLOG | (PRI)) % 10, '>'
 
 static const char priority_message[] = { KMSG_PRIORITY(LOG_INFO), '\0' };
 
+// List of the _only_ needles we supply here to android::strnstr
+static const char suspendStr[] = "PM: suspend entry ";
+static const char resumeStr[] = "PM: suspend exit ";
+static const char suspendedStr[] = "Suspended for ";
+static const char healthdStr[] = "healthd";
+static const char batteryStr[] = ": battery ";
+static const char auditStr[] = " audit(";
+static const char klogdStr[] = "logd.klogd: ";
+
 // Parsing is hard
 
 // called if we see a '<', s is the next character, returns pointer after '>'
-static char *is_prio(char *s) {
-    if (!isdigit(*s++)) {
-        return NULL;
-    }
-    static const size_t max_prio_len = 4;
-    size_t len = 0;
+static char* is_prio(char* s, ssize_t len) {
+    if ((len <= 0) || !isdigit(*s++)) return nullptr;
+    --len;
+    static const size_t max_prio_len = (len < 4) ? len : 4;
+    size_t priolen = 0;
     char c;
-    while (((c = *s++)) && (++len <= max_prio_len)) {
-        if (!isdigit(c)) {
-            return (c == '>') ? s : NULL;
-        }
+    while (((c = *s++)) && (++priolen <= max_prio_len)) {
+        if (!isdigit(c)) return ((c == '>') && (*s == '[')) ? s : nullptr;
     }
-    return NULL;
+    return nullptr;
 }
 
 // called if we see a '[', s is the next character, returns pointer after ']'
-static char *is_timestamp(char *s) {
-    while (*s == ' ') {
+static char* is_timestamp(char* s, ssize_t len) {
+    while ((len > 0) && (*s == ' ')) {
         ++s;
+        --len;
     }
-    if (!isdigit(*s++)) {
-        return NULL;
-    }
+    if ((len <= 0) || !isdigit(*s++)) return nullptr;
+    --len;
     bool first_period = true;
     char c;
-    while ((c = *s++)) {
+    while ((len > 0) && ((c = *s++))) {
+        --len;
         if ((c == '.') && first_period) {
             first_period = false;
         } else if (!isdigit(c)) {
-            return ((c == ']') && !first_period && (*s == ' ')) ? s : NULL;
+            return ((c == ']') && !first_period && (*s == ' ')) ? s : nullptr;
         }
     }
-    return NULL;
+    return nullptr;
 }
 
 // Like strtok_r with "\r\n" except that we look for log signatures (regex)
-//  \(\(<[0-9]\{1,4\}>\)\([[] *[0-9]+[.][0-9]+[]] \)\{0,1\}\|[[] *[0-9]+[.][0-9]+[]] \)
+//  \(\(<[0-9]\{1,4\}>\)\([[] *[0-9]+[.][0-9]+[]] \)\{0,1\}\|[[]
+//  *[0-9]+[.][0-9]+[]] \)
 // and split if we see a second one without a newline.
+// We allow nuls in content, monitoring the overall length and sub-length of
+// the discovered tokens.
 
-#define SIGNATURE_MASK     0xF0
+#define SIGNATURE_MASK 0xF0
 // <digit> following ('0' to '9' masked with ~SIGNATURE_MASK) added to signature
-#define LESS_THAN_SIG      SIGNATURE_MASK
-#define OPEN_BRACKET_SIG   ((SIGNATURE_MASK << 1) & SIGNATURE_MASK)
+#define LESS_THAN_SIG SIGNATURE_MASK
+#define OPEN_BRACKET_SIG ((SIGNATURE_MASK << 1) & SIGNATURE_MASK)
 // space is one more than <digit> of 9
 #define OPEN_BRACKET_SPACE ((char)(OPEN_BRACKET_SIG | 10))
 
-char *log_strtok_r(char *s, char **last) {
+char* android::log_strntok_r(char* s, ssize_t& len, char*& last,
+                             ssize_t& sublen) {
+    sublen = 0;
+    if (len <= 0) return nullptr;
     if (!s) {
-        if (!(s = *last)) {
-            return NULL;
-        }
+        if (!(s = last)) return nullptr;
         // fixup for log signature split <,
         // LESS_THAN_SIG + <digit>
         if ((*s & SIGNATURE_MASK) == LESS_THAN_SIG) {
             *s = (*s & ~SIGNATURE_MASK) + '0';
             *--s = '<';
+            ++len;
         }
         // fixup for log signature split [,
         // OPEN_BRACKET_SPACE is space, OPEN_BRACKET_SIG + <digit>
         if ((*s & SIGNATURE_MASK) == OPEN_BRACKET_SIG) {
-            if (*s == OPEN_BRACKET_SPACE) {
-                *s = ' ';
-            } else {
-                *s = (*s & ~SIGNATURE_MASK) + '0';
-            }
+            *s = (*s == OPEN_BRACKET_SPACE) ? ' ' : (*s & ~SIGNATURE_MASK) + '0';
             *--s = '[';
+            ++len;
         }
     }
 
-    s += strspn(s, "\r\n");
-
-    if (!*s) { // no non-delimiter characters
-        *last = NULL;
-        return NULL;
+    while ((len > 0) && ((*s == '\r') || (*s == '\n'))) {
+        ++s;
+        --len;
     }
+
+    if (len <= 0) return last = nullptr;
     char *peek, *tok = s;
 
     for (;;) {
-        char c = *s++;
-        switch (c) {
-        case '\0':
-            *last = NULL;
+        if (len <= 0) {
+            last = nullptr;
             return tok;
-
-        case '\r':
-        case '\n':
-            s[-1] = '\0';
-            *last = s;
-            return tok;
-
-        case '<':
-            peek = is_prio(s);
-            if (!peek) {
-                break;
-            }
-            if (s != (tok + 1)) { // not first?
-                s[-1] = '\0';
-                *s &= ~SIGNATURE_MASK;
-                *s |= LESS_THAN_SIG; // signature for '<'
-                *last = s;
-                return tok;
-            }
-            s = peek;
-            if ((*s == '[') && ((peek = is_timestamp(s + 1)))) {
-                s = peek;
-            }
-            break;
-
-        case '[':
-            peek = is_timestamp(s);
-            if (!peek) {
-                break;
-            }
-            if (s != (tok + 1)) { // not first?
-                s[-1] = '\0';
-                if (*s == ' ') {
-                    *s = OPEN_BRACKET_SPACE;
-                } else {
-                    *s &= ~SIGNATURE_MASK;
-                    *s |= OPEN_BRACKET_SIG; // signature for '['
-                }
-                *last = s;
-                return tok;
-            }
-            s = peek;
-            break;
         }
+        char c = *s++;
+        --len;
+        ssize_t adjust;
+        switch (c) {
+            case '\r':
+            case '\n':
+                s[-1] = '\0';
+                last = s;
+                return tok;
+
+            case '<':
+                peek = is_prio(s, len);
+                if (!peek) break;
+                if (s != (tok + 1)) {  // not first?
+                    s[-1] = '\0';
+                    *s &= ~SIGNATURE_MASK;
+                    *s |= LESS_THAN_SIG;  // signature for '<'
+                    last = s;
+                    return tok;
+                }
+                adjust = peek - s;
+                if (adjust > len) {
+                    adjust = len;
+                }
+                sublen += adjust;
+                len -= adjust;
+                s = peek;
+                if ((*s == '[') && ((peek = is_timestamp(s + 1, len - 1)))) {
+                    adjust = peek - s;
+                    if (adjust > len) {
+                        adjust = len;
+                    }
+                    sublen += adjust;
+                    len -= adjust;
+                    s = peek;
+                }
+                break;
+
+            case '[':
+                peek = is_timestamp(s, len);
+                if (!peek) break;
+                if (s != (tok + 1)) {  // not first?
+                    s[-1] = '\0';
+                    if (*s == ' ') {
+                        *s = OPEN_BRACKET_SPACE;
+                    } else {
+                        *s &= ~SIGNATURE_MASK;
+                        *s |= OPEN_BRACKET_SIG;  // signature for '['
+                    }
+                    last = s;
+                    return tok;
+                }
+                adjust = peek - s;
+                if (adjust > len) {
+                    adjust = len;
+                }
+                sublen += adjust;
+                len -= adjust;
+                s = peek;
+                break;
+        }
+        ++sublen;
     }
     // NOTREACHED
 }
 
-log_time LogKlog::correction = log_time(CLOCK_REALTIME) - log_time(CLOCK_MONOTONIC);
+log_time LogKlog::correction = (log_time(CLOCK_REALTIME) < log_time(CLOCK_MONOTONIC))
+                                       ? log_time(log_time::EPOCH)
+                                       : (log_time(CLOCK_REALTIME) - log_time(CLOCK_MONOTONIC));
 
-LogKlog::LogKlog(LogBuffer *buf, LogReader *reader, int fdWrite, int fdRead, bool auditd) :
-        SocketListener(fdRead, false),
-        logbuf(buf),
-        reader(reader),
-        signature(CLOCK_MONOTONIC),
-        initialized(false),
-        enableLogging(true),
-        auditd(auditd) {
-    static const char klogd_message[] = "%slogd.klogd: %" PRIu64 "\n";
-    char buffer[sizeof(priority_message) + sizeof(klogd_message) + 20 - 4];
-    snprintf(buffer, sizeof(buffer), klogd_message, priority_message,
-        signature.nsec());
+LogKlog::LogKlog(LogBuffer* buf, int fdWrite, int fdRead, bool auditd, LogStatistics* stats)
+    : SocketListener(fdRead, false),
+      logbuf(buf),
+      signature(CLOCK_MONOTONIC),
+      initialized(false),
+      enableLogging(true),
+      auditd(auditd),
+      stats_(stats) {
+    static const char klogd_message[] = "%s%s%" PRIu64 "\n";
+    char buffer[strlen(priority_message) + strlen(klogdStr) +
+                strlen(klogd_message) + 20];
+    snprintf(buffer, sizeof(buffer), klogd_message, priority_message, klogdStr,
+             signature.nsec());
     write(fdWrite, buffer, strlen(buffer));
 }
 
-bool LogKlog::onDataAvailable(SocketClient *cli) {
+bool LogKlog::onDataAvailable(SocketClient* cli) {
     if (!initialized) {
         prctl(PR_SET_NAME, "logd.klogd");
         initialized = true;
@@ -195,14 +225,15 @@
     }
 
     char buffer[LOGGER_ENTRY_MAX_PAYLOAD];
-    size_t len = 0;
+    ssize_t len = 0;
 
-    for(;;) {
+    for (;;) {
         ssize_t retval = 0;
-        if ((sizeof(buffer) - 1 - len) > 0) {
-            retval = read(cli->getSocket(), buffer + len, sizeof(buffer) - 1 - len);
+        if (len < (ssize_t)(sizeof(buffer) - 1)) {
+            retval =
+                read(cli->getSocket(), buffer + len, sizeof(buffer) - 1 - len);
         }
-        if ((retval == 0) && (len == 0)) {
+        if ((retval == 0) && (len <= 0)) {
             break;
         }
         if (retval < 0) {
@@ -210,19 +241,19 @@
         }
         len += retval;
         bool full = len == (sizeof(buffer) - 1);
-        char *ep = buffer + len;
+        char* ep = buffer + len;
         *ep = '\0';
-        len = 0;
-        for(char *ptr = NULL, *tok = buffer;
-                ((tok = log_strtok_r(tok, &ptr)));
-                tok = NULL) {
-            if (((tok + strlen(tok)) == ep) && (retval != 0) && full) {
-                len = strlen(tok);
-                memmove(buffer, tok, len);
+        ssize_t sublen;
+        for (char *ptr = nullptr, *tok = buffer;
+             !!(tok = android::log_strntok_r(tok, len, ptr, sublen));
+             tok = nullptr) {
+            if (((tok + sublen) >= ep) && (retval != 0) && full) {
+                if (sublen > 0) memmove(buffer, tok, sublen);
+                len = sublen;
                 break;
             }
-            if (*tok) {
-                log(tok);
+            if ((sublen > 0) && *tok) {
+                log(tok, sublen);
             }
         }
     }
@@ -230,11 +261,14 @@
     return true;
 }
 
+void LogKlog::calculateCorrection(const log_time& monotonic,
+                                  const char* real_string, ssize_t len) {
+    static const char real_format[] = "%Y-%m-%d %H:%M:%S.%09q UTC";
+    if (len < (ssize_t)(strlen(real_format) + 5)) return;
 
-void LogKlog::calculateCorrection(const log_time &monotonic,
-                                  const char *real_string) {
-    log_time real;
-    if (!real.strptime(real_string, "%Y-%m-%d %H:%M:%S.%09q UTC")) {
+    log_time real(log_time::EPOCH);
+    const char* ep = real.strptime(real_string, real_format);
+    if (!ep || (ep > &real_string[len]) || (real > log_time(CLOCK_REALTIME))) {
         return;
     }
     // kernel report UTC, log_time::strptime is localtime from calendar.
@@ -245,32 +279,72 @@
     memset(&tm, 0, sizeof(tm));
     tm.tm_isdst = -1;
     localtime_r(&now, &tm);
-    real.tv_sec += tm.tm_gmtoff;
-    correction = real - monotonic;
+    if ((tm.tm_gmtoff < 0) && ((-tm.tm_gmtoff) > (long)real.tv_sec)) {
+        real = log_time(log_time::EPOCH);
+    } else {
+        real.tv_sec += tm.tm_gmtoff;
+    }
+    if (monotonic > real) {
+        correction = log_time(log_time::EPOCH);
+    } else {
+        correction = real - monotonic;
+    }
 }
 
-void LogKlog::sniffTime(log_time &now, const char **buf, bool reverse) {
-    const char *cp;
-    if ((cp = now.strptime(*buf, "[ %s.%q]"))) {
-        static const char suspend[] = "PM: suspend entry ";
-        static const char resume[] = "PM: suspend exit ";
-        static const char suspended[] = "Suspended for ";
+log_time LogKlog::sniffTime(const char*& buf, ssize_t len, bool reverse) {
+    log_time now(log_time::EPOCH);
+    if (len <= 0) return now;
 
-        if (isspace(*cp)) {
+    const char* cp = nullptr;
+    if ((len > 10) && (*buf == '[')) {
+        cp = now.strptime(buf, "[ %s.%q]");  // can index beyond buffer bounds
+        if (cp && (cp > &buf[len - 1])) cp = nullptr;
+    }
+    if (cp) {
+        len -= cp - buf;
+        if ((len > 0) && isspace(*cp)) {
             ++cp;
+            --len;
         }
-        if (!strncmp(cp, suspend, sizeof(suspend) - 1)) {
-            calculateCorrection(now, cp + sizeof(suspend) - 1);
-        } else if (!strncmp(cp, resume, sizeof(resume) - 1)) {
-            calculateCorrection(now, cp + sizeof(resume) - 1);
-        } else if (!strncmp(cp, suspended, sizeof(suspended) - 1)) {
-            log_time real;
-            char *endp;
-            real.tv_sec = strtol(cp + sizeof(suspended) - 1, &endp, 10);
-            if (*endp == '.') {
-                real.tv_nsec = strtol(endp + 1, &endp, 10) * 1000000L;
+        buf = cp;
+
+        const char* b;
+        if (((b = android::strnstr(cp, len, suspendStr))) &&
+            (((b += strlen(suspendStr)) - cp) < len)) {
+            len -= b - cp;
+            calculateCorrection(now, b, len);
+        } else if (((b = android::strnstr(cp, len, resumeStr))) &&
+                   (((b += strlen(resumeStr)) - cp) < len)) {
+            len -= b - cp;
+            calculateCorrection(now, b, len);
+        } else if (((b = android::strnstr(cp, len, healthdStr))) &&
+                   (((b += strlen(healthdStr)) - cp) < len) &&
+                   ((b = android::strnstr(b, len -= b - cp, batteryStr))) &&
+                   (((b += strlen(batteryStr)) - cp) < len)) {
+            // NB: healthd is roughly 150us late, so we use it instead to
+            //     trigger a check for ntp-induced or hardware clock drift.
+            log_time real(CLOCK_REALTIME);
+            log_time mono(CLOCK_MONOTONIC);
+            correction = (real < mono) ? log_time(log_time::EPOCH) : (real - mono);
+        } else if (((b = android::strnstr(cp, len, suspendedStr))) &&
+                   (((b += strlen(suspendStr)) - cp) < len)) {
+            len -= b - cp;
+            log_time real(log_time::EPOCH);
+            char* endp;
+            real.tv_sec = strtol(b, &endp, 10);
+            if ((*endp == '.') && ((endp - b) < len)) {
+                unsigned long multiplier = NS_PER_SEC;
+                real.tv_nsec = 0;
+                len -= endp - b;
+                while (--len && isdigit(*++endp) && (multiplier /= 10)) {
+                    real.tv_nsec += (*endp - '0') * multiplier;
+                }
                 if (reverse) {
-                    correction -= real;
+                    if (real > correction) {
+                        correction = log_time(log_time::EPOCH);
+                    } else {
+                        correction -= real;
+                    }
                 } else {
                     correction += real;
                 }
@@ -278,90 +352,114 @@
         }
 
         convertMonotonicToReal(now);
-        *buf = cp;
     } else {
         now = log_time(CLOCK_REALTIME);
     }
+    return now;
 }
 
-// Passed the entire SYSLOG_ACTION_READ_ALL buffer and interpret a
-// compensated start time.
-void LogKlog::synchronize(const char *buf) {
-    const char *cp = strstr(buf, "] PM: suspend e");
-    if (!cp) {
-        return;
+pid_t LogKlog::sniffPid(const char*& buf, ssize_t len) {
+    if (len <= 0) return 0;
+
+    const char* cp = buf;
+    // sscanf does a strlen, let's check if the string is not nul terminated.
+    // pseudo out-of-bounds access since we always have an extra char on buffer.
+    if (((ssize_t)strnlen(cp, len) == len) && cp[len]) {
+        return 0;
     }
-
-    do {
-        --cp;
-    } while ((cp > buf) && (isdigit(*cp) || isspace(*cp) || (*cp == '.')));
-
-    log_time now;
-    sniffTime(now, &cp, true);
-
-    char *suspended = strstr(buf, "] Suspended for ");
-    if (!suspended || (suspended > cp)) {
-        return;
+    // HTC kernels with modified printk "c0   1648 "
+    if ((len > 9) && (cp[0] == 'c') && isdigit(cp[1]) &&
+        (isdigit(cp[2]) || (cp[2] == ' ')) && (cp[3] == ' ')) {
+        bool gotDigit = false;
+        int i;
+        for (i = 4; i < 9; ++i) {
+            if (isdigit(cp[i])) {
+                gotDigit = true;
+            } else if (gotDigit || (cp[i] != ' ')) {
+                break;
+            }
+        }
+        if ((i == 9) && (cp[i] == ' ')) {
+            int pid = 0;
+            char placeholder;
+            if (sscanf(cp + 4, "%d%c", &pid, &placeholder) == 2) {
+                buf = cp + 10;  // skip-it-all
+                return pid;
+            }
+        }
     }
-    cp = suspended;
-
-    do {
-        --cp;
-    } while ((cp > buf) && (isdigit(*cp) || isspace(*cp) || (*cp == '.')));
-
-    sniffTime(now, &cp, true);
+    while (len) {
+        // Mediatek kernels with modified printk
+        if (*cp == '[') {
+            int pid = 0;
+            char placeholder;
+            if (sscanf(cp, "[%d:%*[a-z_./0-9:A-Z]]%c", &pid, &placeholder) == 2) {
+                return pid;
+            }
+            break;  // Only the first one
+        }
+        ++cp;
+        --len;
+    }
+    return 0;
 }
 
 // kernel log prefix, convert to a kernel log priority number
-static int parseKernelPrio(const char **buf) {
+static int parseKernelPrio(const char*& buf, ssize_t len) {
     int pri = LOG_USER | LOG_INFO;
-    const char *cp = *buf;
-    if (*cp == '<') {
+    const char* cp = buf;
+    if ((len > 0) && (*cp == '<')) {
         pri = 0;
-        while(isdigit(*++cp)) {
+        while (--len && isdigit(*++cp)) {
             pri = (pri * 10) + *cp - '0';
         }
-        if (*cp == '>') {
+        if ((len > 0) && (*cp == '>')) {
             ++cp;
         } else {
-            cp = *buf;
+            cp = buf;
             pri = LOG_USER | LOG_INFO;
         }
-        *buf = cp;
+        buf = cp;
     }
     return pri;
 }
 
 // Convert kernel log priority number into an Android Logger priority number
 static int convertKernelPrioToAndroidPrio(int pri) {
-    switch(pri & LOG_PRIMASK) {
-    case LOG_EMERG:
-        // FALLTHRU
-    case LOG_ALERT:
-        // FALLTHRU
-    case LOG_CRIT:
-        return ANDROID_LOG_FATAL;
+    switch (pri & LOG_PRIMASK) {
+        case LOG_EMERG:
+        case LOG_ALERT:
+        case LOG_CRIT:
+            return ANDROID_LOG_FATAL;
 
-    case LOG_ERR:
-        return ANDROID_LOG_ERROR;
+        case LOG_ERR:
+            return ANDROID_LOG_ERROR;
 
-    case LOG_WARNING:
-        return ANDROID_LOG_WARN;
+        case LOG_WARNING:
+            return ANDROID_LOG_WARN;
 
-    default:
-        // FALLTHRU
-    case LOG_NOTICE:
-        // FALLTHRU
-    case LOG_INFO:
-        break;
+        default:
+        case LOG_NOTICE:
+        case LOG_INFO:
+            break;
 
-    case LOG_DEBUG:
-        return ANDROID_LOG_DEBUG;
+        case LOG_DEBUG:
+            return ANDROID_LOG_DEBUG;
     }
 
     return ANDROID_LOG_INFO;
 }
 
+static const char* strnrchr(const char* s, ssize_t len, char c) {
+    const char* save = nullptr;
+    for (; len > 0; ++s, len--) {
+        if (*s == c) {
+            save = s;
+        }
+    }
+    return save;
+}
+
 //
 // log a message into the kernel log buffer
 //
@@ -395,21 +493,20 @@
 //  logd.klogd:
 // return -1 if message logd.klogd: <signature>
 //
-int LogKlog::log(const char *buf) {
-    if (auditd && strstr(buf, " audit(")) {
+int LogKlog::log(const char* buf, ssize_t len) {
+    if (auditd && android::strnstr(buf, len, auditStr)) {
         return 0;
     }
 
-    int pri = parseKernelPrio(&buf);
+    const char* p = buf;
+    int pri = parseKernelPrio(p, len);
 
-    log_time now;
-    sniffTime(now, &buf, false);
+    log_time now = sniffTime(p, len - (p - buf), false);
 
     // sniff for start marker
-    const char klogd_message[] = "logd.klogd: ";
-    if (!strncmp(buf, klogd_message, sizeof(klogd_message) - 1)) {
-        char *endp;
-        uint64_t sig = strtoll(buf + sizeof(klogd_message) - 1, &endp, 10);
+    const char* start = android::strnstr(p, len - (p - buf), klogdStr);
+    if (start) {
+        uint64_t sig = strtoll(start + strlen(klogdStr), nullptr, 10);
         if (sig == signature.nsec()) {
             if (initialized) {
                 enableLogging = true;
@@ -425,177 +522,251 @@
         return 0;
     }
 
-    // Parse pid, tid and uid (not possible)
-    const pid_t pid = 0;
-    const pid_t tid = 0;
-    const uid_t uid = 0;
+    // Parse pid, tid and uid
+    const pid_t pid = sniffPid(p, len - (p - buf));
+    const pid_t tid = pid;
+    uid_t uid = AID_ROOT;
+    if (pid) {
+        uid = stats_->PidToUid(pid);
+    }
 
     // Parse (rules at top) to pull out a tag from the incoming kernel message.
     // Some may view the following as an ugly heuristic, the desire is to
     // beautify the kernel logs into an Android Logging format; the goal is
     // admirable but costly.
-    while (isspace(*buf)) {
-        ++buf;
+    while ((p < &buf[len]) && (isspace(*p) || !*p)) {
+        ++p;
     }
-    if (!*buf) {
+    if (p >= &buf[len]) {  // timestamp, no content
         return 0;
     }
-    const char *start = buf;
-    const char *tag = "";
-    const char *etag = tag;
-    if (!isspace(*buf)) {
-        const char *bt, *et, *cp;
+    start = p;
+    const char* tag = "";
+    const char* etag = tag;
+    ssize_t taglen = len - (p - buf);
+    const char* bt = p;
 
-        bt = buf;
-        if (!strncmp(buf, "[INFO]", 6)) {
-            // <PRI>[<TIME>] "[INFO]"<tag> ":" message
-            bt = buf + 6;
+    static const char infoBrace[] = "[INFO]";
+    static const ssize_t infoBraceLen = strlen(infoBrace);
+    if ((taglen >= infoBraceLen) &&
+        !fastcmp<strncmp>(p, infoBrace, infoBraceLen)) {
+        // <PRI>[<TIME>] "[INFO]"<tag> ":" message
+        bt = p + infoBraceLen;
+        taglen -= infoBraceLen;
+    }
+
+    const char* et;
+    for (et = bt; (taglen > 0) && *et && (*et != ':') && !isspace(*et);
+         ++et, --taglen) {
+        // skip ':' within [ ... ]
+        if (*et == '[') {
+            while ((taglen > 0) && *et && *et != ']') {
+                ++et;
+                --taglen;
+            }
+            if (taglen <= 0) {
+                break;
+            }
         }
-        for(et = bt; *et && (*et != ':') && !isspace(*et); ++et);
-        for(cp = et; isspace(*cp); ++cp);
-        size_t size;
+    }
+    const char* cp;
+    for (cp = et; (taglen > 0) && isspace(*cp); ++cp, --taglen) {
+    }
 
+    // Validate tag
+    ssize_t size = et - bt;
+    if ((taglen > 0) && (size > 0)) {
         if (*cp == ':') {
+            // ToDo: handle case insensitive colon separated logging stutter:
+            //       <tag> : <tag>: ...
+
             // One Word
             tag = bt;
             etag = et;
-            buf = cp + 1;
-        } else {
-            size = et - bt;
-            if (strncmp(bt, cp, size)) {
-                // <PRI>[<TIME>] <tag>_host '<tag>.<num>' : message
-                if (!strncmp(bt + size - 5, "_host", 5)
-                        && !strncmp(bt, cp, size - 5)) {
-                    const char *b = cp;
-                    cp += size - 5;
+            p = cp + 1;
+        } else if ((taglen > size) && (tolower(*bt) == tolower(*cp))) {
+            // clean up any tag stutter
+            if (!fastcmp<strncasecmp>(bt + 1, cp + 1, size - 1)) {  // no match
+                // <PRI>[<TIME>] <tag> <tag> : message
+                // <PRI>[<TIME>] <tag> <tag>: message
+                // <PRI>[<TIME>] <tag> '<tag>.<num>' : message
+                // <PRI>[<TIME>] <tag> '<tag><num>' : message
+                // <PRI>[<TIME>] <tag> '<tag><stuff>' : message
+                const char* b = cp;
+                cp += size;
+                taglen -= size;
+                while ((--taglen > 0) && !isspace(*++cp) && (*cp != ':')) {
+                }
+                const char* e;
+                for (e = cp; (taglen > 0) && isspace(*cp); ++cp, --taglen) {
+                }
+                if ((taglen > 0) && (*cp == ':')) {
+                    tag = b;
+                    etag = e;
+                    p = cp + 1;
+                }
+            } else {
+                // what about <PRI>[<TIME>] <tag>_host '<tag><stuff>' : message
+                static const char host[] = "_host";
+                static const ssize_t hostlen = strlen(host);
+                if ((size > hostlen) &&
+                    !fastcmp<strncmp>(bt + size - hostlen, host, hostlen) &&
+                    !fastcmp<strncmp>(bt + 1, cp + 1, size - hostlen - 1)) {
+                    const char* b = cp;
+                    cp += size - hostlen;
+                    taglen -= size - hostlen;
                     if (*cp == '.') {
-                        while (!isspace(*++cp) && (*cp != ':'));
-                        const char *e;
-                        for(e = cp; isspace(*cp); ++cp);
-                        if (*cp == ':') {
+                        while ((--taglen > 0) && !isspace(*++cp) &&
+                               (*cp != ':')) {
+                        }
+                        const char* e;
+                        for (e = cp; (taglen > 0) && isspace(*cp);
+                             ++cp, --taglen) {
+                        }
+                        if ((taglen > 0) && (*cp == ':')) {
                             tag = b;
                             etag = e;
-                            buf = cp + 1;
+                            p = cp + 1;
                         }
                     }
                 } else {
-                    while (!isspace(*++cp) && (*cp != ':'));
-                    const char *e;
-                    for(e = cp; isspace(*cp); ++cp);
-                    // Two words
-                    if (*cp == ':') {
-                        tag = bt;
-                        etag = e;
-                        buf = cp + 1;
-                    }
-                }
-            } else if (isspace(cp[size])) {
-                cp += size;
-                while (isspace(*++cp));
-                // <PRI>[<TIME>] <tag> <tag> : message
-                if (*cp == ':') {
-                    tag = bt;
-                    etag = et;
-                    buf = cp + 1;
-                }
-            } else if (cp[size] == ':') {
-                // <PRI>[<TIME>] <tag> <tag> : message
-                tag = bt;
-                etag = et;
-                buf = cp + size + 1;
-            } else if ((cp[size] == '.') || isdigit(cp[size])) {
-                // <PRI>[<TIME>] <tag> '<tag>.<num>' : message
-                // <PRI>[<TIME>] <tag> '<tag><num>' : message
-                const char *b = cp;
-                cp += size;
-                while (!isspace(*++cp) && (*cp != ':'));
-                const char *e = cp;
-                while (isspace(*cp)) {
-                    ++cp;
-                }
-                if (*cp == ':') {
-                    tag = b;
-                    etag = e;
-                    buf = cp + 1;
-                }
-            } else {
-                while (!isspace(*++cp) && (*cp != ':'));
-                const char *e = cp;
-                while (isspace(*cp)) {
-                    ++cp;
-                }
-                // Two words
-                if (*cp == ':') {
-                    tag = bt;
-                    etag = e;
-                    buf = cp + 1;
+                    goto twoWord;
                 }
             }
+        } else {
+        // <PRI>[<TIME>] <tag> <stuff>' : message
+        twoWord:
+            while ((--taglen > 0) && !isspace(*++cp) && (*cp != ':')) {
+            }
+            const char* e;
+            for (e = cp; (taglen > 0) && isspace(*cp); ++cp, --taglen) {
+            }
+            // Two words
+            if ((taglen > 0) && (*cp == ':')) {
+                tag = bt;
+                etag = e;
+                p = cp + 1;
+            }
         }
-        size = etag - tag;
-        if ((size <= 1)
-            // register names like x9
-                || ((size == 2) && (isdigit(tag[0]) || isdigit(tag[1])))
-            // register names like x18 but not driver names like en0
-                || ((size == 3) && (isdigit(tag[1]) && isdigit(tag[2])))
-            // blacklist
-                || ((size == 3) && !strncmp(tag, "CPU", 3))
-                || ((size == 7) && !strncmp(tag, "WARNING", 7))
-                || ((size == 5) && !strncmp(tag, "ERROR", 5))
-                || ((size == 4) && !strncmp(tag, "INFO", 4))) {
-            buf = start;
-            etag = tag = "";
+    }  // else no tag
+
+    static const char cpu[] = "CPU";
+    static const ssize_t cpuLen = strlen(cpu);
+    static const char warning[] = "WARNING";
+    static const ssize_t warningLen = strlen(warning);
+    static const char error[] = "ERROR";
+    static const ssize_t errorLen = strlen(error);
+    static const char info[] = "INFO";
+    static const ssize_t infoLen = strlen(info);
+
+    size = etag - tag;
+    if ((size <= 1) ||
+        // register names like x9
+        ((size == 2) && (isdigit(tag[0]) || isdigit(tag[1]))) ||
+        // register names like x18 but not driver names like en0
+        ((size == 3) && (isdigit(tag[1]) && isdigit(tag[2]))) ||
+        // ignore
+        ((size == cpuLen) && !fastcmp<strncmp>(tag, cpu, cpuLen)) ||
+        ((size == warningLen) && !fastcmp<strncasecmp>(tag, warning, warningLen)) ||
+        ((size == errorLen) && !fastcmp<strncasecmp>(tag, error, errorLen)) ||
+        ((size == infoLen) && !fastcmp<strncasecmp>(tag, info, infoLen))) {
+        p = start;
+        etag = tag = "";
+    }
+
+    // Suppress additional stutter in tag:
+    //   eg: [143:healthd]healthd -> [143:healthd]
+    taglen = etag - tag;
+    // Mediatek-special printk induced stutter
+    const char* mp = strnrchr(tag, taglen, ']');
+    if (mp && (++mp < etag)) {
+        ssize_t s = etag - mp;
+        if (((s + s) < taglen) && !fastcmp<memcmp>(mp, mp - 1 - s, s)) {
+            taglen = mp - tag;
         }
     }
-    size_t l = etag - tag;
+    // Deal with sloppy and simplistic harmless p = cp + 1 etc above.
+    if (len < (p - buf)) {
+        p = &buf[len];
+    }
     // skip leading space
-    while (isspace(*buf)) {
-        ++buf;
+    while ((p < &buf[len]) && (isspace(*p) || !*p)) {
+        ++p;
     }
-    // truncate trailing space
-    size_t b = strlen(buf);
-    while (b && isspace(buf[b-1])) {
+    // truncate trailing space or nuls
+    ssize_t b = len - (p - buf);
+    while ((b > 0) && (isspace(p[b - 1]) || !p[b - 1])) {
         --b;
     }
     // trick ... allow tag with empty content to be logged. log() drops empty
-    if (!b && l) {
-        buf = " ";
+    if ((b <= 0) && (taglen > 0)) {
+        p = " ";
         b = 1;
     }
-    size_t n = 1 + l + 1 + b + 1;
-
-    // Allocate a buffer to hold the interpreted log message
-    int rc = n;
-    char *newstr = reinterpret_cast<char *>(malloc(n));
-    if (!newstr) {
-        rc = -ENOMEM;
-        return rc;
+    // This shouldn't happen, but clamp the size if it does.
+    if (b > LOGGER_ENTRY_MAX_PAYLOAD) {
+        b = LOGGER_ENTRY_MAX_PAYLOAD;
     }
-    char *np = newstr;
+    if (taglen > LOGGER_ENTRY_MAX_PAYLOAD) {
+        taglen = LOGGER_ENTRY_MAX_PAYLOAD;
+    }
+    // calculate buffer copy requirements
+    ssize_t n = 1 + taglen + 1 + b + 1;
+    // Extra checks for likely impossible cases.
+    if ((taglen > n) || (b > n) || (n > (ssize_t)USHRT_MAX) || (n <= 0)) {
+        return -EINVAL;
+    }
+
+    // Careful.
+    // We are using the stack to house the log buffer for speed reasons.
+    // If we malloc'd this buffer, we could get away without n's USHRT_MAX
+    // test above, but we would then required a max(n, USHRT_MAX) as
+    // truncating length argument to logbuf->log() below. Gain is protection
+    // against stack corruption and speedup, loss is truncated long-line content.
+    char newstr[n];
+    char* np = newstr;
 
     // Convert priority into single-byte Android logger priority
     *np = convertKernelPrioToAndroidPrio(pri);
     ++np;
 
     // Copy parsed tag following priority
-    strncpy(np, tag, l);
-    np += l;
+    memcpy(np, tag, taglen);
+    np += taglen;
     *np = '\0';
     ++np;
 
     // Copy main message to the remainder
-    strncpy(np, buf, b);
+    memcpy(np, p, b);
     np[b] = '\0';
 
-    // Log message
-    rc = logbuf->log(LOG_ID_KERNEL, now, uid, pid, tid, newstr,
-                     (n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX);
-    free(newstr);
+    {
+        // Watch out for singular race conditions with timezone causing near
+        // integer quarter-hour jumps in the time and compensate accordingly.
+        // Entries will be temporal within near_seconds * 2. b/21868540
+        static uint32_t vote_time[3];
+        vote_time[2] = vote_time[1];
+        vote_time[1] = vote_time[0];
+        vote_time[0] = now.tv_sec;
 
-    // notify readers
-    if (!rc) {
-        reader->notifyNewLog();
+        if (vote_time[1] && vote_time[2]) {
+            static const unsigned near_seconds = 10;
+            static const unsigned timezones_seconds = 900;
+            int diff0 = (vote_time[0] - vote_time[1]) / near_seconds;
+            unsigned abs0 = (diff0 < 0) ? -diff0 : diff0;
+            int diff1 = (vote_time[1] - vote_time[2]) / near_seconds;
+            unsigned abs1 = (diff1 < 0) ? -diff1 : diff1;
+            if ((abs1 <= 1) &&  // last two were in agreement on timezone
+                ((abs0 + 1) % (timezones_seconds / near_seconds)) <= 2) {
+                abs0 = (abs0 + 1) / (timezones_seconds / near_seconds) *
+                       timezones_seconds;
+                now.tv_sec -= (diff0 < 0) ? -abs0 : abs0;
+            }
+        }
     }
 
+    // Log message
+    int rc = logbuf->Log(LOG_ID_KERNEL, now, uid, pid, tid, newstr, (uint16_t)n);
+
     return rc;
 }
diff --git a/logd/LogKlog.h b/logd/LogKlog.h
index 24b2685..56e0452 100644
--- a/logd/LogKlog.h
+++ b/logd/LogKlog.h
@@ -14,18 +14,16 @@
  * limitations under the License.
  */
 
-#ifndef _LOGD_LOG_KLOG_H__
-#define _LOGD_LOG_KLOG_H__
+#pragma once
 
+#include <private/android_logger.h>
 #include <sysutils/SocketListener.h>
-#include <log/log_read.h>
-#include "LogReader.h"
 
-char *log_strtok_r(char *str, char **saveptr);
+#include "LogBuffer.h"
+#include "LogStatistics.h"
 
 class LogKlog : public SocketListener {
-    LogBuffer *logbuf;
-    LogReader *reader;
+    LogBuffer* logbuf;
     const log_time signature;
     // Set once thread is started, separates KLOG_ACTION_READ_ALL
     // and KLOG_ACTION_READ phases.
@@ -38,18 +36,18 @@
 
     static log_time correction;
 
-public:
-    LogKlog(LogBuffer *buf, LogReader *reader, int fdWrite, int fdRead, bool auditd);
-    int log(const char *buf);
-    void synchronize(const char *buf);
+  public:
+    LogKlog(LogBuffer* buf, int fdWrite, int fdRead, bool auditd, LogStatistics* stats);
+    int log(const char* buf, ssize_t len);
 
-    static void convertMonotonicToReal(log_time &real) { real += correction; }
+    static void convertMonotonicToReal(log_time& real) { real += correction; }
 
-protected:
-    void sniffTime(log_time &now, const char **buf, bool reverse);
-    void calculateCorrection(const log_time &monotonic, const char *real_string);
-    virtual bool onDataAvailable(SocketClient *cli);
+  protected:
+    log_time sniffTime(const char*& buf, ssize_t len, bool reverse);
+    pid_t sniffPid(const char*& buf, ssize_t len);
+    void calculateCorrection(const log_time& monotonic, const char* real_string, ssize_t len);
+    virtual bool onDataAvailable(SocketClient* cli);
 
+  private:
+    LogStatistics* stats_;
 };
-
-#endif
diff --git a/logd/LogListener.cpp b/logd/LogListener.cpp
index b29f5ab..a6ab50b 100644
--- a/logd/LogListener.cpp
+++ b/logd/LogListener.cpp
@@ -15,111 +15,123 @@
  */
 
 #include <limits.h>
+#include <sys/cdefs.h>
 #include <sys/prctl.h>
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <sys/un.h>
 #include <unistd.h>
 
+#include <thread>
+
 #include <cutils/sockets.h>
-#include <log/logger.h>
 #include <private/android_filesystem_config.h>
 #include <private/android_logger.h>
 
+#include "LogBuffer.h"
 #include "LogListener.h"
+#include "LogPermissions.h"
 
-LogListener::LogListener(LogBuffer *buf, LogReader *reader) :
-        SocketListener(getLogSocket(), false),
-        logbuf(buf),
-        reader(reader) {
-}
+LogListener::LogListener(LogBuffer* buf) : socket_(GetLogSocket()), logbuf_(buf) {}
 
-bool LogListener::onDataAvailable(SocketClient *cli) {
-    static bool name_set;
-    if (!name_set) {
-        prctl(PR_SET_NAME, "logd.writer");
-        name_set = true;
-    }
-
-    char buffer[sizeof_log_id_t + sizeof(uint16_t) + sizeof(log_time)
-        + LOGGER_ENTRY_MAX_PAYLOAD];
-    struct iovec iov = { buffer, sizeof(buffer) };
-    memset(buffer, 0, sizeof(buffer));
-
-    char control[CMSG_SPACE(sizeof(struct ucred))];
-    struct msghdr hdr = {
-        NULL,
-        0,
-        &iov,
-        1,
-        control,
-        sizeof(control),
-        0,
-    };
-
-    int socket = cli->getSocket();
-
-    ssize_t n = recvmsg(socket, &hdr, 0);
-    if (n <= (ssize_t)(sizeof(android_log_header_t))) {
+bool LogListener::StartListener() {
+    if (socket_ <= 0) {
         return false;
     }
+    auto thread = std::thread(&LogListener::ThreadFunction, this);
+    thread.detach();
+    return true;
+}
 
-    struct ucred *cred = NULL;
+void LogListener::ThreadFunction() {
+    prctl(PR_SET_NAME, "logd.writer");
 
-    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&hdr);
-    while (cmsg != NULL) {
-        if (cmsg->cmsg_level == SOL_SOCKET
-                && cmsg->cmsg_type  == SCM_CREDENTIALS) {
-            cred = (struct ucred *)CMSG_DATA(cmsg);
+    while (true) {
+        HandleData();
+    }
+}
+
+void LogListener::HandleData() {
+    // + 1 to ensure null terminator if MAX_PAYLOAD buffer is received
+    __attribute__((uninitialized)) char
+            buffer[sizeof(android_log_header_t) + LOGGER_ENTRY_MAX_PAYLOAD + 1];
+    struct iovec iov = {buffer, sizeof(buffer) - 1};
+
+    alignas(4) char control[CMSG_SPACE(sizeof(struct ucred))];
+    struct msghdr hdr = {
+        nullptr, 0, &iov, 1, control, sizeof(control), 0,
+    };
+
+    // To clear the entire buffer is secure/safe, but this contributes to 1.68%
+    // overhead under logging load. We are safe because we check counts, but
+    // still need to clear null terminator
+    // memset(buffer, 0, sizeof(buffer));
+    ssize_t n = recvmsg(socket_, &hdr, 0);
+    if (n <= (ssize_t)(sizeof(android_log_header_t))) {
+        return;
+    }
+
+    buffer[n] = 0;
+
+    struct ucred* cred = nullptr;
+
+    struct cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr);
+    while (cmsg != nullptr) {
+        if (cmsg->cmsg_level == SOL_SOCKET &&
+            cmsg->cmsg_type == SCM_CREDENTIALS) {
+            cred = (struct ucred*)CMSG_DATA(cmsg);
             break;
         }
         cmsg = CMSG_NXTHDR(&hdr, cmsg);
     }
 
-    if (cred == NULL) {
-        return false;
+    if (cred == nullptr) {
+        return;
     }
 
     if (cred->uid == AID_LOGD) {
         // ignore log messages we send to ourself.
         // Such log messages are often generated by libraries we depend on
         // which use standard Android logging.
-        return false;
+        return;
     }
 
-    android_log_header_t *header = reinterpret_cast<android_log_header_t *>(buffer);
-    if (/* header->id < LOG_ID_MIN || */ header->id >= LOG_ID_MAX || header->id == LOG_ID_KERNEL) {
-        return false;
+    android_log_header_t* header =
+        reinterpret_cast<android_log_header_t*>(buffer);
+    log_id_t logId = static_cast<log_id_t>(header->id);
+    if (/* logId < LOG_ID_MIN || */ logId >= LOG_ID_MAX ||
+        logId == LOG_ID_KERNEL) {
+        return;
     }
 
-    char *msg = ((char *)buffer) + sizeof(android_log_header_t);
+    if ((logId == LOG_ID_SECURITY) &&
+        (!__android_log_security() ||
+         !clientHasLogCredentials(cred->uid, cred->gid, cred->pid))) {
+        return;
+    }
+
+    char* msg = ((char*)buffer) + sizeof(android_log_header_t);
     n -= sizeof(android_log_header_t);
 
     // NB: hdr.msg_flags & MSG_TRUNC is not tested, silently passing a
     // truncated message to the logs.
 
-    if (logbuf->log((log_id_t)header->id, header->realtime,
-            cred->uid, cred->pid, header->tid, msg,
-            ((size_t) n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX) >= 0) {
-        reader->notifyNewLog();
-    }
-
-    return true;
+    logbuf_->Log(logId, header->realtime, cred->uid, cred->pid, header->tid, msg,
+                 ((size_t)n <= UINT16_MAX) ? (uint16_t)n : UINT16_MAX);
 }
 
-int LogListener::getLogSocket() {
+int LogListener::GetLogSocket() {
     static const char socketName[] = "logdw";
     int sock = android_get_control_socket(socketName);
 
-    if (sock < 0) {
-        sock = socket_local_server(socketName,
-                                   ANDROID_SOCKET_NAMESPACE_RESERVED,
-                                   SOCK_DGRAM);
-    }
+    if (sock < 0) {  // logd started up in init.sh
+        sock = socket_local_server(
+            socketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_DGRAM);
 
-    int on = 1;
-    if (setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
-        return -1;
+        int on = 1;
+        if (setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on))) {
+            return -1;
+        }
     }
     return sock;
 }
diff --git a/logd/LogListener.h b/logd/LogListener.h
index 7099e13..566af5b 100644
--- a/logd/LogListener.h
+++ b/logd/LogListener.h
@@ -14,24 +14,20 @@
  * limitations under the License.
  */
 
-#ifndef _LOGD_LOG_LISTENER_H__
-#define _LOGD_LOG_LISTENER_H__
+#pragma once
 
-#include <sysutils/SocketListener.h>
-#include "LogReader.h"
+#include "LogBuffer.h"
 
-class LogListener : public SocketListener {
-    LogBuffer *logbuf;
-    LogReader *reader;
+class LogListener {
+  public:
+    explicit LogListener(LogBuffer* buf);
+    bool StartListener();
 
-public:
-    LogListener(LogBuffer *buf, LogReader *reader);
+  private:
+    void ThreadFunction();
+    void HandleData();
+    static int GetLogSocket();
 
-protected:
-    virtual bool onDataAvailable(SocketClient *cli);
-
-private:
-    static int getLogSocket();
+    int socket_;
+    LogBuffer* logbuf_;
 };
-
-#endif
diff --git a/logd/LogPermissions.cpp b/logd/LogPermissions.cpp
new file mode 100644
index 0000000..3fd0ea1
--- /dev/null
+++ b/logd/LogPermissions.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2012-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 "LogPermissions.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <private/android_filesystem_config.h>
+
+// gets a list of supplementary group IDs associated with
+// the socket peer.  This is implemented by opening
+// /proc/PID/status and look for the "Group:" line.
+//
+// This function introduces races especially since status
+// can change 'shape' while reading, the net result is err
+// on lack of permission.
+//
+// Race-free alternative is to introduce pairs of sockets
+// and threads for each command and reading, one each that
+// has open permissions, and one that has restricted
+// permissions.
+
+static bool groupIsLog(char* buf) {
+    char* ptr;
+    static const char ws[] = " \n";
+
+    for (buf = strtok_r(buf, ws, &ptr); buf; buf = strtok_r(nullptr, ws, &ptr)) {
+        errno = 0;
+        gid_t Gid = strtol(buf, nullptr, 10);
+        if (errno != 0) {
+            return false;
+        }
+        if (Gid == AID_LOG) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool clientHasLogCredentials(uid_t uid, gid_t gid, pid_t pid) {
+    if ((uid == AID_ROOT) || (uid == AID_SYSTEM) || (uid == AID_LOG)) {
+        return true;
+    }
+
+    if ((gid == AID_ROOT) || (gid == AID_SYSTEM) || (gid == AID_LOG)) {
+        return true;
+    }
+
+    // FYI We will typically be here for 'adb logcat'
+    char filename[256];
+    snprintf(filename, sizeof(filename), "/proc/%u/status", pid);
+
+    bool ret;
+    bool foundLog = false;
+    bool foundGid = false;
+    bool foundUid = false;
+
+    //
+    // Reading /proc/<pid>/status is rife with race conditions. All of /proc
+    // suffers from this and its use should be minimized. However, we have no
+    // choice.
+    //
+    // Notably the content from one 4KB page to the next 4KB page can be from a
+    // change in shape even if we are gracious enough to attempt to read
+    // atomically. getline can not even guarantee a page read is not split up
+    // and in effect can read from different vintages of the content.
+    //
+    // We are finding out in the field that a 'logcat -c' via adb occasionally
+    // is returned with permission denied when we did only one pass and thus
+    // breaking scripts. For security we still err on denying access if in
+    // doubt, but we expect the falses  should be reduced significantly as
+    // three times is a charm.
+    //
+    for (int retry = 3; !(ret = foundGid && foundUid && foundLog) && retry;
+         --retry) {
+        FILE* file = fopen(filename, "re");
+        if (!file) {
+            continue;
+        }
+
+        char* line = nullptr;
+        size_t len = 0;
+        while (getline(&line, &len, file) > 0) {
+            static const char groups_string[] = "Groups:\t";
+            static const char uid_string[] = "Uid:\t";
+            static const char gid_string[] = "Gid:\t";
+
+            if (strncmp(groups_string, line, sizeof(groups_string) - 1) == 0) {
+                if (groupIsLog(line + sizeof(groups_string) - 1)) {
+                    foundLog = true;
+                }
+            } else if (strncmp(uid_string, line, sizeof(uid_string) - 1) == 0) {
+                uid_t u[4] = { (uid_t)-1, (uid_t)-1, (uid_t)-1, (uid_t)-1 };
+
+                sscanf(line + sizeof(uid_string) - 1, "%u\t%u\t%u\t%u", &u[0],
+                       &u[1], &u[2], &u[3]);
+
+                // Protect against PID reuse by checking that UID is the same
+                if ((uid == u[0]) && (uid == u[1]) && (uid == u[2]) &&
+                    (uid == u[3])) {
+                    foundUid = true;
+                }
+            } else if (strncmp(gid_string, line, sizeof(gid_string) - 1) == 0) {
+                gid_t g[4] = { (gid_t)-1, (gid_t)-1, (gid_t)-1, (gid_t)-1 };
+
+                sscanf(line + sizeof(gid_string) - 1, "%u\t%u\t%u\t%u", &g[0],
+                       &g[1], &g[2], &g[3]);
+
+                // Protect against PID reuse by checking that GID is the same
+                if ((gid == g[0]) && (gid == g[1]) && (gid == g[2]) &&
+                    (gid == g[3])) {
+                    foundGid = true;
+                }
+            }
+        }
+        free(line);
+        fclose(file);
+    }
+
+    return ret;
+}
+
+bool clientHasLogCredentials(SocketClient* cli) {
+    return clientHasLogCredentials(cli->getUid(), cli->getGid(), cli->getPid());
+}
diff --git a/logd/LogCommand.h b/logd/LogPermissions.h
similarity index 71%
rename from logd/LogCommand.h
rename to logd/LogPermissions.h
index e3b96a2..3130db5 100644
--- a/logd/LogCommand.h
+++ b/logd/LogPermissions.h
@@ -14,18 +14,11 @@
  * limitations under the License.
  */
 
-#ifndef _LOGD_COMMAND_H
-#define _LOGD_COMMAND_H
+#pragma once
+
+#include <sys/types.h>
 
 #include <sysutils/SocketClient.h>
-#include <sysutils/FrameworkCommand.h>
 
-class LogCommand : public FrameworkCommand {
-public:
-    LogCommand(const char *cmd);
-    virtual ~LogCommand() {}
-};
-
-bool clientHasLogCredentials(SocketClient * cli);
-
-#endif
+bool clientHasLogCredentials(uid_t uid, gid_t gid, pid_t pid);
+bool clientHasLogCredentials(SocketClient* cli);
diff --git a/logd/LogReader.cpp b/logd/LogReader.cpp
index c7deec0..1c6a85f 100644
--- a/logd/LogReader.cpp
+++ b/logd/LogReader.cpp
@@ -15,28 +15,70 @@
  */
 
 #include <ctype.h>
+#include <inttypes.h>
 #include <poll.h>
+#include <sched.h>
 #include <sys/prctl.h>
 #include <sys/socket.h>
+#include <sys/types.h>
 
+#include <chrono>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
 #include <cutils/sockets.h>
+#include <private/android_filesystem_config.h>
+#include <private/android_logger.h>
 
+#include "LogBuffer.h"
+#include "LogBufferElement.h"
+#include "LogPermissions.h"
 #include "LogReader.h"
-#include "FlushCommand.h"
+#include "LogUtils.h"
+#include "LogWriter.h"
 
-LogReader::LogReader(LogBuffer *logbuf) :
-        SocketListener(getLogSocket(), true),
-        mLogbuf(*logbuf) {
+static bool CanReadSecurityLogs(SocketClient* client) {
+    return client->getUid() == AID_SYSTEM || client->getGid() == AID_SYSTEM;
 }
 
-// When we are notified a new log entry is available, inform
-// all of our listening sockets.
-void LogReader::notifyNewLog() {
-    FlushCommand command(*this);
-    runOnEachSocket(&command);
+static std::string SocketClientToName(SocketClient* client) {
+    return android::base::StringPrintf("pid %d, fd %d", client->getPid(), client->getSocket());
 }
 
-bool LogReader::onDataAvailable(SocketClient *cli) {
+class SocketLogWriter : public LogWriter {
+  public:
+    SocketLogWriter(LogReader* reader, SocketClient* client, bool privileged)
+        : LogWriter(client->getUid(), privileged), reader_(reader), client_(client) {}
+
+    bool Write(const logger_entry& entry, const char* msg) override {
+        struct iovec iovec[2];
+        iovec[0].iov_base = const_cast<logger_entry*>(&entry);
+        iovec[0].iov_len = entry.hdr_size;
+        iovec[1].iov_base = const_cast<char*>(msg);
+        iovec[1].iov_len = entry.len;
+
+        return client_->sendDatav(iovec, 1 + (entry.len != 0)) == 0;
+    }
+
+    void Release() override {
+        reader_->release(client_);
+        client_->decRef();
+    }
+
+    void Shutdown() override { shutdown(client_->getSocket(), SHUT_RDWR); }
+
+    std::string name() const override { return SocketClientToName(client_); }
+
+  private:
+    LogReader* reader_;
+    SocketClient* client_;
+};
+
+LogReader::LogReader(LogBuffer* logbuf, LogReaderList* reader_list)
+    : SocketListener(getLogSocket(), true), log_buffer_(logbuf), reader_list_(reader_list) {}
+
+// Note returning false will release the SocketClient instance.
+bool LogReader::onDataAvailable(SocketClient* cli) {
     static bool name_set;
     if (!name_set) {
         prctl(PR_SET_NAME, "logd.reader");
@@ -47,14 +89,19 @@
 
     int len = read(cli->getSocket(), buffer, sizeof(buffer) - 1);
     if (len <= 0) {
-        doSocketDelete(cli);
+        DoSocketDelete(cli);
         return false;
     }
     buffer[len] = '\0';
 
+    // Clients are only allowed to send one command, disconnect them if they send another.
+    if (DoSocketDelete(cli)) {
+        return false;
+    }
+
     unsigned long tail = 0;
     static const char _tail[] = " tail=";
-    char *cp = strstr(buffer, _tail);
+    char* cp = strstr(buffer, _tail);
     if (cp) {
         tail = atol(cp + sizeof(_tail) - 1);
     }
@@ -67,13 +114,21 @@
         start.strptime(cp + sizeof(_start) - 1, "%s.%q");
     }
 
+    std::chrono::steady_clock::time_point deadline = {};
+    static const char _timeout[] = " timeout=";
+    cp = strstr(buffer, _timeout);
+    if (cp) {
+        long timeout_seconds = atol(cp + sizeof(_timeout) - 1);
+        deadline = std::chrono::steady_clock::now() + std::chrono::seconds(timeout_seconds);
+    }
+
     unsigned int logMask = -1;
     static const char _logIds[] = " lids=";
     cp = strstr(buffer, _logIds);
     if (cp) {
         logMask = 0;
         cp += sizeof(_logIds) - 1;
-        while (*cp && *cp != '\0') {
+        while (*cp != '\0') {
             int val = 0;
             while (isdigit(*cp)) {
                 val = val * 10 + *cp - '0';
@@ -95,90 +150,96 @@
     }
 
     bool nonBlock = false;
-    if (strncmp(buffer, "dumpAndClose", 12) == 0) {
+    if (!fastcmp<strncmp>(buffer, "dumpAndClose", 12)) {
         // Allow writer to get some cycles, and wait for pending notifications
         sched_yield();
-        LogTimeEntry::lock();
-        LogTimeEntry::unlock();
+        logd_lock.lock();
+        logd_lock.unlock();
         sched_yield();
         nonBlock = true;
     }
 
+    bool privileged = clientHasLogCredentials(cli);
+    bool can_read_security = CanReadSecurityLogs(cli);
+    if (!can_read_security) {
+        logMask &= ~(1 << LOG_ID_SECURITY);
+    }
+
+    std::unique_ptr<LogWriter> socket_log_writer(new SocketLogWriter(this, cli, privileged));
+
     uint64_t sequence = 1;
     // Convert realtime to sequence number
     if (start != log_time::EPOCH) {
-        class LogFindStart {
-            const pid_t mPid;
-            const unsigned mLogMask;
-            bool startTimeSet;
-            log_time &start;
-            uint64_t &sequence;
-            uint64_t last;
-
-        public:
-            LogFindStart(unsigned logMask, pid_t pid, log_time &start, uint64_t &sequence) :
-                    mPid(pid),
-                    mLogMask(logMask),
-                    startTimeSet(false),
-                    start(start),
-                    sequence(sequence),
-                    last(sequence) {
+        bool start_time_set = false;
+        uint64_t last = sequence;
+        auto log_find_start = [pid, start, &sequence, &start_time_set, &last](
+                                      log_id_t, pid_t element_pid, uint64_t element_sequence,
+                                      log_time element_realtime) -> FilterResult {
+            if (pid && pid != element_pid) {
+                return FilterResult::kSkip;
             }
-
-            static int callback(const LogBufferElement *element, void *obj) {
-                LogFindStart *me = reinterpret_cast<LogFindStart *>(obj);
-                if ((!me->mPid || (me->mPid == element->getPid()))
-                        && (me->mLogMask & (1 << element->getLogId()))) {
-                    if (me->start == element->getRealTime()) {
-                        me->sequence = element->getSequence();
-                        me->startTimeSet = true;
-                        return -1;
-                    } else {
-                        if (me->start < element->getRealTime()) {
-                            me->sequence = me->last;
-                            me->startTimeSet = true;
-                            return -1;
-                        }
-                        me->last = element->getSequence();
-                    }
+            if (start == element_realtime) {
+                sequence = element_sequence;
+                start_time_set = true;
+                return FilterResult::kStop;
+            } else {
+                if (start < element_realtime) {
+                    sequence = last;
+                    start_time_set = true;
+                    return FilterResult::kStop;
                 }
-                return false;
+                last = element_sequence;
             }
+            return FilterResult::kSkip;
+        };
+        auto lock = std::lock_guard{logd_lock};
+        auto flush_to_state = log_buffer_->CreateFlushToState(sequence, logMask);
+        log_buffer_->FlushTo(socket_log_writer.get(), *flush_to_state, log_find_start);
 
-            bool found() { return startTimeSet; }
-        } logFindStart(logMask, pid, start, sequence);
-
-        logbuf().flushTo(cli, sequence, FlushCommand::hasReadLogs(cli),
-                         logFindStart.callback, &logFindStart);
-
-        if (!logFindStart.found()) {
+        if (!start_time_set) {
             if (nonBlock) {
-                doSocketDelete(cli);
                 return false;
             }
-            sequence = LogBufferElement::getCurrentSequence();
+            sequence = log_buffer_->sequence();
         }
     }
 
-    FlushCommand command(*this, nonBlock, tail, logMask, pid, sequence);
-    command.runSocketCommand(cli);
+    LOG(INFO) << android::base::StringPrintf(
+            "logdr: UID=%d GID=%d PID=%d %c tail=%lu logMask=%x pid=%d "
+            "start=%" PRIu64 "ns deadline=%" PRIi64 "ns",
+            cli->getUid(), cli->getGid(), cli->getPid(), nonBlock ? 'n' : 'b', tail, logMask,
+            (int)pid, start.nsec(), static_cast<int64_t>(deadline.time_since_epoch().count()));
+
+    if (start == log_time::EPOCH) {
+        deadline = {};
+    }
+
+    auto lock = std::lock_guard{logd_lock};
+    auto entry = std::make_unique<LogReaderThread>(log_buffer_, reader_list_,
+                                                   std::move(socket_log_writer), nonBlock, tail,
+                                                   logMask, pid, start, sequence, deadline);
+    // release client and entry reference counts once done
+    cli->incRef();
+    reader_list_->reader_threads().emplace_front(std::move(entry));
+
+    // Set acceptable upper limit to wait for slow reader processing b/27242723
+    struct timeval t = { LOGD_SNDTIMEO, 0 };
+    setsockopt(cli->getSocket(), SOL_SOCKET, SO_SNDTIMEO, (const char*)&t,
+               sizeof(t));
+
     return true;
 }
 
-void LogReader::doSocketDelete(SocketClient *cli) {
-    LastLogTimes &times = mLogbuf.mTimes;
-    LogTimeEntry::lock();
-    LastLogTimes::iterator it = times.begin();
-    while(it != times.end()) {
-        LogTimeEntry *entry = (*it);
-        if (entry->mClient == cli) {
-            times.erase(it);
-            entry->release_Locked();
-            break;
+bool LogReader::DoSocketDelete(SocketClient* cli) {
+    auto cli_name = SocketClientToName(cli);
+    auto lock = std::lock_guard{logd_lock};
+    for (const auto& reader : reader_list_->reader_threads()) {
+        if (reader->name() == cli_name) {
+            reader->Release();
+            return true;
         }
-        it++;
     }
-    LogTimeEntry::unlock();
+    return false;
 }
 
 int LogReader::getLogSocket() {
@@ -186,9 +247,8 @@
     int sock = android_get_control_socket(socketName);
 
     if (sock < 0) {
-        sock = socket_local_server(socketName,
-                                   ANDROID_SOCKET_NAMESPACE_RESERVED,
-                                   SOCK_SEQPACKET);
+        sock = socket_local_server(
+            socketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET);
     }
 
     return sock;
diff --git a/logd/LogReader.h b/logd/LogReader.h
index 91559a3..a4e52c4 100644
--- a/logd/LogReader.h
+++ b/logd/LogReader.h
@@ -14,30 +14,26 @@
  * limitations under the License.
  */
 
-#ifndef _LOGD_LOG_WRITER_H__
-#define _LOGD_LOG_WRITER_H__
+#pragma once
 
 #include <sysutils/SocketListener.h>
+
 #include "LogBuffer.h"
-#include "LogTimes.h"
+#include "LogReaderList.h"
+#include "LogReaderThread.h"
 
 class LogReader : public SocketListener {
-    LogBuffer &mLogbuf;
+  public:
+    explicit LogReader(LogBuffer* logbuf, LogReaderList* reader_list);
 
-public:
-    LogReader(LogBuffer *logbuf);
-    void notifyNewLog();
+  protected:
+    virtual bool onDataAvailable(SocketClient* cli);
 
-    LogBuffer &logbuf(void) const { return mLogbuf; }
-
-protected:
-    virtual bool onDataAvailable(SocketClient *cli);
-
-private:
+  private:
     static int getLogSocket();
 
-    void doSocketDelete(SocketClient *cli);
+    bool DoSocketDelete(SocketClient* cli);
 
+    LogBuffer* log_buffer_;
+    LogReaderList* reader_list_;
 };
-
-#endif
diff --git a/logd/LogReaderList.cpp b/logd/LogReaderList.cpp
new file mode 100644
index 0000000..51cb3d2
--- /dev/null
+++ b/logd/LogReaderList.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 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 "LogReaderList.h"
+
+// When we are notified a new log entry is available, inform
+// listening sockets who are watching this entry's log id.
+void LogReaderList::NotifyNewLog(LogMask log_mask) const {
+    for (const auto& entry : reader_threads_) {
+        if (!entry->IsWatchingMultiple(log_mask)) {
+            continue;
+        }
+        if (entry->deadline().time_since_epoch().count() != 0) {
+            continue;
+        }
+        entry->TriggerReader();
+    }
+}
diff --git a/logd/LogReaderList.h b/logd/LogReaderList.h
new file mode 100644
index 0000000..39de70a
--- /dev/null
+++ b/logd/LogReaderList.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <list>
+#include <memory>
+#include <mutex>
+
+#include "LogBuffer.h"
+#include "LogReaderThread.h"
+#include "LogdLock.h"
+
+class LogReaderList {
+  public:
+    void NotifyNewLog(LogMask log_mask) const REQUIRES(logd_lock);
+
+    std::list<std::unique_ptr<LogReaderThread>>& reader_threads() REQUIRES(logd_lock) {
+        return reader_threads_;
+    }
+
+  private:
+    std::list<std::unique_ptr<LogReaderThread>> reader_threads_ GUARDED_BY(logd_lock);
+};
\ No newline at end of file
diff --git a/logd/LogReaderThread.cpp b/logd/LogReaderThread.cpp
new file mode 100644
index 0000000..cf769ba
--- /dev/null
+++ b/logd/LogReaderThread.cpp
@@ -0,0 +1,169 @@
+/*
+ * 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 "LogReaderThread.h"
+
+#include <errno.h>
+#include <string.h>
+#include <sys/prctl.h>
+
+#include <thread>
+
+#include "LogBuffer.h"
+#include "LogReaderList.h"
+#include "SerializedFlushToState.h"
+
+LogReaderThread::LogReaderThread(LogBuffer* log_buffer, LogReaderList* reader_list,
+                                 std::unique_ptr<LogWriter> writer, bool non_block,
+                                 unsigned long tail, LogMask log_mask, pid_t pid,
+                                 log_time start_time, uint64_t start,
+                                 std::chrono::steady_clock::time_point deadline)
+    : log_buffer_(log_buffer),
+      reader_list_(reader_list),
+      writer_(std::move(writer)),
+      pid_(pid),
+      tail_(tail),
+      count_(0),
+      index_(0),
+      start_time_(start_time),
+      deadline_(deadline),
+      non_block_(non_block) {
+    CleanSkip();
+    flush_to_state_ = log_buffer_->CreateFlushToState(start, log_mask);
+    auto thread = std::thread{&LogReaderThread::ThreadFunction, this};
+    thread.detach();
+}
+
+void LogReaderThread::ThreadFunction() {
+    prctl(PR_SET_NAME, "logd.reader.per");
+
+    auto lock = std::unique_lock{logd_lock};
+    auto lock_assertion = android::base::ScopedLockAssertion{logd_lock};
+
+    while (!release_) {
+        if (deadline_.time_since_epoch().count() != 0) {
+            if (thread_triggered_condition_.wait_until(lock, deadline_) ==
+                std::cv_status::timeout) {
+                deadline_ = {};
+            }
+            if (release_) {
+                break;
+            }
+        }
+
+        if (tail_) {
+            auto first_pass_state = log_buffer_->CreateFlushToState(flush_to_state_->start(),
+                                                                    flush_to_state_->log_mask());
+            log_buffer_->FlushTo(writer_.get(), *first_pass_state,
+                                 [this](log_id_t log_id, pid_t pid, uint64_t sequence,
+                                        log_time realtime) REQUIRES(logd_lock) {
+                                     return FilterFirstPass(log_id, pid, sequence, realtime);
+                                 });
+        }
+        bool flush_success = log_buffer_->FlushTo(
+                writer_.get(), *flush_to_state_,
+                [this](log_id_t log_id, pid_t pid, uint64_t sequence, log_time realtime) REQUIRES(
+                        logd_lock) { return FilterSecondPass(log_id, pid, sequence, realtime); });
+
+        // We only ignore entries before the original start time for the first flushTo(), if we
+        // get entries after this first flush before the original start time, then the client
+        // wouldn't have seen them.
+        // Note: this is still racy and may skip out of order events that came in since the last
+        // time the client disconnected and then reconnected with the new start time.  The long term
+        // solution here is that clients must request events since a specific sequence number.
+        start_time_.tv_sec = 0;
+        start_time_.tv_nsec = 0;
+
+        if (!flush_success) {
+            break;
+        }
+
+        if (non_block_ || release_) {
+            break;
+        }
+
+        CleanSkip();
+
+        if (deadline_.time_since_epoch().count() == 0) {
+            thread_triggered_condition_.wait(lock);
+        }
+    }
+
+    writer_->Release();
+
+    auto& log_reader_threads = reader_list_->reader_threads();
+    auto it = std::find_if(log_reader_threads.begin(), log_reader_threads.end(),
+                           [this](const auto& other) { return other.get() == this; });
+
+    if (it != log_reader_threads.end()) {
+        log_reader_threads.erase(it);
+    }
+}
+
+// A first pass to count the number of elements
+FilterResult LogReaderThread::FilterFirstPass(log_id_t, pid_t pid, uint64_t, log_time realtime) {
+    if ((!pid_ || pid_ == pid) && (start_time_ == log_time::EPOCH || start_time_ <= realtime)) {
+        ++count_;
+    }
+
+    return FilterResult::kSkip;
+}
+
+// A second pass to send the selected elements
+FilterResult LogReaderThread::FilterSecondPass(log_id_t log_id, pid_t pid, uint64_t,
+                                               log_time realtime) {
+    if (skip_ahead_[log_id]) {
+        skip_ahead_[log_id]--;
+        return FilterResult::kSkip;
+    }
+
+    // Truncate to close race between first and second pass
+    if (non_block_ && tail_ && index_ >= count_) {
+        return FilterResult::kStop;
+    }
+
+    if (pid_ && pid_ != pid) {
+        return FilterResult::kSkip;
+    }
+
+    if (start_time_ != log_time::EPOCH && realtime <= start_time_) {
+        return FilterResult::kSkip;
+    }
+
+    if (release_) {
+        return FilterResult::kStop;
+    }
+
+    if (!tail_) {
+        goto ok;
+    }
+
+    ++index_;
+
+    if (count_ > tail_ && index_ <= (count_ - tail_)) {
+        return FilterResult::kSkip;
+    }
+
+    if (!non_block_) {
+        tail_ = 0;
+    }
+
+ok:
+    if (!skip_ahead_[log_id]) {
+        return FilterResult::kWrite;
+    }
+    return FilterResult::kSkip;
+}
diff --git a/logd/LogReaderThread.h b/logd/LogReaderThread.h
new file mode 100644
index 0000000..96137b3
--- /dev/null
+++ b/logd/LogReaderThread.h
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <pthread.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include <chrono>
+#include <condition_variable>
+#include <list>
+#include <memory>
+
+#include <android-base/thread_annotations.h>
+#include <log/log.h>
+
+#include "LogBuffer.h"
+#include "LogWriter.h"
+#include "LogdLock.h"
+
+class LogReaderList;
+
+class LogReaderThread {
+  public:
+    LogReaderThread(LogBuffer* log_buffer, LogReaderList* reader_list,
+                    std::unique_ptr<LogWriter> writer, bool non_block, unsigned long tail,
+                    LogMask log_mask, pid_t pid, log_time start_time, uint64_t sequence,
+                    std::chrono::steady_clock::time_point deadline);
+    void TriggerReader() REQUIRES(logd_lock) { thread_triggered_condition_.notify_all(); }
+
+    void TriggerSkip(log_id_t id, unsigned int skip) REQUIRES(logd_lock) { skip_ahead_[id] = skip; }
+    void CleanSkip() REQUIRES(logd_lock) { memset(skip_ahead_, 0, sizeof(skip_ahead_)); }
+
+    void Release() REQUIRES(logd_lock) {
+        // gracefully shut down the socket.
+        writer_->Shutdown();
+        release_ = true;
+        thread_triggered_condition_.notify_all();
+    }
+
+    bool IsWatching(log_id_t id) const REQUIRES(logd_lock) {
+        return flush_to_state_->log_mask() & (1 << id);
+    }
+    bool IsWatchingMultiple(LogMask log_mask) const REQUIRES(logd_lock) {
+        return flush_to_state_->log_mask() & log_mask;
+    }
+
+    std::string name() const REQUIRES(logd_lock) { return writer_->name(); }
+    uint64_t start() const REQUIRES(logd_lock) { return flush_to_state_->start(); }
+    std::chrono::steady_clock::time_point deadline() const REQUIRES(logd_lock) { return deadline_; }
+    FlushToState& flush_to_state() REQUIRES(logd_lock) { return *flush_to_state_; }
+
+  private:
+    void ThreadFunction();
+    // flushTo filter callbacks
+    FilterResult FilterFirstPass(log_id_t log_id, pid_t pid, uint64_t sequence, log_time realtime)
+            REQUIRES(logd_lock);
+    FilterResult FilterSecondPass(log_id_t log_id, pid_t pid, uint64_t sequence, log_time realtime)
+            REQUIRES(logd_lock);
+
+    std::condition_variable thread_triggered_condition_;
+    LogBuffer* log_buffer_;
+    LogReaderList* reader_list_;
+    std::unique_ptr<LogWriter> writer_ GUARDED_BY(logd_lock);
+
+    // Set to true to cause the thread to end and the LogReaderThread to delete itself.
+    bool release_ GUARDED_BY(logd_lock) = false;
+
+    // If set to non-zero, only pids equal to this are read by the reader.
+    const pid_t pid_;
+    // When a reader is referencing (via start_) old elements in the log buffer, and the log
+    // buffer's size grows past its memory limit, the log buffer may request the reader to skip
+    // ahead a specified number of logs.
+    unsigned int skip_ahead_[LOG_ID_MAX] GUARDED_BY(logd_lock);
+    // LogBuffer::FlushTo() needs to store state across subsequent calls.
+    std::unique_ptr<FlushToState> flush_to_state_ GUARDED_BY(logd_lock);
+
+    // These next three variables are used for reading only the most recent lines aka `adb logcat
+    // -t` / `adb logcat -T`.
+    // tail_ is the number of most recent lines to print.
+    unsigned long tail_;
+    // count_ is the result of a first pass through the log buffer to determine how many total
+    // messages there are.
+    unsigned long count_;
+    // index_ is used along with count_ to only start sending lines once index_ > (count_ - tail_)
+    // and to disconnect the reader (if it is dumpAndClose, `adb logcat -t`), when index_ >= count_.
+    unsigned long index_;
+
+    // When a reader requests logs starting from a given timestamp, its stored here for the first
+    // pass, such that logs before this time stamp that are accumulated in the buffer are ignored.
+    log_time start_time_;
+    // CLOCK_MONOTONIC based deadline used for log wrapping.  If this deadline expires before logs
+    // wrap, then wake up and send the logs to the reader anyway.
+    std::chrono::steady_clock::time_point deadline_ GUARDED_BY(logd_lock);
+    // If this reader is 'dumpAndClose' and will disconnect once it has read its intended logs.
+    const bool non_block_;
+};
diff --git a/logd/LogSize.cpp b/logd/LogSize.cpp
new file mode 100644
index 0000000..fe829ba
--- /dev/null
+++ b/logd/LogSize.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2020 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 <LogSize.h>
+
+#include <array>
+#include <optional>
+#include <string>
+
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+
+bool IsValidBufferSize(size_t value) {
+    return kLogBufferMinSize <= value && value <= kLogBufferMaxSize;
+}
+
+static std::optional<size_t> GetBufferSizeProperty(const std::string& key) {
+    std::string value = android::base::GetProperty(key, "");
+    if (value.empty()) {
+        return {};
+    }
+
+    uint32_t size;
+    if (!android::base::ParseByteCount(value, &size)) {
+        return {};
+    }
+
+    if (!IsValidBufferSize(size)) {
+        return {};
+    }
+
+    return size;
+}
+
+size_t GetBufferSizeFromProperties(log_id_t log_id) {
+    std::string buffer_name = android_log_id_to_name(log_id);
+    std::array<std::string, 4> properties = {
+            "persist.logd.size." + buffer_name,
+            "ro.logd.size." + buffer_name,
+            "persist.logd.size",
+            "ro.logd.size",
+    };
+
+    for (const auto& property : properties) {
+        if (auto size = GetBufferSizeProperty(property)) {
+            return *size;
+        }
+    }
+
+    if (android::base::GetBoolProperty("ro.config.low_ram", false)) {
+        return kLogBufferMinSize;
+    }
+
+    return kDefaultLogBufferSize;
+}
diff --git a/logd/LogSize.h b/logd/LogSize.h
new file mode 100644
index 0000000..d5716ff
--- /dev/null
+++ b/logd/LogSize.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#pragma once
+
+#include <stddef.h>
+
+#include <log/log.h>
+
+static constexpr size_t kDefaultLogBufferSize = 256 * 1024;
+static constexpr size_t kLogBufferMinSize = 64 * 1024;
+static constexpr size_t kLogBufferMaxSize = 256 * 1024 * 1024;
+
+bool IsValidBufferSize(size_t value);
+
+// This returns the buffer size as set in system properties for use in LogBuffer::Init().
+// Note that `logcat -G` calls LogBuffer::SetSize(), which configures log buffer sizes without
+// setting these properties, so this function should never be used except for LogBuffer::Init().
+// LogBuffer::GetSize() should be used instead within logd.  Other processes can use
+// android_logger_get_log_size() or `logcat -g` to query the actual allotted buffer size.
+size_t GetBufferSizeFromProperties(log_id_t log_id);
diff --git a/logd/LogStatistics.cpp b/logd/LogStatistics.cpp
index 48c2fe6..87069b3 100644
--- a/logd/LogStatistics.cpp
+++ b/logd/LogStatistics.cpp
@@ -14,44 +14,97 @@
  * limitations under the License.
  */
 
-#include <algorithm> // std::max
-#include <fcntl.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <log/logger.h>
-#include <private/android_filesystem_config.h>
-#include <utils/String8.h>
-
 #include "LogStatistics.h"
 
-LogStatistics::LogStatistics() : enable(false) {
+#include <ctype.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <list>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <private/android_logger.h>
+
+#include "LogBufferElement.h"
+
+static const uint64_t hourSec = 60 * 60;
+static const uint64_t monthSec = 31 * 24 * hourSec;
+
+std::atomic<size_t> LogStatistics::SizesTotal;
+
+static std::string TagNameKey(const LogStatisticsElement& element) {
+    if (IsBinary(element.log_id)) {
+        uint32_t tag = element.tag;
+        if (tag) {
+            const char* cp = android::tagToName(tag);
+            if (cp) {
+                return std::string(cp);
+            }
+        }
+        return android::base::StringPrintf("[%" PRIu32 "]", tag);
+    }
+    const char* msg = element.msg;
+    if (!msg) {
+        return "chatty";
+    }
+    ++msg;
+    uint16_t len = element.msg_len;
+    len = (len <= 1) ? 0 : strnlen(msg, len - 1);
+    if (!len) {
+        return "<NULL>";
+    }
+    return std::string(msg, len);
+}
+
+LogStatistics::LogStatistics(bool enable_statistics, bool track_total_size,
+                             std::optional<log_time> start_time)
+    : enable(enable_statistics), track_total_size_(track_total_size) {
+    log_time now(CLOCK_REALTIME);
     log_id_for_each(id) {
         mSizes[id] = 0;
         mElements[id] = 0;
+        mDroppedElements[id] = 0;
         mSizesTotal[id] = 0;
         mElementsTotal[id] = 0;
+        if (start_time) {
+            mOldest[id] = *start_time;
+            mNewest[id] = *start_time;
+        } else {
+            mOldest[id] = now;
+            mNewest[id] = now;
+        }
+        mNewestDropped[id] = now;
     }
 }
 
 namespace android {
 
+size_t sizesTotal() {
+    return LogStatistics::sizesTotal();
+}
+
 // caller must own and free character string
-char *pidToName(pid_t pid) {
-    char *retval = NULL;
-    if (pid == 0) { // special case from auditd/klogd for kernel
+char* pidToName(pid_t pid) {
+    char* retval = nullptr;
+    if (pid == 0) {  // special case from auditd/klogd for kernel
         retval = strdup("logd");
     } else {
         char buffer[512];
         snprintf(buffer, sizeof(buffer), "/proc/%u/cmdline", pid);
-        int fd = open(buffer, O_RDONLY);
+        int fd = open(buffer, O_RDONLY | O_CLOEXEC);
         if (fd >= 0) {
             ssize_t ret = read(fd, buffer, sizeof(buffer));
             if (ret > 0) {
-                buffer[sizeof(buffer)-1] = '\0';
+                buffer[sizeof(buffer) - 1] = '\0';
                 // frameworks intermediate state
-                if (strcmp(buffer, "<pre-initialized>")) {
+                if (fastcmp<strcmp>(buffer, "<pre-initialized>")) {
                     retval = strdup(buffer);
                 }
             }
@@ -60,99 +113,241 @@
     }
     return retval;
 }
-
 }
 
-void LogStatistics::add(LogBufferElement *e) {
-    log_id_t log_id = e->getLogId();
-    unsigned short size = e->getMsgLen();
+void LogStatistics::AddTotal(log_id_t log_id, uint16_t size) {
+    auto lock = std::lock_guard{lock_};
+
+    mSizesTotal[log_id] += size;
+    SizesTotal += size;
+    ++mElementsTotal[log_id];
+}
+
+void LogStatistics::Add(LogStatisticsElement element) {
+    auto lock = std::lock_guard{lock_};
+
+    if (!track_total_size_) {
+        element.total_len = element.msg_len;
+    }
+
+    log_id_t log_id = element.log_id;
+    uint16_t size = element.total_len;
     mSizes[log_id] += size;
     ++mElements[log_id];
 
-    mSizesTotal[log_id] += size;
-    ++mElementsTotal[log_id];
+    // When caller adding a chatty entry, they will have already
+    // called add() and subtract() for each entry as they are
+    // evaluated and trimmed, thus recording size and number of
+    // elements, but we must recognize the manufactured dropped
+    // entry as not contributing to the lifetime totals.
+    if (element.dropped_count) {
+        ++mDroppedElements[log_id];
+    } else {
+        mSizesTotal[log_id] += size;
+        SizesTotal += size;
+        ++mElementsTotal[log_id];
+    }
+
+    log_time stamp(element.realtime);
+    if (mNewest[log_id] < stamp) {
+        // A major time update invalidates the statistics :-(
+        log_time diff = stamp - mNewest[log_id];
+        mNewest[log_id] = stamp;
+
+        if (diff.tv_sec > hourSec) {
+            // approximate Do-Your-Best fixup
+            diff += mOldest[log_id];
+            if ((diff > stamp) && ((diff - stamp).tv_sec < hourSec)) {
+                diff = stamp;
+            }
+            if (diff <= stamp) {
+                mOldest[log_id] = diff;
+                if (mNewestDropped[log_id] < diff) {
+                    mNewestDropped[log_id] = diff;
+                }
+            }
+        }
+    }
 
     if (log_id == LOG_ID_KERNEL) {
         return;
     }
 
-    uidTable[log_id].add(e->getUid(), e);
+    uidTable[log_id].Add(element.uid, element);
+    if (element.uid == AID_SYSTEM) {
+        pidSystemTable[log_id].Add(element.pid, element);
+    }
 
     if (!enable) {
         return;
     }
 
-    pidTable.add(e->getPid(), e);
-    tidTable.add(e->getTid(), e);
+    pidTable.Add(element.pid, element);
+    tidTable.Add(element.tid, element);
 
-    uint32_t tag = e->getTag();
+    uint32_t tag = element.tag;
     if (tag) {
-        tagTable.add(tag, e);
+        if (log_id == LOG_ID_SECURITY) {
+            securityTagTable.Add(tag, element);
+        } else {
+            tagTable.Add(tag, element);
+        }
+    }
+
+    if (!element.dropped_count) {
+        tagNameTable.Add(TagNameKey(element), element);
     }
 }
 
-void LogStatistics::subtract(LogBufferElement *e) {
-    log_id_t log_id = e->getLogId();
-    unsigned short size = e->getMsgLen();
+void LogStatistics::Subtract(LogStatisticsElement element) {
+    auto lock = std::lock_guard{lock_};
+
+    if (!track_total_size_) {
+        element.total_len = element.msg_len;
+    }
+
+    log_id_t log_id = element.log_id;
+    uint16_t size = element.total_len;
     mSizes[log_id] -= size;
     --mElements[log_id];
+    if (element.dropped_count) {
+        --mDroppedElements[log_id];
+    }
+
+    if (mOldest[log_id] < element.realtime) {
+        mOldest[log_id] = element.realtime;
+    }
 
     if (log_id == LOG_ID_KERNEL) {
         return;
     }
 
-    uidTable[log_id].subtract(e->getUid(), e);
+    uidTable[log_id].Subtract(element.uid, element);
+    if (element.uid == AID_SYSTEM) {
+        pidSystemTable[log_id].Subtract(element.pid, element);
+    }
 
     if (!enable) {
         return;
     }
 
-    pidTable.subtract(e->getPid(), e);
-    tidTable.subtract(e->getTid(), e);
+    pidTable.Subtract(element.pid, element);
+    tidTable.Subtract(element.tid, element);
 
-    uint32_t tag = e->getTag();
+    uint32_t tag = element.tag;
     if (tag) {
-        tagTable.subtract(tag, e);
+        if (log_id == LOG_ID_SECURITY) {
+            securityTagTable.Subtract(tag, element);
+        } else {
+            tagTable.Subtract(tag, element);
+        }
+    }
+
+    if (!element.dropped_count) {
+        tagNameTable.Subtract(TagNameKey(element), element);
     }
 }
 
 // Atomically set an entry to drop
 // entry->setDropped(1) must follow this call, caller should do this explicitly.
-void LogStatistics::drop(LogBufferElement *e) {
-    log_id_t log_id = e->getLogId();
-    unsigned short size = e->getMsgLen();
-    mSizes[log_id] -= size;
+void LogStatistics::Drop(LogStatisticsElement element) {
+    CHECK_EQ(element.dropped_count, 0U);
 
-    uidTable[log_id].drop(e->getUid(), e);
+    auto lock = std::lock_guard{lock_};
+    log_id_t log_id = element.log_id;
+    uint16_t size = element.msg_len;
+    mSizes[log_id] -= size;
+    ++mDroppedElements[log_id];
+
+    if (mNewestDropped[log_id] < element.realtime) {
+        mNewestDropped[log_id] = element.realtime;
+    }
+
+    uidTable[log_id].Drop(element.uid, element);
+    if (element.uid == AID_SYSTEM) {
+        pidSystemTable[log_id].Drop(element.pid, element);
+    }
 
     if (!enable) {
         return;
     }
 
-    pidTable.drop(e->getPid(), e);
-    tidTable.drop(e->getTid(), e);
+    pidTable.Drop(element.pid, element);
+    tidTable.Drop(element.tid, element);
+
+    uint32_t tag = element.tag;
+    if (tag) {
+        if (log_id == LOG_ID_SECURITY) {
+            securityTagTable.Drop(tag, element);
+        } else {
+            tagTable.Drop(tag, element);
+        }
+    }
+
+    tagNameTable.Subtract(TagNameKey(element), element);
+}
+
+void LogStatistics::Erase(LogStatisticsElement element) {
+    CHECK_GT(element.dropped_count, 0U);
+    CHECK_EQ(element.msg_len, 0U);
+
+    auto lock = std::lock_guard{lock_};
+
+    if (!track_total_size_) {
+        element.total_len = 0;
+    }
+
+    log_id_t log_id = element.log_id;
+    --mElements[log_id];
+    --mDroppedElements[log_id];
+    mSizes[log_id] -= element.total_len;
+
+    uidTable[log_id].Erase(element.uid, element);
+    if (element.uid == AID_SYSTEM) {
+        pidSystemTable[log_id].Erase(element.pid, element);
+    }
+
+    if (!enable) {
+        return;
+    }
+
+    pidTable.Erase(element.pid, element);
+    tidTable.Erase(element.tid, element);
+
+    uint32_t tag = element.tag;
+    if (tag) {
+        if (log_id == LOG_ID_SECURITY) {
+            securityTagTable.Erase(tag, element);
+        } else {
+            tagTable.Erase(tag, element);
+        }
+    }
+}
+
+const char* LogStatistics::UidToName(uid_t uid) const {
+    auto lock = std::lock_guard{lock_};
+    return UidToNameLocked(uid);
 }
 
 // caller must own and free character string
-char *LogStatistics::uidToName(uid_t uid) {
+const char* LogStatistics::UidToNameLocked(uid_t uid) const {
     // Local hard coded favourites
     if (uid == AID_LOGD) {
         return strdup("auditd");
     }
 
-    // Android hard coded
-    const struct android_id_info *info = android_ids;
-
-    for (size_t i = 0; i < android_id_count; ++i) {
-        if (info->aid == uid) {
-            return strdup(info->name);
+    // Android system
+    if (uid < AID_APP) {
+        // in bionic, thread safe as long as we copy the results
+        struct passwd* pwd = getpwuid(uid);
+        if (pwd) {
+            return strdup(pwd->pw_name);
         }
-        ++info;
     }
 
     // Parse /data/system/packages.list
-    uid_t userId = uid % AID_USER;
-    char *name = android::uidToName(userId);
+    uid_t userId = uid % AID_USER_OFFSET;
+    const char* name = android::uidToName(userId);
     if (!name && (userId > (AID_SHARED_GID_START - AID_APP))) {
         name = android::uidToName(userId - (AID_SHARED_GID_START - AID_APP));
     }
@@ -160,19 +355,28 @@
         return name;
     }
 
+    // Android application
+    if (uid >= AID_APP) {
+        struct passwd* pwd = getpwuid(uid);
+        if (pwd) {
+            return strdup(pwd->pw_name);
+        }
+    }
+
     // report uid -> pid(s) -> pidToName if unique
-    for(pidTable_t::iterator it = pidTable.begin(); it != pidTable.end(); ++it) {
-        const PidEntry &entry = it->second;
+    for (pidTable_t::const_iterator it = pidTable.begin(); it != pidTable.end();
+         ++it) {
+        const PidEntry& entry = it->second;
 
-        if (entry.getUid() == uid) {
-            const char *n = entry.getName();
+        if (entry.uid() == uid) {
+            const char* nameTmp = entry.name();
 
-            if (n) {
+            if (nameTmp) {
                 if (!name) {
-                    name = strdup(n);
-                } else if (strcmp(name, n)) {
-                    free(name);
-                    name = NULL;
+                    name = strdup(nameTmp);
+                } else if (fastcmp<strcmp>(name, nameTmp)) {
+                    free(const_cast<char*>(name));
+                    name = nullptr;
                     break;
                 }
             }
@@ -183,318 +387,648 @@
     return name;
 }
 
-static void format_line(android::String8 &output,
-        android::String8 &name, android::String8 &size, android::String8 &pruned) {
-    static const size_t pruned_len = 6;
-    static const size_t total_len = 70 + pruned_len;
-
-    ssize_t drop_len = std::max(pruned.length() + 1, pruned_len);
-    ssize_t size_len = std::max(size.length() + 1,
-                                total_len - name.length() - drop_len - 1);
-
-    if (pruned.length()) {
-        output.appendFormat("%s%*s%*s\n", name.string(),
-                                          (int)size_len, size.string(),
-                                          (int)drop_len, pruned.string());
-    } else {
-        output.appendFormat("%s%*s\n", name.string(),
-                                       (int)size_len, size.string());
+template <typename TKey, typename TEntry>
+void LogStatistics::WorstTwoWithThreshold(const LogHashtable<TKey, TEntry>& table, size_t threshold,
+                                          int* worst, size_t* worst_sizes,
+                                          size_t* second_worst_sizes) const {
+    std::array<const TKey*, 2> max_keys;
+    std::array<const TEntry*, 2> max_entries;
+    table.MaxEntries(AID_ROOT, 0, max_keys, max_entries);
+    if (max_entries[0] == nullptr || max_entries[1] == nullptr) {
+        return;
+    }
+    *worst_sizes = max_entries[0]->getSizes();
+    // b/24782000: Allow time horizon to extend roughly tenfold, assume average entry length is
+    // 100 characters.
+    if (*worst_sizes > threshold && *worst_sizes > (10 * max_entries[0]->dropped_count())) {
+        *worst = *max_keys[0];
+        *second_worst_sizes = max_entries[1]->getSizes();
+        if (*second_worst_sizes < threshold) {
+            *second_worst_sizes = threshold;
+        }
     }
 }
 
-void LogStatistics::format(char **buf, uid_t uid, unsigned int logMask) {
-    static const unsigned short spaces_total = 19;
+void LogStatistics::WorstTwoUids(log_id id, size_t threshold, int* worst, size_t* worst_sizes,
+                                 size_t* second_worst_sizes) const {
+    auto lock = std::lock_guard{lock_};
+    WorstTwoWithThreshold(uidTable[id], threshold, worst, worst_sizes, second_worst_sizes);
+}
 
-    if (*buf) {
-        free(*buf);
-        *buf = NULL;
+void LogStatistics::WorstTwoTags(size_t threshold, int* worst, size_t* worst_sizes,
+                                 size_t* second_worst_sizes) const {
+    auto lock = std::lock_guard{lock_};
+    WorstTwoWithThreshold(tagTable, threshold, worst, worst_sizes, second_worst_sizes);
+}
+
+void LogStatistics::WorstTwoSystemPids(log_id id, size_t worst_uid_sizes, int* worst,
+                                       size_t* second_worst_sizes) const {
+    auto lock = std::lock_guard{lock_};
+    std::array<const pid_t*, 2> max_keys;
+    std::array<const PidEntry*, 2> max_entries;
+    pidSystemTable[id].MaxEntries(AID_SYSTEM, 0, max_keys, max_entries);
+    if (max_entries[0] == nullptr || max_entries[1] == nullptr) {
+        return;
     }
 
+    *worst = *max_keys[0];
+    *second_worst_sizes = worst_uid_sizes - max_entries[0]->getSizes() + max_entries[1]->getSizes();
+}
+
+// Prune at most 10% of the log entries or maxPrune, whichever is less.
+bool LogStatistics::ShouldPrune(log_id id, unsigned long max_size,
+                                unsigned long* prune_rows) const {
+    static constexpr size_t kMinPrune = 4;
+    static constexpr size_t kMaxPrune = 256;
+
+    auto lock = std::lock_guard{lock_};
+    size_t sizes = mSizes[id];
+    if (sizes <= max_size) {
+        return false;
+    }
+    size_t size_over = sizes - ((max_size * 9) / 10);
+    size_t elements = mElements[id] - mDroppedElements[id];
+    size_t min_elements = elements / 100;
+    if (min_elements < kMinPrune) {
+        min_elements = kMinPrune;
+    }
+    *prune_rows = elements * size_over / sizes;
+    if (*prune_rows < min_elements) {
+        *prune_rows = min_elements;
+    }
+    if (*prune_rows > kMaxPrune) {
+        *prune_rows = kMaxPrune;
+    }
+
+    return true;
+}
+
+std::string UidEntry::formatHeader(const std::string& name, log_id_t id) const {
+    bool isprune = worstUidEnabledForLogid(id);
+    return formatLine(android::base::StringPrintf(name.c_str(),
+                                                  android_log_id_to_name(id)),
+                      std::string("Size"),
+                      std::string(isprune ? "+/-  Pruned" : "")) +
+           formatLine(std::string("UID   PACKAGE"), std::string("BYTES"),
+                      std::string(isprune ? "NUM" : ""));
+}
+
+// Helper to truncate name, if too long, and add name dressings
+void LogStatistics::FormatTmp(const char* nameTmp, uid_t uid, std::string& name, std::string& size,
+                              size_t nameLen) const {
+    const char* allocNameTmp = nullptr;
+    if (!nameTmp) nameTmp = allocNameTmp = UidToNameLocked(uid);
+    if (nameTmp) {
+        size_t lenSpace = std::max(nameLen - name.length(), (size_t)1);
+        size_t len = EntryBase::TOTAL_LEN - EntryBase::PRUNED_LEN - size.length() - name.length() -
+                     lenSpace - 2;
+        size_t lenNameTmp = strlen(nameTmp);
+        while ((len < lenNameTmp) && (lenSpace > 1)) {
+            ++len;
+            --lenSpace;
+        }
+        name += android::base::StringPrintf("%*s", (int)lenSpace, "");
+        if (len < lenNameTmp) {
+            name += "...";
+            nameTmp += lenNameTmp - std::max(len - 3, (size_t)1);
+        }
+        name += nameTmp;
+        free(const_cast<char*>(allocNameTmp));
+    }
+}
+
+std::string UidEntry::format(const LogStatistics& stat, log_id_t id, uid_t uid) const
+        REQUIRES(stat.lock_) {
+    std::string name = android::base::StringPrintf("%u", uid);
+    std::string size = android::base::StringPrintf("%zu", getSizes());
+
+    stat.FormatTmp(nullptr, uid, name, size, 6);
+
+    std::string pruned = "";
+    if (worstUidEnabledForLogid(id)) {
+        size_t totalDropped = 0;
+        for (LogStatistics::uidTable_t::const_iterator it =
+                 stat.uidTable[id].begin();
+             it != stat.uidTable[id].end(); ++it) {
+            totalDropped += it->second.dropped_count();
+        }
+        size_t sizes = stat.mSizes[id];
+        size_t totalSize = stat.mSizesTotal[id];
+        size_t totalElements = stat.mElementsTotal[id];
+        float totalVirtualSize =
+            (float)sizes + (float)totalDropped * totalSize / totalElements;
+        size_t entrySize = getSizes();
+        float virtualEntrySize = entrySize;
+        int realPermille = virtualEntrySize * 1000.0 / sizes;
+        size_t dropped = dropped_count();
+        if (dropped) {
+            pruned = android::base::StringPrintf("%zu", dropped);
+            virtualEntrySize += (float)dropped * totalSize / totalElements;
+        }
+        int virtualPermille = virtualEntrySize * 1000.0 / totalVirtualSize;
+        int permille =
+            (realPermille - virtualPermille) * 1000L / (virtualPermille ?: 1);
+        if ((permille < -1) || (1 < permille)) {
+            std::string change;
+            const char* units = "%";
+            const char* prefix = (permille > 0) ? "+" : "";
+
+            if (permille > 999) {
+                permille = (permille + 1000) / 100;  // Now tenths fold
+                units = "X";
+                prefix = "";
+            }
+            if ((-99 < permille) && (permille < 99)) {
+                change = android::base::StringPrintf(
+                    "%s%d.%u%s", prefix, permille / 10,
+                    ((permille < 0) ? (-permille % 10) : (permille % 10)),
+                    units);
+            } else {
+                change = android::base::StringPrintf(
+                    "%s%d%s", prefix, (permille + 5) / 10, units);
+            }
+            ssize_t spaces = EntryBase::PRUNED_LEN - 2 - pruned.length() - change.length();
+            if ((spaces <= 0) && pruned.length()) {
+                spaces = 1;
+            }
+            if (spaces > 0) {
+                change += android::base::StringPrintf("%*s", (int)spaces, "");
+            }
+            pruned = change + pruned;
+        }
+    }
+
+    std::string output = formatLine(name, size, pruned);
+
+    if (uid != AID_SYSTEM) {
+        return output;
+    }
+
+    static const size_t maximum_sorted_entries = 32;
+    std::array<const pid_t*, maximum_sorted_entries> sorted_pids;
+    std::array<const PidEntry*, maximum_sorted_entries> sorted_entries;
+    stat.pidSystemTable[id].MaxEntries(uid, 0, sorted_pids, sorted_entries);
+
+    std::string byPid;
+    size_t index;
+    bool hasDropped = false;
+    for (index = 0; index < maximum_sorted_entries; ++index) {
+        const PidEntry* entry = sorted_entries[index];
+        if (!entry) {
+            break;
+        }
+        if (entry->getSizes() <= (getSizes() / 100)) {
+            break;
+        }
+        if (entry->dropped_count()) {
+            hasDropped = true;
+        }
+        byPid += entry->format(stat, id, *sorted_pids[index]);
+    }
+    if (index > 1) {  // print this only if interesting
+        std::string ditto("\" ");
+        output += formatLine(std::string("  PID/UID   COMMAND LINE"), ditto,
+                             hasDropped ? ditto : std::string(""));
+        output += byPid;
+    }
+
+    return output;
+}
+
+std::string PidEntry::formatHeader(const std::string& name,
+                                   log_id_t /* id */) const {
+    return formatLine(name, std::string("Size"), std::string("Pruned")) +
+           formatLine(std::string("  PID/UID   COMMAND LINE"),
+                      std::string("BYTES"), std::string("NUM"));
+}
+
+std::string PidEntry::format(const LogStatistics& stat, log_id_t, pid_t pid) const
+        REQUIRES(stat.lock_) {
+    std::string name = android::base::StringPrintf("%5u/%u", pid, uid_);
+    std::string size = android::base::StringPrintf("%zu", getSizes());
+
+    stat.FormatTmp(name_, uid_, name, size, 12);
+
+    std::string pruned = "";
+    size_t dropped = dropped_count();
+    if (dropped) {
+        pruned = android::base::StringPrintf("%zu", dropped);
+    }
+
+    return formatLine(name, size, pruned);
+}
+
+std::string TidEntry::formatHeader(const std::string& name,
+                                   log_id_t /* id */) const {
+    return formatLine(name, std::string("Size"), std::string("Pruned")) +
+           formatLine(std::string("  TID/UID   COMM"), std::string("BYTES"),
+                      std::string("NUM"));
+}
+
+std::string TidEntry::format(const LogStatistics& stat, log_id_t, pid_t tid) const
+        REQUIRES(stat.lock_) {
+    std::string name = android::base::StringPrintf("%5u/%u", tid, uid_);
+    std::string size = android::base::StringPrintf("%zu", getSizes());
+
+    stat.FormatTmp(name_, uid_, name, size, 12);
+
+    std::string pruned = "";
+    size_t dropped = dropped_count();
+    if (dropped) {
+        pruned = android::base::StringPrintf("%zu", dropped);
+    }
+
+    return formatLine(name, size, pruned);
+}
+
+std::string TagEntry::formatHeader(const std::string& name, log_id_t id) const {
+    bool isprune = worstUidEnabledForLogid(id);
+    return formatLine(name, std::string("Size"),
+                      std::string(isprune ? "Prune" : "")) +
+           formatLine(std::string("    TAG/UID   TAGNAME"),
+                      std::string("BYTES"), std::string(isprune ? "NUM" : ""));
+}
+
+std::string TagEntry::format(const LogStatistics&, log_id_t, uint32_t) const {
+    std::string name;
+    if (uid_ == (uid_t)-1) {
+        name = android::base::StringPrintf("%7u", key());
+    } else {
+        name = android::base::StringPrintf("%7u/%u", key(), uid_);
+    }
+    const char* nameTmp = this->name();
+    if (nameTmp) {
+        name += android::base::StringPrintf(
+            "%*s%s", (int)std::max(14 - name.length(), (size_t)1), "", nameTmp);
+    }
+
+    std::string size = android::base::StringPrintf("%zu", getSizes());
+
+    std::string pruned = "";
+    size_t dropped = dropped_count();
+    if (dropped) {
+        pruned = android::base::StringPrintf("%zu", dropped);
+    }
+
+    return formatLine(name, size, pruned);
+}
+
+std::string TagNameEntry::formatHeader(const std::string& name,
+                                       log_id_t /* id */) const {
+    return formatLine(name, std::string("Size"), std::string("")) +
+           formatLine(std::string("  TID/PID/UID   LOG_TAG NAME"),
+                      std::string("BYTES"), std::string(""));
+}
+
+std::string TagNameEntry::format(const LogStatistics&, log_id_t,
+                                 const std::string& key_name) const {
+    std::string name;
+    std::string pidstr;
+    if (pid_ != (pid_t)-1) {
+        pidstr = android::base::StringPrintf("%u", pid_);
+        if (tid_ != (pid_t)-1 && tid_ != pid_) pidstr = "/" + pidstr;
+    }
+    int len = 9 - pidstr.length();
+    if (len < 0) len = 0;
+    if (tid_ == (pid_t)-1 || tid_ == pid_) {
+        name = android::base::StringPrintf("%*s", len, "");
+    } else {
+        name = android::base::StringPrintf("%*u", len, tid_);
+    }
+    name += pidstr;
+    if (uid_ != (uid_t)-1) {
+        name += android::base::StringPrintf("/%u", uid_);
+    }
+
+    std::string size = android::base::StringPrintf("%zu", getSizes());
+
+    const char* nameTmp = key_name.data();
+    if (nameTmp) {
+        size_t lenSpace = std::max(16 - name.length(), (size_t)1);
+        size_t len = EntryBase::TOTAL_LEN - EntryBase::PRUNED_LEN - size.length() - name.length() -
+                     lenSpace - 2;
+        size_t lenNameTmp = strlen(nameTmp);
+        while ((len < lenNameTmp) && (lenSpace > 1)) {
+            ++len;
+            --lenSpace;
+        }
+        name += android::base::StringPrintf("%*s", (int)lenSpace, "");
+        if (len < lenNameTmp) {
+            name += "...";
+            nameTmp += lenNameTmp - std::max(len - 3, (size_t)1);
+        }
+        name += nameTmp;
+    }
+
+    std::string pruned = "";
+
+    return formatLine(name, size, pruned);
+}
+
+static std::string formatMsec(uint64_t val) {
+    static const unsigned subsecDigits = 3;
+    static const uint64_t sec = MS_PER_SEC;
+
+    static const uint64_t minute = 60 * sec;
+    static const uint64_t hour = 60 * minute;
+    static const uint64_t day = 24 * hour;
+
+    std::string output;
+    if (val < sec) return output;
+
+    if (val >= day) {
+        output = android::base::StringPrintf("%" PRIu64 "d ", val / day);
+        val = (val % day) + day;
+    }
+    if (val >= minute) {
+        if (val >= hour) {
+            output += android::base::StringPrintf("%" PRIu64 ":",
+                                                  (val / hour) % (day / hour));
+        }
+        output += android::base::StringPrintf(
+            (val >= hour) ? "%02" PRIu64 ":" : "%" PRIu64 ":",
+            (val / minute) % (hour / minute));
+    }
+    output +=
+        android::base::StringPrintf((val >= minute) ? "%02" PRIu64 : "%" PRIu64,
+                                    (val / sec) % (minute / sec));
+    val %= sec;
+    unsigned digits = subsecDigits;
+    while (digits && ((val % 10) == 0)) {
+        val /= 10;
+        --digits;
+    }
+    if (digits) {
+        output += android::base::StringPrintf(".%0*" PRIu64, digits, val);
+    }
+    return output;
+}
+
+template <typename TKey, typename TEntry>
+std::string LogStatistics::FormatTable(const LogHashtable<TKey, TEntry>& table, uid_t uid,
+                                       pid_t pid, const std::string& name, log_id_t id) const
+        REQUIRES(lock_) {
+    static const size_t maximum_sorted_entries = 32;
+    std::string output;
+    std::array<const TKey*, maximum_sorted_entries> sorted_keys;
+    std::array<const TEntry*, maximum_sorted_entries> sorted_entries;
+    table.MaxEntries(uid, pid, sorted_keys, sorted_entries);
+    bool header_printed = false;
+    for (size_t index = 0; index < maximum_sorted_entries; ++index) {
+        const TEntry* entry = sorted_entries[index];
+        if (!entry) {
+            break;
+        }
+        if (entry->getSizes() <= (sorted_entries[0]->getSizes() / 100)) {
+            break;
+        }
+        if (!header_printed) {
+            output += "\n\n";
+            output += entry->formatHeader(name, id);
+            header_printed = true;
+        }
+        output += entry->format(*this, id, *sorted_keys[index]);
+    }
+    return output;
+}
+
+std::string LogStatistics::ReportInteresting() const {
+    auto lock = std::lock_guard{lock_};
+
+    std::vector<std::string> items;
+
+    log_id_for_each(i) { items.emplace_back(std::to_string(mElements[i])); }
+
+    log_id_for_each(i) { items.emplace_back(std::to_string(mSizes[i])); }
+
+    log_id_for_each(i) {
+        items.emplace_back(std::to_string(overhead_[i] ? *overhead_[i] : mSizes[i]));
+    }
+
+    log_id_for_each(i) {
+        uint64_t oldest = mOldest[i].msec() / 1000;
+        uint64_t newest = mNewest[i].msec() / 1000;
+
+        int span = newest - oldest;
+
+        items.emplace_back(std::to_string(span));
+    }
+
+    return android::base::Join(items, ",");
+}
+
+std::string LogStatistics::Format(uid_t uid, pid_t pid, unsigned int logMask) const {
+    auto lock = std::lock_guard{lock_};
+
+    static const uint16_t spaces_total = 19;
+
     // Report on total logging, current and for all time
 
-    android::String8 output("size/num");
+    std::string output = "size/num";
     size_t oldLength;
-    short spaces = 1;
+    int16_t spaces = 1;
 
     log_id_for_each(id) {
-        if (!(logMask & (1 << id))) {
-            continue;
-        }
+        if (!(logMask & (1 << id))) continue;
         oldLength = output.length();
-        if (spaces < 0) {
-            spaces = 0;
-        }
-        output.appendFormat("%*s%s", spaces, "", android_log_id_to_name(id));
+        if (spaces < 0) spaces = 0;
+        output += android::base::StringPrintf("%*s%s", spaces, "",
+                                              android_log_id_to_name(id));
         spaces += spaces_total + oldLength - output.length();
     }
+    if (spaces < 0) spaces = 0;
+    output += android::base::StringPrintf("%*sTotal", spaces, "");
 
-    spaces = 4;
-    output.appendFormat("\nTotal");
+    static const char TotalStr[] = "\nTotal";
+    spaces = 10 - strlen(TotalStr);
+    output += TotalStr;
 
+    size_t totalSize = 0;
+    size_t totalEls = 0;
     log_id_for_each(id) {
-        if (!(logMask & (1 << id))) {
-            continue;
-        }
+        if (!(logMask & (1 << id))) continue;
         oldLength = output.length();
-        if (spaces < 0) {
-            spaces = 0;
-        }
-        output.appendFormat("%*s%zu/%zu", spaces, "",
-                            sizesTotal(id), elementsTotal(id));
+        if (spaces < 0) spaces = 0;
+        size_t szs = mSizesTotal[id];
+        totalSize += szs;
+        size_t els = mElementsTotal[id];
+        totalEls += els;
+        output +=
+            android::base::StringPrintf("%*s%zu/%zu", spaces, "", szs, els);
         spaces += spaces_total + oldLength - output.length();
     }
+    if (spaces < 0) spaces = 0;
+    output += android::base::StringPrintf("%*s%zu/%zu", spaces, "", totalSize,
+                                          totalEls);
 
-    spaces = 6;
-    output.appendFormat("\nNow");
+    static const char NowStr[] = "\nNow";
+    spaces = 10 - strlen(NowStr);
+    output += NowStr;
 
+    totalSize = 0;
+    totalEls = 0;
     log_id_for_each(id) {
-        if (!(logMask & (1 << id))) {
-            continue;
-        }
+        if (!(logMask & (1 << id))) continue;
 
-        size_t els = elements(id);
+        size_t els = mElements[id];
         if (els) {
             oldLength = output.length();
-            if (spaces < 0) {
-                spaces = 0;
-            }
-            output.appendFormat("%*s%zu/%zu", spaces, "", sizes(id), els);
+            if (spaces < 0) spaces = 0;
+            size_t szs = mSizes[id];
+            totalSize += szs;
+            totalEls += els;
+            output +=
+                android::base::StringPrintf("%*s%zu/%zu", spaces, "", szs, els);
             spaces -= output.length() - oldLength;
         }
         spaces += spaces_total;
     }
+    if (spaces < 0) spaces = 0;
+    output += android::base::StringPrintf("%*s%zu/%zu", spaces, "", totalSize,
+                                          totalEls);
 
-    // Report on Chattiest
+    static const char SpanStr[] = "\nLogspan";
+    spaces = 10 - strlen(SpanStr);
+    output += SpanStr;
 
-    // Chattiest by application (UID)
-    static const size_t maximum_sorted_entries = 32;
+    // Total reports the greater of the individual maximum time span, or the
+    // validated minimum start and maximum end time span if it makes sense.
+    uint64_t minTime = UINT64_MAX;
+    uint64_t maxTime = 0;
+    uint64_t maxSpan = 0;
+    totalSize = 0;
+
     log_id_for_each(id) {
-        if (!(logMask & (1 << id))) {
+        if (!(logMask & (1 << id))) continue;
+
+        // validity checking
+        uint64_t oldest = mOldest[id].msec();
+        uint64_t newest = mNewest[id].msec();
+        if (newest <= oldest) {
+            spaces += spaces_total;
             continue;
         }
 
-        bool headerPrinted = false;
-        std::unique_ptr<const UidEntry *[]> sorted = sort(maximum_sorted_entries, id);
-        ssize_t index = -1;
-        while ((index = uidTable_t::next(index, sorted, maximum_sorted_entries)) >= 0) {
-            const UidEntry *entry = sorted[index];
-            uid_t u = entry->getKey();
-            if ((uid != AID_ROOT) && (u != uid)) {
-                continue;
-            }
-
-            if (!headerPrinted) {
-                output.appendFormat("\n\n");
-                android::String8 name("");
-                if (uid == AID_ROOT) {
-                    name.appendFormat(
-                        "Chattiest UIDs in %s log buffer:",
-                        android_log_id_to_name(id));
-                } else {
-                    name.appendFormat(
-                        "Logging for your UID in %s log buffer:",
-                        android_log_id_to_name(id));
-                }
-                android::String8 size("Size");
-                android::String8 pruned("Pruned");
-                if (!worstUidEnabledForLogid(id)) {
-                    pruned.setTo("");
-                }
-                format_line(output, name, size, pruned);
-
-                name.setTo("UID   PACKAGE");
-                size.setTo("BYTES");
-                pruned.setTo("LINES");
-                if (!worstUidEnabledForLogid(id)) {
-                    pruned.setTo("");
-                }
-                format_line(output, name, size, pruned);
-
-                headerPrinted = true;
-            }
-
-            android::String8 name("");
-            name.appendFormat("%u", u);
-            char *n = uidToName(u);
-            if (n) {
-                name.appendFormat("%*s%s", (int)std::max(6 - name.length(), (size_t)1), "", n);
-                free(n);
-            }
-
-            android::String8 size("");
-            size.appendFormat("%zu", entry->getSizes());
-
-            android::String8 pruned("");
-            size_t dropped = entry->getDropped();
-            if (dropped) {
-                pruned.appendFormat("%zu", dropped);
-            }
-
-            format_line(output, name, size, pruned);
+        uint64_t span = newest - oldest;
+        if (span > (monthSec * MS_PER_SEC)) {
+            spaces += spaces_total;
+            continue;
         }
+
+        // total span
+        if (minTime > oldest) minTime = oldest;
+        if (maxTime < newest) maxTime = newest;
+        if (span > maxSpan) maxSpan = span;
+        totalSize += span;
+
+        uint64_t dropped = mNewestDropped[id].msec();
+        if (dropped < oldest) dropped = oldest;
+        if (dropped > newest) dropped = newest;
+
+        oldLength = output.length();
+        output += android::base::StringPrintf("%*s%s", spaces, "",
+                                              formatMsec(span).c_str());
+        unsigned permille = ((newest - dropped) * 1000 + (span / 2)) / span;
+        if ((permille > 1) && (permille < 999)) {
+            output += android::base::StringPrintf("(%u", permille / 10);
+            permille %= 10;
+            if (permille) {
+                output += android::base::StringPrintf(".%u", permille);
+            }
+            output += android::base::StringPrintf("%%)");
+        }
+        spaces -= output.length() - oldLength;
+        spaces += spaces_total;
+    }
+    if ((maxTime > minTime) && ((maxTime -= minTime) < totalSize) &&
+        (maxTime > maxSpan)) {
+        maxSpan = maxTime;
+    }
+    if (spaces < 0) spaces = 0;
+    output += android::base::StringPrintf("%*s%s", spaces, "",
+                                          formatMsec(maxSpan).c_str());
+
+    static const char OverheadStr[] = "\nOverhead";
+    spaces = 10 - strlen(OverheadStr);
+    output += OverheadStr;
+
+    totalSize = 0;
+    log_id_for_each(id) {
+        if (!(logMask & (1 << id))) continue;
+
+        size_t els = mElements[id];
+        if (els) {
+            oldLength = output.length();
+            if (spaces < 0) spaces = 0;
+            size_t szs = 0;
+            if (overhead_[id]) {
+                szs = *overhead_[id];
+            } else if (track_total_size_) {
+                szs = mSizes[id];
+            } else {
+                // Legacy fallback for Chatty without track_total_size_
+                // Estimate the size of this element in the parent std::list<> by adding two void*'s
+                // corresponding to the next/prev pointers and aligning to 64 bit.
+                static const size_t overhead =
+                        (sizeof(LogBufferElement) + 2 * sizeof(void*) + sizeof(uint64_t) - 1) &
+                        -sizeof(uint64_t);
+                szs = mSizes[id] + els * overhead;
+            }
+            totalSize += szs;
+            output += android::base::StringPrintf("%*s%zu", spaces, "", szs);
+            spaces -= output.length() - oldLength;
+        }
+        spaces += spaces_total;
+    }
+    totalSize += sizeOf();
+    if (spaces < 0) spaces = 0;
+    output += android::base::StringPrintf("%*s%zu", spaces, "", totalSize);
+
+    // Report on Chattiest
+
+    std::string name;
+
+    // Chattiest by application (UID)
+    log_id_for_each(id) {
+        if (!(logMask & (1 << id))) continue;
+
+        name = (uid == AID_ROOT) ? "Chattiest UIDs in %s log buffer:"
+                                 : "Logging for your UID in %s log buffer:";
+        output += FormatTable(uidTable[id], uid, pid, name, id);
     }
 
     if (enable) {
-        // Pid table
-        bool headerPrinted = false;
-        std::unique_ptr<const PidEntry *[]> sorted = pidTable.sort(maximum_sorted_entries);
-        ssize_t index = -1;
-        while ((index = pidTable.next(index, sorted, maximum_sorted_entries)) >= 0) {
-            const PidEntry *entry = sorted[index];
-            uid_t u = entry->getUid();
-            if ((uid != AID_ROOT) && (u != uid)) {
-                continue;
-            }
-
-            if (!headerPrinted) {
-                output.appendFormat("\n\n");
-                android::String8 name("");
-                if (uid == AID_ROOT) {
-                    name.appendFormat("Chattiest PIDs:");
-                } else {
-                    name.appendFormat("Logging for this PID:");
-                }
-                android::String8 size("Size");
-                android::String8 pruned("Pruned");
-                format_line(output, name, size, pruned);
-
-                name.setTo("  PID/UID   COMMAND LINE");
-                size.setTo("BYTES");
-                pruned.setTo("LINES");
-                format_line(output, name, size, pruned);
-
-                headerPrinted = true;
-            }
-
-            android::String8 name("");
-            name.appendFormat("%5u/%u", entry->getKey(), u);
-            const char *n = entry->getName();
-            if (n) {
-                name.appendFormat("%*s%s", (int)std::max(12 - name.length(), (size_t)1), "", n);
-            } else {
-                char *un = uidToName(u);
-                if (un) {
-                    name.appendFormat("%*s%s", (int)std::max(12 - name.length(), (size_t)1), "", un);
-                    free(un);
-                }
-            }
-
-            android::String8 size("");
-            size.appendFormat("%zu", entry->getSizes());
-
-            android::String8 pruned("");
-            size_t dropped = entry->getDropped();
-            if (dropped) {
-                pruned.appendFormat("%zu", dropped);
-            }
-
-            format_line(output, name, size, pruned);
-        }
-    }
-
-    if (enable) {
-        // Tid table
-        bool headerPrinted = false;
-        // sort() returns list of references, unique_ptr makes sure self-delete
-        std::unique_ptr<const TidEntry *[]> sorted = tidTable.sort(maximum_sorted_entries);
-        ssize_t index = -1;
-        while ((index = tidTable.next(index, sorted, maximum_sorted_entries)) >= 0) {
-            const TidEntry *entry = sorted[index];
-            uid_t u = entry->getUid();
-            if ((uid != AID_ROOT) && (u != uid)) {
-                continue;
-            }
-
-            if (!headerPrinted) { // Only print header if we have table to print
-                output.appendFormat("\n\n");
-                android::String8 name("Chattiest TIDs:");
-                android::String8 size("Size");
-                android::String8 pruned("Pruned");
-                format_line(output, name, size, pruned);
-
-                name.setTo("  TID/UID   COMM");
-                size.setTo("BYTES");
-                pruned.setTo("LINES");
-                format_line(output, name, size, pruned);
-
-                headerPrinted = true;
-            }
-
-            android::String8 name("");
-            name.appendFormat("%5u/%u", entry->getKey(), u);
-            const char *n = entry->getName();
-            if (n) {
-                name.appendFormat("%*s%s", (int)std::max(12 - name.length(), (size_t)1), "", n);
-            } else {
-                // if we do not have a PID name, lets punt to try UID name?
-                char *un = uidToName(u);
-                if (un) {
-                    name.appendFormat("%*s%s", (int)std::max(12 - name.length(), (size_t)1), "", un);
-                    free(un);
-                }
-                // We tried, better to not have a name at all, we still
-                // have TID/UID by number to report in any case.
-            }
-
-            android::String8 size("");
-            size.appendFormat("%zu", entry->getSizes());
-
-            android::String8 pruned("");
-            size_t dropped = entry->getDropped();
-            if (dropped) {
-                pruned.appendFormat("%zu", dropped);
-            }
-
-            format_line(output, name, size, pruned);
-        }
+        name = ((uid == AID_ROOT) && !pid) ? "Chattiest PIDs:"
+                                           : "Logging for this PID:";
+        output += FormatTable(pidTable, uid, pid, name);
+        name = "Chattiest TIDs";
+        if (pid) name += android::base::StringPrintf(" for PID %d", pid);
+        name += ":";
+        output += FormatTable(tidTable, uid, pid, name);
     }
 
     if (enable && (logMask & (1 << LOG_ID_EVENTS))) {
-        // Tag table
-        bool headerPrinted = false;
-        std::unique_ptr<const TagEntry *[]> sorted = tagTable.sort(maximum_sorted_entries);
-        ssize_t index = -1;
-        while ((index = tagTable.next(index, sorted, maximum_sorted_entries)) >= 0) {
-            const TagEntry *entry = sorted[index];
-            uid_t u = entry->getUid();
-            if ((uid != AID_ROOT) && (u != uid)) {
-                continue;
-            }
-
-            android::String8 pruned("");
-
-            if (!headerPrinted) {
-                output.appendFormat("\n\n");
-                android::String8 name("Chattiest events log buffer TAGs:");
-                android::String8 size("Size");
-                format_line(output, name, size, pruned);
-
-                name.setTo("    TAG/UID   TAGNAME");
-                size.setTo("BYTES");
-                format_line(output, name, size, pruned);
-
-                headerPrinted = true;
-            }
-
-            android::String8 name("");
-            if (u == (uid_t)-1) {
-                name.appendFormat("%7u", entry->getKey());
-            } else {
-                name.appendFormat("%7u/%u", entry->getKey(), u);
-            }
-            const char *n = entry->getName();
-            if (n) {
-                name.appendFormat("%*s%s", (int)std::max(14 - name.length(), (size_t)1), "", n);
-            }
-
-            android::String8 size("");
-            size.appendFormat("%zu", entry->getSizes());
-
-            format_line(output, name, size, pruned);
-        }
+        name = "Chattiest events log buffer TAGs";
+        if (pid) name += android::base::StringPrintf(" for PID %d", pid);
+        name += ":";
+        output += FormatTable(tagTable, uid, pid, name, LOG_ID_EVENTS);
     }
 
-    *buf = strdup(output.string());
+    if (enable && (logMask & (1 << LOG_ID_SECURITY))) {
+        name = "Chattiest security log buffer TAGs";
+        if (pid) name += android::base::StringPrintf(" for PID %d", pid);
+        name += ":";
+        output += FormatTable(securityTagTable, uid, pid, name, LOG_ID_SECURITY);
+    }
+
+    if (enable) {
+        name = "Chattiest TAGs";
+        if (pid) name += android::base::StringPrintf(" for PID %d", pid);
+        name += ":";
+        output += FormatTable(tagNameTable, uid, pid, name);
+    }
+
+    return output;
 }
 
 namespace android {
@@ -502,31 +1036,36 @@
 uid_t pidToUid(pid_t pid) {
     char buffer[512];
     snprintf(buffer, sizeof(buffer), "/proc/%u/status", pid);
-    FILE *fp = fopen(buffer, "r");
+    FILE* fp = fopen(buffer, "re");
     if (fp) {
         while (fgets(buffer, sizeof(buffer), fp)) {
-            int uid;
-            if (sscanf(buffer, "Uid: %d", &uid) == 1) {
+            int uid = AID_LOGD;
+            char space = 0;
+            if ((sscanf(buffer, "Uid: %d%c", &uid, &space) == 2) &&
+                isspace(space)) {
                 fclose(fp);
                 return uid;
             }
         }
         fclose(fp);
     }
-    return AID_LOGD; // associate this with the logger
+    return AID_LOGD;  // associate this with the logger
+}
 }
 
-}
-
-uid_t LogStatistics::pidToUid(pid_t pid) {
-    return pidTable.add(pid)->second.getUid();
+uid_t LogStatistics::PidToUid(pid_t pid) {
+    auto lock = std::lock_guard{lock_};
+    return pidTable.Add(pid)->second.uid();
 }
 
 // caller must free character string
-char *LogStatistics::pidToName(pid_t pid) {
-    const char *name = pidTable.add(pid)->second.getName();
+const char* LogStatistics::PidToName(pid_t pid) const {
+    auto lock = std::lock_guard{lock_};
+    // An inconvenient truth ... getName() can alter the object
+    pidTable_t& writablePidTable = const_cast<pidTable_t&>(pidTable);
+    const char* name = writablePidTable.Add(pid)->second.name();
     if (!name) {
-        return NULL;
+        return nullptr;
     }
     return strdup(name);
 }
diff --git a/logd/LogStatistics.h b/logd/LogStatistics.h
index 760d6b2..faf9283 100644
--- a/logd/LogStatistics.h
+++ b/logd/LogStatistics.h
@@ -14,330 +14,582 @@
  * limitations under the License.
  */
 
-#ifndef _LOGD_LOG_STATISTICS_H__
-#define _LOGD_LOG_STATISTICS_H__
+#pragma once
 
-#include <memory>
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdint.h>
 #include <stdlib.h>
+#include <string.h>
 #include <sys/types.h>
 
+#include <algorithm>  // std::max
+#include <array>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <string_view>
 #include <unordered_map>
 
-#include <log/log.h>
+#include <android-base/stringprintf.h>
+#include <android-base/thread_annotations.h>
+#include <android/log.h>
+#include <log/log_time.h>
+#include <private/android_filesystem_config.h>
+#include <utils/FastStrcmp.h>
 
-#include "LogBufferElement.h"
+#include "LogUtils.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))
+    for (log_id_t i = LOG_ID_MIN; (i) < LOG_ID_MAX; (i) = (log_id_t)((i) + 1))
+
+class LogStatistics;
+class UidEntry;
+class PidEntry;
+
+struct LogStatisticsElement {
+    uid_t uid;
+    pid_t pid;
+    pid_t tid;
+    uint32_t tag;
+    log_time realtime;
+    const char* msg;
+    uint16_t msg_len;
+    uint16_t dropped_count;
+    log_id_t log_id;
+    uint16_t total_len;
+};
 
 template <typename TKey, typename TEntry>
 class LogHashtable {
-
     std::unordered_map<TKey, TEntry> map;
 
-public:
+    size_t bucket_size() const {
+        size_t count = 0;
+        for (size_t idx = 0; idx < map.bucket_count(); ++idx) {
+            size_t bucket_size = map.bucket_size(idx);
+            if (bucket_size == 0) bucket_size = 1;
+            count += bucket_size;
+        }
+        float load_factor = map.max_load_factor();
+        if (load_factor < 1.0) return count;
+        return count * load_factor;
+    }
+
+    static const size_t unordered_map_per_entry_overhead = sizeof(void*);
+    static const size_t unordered_map_bucket_overhead = sizeof(void*);
+
+  public:
+    size_t size() const {
+        return map.size();
+    }
+
+    // Estimate unordered_map memory usage.
+    size_t sizeOf() const {
+        return sizeof(*this) +
+               (size() * (sizeof(TEntry) + unordered_map_per_entry_overhead)) +
+               (bucket_size() * sizeof(size_t) + unordered_map_bucket_overhead);
+    }
 
     typedef typename std::unordered_map<TKey, TEntry>::iterator iterator;
+    typedef
+        typename std::unordered_map<TKey, TEntry>::const_iterator const_iterator;
 
-    std::unique_ptr<const TEntry *[]> sort(size_t n) {
-        if (!n) {
-            std::unique_ptr<const TEntry *[]> sorted(NULL);
-            return sorted;
-        }
+    // Returns a sorted array of up to len highest entries sorted by size.  If fewer than len
+    // entries are found, their positions are set to nullptr.
+    template <size_t len>
+    void MaxEntries(uid_t uid, pid_t pid, std::array<const TKey*, len>& out_keys,
+                    std::array<const TEntry*, len>& out_entries) const {
+        out_keys.fill(nullptr);
+        out_entries.fill(nullptr);
+        for (const auto& [key, entry] : map) {
+            uid_t entry_uid = 0;
+            if constexpr (std::is_same_v<TEntry, UidEntry>) {
+                entry_uid = key;
+            } else {
+                entry_uid = entry.uid();
+            }
+            if (uid != AID_ROOT && uid != entry_uid) {
+                continue;
+            }
+            pid_t entry_pid = 0;
+            if constexpr (std::is_same_v<TEntry, PidEntry>) {
+                entry_pid = key;
+            } else {
+                entry_pid = entry.pid();
+            }
+            if (pid && entry_pid && pid != entry_pid) {
+                continue;
+            }
 
-        const TEntry **retval = new const TEntry* [n];
-        memset(retval, 0, sizeof(*retval) * n);
-
-        for(iterator it = map.begin(); it != map.end(); ++it) {
-            const TEntry &entry = it->second;
-            size_t s = entry.getSizes();
-            ssize_t i = n - 1;
-            while ((!retval[i] || (s > retval[i]->getSizes())) && (--i >= 0))
+            size_t sizes = entry.getSizes();
+            ssize_t index = len - 1;
+            while ((!out_entries[index] || sizes > out_entries[index]->getSizes()) && --index >= 0)
                 ;
-            if (++i < (ssize_t)n) {
-                size_t b = n - i - 1;
-                if (b) {
-                    memmove(&retval[i+1], &retval[i], b * sizeof(retval[0]));
+            if (++index < (ssize_t)len) {
+                size_t num = len - index - 1;
+                if (num) {
+                    memmove(&out_keys[index + 1], &out_keys[index], num * sizeof(const TKey*));
+                    memmove(&out_entries[index + 1], &out_entries[index],
+                            num * sizeof(const TEntry*));
                 }
-                retval[i] = &entry;
+                out_keys[index] = &key;
+                out_entries[index] = &entry;
             }
         }
-        std::unique_ptr<const TEntry *[]> sorted(retval);
-        return sorted;
     }
 
-    // Iteration handler for the sort method output
-    static ssize_t next(ssize_t index, std::unique_ptr<const TEntry *[]> &sorted, size_t n) {
-        ++index;
-        if (!sorted.get() || (index < 0) || (n <= (size_t)index) || !sorted[index]
-         || (sorted[index]->getSizes() <= (sorted[0]->getSizes() / 100))) {
-            return -1;
-        }
-        return index;
-    }
-
-    inline iterator add(TKey key, LogBufferElement *e) {
+    iterator Add(const TKey& key, const LogStatisticsElement& element) {
         iterator it = map.find(key);
         if (it == map.end()) {
-            it = map.insert(std::make_pair(key, TEntry(e))).first;
+            it = map.insert(std::make_pair(key, TEntry(element))).first;
         } else {
-            it->second.add(e);
+            it->second.Add(element);
         }
         return it;
     }
 
-    inline iterator add(TKey key) {
+    iterator Add(const TKey& key) {
         iterator it = map.find(key);
         if (it == map.end()) {
             it = map.insert(std::make_pair(key, TEntry(key))).first;
         } else {
-            it->second.add(key);
+            it->second.Add(key);
         }
         return it;
     }
 
-    void subtract(TKey key, LogBufferElement *e) {
+    void Subtract(const TKey& key, const LogStatisticsElement& element) {
         iterator it = map.find(key);
-        if ((it != map.end()) && it->second.subtract(e)) {
+        if (it != map.end() && it->second.Subtract(element)) {
             map.erase(it);
         }
     }
 
-    inline void drop(TKey key, LogBufferElement *e) {
+    void Drop(const TKey& key, const LogStatisticsElement& element) {
         iterator it = map.find(key);
         if (it != map.end()) {
-            it->second.drop(e);
+            it->second.Drop(element);
         }
     }
 
-    inline iterator begin() { return map.begin(); }
-    inline iterator end() { return map.end(); }
+    void Erase(const TKey& key, const LogStatisticsElement& element) {
+        iterator it = map.find(key);
+        if (it != map.end()) {
+            it->second.Erase(element);
+        }
+    }
 
+    iterator begin() { return map.begin(); }
+    const_iterator begin() const { return map.begin(); }
+    iterator end() { return map.end(); }
+    const_iterator end() const { return map.end(); }
 };
 
-struct EntryBase {
-    size_t size;
+class EntryBase {
+  public:
+    EntryBase() : size_(0) {}
+    explicit EntryBase(const LogStatisticsElement& element) : size_(element.total_len) {}
 
-    EntryBase():size(0) { }
-    EntryBase(LogBufferElement *e):size(e->getMsgLen()) { }
+    size_t getSizes() const { return size_; }
 
-    size_t getSizes() const { return size; }
+    void Add(const LogStatisticsElement& element) { size_ += element.total_len; }
+    bool Subtract(const LogStatisticsElement& element) {
+        size_ -= element.total_len;
+        return size_ == 0;
+    }
+    void Drop(const LogStatisticsElement& element) { size_ -= element.msg_len; }
+    void Erase(const LogStatisticsElement& element) { size_ -= element.total_len; }
 
-    inline void add(LogBufferElement *e) { size += e->getMsgLen(); }
-    inline bool subtract(LogBufferElement *e) { size -= e->getMsgLen(); return !size; }
+    static constexpr size_t PRUNED_LEN = 14;
+    static constexpr size_t TOTAL_LEN = 80;
+
+    static std::string formatLine(const std::string& name,
+                                  const std::string& size,
+                                  const std::string& pruned) {
+        ssize_t drop_len = std::max(pruned.length() + 1, PRUNED_LEN);
+        ssize_t size_len = std::max(size.length() + 1, TOTAL_LEN - name.length() - drop_len - 1);
+
+        std::string ret = android::base::StringPrintf(
+            "%s%*s%*s", name.c_str(), (int)size_len, size.c_str(),
+            (int)drop_len, pruned.c_str());
+        // remove any trailing spaces
+        size_t pos = ret.size();
+        size_t len = 0;
+        while (pos && isspace(ret[--pos])) ++len;
+        if (len) ret.erase(pos + 1, len);
+        return ret + "\n";
+    }
+
+  private:
+    size_t size_;
 };
 
-struct EntryBaseDropped : public EntryBase {
-    size_t dropped;
+class EntryBaseDropped : public EntryBase {
+  public:
+    EntryBaseDropped() : dropped_(0) {}
+    explicit EntryBaseDropped(const LogStatisticsElement& element)
+        : EntryBase(element), dropped_(element.dropped_count) {}
 
-    EntryBaseDropped():dropped(0) { }
-    EntryBaseDropped(LogBufferElement *e):EntryBase(e),dropped(e->getDropped()){ }
+    size_t dropped_count() const { return dropped_; }
 
-    size_t getDropped() const { return dropped; }
+    void Add(const LogStatisticsElement& element) {
+        dropped_ += element.dropped_count;
+        EntryBase::Add(element);
+    }
+    bool Subtract(const LogStatisticsElement& element) {
+        dropped_ -= element.dropped_count;
+        return EntryBase::Subtract(element) && dropped_ == 0;
+    }
+    void Drop(const LogStatisticsElement& element) {
+        dropped_ += 1;
+        EntryBase::Drop(element);
+    }
 
-    inline void add(LogBufferElement *e) {
-        dropped += e->getDropped();
-        EntryBase::add(e);
-    }
-    inline bool subtract(LogBufferElement *e) {
-        dropped -= e->getDropped();
-        return EntryBase::subtract(e) && !dropped;
-    }
-    inline void drop(LogBufferElement *e) {
-        dropped += 1;
-        EntryBase::subtract(e);
-    }
+  private:
+    size_t dropped_;
 };
 
-struct UidEntry : public EntryBaseDropped {
-    const uid_t uid;
+class UidEntry : public EntryBaseDropped {
+  public:
+    explicit UidEntry(const LogStatisticsElement& element)
+        : EntryBaseDropped(element), pid_(element.pid) {}
 
-    UidEntry(LogBufferElement *e):EntryBaseDropped(e),uid(e->getUid()) { }
+    pid_t pid() const { return pid_; }
 
-    inline const uid_t&getKey() const { return uid; }
+    void Add(const LogStatisticsElement& element) {
+        if (pid_ != element.pid) {
+            pid_ = -1;
+        }
+        EntryBaseDropped::Add(element);
+    }
+
+    std::string formatHeader(const std::string& name, log_id_t id) const;
+    std::string format(const LogStatistics& stat, log_id_t id, uid_t uid) const;
+
+  private:
+    pid_t pid_;
 };
 
 namespace android {
 uid_t pidToUid(pid_t pid);
 }
 
-struct PidEntry : public EntryBaseDropped {
-    const pid_t pid;
-    uid_t uid;
-    char *name;
+class PidEntry : public EntryBaseDropped {
+  public:
+    explicit PidEntry(pid_t pid)
+        : EntryBaseDropped(),
+          uid_(android::pidToUid(pid)),
+          name_(android::pidToName(pid)) {}
+    explicit PidEntry(const LogStatisticsElement& element)
+        : EntryBaseDropped(element), uid_(element.uid), name_(android::pidToName(element.pid)) {}
+    PidEntry(const PidEntry& element)
+        : EntryBaseDropped(element),
+          uid_(element.uid_),
+          name_(element.name_ ? strdup(element.name_) : nullptr) {}
+    ~PidEntry() { free(name_); }
 
-    PidEntry(pid_t p):
-        EntryBaseDropped(),
-        pid(p),
-        uid(android::pidToUid(p)),
-        name(android::pidToName(pid)) { }
-    PidEntry(LogBufferElement *e):
-        EntryBaseDropped(e),
-        pid(e->getPid()),
-        uid(e->getUid()),
-        name(android::pidToName(e->getPid())) { }
-    PidEntry(const PidEntry &c):
-        EntryBaseDropped(c),
-        pid(c.pid),
-        uid(c.uid),
-        name(c.name ? strdup(c.name) : NULL) { }
-    ~PidEntry() { free(name); }
+    uid_t uid() const { return uid_; }
+    const char* name() const { return name_; }
 
-    const pid_t&getKey() const { return pid; }
-    const uid_t&getUid() const { return uid; }
-    const char*getName() const { return name; }
-
-    inline void add(pid_t p) {
-        if (name && !strncmp(name, "zygote", 6)) {
-            free(name);
-            name = NULL;
+    void Add(pid_t new_pid) {
+        if (name_ && !fastcmp<strncmp>(name_, "zygote", 6)) {
+            free(name_);
+            name_ = nullptr;
         }
-        if (!name) {
-            char *n = android::pidToName(p);
-            if (n) {
-                name = n;
-            }
+        if (!name_) {
+            name_ = android::pidToName(new_pid);
         }
     }
 
-    inline void add(LogBufferElement *e) {
-        uid_t u = e->getUid();
-        if (getUid() != u) {
-            uid = u;
-            free(name);
-            name = android::pidToName(e->getPid());
+    void Add(const LogStatisticsElement& element) {
+        uid_t incoming_uid = element.uid;
+        if (uid() != incoming_uid) {
+            uid_ = incoming_uid;
+            free(name_);
+            name_ = android::pidToName(element.pid);
         } else {
-            add(e->getPid());
+            Add(element.pid);
         }
-        EntryBaseDropped::add(e);
+        EntryBaseDropped::Add(element);
     }
+
+    std::string formatHeader(const std::string& name, log_id_t id) const;
+    std::string format(const LogStatistics& stat, log_id_t id, pid_t pid) const;
+
+  private:
+    uid_t uid_;
+    char* name_;
 };
 
-struct TidEntry : public EntryBaseDropped {
-    const pid_t tid;
-    uid_t uid;
-    char *name;
+class TidEntry : public EntryBaseDropped {
+  public:
+    TidEntry(pid_t tid, pid_t pid)
+        : EntryBaseDropped(),
+          pid_(pid),
+          uid_(android::pidToUid(tid)),
+          name_(android::tidToName(tid)) {}
+    explicit TidEntry(const LogStatisticsElement& element)
+        : EntryBaseDropped(element),
+          pid_(element.pid),
+          uid_(element.uid),
+          name_(android::tidToName(element.tid)) {}
+    TidEntry(const TidEntry& element)
+        : EntryBaseDropped(element),
+          pid_(element.pid_),
+          uid_(element.uid_),
+          name_(element.name_ ? strdup(element.name_) : nullptr) {}
+    ~TidEntry() { free(name_); }
 
-    TidEntry(pid_t t):
-        EntryBaseDropped(),
-        tid(t),
-        uid(android::pidToUid(t)),
-        name(android::tidToName(tid)) { }
-    TidEntry(LogBufferElement *e):
-        EntryBaseDropped(e),
-        tid(e->getTid()),
-        uid(e->getUid()),
-        name(android::tidToName(e->getTid())) { }
-    TidEntry(const TidEntry &c):
-        EntryBaseDropped(c),
-        tid(c.tid),
-        uid(c.uid),
-        name(c.name ? strdup(c.name) : NULL) { }
-    ~TidEntry() { free(name); }
+    pid_t pid() const { return pid_; }
+    uid_t uid() const { return uid_; }
+    const char* name() const { return name_; }
 
-    const pid_t&getKey() const { return tid; }
-    const uid_t&getUid() const { return uid; }
-    const char*getName() const { return name; }
-
-    inline void add(pid_t t) {
-        if (name && !strncmp(name, "zygote", 6)) {
-            free(name);
-            name = NULL;
+    void Add(pid_t incomingTid) {
+        if (name_ && !fastcmp<strncmp>(name_, "zygote", 6)) {
+            free(name_);
+            name_ = nullptr;
         }
-        if (!name) {
-            char *n = android::tidToName(t);
-            if (n) {
-                name = n;
-            }
+        if (!name_) {
+            name_ = android::tidToName(incomingTid);
         }
     }
 
-    inline void add(LogBufferElement *e) {
-        uid_t u = e->getUid();
-        if (getUid() != u) {
-            uid = u;
-            free(name);
-            name = android::tidToName(e->getTid());
+    void Add(const LogStatisticsElement& element) {
+        uid_t incoming_uid = element.uid;
+        pid_t incoming_pid = element.pid;
+        if (uid() != incoming_uid || pid() != incoming_pid) {
+            uid_ = incoming_uid;
+            pid_ = incoming_pid;
+            free(name_);
+            name_ = android::tidToName(element.tid);
         } else {
-            add(e->getTid());
+            Add(element.tid);
         }
-        EntryBaseDropped::add(e);
+        EntryBaseDropped::Add(element);
     }
+
+    std::string formatHeader(const std::string& name, log_id_t id) const;
+    std::string format(const LogStatistics& stat, log_id_t id, pid_t pid) const;
+
+  private:
+    pid_t pid_;
+    uid_t uid_;
+    char* name_;
 };
 
-struct TagEntry : public EntryBase {
-    const uint32_t tag;
-    uid_t uid;
+class TagEntry : public EntryBaseDropped {
+  public:
+    explicit TagEntry(const LogStatisticsElement& element)
+        : EntryBaseDropped(element), tag_(element.tag), pid_(element.pid), uid_(element.uid) {}
 
-    TagEntry(LogBufferElement *e):
-        EntryBase(e),
-        tag(e->getTag()),
-        uid(e->getUid()) { }
+    uint32_t key() const { return tag_; }
+    pid_t pid() const { return pid_; }
+    uid_t uid() const { return uid_; }
+    const char* name() const { return android::tagToName(tag_); }
 
-    const uint32_t&getKey() const { return tag; }
-    const uid_t&getUid() const { return uid; }
-    const char*getName() const { return android::tagToName(tag); }
-
-    inline void add(LogBufferElement *e) {
-        uid_t u = e->getUid();
-        if (uid != u) {
-            uid = -1;
+    void Add(const LogStatisticsElement& element) {
+        if (uid_ != element.uid) {
+            uid_ = -1;
         }
-        EntryBase::add(e);
+        if (pid_ != element.pid) {
+            pid_ = -1;
+        }
+        EntryBaseDropped::Add(element);
     }
+
+    std::string formatHeader(const std::string& name, log_id_t id) const;
+    std::string format(const LogStatistics& stat, log_id_t id, uint32_t) const;
+
+  private:
+    const uint32_t tag_;
+    pid_t pid_;
+    uid_t uid_;
 };
 
-// Log Statistics
+class TagNameEntry : public EntryBase {
+  public:
+    explicit TagNameEntry(const LogStatisticsElement& element)
+        : EntryBase(element), tid_(element.tid), pid_(element.pid), uid_(element.uid) {}
+
+    pid_t tid() const { return tid_; }
+    pid_t pid() const { return pid_; }
+    uid_t uid() const { return uid_; }
+
+    void Add(const LogStatisticsElement& element) {
+        if (uid_ != element.uid) {
+            uid_ = -1;
+        }
+        if (pid_ != element.pid) {
+            pid_ = -1;
+        }
+        if (tid_ != element.tid) {
+            tid_ = -1;
+        }
+        EntryBase::Add(element);
+    }
+
+    std::string formatHeader(const std::string& name, log_id_t id) const;
+    std::string format(const LogStatistics& stat, log_id_t id, const std::string& key_name) const;
+
+  private:
+    pid_t tid_;
+    pid_t pid_;
+    uid_t uid_;
+};
+
 class LogStatistics {
-    size_t mSizes[LOG_ID_MAX];
-    size_t mElements[LOG_ID_MAX];
-    size_t mSizesTotal[LOG_ID_MAX];
-    size_t mElementsTotal[LOG_ID_MAX];
+    friend UidEntry;
+    friend PidEntry;
+    friend TidEntry;
+
+    size_t mSizes[LOG_ID_MAX] GUARDED_BY(lock_);
+    size_t mElements[LOG_ID_MAX] GUARDED_BY(lock_);
+    size_t mDroppedElements[LOG_ID_MAX] GUARDED_BY(lock_);
+    size_t mSizesTotal[LOG_ID_MAX] GUARDED_BY(lock_);
+    size_t mElementsTotal[LOG_ID_MAX] GUARDED_BY(lock_);
+    log_time mOldest[LOG_ID_MAX] GUARDED_BY(lock_);
+    log_time mNewest[LOG_ID_MAX] GUARDED_BY(lock_);
+    log_time mNewestDropped[LOG_ID_MAX] GUARDED_BY(lock_);
+    static std::atomic<size_t> SizesTotal;
     bool enable;
 
     // uid to size list
     typedef LogHashtable<uid_t, UidEntry> uidTable_t;
-    uidTable_t uidTable[LOG_ID_MAX];
+    uidTable_t uidTable[LOG_ID_MAX] GUARDED_BY(lock_);
+
+    // pid of system to size list
+    typedef LogHashtable<pid_t, PidEntry> pidSystemTable_t;
+    pidSystemTable_t pidSystemTable[LOG_ID_MAX] GUARDED_BY(lock_);
 
     // pid to uid list
     typedef LogHashtable<pid_t, PidEntry> pidTable_t;
-    pidTable_t pidTable;
+    pidTable_t pidTable GUARDED_BY(lock_);
 
     // tid to uid list
     typedef LogHashtable<pid_t, TidEntry> tidTable_t;
-    tidTable_t tidTable;
+    tidTable_t tidTable GUARDED_BY(lock_);
 
     // tag list
     typedef LogHashtable<uint32_t, TagEntry> tagTable_t;
-    tagTable_t tagTable;
+    tagTable_t tagTable GUARDED_BY(lock_);
 
-public:
-    LogStatistics();
+    // security tag list
+    tagTable_t securityTagTable GUARDED_BY(lock_);
 
-    void enableStatistics() { enable = true; }
+    // global tag list
+    typedef LogHashtable<std::string, TagNameEntry> tagNameTable_t;
+    tagNameTable_t tagNameTable;
 
-    void add(LogBufferElement *entry);
-    void subtract(LogBufferElement *entry);
-    // entry->setDropped(1) must follow this call
-    void drop(LogBufferElement *entry);
-    // Correct for merging two entries referencing dropped content
-    void erase(LogBufferElement *e) { --mElements[e->getLogId()]; }
+    size_t sizeOf() const REQUIRES(lock_) {
+        size_t size = sizeof(*this) + pidTable.sizeOf() + tidTable.sizeOf() +
+                      tagTable.sizeOf() + securityTagTable.sizeOf() +
+                      tagNameTable.sizeOf() +
+                      (pidTable.size() * sizeof(pidTable_t::iterator)) +
+                      (tagTable.size() * sizeof(tagTable_t::iterator));
+        for (const auto& it : pidTable) {
+            const char* name = it.second.name();
+            if (name) size += strlen(name) + 1;
+        }
+        for (const auto& it : tidTable) {
+            const char* name = it.second.name();
+            if (name) size += strlen(name) + 1;
+        }
+        for (const auto& it : tagNameTable) {
+            size += sizeof(std::string);
+            size_t len = it.first.size();
+            // Account for short string optimization: if the string's length is <= 22 bytes for 64
+            // bit or <= 10 bytes for 32 bit, then there is no additional allocation.
+            if ((sizeof(std::string) == 24 && len > 22) ||
+                (sizeof(std::string) != 24 && len > 10)) {
+                size += len;
+            }
+        }
+        log_id_for_each(id) {
+            size += uidTable[id].sizeOf();
+            size += uidTable[id].size() * sizeof(uidTable_t::iterator);
+            size += pidSystemTable[id].sizeOf();
+            size += pidSystemTable[id].size() * sizeof(pidSystemTable_t::iterator);
+        }
+        return size;
+    }
 
-    std::unique_ptr<const UidEntry *[]> sort(size_t n, log_id i) { return uidTable[i].sort(n); }
+  public:
+    LogStatistics(bool enable_statistics, bool track_total_size,
+                  std::optional<log_time> start_time = {});
 
-    // 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]; }
-    size_t sizesTotal(log_id_t id) const { return mSizesTotal[id]; }
-    size_t elementsTotal(log_id_t id) const { return mElementsTotal[id]; }
+    void AddTotal(log_id_t log_id, uint16_t size) EXCLUDES(lock_);
 
-    // *strp = malloc, balance with free
-    void format(char **strp, uid_t uid, unsigned int logMask);
+    // Add is for adding an element to the log buffer.  It may be a chatty element in the case of
+    // log deduplication.  Add the total size of the element to statistics.
+    void Add(LogStatisticsElement entry) EXCLUDES(lock_);
+    // Subtract is for removing an element from the log buffer.  It may be a chatty element.
+    // Subtract the total size of the element from statistics.
+    void Subtract(LogStatisticsElement entry) EXCLUDES(lock_);
+    // Drop is for converting a normal element into a chatty element. entry->setDropped(1) must
+    // follow this call.  Subtract only msg_len from statistics, since a chatty element will remain.
+    void Drop(LogStatisticsElement entry) EXCLUDES(lock_);
+    // Erase is for coalescing two chatty elements into one.  Erase() is called on the element that
+    // is removed from the log buffer.  Subtract the total size of the element, which is by
+    // definition only the size of the LogBufferElement + list overhead for chatty elements.
+    void Erase(LogStatisticsElement element) EXCLUDES(lock_);
 
-    // helper (must be locked directly or implicitly by mLogElementsLock)
-    char *pidToName(pid_t pid);
-    uid_t pidToUid(pid_t pid);
-    char *uidToName(uid_t uid);
+    void WorstTwoUids(log_id id, size_t threshold, int* worst, size_t* worst_sizes,
+                      size_t* second_worst_sizes) const EXCLUDES(lock_);
+    void WorstTwoTags(size_t threshold, int* worst, size_t* worst_sizes,
+                      size_t* second_worst_sizes) const EXCLUDES(lock_);
+    void WorstTwoSystemPids(log_id id, size_t worst_uid_sizes, int* worst,
+                            size_t* second_worst_sizes) const EXCLUDES(lock_);
+
+    bool ShouldPrune(log_id id, unsigned long max_size, unsigned long* prune_rows) const
+            EXCLUDES(lock_);
+
+    // Return the consumed size of the given buffer.
+    size_t Sizes(log_id_t id) const EXCLUDES(lock_) {
+        auto lock = std::lock_guard{lock_};
+        if (overhead_[id]) {
+            return *overhead_[id];
+        }
+        return mSizes[id];
+    }
+
+    // Return the uncompressed size of the contents of the given buffer.
+    size_t SizeReadable(log_id_t id) const EXCLUDES(lock_) {
+        auto lock = std::lock_guard{lock_};
+        return mSizes[id];
+    }
+
+    // TODO: Get rid of this entirely.
+    static size_t sizesTotal() {
+        return SizesTotal;
+    }
+
+    std::string ReportInteresting() const EXCLUDES(lock_);
+    std::string Format(uid_t uid, pid_t pid, unsigned int logMask) const EXCLUDES(lock_);
+
+    const char* PidToName(pid_t pid) const EXCLUDES(lock_);
+    uid_t PidToUid(pid_t pid) EXCLUDES(lock_);
+    const char* UidToName(uid_t uid) const EXCLUDES(lock_);
+
+    void set_overhead(log_id_t id, size_t size) {
+        auto lock = std::lock_guard{lock_};
+        overhead_[id] = size;
+    }
+
+  private:
+    template <typename TKey, typename TEntry>
+    void WorstTwoWithThreshold(const LogHashtable<TKey, TEntry>& table, size_t threshold,
+                               int* worst, size_t* worst_sizes, size_t* second_worst_sizes) const;
+    template <typename TKey, typename TEntry>
+    std::string FormatTable(const LogHashtable<TKey, TEntry>& table, uid_t uid, pid_t pid,
+                            const std::string& name = std::string(""),
+                            log_id_t id = LOG_ID_MAX) const REQUIRES(lock_);
+    void FormatTmp(const char* nameTmp, uid_t uid, std::string& name, std::string& size,
+                   size_t nameLen) const REQUIRES(lock_);
+    const char* UidToNameLocked(uid_t uid) const REQUIRES(lock_);
+
+    mutable std::mutex lock_;
+    bool track_total_size_;
+
+    std::optional<size_t> overhead_[LOG_ID_MAX] GUARDED_BY(lock_);
 };
-
-#endif // _LOGD_LOG_STATISTICS_H__
diff --git a/logd/LogTags.cpp b/logd/LogTags.cpp
new file mode 100644
index 0000000..6ab3b48
--- /dev/null
+++ b/logd/LogTags.cpp
@@ -0,0 +1,889 @@
+/*
+ * Copyright (C) 2017 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 <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <android-base/scopeguard.h>
+#include <android-base/stringprintf.h>
+#include <android-base/threads.h>
+#include <log/log_event_list.h>
+#include <log/log_properties.h>
+#include <log/log_read.h>
+#include <private/android_filesystem_config.h>
+
+#include "LogStatistics.h"
+#include "LogTags.h"
+#include "LogUtils.h"
+
+using android::base::make_scope_guard;
+
+static LogTags* logtags;
+
+const char LogTags::system_event_log_tags[] = "/system/etc/event-log-tags";
+const char LogTags::dynamic_event_log_tags[] = "/dev/event-log-tags";
+// Only for debug
+const char LogTags::debug_event_log_tags[] = "/data/misc/logd/event-log-tags";
+
+// Sniff for first uid=%d in utf8z comment string
+static uid_t sniffUid(const char* comment, const char* endp) {
+    if (!comment) return AID_ROOT;
+
+    if (*comment == '#') ++comment;
+    while ((comment < endp) && (*comment != '\n') && isspace(*comment))
+        ++comment;
+    static const char uid_str[] = "uid=";
+    if (((comment + strlen(uid_str)) >= endp) ||
+        fastcmp<strncmp>(comment, uid_str, strlen(uid_str)) ||
+        !isdigit(comment[strlen(uid_str)]))
+        return AID_ROOT;
+    char* cp;
+    unsigned long Uid = strtoul(comment + 4, &cp, 10);
+    if ((cp > endp) || (Uid >= INT_MAX)) return AID_ROOT;
+
+    return Uid;
+}
+
+// Checks for file corruption, and report false if there was no need
+// to rebuild the referenced file.  Failure to rebuild is only logged,
+// does not cause a return value of false.
+bool LogTags::RebuildFileEventLogTags(const char* filename, bool warn) {
+    int fd;
+
+    {
+        android::RWLock::AutoRLock readLock(rwlock);
+
+        if (tag2total.begin() == tag2total.end()) {
+            return false;
+        }
+
+        file2watermark_const_iterator iwater = file2watermark.find(filename);
+        if (iwater == file2watermark.end()) {
+            return false;
+        }
+
+        struct stat sb;
+        if (!stat(filename, &sb) && ((size_t)sb.st_size >= iwater->second)) {
+            return false;
+        }
+
+        // dump what we already know back into the file?
+        fd = TEMP_FAILURE_RETRY(open(
+            filename, O_WRONLY | O_TRUNC | O_CLOEXEC | O_NOFOLLOW | O_BINARY));
+        if (fd >= 0) {
+            time_t now = time(nullptr);
+            struct tm tm;
+            localtime_r(&now, &tm);
+            char timebuf[20];
+            strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", &tm);
+            android::base::WriteStringToFd(
+                android::base::StringPrintf(
+                    "# Rebuilt %.20s, content owned by logd\n", timebuf),
+                fd);
+            for (const auto& it : tag2total) {
+                android::base::WriteStringToFd(
+                    formatEntry_locked(it.first, AID_ROOT), fd);
+            }
+            close(fd);
+        }
+    }
+
+    if (warn) {
+        if (fd < 0) {
+            LOG(ERROR) << filename << " failed to rebuild";
+        } else {
+            LOG(ERROR) << filename << " missing, damaged or truncated; rebuilt";
+        }
+    }
+
+    if (fd >= 0) {
+        android::RWLock::AutoWLock writeLock(rwlock);
+
+        struct stat sb;
+        if (!stat(filename, &sb)) file2watermark[filename] = sb.st_size;
+    }
+
+    return true;
+}
+
+void LogTags::AddEventLogTags(uint32_t tag, uid_t uid, const std::string& Name,
+                              const std::string& Format, const char* source,
+                              bool warn) {
+    std::string Key = Name;
+    if (Format.length()) Key += "+" + Format;
+
+    bool update = !source || !!strcmp(source, system_event_log_tags);
+    bool newOne;
+
+    {
+        android::RWLock::AutoWLock writeLock(rwlock);
+
+        tag2total_const_iterator itot = tag2total.find(tag);
+
+        // unlikely except for dupes, or updates to uid list (more later)
+        if (itot != tag2total.end()) update = false;
+
+        newOne = tag2name.find(tag) == tag2name.end();
+        key2tag[Key] = tag;
+
+        if (Format.length()) {
+            if (key2tag.find(Name) == key2tag.end()) {
+                key2tag[Name] = tag;
+            }
+            tag2format[tag] = Format;
+        }
+        tag2name[tag] = Name;
+
+        tag2uid_const_iterator ut = tag2uid.find(tag);
+        if (ut != tag2uid.end()) {
+            if (uid == AID_ROOT) {
+                tag2uid.erase(ut);
+                update = true;
+            } else if (ut->second.find(uid) == ut->second.end()) {
+                const_cast<uid_list&>(ut->second).emplace(uid);
+                update = true;
+            }
+        } else if (newOne && (uid != AID_ROOT)) {
+            tag2uid[tag].emplace(uid);
+            update = true;
+        }
+
+        // updatePersist -> trigger output on modified
+        // content, reset tag2total if available
+        if (update && (itot != tag2total.end())) tag2total[tag] = 0;
+    }
+
+    if (update) {
+        WritePersistEventLogTags(tag, uid, source);
+    } else if (warn && !newOne && source) {
+        // For the files, we want to report dupes.
+        LOG(DEBUG) << "Multiple tag " << tag << " " << Name << " " << Format << " " << source;
+    }
+}
+
+// Read the event log tags file, and build up our internal database
+void LogTags::ReadFileEventLogTags(const char* filename, bool warn) {
+    bool etc = !strcmp(filename, system_event_log_tags);
+
+    if (!etc) {
+        RebuildFileEventLogTags(filename, warn);
+    }
+    std::string content;
+    if (android::base::ReadFileToString(filename, &content)) {
+        char* cp = (char*)content.c_str();
+        char* endp = cp + content.length();
+
+        {
+            android::RWLock::AutoRLock writeLock(rwlock);
+
+            file2watermark[filename] = content.length();
+        }
+
+        char* lineStart = cp;
+        while (cp < endp) {
+            if (*cp == '\n') {
+                lineStart = cp;
+            } else if (lineStart) {
+                if (*cp == '#') {
+                    /* comment; just scan to end */
+                    lineStart = nullptr;
+                } else if (isdigit(*cp)) {
+                    unsigned long Tag = strtoul(cp, &cp, 10);
+                    if (warn && (Tag > emptyTag)) {
+                        LOG(WARNING) << "tag too large " << Tag;
+                    }
+                    while ((cp < endp) && (*cp != '\n') && isspace(*cp)) ++cp;
+                    if (cp >= endp) break;
+                    if (*cp == '\n') continue;
+                    const char* name = cp;
+                    /* Determine whether it is a valid tag name [a-zA-Z0-9_] */
+                    bool hasAlpha = false;
+                    while ((cp < endp) && (isalnum(*cp) || (*cp == '_'))) {
+                        if (!isdigit(*cp)) hasAlpha = true;
+                        ++cp;
+                    }
+                    std::string Name(name, cp - name);
+#ifdef ALLOW_NOISY_LOGGING_OF_PROBLEM_WITH_LOTS_OF_TECHNICAL_DEBT
+                    static const size_t maximum_official_tag_name_size = 24;
+                    if (warn && (Name.length() > maximum_official_tag_name_size)) {
+                        LOG(WARNING) << "tag name too long " << Name;
+                    }
+#endif
+                    if (hasAlpha &&
+                        ((cp >= endp) || (*cp == '#') || isspace(*cp))) {
+                        if (Tag > emptyTag) {
+                            if (*cp != '\n') lineStart = nullptr;
+                            continue;
+                        }
+                        while ((cp < endp) && (*cp != '\n') && isspace(*cp))
+                            ++cp;
+                        const char* format = cp;
+                        uid_t uid = AID_ROOT;
+                        while ((cp < endp) && (*cp != '\n')) {
+                            if (*cp == '#') {
+                                uid = sniffUid(cp, endp);
+                                lineStart = nullptr;
+                                break;
+                            }
+                            ++cp;
+                        }
+                        while ((cp > format) && isspace(cp[-1])) {
+                            --cp;
+                            lineStart = nullptr;
+                        }
+                        std::string Format(format, cp - format);
+
+                        AddEventLogTags((uint32_t)Tag, uid, Name, Format,
+                                        filename, warn);
+                    } else {
+                        if (warn) {
+                            LOG(ERROR) << android::base::StringPrintf("tag name invalid %.*s",
+                                                                      (int)(cp - name + 1), name);
+                        }
+                        lineStart = nullptr;
+                    }
+                } else if (!isspace(*cp)) {
+                    break;
+                }
+            }
+            cp++;
+        }
+    } else if (warn) {
+#ifdef __ANDROID__
+        LOG(ERROR) << "Cannot read " << filename;
+#endif
+    }
+}
+
+// Extract a 4-byte value from a byte stream.
+static inline uint32_t get4LE(const char* msg) {
+    const uint8_t* src = reinterpret_cast<const uint8_t*>(msg);
+    return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
+}
+
+// Additional persistent sources for invented log tags.  Read the
+// special pmsg event for log tags, and build up our internal
+// database with any found.
+void LogTags::ReadPersistEventLogTags() {
+    struct logger_list* logger_list =
+            android_logger_list_alloc(ANDROID_LOG_PSTORE | ANDROID_LOG_NONBLOCK, 0, (pid_t)0);
+    if (!logger_list) return;
+
+    struct logger* e = android_logger_open(logger_list, LOG_ID_EVENTS);
+    struct logger* s = android_logger_open(logger_list, LOG_ID_SECURITY);
+    if (!e && !s) {
+        android_logger_list_free(logger_list);
+        return;
+    }
+
+    for (;;) {
+        struct log_msg log_msg;
+        int ret = android_logger_list_read(logger_list, &log_msg);
+        if (ret <= 0) break;
+
+        const char* msg = log_msg.msg();
+        if (!msg) continue;
+        if (log_msg.entry.len <= sizeof(uint32_t)) continue;
+        uint32_t Tag = get4LE(msg);
+        if (Tag != TAG_DEF_LOG_TAG) continue;
+        uid_t uid = log_msg.entry.uid;
+
+        std::string Name;
+        std::string Format;
+        android_log_list_element elem;
+        {
+            auto ctx = create_android_log_parser(log_msg.msg() + sizeof(uint32_t),
+                                                 log_msg.entry.len - sizeof(uint32_t));
+            auto guard = make_scope_guard([&ctx]() { android_log_destroy(&ctx); });
+            elem = android_log_read_next(ctx);
+            if (elem.type != EVENT_TYPE_LIST) {
+                continue;
+            }
+            elem = android_log_read_next(ctx);
+            if (elem.type != EVENT_TYPE_INT) {
+                continue;
+            }
+            Tag = elem.data.int32;
+            elem = android_log_read_next(ctx);
+            if (elem.type != EVENT_TYPE_STRING) {
+                continue;
+            }
+            Name = std::string(elem.data.string, elem.len);
+            elem = android_log_read_next(ctx);
+            if (elem.type != EVENT_TYPE_STRING) {
+                continue;
+            }
+            Format = std::string(elem.data.string, elem.len);
+            elem = android_log_read_next(ctx);
+        }
+        if ((elem.type != EVENT_TYPE_LIST_STOP) || !elem.complete) continue;
+
+        AddEventLogTags(Tag, uid, Name, Format);
+    }
+    android_logger_list_free(logger_list);
+}
+
+LogTags::LogTags() {
+    ReadFileEventLogTags(system_event_log_tags);
+    // Following will likely fail on boot, but is required if logd restarts
+    ReadFileEventLogTags(dynamic_event_log_tags, false);
+    if (__android_log_is_debuggable()) {
+        ReadFileEventLogTags(debug_event_log_tags, false);
+    }
+    ReadPersistEventLogTags();
+
+    logtags = this;
+}
+
+// Converts an event tag into a name
+const char* LogTags::tagToName(uint32_t tag) const {
+    tag2name_const_iterator it;
+
+    android::RWLock::AutoRLock readLock(const_cast<android::RWLock&>(rwlock));
+
+    it = tag2name.find(tag);
+    if ((it == tag2name.end()) || (it->second.length() == 0)) return nullptr;
+
+    return it->second.c_str();
+}
+
+// Prototype in LogUtils.h allowing external access to our database.
+//
+// This must be a pure reader to our database, as everything else is
+// guaranteed single-threaded except this access point which is
+// asynchonous and can be multithreaded and thus rentrant.  The
+// object's rwlock is only used to guarantee atomic access to the
+// unordered_map to prevent corruption, with a requirement to be a
+// low chance of contention for this call.  If we end up changing
+// this algorithm resulting in write, then we should use a different
+// lock than the object's rwlock to protect groups of associated
+// actions.
+const char* android::tagToName(uint32_t tag) {
+    LogTags* me = logtags;
+
+    if (!me) return nullptr;
+    me->WritePmsgEventLogTags(tag);
+    return me->tagToName(tag);
+}
+
+// converts an event tag into a format
+const char* LogTags::tagToFormat(uint32_t tag) const {
+    tag2format_const_iterator iform;
+
+    android::RWLock::AutoRLock readLock(const_cast<android::RWLock&>(rwlock));
+
+    iform = tag2format.find(tag);
+    if (iform == tag2format.end()) return nullptr;
+
+    return iform->second.c_str();
+}
+
+// converts a name into an event tag
+uint32_t LogTags::nameToTag(const char* name) const {
+    uint32_t ret = emptyTag;
+
+    // Bug: Only works for a single entry, we can have multiple entries,
+    // one for each format, so we find first entry recorded, or entry with
+    // no format associated with it.
+
+    android::RWLock::AutoRLock readLock(const_cast<android::RWLock&>(rwlock));
+
+    key2tag_const_iterator ik = key2tag.find(std::string(name));
+    if (ik != key2tag.end()) ret = ik->second;
+
+    return ret;
+}
+
+// Caller must perform locks, can be under reader (for pre-check) or
+// writer lock.  We use this call to invent a new deterministically
+// random tag, unique is cleared if no conflicts.  If format is NULL,
+// we are in readonly mode.
+uint32_t LogTags::nameToTag_locked(const std::string& name, const char* format,
+                                   bool& unique) {
+    key2tag_const_iterator ik;
+
+    bool write = format != nullptr;
+    unique = write;
+
+    if (!write) {
+        // Bug: Only works for a single entry, we can have multiple entries,
+        // one for each format, so we find first entry recorded, or entry with
+        // no format associated with it.
+        ik = key2tag.find(name);
+        if (ik == key2tag.end()) return emptyTag;
+        return ik->second;
+    }
+
+    std::string Key(name);
+    if (*format) Key += std::string("+") + format;
+
+    ik = key2tag.find(Key);
+    if (ik != key2tag.end()) {
+        unique = false;
+        return ik->second;
+    }
+
+    size_t Hash = key2tag.hash_function()(Key);
+    uint32_t Tag = Hash;
+    // This sets an upper limit on the conflics we are allowed to deal with.
+    for (unsigned i = 0; i < 256;) {
+        tag2name_const_iterator it = tag2name.find(Tag);
+        if (it == tag2name.end()) return Tag;
+        std::string localKey(it->second);
+        tag2format_const_iterator iform = tag2format.find(Tag);
+        if ((iform == tag2format.end()) && iform->second.length()) {
+            localKey += "+" + iform->second;
+        }
+        unique = !!it->second.compare(localKey);
+        if (!unique) return Tag;  // unlikely except in a race
+
+        ++i;
+        // Algorithm to convert hash to next tag
+        if (i < 32) {
+            Tag = (Hash >> i);
+            // size_t is 32 bits, or upper word zero, rotate
+            if ((sizeof(Hash) <= 4) || ((Hash & (uint64_t(-1LL) << 32)) == 0)) {
+                Tag |= Hash << (32 - i);
+            }
+        } else {
+            Tag = Hash + i - 31;
+        }
+    }
+    return emptyTag;
+}
+
+static int openFile(const char* name, int mode, bool warning) {
+    int fd = TEMP_FAILURE_RETRY(open(name, mode));
+    if (fd < 0 && warning) {
+        PLOG(ERROR) << "Failed to open " << name;
+    }
+    return fd;
+}
+
+void LogTags::WritePmsgEventLogTags(uint32_t tag, uid_t uid) {
+    android::RWLock::AutoRLock readLock(rwlock);
+
+    tag2total_const_iterator itot = tag2total.find(tag);
+    if (itot == tag2total.end()) return;  // source is a static entry
+
+    size_t lastTotal = itot->second;
+
+    // Every 16K (half the smallest configurable pmsg buffer size) record
+    static const size_t rate_to_pmsg = 16 * 1024;
+    if (lastTotal && (LogStatistics::sizesTotal() - lastTotal) < rate_to_pmsg) {
+        return;
+    }
+
+    static int pmsg_fd = -1;
+    if (pmsg_fd < 0) {
+        pmsg_fd = TEMP_FAILURE_RETRY(open("/dev/pmsg0", O_WRONLY | O_CLOEXEC));
+        // unlikely, but deal with partners with borken pmsg
+        if (pmsg_fd < 0) return;
+    }
+
+    std::string Name = tag2name[tag];
+    tag2format_const_iterator iform = tag2format.find(tag);
+    std::string Format = (iform != tag2format.end()) ? iform->second : "";
+
+    auto ctx = create_android_logger(TAG_DEF_LOG_TAG);
+    auto guard = make_scope_guard([&ctx]() { android_log_destroy(&ctx); });
+    if (android_log_write_int32(ctx, static_cast<int32_t>(tag) < 0) ||
+        android_log_write_string8_len(ctx, Name.c_str(), Name.size()) < 0 ||
+        android_log_write_string8_len(ctx, Format.c_str(), Format.size()) < 0) {
+        return;
+    }
+
+    const char* cp = nullptr;
+    ssize_t len = android_log_write_list_buffer(ctx, &cp);
+
+    if (len <= 0 || cp == nullptr) {
+        return;
+    }
+
+    std::string buffer(cp, len);
+
+    /*
+     *  struct {
+     *      // what we provide to pstore
+     *      android_pmsg_log_header_t pmsgHeader;
+     *      // what we provide to file
+     *      android_log_header_t header;
+     *      // caller provides
+     *      union {
+     *          struct {
+     *              char     prio;
+     *              char     payload[];
+     *          } string;
+     *          struct {
+     *              uint32_t tag
+     *              char     payload[];
+     *          } binary;
+     *      };
+     *  };
+     */
+
+    struct timespec ts;
+    clock_gettime(CLOCK_REALTIME, &ts);
+
+    android_log_header_t header = {
+            .id = LOG_ID_EVENTS,
+            .tid = static_cast<uint16_t>(android::base::GetThreadId()),
+            .realtime.tv_sec = static_cast<uint32_t>(ts.tv_sec),
+            .realtime.tv_nsec = static_cast<uint32_t>(ts.tv_nsec),
+    };
+
+    uint32_t outTag = TAG_DEF_LOG_TAG;
+    outTag = get4LE((const char*)&outTag);
+
+    android_pmsg_log_header_t pmsgHeader = {
+        .magic = LOGGER_MAGIC,
+        .len = (uint16_t)(sizeof(pmsgHeader) + sizeof(header) + sizeof(outTag) +
+                          buffer.length()),
+        .uid = (uint16_t)AID_ROOT,
+        .pid = (uint16_t)getpid(),
+    };
+
+    struct iovec Vec[] = { { (unsigned char*)&pmsgHeader, sizeof(pmsgHeader) },
+                           { (unsigned char*)&header, sizeof(header) },
+                           { (unsigned char*)&outTag, sizeof(outTag) },
+                           { (unsigned char*)const_cast<char*>(buffer.data()),
+                             buffer.length() } };
+
+    tag2uid_const_iterator ut = tag2uid.find(tag);
+    if (ut == tag2uid.end()) {
+        TEMP_FAILURE_RETRY(writev(pmsg_fd, Vec, arraysize(Vec)));
+    } else if (uid != AID_ROOT) {
+        pmsgHeader.uid = (uint16_t)uid;
+        TEMP_FAILURE_RETRY(writev(pmsg_fd, Vec, arraysize(Vec)));
+    } else {
+        for (auto& it : ut->second) {
+            pmsgHeader.uid = (uint16_t)it;
+            TEMP_FAILURE_RETRY(writev(pmsg_fd, Vec, arraysize(Vec)));
+        }
+    }
+}
+
+void LogTags::WriteDynamicEventLogTags(uint32_t tag, uid_t uid) {
+    static const int mode =
+        O_WRONLY | O_APPEND | O_CLOEXEC | O_NOFOLLOW | O_BINARY;
+
+    int fd = openFile(dynamic_event_log_tags, mode, true);
+    if (fd < 0) return;
+
+    android::RWLock::AutoWLock writeLock(rwlock);
+
+    std::string ret = formatEntry_locked(tag, uid, false);
+    android::base::WriteStringToFd(ret, fd);
+    close(fd);
+
+    size_t size = 0;
+    file2watermark_const_iterator iwater;
+
+    iwater = file2watermark.find(dynamic_event_log_tags);
+    if (iwater != file2watermark.end()) size = iwater->second;
+
+    file2watermark[dynamic_event_log_tags] = size + ret.length();
+}
+
+void LogTags::WriteDebugEventLogTags(uint32_t tag, uid_t uid) {
+    static const int mode =
+        O_WRONLY | O_APPEND | O_CLOEXEC | O_NOFOLLOW | O_BINARY;
+
+    static bool one = true;
+    int fd = openFile(debug_event_log_tags, mode, one);
+    one = fd >= 0;
+    if (!one) return;
+
+    android::RWLock::AutoWLock writeLock(rwlock);
+
+    std::string ret = formatEntry_locked(tag, uid, false);
+    android::base::WriteStringToFd(ret, fd);
+    close(fd);
+
+    size_t size = 0;
+    file2watermark_const_iterator iwater;
+
+    iwater = file2watermark.find(debug_event_log_tags);
+    if (iwater != file2watermark.end()) size = iwater->second;
+
+    file2watermark[debug_event_log_tags] = size + ret.length();
+}
+
+// How we maintain some runtime or reboot stickiness
+void LogTags::WritePersistEventLogTags(uint32_t tag, uid_t uid,
+                                       const char* source) {
+    // very unlikely
+    bool etc = source && !strcmp(source, system_event_log_tags);
+    if (etc) return;
+
+    bool dynamic = source && !strcmp(source, dynamic_event_log_tags);
+    bool debug = (!dynamic && source && !strcmp(source, debug_event_log_tags)) ||
+                 !__android_log_is_debuggable();
+
+    WritePmsgEventLogTags(tag, uid);
+
+    size_t lastTotal = 0;
+    {
+        android::RWLock::AutoRLock readLock(rwlock);
+
+        tag2total_const_iterator itot = tag2total.find(tag);
+        if (itot != tag2total.end()) lastTotal = itot->second;
+    }
+
+    if (lastTotal == 0) {  // denotes first time for this one
+        if (!dynamic || !RebuildFileEventLogTags(dynamic_event_log_tags)) {
+            WriteDynamicEventLogTags(tag, uid);
+        }
+
+        if (!debug && !RebuildFileEventLogTags(debug_event_log_tags)) {
+            WriteDebugEventLogTags(tag, uid);
+        }
+    }
+
+    lastTotal = LogStatistics::sizesTotal();
+    if (!lastTotal) ++lastTotal;
+
+    // record totals for next watermark.
+    android::RWLock::AutoWLock writeLock(rwlock);
+    tag2total[tag] = lastTotal;
+}
+
+// nameToTag converts a name into an event tag. If format is NULL, then we
+// are in readonly mode.
+uint32_t LogTags::nameToTag(uid_t uid, const char* name, const char* format) {
+    std::string Name = std::string(name);
+    bool write = format != nullptr;
+    bool updateUid = uid != AID_ROOT;
+    bool updateFormat = format && *format;
+    bool unique;
+    uint32_t Tag;
+
+    {
+        android::RWLock::AutoRLock readLock(rwlock);
+
+        Tag = nameToTag_locked(Name, format, unique);
+        if (updateUid && (Tag != emptyTag) && !unique) {
+            tag2uid_const_iterator ut = tag2uid.find(Tag);
+            if (updateUid) {
+                if ((ut != tag2uid.end()) &&
+                    (ut->second.find(uid) == ut->second.end())) {
+                    unique = write;  // write passthrough to update uid counts
+                    if (!write) Tag = emptyTag;  // deny read access
+                }
+            } else {
+                unique = write && (ut != tag2uid.end());
+            }
+        }
+    }
+
+    if (Tag == emptyTag) return Tag;
+    WritePmsgEventLogTags(Tag, uid);  // record references periodically
+    if (!unique) return Tag;
+
+    bool updateWrite = false;
+    bool updateTag;
+
+    // Special case of AddEventLogTags, checks per-uid counter which makes
+    // no sense there, and is also optimized somewhat to reduce write times.
+    {
+        android::RWLock::AutoWLock writeLock(rwlock);
+
+        // double check after switch from read lock to write lock for Tag
+        updateTag = tag2name.find(Tag) == tag2name.end();
+        // unlikely, either update, race inviting conflict or multiple uids
+        if (!updateTag) {
+            Tag = nameToTag_locked(Name, format, unique);
+            if (Tag == emptyTag) return Tag;
+            // is it multiple uid's setting this value
+            if (!unique) {
+                tag2uid_const_iterator ut = tag2uid.find(Tag);
+                if (updateUid) {
+                    // Add it to the uid list
+                    if ((ut == tag2uid.end()) ||
+                        (ut->second.find(uid) != ut->second.end())) {
+                        return Tag;
+                    }
+                    const_cast<uid_list&>(ut->second).emplace(uid);
+                    updateWrite = true;
+                } else {
+                    if (ut == tag2uid.end()) return Tag;
+                    // (system) adding a global one, erase the uid list
+                    tag2uid.erase(ut);
+                    updateWrite = true;
+                }
+            }
+        }
+
+        // Update section
+        size_t count;
+        if (updateUid) {
+            count = 0;
+            uid2count_const_iterator ci = uid2count.find(uid);
+            if (ci != uid2count.end()) {
+                count = ci->second;
+                if (count >= max_per_uid) {
+                    if (!updateWrite) return emptyTag;
+                    // If we are added to the per-Uid perms, leak the Tag
+                    // if it already exists.
+                    updateUid = false;
+                    updateTag = false;
+                    updateFormat = false;
+                }
+            }
+        }
+
+        // updateWrite -> trigger output on modified content, reset tag2total
+        //    also sets static to dynamic entries if they are alterred,
+        //    only occurs if they have a uid, and runtime adds another uid.
+        if (updateWrite) tag2total[Tag] = 0;
+
+        if (updateTag) {
+            // mark as a dynamic entry, but do not upset current total counter
+            tag2total_const_iterator itot = tag2total.find(Tag);
+            if (itot == tag2total.end()) tag2total[Tag] = 0;
+
+            if (*format) {
+                key2tag[Name + "+" + format] = Tag;
+                if (key2tag.find(Name) == key2tag.end()) key2tag[Name] = Tag;
+            } else {
+                key2tag[Name] = Tag;
+            }
+            tag2name[Tag] = Name;
+        }
+        if (updateFormat) tag2format[Tag] = format;
+
+        if (updateUid) {
+            tag2uid[Tag].emplace(uid);
+            uid2count[uid] = count + 1;
+        }
+    }
+
+    if (updateTag || updateFormat || updateWrite) {
+        WritePersistEventLogTags(Tag, uid);
+    }
+
+    return Tag;
+}
+
+std::string LogTags::formatEntry(uint32_t tag, uid_t uid, const char* name,
+                                 const char* format) {
+    if (!format || !format[0]) {
+        return android::base::StringPrintf("%" PRIu32 "\t%s\n", tag, name);
+    }
+    size_t len = (strlen(name) + 7) / 8;
+    static const char tabs[] = "\t\t\t";
+    if (len > strlen(tabs)) len = strlen(tabs);
+    std::string Uid;
+    if (uid != AID_ROOT) Uid = android::base::StringPrintf(" # uid=%u", uid);
+    return android::base::StringPrintf("%" PRIu32 "\t%s%s\t%s%s\n", tag, name,
+                                       &tabs[len], format, Uid.c_str());
+}
+
+std::string LogTags::formatEntry_locked(uint32_t tag, uid_t uid,
+                                        bool authenticate) {
+    const char* name = tag2name[tag].c_str();
+
+    const char* format = "";
+    tag2format_const_iterator iform = tag2format.find(tag);
+    if (iform != tag2format.end()) format = iform->second.c_str();
+
+    // Access permission test, do not report dynamic entries
+    // that do not belong to us.
+    tag2uid_const_iterator ut = tag2uid.find(tag);
+    if (ut == tag2uid.end()) {
+        return formatEntry(tag, AID_ROOT, name, format);
+    }
+    if (uid != AID_ROOT) {
+        if (authenticate && (ut->second.find(uid) == ut->second.end())) {
+            return std::string("");
+        }
+        return formatEntry(tag, uid, name, format);
+    }
+
+    // Show all, one for each registered uid (we are group root)
+    std::string ret;
+    for (auto& it : ut->second) {
+        ret += formatEntry(tag, it, name, format);
+    }
+    return ret;
+}
+
+std::string LogTags::formatEntry(uint32_t tag, uid_t uid) {
+    android::RWLock::AutoRLock readLock(rwlock);
+    return formatEntry_locked(tag, uid);
+}
+
+std::string LogTags::formatGetEventTag(uid_t uid, const char* name,
+                                       const char* format) {
+    bool all = name && (name[0] == '*') && !name[1];
+    bool list = !name || all;
+    std::string ret;
+
+    if (!list) {
+        // switch to read entry only if format == "*"
+        if (format && (format[0] == '*') && !format[1]) format = nullptr;
+
+        // WAI: for null format, only works for a single entry, we can have
+        // multiple entries, one for each format, so we find first entry
+        // recorded, or entry with no format associated with it.
+        // We may desire to print all that match the name, but we did not
+        // add a mapping table for that and the cost is too high.
+        uint32_t tag = nameToTag(uid, name, format);
+        if (tag == emptyTag) return std::string("-1 ESRCH");
+        if (uid == AID_ROOT) {
+            android::RWLock::AutoRLock readLock(rwlock);
+
+            // first uid in list so as to manufacture an accurate reference
+            tag2uid_const_iterator ut = tag2uid.find(tag);
+            if ((ut != tag2uid.end()) &&
+                (ut->second.begin() != ut->second.end())) {
+                uid = *(ut->second.begin());
+            }
+        }
+        ret = formatEntry(tag, uid, name, format ?: tagToFormat(tag));
+        if (!ret.length()) return std::string("-1 ESRCH");
+        return ret;
+    }
+
+    android::RWLock::AutoRLock readLock(rwlock);
+    if (all) {
+        // everything under the sun
+        for (const auto& it : tag2name) {
+            ret += formatEntry_locked(it.first, uid);
+        }
+    } else {
+        // set entries are dynamic
+        for (const auto& it : tag2total) {
+            ret += formatEntry_locked(it.first, uid);
+        }
+    }
+    return ret;
+}
diff --git a/logd/LogTags.h b/logd/LogTags.h
new file mode 100644
index 0000000..cce700c
--- /dev/null
+++ b/logd/LogTags.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#pragma once
+
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <private/android_filesystem_config.h>
+#include <utils/RWLock.h>
+
+class LogTags {
+    // This lock protects all the unordered_map accesses below.  It
+    // is a reader/writer lock so that contentions are kept to a
+    // minimum since writes are rare, even administratably when
+    // reads are extended.  Resist the temptation to use the writer
+    // lock to protect anything outside the following unordered_maps
+    // as that would increase the reader contentions.  Use a separate
+    // mutex to protect the other entities.
+    android::RWLock rwlock;
+
+    // key is Name + "+" + Format
+    std::unordered_map<std::string, uint32_t> key2tag;
+    typedef std::unordered_map<std::string, uint32_t>::const_iterator
+        key2tag_const_iterator;
+
+    // Allows us to manage access permissions based on uid registrants
+    // Global entries are specifically erased.
+    typedef std::unordered_set<uid_t> uid_list;
+    std::unordered_map<uint32_t, uid_list> tag2uid;
+    typedef std::unordered_map<uint32_t, uid_list>::const_iterator
+        tag2uid_const_iterator;
+
+    std::unordered_map<uint32_t, std::string> tag2name;
+    typedef std::unordered_map<uint32_t, std::string>::const_iterator
+        tag2name_const_iterator;
+
+    std::unordered_map<uint32_t, std::string> tag2format;
+    typedef std::unordered_map<uint32_t, std::string>::const_iterator
+        tag2format_const_iterator;
+
+    static const size_t max_per_uid = 256;  // Put a cap on the tags per uid
+    std::unordered_map<uid_t, size_t> uid2count;
+    typedef std::unordered_map<uid_t, size_t>::const_iterator
+        uid2count_const_iterator;
+
+    // Dynamic entries are assigned
+    std::unordered_map<uint32_t, size_t> tag2total;
+    typedef std::unordered_map<uint32_t, size_t>::const_iterator
+        tag2total_const_iterator;
+
+    // emplace unique tag
+    uint32_t nameToTag(uid_t uid, const char* name, const char* format);
+    // find unique or associated tag
+    uint32_t nameToTag_locked(const std::string& name, const char* format,
+                              bool& unique);
+
+    // Record expected file watermarks to detect corruption.
+    std::unordered_map<std::string, size_t> file2watermark;
+    typedef std::unordered_map<std::string, size_t>::const_iterator
+        file2watermark_const_iterator;
+
+    void ReadPersistEventLogTags();
+
+    // format helpers
+    // format a single entry, does not need object data
+    static std::string formatEntry(uint32_t tag, uid_t uid, const char* name,
+                                   const char* format);
+    // caller locks, database lookup, authenticate against uid
+    std::string formatEntry_locked(uint32_t tag, uid_t uid,
+                                   bool authenticate = true);
+
+    bool RebuildFileEventLogTags(const char* filename, bool warn = true);
+
+    void AddEventLogTags(uint32_t tag, uid_t uid, const std::string& Name,
+                         const std::string& Format, const char* source = nullptr,
+                         bool warn = false);
+
+    void WriteDynamicEventLogTags(uint32_t tag, uid_t uid);
+    void WriteDebugEventLogTags(uint32_t tag, uid_t uid);
+    // push tag details to persistent storage
+    void WritePersistEventLogTags(uint32_t tag, uid_t uid = AID_ROOT,
+                                  const char* source = nullptr);
+
+    static const uint32_t emptyTag = uint32_t(-1);
+
+   public:
+    static const char system_event_log_tags[];
+    static const char dynamic_event_log_tags[];
+    // Only for userdebug and eng
+    static const char debug_event_log_tags[];
+
+    LogTags();
+
+    void WritePmsgEventLogTags(uint32_t tag, uid_t uid = AID_ROOT);
+    void ReadFileEventLogTags(const char* filename, bool warn = true);
+
+    // reverse lookup from tag
+    const char* tagToName(uint32_t tag) const;
+    const char* tagToFormat(uint32_t tag) const;
+    std::string formatEntry(uint32_t tag, uid_t uid);
+    // find associated tag
+    uint32_t nameToTag(const char* name) const;
+
+    // emplace tag if necessary, provide event-log-tag formated output in string
+    std::string formatGetEventTag(uid_t uid, const char* name,
+                                  const char* format);
+};
diff --git a/logd/LogTimes.cpp b/logd/LogTimes.cpp
deleted file mode 100644
index 68a0680..0000000
--- a/logd/LogTimes.cpp
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * 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 <sys/prctl.h>
-
-#include "FlushCommand.h"
-#include "LogBuffer.h"
-#include "LogTimes.h"
-#include "LogReader.h"
-
-pthread_mutex_t LogTimeEntry::timesLock = PTHREAD_MUTEX_INITIALIZER;
-
-LogTimeEntry::LogTimeEntry(LogReader &reader, SocketClient *client,
-                           bool nonBlock, unsigned long tail,
-                           unsigned int logMask, pid_t pid,
-                           uint64_t start) :
-        mRefCount(1),
-        mRelease(false),
-        mError(false),
-        threadRunning(false),
-        leadingDropped(false),
-        mReader(reader),
-        mLogMask(logMask),
-        mPid(pid),
-        mCount(0),
-        mTail(tail),
-        mIndex(0),
-        mClient(client),
-        mStart(start),
-        mNonBlock(nonBlock),
-        mEnd(LogBufferElement::getCurrentSequence()) {
-    pthread_cond_init(&threadTriggeredCondition, NULL);
-    cleanSkip_Locked();
-}
-
-void LogTimeEntry::startReader_Locked(void) {
-    pthread_attr_t attr;
-
-    threadRunning = true;
-
-    if (!pthread_attr_init(&attr)) {
-        if (!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) {
-            if (!pthread_create(&mThread, &attr,
-                                LogTimeEntry::threadStart, this)) {
-                pthread_attr_destroy(&attr);
-                return;
-            }
-        }
-        pthread_attr_destroy(&attr);
-    }
-    threadRunning = false;
-    if (mClient) {
-        mClient->decRef();
-    }
-    decRef_Locked();
-}
-
-void LogTimeEntry::threadStop(void *obj) {
-    LogTimeEntry *me = reinterpret_cast<LogTimeEntry *>(obj);
-
-    lock();
-
-    if (me->mNonBlock) {
-        me->error_Locked();
-    }
-
-    SocketClient *client = me->mClient;
-
-    if (me->isError_Locked()) {
-        LogReader &reader = me->mReader;
-        LastLogTimes &times = reader.logbuf().mTimes;
-
-        LastLogTimes::iterator it = times.begin();
-        while(it != times.end()) {
-            if (*it == me) {
-                times.erase(it);
-                me->release_Locked();
-                break;
-            }
-            it++;
-        }
-
-        me->mClient = NULL;
-        reader.release(client);
-    }
-
-    if (client) {
-        client->decRef();
-    }
-
-    me->threadRunning = false;
-    me->decRef_Locked();
-
-    unlock();
-}
-
-void *LogTimeEntry::threadStart(void *obj) {
-    prctl(PR_SET_NAME, "logd.reader.per");
-
-    LogTimeEntry *me = reinterpret_cast<LogTimeEntry *>(obj);
-
-    pthread_cleanup_push(threadStop, obj);
-
-    SocketClient *client = me->mClient;
-    if (!client) {
-        me->error();
-        return NULL;
-    }
-
-    LogBuffer &logbuf = me->mReader.logbuf();
-
-    bool privileged = FlushCommand::hasReadLogs(client);
-
-    me->leadingDropped = true;
-
-    lock();
-
-    while (me->threadRunning && !me->isError_Locked()) {
-        uint64_t start = me->mStart;
-
-        unlock();
-
-        if (me->mTail) {
-            logbuf.flushTo(client, start, privileged, FilterFirstPass, me);
-            me->leadingDropped = true;
-        }
-        start = logbuf.flushTo(client, start, privileged, FilterSecondPass, me);
-
-        lock();
-
-        if (start == LogBufferElement::FLUSH_ERROR) {
-            me->error_Locked();
-        }
-
-        if (me->mNonBlock || !me->threadRunning || me->isError_Locked()) {
-            break;
-        }
-
-        me->cleanSkip_Locked();
-
-        pthread_cond_wait(&me->threadTriggeredCondition, &timesLock);
-    }
-
-    unlock();
-
-    pthread_cleanup_pop(true);
-
-    return NULL;
-}
-
-// A first pass to count the number of elements
-int LogTimeEntry::FilterFirstPass(const LogBufferElement *element, void *obj) {
-    LogTimeEntry *me = reinterpret_cast<LogTimeEntry *>(obj);
-
-    LogTimeEntry::lock();
-
-    if (me->leadingDropped) {
-        if (element->getDropped()) {
-            LogTimeEntry::unlock();
-            return false;
-        }
-        me->leadingDropped = false;
-    }
-
-    if (me->mCount == 0) {
-        me->mStart = element->getSequence();
-    }
-
-    if ((!me->mPid || (me->mPid == element->getPid()))
-            && (me->isWatching(element->getLogId()))) {
-        ++me->mCount;
-    }
-
-    LogTimeEntry::unlock();
-
-    return false;
-}
-
-// A second pass to send the selected elements
-int LogTimeEntry::FilterSecondPass(const LogBufferElement *element, void *obj) {
-    LogTimeEntry *me = reinterpret_cast<LogTimeEntry *>(obj);
-
-    LogTimeEntry::lock();
-
-    me->mStart = element->getSequence();
-
-    if (me->skipAhead[element->getLogId()]) {
-        me->skipAhead[element->getLogId()]--;
-        goto skip;
-    }
-
-    if (me->leadingDropped) {
-        if (element->getDropped()) {
-            goto skip;
-        }
-        me->leadingDropped = false;
-    }
-
-    // Truncate to close race between first and second pass
-    if (me->mNonBlock && me->mTail && (me->mIndex >= me->mCount)) {
-        goto stop;
-    }
-
-    if (!me->isWatching(element->getLogId())) {
-        goto skip;
-    }
-
-    if (me->mPid && (me->mPid != element->getPid())) {
-        goto skip;
-    }
-
-    if (me->isError_Locked()) {
-        goto stop;
-    }
-
-    if (!me->mTail) {
-        goto ok;
-    }
-
-    ++me->mIndex;
-
-    if ((me->mCount > me->mTail) && (me->mIndex <= (me->mCount - me->mTail))) {
-        goto skip;
-    }
-
-    if (!me->mNonBlock) {
-        me->mTail = 0;
-    }
-
-ok:
-    if (!me->skipAhead[element->getLogId()]) {
-        LogTimeEntry::unlock();
-        return true;
-    }
-    // FALLTHRU
-
-skip:
-    LogTimeEntry::unlock();
-    return false;
-
-stop:
-    LogTimeEntry::unlock();
-    return -1;
-}
-
-void LogTimeEntry::cleanSkip_Locked(void) {
-    for (log_id_t i = LOG_ID_MIN; i < LOG_ID_MAX; i = (log_id_t) (i + 1)) {
-        skipAhead[i] = 0;
-    }
-}
diff --git a/logd/LogTimes.h b/logd/LogTimes.h
deleted file mode 100644
index 783bce6..0000000
--- a/logd/LogTimes.h
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef _LOGD_LOG_TIMES_H__
-#define _LOGD_LOG_TIMES_H__
-
-#include <pthread.h>
-#include <time.h>
-#include <sys/types.h>
-#include <sysutils/SocketClient.h>
-#include <utils/List.h>
-#include <log/log.h>
-
-class LogReader;
-
-class LogTimeEntry {
-    static pthread_mutex_t timesLock;
-    unsigned int mRefCount;
-    bool mRelease;
-    bool mError;
-    bool threadRunning;
-    bool leadingDropped;
-    pthread_cond_t threadTriggeredCondition;
-    pthread_t mThread;
-    LogReader &mReader;
-    static void *threadStart(void *me);
-    static void threadStop(void *me);
-    const unsigned int mLogMask;
-    const pid_t mPid;
-    unsigned int skipAhead[LOG_ID_MAX];
-    unsigned long mCount;
-    unsigned long mTail;
-    unsigned long mIndex;
-
-public:
-    LogTimeEntry(LogReader &reader, SocketClient *client, bool nonBlock,
-                 unsigned long tail, unsigned int logMask, pid_t pid,
-                 uint64_t start);
-
-    SocketClient *mClient;
-    uint64_t mStart;
-    const bool mNonBlock;
-    const uint64_t mEnd; // only relevant if mNonBlock
-
-    // Protect List manipulations
-    static void lock(void) { pthread_mutex_lock(&timesLock); }
-    static void unlock(void) { pthread_mutex_unlock(&timesLock); }
-
-    void startReader_Locked(void);
-
-    bool runningReader_Locked(void) const {
-        return threadRunning || mRelease || mError || mNonBlock;
-    }
-    void triggerReader_Locked(void) {
-        pthread_cond_signal(&threadTriggeredCondition);
-    }
-
-    void triggerSkip_Locked(log_id_t id, unsigned int skip) { skipAhead[id] = skip; }
-    void cleanSkip_Locked(void);
-
-    // Called after LogTimeEntry removed from list, lock implicitly held
-    void release_Locked(void) {
-        mRelease = true;
-        pthread_cond_signal(&threadTriggeredCondition);
-        if (mRefCount || threadRunning) {
-            return;
-        }
-        // No one else is holding a reference to this
-        delete this;
-    }
-
-    // Called to mark socket in jeopardy
-    void error_Locked(void) { mError = true; }
-    void error(void) { lock(); error_Locked(); unlock(); }
-
-    bool isError_Locked(void) const { return mRelease || mError; }
-
-    // Mark Used
-    //  Locking implied, grabbed when protection around loop iteration
-    void incRef_Locked(void) { ++mRefCount; }
-
-    bool owned_Locked(void) const { return mRefCount != 0; }
-
-    void decRef_Locked(void) {
-        if ((mRefCount && --mRefCount) || !mRelease || threadRunning) {
-            return;
-        }
-        // No one else is holding a reference to this
-        delete this;
-    }
-    bool isWatching(log_id_t id) { return (mLogMask & (1<<id)) != 0; }
-    // flushTo filter callbacks
-    static int FilterFirstPass(const LogBufferElement *element, void *me);
-    static int FilterSecondPass(const LogBufferElement *element, void *me);
-};
-
-typedef android::List<LogTimeEntry *> LastLogTimes;
-
-#endif
diff --git a/logd/LogUtils.h b/logd/LogUtils.h
new file mode 100644
index 0000000..c0f62d3
--- /dev/null
+++ b/logd/LogUtils.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2012-2015 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.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#include <private/android_logger.h>
+#include <utils/FastStrcmp.h>
+
+// Hijack this header as a common include file used by most all sources
+// to report some utilities defined here and there.
+
+#define LOGD_SNDTIMEO 32
+
+namespace android {
+
+// Furnished in main.cpp. Caller must own and free returned value
+char* uidToName(uid_t uid);
+
+// Caller must own and free returned value
+char* pidToName(pid_t pid);
+char* tidToName(pid_t tid);
+
+// Furnished in LogTags.cpp. Thread safe.
+const char* tagToName(uint32_t tag);
+
+// Furnished by LogKlog.cpp
+char* log_strntok_r(char* s, ssize_t& len, char*& saveptr, ssize_t& sublen);
+
+// needle should reference a string longer than 1 character
+static inline const char* strnstr(const char* s, ssize_t len,
+                                  const char* needle) {
+    if (len <= 0) return nullptr;
+
+    const char c = *needle++;
+    const size_t needleLen = strlen(needle);
+    do {
+        do {
+            if (len <= (ssize_t)needleLen) return nullptr;
+            --len;
+        } while (*s++ != c);
+    } while (fastcmp<memcmp>(s, needle, needleLen));
+    s--;
+    return s;
+}
+}
+
+// Returns true if the log buffer is meant for binary logs.
+static inline bool IsBinary(log_id_t log_id) {
+    return log_id == LOG_ID_EVENTS || log_id == LOG_ID_STATS || log_id == LOG_ID_SECURITY;
+}
+
+// Returns the numeric log tag for binary log messages.
+static inline uint32_t MsgToTag(const char* msg, uint16_t msg_len) {
+    if (msg_len < sizeof(android_event_header_t)) {
+        return 0;
+    }
+
+    return reinterpret_cast<const android_event_header_t*>(msg)->tag;
+}
+
+static inline bool worstUidEnabledForLogid(log_id_t id) {
+    return (id == LOG_ID_MAIN) || (id == LOG_ID_SYSTEM) ||
+           (id == LOG_ID_RADIO) || (id == LOG_ID_EVENTS);
+}
diff --git a/logd/LogWhiteBlackList.cpp b/logd/LogWhiteBlackList.cpp
deleted file mode 100644
index 277b3ca..0000000
--- a/logd/LogWhiteBlackList.cpp
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * 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 <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) {
-        if (mPid != pid_all) {
-            asprintf(strp, "%u/%u", mUid, mPid);
-        } else {
-            asprintf(strp, "%u", mUid);
-        }
-    } else if (mPid != pid_all) {
-        asprintf(strp, "/%u", mPid);
-    } else { // NB: mPid == pid_all can not happen if mUid == uid_all
-        asprintf(strp, "/");
-    }
-}
-
-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;
-}
diff --git a/logd/LogWhiteBlackList.h b/logd/LogWhiteBlackList.h
deleted file mode 100644
index 5f60801..0000000
--- a/logd/LogWhiteBlackList.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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 naughty(void) { return !mNaughty.empty(); }
-    bool nice(LogBufferElement *element);
-    bool nice(void) { return !mNice.empty(); }
-    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/logd/LogWriter.h b/logd/LogWriter.h
new file mode 100644
index 0000000..d43c604
--- /dev/null
+++ b/logd/LogWriter.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <log/log_read.h>
+
+// An interface for writing logs to a reader.
+class LogWriter {
+  public:
+    LogWriter(uid_t uid, bool privileged) : uid_(uid), privileged_(privileged) {}
+    virtual ~LogWriter() {}
+
+    virtual bool Write(const logger_entry& entry, const char* msg) = 0;
+    virtual void Shutdown() {}
+    virtual void Release() {}
+
+    virtual std::string name() const = 0;
+    uid_t uid() const { return uid_; }
+
+    bool privileged() const { return privileged_; }
+
+  private:
+    uid_t uid_;
+
+    // If this writer sees logs from all UIDs or only its own UID.  See clientHasLogCredentials().
+    bool privileged_;
+};
\ No newline at end of file
diff --git a/logd/LogdLock.cpp b/logd/LogdLock.cpp
new file mode 100644
index 0000000..16a37a5
--- /dev/null
+++ b/logd/LogdLock.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 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/LICENSE2.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 "LogdLock.h"
+
+std::mutex logd_lock;
diff --git a/logd/LogdLock.h b/logd/LogdLock.h
new file mode 100644
index 0000000..6f637c8
--- /dev/null
+++ b/logd/LogdLock.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2020 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/LICENSE2.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.
+ */
+
+#pragma once
+
+#include <mutex>
+
+extern std::mutex logd_lock;
diff --git a/logd/OWNERS b/logd/OWNERS
new file mode 100644
index 0000000..2394e32
--- /dev/null
+++ b/logd/OWNERS
@@ -0,0 +1,2 @@
+cferris@google.com
+tomcherry@google.com
diff --git a/logd/PruneList.cpp b/logd/PruneList.cpp
new file mode 100644
index 0000000..c3859f3
--- /dev/null
+++ b/logd/PruneList.cpp
@@ -0,0 +1,201 @@
+/*
+ * 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 "PruneList.h"
+
+#include <ctype.h>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+bool Prune::Matches(LogBufferElement* element) const {
+    return (uid_ == UID_ALL || uid_ == element->uid()) &&
+           (pid_ == PID_ALL || pid_ == element->pid());
+}
+
+std::string Prune::Format() const {
+    if (uid_ != UID_ALL) {
+        if (pid_ != PID_ALL) {
+            return android::base::StringPrintf("%u/%u", uid_, pid_);
+        }
+        return android::base::StringPrintf("%u", uid_);
+    }
+    if (pid_ != PID_ALL) {
+        return android::base::StringPrintf("/%u", pid_);
+    }
+    // NB: pid_ == PID_ALL can not happen if uid_ == UID_ALL
+    return std::string("/");
+}
+
+PruneList::PruneList() {
+    Init(nullptr);
+}
+
+bool PruneList::Init(const char* str) {
+    high_priority_prune_.clear();
+    low_priority_prune_.clear();
+
+    // default here means take ro.logd.filter, persist.logd.filter then internal default in order.
+    if (str && !strcmp(str, "default")) {
+        str = nullptr;
+    }
+    if (str && !strcmp(str, "disable")) {
+        str = "";
+    }
+
+    std::string filter;
+
+    if (str) {
+        filter = str;
+    } else {
+        filter = android::base::GetProperty("ro.logd.filter", "default");
+        auto persist_filter = android::base::GetProperty("persist.logd.filter", "default");
+        // default here means take ro.logd.filter
+        if (persist_filter != "default") {
+            filter = persist_filter;
+        }
+    }
+
+    // default here means take internal default.
+    if (filter == "default") {
+        filter = "~! ~1000/!";
+    }
+    if (filter == "disable") {
+        filter = "";
+    }
+
+    worst_uid_enabled_ = false;
+    worst_pid_of_system_enabled_ = false;
+
+    for (str = filter.c_str(); *str; ++str) {
+        if (isspace(*str)) {
+            continue;
+        }
+
+        std::list<Prune>* list;
+        if (*str == '~' || *str == '!') {  // ~ supported, ! undocumented
+            ++str;
+            // special case, prune the worst UID of those using at least 1/8th of the buffer.
+            if (*str == '!') {
+                worst_uid_enabled_ = true;
+                ++str;
+                if (!*str) {
+                    break;
+                }
+                if (!isspace(*str)) {
+                    LOG(ERROR) << "Nothing expected after '~!', but found '" << str << "'";
+                    return false;
+                }
+                continue;
+            }
+            // special case, translated to worst PID of System at priority
+            static const char WORST_SYSTEM_PID[] = "1000/!";
+            if (!strncmp(str, WORST_SYSTEM_PID, sizeof(WORST_SYSTEM_PID) - 1)) {
+                worst_pid_of_system_enabled_ = true;
+                str += sizeof(WORST_SYSTEM_PID) - 1;
+                if (!*str) {
+                    break;
+                }
+                if (!isspace(*str)) {
+                    LOG(ERROR) << "Nothing expected after '~1000/!', but found '" << str << "'";
+                    return false;
+                }
+                continue;
+            }
+            if (!*str) {
+                LOG(ERROR) << "Expected UID or PID after '~', but found nothing";
+                return false;
+            }
+            list = &high_priority_prune_;
+        } else {
+            list = &low_priority_prune_;
+        }
+
+        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) {
+            LOG(ERROR) << "Expected UID/PID combination, but found none";
+            return false;
+        }
+
+        if (*str && !isspace(*str)) {
+            LOG(ERROR) << "Nothing expected after UID/PID combination, but found '" << str << "'";
+            return false;
+        }
+
+        list->emplace_back(uid, pid);
+        if (!*str) {
+            break;
+        }
+    }
+
+    return true;
+}
+
+std::string PruneList::Format() const {
+    std::vector<std::string> prune_rules;
+
+    if (worst_uid_enabled_) {
+        prune_rules.emplace_back("~!");
+    }
+    if (worst_pid_of_system_enabled_) {
+        prune_rules.emplace_back("~1000/!");
+    }
+    for (const auto& rule : low_priority_prune_) {
+        prune_rules.emplace_back(rule.Format());
+    }
+    for (const auto& rule : high_priority_prune_) {
+        prune_rules.emplace_back("~" + rule.Format());
+    }
+    return android::base::Join(prune_rules, " ");
+}
+
+bool PruneList::IsHighPriority(LogBufferElement* element) const {
+    for (const auto& rule : high_priority_prune_) {
+        if (rule.Matches(element)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool PruneList::IsLowPriority(LogBufferElement* element) const {
+    for (const auto& rule : low_priority_prune_) {
+        if (rule.Matches(element)) {
+            return true;
+        }
+    }
+    return false;
+}
diff --git a/logd/PruneList.h b/logd/PruneList.h
new file mode 100644
index 0000000..94de5c5
--- /dev/null
+++ b/logd/PruneList.h
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <string.h>
+#include <list>
+
+#include "LogBufferElement.h"
+
+class Prune {
+  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_(uid), pid_(pid) {}
+
+    bool Matches(LogBufferElement* element) const;
+    std::string Format() const;
+
+    uid_t uid() const { return uid_; }
+    pid_t pid() const { return pid_; }
+
+  private:
+    const uid_t uid_;
+    const pid_t pid_;
+};
+
+class PruneList {
+  public:
+    PruneList();
+
+    bool Init(const char* str);
+    std::string Format() const;
+
+    bool IsHighPriority(LogBufferElement* element) const;
+    bool IsLowPriority(LogBufferElement* element) const;
+
+    bool HasHighPriorityPruneRules() const { return !high_priority_prune_.empty(); }
+    bool HasLowPriorityPruneRules() const { return !low_priority_prune_.empty(); }
+
+    bool worst_uid_enabled() const { return worst_uid_enabled_; }
+    bool worst_pid_of_system_enabled() const { return worst_pid_of_system_enabled_; }
+
+  private:
+    std::list<Prune> high_priority_prune_;
+    std::list<Prune> low_priority_prune_;
+
+    bool worst_uid_enabled_;
+    bool worst_pid_of_system_enabled_;
+};
diff --git a/logd/README.compression.md b/logd/README.compression.md
new file mode 100644
index 0000000..4ba634a
--- /dev/null
+++ b/logd/README.compression.md
@@ -0,0 +1,81 @@
+# Log Compression instead of Chatty in Android S
+
+## The problem
+
+* Log buffer space is precious, but suffers from the tragedy of the commons
+* Log spam fills the buffers making them less useful in logcat/bugreports
+* “Spam” is often in the eye of the beholder: which messages are important depends on what you’re trying to debug
+
+## The idea
+
+* Chatty isn’t helping as much as we’d hoped, and is surprisingly expensive
+* Compress logs to make more efficient use of the buffer
+* Address the root cause of log spam at its source:
+    * Do not hide log spam at runtime, which de-incentivize fixes
+    * Add presubmit coverage similar to SELinux violations to keep log spam down
+
+---
+
+## Chatty in Theory
+
+* Delete messages classified as spam to extend the range of logs from other sources
+* “Spam” defined as:
+    * Logs from UIDs whose logs consume over 12.5% of a log buffer
+    * Back-to-back exact duplicate messages
+
+## Chatty in Practice
+
+* Developer confusion about missing and de-duplicated logs
+* Lowered incentive to fix the root cause of bad logging behavior
+* High CPU overhead
+* Memory usage greatly exceeds configured buffer size
+* Only marginal increase in log range
+
+---
+
+## Log Compression in Theory
+
+* Store many more logs in the same log buffer size => better for diagnosis
+* Memory usage stays below configured log size => better system health
+* No gaps in logs, no de-duplicated logs => no developer confusion
+* No hiding bad behavior => increased accountability/incentive to fix root causes
+
+## Log Compression Preliminary Results
+
+* Captured 2, 5 day periods of full time personal usage of Pixel 4 and replayed the logs offline
+* Compression vs Chatty:
+    * **3.5x more log messages on average**
+    * **50% less CPU usage**
+    * **50% less memory usage**
+
+---
+
+## Log Messages in 1MB
+
+* The number of log messages still available in logcat after ‘Message Count’ messages have been logged to a 1MB log buffer
+* Note: ‘Simple’ is the Chatty code without log spam detection and without de-duplication.
+
+![Total Log Count](doc_images/total_log_count.png)
+
+---
+
+## CPU Time
+
+* Total CPU time on ARM64 (Walleye) and 32bit x86 (Cuttlefish)
+* X axis represents different log buffer size configurations.
+    * Chatty uses significantly more CPU time at 1MB (the default Pixel configuration)
+    * Chatty scales poorly with increased log buffer sizes
+* Note: “simple” isn’t “compression without actually compressing”, it’s “chatty without doing the chatty elimination”, which is why “simple” is more expensive than “compression” on walleye.
+
+![CPU Time Walleye](doc_images/cpu_walleye.png)
+![CPU Time Cuttlefish](doc_images/cpu_cuttlefish.png)
+
+---
+
+## Memory Usage
+
+* The memory used by ‘Message Count’ messages, on both Walleye and Cuttlefish
+* Note: Chatty does not consider the metadata (UID, PID, timestamp, etc) in its calculation of log buffer size, so a 1MB log buffer will consume more than 1MB.  Note that there are 8 log buffers, 5 of which are typically filled.
+
+![Memory Usage](doc_images/memory_usage.png)
+
diff --git a/logd/README.property b/logd/README.property
index a472efd..8fd7f48 100644
--- a/logd/README.property
+++ b/logd/README.property
@@ -1,27 +1,72 @@
-The properties that logd responds to are:
+The properties that logd and friends react to are:
 
 name                       type default  description
-logd.auditd                 bool  true   Enable selinux audit daemon
-logd.auditd.dmesg           bool  true   selinux audit messages duplicated and
-                                         sent on to dmesg log
-logd.klogd                  bool depends Enable klogd daemon
-logd.statistics             bool depends Enable logcat -S statistics.
-ro.config.low_ram           bool  false  if true, logd.statistics & logd.klogd
-                                         default false
-ro.build.type               string       if user, logd.statistics & logd.klogd
-                                         default false
-persist.logd.logpersistd    string       Enable logpersist daemon, "logcatd"
-                                         turns on logcat -f in logd context
-persist.logd.size          number 256K   default size of the buffer for all
-                                         log ids at initial startup, at runtime
-                                         use: logcat -b all -G <value>
-persist.logd.size.main     number 256K   Size of the buffer for the main log
-persist.logd.size.system   number 256K   Size of the buffer for the system log
-persist.logd.size.radio    number 256K   Size of the buffer for the radio log
-persist.logd.size.event    number 256K   Size of the buffer for the event log
-persist.logd.size.crash    number 256K   Size of the buffer for the crash log
+ro.logd.auditd             bool   true   Enable selinux audit daemon
+ro.logd.auditd.dmesg       bool   true   selinux audit messages sent to dmesg.
+ro.logd.auditd.main        bool   true   selinux audit messages sent to main.
+ro.logd.auditd.events      bool   true   selinux audit messages sent to events.
+persist.logd.security      bool   false  Enable security buffer.
+ro.organization_owned      bool   false  Override persist.logd.security to false
+ro.logd.kernel             bool  svelte+ Enable klogd daemon
+logd.statistics            bool  svelte+ Enable logcat -S statistics.
+ro.debuggable              number        if not "1", logd.statistics &
+                                         ro.logd.kernel default false.
+logd.logpersistd.enable    bool   auto   Safe to start logpersist daemon service
+logd.logpersistd          string persist Enable logpersist daemon, "logcatd"
+                                         turns on logcat -f in logd context.
+					 Responds to logcatd, clear and stop.
+logd.logpersistd.buffer          persist logpersistd buffers to collect
+logd.logpersistd.size            persist logpersistd size in MB
+logd.logpersistd.rotate_kbytes   	 persist logpersistd outout file size in KB.
+persist.logd.logpersistd   string        Enable logpersist daemon, "logcatd"
+                                         turns on logcat -f in logd context.
+persist.logd.logpersistd.buffer    all   logpersistd buffers to collect
+persist.logd.logpersistd.size      256   logpersistd size in MB
+persist.logd.logpersistd.count     256   sets max number of rotated logs to <count>.
+persist.logd.logpersistd.rotate_kbytes   1024  logpersistd output file size in KB
+persist.logd.size          number  ro    Global default size of the buffer for
+                                         all log ids at initial startup, at
+                                         runtime use: logcat -b all -G <value>
+ro.logd.size               number svelte default for persist.logd.size. Larger
+                                         platform default sizes than 256KB are
+                                         known to not scale well under log spam
+                                         pressure. Address the spam first,
+                                         resist increasing the log buffer.
+persist.logd.size.<buffer> number  ro    Size of the buffer for <buffer> log
+ro.logd.size.<buffer>      number svelte default for persist.logd.size.<buffer>
+ro.config.low_ram          bool   false  if true, logd.statistics,
+                                         ro.logd.kernel default false,
+                                         logd.size 64K instead of 256K.
+persist.logd.filter        string        Pruning filter to optimize content.
+                                         At runtime use: logcat -P "<string>"
+ro.logd.filter       string "~! ~1000/!" default for persist.logd.filter.
+                                         This default means to prune the
+                                         oldest entries of chattiest UID, and
+                                         the chattiest PID of system
+                                         (1000, or AID_SYSTEM).
+log.tag                   string persist The global logging level, VERBOSE,
+                                         DEBUG, INFO, WARN, ERROR, ASSERT or
+                                         SILENT. Only the first character is
+                                         the key character.
+persist.log.tag            string build  default for log.tag
+log.tag.<tag>             string persist The <tag> specific logging level.
+persist.log.tag.<tag>      string build  default for log.tag.<tag>
+
+logd.buffer_type           string (empty) Set the log buffer type.  Current choices are 'simple',
+                                          'chatty', or 'serialized'.  Defaults to 'chatty' if empty.
 
 NB:
-- number support multipliers (K or M) for convenience. Range is limited
-  to between 64K and 256M for log buffer sizes. Individual logs override the
-  global default.
+- auto - managed by /init
+- svelte - see ro.config.low_ram for details.
+- svelte+ - If empty, default to true if `ro.config.low_ram == false && ro.debuggable == true`
+- ro - <base property> temporary override, ro.<base property> platform default.
+- persist - <base property> override, persist.<base property> platform default.
+- build - VERBOSE for native, DEBUG for jvm isLoggable, or developer option.
+- number - support multipliers (K or M) for convenience. Range is limited
+  to between 64K and 256M for log buffer sizes. Individual log buffer ids
+  such as main, system, ... override global default.
+- Pruning filter rules are specified as UID, UID/PID or /PID. A '~' prefix indicates that elements
+  matching the rule should be pruned with higher priority otherwise they're pruned with lower
+  priority. All other pruning activity is oldest first. Special case ~! represents an automatic
+  pruning for the noisiest UID as determined by the current statistics.  Special case ~1000/!
+  represents pruning of the worst PID within AID_SYSTEM when AID_SYSTEM is the noisiest UID.
diff --git a/logd/README.replay.md b/logd/README.replay.md
new file mode 100644
index 0000000..5f7ec9e
--- /dev/null
+++ b/logd/README.replay.md
@@ -0,0 +1,46 @@
+logd can record and replay log messages for offline analysis.
+
+Recording Messages
+------------------
+
+logd has a `RecordingLogBuffer` buffer that records messages to /data/misc/logd/recorded-messages.
+It stores messages in memory until that file is accessible, in order to capture all messages since
+the beginning of boot.  It is only meant for logging developers to use and must be manually enabled
+in by adding `RecordingLogBuffer.cpp` to `Android.bp` and setting
+`log_buffer = new SimpleLogBuffer(&reader_list, &log_tags, &log_statistics);` in `main.cpp`.
+
+Recording messages may delay the Log() function from completing and it is highly recommended to make
+the logd socket in `liblog` blocking, by removing `SOCK_NONBLOCK` from the `socket()` call in
+`liblog/logd_writer.cpp`.
+
+Replaying Messages
+------------------
+
+Recorded messages can be replayed offline with the `replay_messages` tool.  It runs on host and
+device and supports the following options:
+
+1. `interesting` - this prints 'interesting' statistics for each of the log buffer types (simple,
+   chatty, serialized).  The statistics are:
+    1. Log Entry Count
+    2. Size (the uncompressed size of the log messages in bytes)
+    3. Overhead (the total cost of the log messages in memory in bytes)
+    4. Range (the range of time that the logs cover in seconds)
+2. `memory_usage BUFFER_TYPE` - this prints the memory usage (sum of private dirty pages of the
+  `replay_messages` process).  Note that the input file is mmap()'ed as RO/Shared so it does not
+  appear in these dirty pages, and a baseline is taken before allocating the log buffers, so only
+  their contributions are measured.  The tool outputs the memory usage every 100,000 messages.
+3. `latency BUFFER_TYPE` - this prints statistics of the latency of the Log() function for the given
+  buffer type.  It specifically prints the 1st, 2nd, and 3rd quartiles; the 95th, 99th, and 99.99th
+  percentiles; and the maximum latency.
+4. `print_logs BUFFER_TYPE [buffers] [print_point]` - this prints the logs as processed by the given
+  buffer_type from the buffers specified by `buffers` starting after the number of logs specified by
+  `print_point` have been logged.  This acts as if a user called `logcat` immediately after the
+  specified logs have been logged, which is particularly useful since it will show the chatty
+  pruning messages at that point.  It additionally prints the statistics from `logcat -S` after the
+  logs.
+  `buffers` is a comma separated list of the numeric buffer id values from `<android/log.h>`.  For
+  example, `0,1,3` represents the main, radio, and system buffers.  It can can also be `all`.
+  `print_point` is an positive integer.  If it is unspecified, logs are printed after the entire
+  input file is consumed.
+5. `nothing BUFFER_TYPE` - this does nothing other than read the input file and call Log() for the
+  given buffer type.  This is used for profiling CPU usage of strictly the log buffer.
diff --git a/liblog/fake_log_device.h b/logd/RecordedLogMessage.h
similarity index 62%
copy from liblog/fake_log_device.h
copy to logd/RecordedLogMessage.h
index 9d168cd..f18c422 100644
--- a/liblog/fake_log_device.h
+++ b/logd/RecordedLogMessage.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -14,15 +14,17 @@
  * limitations under the License.
  */
 
-#ifndef _LIBLOG_FAKE_LOG_DEVICE_H
-#define _LIBLOG_FAKE_LOG_DEVICE_H
+#pragma once
 
-#include <sys/types.h>
+#include <inttypes.h>
 
-struct iovec;
+#include <log/log_time.h>
 
-int fakeLogOpen(const char *pathName, int flags);
-int fakeLogClose(int fd);
-ssize_t fakeLogWritev(int fd, const struct iovec* vector, int count);
-
-#endif // _LIBLOG_FAKE_LOG_DEVICE_H
+struct __attribute__((packed)) RecordedLogMessage {
+    uint32_t uid;
+    uint32_t pid;
+    uint32_t tid;
+    log_time realtime;
+    uint16_t msg_len;
+    uint8_t log_id;
+};
diff --git a/logd/RecordingLogBuffer.cpp b/logd/RecordingLogBuffer.cpp
new file mode 100644
index 0000000..f5991f3
--- /dev/null
+++ b/logd/RecordingLogBuffer.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 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 "RecordingLogBuffer.h"
+
+#include <android-base/file.h>
+
+static void WriteLogMessage(int fd, const RecordedLogMessage& meta, const std::string& msg) {
+    android::base::WriteFully(fd, &meta, sizeof(meta));
+    android::base::WriteFully(fd, msg.c_str(), meta.msg_len);
+}
+
+void RecordingLogBuffer::RecordLogMessage(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid,
+                                          pid_t tid, const char* msg, uint16_t len) {
+    auto lock = std::lock_guard{lock_};
+    if (len > LOGGER_ENTRY_MAX_PAYLOAD) {
+        len = LOGGER_ENTRY_MAX_PAYLOAD;
+    }
+
+    RecordedLogMessage recorded_log_message = {
+            .uid = uid,
+            .pid = static_cast<uint32_t>(pid),
+            .tid = static_cast<uint32_t>(tid),
+            .realtime = realtime,
+            .msg_len = len,
+            .log_id = static_cast<uint8_t>(log_id),
+    };
+
+    if (!fd_.ok()) {
+        fd_.reset(open("/data/misc/logd/recorded-messages",
+                       O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 0666));
+        if (!fd_.ok()) {
+            since_boot_messages_.emplace_back(recorded_log_message, std::string(msg, len));
+            return;
+        } else {
+            for (const auto& [meta, msg] : since_boot_messages_) {
+                WriteLogMessage(fd_.get(), meta, msg);
+            }
+        }
+    }
+
+    WriteLogMessage(fd_.get(), recorded_log_message, std::string(msg, len));
+}
+
+int RecordingLogBuffer::Log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid,
+                            const char* msg, uint16_t len) {
+    RecordLogMessage(log_id, realtime, uid, pid, tid, msg, len);
+    return SimpleLogBuffer::Log(log_id, realtime, uid, pid, tid, msg, len);
+}
\ No newline at end of file
diff --git a/logd/RecordingLogBuffer.h b/logd/RecordingLogBuffer.h
new file mode 100644
index 0000000..49a0aba
--- /dev/null
+++ b/logd/RecordingLogBuffer.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include "SimpleLogBuffer.h"
+
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
+#include "RecordedLogMessage.h"
+
+class RecordingLogBuffer : public SimpleLogBuffer {
+  public:
+    RecordingLogBuffer(LogReaderList* reader_list, LogTags* tags, LogStatistics* stats)
+        : SimpleLogBuffer(reader_list, tags, stats) {}
+
+    int Log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid, const char* msg,
+            uint16_t len) override;
+
+  private:
+    void RecordLogMessage(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid,
+                          const char* msg, uint16_t len);
+
+    std::vector<std::pair<RecordedLogMessage, std::string>> since_boot_messages_;
+    android::base::unique_fd fd_;
+};
diff --git a/logd/ReplayMessages.cpp b/logd/ReplayMessages.cpp
new file mode 100644
index 0000000..bad8c56
--- /dev/null
+++ b/logd/ReplayMessages.cpp
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2020 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 <inttypes.h>
+
+#include <chrono>
+#include <map>
+
+#include <android-base/file.h>
+#include <android-base/mapped_file.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <android/log.h>
+#include <log/log_time.h>
+#include <log/logprint.h>
+
+#include "ChattyLogBuffer.h"
+#include "LogBuffer.h"
+#include "LogStatistics.h"
+#include "RecordedLogMessage.h"
+#include "SerializedLogBuffer.h"
+#include "SimpleLogBuffer.h"
+
+using android::base::MappedFile;
+using android::base::ParseInt;
+using android::base::ParseUint;
+using android::base::Split;
+
+char* android::uidToName(uid_t) {
+    return nullptr;
+}
+
+static size_t GetPrivateDirty() {
+    // Allocate once and hope that we don't need to reallocate >40000, to prevent heap fragmentation
+    static std::string smaps(40000, '\0');
+    android::base::ReadFileToString("/proc/self/smaps", &smaps);
+
+    size_t result = 0;
+    size_t base = 0;
+    size_t found;
+    while (true) {
+        found = smaps.find("Private_Dirty:", base);
+        if (found == smaps.npos) break;
+
+        found += sizeof("Private_Dirty:");
+
+        result += atoi(&smaps[found]);
+
+        base = found + 1;
+    }
+
+    return result;
+}
+
+static AndroidLogFormat* GetLogFormat() {
+    static AndroidLogFormat* format = [] {
+        auto* format = android_log_format_new();
+        android_log_setPrintFormat(format, android_log_formatFromString("threadtime"));
+        android_log_setPrintFormat(format, android_log_formatFromString("uid"));
+        return format;
+    }();
+    return format;
+}
+
+static void PrintMessage(struct log_msg* buf) {
+    bool is_binary =
+            buf->id() == LOG_ID_EVENTS || buf->id() == LOG_ID_STATS || buf->id() == LOG_ID_SECURITY;
+
+    AndroidLogEntry entry;
+    int err;
+    if (is_binary) {
+        char binaryMsgBuf[1024];
+        err = android_log_processBinaryLogBuffer(&buf->entry, &entry, nullptr, binaryMsgBuf,
+                                                 sizeof(binaryMsgBuf));
+    } else {
+        err = android_log_processLogBuffer(&buf->entry, &entry);
+    }
+    if (err < 0) {
+        fprintf(stderr, "Error parsing log message\n");
+    }
+
+    android_log_printLogLine(GetLogFormat(), STDOUT_FILENO, &entry);
+}
+
+static log_time GetFirstTimeStamp(const MappedFile& recorded_messages) {
+    if (sizeof(RecordedLogMessage) >= recorded_messages.size()) {
+        fprintf(stderr, "At least one log message must be present in the input\n");
+        exit(1);
+    }
+
+    auto* meta = reinterpret_cast<RecordedLogMessage*>(recorded_messages.data());
+    return meta->realtime;
+}
+
+static LogMask BuffersToLogMask(const char* buffers) {
+    if (buffers == nullptr || !strcmp(buffers, "all")) {
+        return kLogMaskAll;
+    }
+    auto string_ids = Split(buffers, ",");
+    LogMask log_mask = 0;
+    for (const auto& string_id : string_ids) {
+        int buffer_id;
+        if (!ParseInt(string_id, &buffer_id, 0, 7)) {
+            fprintf(stderr, "Could not parse buffer_id '%s'\n", string_id.c_str());
+            exit(1);
+        }
+        log_mask |= 1 << buffer_id;
+    }
+    return log_mask;
+}
+
+class StdoutWriter : public LogWriter {
+  public:
+    StdoutWriter() : LogWriter(0, true) {}
+    bool Write(const logger_entry& entry, const char* message) override {
+        struct log_msg log_msg;
+        log_msg.entry = entry;
+        if (log_msg.entry.len > LOGGER_ENTRY_MAX_PAYLOAD) {
+            fprintf(stderr, "payload too large %" PRIu16, log_msg.entry.len);
+            exit(1);
+        }
+        memcpy(log_msg.msg(), message, log_msg.entry.len);
+
+        PrintMessage(&log_msg);
+
+        return true;
+    }
+
+    void Shutdown() override {
+        fprintf(stderr, "LogWriter::Shutdown() called\n");
+        exit(1);
+    }
+
+    std::string name() const override { return "stdout writer"; }
+};
+
+class Operation {
+  public:
+    virtual ~Operation() {}
+
+    virtual void Begin() {}
+    virtual void Log(const RecordedLogMessage& meta, const char* msg) = 0;
+    virtual void End() {}
+};
+
+class PrintInteresting : public Operation {
+  public:
+    PrintInteresting(log_time first_log_timestamp)
+        : stats_simple_{false, false, first_log_timestamp},
+          stats_chatty_{false, false, first_log_timestamp},
+          stats_serialized_{false, true, first_log_timestamp} {}
+
+    void Begin() override {
+        printf("message_count,simple_main_lines,simple_radio_lines,simple_events_lines,simple_"
+               "system_lines,simple_crash_lines,simple_stats_lines,simple_security_lines,simple_"
+               "kernel_lines,simple_main_size,simple_radio_size,simple_events_size,simple_system_"
+               "size,simple_crash_size,simple_stats_size,simple_security_size,simple_kernel_size,"
+               "simple_main_overhead,simple_radio_overhead,simple_events_overhead,simple_system_"
+               "overhead,simple_crash_overhead,simple_stats_overhead,simple_security_overhead,"
+               "simple_kernel_overhead,simple_main_range,simple_radio_range,simple_events_range,"
+               "simple_system_range,simple_crash_range,simple_stats_range,simple_security_range,"
+               "simple_kernel_range,chatty_main_lines,chatty_radio_lines,chatty_events_lines,"
+               "chatty_system_lines,chatty_crash_lines,chatty_stats_lines,chatty_security_lines,"
+               "chatty_"
+               "kernel_lines,chatty_main_size,chatty_radio_size,chatty_events_size,chatty_system_"
+               "size,chatty_crash_size,chatty_stats_size,chatty_security_size,chatty_kernel_size,"
+               "chatty_main_overhead,chatty_radio_overhead,chatty_events_overhead,chatty_system_"
+               "overhead,chatty_crash_overhead,chatty_stats_overhead,chatty_security_overhead,"
+               "chatty_kernel_overhead,chatty_main_range,chatty_radio_range,chatty_events_range,"
+               "chatty_system_range,chatty_crash_range,chatty_stats_range,chatty_security_range,"
+               "chatty_kernel_range,serialized_main_lines,serialized_radio_lines,serialized_events_"
+               "lines,serialized_"
+               "system_lines,serialized_crash_lines,serialized_stats_lines,serialized_security_"
+               "lines,serialized_"
+               "kernel_lines,serialized_main_size,serialized_radio_size,serialized_events_size,"
+               "serialized_system_"
+               "size,serialized_crash_size,serialized_stats_size,serialized_security_size,"
+               "serialized_kernel_size,"
+               "serialized_main_overhead,serialized_radio_overhead,serialized_events_overhead,"
+               "serialized_system_"
+               "overhead,serialized_crash_overhead,serialized_stats_overhead,serialized_security_"
+               "overhead,"
+               "serialized_kernel_overhead,serialized_main_range,serialized_radio_range,serialized_"
+               "events_range,"
+               "serialized_system_range,serialized_crash_range,serialized_stats_range,serialized_"
+               "security_range,"
+               "serialized_kernel_range\n");
+    }
+
+    void Log(const RecordedLogMessage& meta, const char* msg) override {
+        simple_log_buffer_.Log(static_cast<log_id_t>(meta.log_id), meta.realtime, meta.uid,
+                               meta.pid, meta.tid, msg, meta.msg_len);
+
+        chatty_log_buffer_.Log(static_cast<log_id_t>(meta.log_id), meta.realtime, meta.uid,
+                               meta.pid, meta.tid, msg, meta.msg_len);
+
+        serialized_log_buffer_.Log(static_cast<log_id_t>(meta.log_id), meta.realtime, meta.uid,
+                                   meta.pid, meta.tid, msg, meta.msg_len);
+
+        if (num_message_ % 10000 == 0) {
+            printf("%" PRIu64 ",%s,%s,%s\n", num_message_,
+                   stats_simple_.ReportInteresting().c_str(),
+                   stats_chatty_.ReportInteresting().c_str(),
+                   stats_serialized_.ReportInteresting().c_str());
+        }
+
+        num_message_++;
+    }
+
+  private:
+    uint64_t num_message_ = 1;
+
+    LogReaderList reader_list_;
+    LogTags tags_;
+    PruneList prune_list_;
+
+    LogStatistics stats_simple_;
+    SimpleLogBuffer simple_log_buffer_{&reader_list_, &tags_, &stats_simple_};
+
+    LogStatistics stats_chatty_;
+    ChattyLogBuffer chatty_log_buffer_{&reader_list_, &tags_, &prune_list_, &stats_chatty_};
+
+    LogStatistics stats_serialized_;
+    SerializedLogBuffer serialized_log_buffer_{&reader_list_, &tags_, &stats_serialized_};
+};
+
+class SingleBufferOperation : public Operation {
+  public:
+    SingleBufferOperation(log_time first_log_timestamp, const char* buffer) {
+        if (!strcmp(buffer, "simple")) {
+            stats_.reset(new LogStatistics{false, false, first_log_timestamp});
+            log_buffer_.reset(new SimpleLogBuffer(&reader_list_, &tags_, stats_.get()));
+        } else if (!strcmp(buffer, "chatty")) {
+            stats_.reset(new LogStatistics{false, false, first_log_timestamp});
+            log_buffer_.reset(
+                    new ChattyLogBuffer(&reader_list_, &tags_, &prune_list_, stats_.get()));
+        } else if (!strcmp(buffer, "serialized")) {
+            stats_.reset(new LogStatistics{false, true, first_log_timestamp});
+            log_buffer_.reset(new SerializedLogBuffer(&reader_list_, &tags_, stats_.get()));
+        } else {
+            fprintf(stderr, "invalid log buffer type '%s'\n", buffer);
+            abort();
+        }
+    }
+
+    void Log(const RecordedLogMessage& meta, const char* msg) override {
+        PreOperation();
+        log_buffer_->Log(static_cast<log_id_t>(meta.log_id), meta.realtime, meta.uid, meta.pid,
+                         meta.tid, msg, meta.msg_len);
+
+        Operation();
+
+        num_message_++;
+    }
+
+    virtual void PreOperation() {}
+    virtual void Operation() {}
+
+  protected:
+    uint64_t num_message_ = 1;
+
+    LogReaderList reader_list_;
+    LogTags tags_;
+    PruneList prune_list_;
+
+    std::unique_ptr<LogStatistics> stats_;
+    std::unique_ptr<LogBuffer> log_buffer_;
+};
+
+class PrintMemory : public SingleBufferOperation {
+  public:
+    PrintMemory(log_time first_log_timestamp, const char* buffer)
+        : SingleBufferOperation(first_log_timestamp, buffer) {}
+
+    void Operation() override {
+        if (num_message_ % 100000 == 0) {
+            printf("%" PRIu64 ",%s\n", num_message_,
+                   std::to_string(GetPrivateDirty() - baseline_memory_).c_str());
+        }
+    }
+
+  private:
+    size_t baseline_memory_ = GetPrivateDirty();
+};
+
+class PrintLogs : public SingleBufferOperation {
+  public:
+    PrintLogs(log_time first_log_timestamp, const char* buffer, const char* buffers,
+              const char* print_point)
+        : SingleBufferOperation(first_log_timestamp, buffer) {
+        mask_ = BuffersToLogMask(buffers);
+        if (print_point != nullptr) {
+            uint64_t result = 0;
+            if (!ParseUint(print_point, &result)) {
+                fprintf(stderr, "Could not parse print point '%s'\n", print_point);
+                exit(1);
+            }
+            print_point_ = result;
+        }
+    }
+
+    void Operation() override {
+        if (print_point_ && num_message_ >= *print_point_) {
+            End();
+            exit(0);
+        }
+    }
+
+    void End() override {
+        auto lock = std::lock_guard{logd_lock};
+        std::unique_ptr<LogWriter> test_writer(new StdoutWriter());
+        std::unique_ptr<FlushToState> flush_to_state = log_buffer_->CreateFlushToState(1, mask_);
+        log_buffer_->FlushTo(test_writer.get(), *flush_to_state, nullptr);
+
+        auto stats_string = stats_->Format(0, 0, mask_);
+        printf("%s\n", stats_string.c_str());
+    }
+
+  private:
+    LogMask mask_ = kLogMaskAll;
+    std::optional<uint64_t> print_point_;
+};
+
+class PrintLatency : public SingleBufferOperation {
+  public:
+    PrintLatency(log_time first_log_timestamp, const char* buffer)
+        : SingleBufferOperation(first_log_timestamp, buffer) {}
+
+    void PreOperation() override { operation_start_ = std::chrono::steady_clock::now(); }
+
+    void Operation() override {
+        auto end = std::chrono::steady_clock::now();
+        auto duration = (end - operation_start_).count();
+        durations_.emplace_back(duration);
+    }
+
+    void End() override {
+        std::sort(durations_.begin(), durations_.end());
+        auto q1 = durations_.size() / 4;
+        auto q2 = durations_.size() / 2;
+        auto q3 = 3 * durations_.size() / 4;
+
+        auto p95 = 95 * durations_.size() / 100;
+        auto p99 = 99 * durations_.size() / 100;
+        auto p9999 = 9999 * durations_.size() / 10000;
+
+        printf("q1: %lld q2: %lld q3: %lld  p95: %lld p99: %lld p99.99: %lld  max: %lld\n",
+               durations_[q1], durations_[q2], durations_[q3], durations_[p95], durations_[p99],
+               durations_[p9999], durations_.back());
+    }
+
+  private:
+    std::chrono::steady_clock::time_point operation_start_;
+    std::vector<long long> durations_;
+};
+
+class PrintAllLogs : public SingleBufferOperation {
+  public:
+    PrintAllLogs(log_time first_log_timestamp, const char* buffer, const char* buffers)
+        : SingleBufferOperation(first_log_timestamp, buffer) {
+        LogMask mask = BuffersToLogMask(buffers);
+        auto lock = std::unique_lock{logd_lock};
+        std::unique_ptr<LogWriter> stdout_writer(new StdoutWriter());
+        std::unique_ptr<LogReaderThread> log_reader(
+                new LogReaderThread(log_buffer_.get(), &reader_list_, std::move(stdout_writer),
+                                    false, 0, mask, 0, {}, 1, {}));
+        reader_list_.reader_threads().emplace_back(std::move(log_reader));
+    }
+
+    void Operation() override {
+        // If the rate of reading logs is slower than the rate of incoming logs, then the reader
+        // thread is disconnected to not overflow log buffers, therefore we artificially slow down
+        // the incoming log rate.
+        usleep(100);
+    }
+};
+
+int main(int argc, char** argv) {
+    if (argc < 3) {
+        fprintf(stderr, "Usage: %s FILE OPERATION [BUFFER] [OPTIONS]\n", argv[0]);
+        return 1;
+    }
+
+    if (strcmp(argv[2], "interesting") != 0 && argc < 4) {
+        fprintf(stderr, "Operations other than 'interesting' require a BUFFER argument\n");
+        return 1;
+    }
+
+    int recorded_messages_fd = open(argv[1], O_RDONLY);
+    if (recorded_messages_fd == -1) {
+        fprintf(stderr, "Couldn't open input file\n");
+        return 1;
+    }
+    struct stat fd_stat;
+    if (fstat(recorded_messages_fd, &fd_stat) != 0) {
+        fprintf(stderr, "Couldn't fstat input file\n");
+        return 1;
+    }
+    auto recorded_messages = MappedFile::FromFd(recorded_messages_fd, 0,
+                                                static_cast<size_t>(fd_stat.st_size), PROT_READ);
+    if (recorded_messages == nullptr) {
+        fprintf(stderr, "Couldn't mmap input file\n");
+        return 1;
+    }
+
+    // LogStatistics typically uses 'now()' to initialize its log range state, but this doesn't work
+    // when replaying older logs, so we instead give it the timestamp from the first log.
+    log_time first_log_timestamp = GetFirstTimeStamp(*recorded_messages);
+
+    std::unique_ptr<Operation> operation;
+    if (!strcmp(argv[2], "interesting")) {
+        operation.reset(new PrintInteresting(first_log_timestamp));
+    } else if (!strcmp(argv[2], "memory_usage")) {
+        operation.reset(new PrintMemory(first_log_timestamp, argv[3]));
+    } else if (!strcmp(argv[2], "latency")) {
+        operation.reset(new PrintLatency(first_log_timestamp, argv[3]));
+    } else if (!strcmp(argv[2], "print_logs")) {
+        operation.reset(new PrintLogs(first_log_timestamp, argv[3], argc > 4 ? argv[4] : nullptr,
+                                      argc > 5 ? argv[5] : nullptr));
+    } else if (!strcmp(argv[2], "print_all_logs")) {
+        operation.reset(
+                new PrintAllLogs(first_log_timestamp, argv[3], argc > 4 ? argv[4] : nullptr));
+    } else if (!strcmp(argv[2], "nothing")) {
+        operation.reset(new SingleBufferOperation(first_log_timestamp, argv[3]));
+    } else {
+        fprintf(stderr, "unknown operation '%s'\n", argv[2]);
+        return 1;
+    }
+
+    // LogBuffer::Log() won't log without this on host.
+    __android_log_set_minimum_priority(ANDROID_LOG_VERBOSE);
+    // But we still want to suppress messages <= error to not interrupt the rest of the output.
+    __android_log_set_logger([](const struct __android_log_message* log_message) {
+        if (log_message->priority < ANDROID_LOG_ERROR) {
+            return;
+        }
+        __android_log_stderr_logger(log_message);
+    });
+
+    operation->Begin();
+
+    uint64_t read_position = 0;
+    while (read_position + sizeof(RecordedLogMessage) < recorded_messages->size()) {
+        auto* meta =
+                reinterpret_cast<RecordedLogMessage*>(recorded_messages->data() + read_position);
+        if (read_position + sizeof(RecordedLogMessage) + meta->msg_len >=
+            recorded_messages->size()) {
+            break;
+        }
+        char* msg = recorded_messages->data() + read_position + sizeof(RecordedLogMessage);
+        read_position += sizeof(RecordedLogMessage) + meta->msg_len;
+
+        operation->Log(*meta, msg);
+    }
+
+    operation->End();
+
+    return 0;
+}
diff --git a/logd/SerializedData.h b/logd/SerializedData.h
new file mode 100644
index 0000000..d3d1e18
--- /dev/null
+++ b/logd/SerializedData.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <algorithm>
+#include <memory>
+
+// This class is used instead of std::string or std::vector because their clear(), erase(), etc
+// functions don't actually deallocate.  shrink_to_fit() does deallocate but is not guaranteed to
+// work and swapping with an empty string/vector is clunky.
+class SerializedData {
+  public:
+    SerializedData() {}
+    SerializedData(size_t size) : data_(new uint8_t[size]), size_(size) {}
+
+    void Resize(size_t new_size) {
+        if (size_ == 0) {
+            data_.reset(new uint8_t[new_size]);
+            size_ = new_size;
+        } else if (new_size == 0) {
+            data_.reset();
+            size_ = 0;
+        } else if (new_size != size_) {
+            std::unique_ptr<uint8_t[]> new_data(new uint8_t[new_size]);
+            size_t copy_size = std::min(size_, new_size);
+            memcpy(new_data.get(), data_.get(), copy_size);
+            data_.swap(new_data);
+            size_ = new_size;
+        }
+    }
+
+    uint8_t* data() { return data_.get(); }
+    const uint8_t* data() const { return data_.get(); }
+    size_t size() const { return size_; }
+
+  private:
+    std::unique_ptr<uint8_t[]> data_;
+    size_t size_ = 0;
+};
\ No newline at end of file
diff --git a/logd/SerializedFlushToState.cpp b/logd/SerializedFlushToState.cpp
new file mode 100644
index 0000000..30276ff
--- /dev/null
+++ b/logd/SerializedFlushToState.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2020 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 "SerializedFlushToState.h"
+
+#include <limits>
+
+#include <android-base/logging.h>
+
+SerializedFlushToState::SerializedFlushToState(uint64_t start, LogMask log_mask,
+                                               std::list<SerializedLogChunk>* logs)
+    : FlushToState(start, log_mask), logs_(logs) {
+    log_id_for_each(i) {
+        if (((1 << i) & log_mask) == 0) {
+            continue;
+        }
+        logs_needed_from_next_position_[i] = true;
+    }
+}
+
+SerializedFlushToState::~SerializedFlushToState() {
+    log_id_for_each(i) {
+        if (log_positions_[i]) {
+            log_positions_[i]->buffer_it->DecReaderRefCount();
+        }
+    }
+}
+
+void SerializedFlushToState::CreateLogPosition(log_id_t log_id) {
+    CHECK(!logs_[log_id].empty());
+    LogPosition log_position;
+    auto it = logs_[log_id].begin();
+    while (it != logs_[log_id].end() && start() > it->highest_sequence_number()) {
+        ++it;
+    }
+    if (it == logs_[log_id].end()) {
+        --it;
+    }
+    it->IncReaderRefCount();
+    log_position.buffer_it = it;
+
+    // Find the offset of the first log with sequence number >= start().
+    int read_offset = 0;
+    while (read_offset < it->write_offset()) {
+        const auto* entry = it->log_entry(read_offset);
+        if (entry->sequence() >= start()) {
+            break;
+        }
+        read_offset += entry->total_len();
+    }
+    log_position.read_offset = read_offset;
+
+    log_positions_[log_id].emplace(log_position);
+}
+
+void SerializedFlushToState::UpdateLogsNeeded(log_id_t log_id) {
+    auto& buffer_it = log_positions_[log_id]->buffer_it;
+    auto read_offset = log_positions_[log_id]->read_offset;
+
+    // If there is another log to read in this buffer, let it be read.
+    if (read_offset < buffer_it->write_offset()) {
+        logs_needed_from_next_position_[log_id] = false;
+    } else if (read_offset == buffer_it->write_offset()) {
+        // If there are no more logs to read in this buffer and it's the last buffer, then
+        // set logs_needed_from_next_position_ to wait until more logs get logged.
+        if (buffer_it == std::prev(logs_[log_id].end())) {
+            logs_needed_from_next_position_[log_id] = true;
+        } else {
+            // Otherwise, if there is another buffer piece, move to that and do the same check.
+            buffer_it->DecReaderRefCount();
+            ++buffer_it;
+            buffer_it->IncReaderRefCount();
+            log_positions_[log_id]->read_offset = 0;
+            if (buffer_it->write_offset() == 0) {
+                logs_needed_from_next_position_[log_id] = true;
+            } else {
+                logs_needed_from_next_position_[log_id] = false;
+            }
+        }
+    } else {
+        // read_offset > buffer_it->write_offset() should never happen.
+        LOG(FATAL) << "read_offset (" << read_offset << ") > buffer_it->write_offset() ("
+                   << buffer_it->write_offset() << ")";
+    }
+}
+
+void SerializedFlushToState::CheckForNewLogs() {
+    log_id_for_each(i) {
+        if (!logs_needed_from_next_position_[i]) {
+            continue;
+        }
+        if (!log_positions_[i]) {
+            if (logs_[i].empty()) {
+                continue;
+            }
+            CreateLogPosition(i);
+        }
+        UpdateLogsNeeded(i);
+    }
+}
+
+bool SerializedFlushToState::HasUnreadLogs() {
+    CheckForNewLogs();
+    log_id_for_each(i) {
+        if (log_positions_[i] && !logs_needed_from_next_position_[i]) {
+            return true;
+        }
+    }
+    return false;
+}
+
+LogWithId SerializedFlushToState::PopNextUnreadLog() {
+    uint64_t min_sequence = std::numeric_limits<uint64_t>::max();
+    log_id_t log_id;
+    const SerializedLogEntry* entry = nullptr;
+    log_id_for_each(i) {
+        if (!log_positions_[i] || logs_needed_from_next_position_[i]) {
+            continue;
+        }
+        if (log_positions_[i]->log_entry()->sequence() < min_sequence) {
+            log_id = i;
+            entry = log_positions_[i]->log_entry();
+            min_sequence = entry->sequence();
+        }
+    }
+    CHECK_NE(nullptr, entry);
+
+    log_positions_[log_id]->read_offset += entry->total_len();
+
+    logs_needed_from_next_position_[log_id] = true;
+
+    return {log_id, entry};
+}
+
+void SerializedFlushToState::Prune(log_id_t log_id,
+                                   const std::list<SerializedLogChunk>::iterator& buffer_it) {
+    // If we don't have a position for this log or if we're not referencing buffer_it, ignore.
+    if (!log_positions_[log_id].has_value() || log_positions_[log_id]->buffer_it != buffer_it) {
+        return;
+    }
+
+    // Decrease the ref count since we're deleting our reference.
+    buffer_it->DecReaderRefCount();
+
+    // Delete in the reference.
+    log_positions_[log_id].reset();
+
+    // Finally set logs_needed_from_next_position_, so CheckForNewLogs() will re-create the
+    // log_position_ object during the next read.
+    logs_needed_from_next_position_[log_id] = true;
+}
diff --git a/logd/SerializedFlushToState.h b/logd/SerializedFlushToState.h
new file mode 100644
index 0000000..1a69f7b
--- /dev/null
+++ b/logd/SerializedFlushToState.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <bitset>
+#include <list>
+#include <queue>
+
+#include "LogBuffer.h"
+#include "LogdLock.h"
+#include "SerializedLogChunk.h"
+#include "SerializedLogEntry.h"
+
+struct LogPosition {
+    std::list<SerializedLogChunk>::iterator buffer_it;
+    int read_offset;
+
+    const SerializedLogEntry* log_entry() const { return buffer_it->log_entry(read_offset); }
+};
+
+struct LogWithId {
+    log_id_t log_id;
+    const SerializedLogEntry* entry;
+};
+
+// This class tracks the specific point where a FlushTo client has read through the logs.  It
+// directly references the std::list<> iterators from the parent SerializedLogBuffer and the offset
+// into each log chunk where it has last read.  All interactions with this class, except for its
+// construction, must be done with SerializedLogBuffer::lock_ held.
+class SerializedFlushToState : public FlushToState {
+  public:
+    // Initializes this state object.  For each log buffer set in log_mask, this sets
+    // logs_needed_from_next_position_.
+    SerializedFlushToState(uint64_t start, LogMask log_mask, std::list<SerializedLogChunk>* logs)
+            REQUIRES(logd_lock);
+
+    // Decrease the reference of all referenced logs.  This happens when a reader is disconnected.
+    ~SerializedFlushToState() override;
+
+    // Updates the state of log_positions_ and logs_needed_from_next_position_ then returns true if
+    // there are any unread logs, false otherwise.
+    bool HasUnreadLogs() REQUIRES(logd_lock);
+
+    // Returns the next unread log and sets logs_needed_from_next_position_ to indicate that we're
+    // waiting for more logs from the associated log buffer.
+    LogWithId PopNextUnreadLog() REQUIRES(logd_lock);
+
+    // If the parent log buffer prunes logs, the reference that this class contains may become
+    // invalid, so this must be called first to drop the reference to buffer_it, if any.
+    void Prune(log_id_t log_id, const std::list<SerializedLogChunk>::iterator& buffer_it)
+            REQUIRES(logd_lock);
+
+  private:
+    // Set logs_needed_from_next_position_[i] to indicate if log_positions_[i] points to an unread
+    // log or to the point at which the next log will appear.
+    void UpdateLogsNeeded(log_id_t log_id) REQUIRES(logd_lock);
+
+    // Create a LogPosition object for the given log_id by searching through the log chunks for the
+    // first chunk and then first log entry within that chunk that is greater or equal to start().
+    void CreateLogPosition(log_id_t log_id) REQUIRES(logd_lock);
+
+    // Checks to see if any log buffers set in logs_needed_from_next_position_ have new logs and
+    // calls UpdateLogsNeeded() if so.
+    void CheckForNewLogs() REQUIRES(logd_lock);
+
+    std::list<SerializedLogChunk>* logs_ GUARDED_BY(logd_lock) = nullptr;
+    // An optional structure that contains an iterator to the serialized log buffer and offset into
+    // it that this logger should handle next.
+    std::optional<LogPosition> log_positions_[LOG_ID_MAX] GUARDED_BY(logd_lock);
+    // A bit for each log that is set if a given log_id has no logs or if this client has read all
+    // of its logs. In order words: `logs_[i].empty() || (buffer_it == std::prev(logs_.end) &&
+    // next_log_position == logs_write_position_)`.  These will be re-checked in each
+    // loop in case new logs came in.
+    std::bitset<LOG_ID_MAX> logs_needed_from_next_position_ GUARDED_BY(logd_lock) = {};
+};
diff --git a/logd/SerializedFlushToStateTest.cpp b/logd/SerializedFlushToStateTest.cpp
new file mode 100644
index 0000000..f41de37
--- /dev/null
+++ b/logd/SerializedFlushToStateTest.cpp
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2020 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 "SerializedFlushToState.h"
+
+#include <map>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
+
+using android::base::Join;
+using android::base::StringPrintf;
+
+constexpr size_t kChunkSize = 3 * 4096;
+
+class SerializedFlushToStateTest : public testing::Test {
+  protected:
+    void SetUp() override {
+        // This test spams many unneeded INFO logs, so we suppress them.
+        old_log_severity_ = android::base::SetMinimumLogSeverity(android::base::WARNING);
+    }
+    void TearDown() override { android::base::SetMinimumLogSeverity(old_log_severity_); }
+
+    std::string TestReport(const std::vector<uint64_t>& expected, const std::vector<uint64_t>& read)
+            REQUIRES(logd_lock) {
+        auto sequence_to_log_id = [&](uint64_t sequence) -> int {
+            for (const auto& [log_id, sequences] : sequence_numbers_per_buffer_) {
+                if (std::find(sequences.begin(), sequences.end(), sequence) != sequences.end()) {
+                    return log_id;
+                }
+            }
+            return -1;
+        };
+
+        std::map<int, std::vector<uint64_t>> missing_sequences;
+        std::vector<uint64_t> missing_expected;
+        std::set_difference(expected.begin(), expected.end(), read.begin(), read.end(),
+                            std::back_inserter(missing_expected));
+        for (uint64_t sequence : missing_expected) {
+            int log_id = sequence_to_log_id(sequence);
+            missing_sequences[log_id].emplace_back(sequence);
+        }
+
+        std::map<int, std::vector<uint64_t>> extra_sequences;
+        std::vector<uint64_t> extra_read;
+        std::set_difference(read.begin(), read.end(), expected.begin(), expected.end(),
+                            std::back_inserter(extra_read));
+        for (uint64_t sequence : extra_read) {
+            int log_id = sequence_to_log_id(sequence);
+            extra_sequences[log_id].emplace_back(sequence);
+        }
+
+        std::vector<std::string> errors;
+        for (const auto& [log_id, sequences] : missing_sequences) {
+            errors.emplace_back(
+                    StringPrintf("Log id %d missing %zu sequences", log_id, sequences.size()));
+        }
+
+        for (const auto& [log_id, sequences] : extra_sequences) {
+            errors.emplace_back(
+                    StringPrintf("Log id %d has extra %zu sequences", log_id, sequences.size()));
+        }
+
+        return Join(errors, ", ");
+    }
+
+    // Read sequence numbers in order from SerializedFlushToState for every mask combination and all
+    // sequence numbers from 0 through the highest logged sequence number + 1.
+    // This assumes that all of the logs have already been written.
+    void TestAllReading() REQUIRES(logd_lock) {
+        uint64_t max_sequence = sequence_ + 1;
+        uint32_t max_mask = (1 << LOG_ID_MAX) - 1;
+        for (uint64_t sequence = 0; sequence < max_sequence; ++sequence) {
+            for (uint32_t mask = 0; mask < max_mask; ++mask) {
+                auto state = SerializedFlushToState{sequence, mask, log_chunks_};
+                TestReading(sequence, mask, state);
+            }
+        }
+    }
+
+    // Similar to TestAllReading() except that it doesn't assume any logs are in the buffer, instead
+    // it calls write_logs() in a loop for sequence/mask combination.  It clears log_chunks_ and
+    // sequence_numbers_per_buffer_ between calls, such that only the sequence numbers written in
+    // the previous call to write_logs() are expected.
+    void TestAllReadingWithFutureMessages(const std::function<bool(int)>& write_logs)
+            REQUIRES(logd_lock) {
+        uint64_t max_sequence = sequence_ + 1;
+        uint32_t max_mask = (1 << LOG_ID_MAX) - 1;
+        for (uint64_t sequence = 1; sequence < max_sequence; ++sequence) {
+            for (uint32_t mask = 1; mask < max_mask; ++mask) {
+                log_id_for_each(i) { log_chunks_[i].clear(); }
+                auto state = SerializedFlushToState{sequence, mask, log_chunks_};
+                int loop_count = 0;
+                while (write_logs(loop_count++)) {
+                    TestReading(sequence, mask, state);
+                    sequence_numbers_per_buffer_.clear();
+                }
+            }
+        }
+    }
+
+    void TestReading(uint64_t start, LogMask log_mask, SerializedFlushToState& state)
+            REQUIRES(logd_lock) {
+        std::vector<uint64_t> expected_sequence;
+        log_id_for_each(i) {
+            if (((1 << i) & log_mask) == 0) {
+                continue;
+            }
+            for (const auto& sequence : sequence_numbers_per_buffer_[i]) {
+                if (sequence >= start) {
+                    expected_sequence.emplace_back(sequence);
+                }
+            }
+        }
+        std::sort(expected_sequence.begin(), expected_sequence.end());
+
+        std::vector<uint64_t> read_sequence;
+
+        while (state.HasUnreadLogs()) {
+            auto top = state.PopNextUnreadLog();
+            read_sequence.emplace_back(top.entry->sequence());
+        }
+
+        EXPECT_TRUE(std::is_sorted(read_sequence.begin(), read_sequence.end()));
+
+        EXPECT_EQ(expected_sequence.size(), read_sequence.size());
+
+        EXPECT_EQ(expected_sequence, read_sequence)
+                << "start: " << start << " log_mask: " << log_mask << " "
+                << TestReport(expected_sequence, read_sequence);
+    }
+
+    // Add a chunk with the given messages to the a given log buffer.  Keep track of the sequence
+    // numbers for future validation.  Optionally mark the block as having finished writing.
+    void AddChunkWithMessages(bool finish_writing, int buffer,
+                              const std::vector<std::string>& messages) REQUIRES(logd_lock) {
+        auto chunk = SerializedLogChunk{kChunkSize};
+        for (const auto& message : messages) {
+            auto sequence = sequence_++;
+            sequence_numbers_per_buffer_[buffer].emplace_back(sequence);
+            ASSERT_TRUE(chunk.CanLog(message.size() + 1));
+            chunk.Log(sequence, log_time(), 0, 1, 1, message.c_str(), message.size() + 1);
+        }
+        if (finish_writing) {
+            chunk.FinishWriting();
+        }
+        log_chunks_[buffer].emplace_back(std::move(chunk));
+    }
+
+    android::base::LogSeverity old_log_severity_;
+    std::map<int, std::vector<uint64_t>> sequence_numbers_per_buffer_;
+    std::list<SerializedLogChunk> log_chunks_[LOG_ID_MAX];
+    uint64_t sequence_ = 1;
+};
+
+// 0: multiple chunks, with variable number of entries, with/without finishing writing
+// 1: 1 chunk with 1 log and finished writing
+// 2: 1 chunk with 1 log and not finished writing
+// 3: 1 chunk with 0 logs and not finished writing
+// 4: 1 chunk with 0 logs and finished writing (impossible, but SerializedFlushToState handles it)
+// 5-7: 0 chunks
+TEST_F(SerializedFlushToStateTest, smoke) {
+    auto lock = std::lock_guard{logd_lock};
+    AddChunkWithMessages(true, 0, {"1st", "2nd"});
+    AddChunkWithMessages(true, 1, {"3rd"});
+    AddChunkWithMessages(false, 0, {"4th"});
+    AddChunkWithMessages(true, 0, {"4th", "5th", "more", "even", "more", "go", "here"});
+    AddChunkWithMessages(false, 2, {"6th"});
+    AddChunkWithMessages(true, 0, {"7th"});
+    AddChunkWithMessages(false, 3, {});
+    AddChunkWithMessages(true, 4, {});
+
+    TestAllReading();
+}
+
+TEST_F(SerializedFlushToStateTest, random) {
+    auto lock = std::lock_guard{logd_lock};
+    srand(1);
+    for (int count = 0; count < 20; ++count) {
+        unsigned int num_messages = 1 + rand() % 15;
+        auto messages = std::vector<std::string>{num_messages, "same message"};
+
+        bool compress = rand() % 2;
+        int buf = rand() % LOG_ID_MAX;
+
+        AddChunkWithMessages(compress, buf, messages);
+    }
+
+    TestAllReading();
+}
+
+// Same start as smoke, but we selectively write logs to the buffers and ensure they're read.
+TEST_F(SerializedFlushToStateTest, future_writes) {
+    auto lock = std::lock_guard{logd_lock};
+    auto write_logs = [&](int loop_count) REQUIRES(logd_lock) {
+        switch (loop_count) {
+            case 0:
+                // Initial writes.
+                AddChunkWithMessages(true, 0, {"1st", "2nd"});
+                AddChunkWithMessages(true, 1, {"3rd"});
+                AddChunkWithMessages(false, 0, {"4th"});
+                AddChunkWithMessages(true, 0, {"4th", "5th", "more", "even", "more", "go", "here"});
+                AddChunkWithMessages(false, 2, {"6th"});
+                AddChunkWithMessages(true, 0, {"7th"});
+                AddChunkWithMessages(false, 3, {});
+                AddChunkWithMessages(true, 4, {});
+                break;
+            case 1:
+                // Smoke test, add a simple chunk.
+                AddChunkWithMessages(true, 0, {"1st", "2nd"});
+                break;
+            case 2:
+                // Add chunks to all but one of the logs.
+                AddChunkWithMessages(true, 0, {"1st", "2nd"});
+                AddChunkWithMessages(true, 1, {"1st", "2nd"});
+                AddChunkWithMessages(true, 2, {"1st", "2nd"});
+                AddChunkWithMessages(true, 3, {"1st", "2nd"});
+                AddChunkWithMessages(true, 4, {"1st", "2nd"});
+                AddChunkWithMessages(true, 5, {"1st", "2nd"});
+                AddChunkWithMessages(true, 6, {"1st", "2nd"});
+                break;
+            case 3:
+                // Finally add chunks to all logs.
+                AddChunkWithMessages(true, 0, {"1st", "2nd"});
+                AddChunkWithMessages(true, 1, {"1st", "2nd"});
+                AddChunkWithMessages(true, 2, {"1st", "2nd"});
+                AddChunkWithMessages(true, 3, {"1st", "2nd"});
+                AddChunkWithMessages(true, 4, {"1st", "2nd"});
+                AddChunkWithMessages(true, 5, {"1st", "2nd"});
+                AddChunkWithMessages(true, 6, {"1st", "2nd"});
+                AddChunkWithMessages(true, 7, {"1st", "2nd"});
+                break;
+            default:
+                return false;
+        }
+        return true;
+    };
+
+    TestAllReadingWithFutureMessages(write_logs);
+}
+
+TEST_F(SerializedFlushToStateTest, no_dangling_references) {
+    auto lock = std::lock_guard{logd_lock};
+    AddChunkWithMessages(true, 0, {"1st", "2nd"});
+    AddChunkWithMessages(true, 0, {"3rd", "4th"});
+
+    auto state = SerializedFlushToState{1, kLogMaskAll, log_chunks_};
+
+    ASSERT_EQ(log_chunks_[0].size(), 2U);
+    auto first_chunk = log_chunks_[0].begin();
+    auto second_chunk = std::next(first_chunk);
+
+    ASSERT_TRUE(state.HasUnreadLogs());
+    auto first_log = state.PopNextUnreadLog();
+    EXPECT_STREQ(first_log.entry->msg(), "1st");
+    EXPECT_EQ(first_chunk->reader_ref_count(), 1U);
+    EXPECT_EQ(second_chunk->reader_ref_count(), 0U);
+
+    ASSERT_TRUE(state.HasUnreadLogs());
+    auto second_log = state.PopNextUnreadLog();
+    EXPECT_STREQ(second_log.entry->msg(), "2nd");
+    EXPECT_EQ(first_chunk->reader_ref_count(), 1U);
+    EXPECT_EQ(second_chunk->reader_ref_count(), 0U);
+
+    ASSERT_TRUE(state.HasUnreadLogs());
+    auto third_log = state.PopNextUnreadLog();
+    EXPECT_STREQ(third_log.entry->msg(), "3rd");
+    EXPECT_EQ(first_chunk->reader_ref_count(), 0U);
+    EXPECT_EQ(second_chunk->reader_ref_count(), 1U);
+
+    ASSERT_TRUE(state.HasUnreadLogs());
+    auto fourth_log = state.PopNextUnreadLog();
+    EXPECT_STREQ(fourth_log.entry->msg(), "4th");
+    EXPECT_EQ(first_chunk->reader_ref_count(), 0U);
+    EXPECT_EQ(second_chunk->reader_ref_count(), 1U);
+
+    EXPECT_FALSE(state.HasUnreadLogs());
+}
+
+TEST(SerializedFlushToState, Prune) {
+    auto lock = std::lock_guard{logd_lock};
+    auto chunk = SerializedLogChunk{kChunkSize};
+    chunk.Log(1, log_time(), 0, 1, 1, "abc", 3);
+    chunk.Log(2, log_time(), 0, 1, 1, "abc", 3);
+    chunk.Log(3, log_time(), 0, 1, 1, "abc", 3);
+    chunk.FinishWriting();
+
+    std::list<SerializedLogChunk> log_chunks[LOG_ID_MAX];
+    log_chunks[LOG_ID_MAIN].emplace_back(std::move(chunk));
+
+    auto state = SerializedFlushToState{1, kLogMaskAll, log_chunks};
+    ASSERT_TRUE(state.HasUnreadLogs());
+
+    state.Prune(LOG_ID_MAIN, log_chunks[LOG_ID_MAIN].begin());
+}
diff --git a/logd/SerializedLogBuffer.cpp b/logd/SerializedLogBuffer.cpp
new file mode 100644
index 0000000..aa80864
--- /dev/null
+++ b/logd/SerializedLogBuffer.cpp
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2020 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 "SerializedLogBuffer.h"
+
+#include <sys/prctl.h>
+
+#include <limits>
+
+#include <android-base/logging.h>
+#include <android-base/scopeguard.h>
+
+#include "LogSize.h"
+#include "LogStatistics.h"
+#include "SerializedFlushToState.h"
+
+SerializedLogBuffer::SerializedLogBuffer(LogReaderList* reader_list, LogTags* tags,
+                                         LogStatistics* stats)
+    : reader_list_(reader_list), tags_(tags), stats_(stats) {
+    Init();
+}
+
+void SerializedLogBuffer::Init() {
+    log_id_for_each(i) {
+        if (!SetSize(i, GetBufferSizeFromProperties(i))) {
+            SetSize(i, kLogBufferMinSize);
+        }
+    }
+
+    // Release any sleeping reader threads to dump their current content.
+    auto lock = std::lock_guard{logd_lock};
+    for (const auto& reader_thread : reader_list_->reader_threads()) {
+        reader_thread->TriggerReader();
+    }
+}
+
+bool SerializedLogBuffer::ShouldLog(log_id_t log_id, const char* msg, uint16_t len) {
+    if (log_id == LOG_ID_SECURITY) {
+        return true;
+    }
+
+    int prio = ANDROID_LOG_INFO;
+    const char* tag = nullptr;
+    size_t tag_len = 0;
+    if (IsBinary(log_id)) {
+        int32_t tag_int = MsgToTag(msg, len);
+        tag = tags_->tagToName(tag_int);
+        if (tag) {
+            tag_len = strlen(tag);
+        }
+    } else {
+        prio = *msg;
+        tag = msg + 1;
+        tag_len = strnlen(tag, len - 1);
+    }
+    return __android_log_is_loggable_len(prio, tag, tag_len, ANDROID_LOG_VERBOSE);
+}
+
+int SerializedLogBuffer::Log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid,
+                             const char* msg, uint16_t len) {
+    if (log_id >= LOG_ID_MAX || len == 0) {
+        return -EINVAL;
+    }
+
+    if (len > LOGGER_ENTRY_MAX_PAYLOAD) {
+        len = LOGGER_ENTRY_MAX_PAYLOAD;
+    }
+
+    if (!ShouldLog(log_id, msg, len)) {
+        stats_->AddTotal(log_id, len);
+        return -EACCES;
+    }
+
+    auto sequence = sequence_.fetch_add(1, std::memory_order_relaxed);
+
+    auto lock = std::lock_guard{logd_lock};
+
+    if (logs_[log_id].empty()) {
+        logs_[log_id].push_back(SerializedLogChunk(max_size_[log_id] / 4));
+    }
+
+    auto total_len = sizeof(SerializedLogEntry) + len;
+    if (!logs_[log_id].back().CanLog(total_len)) {
+        logs_[log_id].back().FinishWriting();
+        logs_[log_id].push_back(SerializedLogChunk(max_size_[log_id] / 4));
+    }
+
+    auto entry = logs_[log_id].back().Log(sequence, realtime, uid, pid, tid, msg, len);
+    stats_->Add(entry->ToLogStatisticsElement(log_id));
+
+    MaybePrune(log_id);
+
+    reader_list_->NotifyNewLog(1 << log_id);
+    return len;
+}
+
+void SerializedLogBuffer::MaybePrune(log_id_t log_id) {
+    size_t total_size = GetSizeUsed(log_id);
+    size_t after_size = total_size;
+    if (total_size > max_size_[log_id]) {
+        Prune(log_id, total_size - max_size_[log_id], 0);
+        after_size = GetSizeUsed(log_id);
+        LOG(VERBOSE) << "Pruned Logs from log_id: " << log_id << ", previous size: " << total_size
+                     << " after size: " << after_size;
+    }
+
+    stats_->set_overhead(log_id, after_size);
+}
+
+void SerializedLogBuffer::RemoveChunkFromStats(log_id_t log_id, SerializedLogChunk& chunk) {
+    chunk.IncReaderRefCount();
+    int read_offset = 0;
+    while (read_offset < chunk.write_offset()) {
+        auto* entry = chunk.log_entry(read_offset);
+        stats_->Subtract(entry->ToLogStatisticsElement(log_id));
+        read_offset += entry->total_len();
+    }
+    chunk.DecReaderRefCount();
+}
+
+void SerializedLogBuffer::NotifyReadersOfPrune(
+        log_id_t log_id, const std::list<SerializedLogChunk>::iterator& chunk) {
+    for (const auto& reader_thread : reader_list_->reader_threads()) {
+        auto& state = reinterpret_cast<SerializedFlushToState&>(reader_thread->flush_to_state());
+        state.Prune(log_id, chunk);
+    }
+}
+
+void SerializedLogBuffer::Prune(log_id_t log_id, size_t bytes_to_free, uid_t uid) {
+    auto& log_buffer = logs_[log_id];
+    auto it = log_buffer.begin();
+    while (it != log_buffer.end()) {
+        for (const auto& reader_thread : reader_list_->reader_threads()) {
+            if (!reader_thread->IsWatching(log_id)) {
+                continue;
+            }
+
+            if (reader_thread->deadline().time_since_epoch().count() != 0) {
+                // Always wake up wrapped readers when pruning.  'Wrapped' readers are an
+                // optimization that allows the reader to wait until logs starting at a specified
+                // time stamp are about to be pruned.  This is error-prone however, since if that
+                // timestamp is about to be pruned, the reader is not likely to read the messages
+                // fast enough to not back-up logd.  Instead, we can achieve an nearly-as-efficient
+                // but not error-prune batching effect by waking the reader whenever any chunk is
+                // about to be pruned.
+                reader_thread->TriggerReader();
+            }
+
+            // Some readers may be still reading from this log chunk, log a warning that they are
+            // about to lose logs.
+            // TODO: We should forcefully disconnect the reader instead, such that the reader itself
+            // has an indication that they've lost logs.
+            if (reader_thread->start() <= it->highest_sequence_number()) {
+                LOG(WARNING) << "Skipping entries from slow reader, " << reader_thread->name()
+                             << ", from LogBuffer::Prune()";
+            }
+        }
+
+        // Increment ahead of time since we're going to erase this iterator from the list.
+        auto it_to_prune = it++;
+
+        // Readers may have a reference to the chunk to track their last read log_position.
+        // Notify them to delete the reference.
+        NotifyReadersOfPrune(log_id, it_to_prune);
+
+        if (uid != 0) {
+            // Reorder the log buffer to remove logs from the given UID.  If there are no logs left
+            // in the buffer after the removal, delete it.
+            if (it_to_prune->ClearUidLogs(uid, log_id, stats_)) {
+                log_buffer.erase(it_to_prune);
+            }
+        } else {
+            size_t buffer_size = it_to_prune->PruneSize();
+            RemoveChunkFromStats(log_id, *it_to_prune);
+            log_buffer.erase(it_to_prune);
+            if (buffer_size >= bytes_to_free) {
+                return;
+            }
+            bytes_to_free -= buffer_size;
+        }
+    }
+}
+
+std::unique_ptr<FlushToState> SerializedLogBuffer::CreateFlushToState(uint64_t start,
+                                                                      LogMask log_mask) {
+    return std::make_unique<SerializedFlushToState>(start, log_mask, logs_);
+}
+
+bool SerializedLogBuffer::FlushTo(
+        LogWriter* writer, FlushToState& abstract_state,
+        const std::function<FilterResult(log_id_t log_id, pid_t pid, uint64_t sequence,
+                                         log_time realtime)>& filter) {
+    auto& state = reinterpret_cast<SerializedFlushToState&>(abstract_state);
+
+    while (state.HasUnreadLogs()) {
+        LogWithId top = state.PopNextUnreadLog();
+        auto* entry = top.entry;
+        auto log_id = top.log_id;
+
+        if (entry->sequence() < state.start()) {
+            continue;
+        }
+        state.set_start(entry->sequence());
+
+        if (!writer->privileged() && entry->uid() != writer->uid()) {
+            continue;
+        }
+
+        if (filter) {
+            auto ret = filter(log_id, entry->pid(), entry->sequence(), entry->realtime());
+            if (ret == FilterResult::kSkip) {
+                continue;
+            }
+            if (ret == FilterResult::kStop) {
+                break;
+            }
+        }
+
+        // We copy the log entry such that we can flush it without the lock.  We never block pruning
+        // waiting for this Flush() to complete.
+        constexpr size_t kMaxEntrySize = sizeof(*entry) + LOGGER_ENTRY_MAX_PAYLOAD + 1;
+        unsigned char entry_copy[kMaxEntrySize] __attribute__((uninitialized));
+        CHECK_LT(entry->msg_len(), LOGGER_ENTRY_MAX_PAYLOAD + 1);
+        memcpy(entry_copy, entry, sizeof(*entry) + entry->msg_len());
+        logd_lock.unlock();
+
+        if (!reinterpret_cast<SerializedLogEntry*>(entry_copy)->Flush(writer, log_id)) {
+            logd_lock.lock();
+            return false;
+        }
+
+        logd_lock.lock();
+    }
+
+    state.set_start(state.start() + 1);
+    return true;
+}
+
+bool SerializedLogBuffer::Clear(log_id_t id, uid_t uid) {
+    auto lock = std::lock_guard{logd_lock};
+    Prune(id, ULONG_MAX, uid);
+
+    // Clearing SerializedLogBuffer never waits for readers and therefore is always successful.
+    return true;
+}
+
+size_t SerializedLogBuffer::GetSizeUsed(log_id_t id) {
+    size_t total_size = 0;
+    for (const auto& chunk : logs_[id]) {
+        total_size += chunk.PruneSize();
+    }
+    return total_size;
+}
+
+size_t SerializedLogBuffer::GetSize(log_id_t id) {
+    auto lock = std::lock_guard{logd_lock};
+    return max_size_[id];
+}
+
+// New SerializedLogChunk objects will be allocated according to the new size, but older one are
+// unchanged.  MaybePrune() is called on the log buffer to reduce it to an appropriate size if the
+// new size is lower.
+bool SerializedLogBuffer::SetSize(log_id_t id, size_t size) {
+    // Reasonable limits ...
+    if (!IsValidBufferSize(size)) {
+        return false;
+    }
+
+    auto lock = std::lock_guard{logd_lock};
+    max_size_[id] = size;
+
+    MaybePrune(id);
+
+    return true;
+}
diff --git a/logd/SerializedLogBuffer.h b/logd/SerializedLogBuffer.h
new file mode 100644
index 0000000..239a81e
--- /dev/null
+++ b/logd/SerializedLogBuffer.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <bitset>
+#include <list>
+#include <mutex>
+#include <queue>
+#include <thread>
+#include <vector>
+
+#include <android-base/thread_annotations.h>
+
+#include "LogBuffer.h"
+#include "LogReaderList.h"
+#include "LogStatistics.h"
+#include "LogTags.h"
+#include "LogdLock.h"
+#include "SerializedLogChunk.h"
+#include "SerializedLogEntry.h"
+
+class SerializedLogBuffer final : public LogBuffer {
+  public:
+    SerializedLogBuffer(LogReaderList* reader_list, LogTags* tags, LogStatistics* stats);
+    void Init() override;
+
+    int Log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid, const char* msg,
+            uint16_t len) override;
+    std::unique_ptr<FlushToState> CreateFlushToState(uint64_t start, LogMask log_mask)
+            REQUIRES(logd_lock) override;
+    bool FlushTo(LogWriter* writer, FlushToState& state,
+                 const std::function<FilterResult(log_id_t log_id, pid_t pid, uint64_t sequence,
+                                                  log_time realtime)>& filter)
+            REQUIRES(logd_lock) override;
+
+    bool Clear(log_id_t id, uid_t uid) override;
+    size_t GetSize(log_id_t id) override;
+    bool SetSize(log_id_t id, size_t size) override;
+
+    uint64_t sequence() const override { return sequence_.load(std::memory_order_relaxed); }
+
+  private:
+    bool ShouldLog(log_id_t log_id, const char* msg, uint16_t len);
+    void MaybePrune(log_id_t log_id) REQUIRES(logd_lock);
+    void Prune(log_id_t log_id, size_t bytes_to_free, uid_t uid) REQUIRES(logd_lock);
+    void NotifyReadersOfPrune(log_id_t log_id, const std::list<SerializedLogChunk>::iterator& chunk)
+            REQUIRES(logd_lock);
+    void RemoveChunkFromStats(log_id_t log_id, SerializedLogChunk& chunk);
+    size_t GetSizeUsed(log_id_t id) REQUIRES(logd_lock);
+
+    LogReaderList* reader_list_;
+    LogTags* tags_;
+    LogStatistics* stats_;
+
+    size_t max_size_[LOG_ID_MAX] GUARDED_BY(logd_lock) = {};
+    std::list<SerializedLogChunk> logs_[LOG_ID_MAX] GUARDED_BY(logd_lock);
+
+    std::atomic<uint64_t> sequence_ = 1;
+};
diff --git a/logd/SerializedLogChunk.cpp b/logd/SerializedLogChunk.cpp
new file mode 100644
index 0000000..1ffe7a8
--- /dev/null
+++ b/logd/SerializedLogChunk.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 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 "SerializedLogChunk.h"
+
+#include <android-base/logging.h>
+
+#include "CompressionEngine.h"
+
+SerializedLogChunk::~SerializedLogChunk() {
+    CHECK_EQ(reader_ref_count_, 0U);
+}
+
+void SerializedLogChunk::Compress() {
+    CHECK_EQ(compressed_log_.size(), 0U);
+    CompressionEngine::GetInstance().Compress(contents_, write_offset_, compressed_log_);
+    LOG(VERBOSE) << "Compressed Log, buffer max size: " << contents_.size()
+                 << " size used: " << write_offset_
+                 << " compressed size: " << compressed_log_.size();
+}
+
+// TODO: Develop a better reference counting strategy to guard against the case where the writer is
+// much faster than the reader, and we needlessly compess / decompress the logs.
+void SerializedLogChunk::IncReaderRefCount() {
+    if (++reader_ref_count_ != 1 || writer_active_) {
+        return;
+    }
+    contents_.Resize(write_offset_);
+    CompressionEngine::GetInstance().Decompress(compressed_log_, contents_);
+}
+
+void SerializedLogChunk::DecReaderRefCount() {
+    CHECK_NE(reader_ref_count_, 0U);
+    if (--reader_ref_count_ != 0) {
+        return;
+    }
+    if (!writer_active_) {
+        contents_.Resize(0);
+    }
+}
+
+bool SerializedLogChunk::ClearUidLogs(uid_t uid, log_id_t log_id, LogStatistics* stats) {
+    CHECK_EQ(reader_ref_count_, 0U);
+    if (write_offset_ == 0) {
+        return true;
+    }
+
+    IncReaderRefCount();
+
+    int read_offset = 0;
+    int new_write_offset = 0;
+    while (read_offset < write_offset_) {
+        const auto* entry = log_entry(read_offset);
+        if (entry->uid() == uid) {
+            read_offset += entry->total_len();
+            if (stats != nullptr) {
+                stats->Subtract(entry->ToLogStatisticsElement(log_id));
+            }
+            continue;
+        }
+        size_t entry_total_len = entry->total_len();
+        if (read_offset != new_write_offset) {
+            memmove(contents_.data() + new_write_offset, contents_.data() + read_offset,
+                    entry_total_len);
+        }
+        read_offset += entry_total_len;
+        new_write_offset += entry_total_len;
+    }
+
+    if (new_write_offset == 0) {
+        DecReaderRefCount();
+        return true;
+    }
+
+    // Clear the old compressed logs and set write_offset_ appropriately to compress the new
+    // partially cleared log.
+    if (new_write_offset != write_offset_) {
+        write_offset_ = new_write_offset;
+        if (!writer_active_) {
+            compressed_log_.Resize(0);
+            Compress();
+        }
+    }
+
+    DecReaderRefCount();
+
+    return false;
+}
+
+bool SerializedLogChunk::CanLog(size_t len) {
+    return write_offset_ + len <= contents_.size();
+}
+
+SerializedLogEntry* SerializedLogChunk::Log(uint64_t sequence, log_time realtime, uid_t uid,
+                                            pid_t pid, pid_t tid, const char* msg, uint16_t len) {
+    auto new_log_address = contents_.data() + write_offset_;
+    auto* entry = new (new_log_address) SerializedLogEntry(uid, pid, tid, sequence, realtime, len);
+    memcpy(entry->msg(), msg, len);
+    write_offset_ += entry->total_len();
+    highest_sequence_number_ = sequence;
+    return entry;
+}
diff --git a/logd/SerializedLogChunk.h b/logd/SerializedLogChunk.h
new file mode 100644
index 0000000..645433d
--- /dev/null
+++ b/logd/SerializedLogChunk.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <android-base/logging.h>
+
+#include "LogWriter.h"
+#include "SerializedData.h"
+#include "SerializedLogEntry.h"
+
+class SerializedLogChunk {
+  public:
+    explicit SerializedLogChunk(size_t size) : contents_(size) {}
+    SerializedLogChunk(SerializedLogChunk&& other) noexcept = default;
+    ~SerializedLogChunk();
+
+    void Compress();
+    void IncReaderRefCount();
+    void DecReaderRefCount();
+
+    // Must have no readers referencing this.  Return true if there are no logs left in this chunk.
+    bool ClearUidLogs(uid_t uid, log_id_t log_id, LogStatistics* stats);
+
+    bool CanLog(size_t len);
+    SerializedLogEntry* Log(uint64_t sequence, log_time realtime, uid_t uid, pid_t pid, pid_t tid,
+                            const char* msg, uint16_t len);
+
+    // If this buffer has been compressed, we only consider its compressed size when accounting for
+    // memory consumption for pruning.  This is since the uncompressed log is only by used by
+    // readers, and thus not a representation of how much these logs cost to keep in memory.
+    size_t PruneSize() const {
+        return sizeof(*this) + (compressed_log_.size() ?: contents_.size());
+    }
+
+    void FinishWriting() {
+        writer_active_ = false;
+        Compress();
+        if (reader_ref_count_ == 0) {
+            contents_.Resize(0);
+        }
+    }
+
+    const SerializedLogEntry* log_entry(int offset) const {
+        CHECK(writer_active_ || reader_ref_count_ > 0);
+        return reinterpret_cast<const SerializedLogEntry*>(data() + offset);
+    }
+    const uint8_t* data() const { return contents_.data(); }
+    int write_offset() const { return write_offset_; }
+    uint64_t highest_sequence_number() const { return highest_sequence_number_; }
+
+    // Exposed for testing
+    uint32_t reader_ref_count() const { return reader_ref_count_; }
+
+  private:
+    // The decompressed contents of this log buffer.  Deallocated when the ref_count reaches 0 and
+    // writer_active_ is false.
+    SerializedData contents_;
+    int write_offset_ = 0;
+    uint32_t reader_ref_count_ = 0;
+    bool writer_active_ = true;
+    uint64_t highest_sequence_number_ = 1;
+    SerializedData compressed_log_;
+};
diff --git a/logd/SerializedLogChunkTest.cpp b/logd/SerializedLogChunkTest.cpp
new file mode 100644
index 0000000..862c3fe
--- /dev/null
+++ b/logd/SerializedLogChunkTest.cpp
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2020 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 "SerializedLogChunk.h"
+
+#include <limits>
+
+#include <android-base/stringprintf.h>
+#include <android/log.h>
+#include <gtest/gtest.h>
+
+using android::base::StringPrintf;
+
+TEST(SerializedLogChunk, smoke) {
+    size_t chunk_size = 10 * 4096;
+    auto chunk = SerializedLogChunk{chunk_size};
+    EXPECT_EQ(chunk_size + sizeof(SerializedLogChunk), chunk.PruneSize());
+
+    static const char log_message[] = "log message";
+    size_t expected_total_len = sizeof(SerializedLogEntry) + sizeof(log_message);
+    ASSERT_TRUE(chunk.CanLog(expected_total_len));
+    EXPECT_TRUE(chunk.CanLog(chunk_size));
+    EXPECT_FALSE(chunk.CanLog(chunk_size + 1));
+
+    log_time time(CLOCK_REALTIME);
+    auto* entry = chunk.Log(1234, time, 0, 1, 2, log_message, sizeof(log_message));
+    ASSERT_NE(nullptr, entry);
+
+    EXPECT_EQ(1234U, entry->sequence());
+    EXPECT_EQ(time, entry->realtime());
+    EXPECT_EQ(0U, entry->uid());
+    EXPECT_EQ(1, entry->pid());
+    EXPECT_EQ(2, entry->tid());
+    EXPECT_EQ(sizeof(log_message), entry->msg_len());
+    EXPECT_STREQ(log_message, entry->msg());
+    EXPECT_EQ(expected_total_len, entry->total_len());
+
+    EXPECT_FALSE(chunk.CanLog(chunk_size));
+    EXPECT_EQ(static_cast<int>(expected_total_len), chunk.write_offset());
+    EXPECT_EQ(1234U, chunk.highest_sequence_number());
+}
+
+TEST(SerializedLogChunk, fill_log_exactly) {
+    static const char log_message[] = "this is a log message";
+    size_t individual_message_size = sizeof(SerializedLogEntry) + sizeof(log_message);
+    size_t chunk_size = individual_message_size * 3;
+    auto chunk = SerializedLogChunk{chunk_size};
+    EXPECT_EQ(chunk_size + sizeof(SerializedLogChunk), chunk.PruneSize());
+
+    ASSERT_TRUE(chunk.CanLog(individual_message_size));
+    EXPECT_NE(nullptr, chunk.Log(1, log_time(), 1000, 1, 1, log_message, sizeof(log_message)));
+
+    ASSERT_TRUE(chunk.CanLog(individual_message_size));
+    EXPECT_NE(nullptr, chunk.Log(2, log_time(), 1000, 2, 1, log_message, sizeof(log_message)));
+
+    ASSERT_TRUE(chunk.CanLog(individual_message_size));
+    EXPECT_NE(nullptr, chunk.Log(3, log_time(), 1000, 3, 1, log_message, sizeof(log_message)));
+
+    EXPECT_FALSE(chunk.CanLog(1));
+}
+
+TEST(SerializedLogChunk, three_logs) {
+    size_t chunk_size = 10 * 4096;
+    auto chunk = SerializedLogChunk{chunk_size};
+
+    chunk.Log(2, log_time(0x1234, 0x5678), 0x111, 0x222, 0x333, "initial message",
+              strlen("initial message"));
+    chunk.Log(3, log_time(0x2345, 0x6789), 0x444, 0x555, 0x666, "second message",
+              strlen("second message"));
+    auto uint64_t_max = std::numeric_limits<uint64_t>::max();
+    auto uint32_t_max = std::numeric_limits<uint32_t>::max();
+    chunk.Log(uint64_t_max, log_time(uint32_t_max, uint32_t_max), uint32_t_max, uint32_t_max,
+              uint32_t_max, "last message", strlen("last message"));
+
+    static const char expected_buffer_data[] =
+            "\x11\x01\x00\x00\x22\x02\x00\x00\x33\x03\x00\x00"  // UID PID TID
+            "\x02\x00\x00\x00\x00\x00\x00\x00"                  // Sequence
+            "\x34\x12\x00\x00\x78\x56\x00\x00"                  // Timestamp
+            "\x0F\x00initial message"                           // msg_len + message
+            "\x44\x04\x00\x00\x55\x05\x00\x00\x66\x06\x00\x00"  // UID PID TID
+            "\x03\x00\x00\x00\x00\x00\x00\x00"                  // Sequence
+            "\x45\x23\x00\x00\x89\x67\x00\x00"                  // Timestamp
+            "\x0E\x00second message"                            // msg_len + message
+            "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"  // UID PID TID
+            "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"                  // Sequence
+            "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"                  // Timestamp
+            "\x0C\x00last message";                             // msg_len + message
+
+    for (size_t i = 0; i < sizeof(expected_buffer_data); ++i) {
+        EXPECT_EQ(static_cast<uint8_t>(expected_buffer_data[i]), chunk.data()[i])
+                << "position: " << i;
+    }
+}
+
+// Check that the CHECK() in DecReaderRefCount() if the ref count goes bad is caught.
+TEST(SerializedLogChunk, catch_DecCompressedRef_CHECK) {
+    size_t chunk_size = 10 * 4096;
+    auto chunk = SerializedLogChunk{chunk_size};
+    EXPECT_DEATH({ chunk.DecReaderRefCount(); }, "");
+}
+
+// Check that the CHECK() in ClearUidLogs() if the ref count is greater than 0 is caught.
+TEST(SerializedLogChunk, catch_ClearUidLogs_CHECK) {
+    size_t chunk_size = 10 * 4096;
+    auto chunk = SerializedLogChunk{chunk_size};
+    chunk.IncReaderRefCount();
+    EXPECT_DEATH({ chunk.ClearUidLogs(1000, LOG_ID_MAIN, nullptr); }, "");
+    chunk.DecReaderRefCount();
+}
+
+class UidClearTest : public testing::TestWithParam<bool> {
+  protected:
+    template <typename Write, typename Check>
+    void Test(const Write& write, const Check& check, bool expected_result) {
+        write(chunk_);
+
+        bool finish_writing = GetParam();
+        if (finish_writing) {
+            chunk_.FinishWriting();
+        }
+        EXPECT_EQ(expected_result, chunk_.ClearUidLogs(kUidToClear, LOG_ID_MAIN, nullptr));
+        if (finish_writing) {
+            chunk_.IncReaderRefCount();
+        }
+
+        check(chunk_);
+
+        if (finish_writing) {
+            chunk_.DecReaderRefCount();
+        }
+    }
+
+    static constexpr size_t kChunkSize = 10 * 4096;
+    static constexpr uid_t kUidToClear = 1000;
+    static constexpr uid_t kOtherUid = 1234;
+
+    SerializedLogChunk chunk_{kChunkSize};
+};
+
+// Test that ClearUidLogs() is a no-op if there are no logs of that UID in the buffer.
+TEST_P(UidClearTest, no_logs_in_chunk) {
+    auto write = [](SerializedLogChunk&) {};
+    auto check = [](SerializedLogChunk&) {};
+
+    Test(write, check, true);
+}
+
+// Test that ClearUidLogs() is a no-op if there are no logs of that UID in the buffer.
+TEST_P(UidClearTest, no_logs_from_uid) {
+    static const char msg[] = "this is a log message";
+    auto write = [](SerializedLogChunk& chunk) {
+        chunk.Log(1, log_time(), kOtherUid, 1, 2, msg, sizeof(msg));
+    };
+
+    auto check = [](SerializedLogChunk& chunk) {
+        auto* entry = chunk.log_entry(0);
+        EXPECT_STREQ(msg, entry->msg());
+    };
+
+    Test(write, check, false);
+}
+
+// Test that ClearUidLogs() returns true if all logs in a given buffer correspond to the given UID.
+TEST_P(UidClearTest, all_single) {
+    static const char msg[] = "this is a log message";
+    auto write = [](SerializedLogChunk& chunk) {
+        chunk.Log(1, log_time(), kUidToClear, 1, 2, msg, sizeof(msg));
+    };
+    auto check = [](SerializedLogChunk&) {};
+
+    Test(write, check, true);
+}
+
+// Test that ClearUidLogs() returns true if all logs in a given buffer correspond to the given UID.
+TEST_P(UidClearTest, all_multiple) {
+    static const char msg[] = "this is a log message";
+    auto write = [](SerializedLogChunk& chunk) {
+        chunk.Log(2, log_time(), kUidToClear, 1, 2, msg, sizeof(msg));
+        chunk.Log(3, log_time(), kUidToClear, 1, 2, msg, sizeof(msg));
+        chunk.Log(4, log_time(), kUidToClear, 1, 2, msg, sizeof(msg));
+    };
+    auto check = [](SerializedLogChunk&) {};
+
+    Test(write, check, true);
+}
+
+static std::string MakePrintable(const uint8_t* in, size_t length) {
+    std::string result;
+    for (size_t i = 0; i < length; ++i) {
+        uint8_t c = in[i];
+        if (isprint(c)) {
+            result.push_back(c);
+        } else {
+            result.append(StringPrintf("\\%02x", static_cast<int>(c) & 0xFF));
+        }
+    }
+    return result;
+}
+
+// This test clears UID logs at the beginning and end of the buffer, as well as two back to back
+// logs in the interior.
+TEST_P(UidClearTest, clear_beginning_and_end) {
+    static const char msg1[] = "this is a log message";
+    static const char msg2[] = "non-cleared message";
+    static const char msg3[] = "back to back cleared messages";
+    static const char msg4[] = "second in a row gone";
+    static const char msg5[] = "but we save this one";
+    static const char msg6[] = "and this 1!";
+    static const char msg7[] = "the last one goes too";
+    auto write = [](SerializedLogChunk& chunk) {
+        ASSERT_NE(nullptr, chunk.Log(1, log_time(), kUidToClear, 1, 2, msg1, sizeof(msg1)));
+        ASSERT_NE(nullptr, chunk.Log(2, log_time(), kOtherUid, 1, 2, msg2, sizeof(msg2)));
+        ASSERT_NE(nullptr, chunk.Log(3, log_time(), kUidToClear, 1, 2, msg3, sizeof(msg3)));
+        ASSERT_NE(nullptr, chunk.Log(4, log_time(), kUidToClear, 1, 2, msg4, sizeof(msg4)));
+        ASSERT_NE(nullptr, chunk.Log(5, log_time(), kOtherUid, 1, 2, msg5, sizeof(msg5)));
+        ASSERT_NE(nullptr, chunk.Log(6, log_time(), kOtherUid, 1, 2, msg6, sizeof(msg6)));
+        ASSERT_NE(nullptr, chunk.Log(7, log_time(), kUidToClear, 1, 2, msg7, sizeof(msg7)));
+    };
+
+    auto check = [](SerializedLogChunk& chunk) {
+        size_t read_offset = 0;
+        auto* entry = chunk.log_entry(read_offset);
+        EXPECT_STREQ(msg2, entry->msg());
+        read_offset += entry->total_len();
+
+        entry = chunk.log_entry(read_offset);
+        EXPECT_STREQ(msg5, entry->msg());
+        read_offset += entry->total_len();
+
+        entry = chunk.log_entry(read_offset);
+        EXPECT_STREQ(msg6, entry->msg()) << MakePrintable(chunk.data(), chunk.write_offset());
+        read_offset += entry->total_len();
+
+        EXPECT_EQ(static_cast<int>(read_offset), chunk.write_offset());
+    };
+    Test(write, check, false);
+}
+
+// This tests the opposite case of beginning_and_end, in which we don't clear the beginning or end
+// logs.  There is a single log pruned in the middle instead of back to back logs.
+TEST_P(UidClearTest, save_beginning_and_end) {
+    static const char msg1[] = "saved first message";
+    static const char msg2[] = "cleared interior message";
+    static const char msg3[] = "last message stays";
+    auto write = [](SerializedLogChunk& chunk) {
+        ASSERT_NE(nullptr, chunk.Log(1, log_time(), kOtherUid, 1, 2, msg1, sizeof(msg1)));
+        ASSERT_NE(nullptr, chunk.Log(2, log_time(), kUidToClear, 1, 2, msg2, sizeof(msg2)));
+        ASSERT_NE(nullptr, chunk.Log(3, log_time(), kOtherUid, 1, 2, msg3, sizeof(msg3)));
+    };
+
+    auto check = [](SerializedLogChunk& chunk) {
+        size_t read_offset = 0;
+        auto* entry = chunk.log_entry(read_offset);
+        EXPECT_STREQ(msg1, entry->msg());
+        read_offset += entry->total_len();
+
+        entry = chunk.log_entry(read_offset);
+        EXPECT_STREQ(msg3, entry->msg());
+        read_offset += entry->total_len();
+
+        EXPECT_EQ(static_cast<int>(read_offset), chunk.write_offset());
+    };
+    Test(write, check, false);
+}
+
+INSTANTIATE_TEST_CASE_P(UidClearTests, UidClearTest, testing::Values(true, false));
+
+// b/166187079: ClearUidLogs() should not compress the log if writer_active_ is true
+TEST(SerializedLogChunk, ClearUidLogs_then_FinishWriting) {
+    static constexpr size_t kChunkSize = 10 * 4096;
+    static constexpr uid_t kUidToClear = 1000;
+    static constexpr uid_t kOtherUid = 1234;
+
+    SerializedLogChunk chunk{kChunkSize};
+    static const char msg1[] = "saved first message";
+    static const char msg2[] = "cleared interior message";
+    static const char msg3[] = "last message stays";
+    ASSERT_NE(nullptr, chunk.Log(1, log_time(), kOtherUid, 1, 2, msg1, sizeof(msg1)));
+    ASSERT_NE(nullptr, chunk.Log(2, log_time(), kUidToClear, 1, 2, msg2, sizeof(msg2)));
+    ASSERT_NE(nullptr, chunk.Log(3, log_time(), kOtherUid, 1, 2, msg3, sizeof(msg3)));
+
+    chunk.ClearUidLogs(kUidToClear, LOG_ID_MAIN, nullptr);
+
+    ASSERT_NE(nullptr, chunk.Log(4, log_time(), kOtherUid, 1, 2, msg3, sizeof(msg3)));
+
+    chunk.FinishWriting();
+    // The next line would violate a CHECK() during decompression with the faulty code.
+    chunk.IncReaderRefCount();
+    chunk.DecReaderRefCount();
+}
diff --git a/logd/SerializedLogEntry.h b/logd/SerializedLogEntry.h
new file mode 100644
index 0000000..2f2c244
--- /dev/null
+++ b/logd/SerializedLogEntry.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include <log/log.h>
+#include <log/log_read.h>
+
+#include "LogStatistics.h"
+#include "LogWriter.h"
+
+// These structs are packed into a single chunk of memory for each log type within a
+// SerializedLogChunk object.  Their message is contained immediately at the end of the struct.  The
+// address of the next log in the buffer is *this + sizeof(SerializedLogEntry) + msg_len_.  If that
+// value would overflow the chunk of memory associated with the SerializedLogChunk object, then a
+// new SerializedLogChunk must be allocated to contain the next SerializedLogEntry.
+class __attribute__((packed)) SerializedLogEntry {
+  public:
+    SerializedLogEntry(uid_t uid, pid_t pid, pid_t tid, uint64_t sequence, log_time realtime,
+                       uint16_t len)
+        : uid_(uid),
+          pid_(pid),
+          tid_(tid),
+          sequence_(sequence),
+          realtime_(realtime),
+          msg_len_(len) {}
+    SerializedLogEntry(const SerializedLogEntry& elem) = delete;
+    SerializedLogEntry& operator=(const SerializedLogEntry& elem) = delete;
+    ~SerializedLogEntry() {
+        // Never place anything in this destructor.  This class is in place constructed and never
+        // destructed.
+    }
+
+    LogStatisticsElement ToLogStatisticsElement(log_id_t log_id) const {
+        return LogStatisticsElement{
+                .uid = uid(),
+                .pid = pid(),
+                .tid = tid(),
+                .tag = IsBinary(log_id) ? MsgToTag(msg(), msg_len()) : 0,
+                .realtime = realtime(),
+                .msg = msg(),
+                .msg_len = msg_len(),
+                .dropped_count = 0,
+                .log_id = log_id,
+                .total_len = total_len(),
+        };
+    }
+
+    bool Flush(LogWriter* writer, log_id_t log_id) const {
+        struct logger_entry entry = {};
+
+        entry.hdr_size = sizeof(struct logger_entry);
+        entry.lid = log_id;
+        entry.pid = pid();
+        entry.tid = tid();
+        entry.uid = uid();
+        entry.sec = realtime().tv_sec;
+        entry.nsec = realtime().tv_nsec;
+        entry.len = msg_len();
+
+        return writer->Write(entry, msg());
+    }
+
+    uid_t uid() const { return uid_; }
+    pid_t pid() const { return pid_; }
+    pid_t tid() const { return tid_; }
+    uint16_t msg_len() const { return msg_len_; }
+    uint64_t sequence() const { return sequence_; }
+    log_time realtime() const { return realtime_; }
+
+    char* msg() { return reinterpret_cast<char*>(this) + sizeof(*this); }
+    const char* msg() const { return reinterpret_cast<const char*>(this) + sizeof(*this); }
+    uint16_t total_len() const { return sizeof(*this) + msg_len_; }
+
+  private:
+    const uint32_t uid_;
+    const uint32_t pid_;
+    const uint32_t tid_;
+    const uint64_t sequence_;
+    const log_time realtime_;
+    const uint16_t msg_len_;
+};
diff --git a/logd/SimpleLogBuffer.cpp b/logd/SimpleLogBuffer.cpp
new file mode 100644
index 0000000..55b31f8
--- /dev/null
+++ b/logd/SimpleLogBuffer.cpp
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2020 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 "SimpleLogBuffer.h"
+
+#include <android-base/logging.h>
+
+#include "LogBufferElement.h"
+#include "LogSize.h"
+
+SimpleLogBuffer::SimpleLogBuffer(LogReaderList* reader_list, LogTags* tags, LogStatistics* stats)
+    : reader_list_(reader_list), tags_(tags), stats_(stats) {
+    Init();
+}
+
+SimpleLogBuffer::~SimpleLogBuffer() {}
+
+void SimpleLogBuffer::Init() {
+    log_id_for_each(i) {
+        if (!SetSize(i, GetBufferSizeFromProperties(i))) {
+            SetSize(i, kLogBufferMinSize);
+        }
+    }
+
+    // Release any sleeping reader threads to dump their current content.
+    auto lock = std::lock_guard{logd_lock};
+    for (const auto& reader_thread : reader_list_->reader_threads()) {
+        reader_thread->TriggerReader();
+    }
+}
+
+std::list<LogBufferElement>::iterator SimpleLogBuffer::GetOldest(log_id_t log_id) {
+    auto it = logs().begin();
+    if (oldest_[log_id]) {
+        it = *oldest_[log_id];
+    }
+    while (it != logs().end() && it->log_id() != log_id) {
+        it++;
+    }
+    if (it != logs().end()) {
+        oldest_[log_id] = it;
+    }
+    return it;
+}
+
+bool SimpleLogBuffer::ShouldLog(log_id_t log_id, const char* msg, uint16_t len) {
+    if (log_id == LOG_ID_SECURITY) {
+        return true;
+    }
+
+    int prio = ANDROID_LOG_INFO;
+    const char* tag = nullptr;
+    size_t tag_len = 0;
+    if (IsBinary(log_id)) {
+        int32_t numeric_tag = MsgToTag(msg, len);
+        tag = tags_->tagToName(numeric_tag);
+        if (tag) {
+            tag_len = strlen(tag);
+        }
+    } else {
+        prio = *msg;
+        tag = msg + 1;
+        tag_len = strnlen(tag, len - 1);
+    }
+    return __android_log_is_loggable_len(prio, tag, tag_len, ANDROID_LOG_VERBOSE);
+}
+
+int SimpleLogBuffer::Log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid,
+                         const char* msg, uint16_t len) {
+    if (log_id >= LOG_ID_MAX) {
+        return -EINVAL;
+    }
+
+    if (!ShouldLog(log_id, msg, len)) {
+        // Log traffic received to total
+        stats_->AddTotal(log_id, len);
+        return -EACCES;
+    }
+
+    // Slip the time by 1 nsec if the incoming lands on xxxxxx000 ns.
+    // This prevents any chance that an outside source can request an
+    // exact entry with time specified in ms or us precision.
+    if ((realtime.tv_nsec % 1000) == 0) ++realtime.tv_nsec;
+
+    auto lock = std::lock_guard{logd_lock};
+    auto sequence = sequence_.fetch_add(1, std::memory_order_relaxed);
+    LogInternal(LogBufferElement(log_id, realtime, uid, pid, tid, sequence, msg, len));
+    return len;
+}
+
+void SimpleLogBuffer::LogInternal(LogBufferElement&& elem) {
+    log_id_t log_id = elem.log_id();
+
+    logs_.emplace_back(std::move(elem));
+    stats_->Add(logs_.back().ToLogStatisticsElement());
+    MaybePrune(log_id);
+    reader_list_->NotifyNewLog(1 << log_id);
+}
+
+// These extra parameters are only required for chatty, but since they're a no-op for
+// SimpleLogBuffer, it's easier to include them here, then to duplicate FlushTo() for
+// ChattyLogBuffer.
+class ChattyFlushToState : public FlushToState {
+  public:
+    ChattyFlushToState(uint64_t start, LogMask log_mask) : FlushToState(start, log_mask) {}
+
+    pid_t* last_tid() { return last_tid_; }
+
+    bool drop_chatty_messages() const { return drop_chatty_messages_; }
+    void set_drop_chatty_messages(bool value) { drop_chatty_messages_ = value; }
+
+  private:
+    pid_t last_tid_[LOG_ID_MAX] = {};
+    bool drop_chatty_messages_ = true;
+};
+
+std::unique_ptr<FlushToState> SimpleLogBuffer::CreateFlushToState(uint64_t start,
+                                                                  LogMask log_mask) {
+    return std::make_unique<ChattyFlushToState>(start, log_mask);
+}
+
+bool SimpleLogBuffer::FlushTo(
+        LogWriter* writer, FlushToState& abstract_state,
+        const std::function<FilterResult(log_id_t log_id, pid_t pid, uint64_t sequence,
+                                         log_time realtime)>& filter) {
+    auto& state = reinterpret_cast<ChattyFlushToState&>(abstract_state);
+
+    std::list<LogBufferElement>::iterator it;
+    if (state.start() <= 1) {
+        // client wants to start from the beginning
+        it = logs_.begin();
+    } else {
+        // Client wants to start from some specified time. Chances are
+        // we are better off starting from the end of the time sorted list.
+        for (it = logs_.end(); it != logs_.begin();
+             /* do nothing */) {
+            --it;
+            if (it->sequence() == state.start()) {
+                break;
+            } else if (it->sequence() < state.start()) {
+                it++;
+                break;
+            }
+        }
+    }
+
+    for (; it != logs_.end(); ++it) {
+        LogBufferElement& element = *it;
+
+        state.set_start(element.sequence());
+
+        if (!writer->privileged() && element.uid() != writer->uid()) {
+            continue;
+        }
+
+        if (((1 << element.log_id()) & state.log_mask()) == 0) {
+            continue;
+        }
+
+        if (filter) {
+            FilterResult ret =
+                    filter(element.log_id(), element.pid(), element.sequence(), element.realtime());
+            if (ret == FilterResult::kSkip) {
+                continue;
+            }
+            if (ret == FilterResult::kStop) {
+                break;
+            }
+        }
+
+        // drop_chatty_messages is initialized to true, so if the first message that we attempt to
+        // flush is a chatty message, we drop it.  Once we see a non-chatty message it gets set to
+        // false to let further chatty messages be printed.
+        if (state.drop_chatty_messages()) {
+            if (element.dropped_count() != 0) {
+                continue;
+            }
+            state.set_drop_chatty_messages(false);
+        }
+
+        bool same_tid = state.last_tid()[element.log_id()] == element.tid();
+        // Dropped (chatty) immediately following a valid log from the same source in the same log
+        // buffer indicates we have a multiple identical squash.  chatty that differs source is due
+        // to spam filter.  chatty to chatty of different source is also due to spam filter.
+        state.last_tid()[element.log_id()] =
+                (element.dropped_count() && !same_tid) ? 0 : element.tid();
+
+        logd_lock.unlock();
+        // We never prune logs equal to or newer than any LogReaderThreads' `start` value, so the
+        // `element` pointer is safe here without the lock
+        if (!element.FlushTo(writer, stats_, same_tid)) {
+            logd_lock.lock();
+            return false;
+        }
+        logd_lock.lock();
+    }
+
+    state.set_start(state.start() + 1);
+    return true;
+}
+
+bool SimpleLogBuffer::Clear(log_id_t id, uid_t uid) {
+    // Try three times to clear, then disconnect the readers and try one final time.
+    for (int retry = 0; retry < 3; ++retry) {
+        {
+            auto lock = std::lock_guard{logd_lock};
+            if (Prune(id, ULONG_MAX, uid)) {
+                return true;
+            }
+        }
+        sleep(1);
+    }
+    // Check if it is still busy after the sleep, we try to prune one entry, not another clear run,
+    // so we are looking for the quick side effect of the return value to tell us if we have a
+    // _blocked_ reader.
+    bool busy = false;
+    {
+        auto lock = std::lock_guard{logd_lock};
+        busy = !Prune(id, 1, uid);
+    }
+    // It is still busy, disconnect all readers.
+    if (busy) {
+        auto lock = std::lock_guard{logd_lock};
+        for (const auto& reader_thread : reader_list_->reader_threads()) {
+            if (reader_thread->IsWatching(id)) {
+                LOG(WARNING) << "Kicking blocked reader, " << reader_thread->name()
+                             << ", from LogBuffer::clear()";
+                reader_thread->Release();
+            }
+        }
+    }
+    auto lock = std::lock_guard{logd_lock};
+    return Prune(id, ULONG_MAX, uid);
+}
+
+// get the total space allocated to "id"
+size_t SimpleLogBuffer::GetSize(log_id_t id) {
+    auto lock = std::lock_guard{logd_lock};
+    size_t retval = max_size_[id];
+    return retval;
+}
+
+// set the total space allocated to "id"
+bool SimpleLogBuffer::SetSize(log_id_t id, size_t size) {
+    // Reasonable limits ...
+    if (!IsValidBufferSize(size)) {
+        return false;
+    }
+
+    auto lock = std::lock_guard{logd_lock};
+    max_size_[id] = size;
+    return true;
+}
+
+void SimpleLogBuffer::MaybePrune(log_id_t id) {
+    unsigned long prune_rows;
+    if (stats_->ShouldPrune(id, max_size_[id], &prune_rows)) {
+        Prune(id, prune_rows, 0);
+    }
+}
+
+bool SimpleLogBuffer::Prune(log_id_t id, unsigned long prune_rows, uid_t caller_uid) {
+    // Don't prune logs that are newer than the point at which any reader threads are reading from.
+    LogReaderThread* oldest = nullptr;
+    for (const auto& reader_thread : reader_list_->reader_threads()) {
+        if (!reader_thread->IsWatching(id)) {
+            continue;
+        }
+        if (!oldest || oldest->start() > reader_thread->start() ||
+            (oldest->start() == reader_thread->start() &&
+             reader_thread->deadline().time_since_epoch().count() != 0)) {
+            oldest = reader_thread.get();
+        }
+    }
+
+    auto it = GetOldest(id);
+
+    while (it != logs_.end()) {
+        LogBufferElement& element = *it;
+
+        if (element.log_id() != id) {
+            ++it;
+            continue;
+        }
+
+        if (caller_uid != 0 && element.uid() != caller_uid) {
+            ++it;
+            continue;
+        }
+
+        if (oldest && oldest->start() <= element.sequence()) {
+            KickReader(oldest, id, prune_rows);
+            return false;
+        }
+
+        stats_->Subtract(element.ToLogStatisticsElement());
+        it = Erase(it);
+        if (--prune_rows == 0) {
+            return true;
+        }
+    }
+    return true;
+}
+
+std::list<LogBufferElement>::iterator SimpleLogBuffer::Erase(
+        std::list<LogBufferElement>::iterator it) {
+    bool oldest_is_it[LOG_ID_MAX];
+    log_id_for_each(i) { oldest_is_it[i] = oldest_[i] && it == *oldest_[i]; }
+
+    it = logs_.erase(it);
+
+    log_id_for_each(i) {
+        if (oldest_is_it[i]) {
+            if (__predict_false(it == logs().end())) {
+                oldest_[i] = std::nullopt;
+            } else {
+                oldest_[i] = it;  // Store the next iterator even if it does not correspond to
+                                  // the same log_id, as a starting point for GetOldest().
+            }
+        }
+    }
+
+    return it;
+}
+
+// If the selected reader is blocking our pruning progress, decide on
+// what kind of mitigation is necessary to unblock the situation.
+void SimpleLogBuffer::KickReader(LogReaderThread* reader, log_id_t id, unsigned long prune_rows) {
+    if (stats_->Sizes(id) > (2 * max_size_[id])) {  // +100%
+        // A misbehaving or slow reader has its connection
+        // dropped if we hit too much memory pressure.
+        LOG(WARNING) << "Kicking blocked reader, " << reader->name()
+                     << ", from LogBuffer::kickMe()";
+        reader->Release();
+    } else if (reader->deadline().time_since_epoch().count() != 0) {
+        // Allow a blocked WRAP deadline reader to trigger and start reporting the log data.
+        reader->TriggerReader();
+    } else {
+        // tell slow reader to skip entries to catch up
+        LOG(WARNING) << "Skipping " << prune_rows << " entries from slow reader, " << reader->name()
+                     << ", from LogBuffer::kickMe()";
+        reader->TriggerSkip(id, prune_rows);
+    }
+}
diff --git a/logd/SimpleLogBuffer.h b/logd/SimpleLogBuffer.h
new file mode 100644
index 0000000..51779ab
--- /dev/null
+++ b/logd/SimpleLogBuffer.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <list>
+#include <mutex>
+
+#include "LogBuffer.h"
+#include "LogBufferElement.h"
+#include "LogReaderList.h"
+#include "LogStatistics.h"
+#include "LogTags.h"
+#include "LogdLock.h"
+
+class SimpleLogBuffer : public LogBuffer {
+  public:
+    SimpleLogBuffer(LogReaderList* reader_list, LogTags* tags, LogStatistics* stats);
+    ~SimpleLogBuffer();
+    void Init() override final;
+
+    int Log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid, const char* msg,
+            uint16_t len) override;
+    std::unique_ptr<FlushToState> CreateFlushToState(uint64_t start, LogMask log_mask)
+            REQUIRES(logd_lock) override;
+    bool FlushTo(LogWriter* writer, FlushToState& state,
+                 const std::function<FilterResult(log_id_t log_id, pid_t pid, uint64_t sequence,
+                                                  log_time realtime)>& filter)
+            REQUIRES(logd_lock) override;
+
+    bool Clear(log_id_t id, uid_t uid) override;
+    size_t GetSize(log_id_t id) override;
+    bool SetSize(log_id_t id, size_t size) override final;
+
+    uint64_t sequence() const override { return sequence_.load(std::memory_order_relaxed); }
+
+  protected:
+    virtual bool Prune(log_id_t id, unsigned long prune_rows, uid_t uid) REQUIRES(logd_lock);
+    virtual void LogInternal(LogBufferElement&& elem) REQUIRES(logd_lock);
+
+    // Returns an iterator to the oldest element for a given log type, or logs_.end() if
+    // there are no logs for the given log type. Requires logs_logd_lock to be held.
+    std::list<LogBufferElement>::iterator GetOldest(log_id_t log_id) REQUIRES(logd_lock);
+    std::list<LogBufferElement>::iterator Erase(std::list<LogBufferElement>::iterator it)
+            REQUIRES(logd_lock);
+    void KickReader(LogReaderThread* reader, log_id_t id, unsigned long prune_rows)
+            REQUIRES(logd_lock);
+
+    LogStatistics* stats() { return stats_; }
+    LogReaderList* reader_list() { return reader_list_; }
+    size_t max_size(log_id_t id) REQUIRES_SHARED(logd_lock) { return max_size_[id]; }
+    std::list<LogBufferElement>& logs() { return logs_; }
+
+  private:
+    bool ShouldLog(log_id_t log_id, const char* msg, uint16_t len);
+    void MaybePrune(log_id_t id) REQUIRES(logd_lock);
+
+    LogReaderList* reader_list_;
+    LogTags* tags_;
+    LogStatistics* stats_;
+
+    std::atomic<uint64_t> sequence_ = 1;
+
+    size_t max_size_[LOG_ID_MAX] GUARDED_BY(logd_lock);
+    std::list<LogBufferElement> logs_ GUARDED_BY(logd_lock);
+    // Keeps track of the iterator to the oldest log message of a given log type, as an
+    // optimization when pruning logs.  Use GetOldest() to retrieve.
+    std::optional<std::list<LogBufferElement>::iterator> oldest_[LOG_ID_MAX] GUARDED_BY(logd_lock);
+};
diff --git a/logd/auditctl.cpp b/logd/auditctl.cpp
new file mode 100644
index 0000000..98bb02d
--- /dev/null
+++ b/logd/auditctl.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 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 <android-base/parseint.h>
+#include <error.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "libaudit.h"
+
+static void usage(const char* cmdline) {
+    fprintf(stderr, "Usage: %s [-r rate]\n", cmdline);
+}
+
+static void do_update_rate(uint32_t rate) {
+    int fd = audit_open();
+    if (fd == -1) {
+        error(EXIT_FAILURE, errno, "Unable to open audit socket");
+    }
+    int result = audit_rate_limit(fd, rate);
+    close(fd);
+    if (result < 0) {
+        fprintf(stderr, "Can't update audit rate limit: %d\n", result);
+        exit(EXIT_FAILURE);
+    }
+}
+
+int main(int argc, char* argv[]) {
+    uint32_t rate = 0;
+    bool update_rate = false;
+    int opt;
+
+    while ((opt = getopt(argc, argv, "r:")) != -1) {
+        switch (opt) {
+            case 'r':
+                if (!android::base::ParseUint<uint32_t>(optarg, &rate)) {
+                    error(EXIT_FAILURE, errno, "Invalid Rate");
+                }
+                update_rate = true;
+                break;
+            default: /* '?' */
+                usage(argv[0]);
+                exit(EXIT_FAILURE);
+        }
+    }
+
+    // In the future, we may add other options to auditctl
+    // so this if statement will expand.
+    // if (!update_rate && !update_backlog && !update_whatever) ...
+    if (!update_rate) {
+        fprintf(stderr, "Nothing to do\n");
+        usage(argv[0]);
+        exit(EXIT_FAILURE);
+    }
+
+    if (update_rate) {
+        do_update_rate(rate);
+    }
+
+    return 0;
+}
diff --git a/logd/doc_images/cpu_cuttlefish.png b/logd/doc_images/cpu_cuttlefish.png
new file mode 100644
index 0000000..8d809ca
--- /dev/null
+++ b/logd/doc_images/cpu_cuttlefish.png
Binary files differ
diff --git a/logd/doc_images/cpu_walleye.png b/logd/doc_images/cpu_walleye.png
new file mode 100644
index 0000000..39c951b
--- /dev/null
+++ b/logd/doc_images/cpu_walleye.png
Binary files differ
diff --git a/logd/doc_images/memory_usage.png b/logd/doc_images/memory_usage.png
new file mode 100644
index 0000000..434d6d3
--- /dev/null
+++ b/logd/doc_images/memory_usage.png
Binary files differ
diff --git a/logd/doc_images/total_log_count.png b/logd/doc_images/total_log_count.png
new file mode 100644
index 0000000..e73c2c1
--- /dev/null
+++ b/logd/doc_images/total_log_count.png
Binary files differ
diff --git a/logd/event.logtags b/logd/event.logtags
index db8c19b..fa13a62 100644
--- a/logd/event.logtags
+++ b/logd/event.logtags
@@ -29,9 +29,11 @@
 # 4: Number of allocations
 # 5: Id
 # 6: Percent
+# s: Number of seconds (monotonic time)
 # Default value for data of type int/long is 2 (bytes).
 #
 # TODO: generate ".java" and ".h" files with integer constants from this file.
 
 1003  auditd (avc|3)
-1004  logd (dropped|3)
+1004  chatty (dropped|3)
+1005  tag_def (tag|1),(name|3),(format|3)
diff --git a/logd/fuzz/Android.bp b/logd/fuzz/Android.bp
new file mode 100644
index 0000000..d346cd7
--- /dev/null
+++ b/logd/fuzz/Android.bp
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 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.
+ */
+
+cc_defaults {
+    name: "log_fuzzer_defaults",
+    static_libs: [
+        "libbase",
+        "libcutils",
+        "libselinux",
+        "liblog",
+        "liblogd",
+        "libcutils",
+        "libz",
+        "libzstd",
+    ],
+    cflags: ["-Wextra"],
+    host_supported: true,
+}
+
+cc_fuzz {
+    name: "log_buffer_log_fuzzer",
+    defaults: ["log_fuzzer_defaults"],
+    srcs: [
+        "log_buffer_log_fuzzer.cpp",
+    ],
+}
+
+cc_fuzz {
+    name: "serialized_log_buffer_fuzzer",
+    defaults: ["log_fuzzer_defaults"],
+    srcs: [
+        "serialized_log_buffer_fuzzer.cpp",
+    ],
+    corpus: [
+        "corpus/logentry_use_after_compress",
+    ]
+}
diff --git a/logd/fuzz/corpus/logentry_use_after_compress b/logd/fuzz/corpus/logentry_use_after_compress
new file mode 100644
index 0000000..2081b13
--- /dev/null
+++ b/logd/fuzz/corpus/logentry_use_after_compress
Binary files differ
diff --git a/logd/fuzz/log_buffer_log_fuzzer.cpp b/logd/fuzz/log_buffer_log_fuzzer.cpp
new file mode 100644
index 0000000..2fe9407
--- /dev/null
+++ b/logd/fuzz/log_buffer_log_fuzzer.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2019 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 <string>
+
+#include <android-base/logging.h>
+
+#include "../ChattyLogBuffer.h"
+#include "../LogReaderList.h"
+#include "../LogReaderThread.h"
+#include "../LogStatistics.h"
+#include "../SerializedLogBuffer.h"
+
+// We don't want to waste a lot of entropy on messages
+#define MAX_MSG_LENGTH 5
+
+// Tag IDs usually start at 1000, we only want to try 1000 through 1009
+#define MIN_TAG_ID 1000
+#define TAG_MOD 10
+
+char* android::uidToName(uid_t) {
+    return strdup("fake");
+}
+
+struct LogInput {
+  public:
+    log_id_t log_id;
+    log_time realtime;
+    uid_t uid;
+    pid_t pid;
+    pid_t tid;
+    unsigned int log_mask;
+};
+
+int write_log_messages(const uint8_t** pdata, size_t* data_left, LogBuffer* log_buffer,
+                       LogStatistics* stats) {
+    const uint8_t* data = *pdata;
+    const LogInput* logInput = reinterpret_cast<const LogInput*>(data);
+    data += sizeof(LogInput);
+    *data_left -= sizeof(LogInput);
+
+    uint32_t tag = MIN_TAG_ID + data[0] % TAG_MOD;
+    uint8_t msg_length = data[1] % MAX_MSG_LENGTH;
+    if (msg_length < 2) {
+        // Not enough data for message
+        return 0;
+    }
+
+    data += 2 * sizeof(uint8_t);
+    *data_left -= 2 * sizeof(uint8_t);
+
+    if (*data_left < msg_length) {
+        // Not enough data for tag and message
+        *pdata = data;
+        return 0;
+    }
+
+    // We need nullterm'd strings
+    char msg[sizeof(uint32_t) + MAX_MSG_LENGTH + sizeof(char)];
+    char* msg_only = msg + sizeof(uint32_t);
+    memcpy(msg, &tag, sizeof(uint32_t));
+    memcpy(msg_only, data, msg_length);
+    msg_only[msg_length] = '\0';
+    data += msg_length;
+    *data_left -= msg_length;
+
+    // Other elements not in enum.
+    log_id_t log_id = static_cast<log_id_t>(unsigned(logInput->log_id) % (LOG_ID_MAX + 1));
+    log_buffer->Log(log_id, logInput->realtime, logInput->uid, logInput->pid, logInput->tid, msg,
+                    sizeof(uint32_t) + msg_length + 1);
+    stats->Format(logInput->uid, logInput->pid, logInput->log_mask);
+    *pdata = data;
+    return 1;
+}
+
+class NoopWriter : public LogWriter {
+  public:
+    NoopWriter() : LogWriter(0, true) {}
+    bool Write(const logger_entry&, const char*) override { return true; }
+
+    std::string name() const override { return "noop_writer"; }
+};
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    // We want a random tag length and a random remaining message length
+    if (data == nullptr || size < sizeof(LogInput) + 2 * sizeof(uint8_t)) {
+        return 0;
+    }
+
+    android::base::SetMinimumLogSeverity(android::base::ERROR);
+
+    LogReaderList reader_list;
+    LogTags tags;
+    PruneList prune_list;
+    LogStatistics stats(true, true);
+    std::unique_ptr<LogBuffer> log_buffer;
+#ifdef FUZZ_SERIALIZED
+    log_buffer.reset(new SerializedLogBuffer(&reader_list, &tags, &stats));
+#else
+    log_buffer.reset(new ChattyLogBuffer(&reader_list, &tags, &prune_list, &stats));
+#endif
+    size_t data_left = size;
+    const uint8_t** pdata = &data;
+
+    prune_list.Init(nullptr);
+    // We want to get pruning code to get called.
+    log_id_for_each(i) { log_buffer->SetSize(i, 10000); }
+
+    while (data_left >= sizeof(LogInput) + 2 * sizeof(uint8_t)) {
+        if (!write_log_messages(pdata, &data_left, log_buffer.get(), &stats)) {
+            return 0;
+        }
+    }
+
+    // Read out all of the logs.
+    {
+        auto lock = std::unique_lock{logd_lock};
+        std::unique_ptr<LogWriter> test_writer(new NoopWriter());
+        std::unique_ptr<LogReaderThread> log_reader(
+                new LogReaderThread(log_buffer.get(), &reader_list, std::move(test_writer), true, 0,
+                                    kLogMaskAll, 0, {}, 1, {}));
+        reader_list.reader_threads().emplace_back(std::move(log_reader));
+    }
+
+    // Wait until the reader has finished.
+    while (true) {
+        usleep(50);
+        auto lock = std::unique_lock{logd_lock};
+        if (reader_list.reader_threads().size() == 0) {
+            break;
+        }
+    }
+
+    log_id_for_each(i) { log_buffer->Clear(i, 0); }
+    return 0;
+}
diff --git a/liblog/fake_log_device.h b/logd/fuzz/serialized_log_buffer_fuzzer.cpp
similarity index 61%
copy from liblog/fake_log_device.h
copy to logd/fuzz/serialized_log_buffer_fuzzer.cpp
index 9d168cd..d4795b0 100644
--- a/liblog/fake_log_device.h
+++ b/logd/fuzz/serialized_log_buffer_fuzzer.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright 2020 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.
@@ -14,15 +14,6 @@
  * limitations under the License.
  */
 
-#ifndef _LIBLOG_FAKE_LOG_DEVICE_H
-#define _LIBLOG_FAKE_LOG_DEVICE_H
+#define FUZZ_SERIALIZED
 
-#include <sys/types.h>
-
-struct iovec;
-
-int fakeLogOpen(const char *pathName, int flags);
-int fakeLogClose(int fd);
-ssize_t fakeLogWritev(int fd, const struct iovec* vector, int count);
-
-#endif // _LIBLOG_FAKE_LOG_DEVICE_H
+#include "log_buffer_log_fuzzer.cpp"
diff --git a/logd/libaudit.c b/logd/libaudit.c
deleted file mode 100644
index d00d579..0000000
--- a/logd/libaudit.c
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright 2012, Samsung Telecommunications of America
- * 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.
- *
- * Written by William Roberts <w.roberts@sta.samsung.com>
- *
- */
-
-#include <errno.h>
-#include <string.h>
-#include <unistd.h>
-
-#define LOG_TAG "libaudit"
-#include <log/log.h>
-
-#include "libaudit.h"
-
-/**
- * Waits for an ack from the kernel
- * @param fd
- *  The netlink socket fd
- * @param seq
- *  The current sequence number were acking on
- * @return
- *  This function returns 0 on success, else -errno.
- */
-static int get_ack(int fd, int16_t seq)
-{
-    int rc;
-    struct audit_message rep;
-
-    /* Sanity check, this is an internal interface this shouldn't happen */
-    if (fd < 0) {
-        return -EINVAL;
-    }
-
-    rc = audit_get_reply(fd, &rep, GET_REPLY_BLOCKING, MSG_PEEK);
-    if (rc < 0) {
-        return rc;
-    }
-
-    if (rep.nlh.nlmsg_type == NLMSG_ERROR) {
-        audit_get_reply(fd, &rep, GET_REPLY_BLOCKING, 0);
-        rc = ((struct nlmsgerr *)rep.data)->error;
-        if (rc) {
-            return -rc;
-        }
-    }
-
-    if ((int16_t)rep.nlh.nlmsg_seq != seq) {
-        SLOGW("Expected sequence number between user space and kernel space is out of skew, "
-          "expected %u got %u", seq, rep.nlh.nlmsg_seq);
-    }
-
-    return 0;
-}
-
-/**
- *
- * @param fd
- *  The netlink socket fd
- * @param type
- *  The type of netlink message
- * @param data
- *  The data to send
- * @param size
- *  The length of the data in bytes
- * @return
- *  This function returns a positive sequence number on success, else -errno.
- */
-static int audit_send(int fd, int type, const void *data, size_t size)
-{
-    int rc;
-    static int16_t sequence = 0;
-    struct audit_message req;
-    struct sockaddr_nl addr;
-
-    memset(&req, 0, sizeof(req));
-    memset(&addr, 0, sizeof(addr));
-
-    /* We always send netlink messaged */
-    addr.nl_family = AF_NETLINK;
-
-    /* Set up the netlink headers */
-    req.nlh.nlmsg_type = type;
-    req.nlh.nlmsg_len = NLMSG_SPACE(size);
-    req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
-
-    /*
-     * Check for a valid fd, even though sendto would catch this, its easier
-     * to always blindly increment the sequence number
-     */
-    if (fd < 0) {
-        return -EBADF;
-    }
-
-    /* Ensure the message is not too big */
-    if (NLMSG_SPACE(size) > MAX_AUDIT_MESSAGE_LENGTH) {
-        SLOGE("netlink message is too large");
-        return -EINVAL;
-    }
-
-    /* Only memcpy in the data if it was specified */
-    if (size && data) {
-        memcpy(NLMSG_DATA(&req.nlh), data, size);
-    }
-
-    /*
-     * Only increment the sequence number on a guarantee
-     * you will send it to the kernel.
-     *
-     * Also, the sequence is defined as a u32 in the kernel
-     * struct. Using an int here might not work on 32/64 bit splits. A
-     * signed 64 bit value can overflow a u32..but a u32
-     * might not fit in the response, so we need to use s32.
-     * Which is still kind of hackish since int could be 16 bits
-     * in size. The only safe type to use here is a signed 16
-     * bit value.
-     */
-    req.nlh.nlmsg_seq = ++sequence;
-
-    /* While failing and its due to interrupts */
-
-    rc = TEMP_FAILURE_RETRY(sendto(fd, &req, req.nlh.nlmsg_len, 0,
-                                   (struct sockaddr*) &addr, sizeof(addr)));
-
-    /* Not all the bytes were sent */
-    if (rc < 0) {
-        rc = -errno;
-        SLOGE("Error sending data over the netlink socket: %s", strerror(-errno));
-        goto out;
-    } else if ((uint32_t) rc != req.nlh.nlmsg_len) {
-        rc = -EPROTO;
-        goto out;
-    }
-
-    /* We sent all the bytes, get the ack */
-    rc = get_ack(fd, sequence);
-
-    /* If the ack failed, return the error, else return the sequence number */
-    rc = (rc == 0) ? (int) sequence : rc;
-
-out:
-    /* Don't let sequence roll to negative */
-    if (sequence < 0) {
-        SLOGW("Auditd to Kernel sequence number has rolled over");
-        sequence = 0;
-    }
-
-    return rc;
-}
-
-int audit_setup(int fd, uint32_t pid)
-{
-    int rc;
-    struct audit_message rep;
-    struct audit_status status;
-
-    memset(&status, 0, sizeof(status));
-
-    /*
-     * In order to set the auditd PID we send an audit message over the netlink
-     * socket with the pid field of the status struct set to our current pid,
-     * and the the mask set to AUDIT_STATUS_PID
-     */
-    status.pid = pid;
-    status.mask = AUDIT_STATUS_PID | AUDIT_STATUS_RATE_LIMIT;
-    status.rate_limit = 20; // audit entries per second
-
-    /* Let the kernel know this pid will be registering for audit events */
-    rc = audit_send(fd, AUDIT_SET, &status, sizeof(status));
-    if (rc < 0) {
-        SLOGE("Could net set pid for audit events, error: %s", strerror(-rc));
-        return rc;
-    }
-
-    /*
-     * In a request where we need to wait for a response, wait for the message
-     * and discard it. This message confirms and sync's us with the kernel.
-     * This daemon is now registered as the audit logger.
-     *
-     * TODO
-     * If the daemon dies and restarts the message didn't come back,
-     * so I went to non-blocking and it seemed to fix the bug.
-     * Need to investigate further.
-     */
-    audit_get_reply(fd, &rep, GET_REPLY_NONBLOCKING, 0);
-
-    return 0;
-}
-
-int audit_open()
-{
-    return socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_AUDIT);
-}
-
-int audit_get_reply(int fd, struct audit_message *rep, reply_t block, int peek)
-{
-    ssize_t len;
-    int flags;
-    int rc = 0;
-
-    struct sockaddr_nl nladdr;
-    socklen_t nladdrlen = sizeof(nladdr);
-
-    if (fd < 0) {
-        return -EBADF;
-    }
-
-    /* Set up the flags for recv from */
-    flags = (block == GET_REPLY_NONBLOCKING) ? MSG_DONTWAIT : 0;
-    flags |= peek;
-
-    /*
-     * Get the data from the netlink socket but on error we need to be carefull,
-     * the interface shows that EINTR can never be returned, other errors,
-     * however, can be returned.
-     */
-    len = TEMP_FAILURE_RETRY(recvfrom(fd, rep, sizeof(*rep), flags,
-                                      (struct sockaddr*) &nladdr, &nladdrlen));
-
-    /*
-     * EAGAIN should be re-tried until success or another error manifests.
-     */
-    if (len < 0) {
-        rc = -errno;
-        if (block == GET_REPLY_NONBLOCKING && rc == -EAGAIN) {
-            /* If request is non blocking and errno is EAGAIN, just return 0 */
-            return 0;
-        }
-        SLOGE("Error receiving from netlink socket, error: %s", strerror(-rc));
-        return rc;
-    }
-
-    if (nladdrlen != sizeof(nladdr)) {
-        SLOGE("Protocol fault, error: %s", strerror(EPROTO));
-        return -EPROTO;
-    }
-
-    /* Make sure the netlink message was not spoof'd */
-    if (nladdr.nl_pid) {
-        SLOGE("Invalid netlink pid received, expected 0 got: %d", nladdr.nl_pid);
-        return -EINVAL;
-    }
-
-    /* Check if the reply from the kernel was ok */
-    if (!NLMSG_OK(&rep->nlh, (size_t)len)) {
-        rc = (len == sizeof(*rep)) ? -EFBIG : -EBADE;
-        SLOGE("Bad kernel response %s", strerror(-rc));
-    }
-
-    return rc;
-}
-
-void audit_close(int fd)
-{
-    int rc = close(fd);
-    if (rc < 0) {
-        SLOGE("Attempting to close invalid fd %d, error: %s", fd, strerror(errno));
-    }
-    return;
-}
diff --git a/logd/libaudit.cpp b/logd/libaudit.cpp
new file mode 100644
index 0000000..ccea0a2
--- /dev/null
+++ b/logd/libaudit.cpp
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2012, Samsung Telecommunications of America
+ * 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.
+ *
+ * Written by William Roberts <w.roberts@sta.samsung.com>
+ *
+ */
+
+#include "libaudit.h"
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <limits>
+
+/**
+ * Waits for an ack from the kernel
+ * @param fd
+ *  The netlink socket fd
+ * @return
+ *  This function returns 0 on success, else -errno.
+ */
+static int get_ack(int fd) {
+    struct audit_message rep = {};
+    int rc = audit_get_reply(fd, &rep, GET_REPLY_BLOCKING, MSG_PEEK);
+    if (rc < 0) {
+        return rc;
+    }
+
+    if (rep.nlh.nlmsg_type == NLMSG_ERROR) {
+        audit_get_reply(fd, &rep, GET_REPLY_BLOCKING, 0);
+        rc = reinterpret_cast<struct nlmsgerr*>(rep.data)->error;
+        if (rc) {
+            return -rc;
+        }
+    }
+
+    return 0;
+}
+
+/**
+ *
+ * @param fd
+ *  The netlink socket fd
+ * @param type
+ *  The type of netlink message
+ * @param data
+ *  The data to send
+ * @param size
+ *  The length of the data in bytes
+ * @return
+ *  This function returns a positive sequence number on success, else -errno.
+ */
+static int audit_send(int fd, int type, const void* data, size_t size) {
+    struct sockaddr_nl addr = {.nl_family = AF_NETLINK};
+
+    /* Set up the netlink headers */
+    struct audit_message req = {};
+    req.nlh.nlmsg_type = static_cast<uint16_t>(type);
+    req.nlh.nlmsg_len = NLMSG_SPACE(size);
+    req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+
+    /*
+     * Check for a valid fd, even though sendto would catch this, its easier
+     * to always blindly increment the sequence number
+     */
+    if (fd < 0) {
+        return -EBADF;
+    }
+
+    /* Ensure the message is not too big */
+    if (NLMSG_SPACE(size) > MAX_AUDIT_MESSAGE_LENGTH) {
+        return -EINVAL;
+    }
+
+    /* Only memcpy in the data if it was specified */
+    if (size && data) {
+        memcpy(NLMSG_DATA(&req.nlh), data, size);
+    }
+
+    /*
+     * Only increment the sequence number on a guarantee
+     * you will send it to the kernel.
+     */
+    static uint32_t sequence = 0;
+    if (sequence == std::numeric_limits<uint32_t>::max()) {
+        sequence = 1;
+    } else {
+        sequence++;
+    }
+    req.nlh.nlmsg_seq = sequence;
+
+    ssize_t rc = TEMP_FAILURE_RETRY(
+            sendto(fd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*)&addr, sizeof(addr)));
+
+    /* Not all the bytes were sent */
+    if (rc < 0) {
+        return -errno;
+    } else if ((uint32_t)rc != req.nlh.nlmsg_len) {
+        return -EPROTO;
+    }
+
+    /* We sent all the bytes, get the ack */
+    rc = get_ack(fd);
+
+    /* If the ack failed, return the error, else return the sequence number */
+    rc = (rc == 0) ? (int)sequence : rc;
+
+    return rc;
+}
+
+int audit_setup(int fd, pid_t pid) {
+    /*
+     * In order to set the auditd PID we send an audit message over the netlink
+     * socket with the pid field of the status struct set to our current pid,
+     * and the the mask set to AUDIT_STATUS_PID
+     */
+    struct audit_status status = {
+            .mask = AUDIT_STATUS_PID,
+            .pid = static_cast<uint32_t>(pid),
+    };
+
+    /* Let the kernel know this pid will be registering for audit events */
+    int rc = audit_send(fd, AUDIT_SET, &status, sizeof(status));
+    if (rc < 0) {
+        return rc;
+    }
+
+    /*
+     * In a request where we need to wait for a response, wait for the message
+     * and discard it. This message confirms and sync's us with the kernel.
+     * This daemon is now registered as the audit logger.
+     *
+     * TODO
+     * If the daemon dies and restarts the message didn't come back,
+     * so I went to non-blocking and it seemed to fix the bug.
+     * Need to investigate further.
+     */
+    struct audit_message rep = {};
+    audit_get_reply(fd, &rep, GET_REPLY_NONBLOCKING, 0);
+
+    return 0;
+}
+
+int audit_open() {
+    return socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_AUDIT);
+}
+
+int audit_rate_limit(int fd, uint32_t limit) {
+    struct audit_status status = {
+            .mask = AUDIT_STATUS_RATE_LIMIT, .rate_limit = limit, /* audit entries per second */
+    };
+    return audit_send(fd, AUDIT_SET, &status, sizeof(status));
+}
+
+int audit_get_reply(int fd, struct audit_message* rep, reply_t block, int peek) {
+    if (fd < 0) {
+        return -EBADF;
+    }
+
+    int flags = (block == GET_REPLY_NONBLOCKING) ? MSG_DONTWAIT : 0;
+    flags |= peek;
+
+    /*
+     * Get the data from the netlink socket but on error we need to be carefull,
+     * the interface shows that EINTR can never be returned, other errors,
+     * however, can be returned.
+     */
+    struct sockaddr_nl nladdr;
+    socklen_t nladdrlen = sizeof(nladdr);
+    ssize_t len = TEMP_FAILURE_RETRY(
+            recvfrom(fd, rep, sizeof(*rep), flags, (struct sockaddr*)&nladdr, &nladdrlen));
+
+    /*
+     * EAGAIN should be re-tried until success or another error manifests.
+     */
+    if (len < 0) {
+        if (block == GET_REPLY_NONBLOCKING && errno == EAGAIN) {
+            /* If request is non blocking and errno is EAGAIN, just return 0 */
+            return 0;
+        }
+        return -errno;
+    }
+
+    if (nladdrlen != sizeof(nladdr)) {
+        return -EPROTO;
+    }
+
+    /* Make sure the netlink message was not spoof'd */
+    if (nladdr.nl_pid) {
+        return -EINVAL;
+    }
+
+    /* Check if the reply from the kernel was ok */
+    if (!NLMSG_OK(&rep->nlh, (size_t)len)) {
+        return len == sizeof(*rep) ? -EFBIG : -EBADE;
+    }
+
+    return 0;
+}
+
+void audit_close(int fd) {
+    close(fd);
+}
diff --git a/logd/libaudit.h b/logd/libaudit.h
index b9e330d..27b0866 100644
--- a/logd/libaudit.h
+++ b/logd/libaudit.h
@@ -17,25 +17,21 @@
  * Written by William Roberts <w.roberts@sta.samsung.com>
  */
 
-#ifndef _LIBAUDIT_H_
-#define _LIBAUDIT_H_
+#pragma once
 
 #include <stdint.h>
 #include <sys/cdefs.h>
 #include <sys/socket.h>
 #include <sys/types.h>
 
-#include <linux/netlink.h>
 #include <linux/audit.h>
+#include <linux/netlink.h>
 
 __BEGIN_DECLS
 
-#define MAX_AUDIT_MESSAGE_LENGTH    8970
+#define MAX_AUDIT_MESSAGE_LENGTH 8970
 
-typedef enum {
-    GET_REPLY_BLOCKING=0,
-    GET_REPLY_NONBLOCKING
-} reply_t;
+typedef enum { GET_REPLY_BLOCKING = 0, GET_REPLY_NONBLOCKING } reply_t;
 
 /* type == AUDIT_SIGNAL_INFO */
 struct audit_sig_info {
@@ -46,7 +42,7 @@
 
 struct audit_message {
     struct nlmsghdr nlh;
-    char   data[MAX_AUDIT_MESSAGE_LENGTH];
+    char data[MAX_AUDIT_MESSAGE_LENGTH];
 };
 
 /**
@@ -55,7 +51,7 @@
  *  A valid fd on success or < 0 on error with errno set.
  *  Returns the same errors as man 2 socket.
  */
-extern int  audit_open(void);
+extern int audit_open(void);
 
 /**
  * Closes the fd returned from audit_open()
@@ -78,20 +74,30 @@
  * @return
  *  This function returns 0 on success, else -errno.
  */
-extern int  audit_get_reply(int fd, struct audit_message *rep, reply_t block,
-               int peek);
+extern int audit_get_reply(int fd, struct audit_message* rep, reply_t block,
+                           int peek);
 
 /**
- * Sets a pid to recieve audit netlink events from the kernel
+ * Sets a pid to receive audit netlink events from the kernel
  * @param fd
  *  The fd returned by a call to audit_open()
  * @param pid
- *  The pid whom to set as the reciever of audit messages
+ *  The pid whom to set as the receiver of audit messages
  * @return
  *  This function returns 0 on success, -errno on error.
  */
-extern int  audit_setup(int fd, uint32_t pid);
+extern int audit_setup(int fd, pid_t pid);
+
+/**
+ * Throttle kernel messages at the provided rate
+ * @param fd
+ *  The fd returned by a call to audit_open()
+ * @param rate
+ *  The rate, in messages per second, above which the kernel
+ *  should drop audit messages.
+ * @return
+ *  This function returns 0 on success, -errno on error.
+ */
+extern int audit_rate_limit(int fd, uint32_t limit);
 
 __END_DECLS
-
-#endif
diff --git a/logd/logd.rc b/logd/logd.rc
new file mode 100644
index 0000000..530f342
--- /dev/null
+++ b/logd/logd.rc
@@ -0,0 +1,35 @@
+service logd /system/bin/logd
+    socket logd stream 0666 logd logd
+    socket logdr seqpacket 0666 logd logd
+    socket logdw dgram+passcred 0222 logd logd
+    file /proc/kmsg r
+    file /dev/kmsg w
+    user logd
+    group logd system package_info readproc
+    capabilities SYSLOG AUDIT_CONTROL
+    priority 10
+    writepid /dev/cpuset/system-background/tasks
+
+service logd-reinit /system/bin/logd --reinit
+    oneshot
+    disabled
+    user logd
+    group logd
+    writepid /dev/cpuset/system-background/tasks
+
+# Limit SELinux denial generation to 5/second
+service logd-auditctl /system/bin/auditctl -r 5
+    oneshot
+    disabled
+    user logd
+    group logd
+    capabilities AUDIT_CONTROL
+
+on fs
+    write /dev/event-log-tags "# content owned by logd
+"
+    chown logd logd /dev/event-log-tags
+    chmod 0644 /dev/event-log-tags
+
+on property:sys.boot_completed=1
+    start logd-auditctl
diff --git a/logd/logd_test.cpp b/logd/logd_test.cpp
new file mode 100644
index 0000000..828f580
--- /dev/null
+++ b/logd/logd_test.cpp
@@ -0,0 +1,872 @@
+/*
+ * 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 <fcntl.h>
+#include <inttypes.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/macros.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <cutils/sockets.h>
+#include <gtest/gtest.h>
+#include <log/log_read.h>
+#include <private/android_filesystem_config.h>
+#include <private/android_logger.h>
+#ifdef __ANDROID__
+#include <selinux/selinux.h>
+#endif
+
+#include "LogUtils.h"  // For LOGD_SNDTIMEO.
+
+using android::base::unique_fd;
+
+#ifdef __ANDROID__
+static void send_to_control(char* buf, size_t len) {
+    int sock = socket_local_client("logd", ANDROID_SOCKET_NAMESPACE_RESERVED,
+                                   SOCK_STREAM);
+    if (sock >= 0) {
+        if (write(sock, buf, strlen(buf) + 1) > 0) {
+            ssize_t ret;
+            while ((ret = read(sock, buf, len)) > 0) {
+                if (((size_t)ret == len) || (len < PAGE_SIZE)) {
+                    break;
+                }
+                len -= ret;
+                buf += ret;
+
+                struct pollfd p = {.fd = sock, .events = POLLIN, .revents = 0 };
+
+                ret = poll(&p, 1, 20);
+                if ((ret <= 0) || !(p.revents & POLLIN)) {
+                    break;
+                }
+            }
+        }
+        close(sock);
+    }
+}
+
+/*
+ * returns statistics
+ */
+static void my_android_logger_get_statistics(char* buf, size_t len) {
+    snprintf(buf, len, "getStatistics 0 1 2 3 4");
+    send_to_control(buf, len);
+}
+
+static void alloc_statistics(char** buffer, size_t* length) {
+    size_t len = 8192;
+    char* buf;
+
+    for (int retry = 32; (retry >= 0); delete[] buf, --retry) {
+        buf = new char[len];
+        my_android_logger_get_statistics(buf, len);
+
+        buf[len - 1] = '\0';
+        size_t ret = atol(buf) + 1;
+        if (ret < 4) {
+            delete[] buf;
+            buf = nullptr;
+            break;
+        }
+        bool check = ret <= len;
+        len = ret;
+        if (check) {
+            break;
+        }
+        len += len / 8;  // allow for some slop
+    }
+    *buffer = buf;
+    *length = len;
+}
+
+static char* find_benchmark_spam(char* cp) {
+    // liblog_benchmarks has been run designed to SPAM.  The signature of
+    // a noisiest UID statistics is:
+    //
+    // Chattiest UIDs in main log buffer:                           Size Pruned
+    // UID   PACKAGE                                                BYTES LINES
+    // 0     root                                                  54164 147569
+    //
+    char* benchmark = nullptr;
+    do {
+        static const char signature[] = "\n0     root ";
+
+        benchmark = strstr(cp, signature);
+        if (!benchmark) {
+            break;
+        }
+        cp = benchmark + sizeof(signature);
+        while (isspace(*cp)) {
+            ++cp;
+        }
+        benchmark = cp;
+#ifdef DEBUG
+        char* end = strstr(benchmark, "\n");
+        if (end == nullptr) {
+            end = benchmark + strlen(benchmark);
+        }
+        fprintf(stderr, "parse for spam counter in \"%.*s\"\n",
+                (int)(end - benchmark), benchmark);
+#endif
+        // content
+        while (isdigit(*cp)) {
+            ++cp;
+        }
+        while (isspace(*cp)) {
+            ++cp;
+        }
+        // optional +/- field?
+        if ((*cp == '-') || (*cp == '+')) {
+            while (isdigit(*++cp) || (*cp == '.') || (*cp == '%') ||
+                   (*cp == 'X')) {
+                ;
+            }
+            while (isspace(*cp)) {
+                ++cp;
+            }
+        }
+        // number of entries pruned
+        unsigned long value = 0;
+        while (isdigit(*cp)) {
+            value = value * 10ULL + *cp - '0';
+            ++cp;
+        }
+        if (value > 10UL) {
+            break;
+        }
+        benchmark = nullptr;
+    } while (*cp);
+    return benchmark;
+}
+#endif
+
+#ifdef LOGD_ENABLE_FLAKY_TESTS
+TEST(logd, statistics) {
+#ifdef __ANDROID__
+    size_t len;
+    char* buf;
+
+    // Drop cache so that any access problems can be discovered.
+    if (!android::base::WriteStringToFile("3\n", "/proc/sys/vm/drop_caches")) {
+        GTEST_LOG_(INFO) << "Could not open trigger dropping inode cache";
+    }
+
+    alloc_statistics(&buf, &len);
+
+    ASSERT_TRUE(nullptr != buf);
+
+    // 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;
+        }
+    }
+
+    fprintf(stderr, "%s", cp);
+
+    EXPECT_LT((size_t)64, strlen(cp));
+
+    EXPECT_EQ(0, truncated);
+
+    char* main_logs = strstr(cp, "\nChattiest UIDs in main ");
+    EXPECT_TRUE(nullptr != main_logs);
+
+    char* radio_logs = strstr(cp, "\nChattiest UIDs in radio ");
+    if (!radio_logs)
+        GTEST_LOG_(INFO) << "Value of: nullptr != radio_logs\n"
+                            "Actual: false\n"
+                            "Expected: false\n";
+
+    char* system_logs = strstr(cp, "\nChattiest UIDs in system ");
+    EXPECT_TRUE(nullptr != system_logs);
+
+    char* events_logs = strstr(cp, "\nChattiest UIDs in events ");
+    EXPECT_TRUE(nullptr != events_logs);
+
+    // Check if there is any " u0_a#### " as this means packagelistparser broken
+    char* used_getpwuid = nullptr;
+    int used_getpwuid_len;
+    char* uid_name = cp;
+    static const char getpwuid_prefix[] = " u0_a";
+    while ((uid_name = strstr(uid_name, getpwuid_prefix)) != nullptr) {
+        used_getpwuid = uid_name + 1;
+        uid_name += strlen(getpwuid_prefix);
+        while (isdigit(*uid_name)) ++uid_name;
+        used_getpwuid_len = uid_name - used_getpwuid;
+        if (isspace(*uid_name)) break;
+        used_getpwuid = nullptr;
+    }
+    EXPECT_TRUE(nullptr == used_getpwuid);
+    if (used_getpwuid) {
+        fprintf(stderr, "libpackagelistparser failed to pick up %.*s\n",
+                used_getpwuid_len, used_getpwuid);
+    }
+
+    delete[] buf;
+#else
+    GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+#endif
+
+#ifdef __ANDROID__
+static void caught_signal(int /* signum */) {
+}
+
+static void dump_log_msg(const char* prefix, log_msg* msg, int lid) {
+    std::cout << std::flush;
+    std::cerr << std::flush;
+    fflush(stdout);
+    fflush(stderr);
+    EXPECT_GE(msg->entry.hdr_size, sizeof(logger_entry));
+
+    fprintf(stderr, "%s: [%u] ", prefix, msg->len());
+    fprintf(stderr, "hdr_size=%u ", msg->entry.hdr_size);
+    fprintf(stderr, "pid=%u tid=%u %u.%09u ", msg->entry.pid, msg->entry.tid, msg->entry.sec,
+            msg->entry.nsec);
+    lid = msg->entry.lid;
+
+    switch (lid) {
+        case 0:
+            fprintf(stderr, "lid=main ");
+            break;
+        case 1:
+            fprintf(stderr, "lid=radio ");
+            break;
+        case 2:
+            fprintf(stderr, "lid=events ");
+            break;
+        case 3:
+            fprintf(stderr, "lid=system ");
+            break;
+        case 4:
+            fprintf(stderr, "lid=crash ");
+            break;
+        case 5:
+            fprintf(stderr, "lid=security ");
+            break;
+        case 6:
+            fprintf(stderr, "lid=kernel ");
+            break;
+        default:
+            if (lid >= 0) {
+                fprintf(stderr, "lid=%d ", lid);
+            }
+    }
+
+    unsigned int len = msg->entry.len;
+    fprintf(stderr, "msg[%u]={", len);
+    unsigned char* cp = reinterpret_cast<unsigned char*>(msg->msg());
+    if (!cp) {
+        static const unsigned char garbage[] = "<INVALID>";
+        cp = const_cast<unsigned char*>(garbage);
+        len = strlen(reinterpret_cast<const char*>(garbage));
+    }
+    while (len) {
+        unsigned char* p = cp;
+        while (*p && (((' ' <= *p) && (*p < 0x7F)) || (*p == '\n'))) {
+            ++p;
+        }
+        if (((p - cp) > 3) && !*p && ((unsigned int)(p - cp) < len)) {
+            fprintf(stderr, "\"");
+            while (*cp) {
+                if (*cp != '\n') {
+                    fprintf(stderr, "%c", *cp);
+                } else {
+                    fprintf(stderr, "\\n");
+                }
+                ++cp;
+                --len;
+            }
+            fprintf(stderr, "\"");
+        } else {
+            fprintf(stderr, "%02x", *cp);
+        }
+        ++cp;
+        if (--len) {
+            fprintf(stderr, ", ");
+        }
+    }
+    fprintf(stderr, "}\n");
+    fflush(stderr);
+}
+#endif
+
+#ifdef __ANDROID__
+// BAD ROBOT
+//   Benchmark threshold are generally considered bad form unless there is
+//   is some human love applied to the continued maintenance and whether the
+//   thresholds are tuned on a per-target basis. Here we check if the values
+//   are more than double what is expected. Doubling will not prevent failure
+//   on busy or low-end systems that could have a tendency to stretch values.
+//
+//   The primary goal of this test is to simulate a spammy app (benchmark
+//   being the worst) and check to make sure the logger can deal with it
+//   appropriately by checking all the statistics are in an expected range.
+//
+TEST(logd, benchmark) {
+    size_t len;
+    char* buf;
+
+    alloc_statistics(&buf, &len);
+    bool benchmark_already_run = buf && find_benchmark_spam(buf);
+    delete[] buf;
+
+    if (benchmark_already_run) {
+        fprintf(stderr,
+                "WARNING: spam already present and too much history\n"
+                "         false OK for prune by worst UID check\n");
+    }
+
+    FILE* fp;
+
+    // Introduce some extreme spam for the worst UID filter
+    ASSERT_TRUE(
+        nullptr !=
+        (fp = popen("/data/nativetest/liblog-benchmarks/liblog-benchmarks"
+                    " BM_log_maximum_retry"
+                    " BM_log_maximum"
+                    " BM_clock_overhead"
+                    " BM_log_print_overhead"
+                    " BM_log_latency"
+                    " BM_log_delay",
+                    "r")));
+
+    char buffer[5120];
+
+    static const char* benchmarks[] = {
+        "BM_log_maximum_retry ",  "BM_log_maximum ", "BM_clock_overhead ",
+        "BM_log_print_overhead ", "BM_log_latency ", "BM_log_delay "
+    };
+    static const unsigned int log_maximum_retry = 0;
+    static const unsigned int log_maximum = 1;
+    static const unsigned int clock_overhead = 2;
+    static const unsigned int log_print_overhead = 3;
+    static const unsigned int log_latency = 4;
+    static const unsigned int log_delay = 5;
+
+    unsigned long ns[arraysize(benchmarks)];
+
+    memset(ns, 0, sizeof(ns));
+
+    while (fgets(buffer, sizeof(buffer), fp)) {
+        for (unsigned i = 0; i < arraysize(ns); ++i) {
+            char* cp = strstr(buffer, benchmarks[i]);
+            if (!cp) {
+                continue;
+            }
+            sscanf(cp, "%*s %lu %lu", &ns[i], &ns[i]);
+            fprintf(stderr, "%-22s%8lu\n", benchmarks[i], ns[i]);
+        }
+    }
+    int ret = pclose(fp);
+
+    if (!WIFEXITED(ret) || (WEXITSTATUS(ret) == 127)) {
+        fprintf(stderr,
+                "WARNING: "
+                "/data/nativetest/liblog-benchmarks/liblog-benchmarks missing\n"
+                "         can not perform test\n");
+        return;
+    }
+
+    EXPECT_GE(200000UL, ns[log_maximum_retry]);  // 104734 user
+    EXPECT_NE(0UL, ns[log_maximum_retry]);       // failure to parse
+
+    EXPECT_GE(90000UL, ns[log_maximum]);  // 46913 user
+    EXPECT_NE(0UL, ns[log_maximum]);      // failure to parse
+
+    EXPECT_GE(4096UL, ns[clock_overhead]);  // 4095
+    EXPECT_NE(0UL, ns[clock_overhead]);     // failure to parse
+
+    EXPECT_GE(250000UL, ns[log_print_overhead]);  // 126886 user
+    EXPECT_NE(0UL, ns[log_print_overhead]);       // failure to parse
+
+    EXPECT_GE(10000000UL,
+              ns[log_latency]);  // 1453559 user space (background cgroup)
+    EXPECT_NE(0UL, ns[log_latency]);  // failure to parse
+
+    EXPECT_GE(20000000UL, ns[log_delay]);  // 10500289 user
+    EXPECT_NE(0UL, ns[log_delay]);         // failure to parse
+
+    alloc_statistics(&buf, &len);
+
+    bool collected_statistics = !!buf;
+    EXPECT_EQ(true, collected_statistics);
+
+    ASSERT_TRUE(nullptr != buf);
+
+    char* benchmark_statistics_found = find_benchmark_spam(buf);
+    ASSERT_TRUE(benchmark_statistics_found != nullptr);
+
+    // Check how effective the SPAM filter is, parse out Now size.
+    // 0     root                      54164 147569
+    //                                 ^-- benchmark_statistics_found
+
+    unsigned long nowSpamSize = atol(benchmark_statistics_found);
+
+    delete[] buf;
+
+    ASSERT_NE(0UL, nowSpamSize);
+
+    // Determine if we have the spam filter enabled
+    int sock = socket_local_client("logd", ANDROID_SOCKET_NAMESPACE_RESERVED,
+                                   SOCK_STREAM);
+
+    ASSERT_TRUE(sock >= 0);
+
+    static const char getPruneList[] = "getPruneList";
+    if (write(sock, getPruneList, sizeof(getPruneList)) > 0) {
+        char buffer[80];
+        memset(buffer, 0, sizeof(buffer));
+        read(sock, buffer, sizeof(buffer));
+        char* cp = strchr(buffer, '\n');
+        if (!cp || (cp[1] != '~') || (cp[2] != '!')) {
+            close(sock);
+            fprintf(stderr,
+                    "WARNING: "
+                    "Logger has SPAM filtration turned off \"%s\"\n",
+                    buffer);
+            return;
+        }
+    } else {
+        int save_errno = errno;
+        close(sock);
+        FAIL() << "Can not send " << getPruneList << " to logger -- "
+               << strerror(save_errno);
+    }
+
+    static const unsigned long expected_absolute_minimum_log_size = 65536UL;
+    unsigned long totalSize = expected_absolute_minimum_log_size;
+    static const char getSize[] = { 'g', 'e', 't', 'L', 'o', 'g',
+                                    'S', 'i', 'z', 'e', ' ', LOG_ID_MAIN + '0',
+                                    '\0' };
+    if (write(sock, getSize, sizeof(getSize)) > 0) {
+        char buffer[80];
+        memset(buffer, 0, sizeof(buffer));
+        read(sock, buffer, sizeof(buffer));
+        totalSize = atol(buffer);
+        if (totalSize < expected_absolute_minimum_log_size) {
+            fprintf(stderr,
+                    "WARNING: "
+                    "Logger had unexpected referenced size \"%s\"\n",
+                    buffer);
+            totalSize = expected_absolute_minimum_log_size;
+        }
+    }
+    close(sock);
+
+    // logd allows excursions to 110% of total size
+    totalSize = (totalSize * 11) / 10;
+
+    // 50% threshold for SPAM filter (<20% typical, lots of engineering margin)
+    ASSERT_GT(totalSize, nowSpamSize * 2);
+}
+#endif
+
+// b/26447386 confirm fixed
+void timeout_negative(const char* command) {
+#ifdef __ANDROID__
+    log_msg msg_wrap, msg_timeout;
+    bool content_wrap = false, content_timeout = false, written = false;
+    unsigned int alarm_wrap = 0, alarm_timeout = 0;
+    // A few tries to get it right just in case wrap kicks in due to
+    // content providers being active during the test.
+    int i = 3;
+
+    while (--i) {
+        int fd = socket_local_client("logdr", ANDROID_SOCKET_NAMESPACE_RESERVED,
+                                     SOCK_SEQPACKET);
+        ASSERT_LT(0, fd);
+
+        std::string ask(command);
+
+        struct sigaction ignore, old_sigaction;
+        memset(&ignore, 0, sizeof(ignore));
+        ignore.sa_handler = caught_signal;
+        sigemptyset(&ignore.sa_mask);
+        sigaction(SIGALRM, &ignore, &old_sigaction);
+        unsigned int old_alarm = alarm(3);
+
+        size_t len = ask.length() + 1;
+        written = write(fd, ask.c_str(), len) == (ssize_t)len;
+        if (!written) {
+            alarm(old_alarm);
+            sigaction(SIGALRM, &old_sigaction, nullptr);
+            close(fd);
+            continue;
+        }
+
+        // alarm triggers at 50% of the --wrap time out
+        content_wrap = recv(fd, msg_wrap.buf, sizeof(msg_wrap), 0) > 0;
+
+        alarm_wrap = alarm(5);
+
+        // alarm triggers at 133% of the --wrap time out
+        content_timeout = recv(fd, msg_timeout.buf, sizeof(msg_timeout), 0) > 0;
+        if (!content_timeout) {  // make sure we hit dumpAndClose
+            content_timeout =
+                recv(fd, msg_timeout.buf, sizeof(msg_timeout), 0) > 0;
+        }
+
+        if (old_alarm > 0) {
+            unsigned int time_spent = 3 - alarm_wrap;
+            if (old_alarm > time_spent + 1) {
+                old_alarm -= time_spent;
+            } else {
+                old_alarm = 2;
+            }
+        }
+        alarm_timeout = alarm(old_alarm);
+        sigaction(SIGALRM, &old_sigaction, nullptr);
+
+        close(fd);
+
+        if (content_wrap && alarm_wrap && content_timeout && alarm_timeout) {
+            break;
+        }
+    }
+
+    if (content_wrap) {
+        dump_log_msg("wrap", &msg_wrap, -1);
+    }
+
+    if (content_timeout) {
+        dump_log_msg("timeout", &msg_timeout, -1);
+    }
+
+    EXPECT_TRUE(written);
+    EXPECT_TRUE(content_wrap);
+    EXPECT_NE(0U, alarm_wrap);
+    EXPECT_TRUE(content_timeout);
+    EXPECT_NE(0U, alarm_timeout);
+#else
+    command = nullptr;
+    GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(logd, timeout_no_start) {
+    timeout_negative("dumpAndClose lids=0,1,2,3,4,5 timeout=6");
+}
+
+TEST(logd, timeout_start_epoch) {
+    timeout_negative(
+        "dumpAndClose lids=0,1,2,3,4,5 timeout=6 start=0.000000000");
+}
+
+#ifdef ENABLE_FLAKY_TESTS
+// b/26447386 refined behavior
+TEST(logd, timeout) {
+#ifdef __ANDROID__
+    // b/33962045 This test interferes with other log reader tests that
+    // follow because of file descriptor socket persistence in the same
+    // process.  So let's fork it to isolate it from giving us pain.
+
+    pid_t pid = fork();
+
+    if (pid) {
+        siginfo_t info = {};
+        ASSERT_EQ(0, TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED)));
+        ASSERT_EQ(0, info.si_status);
+        return;
+    }
+
+    log_msg msg_wrap, msg_timeout;
+    bool content_wrap = false, content_timeout = false, written = false;
+    unsigned int alarm_wrap = 0, alarm_timeout = 0;
+    // A few tries to get it right just in case wrap kicks in due to
+    // content providers being active during the test.
+    int i = 5;
+    log_time start(CLOCK_REALTIME);
+    start.tv_sec -= 30;  // reach back a moderate period of time
+
+    while (--i) {
+        int fd = socket_local_client("logdr", ANDROID_SOCKET_NAMESPACE_RESERVED,
+                                     SOCK_SEQPACKET);
+        int save_errno = errno;
+        if (fd < 0) {
+            fprintf(stderr, "failed to open /dev/socket/logdr %s\n",
+                    strerror(save_errno));
+            _exit(fd);
+        }
+
+        std::string ask = android::base::StringPrintf(
+            "dumpAndClose lids=0,1,2,3,4,5 timeout=6 start=%" PRIu32
+            ".%09" PRIu32,
+            start.tv_sec, start.tv_nsec);
+
+        struct sigaction ignore, old_sigaction;
+        memset(&ignore, 0, sizeof(ignore));
+        ignore.sa_handler = caught_signal;
+        sigemptyset(&ignore.sa_mask);
+        sigaction(SIGALRM, &ignore, &old_sigaction);
+        unsigned int old_alarm = alarm(3);
+
+        size_t len = ask.length() + 1;
+        written = write(fd, ask.c_str(), len) == (ssize_t)len;
+        if (!written) {
+            alarm(old_alarm);
+            sigaction(SIGALRM, &old_sigaction, nullptr);
+            close(fd);
+            continue;
+        }
+
+        // alarm triggers at 50% of the --wrap time out
+        content_wrap = recv(fd, msg_wrap.buf, sizeof(msg_wrap), 0) > 0;
+
+        alarm_wrap = alarm(5);
+
+        // alarm triggers at 133% of the --wrap time out
+        content_timeout = recv(fd, msg_timeout.buf, sizeof(msg_timeout), 0) > 0;
+        if (!content_timeout) {  // make sure we hit dumpAndClose
+            content_timeout =
+                recv(fd, msg_timeout.buf, sizeof(msg_timeout), 0) > 0;
+        }
+
+        if (old_alarm > 0) {
+            unsigned int time_spent = 3 - alarm_wrap;
+            if (old_alarm > time_spent + 1) {
+                old_alarm -= time_spent;
+            } else {
+                old_alarm = 2;
+            }
+        }
+        alarm_timeout = alarm(old_alarm);
+        sigaction(SIGALRM, &old_sigaction, nullptr);
+
+        close(fd);
+
+        if (!content_wrap && !alarm_wrap && content_timeout && alarm_timeout) {
+            break;
+        }
+
+        // modify start time in case content providers are relatively
+        // active _or_ inactive during the test.
+        if (content_timeout) {
+            log_time msg(msg_timeout.entry.sec, msg_timeout.entry.nsec);
+            if (msg < start) {
+                fprintf(stderr, "%u.%09u < %u.%09u\n", msg_timeout.entry.sec,
+                        msg_timeout.entry.nsec, (unsigned)start.tv_sec,
+                        (unsigned)start.tv_nsec);
+                _exit(-1);
+            }
+            if (msg > start) {
+                start = msg;
+                start.tv_sec += 30;
+                log_time now = log_time(CLOCK_REALTIME);
+                if (start > now) {
+                    start = now;
+                    --start.tv_sec;
+                }
+            }
+        } else {
+            start.tv_sec -= 120;  // inactive, reach further back!
+        }
+    }
+
+    if (content_wrap) {
+        dump_log_msg("wrap", &msg_wrap, -1);
+    }
+
+    if (content_timeout) {
+        dump_log_msg("timeout", &msg_timeout, -1);
+    }
+
+    if (content_wrap || !content_timeout) {
+        fprintf(stderr, "start=%" PRIu32 ".%09" PRIu32 "\n", start.tv_sec,
+                start.tv_nsec);
+    }
+
+    EXPECT_TRUE(written);
+    EXPECT_FALSE(content_wrap);
+    EXPECT_EQ(0U, alarm_wrap);
+    EXPECT_TRUE(content_timeout);
+    EXPECT_NE(0U, alarm_timeout);
+
+    _exit(!written + content_wrap + alarm_wrap + !content_timeout +
+          !alarm_timeout);
+#else
+    GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+#endif
+
+#ifdef LOGD_ENABLE_FLAKY_TESTS
+// b/27242723 confirmed fixed
+TEST(logd, SNDTIMEO) {
+#ifdef __ANDROID__
+    static const unsigned sndtimeo =
+        LOGD_SNDTIMEO;  // <sigh> it has to be done!
+    static const unsigned sleep_time = sndtimeo + 3;
+    static const unsigned alarm_time = sleep_time + 5;
+
+    int fd;
+
+    ASSERT_TRUE(
+        (fd = socket_local_client("logdr", ANDROID_SOCKET_NAMESPACE_RESERVED,
+                                  SOCK_SEQPACKET)) > 0);
+
+    struct sigaction ignore, old_sigaction;
+    memset(&ignore, 0, sizeof(ignore));
+    ignore.sa_handler = caught_signal;
+    sigemptyset(&ignore.sa_mask);
+    sigaction(SIGALRM, &ignore, &old_sigaction);
+    unsigned int old_alarm = alarm(alarm_time);
+
+    static const char ask[] = "stream lids=0,1,2,3,4,5,6";  // all sources
+    bool reader_requested = write(fd, ask, sizeof(ask)) == sizeof(ask);
+    EXPECT_TRUE(reader_requested);
+
+    log_msg msg;
+    bool read_one = recv(fd, msg.buf, sizeof(msg), 0) > 0;
+
+    EXPECT_TRUE(read_one);
+    if (read_one) {
+        dump_log_msg("user", &msg, -1);
+    }
+
+    fprintf(stderr, "Sleep for >%d seconds logd SO_SNDTIMEO ...\n", sndtimeo);
+    sleep(sleep_time);
+
+    // flush will block if we did not trigger. if it did, last entry returns 0
+    int recv_ret;
+    do {
+        recv_ret = recv(fd, msg.buf, sizeof(msg), 0);
+    } while (recv_ret > 0);
+    int save_errno = (recv_ret < 0) ? errno : 0;
+
+    EXPECT_NE(0U, alarm(old_alarm));
+    sigaction(SIGALRM, &old_sigaction, nullptr);
+
+    EXPECT_EQ(0, recv_ret);
+    if (recv_ret > 0) {
+        dump_log_msg("user", &msg, -1);
+    }
+    EXPECT_EQ(0, save_errno);
+
+    close(fd);
+#else
+    GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+#endif
+
+TEST(logd, getEventTag_list) {
+#ifdef __ANDROID__
+    char buffer[256];
+    memset(buffer, 0, sizeof(buffer));
+    snprintf(buffer, sizeof(buffer), "getEventTag name=*");
+    send_to_control(buffer, sizeof(buffer));
+    buffer[sizeof(buffer) - 1] = '\0';
+    char* cp;
+    long ret = strtol(buffer, &cp, 10);
+    EXPECT_GT(ret, 4096);
+#else
+    GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(logd, getEventTag_42) {
+#ifdef __ANDROID__
+    char buffer[256];
+    memset(buffer, 0, sizeof(buffer));
+    snprintf(buffer, sizeof(buffer), "getEventTag id=42");
+    send_to_control(buffer, sizeof(buffer));
+    buffer[sizeof(buffer) - 1] = '\0';
+    char* cp;
+    long ret = strtol(buffer, &cp, 10);
+    EXPECT_GT(ret, 16);
+    EXPECT_TRUE(strstr(buffer, "\t(to life the universe etc|3)") != nullptr);
+    EXPECT_TRUE(strstr(buffer, "answer") != nullptr);
+#else
+    GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(logd, getEventTag_newentry) {
+#ifdef __ANDROID__
+    char buffer[256];
+    memset(buffer, 0, sizeof(buffer));
+    log_time now(CLOCK_MONOTONIC);
+    char name[64];
+    snprintf(name, sizeof(name), "a%" PRIu64, now.nsec());
+    snprintf(buffer, sizeof(buffer), "getEventTag name=%s format=\"(new|1)\"",
+             name);
+    send_to_control(buffer, sizeof(buffer));
+    buffer[sizeof(buffer) - 1] = '\0';
+    char* cp;
+    long ret = strtol(buffer, &cp, 10);
+    EXPECT_GT(ret, 16);
+    EXPECT_TRUE(strstr(buffer, "\t(new|1)") != nullptr);
+    EXPECT_TRUE(strstr(buffer, name) != nullptr);
+// ToDo: also look for this in /data/misc/logd/event-log-tags and
+// /dev/event-log-tags.
+#else
+    GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
+
+TEST(logd, no_epipe) {
+#ifdef __ANDROID__
+    // Actually generating SIGPIPE in logd is racy, since we need to close the socket quicker than
+    // logd finishes writing the data to it, so we try 10 times, which should be enough to trigger
+    // SIGPIPE if logd isn't ignoring SIGPIPE
+    for (int i = 0; i < 10; ++i) {
+        unique_fd sock1(
+                socket_local_client("logd", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM));
+        ASSERT_GT(sock1, 0);
+        unique_fd sock2(
+                socket_local_client("logd", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM));
+        ASSERT_GT(sock2, 0);
+
+        std::string message = "getStatistics 0 1 2 3 4 5 6 7";
+
+        ASSERT_GT(write(sock1, message.c_str(), message.length()), 0);
+        sock1.reset();
+        ASSERT_GT(write(sock2, message.c_str(), message.length()), 0);
+
+        struct pollfd p = {.fd = sock2, .events = POLLIN, .revents = 0};
+
+        int ret = poll(&p, 1, 20);
+        EXPECT_EQ(ret, 1);
+        EXPECT_TRUE(p.revents & POLLIN);
+        EXPECT_FALSE(p.revents & POLL_ERR);
+    }
+#else
+    GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
diff --git a/logd/logpersist b/logd/logpersist
deleted file mode 100755
index 215e1e2..0000000
--- a/logd/logpersist
+++ /dev/null
@@ -1,36 +0,0 @@
-#! /system/bin/sh
-# logpersist cat start and stop handlers
-data=/data/misc/logd
-property=persist.logd.logpersistd
-service=logcatd
-progname="${0##*/}"
-if [ X"${1}" = "-h" -o X"${1}" = X"--help" ]; then
-  echo "${progname%.*}.cat            - dump current ${service%d} logs"
-  echo "${progname%.*}.start          - start ${service} service"
-  echo "${progname%.*}.stop [--clear] - stop ${service} service"
-  exit 0
-fi
-case ${progname} in
-*.cat)
-  su 1036 ls "${data}" |
-  tr -d '\r' |
-  sort -ru |
-  sed "s#^#${data}/#" |
-  su 1036 xargs cat
-  ;;
-*.start)
-  su 0 setprop ${property} ${service}
-  getprop ${property}
-  sleep 1
-  ps -t | grep "${data##*/}.*${service%d}"
-  ;;
-*.stop)
-  su 0 stop ${service}
-  su 0 setprop ${property} ""
-  [ X"${1}" != X"-c" -a X"${1}" != X"--clear" ] ||
-  ( sleep 1 ; su 1036,9998 rm -rf "${data}" )
-  ;;
-*)
-  echo "Unexpected command ${0##*/} ${@}" >&2
-  exit 1
-esac
diff --git a/logd/logtagd.rc b/logd/logtagd.rc
new file mode 100644
index 0000000..248a78c
--- /dev/null
+++ b/logd/logtagd.rc
@@ -0,0 +1,9 @@
+#
+# logtagd event log tag service (debug only)
+#
+on post-fs-data
+    mkdir /data/misc/logd 0750 logd log
+    write /data/misc/logd/event-log-tags ""
+    chown logd log /data/misc/logd/event-log-tags
+    chmod 0600 /data/misc/logd/event-log-tags
+    restorecon /data/misc/logd/event-log-tags
diff --git a/logd/main.cpp b/logd/main.cpp
index a3241d0..c92c5b7 100644
--- a/logd/main.cpp
+++ b/logd/main.cpp
@@ -17,6 +17,7 @@
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <linux/capability.h>
 #include <poll.h>
 #include <sched.h>
 #include <semaphore.h>
@@ -35,266 +36,132 @@
 
 #include <memory>
 
-#include <cutils/properties.h>
-#include <cutils/sched_policy.h>
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <cutils/android_get_control_file.h>
 #include <cutils/sockets.h>
 #include <log/event_tag_map.h>
+#include <packagelistparser/packagelistparser.h>
 #include <private/android_filesystem_config.h>
+#include <private/android_logger.h>
+#include <processgroup/sched_policy.h>
 #include <utils/threads.h>
 
+#include "ChattyLogBuffer.h"
 #include "CommandListener.h"
-#include "LogBuffer.h"
-#include "LogListener.h"
 #include "LogAudit.h"
+#include "LogBuffer.h"
 #include "LogKlog.h"
+#include "LogListener.h"
+#include "LogReader.h"
+#include "LogStatistics.h"
+#include "LogTags.h"
+#include "LogUtils.h"
+#include "SerializedLogBuffer.h"
+#include "SimpleLogBuffer.h"
 
-#define KMSG_PRIORITY(PRI)                            \
-    '<',                                              \
-    '0' + LOG_MAKEPRI(LOG_DAEMON, LOG_PRI(PRI)) / 10, \
-    '0' + LOG_MAKEPRI(LOG_DAEMON, LOG_PRI(PRI)) % 10, \
-    '>'
+using android::base::GetBoolProperty;
+using android::base::GetProperty;
+using android::base::SetProperty;
 
-//
-//  The service is designed to be run by init, it does not respond well
-// to starting up manually. When starting up manually the sockets will
-// fail to open typically for one of the following reasons:
-//     EADDRINUSE if logger is running.
-//     EACCESS if started without precautions (below)
-//
-// Here is a cookbook procedure for starting up logd manually assuming
-// init is out of the way, pedantically all permissions and selinux
-// security is put back in place:
-//
-//    setenforce 0
-//    rm /dev/socket/logd*
-//    chmod 777 /dev/socket
-//        # here is where you would attach the debugger or valgrind for example
-//    runcon u:r:logd:s0 /system/bin/logd </dev/null >/dev/null 2>&1 &
-//    sleep 1
-//    chmod 755 /dev/socket
-//    chown logd.logd /dev/socket/logd*
-//    restorecon /dev/socket/logd*
-//    setenforce 1
-//
-// If minimalism prevails, typical for debugging and security is not a concern:
-//
-//    setenforce 0
-//    chmod 777 /dev/socket
-//    logd
-//
+#define KMSG_PRIORITY(PRI)                                 \
+    '<', '0' + LOG_MAKEPRI(LOG_DAEMON, LOG_PRI(PRI)) / 10, \
+        '0' + LOG_MAKEPRI(LOG_DAEMON, LOG_PRI(PRI)) % 10, '>'
 
-static int drop_privs() {
-    struct sched_param param;
-    memset(&param, 0, sizeof(param));
-
+// The service is designed to be run by init, it does not respond well to starting up manually. Init
+// has a 'sigstop' feature that sends SIGSTOP to a service immediately before calling exec().  This
+// allows debuggers, etc to be attached to logd at the very beginning, while still having init
+// handle the user, groups, capabilities, files, etc setup.
+static void DropPrivs(bool klogd, bool auditd) {
     if (set_sched_policy(0, SP_BACKGROUND) < 0) {
-        return -1;
+        PLOG(FATAL) << "failed to set background scheduling policy";
     }
 
-    if (sched_setscheduler((pid_t) 0, SCHED_BATCH, &param) < 0) {
-        return -1;
+    sched_param param = {};
+    if (sched_setscheduler((pid_t)0, SCHED_BATCH, &param) < 0) {
+        PLOG(FATAL) << "failed to set batch scheduler";
     }
 
-    if (setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND) < 0) {
-        return -1;
+    if (!GetBoolProperty("ro.debuggable", false)) {
+        if (prctl(PR_SET_DUMPABLE, 0) == -1) {
+            PLOG(FATAL) << "failed to clear PR_SET_DUMPABLE";
+        }
     }
 
-    if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
-        return -1;
+    std::unique_ptr<struct _cap_struct, int (*)(void*)> caps(cap_init(), cap_free);
+    if (cap_clear(caps.get()) < 0) {
+        PLOG(FATAL) << "cap_clear() failed";
     }
-
-    if (setgroups(0, NULL) == -1) {
-        return -1;
+    if (klogd) {
+        cap_value_t cap_syslog = CAP_SYSLOG;
+        if (cap_set_flag(caps.get(), CAP_PERMITTED, 1, &cap_syslog, CAP_SET) < 0 ||
+            cap_set_flag(caps.get(), CAP_EFFECTIVE, 1, &cap_syslog, CAP_SET) < 0) {
+            PLOG(FATAL) << "Failed to set CAP_SYSLOG";
+        }
     }
-
-    if (setgid(AID_LOGD) != 0) {
-        return -1;
+    if (auditd) {
+        cap_value_t cap_audit_control = CAP_AUDIT_CONTROL;
+        if (cap_set_flag(caps.get(), CAP_PERMITTED, 1, &cap_audit_control, CAP_SET) < 0 ||
+            cap_set_flag(caps.get(), CAP_EFFECTIVE, 1, &cap_audit_control, CAP_SET) < 0) {
+            PLOG(FATAL) << "Failed to set CAP_AUDIT_CONTROL";
+        }
     }
-
-    if (setuid(AID_LOGD) != 0) {
-        return -1;
+    if (cap_set_proc(caps.get()) < 0) {
+        PLOG(FATAL) << "cap_set_proc() failed";
     }
-
-    struct __user_cap_header_struct capheader;
-    struct __user_cap_data_struct capdata[2];
-    memset(&capheader, 0, sizeof(capheader));
-    memset(&capdata, 0, sizeof(capdata));
-    capheader.version = _LINUX_CAPABILITY_VERSION_3;
-    capheader.pid = 0;
-
-    capdata[CAP_TO_INDEX(CAP_SYSLOG)].permitted = CAP_TO_MASK(CAP_SYSLOG);
-    capdata[CAP_TO_INDEX(CAP_AUDIT_CONTROL)].permitted |= CAP_TO_MASK(CAP_AUDIT_CONTROL);
-
-    capdata[0].effective = capdata[0].permitted;
-    capdata[1].effective = capdata[1].permitted;
-    capdata[0].inheritable = 0;
-    capdata[1].inheritable = 0;
-
-    if (capset(&capheader, &capdata[0]) < 0) {
-        return -1;
-    }
-
-    return 0;
 }
 
-// Property helper
-static bool property_get_bool(const char *key, bool def) {
-    char property[PROPERTY_VALUE_MAX];
-    property_get(key, property, "");
+// GetBoolProperty that defaults to true if `ro.debuggable == true && ro.config.low_rawm == false`.
+static bool GetBoolPropertyEngSvelteDefault(const std::string& name) {
+    bool default_value =
+            GetBoolProperty("ro.debuggable", false) && !GetBoolProperty("ro.config.low_ram", false);
 
-    if (!strcasecmp(property, "true")) {
-        return true;
-    }
-    if (!strcasecmp(property, "false")) {
-        return false;
-    }
-
-    return def;
+    return GetBoolProperty(name, default_value);
 }
 
-// Remove the static, and use this variable
-// globally for debugging if necessary. eg:
-//   write(fdDmesg, "I am here\n", 10);
-static int fdDmesg = -1;
+char* android::uidToName(uid_t u) {
+    struct Userdata {
+        uid_t uid;
+        char* name;
+    } userdata = {
+            .uid = u,
+            .name = nullptr,
+    };
 
-static sem_t uidName;
-static uid_t uid;
-static char *name;
-
-static sem_t reinit;
-static bool reinit_running = false;
-static LogBuffer *logBuf = NULL;
-
-static void *reinit_thread_start(void * /*obj*/) {
-    prctl(PR_SET_NAME, "logd.daemon");
-    set_sched_policy(0, SP_BACKGROUND);
-    setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND);
-
-    setgid(AID_SYSTEM);
-    setuid(AID_SYSTEM);
-
-    while (reinit_running && !sem_wait(&reinit) && reinit_running) {
-
-        // uidToName Privileged Worker
-        if (uid) {
-            name = NULL;
-
-            FILE *fp = fopen("/data/system/packages.list", "r");
-            if (fp) {
-                // This simple parser is sensitive to format changes in
-                // frameworks/base/services/core/java/com/android/server/pm/Settings.java
-                // A dependency note has been added to that file to correct
-                // this parser.
-
-                char *buffer = NULL;
-                size_t len;
-                while (getline(&buffer, &len, fp) > 0) {
-                    char *userId = strchr(buffer, ' ');
-                    if (!userId) {
-                        continue;
-                    }
-                    *userId = '\0';
-                    unsigned long value = strtoul(userId + 1, NULL, 10);
-                    if (value != uid) {
-                        continue;
-                    }
-                    name = strdup(buffer);
-                    break;
+    packagelist_parse(
+            [](pkg_info* info, void* callback_parameter) {
+                auto userdata = reinterpret_cast<Userdata*>(callback_parameter);
+                bool result = true;
+                if (info->uid == userdata->uid) {
+                    userdata->name = strdup(info->name);
+                    // false to stop processing
+                    result = false;
                 }
-                free(buffer);
-                fclose(fp);
-            }
-            uid = 0;
-            sem_post(&uidName);
-            continue;
-        }
+                packagelist_free(info);
+                return result;
+            },
+            &userdata);
 
-        if (fdDmesg >= 0) {
-            static const char reinit_message[] = { KMSG_PRIORITY(LOG_INFO),
-                'l', 'o', 'g', 'd', '.', 'd', 'a', 'e', 'm', 'o', 'n', ':',
-                ' ', 'r', 'e', 'i', 'n', 'i', 't', '\n' };
-            write(fdDmesg, reinit_message, sizeof(reinit_message));
-        }
-
-        // Anything that reads persist.<property>
-        if (logBuf) {
-            logBuf->init();
-        }
-    }
-
-    return NULL;
+    return userdata.name;
 }
 
-static sem_t sem_name;
-
-char *android::uidToName(uid_t u) {
-    if (!u || !reinit_running) {
-        return NULL;
-    }
-
-    sem_wait(&sem_name);
-
-    // Not multi-thread safe, we use sem_name to protect
-    uid = u;
-
-    name = NULL;
-    sem_post(&reinit);
-    sem_wait(&uidName);
-    char *ret = name;
-
-    sem_post(&sem_name);
-
-    return ret;
-}
-
-// Serves as a global method to trigger reinitialization
-// and as a function that can be provided to signal().
-void reinit_signal_handler(int /*signal*/) {
-    sem_post(&reinit);
-}
-
-// tagToName converts an events tag into a name
-const char *android::tagToName(uint32_t tag) {
-    static const EventTagMap *map;
-
-    if (!map) {
-        sem_wait(&sem_name);
-        if (!map) {
-            map = android_openEventTagMap(EVENT_TAG_MAP_FILE);
-        }
-        sem_post(&sem_name);
-        if (!map) {
-            return NULL;
-        }
-    }
-    return android_lookupEventTag(map, tag);
-}
-
-static bool property_get_bool_svelte(const char *key) {
-    bool not_user;
-    {
-        char property[PROPERTY_VALUE_MAX];
-        property_get("ro.build.type", property, "");
-        not_user = !!strcmp(property, "user");
-    }
-    return property_get_bool(key, not_user
-            && !property_get_bool("ro.config.low_ram", false));
-}
-
-static void readDmesg(LogAudit *al, LogKlog *kl) {
+static void readDmesg(LogAudit* al, LogKlog* kl) {
     if (!al && !kl) {
         return;
     }
 
-    int len = klogctl(KLOG_SIZE_BUFFER, NULL, 0);
-    if (len <= 0) {
+    int rc = klogctl(KLOG_SIZE_BUFFER, nullptr, 0);
+    if (rc <= 0) {
         return;
     }
 
-    len += 1024; // Margin for additional input race or trailing nul
-    std::unique_ptr<char []> buf(new char[len]);
+    // Margin for additional input race or trailing nul
+    ssize_t len = rc + 1024;
+    std::unique_ptr<char[]> buf(new char[len]);
 
-    int rc = klogctl(KLOG_READ_ALL, buf.get(), len);
+    rc = klogctl(KLOG_READ_ALL, buf.get(), len);
     if (rc <= 0) {
         return;
     }
@@ -302,163 +169,168 @@
     if (rc < len) {
         len = rc + 1;
     }
-    buf[len - 1] = '\0';
+    buf[--len] = '\0';
 
-    if (kl) {
-        kl->synchronize(buf.get());
-    }
-
-    for (char *ptr = NULL, *tok = buf.get();
-         (rc >= 0) && ((tok = log_strtok_r(tok, &ptr)));
-         tok = NULL) {
+    ssize_t sublen;
+    for (char *ptr = nullptr, *tok = buf.get();
+         (rc >= 0) && !!(tok = android::log_strntok_r(tok, len, ptr, sublen));
+         tok = nullptr) {
+        if ((sublen <= 0) || !*tok) continue;
         if (al) {
-            rc = al->log(tok);
+            rc = al->log(tok, sublen);
         }
         if (kl) {
-            rc = kl->log(tok);
+            rc = kl->log(tok, sublen);
         }
     }
 }
 
+static int issueReinit() {
+    int sock = TEMP_FAILURE_RETRY(socket_local_client(
+        "logd", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM));
+    if (sock < 0) return -errno;
+
+    static const char reinitStr[] = "reinit";
+    ssize_t ret = TEMP_FAILURE_RETRY(write(sock, reinitStr, sizeof(reinitStr)));
+    if (ret < 0) return -errno;
+
+    struct pollfd p;
+    memset(&p, 0, sizeof(p));
+    p.fd = sock;
+    p.events = POLLIN;
+    ret = TEMP_FAILURE_RETRY(poll(&p, 1, 1000));
+    if (ret < 0) return -errno;
+    if ((ret == 0) || !(p.revents & POLLIN)) return -ETIME;
+
+    static const char success[] = "success";
+    char buffer[sizeof(success) - 1];
+    memset(buffer, 0, sizeof(buffer));
+    ret = TEMP_FAILURE_RETRY(read(sock, buffer, sizeof(buffer)));
+    if (ret < 0) return -errno;
+
+    return strncmp(buffer, success, sizeof(success) - 1) != 0;
+}
+
 // Foreground waits for exit of the main persistent threads
 // that are started here. The threads are created to manage
 // UNIX domain client sockets for writing, reading and
 // controlling the user space logger, and for any additional
 // logging plugins like auditd and restart control. Additional
 // transitory per-client threads are created for each reader.
-int main(int argc, char *argv[]) {
-    int fdPmesg = -1;
-    bool klogd = property_get_bool_svelte("logd.klogd");
-    if (klogd) {
-        fdPmesg = open("/proc/kmsg", O_RDONLY | O_NDELAY);
-    }
-    fdDmesg = open("/dev/kmsg", O_WRONLY);
-
+int main(int argc, char* argv[]) {
+    // We want EPIPE when a reader disconnects, not to terminate logd.
+    signal(SIGPIPE, SIG_IGN);
+    // logd is written under the assumption that the timezone is UTC.
+    // If TZ is not set, persist.sys.timezone is looked up in some time utility
+    // libc functions, including mktime. It confuses the logd time handling,
+    // so here explicitly set TZ to UTC, which overrides the property.
+    setenv("TZ", "UTC", 1);
     // issue reinit command. KISS argument parsing.
     if ((argc > 1) && argv[1] && !strcmp(argv[1], "--reinit")) {
-        int sock = TEMP_FAILURE_RETRY(
-            socket_local_client("logd",
-                                ANDROID_SOCKET_NAMESPACE_RESERVED,
-                                SOCK_STREAM));
-        if (sock < 0) {
-            return -errno;
-        }
-        static const char reinit[] = "reinit";
-        ssize_t ret = TEMP_FAILURE_RETRY(write(sock, reinit, sizeof(reinit)));
-        if (ret < 0) {
-            return -errno;
-        }
-        struct pollfd p;
-        memset(&p, 0, sizeof(p));
-        p.fd = sock;
-        p.events = POLLIN;
-        ret = TEMP_FAILURE_RETRY(poll(&p, 1, 100));
-        if (ret < 0) {
-            return -errno;
-        }
-        if ((ret == 0) || !(p.revents & POLLIN)) {
-            return -ETIME;
-        }
-        static const char success[] = "success";
-        char buffer[sizeof(success) - 1];
-        memset(buffer, 0, sizeof(buffer));
-        ret = TEMP_FAILURE_RETRY(read(sock, buffer, sizeof(buffer)));
-        if (ret < 0) {
-            return -errno;
-        }
-        return strncmp(buffer, success, sizeof(success) - 1) != 0;
+        return issueReinit();
     }
 
-    // Reinit Thread
-    sem_init(&reinit, 0, 0);
-    sem_init(&uidName, 0, 0);
-    sem_init(&sem_name, 0, 1);
-    pthread_attr_t attr;
-    if (!pthread_attr_init(&attr)) {
-        struct sched_param param;
+    android::base::InitLogging(
+            argv, [](android::base::LogId log_id, android::base::LogSeverity severity,
+                     const char* tag, const char* file, unsigned int line, const char* message) {
+                if (tag && strcmp(tag, "logd") != 0) {
+                    auto prefixed_message = android::base::StringPrintf("%s: %s", tag, message);
+                    android::base::KernelLogger(log_id, severity, "logd", file, line,
+                                                prefixed_message.c_str());
+                } else {
+                    android::base::KernelLogger(log_id, severity, "logd", file, line, message);
+                }
+            });
 
-        memset(&param, 0, sizeof(param));
-        pthread_attr_setschedparam(&attr, &param);
-        pthread_attr_setschedpolicy(&attr, SCHED_BATCH);
-        if (!pthread_attr_setdetachstate(&attr,
-                                         PTHREAD_CREATE_DETACHED)) {
-            pthread_t thread;
-            reinit_running = true;
-            if (pthread_create(&thread, &attr, reinit_thread_start, NULL)) {
-                reinit_running = false;
-            }
+    static const char dev_kmsg[] = "/dev/kmsg";
+    int fdDmesg = android_get_control_file(dev_kmsg);
+    if (fdDmesg < 0) {
+        fdDmesg = TEMP_FAILURE_RETRY(open(dev_kmsg, O_WRONLY | O_CLOEXEC));
+    }
+
+    int fdPmesg = -1;
+    bool klogd = GetBoolPropertyEngSvelteDefault("ro.logd.kernel");
+    if (klogd) {
+        SetProperty("ro.logd.kernel", "true");
+        static const char proc_kmsg[] = "/proc/kmsg";
+        fdPmesg = android_get_control_file(proc_kmsg);
+        if (fdPmesg < 0) {
+            fdPmesg = TEMP_FAILURE_RETRY(
+                open(proc_kmsg, O_RDONLY | O_NDELAY | O_CLOEXEC));
         }
-        pthread_attr_destroy(&attr);
+        if (fdPmesg < 0) PLOG(ERROR) << "Failed to open " << proc_kmsg;
     }
 
-    if (drop_privs() != 0) {
-        return -1;
-    }
+    bool auditd = GetBoolProperty("ro.logd.auditd", true);
+    DropPrivs(klogd, auditd);
 
-    // Serves the purpose of managing the last logs times read on a
-    // socket connection, and as a reader lock on a range of log
-    // entries.
+    // A cache of event log tags
+    LogTags log_tags;
 
-    LastLogTimes *times = new LastLogTimes();
+    // Pruning configuration.
+    PruneList prune_list;
 
-    // LogBuffer is the object which is responsible for holding all
-    // log entries.
+    std::string buffer_type = GetProperty("logd.buffer_type", "serialized");
 
-    logBuf = new LogBuffer(times);
+    // Partial (required for chatty) or full logging statistics.
+    LogStatistics log_statistics(GetBoolPropertyEngSvelteDefault("logd.statistics"),
+                                 buffer_type == "serialized");
 
-    signal(SIGHUP, reinit_signal_handler);
+    // Serves the purpose of managing the last logs times read on a socket connection, and as a
+    // reader lock on a range of log entries.
+    LogReaderList reader_list;
 
-    if (property_get_bool_svelte("logd.statistics")) {
-        logBuf->enableStatistics();
+    // LogBuffer is the object which is responsible for holding all log entries.
+    LogBuffer* log_buffer = nullptr;
+    if (buffer_type == "chatty") {
+        log_buffer = new ChattyLogBuffer(&reader_list, &log_tags, &prune_list, &log_statistics);
+    } else if (buffer_type == "serialized") {
+        log_buffer = new SerializedLogBuffer(&reader_list, &log_tags, &log_statistics);
+    } else if (buffer_type == "simple") {
+        log_buffer = new SimpleLogBuffer(&reader_list, &log_tags, &log_statistics);
+    } else {
+        LOG(FATAL) << "buffer_type must be one of 'chatty', 'serialized', or 'simple'";
     }
 
     // LogReader listens on /dev/socket/logdr. When a client
     // connects, log entries in the LogBuffer are written to the client.
-
-    LogReader *reader = new LogReader(logBuf);
+    LogReader* reader = new LogReader(log_buffer, &reader_list);
     if (reader->startListener()) {
-        exit(1);
+        return EXIT_FAILURE;
     }
 
     // LogListener listens on /dev/socket/logdw for client
     // initiated log messages. New log entries are added to LogBuffer
     // and LogReader is notified to send updates to connected clients.
-
-    LogListener *swl = new LogListener(logBuf, reader);
-    // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value
-    if (swl->startListener(300)) {
-        exit(1);
+    LogListener* swl = new LogListener(log_buffer);
+    if (!swl->StartListener()) {
+        return EXIT_FAILURE;
     }
 
     // Command listener listens on /dev/socket/logd for incoming logd
     // administrative commands.
-
-    CommandListener *cl = new CommandListener(logBuf, reader, swl);
+    CommandListener* cl = new CommandListener(log_buffer, &log_tags, &prune_list, &log_statistics);
     if (cl->startListener()) {
-        exit(1);
+        return EXIT_FAILURE;
     }
 
     // LogAudit listens on NETLINK_AUDIT socket for selinux
     // initiated log messages. New log entries are added to LogBuffer
     // and LogReader is notified to send updates to connected clients.
-
-    bool auditd = property_get_bool("logd.auditd", true);
-
-    LogAudit *al = NULL;
+    LogAudit* al = nullptr;
     if (auditd) {
-        bool dmesg = property_get_bool("logd.auditd.dmesg", true);
-        al = new LogAudit(logBuf, reader, dmesg ? fdDmesg : -1);
+        int dmesg_fd = GetBoolProperty("ro.logd.auditd.dmesg", true) ? fdDmesg : -1;
+        al = new LogAudit(log_buffer, dmesg_fd, &log_statistics);
     }
 
-    LogKlog *kl = NULL;
+    LogKlog* kl = nullptr;
     if (klogd) {
-        kl = new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al != NULL);
+        kl = new LogKlog(log_buffer, fdDmesg, fdPmesg, al != nullptr, &log_statistics);
     }
 
     readDmesg(al, kl);
 
     // failure is an option ... messages are in dmesg (required by standard)
-
     if (kl && kl->startListener()) {
         delete kl;
     }
@@ -469,5 +341,5 @@
 
     TEMP_FAILURE_RETRY(pause());
 
-    exit(0);
+    return EXIT_SUCCESS;
 }
diff --git a/logd/tests/Android.mk b/logd/tests/Android.mk
deleted file mode 100644
index 85ca4ac..0000000
--- a/logd/tests/Android.mk
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-# -----------------------------------------------------------------------------
-# Benchmarks. (see ../../liblog/tests)
-# -----------------------------------------------------------------------------
-
-test_module_prefix := logd-
-test_tags := tests
-
-# -----------------------------------------------------------------------------
-# Unit tests.
-# -----------------------------------------------------------------------------
-
-test_c_flags := \
-    -fstack-protector-all \
-    -g \
-    -Wall -Wextra \
-    -Werror \
-    -fno-builtin \
-
-ifneq ($(TARGET_USES_LOGD),false)
-test_c_flags += -DTARGET_USES_LOGD=1
-endif
-
-test_src_files := \
-    logd_test.cpp
-
-# Build tests for the logger. Run with:
-#   adb shell /data/nativetest/logd-unit-tests/logd-unit-tests
-include $(CLEAR_VARS)
-LOCAL_MODULE := $(test_module_prefix)unit-tests
-LOCAL_MODULE_TAGS := $(test_tags)
-LOCAL_CFLAGS += $(test_c_flags)
-LOCAL_SHARED_LIBRARIES := libcutils
-LOCAL_SRC_FILES := $(test_src_files)
-include $(BUILD_NATIVE_TEST)
diff --git a/logd/tests/logd_test.cpp b/logd/tests/logd_test.cpp
deleted file mode 100644
index 3266360..0000000
--- a/logd/tests/logd_test.cpp
+++ /dev/null
@@ -1,535 +0,0 @@
-/*
- * 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 <fcntl.h>
-#include <poll.h>
-#include <signal.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <gtest/gtest.h>
-
-#include "cutils/sockets.h"
-#include "log/log.h"
-#include "log/logger.h"
-
-#define __unused __attribute__((__unused__))
-
-/*
- * returns statistics
- */
-static void my_android_logger_get_statistics(char *buf, size_t len)
-{
-    snprintf(buf, len, "getStatistics 0 1 2 3 4");
-    int sock = socket_local_client("logd",
-                                   ANDROID_SOCKET_NAMESPACE_RESERVED,
-                                   SOCK_STREAM);
-    if (sock >= 0) {
-        if (write(sock, buf, strlen(buf) + 1) > 0) {
-            ssize_t ret;
-            while ((ret = read(sock, buf, len)) > 0) {
-                if ((size_t)ret == len) {
-                    break;
-                }
-                len -= ret;
-                buf += ret;
-
-                struct pollfd p = {
-                    .fd = sock,
-                    .events = POLLIN,
-                    .revents = 0
-                };
-
-                ret = poll(&p, 1, 20);
-                if ((ret <= 0) || !(p.revents & POLLIN)) {
-                    break;
-                }
-            }
-        }
-        close(sock);
-    }
-}
-
-static void alloc_statistics(char **buffer, size_t *length)
-{
-    size_t len = 8192;
-    char *buf;
-
-    for(int retry = 32; (retry >= 0); delete [] buf, --retry) {
-        buf = new char [len];
-        my_android_logger_get_statistics(buf, len);
-
-        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;
-        }
-        len += len / 8; // allow for some slop
-    }
-    *buffer = buf;
-    *length = len;
-}
-
-static char *find_benchmark_spam(char *cp)
-{
-    // liblog_benchmarks has been run designed to SPAM.  The signature of
-    // a noisiest UID statistics is:
-    //
-    // Chattiest UIDs in main log buffer:                           Size Pruned
-    // UID   PACKAGE                                                BYTES LINES
-    // 0     root                                                  54164 147569
-    //
-    char *benchmark = NULL;
-    do {
-        static const char signature[] = "\n0     root ";
-
-        benchmark = strstr(cp, signature);
-        if (!benchmark) {
-            break;
-        }
-        cp = benchmark + sizeof(signature);
-        while (isspace(*cp)) {
-            ++cp;
-        }
-        benchmark = cp;
-        while (isdigit(*cp)) {
-            ++cp;
-        }
-        while (isspace(*cp)) {
-            ++cp;
-        }
-        unsigned long value = 0;
-        while (isdigit(*cp)) {
-            value = value * 10ULL + *cp - '0';
-            ++cp;
-        }
-        if (value > 100000UL) {
-            break;
-        }
-        benchmark = NULL;
-    } while (*cp);
-    return benchmark;
-}
-
-TEST(logd, statistics) {
-    size_t len;
-    char *buf;
-
-    alloc_statistics(&buf, &len);
-
-#ifdef TARGET_USES_LOGD
-    ASSERT_TRUE(NULL != buf);
-#else
-    if (!buf) {
-        return;
-    }
-#endif
-
-    // 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;
-        }
-    }
-
-    fprintf(stderr, "%s", cp);
-
-    EXPECT_LT((size_t)64, strlen(cp));
-
-    EXPECT_EQ(0, truncated);
-
-#ifdef TARGET_USES_LOGD
-    char *main_logs = strstr(cp, "\nChattiest UIDs in main ");
-    EXPECT_TRUE(NULL != main_logs);
-
-    char *radio_logs = strstr(cp, "\nChattiest UIDs in radio ");
-    EXPECT_TRUE(NULL != radio_logs);
-
-    char *system_logs = strstr(cp, "\nChattiest UIDs in system ");
-    EXPECT_TRUE(NULL != system_logs);
-
-    char *events_logs = strstr(cp, "\nChattiest UIDs in events ");
-    EXPECT_TRUE(NULL != events_logs);
-#endif
-
-    delete [] buf;
-}
-
-static void caught_signal(int signum __unused) { }
-
-static void dump_log_msg(const char *prefix,
-                         log_msg *msg, unsigned int version, int lid) {
-    switch(msg->entry.hdr_size) {
-    case 0:
-        version = 1;
-        break;
-
-    case sizeof(msg->entry_v2):
-        if (version == 0) {
-            version = 2;
-        }
-        break;
-    }
-
-    fprintf(stderr, "%s: v%u[%u] ", prefix, version, msg->len());
-    if (version != 1) {
-        fprintf(stderr, "hdr_size=%u ", msg->entry.hdr_size);
-    }
-    fprintf(stderr, "pid=%u tid=%u %u.%09u ",
-            msg->entry.pid, msg->entry.tid, msg->entry.sec, msg->entry.nsec);
-    switch(version) {
-    case 1:
-         break;
-    case 2:
-        fprintf(stderr, "euid=%u ", msg->entry_v2.euid);
-        break;
-    case 3:
-    default:
-        lid = msg->entry.lid;
-        break;
-    }
-
-    switch(lid) {
-    case 0:
-        fprintf(stderr, "lid=main ");
-        break;
-    case 1:
-        fprintf(stderr, "lid=radio ");
-        break;
-    case 2:
-        fprintf(stderr, "lid=events ");
-        break;
-    case 3:
-        fprintf(stderr, "lid=system ");
-        break;
-    default:
-        if (lid >= 0) {
-            fprintf(stderr, "lid=%d ", lid);
-        }
-    }
-
-    unsigned int len = msg->entry.len;
-    fprintf(stderr, "msg[%u]={", len);
-    unsigned char *cp = reinterpret_cast<unsigned char *>(msg->msg());
-    while(len) {
-        unsigned char *p = cp;
-        while (*p && (((' ' <= *p) && (*p < 0x7F)) || (*p == '\n'))) {
-            ++p;
-        }
-        if (((p - cp) > 3) && !*p && ((unsigned int)(p - cp) < len)) {
-            fprintf(stderr, "\"");
-            while (*cp) {
-                if (*cp != '\n') {
-                    fprintf(stderr, "%c", *cp);
-                } else {
-                    fprintf(stderr, "\\n");
-                }
-                ++cp;
-                --len;
-            }
-            fprintf(stderr, "\"");
-        } else {
-            fprintf(stderr, "%02x", *cp);
-        }
-        ++cp;
-        if (--len) {
-            fprintf(stderr, ", ");
-        }
-    }
-    fprintf(stderr, "}\n");
-}
-
-TEST(logd, both) {
-    log_msg msg;
-
-    // check if we can read any logs from logd
-    bool user_logger_available = false;
-    bool user_logger_content = false;
-
-    int fd = socket_local_client("logdr",
-                                 ANDROID_SOCKET_NAMESPACE_RESERVED,
-                                 SOCK_SEQPACKET);
-    if (fd >= 0) {
-        struct sigaction ignore, old_sigaction;
-        memset(&ignore, 0, sizeof(ignore));
-        ignore.sa_handler = caught_signal;
-        sigemptyset(&ignore.sa_mask);
-        sigaction(SIGALRM, &ignore, &old_sigaction);
-        unsigned int old_alarm = alarm(10);
-
-        static const char ask[] = "dumpAndClose lids=0,1,2,3";
-        user_logger_available = write(fd, ask, sizeof(ask)) == sizeof(ask);
-
-        user_logger_content = recv(fd, msg.buf, sizeof(msg), 0) > 0;
-
-        if (user_logger_content) {
-            dump_log_msg("user", &msg, 3, -1);
-        }
-
-        alarm(old_alarm);
-        sigaction(SIGALRM, &old_sigaction, NULL);
-
-        close(fd);
-    }
-
-    // check if we can read any logs from kernel logger
-    bool kernel_logger_available = false;
-    bool kernel_logger_content = false;
-
-    static const char *loggers[] = {
-        "/dev/log/main",   "/dev/log_main",
-        "/dev/log/radio",  "/dev/log_radio",
-        "/dev/log/events", "/dev/log_events",
-        "/dev/log/system", "/dev/log_system",
-    };
-
-    for (unsigned int i = 0; i < (sizeof(loggers) / sizeof(loggers[0])); ++i) {
-        fd = open(loggers[i], O_RDONLY);
-        if (fd < 0) {
-            continue;
-        }
-        kernel_logger_available = true;
-        fcntl(fd, F_SETFL, O_RDONLY | O_NONBLOCK);
-        int result = TEMP_FAILURE_RETRY(read(fd, msg.buf, sizeof(msg)));
-        if (result > 0) {
-            kernel_logger_content = true;
-            dump_log_msg("kernel", &msg, 0, i / 2);
-        }
-        close(fd);
-    }
-
-    static const char yes[] = "\xE2\x9C\x93";
-    static const char no[] = "\xE2\x9c\x98";
-    fprintf(stderr,
-            "LOGGER  Available  Content\n"
-            "user    %-13s%s\n"
-            "kernel  %-13s%s\n"
-            " status %-11s%s\n",
-            (user_logger_available)   ? yes : no,
-            (user_logger_content)     ? yes : no,
-            (kernel_logger_available) ? yes : no,
-            (kernel_logger_content)   ? yes : no,
-            (user_logger_available && kernel_logger_available) ? "ERROR" : "ok",
-            (user_logger_content && kernel_logger_content) ? "ERROR" : "ok");
-
-    EXPECT_EQ(0, user_logger_available && kernel_logger_available);
-    EXPECT_EQ(0, !user_logger_available && !kernel_logger_available);
-    EXPECT_EQ(0, user_logger_content && kernel_logger_content);
-    EXPECT_EQ(0, !user_logger_content && !kernel_logger_content);
-}
-
-// BAD ROBOT
-//   Benchmark threshold are generally considered bad form unless there is
-//   is some human love applied to the continued maintenance and whether the
-//   thresholds are tuned on a per-target basis. Here we check if the values
-//   are more than double what is expected. Doubling will not prevent failure
-//   on busy or low-end systems that could have a tendency to stretch values.
-//
-//   The primary goal of this test is to simulate a spammy app (benchmark
-//   being the worst) and check to make sure the logger can deal with it
-//   appropriately by checking all the statistics are in an expected range.
-//
-TEST(logd, benchmark) {
-    size_t len;
-    char *buf;
-
-    alloc_statistics(&buf, &len);
-    bool benchmark_already_run = buf && find_benchmark_spam(buf);
-    delete [] buf;
-
-    if (benchmark_already_run) {
-        fprintf(stderr, "WARNING: spam already present and too much history\n"
-                        "         false OK for prune by worst UID check\n");
-    }
-
-    FILE *fp;
-
-    // Introduce some extreme spam for the worst UID filter
-    ASSERT_TRUE(NULL != (fp = popen(
-        "/data/nativetest/liblog-benchmarks/liblog-benchmarks",
-        "r")));
-
-    char buffer[5120];
-
-    static const char *benchmarks[] = {
-        "BM_log_maximum_retry ",
-        "BM_log_maximum ",
-        "BM_clock_overhead ",
-        "BM_log_overhead ",
-        "BM_log_latency ",
-        "BM_log_delay "
-    };
-    static const unsigned int log_maximum_retry = 0;
-    static const unsigned int log_maximum = 1;
-    static const unsigned int clock_overhead = 2;
-    static const unsigned int log_overhead = 3;
-    static const unsigned int log_latency = 4;
-    static const unsigned int log_delay = 5;
-
-    unsigned long ns[sizeof(benchmarks) / sizeof(benchmarks[0])];
-
-    memset(ns, 0, sizeof(ns));
-
-    while (fgets(buffer, sizeof(buffer), fp)) {
-        for (unsigned i = 0; i < sizeof(ns) / sizeof(ns[0]); ++i) {
-            char *cp = strstr(buffer, benchmarks[i]);
-            if (!cp) {
-                continue;
-            }
-            sscanf(cp, "%*s %lu %lu", &ns[i], &ns[i]);
-            fprintf(stderr, "%-22s%8lu\n", benchmarks[i], ns[i]);
-        }
-    }
-    int ret = pclose(fp);
-
-    if (!WIFEXITED(ret) || (WEXITSTATUS(ret) == 127)) {
-        fprintf(stderr,
-                "WARNING: "
-                "/data/nativetest/liblog-benchmarks/liblog-benchmarks missing\n"
-                "         can not perform test\n");
-        return;
-    }
-
-#ifdef TARGET_USES_LOGD
-    EXPECT_GE(200000UL, ns[log_maximum_retry]); // 104734 user
-#else
-    EXPECT_GE(10000UL, ns[log_maximum_retry]); // 5636 kernel
-#endif
-
-#ifdef TARGET_USES_LOGD
-    EXPECT_GE(90000UL, ns[log_maximum]); // 46913 user
-#else
-    EXPECT_GE(10000UL, ns[log_maximum]); // 5637 kernel
-#endif
-
-    EXPECT_GE(4096UL, ns[clock_overhead]); // 4095
-
-#ifdef TARGET_USES_LOGD
-    EXPECT_GE(250000UL, ns[log_overhead]); // 126886 user
-#else
-    EXPECT_GE(100000UL, ns[log_overhead]); // 50945 kernel
-#endif
-
-#ifdef TARGET_USES_LOGD
-    EXPECT_GE(10000UL, ns[log_latency]); // 5669 user space
-#else
-    EXPECT_GE(500000UL, ns[log_latency]); // 254200 kernel
-#endif
-
-#ifdef TARGET_USES_LOGD
-    EXPECT_GE(20000000UL, ns[log_delay]); // 10500289 user
-#else
-    EXPECT_GE(55000UL, ns[log_delay]); // 27341 kernel
-#endif
-
-    for (unsigned i = 0; i < sizeof(ns) / sizeof(ns[0]); ++i) {
-        EXPECT_NE(0UL, ns[i]);
-    }
-
-    alloc_statistics(&buf, &len);
-
-#ifdef TARGET_USES_LOGD
-    bool collected_statistics = !!buf;
-    EXPECT_EQ(true, collected_statistics);
-#else
-    if (!buf) {
-        return;
-    }
-#endif
-
-    ASSERT_TRUE(NULL != buf);
-
-    char *benchmark_statistics_found = find_benchmark_spam(buf);
-    ASSERT_TRUE(benchmark_statistics_found != NULL);
-
-    // Check how effective the SPAM filter is, parse out Now size.
-    // 0     root                      54164 147569
-    //                                 ^-- benchmark_statistics_found
-
-    unsigned long nowSpamSize = atol(benchmark_statistics_found);
-
-    delete [] buf;
-
-    ASSERT_NE(0UL, nowSpamSize);
-
-    // Determine if we have the spam filter enabled
-    int sock = socket_local_client("logd",
-                                   ANDROID_SOCKET_NAMESPACE_RESERVED,
-                                   SOCK_STREAM);
-
-    ASSERT_TRUE(sock >= 0);
-
-    static const char getPruneList[] = "getPruneList";
-    if (write(sock, getPruneList, sizeof(getPruneList)) > 0) {
-        char buffer[80];
-        memset(buffer, 0, sizeof(buffer));
-        read(sock, buffer, sizeof(buffer));
-        char *cp = strchr(buffer, '\n');
-        if (!cp || (cp[1] != '~') || (cp[2] != '!')) {
-            close(sock);
-            fprintf(stderr,
-                    "WARNING: "
-                    "Logger has SPAM filtration turned off \"%s\"\n", buffer);
-            return;
-        }
-    } else {
-        int save_errno = errno;
-        close(sock);
-        FAIL() << "Can not send " << getPruneList << " to logger -- " << strerror(save_errno);
-    }
-
-    static const unsigned long expected_absolute_minimum_log_size = 65536UL;
-    unsigned long totalSize = expected_absolute_minimum_log_size;
-    static const char getSize[] = {
-        'g', 'e', 't', 'L', 'o', 'g', 'S', 'i', 'z', 'e', ' ',
-        LOG_ID_MAIN + '0', '\0'
-    };
-    if (write(sock, getSize, sizeof(getSize)) > 0) {
-        char buffer[80];
-        memset(buffer, 0, sizeof(buffer));
-        read(sock, buffer, sizeof(buffer));
-        totalSize = atol(buffer);
-        if (totalSize < expected_absolute_minimum_log_size) {
-            fprintf(stderr,
-                    "WARNING: "
-                    "Logger had unexpected referenced size \"%s\"\n", buffer);
-            totalSize = expected_absolute_minimum_log_size;
-        }
-    }
-    close(sock);
-
-    // logd allows excursions to 110% of total size
-    totalSize = (totalSize * 11 ) / 10;
-
-    // 50% threshold for SPAM filter (<20% typical, lots of engineering margin)
-    ASSERT_GT(totalSize, nowSpamSize * 2);
-}
diff --git a/logwrapper/Android.bp b/logwrapper/Android.bp
new file mode 100644
index 0000000..8851a47
--- /dev/null
+++ b/logwrapper/Android.bp
@@ -0,0 +1,70 @@
+cc_defaults {
+    name: "logwrapper_defaults",
+    cflags: [
+        "-Werror",
+    ],
+}
+
+// ========================================================
+// Static and shared library
+// ========================================================
+
+cc_library {
+    name: "liblogwrap",
+    defaults: ["logwrapper_defaults"],
+    recovery_available: true,
+    srcs: ["logwrap.cpp"],
+    shared_libs: [
+        "libcutils",
+        "liblog",
+    ],
+    header_libs: ["libbase_headers"],
+    export_include_dirs: ["include"],
+    local_include_dirs: ["include"],
+}
+
+// ========================================================
+// Executable
+// ========================================================
+
+cc_defaults {
+    name: "logwrapper_common",
+    defaults: ["logwrapper_defaults"],
+    local_include_dirs: ["include"],
+    srcs: [
+        "logwrap.cpp",
+        "logwrapper.cpp",
+    ],
+    header_libs: ["libbase_headers"],
+    shared_libs: ["libcutils", "liblog"],
+}
+
+cc_binary {
+    name: "logwrapper",
+    defaults: ["logwrapper_common"],
+}
+
+cc_binary {
+    name: "logwrapper_vendor",
+    defaults: ["logwrapper_common"],
+    stem: "logwrapper",
+    vendor: true,
+}
+
+// ========================================================
+// Benchmark
+// ========================================================
+
+cc_benchmark {
+    name: "logwrap_fork_execvp_benchmark",
+    defaults: ["logwrapper_defaults"],
+    srcs: [
+        "logwrap_fork_execvp_benchmark.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "liblog",
+        "liblogwrap",
+    ],
+}
diff --git a/logwrapper/Android.mk b/logwrapper/Android.mk
deleted file mode 100644
index 61b4659..0000000
--- a/logwrapper/Android.mk
+++ /dev/null
@@ -1,37 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# ========================================================
-# Static library
-# ========================================================
-include $(CLEAR_VARS)
-LOCAL_MODULE := liblogwrap
-LOCAL_SRC_FILES := logwrap.c
-LOCAL_SHARED_LIBRARIES := libcutils liblog
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-LOCAL_CFLAGS := -Werror
-include $(BUILD_STATIC_LIBRARY)
-
-# ========================================================
-# Shared library
-# ========================================================
-include $(CLEAR_VARS)
-LOCAL_MODULE := liblogwrap
-LOCAL_SHARED_LIBRARIES := libcutils liblog
-LOCAL_WHOLE_STATIC_LIBRARIES := liblogwrap
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-LOCAL_CFLAGS := -Werror
-include $(BUILD_SHARED_LIBRARY)
-
-# ========================================================
-# Executable
-# ========================================================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES:= logwrapper.c
-LOCAL_MODULE := logwrapper
-LOCAL_STATIC_LIBRARIES := liblog liblogwrap libcutils
-LOCAL_CFLAGS := -Werror
-include $(BUILD_EXECUTABLE)
diff --git a/logwrapper/OWNERS b/logwrapper/OWNERS
new file mode 100644
index 0000000..babbe4d
--- /dev/null
+++ b/logwrapper/OWNERS
@@ -0,0 +1 @@
+tomcherry@google.com
diff --git a/logwrapper/include/logwrap/logwrap.h b/logwrapper/include/logwrap/logwrap.h
index 4307a30..cb40ee2 100644
--- a/logwrapper/include/logwrap/logwrap.h
+++ b/logwrapper/include/logwrap/logwrap.h
@@ -15,22 +15,11 @@
  * limitations under the License.
  */
 
-#ifndef __LIBS_LOGWRAP_H
-#define __LIBS_LOGWRAP_H
-
-#include <stdbool.h>
-
-__BEGIN_DECLS
+#pragma once
 
 /*
  * Run a command while logging its stdout and stderr
  *
- * WARNING: while this function is running it will clear all SIGCHLD handlers
- * if you rely on SIGCHLD in the caller there is a chance zombies will be
- * created if you're not calling waitpid after calling this. This function will
- * log a warning when it clears SIGCHLD for processes other than the child it
- * created.
- *
  * Arguments:
  *   argc:   the number of elements in argv
  *   argv:   an array of strings containing the command to be executed and its
@@ -39,10 +28,10 @@
  *   status: the equivalent child status as populated by wait(status). This
  *           value is only valid when logwrap successfully completes. If NULL
  *           the return value of the child will be the function's return value.
- *   ignore_int_quit: set to true if you want to completely ignore SIGINT and
- *           SIGQUIT while logwrap is running. This may force the end-user to
- *           send a signal twice to signal the caller (once for the child, and
- *           once for the caller)
+ *   forward_signals: set to true if you want to forward SIGINT, SIGQUIT, and
+ *           SIGHUP to the child process, while it is running.  You likely do
+ *           not need to use this; it is primarily for the logwrapper
+ *           executable itself.
  *   log_target: Specify where to log the output of the child, either LOG_NONE,
  *           LOG_ALOG (for the Android system log), LOG_KLOG (for the kernel
  *           log), or LOG_FILE (and you need to specify a pathname in the
@@ -62,26 +51,11 @@
  *
  */
 
-/* Values for the log_target parameter android_fork_execvp_ext() */
+/* Values for the log_target parameter logwrap_fork_execvp() */
 #define LOG_NONE        0
 #define LOG_ALOG        1
 #define LOG_KLOG        2
 #define LOG_FILE        4
 
-int android_fork_execvp_ext(int argc, char* argv[], int *status, bool ignore_int_quit,
-        int log_target, bool abbreviated, char *file_path);
-
-/* Similar to above, except abbreviated logging is not available, and if logwrap
- * is true, logging is to the Android system log, and if false, there is no
- * logging.
- */
-static inline int android_fork_execvp(int argc, char* argv[], int *status,
-                                     bool ignore_int_quit, bool logwrap)
-{
-    return android_fork_execvp_ext(argc, argv, status, ignore_int_quit,
-                                   (logwrap ? LOG_ALOG : LOG_NONE), false, NULL);
-}
-
-__END_DECLS
-
-#endif /* __LIBS_LOGWRAP_H */
+int logwrap_fork_execvp(int argc, const char* const* argv, int* status, bool forward_signals,
+                        int log_target, bool abbreviated, const char* file_path);
diff --git a/logwrapper/logwrap.c b/logwrapper/logwrap.cpp
similarity index 62%
rename from logwrapper/logwrap.c
rename to logwrapper/logwrap.cpp
index 44455d1..5a518bc 100644
--- a/logwrapper/logwrap.c
+++ b/logwrapper/logwrap.cpp
@@ -14,41 +14,44 @@
  * limitations under the License.
  */
 
-#include <string.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <poll.h>
-#include <sys/wait.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <libgen.h>
-#include <stdbool.h>
+#include <poll.h>
 #include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
 
-#include <logwrap/logwrap.h>
-#include "private/android_filesystem_config.h"
-#include "cutils/log.h"
+#include <algorithm>
+
+#include <android-base/macros.h>
 #include <cutils/klog.h>
-
-#define ARRAY_SIZE(x)   (sizeof(x) / sizeof(*(x)))
-#define MIN(a,b) (((a)<(b))?(a):(b))
+#include <log/log.h>
+#include <logwrap/logwrap.h>
 
 static pthread_mutex_t fd_mutex = PTHREAD_MUTEX_INITIALIZER;
+// Protected by fd_mutex.  These signals must be blocked while modifying as well.
+static pid_t child_pid;
+static struct sigaction old_int;
+static struct sigaction old_quit;
+static struct sigaction old_hup;
 
-#define ERROR(fmt, args...)                                                   \
-do {                                                                          \
-    fprintf(stderr, fmt, ## args);                                            \
-    ALOG(LOG_ERROR, "logwrapper", fmt, ## args);                              \
-} while(0)
+#define ERROR(fmt, args...)                         \
+    do {                                            \
+        fprintf(stderr, fmt, ##args);               \
+        ALOG(LOG_ERROR, "logwrapper", fmt, ##args); \
+    } while (0)
 
-#define FATAL_CHILD(fmt, args...)                                             \
-do {                                                                          \
-    ERROR(fmt, ## args);                                                      \
-    _exit(-1);                                                                \
-} while(0)
+#define FATAL_CHILD(fmt, args...) \
+    do {                          \
+        ERROR(fmt, ##args);       \
+        _exit(-1);                \
+    } while (0)
 
 #define MAX_KLOG_TAG 16
 
@@ -57,7 +60,7 @@
  */
 #define BEGINNING_BUF_SIZE 0x1000
 struct beginning_buf {
-    char *buf;
+    char* buf;
     size_t alloc_len;
     /* buf_size is the usable space, which is one less than the allocated size */
     size_t buf_size;
@@ -70,7 +73,7 @@
  */
 #define ENDING_BUF_SIZE 0x1000
 struct ending_buf {
-    char *buf;
+    char* buf;
     ssize_t alloc_len;
     /* buf_size is the usable space, which is one less than the allocated size */
     ssize_t buf_size;
@@ -80,7 +83,7 @@
     int write;
 };
 
- /* A structure to hold all the abbreviated buf data */
+/* A structure to hold all the abbreviated buf data */
 struct abbr_buf {
     struct beginning_buf b_buf;
     struct ending_buf e_buf;
@@ -91,19 +94,17 @@
 struct log_info {
     int log_target;
     char klog_fmt[MAX_KLOG_TAG * 2];
-    char *btag;
+    const char* btag;
     bool abbreviated;
-    FILE *fp;
+    FILE* fp;
     struct abbr_buf a_buf;
 };
 
 /* Forware declaration */
-static void add_line_to_abbr_buf(struct abbr_buf *a_buf, char *linebuf, int linelen);
+static void add_line_to_abbr_buf(struct abbr_buf* a_buf, char* linebuf, int linelen);
 
 /* Return 0 on success, and 1 when full */
-static int add_line_to_linear_buf(struct beginning_buf *b_buf,
-                                   char *line, ssize_t line_len)
-{
+static int add_line_to_linear_buf(struct beginning_buf* b_buf, char* line, ssize_t line_len) {
     int full = 0;
 
     if ((line_len + b_buf->used_len) > b_buf->buf_size) {
@@ -117,20 +118,18 @@
     return full;
 }
 
-static void add_line_to_circular_buf(struct ending_buf *e_buf,
-                                     char *line, ssize_t line_len)
-{
+static void add_line_to_circular_buf(struct ending_buf* e_buf, char* line, ssize_t line_len) {
     ssize_t free_len;
     ssize_t needed_space;
     int cnt;
 
-    if (e_buf->buf == NULL) {
+    if (e_buf->buf == nullptr) {
         return;
     }
 
-   if (line_len > e_buf->buf_size) {
-       return;
-   }
+    if (line_len > e_buf->buf_size) {
+        return;
+    }
 
     free_len = e_buf->buf_size - e_buf->used_len;
 
@@ -145,7 +144,7 @@
     /* Copy the line into the circular buffer, dealing with possible
      * wraparound.
      */
-    cnt = MIN(line_len, e_buf->buf_size - e_buf->write);
+    cnt = std::min(line_len, e_buf->buf_size - e_buf->write);
     memcpy(e_buf->buf + e_buf->write, line, cnt);
     if (cnt < line_len) {
         memcpy(e_buf->buf, line + cnt, line_len - cnt);
@@ -155,7 +154,7 @@
 }
 
 /* Log directly to the specified log */
-static void do_log_line(struct log_info *log_info, char *line) {
+static void do_log_line(struct log_info* log_info, const char* line) {
     if (log_info->log_target & LOG_KLOG) {
         klog_write(6, log_info->klog_fmt, line);
     }
@@ -170,7 +169,7 @@
 /* Log to either the abbreviated buf, or directly to the specified log
  * via do_log_line() above.
  */
-static void log_line(struct log_info *log_info, char *line, int len) {
+static void log_line(struct log_info* log_info, char* line, int len) {
     if (log_info->abbreviated) {
         add_line_to_abbr_buf(&log_info->a_buf, line, len);
     } else {
@@ -185,9 +184,8 @@
  * than buf_size (the usable size of the buffer) to make sure there is
  * room to temporarily stuff a null byte to terminate a line for logging.
  */
-static void print_buf_lines(struct log_info *log_info, char *buf, int buf_size)
-{
-    char *line_start;
+static void print_buf_lines(struct log_info* log_info, char* buf, int buf_size) {
+    char* line_start;
     char c;
     int i;
 
@@ -213,17 +211,17 @@
      */
 }
 
-static void init_abbr_buf(struct abbr_buf *a_buf) {
-    char *new_buf;
+static void init_abbr_buf(struct abbr_buf* a_buf) {
+    char* new_buf;
 
     memset(a_buf, 0, sizeof(struct abbr_buf));
-    new_buf = malloc(BEGINNING_BUF_SIZE);
+    new_buf = static_cast<char*>(malloc(BEGINNING_BUF_SIZE));
     if (new_buf) {
         a_buf->b_buf.buf = new_buf;
         a_buf->b_buf.alloc_len = BEGINNING_BUF_SIZE;
         a_buf->b_buf.buf_size = BEGINNING_BUF_SIZE - 1;
     }
-    new_buf = malloc(ENDING_BUF_SIZE);
+    new_buf = static_cast<char*>(malloc(ENDING_BUF_SIZE));
     if (new_buf) {
         a_buf->e_buf.buf = new_buf;
         a_buf->e_buf.alloc_len = ENDING_BUF_SIZE;
@@ -231,23 +229,22 @@
     }
 }
 
-static void free_abbr_buf(struct abbr_buf *a_buf) {
+static void free_abbr_buf(struct abbr_buf* a_buf) {
     free(a_buf->b_buf.buf);
     free(a_buf->e_buf.buf);
 }
 
-static void add_line_to_abbr_buf(struct abbr_buf *a_buf, char *linebuf, int linelen) {
+static void add_line_to_abbr_buf(struct abbr_buf* a_buf, char* linebuf, int linelen) {
     if (!a_buf->beginning_buf_full) {
-        a_buf->beginning_buf_full =
-            add_line_to_linear_buf(&a_buf->b_buf, linebuf, linelen);
+        a_buf->beginning_buf_full = add_line_to_linear_buf(&a_buf->b_buf, linebuf, linelen);
     }
     if (a_buf->beginning_buf_full) {
         add_line_to_circular_buf(&a_buf->e_buf, linebuf, linelen);
     }
 }
 
-static void print_abbr_buf(struct log_info *log_info) {
-    struct abbr_buf *a_buf = &log_info->a_buf;
+static void print_abbr_buf(struct log_info* log_info) {
+    struct abbr_buf* a_buf = &log_info->a_buf;
 
     /* Add the abbreviated output to the kernel log */
     if (a_buf->b_buf.alloc_len) {
@@ -270,14 +267,13 @@
      * and then cal print_buf_lines on it */
     if (a_buf->e_buf.read < a_buf->e_buf.write) {
         /* no wrap around, just print it */
-        print_buf_lines(log_info, a_buf->e_buf.buf + a_buf->e_buf.read,
-                        a_buf->e_buf.used_len);
+        print_buf_lines(log_info, a_buf->e_buf.buf + a_buf->e_buf.read, a_buf->e_buf.used_len);
     } else {
         /* The circular buffer will always have at least 1 byte unused,
          * so by allocating alloc_len here we will have at least
          * 1 byte of space available as required by print_buf_lines().
          */
-        char * nbuf = malloc(a_buf->e_buf.alloc_len);
+        char* nbuf = static_cast<char*>(malloc(a_buf->e_buf.alloc_len));
         if (!nbuf) {
             return;
         }
@@ -290,15 +286,54 @@
     }
 }
 
-static int parent(const char *tag, int parent_read, pid_t pid,
-        int *chld_sts, int log_target, bool abbreviated, char *file_path) {
+static void signal_handler(int signal_num);
+
+static void block_signals(sigset_t* oldset) {
+    sigset_t blockset;
+
+    sigemptyset(&blockset);
+    sigaddset(&blockset, SIGINT);
+    sigaddset(&blockset, SIGQUIT);
+    sigaddset(&blockset, SIGHUP);
+    pthread_sigmask(SIG_BLOCK, &blockset, oldset);
+}
+
+static void unblock_signals(sigset_t* oldset) {
+    pthread_sigmask(SIG_SETMASK, oldset, nullptr);
+}
+
+static void setup_signal_handlers(pid_t pid) {
+    struct sigaction handler = {.sa_handler = signal_handler};
+
+    child_pid = pid;
+    sigaction(SIGINT, &handler, &old_int);
+    sigaction(SIGQUIT, &handler, &old_quit);
+    sigaction(SIGHUP, &handler, &old_hup);
+}
+
+static void restore_signal_handlers() {
+    sigaction(SIGINT, &old_int, nullptr);
+    sigaction(SIGQUIT, &old_quit, nullptr);
+    sigaction(SIGHUP, &old_hup, nullptr);
+    child_pid = 0;
+}
+
+static void signal_handler(int signal_num) {
+    if (child_pid == 0 || kill(child_pid, signal_num) != 0) {
+        restore_signal_handlers();
+        raise(signal_num);
+    }
+}
+
+static int parent(const char* tag, int parent_read, pid_t pid, int* chld_sts, int log_target,
+                  bool abbreviated, const char* file_path, bool forward_signals) {
     int status = 0;
     char buffer[4096];
     struct pollfd poll_fds[] = {
-        [0] = {
-            .fd = parent_read,
-            .events = POLLIN,
-        },
+            {
+                    .fd = parent_read,
+                    .events = POLLIN,
+            },
     };
     int rc = 0;
     int fd;
@@ -309,11 +344,16 @@
     int b = 0;  // end index of unprocessed data
     int sz;
     bool found_child = false;
+    // There is a very small chance that opening child_ptty in the child will fail, but in this case
+    // POLLHUP will not be generated below.  Therefore, we use a 1 second timeout for poll() until
+    // we receive a message from child_ptty.  If this times out, we call waitpid() with WNOHANG to
+    // check the status of the child process and exit appropriately if it has terminated.
+    bool received_messages = false;
     char tmpbuf[256];
 
     log_info.btag = basename(tag);
     if (!log_info.btag) {
-        log_info.btag = (char*) tag;
+        log_info.btag = tag;
     }
 
     if (abbreviated && (log_target == LOG_NONE)) {
@@ -324,8 +364,8 @@
     }
 
     if (log_target & LOG_KLOG) {
-        snprintf(log_info.klog_fmt, sizeof(log_info.klog_fmt),
-                 "<6>%.*s: %%s\n", MAX_KLOG_TAG, log_info.btag);
+        snprintf(log_info.klog_fmt, sizeof(log_info.klog_fmt), "<6>%.*s: %%s\n", MAX_KLOG_TAG,
+                 log_info.btag);
     }
 
     if ((log_target & LOG_FILE) && !file_path) {
@@ -334,7 +374,7 @@
     }
 
     if (log_target & LOG_FILE) {
-        fd = open(file_path, O_WRONLY | O_CREAT, 0664);
+        fd = open(file_path, O_WRONLY | O_CREAT | O_CLOEXEC, 0664);
         if (fd < 0) {
             ERROR("Cannot log to file %s\n", file_path);
             log_target &= ~LOG_FILE;
@@ -348,15 +388,16 @@
     log_info.abbreviated = abbreviated;
 
     while (!found_child) {
-        if (TEMP_FAILURE_RETRY(poll(poll_fds, ARRAY_SIZE(poll_fds), -1)) < 0) {
+        int timeout = received_messages ? -1 : 1000;
+        if (TEMP_FAILURE_RETRY(poll(poll_fds, arraysize(poll_fds), timeout)) < 0) {
             ERROR("poll failed\n");
             rc = -1;
             goto err_poll;
         }
 
         if (poll_fds[0].revents & POLLIN) {
-            sz = TEMP_FAILURE_RETRY(
-                read(parent_read, &buffer[b], sizeof(buffer) - 1 - b));
+            received_messages = true;
+            sz = TEMP_FAILURE_RETRY(read(parent_read, &buffer[b], sizeof(buffer) - 1 - b));
 
             sz += b;
             // Log one line at a time
@@ -397,10 +438,20 @@
             }
         }
 
-        if (poll_fds[0].revents & POLLHUP) {
+        if (!received_messages || (poll_fds[0].revents & POLLHUP)) {
             int ret;
+            sigset_t oldset;
 
-            ret = waitpid(pid, &status, WNOHANG);
+            if (forward_signals) {
+                // Our signal handlers forward these signals to 'child_pid', but waitpid() may reap
+                // the child, so we must block these signals until we either 1) conclude that the
+                // child is still running or 2) determine the child has been reaped and we have
+                // reset the signals to their original disposition.
+                block_signals(&oldset);
+            }
+
+            int flags = (poll_fds[0].revents & POLLHUP) ? 0 : WNOHANG;
+            ret = TEMP_FAILURE_RETRY(waitpid(pid, &status, flags));
             if (ret < 0) {
                 rc = errno;
                 ALOG(LOG_ERROR, "logwrap", "waitpid failed with %s\n", strerror(errno));
@@ -409,22 +460,29 @@
             if (ret > 0) {
                 found_child = true;
             }
+
+            if (forward_signals) {
+                if (found_child) {
+                    restore_signal_handlers();
+                }
+                unblock_signals(&oldset);
+            }
         }
     }
 
-    if (chld_sts != NULL) {
+    if (chld_sts != nullptr) {
         *chld_sts = status;
     } else {
-      if (WIFEXITED(status))
-        rc = WEXITSTATUS(status);
-      else
-        rc = -ECHILD;
+        if (WIFEXITED(status))
+            rc = WEXITSTATUS(status);
+        else
+            rc = -ECHILD;
     }
 
     // Flush remaining data
     if (a != b) {
-      buffer[b] = '\0';
-      log_line(&log_info, &buffer[a], b - a);
+        buffer[b] = '\0';
+        log_line(&log_info, &buffer[a], b - a);
     }
 
     /* All the output has been processed, time to dump the abbreviated output */
@@ -433,21 +491,21 @@
     }
 
     if (WIFEXITED(status)) {
-      if (WEXITSTATUS(status)) {
-        snprintf(tmpbuf, sizeof(tmpbuf),
-                 "%s terminated by exit(%d)\n", log_info.btag, WEXITSTATUS(status));
-        do_log_line(&log_info, tmpbuf);
-      }
+        if (WEXITSTATUS(status)) {
+            snprintf(tmpbuf, sizeof(tmpbuf), "%s terminated by exit(%d)\n", log_info.btag,
+                     WEXITSTATUS(status));
+            do_log_line(&log_info, tmpbuf);
+        }
     } else {
-      if (WIFSIGNALED(status)) {
-        snprintf(tmpbuf, sizeof(tmpbuf),
-                       "%s terminated by signal %d\n", log_info.btag, WTERMSIG(status));
-        do_log_line(&log_info, tmpbuf);
-      } else if (WIFSTOPPED(status)) {
-        snprintf(tmpbuf, sizeof(tmpbuf),
-                       "%s stopped by signal %d\n", log_info.btag, WSTOPSIG(status));
-        do_log_line(&log_info, tmpbuf);
-      }
+        if (WIFSIGNALED(status)) {
+            snprintf(tmpbuf, sizeof(tmpbuf), "%s terminated by signal %d\n", log_info.btag,
+                     WTERMSIG(status));
+            do_log_line(&log_info, tmpbuf);
+        } else if (WIFSTOPPED(status)) {
+            snprintf(tmpbuf, sizeof(tmpbuf), "%s stopped by signal %d\n", log_info.btag,
+                     WSTOPSIG(status));
+            do_log_line(&log_info, tmpbuf);
+        }
     }
 
 err_waitpid:
@@ -461,26 +519,21 @@
     return rc;
 }
 
-static void child(int argc, char* argv[]) {
+static void child(int argc, const char* const* argv) {
     // create null terminated argv_child array
     char* argv_child[argc + 1];
-    memcpy(argv_child, argv, argc * sizeof(char *));
-    argv_child[argc] = NULL;
+    memcpy(argv_child, argv, argc * sizeof(char*));
+    argv_child[argc] = nullptr;
 
     if (execvp(argv_child[0], argv_child)) {
-        FATAL_CHILD("executing %s failed: %s\n", argv_child[0],
-                strerror(errno));
+        FATAL_CHILD("executing %s failed: %s\n", argv_child[0], strerror(errno));
     }
 }
 
-int android_fork_execvp_ext(int argc, char* argv[], int *status, bool ignore_int_quit,
-        int log_target, bool abbreviated, char *file_path) {
+int logwrap_fork_execvp(int argc, const char* const* argv, int* status, bool forward_signals,
+                        int log_target, bool abbreviated, const char* file_path) {
     pid_t pid;
     int parent_ptty;
-    int child_ptty;
-    struct sigaction intact;
-    struct sigaction quitact;
-    sigset_t blockset;
     sigset_t oldset;
     int rc = 0;
 
@@ -491,7 +544,7 @@
     }
 
     /* Use ptty instead of socketpair so that STDOUT is not buffered */
-    parent_ptty = TEMP_FAILURE_RETRY(open("/dev/ptmx", O_RDWR));
+    parent_ptty = TEMP_FAILURE_RETRY(posix_openpt(O_RDWR | O_CLOEXEC));
     if (parent_ptty < 0) {
         ERROR("Cannot create parent ptty\n");
         rc = -1;
@@ -500,63 +553,59 @@
 
     char child_devname[64];
     if (grantpt(parent_ptty) || unlockpt(parent_ptty) ||
-            ptsname_r(parent_ptty, child_devname, sizeof(child_devname)) != 0) {
+        ptsname_r(parent_ptty, child_devname, sizeof(child_devname)) != 0) {
         ERROR("Problem with /dev/ptmx\n");
         rc = -1;
         goto err_ptty;
     }
 
-    child_ptty = TEMP_FAILURE_RETRY(open(child_devname, O_RDWR));
-    if (child_ptty < 0) {
-        ERROR("Cannot open child_ptty\n");
-        rc = -1;
-        goto err_child_ptty;
+    if (forward_signals) {
+        // Block these signals until we have the child pid and our signal handlers set up.
+        block_signals(&oldset);
     }
 
-    sigemptyset(&blockset);
-    sigaddset(&blockset, SIGINT);
-    sigaddset(&blockset, SIGQUIT);
-    pthread_sigmask(SIG_BLOCK, &blockset, &oldset);
-
     pid = fork();
     if (pid < 0) {
-        close(child_ptty);
         ERROR("Failed to fork\n");
         rc = -1;
         goto err_fork;
     } else if (pid == 0) {
         pthread_mutex_unlock(&fd_mutex);
-        pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+        if (forward_signals) {
+            unblock_signals(&oldset);
+        }
+
+        setsid();
+
+        int child_ptty = TEMP_FAILURE_RETRY(open(child_devname, O_RDWR | O_CLOEXEC));
+        if (child_ptty < 0) {
+            FATAL_CHILD("Cannot open child_ptty: %s\n", strerror(errno));
+        }
         close(parent_ptty);
 
-        // redirect stdout and stderr
         dup2(child_ptty, 1);
         dup2(child_ptty, 2);
         close(child_ptty);
 
         child(argc, argv);
     } else {
-        close(child_ptty);
-        if (ignore_int_quit) {
-            struct sigaction ignact;
-
-            memset(&ignact, 0, sizeof(ignact));
-            ignact.sa_handler = SIG_IGN;
-            sigaction(SIGINT, &ignact, &intact);
-            sigaction(SIGQUIT, &ignact, &quitact);
+        if (forward_signals) {
+            setup_signal_handlers(pid);
+            unblock_signals(&oldset);
         }
 
-        rc = parent(argv[0], parent_ptty, pid, status, log_target,
-                    abbreviated, file_path);
+        rc = parent(argv[0], parent_ptty, pid, status, log_target, abbreviated, file_path,
+                    forward_signals);
+
+        if (forward_signals) {
+            restore_signal_handlers();
+        }
     }
 
-    if (ignore_int_quit) {
-        sigaction(SIGINT, &intact, NULL);
-        sigaction(SIGQUIT, &quitact, NULL);
-    }
 err_fork:
-    pthread_sigmask(SIG_SETMASK, &oldset, NULL);
-err_child_ptty:
+    if (forward_signals) {
+        unblock_signals(&oldset);
+    }
 err_ptty:
     close(parent_ptty);
 err_open:
diff --git a/logwrapper/logwrap_fork_execvp_benchmark.cpp b/logwrapper/logwrap_fork_execvp_benchmark.cpp
new file mode 100644
index 0000000..b2d0c71
--- /dev/null
+++ b/logwrapper/logwrap_fork_execvp_benchmark.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 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 "logwrap/logwrap.h"
+
+#include <android-base/logging.h>
+#include <benchmark/benchmark.h>
+
+static void BM_android_fork_execvp_ext(benchmark::State& state) {
+    const char* argv[] = {"/system/bin/echo", "hello", "world"};
+    const int argc = 3;
+    while (state.KeepRunning()) {
+        int rc = logwrap_fork_execvp(argc, argv, nullptr, false, LOG_NONE, false, nullptr);
+        CHECK_EQ(0, rc);
+    }
+}
+BENCHMARK(BM_android_fork_execvp_ext);
+
+BENCHMARK_MAIN();
diff --git a/logwrapper/logwrapper.c b/logwrapper/logwrapper.cpp
similarity index 62%
rename from logwrapper/logwrapper.c
rename to logwrapper/logwrapper.cpp
index 9e0385d..7118d12 100644
--- a/logwrapper/logwrapper.c
+++ b/logwrapper/logwrapper.cpp
@@ -20,32 +20,30 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
-#include <logwrap/logwrap.h>
 #include <cutils/klog.h>
+#include <log/log.h>
+#include <logwrap/logwrap.h>
 
-#include "cutils/log.h"
-
-void fatal(const char *msg) {
+void fatal(const char* msg) {
     fprintf(stderr, "%s", msg);
     ALOG(LOG_ERROR, "logwrapper", "%s", msg);
     exit(-1);
 }
 
 void usage() {
-    fatal(
-        "Usage: logwrapper [-a] [-d] [-k] BINARY [ARGS ...]\n"
-        "\n"
-        "Forks and executes BINARY ARGS, redirecting stdout and stderr to\n"
-        "the Android logging system. Tag is set to BINARY, priority is\n"
-        "always LOG_INFO.\n"
-        "\n"
-        "-a: Causes logwrapper to do abbreviated logging.\n"
-        "    This logs up to the first 4K and last 4K of the command\n"
-        "    being run, and logs the output when the command exits\n"
-        "-d: Causes logwrapper to SIGSEGV when BINARY terminates\n"
-        "    fault address is set to the status of wait()\n"
-        "-k: Causes logwrapper to log to the kernel log instead of\n"
-        "    the Android system log\n");
+    fatal("Usage: logwrapper [-a] [-d] [-k] BINARY [ARGS ...]\n"
+          "\n"
+          "Forks and executes BINARY ARGS, redirecting stdout and stderr to\n"
+          "the Android logging system. Tag is set to BINARY, priority is\n"
+          "always LOG_INFO.\n"
+          "\n"
+          "-a: Causes logwrapper to do abbreviated logging.\n"
+          "    This logs up to the first 4K and last 4K of the command\n"
+          "    being run, and logs the output when the command exits\n"
+          "-d: Causes logwrapper to SIGSEGV when BINARY terminates\n"
+          "    fault address is set to the status of wait()\n"
+          "-k: Causes logwrapper to log to the kernel log instead of\n"
+          "    the Android system log\n");
 }
 
 int main(int argc, char* argv[]) {
@@ -70,7 +68,7 @@
                 break;
             case '?':
             default:
-              usage();
+                usage();
         }
     }
     argc -= optind;
@@ -80,8 +78,7 @@
         usage();
     }
 
-    rc = android_fork_execvp_ext(argc, &argv[0], &status, true,
-                                 log_target, abbreviated, NULL);
+    rc = logwrap_fork_execvp(argc, &argv[0], &status, true, log_target, abbreviated, nullptr);
     if (!rc) {
         if (WIFEXITED(status))
             rc = WEXITSTATUS(status);
@@ -90,8 +87,8 @@
     }
 
     if (seg_fault_on_exit) {
-        uintptr_t fault_address = (uintptr_t) status;
-        *(int *) fault_address = 0;  // causes SIGSEGV with fault_address = status
+        uintptr_t fault_address = (uintptr_t)status;
+        *(int*)fault_address = 0;  // causes SIGSEGV with fault_address = status
     }
 
     return rc;