[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
+ * `<assert.h>` 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(¶m, 0, sizeof(param));
+ pthread_attr_setschedparam(&attr, ¶m);
+ 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(¶m, 0, sizeof(param));
- if (sched_setscheduler((pid_t) 0, SCHED_BATCH, ¶m) < 0) {
+ struct sched_param param = {};
+ if (sched_setscheduler((pid_t)0, SCHED_BATCH, ¶m) < 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 × = 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 × = 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 × = 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, ×Lock);
- }
-
- 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(×Lock); }
- static void unlock(void) { pthread_mutex_unlock(×Lock); }
-
- 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(¶m, 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, ¶m) < 0) {
- return -1;
+ sched_param param = {};
+ if (sched_setscheduler((pid_t)0, SCHED_BATCH, ¶m) < 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(¶m, 0, sizeof(param));
- pthread_attr_setschedparam(&attr, ¶m);
- 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;