diff --git a/include/log/log_read.h b/include/log/log_read.h index 861c19229..2601622e0 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(tv_sec) * NS_PER_SEC + tv_nsec; } diff --git a/include/log/logger.h b/include/log/logger.h index 966397a2c..6414d844b 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(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 0ed0d78e6..9c26baf19 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/logd/Android.mk b/logd/Android.mk new file mode 100644 index 000000000..f536dad18 --- /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 000000000..f5cb8dc47 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 000000000..861abbf4b --- /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 +#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 000000000..b848fd05a --- /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 +#include +#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 000000000..715daacc0 --- /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 + +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 000000000..8b273e27c --- /dev/null +++ b/logd/LogBuffer.cpp @@ -0,0 +1,214 @@ +/* + * 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 +#include +#include +#include + +#include + +#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() <= elem->getRealTime()) { + 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 +// 10% of the log entries. +// +// mLogElementsLock must be held when this function is called. +void LogBuffer::maybePrune(log_id_t id) { + if (mSizes[id] > LOG_BUFFER_SIZE) { + prune(id, mElements[id] / 10); + } +} + +// 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 000000000..7c69f1bdc --- /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 + +#include +#include +#include + +#include "LogBufferElement.h" +#include "LogTimes.h" + +typedef android::List 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 000000000..1c55623c3 --- /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 +#include +#include +#include + +#include + +#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 000000000..390c97c23 --- /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 +#include +#include +#include + +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 000000000..ec8365aa1 --- /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 000000000..aef6706bd --- /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 + +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 000000000..c6b248bdc --- /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 +#include +#include +#include + +#include +#include + +#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 000000000..7099e1325 --- /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 +#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 000000000..5b540bf5d --- /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 +#include +#include + +#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 000000000..b267c75d2 --- /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 +#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 000000000..d6d4e9331 --- /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(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(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(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(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 000000000..ac52db23d --- /dev/null +++ b/logd/LogTimes.h @@ -0,0 +1,108 @@ +/* + * 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 +#include +#include +#include +#include + +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 LastLogTimes; + +#endif diff --git a/logd/main.cpp b/logd/main.cpp new file mode 100644 index 000000000..667e5bbfc --- /dev/null +++ b/logd/main.cpp @@ -0,0 +1,115 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "private/android_filesystem_config.h" +#include "CommandListener.h" +#include "LogBuffer.h" +#include "LogListener.h" + +static int drop_privs() { + 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 46fb8bd75..3c52ba9ef 100644 --- a/rootdir/init.rc +++ b/rootdir/init.rc @@ -456,6 +456,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