diff options
Diffstat (limited to 'logd')
31 files changed, 5278 insertions, 0 deletions
diff --git a/logd/Android.mk b/logd/Android.mk new file mode 100644 index 0000000..188511f --- /dev/null +++ b/logd/Android.mk @@ -0,0 +1,33 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE:= logd + +LOCAL_SRC_FILES := \ + main.cpp \ + LogCommand.cpp \ + CommandListener.cpp \ + LogListener.cpp \ + LogReader.cpp \ + FlushCommand.cpp \ + LogBuffer.cpp \ + LogBufferElement.cpp \ + LogTimes.cpp \ + LogStatistics.cpp \ + LogWhiteBlackList.cpp \ + libaudit.c \ + LogAudit.cpp \ + event.logtags + +LOCAL_SHARED_LIBRARIES := \ + libsysutils \ + liblog \ + libcutils \ + libutils + +LOCAL_CFLAGS := -Werror $(shell sed -n 's/^\([0-9]*\)[ \t]*auditd[ \t].*/-DAUDITD_LOG_TAG=\1/p' $(LOCAL_PATH)/event.logtags) + +include $(BUILD_EXECUTABLE) + +include $(call first-makefiles-under,$(LOCAL_PATH)) diff --git a/logd/CommandListener.cpp b/logd/CommandListener.cpp new file mode 100644 index 0000000..d7088b4 --- /dev/null +++ b/logd/CommandListener.cpp @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2012-2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <arpa/inet.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <string.h> +#include <stdlib.h> +#include <sys/prctl.h> +#include <sys/socket.h> +#include <sys/types.h> + +#include <cutils/sockets.h> +#include <private/android_filesystem_config.h> +#include <sysutils/SocketClient.h> + +#include "CommandListener.h" +#include "LogCommand.h" + +CommandListener::CommandListener(LogBuffer *buf, LogReader * /*reader*/, + LogListener * /*swl*/) + : FrameworkListener(getLogSocket()) + , mBuf(*buf) { + // registerCmd(new ShutdownCmd(buf, writer, swl)); + registerCmd(new ClearCmd(buf)); + registerCmd(new GetBufSizeCmd(buf)); + registerCmd(new SetBufSizeCmd(buf)); + registerCmd(new GetBufSizeUsedCmd(buf)); + registerCmd(new GetStatisticsCmd(buf)); + registerCmd(new SetPruneListCmd(buf)); + registerCmd(new GetPruneListCmd(buf)); +} + +CommandListener::ShutdownCmd::ShutdownCmd(LogBuffer *buf, LogReader *reader, + LogListener *swl) + : LogCommand("shutdown") + , mBuf(*buf) + , mReader(*reader) + , mSwl(*swl) +{ } + +int CommandListener::ShutdownCmd::runCommand(SocketClient * /*cli*/, + int /*argc*/, + char ** /*argv*/) { + mSwl.stopListener(); + mReader.stopListener(); + exit(0); +} + +CommandListener::ClearCmd::ClearCmd(LogBuffer *buf) + : LogCommand("clear") + , mBuf(*buf) +{ } + +static void setname() { + prctl(PR_SET_NAME, "logd.control"); +} + +int CommandListener::ClearCmd::runCommand(SocketClient *cli, + int argc, char **argv) { + setname(); + uid_t uid = cli->getUid(); + if (clientHasLogCredentials(cli)) { + uid = AID_ROOT; + } + + if (argc < 2) { + cli->sendMsg("Missing Argument"); + return 0; + } + + int id = atoi(argv[1]); + if ((id < LOG_ID_MIN) || (LOG_ID_MAX <= id)) { + cli->sendMsg("Range Error"); + return 0; + } + + mBuf.clear((log_id_t) id, uid); + cli->sendMsg("success"); + return 0; +} + +CommandListener::GetBufSizeCmd::GetBufSizeCmd(LogBuffer *buf) + : LogCommand("getLogSize") + , mBuf(*buf) +{ } + +int CommandListener::GetBufSizeCmd::runCommand(SocketClient *cli, + int argc, char **argv) { + setname(); + if (argc < 2) { + cli->sendMsg("Missing Argument"); + return 0; + } + + int id = atoi(argv[1]); + if ((id < LOG_ID_MIN) || (LOG_ID_MAX <= id)) { + cli->sendMsg("Range Error"); + return 0; + } + + unsigned long size = mBuf.getSize((log_id_t) id); + char buf[512]; + snprintf(buf, sizeof(buf), "%lu", size); + cli->sendMsg(buf); + return 0; +} + +CommandListener::SetBufSizeCmd::SetBufSizeCmd(LogBuffer *buf) + : LogCommand("setLogSize") + , mBuf(*buf) +{ } + +int CommandListener::SetBufSizeCmd::runCommand(SocketClient *cli, + int argc, char **argv) { + setname(); + if (!clientHasLogCredentials(cli)) { + cli->sendMsg("Permission Denied"); + return 0; + } + + if (argc < 3) { + cli->sendMsg("Missing Argument"); + return 0; + } + + int id = atoi(argv[1]); + if ((id < LOG_ID_MIN) || (LOG_ID_MAX <= id)) { + cli->sendMsg("Range Error"); + return 0; + } + + unsigned long size = atol(argv[2]); + if (mBuf.setSize((log_id_t) id, size)) { + cli->sendMsg("Range Error"); + return 0; + } + + cli->sendMsg("success"); + return 0; +} + +CommandListener::GetBufSizeUsedCmd::GetBufSizeUsedCmd(LogBuffer *buf) + : LogCommand("getLogSizeUsed") + , mBuf(*buf) +{ } + +int CommandListener::GetBufSizeUsedCmd::runCommand(SocketClient *cli, + int argc, char **argv) { + setname(); + if (argc < 2) { + cli->sendMsg("Missing Argument"); + return 0; + } + + int id = atoi(argv[1]); + if ((id < LOG_ID_MIN) || (LOG_ID_MAX <= id)) { + cli->sendMsg("Range Error"); + return 0; + } + + unsigned long size = mBuf.getSizeUsed((log_id_t) id); + char buf[512]; + snprintf(buf, sizeof(buf), "%lu", size); + cli->sendMsg(buf); + return 0; +} + +CommandListener::GetStatisticsCmd::GetStatisticsCmd(LogBuffer *buf) + : LogCommand("getStatistics") + , mBuf(*buf) +{ } + +static void package_string(char **strp) { + const char *a = *strp; + if (!a) { + a = ""; + } + + // Calculate total buffer size prefix, count is the string length w/o nul + char fmt[32]; + for(size_t l = strlen(a), y = 0, x = 6; y != x; y = x, x = strlen(fmt) - 2) { + snprintf(fmt, sizeof(fmt), "%zu\n%%s\n\f", l + x); + } + + char *b = *strp; + *strp = NULL; + asprintf(strp, fmt, a); + free(b); +} + +int CommandListener::GetStatisticsCmd::runCommand(SocketClient *cli, + int argc, char **argv) { + setname(); + uid_t uid = cli->getUid(); + if (clientHasLogCredentials(cli)) { + uid = AID_ROOT; + } + + unsigned int logMask = -1; + if (argc > 1) { + logMask = 0; + for (int i = 1; i < argc; ++i) { + int id = atoi(argv[i]); + if ((id < LOG_ID_MIN) || (LOG_ID_MAX <= id)) { + cli->sendMsg("Range Error"); + return 0; + } + logMask |= 1 << id; + } + } + + char *buf = NULL; + + mBuf.formatStatistics(&buf, uid, logMask); + if (!buf) { + cli->sendMsg("Failed"); + } else { + package_string(&buf); + cli->sendMsg(buf); + free(buf); + } + return 0; +} + +CommandListener::GetPruneListCmd::GetPruneListCmd(LogBuffer *buf) + : LogCommand("getPruneList") + , mBuf(*buf) +{ } + +int CommandListener::GetPruneListCmd::runCommand(SocketClient *cli, + int /*argc*/, char ** /*argv*/) { + setname(); + char *buf = NULL; + mBuf.formatPrune(&buf); + if (!buf) { + cli->sendMsg("Failed"); + } else { + package_string(&buf); + cli->sendMsg(buf); + free(buf); + } + return 0; +} + +CommandListener::SetPruneListCmd::SetPruneListCmd(LogBuffer *buf) + : LogCommand("setPruneList") + , mBuf(*buf) +{ } + +int CommandListener::SetPruneListCmd::runCommand(SocketClient *cli, + int argc, char **argv) { + setname(); + if (!clientHasLogCredentials(cli)) { + cli->sendMsg("Permission Denied"); + return 0; + } + + char *cp = NULL; + for (int i = 1; i < argc; ++i) { + char *p = cp; + if (p) { + cp = NULL; + asprintf(&cp, "%s %s", p, argv[i]); + free(p); + } else { + asprintf(&cp, "%s", argv[i]); + } + } + + int ret = mBuf.initPrune(cp); + free(cp); + + if (ret) { + cli->sendMsg("Invalid"); + return 0; + } + + cli->sendMsg("success"); + + return 0; +} + +int CommandListener::getLogSocket() { + static const char socketName[] = "logd"; + int sock = android_get_control_socket(socketName); + + if (sock < 0) { + sock = socket_local_server(socketName, + ANDROID_SOCKET_NAMESPACE_RESERVED, + SOCK_STREAM); + } + + return sock; +} diff --git a/logd/CommandListener.h b/logd/CommandListener.h new file mode 100644 index 0000000..cd1c306 --- /dev/null +++ b/logd/CommandListener.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2012-2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _COMMANDLISTENER_H__ +#define _COMMANDLISTENER_H__ + +#include <sysutils/FrameworkListener.h> +#include "LogCommand.h" +#include "LogBuffer.h" +#include "LogReader.h" +#include "LogListener.h" + +class CommandListener : public FrameworkListener { + LogBuffer &mBuf; + +public: + CommandListener(LogBuffer *buf, LogReader *reader, LogListener *swl); + virtual ~CommandListener() {} + +private: + static int getLogSocket(); + + class ShutdownCmd : public LogCommand { + LogBuffer &mBuf; + LogReader &mReader; + LogListener &mSwl; + + public: + ShutdownCmd(LogBuffer *buf, LogReader *reader, LogListener *swl); + virtual ~ShutdownCmd() {} + int runCommand(SocketClient *c, int argc, char ** argv); + }; + +#define LogBufferCmd(name) \ + class name##Cmd : public LogCommand { \ + LogBuffer &mBuf; \ + public: \ + name##Cmd(LogBuffer *buf); \ + virtual ~name##Cmd() {} \ + int runCommand(SocketClient *c, int argc, char ** argv); \ + }; + + LogBufferCmd(Clear) + LogBufferCmd(GetBufSize) + LogBufferCmd(SetBufSize) + LogBufferCmd(GetBufSizeUsed) + LogBufferCmd(GetStatistics) + LogBufferCmd(GetPruneList) + LogBufferCmd(SetPruneList) +}; + +#endif diff --git a/logd/FlushCommand.cpp b/logd/FlushCommand.cpp new file mode 100644 index 0000000..3be07c0 --- /dev/null +++ b/logd/FlushCommand.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2012-2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> + +#include "FlushCommand.h" +#include "LogBufferElement.h" +#include "LogCommand.h" +#include "LogReader.h" +#include "LogTimes.h" + +FlushCommand::FlushCommand(LogReader &reader, + bool nonBlock, + unsigned long tail, + unsigned int logMask, + pid_t pid, + log_time start) + : mReader(reader) + , mNonBlock(nonBlock) + , mTail(tail) + , mLogMask(logMask) + , mPid(pid) + , mStart(start) +{ } + +// runSocketCommand is called once for every open client on the +// log reader socket. Here we manage and associated the reader +// client tracking and log region locks LastLogTimes list of +// LogTimeEntrys, and spawn a transitory per-client thread to +// work at filing data to the socket. +// +// global LogTimeEntry::lock() is used to protect access, +// reference counts are used to ensure that individual +// LogTimeEntry lifetime is managed when not protected. +void FlushCommand::runSocketCommand(SocketClient *client) { + LogTimeEntry *entry = NULL; + LastLogTimes × = mReader.logbuf().mTimes; + + LogTimeEntry::lock(); + LastLogTimes::iterator it = times.begin(); + while(it != times.end()) { + entry = (*it); + if (entry->mClient == client) { + entry->triggerReader_Locked(); + if (entry->runningReader_Locked()) { + LogTimeEntry::unlock(); + return; + } + entry->incRef_Locked(); + break; + } + it++; + } + + if (it == times.end()) { + // Create LogTimeEntry in notifyNewLog() ? + if (mTail == (unsigned long) -1) { + LogTimeEntry::unlock(); + return; + } + entry = new LogTimeEntry(mReader, client, mNonBlock, mTail, mLogMask, mPid, mStart); + times.push_back(entry); + } + + client->incRef(); + + // release client and entry reference counts once done + entry->startReader_Locked(); + LogTimeEntry::unlock(); +} + +bool FlushCommand::hasReadLogs(SocketClient *client) { + return clientHasLogCredentials(client); +} diff --git a/logd/FlushCommand.h b/logd/FlushCommand.h new file mode 100644 index 0000000..f34c06a --- /dev/null +++ b/logd/FlushCommand.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012-2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _FLUSH_COMMAND_H +#define _FLUSH_COMMAND_H + +#include <log/log_read.h> +#include <sysutils/SocketClientCommand.h> + +class LogBufferElement; + +#include "LogTimes.h" + +class LogReader; + +class FlushCommand : public SocketClientCommand { + LogReader &mReader; + bool mNonBlock; + unsigned long mTail; + unsigned int mLogMask; + pid_t mPid; + log_time mStart; + +public: + FlushCommand(LogReader &mReader, + bool nonBlock = false, + unsigned long tail = -1, + unsigned int logMask = -1, + pid_t pid = 0, + log_time start = LogTimeEntry::EPOCH); + virtual void runSocketCommand(SocketClient *client); + + static bool hasReadLogs(SocketClient *client); +}; + +#endif diff --git a/logd/LogAudit.cpp b/logd/LogAudit.cpp new file mode 100644 index 0000000..f8d6162 --- /dev/null +++ b/logd/LogAudit.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdarg.h> +#include <stdlib.h> +#include <sys/klog.h> +#include <sys/prctl.h> +#include <sys/uio.h> + +#include "libaudit.h" +#include "LogAudit.h" + +LogAudit::LogAudit(LogBuffer *buf, LogReader *reader, int fdDmsg) + : SocketListener(getLogSocket(), false) + , logbuf(buf) + , reader(reader) + , fdDmesg(-1) { + logDmesg(); + fdDmesg = fdDmsg; +} + +bool LogAudit::onDataAvailable(SocketClient *cli) { + prctl(PR_SET_NAME, "logd.auditd"); + + struct audit_message rep; + + rep.nlh.nlmsg_type = 0; + rep.nlh.nlmsg_len = 0; + rep.data[0] = '\0'; + + if (audit_get_reply(cli->getSocket(), &rep, GET_REPLY_BLOCKING, 0) < 0) { + SLOGE("Failed on audit_get_reply with error: %s", strerror(errno)); + return false; + } + + logPrint("type=%d %.*s", rep.nlh.nlmsg_type, rep.nlh.nlmsg_len, rep.data); + + return true; +} + +int LogAudit::logPrint(const char *fmt, ...) { + if (fmt == NULL) { + return -EINVAL; + } + + va_list args; + + char *str = NULL; + va_start(args, fmt); + int rc = vasprintf(&str, fmt, args); + va_end(args); + + if (rc < 0) { + return rc; + } + + char *cp; + while ((cp = strstr(str, " "))) { + memmove(cp, cp + 1, strlen(cp + 1) + 1); + } + + if (fdDmesg >= 0) { + struct iovec iov[2]; + + iov[0].iov_base = str; + iov[0].iov_len = strlen(str); + iov[1].iov_base = const_cast<char *>("\n"); + iov[1].iov_len = 1; + + writev(fdDmesg, iov, sizeof(iov) / sizeof(iov[0])); + } + + pid_t pid = getpid(); + pid_t tid = gettid(); + uid_t uid = getuid(); + log_time now; + + static const char audit_str[] = " audit("; + char *timeptr = strstr(str, audit_str); + if (timeptr + && ((cp = now.strptime(timeptr + sizeof(audit_str) - 1, "%s.%q"))) + && (*cp == ':')) { + memcpy(timeptr + sizeof(audit_str) - 1, "0.0", 3); + memmove(timeptr + sizeof(audit_str) - 1 + 3, cp, strlen(cp) + 1); + } else { + now.strptime("", ""); // side effect of setting CLOCK_REALTIME + } + + static const char pid_str[] = " pid="; + char *pidptr = strstr(str, pid_str); + if (pidptr && isdigit(pidptr[sizeof(pid_str) - 1])) { + cp = pidptr + sizeof(pid_str) - 1; + pid = 0; + while (isdigit(*cp)) { + pid = (pid * 10) + (*cp - '0'); + ++cp; + } + tid = pid; + uid = logbuf->pidToUid(pid); + memmove(pidptr, cp, strlen(cp) + 1); + } + + // log to events + + size_t l = strlen(str); + size_t n = l + sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint32_t); + + bool notify = false; + + char *newstr = reinterpret_cast<char *>(malloc(n)); + if (!newstr) { + rc = -ENOMEM; + } else { + cp = newstr; + *cp++ = AUDITD_LOG_TAG & 0xFF; + *cp++ = (AUDITD_LOG_TAG >> 8) & 0xFF; + *cp++ = (AUDITD_LOG_TAG >> 16) & 0xFF; + *cp++ = (AUDITD_LOG_TAG >> 24) & 0xFF; + *cp++ = EVENT_TYPE_STRING; + *cp++ = l & 0xFF; + *cp++ = (l >> 8) & 0xFF; + *cp++ = (l >> 16) & 0xFF; + *cp++ = (l >> 24) & 0xFF; + memcpy(cp, str, l); + + logbuf->log(LOG_ID_EVENTS, now, uid, pid, tid, newstr, + (n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX); + free(newstr); + + notify = true; + } + + // log to main + + static const char comm_str[] = " comm=\""; + const char *comm = strstr(str, comm_str); + const char *estr = str + strlen(str); + if (comm) { + estr = comm; + comm += sizeof(comm_str) - 1; + } else if (pid == getpid()) { + pid = tid; + comm = "auditd"; + } else if (!(comm = logbuf->pidToName(pid))) { + comm = "unknown"; + } + + const char *ecomm = strchr(comm, '"'); + if (ecomm) { + ++ecomm; + l = ecomm - comm; + } else { + l = strlen(comm) + 1; + ecomm = ""; + } + n = (estr - str) + strlen(ecomm) + l + 2; + + newstr = reinterpret_cast<char *>(malloc(n)); + if (!newstr) { + rc = -ENOMEM; + } else { + *newstr = (strstr(str, " permissive=1") + || strstr(str, " policy loaded ")) + ? ANDROID_LOG_INFO + : ANDROID_LOG_WARN; + strlcpy(newstr + 1, comm, l); + strncpy(newstr + 1 + l, str, estr - str); + strcpy(newstr + 1 + l + (estr - str), ecomm); + + logbuf->log(LOG_ID_MAIN, now, uid, pid, tid, newstr, + (n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX); + free(newstr); + + notify = true; + } + + free(str); + + if (notify) { + reader->notifyNewLog(); + } + + return rc; +} + +void LogAudit::logDmesg() { + int len = klogctl(KLOG_SIZE_BUFFER, NULL, 0); + if (len <= 0) { + return; + } + + len++; + char buf[len]; + + int rc = klogctl(KLOG_READ_ALL, buf, len); + + buf[len - 1] = '\0'; + + for(char *tok = buf; (rc >= 0) && ((tok = strtok(tok, "\r\n"))); tok = NULL) { + char *audit = strstr(tok, " audit("); + if (!audit) { + continue; + } + + *audit++ = '\0'; + + char *type = strstr(tok, "type="); + if (type) { + rc = logPrint("%s %s", type, audit); + } else { + rc = logPrint("%s", audit); + } + } +} + +int LogAudit::getLogSocket() { + int fd = audit_open(); + if (fd < 0) { + return fd; + } + if (audit_set_pid(fd, getpid(), WAIT_YES) < 0) { + audit_close(fd); + fd = -1; + } + return fd; +} diff --git a/logd/LogAudit.h b/logd/LogAudit.h new file mode 100644 index 0000000..111030a --- /dev/null +++ b/logd/LogAudit.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LOGD_LOG_AUDIT_H__ +#define _LOGD_LOG_AUDIT_H__ + +#include <sysutils/SocketListener.h> +#include "LogReader.h" + +class LogAudit : public SocketListener { + LogBuffer *logbuf; + LogReader *reader; + int fdDmesg; + +public: + LogAudit(LogBuffer *buf, LogReader *reader, int fdDmesg); + +protected: + virtual bool onDataAvailable(SocketClient *cli); + +private: + static int getLogSocket(); + void logDmesg(); + int logPrint(const char *fmt, ...) + __attribute__ ((__format__ (__printf__, 2, 3))); +}; + +#endif diff --git a/logd/LogBuffer.cpp b/logd/LogBuffer.cpp new file mode 100644 index 0000000..cd9ea20 --- /dev/null +++ b/logd/LogBuffer.cpp @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2012-2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/user.h> +#include <time.h> +#include <unistd.h> + +#include <cutils/properties.h> +#include <log/logger.h> + +#include "LogBuffer.h" +#include "LogReader.h" +#include "LogStatistics.h" +#include "LogWhiteBlackList.h" + +// Default +#define LOG_BUFFER_SIZE (256 * 1024) // Tuned on a per-platform basis here? +#define log_buffer_size(id) mMaxSize[id] +#define LOG_BUFFER_MIN_SIZE (64 * 1024UL) +#define LOG_BUFFER_MAX_SIZE (256 * 1024 * 1024UL) + +static bool valid_size(unsigned long value) { + if ((value < LOG_BUFFER_MIN_SIZE) || (LOG_BUFFER_MAX_SIZE < value)) { + return false; + } + + long pages = sysconf(_SC_PHYS_PAGES); + if (pages < 1) { + return true; + } + + long pagesize = sysconf(_SC_PAGESIZE); + if (pagesize <= 1) { + pagesize = PAGE_SIZE; + } + + // maximum memory impact a somewhat arbitrary ~3% + pages = (pages + 31) / 32; + unsigned long maximum = pages * pagesize; + + if ((maximum < LOG_BUFFER_MIN_SIZE) || (LOG_BUFFER_MAX_SIZE < maximum)) { + return true; + } + + return value <= maximum; +} + +static unsigned long property_get_size(const char *key) { + char property[PROPERTY_VALUE_MAX]; + property_get(key, property, ""); + + char *cp; + unsigned long value = strtoul(property, &cp, 10); + + switch(*cp) { + case 'm': + case 'M': + value *= 1024; + /* FALLTHRU */ + case 'k': + case 'K': + value *= 1024; + /* FALLTHRU */ + case '\0': + break; + + default: + value = 0; + } + + if (!valid_size(value)) { + value = 0; + } + + return value; +} + +LogBuffer::LogBuffer(LastLogTimes *times) + : mTimes(*times) { + pthread_mutex_init(&mLogElementsLock, NULL); + dgram_qlen_statistics = false; + + static const char global_tuneable[] = "persist.logd.size"; // Settings App + static const char global_default[] = "ro.logd.size"; // BoardConfig.mk + + unsigned long default_size = property_get_size(global_tuneable); + if (!default_size) { + default_size = property_get_size(global_default); + } + + log_id_for_each(i) { + char key[PROP_NAME_MAX]; + + snprintf(key, sizeof(key), "%s.%s", + global_tuneable, android_log_id_to_name(i)); + unsigned long property_size = property_get_size(key); + + if (!property_size) { + snprintf(key, sizeof(key), "%s.%s", + global_default, android_log_id_to_name(i)); + property_size = property_get_size(key); + } + + if (!property_size) { + property_size = default_size; + } + + if (!property_size) { + property_size = LOG_BUFFER_SIZE; + } + + if (setSize(i, property_size)) { + setSize(i, LOG_BUFFER_MIN_SIZE); + } + } +} + +void LogBuffer::log(log_id_t log_id, log_time realtime, + uid_t uid, pid_t pid, pid_t tid, + const char *msg, unsigned short len) { + if ((log_id >= LOG_ID_MAX) || (log_id < 0)) { + return; + } + LogBufferElement *elem = new LogBufferElement(log_id, realtime, + uid, pid, tid, msg, len); + + pthread_mutex_lock(&mLogElementsLock); + + // Insert elements in time sorted order if possible + // NB: if end is region locked, place element at end of list + LogBufferElementCollection::iterator it = mLogElements.end(); + LogBufferElementCollection::iterator last = it; + while (--it != mLogElements.begin()) { + if ((*it)->getRealTime() <= realtime) { + // halves the peak performance, use with caution + if (dgram_qlen_statistics) { + LogBufferElementCollection::iterator ib = it; + unsigned short buckets, num = 1; + for (unsigned short i = 0; (buckets = stats.dgram_qlen(i)); ++i) { + buckets -= num; + num += buckets; + while (buckets && (--ib != mLogElements.begin())) { + --buckets; + } + if (buckets) { + break; + } + stats.recordDiff( + elem->getRealTime() - (*ib)->getRealTime(), i); + } + } + break; + } + last = it; + } + + if (last == mLogElements.end()) { + mLogElements.push_back(elem); + } else { + log_time end = log_time::EPOCH; + bool end_set = false; + bool end_always = false; + + LogTimeEntry::lock(); + + LastLogTimes::iterator t = mTimes.begin(); + while(t != mTimes.end()) { + LogTimeEntry *entry = (*t); + if (entry->owned_Locked()) { + if (!entry->mNonBlock) { + end_always = true; + break; + } + if (!end_set || (end <= entry->mEnd)) { + end = entry->mEnd; + end_set = true; + } + } + t++; + } + + if (end_always + || (end_set && (end >= (*last)->getMonotonicTime()))) { + mLogElements.push_back(elem); + } else { + mLogElements.insert(last,elem); + } + + LogTimeEntry::unlock(); + } + + stats.add(len, log_id, uid, pid); + maybePrune(log_id); + pthread_mutex_unlock(&mLogElementsLock); +} + +// If we're using more than 256K of memory for log entries, prune +// at least 10% of the log entries. +// +// mLogElementsLock must be held when this function is called. +void LogBuffer::maybePrune(log_id_t id) { + size_t sizes = stats.sizes(id); + if (sizes > log_buffer_size(id)) { + size_t sizeOver90Percent = sizes - ((log_buffer_size(id) * 9) / 10); + size_t elements = stats.elements(id); + unsigned long pruneRows = elements * sizeOver90Percent / sizes; + elements /= 10; + if (pruneRows <= elements) { + pruneRows = elements; + } + prune(id, pruneRows); + } +} + +// prune "pruneRows" of type "id" from the buffer. +// +// mLogElementsLock must be held when this function is called. +void LogBuffer::prune(log_id_t id, unsigned long pruneRows, uid_t caller_uid) { + LogTimeEntry *oldest = NULL; + + LogTimeEntry::lock(); + + // Region locked? + LastLogTimes::iterator t = mTimes.begin(); + while(t != mTimes.end()) { + LogTimeEntry *entry = (*t); + if (entry->owned_Locked() + && (!oldest || (oldest->mStart > entry->mStart))) { + oldest = entry; + } + t++; + } + + LogBufferElementCollection::iterator it; + + if (caller_uid != AID_ROOT) { + for(it = mLogElements.begin(); it != mLogElements.end();) { + LogBufferElement *e = *it; + + if (oldest && (oldest->mStart <= e->getMonotonicTime())) { + break; + } + + if (e->getLogId() != id) { + ++it; + continue; + } + + uid_t uid = e->getUid(); + + if (uid == caller_uid) { + it = mLogElements.erase(it); + unsigned short len = e->getMsgLen(); + stats.subtract(len, id, uid, e->getPid()); + delete e; + pruneRows--; + if (pruneRows == 0) { + break; + } + } else { + ++it; + } + } + LogTimeEntry::unlock(); + return; + } + + // prune by worst offender by uid + while (pruneRows > 0) { + // recalculate the worst offender on every batched pass + uid_t worst = (uid_t) -1; + size_t worst_sizes = 0; + size_t second_worst_sizes = 0; + + if ((id != LOG_ID_CRASH) && mPrune.worstUidEnabled()) { + LidStatistics &l = stats.id(id); + l.sort(); + UidStatisticsCollection::iterator iu = l.begin(); + if (iu != l.end()) { + UidStatistics *u = *iu; + worst = u->getUid(); + worst_sizes = u->sizes(); + if (++iu != l.end()) { + second_worst_sizes = (*iu)->sizes(); + } + } + } + + bool kick = false; + for(it = mLogElements.begin(); it != mLogElements.end();) { + LogBufferElement *e = *it; + + if (oldest && (oldest->mStart <= e->getMonotonicTime())) { + break; + } + + if (e->getLogId() != id) { + ++it; + continue; + } + + uid_t uid = e->getUid(); + + if (uid == worst) { + it = mLogElements.erase(it); + unsigned short len = e->getMsgLen(); + stats.subtract(len, id, worst, e->getPid()); + delete e; + kick = true; + pruneRows--; + if ((pruneRows == 0) || (worst_sizes < second_worst_sizes)) { + break; + } + worst_sizes -= len; + } else if (mPrune.naughty(e)) { // BlackListed + it = mLogElements.erase(it); + stats.subtract(e->getMsgLen(), id, uid, e->getPid()); + delete e; + pruneRows--; + if (pruneRows == 0) { + break; + } + } else { + ++it; + } + } + + if (!kick || !mPrune.worstUidEnabled()) { + break; // the following loop will ask bad clients to skip/drop + } + } + + bool whitelist = false; + it = mLogElements.begin(); + while((pruneRows > 0) && (it != mLogElements.end())) { + LogBufferElement *e = *it; + if (e->getLogId() == id) { + if (oldest && (oldest->mStart <= e->getMonotonicTime())) { + if (!whitelist) { + if (stats.sizes(id) > (2 * log_buffer_size(id))) { + // kick a misbehaving log reader client off the island + oldest->release_Locked(); + } else { + oldest->triggerSkip_Locked(pruneRows); + } + } + break; + } + + if (mPrune.nice(e)) { // WhiteListed + whitelist = true; + it++; + continue; + } + + it = mLogElements.erase(it); + stats.subtract(e->getMsgLen(), id, e->getUid(), e->getPid()); + delete e; + pruneRows--; + } else { + it++; + } + } + + if (whitelist && (pruneRows > 0)) { + it = mLogElements.begin(); + while((it != mLogElements.end()) && (pruneRows > 0)) { + LogBufferElement *e = *it; + if (e->getLogId() == id) { + if (oldest && (oldest->mStart <= e->getMonotonicTime())) { + if (stats.sizes(id) > (2 * log_buffer_size(id))) { + // kick a misbehaving log reader client off the island + oldest->release_Locked(); + } else { + oldest->triggerSkip_Locked(pruneRows); + } + break; + } + it = mLogElements.erase(it); + stats.subtract(e->getMsgLen(), id, e->getUid(), e->getPid()); + delete e; + pruneRows--; + } else { + it++; + } + } + } + + LogTimeEntry::unlock(); +} + +// clear all rows of type "id" from the buffer. +void LogBuffer::clear(log_id_t id, uid_t uid) { + pthread_mutex_lock(&mLogElementsLock); + prune(id, ULONG_MAX, uid); + pthread_mutex_unlock(&mLogElementsLock); +} + +// get the used space associated with "id". +unsigned long LogBuffer::getSizeUsed(log_id_t id) { + pthread_mutex_lock(&mLogElementsLock); + size_t retval = stats.sizes(id); + pthread_mutex_unlock(&mLogElementsLock); + return retval; +} + +// set the total space allocated to "id" +int LogBuffer::setSize(log_id_t id, unsigned long size) { + // Reasonable limits ... + if (!valid_size(size)) { + return -1; + } + pthread_mutex_lock(&mLogElementsLock); + log_buffer_size(id) = size; + pthread_mutex_unlock(&mLogElementsLock); + return 0; +} + +// get the total space allocated to "id" +unsigned long LogBuffer::getSize(log_id_t id) { + pthread_mutex_lock(&mLogElementsLock); + size_t retval = log_buffer_size(id); + pthread_mutex_unlock(&mLogElementsLock); + return retval; +} + +log_time LogBuffer::flushTo( + SocketClient *reader, const log_time start, bool privileged, + bool (*filter)(const LogBufferElement *element, void *arg), void *arg) { + LogBufferElementCollection::iterator it; + log_time max = start; + uid_t uid = reader->getUid(); + + pthread_mutex_lock(&mLogElementsLock); + for (it = mLogElements.begin(); it != mLogElements.end(); ++it) { + LogBufferElement *element = *it; + + if (!privileged && (element->getUid() != uid)) { + continue; + } + + if (element->getMonotonicTime() <= start) { + continue; + } + + // NB: calling out to another object with mLogElementsLock held (safe) + if (filter && !(*filter)(element, arg)) { + continue; + } + + pthread_mutex_unlock(&mLogElementsLock); + + // range locking in LastLogTimes looks after us + max = element->flushTo(reader); + + if (max == element->FLUSH_ERROR) { + return max; + } + + pthread_mutex_lock(&mLogElementsLock); + } + pthread_mutex_unlock(&mLogElementsLock); + + return max; +} + +void LogBuffer::formatStatistics(char **strp, uid_t uid, unsigned int logMask) { + log_time oldest(CLOCK_MONOTONIC); + + pthread_mutex_lock(&mLogElementsLock); + + // Find oldest element in the log(s) + LogBufferElementCollection::iterator it; + for (it = mLogElements.begin(); it != mLogElements.end(); ++it) { + LogBufferElement *element = *it; + + if ((logMask & (1 << element->getLogId()))) { + oldest = element->getMonotonicTime(); + break; + } + } + + stats.format(strp, uid, logMask, oldest); + + pthread_mutex_unlock(&mLogElementsLock); +} diff --git a/logd/LogBuffer.h b/logd/LogBuffer.h new file mode 100644 index 0000000..4b982a8 --- /dev/null +++ b/logd/LogBuffer.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2012-2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LOGD_LOG_BUFFER_H__ +#define _LOGD_LOG_BUFFER_H__ + +#include <sys/types.h> + +#include <log/log.h> +#include <sysutils/SocketClient.h> +#include <utils/List.h> + +#include <private/android_filesystem_config.h> + +#include "LogBufferElement.h" +#include "LogTimes.h" +#include "LogStatistics.h" +#include "LogWhiteBlackList.h" + +typedef android::List<LogBufferElement *> LogBufferElementCollection; + +class LogBuffer { + LogBufferElementCollection mLogElements; + pthread_mutex_t mLogElementsLock; + + LogStatistics stats; + + bool dgram_qlen_statistics; + + PruneList mPrune; + + unsigned long mMaxSize[LOG_ID_MAX]; + +public: + LastLogTimes &mTimes; + + LogBuffer(LastLogTimes *times); + + void log(log_id_t log_id, log_time realtime, + uid_t uid, pid_t pid, pid_t tid, + const char *msg, unsigned short len); + log_time flushTo(SocketClient *writer, const log_time start, + bool privileged, + bool (*filter)(const LogBufferElement *element, void *arg) = NULL, + void *arg = NULL); + + void clear(log_id_t id, uid_t uid = AID_ROOT); + unsigned long getSize(log_id_t id); + int setSize(log_id_t id, unsigned long size); + unsigned long getSizeUsed(log_id_t id); + // *strp uses malloc, use free to release. + void formatStatistics(char **strp, uid_t uid, unsigned int logMask); + + void enableDgramQlenStatistics() { + stats.enableDgramQlenStatistics(); + dgram_qlen_statistics = true; + } + + int initPrune(char *cp) { return mPrune.init(cp); } + // *strp uses malloc, use free to release. + void formatPrune(char **strp) { mPrune.format(strp); } + + // helper + char *pidToName(pid_t pid) { return stats.pidToName(pid); } + uid_t pidToUid(pid_t pid) { return stats.pidToUid(pid); } + +private: + void maybePrune(log_id_t id); + void prune(log_id_t id, unsigned long pruneRows, uid_t uid = AID_ROOT); + +}; + +#endif // _LOGD_LOG_BUFFER_H__ diff --git a/logd/LogBufferElement.cpp b/logd/LogBufferElement.cpp new file mode 100644 index 0000000..d959ceb --- /dev/null +++ b/logd/LogBufferElement.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012-2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include <log/logger.h> + +#include "LogBufferElement.h" +#include "LogReader.h" + +const log_time LogBufferElement::FLUSH_ERROR((uint32_t)0, (uint32_t)0); + +LogBufferElement::LogBufferElement(log_id_t log_id, log_time realtime, + uid_t uid, pid_t pid, pid_t tid, + const char *msg, unsigned short len) + : mLogId(log_id) + , mUid(uid) + , mPid(pid) + , mTid(tid) + , mMsgLen(len) + , mMonotonicTime(CLOCK_MONOTONIC) + , mRealTime(realtime) { + mMsg = new char[len]; + memcpy(mMsg, msg, len); +} + +LogBufferElement::~LogBufferElement() { + delete [] mMsg; +} + +log_time LogBufferElement::flushTo(SocketClient *reader) { + struct logger_entry_v3 entry; + memset(&entry, 0, sizeof(struct logger_entry_v3)); + entry.hdr_size = sizeof(struct logger_entry_v3); + entry.len = mMsgLen; + entry.lid = mLogId; + entry.pid = mPid; + entry.tid = mTid; + entry.sec = mRealTime.tv_sec; + entry.nsec = mRealTime.tv_nsec; + + struct iovec iovec[2]; + iovec[0].iov_base = &entry; + iovec[0].iov_len = sizeof(struct logger_entry_v3); + iovec[1].iov_base = mMsg; + iovec[1].iov_len = mMsgLen; + if (reader->sendDatav(iovec, 2)) { + return FLUSH_ERROR; + } + + return mMonotonicTime; +} diff --git a/logd/LogBufferElement.h b/logd/LogBufferElement.h new file mode 100644 index 0000000..fdca973 --- /dev/null +++ b/logd/LogBufferElement.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2012-2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LOGD_LOG_BUFFER_ELEMENT_H__ +#define _LOGD_LOG_BUFFER_ELEMENT_H__ + +#include <sys/types.h> +#include <sysutils/SocketClient.h> +#include <log/log.h> +#include <log/log_read.h> + +class LogBufferElement { + const log_id_t mLogId; + const uid_t mUid; + const pid_t mPid; + const pid_t mTid; + char *mMsg; + const unsigned short mMsgLen; + const log_time mMonotonicTime; + const log_time mRealTime; + +public: + LogBufferElement(log_id_t log_id, log_time realtime, + uid_t uid, pid_t pid, pid_t tid, + const char *msg, unsigned short len); + virtual ~LogBufferElement(); + + log_id_t getLogId() const { return mLogId; } + uid_t getUid(void) const { return mUid; } + pid_t getPid(void) const { return mPid; } + pid_t getTid(void) const { return mTid; } + unsigned short getMsgLen() const { return mMsgLen; } + log_time getMonotonicTime(void) const { return mMonotonicTime; } + log_time getRealTime(void) const { return mRealTime; } + + static const log_time FLUSH_ERROR; + log_time flushTo(SocketClient *writer); +}; + +#endif diff --git a/logd/LogCommand.cpp b/logd/LogCommand.cpp new file mode 100644 index 0000000..e4c138e --- /dev/null +++ b/logd/LogCommand.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2012-2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#include <private/android_filesystem_config.h> + +#include "LogCommand.h" + +LogCommand::LogCommand(const char *cmd) + : FrameworkCommand(cmd) { +} + +// gets a list of supplementary group IDs associated with +// the socket peer. This is implemented by opening +// /proc/PID/status and look for the "Group:" line. +// +// This function introduces races especially since status +// can change 'shape' while reading, the net result is err +// on lack of permission. +// +// Race-free alternative is to introduce pairs of sockets +// and threads for each command and reading, one each that +// has open permissions, and one that has restricted +// permissions. + +static bool groupIsLog(char *buf) { + char *ptr; + static const char ws[] = " \n"; + bool ret = false; + + for (buf = strtok_r(buf, ws, &ptr); buf; buf = strtok_r(NULL, ws, &ptr)) { + errno = 0; + gid_t Gid = strtol(buf, NULL, 10); + if (errno != 0) { + return false; + } + if (Gid == AID_LOG) { + ret = true; + } + } + return ret; +} + +bool clientHasLogCredentials(SocketClient * cli) { + uid_t uid = cli->getUid(); + if (uid == AID_ROOT) { + return true; + } + + gid_t gid = cli->getGid(); + if ((gid == AID_ROOT) || (gid == AID_SYSTEM) || (gid == AID_LOG)) { + return true; + } + + // FYI We will typically be here for 'adb logcat' + bool ret = false; + + char filename[1024]; + snprintf(filename, sizeof(filename), "/proc/%d/status", cli->getPid()); + + FILE *file = fopen(filename, "r"); + if (!file) { + return ret; + } + + bool foundGid = false; + bool foundUid = false; + + char line[1024]; + while (fgets(line, sizeof(line), file)) { + static const char groups_string[] = "Groups:\t"; + static const char uid_string[] = "Uid:\t"; + static const char gid_string[] = "Gid:\t"; + + if (strncmp(groups_string, line, strlen(groups_string)) == 0) { + ret = groupIsLog(line + strlen(groups_string)); + if (!ret) { + break; + } + } else if (strncmp(uid_string, line, strlen(uid_string)) == 0) { + uid_t u[4] = { (uid_t) -1, (uid_t) -1, (uid_t) -1, (uid_t) -1}; + + sscanf(line + strlen(uid_string), "%u\t%u\t%u\t%u", + &u[0], &u[1], &u[2], &u[3]); + + // Protect against PID reuse by checking that the UID is the same + if ((uid != u[0]) || (uid != u[1]) || (uid != u[2]) || (uid != u[3])) { + ret = false; + break; + } + foundUid = true; + } else if (strncmp(gid_string, line, strlen(gid_string)) == 0) { + gid_t g[4] = { (gid_t) -1, (gid_t) -1, (gid_t) -1, (gid_t) -1}; + + sscanf(line + strlen(gid_string), "%u\t%u\t%u\t%u", + &g[0], &g[1], &g[2], &g[3]); + + // Protect against PID reuse by checking that the GID is the same + if ((gid != g[0]) || (gid != g[1]) || (gid != g[2]) || (gid != g[3])) { + ret = false; + break; + } + foundGid = true; + } + } + + fclose(file); + + if (!foundGid || !foundUid) { + ret = false; + } + + return ret; +} diff --git a/logd/LogCommand.h b/logd/LogCommand.h new file mode 100644 index 0000000..e3b96a2 --- /dev/null +++ b/logd/LogCommand.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012-2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LOGD_COMMAND_H +#define _LOGD_COMMAND_H + +#include <sysutils/SocketClient.h> +#include <sysutils/FrameworkCommand.h> + +class LogCommand : public FrameworkCommand { +public: + LogCommand(const char *cmd); + virtual ~LogCommand() {} +}; + +bool clientHasLogCredentials(SocketClient * cli); + +#endif diff --git a/logd/LogListener.cpp b/logd/LogListener.cpp new file mode 100644 index 0000000..8186cea --- /dev/null +++ b/logd/LogListener.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2012-2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <limits.h> +#include <sys/prctl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/un.h> +#include <unistd.h> + +#include <cutils/sockets.h> +#include <log/logger.h> + +#include "LogListener.h" + +LogListener::LogListener(LogBuffer *buf, LogReader *reader) + : SocketListener(getLogSocket(), false) + , logbuf(buf) + , reader(reader) +{ } + +bool LogListener::onDataAvailable(SocketClient *cli) { + prctl(PR_SET_NAME, "logd.writer"); + + char buffer[sizeof_log_id_t + sizeof(uint16_t) + sizeof(log_time) + + LOGGER_ENTRY_MAX_PAYLOAD]; + struct iovec iov = { buffer, sizeof(buffer) }; + memset(buffer, 0, sizeof(buffer)); + + char control[CMSG_SPACE(sizeof(struct ucred))]; + struct msghdr hdr = { + NULL, + 0, + &iov, + 1, + control, + sizeof(control), + 0, + }; + + int socket = cli->getSocket(); + + ssize_t n = recvmsg(socket, &hdr, 0); + if (n <= (ssize_t)(sizeof_log_id_t + sizeof(uint16_t) + sizeof(log_time))) { + return false; + } + + struct ucred *cred = NULL; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&hdr); + while (cmsg != NULL) { + if (cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_CREDENTIALS) { + cred = (struct ucred *)CMSG_DATA(cmsg); + break; + } + cmsg = CMSG_NXTHDR(&hdr, cmsg); + } + + if (cred == NULL) { + return false; + } + + if (cred->uid == getuid()) { + // ignore log messages we send to ourself. + // Such log messages are often generated by libraries we depend on + // which use standard Android logging. + return false; + } + + // First log element is always log_id. + log_id_t log_id = (log_id_t) *((typeof_log_id_t *) buffer); + if (log_id < 0 || log_id >= LOG_ID_MAX) { + return false; + } + char *msg = ((char *)buffer) + sizeof_log_id_t; + n -= sizeof_log_id_t; + + // second element is the thread id of the caller + pid_t tid = (pid_t) *((uint16_t *) msg); + msg += sizeof(uint16_t); + n -= sizeof(uint16_t); + + // third element is the realtime at point of caller + log_time realtime(msg); + msg += sizeof(log_time); + n -= sizeof(log_time); + + // NB: hdr.msg_flags & MSG_TRUNC is not tested, silently passing a + // truncated message to the logs. + + logbuf->log(log_id, realtime, cred->uid, cred->pid, tid, msg, + ((size_t) n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX); + reader->notifyNewLog(); + + return true; +} + +int LogListener::getLogSocket() { + static const char socketName[] = "logdw"; + int sock = android_get_control_socket(socketName); + + if (sock < 0) { + sock = socket_local_server(socketName, + ANDROID_SOCKET_NAMESPACE_RESERVED, + SOCK_DGRAM); + } + + int on = 1; + if (setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) { + return -1; + } + return sock; +} diff --git a/logd/LogListener.h b/logd/LogListener.h new file mode 100644 index 0000000..7099e13 --- /dev/null +++ b/logd/LogListener.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2012-2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LOGD_LOG_LISTENER_H__ +#define _LOGD_LOG_LISTENER_H__ + +#include <sysutils/SocketListener.h> +#include "LogReader.h" + +class LogListener : public SocketListener { + LogBuffer *logbuf; + LogReader *reader; + +public: + LogListener(LogBuffer *buf, LogReader *reader); + +protected: + virtual bool onDataAvailable(SocketClient *cli); + +private: + static int getLogSocket(); +}; + +#endif diff --git a/logd/LogReader.cpp b/logd/LogReader.cpp new file mode 100644 index 0000000..8458c19 --- /dev/null +++ b/logd/LogReader.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2012-2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ctype.h> +#include <poll.h> +#include <sys/prctl.h> +#include <sys/socket.h> + +#include <cutils/sockets.h> + +#include "LogReader.h" +#include "FlushCommand.h" + +LogReader::LogReader(LogBuffer *logbuf) + : SocketListener(getLogSocket(), true) + , mLogbuf(*logbuf) +{ } + +// When we are notified a new log entry is available, inform +// all of our listening sockets. +void LogReader::notifyNewLog() { + FlushCommand command(*this); + runOnEachSocket(&command); +} + +bool LogReader::onDataAvailable(SocketClient *cli) { + prctl(PR_SET_NAME, "logd.reader"); + + char buffer[255]; + + int len = read(cli->getSocket(), buffer, sizeof(buffer) - 1); + if (len <= 0) { + doSocketDelete(cli); + return false; + } + buffer[len] = '\0'; + + unsigned long tail = 0; + static const char _tail[] = " tail="; + char *cp = strstr(buffer, _tail); + if (cp) { + tail = atol(cp + sizeof(_tail) - 1); + } + + log_time start(log_time::EPOCH); + static const char _start[] = " start="; + cp = strstr(buffer, _start); + if (cp) { + // Parse errors will result in current time + start.strptime(cp + sizeof(_start) - 1, "%s.%q"); + } + + unsigned int logMask = -1; + static const char _logIds[] = " lids="; + cp = strstr(buffer, _logIds); + if (cp) { + logMask = 0; + cp += sizeof(_logIds) - 1; + while (*cp && *cp != '\0') { + int val = 0; + while (isdigit(*cp)) { + val = val * 10 + *cp - '0'; + ++cp; + } + logMask |= 1 << val; + if (*cp != ',') { + break; + } + ++cp; + } + } + + pid_t pid = 0; + static const char _pid[] = " pid="; + cp = strstr(buffer, _pid); + if (cp) { + pid = atol(cp + sizeof(_pid) - 1); + } + + bool nonBlock = false; + if (strncmp(buffer, "dumpAndClose", 12) == 0) { + nonBlock = true; + } + + // Convert realtime to monotonic time + if (start == log_time::EPOCH) { + start = LogTimeEntry::EPOCH; + } else { + class LogFindStart { + const pid_t mPid; + const unsigned mLogMask; + bool startTimeSet; + log_time &start; + log_time last; + + public: + LogFindStart(unsigned logMask, pid_t pid, log_time &start) + : mPid(pid) + , mLogMask(logMask) + , startTimeSet(false) + , start(start) + , last(LogTimeEntry::EPOCH) + { } + + static bool callback(const LogBufferElement *element, void *obj) { + LogFindStart *me = reinterpret_cast<LogFindStart *>(obj); + if (!me->startTimeSet + && (!me->mPid || (me->mPid == element->getPid())) + && (me->mLogMask & (1 << element->getLogId()))) { + if (me->start == element->getRealTime()) { + me->start = element->getMonotonicTime(); + me->startTimeSet = true; + } else { + if (me->start < element->getRealTime()) { + me->start = me->last; + me->startTimeSet = true; + } + me->last = element->getMonotonicTime(); + } + } + return false; + } + + bool found() { return startTimeSet; } + } logFindStart(logMask, pid, start); + + logbuf().flushTo(cli, LogTimeEntry::EPOCH, + FlushCommand::hasReadLogs(cli), + logFindStart.callback, &logFindStart); + + if (!logFindStart.found()) { + if (nonBlock) { + doSocketDelete(cli); + return false; + } + log_time now(CLOCK_MONOTONIC); + start = now; + } + } + + FlushCommand command(*this, nonBlock, tail, logMask, pid, start); + command.runSocketCommand(cli); + return true; +} + +void LogReader::doSocketDelete(SocketClient *cli) { + LastLogTimes × = mLogbuf.mTimes; + LogTimeEntry::lock(); + LastLogTimes::iterator it = times.begin(); + while(it != times.end()) { + LogTimeEntry *entry = (*it); + if (entry->mClient == cli) { + times.erase(it); + entry->release_Locked(); + break; + } + it++; + } + LogTimeEntry::unlock(); +} + +int LogReader::getLogSocket() { + static const char socketName[] = "logdr"; + int sock = android_get_control_socket(socketName); + + if (sock < 0) { + sock = socket_local_server(socketName, + ANDROID_SOCKET_NAMESPACE_RESERVED, + SOCK_SEQPACKET); + } + + return sock; +} diff --git a/logd/LogReader.h b/logd/LogReader.h new file mode 100644 index 0000000..91559a3 --- /dev/null +++ b/logd/LogReader.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012-2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LOGD_LOG_WRITER_H__ +#define _LOGD_LOG_WRITER_H__ + +#include <sysutils/SocketListener.h> +#include "LogBuffer.h" +#include "LogTimes.h" + +class LogReader : public SocketListener { + LogBuffer &mLogbuf; + +public: + LogReader(LogBuffer *logbuf); + void notifyNewLog(); + + LogBuffer &logbuf(void) const { return mLogbuf; } + +protected: + virtual bool onDataAvailable(SocketClient *cli); + +private: + static int getLogSocket(); + + void doSocketDelete(SocketClient *cli); + +}; + +#endif diff --git a/logd/LogStatistics.cpp b/logd/LogStatistics.cpp new file mode 100644 index 0000000..81c9bab --- /dev/null +++ b/logd/LogStatistics.cpp @@ -0,0 +1,926 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fcntl.h> +#include <stdarg.h> +#include <time.h> + +#include <log/logger.h> +#include <private/android_filesystem_config.h> +#include <utils/String8.h> + +#include "LogStatistics.h" + +PidStatistics::PidStatistics(pid_t pid, char *name) + : pid(pid) + , mSizesTotal(0) + , mElementsTotal(0) + , mSizes(0) + , mElements(0) + , name(name) + , mGone(false) +{ } + +#ifdef DO_NOT_ERROR_IF_PIDSTATISTICS_USES_A_COPY_CONSTRUCTOR +PidStatistics::PidStatistics(const PidStatistics ©) + : pid(copy->pid) + , name(copy->name ? strdup(copy->name) : NULL) + , mSizesTotal(copy->mSizesTotal) + , mElementsTotal(copy->mElementsTotal) + , mSizes(copy->mSizes) + , mElements(copy->mElements) + , mGone(copy->mGone) +{ } +#endif + +PidStatistics::~PidStatistics() { + free(name); +} + +bool PidStatistics::pidGone() { + if (mGone) { + return true; + } + if (pid == gone) { + return true; + } + if (kill(pid, 0) && (errno != EPERM)) { + mGone = true; + return true; + } + return false; +} + +void PidStatistics::setName(char *new_name) { + free(name); + name = new_name; +} + +void PidStatistics::add(unsigned short size) { + mSizesTotal += size; + ++mElementsTotal; + mSizes += size; + ++mElements; +} + +bool PidStatistics::subtract(unsigned short size) { + mSizes -= size; + --mElements; + return (mElements == 0) && pidGone(); +} + +void PidStatistics::addTotal(size_t size, size_t element) { + if (pid == gone) { + mSizesTotal += size; + mElementsTotal += element; + } +} + +// must call free to release return value +char *PidStatistics::pidToName(pid_t pid) { + char *retval = NULL; + if (pid != gone) { + char buffer[512]; + snprintf(buffer, sizeof(buffer), "/proc/%u/cmdline", pid); + int fd = open(buffer, O_RDONLY); + if (fd >= 0) { + ssize_t ret = read(fd, buffer, sizeof(buffer)); + if (ret > 0) { + buffer[sizeof(buffer)-1] = '\0'; + // frameworks intermediate state + if (strcmp(buffer, "<pre-initialized>")) { + retval = strdup(buffer); + } + } + close(fd); + } + } + return retval; +} + +UidStatistics::UidStatistics(uid_t uid) + : uid(uid) + , mSizes(0) + , mElements(0) { + Pids.clear(); +} + +UidStatistics::~UidStatistics() { + PidStatisticsCollection::iterator it; + for (it = begin(); it != end();) { + delete (*it); + it = Pids.erase(it); + } +} + +void UidStatistics::add(unsigned short size, pid_t pid) { + mSizes += size; + ++mElements; + + PidStatistics *p; + PidStatisticsCollection::iterator last; + PidStatisticsCollection::iterator it; + for (last = it = begin(); it != end(); last = it, ++it) { + p = *it; + if (pid == p->getPid()) { + p->add(size); + return; + } + } + // insert if the gone entry. + bool insert = (last != it) && (p->getPid() == p->gone); + p = new PidStatistics(pid, pidToName(pid)); + if (insert) { + Pids.insert(last, p); + } else { + Pids.push_back(p); + } + p->add(size); +} + +void UidStatistics::subtract(unsigned short size, pid_t pid) { + mSizes -= size; + --mElements; + + PidStatisticsCollection::iterator it; + for (it = begin(); it != end(); ++it) { + PidStatistics *p = *it; + if (pid == p->getPid()) { + if (p->subtract(size)) { + size_t szsTotal = p->sizesTotal(); + size_t elsTotal = p->elementsTotal(); + delete p; + Pids.erase(it); + it = end(); + --it; + if (it == end()) { + p = new PidStatistics(p->gone); + Pids.push_back(p); + } else { + p = *it; + if (p->getPid() != p->gone) { + p = new PidStatistics(p->gone); + Pids.push_back(p); + } + } + p->addTotal(szsTotal, elsTotal); + } + return; + } + } +} + +void UidStatistics::sort() { + for (bool pass = true; pass;) { + pass = false; + PidStatisticsCollection::iterator it = begin(); + if (it != end()) { + PidStatisticsCollection::iterator lt = it; + PidStatistics *l = (*lt); + while (++it != end()) { + PidStatistics *n = (*it); + if ((n->getPid() != n->gone) && (n->sizes() > l->sizes())) { + pass = true; + Pids.erase(it); + Pids.insert(lt, n); + it = lt; + n = l; + } + lt = it; + l = n; + } + } + } +} + +size_t UidStatistics::sizes(pid_t pid) { + if (pid == pid_all) { + return sizes(); + } + + PidStatisticsCollection::iterator it; + for (it = begin(); it != end(); ++it) { + PidStatistics *p = *it; + if (pid == p->getPid()) { + return p->sizes(); + } + } + return 0; +} + +size_t UidStatistics::elements(pid_t pid) { + if (pid == pid_all) { + return elements(); + } + + PidStatisticsCollection::iterator it; + for (it = begin(); it != end(); ++it) { + PidStatistics *p = *it; + if (pid == p->getPid()) { + return p->elements(); + } + } + return 0; +} + +size_t UidStatistics::sizesTotal(pid_t pid) { + size_t sizes = 0; + PidStatisticsCollection::iterator it; + for (it = begin(); it != end(); ++it) { + PidStatistics *p = *it; + if ((pid == pid_all) || (pid == p->getPid())) { + sizes += p->sizesTotal(); + } + } + return sizes; +} + +size_t UidStatistics::elementsTotal(pid_t pid) { + size_t elements = 0; + PidStatisticsCollection::iterator it; + for (it = begin(); it != end(); ++it) { + PidStatistics *p = *it; + if ((pid == pid_all) || (pid == p->getPid())) { + elements += p->elementsTotal(); + } + } + return elements; +} + +LidStatistics::LidStatistics() { + Uids.clear(); +} + +LidStatistics::~LidStatistics() { + UidStatisticsCollection::iterator it; + for (it = begin(); it != end();) { + delete (*it); + it = Uids.erase(it); + } +} + +void LidStatistics::add(unsigned short size, uid_t uid, pid_t pid) { + UidStatistics *u; + UidStatisticsCollection::iterator it; + UidStatisticsCollection::iterator last; + + if (uid == (uid_t) -1) { // init + uid = (uid_t) AID_ROOT; + } + + for (last = it = begin(); it != end(); last = it, ++it) { + u = *it; + if (uid == u->getUid()) { + u->add(size, pid); + if ((last != it) && ((*last)->sizesTotal() < u->sizesTotal())) { + Uids.erase(it); + Uids.insert(last, u); + } + return; + } + } + u = new UidStatistics(uid); + if ((last != it) && ((*last)->sizesTotal() < (size_t) size)) { + Uids.insert(last, u); + } else { + Uids.push_back(u); + } + u->add(size, pid); +} + +void LidStatistics::subtract(unsigned short size, uid_t uid, pid_t pid) { + UidStatisticsCollection::iterator it; + for (it = begin(); it != end(); ++it) { + UidStatistics *u = *it; + if (uid == u->getUid()) { + u->subtract(size, pid); + return; + } + } +} + +void LidStatistics::sort() { + for (bool pass = true; pass;) { + pass = false; + UidStatisticsCollection::iterator it = begin(); + if (it != end()) { + UidStatisticsCollection::iterator lt = it; + UidStatistics *l = (*lt); + while (++it != end()) { + UidStatistics *n = (*it); + if (n->sizes() > l->sizes()) { + pass = true; + Uids.erase(it); + Uids.insert(lt, n); + it = lt; + n = l; + } + lt = it; + l = n; + } + } + } +} + +size_t LidStatistics::sizes(uid_t uid, pid_t pid) { + size_t sizes = 0; + UidStatisticsCollection::iterator it; + for (it = begin(); it != end(); ++it) { + UidStatistics *u = *it; + if ((uid == uid_all) || (uid == u->getUid())) { + sizes += u->sizes(pid); + } + } + return sizes; +} + +size_t LidStatistics::elements(uid_t uid, pid_t pid) { + size_t elements = 0; + UidStatisticsCollection::iterator it; + for (it = begin(); it != end(); ++it) { + UidStatistics *u = *it; + if ((uid == uid_all) || (uid == u->getUid())) { + elements += u->elements(pid); + } + } + return elements; +} + +size_t LidStatistics::sizesTotal(uid_t uid, pid_t pid) { + size_t sizes = 0; + UidStatisticsCollection::iterator it; + for (it = begin(); it != end(); ++it) { + UidStatistics *u = *it; + if ((uid == uid_all) || (uid == u->getUid())) { + sizes += u->sizesTotal(pid); + } + } + return sizes; +} + +size_t LidStatistics::elementsTotal(uid_t uid, pid_t pid) { + size_t elements = 0; + UidStatisticsCollection::iterator it; + for (it = begin(); it != end(); ++it) { + UidStatistics *u = *it; + if ((uid == uid_all) || (uid == u->getUid())) { + elements += u->elementsTotal(pid); + } + } + return elements; +} + +LogStatistics::LogStatistics() + : start(CLOCK_MONOTONIC) { + log_id_for_each(i) { + mSizes[i] = 0; + mElements[i] = 0; + } + + dgram_qlen_statistics = false; + for(unsigned short bucket = 0; dgram_qlen(bucket); ++bucket) { + mMinimum[bucket].tv_sec = mMinimum[bucket].tv_sec_max; + mMinimum[bucket].tv_nsec = mMinimum[bucket].tv_nsec_max; + } +} + +// Each bucket below represents a dgram_qlen of log messages. By +// finding the minimum period of time from start to finish +// of each dgram_qlen, we can get a performance expectation for +// the user space logger. The net result is that the period +// of time divided by the dgram_qlen will give us the average time +// between log messages; at the point where the average time +// is greater than the throughput capability of the logger +// we will not longer require the benefits of the FIFO formed +// by max_dgram_qlen. We will also expect to see a very visible +// knee in the average time between log messages at this point, +// so we do not necessarily have to compare the rate against the +// measured performance (BM_log_maximum_retry) of the logger. +// +// for example (reformatted): +// +// Minimum time between log events per dgram_qlen: +// 1 2 3 5 10 20 30 50 100 200 300 400 500 600 +// 5u2 12u 13u 15u 16u 27u 30u 36u 407u 3m1 3m3 3m9 3m9 5m5 +// +// demonstrates a clear knee rising at 100, so this means that for this +// case max_dgram_qlen = 100 would be more than sufficient to handle the +// worst that the system could stuff into the logger. The +// BM_log_maximum_retry performance (derated by the log collection) on the +// same system was 33.2us so we would almost be fine with max_dgram_qlen = 50. +// BM_log_maxumum_retry with statistics off is roughly 20us, so +// max_dgram_qlen = 20 would work. We will be more than willing to have +// a large engineering margin so the rule of thumb that lead us to 100 is +// fine. +// +// bucket dgram_qlen are tuned for /proc/sys/net/unix/max_dgram_qlen = 300 +const unsigned short LogStatistics::mBuckets[] = { + 1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 400, 500, 600 +}; + +unsigned short LogStatistics::dgram_qlen(unsigned short bucket) { + if (bucket >= sizeof(mBuckets) / sizeof(mBuckets[0])) { + return 0; + } + return mBuckets[bucket]; +} + +unsigned long long LogStatistics::minimum(unsigned short bucket) { + if (mMinimum[bucket].tv_sec == mMinimum[bucket].tv_sec_max) { + return 0; + } + return mMinimum[bucket].nsec(); +} + +void LogStatistics::recordDiff(log_time diff, unsigned short bucket) { + if ((diff.tv_sec || diff.tv_nsec) && (mMinimum[bucket] > diff)) { + mMinimum[bucket] = diff; + } +} + +void LogStatistics::add(unsigned short size, + log_id_t log_id, uid_t uid, pid_t pid) { + mSizes[log_id] += size; + ++mElements[log_id]; + id(log_id).add(size, uid, pid); +} + +void LogStatistics::subtract(unsigned short size, + log_id_t log_id, uid_t uid, pid_t pid) { + mSizes[log_id] -= size; + --mElements[log_id]; + id(log_id).subtract(size, uid, pid); +} + +size_t LogStatistics::sizes(log_id_t log_id, uid_t uid, pid_t pid) { + if (log_id != log_id_all) { + return id(log_id).sizes(uid, pid); + } + size_t sizes = 0; + log_id_for_each(i) { + sizes += id(i).sizes(uid, pid); + } + return sizes; +} + +size_t LogStatistics::elements(log_id_t log_id, uid_t uid, pid_t pid) { + if (log_id != log_id_all) { + return id(log_id).elements(uid, pid); + } + size_t elements = 0; + log_id_for_each(i) { + elements += id(i).elements(uid, pid); + } + return elements; +} + +size_t LogStatistics::sizesTotal(log_id_t log_id, uid_t uid, pid_t pid) { + if (log_id != log_id_all) { + return id(log_id).sizesTotal(uid, pid); + } + size_t sizes = 0; + log_id_for_each(i) { + sizes += id(i).sizesTotal(uid, pid); + } + return sizes; +} + +size_t LogStatistics::elementsTotal(log_id_t log_id, uid_t uid, pid_t pid) { + if (log_id != log_id_all) { + return id(log_id).elementsTotal(uid, pid); + } + size_t elements = 0; + log_id_for_each(i) { + elements += id(i).elementsTotal(uid, pid); + } + return elements; +} + +void LogStatistics::format(char **buf, + uid_t uid, unsigned int logMask, log_time oldest) { + static const unsigned short spaces_current = 13; + static const unsigned short spaces_total = 19; + + if (*buf) { + free(*buf); + *buf = NULL; + } + + android::String8 string(" span -> size/num"); + size_t oldLength; + short spaces = 2; + + log_id_for_each(i) { + if (!logMask & (1 << i)) { + continue; + } + oldLength = string.length(); + if (spaces < 0) { + spaces = 0; + } + string.appendFormat("%*s%s", spaces, "", android_log_id_to_name(i)); + spaces += spaces_total + oldLength - string.length(); + + LidStatistics &l = id(i); + l.sort(); + + UidStatisticsCollection::iterator iu; + for (iu = l.begin(); iu != l.end(); ++iu) { + (*iu)->sort(); + } + } + + spaces = 1; + log_time t(CLOCK_MONOTONIC); + unsigned long long d = t.nsec() - start.nsec(); + string.appendFormat("\nTotal%4llu:%02llu:%02llu.%09llu", + d / NS_PER_SEC / 60 / 60, (d / NS_PER_SEC / 60) % 60, + (d / NS_PER_SEC) % 60, d % NS_PER_SEC); + + log_id_for_each(i) { + if (!(logMask & (1 << i))) { + continue; + } + oldLength = string.length(); + if (spaces < 0) { + spaces = 0; + } + string.appendFormat("%*s%zu/%zu", spaces, "", + sizesTotal(i), elementsTotal(i)); + spaces += spaces_total + oldLength - string.length(); + } + + spaces = 1; + d = t.nsec() - oldest.nsec(); + string.appendFormat("\nNow%6llu:%02llu:%02llu.%09llu", + d / NS_PER_SEC / 60 / 60, (d / NS_PER_SEC / 60) % 60, + (d / NS_PER_SEC) % 60, d % NS_PER_SEC); + + log_id_for_each(i) { + if (!(logMask & (1 << i))) { + continue; + } + + size_t els = elements(i); + if (els) { + oldLength = string.length(); + if (spaces < 0) { + spaces = 0; + } + string.appendFormat("%*s%zu/%zu", spaces, "", sizes(i), els); + spaces -= string.length() - oldLength; + } + spaces += spaces_total; + } + + // Construct list of worst spammers by Pid + static const unsigned char num_spammers = 10; + bool header = false; + + log_id_for_each(i) { + if (!(logMask & (1 << i))) { + continue; + } + + PidStatisticsCollection pids; + pids.clear(); + + LidStatistics &l = id(i); + UidStatisticsCollection::iterator iu; + for (iu = l.begin(); iu != l.end(); ++iu) { + UidStatistics &u = *(*iu); + PidStatisticsCollection::iterator ip; + for (ip = u.begin(); ip != u.end(); ++ip) { + PidStatistics *p = (*ip); + if (p->getPid() == p->gone) { + break; + } + + size_t mySizes = p->sizes(); + + PidStatisticsCollection::iterator q; + unsigned char num = 0; + for (q = pids.begin(); q != pids.end(); ++q) { + if (mySizes > (*q)->sizes()) { + pids.insert(q, p); + break; + } + // do we need to traverse deeper in the list? + if (++num > num_spammers) { + break; + } + } + if (q == pids.end()) { + pids.push_back(p); + } + } + } + + size_t threshold = sizes(i); + if (threshold < 65536) { + threshold = 65536; + } + threshold /= 100; + + PidStatisticsCollection::iterator pt = pids.begin(); + + for(int line = 0; + (pt != pids.end()) && (line < num_spammers); + ++line, pt = pids.erase(pt)) { + PidStatistics *p = *pt; + + size_t sizes = p->sizes(); + if (sizes < threshold) { + break; + } + + char *name = p->getName(); + pid_t pid = p->getPid(); + if (!name || !*name) { + name = pidToName(pid); + if (name) { + if (*name) { + p->setName(name); + } else { + free(name); + name = NULL; + } + } + } + + if (!header) { + string.appendFormat("\n\nChattiest clients:\n" + "log id %-*s PID[?] name", + spaces_total, "size/total"); + header = true; + } + + size_t sizesTotal = p->sizesTotal(); + + android::String8 sz(""); + sz.appendFormat((sizes != sizesTotal) ? "%zu/%zu" : "%zu", + sizes, sizesTotal); + + android::String8 pd(""); + pd.appendFormat("%u%c", pid, p->pidGone() ? '?' : ' '); + + string.appendFormat("\n%-7s%-*s %-7s%s", + line ? "" : android_log_id_to_name(i), + spaces_total, sz.string(), pd.string(), + name ? name : ""); + } + + pids.clear(); + } + + if (dgram_qlen_statistics) { + const unsigned short spaces_time = 6; + const unsigned long long max_seconds = 100000; + spaces = 0; + string.append("\n\nMinimum time between log events per dgram_qlen:\n"); + for(unsigned short i = 0; dgram_qlen(i); ++i) { + oldLength = string.length(); + if (spaces < 0) { + spaces = 0; + } + string.appendFormat("%*s%u", spaces, "", dgram_qlen(i)); + spaces += spaces_time + oldLength - string.length(); + } + string.append("\n"); + spaces = 0; + unsigned short n; + for(unsigned short i = 0; (n = dgram_qlen(i)); ++i) { + unsigned long long duration = minimum(i); + if (duration) { + duration /= n; + if (duration >= (NS_PER_SEC * max_seconds)) { + duration = NS_PER_SEC * (max_seconds - 1); + } + oldLength = string.length(); + if (spaces < 0) { + spaces = 0; + } + string.appendFormat("%*s", spaces, ""); + if (duration >= (NS_PER_SEC * 10)) { + string.appendFormat("%llu", + (duration + (NS_PER_SEC / 2)) + / NS_PER_SEC); + } else if (duration >= (NS_PER_SEC / (1000 / 10))) { + string.appendFormat("%llum", + (duration + (NS_PER_SEC / 2 / 1000)) + / (NS_PER_SEC / 1000)); + } else if (duration >= (NS_PER_SEC / (1000000 / 10))) { + string.appendFormat("%lluu", + (duration + (NS_PER_SEC / 2 / 1000000)) + / (NS_PER_SEC / 1000000)); + } else { + string.appendFormat("%llun", duration); + } + spaces -= string.length() - oldLength; + } + spaces += spaces_time; + } + } + + log_id_for_each(i) { + if (!(logMask & (1 << i))) { + continue; + } + + header = false; + bool first = true; + + UidStatisticsCollection::iterator ut; + for(ut = id(i).begin(); ut != id(i).end(); ++ut) { + UidStatistics *up = *ut; + if ((uid != AID_ROOT) && (uid != up->getUid())) { + continue; + } + + PidStatisticsCollection::iterator pt = up->begin(); + if (pt == up->end()) { + continue; + } + + android::String8 intermediate; + + if (!header) { + // header below tuned to match spaces_total and spaces_current + spaces = 0; + intermediate = string.format("%s: UID/PID Total size/num", + android_log_id_to_name(i)); + string.appendFormat("\n\n%-31sNow " + "UID/PID[?] Total Now", + intermediate.string()); + intermediate.clear(); + header = true; + } + + bool oneline = ++pt == up->end(); + --pt; + + if (!oneline) { + first = true; + } else if (!first && (spaces > 0)) { + string.appendFormat("%*s", spaces, ""); + } + spaces = 0; + + uid_t u = up->getUid(); + PidStatistics *pp = *pt; + pid_t p = pp->getPid(); + + intermediate = string.format(oneline + ? ((p == PidStatistics::gone) + ? "%d/?" + : "%d/%d%c") + : "%d", + u, p, pp->pidGone() ? '?' : '\0'); + string.appendFormat(first ? "\n%-12s" : "%-12s", + intermediate.string()); + intermediate.clear(); + + size_t elsTotal = up->elementsTotal(); + oldLength = string.length(); + string.appendFormat("%zu/%zu", up->sizesTotal(), elsTotal); + spaces += spaces_total + oldLength - string.length(); + + size_t els = up->elements(); + if (els == elsTotal) { + if (spaces < 0) { + spaces = 0; + } + string.appendFormat("%*s=", spaces, ""); + spaces = -1; + } else if (els) { + oldLength = string.length(); + if (spaces < 0) { + spaces = 0; + } + string.appendFormat("%*s%zu/%zu", spaces, "", up->sizes(), els); + spaces -= string.length() - oldLength; + } + spaces += spaces_current; + + first = !first; + + if (oneline) { + continue; + } + + size_t gone_szs = 0; + size_t gone_els = 0; + + for(; pt != up->end(); ++pt) { + pp = *pt; + p = pp->getPid(); + + // If a PID no longer has any current logs, and is not + // active anymore, skip & report totals for gone. + elsTotal = pp->elementsTotal(); + size_t szsTotal = pp->sizesTotal(); + if (p == pp->gone) { + gone_szs += szsTotal; + gone_els += elsTotal; + continue; + } + els = pp->elements(); + bool gone = pp->pidGone(); + if (gone && (els == 0)) { + // ToDo: garbage collection: move this statistical bucket + // from its current UID/PID to UID/? (races and + // wrap around are our achilles heel). Below is + // merely lipservice to catch PIDs that were still + // around when the stats were pruned to zero. + gone_szs += szsTotal; + gone_els += elsTotal; + continue; + } + + if (!first && (spaces > 0)) { + string.appendFormat("%*s", spaces, ""); + } + spaces = 0; + + intermediate = string.format(gone ? "%d/%d?" : "%d/%d", u, p); + string.appendFormat(first ? "\n%-12s" : "%-12s", + intermediate.string()); + intermediate.clear(); + + oldLength = string.length(); + string.appendFormat("%zu/%zu", szsTotal, elsTotal); + spaces += spaces_total + oldLength - string.length(); + + if (els == elsTotal) { + if (spaces < 0) { + spaces = 0; + } + string.appendFormat("%*s=", spaces, ""); + spaces = -1; + } else if (els) { + oldLength = string.length(); + if (spaces < 0) { + spaces = 0; + } + string.appendFormat("%*s%zu/%zu", spaces, "", + pp->sizes(), els); + spaces -= string.length() - oldLength; + } + spaces += spaces_current; + + first = !first; + } + + if (gone_els) { + if (!first && (spaces > 0)) { + string.appendFormat("%*s", spaces, ""); + } + + intermediate = string.format("%d/?", u); + string.appendFormat(first ? "\n%-12s" : "%-12s", + intermediate.string()); + intermediate.clear(); + + spaces = spaces_total + spaces_current; + + oldLength = string.length(); + string.appendFormat("%zu/%zu", gone_szs, gone_els); + spaces -= string.length() - oldLength; + + first = !first; + } + } + } + + *buf = strdup(string.string()); +} + +uid_t LogStatistics::pidToUid(pid_t pid) { + log_id_for_each(i) { + LidStatistics &l = id(i); + UidStatisticsCollection::iterator iu; + for (iu = l.begin(); iu != l.end(); ++iu) { + UidStatistics &u = *(*iu); + PidStatisticsCollection::iterator ip; + for (ip = u.begin(); ip != u.end(); ++ip) { + if ((*ip)->getPid() == pid) { + return u.getUid(); + } + } + } + } + return getuid(); // associate this with the logger +} diff --git a/logd/LogStatistics.h b/logd/LogStatistics.h new file mode 100644 index 0000000..3733137 --- /dev/null +++ b/logd/LogStatistics.h @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LOGD_LOG_STATISTICS_H__ +#define _LOGD_LOG_STATISTICS_H__ + +#include <sys/types.h> + +#include <log/log.h> +#include <log/log_read.h> +#include <utils/List.h> + +#define log_id_for_each(i) \ + for (log_id_t i = LOG_ID_MIN; i < LOG_ID_MAX; i = (log_id_t) (i + 1)) + +class PidStatistics { + const pid_t pid; + + // Total + size_t mSizesTotal; + size_t mElementsTotal; + // Current + size_t mSizes; + size_t mElements; + + char *name; + bool mGone; + +public: + static const pid_t gone = (pid_t) -1; + + PidStatistics(pid_t pid, char *name = NULL); + PidStatistics(const PidStatistics ©); + ~PidStatistics(); + + pid_t getPid() const { return pid; } + bool pidGone(); + char *getName() const { return name; } + void setName(char *name); + + void add(unsigned short size); + bool subtract(unsigned short size); // returns true if stats and PID gone + void addTotal(size_t size, size_t element); + + size_t sizes() const { return mSizes; } + size_t elements() const { return mElements; } + + size_t sizesTotal() const { return mSizesTotal; } + size_t elementsTotal() const { return mElementsTotal; } + + // helper + static char *pidToName(pid_t pid); +}; + +typedef android::List<PidStatistics *> PidStatisticsCollection; + +class UidStatistics { + const uid_t uid; + + PidStatisticsCollection Pids; + + size_t mSizes; + size_t mElements; + +public: + UidStatistics(uid_t uid); + ~UidStatistics(); + + PidStatisticsCollection::iterator begin() { return Pids.begin(); } + PidStatisticsCollection::iterator end() { return Pids.end(); } + + uid_t getUid() { return uid; } + + void add(unsigned short size, pid_t pid); + void subtract(unsigned short size, pid_t pid); + void sort(); + + static const pid_t pid_all = (pid_t) -1; + + // fast track current value + size_t sizes() const { return mSizes; }; + size_t elements() const { return mElements; }; + + // statistical track + size_t sizes(pid_t pid); + size_t elements(pid_t pid); + + size_t sizesTotal(pid_t pid = pid_all); + size_t elementsTotal(pid_t pid = pid_all); + + // helper + static char *pidToName(pid_t pid) { return PidStatistics::pidToName(pid); } +}; + +typedef android::List<UidStatistics *> UidStatisticsCollection; + +class LidStatistics { + UidStatisticsCollection Uids; + +public: + LidStatistics(); + ~LidStatistics(); + + UidStatisticsCollection::iterator begin() { return Uids.begin(); } + UidStatisticsCollection::iterator end() { return Uids.end(); } + + void add(unsigned short size, uid_t uid, pid_t pid); + void subtract(unsigned short size, uid_t uid, pid_t pid); + void sort(); + + static const pid_t pid_all = (pid_t) -1; + static const uid_t uid_all = (uid_t) -1; + + size_t sizes(uid_t uid = uid_all, pid_t pid = pid_all); + size_t elements(uid_t uid = uid_all, pid_t pid = pid_all); + + size_t sizesTotal(uid_t uid = uid_all, pid_t pid = pid_all); + size_t elementsTotal(uid_t uid = uid_all, pid_t pid = pid_all); +}; + +// Log Statistics +class LogStatistics { + LidStatistics LogIds[LOG_ID_MAX]; + + size_t mSizes[LOG_ID_MAX]; + size_t mElements[LOG_ID_MAX]; + + bool dgram_qlen_statistics; + + static const unsigned short mBuckets[14]; + log_time mMinimum[sizeof(mBuckets) / sizeof(mBuckets[0])]; + +public: + const log_time start; + + LogStatistics(); + + LidStatistics &id(log_id_t log_id) { return LogIds[log_id]; } + + void enableDgramQlenStatistics() { dgram_qlen_statistics = true; } + static unsigned short dgram_qlen(unsigned short bucket); + unsigned long long minimum(unsigned short bucket); + void recordDiff(log_time diff, unsigned short bucket); + + void add(unsigned short size, log_id_t log_id, uid_t uid, pid_t pid); + void subtract(unsigned short size, log_id_t log_id, uid_t uid, pid_t pid); + void sort(); + + // fast track current value by id only + size_t sizes(log_id_t id) const { return mSizes[id]; } + size_t elements(log_id_t id) const { return mElements[id]; } + + // statistical track + static const log_id_t log_id_all = (log_id_t) -1; + static const uid_t uid_all = (uid_t) -1; + static const pid_t pid_all = (pid_t) -1; + + size_t sizes(log_id_t id, uid_t uid, pid_t pid = pid_all); + size_t elements(log_id_t id, uid_t uid, pid_t pid = pid_all); + size_t sizes() { return sizes(log_id_all, uid_all); } + size_t elements() { return elements(log_id_all, uid_all); } + + size_t sizesTotal(log_id_t id = log_id_all, + uid_t uid = uid_all, + pid_t pid = pid_all); + size_t elementsTotal(log_id_t id = log_id_all, + uid_t uid = uid_all, + pid_t pid = pid_all); + + // *strp = malloc, balance with free + void format(char **strp, uid_t uid, unsigned int logMask, log_time oldest); + + // helper + static char *pidToName(pid_t pid) { return PidStatistics::pidToName(pid); } + uid_t pidToUid(pid_t pid); +}; + +#endif // _LOGD_LOG_STATISTICS_H__ diff --git a/logd/LogTimes.cpp b/logd/LogTimes.cpp new file mode 100644 index 0000000..e7e3ec2 --- /dev/null +++ b/logd/LogTimes.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sys/prctl.h> + +#include "FlushCommand.h" +#include "LogBuffer.h" +#include "LogTimes.h" +#include "LogReader.h" + +pthread_mutex_t LogTimeEntry::timesLock = PTHREAD_MUTEX_INITIALIZER; + +const struct timespec LogTimeEntry::EPOCH = { 0, 1 }; + +LogTimeEntry::LogTimeEntry(LogReader &reader, SocketClient *client, + bool nonBlock, unsigned long tail, + unsigned int logMask, pid_t pid, + log_time start) + : mRefCount(1) + , mRelease(false) + , mError(false) + , threadRunning(false) + , threadTriggered(true) + , mReader(reader) + , mLogMask(logMask) + , mPid(pid) + , skipAhead(0) + , mCount(0) + , mTail(tail) + , mIndex(0) + , mClient(client) + , mStart(start) + , mNonBlock(nonBlock) + , mEnd(CLOCK_MONOTONIC) +{ } + +void LogTimeEntry::startReader_Locked(void) { + pthread_attr_t attr; + + threadRunning = true; + + if (!pthread_attr_init(&attr)) { + if (!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) { + if (!pthread_create(&mThread, &attr, + LogTimeEntry::threadStart, this)) { + pthread_attr_destroy(&attr); + return; + } + } + pthread_attr_destroy(&attr); + } + threadRunning = false; + if (mClient) { + mClient->decRef(); + } + decRef_Locked(); +} + +void LogTimeEntry::threadStop(void *obj) { + LogTimeEntry *me = reinterpret_cast<LogTimeEntry *>(obj); + + lock(); + + me->threadRunning = false; + if (me->mNonBlock) { + me->error_Locked(); + } + + SocketClient *client = me->mClient; + + if (me->isError_Locked()) { + LogReader &reader = me->mReader; + LastLogTimes × = reader.logbuf().mTimes; + + LastLogTimes::iterator it = times.begin(); + while(it != times.end()) { + if (*it == me) { + times.erase(it); + me->release_Locked(); + break; + } + it++; + } + + me->mClient = NULL; + reader.release(client); + } + + if (client) { + client->decRef(); + } + + me->decRef_Locked(); + + unlock(); +} + +void *LogTimeEntry::threadStart(void *obj) { + prctl(PR_SET_NAME, "logd.reader.per"); + + LogTimeEntry *me = reinterpret_cast<LogTimeEntry *>(obj); + + pthread_cleanup_push(threadStop, obj); + + SocketClient *client = me->mClient; + if (!client) { + me->error(); + pthread_exit(NULL); + } + + LogBuffer &logbuf = me->mReader.logbuf(); + + bool privileged = FlushCommand::hasReadLogs(client); + + lock(); + + me->threadTriggered = true; + + while(me->threadTriggered && !me->isError_Locked()) { + + me->threadTriggered = false; + + log_time start = me->mStart; + + unlock(); + + if (me->mTail) { + logbuf.flushTo(client, start, privileged, FilterFirstPass, me); + } + start = logbuf.flushTo(client, start, privileged, FilterSecondPass, me); + + if (start == LogBufferElement::FLUSH_ERROR) { + me->error(); + } + + if (me->mNonBlock) { + lock(); + break; + } + + sched_yield(); + + lock(); + } + + unlock(); + + pthread_exit(NULL); + + pthread_cleanup_pop(true); + + return NULL; +} + +// A first pass to count the number of elements +bool LogTimeEntry::FilterFirstPass(const LogBufferElement *element, void *obj) { + LogTimeEntry *me = reinterpret_cast<LogTimeEntry *>(obj); + + LogTimeEntry::lock(); + + if (me->mCount == 0) { + me->mStart = element->getMonotonicTime(); + } + + if ((!me->mPid || (me->mPid == element->getPid())) + && (me->mLogMask & (1 << element->getLogId()))) { + ++me->mCount; + } + + LogTimeEntry::unlock(); + + return false; +} + +// A second pass to send the selected elements +bool LogTimeEntry::FilterSecondPass(const LogBufferElement *element, void *obj) { + LogTimeEntry *me = reinterpret_cast<LogTimeEntry *>(obj); + + LogTimeEntry::lock(); + + if (me->skipAhead) { + me->skipAhead--; + goto skip; + } + + me->mStart = element->getMonotonicTime(); + + // Truncate to close race between first and second pass + if (me->mNonBlock && me->mTail && (me->mIndex >= me->mCount)) { + goto skip; + } + + if ((me->mLogMask & (1 << element->getLogId())) == 0) { + goto skip; + } + + if (me->mPid && (me->mPid != element->getPid())) { + goto skip; + } + + if (me->isError_Locked()) { + goto skip; + } + + if (!me->mTail) { + goto ok; + } + + ++me->mIndex; + + if ((me->mCount > me->mTail) && (me->mIndex <= (me->mCount - me->mTail))) { + goto skip; + } + + if (!me->mNonBlock) { + me->mTail = 0; + } + +ok: + if (!me->skipAhead) { + LogTimeEntry::unlock(); + return true; + } + // FALLTHRU + +skip: + LogTimeEntry::unlock(); + return false; +} diff --git a/logd/LogTimes.h b/logd/LogTimes.h new file mode 100644 index 0000000..beaf646 --- /dev/null +++ b/logd/LogTimes.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2012-2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LOGD_LOG_TIMES_H__ +#define _LOGD_LOG_TIMES_H__ + +#include <pthread.h> +#include <time.h> +#include <sys/types.h> +#include <sysutils/SocketClient.h> +#include <utils/List.h> + +class LogReader; + +class LogTimeEntry { + static pthread_mutex_t timesLock; + unsigned int mRefCount; + bool mRelease; + bool mError; + bool threadRunning; + bool threadTriggered; + pthread_t mThread; + LogReader &mReader; + static void *threadStart(void *me); + static void threadStop(void *me); + const unsigned int mLogMask; + const pid_t mPid; + unsigned int skipAhead; + unsigned long mCount; + unsigned long mTail; + unsigned long mIndex; + +public: + LogTimeEntry(LogReader &reader, SocketClient *client, bool nonBlock, + unsigned long tail, unsigned int logMask, pid_t pid, + log_time start); + + SocketClient *mClient; + static const struct timespec EPOCH; + log_time mStart; + const bool mNonBlock; + const log_time mEnd; // only relevant if mNonBlock + + // Protect List manipulations + static void lock(void) { pthread_mutex_lock(×Lock); } + static void unlock(void) { pthread_mutex_unlock(×Lock); } + + void startReader_Locked(void); + + bool runningReader_Locked(void) const { + return threadRunning || mRelease || mError || mNonBlock; + } + void triggerReader_Locked(void) { threadTriggered = true; } + void triggerSkip_Locked(unsigned int skip) { skipAhead = skip; } + + // Called after LogTimeEntry removed from list, lock implicitly held + void release_Locked(void) { + mRelease = true; + if (mRefCount || threadRunning) { + return; + } + // No one else is holding a reference to this + delete this; + } + + // Called to mark socket in jeopardy + void error_Locked(void) { mError = true; } + void error(void) { lock(); mError = true; unlock(); } + + bool isError_Locked(void) const { return mRelease || mError; } + + // Mark Used + // Locking implied, grabbed when protection around loop iteration + void incRef_Locked(void) { ++mRefCount; } + + bool owned_Locked(void) const { return mRefCount != 0; } + + void decRef_Locked(void) { + if ((mRefCount && --mRefCount) || !mRelease || threadRunning) { + return; + } + // No one else is holding a reference to this + delete this; + } + + // flushTo filter callbacks + static bool FilterFirstPass(const LogBufferElement *element, void *me); + static bool FilterSecondPass(const LogBufferElement *element, void *me); +}; + +typedef android::List<LogTimeEntry *> LastLogTimes; + +#endif diff --git a/logd/LogWhiteBlackList.cpp b/logd/LogWhiteBlackList.cpp new file mode 100644 index 0000000..e87b604 --- /dev/null +++ b/logd/LogWhiteBlackList.cpp @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ctype.h> + +#include <utils/String8.h> + +#include "LogWhiteBlackList.h" + +// White and Black list + +Prune::Prune(uid_t uid, pid_t pid) + : mUid(uid) + , mPid(pid) +{ } + +int Prune::cmp(uid_t uid, pid_t pid) const { + if ((mUid == uid_all) || (mUid == uid)) { + if (mPid == pid_all) { + return 0; + } + return pid - mPid; + } + return uid - mUid; +} + +void Prune::format(char **strp) { + if (mUid != uid_all) { + asprintf(strp, (mPid != pid_all) ? "%u/%u" : "%u", mUid, mPid); + } else { + // NB: mPid == pid_all can not happen if mUid == uid_all + asprintf(strp, (mPid != pid_all) ? "/%u" : "/", mPid); + } +} + +PruneList::PruneList() + : mWorstUidEnabled(false) { + mNaughty.clear(); + mNice.clear(); +} + +PruneList::~PruneList() { + PruneCollection::iterator it; + for (it = mNice.begin(); it != mNice.end();) { + delete (*it); + it = mNice.erase(it); + } + for (it = mNaughty.begin(); it != mNaughty.end();) { + delete (*it); + it = mNaughty.erase(it); + } +} + +int PruneList::init(char *str) { + mWorstUidEnabled = false; + PruneCollection::iterator it; + for (it = mNice.begin(); it != mNice.end();) { + delete (*it); + it = mNice.erase(it); + } + for (it = mNaughty.begin(); it != mNaughty.end();) { + delete (*it); + it = mNaughty.erase(it); + } + + if (!str) { + return 0; + } + + mWorstUidEnabled = false; + + for(; *str; ++str) { + if (isspace(*str)) { + continue; + } + + PruneCollection *list; + if ((*str == '~') || (*str == '!')) { // ~ supported, ! undocumented + ++str; + // special case, translates to worst UID at priority in blacklist + if (*str == '!') { + mWorstUidEnabled = true; + ++str; + if (!*str) { + break; + } + if (!isspace(*str)) { + return 1; + } + continue; + } + if (!*str) { + return 1; + } + list = &mNaughty; + } else { + list = &mNice; + } + + uid_t uid = Prune::uid_all; + if (isdigit(*str)) { + uid = 0; + do { + uid = uid * 10 + *str++ - '0'; + } while (isdigit(*str)); + } + + pid_t pid = Prune::pid_all; + if (*str == '/') { + ++str; + if (isdigit(*str)) { + pid = 0; + do { + pid = pid * 10 + *str++ - '0'; + } while (isdigit(*str)); + } + } + + if ((uid == Prune::uid_all) && (pid == Prune::pid_all)) { + return 1; + } + + if (*str && !isspace(*str)) { + return 1; + } + + // insert sequentially into list + PruneCollection::iterator it = list->begin(); + while (it != list->end()) { + Prune *p = *it; + int m = uid - p->mUid; + if (m == 0) { + if (p->mPid == p->pid_all) { + break; + } + if ((pid == p->pid_all) && (p->mPid != p->pid_all)) { + it = list->erase(it); + continue; + } + m = pid - p->mPid; + } + if (m <= 0) { + if (m < 0) { + list->insert(it, new Prune(uid,pid)); + } + break; + } + ++it; + } + if (it == list->end()) { + list->push_back(new Prune(uid,pid)); + } + if (!*str) { + break; + } + } + + return 0; +} + +void PruneList::format(char **strp) { + if (*strp) { + free(*strp); + *strp = NULL; + } + + static const char nice_format[] = " %s"; + const char *fmt = nice_format + 1; + + android::String8 string; + + if (mWorstUidEnabled) { + string.setTo("~!"); + fmt = nice_format; + } + + PruneCollection::iterator it; + + for (it = mNice.begin(); it != mNice.end(); ++it) { + char *a = NULL; + (*it)->format(&a); + + string.appendFormat(fmt, a); + fmt = nice_format; + + free(a); + } + + static const char naughty_format[] = " ~%s"; + fmt = naughty_format + (*fmt != ' '); + for (it = mNaughty.begin(); it != mNaughty.end(); ++it) { + char *a = NULL; + (*it)->format(&a); + + string.appendFormat(fmt, a); + fmt = naughty_format; + + free(a); + } + + *strp = strdup(string.string()); +} + +// ToDo: Lists are in sorted order, Prune->cmp() returns + or - +// If there is scaling issues, resort to a better algorithm than linear +// based on these assumptions. + +bool PruneList::naughty(LogBufferElement *element) { + PruneCollection::iterator it; + for (it = mNaughty.begin(); it != mNaughty.end(); ++it) { + if (!(*it)->cmp(element)) { + return true; + } + } + return false; +} + +bool PruneList::nice(LogBufferElement *element) { + PruneCollection::iterator it; + for (it = mNice.begin(); it != mNice.end(); ++it) { + if (!(*it)->cmp(element)) { + return true; + } + } + return false; +} diff --git a/logd/LogWhiteBlackList.h b/logd/LogWhiteBlackList.h new file mode 100644 index 0000000..769d651 --- /dev/null +++ b/logd/LogWhiteBlackList.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LOGD_LOG_WHITE_BLACK_LIST_H__ +#define _LOGD_LOG_WHITE_BLACK_LIST_H__ + +#include <sys/types.h> + +#include <utils/List.h> + +#include <LogBufferElement.h> + +// White and Blacklist + +class Prune { + friend class PruneList; + + const uid_t mUid; + const pid_t mPid; + int cmp(uid_t uid, pid_t pid) const; + +public: + static const uid_t uid_all = (uid_t) -1; + static const pid_t pid_all = (pid_t) -1; + + Prune(uid_t uid, pid_t pid); + + uid_t getUid() const { return mUid; } + pid_t getPid() const { return mPid; } + + int cmp(LogBufferElement *e) const { return cmp(e->getUid(), e->getPid()); } + + // *strp is malloc'd, use free to release + void format(char **strp); +}; + +typedef android::List<Prune *> PruneCollection; + +class PruneList { + PruneCollection mNaughty; + PruneCollection mNice; + bool mWorstUidEnabled; + +public: + PruneList(); + ~PruneList(); + + int init(char *str); + + bool naughty(LogBufferElement *element); + bool nice(LogBufferElement *element); + bool worstUidEnabled() const { return mWorstUidEnabled; } + + // *strp is malloc'd, use free to release + void format(char **strp); +}; + +#endif // _LOGD_LOG_WHITE_BLACK_LIST_H__ diff --git a/logd/README.auditd b/logd/README.auditd new file mode 100644 index 0000000..3f614a3 --- /dev/null +++ b/logd/README.auditd @@ -0,0 +1,17 @@ +Auditd Daemon + +The audit daemon is a simplified version of its desktop +counterpart designed to gather the audit logs from the +audit kernel subsystem. The audit subsystem of the kernel +includes Linux Security Modules (LSM) messages as well. + +To enable the audit subsystem, you must add this to your +kernel config: +CONFIG_AUDIT=y + +To enable a LSM, you must consult that LSM's documentation, the +example below is for SELinux: +CONFIG_SECURITY_SELINUX=y + +This does not include possible dependencies that may need to be +satisfied for that particular LSM. diff --git a/logd/README.property b/logd/README.property new file mode 100644 index 0000000..f4b3c3c --- /dev/null +++ b/logd/README.property @@ -0,0 +1,25 @@ +The properties that logd responds to are: + +name type default description +logd.auditd bool true Enable selinux audit daemon +logd.auditd.dmesg bool true selinux audit messages duplicated and + sent on to dmesg log +logd.statistics.dgram_qlen bool false Record dgram_qlen statistics. This + represents a performance impact and + is used to determine the platform's + minimum domain socket network FIFO + size (see source for details) based + on typical load (logcat -S to view) +persist.logd.size number 256K default size of the buffer for all + log ids at initial startup, at runtime + use: logcat -b all -G <value> +persist.logd.size.main number 256K Size of the buffer for the main log +persist.logd.size.system number 256K Size of the buffer for the system log +persist.logd.size.radio number 256K Size of the buffer for the radio log +persist.logd.size.event number 256K Size of the buffer for the event log +persist.logd.size.crash number 256K Size of the buffer for the crash log + +NB: +- number support multipliers (K or M) for convenience. Range is limited + to between 64K and 256M for log buffer sizes. Individual logs override the + global default. diff --git a/logd/event.logtags b/logd/event.logtags new file mode 100644 index 0000000..a63f034 --- /dev/null +++ b/logd/event.logtags @@ -0,0 +1,36 @@ +# The entries in this file map a sparse set of log tag numbers to tag names. +# This is installed on the device, in /system/etc, and parsed by logcat. +# +# Tag numbers are decimal integers, from 0 to 2^31. (Let's leave the +# negative values alone for now.) +# +# Tag names are one or more ASCII letters and numbers or underscores, i.e. +# "[A-Z][a-z][0-9]_". Do not include spaces or punctuation (the former +# impacts log readability, the latter makes regex searches more annoying). +# +# Tag numbers and names are separated by whitespace. Blank lines and lines +# starting with '#' are ignored. +# +# Optionally, after the tag names can be put a description for the value(s) +# of the tag. Description are in the format +# (<name>|data type[|data unit]) +# Multiple values are separated by commas. +# +# The data type is a number from the following values: +# 1: int +# 2: long +# 3: string +# 4: list +# +# The data unit is a number taken from the following list: +# 1: Number of objects +# 2: Number of bytes +# 3: Number of milliseconds +# 4: Number of allocations +# 5: Id +# 6: Percent +# Default value for data of type int/long is 2 (bytes). +# +# TODO: generate ".java" and ".h" files with integer constants from this file. + +1003 auditd (avc|3) diff --git a/logd/libaudit.c b/logd/libaudit.c new file mode 100644 index 0000000..ca88d1b --- /dev/null +++ b/logd/libaudit.c @@ -0,0 +1,276 @@ +/* + * Copyright 2012, Samsung Telecommunications of America + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Written by William Roberts <w.roberts@sta.samsung.com> + * + */ + +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#define LOG_TAG "libaudit" +#include <log/log.h> + +#include "libaudit.h" + +/** + * Waits for an ack from the kernel + * @param fd + * The netlink socket fd + * @param seq + * The current sequence number were acking on + * @return + * This function returns 0 on success, else -errno. + */ +static int get_ack(int fd, int16_t seq) +{ + int rc; + struct audit_message rep; + + /* Sanity check, this is an internal interface this shouldn't happen */ + if (fd < 0) { + return -EINVAL; + } + + rc = audit_get_reply(fd, &rep, GET_REPLY_BLOCKING, MSG_PEEK); + if (rc < 0) { + return rc; + } + + if (rep.nlh.nlmsg_type == NLMSG_ERROR) { + audit_get_reply(fd, &rep, GET_REPLY_BLOCKING, 0); + rc = ((struct nlmsgerr *)rep.data)->error; + if (rc) { + return -rc; + } + } + + if ((int16_t)rep.nlh.nlmsg_seq != seq) { + SLOGW("Expected sequence number between user space and kernel space is out of skew, " + "expected %u got %u", seq, rep.nlh.nlmsg_seq); + } + + return 0; +} + +/** + * + * @param fd + * The netlink socket fd + * @param type + * The type of netlink message + * @param data + * The data to send + * @param size + * The length of the data in bytes + * @return + * This function returns a positive sequence number on success, else -errno. + */ +static int audit_send(int fd, int type, const void *data, size_t size) +{ + int rc; + static int16_t sequence = 0; + struct audit_message req; + struct sockaddr_nl addr; + + memset(&req, 0, sizeof(req)); + memset(&addr, 0, sizeof(addr)); + + /* We always send netlink messaged */ + addr.nl_family = AF_NETLINK; + + /* Set up the netlink headers */ + req.nlh.nlmsg_type = type; + req.nlh.nlmsg_len = NLMSG_SPACE(size); + req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + + /* + * Check for a valid fd, even though sendto would catch this, its easier + * to always blindly increment the sequence number + */ + if (fd < 0) { + return -EBADF; + } + + /* Ensure the message is not too big */ + if (NLMSG_SPACE(size) > MAX_AUDIT_MESSAGE_LENGTH) { + SLOGE("netlink message is too large"); + return -EINVAL; + } + + /* Only memcpy in the data if it was specified */ + if (size && data) { + memcpy(NLMSG_DATA(&req.nlh), data, size); + } + + /* + * Only increment the sequence number on a guarantee + * you will send it to the kernel. + * + * Also, the sequence is defined as a u32 in the kernel + * struct. Using an int here might not work on 32/64 bit splits. A + * signed 64 bit value can overflow a u32..but a u32 + * might not fit in the response, so we need to use s32. + * Which is still kind of hackish since int could be 16 bits + * in size. The only safe type to use here is a signed 16 + * bit value. + */ + req.nlh.nlmsg_seq = ++sequence; + + /* While failing and its due to interrupts */ + + rc = TEMP_FAILURE_RETRY(sendto(fd, &req, req.nlh.nlmsg_len, 0, + (struct sockaddr*) &addr, sizeof(addr))); + + /* Not all the bytes were sent */ + if (rc < 0) { + rc = -errno; + SLOGE("Error sending data over the netlink socket: %s", strerror(-errno)); + goto out; + } else if ((uint32_t) rc != req.nlh.nlmsg_len) { + rc = -EPROTO; + goto out; + } + + /* We sent all the bytes, get the ack */ + rc = get_ack(fd, sequence); + + /* If the ack failed, return the error, else return the sequence number */ + rc = (rc == 0) ? (int) sequence : rc; + +out: + /* Don't let sequence roll to negative */ + if (sequence < 0) { + SLOGW("Auditd to Kernel sequence number has rolled over"); + sequence = 0; + } + + return rc; +} + +int audit_set_pid(int fd, uint32_t pid, rep_wait_t wmode) +{ + int rc; + struct audit_message rep; + struct audit_status status; + + memset(&status, 0, sizeof(status)); + + /* + * In order to set the auditd PID we send an audit message over the netlink + * socket with the pid field of the status struct set to our current pid, + * and the the mask set to AUDIT_STATUS_PID + */ + status.pid = pid; + status.mask = AUDIT_STATUS_PID; + + /* Let the kernel know this pid will be registering for audit events */ + rc = audit_send(fd, AUDIT_SET, &status, sizeof(status)); + if (rc < 0) { + SLOGE("Could net set pid for audit events, error: %s", strerror(-rc)); + return rc; + } + + /* + * In a request where we need to wait for a response, wait for the message + * and discard it. This message confirms and sync's us with the kernel. + * This daemon is now registered as the audit logger. Only wait if the + * wmode is != WAIT_NO + */ + if (wmode != WAIT_NO) { + /* TODO + * If the daemon dies and restarts the message didn't come back, + * so I went to non-blocking and it seemed to fix the bug. + * Need to investigate further. + */ + audit_get_reply(fd, &rep, GET_REPLY_NONBLOCKING, 0); + } + + return 0; +} + +int audit_open() +{ + return socket(PF_NETLINK, SOCK_RAW, NETLINK_AUDIT); +} + +int audit_get_reply(int fd, struct audit_message *rep, reply_t block, int peek) +{ + ssize_t len; + int flags; + int rc = 0; + + struct sockaddr_nl nladdr; + socklen_t nladdrlen = sizeof(nladdr); + + if (fd < 0) { + return -EBADF; + } + + /* Set up the flags for recv from */ + flags = (block == GET_REPLY_NONBLOCKING) ? MSG_DONTWAIT : 0; + flags |= peek; + + /* + * Get the data from the netlink socket but on error we need to be carefull, + * the interface shows that EINTR can never be returned, other errors, + * however, can be returned. + */ + len = TEMP_FAILURE_RETRY(recvfrom(fd, rep, sizeof(*rep), flags, + (struct sockaddr*) &nladdr, &nladdrlen)); + + /* + * EAGAIN should be re-tried until success or another error manifests. + */ + if (len < 0) { + rc = -errno; + if (block == GET_REPLY_NONBLOCKING && rc == -EAGAIN) { + /* If request is non blocking and errno is EAGAIN, just return 0 */ + return 0; + } + SLOGE("Error receiving from netlink socket, error: %s", strerror(-rc)); + return rc; + } + + if (nladdrlen != sizeof(nladdr)) { + SLOGE("Protocol fault, error: %s", strerror(EPROTO)); + return -EPROTO; + } + + /* Make sure the netlink message was not spoof'd */ + if (nladdr.nl_pid) { + SLOGE("Invalid netlink pid received, expected 0 got: %d", nladdr.nl_pid); + return -EINVAL; + } + + /* Check if the reply from the kernel was ok */ + if (!NLMSG_OK(&rep->nlh, (size_t)len)) { + rc = (len == sizeof(*rep)) ? -EFBIG : -EBADE; + SLOGE("Bad kernel response %s", strerror(-rc)); + } + + return rc; +} + +void audit_close(int fd) +{ + int rc = close(fd); + if (rc < 0) { + SLOGE("Attempting to close invalid fd %d, error: %s", fd, strerror(errno)); + } + return; +} diff --git a/logd/libaudit.h b/logd/libaudit.h new file mode 100644 index 0000000..cb114f9 --- /dev/null +++ b/logd/libaudit.h @@ -0,0 +1,104 @@ +/* + * Copyright 2012, Samsung Telecommunications of America + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Written by William Roberts <w.roberts@sta.samsung.com> + */ + +#ifndef _LIBAUDIT_H_ +#define _LIBAUDIT_H_ + +#include <stdint.h> +#include <sys/cdefs.h> +#include <sys/socket.h> +#include <sys/types.h> + +#include <linux/netlink.h> +#include <linux/audit.h> + +__BEGIN_DECLS + +#define MAX_AUDIT_MESSAGE_LENGTH 8970 + +typedef enum { + GET_REPLY_BLOCKING=0, + GET_REPLY_NONBLOCKING +} reply_t; + +typedef enum { + WAIT_NO, + WAIT_YES +} rep_wait_t; + +/* type == AUDIT_SIGNAL_INFO */ +struct audit_sig_info { + uid_t uid; + pid_t pid; + char ctx[0]; +}; + +struct audit_message { + struct nlmsghdr nlh; + char data[MAX_AUDIT_MESSAGE_LENGTH]; +}; + +/** + * Opens a connection to the Audit netlink socket + * @return + * A valid fd on success or < 0 on error with errno set. + * Returns the same errors as man 2 socket. + */ +extern int audit_open(void); + +/** + * Closes the fd returned from audit_open() + * @param fd + * The fd to close + */ +extern void audit_close(int fd); + +/** + * + * @param fd + * The fd returned by a call to audit_open() + * @param rep + * The response struct to store the response in. + * @param block + * Whether or not to block on IO + * @param peek + * Whether or not we are to remove the message from + * the queue when we do a read on the netlink socket. + * @return + * This function returns 0 on success, else -errno. + */ +extern int audit_get_reply(int fd, struct audit_message *rep, reply_t block, + int peek); + +/** + * Sets a pid to recieve audit netlink events from the kernel + * @param fd + * The fd returned by a call to audit_open() + * @param pid + * The pid whom to set as the reciever of audit messages + * @param wmode + * Whether or not to block on the underlying socket io calls. + * @return + * This function returns 0 on success, -errno on error. + */ +extern int audit_set_pid(int fd, uint32_t pid, rep_wait_t wmode); + +__END_DECLS + +#endif diff --git a/logd/main.cpp b/logd/main.cpp new file mode 100644 index 0000000..ece5a3a --- /dev/null +++ b/logd/main.cpp @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2012-2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <sched.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/capability.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <linux/prctl.h> + +#include <cutils/properties.h> + +#include "private/android_filesystem_config.h" +#include "CommandListener.h" +#include "LogBuffer.h" +#include "LogListener.h" +#include "LogAudit.h" + +// +// The service is designed to be run by init, it does not respond well +// to starting up manually. When starting up manually the sockets will +// fail to open typically for one of the following reasons: +// EADDRINUSE if logger is running. +// EACCESS if started without precautions (below) +// +// Here is a cookbook procedure for starting up logd manually assuming +// init is out of the way, pedantically all permissions and selinux +// security is put back in place: +// +// setenforce 0 +// rm /dev/socket/logd* +// chmod 777 /dev/socket +// # here is where you would attach the debugger or valgrind for example +// runcon u:r:logd:s0 /system/bin/logd </dev/null >/dev/null 2>&1 & +// sleep 1 +// chmod 755 /dev/socket +// chown logd.logd /dev/socket/logd* +// restorecon /dev/socket/logd* +// setenforce 1 +// +// If minimalism prevails, typical for debugging and security is not a concern: +// +// setenforce 0 +// chmod 777 /dev/socket +// logd +// + +static int drop_privs() { + struct sched_param param; + memset(¶m, 0, sizeof(param)); + + if (sched_setscheduler((pid_t) 0, SCHED_BATCH, ¶m) < 0) { + return -1; + } + + if (prctl(PR_SET_KEEPCAPS, 1) < 0) { + return -1; + } + + if (setgid(AID_LOGD) != 0) { + return -1; + } + + if (setuid(AID_LOGD) != 0) { + return -1; + } + + struct __user_cap_header_struct capheader; + struct __user_cap_data_struct capdata[2]; + memset(&capheader, 0, sizeof(capheader)); + memset(&capdata, 0, sizeof(capdata)); + capheader.version = _LINUX_CAPABILITY_VERSION_3; + capheader.pid = 0; + + capdata[CAP_TO_INDEX(CAP_SYSLOG)].permitted = CAP_TO_MASK(CAP_SYSLOG); + capdata[CAP_TO_INDEX(CAP_AUDIT_CONTROL)].permitted |= CAP_TO_MASK(CAP_AUDIT_CONTROL); + + capdata[0].effective = capdata[0].permitted; + capdata[1].effective = capdata[1].permitted; + capdata[0].inheritable = 0; + capdata[1].inheritable = 0; + + if (capset(&capheader, &capdata[0]) < 0) { + return -1; + } + + return 0; +} + +// Property helper +static bool property_get_bool(const char *key, bool def) { + char property[PROPERTY_VALUE_MAX]; + property_get(key, property, ""); + + if (!strcasecmp(property, "true")) { + return true; + } + if (!strcasecmp(property, "false")) { + return false; + } + + return def; +} + +// Foreground waits for exit of the three main persistent threads that +// are started here. The three threads are created to manage UNIX +// domain client sockets for writing, reading and controlling the user +// space logger. Additional transitory per-client threads are created +// for each reader once they register. +int main() { + bool auditd = property_get_bool("logd.auditd", true); + + int fdDmesg = -1; + if (auditd && property_get_bool("logd.auditd.dmesg", true)) { + fdDmesg = open("/dev/kmsg", O_WRONLY); + } + + if (drop_privs() != 0) { + return -1; + } + + // Serves the purpose of managing the last logs times read on a + // socket connection, and as a reader lock on a range of log + // entries. + + LastLogTimes *times = new LastLogTimes(); + + // LogBuffer is the object which is responsible for holding all + // log entries. + + LogBuffer *logBuf = new LogBuffer(times); + + if (property_get_bool("logd.statistics.dgram_qlen", false)) { + logBuf->enableDgramQlenStatistics(); + } + + // LogReader listens on /dev/socket/logdr. When a client + // connects, log entries in the LogBuffer are written to the client. + + LogReader *reader = new LogReader(logBuf); + if (reader->startListener()) { + exit(1); + } + + // LogListener listens on /dev/socket/logdw for client + // initiated log messages. New log entries are added to LogBuffer + // and LogReader is notified to send updates to connected clients. + + LogListener *swl = new LogListener(logBuf, reader); + // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value + if (swl->startListener(300)) { + exit(1); + } + + // Command listener listens on /dev/socket/logd for incoming logd + // administrative commands. + + CommandListener *cl = new CommandListener(logBuf, reader, swl); + if (cl->startListener()) { + exit(1); + } + + // LogAudit listens on NETLINK_AUDIT socket for selinux + // initiated log messages. New log entries are added to LogBuffer + // and LogReader is notified to send updates to connected clients. + + if (auditd) { + // failure is an option ... messages are in dmesg (required by standard) + LogAudit *al = new LogAudit(logBuf, reader, fdDmesg); + if (al->startListener()) { + delete al; + close(fdDmesg); + } + } + + pause(); + exit(0); +} + diff --git a/logd/tests/Android.mk b/logd/tests/Android.mk new file mode 100644 index 0000000..f851288 --- /dev/null +++ b/logd/tests/Android.mk @@ -0,0 +1,53 @@ +# +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) + +# ----------------------------------------------------------------------------- +# Benchmarks. (see ../../liblog/tests) +# ----------------------------------------------------------------------------- + +test_module_prefix := logd- +test_tags := tests + +# ----------------------------------------------------------------------------- +# Unit tests. +# ----------------------------------------------------------------------------- + +test_c_flags := \ + -fstack-protector-all \ + -g \ + -Wall -Wextra \ + -Werror \ + -fno-builtin \ + +ifneq ($(TARGET_USES_LOGD),false) +test_c_flags += -DTARGET_USES_LOGD=1 +endif + +test_src_files := \ + logd_test.cpp + +# Build tests for the logger. Run with: +# adb shell /data/nativetest/logd-unit-tests/logd-unit-tests +include $(CLEAR_VARS) +LOCAL_MODULE := $(test_module_prefix)unit-tests +LOCAL_MODULE_TAGS := $(test_tags) +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk +LOCAL_CFLAGS += $(test_c_flags) +LOCAL_SHARED_LIBRARIES := libcutils +LOCAL_SRC_FILES := $(test_src_files) +include $(BUILD_NATIVE_TEST) diff --git a/logd/tests/logd_test.cpp b/logd/tests/logd_test.cpp new file mode 100644 index 0000000..957fdb5 --- /dev/null +++ b/logd/tests/logd_test.cpp @@ -0,0 +1,703 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fcntl.h> +#include <poll.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> + +#include <gtest/gtest.h> + +#include "cutils/sockets.h" +#include "log/log.h" +#include "log/logger.h" + +#define __unused __attribute__((__unused__)) + +/* + * returns statistics + */ +static void my_android_logger_get_statistics(char *buf, size_t len) +{ + snprintf(buf, len, "getStatistics 0 1 2 3 4"); + int sock = socket_local_client("logd", + ANDROID_SOCKET_NAMESPACE_RESERVED, + SOCK_STREAM); + if (sock >= 0) { + if (write(sock, buf, strlen(buf) + 1) > 0) { + ssize_t ret; + while ((ret = read(sock, buf, len)) > 0) { + if ((size_t)ret == len) { + break; + } + len -= ret; + buf += ret; + + struct pollfd p = { + .fd = sock, + .events = POLLIN, + .revents = 0 + }; + + ret = poll(&p, 1, 20); + if ((ret <= 0) || !(p.revents & POLLIN)) { + break; + } + } + } + close(sock); + } +} + +static void alloc_statistics(char **buffer, size_t *length) +{ + size_t len = 8192; + char *buf; + + for(int retry = 32; (retry >= 0); delete [] buf, --retry) { + buf = new char [len]; + my_android_logger_get_statistics(buf, len); + + buf[len-1] = '\0'; + size_t ret = atol(buf) + 1; + if (ret < 4) { + delete [] buf; + buf = NULL; + break; + } + bool check = ret <= len; + len = ret; + if (check) { + break; + } + len += len / 8; // allow for some slop + } + *buffer = buf; + *length = len; +} + +static char *find_benchmark_spam(char *cp) +{ + // liblog_benchmarks has been run designed to SPAM. The signature of + // a noisiest UID statistics is one of the following: + // + // main: UID/PID Total size/num Now UID/PID[?] Total + // 0 7500306/304207 71608/3183 0/4225? 7454388/303656 + // <wrap> 93432/1012 + // -or- + // 0/gone 7454388/303656 93432/1012 + // + // basically if we see a *large* number of 0/????? entries + unsigned long value; + do { + char *benchmark = strstr(cp, " 0/"); + char *benchmark_newline = strstr(cp, "\n0/"); + if (!benchmark) { + benchmark = benchmark_newline; + } + if (benchmark_newline && (benchmark > benchmark_newline)) { + benchmark = benchmark_newline; + } + cp = benchmark; + if (!cp) { + break; + } + cp += 3; + while (isdigit(*cp) || (*cp == 'g') || (*cp == 'o') || (*cp == 'n')) { + ++cp; + } + value = 0; + // ###? or gone + if ((*cp == '?') || (*cp == 'e')) { + while (*++cp == ' '); + while (isdigit(*cp)) { + value = value * 10ULL + *cp - '0'; + ++cp; + } + if (*cp != '/') { + value = 0; + continue; + } + while (isdigit(*++cp)); + while (*cp == ' ') ++cp; + if (!isdigit(*cp)) { + value = 0; + } + } + } while ((value < 900000ULL) && *cp); + return cp; +} + +TEST(logd, statistics) { + size_t len; + char *buf; + + alloc_statistics(&buf, &len); + +#ifdef TARGET_USES_LOGD + ASSERT_TRUE(NULL != buf); +#else + if (!buf) { + return; + } +#endif + + // remove trailing FF + char *cp = buf + len - 1; + *cp = '\0'; + bool truncated = *--cp != '\f'; + if (!truncated) { + *cp = '\0'; + } + + // squash out the byte count + cp = buf; + if (!truncated) { + while (isdigit(*cp) || (*cp == '\n')) { + ++cp; + } + } + + fprintf(stderr, "%s", cp); + + EXPECT_LT((size_t)64, strlen(cp)); + + EXPECT_EQ(0, truncated); + +#ifdef TARGET_USES_LOGD + char *main_logs = strstr(cp, "\nmain:"); + EXPECT_TRUE(NULL != main_logs); + + char *radio_logs = strstr(cp, "\nradio:"); + EXPECT_TRUE(NULL != radio_logs); + + char *system_logs = strstr(cp, "\nsystem:"); + EXPECT_TRUE(NULL != system_logs); + + char *events_logs = strstr(cp, "\nevents:"); + EXPECT_TRUE(NULL != events_logs); +#endif + + // Parse timing stats + + cp = strstr(cp, "Minimum time between log events per dgram_qlen:"); + + if (cp) { + while (*cp && (*cp != '\n')) { + ++cp; + } + if (*cp == '\n') { + ++cp; + } + + char *list_of_spans = cp; + EXPECT_NE('\0', *list_of_spans); + + unsigned short number_of_buckets = 0; + unsigned short *dgram_qlen = NULL; + unsigned short bucket = 0; + while (*cp && (*cp != '\n')) { + bucket = 0; + while (isdigit(*cp)) { + bucket = bucket * 10 + *cp - '0'; + ++cp; + } + while (*cp == ' ') { + ++cp; + } + if (!bucket) { + break; + } + unsigned short *new_dgram_qlen = new unsigned short[number_of_buckets + 1]; + EXPECT_TRUE(new_dgram_qlen != NULL); + if (dgram_qlen) { + memcpy(new_dgram_qlen, dgram_qlen, sizeof(*dgram_qlen) * number_of_buckets); + delete [] dgram_qlen; + } + + dgram_qlen = new_dgram_qlen; + dgram_qlen[number_of_buckets++] = bucket; + } + + char *end_of_spans = cp; + EXPECT_NE('\0', *end_of_spans); + + EXPECT_LT(5, number_of_buckets); + + unsigned long long *times = new unsigned long long [number_of_buckets]; + ASSERT_TRUE(times != NULL); + + memset(times, 0, sizeof(*times) * number_of_buckets); + + while (*cp == '\n') { + ++cp; + } + + unsigned short number_of_values = 0; + unsigned long long value; + while (*cp && (*cp != '\n')) { + EXPECT_GE(number_of_buckets, number_of_values); + + value = 0; + while (isdigit(*cp)) { + value = value * 10ULL + *cp - '0'; + ++cp; + } + + switch(*cp) { + case ' ': + case '\n': + value *= 1000ULL; + /* FALLTHRU */ + case 'm': + value *= 1000ULL; + /* FALLTHRU */ + case 'u': + value *= 1000ULL; + /* FALLTHRU */ + case 'n': + default: + break; + } + while (*++cp == ' '); + + if (!value) { + break; + } + + times[number_of_values] = value; + ++number_of_values; + } + +#ifdef TARGET_USES_LOGD + EXPECT_EQ(number_of_values, number_of_buckets); +#endif + + FILE *fp; + ASSERT_TRUE(NULL != (fp = fopen("/proc/sys/net/unix/max_dgram_qlen", "r"))); + + unsigned max_dgram_qlen = 0; + fscanf(fp, "%u", &max_dgram_qlen); + + fclose(fp); + + // Find launch point + unsigned short launch = 0; + unsigned long long total = 0; + do { + total += times[launch]; + } while (((++launch < number_of_buckets) + && ((total / launch) >= (times[launch] / 8ULL))) + || (launch == 1)); // too soon + + bool failure = number_of_buckets <= launch; + if (!failure) { + unsigned short l = launch; + if (l >= number_of_buckets) { + l = number_of_buckets - 1; + } + failure = max_dgram_qlen < dgram_qlen[l]; + } + + // We can get failure if at any time liblog_benchmarks has been run + // because designed to overload /proc/sys/net/unix/max_dgram_qlen even + // at excessive values like 20000. It does so to measure the raw processing + // performance of logd. + if (failure) { + cp = find_benchmark_spam(cp); + } + + if (cp) { + // Fake a failure, but without the failure code + if (number_of_buckets <= launch) { + printf ("Expected: number_of_buckets > launch, actual: %u vs %u\n", + number_of_buckets, launch); + } + if (launch >= number_of_buckets) { + launch = number_of_buckets - 1; + } + if (max_dgram_qlen < dgram_qlen[launch]) { + printf ("Expected: max_dgram_qlen >= dgram_qlen[%d]," + " actual: %u vs %u\n", + launch, max_dgram_qlen, dgram_qlen[launch]); + } + } else +#ifndef TARGET_USES_LOGD + if (total) +#endif + { + EXPECT_GT(number_of_buckets, launch); + if (launch >= number_of_buckets) { + launch = number_of_buckets - 1; + } + EXPECT_GE(max_dgram_qlen, dgram_qlen[launch]); + } + + delete [] dgram_qlen; + delete [] times; + } + delete [] buf; +} + +static void caught_signal(int signum __unused) { } + +static void dump_log_msg(const char *prefix, + log_msg *msg, unsigned int version, int lid) { + switch(msg->entry.hdr_size) { + case 0: + version = 1; + break; + + case sizeof(msg->entry_v2): + if (version == 0) { + version = 2; + } + break; + } + + fprintf(stderr, "%s: v%u[%u] ", prefix, version, msg->len()); + if (version != 1) { + fprintf(stderr, "hdr_size=%u ", msg->entry.hdr_size); + } + fprintf(stderr, "pid=%u tid=%u %u.%09u ", + msg->entry.pid, msg->entry.tid, msg->entry.sec, msg->entry.nsec); + switch(version) { + case 1: + break; + case 2: + fprintf(stderr, "euid=%u ", msg->entry_v2.euid); + break; + case 3: + default: + lid = msg->entry.lid; + break; + } + + switch(lid) { + case 0: + fprintf(stderr, "lid=main "); + break; + case 1: + fprintf(stderr, "lid=radio "); + break; + case 2: + fprintf(stderr, "lid=events "); + break; + case 3: + fprintf(stderr, "lid=system "); + break; + default: + if (lid >= 0) { + fprintf(stderr, "lid=%d ", lid); + } + } + + unsigned int len = msg->entry.len; + fprintf(stderr, "msg[%u]={", len); + unsigned char *cp = reinterpret_cast<unsigned char *>(msg->msg()); + while(len) { + unsigned char *p = cp; + while (*p && (((' ' <= *p) && (*p < 0x7F)) || (*p == '\n'))) { + ++p; + } + if (((p - cp) > 3) && !*p && ((unsigned int)(p - cp) < len)) { + fprintf(stderr, "\""); + while (*cp) { + fprintf(stderr, (*cp != '\n') ? "%c" : "\\n", *cp); + ++cp; + --len; + } + fprintf(stderr, "\""); + } else { + fprintf(stderr, "%02x", *cp); + } + ++cp; + if (--len) { + fprintf(stderr, ", "); + } + } + fprintf(stderr, "}\n"); +} + +TEST(logd, both) { + log_msg msg; + + // check if we can read any logs from logd + bool user_logger_available = false; + bool user_logger_content = false; + + int fd = socket_local_client("logdr", + ANDROID_SOCKET_NAMESPACE_RESERVED, + SOCK_SEQPACKET); + if (fd >= 0) { + struct sigaction ignore, old_sigaction; + memset(&ignore, 0, sizeof(ignore)); + ignore.sa_handler = caught_signal; + sigemptyset(&ignore.sa_mask); + sigaction(SIGALRM, &ignore, &old_sigaction); + unsigned int old_alarm = alarm(10); + + static const char ask[] = "dumpAndClose lids=0,1,2,3"; + user_logger_available = write(fd, ask, sizeof(ask)) == sizeof(ask); + + user_logger_content = recv(fd, msg.buf, sizeof(msg), 0) > 0; + + if (user_logger_content) { + dump_log_msg("user", &msg, 3, -1); + } + + alarm(old_alarm); + sigaction(SIGALRM, &old_sigaction, NULL); + + close(fd); + } + + // check if we can read any logs from kernel logger + bool kernel_logger_available = false; + bool kernel_logger_content = false; + + static const char *loggers[] = { + "/dev/log/main", "/dev/log_main", + "/dev/log/radio", "/dev/log_radio", + "/dev/log/events", "/dev/log_events", + "/dev/log/system", "/dev/log_system", + }; + + for (unsigned int i = 0; i < (sizeof(loggers) / sizeof(loggers[0])); ++i) { + fd = open(loggers[i], O_RDONLY); + if (fd < 0) { + continue; + } + kernel_logger_available = true; + fcntl(fd, F_SETFL, O_RDONLY | O_NONBLOCK); + int result = TEMP_FAILURE_RETRY(read(fd, msg.buf, sizeof(msg))); + if (result > 0) { + kernel_logger_content = true; + dump_log_msg("kernel", &msg, 0, i / 2); + } + close(fd); + } + + static const char yes[] = "\xE2\x9C\x93"; + static const char no[] = "\xE2\x9c\x98"; + fprintf(stderr, + "LOGGER Available Content\n" + "user %-13s%s\n" + "kernel %-13s%s\n" + " status %-11s%s\n", + (user_logger_available) ? yes : no, + (user_logger_content) ? yes : no, + (kernel_logger_available) ? yes : no, + (kernel_logger_content) ? yes : no, + (user_logger_available && kernel_logger_available) ? "WARNING" : "ok", + (user_logger_content && kernel_logger_content) ? "ERROR" : "ok"); + + if (user_logger_available && kernel_logger_available) { + printf("WARNING: kernel & user logger; both consuming resources!!!\n"); + } + + EXPECT_EQ(0, user_logger_content && kernel_logger_content); + EXPECT_EQ(0, !user_logger_content && !kernel_logger_content); +} + +// BAD ROBOT +// Benchmark threshold are generally considered bad form unless there is +// is some human love applied to the continued maintenance and whether the +// thresholds are tuned on a per-target basis. Here we check if the values +// are more than double what is expected. Doubling will not prevent failure +// on busy or low-end systems that could have a tendency to stretch values. +// +// The primary goal of this test is to simulate a spammy app (benchmark +// being the worst) and check to make sure the logger can deal with it +// appropriately by checking all the statistics are in an expected range. +// +TEST(logd, benchmark) { + size_t len; + char *buf; + + alloc_statistics(&buf, &len); + bool benchmark_already_run = buf && find_benchmark_spam(buf); + delete [] buf; + + if (benchmark_already_run) { + fprintf(stderr, "WARNING: spam already present and too much history\n" + " false OK for prune by worst UID check\n"); + } + + FILE *fp; + + // Introduce some extreme spam for the worst UID filter + ASSERT_TRUE(NULL != (fp = popen( + "/data/nativetest/liblog-benchmarks/liblog-benchmarks", + "r"))); + + char buffer[5120]; + + static const char *benchmarks[] = { + "BM_log_maximum_retry ", + "BM_log_maximum ", + "BM_clock_overhead ", + "BM_log_overhead ", + "BM_log_latency ", + "BM_log_delay " + }; + static const unsigned int log_maximum_retry = 0; + static const unsigned int log_maximum = 1; + static const unsigned int clock_overhead = 2; + static const unsigned int log_overhead = 3; + static const unsigned int log_latency = 4; + static const unsigned int log_delay = 5; + + unsigned long ns[sizeof(benchmarks) / sizeof(benchmarks[0])]; + + memset(ns, 0, sizeof(ns)); + + while (fgets(buffer, sizeof(buffer), fp)) { + for (unsigned i = 0; i < sizeof(ns) / sizeof(ns[0]); ++i) { + char *cp = strstr(buffer, benchmarks[i]); + if (!cp) { + continue; + } + sscanf(cp, "%*s %lu %lu", &ns[i], &ns[i]); + fprintf(stderr, "%-22s%8lu\n", benchmarks[i], ns[i]); + } + } + int ret = pclose(fp); + + if (!WIFEXITED(ret) || (WEXITSTATUS(ret) == 127)) { + fprintf(stderr, + "WARNING: " + "/data/nativetest/liblog-benchmarks/liblog-benchmarks missing\n" + " can not perform test\n"); + return; + } + +#ifdef TARGET_USES_LOGD + EXPECT_GE(100000UL, ns[log_maximum_retry]); // 42777 user +#else + EXPECT_GE(10000UL, ns[log_maximum_retry]); // 5636 kernel +#endif + +#ifdef TARGET_USES_LOGD + EXPECT_GE(30000UL, ns[log_maximum]); // 27305 user +#else + EXPECT_GE(10000UL, ns[log_maximum]); // 5637 kernel +#endif + + EXPECT_GE(4096UL, ns[clock_overhead]); // 4095 + +#ifdef TARGET_USES_LOGD + EXPECT_GE(250000UL, ns[log_overhead]); // 121876 user +#else + EXPECT_GE(100000UL, ns[log_overhead]); // 50945 kernel +#endif + +#ifdef TARGET_USES_LOGD + EXPECT_GE(7500UL, ns[log_latency]); // 3718 user space +#else + EXPECT_GE(500000UL, ns[log_latency]); // 254200 kernel +#endif + +#ifdef TARGET_USES_LOGD + EXPECT_GE(20000000UL, ns[log_delay]); // 10500289 user +#else + EXPECT_GE(55000UL, ns[log_delay]); // 27341 kernel +#endif + + for (unsigned i = 0; i < sizeof(ns) / sizeof(ns[0]); ++i) { + EXPECT_NE(0UL, ns[i]); + } + + alloc_statistics(&buf, &len); + +#ifdef TARGET_USES_LOGD + bool collected_statistics = !!buf; + EXPECT_EQ(true, collected_statistics); +#else + if (!buf) { + return; + } +#endif + + ASSERT_TRUE(NULL != buf); + + char *benchmark_statistics_found = find_benchmark_spam(buf); + ASSERT_TRUE(benchmark_statistics_found != NULL); + + // Check how effective the SPAM filter is, parse out Now size. + // Total Now + // 0/4225? 7454388/303656 31488/755 + // ^-- benchmark_statistics_found + + unsigned long nowSpamSize = atol(benchmark_statistics_found); + + delete [] buf; + + ASSERT_NE(0UL, nowSpamSize); + + // Determine if we have the spam filter enabled + int sock = socket_local_client("logd", + ANDROID_SOCKET_NAMESPACE_RESERVED, + SOCK_STREAM); + + ASSERT_TRUE(sock >= 0); + + static const char getPruneList[] = "getPruneList"; + if (write(sock, getPruneList, sizeof(getPruneList)) > 0) { + char buffer[80]; + memset(buffer, 0, sizeof(buffer)); + read(sock, buffer, sizeof(buffer)); + char *cp = strchr(buffer, '\n'); + if (!cp || (cp[1] != '~') || (cp[2] != '!')) { + close(sock); + fprintf(stderr, + "WARNING: " + "Logger has SPAM filtration turned off \"%s\"\n", buffer); + return; + } + } else { + int save_errno = errno; + close(sock); + FAIL() << "Can not send " << getPruneList << " to logger -- " << strerror(save_errno); + } + + static const unsigned long expected_absolute_minimum_log_size = 65536UL; + unsigned long totalSize = expected_absolute_minimum_log_size; + static const char getSize[] = { + 'g', 'e', 't', 'L', 'o', 'g', 'S', 'i', 'z', 'e', ' ', + LOG_ID_MAIN + '0', '\0' + }; + if (write(sock, getSize, sizeof(getSize)) > 0) { + char buffer[80]; + memset(buffer, 0, sizeof(buffer)); + read(sock, buffer, sizeof(buffer)); + totalSize = atol(buffer); + if (totalSize < expected_absolute_minimum_log_size) { + fprintf(stderr, + "WARNING: " + "Logger had unexpected referenced size \"%s\"\n", buffer); + totalSize = expected_absolute_minimum_log_size; + } + } + close(sock); + + // logd allows excursions to 110% of total size + totalSize = (totalSize * 11 ) / 10; + + // 50% threshold for SPAM filter (<20% typical, lots of engineering margin) + ASSERT_GT(totalSize, nowSpamSize * 2); +} |