summaryrefslogtreecommitdiffstats
path: root/logd
diff options
context:
space:
mode:
Diffstat (limited to 'logd')
-rw-r--r--logd/Android.mk33
-rw-r--r--logd/CommandListener.cpp310
-rw-r--r--logd/CommandListener.h65
-rw-r--r--logd/FlushCommand.cpp87
-rw-r--r--logd/FlushCommand.h48
-rw-r--r--logd/LogAudit.cpp242
-rw-r--r--logd/LogAudit.h41
-rw-r--r--logd/LogBuffer.cpp503
-rw-r--r--logd/LogBuffer.h86
-rw-r--r--logd/LogBufferElement.cpp68
-rw-r--r--logd/LogBufferElement.h53
-rw-r--r--logd/LogCommand.cpp130
-rw-r--r--logd/LogCommand.h31
-rw-r--r--logd/LogListener.cpp127
-rw-r--r--logd/LogListener.h37
-rw-r--r--logd/LogReader.cpp186
-rw-r--r--logd/LogReader.h43
-rw-r--r--logd/LogStatistics.cpp926
-rw-r--r--logd/LogStatistics.h191
-rw-r--r--logd/LogTimes.cpp242
-rw-r--r--logd/LogTimes.h106
-rw-r--r--logd/LogWhiteBlackList.cpp239
-rw-r--r--logd/LogWhiteBlackList.h71
-rw-r--r--logd/README.auditd17
-rw-r--r--logd/README.property25
-rw-r--r--logd/event.logtags36
-rw-r--r--logd/libaudit.c276
-rw-r--r--logd/libaudit.h104
-rw-r--r--logd/main.cpp199
-rw-r--r--logd/tests/Android.mk53
-rw-r--r--logd/tests/logd_test.cpp703
31 files changed, 5278 insertions, 0 deletions
diff --git a/logd/Android.mk b/logd/Android.mk
new file mode 100644
index 0000000..188511f
--- /dev/null
+++ b/logd/Android.mk
@@ -0,0 +1,33 @@
+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 \
+ event.logtags
+
+LOCAL_SHARED_LIBRARIES := \
+ libsysutils \
+ liblog \
+ libcutils \
+ libutils
+
+LOCAL_CFLAGS := -Werror $(shell sed -n 's/^\([0-9]*\)[ \t]*auditd[ \t].*/-DAUDITD_LOG_TAG=\1/p' $(LOCAL_PATH)/event.logtags)
+
+include $(BUILD_EXECUTABLE)
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/logd/CommandListener.cpp b/logd/CommandListener.cpp
new file mode 100644
index 0000000..d7088b4
--- /dev/null
+++ b/logd/CommandListener.cpp
@@ -0,0 +1,310 @@
+/*
+ * 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 <arpa/inet.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <cutils/sockets.h>
+#include <private/android_filesystem_config.h>
+#include <sysutils/SocketClient.h>
+
+#include "CommandListener.h"
+#include "LogCommand.h"
+
+CommandListener::CommandListener(LogBuffer *buf, LogReader * /*reader*/,
+ LogListener * /*swl*/)
+ : FrameworkListener(getLogSocket())
+ , mBuf(*buf) {
+ // 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));
+}
+
+CommandListener::ShutdownCmd::ShutdownCmd(LogBuffer *buf, LogReader *reader,
+ LogListener *swl)
+ : LogCommand("shutdown")
+ , mBuf(*buf)
+ , 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)
+{ }
+
+static void setname() {
+ prctl(PR_SET_NAME, "logd.control");
+}
+
+int CommandListener::ClearCmd::runCommand(SocketClient *cli,
+ int argc, char **argv) {
+ setname();
+ 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;
+}
+
+CommandListener::GetBufSizeCmd::GetBufSizeCmd(LogBuffer *buf)
+ : LogCommand("getLogSize")
+ , mBuf(*buf)
+{ }
+
+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;
+}
+
+CommandListener::SetBufSizeCmd::SetBufSizeCmd(LogBuffer *buf)
+ : LogCommand("setLogSize")
+ , mBuf(*buf)
+{ }
+
+int CommandListener::SetBufSizeCmd::runCommand(SocketClient *cli,
+ int argc, char **argv) {
+ setname();
+ if (!clientHasLogCredentials(cli)) {
+ cli->sendMsg("Permission Denied");
+ return 0;
+ }
+
+ if (argc < 3) {
+ cli->sendMsg("Missing Argument");
+ return 0;
+ }
+
+ int id = atoi(argv[1]);
+ if ((id < LOG_ID_MIN) || (LOG_ID_MAX <= id)) {
+ cli->sendMsg("Range Error");
+ return 0;
+ }
+
+ unsigned long size = atol(argv[2]);
+ if (mBuf.setSize((log_id_t) id, size)) {
+ cli->sendMsg("Range Error");
+ return 0;
+ }
+
+ cli->sendMsg("success");
+ return 0;
+}
+
+CommandListener::GetBufSizeUsedCmd::GetBufSizeUsedCmd(LogBuffer *buf)
+ : LogCommand("getLogSizeUsed")
+ , mBuf(*buf)
+{ }
+
+int CommandListener::GetBufSizeUsedCmd::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.getSizeUsed((log_id_t) id);
+ char buf[512];
+ snprintf(buf, sizeof(buf), "%lu", size);
+ cli->sendMsg(buf);
+ return 0;
+}
+
+CommandListener::GetStatisticsCmd::GetStatisticsCmd(LogBuffer *buf)
+ : LogCommand("getStatistics")
+ , mBuf(*buf)
+{ }
+
+static void package_string(char **strp) {
+ const char *a = *strp;
+ if (!a) {
+ a = "";
+ }
+
+ // Calculate total buffer size prefix, count is the string length w/o nul
+ char fmt[32];
+ for(size_t l = strlen(a), y = 0, x = 6; y != x; y = x, x = strlen(fmt) - 2) {
+ snprintf(fmt, sizeof(fmt), "%zu\n%%s\n\f", l + x);
+ }
+
+ char *b = *strp;
+ *strp = NULL;
+ asprintf(strp, fmt, a);
+ free(b);
+}
+
+int CommandListener::GetStatisticsCmd::runCommand(SocketClient *cli,
+ int argc, char **argv) {
+ setname();
+ uid_t uid = cli->getUid();
+ if (clientHasLogCredentials(cli)) {
+ uid = AID_ROOT;
+ }
+
+ unsigned int logMask = -1;
+ if (argc > 1) {
+ logMask = 0;
+ for (int i = 1; i < argc; ++i) {
+ int id = atoi(argv[i]);
+ if ((id < LOG_ID_MIN) || (LOG_ID_MAX <= id)) {
+ cli->sendMsg("Range Error");
+ return 0;
+ }
+ logMask |= 1 << id;
+ }
+ }
+
+ char *buf = NULL;
+
+ mBuf.formatStatistics(&buf, uid, logMask);
+ if (!buf) {
+ cli->sendMsg("Failed");
+ } else {
+ package_string(&buf);
+ cli->sendMsg(buf);
+ free(buf);
+ }
+ return 0;
+}
+
+CommandListener::GetPruneListCmd::GetPruneListCmd(LogBuffer *buf)
+ : LogCommand("getPruneList")
+ , mBuf(*buf)
+{ }
+
+int CommandListener::GetPruneListCmd::runCommand(SocketClient *cli,
+ int /*argc*/, char ** /*argv*/) {
+ setname();
+ char *buf = NULL;
+ mBuf.formatPrune(&buf);
+ if (!buf) {
+ cli->sendMsg("Failed");
+ } else {
+ package_string(&buf);
+ cli->sendMsg(buf);
+ free(buf);
+ }
+ return 0;
+}
+
+CommandListener::SetPruneListCmd::SetPruneListCmd(LogBuffer *buf)
+ : LogCommand("setPruneList")
+ , mBuf(*buf)
+{ }
+
+int CommandListener::SetPruneListCmd::runCommand(SocketClient *cli,
+ int argc, char **argv) {
+ setname();
+ if (!clientHasLogCredentials(cli)) {
+ cli->sendMsg("Permission Denied");
+ return 0;
+ }
+
+ char *cp = NULL;
+ for (int i = 1; i < argc; ++i) {
+ char *p = cp;
+ if (p) {
+ cp = NULL;
+ asprintf(&cp, "%s %s", p, argv[i]);
+ free(p);
+ } else {
+ asprintf(&cp, "%s", argv[i]);
+ }
+ }
+
+ int ret = mBuf.initPrune(cp);
+ free(cp);
+
+ if (ret) {
+ cli->sendMsg("Invalid");
+ return 0;
+ }
+
+ cli->sendMsg("success");
+
+ return 0;
+}
+
+int CommandListener::getLogSocket() {
+ static const char socketName[] = "logd";
+ int sock = android_get_control_socket(socketName);
+
+ if (sock < 0) {
+ sock = socket_local_server(socketName,
+ ANDROID_SOCKET_NAMESPACE_RESERVED,
+ SOCK_STREAM);
+ }
+
+ return sock;
+}
diff --git a/logd/CommandListener.h b/logd/CommandListener.h
new file mode 100644
index 0000000..cd1c306
--- /dev/null
+++ b/logd/CommandListener.h
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#ifndef _COMMANDLISTENER_H__
+#define _COMMANDLISTENER_H__
+
+#include <sysutils/FrameworkListener.h>
+#include "LogCommand.h"
+#include "LogBuffer.h"
+#include "LogReader.h"
+#include "LogListener.h"
+
+class CommandListener : public FrameworkListener {
+ LogBuffer &mBuf;
+
+public:
+ CommandListener(LogBuffer *buf, LogReader *reader, LogListener *swl);
+ virtual ~CommandListener() {}
+
+private:
+ static int getLogSocket();
+
+ class ShutdownCmd : public LogCommand {
+ LogBuffer &mBuf;
+ LogReader &mReader;
+ LogListener &mSwl;
+
+ public:
+ ShutdownCmd(LogBuffer *buf, LogReader *reader, LogListener *swl);
+ virtual ~ShutdownCmd() {}
+ int runCommand(SocketClient *c, int argc, char ** argv);
+ };
+
+#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)
+};
+
+#endif
diff --git a/logd/FlushCommand.cpp b/logd/FlushCommand.cpp
new file mode 100644
index 0000000..3be07c0
--- /dev/null
+++ b/logd/FlushCommand.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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,
+ log_time start)
+ : mReader(reader)
+ , mNonBlock(nonBlock)
+ , mTail(tail)
+ , mLogMask(logMask)
+ , mPid(pid)
+ , mStart(start)
+{ }
+
+// runSocketCommand is called once for every open client on the
+// log reader socket. Here we manage and associated the reader
+// client tracking and log region locks LastLogTimes list of
+// LogTimeEntrys, and spawn a transitory per-client thread to
+// work at filing data to the socket.
+//
+// global LogTimeEntry::lock() is used to protect access,
+// reference counts are used to ensure that individual
+// LogTimeEntry lifetime is managed when not protected.
+void FlushCommand::runSocketCommand(SocketClient *client) {
+ LogTimeEntry *entry = NULL;
+ LastLogTimes &times = mReader.logbuf().mTimes;
+
+ LogTimeEntry::lock();
+ LastLogTimes::iterator it = times.begin();
+ while(it != times.end()) {
+ entry = (*it);
+ if (entry->mClient == client) {
+ entry->triggerReader_Locked();
+ if (entry->runningReader_Locked()) {
+ LogTimeEntry::unlock();
+ return;
+ }
+ entry->incRef_Locked();
+ break;
+ }
+ it++;
+ }
+
+ if (it == times.end()) {
+ // Create LogTimeEntry in notifyNewLog() ?
+ if (mTail == (unsigned long) -1) {
+ LogTimeEntry::unlock();
+ return;
+ }
+ entry = new LogTimeEntry(mReader, client, mNonBlock, mTail, mLogMask, mPid, mStart);
+ times.push_back(entry);
+ }
+
+ client->incRef();
+
+ // release client and entry reference counts once done
+ entry->startReader_Locked();
+ LogTimeEntry::unlock();
+}
+
+bool FlushCommand::hasReadLogs(SocketClient *client) {
+ return clientHasLogCredentials(client);
+}
diff --git a/logd/FlushCommand.h b/logd/FlushCommand.h
new file mode 100644
index 0000000..f34c06a
--- /dev/null
+++ b/logd/FlushCommand.h
@@ -0,0 +1,48 @@
+/*
+ * 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;
+ log_time mStart;
+
+public:
+ FlushCommand(LogReader &mReader,
+ bool nonBlock = false,
+ unsigned long tail = -1,
+ unsigned int logMask = -1,
+ pid_t pid = 0,
+ log_time start = LogTimeEntry::EPOCH);
+ virtual void runSocketCommand(SocketClient *client);
+
+ static bool hasReadLogs(SocketClient *client);
+};
+
+#endif
diff --git a/logd/LogAudit.cpp b/logd/LogAudit.cpp
new file mode 100644
index 0000000..f8d6162
--- /dev/null
+++ b/logd/LogAudit.cpp
@@ -0,0 +1,242 @@
+/*
+ * 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 <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <sys/klog.h>
+#include <sys/prctl.h>
+#include <sys/uio.h>
+
+#include "libaudit.h"
+#include "LogAudit.h"
+
+LogAudit::LogAudit(LogBuffer *buf, LogReader *reader, int fdDmsg)
+ : SocketListener(getLogSocket(), false)
+ , logbuf(buf)
+ , reader(reader)
+ , fdDmesg(-1) {
+ logDmesg();
+ fdDmesg = fdDmsg;
+}
+
+bool LogAudit::onDataAvailable(SocketClient *cli) {
+ prctl(PR_SET_NAME, "logd.auditd");
+
+ struct audit_message rep;
+
+ rep.nlh.nlmsg_type = 0;
+ rep.nlh.nlmsg_len = 0;
+ rep.data[0] = '\0';
+
+ if (audit_get_reply(cli->getSocket(), &rep, GET_REPLY_BLOCKING, 0) < 0) {
+ SLOGE("Failed on audit_get_reply with error: %s", strerror(errno));
+ return false;
+ }
+
+ 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) {
+ return -EINVAL;
+ }
+
+ va_list args;
+
+ char *str = NULL;
+ va_start(args, fmt);
+ int rc = vasprintf(&str, fmt, args);
+ va_end(args);
+
+ if (rc < 0) {
+ return rc;
+ }
+
+ char *cp;
+ while ((cp = strstr(str, " "))) {
+ memmove(cp, cp + 1, strlen(cp + 1) + 1);
+ }
+
+ if (fdDmesg >= 0) {
+ struct iovec iov[2];
+
+ iov[0].iov_base = str;
+ iov[0].iov_len = strlen(str);
+ iov[1].iov_base = const_cast<char *>("\n");
+ iov[1].iov_len = 1;
+
+ writev(fdDmesg, iov, sizeof(iov) / sizeof(iov[0]));
+ }
+
+ pid_t pid = getpid();
+ pid_t tid = gettid();
+ uid_t uid = getuid();
+ 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);
+ } else {
+ now.strptime("", ""); // side effect of setting CLOCK_REALTIME
+ }
+
+ static const char pid_str[] = " pid=";
+ char *pidptr = strstr(str, pid_str);
+ if (pidptr && isdigit(pidptr[sizeof(pid_str) - 1])) {
+ cp = pidptr + sizeof(pid_str) - 1;
+ pid = 0;
+ while (isdigit(*cp)) {
+ pid = (pid * 10) + (*cp - '0');
+ ++cp;
+ }
+ tid = pid;
+ uid = logbuf->pidToUid(pid);
+ memmove(pidptr, cp, strlen(cp) + 1);
+ }
+
+ // log to events
+
+ size_t l = strlen(str);
+ size_t n = l + sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint32_t);
+
+ bool notify = false;
+
+ char *newstr = reinterpret_cast<char *>(malloc(n));
+ if (!newstr) {
+ rc = -ENOMEM;
+ } else {
+ cp = newstr;
+ *cp++ = AUDITD_LOG_TAG & 0xFF;
+ *cp++ = (AUDITD_LOG_TAG >> 8) & 0xFF;
+ *cp++ = (AUDITD_LOG_TAG >> 16) & 0xFF;
+ *cp++ = (AUDITD_LOG_TAG >> 24) & 0xFF;
+ *cp++ = EVENT_TYPE_STRING;
+ *cp++ = l & 0xFF;
+ *cp++ = (l >> 8) & 0xFF;
+ *cp++ = (l >> 16) & 0xFF;
+ *cp++ = (l >> 24) & 0xFF;
+ memcpy(cp, str, l);
+
+ logbuf->log(LOG_ID_EVENTS, now, uid, pid, tid, newstr,
+ (n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX);
+ free(newstr);
+
+ notify = true;
+ }
+
+ // log to main
+
+ static const char comm_str[] = " comm=\"";
+ const char *comm = strstr(str, comm_str);
+ const char *estr = str + strlen(str);
+ if (comm) {
+ estr = comm;
+ comm += sizeof(comm_str) - 1;
+ } else if (pid == getpid()) {
+ pid = tid;
+ comm = "auditd";
+ } else if (!(comm = logbuf->pidToName(pid))) {
+ comm = "unknown";
+ }
+
+ const char *ecomm = strchr(comm, '"');
+ if (ecomm) {
+ ++ecomm;
+ l = ecomm - comm;
+ } else {
+ l = strlen(comm) + 1;
+ ecomm = "";
+ }
+ n = (estr - str) + strlen(ecomm) + l + 2;
+
+ newstr = reinterpret_cast<char *>(malloc(n));
+ if (!newstr) {
+ rc = -ENOMEM;
+ } else {
+ *newstr = (strstr(str, " permissive=1")
+ || strstr(str, " policy loaded "))
+ ? 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);
+
+ logbuf->log(LOG_ID_MAIN, now, uid, pid, tid, newstr,
+ (n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX);
+ free(newstr);
+
+ notify = true;
+ }
+
+ free(str);
+
+ if (notify) {
+ reader->notifyNewLog();
+ }
+
+ return rc;
+}
+
+void LogAudit::logDmesg() {
+ int len = klogctl(KLOG_SIZE_BUFFER, NULL, 0);
+ if (len <= 0) {
+ return;
+ }
+
+ len++;
+ char buf[len];
+
+ int rc = klogctl(KLOG_READ_ALL, buf, len);
+
+ buf[len - 1] = '\0';
+
+ for(char *tok = buf; (rc >= 0) && ((tok = strtok(tok, "\r\n"))); tok = NULL) {
+ char *audit = strstr(tok, " audit(");
+ if (!audit) {
+ continue;
+ }
+
+ *audit++ = '\0';
+
+ char *type = strstr(tok, "type=");
+ if (type) {
+ rc = logPrint("%s %s", type, audit);
+ } else {
+ rc = logPrint("%s", audit);
+ }
+ }
+}
+
+int LogAudit::getLogSocket() {
+ int fd = audit_open();
+ if (fd < 0) {
+ return fd;
+ }
+ if (audit_set_pid(fd, getpid(), WAIT_YES) < 0) {
+ audit_close(fd);
+ fd = -1;
+ }
+ return fd;
+}
diff --git a/logd/LogAudit.h b/logd/LogAudit.h
new file mode 100644
index 0000000..111030a
--- /dev/null
+++ b/logd/LogAudit.h
@@ -0,0 +1,41 @@
+/*
+ * 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_AUDIT_H__
+#define _LOGD_LOG_AUDIT_H__
+
+#include <sysutils/SocketListener.h>
+#include "LogReader.h"
+
+class LogAudit : public SocketListener {
+ LogBuffer *logbuf;
+ LogReader *reader;
+ int fdDmesg;
+
+public:
+ LogAudit(LogBuffer *buf, LogReader *reader, int fdDmesg);
+
+protected:
+ virtual bool onDataAvailable(SocketClient *cli);
+
+private:
+ static int getLogSocket();
+ void logDmesg();
+ int logPrint(const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 2, 3)));
+};
+
+#endif
diff --git a/logd/LogBuffer.cpp b/logd/LogBuffer.cpp
new file mode 100644
index 0000000..cd9ea20
--- /dev/null
+++ b/logd/LogBuffer.cpp
@@ -0,0 +1,503 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/user.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <cutils/properties.h>
+#include <log/logger.h>
+
+#include "LogBuffer.h"
+#include "LogReader.h"
+#include "LogStatistics.h"
+#include "LogWhiteBlackList.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;
+}
+
+LogBuffer::LogBuffer(LastLogTimes *times)
+ : mTimes(*times) {
+ pthread_mutex_init(&mLogElementsLock, NULL);
+ dgram_qlen_statistics = false;
+
+ 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);
+ }
+ }
+}
+
+void 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;
+ }
+ LogBufferElement *elem = new LogBufferElement(log_id, realtime,
+ uid, pid, tid, msg, len);
+
+ 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 (--it != mLogElements.begin()) {
+ if ((*it)->getRealTime() <= realtime) {
+ // halves the peak performance, use with caution
+ if (dgram_qlen_statistics) {
+ LogBufferElementCollection::iterator ib = it;
+ unsigned short buckets, num = 1;
+ for (unsigned short i = 0; (buckets = stats.dgram_qlen(i)); ++i) {
+ buckets -= num;
+ num += buckets;
+ while (buckets && (--ib != mLogElements.begin())) {
+ --buckets;
+ }
+ if (buckets) {
+ break;
+ }
+ stats.recordDiff(
+ elem->getRealTime() - (*ib)->getRealTime(), i);
+ }
+ }
+ break;
+ }
+ last = it;
+ }
+
+ if (last == mLogElements.end()) {
+ mLogElements.push_back(elem);
+ } else {
+ log_time end = log_time::EPOCH;
+ 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)->getMonotonicTime()))) {
+ mLogElements.push_back(elem);
+ } else {
+ mLogElements.insert(last,elem);
+ }
+
+ LogTimeEntry::unlock();
+ }
+
+ stats.add(len, log_id, uid, pid);
+ maybePrune(log_id);
+ pthread_mutex_unlock(&mLogElementsLock);
+}
+
+// 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);
+ }
+}
+
+// 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()
+ && (!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->getMonotonicTime())) {
+ break;
+ }
+
+ if (e->getLogId() != id) {
+ ++it;
+ continue;
+ }
+
+ uid_t uid = e->getUid();
+
+ if (uid == caller_uid) {
+ it = mLogElements.erase(it);
+ unsigned short len = e->getMsgLen();
+ stats.subtract(len, id, uid, e->getPid());
+ delete e;
+ pruneRows--;
+ if (pruneRows == 0) {
+ break;
+ }
+ } else {
+ ++it;
+ }
+ }
+ LogTimeEntry::unlock();
+ return;
+ }
+
+ // prune by worst offender by uid
+ while (pruneRows > 0) {
+ // recalculate the worst offender on every batched pass
+ uid_t worst = (uid_t) -1;
+ size_t worst_sizes = 0;
+ size_t second_worst_sizes = 0;
+
+ if ((id != LOG_ID_CRASH) && mPrune.worstUidEnabled()) {
+ LidStatistics &l = stats.id(id);
+ l.sort();
+ UidStatisticsCollection::iterator iu = l.begin();
+ if (iu != l.end()) {
+ UidStatistics *u = *iu;
+ worst = u->getUid();
+ worst_sizes = u->sizes();
+ if (++iu != l.end()) {
+ second_worst_sizes = (*iu)->sizes();
+ }
+ }
+ }
+
+ bool kick = false;
+ for(it = mLogElements.begin(); it != mLogElements.end();) {
+ LogBufferElement *e = *it;
+
+ if (oldest && (oldest->mStart <= e->getMonotonicTime())) {
+ break;
+ }
+
+ if (e->getLogId() != id) {
+ ++it;
+ continue;
+ }
+
+ uid_t uid = e->getUid();
+
+ if (uid == worst) {
+ it = mLogElements.erase(it);
+ unsigned short len = e->getMsgLen();
+ stats.subtract(len, id, worst, e->getPid());
+ delete e;
+ kick = true;
+ pruneRows--;
+ if ((pruneRows == 0) || (worst_sizes < second_worst_sizes)) {
+ break;
+ }
+ worst_sizes -= len;
+ } else if (mPrune.naughty(e)) { // BlackListed
+ it = mLogElements.erase(it);
+ stats.subtract(e->getMsgLen(), id, uid, e->getPid());
+ delete e;
+ pruneRows--;
+ if (pruneRows == 0) {
+ break;
+ }
+ } else {
+ ++it;
+ }
+ }
+
+ if (!kick || !mPrune.worstUidEnabled()) {
+ break; // the following loop will ask bad clients to skip/drop
+ }
+ }
+
+ bool whitelist = false;
+ it = mLogElements.begin();
+ while((pruneRows > 0) && (it != mLogElements.end())) {
+ LogBufferElement *e = *it;
+ if (e->getLogId() == id) {
+ if (oldest && (oldest->mStart <= e->getMonotonicTime())) {
+ if (!whitelist) {
+ if (stats.sizes(id) > (2 * log_buffer_size(id))) {
+ // kick a misbehaving log reader client off the island
+ oldest->release_Locked();
+ } else {
+ oldest->triggerSkip_Locked(pruneRows);
+ }
+ }
+ break;
+ }
+
+ if (mPrune.nice(e)) { // WhiteListed
+ whitelist = true;
+ it++;
+ continue;
+ }
+
+ it = mLogElements.erase(it);
+ stats.subtract(e->getMsgLen(), id, e->getUid(), e->getPid());
+ delete e;
+ pruneRows--;
+ } else {
+ it++;
+ }
+ }
+
+ if (whitelist && (pruneRows > 0)) {
+ it = mLogElements.begin();
+ while((it != mLogElements.end()) && (pruneRows > 0)) {
+ LogBufferElement *e = *it;
+ if (e->getLogId() == id) {
+ if (oldest && (oldest->mStart <= e->getMonotonicTime())) {
+ if (stats.sizes(id) > (2 * log_buffer_size(id))) {
+ // kick a misbehaving log reader client off the island
+ oldest->release_Locked();
+ } else {
+ oldest->triggerSkip_Locked(pruneRows);
+ }
+ break;
+ }
+ it = mLogElements.erase(it);
+ stats.subtract(e->getMsgLen(), id, e->getUid(), e->getPid());
+ delete e;
+ pruneRows--;
+ } else {
+ it++;
+ }
+ }
+ }
+
+ 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;
+}
+
+log_time LogBuffer::flushTo(
+ SocketClient *reader, const log_time start, bool privileged,
+ bool (*filter)(const LogBufferElement *element, void *arg), void *arg) {
+ LogBufferElementCollection::iterator it;
+ log_time max = start;
+ uid_t uid = reader->getUid();
+
+ pthread_mutex_lock(&mLogElementsLock);
+ for (it = mLogElements.begin(); it != mLogElements.end(); ++it) {
+ LogBufferElement *element = *it;
+
+ if (!privileged && (element->getUid() != uid)) {
+ continue;
+ }
+
+ if (element->getMonotonicTime() <= start) {
+ continue;
+ }
+
+ // NB: calling out to another object with mLogElementsLock held (safe)
+ if (filter && !(*filter)(element, arg)) {
+ continue;
+ }
+
+ pthread_mutex_unlock(&mLogElementsLock);
+
+ // range locking in LastLogTimes looks after us
+ max = element->flushTo(reader);
+
+ 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) {
+ log_time oldest(CLOCK_MONOTONIC);
+
+ pthread_mutex_lock(&mLogElementsLock);
+
+ // Find oldest element in the log(s)
+ LogBufferElementCollection::iterator it;
+ for (it = mLogElements.begin(); it != mLogElements.end(); ++it) {
+ LogBufferElement *element = *it;
+
+ if ((logMask & (1 << element->getLogId()))) {
+ oldest = element->getMonotonicTime();
+ break;
+ }
+ }
+
+ stats.format(strp, uid, logMask, oldest);
+
+ pthread_mutex_unlock(&mLogElementsLock);
+}
diff --git a/logd/LogBuffer.h b/logd/LogBuffer.h
new file mode 100644
index 0000000..4b982a8
--- /dev/null
+++ b/logd/LogBuffer.h
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+#ifndef _LOGD_LOG_BUFFER_H__
+#define _LOGD_LOG_BUFFER_H__
+
+#include <sys/types.h>
+
+#include <log/log.h>
+#include <sysutils/SocketClient.h>
+#include <utils/List.h>
+
+#include <private/android_filesystem_config.h>
+
+#include "LogBufferElement.h"
+#include "LogTimes.h"
+#include "LogStatistics.h"
+#include "LogWhiteBlackList.h"
+
+typedef android::List<LogBufferElement *> LogBufferElementCollection;
+
+class LogBuffer {
+ LogBufferElementCollection mLogElements;
+ pthread_mutex_t mLogElementsLock;
+
+ LogStatistics stats;
+
+ bool dgram_qlen_statistics;
+
+ PruneList mPrune;
+
+ unsigned long mMaxSize[LOG_ID_MAX];
+
+public:
+ LastLogTimes &mTimes;
+
+ LogBuffer(LastLogTimes *times);
+
+ void log(log_id_t log_id, log_time realtime,
+ uid_t uid, pid_t pid, pid_t tid,
+ const char *msg, unsigned short len);
+ log_time flushTo(SocketClient *writer, const log_time start,
+ bool privileged,
+ bool (*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 enableDgramQlenStatistics() {
+ stats.enableDgramQlenStatistics();
+ dgram_qlen_statistics = true;
+ }
+
+ int initPrune(char *cp) { return mPrune.init(cp); }
+ // *strp uses malloc, use free to release.
+ void formatPrune(char **strp) { mPrune.format(strp); }
+
+ // helper
+ char *pidToName(pid_t pid) { return stats.pidToName(pid); }
+ uid_t pidToUid(pid_t pid) { return stats.pidToUid(pid); }
+
+private:
+ void maybePrune(log_id_t id);
+ void prune(log_id_t id, unsigned long pruneRows, uid_t uid = AID_ROOT);
+
+};
+
+#endif // _LOGD_LOG_BUFFER_H__
diff --git a/logd/LogBufferElement.cpp b/logd/LogBufferElement.cpp
new file mode 100644
index 0000000..d959ceb
--- /dev/null
+++ b/logd/LogBufferElement.cpp
@@ -0,0 +1,68 @@
+/*
+ * 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 <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <log/logger.h>
+
+#include "LogBufferElement.h"
+#include "LogReader.h"
+
+const log_time LogBufferElement::FLUSH_ERROR((uint32_t)0, (uint32_t)0);
+
+LogBufferElement::LogBufferElement(log_id_t log_id, log_time realtime,
+ uid_t uid, pid_t pid, pid_t tid,
+ const char *msg, unsigned short len)
+ : mLogId(log_id)
+ , mUid(uid)
+ , mPid(pid)
+ , mTid(tid)
+ , mMsgLen(len)
+ , mMonotonicTime(CLOCK_MONOTONIC)
+ , mRealTime(realtime) {
+ mMsg = new char[len];
+ memcpy(mMsg, msg, len);
+}
+
+LogBufferElement::~LogBufferElement() {
+ delete [] mMsg;
+}
+
+log_time LogBufferElement::flushTo(SocketClient *reader) {
+ struct logger_entry_v3 entry;
+ memset(&entry, 0, sizeof(struct logger_entry_v3));
+ entry.hdr_size = sizeof(struct logger_entry_v3);
+ entry.len = mMsgLen;
+ 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);
+ iovec[1].iov_base = mMsg;
+ iovec[1].iov_len = mMsgLen;
+ if (reader->sendDatav(iovec, 2)) {
+ return FLUSH_ERROR;
+ }
+
+ return mMonotonicTime;
+}
diff --git a/logd/LogBufferElement.h b/logd/LogBufferElement.h
new file mode 100644
index 0000000..fdca973
--- /dev/null
+++ b/logd/LogBufferElement.h
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#ifndef _LOGD_LOG_BUFFER_ELEMENT_H__
+#define _LOGD_LOG_BUFFER_ELEMENT_H__
+
+#include <sys/types.h>
+#include <sysutils/SocketClient.h>
+#include <log/log.h>
+#include <log/log_read.h>
+
+class LogBufferElement {
+ const log_id_t mLogId;
+ const uid_t mUid;
+ const pid_t mPid;
+ const pid_t mTid;
+ char *mMsg;
+ const unsigned short mMsgLen;
+ const log_time mMonotonicTime;
+ const log_time mRealTime;
+
+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 getMsgLen() const { return mMsgLen; }
+ log_time getMonotonicTime(void) const { return mMonotonicTime; }
+ log_time getRealTime(void) const { return mRealTime; }
+
+ static const log_time FLUSH_ERROR;
+ log_time flushTo(SocketClient *writer);
+};
+
+#endif
diff --git a/logd/LogCommand.cpp b/logd/LogCommand.cpp
new file mode 100644
index 0000000..e4c138e
--- /dev/null
+++ b/logd/LogCommand.cpp
@@ -0,0 +1,130 @@
+/*
+ * 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 <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/LogCommand.h b/logd/LogCommand.h
new file mode 100644
index 0000000..e3b96a2
--- /dev/null
+++ b/logd/LogCommand.h
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+#ifndef _LOGD_COMMAND_H
+#define _LOGD_COMMAND_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
diff --git a/logd/LogListener.cpp b/logd/LogListener.cpp
new file mode 100644
index 0000000..8186cea
--- /dev/null
+++ b/logd/LogListener.cpp
@@ -0,0 +1,127 @@
+/*
+ * 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 <limits.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <cutils/sockets.h>
+#include <log/logger.h>
+
+#include "LogListener.h"
+
+LogListener::LogListener(LogBuffer *buf, LogReader *reader)
+ : SocketListener(getLogSocket(), false)
+ , logbuf(buf)
+ , reader(reader)
+{ }
+
+bool LogListener::onDataAvailable(SocketClient *cli) {
+ prctl(PR_SET_NAME, "logd.writer");
+
+ 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_log_id_t + sizeof(uint16_t) + sizeof(log_time))) {
+ return false;
+ }
+
+ struct ucred *cred = NULL;
+
+ 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);
+ break;
+ }
+ cmsg = CMSG_NXTHDR(&hdr, cmsg);
+ }
+
+ if (cred == NULL) {
+ return false;
+ }
+
+ if (cred->uid == getuid()) {
+ // 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;
+ }
+
+ // First log element is always log_id.
+ log_id_t log_id = (log_id_t) *((typeof_log_id_t *) buffer);
+ if (log_id < 0 || log_id >= LOG_ID_MAX) {
+ return false;
+ }
+ char *msg = ((char *)buffer) + sizeof_log_id_t;
+ n -= sizeof_log_id_t;
+
+ // second element is the thread id of the caller
+ pid_t tid = (pid_t) *((uint16_t *) msg);
+ msg += sizeof(uint16_t);
+ n -= sizeof(uint16_t);
+
+ // third element is the realtime at point of caller
+ log_time realtime(msg);
+ msg += sizeof(log_time);
+ n -= sizeof(log_time);
+
+ // NB: hdr.msg_flags & MSG_TRUNC is not tested, silently passing a
+ // truncated message to the logs.
+
+ logbuf->log(log_id, realtime, cred->uid, cred->pid, tid, msg,
+ ((size_t) n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX);
+ reader->notifyNewLog();
+
+ return true;
+}
+
+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);
+ }
+
+ int on = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
+ return -1;
+ }
+ return sock;
+}
diff --git a/logd/LogListener.h b/logd/LogListener.h
new file mode 100644
index 0000000..7099e13
--- /dev/null
+++ b/logd/LogListener.h
@@ -0,0 +1,37 @@
+/*
+ * 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_LISTENER_H__
+#define _LOGD_LOG_LISTENER_H__
+
+#include <sysutils/SocketListener.h>
+#include "LogReader.h"
+
+class LogListener : public SocketListener {
+ LogBuffer *logbuf;
+ LogReader *reader;
+
+public:
+ LogListener(LogBuffer *buf, LogReader *reader);
+
+protected:
+ virtual bool onDataAvailable(SocketClient *cli);
+
+private:
+ static int getLogSocket();
+};
+
+#endif
diff --git a/logd/LogReader.cpp b/logd/LogReader.cpp
new file mode 100644
index 0000000..8458c19
--- /dev/null
+++ b/logd/LogReader.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2012-2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ctype.h>
+#include <poll.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+
+#include <cutils/sockets.h>
+
+#include "LogReader.h"
+#include "FlushCommand.h"
+
+LogReader::LogReader(LogBuffer *logbuf)
+ : SocketListener(getLogSocket(), true)
+ , mLogbuf(*logbuf)
+{ }
+
+// When we are notified a new log entry is available, inform
+// all of our listening sockets.
+void LogReader::notifyNewLog() {
+ FlushCommand command(*this);
+ runOnEachSocket(&command);
+}
+
+bool LogReader::onDataAvailable(SocketClient *cli) {
+ prctl(PR_SET_NAME, "logd.reader");
+
+ char buffer[255];
+
+ int len = read(cli->getSocket(), buffer, sizeof(buffer) - 1);
+ if (len <= 0) {
+ doSocketDelete(cli);
+ return false;
+ }
+ buffer[len] = '\0';
+
+ unsigned long tail = 0;
+ static const char _tail[] = " tail=";
+ char *cp = strstr(buffer, _tail);
+ if (cp) {
+ tail = atol(cp + sizeof(_tail) - 1);
+ }
+
+ log_time start(log_time::EPOCH);
+ static const char _start[] = " start=";
+ cp = strstr(buffer, _start);
+ if (cp) {
+ // Parse errors will result in current time
+ start.strptime(cp + sizeof(_start) - 1, "%s.%q");
+ }
+
+ unsigned int logMask = -1;
+ static const char _logIds[] = " lids=";
+ cp = strstr(buffer, _logIds);
+ if (cp) {
+ logMask = 0;
+ cp += sizeof(_logIds) - 1;
+ while (*cp && *cp != '\0') {
+ int val = 0;
+ while (isdigit(*cp)) {
+ val = val * 10 + *cp - '0';
+ ++cp;
+ }
+ logMask |= 1 << val;
+ if (*cp != ',') {
+ break;
+ }
+ ++cp;
+ }
+ }
+
+ pid_t pid = 0;
+ static const char _pid[] = " pid=";
+ cp = strstr(buffer, _pid);
+ if (cp) {
+ pid = atol(cp + sizeof(_pid) - 1);
+ }
+
+ bool nonBlock = false;
+ if (strncmp(buffer, "dumpAndClose", 12) == 0) {
+ nonBlock = true;
+ }
+
+ // Convert realtime to monotonic time
+ if (start == log_time::EPOCH) {
+ start = LogTimeEntry::EPOCH;
+ } else {
+ class LogFindStart {
+ const pid_t mPid;
+ const unsigned mLogMask;
+ bool startTimeSet;
+ log_time &start;
+ log_time last;
+
+ public:
+ LogFindStart(unsigned logMask, pid_t pid, log_time &start)
+ : mPid(pid)
+ , mLogMask(logMask)
+ , startTimeSet(false)
+ , start(start)
+ , last(LogTimeEntry::EPOCH)
+ { }
+
+ static bool callback(const LogBufferElement *element, void *obj) {
+ LogFindStart *me = reinterpret_cast<LogFindStart *>(obj);
+ if (!me->startTimeSet
+ && (!me->mPid || (me->mPid == element->getPid()))
+ && (me->mLogMask & (1 << element->getLogId()))) {
+ if (me->start == element->getRealTime()) {
+ me->start = element->getMonotonicTime();
+ me->startTimeSet = true;
+ } else {
+ if (me->start < element->getRealTime()) {
+ me->start = me->last;
+ me->startTimeSet = true;
+ }
+ me->last = element->getMonotonicTime();
+ }
+ }
+ return false;
+ }
+
+ bool found() { return startTimeSet; }
+ } logFindStart(logMask, pid, start);
+
+ logbuf().flushTo(cli, LogTimeEntry::EPOCH,
+ FlushCommand::hasReadLogs(cli),
+ logFindStart.callback, &logFindStart);
+
+ if (!logFindStart.found()) {
+ if (nonBlock) {
+ doSocketDelete(cli);
+ return false;
+ }
+ log_time now(CLOCK_MONOTONIC);
+ start = now;
+ }
+ }
+
+ FlushCommand command(*this, nonBlock, tail, logMask, pid, start);
+ command.runSocketCommand(cli);
+ return true;
+}
+
+void LogReader::doSocketDelete(SocketClient *cli) {
+ LastLogTimes &times = mLogbuf.mTimes;
+ LogTimeEntry::lock();
+ LastLogTimes::iterator it = times.begin();
+ while(it != times.end()) {
+ LogTimeEntry *entry = (*it);
+ if (entry->mClient == cli) {
+ times.erase(it);
+ entry->release_Locked();
+ break;
+ }
+ it++;
+ }
+ LogTimeEntry::unlock();
+}
+
+int LogReader::getLogSocket() {
+ static const char socketName[] = "logdr";
+ int sock = android_get_control_socket(socketName);
+
+ if (sock < 0) {
+ sock = socket_local_server(socketName,
+ ANDROID_SOCKET_NAMESPACE_RESERVED,
+ SOCK_SEQPACKET);
+ }
+
+ return sock;
+}
diff --git a/logd/LogReader.h b/logd/LogReader.h
new file mode 100644
index 0000000..91559a3
--- /dev/null
+++ b/logd/LogReader.h
@@ -0,0 +1,43 @@
+/*
+ * 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_WRITER_H__
+#define _LOGD_LOG_WRITER_H__
+
+#include <sysutils/SocketListener.h>
+#include "LogBuffer.h"
+#include "LogTimes.h"
+
+class LogReader : public SocketListener {
+ LogBuffer &mLogbuf;
+
+public:
+ LogReader(LogBuffer *logbuf);
+ void notifyNewLog();
+
+ LogBuffer &logbuf(void) const { return mLogbuf; }
+
+protected:
+ virtual bool onDataAvailable(SocketClient *cli);
+
+private:
+ static int getLogSocket();
+
+ void doSocketDelete(SocketClient *cli);
+
+};
+
+#endif
diff --git a/logd/LogStatistics.cpp b/logd/LogStatistics.cpp
new file mode 100644
index 0000000..81c9bab
--- /dev/null
+++ b/logd/LogStatistics.cpp
@@ -0,0 +1,926 @@
+/*
+ * 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 <stdarg.h>
+#include <time.h>
+
+#include <log/logger.h>
+#include <private/android_filesystem_config.h>
+#include <utils/String8.h>
+
+#include "LogStatistics.h"
+
+PidStatistics::PidStatistics(pid_t pid, char *name)
+ : pid(pid)
+ , mSizesTotal(0)
+ , mElementsTotal(0)
+ , mSizes(0)
+ , mElements(0)
+ , name(name)
+ , mGone(false)
+{ }
+
+#ifdef DO_NOT_ERROR_IF_PIDSTATISTICS_USES_A_COPY_CONSTRUCTOR
+PidStatistics::PidStatistics(const PidStatistics &copy)
+ : pid(copy->pid)
+ , name(copy->name ? strdup(copy->name) : NULL)
+ , mSizesTotal(copy->mSizesTotal)
+ , mElementsTotal(copy->mElementsTotal)
+ , mSizes(copy->mSizes)
+ , mElements(copy->mElements)
+ , mGone(copy->mGone)
+{ }
+#endif
+
+PidStatistics::~PidStatistics() {
+ free(name);
+}
+
+bool PidStatistics::pidGone() {
+ if (mGone) {
+ return true;
+ }
+ if (pid == gone) {
+ return true;
+ }
+ if (kill(pid, 0) && (errno != EPERM)) {
+ mGone = true;
+ return true;
+ }
+ return false;
+}
+
+void PidStatistics::setName(char *new_name) {
+ free(name);
+ name = new_name;
+}
+
+void PidStatistics::add(unsigned short size) {
+ mSizesTotal += size;
+ ++mElementsTotal;
+ mSizes += size;
+ ++mElements;
+}
+
+bool PidStatistics::subtract(unsigned short size) {
+ mSizes -= size;
+ --mElements;
+ return (mElements == 0) && pidGone();
+}
+
+void PidStatistics::addTotal(size_t size, size_t element) {
+ if (pid == gone) {
+ mSizesTotal += size;
+ mElementsTotal += element;
+ }
+}
+
+// must call free to release return value
+char *PidStatistics::pidToName(pid_t pid) {
+ char *retval = NULL;
+ if (pid != gone) {
+ char buffer[512];
+ snprintf(buffer, sizeof(buffer), "/proc/%u/cmdline", pid);
+ int fd = open(buffer, O_RDONLY);
+ if (fd >= 0) {
+ ssize_t ret = read(fd, buffer, sizeof(buffer));
+ if (ret > 0) {
+ buffer[sizeof(buffer)-1] = '\0';
+ // frameworks intermediate state
+ if (strcmp(buffer, "<pre-initialized>")) {
+ retval = strdup(buffer);
+ }
+ }
+ close(fd);
+ }
+ }
+ return retval;
+}
+
+UidStatistics::UidStatistics(uid_t uid)
+ : uid(uid)
+ , mSizes(0)
+ , mElements(0) {
+ Pids.clear();
+}
+
+UidStatistics::~UidStatistics() {
+ PidStatisticsCollection::iterator it;
+ for (it = begin(); it != end();) {
+ delete (*it);
+ it = Pids.erase(it);
+ }
+}
+
+void UidStatistics::add(unsigned short size, pid_t pid) {
+ mSizes += size;
+ ++mElements;
+
+ PidStatistics *p;
+ PidStatisticsCollection::iterator last;
+ PidStatisticsCollection::iterator it;
+ for (last = it = begin(); it != end(); last = it, ++it) {
+ p = *it;
+ if (pid == p->getPid()) {
+ p->add(size);
+ return;
+ }
+ }
+ // insert if the gone entry.
+ bool insert = (last != it) && (p->getPid() == p->gone);
+ p = new PidStatistics(pid, pidToName(pid));
+ if (insert) {
+ Pids.insert(last, p);
+ } else {
+ Pids.push_back(p);
+ }
+ p->add(size);
+}
+
+void UidStatistics::subtract(unsigned short size, pid_t pid) {
+ mSizes -= size;
+ --mElements;
+
+ PidStatisticsCollection::iterator it;
+ for (it = begin(); it != end(); ++it) {
+ PidStatistics *p = *it;
+ if (pid == p->getPid()) {
+ if (p->subtract(size)) {
+ size_t szsTotal = p->sizesTotal();
+ size_t elsTotal = p->elementsTotal();
+ delete p;
+ Pids.erase(it);
+ it = end();
+ --it;
+ if (it == end()) {
+ p = new PidStatistics(p->gone);
+ Pids.push_back(p);
+ } else {
+ p = *it;
+ if (p->getPid() != p->gone) {
+ p = new PidStatistics(p->gone);
+ Pids.push_back(p);
+ }
+ }
+ p->addTotal(szsTotal, elsTotal);
+ }
+ return;
+ }
+ }
+}
+
+void UidStatistics::sort() {
+ for (bool pass = true; pass;) {
+ pass = false;
+ PidStatisticsCollection::iterator it = begin();
+ if (it != end()) {
+ PidStatisticsCollection::iterator lt = it;
+ PidStatistics *l = (*lt);
+ while (++it != end()) {
+ PidStatistics *n = (*it);
+ if ((n->getPid() != n->gone) && (n->sizes() > l->sizes())) {
+ pass = true;
+ Pids.erase(it);
+ Pids.insert(lt, n);
+ it = lt;
+ n = l;
+ }
+ lt = it;
+ l = n;
+ }
+ }
+ }
+}
+
+size_t UidStatistics::sizes(pid_t pid) {
+ if (pid == pid_all) {
+ return sizes();
+ }
+
+ PidStatisticsCollection::iterator it;
+ for (it = begin(); it != end(); ++it) {
+ PidStatistics *p = *it;
+ if (pid == p->getPid()) {
+ return p->sizes();
+ }
+ }
+ return 0;
+}
+
+size_t UidStatistics::elements(pid_t pid) {
+ if (pid == pid_all) {
+ return elements();
+ }
+
+ PidStatisticsCollection::iterator it;
+ for (it = begin(); it != end(); ++it) {
+ PidStatistics *p = *it;
+ if (pid == p->getPid()) {
+ return p->elements();
+ }
+ }
+ return 0;
+}
+
+size_t UidStatistics::sizesTotal(pid_t pid) {
+ size_t sizes = 0;
+ PidStatisticsCollection::iterator it;
+ for (it = begin(); it != end(); ++it) {
+ PidStatistics *p = *it;
+ if ((pid == pid_all) || (pid == p->getPid())) {
+ sizes += p->sizesTotal();
+ }
+ }
+ return sizes;
+}
+
+size_t UidStatistics::elementsTotal(pid_t pid) {
+ size_t elements = 0;
+ PidStatisticsCollection::iterator it;
+ for (it = begin(); it != end(); ++it) {
+ PidStatistics *p = *it;
+ if ((pid == pid_all) || (pid == p->getPid())) {
+ elements += p->elementsTotal();
+ }
+ }
+ return elements;
+}
+
+LidStatistics::LidStatistics() {
+ Uids.clear();
+}
+
+LidStatistics::~LidStatistics() {
+ UidStatisticsCollection::iterator it;
+ for (it = begin(); it != end();) {
+ delete (*it);
+ it = Uids.erase(it);
+ }
+}
+
+void LidStatistics::add(unsigned short size, uid_t uid, pid_t pid) {
+ UidStatistics *u;
+ UidStatisticsCollection::iterator it;
+ UidStatisticsCollection::iterator last;
+
+ if (uid == (uid_t) -1) { // init
+ uid = (uid_t) AID_ROOT;
+ }
+
+ for (last = it = begin(); it != end(); last = it, ++it) {
+ u = *it;
+ if (uid == u->getUid()) {
+ u->add(size, pid);
+ if ((last != it) && ((*last)->sizesTotal() < u->sizesTotal())) {
+ Uids.erase(it);
+ Uids.insert(last, u);
+ }
+ return;
+ }
+ }
+ u = new UidStatistics(uid);
+ if ((last != it) && ((*last)->sizesTotal() < (size_t) size)) {
+ Uids.insert(last, u);
+ } else {
+ Uids.push_back(u);
+ }
+ u->add(size, pid);
+}
+
+void LidStatistics::subtract(unsigned short size, uid_t uid, pid_t pid) {
+ UidStatisticsCollection::iterator it;
+ for (it = begin(); it != end(); ++it) {
+ UidStatistics *u = *it;
+ if (uid == u->getUid()) {
+ u->subtract(size, pid);
+ return;
+ }
+ }
+}
+
+void LidStatistics::sort() {
+ for (bool pass = true; pass;) {
+ pass = false;
+ UidStatisticsCollection::iterator it = begin();
+ if (it != end()) {
+ UidStatisticsCollection::iterator lt = it;
+ UidStatistics *l = (*lt);
+ while (++it != end()) {
+ UidStatistics *n = (*it);
+ if (n->sizes() > l->sizes()) {
+ pass = true;
+ Uids.erase(it);
+ Uids.insert(lt, n);
+ it = lt;
+ n = l;
+ }
+ lt = it;
+ l = n;
+ }
+ }
+ }
+}
+
+size_t LidStatistics::sizes(uid_t uid, pid_t pid) {
+ size_t sizes = 0;
+ UidStatisticsCollection::iterator it;
+ for (it = begin(); it != end(); ++it) {
+ UidStatistics *u = *it;
+ if ((uid == uid_all) || (uid == u->getUid())) {
+ sizes += u->sizes(pid);
+ }
+ }
+ return sizes;
+}
+
+size_t LidStatistics::elements(uid_t uid, pid_t pid) {
+ size_t elements = 0;
+ UidStatisticsCollection::iterator it;
+ for (it = begin(); it != end(); ++it) {
+ UidStatistics *u = *it;
+ if ((uid == uid_all) || (uid == u->getUid())) {
+ elements += u->elements(pid);
+ }
+ }
+ return elements;
+}
+
+size_t LidStatistics::sizesTotal(uid_t uid, pid_t pid) {
+ size_t sizes = 0;
+ UidStatisticsCollection::iterator it;
+ for (it = begin(); it != end(); ++it) {
+ UidStatistics *u = *it;
+ if ((uid == uid_all) || (uid == u->getUid())) {
+ sizes += u->sizesTotal(pid);
+ }
+ }
+ return sizes;
+}
+
+size_t LidStatistics::elementsTotal(uid_t uid, pid_t pid) {
+ size_t elements = 0;
+ UidStatisticsCollection::iterator it;
+ for (it = begin(); it != end(); ++it) {
+ UidStatistics *u = *it;
+ if ((uid == uid_all) || (uid == u->getUid())) {
+ elements += u->elementsTotal(pid);
+ }
+ }
+ return elements;
+}
+
+LogStatistics::LogStatistics()
+ : start(CLOCK_MONOTONIC) {
+ log_id_for_each(i) {
+ mSizes[i] = 0;
+ mElements[i] = 0;
+ }
+
+ dgram_qlen_statistics = false;
+ for(unsigned short bucket = 0; dgram_qlen(bucket); ++bucket) {
+ mMinimum[bucket].tv_sec = mMinimum[bucket].tv_sec_max;
+ mMinimum[bucket].tv_nsec = mMinimum[bucket].tv_nsec_max;
+ }
+}
+
+// Each bucket below represents a dgram_qlen of log messages. By
+// finding the minimum period of time from start to finish
+// of each dgram_qlen, we can get a performance expectation for
+// the user space logger. The net result is that the period
+// of time divided by the dgram_qlen will give us the average time
+// between log messages; at the point where the average time
+// is greater than the throughput capability of the logger
+// we will not longer require the benefits of the FIFO formed
+// by max_dgram_qlen. We will also expect to see a very visible
+// knee in the average time between log messages at this point,
+// so we do not necessarily have to compare the rate against the
+// measured performance (BM_log_maximum_retry) of the logger.
+//
+// for example (reformatted):
+//
+// Minimum time between log events per dgram_qlen:
+// 1 2 3 5 10 20 30 50 100 200 300 400 500 600
+// 5u2 12u 13u 15u 16u 27u 30u 36u 407u 3m1 3m3 3m9 3m9 5m5
+//
+// demonstrates a clear knee rising at 100, so this means that for this
+// case max_dgram_qlen = 100 would be more than sufficient to handle the
+// worst that the system could stuff into the logger. The
+// BM_log_maximum_retry performance (derated by the log collection) on the
+// same system was 33.2us so we would almost be fine with max_dgram_qlen = 50.
+// BM_log_maxumum_retry with statistics off is roughly 20us, so
+// max_dgram_qlen = 20 would work. We will be more than willing to have
+// a large engineering margin so the rule of thumb that lead us to 100 is
+// fine.
+//
+// bucket dgram_qlen are tuned for /proc/sys/net/unix/max_dgram_qlen = 300
+const unsigned short LogStatistics::mBuckets[] = {
+ 1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 400, 500, 600
+};
+
+unsigned short LogStatistics::dgram_qlen(unsigned short bucket) {
+ if (bucket >= sizeof(mBuckets) / sizeof(mBuckets[0])) {
+ return 0;
+ }
+ return mBuckets[bucket];
+}
+
+unsigned long long LogStatistics::minimum(unsigned short bucket) {
+ if (mMinimum[bucket].tv_sec == mMinimum[bucket].tv_sec_max) {
+ return 0;
+ }
+ return mMinimum[bucket].nsec();
+}
+
+void LogStatistics::recordDiff(log_time diff, unsigned short bucket) {
+ if ((diff.tv_sec || diff.tv_nsec) && (mMinimum[bucket] > diff)) {
+ mMinimum[bucket] = diff;
+ }
+}
+
+void LogStatistics::add(unsigned short size,
+ log_id_t log_id, uid_t uid, pid_t pid) {
+ mSizes[log_id] += size;
+ ++mElements[log_id];
+ id(log_id).add(size, uid, pid);
+}
+
+void LogStatistics::subtract(unsigned short size,
+ log_id_t log_id, uid_t uid, pid_t pid) {
+ mSizes[log_id] -= size;
+ --mElements[log_id];
+ id(log_id).subtract(size, uid, pid);
+}
+
+size_t LogStatistics::sizes(log_id_t log_id, uid_t uid, pid_t pid) {
+ if (log_id != log_id_all) {
+ return id(log_id).sizes(uid, pid);
+ }
+ size_t sizes = 0;
+ log_id_for_each(i) {
+ sizes += id(i).sizes(uid, pid);
+ }
+ return sizes;
+}
+
+size_t LogStatistics::elements(log_id_t log_id, uid_t uid, pid_t pid) {
+ if (log_id != log_id_all) {
+ return id(log_id).elements(uid, pid);
+ }
+ size_t elements = 0;
+ log_id_for_each(i) {
+ elements += id(i).elements(uid, pid);
+ }
+ return elements;
+}
+
+size_t LogStatistics::sizesTotal(log_id_t log_id, uid_t uid, pid_t pid) {
+ if (log_id != log_id_all) {
+ return id(log_id).sizesTotal(uid, pid);
+ }
+ size_t sizes = 0;
+ log_id_for_each(i) {
+ sizes += id(i).sizesTotal(uid, pid);
+ }
+ return sizes;
+}
+
+size_t LogStatistics::elementsTotal(log_id_t log_id, uid_t uid, pid_t pid) {
+ if (log_id != log_id_all) {
+ return id(log_id).elementsTotal(uid, pid);
+ }
+ size_t elements = 0;
+ log_id_for_each(i) {
+ elements += id(i).elementsTotal(uid, pid);
+ }
+ return elements;
+}
+
+void LogStatistics::format(char **buf,
+ uid_t uid, unsigned int logMask, log_time oldest) {
+ static const unsigned short spaces_current = 13;
+ static const unsigned short spaces_total = 19;
+
+ if (*buf) {
+ free(*buf);
+ *buf = NULL;
+ }
+
+ android::String8 string(" span -> size/num");
+ size_t oldLength;
+ short spaces = 2;
+
+ log_id_for_each(i) {
+ if (!logMask & (1 << i)) {
+ continue;
+ }
+ oldLength = string.length();
+ if (spaces < 0) {
+ spaces = 0;
+ }
+ string.appendFormat("%*s%s", spaces, "", android_log_id_to_name(i));
+ spaces += spaces_total + oldLength - string.length();
+
+ LidStatistics &l = id(i);
+ l.sort();
+
+ UidStatisticsCollection::iterator iu;
+ for (iu = l.begin(); iu != l.end(); ++iu) {
+ (*iu)->sort();
+ }
+ }
+
+ spaces = 1;
+ log_time t(CLOCK_MONOTONIC);
+ unsigned long long d = t.nsec() - start.nsec();
+ string.appendFormat("\nTotal%4llu:%02llu:%02llu.%09llu",
+ d / NS_PER_SEC / 60 / 60, (d / NS_PER_SEC / 60) % 60,
+ (d / NS_PER_SEC) % 60, d % NS_PER_SEC);
+
+ log_id_for_each(i) {
+ if (!(logMask & (1 << i))) {
+ continue;
+ }
+ oldLength = string.length();
+ if (spaces < 0) {
+ spaces = 0;
+ }
+ string.appendFormat("%*s%zu/%zu", spaces, "",
+ sizesTotal(i), elementsTotal(i));
+ spaces += spaces_total + oldLength - string.length();
+ }
+
+ spaces = 1;
+ d = t.nsec() - oldest.nsec();
+ string.appendFormat("\nNow%6llu:%02llu:%02llu.%09llu",
+ d / NS_PER_SEC / 60 / 60, (d / NS_PER_SEC / 60) % 60,
+ (d / NS_PER_SEC) % 60, d % NS_PER_SEC);
+
+ log_id_for_each(i) {
+ if (!(logMask & (1 << i))) {
+ continue;
+ }
+
+ size_t els = elements(i);
+ if (els) {
+ oldLength = string.length();
+ if (spaces < 0) {
+ spaces = 0;
+ }
+ string.appendFormat("%*s%zu/%zu", spaces, "", sizes(i), els);
+ spaces -= string.length() - oldLength;
+ }
+ spaces += spaces_total;
+ }
+
+ // Construct list of worst spammers by Pid
+ static const unsigned char num_spammers = 10;
+ bool header = false;
+
+ log_id_for_each(i) {
+ if (!(logMask & (1 << i))) {
+ continue;
+ }
+
+ PidStatisticsCollection pids;
+ pids.clear();
+
+ LidStatistics &l = id(i);
+ UidStatisticsCollection::iterator iu;
+ for (iu = l.begin(); iu != l.end(); ++iu) {
+ UidStatistics &u = *(*iu);
+ PidStatisticsCollection::iterator ip;
+ for (ip = u.begin(); ip != u.end(); ++ip) {
+ PidStatistics *p = (*ip);
+ if (p->getPid() == p->gone) {
+ break;
+ }
+
+ size_t mySizes = p->sizes();
+
+ PidStatisticsCollection::iterator q;
+ unsigned char num = 0;
+ for (q = pids.begin(); q != pids.end(); ++q) {
+ if (mySizes > (*q)->sizes()) {
+ pids.insert(q, p);
+ break;
+ }
+ // do we need to traverse deeper in the list?
+ if (++num > num_spammers) {
+ break;
+ }
+ }
+ if (q == pids.end()) {
+ pids.push_back(p);
+ }
+ }
+ }
+
+ size_t threshold = sizes(i);
+ if (threshold < 65536) {
+ threshold = 65536;
+ }
+ threshold /= 100;
+
+ PidStatisticsCollection::iterator pt = pids.begin();
+
+ for(int line = 0;
+ (pt != pids.end()) && (line < num_spammers);
+ ++line, pt = pids.erase(pt)) {
+ PidStatistics *p = *pt;
+
+ size_t sizes = p->sizes();
+ if (sizes < threshold) {
+ break;
+ }
+
+ char *name = p->getName();
+ pid_t pid = p->getPid();
+ if (!name || !*name) {
+ name = pidToName(pid);
+ if (name) {
+ if (*name) {
+ p->setName(name);
+ } else {
+ free(name);
+ name = NULL;
+ }
+ }
+ }
+
+ if (!header) {
+ string.appendFormat("\n\nChattiest clients:\n"
+ "log id %-*s PID[?] name",
+ spaces_total, "size/total");
+ header = true;
+ }
+
+ size_t sizesTotal = p->sizesTotal();
+
+ android::String8 sz("");
+ sz.appendFormat((sizes != sizesTotal) ? "%zu/%zu" : "%zu",
+ sizes, sizesTotal);
+
+ android::String8 pd("");
+ pd.appendFormat("%u%c", pid, p->pidGone() ? '?' : ' ');
+
+ string.appendFormat("\n%-7s%-*s %-7s%s",
+ line ? "" : android_log_id_to_name(i),
+ spaces_total, sz.string(), pd.string(),
+ name ? name : "");
+ }
+
+ pids.clear();
+ }
+
+ if (dgram_qlen_statistics) {
+ const unsigned short spaces_time = 6;
+ const unsigned long long max_seconds = 100000;
+ spaces = 0;
+ string.append("\n\nMinimum time between log events per dgram_qlen:\n");
+ for(unsigned short i = 0; dgram_qlen(i); ++i) {
+ oldLength = string.length();
+ if (spaces < 0) {
+ spaces = 0;
+ }
+ string.appendFormat("%*s%u", spaces, "", dgram_qlen(i));
+ spaces += spaces_time + oldLength - string.length();
+ }
+ string.append("\n");
+ spaces = 0;
+ unsigned short n;
+ for(unsigned short i = 0; (n = dgram_qlen(i)); ++i) {
+ unsigned long long duration = minimum(i);
+ if (duration) {
+ duration /= n;
+ if (duration >= (NS_PER_SEC * max_seconds)) {
+ duration = NS_PER_SEC * (max_seconds - 1);
+ }
+ oldLength = string.length();
+ if (spaces < 0) {
+ spaces = 0;
+ }
+ string.appendFormat("%*s", spaces, "");
+ if (duration >= (NS_PER_SEC * 10)) {
+ string.appendFormat("%llu",
+ (duration + (NS_PER_SEC / 2))
+ / NS_PER_SEC);
+ } else if (duration >= (NS_PER_SEC / (1000 / 10))) {
+ string.appendFormat("%llum",
+ (duration + (NS_PER_SEC / 2 / 1000))
+ / (NS_PER_SEC / 1000));
+ } else if (duration >= (NS_PER_SEC / (1000000 / 10))) {
+ string.appendFormat("%lluu",
+ (duration + (NS_PER_SEC / 2 / 1000000))
+ / (NS_PER_SEC / 1000000));
+ } else {
+ string.appendFormat("%llun", duration);
+ }
+ spaces -= string.length() - oldLength;
+ }
+ spaces += spaces_time;
+ }
+ }
+
+ log_id_for_each(i) {
+ if (!(logMask & (1 << i))) {
+ continue;
+ }
+
+ header = false;
+ bool first = true;
+
+ UidStatisticsCollection::iterator ut;
+ for(ut = id(i).begin(); ut != id(i).end(); ++ut) {
+ UidStatistics *up = *ut;
+ if ((uid != AID_ROOT) && (uid != up->getUid())) {
+ continue;
+ }
+
+ PidStatisticsCollection::iterator pt = up->begin();
+ if (pt == up->end()) {
+ continue;
+ }
+
+ android::String8 intermediate;
+
+ if (!header) {
+ // header below tuned to match spaces_total and spaces_current
+ spaces = 0;
+ intermediate = string.format("%s: UID/PID Total size/num",
+ android_log_id_to_name(i));
+ string.appendFormat("\n\n%-31sNow "
+ "UID/PID[?] Total Now",
+ intermediate.string());
+ intermediate.clear();
+ header = true;
+ }
+
+ bool oneline = ++pt == up->end();
+ --pt;
+
+ if (!oneline) {
+ first = true;
+ } else if (!first && (spaces > 0)) {
+ string.appendFormat("%*s", spaces, "");
+ }
+ spaces = 0;
+
+ uid_t u = up->getUid();
+ PidStatistics *pp = *pt;
+ pid_t p = pp->getPid();
+
+ intermediate = string.format(oneline
+ ? ((p == PidStatistics::gone)
+ ? "%d/?"
+ : "%d/%d%c")
+ : "%d",
+ u, p, pp->pidGone() ? '?' : '\0');
+ string.appendFormat(first ? "\n%-12s" : "%-12s",
+ intermediate.string());
+ intermediate.clear();
+
+ size_t elsTotal = up->elementsTotal();
+ oldLength = string.length();
+ string.appendFormat("%zu/%zu", up->sizesTotal(), elsTotal);
+ spaces += spaces_total + oldLength - string.length();
+
+ size_t els = up->elements();
+ if (els == elsTotal) {
+ if (spaces < 0) {
+ spaces = 0;
+ }
+ string.appendFormat("%*s=", spaces, "");
+ spaces = -1;
+ } else if (els) {
+ oldLength = string.length();
+ if (spaces < 0) {
+ spaces = 0;
+ }
+ string.appendFormat("%*s%zu/%zu", spaces, "", up->sizes(), els);
+ spaces -= string.length() - oldLength;
+ }
+ spaces += spaces_current;
+
+ first = !first;
+
+ if (oneline) {
+ continue;
+ }
+
+ size_t gone_szs = 0;
+ size_t gone_els = 0;
+
+ for(; pt != up->end(); ++pt) {
+ pp = *pt;
+ p = pp->getPid();
+
+ // If a PID no longer has any current logs, and is not
+ // active anymore, skip & report totals for gone.
+ elsTotal = pp->elementsTotal();
+ size_t szsTotal = pp->sizesTotal();
+ if (p == pp->gone) {
+ gone_szs += szsTotal;
+ gone_els += elsTotal;
+ continue;
+ }
+ els = pp->elements();
+ bool gone = pp->pidGone();
+ if (gone && (els == 0)) {
+ // ToDo: garbage collection: move this statistical bucket
+ // from its current UID/PID to UID/? (races and
+ // wrap around are our achilles heel). Below is
+ // merely lipservice to catch PIDs that were still
+ // around when the stats were pruned to zero.
+ gone_szs += szsTotal;
+ gone_els += elsTotal;
+ continue;
+ }
+
+ if (!first && (spaces > 0)) {
+ string.appendFormat("%*s", spaces, "");
+ }
+ spaces = 0;
+
+ intermediate = string.format(gone ? "%d/%d?" : "%d/%d", u, p);
+ string.appendFormat(first ? "\n%-12s" : "%-12s",
+ intermediate.string());
+ intermediate.clear();
+
+ oldLength = string.length();
+ string.appendFormat("%zu/%zu", szsTotal, elsTotal);
+ spaces += spaces_total + oldLength - string.length();
+
+ if (els == elsTotal) {
+ if (spaces < 0) {
+ spaces = 0;
+ }
+ string.appendFormat("%*s=", spaces, "");
+ spaces = -1;
+ } else if (els) {
+ oldLength = string.length();
+ if (spaces < 0) {
+ spaces = 0;
+ }
+ string.appendFormat("%*s%zu/%zu", spaces, "",
+ pp->sizes(), els);
+ spaces -= string.length() - oldLength;
+ }
+ spaces += spaces_current;
+
+ first = !first;
+ }
+
+ if (gone_els) {
+ if (!first && (spaces > 0)) {
+ string.appendFormat("%*s", spaces, "");
+ }
+
+ intermediate = string.format("%d/?", u);
+ string.appendFormat(first ? "\n%-12s" : "%-12s",
+ intermediate.string());
+ intermediate.clear();
+
+ spaces = spaces_total + spaces_current;
+
+ oldLength = string.length();
+ string.appendFormat("%zu/%zu", gone_szs, gone_els);
+ spaces -= string.length() - oldLength;
+
+ first = !first;
+ }
+ }
+ }
+
+ *buf = strdup(string.string());
+}
+
+uid_t LogStatistics::pidToUid(pid_t pid) {
+ log_id_for_each(i) {
+ LidStatistics &l = id(i);
+ UidStatisticsCollection::iterator iu;
+ for (iu = l.begin(); iu != l.end(); ++iu) {
+ UidStatistics &u = *(*iu);
+ PidStatisticsCollection::iterator ip;
+ for (ip = u.begin(); ip != u.end(); ++ip) {
+ if ((*ip)->getPid() == pid) {
+ return u.getUid();
+ }
+ }
+ }
+ }
+ return getuid(); // associate this with the logger
+}
diff --git a/logd/LogStatistics.h b/logd/LogStatistics.h
new file mode 100644
index 0000000..3733137
--- /dev/null
+++ b/logd/LogStatistics.h
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _LOGD_LOG_STATISTICS_H__
+#define _LOGD_LOG_STATISTICS_H__
+
+#include <sys/types.h>
+
+#include <log/log.h>
+#include <log/log_read.h>
+#include <utils/List.h>
+
+#define log_id_for_each(i) \
+ for (log_id_t i = LOG_ID_MIN; i < LOG_ID_MAX; i = (log_id_t) (i + 1))
+
+class PidStatistics {
+ const pid_t pid;
+
+ // Total
+ size_t mSizesTotal;
+ size_t mElementsTotal;
+ // Current
+ size_t mSizes;
+ size_t mElements;
+
+ char *name;
+ bool mGone;
+
+public:
+ static const pid_t gone = (pid_t) -1;
+
+ PidStatistics(pid_t pid, char *name = NULL);
+ PidStatistics(const PidStatistics &copy);
+ ~PidStatistics();
+
+ pid_t getPid() const { return pid; }
+ bool pidGone();
+ char *getName() const { return name; }
+ void setName(char *name);
+
+ void add(unsigned short size);
+ bool subtract(unsigned short size); // returns true if stats and PID gone
+ void addTotal(size_t size, size_t element);
+
+ size_t sizes() const { return mSizes; }
+ size_t elements() const { return mElements; }
+
+ size_t sizesTotal() const { return mSizesTotal; }
+ size_t elementsTotal() const { return mElementsTotal; }
+
+ // helper
+ static char *pidToName(pid_t pid);
+};
+
+typedef android::List<PidStatistics *> PidStatisticsCollection;
+
+class UidStatistics {
+ const uid_t uid;
+
+ PidStatisticsCollection Pids;
+
+ size_t mSizes;
+ size_t mElements;
+
+public:
+ UidStatistics(uid_t uid);
+ ~UidStatistics();
+
+ PidStatisticsCollection::iterator begin() { return Pids.begin(); }
+ PidStatisticsCollection::iterator end() { return Pids.end(); }
+
+ uid_t getUid() { return uid; }
+
+ void add(unsigned short size, pid_t pid);
+ void subtract(unsigned short size, pid_t pid);
+ void sort();
+
+ static const pid_t pid_all = (pid_t) -1;
+
+ // fast track current value
+ size_t sizes() const { return mSizes; };
+ size_t elements() const { return mElements; };
+
+ // statistical track
+ size_t sizes(pid_t pid);
+ size_t elements(pid_t pid);
+
+ size_t sizesTotal(pid_t pid = pid_all);
+ size_t elementsTotal(pid_t pid = pid_all);
+
+ // helper
+ static char *pidToName(pid_t pid) { return PidStatistics::pidToName(pid); }
+};
+
+typedef android::List<UidStatistics *> UidStatisticsCollection;
+
+class LidStatistics {
+ UidStatisticsCollection Uids;
+
+public:
+ LidStatistics();
+ ~LidStatistics();
+
+ UidStatisticsCollection::iterator begin() { return Uids.begin(); }
+ UidStatisticsCollection::iterator end() { return Uids.end(); }
+
+ void add(unsigned short size, uid_t uid, pid_t pid);
+ void subtract(unsigned short size, uid_t uid, pid_t pid);
+ void sort();
+
+ static const pid_t pid_all = (pid_t) -1;
+ static const uid_t uid_all = (uid_t) -1;
+
+ size_t sizes(uid_t uid = uid_all, pid_t pid = pid_all);
+ size_t elements(uid_t uid = uid_all, pid_t pid = pid_all);
+
+ size_t sizesTotal(uid_t uid = uid_all, pid_t pid = pid_all);
+ size_t elementsTotal(uid_t uid = uid_all, pid_t pid = pid_all);
+};
+
+// Log Statistics
+class LogStatistics {
+ LidStatistics LogIds[LOG_ID_MAX];
+
+ size_t mSizes[LOG_ID_MAX];
+ size_t mElements[LOG_ID_MAX];
+
+ bool dgram_qlen_statistics;
+
+ static const unsigned short mBuckets[14];
+ log_time mMinimum[sizeof(mBuckets) / sizeof(mBuckets[0])];
+
+public:
+ const log_time start;
+
+ LogStatistics();
+
+ LidStatistics &id(log_id_t log_id) { return LogIds[log_id]; }
+
+ void enableDgramQlenStatistics() { dgram_qlen_statistics = true; }
+ static unsigned short dgram_qlen(unsigned short bucket);
+ unsigned long long minimum(unsigned short bucket);
+ void recordDiff(log_time diff, unsigned short bucket);
+
+ void add(unsigned short size, log_id_t log_id, uid_t uid, pid_t pid);
+ void subtract(unsigned short size, log_id_t log_id, uid_t uid, pid_t pid);
+ void sort();
+
+ // fast track current value by id only
+ size_t sizes(log_id_t id) const { return mSizes[id]; }
+ size_t elements(log_id_t id) const { return mElements[id]; }
+
+ // statistical track
+ static const log_id_t log_id_all = (log_id_t) -1;
+ static const uid_t uid_all = (uid_t) -1;
+ static const pid_t pid_all = (pid_t) -1;
+
+ size_t sizes(log_id_t id, uid_t uid, pid_t pid = pid_all);
+ size_t elements(log_id_t id, uid_t uid, pid_t pid = pid_all);
+ size_t sizes() { return sizes(log_id_all, uid_all); }
+ size_t elements() { return elements(log_id_all, uid_all); }
+
+ size_t sizesTotal(log_id_t id = log_id_all,
+ uid_t uid = uid_all,
+ pid_t pid = pid_all);
+ size_t elementsTotal(log_id_t id = log_id_all,
+ uid_t uid = uid_all,
+ pid_t pid = pid_all);
+
+ // *strp = malloc, balance with free
+ void format(char **strp, uid_t uid, unsigned int logMask, log_time oldest);
+
+ // helper
+ static char *pidToName(pid_t pid) { return PidStatistics::pidToName(pid); }
+ uid_t pidToUid(pid_t pid);
+};
+
+#endif // _LOGD_LOG_STATISTICS_H__
diff --git a/logd/LogTimes.cpp b/logd/LogTimes.cpp
new file mode 100644
index 0000000..e7e3ec2
--- /dev/null
+++ b/logd/LogTimes.cpp
@@ -0,0 +1,242 @@
+/*
+ * 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;
+
+const struct timespec LogTimeEntry::EPOCH = { 0, 1 };
+
+LogTimeEntry::LogTimeEntry(LogReader &reader, SocketClient *client,
+ bool nonBlock, unsigned long tail,
+ unsigned int logMask, pid_t pid,
+ log_time start)
+ : mRefCount(1)
+ , mRelease(false)
+ , mError(false)
+ , threadRunning(false)
+ , threadTriggered(true)
+ , mReader(reader)
+ , mLogMask(logMask)
+ , mPid(pid)
+ , skipAhead(0)
+ , mCount(0)
+ , mTail(tail)
+ , mIndex(0)
+ , mClient(client)
+ , mStart(start)
+ , mNonBlock(nonBlock)
+ , mEnd(CLOCK_MONOTONIC)
+{ }
+
+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();
+
+ me->threadRunning = false;
+ if (me->mNonBlock) {
+ me->error_Locked();
+ }
+
+ SocketClient *client = me->mClient;
+
+ if (me->isError_Locked()) {
+ LogReader &reader = me->mReader;
+ LastLogTimes &times = reader.logbuf().mTimes;
+
+ LastLogTimes::iterator it = times.begin();
+ while(it != times.end()) {
+ if (*it == me) {
+ times.erase(it);
+ me->release_Locked();
+ break;
+ }
+ it++;
+ }
+
+ me->mClient = NULL;
+ reader.release(client);
+ }
+
+ if (client) {
+ client->decRef();
+ }
+
+ me->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();
+ pthread_exit(NULL);
+ }
+
+ LogBuffer &logbuf = me->mReader.logbuf();
+
+ bool privileged = FlushCommand::hasReadLogs(client);
+
+ lock();
+
+ me->threadTriggered = true;
+
+ while(me->threadTriggered && !me->isError_Locked()) {
+
+ me->threadTriggered = false;
+
+ log_time start = me->mStart;
+
+ unlock();
+
+ if (me->mTail) {
+ logbuf.flushTo(client, start, privileged, FilterFirstPass, me);
+ }
+ start = logbuf.flushTo(client, start, privileged, FilterSecondPass, me);
+
+ if (start == LogBufferElement::FLUSH_ERROR) {
+ me->error();
+ }
+
+ if (me->mNonBlock) {
+ lock();
+ break;
+ }
+
+ sched_yield();
+
+ lock();
+ }
+
+ unlock();
+
+ pthread_exit(NULL);
+
+ pthread_cleanup_pop(true);
+
+ return NULL;
+}
+
+// A first pass to count the number of elements
+bool LogTimeEntry::FilterFirstPass(const LogBufferElement *element, void *obj) {
+ LogTimeEntry *me = reinterpret_cast<LogTimeEntry *>(obj);
+
+ LogTimeEntry::lock();
+
+ if (me->mCount == 0) {
+ me->mStart = element->getMonotonicTime();
+ }
+
+ if ((!me->mPid || (me->mPid == element->getPid()))
+ && (me->mLogMask & (1 << element->getLogId()))) {
+ ++me->mCount;
+ }
+
+ LogTimeEntry::unlock();
+
+ return false;
+}
+
+// A second pass to send the selected elements
+bool LogTimeEntry::FilterSecondPass(const LogBufferElement *element, void *obj) {
+ LogTimeEntry *me = reinterpret_cast<LogTimeEntry *>(obj);
+
+ LogTimeEntry::lock();
+
+ if (me->skipAhead) {
+ me->skipAhead--;
+ goto skip;
+ }
+
+ me->mStart = element->getMonotonicTime();
+
+ // Truncate to close race between first and second pass
+ if (me->mNonBlock && me->mTail && (me->mIndex >= me->mCount)) {
+ goto skip;
+ }
+
+ if ((me->mLogMask & (1 << element->getLogId())) == 0) {
+ goto skip;
+ }
+
+ if (me->mPid && (me->mPid != element->getPid())) {
+ goto skip;
+ }
+
+ if (me->isError_Locked()) {
+ goto skip;
+ }
+
+ 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) {
+ LogTimeEntry::unlock();
+ return true;
+ }
+ // FALLTHRU
+
+skip:
+ LogTimeEntry::unlock();
+ return false;
+}
diff --git a/logd/LogTimes.h b/logd/LogTimes.h
new file mode 100644
index 0000000..beaf646
--- /dev/null
+++ b/logd/LogTimes.h
@@ -0,0 +1,106 @@
+/*
+ * 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>
+
+class LogReader;
+
+class LogTimeEntry {
+ static pthread_mutex_t timesLock;
+ unsigned int mRefCount;
+ bool mRelease;
+ bool mError;
+ bool threadRunning;
+ bool threadTriggered;
+ 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;
+ 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,
+ log_time start);
+
+ SocketClient *mClient;
+ static const struct timespec EPOCH;
+ log_time mStart;
+ const bool mNonBlock;
+ const log_time mEnd; // only relevant if mNonBlock
+
+ // Protect List manipulations
+ static void lock(void) { pthread_mutex_lock(&timesLock); }
+ static void unlock(void) { pthread_mutex_unlock(&timesLock); }
+
+ void startReader_Locked(void);
+
+ bool runningReader_Locked(void) const {
+ return threadRunning || mRelease || mError || mNonBlock;
+ }
+ void triggerReader_Locked(void) { threadTriggered = true; }
+ void triggerSkip_Locked(unsigned int skip) { skipAhead = skip; }
+
+ // Called after LogTimeEntry removed from list, lock implicitly held
+ void release_Locked(void) {
+ mRelease = true;
+ 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(); mError = true; 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;
+ }
+
+ // flushTo filter callbacks
+ static bool FilterFirstPass(const LogBufferElement *element, void *me);
+ static bool FilterSecondPass(const LogBufferElement *element, void *me);
+};
+
+typedef android::List<LogTimeEntry *> LastLogTimes;
+
+#endif
diff --git a/logd/LogWhiteBlackList.cpp b/logd/LogWhiteBlackList.cpp
new file mode 100644
index 0000000..e87b604
--- /dev/null
+++ b/logd/LogWhiteBlackList.cpp
@@ -0,0 +1,239 @@
+/*
+ * 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) {
+ asprintf(strp, (mPid != pid_all) ? "%u/%u" : "%u", mUid, mPid);
+ } else {
+ // NB: mPid == pid_all can not happen if mUid == uid_all
+ asprintf(strp, (mPid != pid_all) ? "/%u" : "/", mPid);
+ }
+}
+
+PruneList::PruneList()
+ : mWorstUidEnabled(false) {
+ 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 = false;
+ 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
new file mode 100644
index 0000000..769d651
--- /dev/null
+++ b/logd/LogWhiteBlackList.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _LOGD_LOG_WHITE_BLACK_LIST_H__
+#define _LOGD_LOG_WHITE_BLACK_LIST_H__
+
+#include <sys/types.h>
+
+#include <utils/List.h>
+
+#include <LogBufferElement.h>
+
+// White and Blacklist
+
+class Prune {
+ friend class PruneList;
+
+ const uid_t mUid;
+ const pid_t mPid;
+ int cmp(uid_t uid, pid_t pid) const;
+
+public:
+ static const uid_t uid_all = (uid_t) -1;
+ static const pid_t pid_all = (pid_t) -1;
+
+ Prune(uid_t uid, pid_t pid);
+
+ uid_t getUid() const { return mUid; }
+ pid_t getPid() const { return mPid; }
+
+ int cmp(LogBufferElement *e) const { return cmp(e->getUid(), e->getPid()); }
+
+ // *strp is malloc'd, use free to release
+ void format(char **strp);
+};
+
+typedef android::List<Prune *> PruneCollection;
+
+class PruneList {
+ PruneCollection mNaughty;
+ PruneCollection mNice;
+ bool mWorstUidEnabled;
+
+public:
+ PruneList();
+ ~PruneList();
+
+ int init(char *str);
+
+ bool naughty(LogBufferElement *element);
+ bool nice(LogBufferElement *element);
+ bool worstUidEnabled() const { return mWorstUidEnabled; }
+
+ // *strp is malloc'd, use free to release
+ void format(char **strp);
+};
+
+#endif // _LOGD_LOG_WHITE_BLACK_LIST_H__
diff --git a/logd/README.auditd b/logd/README.auditd
new file mode 100644
index 0000000..3f614a3
--- /dev/null
+++ b/logd/README.auditd
@@ -0,0 +1,17 @@
+Auditd Daemon
+
+The audit daemon is a simplified version of its desktop
+counterpart designed to gather the audit logs from the
+audit kernel subsystem. The audit subsystem of the kernel
+includes Linux Security Modules (LSM) messages as well.
+
+To enable the audit subsystem, you must add this to your
+kernel config:
+CONFIG_AUDIT=y
+
+To enable a LSM, you must consult that LSM's documentation, the
+example below is for SELinux:
+CONFIG_SECURITY_SELINUX=y
+
+This does not include possible dependencies that may need to be
+satisfied for that particular LSM.
diff --git a/logd/README.property b/logd/README.property
new file mode 100644
index 0000000..f4b3c3c
--- /dev/null
+++ b/logd/README.property
@@ -0,0 +1,25 @@
+The properties that logd responds 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.statistics.dgram_qlen bool false Record dgram_qlen statistics. This
+ represents a performance impact and
+ is used to determine the platform's
+ minimum domain socket network FIFO
+ size (see source for details) based
+ on typical load (logcat -S to view)
+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
+
+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.
diff --git a/logd/event.logtags b/logd/event.logtags
new file mode 100644
index 0000000..a63f034
--- /dev/null
+++ b/logd/event.logtags
@@ -0,0 +1,36 @@
+# The entries in this file map a sparse set of log tag numbers to tag names.
+# This is installed on the device, in /system/etc, and parsed by logcat.
+#
+# Tag numbers are decimal integers, from 0 to 2^31. (Let's leave the
+# negative values alone for now.)
+#
+# Tag names are one or more ASCII letters and numbers or underscores, i.e.
+# "[A-Z][a-z][0-9]_". Do not include spaces or punctuation (the former
+# impacts log readability, the latter makes regex searches more annoying).
+#
+# Tag numbers and names are separated by whitespace. Blank lines and lines
+# starting with '#' are ignored.
+#
+# 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
+#
+# 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
+# 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)
diff --git a/logd/libaudit.c b/logd/libaudit.c
new file mode 100644
index 0000000..ca88d1b
--- /dev/null
+++ b/logd/libaudit.c
@@ -0,0 +1,276 @@
+/*
+ * 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_set_pid(int fd, uint32_t pid, rep_wait_t wmode)
+{
+ 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;
+
+ /* 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. Only wait if the
+ * wmode is != WAIT_NO
+ */
+ if (wmode != WAIT_NO) {
+ /* 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, 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.h b/logd/libaudit.h
new file mode 100644
index 0000000..cb114f9
--- /dev/null
+++ b/logd/libaudit.h
@@ -0,0 +1,104 @@
+/*
+ * 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>
+ */
+
+#ifndef _LIBAUDIT_H_
+#define _LIBAUDIT_H_
+
+#include <stdint.h>
+#include <sys/cdefs.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <linux/netlink.h>
+#include <linux/audit.h>
+
+__BEGIN_DECLS
+
+#define MAX_AUDIT_MESSAGE_LENGTH 8970
+
+typedef enum {
+ GET_REPLY_BLOCKING=0,
+ GET_REPLY_NONBLOCKING
+} reply_t;
+
+typedef enum {
+ WAIT_NO,
+ WAIT_YES
+} rep_wait_t;
+
+/* type == AUDIT_SIGNAL_INFO */
+struct audit_sig_info {
+ uid_t uid;
+ pid_t pid;
+ char ctx[0];
+};
+
+struct audit_message {
+ struct nlmsghdr nlh;
+ char data[MAX_AUDIT_MESSAGE_LENGTH];
+};
+
+/**
+ * Opens a connection to the Audit netlink socket
+ * @return
+ * 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);
+
+/**
+ * Closes the fd returned from audit_open()
+ * @param fd
+ * The fd to close
+ */
+extern void audit_close(int fd);
+
+/**
+ *
+ * @param fd
+ * The fd returned by a call to audit_open()
+ * @param rep
+ * The response struct to store the response in.
+ * @param block
+ * Whether or not to block on IO
+ * @param peek
+ * Whether or not we are to remove the message from
+ * the queue when we do a read on the netlink socket.
+ * @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);
+
+/**
+ * Sets a pid to recieve 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
+ * @param wmode
+ * Whether or not to block on the underlying socket io calls.
+ * @return
+ * This function returns 0 on success, -errno on error.
+ */
+extern int audit_set_pid(int fd, uint32_t pid, rep_wait_t wmode);
+
+__END_DECLS
+
+#endif
diff --git a/logd/main.cpp b/logd/main.cpp
new file mode 100644
index 0000000..ece5a3a
--- /dev/null
+++ b/logd/main.cpp
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2012-2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/capability.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <linux/prctl.h>
+
+#include <cutils/properties.h>
+
+#include "private/android_filesystem_config.h"
+#include "CommandListener.h"
+#include "LogBuffer.h"
+#include "LogListener.h"
+#include "LogAudit.h"
+
+//
+// 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
+//
+
+static int drop_privs() {
+ struct sched_param param;
+ memset(&param, 0, sizeof(param));
+
+ if (sched_setscheduler((pid_t) 0, SCHED_BATCH, &param) < 0) {
+ return -1;
+ }
+
+ if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
+ return -1;
+ }
+
+ if (setgid(AID_LOGD) != 0) {
+ return -1;
+ }
+
+ if (setuid(AID_LOGD) != 0) {
+ return -1;
+ }
+
+ 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, "");
+
+ if (!strcasecmp(property, "true")) {
+ return true;
+ }
+ if (!strcasecmp(property, "false")) {
+ return false;
+ }
+
+ return def;
+}
+
+// Foreground waits for exit of the three main persistent threads that
+// are started here. The three threads are created to manage UNIX
+// domain client sockets for writing, reading and controlling the user
+// space logger. Additional transitory per-client threads are created
+// for each reader once they register.
+int main() {
+ bool auditd = property_get_bool("logd.auditd", true);
+
+ int fdDmesg = -1;
+ if (auditd && property_get_bool("logd.auditd.dmesg", true)) {
+ fdDmesg = open("/dev/kmsg", O_WRONLY);
+ }
+
+ if (drop_privs() != 0) {
+ return -1;
+ }
+
+ // 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.
+
+ LastLogTimes *times = new LastLogTimes();
+
+ // LogBuffer is the object which is responsible for holding all
+ // log entries.
+
+ LogBuffer *logBuf = new LogBuffer(times);
+
+ if (property_get_bool("logd.statistics.dgram_qlen", false)) {
+ logBuf->enableDgramQlenStatistics();
+ }
+
+ // 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);
+ if (reader->startListener()) {
+ exit(1);
+ }
+
+ // 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);
+ }
+
+ // Command listener listens on /dev/socket/logd for incoming logd
+ // administrative commands.
+
+ CommandListener *cl = new CommandListener(logBuf, reader, swl);
+ if (cl->startListener()) {
+ exit(1);
+ }
+
+ // 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.
+
+ if (auditd) {
+ // failure is an option ... messages are in dmesg (required by standard)
+ LogAudit *al = new LogAudit(logBuf, reader, fdDmesg);
+ if (al->startListener()) {
+ delete al;
+ close(fdDmesg);
+ }
+ }
+
+ pause();
+ exit(0);
+}
+
diff --git a/logd/tests/Android.mk b/logd/tests/Android.mk
new file mode 100644
index 0000000..f851288
--- /dev/null
+++ b/logd/tests/Android.mk
@@ -0,0 +1,53 @@
+#
+# 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_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+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
new file mode 100644
index 0000000..957fdb5
--- /dev/null
+++ b/logd/tests/logd_test.cpp
@@ -0,0 +1,703 @@
+/*
+ * 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 one of the following:
+ //
+ // main: UID/PID Total size/num Now UID/PID[?] Total
+ // 0 7500306/304207 71608/3183 0/4225? 7454388/303656
+ // <wrap> 93432/1012
+ // -or-
+ // 0/gone 7454388/303656 93432/1012
+ //
+ // basically if we see a *large* number of 0/????? entries
+ unsigned long value;
+ do {
+ char *benchmark = strstr(cp, " 0/");
+ char *benchmark_newline = strstr(cp, "\n0/");
+ if (!benchmark) {
+ benchmark = benchmark_newline;
+ }
+ if (benchmark_newline && (benchmark > benchmark_newline)) {
+ benchmark = benchmark_newline;
+ }
+ cp = benchmark;
+ if (!cp) {
+ break;
+ }
+ cp += 3;
+ while (isdigit(*cp) || (*cp == 'g') || (*cp == 'o') || (*cp == 'n')) {
+ ++cp;
+ }
+ value = 0;
+ // ###? or gone
+ if ((*cp == '?') || (*cp == 'e')) {
+ while (*++cp == ' ');
+ while (isdigit(*cp)) {
+ value = value * 10ULL + *cp - '0';
+ ++cp;
+ }
+ if (*cp != '/') {
+ value = 0;
+ continue;
+ }
+ while (isdigit(*++cp));
+ while (*cp == ' ') ++cp;
+ if (!isdigit(*cp)) {
+ value = 0;
+ }
+ }
+ } while ((value < 900000ULL) && *cp);
+ return cp;
+}
+
+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, "\nmain:");
+ EXPECT_TRUE(NULL != main_logs);
+
+ char *radio_logs = strstr(cp, "\nradio:");
+ EXPECT_TRUE(NULL != radio_logs);
+
+ char *system_logs = strstr(cp, "\nsystem:");
+ EXPECT_TRUE(NULL != system_logs);
+
+ char *events_logs = strstr(cp, "\nevents:");
+ EXPECT_TRUE(NULL != events_logs);
+#endif
+
+ // Parse timing stats
+
+ cp = strstr(cp, "Minimum time between log events per dgram_qlen:");
+
+ if (cp) {
+ while (*cp && (*cp != '\n')) {
+ ++cp;
+ }
+ if (*cp == '\n') {
+ ++cp;
+ }
+
+ char *list_of_spans = cp;
+ EXPECT_NE('\0', *list_of_spans);
+
+ unsigned short number_of_buckets = 0;
+ unsigned short *dgram_qlen = NULL;
+ unsigned short bucket = 0;
+ while (*cp && (*cp != '\n')) {
+ bucket = 0;
+ while (isdigit(*cp)) {
+ bucket = bucket * 10 + *cp - '0';
+ ++cp;
+ }
+ while (*cp == ' ') {
+ ++cp;
+ }
+ if (!bucket) {
+ break;
+ }
+ unsigned short *new_dgram_qlen = new unsigned short[number_of_buckets + 1];
+ EXPECT_TRUE(new_dgram_qlen != NULL);
+ if (dgram_qlen) {
+ memcpy(new_dgram_qlen, dgram_qlen, sizeof(*dgram_qlen) * number_of_buckets);
+ delete [] dgram_qlen;
+ }
+
+ dgram_qlen = new_dgram_qlen;
+ dgram_qlen[number_of_buckets++] = bucket;
+ }
+
+ char *end_of_spans = cp;
+ EXPECT_NE('\0', *end_of_spans);
+
+ EXPECT_LT(5, number_of_buckets);
+
+ unsigned long long *times = new unsigned long long [number_of_buckets];
+ ASSERT_TRUE(times != NULL);
+
+ memset(times, 0, sizeof(*times) * number_of_buckets);
+
+ while (*cp == '\n') {
+ ++cp;
+ }
+
+ unsigned short number_of_values = 0;
+ unsigned long long value;
+ while (*cp && (*cp != '\n')) {
+ EXPECT_GE(number_of_buckets, number_of_values);
+
+ value = 0;
+ while (isdigit(*cp)) {
+ value = value * 10ULL + *cp - '0';
+ ++cp;
+ }
+
+ switch(*cp) {
+ case ' ':
+ case '\n':
+ value *= 1000ULL;
+ /* FALLTHRU */
+ case 'm':
+ value *= 1000ULL;
+ /* FALLTHRU */
+ case 'u':
+ value *= 1000ULL;
+ /* FALLTHRU */
+ case 'n':
+ default:
+ break;
+ }
+ while (*++cp == ' ');
+
+ if (!value) {
+ break;
+ }
+
+ times[number_of_values] = value;
+ ++number_of_values;
+ }
+
+#ifdef TARGET_USES_LOGD
+ EXPECT_EQ(number_of_values, number_of_buckets);
+#endif
+
+ FILE *fp;
+ ASSERT_TRUE(NULL != (fp = fopen("/proc/sys/net/unix/max_dgram_qlen", "r")));
+
+ unsigned max_dgram_qlen = 0;
+ fscanf(fp, "%u", &max_dgram_qlen);
+
+ fclose(fp);
+
+ // Find launch point
+ unsigned short launch = 0;
+ unsigned long long total = 0;
+ do {
+ total += times[launch];
+ } while (((++launch < number_of_buckets)
+ && ((total / launch) >= (times[launch] / 8ULL)))
+ || (launch == 1)); // too soon
+
+ bool failure = number_of_buckets <= launch;
+ if (!failure) {
+ unsigned short l = launch;
+ if (l >= number_of_buckets) {
+ l = number_of_buckets - 1;
+ }
+ failure = max_dgram_qlen < dgram_qlen[l];
+ }
+
+ // We can get failure if at any time liblog_benchmarks has been run
+ // because designed to overload /proc/sys/net/unix/max_dgram_qlen even
+ // at excessive values like 20000. It does so to measure the raw processing
+ // performance of logd.
+ if (failure) {
+ cp = find_benchmark_spam(cp);
+ }
+
+ if (cp) {
+ // Fake a failure, but without the failure code
+ if (number_of_buckets <= launch) {
+ printf ("Expected: number_of_buckets > launch, actual: %u vs %u\n",
+ number_of_buckets, launch);
+ }
+ if (launch >= number_of_buckets) {
+ launch = number_of_buckets - 1;
+ }
+ if (max_dgram_qlen < dgram_qlen[launch]) {
+ printf ("Expected: max_dgram_qlen >= dgram_qlen[%d],"
+ " actual: %u vs %u\n",
+ launch, max_dgram_qlen, dgram_qlen[launch]);
+ }
+ } else
+#ifndef TARGET_USES_LOGD
+ if (total)
+#endif
+ {
+ EXPECT_GT(number_of_buckets, launch);
+ if (launch >= number_of_buckets) {
+ launch = number_of_buckets - 1;
+ }
+ EXPECT_GE(max_dgram_qlen, dgram_qlen[launch]);
+ }
+
+ delete [] dgram_qlen;
+ delete [] times;
+ }
+ 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) {
+ fprintf(stderr, (*cp != '\n') ? "%c" : "\\n", *cp);
+ ++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) ? "WARNING" : "ok",
+ (user_logger_content && kernel_logger_content) ? "ERROR" : "ok");
+
+ if (user_logger_available && kernel_logger_available) {
+ printf("WARNING: kernel & user logger; both consuming resources!!!\n");
+ }
+
+ 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(100000UL, ns[log_maximum_retry]); // 42777 user
+#else
+ EXPECT_GE(10000UL, ns[log_maximum_retry]); // 5636 kernel
+#endif
+
+#ifdef TARGET_USES_LOGD
+ EXPECT_GE(30000UL, ns[log_maximum]); // 27305 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]); // 121876 user
+#else
+ EXPECT_GE(100000UL, ns[log_overhead]); // 50945 kernel
+#endif
+
+#ifdef TARGET_USES_LOGD
+ EXPECT_GE(7500UL, ns[log_latency]); // 3718 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.
+ // Total Now
+ // 0/4225? 7454388/303656 31488/755
+ // ^-- 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);
+}