diff options
| -rw-r--r-- | include/log/log_read.h | 6 | ||||
| -rw-r--r-- | include/log/logger.h | 22 | ||||
| -rw-r--r-- | include/private/android_filesystem_config.h | 2 | ||||
| -rw-r--r-- | libcutils/socket_local_server.c | 4 | ||||
| -rw-r--r-- | libutils/String8.cpp | 13 | ||||
| -rw-r--r-- | logcat/tests/logcat_test.cpp | 104 | ||||
| -rw-r--r-- | logd/Android.mk | 28 | ||||
| -rw-r--r-- | logd/CommandListener.cpp | 137 | ||||
| -rw-r--r-- | logd/CommandListener.h | 59 | ||||
| -rw-r--r-- | logd/FlushCommand.cpp | 86 | ||||
| -rw-r--r-- | logd/FlushCommand.h | 41 | ||||
| -rw-r--r-- | logd/LogBuffer.cpp | 223 | ||||
| -rw-r--r-- | logd/LogBuffer.h | 60 | ||||
| -rw-r--r-- | logd/LogBufferElement.cpp | 64 | ||||
| -rw-r--r-- | logd/LogBufferElement.h | 50 | ||||
| -rw-r--r-- | logd/LogCommand.cpp | 21 | ||||
| -rw-r--r-- | logd/LogCommand.h | 28 | ||||
| -rw-r--r-- | logd/LogListener.cpp | 108 | ||||
| -rw-r--r-- | logd/LogListener.h | 37 | ||||
| -rw-r--r-- | logd/LogReader.cpp | 105 | ||||
| -rw-r--r-- | logd/LogReader.h | 41 | ||||
| -rw-r--r-- | logd/LogTimes.cpp | 225 | ||||
| -rw-r--r-- | logd/LogTimes.h | 105 | ||||
| -rw-r--r-- | logd/main.cpp | 123 | ||||
| -rw-r--r-- | rootdir/init.rc | 17 |
25 files changed, 1681 insertions, 28 deletions
diff --git a/include/log/log_read.h b/include/log/log_read.h index 861c192..2601622 100644 --- a/include/log/log_read.h +++ b/include/log/log_read.h @@ -23,12 +23,12 @@ #ifdef __cplusplus struct log_time : public timespec { public: - log_time(timespec &T) + log_time(const timespec &T) { tv_sec = T.tv_sec; tv_nsec = T.tv_nsec; } - log_time(void) + log_time() { } log_time(clockid_t id) @@ -67,7 +67,7 @@ public: { return !(*this > T); } - uint64_t nsec(void) const + uint64_t nsec() const { return static_cast<uint64_t>(tv_sec) * NS_PER_SEC + tv_nsec; } diff --git a/include/log/logger.h b/include/log/logger.h index 966397a..6414d84 100644 --- a/include/log/logger.h +++ b/include/log/logger.h @@ -35,7 +35,7 @@ struct logger_entry { /* * The userspace structure for version 2 of the logger_entry ABI. * This structure is returned to userspace if ioctl(LOGGER_SET_VERSION) - * is called with version==2 + * is called with version==2; or used with the user space log daemon. */ struct logger_entry_v2 { uint16_t len; /* length of the payload */ @@ -48,6 +48,17 @@ struct logger_entry_v2 { char msg[0]; /* the entry's payload */ }; +struct logger_entry_v3 { + uint16_t len; /* length of the payload */ + uint16_t hdr_size; /* sizeof(struct logger_entry_v2) */ + int32_t pid; /* generating process's pid */ + int32_t tid; /* generating process's tid */ + int32_t sec; /* seconds since Epoch */ + int32_t nsec; /* nanoseconds */ + uint32_t lid; /* log id of the payload */ + char msg[0]; /* the entry's payload */ +}; + /* * The maximum size of the log entry payload that can be * written to the kernel logger driver. An attempt to write @@ -69,6 +80,7 @@ struct log_msg { union { unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1]; struct logger_entry_v2 entry; + struct logger_entry_v3 entry_v3; struct logger_entry_v2 entry_v2; struct logger_entry entry_v1; struct { @@ -106,21 +118,21 @@ struct log_msg { { return !(*this > T); } - uint64_t nsec(void) const + uint64_t nsec() const { return static_cast<uint64_t>(entry.sec) * NS_PER_SEC + entry.nsec; } /* packet methods */ - log_id_t id(void) + log_id_t id() { return extra.id; } - char *msg(void) + char *msg() { return entry.hdr_size ? (char *) buf + entry.hdr_size : entry_v1.msg; } - unsigned int len(void) + unsigned int len() { return (entry.hdr_size ? entry.hdr_size : sizeof(entry_v1)) + entry.len; } diff --git a/include/private/android_filesystem_config.h b/include/private/android_filesystem_config.h index 0ed0d78..9c26baf 100644 --- a/include/private/android_filesystem_config.h +++ b/include/private/android_filesystem_config.h @@ -76,6 +76,7 @@ #define AID_SDCARD_PICS 1033 /* external storage photos access */ #define AID_SDCARD_AV 1034 /* external storage audio/video access */ #define AID_SDCARD_ALL 1035 /* access all users external storage */ +#define AID_LOGD 1036 /* log daemon */ #define AID_SHELL 2000 /* adb and debug shell user */ #define AID_CACHE 2001 /* cache access */ @@ -151,6 +152,7 @@ static const struct android_id_info android_ids[] = { { "sdcard_pics", AID_SDCARD_PICS, }, { "sdcard_av", AID_SDCARD_AV, }, { "sdcard_all", AID_SDCARD_ALL, }, + { "logd", AID_LOGD, }, { "shell", AID_SHELL, }, { "cache", AID_CACHE, }, diff --git a/libcutils/socket_local_server.c b/libcutils/socket_local_server.c index 4971b1b..7628fe4 100644 --- a/libcutils/socket_local_server.c +++ b/libcutils/socket_local_server.c @@ -43,6 +43,8 @@ int socket_local_server(const char *name, int namespaceId, int type) #define LISTEN_BACKLOG 4 +/* Only the bottom bits are really the socket type; there are flags too. */ +#define SOCK_TYPE_MASK 0xf /** * Binds a pre-created socket(AF_LOCAL) 's' to 'name' @@ -107,7 +109,7 @@ int socket_local_server(const char *name, int namespace, int type) return -1; } - if (type == SOCK_STREAM) { + if ((type & SOCK_TYPE_MASK) == SOCK_STREAM) { int ret; ret = listen(s, LISTEN_BACKLOG); diff --git a/libutils/String8.cpp b/libutils/String8.cpp index e852d77..8acb4d4 100644 --- a/libutils/String8.cpp +++ b/libutils/String8.cpp @@ -323,8 +323,17 @@ status_t String8::appendFormat(const char* fmt, ...) status_t String8::appendFormatV(const char* fmt, va_list args) { - int result = NO_ERROR; - int n = vsnprintf(NULL, 0, fmt, args); + int n, result = NO_ERROR; + va_list tmp_args; + + /* args is undefined after vsnprintf. + * So we need a copy here to avoid the + * second vsnprintf access undefined args. + */ + va_copy(tmp_args, args); + n = vsnprintf(NULL, 0, fmt, tmp_args); + va_end(tmp_args); + if (n != 0) { size_t oldLength = length(); char* buf = lockBuffer(oldLength + n); diff --git a/logcat/tests/logcat_test.cpp b/logcat/tests/logcat_test.cpp index f963a3a..fc696bb 100644 --- a/logcat/tests/logcat_test.cpp +++ b/logcat/tests/logcat_test.cpp @@ -319,12 +319,16 @@ static void caught_blocking(int signum) TEST(logcat, blocking) { FILE *fp; - unsigned long long v = 0xDEADBEEFA55A0000ULL; + unsigned long long v = 0xDEADBEEFA55F0000ULL; pid_t pid = getpid(); v += pid & 0xFFFF; + LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v))); + + v &= 0xFFFFFFFFFFFAFFFFULL; + ASSERT_EQ(0, NULL == (fp = popen( "( trap exit HUP QUIT INT PIPE KILL ; sleep 6; echo DONE )&" " logcat -b events 2>&1", @@ -341,12 +345,12 @@ TEST(logcat, blocking) { while (fgets(buffer, sizeof(buffer), fp)) { alarm(2); - ++count; - if (!strncmp(buffer, "DONE", 4)) { break; } + ++count; + int p; unsigned long long l; @@ -369,7 +373,7 @@ TEST(logcat, blocking) { pclose(fp); - ASSERT_LT(10, count); + ASSERT_LE(2, count); ASSERT_EQ(1, signals); } @@ -385,12 +389,16 @@ static void caught_blocking_tail(int signum) TEST(logcat, blocking_tail) { FILE *fp; - unsigned long long v = 0xA55ADEADBEEF0000ULL; + unsigned long long v = 0xA55FDEADBEEF0000ULL; pid_t pid = getpid(); v += pid & 0xFFFF; + LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v))); + + v &= 0xFFFAFFFFFFFFFFFFULL; + ASSERT_EQ(0, NULL == (fp = popen( "( trap exit HUP QUIT INT PIPE KILL ; sleep 6; echo DONE )&" " logcat -b events -T 5 2>&1", @@ -407,12 +415,12 @@ TEST(logcat, blocking_tail) { while (fgets(buffer, sizeof(buffer), fp)) { alarm(2); - ++count; - if (!strncmp(buffer, "DONE", 4)) { break; } + ++count; + int p; unsigned long long l; @@ -431,13 +439,91 @@ TEST(logcat, blocking_tail) { alarm(0); signal(SIGALRM, SIG_DFL); - /* Generate SIGPIPE */ + // Generate SIGPIPE fclose(fp); caught_blocking_tail(0); pclose(fp); - ASSERT_LT(5, count); + ASSERT_LE(2, count); + + ASSERT_EQ(1, signals); +} + +static void caught_blocking_clear(int signum) +{ + unsigned long long v = 0xDEADBEEFA55C0000ULL; + + v += getpid() & 0xFFFF; + + LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v))); +} + +TEST(logcat, blocking_clear) { + FILE *fp; + unsigned long long v = 0xDEADBEEFA55C0000ULL; + + pid_t pid = getpid(); + + v += pid & 0xFFFF; + + // This test is racey; an event occurs between clear and dump. + // We accept that we will get a false positive, but never a false negative. + ASSERT_EQ(0, NULL == (fp = popen( + "( trap exit HUP QUIT INT PIPE KILL ; sleep 6; echo DONE )&" + " logcat -b events -c 2>&1 ;" + " logcat -b events 2>&1", + "r"))); + + char buffer[5120]; + + int count = 0; + + int signals = 0; + + signal(SIGALRM, caught_blocking_clear); + alarm(2); + while (fgets(buffer, sizeof(buffer), fp)) { + alarm(2); + + if (!strncmp(buffer, "clearLog: ", 10)) { + fprintf(stderr, "WARNING: Test lacks permission to run :-(\n"); + count = signals = 1; + break; + } + + if (!strncmp(buffer, "DONE", 4)) { + break; + } + + ++count; + + int p; + unsigned long long l; + + if ((2 != sscanf(buffer, "I/[0] ( %u): %lld", &p, &l)) + || (p != pid)) { + continue; + } + + if (l == v) { + if (count > 1) { + fprintf(stderr, "WARNING: Possible false positive\n"); + } + ++signals; + break; + } + } + alarm(0); + signal(SIGALRM, SIG_DFL); + + // Generate SIGPIPE + fclose(fp); + caught_blocking_clear(0); + + pclose(fp); + + ASSERT_LE(1, count); ASSERT_EQ(1, signals); } diff --git a/logd/Android.mk b/logd/Android.mk new file mode 100644 index 0000000..f536dad --- /dev/null +++ b/logd/Android.mk @@ -0,0 +1,28 @@ +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 + +LOCAL_C_INCLUDES := $(KERNEL_HEADERS) + +LOCAL_SHARED_LIBRARIES := \ + libsysutils \ + liblog \ + libcutils + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_EXECUTABLE) + diff --git a/logd/CommandListener.cpp b/logd/CommandListener.cpp new file mode 100644 index 0000000..f5cb8dc --- /dev/null +++ b/logd/CommandListener.cpp @@ -0,0 +1,137 @@ +/* + * 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 <arpa/inet.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <string.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/types.h> + +#include <sysutils/SocketClient.h> +#include <private/android_filesystem_config.h> + +#include "CommandListener.h" + +CommandListener::CommandListener(LogBuffer *buf, LogReader * /*reader*/, + LogListener * /*swl*/) + : FrameworkListener("logd") + , mBuf(*buf) { + // registerCmd(new ShutdownCmd(buf, writer, swl)); + registerCmd(new ClearCmd(buf)); + registerCmd(new GetBufSizeCmd(buf)); + registerCmd(new GetBufSizeUsedCmd(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) +{ } + +int CommandListener::ClearCmd::runCommand(SocketClient *cli, + int argc, char **argv) { + if ((cli->getUid() != AID_ROOT) + && (cli->getGid() != AID_ROOT) + && (cli->getGid() != AID_LOG)) { + cli->sendMsg("Permission Denied"); + return 0; + } + + if (argc < 2) { + cli->sendMsg("Missing Argument"); + return 0; + } + + int id = atoi(argv[1]); + if ((id < LOG_ID_MIN) || (id >= LOG_ID_MAX)) { + cli->sendMsg("Range Error"); + return 0; + } + + mBuf.clear((log_id_t) id); + cli->sendMsg("success"); + return 0; +} + + +CommandListener::GetBufSizeCmd::GetBufSizeCmd(LogBuffer *buf) + : LogCommand("getLogSize") + , mBuf(*buf) +{ } + +int CommandListener::GetBufSizeCmd::runCommand(SocketClient *cli, + int argc, char **argv) { + if (argc < 2) { + cli->sendMsg("Missing Argument"); + return 0; + } + + int id = atoi(argv[1]); + if ((id < LOG_ID_MIN) || (id >= LOG_ID_MAX)) { + 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::GetBufSizeUsedCmd::GetBufSizeUsedCmd(LogBuffer *buf) + : LogCommand("getLogSizeUsed") + , mBuf(*buf) +{ } + +int CommandListener::GetBufSizeUsedCmd::runCommand(SocketClient *cli, + int argc, char **argv) { + if (argc < 2) { + cli->sendMsg("Missing Argument"); + return 0; + } + + int id = atoi(argv[1]); + if ((id < LOG_ID_MIN) || (id >= LOG_ID_MAX)) { + 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; +} diff --git a/logd/CommandListener.h b/logd/CommandListener.h new file mode 100644 index 0000000..861abbf --- /dev/null +++ b/logd/CommandListener.h @@ -0,0 +1,59 @@ +/* + * 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: + 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(GetBufSizeUsed) +}; + +#endif diff --git a/logd/FlushCommand.cpp b/logd/FlushCommand.cpp new file mode 100644 index 0000000..f6f8cb8 --- /dev/null +++ b/logd/FlushCommand.cpp @@ -0,0 +1,86 @@ +/* + * 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 <stdlib.h> +#include <private/android_filesystem_config.h> +#include "FlushCommand.h" +#include "LogBufferElement.h" +#include "LogTimes.h" +#include "LogReader.h" + +FlushCommand::FlushCommand(LogReader &reader, + bool nonBlock, + unsigned long tail, + unsigned int logMask, + pid_t pid) + : mReader(reader) + , mNonBlock(nonBlock) + , mTail(tail) + , mLogMask(logMask) + , mPid(pid) +{ } + +// 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); + 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 (client->getUid() == AID_ROOT) + || (client->getGid() == AID_ROOT) + || (client->getGid() == AID_LOG); +} diff --git a/logd/FlushCommand.h b/logd/FlushCommand.h new file mode 100644 index 0000000..715daac --- /dev/null +++ b/logd/FlushCommand.h @@ -0,0 +1,41 @@ +/* + * 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 <sysutils/SocketClientCommand.h> + +class LogReader; + +class FlushCommand : public SocketClientCommand { + LogReader &mReader; + bool mNonBlock; + unsigned long mTail; + unsigned int mLogMask; + pid_t mPid; + +public: + FlushCommand(LogReader &mReader, + bool nonBlock = false, + unsigned long tail = -1, + unsigned int logMask = -1, + pid_t pid = 0); + virtual void runSocketCommand(SocketClient *client); + + static bool hasReadLogs(SocketClient *client); +}; + +#endif diff --git a/logd/LogBuffer.cpp b/logd/LogBuffer.cpp new file mode 100644 index 0000000..7340a36 --- /dev/null +++ b/logd/LogBuffer.cpp @@ -0,0 +1,223 @@ +/* + * 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 "LogBuffer.h" +#include "LogReader.h" + +#define LOG_BUFFER_SIZE (256 * 1024) // Tuned on a per-platform basis here? + +LogBuffer::LogBuffer(LastLogTimes *times) + : mTimes(*times) { + int i; + for (i = 0; i < LOG_ID_MAX; i++) { + mSizes[i] = 0; + mElements[i] = 0; + } + pthread_mutex_init(&mLogElementsLock, NULL); +} + +void LogBuffer::log(log_id_t log_id, struct timespec realtime, + uid_t uid, pid_t pid, 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, 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) { + break; + } + last = it; + } + + if (last == mLogElements.end()) { + mLogElements.push_back(elem); + } else { + log_time end; + 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(); + } + + mSizes[log_id] += len; + mElements[log_id]++; + 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) { + unsigned long sizes = mSizes[id]; + if (sizes > LOG_BUFFER_SIZE) { + unsigned long sizeOver90Percent = sizes - ((LOG_BUFFER_SIZE * 9) / 10); + unsigned long elements = mElements[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) { + 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 = mLogElements.begin(); + while((pruneRows > 0) && (it != mLogElements.end())) { + LogBufferElement *e = *it; + if (e->getLogId() == id) { + if (oldest && (oldest->mStart <= e->getMonotonicTime())) { + if (mSizes[id] > (2 * LOG_BUFFER_SIZE)) { + // kick a misbehaving log reader client off the island + oldest->release_Locked(); + } else { + oldest->triggerSkip_Locked(pruneRows); + } + break; + } + it = mLogElements.erase(it); + mSizes[id] -= e->getMsgLen(); + mElements[id]--; + delete e; + pruneRows--; + } else { + it++; + } + } + + LogTimeEntry::unlock(); +} + +// clear all rows of type "id" from the buffer. +void LogBuffer::clear(log_id_t id) { + pthread_mutex_lock(&mLogElementsLock); + prune(id, ULONG_MAX); + pthread_mutex_unlock(&mLogElementsLock); +} + +// get the used space associated with "id". +unsigned long LogBuffer::getSizeUsed(log_id_t id) { + pthread_mutex_lock(&mLogElementsLock); + unsigned long retval = mSizes[id]; + pthread_mutex_unlock(&mLogElementsLock); + return retval; +} + +// get the total space allocated to "id" +unsigned long LogBuffer::getSize(log_id_t /*id*/) { + return LOG_BUFFER_SIZE; +} + +struct timespec LogBuffer::flushTo( + SocketClient *reader, const struct timespec 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; +} diff --git a/logd/LogBuffer.h b/logd/LogBuffer.h new file mode 100644 index 0000000..7c69f1b --- /dev/null +++ b/logd/LogBuffer.h @@ -0,0 +1,60 @@ +/* + * 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 "LogBufferElement.h" +#include "LogTimes.h" + +typedef android::List<LogBufferElement *> LogBufferElementCollection; + +class LogBuffer { + LogBufferElementCollection mLogElements; + pthread_mutex_t mLogElementsLock; + + unsigned long mSizes[LOG_ID_MAX]; + unsigned long mElements[LOG_ID_MAX]; + +public: + LastLogTimes &mTimes; + + LogBuffer(LastLogTimes *times); + + void log(log_id_t log_id, struct timespec realtime, + uid_t uid, pid_t pid, const char *msg, unsigned short len); + struct timespec flushTo(SocketClient *writer, const struct timespec start, + bool privileged, + bool (*filter)(const LogBufferElement *element, void *arg) = NULL, + void *arg = NULL); + + void clear(log_id_t id); + unsigned long getSize(log_id_t id); + unsigned long getSizeUsed(log_id_t id); + +private: + void maybePrune(log_id_t id); + void prune(log_id_t id, unsigned long pruneRows); + +}; + +#endif diff --git a/logd/LogBufferElement.cpp b/logd/LogBufferElement.cpp new file mode 100644 index 0000000..1c55623 --- /dev/null +++ b/logd/LogBufferElement.cpp @@ -0,0 +1,64 @@ +/* + * 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 struct timespec LogBufferElement::FLUSH_ERROR = { 0, 0 }; + +LogBufferElement::LogBufferElement(log_id_t log_id, struct timespec realtime, uid_t uid, pid_t pid, const char *msg, unsigned short len) + : mLogId(log_id) + , mUid(uid) + , mPid(pid) + , mMsgLen(len) + , mMonotonicTime(CLOCK_MONOTONIC) + , mRealTime(realtime) { + mMsg = new char[len]; + memcpy(mMsg, msg, len); +} + +LogBufferElement::~LogBufferElement() { + delete [] mMsg; +} + +struct timespec 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.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..390c97c --- /dev/null +++ b/logd/LogBufferElement.h @@ -0,0 +1,50 @@ +/* + * 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; + char *mMsg; + const unsigned short mMsgLen; + const log_time mMonotonicTime; + const log_time mRealTime; + +public: + LogBufferElement(log_id_t log_id, struct timespec realtime, + uid_t uid, pid_t pid, 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; } + unsigned short getMsgLen() const { return mMsgLen; } + log_time getMonotonicTime(void) const { return mMonotonicTime; } + log_time getRealTime(void) const { return mRealTime; } + + static const struct timespec FLUSH_ERROR; + struct timespec flushTo(SocketClient *writer); +}; + +#endif diff --git a/logd/LogCommand.cpp b/logd/LogCommand.cpp new file mode 100644 index 0000000..6ccc93e --- /dev/null +++ b/logd/LogCommand.cpp @@ -0,0 +1,21 @@ +/* + * 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 "LogCommand.h" + +LogCommand::LogCommand(const char *cmd) + : FrameworkCommand(cmd) { +} diff --git a/logd/LogCommand.h b/logd/LogCommand.h new file mode 100644 index 0000000..aef6706 --- /dev/null +++ b/logd/LogCommand.h @@ -0,0 +1,28 @@ +/* + * 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_COMMAND_H +#define _LOGD_COMMAND_H + +#include <sysutils/FrameworkCommand.h> + +class LogCommand : public FrameworkCommand { +public: + LogCommand(const char *cmd); + virtual ~LogCommand() {} +}; + +#endif diff --git a/logd/LogListener.cpp b/logd/LogListener.cpp new file mode 100644 index 0000000..c6b248b --- /dev/null +++ b/logd/LogListener.cpp @@ -0,0 +1,108 @@ +/* + * 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 <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) { + char buffer[1024]; + 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) { + 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; + + log_time realtime(msg); + msg += sizeof(log_time); + n -= sizeof(log_time); + + unsigned short len = n; + if (len == n) { + logbuf->log(log_id, realtime, cred->uid, cred->pid, msg, len); + reader->notifyNewLog(); + } + + return true; +} + +int LogListener::getLogSocket() { + int sock = android_get_control_socket("logdw"); + 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..5b540bf --- /dev/null +++ b/logd/LogReader.cpp @@ -0,0 +1,105 @@ +/* + * 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 <poll.h> +#include <sys/socket.h> +#include <cutils/sockets.h> + +#include "LogReader.h" +#include "FlushCommand.h" + +LogReader::LogReader(LogBuffer *logbuf) + : SocketListener("logdr", 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) { + 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); + } + + 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 (('0' <= *cp) && (*cp <= '9')) { + val *= 10; + val += *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; + } + + FlushCommand command(*this, nonBlock, tail, logMask, pid); + 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(); +} diff --git a/logd/LogReader.h b/logd/LogReader.h new file mode 100644 index 0000000..b267c75 --- /dev/null +++ b/logd/LogReader.h @@ -0,0 +1,41 @@ +/* + * 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: + void doSocketDelete(SocketClient *cli); + +}; + +#endif diff --git a/logd/LogTimes.cpp b/logd/LogTimes.cpp new file mode 100644 index 0000000..67cc65e --- /dev/null +++ b/logd/LogTimes.cpp @@ -0,0 +1,225 @@ +/* + * 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 "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) + : 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(EPOCH) + , mNonBlock(nonBlock) + , mEnd(CLOCK_MONOTONIC) +{ } + +void LogTimeEntry::startReader_Locked(void) { + threadRunning = true; + if (pthread_create(&mThread, NULL, LogTimeEntry::threadStart, this)) { + 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) { + 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--; + } + + 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..cb6f566 --- /dev/null +++ b/logd/LogTimes.h @@ -0,0 +1,105 @@ +/* + * 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); + + 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/main.cpp b/logd/main.cpp new file mode 100644 index 0000000..1891206 --- /dev/null +++ b/logd/main.cpp @@ -0,0 +1,123 @@ +/* + * 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 <linux/prctl.h> + +#include "private/android_filesystem_config.h" +#include "CommandListener.h" +#include "LogBuffer.h" +#include "LogListener.h" + +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_SYSLOG)].effective = CAP_TO_MASK(CAP_SYSLOG); + capdata[0].inheritable = 0; + capdata[1].inheritable = 0; + + if (capset(&capheader, &capdata[0]) < 0) { + return -1; + } + + return 0; +} + +// 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() { + 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); + + // 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); + if (swl->startListener()) { + 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); + } + + pause(); + exit(0); +} + diff --git a/rootdir/init.rc b/rootdir/init.rc index 46fb8bd..9975368 100644 --- a/rootdir/init.rc +++ b/rootdir/init.rc @@ -214,23 +214,16 @@ on post-fs-data mkdir /data/misc/radio 0770 system radio mkdir /data/misc/sms 0770 system radio mkdir /data/misc/zoneinfo 0775 system system - restorecon_recursive /data/misc/zoneinfo mkdir /data/misc/vpn 0770 system vpn mkdir /data/misc/systemkeys 0700 system system mkdir /data/misc/wifi 0770 wifi wifi mkdir /data/misc/wifi/sockets 0770 wifi wifi - restorecon_recursive /data/misc/wifi/sockets mkdir /data/misc/wifi/wpa_supplicant 0770 wifi wifi mkdir /data/misc/dhcp 0770 dhcp dhcp # give system access to wpa_supplicant.conf for backup and restore chmod 0660 /data/misc/wifi/wpa_supplicant.conf mkdir /data/local 0751 root root mkdir /data/misc/media 0700 media media - restorecon_recursive /data/misc/media - - # Set security context of any pre-existing /data/misc/adb/adb_keys file. - restorecon /data/misc/adb - restorecon /data/misc/adb/adb_keys # For security reasons, /data/local/tmp should always be empty. # Do not place files or directories in /data/local/tmp @@ -262,7 +255,6 @@ on post-fs-data # create directory for MediaDrm plug-ins - give drm the read/write access to # the following directory. mkdir /data/mediadrm 0770 mediadrm mediadrm - restorecon_recursive /data/mediadrm # symlink to bugreport storage location symlink /data/data/com.android.shell/files/bugreports /data/bugreports @@ -273,6 +265,9 @@ on post-fs-data # Reload policy from /data/security if present. setprop selinux.reload_policy 1 + # Set SELinux security contexts on upgrade or policy update. + restorecon_recursive /data + # If there is no fs-post-data action in the init.<device>.rc file, you # must uncomment this line, otherwise encrypted filesystems # won't work. @@ -456,6 +451,12 @@ service adbd /sbin/adbd --root_seclabel=u:r:su:s0 on property:ro.kernel.qemu=1 start adbd +service logd /system/bin/logd + class main + socket logd stream 0666 logd logd + socket logdr seqpacket 0666 logd logd + socket logdw dgram 0222 logd logd + service servicemanager /system/bin/servicemanager class core user system |
