summaryrefslogtreecommitdiffstats
path: root/logd
diff options
context:
space:
mode:
Diffstat (limited to 'logd')
-rw-r--r--logd/Android.mk12
-rw-r--r--logd/CommandListener.cpp39
-rw-r--r--logd/CommandListener.h6
-rw-r--r--logd/LogAudit.cpp203
-rw-r--r--logd/LogAudit.h41
-rw-r--r--logd/LogBuffer.cpp87
-rw-r--r--logd/LogBuffer.h17
-rw-r--r--logd/LogListener.cpp25
-rw-r--r--logd/LogReader.cpp19
-rw-r--r--logd/LogReader.h2
-rw-r--r--logd/LogStatistics.cpp457
-rw-r--r--logd/LogStatistics.h45
-rw-r--r--logd/LogTimes.cpp25
-rw-r--r--logd/LogWhiteBlackList.cpp4
-rw-r--r--logd/README.auditd17
-rw-r--r--logd/README.property12
-rw-r--r--logd/libaudit.c276
-rw-r--r--logd/libaudit.h104
-rw-r--r--logd/main.cpp77
-rw-r--r--logd/tests/Android.mk53
-rw-r--r--logd/tests/logd_test.cpp677
21 files changed, 2052 insertions, 146 deletions
diff --git a/logd/Android.mk b/logd/Android.mk
index b0bc746..9f4c64f 100644
--- a/logd/Android.mk
+++ b/logd/Android.mk
@@ -4,10 +4,6 @@ include $(CLEAR_VARS)
LOCAL_MODULE:= logd
-ifneq ($(filter userdebug eng,$(TARGET_BUILD_VARIANT)),)
-LOCAL_CFLAGS += -DUSERDEBUG_BUILD=1
-endif
-
LOCAL_SRC_FILES := \
main.cpp \
LogCommand.cpp \
@@ -19,7 +15,9 @@ LOCAL_SRC_FILES := \
LogBufferElement.cpp \
LogTimes.cpp \
LogStatistics.cpp \
- LogWhiteBlackList.cpp
+ LogWhiteBlackList.cpp \
+ libaudit.c \
+ LogAudit.cpp
LOCAL_SHARED_LIBRARIES := \
libsysutils \
@@ -27,6 +25,8 @@ LOCAL_SHARED_LIBRARIES := \
libcutils \
libutils
-LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := -Werror
include $(BUILD_EXECUTABLE)
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/logd/CommandListener.cpp b/logd/CommandListener.cpp
index 12b10ca..9d7d152 100644
--- a/logd/CommandListener.cpp
+++ b/logd/CommandListener.cpp
@@ -21,9 +21,11 @@
#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>
@@ -32,20 +34,16 @@
CommandListener::CommandListener(LogBuffer *buf, LogReader * /*reader*/,
LogListener * /*swl*/)
- : FrameworkListener("logd")
+ : FrameworkListener(getLogSocket())
, mBuf(*buf) {
// registerCmd(new ShutdownCmd(buf, writer, swl));
registerCmd(new ClearCmd(buf));
registerCmd(new GetBufSizeCmd(buf));
-#ifdef USERDEBUG_BUILD
registerCmd(new SetBufSizeCmd(buf));
-#endif
registerCmd(new GetBufSizeUsedCmd(buf));
registerCmd(new GetStatisticsCmd(buf));
-#ifdef USERDEBUG_BUILD
registerCmd(new SetPruneListCmd(buf));
registerCmd(new GetPruneListCmd(buf));
-#endif
}
CommandListener::ShutdownCmd::ShutdownCmd(LogBuffer *buf, LogReader *reader,
@@ -69,8 +67,13 @@ CommandListener::ClearCmd::ClearCmd(LogBuffer *buf)
, mBuf(*buf)
{ }
+static void setname() {
+ prctl(PR_SET_NAME, "logd.control");
+}
+
int CommandListener::ClearCmd::runCommand(SocketClient *cli,
int argc, char **argv) {
+ setname();
if (!clientHasLogCredentials(cli)) {
cli->sendMsg("Permission Denied");
return 0;
@@ -99,6 +102,7 @@ CommandListener::GetBufSizeCmd::GetBufSizeCmd(LogBuffer *buf)
int CommandListener::GetBufSizeCmd::runCommand(SocketClient *cli,
int argc, char **argv) {
+ setname();
if (argc < 2) {
cli->sendMsg("Missing Argument");
return 0;
@@ -117,8 +121,6 @@ int CommandListener::GetBufSizeCmd::runCommand(SocketClient *cli,
return 0;
}
-#ifdef USERDEBUG_BUILD
-
CommandListener::SetBufSizeCmd::SetBufSizeCmd(LogBuffer *buf)
: LogCommand("setLogSize")
, mBuf(*buf)
@@ -126,6 +128,7 @@ CommandListener::SetBufSizeCmd::SetBufSizeCmd(LogBuffer *buf)
int CommandListener::SetBufSizeCmd::runCommand(SocketClient *cli,
int argc, char **argv) {
+ setname();
if (!clientHasLogCredentials(cli)) {
cli->sendMsg("Permission Denied");
return 0;
@@ -152,8 +155,6 @@ int CommandListener::SetBufSizeCmd::runCommand(SocketClient *cli,
return 0;
}
-#endif // USERDEBUG_BUILD
-
CommandListener::GetBufSizeUsedCmd::GetBufSizeUsedCmd(LogBuffer *buf)
: LogCommand("getLogSizeUsed")
, mBuf(*buf)
@@ -161,6 +162,7 @@ CommandListener::GetBufSizeUsedCmd::GetBufSizeUsedCmd(LogBuffer *buf)
int CommandListener::GetBufSizeUsedCmd::runCommand(SocketClient *cli,
int argc, char **argv) {
+ setname();
if (argc < 2) {
cli->sendMsg("Missing Argument");
return 0;
@@ -204,8 +206,8 @@ static void package_string(char **strp) {
int CommandListener::GetStatisticsCmd::runCommand(SocketClient *cli,
int argc, char **argv) {
+ setname();
uid_t uid = cli->getUid();
- gid_t gid = cli->getGid();
if (clientHasLogCredentials(cli)) {
uid = AID_ROOT;
}
@@ -236,8 +238,6 @@ int CommandListener::GetStatisticsCmd::runCommand(SocketClient *cli,
return 0;
}
-#ifdef USERDEBUG_BUILD
-
CommandListener::GetPruneListCmd::GetPruneListCmd(LogBuffer *buf)
: LogCommand("getPruneList")
, mBuf(*buf)
@@ -245,6 +245,7 @@ CommandListener::GetPruneListCmd::GetPruneListCmd(LogBuffer *buf)
int CommandListener::GetPruneListCmd::runCommand(SocketClient *cli,
int /*argc*/, char ** /*argv*/) {
+ setname();
char *buf = NULL;
mBuf.formatPrune(&buf);
if (!buf) {
@@ -264,6 +265,7 @@ CommandListener::SetPruneListCmd::SetPruneListCmd(LogBuffer *buf)
int CommandListener::SetPruneListCmd::runCommand(SocketClient *cli,
int argc, char **argv) {
+ setname();
if (!clientHasLogCredentials(cli)) {
cli->sendMsg("Permission Denied");
return 0;
@@ -294,4 +296,15 @@ int CommandListener::SetPruneListCmd::runCommand(SocketClient *cli,
return 0;
}
-#endif // USERDEBUG_BUILD
+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
index de1dcb9..cd1c306 100644
--- a/logd/CommandListener.h
+++ b/logd/CommandListener.h
@@ -31,6 +31,8 @@ public:
virtual ~CommandListener() {}
private:
+ static int getLogSocket();
+
class ShutdownCmd : public LogCommand {
LogBuffer &mBuf;
LogReader &mReader;
@@ -53,15 +55,11 @@ private:
LogBufferCmd(Clear)
LogBufferCmd(GetBufSize)
-#ifdef USERDEBUG_BUILD
LogBufferCmd(SetBufSize)
-#endif
LogBufferCmd(GetBufSizeUsed)
LogBufferCmd(GetStatistics)
-#ifdef USERDEBUG_BUILD
LogBufferCmd(GetPruneList)
LogBufferCmd(SetPruneList)
-#endif
};
#endif
diff --git a/logd/LogAudit.cpp b/logd/LogAudit.cpp
new file mode 100644
index 0000000..add0f0e
--- /dev/null
+++ b/logd/LogAudit.cpp
@@ -0,0 +1,203 @@
+/*
+ * 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;
+}
+
+#define AUDIT_LOG_ID LOG_ID_MAIN
+#define AUDIT_LOG_PRIO ANDROID_LOG_WARN
+
+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;
+ }
+
+ 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);
+ char *cp;
+ if (timeptr
+ && ((cp = now.strptime(timeptr + sizeof(audit_str) - 1, "%s.%q")))
+ && (*cp == ':')) {
+ memcpy(timeptr + sizeof(audit_str) - 1, "0.0", 3);
+ strcpy(timeptr + sizeof(audit_str) - 1 + 3, cp);
+ } 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);
+ strcpy(pidptr, cp);
+ }
+
+ static const char comm_str[] = " comm=\"";
+ char *comm = strstr(str, comm_str);
+ if (comm) {
+ cp = comm;
+ comm += sizeof(comm_str) - 1;
+ char *ecomm = strchr(comm, '"');
+ if (ecomm) {
+ *ecomm = '\0';
+ }
+ comm = strdup(comm);
+ if (ecomm) {
+ strcpy(cp, ecomm + 1);
+ }
+ } else if (pid == getpid()) {
+ pid = tid;
+ comm = strdup("auditd");
+ } else if (!(comm = logbuf->pidToName(pid))) {
+ comm = strdup("unknown");
+ }
+
+ size_t l = strlen(comm) + 1;
+ size_t n = l + strlen(str) + 2;
+
+ char *newstr = reinterpret_cast<char *>(malloc(n));
+ if (!newstr) {
+ free(comm);
+ free(str);
+ return -ENOMEM;
+ }
+
+ *newstr = AUDIT_LOG_PRIO;
+ strcpy(newstr + 1, comm);
+ free(comm);
+ strcpy(newstr + 1 + l, str);
+ free(str);
+
+ logbuf->log(AUDIT_LOG_ID, now, uid, pid, tid, newstr,
+ (n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX);
+ reader->notifyNewLog();
+
+ free(newstr);
+
+ 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
index 1c5cef0..38a237c 100644
--- a/logd/LogBuffer.cpp
+++ b/logd/LogBuffer.cpp
@@ -28,20 +28,16 @@
// Default
#define LOG_BUFFER_SIZE (256 * 1024) // Tuned on a per-platform basis here?
-#ifdef USERDEBUG_BUILD
#define log_buffer_size(id) mMaxSize[id]
-#else
-#define log_buffer_size(id) LOG_BUFFER_SIZE
-#endif
LogBuffer::LogBuffer(LastLogTimes *times)
: mTimes(*times) {
pthread_mutex_init(&mLogElementsLock, NULL);
-#ifdef USERDEBUG_BUILD
+ dgram_qlen_statistics = false;
+
log_id_for_each(i) {
mMaxSize[i] = LOG_BUFFER_SIZE;
}
-#endif
}
void LogBuffer::log(log_id_t log_id, log_time realtime,
@@ -61,6 +57,23 @@ void LogBuffer::log(log_id_t log_id, log_time realtime,
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;
@@ -152,22 +165,16 @@ void LogBuffer::prune(log_id_t id, unsigned long pruneRows) {
size_t worst_sizes = 0;
size_t second_worst_sizes = 0;
-#ifdef USERDEBUG_BUILD
- if (mPrune.worstUidEnabled())
-#endif
- {
+ if ((id != LOG_ID_CRASH) && mPrune.worstUidEnabled()) {
LidStatistics &l = stats.id(id);
- UidStatisticsCollection::iterator iu;
- for (iu = l.begin(); iu != l.end(); ++iu) {
- UidStatistics *u = (*iu);
- size_t sizes = u->sizes();
- if (worst_sizes < sizes) {
- second_worst_sizes = worst_sizes;
- worst_sizes = sizes;
- worst = u->getUid();
- }
- if ((second_worst_sizes < sizes) && (sizes < worst_sizes)) {
- second_worst_sizes = sizes;
+ 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();
}
}
}
@@ -198,9 +205,7 @@ void LogBuffer::prune(log_id_t id, unsigned long pruneRows) {
break;
}
worst_sizes -= len;
- }
-#ifdef USERDEBUG_BUILD
- else if (mPrune.naughty(e)) { // BlackListed
+ } else if (mPrune.naughty(e)) { // BlackListed
it = mLogElements.erase(it);
stats.subtract(e->getMsgLen(), id, uid, e->getPid());
delete e;
@@ -208,34 +213,23 @@ void LogBuffer::prune(log_id_t id, unsigned long pruneRows) {
if (pruneRows == 0) {
break;
}
- }
-#endif
- else {
+ } else {
++it;
}
}
- if (!kick
-#ifdef USERDEBUG_BUILD
- || !mPrune.worstUidEnabled()
-#endif
- ) {
+ if (!kick || !mPrune.worstUidEnabled()) {
break; // the following loop will ask bad clients to skip/drop
}
}
-#ifdef USERDEBUG_BUILD
bool whitelist = false;
-#endif
it = mLogElements.begin();
while((pruneRows > 0) && (it != mLogElements.end())) {
LogBufferElement *e = *it;
if (e->getLogId() == id) {
if (oldest && (oldest->mStart <= e->getMonotonicTime())) {
-#ifdef USERDEBUG_BUILD
- if (!whitelist)
-#endif
- {
+ if (!whitelist) {
if (stats.sizes(id) > (2 * log_buffer_size(id))) {
// kick a misbehaving log reader client off the island
oldest->release_Locked();
@@ -245,13 +239,13 @@ void LogBuffer::prune(log_id_t id, unsigned long pruneRows) {
}
break;
}
-#ifdef USERDEBUG_BUILD
+
if (mPrune.nice(e)) { // WhiteListed
whitelist = true;
it++;
continue;
}
-#endif
+
it = mLogElements.erase(it);
stats.subtract(e->getMsgLen(), id, e->getUid(), e->getPid());
delete e;
@@ -261,7 +255,6 @@ void LogBuffer::prune(log_id_t id, unsigned long pruneRows) {
}
}
-#ifdef USERDEBUG_BUILD
if (whitelist && (pruneRows > 0)) {
it = mLogElements.begin();
while((it != mLogElements.end()) && (pruneRows > 0)) {
@@ -285,7 +278,6 @@ void LogBuffer::prune(log_id_t id, unsigned long pruneRows) {
}
}
}
-#endif
LogTimeEntry::unlock();
}
@@ -305,8 +297,6 @@ unsigned long LogBuffer::getSizeUsed(log_id_t id) {
return retval;
}
-#ifdef USERDEBUG_BUILD
-
// set the total space allocated to "id"
int LogBuffer::setSize(log_id_t id, unsigned long size) {
// Reasonable limits ...
@@ -327,15 +317,6 @@ unsigned long LogBuffer::getSize(log_id_t id) {
return retval;
}
-#else // ! USERDEBUG_BUILD
-
-// get the total space allocated to "id"
-unsigned long LogBuffer::getSize(log_id_t /*id*/) {
- return log_buffer_size(id);
-}
-
-#endif
-
log_time LogBuffer::flushTo(
SocketClient *reader, const log_time start, bool privileged,
bool (*filter)(const LogBufferElement *element, void *arg), void *arg) {
diff --git a/logd/LogBuffer.h b/logd/LogBuffer.h
index cbbb2ce..b8a54b9 100644
--- a/logd/LogBuffer.h
+++ b/logd/LogBuffer.h
@@ -36,11 +36,11 @@ class LogBuffer {
LogStatistics stats;
-#ifdef USERDEBUG_BUILD
+ bool dgram_qlen_statistics;
+
PruneList mPrune;
unsigned long mMaxSize[LOG_ID_MAX];
-#endif
public:
LastLogTimes &mTimes;
@@ -57,18 +57,23 @@ public:
void clear(log_id_t id);
unsigned long getSize(log_id_t id);
-#ifdef USERDEBUG_BUILD
int setSize(log_id_t id, unsigned long size);
-#endif
unsigned long getSizeUsed(log_id_t id);
// *strp uses malloc, use free to release.
void formatStatistics(char **strp, uid_t uid, unsigned int logMask);
-#ifdef USERDEBUG_BUILD
+ 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); }
-#endif
+
+ // 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);
diff --git a/logd/LogListener.cpp b/logd/LogListener.cpp
index b835b4f..8186cea 100644
--- a/logd/LogListener.cpp
+++ b/logd/LogListener.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <limits.h>
+#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
@@ -31,7 +33,9 @@ LogListener::LogListener(LogBuffer *buf, LogReader *reader)
{ }
bool LogListener::onDataAvailable(SocketClient *cli) {
- char buffer[sizeof_log_id_t + sizeof(log_time) + sizeof(char)
+ 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));
@@ -97,17 +101,24 @@ bool LogListener::onDataAvailable(SocketClient *cli) {
// NB: hdr.msg_flags & MSG_TRUNC is not tested, silently passing a
// truncated message to the logs.
- unsigned short len = n; // cap to internal maximum
- if (len == n) {
- logbuf->log(log_id, realtime, cred->uid, cred->pid, tid, msg, len);
- reader->notifyNewLog();
- }
+
+ 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() {
- int sock = android_get_control_socket("logdw");
+ 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;
diff --git a/logd/LogReader.cpp b/logd/LogReader.cpp
index 60a3507..8458c19 100644
--- a/logd/LogReader.cpp
+++ b/logd/LogReader.cpp
@@ -16,14 +16,16 @@
#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("logdr", true)
+ : SocketListener(getLogSocket(), true)
, mLogbuf(*logbuf)
{ }
@@ -35,6 +37,8 @@ void LogReader::notifyNewLog() {
}
bool LogReader::onDataAvailable(SocketClient *cli) {
+ prctl(PR_SET_NAME, "logd.reader");
+
char buffer[255];
int len = read(cli->getSocket(), buffer, sizeof(buffer) - 1);
@@ -167,3 +171,16 @@ void LogReader::doSocketDelete(SocketClient *cli) {
}
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
index b267c75..91559a3 100644
--- a/logd/LogReader.h
+++ b/logd/LogReader.h
@@ -34,6 +34,8 @@ protected:
virtual bool onDataAvailable(SocketClient *cli);
private:
+ static int getLogSocket();
+
void doSocketDelete(SocketClient *cli);
};
diff --git a/logd/LogStatistics.cpp b/logd/LogStatistics.cpp
index 49ee50d..81c9bab 100644
--- a/logd/LogStatistics.cpp
+++ b/logd/LogStatistics.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <fcntl.h>
#include <stdarg.h>
#include <time.h>
@@ -23,12 +24,50 @@
#include "LogStatistics.h"
-PidStatistics::PidStatistics(pid_t pid)
+PidStatistics::PidStatistics(pid_t pid, char *name)
: pid(pid)
, mSizesTotal(0)
, mElementsTotal(0)
, mSizes(0)
- , mElements(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;
@@ -40,7 +79,7 @@ void PidStatistics::add(unsigned short size) {
bool PidStatistics::subtract(unsigned short size) {
mSizes -= size;
--mElements;
- return mElements == 0 && kill(pid, 0);
+ return (mElements == 0) && pidGone();
}
void PidStatistics::addTotal(size_t size, size_t element) {
@@ -50,8 +89,32 @@ void PidStatistics::addTotal(size_t size, size_t 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) {
+ : uid(uid)
+ , mSizes(0)
+ , mElements(0) {
Pids.clear();
}
@@ -64,6 +127,9 @@ UidStatistics::~UidStatistics() {
}
void UidStatistics::add(unsigned short size, pid_t pid) {
+ mSizes += size;
+ ++mElements;
+
PidStatistics *p;
PidStatisticsCollection::iterator last;
PidStatisticsCollection::iterator it;
@@ -71,19 +137,12 @@ void UidStatistics::add(unsigned short size, pid_t pid) {
p = *it;
if (pid == p->getPid()) {
p->add(size);
- // poor-man sort, bubble upwards if bigger than last
- if ((last != it) && ((*last)->sizesTotal() < p->sizesTotal())) {
- Pids.erase(it);
- Pids.insert(last, p);
- }
return;
}
}
- // poor-man sort, insert if bigger than last or last is the gone entry.
- bool insert = (last != it)
- && ((p->getPid() == p->gone)
- || ((*last)->sizesTotal() < (size_t) size));
- p = new PidStatistics(pid);
+ // 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 {
@@ -93,6 +152,9 @@ void UidStatistics::add(unsigned short size, pid_t pid) {
}
void UidStatistics::subtract(unsigned short size, pid_t pid) {
+ mSizes -= size;
+ --mElements;
+
PidStatisticsCollection::iterator it;
for (it = begin(); it != end(); ++it) {
PidStatistics *p = *it;
@@ -121,28 +183,57 @@ void UidStatistics::subtract(unsigned short size, pid_t pid) {
}
}
+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) {
- size_t sizes = 0;
+ if (pid == pid_all) {
+ return sizes();
+ }
+
PidStatisticsCollection::iterator it;
for (it = begin(); it != end(); ++it) {
PidStatistics *p = *it;
- if ((pid == pid_all) || (pid == p->getPid())) {
- sizes += p->sizes();
+ if (pid == p->getPid()) {
+ return p->sizes();
}
}
- return sizes;
+ return 0;
}
size_t UidStatistics::elements(pid_t pid) {
- size_t elements = 0;
+ if (pid == pid_all) {
+ return elements();
+ }
+
PidStatisticsCollection::iterator it;
for (it = begin(); it != end(); ++it) {
PidStatistics *p = *it;
- if ((pid == pid_all) || (pid == p->getPid())) {
- elements += p->elements();
+ if (pid == p->getPid()) {
+ return p->elements();
}
}
- return elements;
+ return 0;
}
size_t UidStatistics::sizesTotal(pid_t pid) {
@@ -221,6 +312,29 @@ void LidStatistics::subtract(unsigned short size, uid_t uid, pid_t pid) {
}
}
+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;
@@ -275,6 +389,66 @@ LogStatistics::LogStatistics()
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,
@@ -337,11 +511,11 @@ size_t LogStatistics::elementsTotal(log_id_t log_id, uid_t uid, pid_t pid) {
void LogStatistics::format(char **buf,
uid_t uid, unsigned int logMask, log_time oldest) {
- const unsigned short spaces_current = 13;
- const unsigned short spaces_total = 19;
+ static const unsigned short spaces_current = 13;
+ static const unsigned short spaces_total = 19;
if (*buf) {
- free(buf);
+ free(*buf);
*buf = NULL;
}
@@ -350,10 +524,22 @@ void LogStatistics::format(char **buf,
short spaces = 2;
log_id_for_each(i) {
- if (logMask & (1 << i)) {
- oldLength = string.length();
- string.appendFormat("%*s%s", spaces, "", android_log_id_to_name(i));
- spaces += spaces_total + oldLength - string.length();
+ 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();
}
}
@@ -369,6 +555,9 @@ void LogStatistics::format(char **buf,
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();
@@ -388,18 +577,170 @@ void LogStatistics::format(char **buf,
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;
}
- bool header = false;
+ 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;
@@ -433,21 +774,22 @@ void LogStatistics::format(char **buf,
if (!oneline) {
first = true;
- } else if (!first && spaces) {
+ } else if (!first && (spaces > 0)) {
string.appendFormat("%*s", spaces, "");
}
spaces = 0;
uid_t u = up->getUid();
- pid_t p = (*pt)->getPid();
+ PidStatistics *pp = *pt;
+ pid_t p = pp->getPid();
intermediate = string.format(oneline
? ((p == PidStatistics::gone)
? "%d/?"
- : "%d/%d")
+ : "%d/%d%c")
: "%d",
- u, p);
- string.appendFormat((first) ? "\n%-12s" : "%-12s",
+ u, p, pp->pidGone() ? '?' : '\0');
+ string.appendFormat(first ? "\n%-12s" : "%-12s",
intermediate.string());
intermediate.clear();
@@ -458,10 +800,16 @@ void LogStatistics::format(char **buf,
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;
}
@@ -477,8 +825,8 @@ void LogStatistics::format(char **buf,
size_t gone_els = 0;
for(; pt != up->end(); ++pt) {
- PidStatistics *pp = *pt;
- pid_t p = pp->getPid();
+ pp = *pt;
+ p = pp->getPid();
// If a PID no longer has any current logs, and is not
// active anymore, skip & report totals for gone.
@@ -490,7 +838,7 @@ void LogStatistics::format(char **buf,
continue;
}
els = pp->elements();
- bool gone = kill(p, 0);
+ bool gone = pp->pidGone();
if (gone && (els == 0)) {
// ToDo: garbage collection: move this statistical bucket
// from its current UID/PID to UID/? (races and
@@ -502,13 +850,13 @@ void LogStatistics::format(char **buf,
continue;
}
- if (!first && spaces) {
+ 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.format(gone ? "%d/%d?" : "%d/%d", u, p);
+ string.appendFormat(first ? "\n%-12s" : "%-12s",
intermediate.string());
intermediate.clear();
@@ -517,10 +865,16 @@ void LogStatistics::format(char **buf,
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;
@@ -531,12 +885,12 @@ void LogStatistics::format(char **buf,
}
if (gone_els) {
- if (!first && spaces) {
+ if (!first && (spaces > 0)) {
string.appendFormat("%*s", spaces, "");
}
intermediate = string.format("%d/?", u);
- string.appendFormat((first) ? "\n%-12s" : "%-12s",
+ string.appendFormat(first ? "\n%-12s" : "%-12s",
intermediate.string());
intermediate.clear();
@@ -553,3 +907,20 @@ void LogStatistics::format(char **buf,
*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
index d44afa2..3733137 100644
--- a/logd/LogStatistics.h
+++ b/logd/LogStatistics.h
@@ -36,12 +36,20 @@ class PidStatistics {
size_t mSizes;
size_t mElements;
+ char *name;
+ bool mGone;
+
public:
static const pid_t gone = (pid_t) -1;
- PidStatistics(pid_t pid);
+ 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
@@ -52,6 +60,9 @@ public:
size_t sizesTotal() const { return mSizesTotal; }
size_t elementsTotal() const { return mElementsTotal; }
+
+ // helper
+ static char *pidToName(pid_t pid);
};
typedef android::List<PidStatistics *> PidStatisticsCollection;
@@ -61,6 +72,9 @@ class UidStatistics {
PidStatisticsCollection Pids;
+ size_t mSizes;
+ size_t mElements;
+
public:
UidStatistics(uid_t uid);
~UidStatistics();
@@ -72,14 +86,23 @@ public:
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;
- size_t sizes(pid_t pid = pid_all);
- size_t elements(pid_t pid = pid_all);
+ // 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;
@@ -96,6 +119,7 @@ public:
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;
@@ -114,6 +138,11 @@ class LogStatistics {
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;
@@ -121,8 +150,14 @@ public:
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]; }
@@ -147,6 +182,10 @@ public:
// *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
index 8cb015c..1a9a548 100644
--- a/logd/LogTimes.cpp
+++ b/logd/LogTimes.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <sys/prctl.h>
+
#include "FlushCommand.h"
#include "LogBuffer.h"
#include "LogTimes.h"
@@ -46,14 +48,25 @@ LogTimeEntry::LogTimeEntry(LogReader &reader, SocketClient *client,
{ }
void LogTimeEntry::startReader_Locked(void) {
+ pthread_attr_t attr;
+
threadRunning = true;
- if (pthread_create(&mThread, NULL, LogTimeEntry::threadStart, this)) {
- threadRunning = false;
- if (mClient) {
- mClient->decRef();
+
+ 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;
+ }
}
- decRef_Locked();
+ pthread_attr_destroy(&attr);
+ }
+ threadRunning = false;
+ if (mClient) {
+ mClient->decRef();
}
+ decRef_Locked();
}
void LogTimeEntry::threadStop(void *obj) {
@@ -96,6 +109,8 @@ void LogTimeEntry::threadStop(void *obj) {
}
void *LogTimeEntry::threadStart(void *obj) {
+ prctl(PR_SET_NAME, "logd.reader.per");
+
LogTimeEntry *me = reinterpret_cast<LogTimeEntry *>(obj);
pthread_cleanup_push(threadStop, obj);
diff --git a/logd/LogWhiteBlackList.cpp b/logd/LogWhiteBlackList.cpp
index 5f8173f..c6c7b23 100644
--- a/logd/LogWhiteBlackList.cpp
+++ b/logd/LogWhiteBlackList.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#ifdef USERDEBUG_BUILD
-
#include <ctype.h>
#include <utils/String8.h>
@@ -239,5 +237,3 @@ bool PruneList::nice(LogBufferElement *element) {
}
return false;
}
-
-#endif // USERDEBUG_BUILD
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..5d92d09
--- /dev/null
+++ b/logd/README.property
@@ -0,0 +1,12 @@
+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)
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
index 6216b95..ece5a3a 100644
--- a/logd/main.cpp
+++ b/logd/main.cpp
@@ -24,13 +24,46 @@
#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;
@@ -60,7 +93,10 @@ static int drop_privs() {
capheader.pid = 0;
capdata[CAP_TO_INDEX(CAP_SYSLOG)].permitted = CAP_TO_MASK(CAP_SYSLOG);
- capdata[CAP_TO_INDEX(CAP_SYSLOG)].effective = 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;
@@ -71,12 +107,34 @@ static int drop_privs() {
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;
}
@@ -92,6 +150,10 @@ int main() {
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.
@@ -118,6 +180,19 @@ int main() {
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..123e317
--- /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 \
+
+ifeq ($(TARGET_USES_LOGD),true)
+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..5b51b1f
--- /dev/null
+++ b/logd/tests/logd_test.cpp
@@ -0,0 +1,677 @@
+/*
+ * 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) {
+ if (strncmp(benchmarks[i], buffer, strlen(benchmarks[i]))) {
+ continue;
+ }
+ sscanf(buffer, "%*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(25000UL, ns[log_maximum]); // 14055 user
+#else
+ EXPECT_GE(10000UL, ns[log_maximum]); // 5637 kernel
+#endif
+
+ EXPECT_GE(4000UL, ns[clock_overhead]); // 2008
+
+#ifdef TARGET_USES_LOGD
+ EXPECT_GE(250000UL, ns[log_overhead]); // 113219 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]); // 9542541 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 nowSize = atol(benchmark_statistics_found);
+
+ delete [] buf;
+
+ ASSERT_NE(0UL, nowSize);
+
+ int sock = socket_local_client("logd",
+ ANDROID_SOCKET_NAMESPACE_RESERVED,
+ SOCK_STREAM);
+ static const unsigned long expected_absolute_minimum_log_size = 65536UL;
+ unsigned long totalSize = expected_absolute_minimum_log_size;
+ if (sock >= 0) {
+ 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) {
+ 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, nowSize * 2);
+}