diff options
Diffstat (limited to 'logd')
-rw-r--r-- | logd/Android.mk | 12 | ||||
-rw-r--r-- | logd/CommandListener.cpp | 39 | ||||
-rw-r--r-- | logd/CommandListener.h | 6 | ||||
-rw-r--r-- | logd/LogAudit.cpp | 203 | ||||
-rw-r--r-- | logd/LogAudit.h | 41 | ||||
-rw-r--r-- | logd/LogBuffer.cpp | 87 | ||||
-rw-r--r-- | logd/LogBuffer.h | 17 | ||||
-rw-r--r-- | logd/LogListener.cpp | 25 | ||||
-rw-r--r-- | logd/LogReader.cpp | 19 | ||||
-rw-r--r-- | logd/LogReader.h | 2 | ||||
-rw-r--r-- | logd/LogStatistics.cpp | 457 | ||||
-rw-r--r-- | logd/LogStatistics.h | 45 | ||||
-rw-r--r-- | logd/LogTimes.cpp | 25 | ||||
-rw-r--r-- | logd/LogWhiteBlackList.cpp | 4 | ||||
-rw-r--r-- | logd/README.auditd | 17 | ||||
-rw-r--r-- | logd/README.property | 12 | ||||
-rw-r--r-- | logd/libaudit.c | 276 | ||||
-rw-r--r-- | logd/libaudit.h | 104 | ||||
-rw-r--r-- | logd/main.cpp | 77 | ||||
-rw-r--r-- | logd/tests/Android.mk | 53 | ||||
-rw-r--r-- | logd/tests/logd_test.cpp | 677 |
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 ©) + : 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 ©); + ~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); +} |