logd: initial checkin.

* Create a new userspace log daemon for handling logging messages.

Original-Change-Id: I75267df16359684490121e6c31cca48614d79856
Signed-off-by: Nick Kralevich <nnk@google.com>

* Merge conflicts
* rename new syslog daemon to logd to prevent confusion with bionic syslog
* replace racy getGroups call with KISS call to client->getGid()
* Timestamps are filed at logging source
* insert entries into list in timestamp order
* Added LogTimeEntry tail filtration handling
* Added region locking around LogWriter list
* separate threads for each writer
* /dev/socket/logd* permissions

Signed-off-by: Mark Salyzyn <salyzyn@google.com>

(cherry picked from commit 3e76e0a497)

Author: Nick Kralevich <nnk@google.com>
Change-Id: Ice88b1412d8f9daa7f9119b2b5aaf684a5e28098
This commit is contained in:
Mark Salyzyn 2014-02-26 09:50:16 -08:00
parent 9b986497e7
commit 0175b0747a
22 changed files with 1555 additions and 8 deletions

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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, },

28
logd/Android.mk Normal file
View file

@ -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)

137
logd/CommandListener.cpp Normal file
View file

@ -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;
}

59
logd/CommandListener.h Normal file
View file

@ -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

86
logd/FlushCommand.cpp Normal file
View file

@ -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 &times = 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);
}

41
logd/FlushCommand.h Normal file
View file

@ -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

214
logd/LogBuffer.cpp Normal file
View file

@ -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 <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() <= 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;
}

60
logd/LogBuffer.h Normal file
View file

@ -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

64
logd/LogBufferElement.cpp Normal file
View file

@ -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;
}

50
logd/LogBufferElement.h Normal file
View file

@ -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

21
logd/LogCommand.cpp Normal file
View file

@ -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) {
}

28
logd/LogCommand.h Normal file
View file

@ -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

108
logd/LogListener.cpp Normal file
View file

@ -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;
}

37
logd/LogListener.h Normal file
View file

@ -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

105
logd/LogReader.cpp Normal file
View file

@ -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 &times = 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();
}

41
logd/LogReader.h Normal file
View file

@ -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

225
logd/LogTimes.cpp Normal file
View file

@ -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 &times = 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;
}

108
logd/LogTimes.h Normal file
View file

@ -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 <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(&timesLock); }
static void unlock(void) { pthread_mutex_unlock(&timesLock); }
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

115
logd/main.cpp Normal file
View file

@ -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 <dirent.h>
#include <errno.h>
#include <fcntl.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() {
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);
}

View file

@ -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