summaryrefslogtreecommitdiffstats
path: root/liblog
diff options
context:
space:
mode:
Diffstat (limited to 'liblog')
-rw-r--r--liblog/Android.mk72
-rw-r--r--liblog/event_tag_map.c438
-rw-r--r--liblog/fake_log_device.c677
-rw-r--r--liblog/logd_write.c229
-rw-r--r--liblog/logprint.c972
5 files changed, 2388 insertions, 0 deletions
diff --git a/liblog/Android.mk b/liblog/Android.mk
new file mode 100644
index 0000000..0eec87f
--- /dev/null
+++ b/liblog/Android.mk
@@ -0,0 +1,72 @@
+#
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH := $(my-dir)
+include $(CLEAR_VARS)
+
+liblog_sources := logd_write.c
+
+# some files must not be compiled when building against Mingw
+# they correspond to features not used by our host development tools
+# which are also hard or even impossible to port to native Win32
+WITH_MINGW :=
+ifeq ($(HOST_OS),windows)
+ ifeq ($(strip $(USE_CYGWIN)),)
+ WITH_MINGW := true
+ endif
+endif
+# USE_MINGW is defined when we build against Mingw on Linux
+ifneq ($(strip $(USE_MINGW)),)
+ WITH_MINGW := true
+endif
+
+ifndef WITH_MINGW
+ liblog_sources += \
+ logprint.c \
+ event_tag_map.c
+endif
+
+liblog_host_sources := $(liblog_sources) fake_log_device.c
+
+# Static library for host
+# ========================================================
+LOCAL_MODULE := liblog
+LOCAL_SRC_FILES := $(liblog_host_sources)
+LOCAL_LDLIBS := -lpthread
+LOCAL_CFLAGS := -DFAKE_LOG_DEVICE=1
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+ifeq ($(TARGET_SIMULATOR),true)
+ # Shared library for simulator
+ # ========================================================
+ include $(CLEAR_VARS)
+ LOCAL_MODULE := liblog
+ LOCAL_SRC_FILES := $(liblog_host_sources)
+ LOCAL_LDLIBS := -lpthread
+ LOCAL_CFLAGS := -DFAKE_LOG_DEVICE=1
+ include $(BUILD_SHARED_LIBRARY)
+else # !sim
+ # Shared and static library for target
+ # ========================================================
+ include $(CLEAR_VARS)
+ LOCAL_MODULE := liblog
+ LOCAL_SRC_FILES := $(liblog_sources)
+ include $(BUILD_STATIC_LIBRARY)
+
+ include $(CLEAR_VARS)
+ LOCAL_MODULE := liblog
+ LOCAL_WHOLE_STATIC_LIBRARIES := liblog
+ include $(BUILD_SHARED_LIBRARY)
+endif # !sim
diff --git a/liblog/event_tag_map.c b/liblog/event_tag_map.c
new file mode 100644
index 0000000..e70754e
--- /dev/null
+++ b/liblog/event_tag_map.c
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2007 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 "cutils/event_tag_map.h"
+#include "cutils/log.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <errno.h>
+#include <assert.h>
+
+#define OUT_TAG "EventTagMap"
+
+/*
+ * Single entry.
+ */
+typedef struct EventTag {
+ unsigned int tagIndex;
+ const char* tagStr;
+} EventTag;
+
+/*
+ * Map.
+ */
+struct EventTagMap {
+ /* memory-mapped source file; we get strings from here */
+ void* mapAddr;
+ size_t mapLen;
+
+ /* array of event tags, sorted numerically by tag index */
+ EventTag* tagArray;
+ int numTags;
+};
+
+/* fwd */
+static int processFile(EventTagMap* map);
+static int countMapLines(const EventTagMap* map);
+static int parseMapLines(EventTagMap* map);
+static int scanTagLine(char** pData, EventTag* tag, int lineNum);
+static int sortTags(EventTagMap* map);
+static void dumpTags(const EventTagMap* map);
+
+
+/*
+ * Open the map file and allocate a structure to manage it.
+ *
+ * We create a private mapping because we want to terminate the log tag
+ * strings with '\0'.
+ */
+EventTagMap* android_openEventTagMap(const char* fileName)
+{
+ EventTagMap* newTagMap;
+ off_t end;
+ int fd = -1;
+
+ newTagMap = calloc(1, sizeof(EventTagMap));
+ if (newTagMap == NULL)
+ return NULL;
+
+ fd = open(fileName, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "%s: unable to open map '%s': %s\n",
+ OUT_TAG, fileName, strerror(errno));
+ goto fail;
+ }
+
+ end = lseek(fd, 0L, SEEK_END);
+ (void) lseek(fd, 0L, SEEK_SET);
+ if (end < 0) {
+ fprintf(stderr, "%s: unable to seek map '%s'\n", OUT_TAG, fileName);
+ goto fail;
+ }
+
+ newTagMap->mapAddr = mmap(NULL, end, PROT_READ | PROT_WRITE, MAP_PRIVATE,
+ fd, 0);
+ if (newTagMap->mapAddr == MAP_FAILED) {
+ fprintf(stderr, "%s: mmap(%s) failed: %s\n",
+ OUT_TAG, fileName, strerror(errno));
+ goto fail;
+ }
+ newTagMap->mapLen = end;
+
+ if (processFile(newTagMap) != 0)
+ goto fail;
+
+ return newTagMap;
+
+fail:
+ android_closeEventTagMap(newTagMap);
+ if (fd >= 0)
+ close(fd);
+ return NULL;
+}
+
+/*
+ * Close the map.
+ */
+void android_closeEventTagMap(EventTagMap* map)
+{
+ if (map == NULL)
+ return;
+
+ munmap(map->mapAddr, map->mapLen);
+ free(map);
+}
+
+/*
+ * Look up an entry in the map.
+ *
+ * The entries are sorted by tag number, so we can do a binary search.
+ */
+const char* android_lookupEventTag(const EventTagMap* map, int tag)
+{
+ int hi, lo, mid;
+
+ lo = 0;
+ hi = map->numTags-1;
+
+ while (lo <= hi) {
+ int cmp;
+
+ mid = (lo+hi)/2;
+ cmp = map->tagArray[mid].tagIndex - tag;
+ if (cmp < 0) {
+ /* tag is bigger */
+ lo = mid + 1;
+ } else if (cmp > 0) {
+ /* tag is smaller */
+ hi = mid - 1;
+ } else {
+ /* found */
+ return map->tagArray[mid].tagStr;
+ }
+ }
+
+ return NULL;
+}
+
+
+
+/*
+ * Determine whether "c" is a whitespace char.
+ */
+static inline int isCharWhitespace(char c)
+{
+ return (c == ' ' || c == '\n' || c == '\r' || c == '\t');
+}
+
+/*
+ * Determine whether "c" is a valid tag char.
+ */
+static inline int isCharValidTag(char c)
+{
+ return ((c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9') ||
+ (c == '_'));
+}
+
+/*
+ * Determine whether "c" is a valid decimal digit.
+ */
+static inline int isCharDigit(char c)
+{
+ return (c >= '0' && c <= '9');
+}
+
+
+/*
+ * Crunch through the file, parsing the contents and creating a tag index.
+ */
+static int processFile(EventTagMap* map)
+{
+ EventTag* tagArray = NULL;
+
+ /* get a tag count */
+ map->numTags = countMapLines(map);
+ if (map->numTags < 0)
+ return -1;
+
+ //printf("+++ found %d tags\n", map->numTags);
+
+ /* allocate storage for the tag index array */
+ map->tagArray = calloc(1, sizeof(EventTag) * map->numTags);
+ if (map->tagArray == NULL)
+ return -1;
+
+ /* parse the file, null-terminating tag strings */
+ if (parseMapLines(map) != 0) {
+ fprintf(stderr, "%s: file parse failed\n", OUT_TAG);
+ return -1;
+ }
+
+ /* sort the tags and check for duplicates */
+ if (sortTags(map) != 0)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * Run through all lines in the file, determining whether they're blank,
+ * comments, or possibly have a tag entry.
+ *
+ * This is a very "loose" scan. We don't try to detect syntax errors here.
+ * The later pass is more careful, but the number of tags found there must
+ * match the number of tags found here.
+ *
+ * Returns the number of potential tag entries found.
+ */
+static int countMapLines(const EventTagMap* map)
+{
+ int numTags, unknown;
+ const char* cp;
+ const char* endp;
+
+ cp = (const char*) map->mapAddr;
+ endp = cp + map->mapLen;
+
+ numTags = 0;
+ unknown = 1;
+ while (cp < endp) {
+ if (*cp == '\n') {
+ unknown = 1;
+ } else if (unknown) {
+ if (isCharDigit(*cp)) {
+ /* looks like a tag to me */
+ numTags++;
+ unknown = 0;
+ } else if (isCharWhitespace(*cp)) {
+ /* might be leading whitespace before tag num, keep going */
+ } else {
+ /* assume comment; second pass can complain in detail */
+ unknown = 0;
+ }
+ } else {
+ /* we've made up our mind; just scan to end of line */
+ }
+ cp++;
+ }
+
+ return numTags;
+}
+
+/*
+ * Parse the tags out of the file.
+ */
+static int parseMapLines(EventTagMap* map)
+{
+ int tagNum, lineStart, lineNum;
+ char* cp;
+ char* endp;
+
+ cp = (char*) map->mapAddr;
+ endp = cp + map->mapLen;
+
+ /* insist on EOL at EOF; simplifies parsing and null-termination */
+ if (*(endp-1) != '\n') {
+ fprintf(stderr, "%s: map file missing EOL on last line\n", OUT_TAG);
+ return -1;
+ }
+
+ tagNum = 0;
+ lineStart = 1;
+ lineNum = 1;
+ while (cp < endp) {
+ //printf("{%02x}", *cp); fflush(stdout);
+ if (*cp == '\n') {
+ lineStart = 1;
+ lineNum++;
+ } else if (lineStart) {
+ if (*cp == '#') {
+ /* comment; just scan to end */
+ lineStart = 0;
+ } else if (isCharDigit(*cp)) {
+ /* looks like a tag; scan it out */
+ if (tagNum >= map->numTags) {
+ fprintf(stderr,
+ "%s: more tags than expected (%d)\n", OUT_TAG, tagNum);
+ return -1;
+ }
+ if (scanTagLine(&cp, &map->tagArray[tagNum], lineNum) != 0)
+ return -1;
+ tagNum++;
+ lineNum++; // we eat the '\n'
+ /* leave lineStart==1 */
+ } else if (isCharWhitespace(*cp)) {
+ /* looks like leading whitespace; keep scanning */
+ } else {
+ fprintf(stderr,
+ "%s: unexpected chars (0x%02x) in tag number on line %d\n",
+ OUT_TAG, *cp, lineNum);
+ return -1;
+ }
+ } else {
+ /* this is a blank or comment line */
+ }
+ cp++;
+ }
+
+ if (tagNum != map->numTags) {
+ fprintf(stderr, "%s: parsed %d tags, expected %d\n",
+ OUT_TAG, tagNum, map->numTags);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Scan one tag line.
+ *
+ * "*pData" should be pointing to the first digit in the tag number. On
+ * successful return, it will be pointing to the last character in the
+ * tag line (i.e. the character before the start of the next line).
+ *
+ * Returns 0 on success, nonzero on failure.
+ */
+static int scanTagLine(char** pData, EventTag* tag, int lineNum)
+{
+ char* cp = *pData;
+ char* startp;
+ char* endp;
+ unsigned long val;
+
+ startp = cp;
+ while (isCharDigit(*++cp))
+ ;
+ *cp = '\0';
+
+ val = strtoul(startp, &endp, 10);
+ assert(endp == cp);
+ if (endp != cp)
+ fprintf(stderr, "ARRRRGH\n");
+
+ tag->tagIndex = val;
+
+ while (*++cp != '\n' && isCharWhitespace(*cp))
+ ;
+
+ if (*cp == '\n') {
+ fprintf(stderr,
+ "%s: missing tag string on line %d\n", OUT_TAG, lineNum);
+ return -1;
+ }
+
+ tag->tagStr = cp;
+
+ while (isCharValidTag(*++cp))
+ ;
+
+ if (*cp == '\n') {
+ /* null terminate and return */
+ *cp = '\0';
+ } else if (isCharWhitespace(*cp)) {
+ /* CRLF or trailin spaces; zap this char, then scan for the '\n' */
+ *cp = '\0';
+
+ /* just ignore the rest of the line till \n
+ TODO: read the tag description that follows the tag name
+ */
+ while (*++cp != '\n') {
+ }
+ } else {
+ fprintf(stderr,
+ "%s: invalid tag chars on line %d\n", OUT_TAG, lineNum);
+ return -1;
+ }
+
+ *pData = cp;
+
+ //printf("+++ Line %d: got %d '%s'\n", lineNum, tag->tagIndex, tag->tagStr);
+ return 0;
+}
+
+/*
+ * Compare two EventTags.
+ */
+static int compareEventTags(const void* v1, const void* v2)
+{
+ const EventTag* tag1 = (const EventTag*) v1;
+ const EventTag* tag2 = (const EventTag*) v2;
+
+ return tag1->tagIndex - tag2->tagIndex;
+}
+
+/*
+ * Sort the EventTag array so we can do fast lookups by tag index. After
+ * the sort we do a quick check for duplicate tag indices.
+ *
+ * Returns 0 on success.
+ */
+static int sortTags(EventTagMap* map)
+{
+ int i;
+
+ qsort(map->tagArray, map->numTags, sizeof(EventTag), compareEventTags);
+
+ for (i = 1; i < map->numTags; i++) {
+ if (map->tagArray[i].tagIndex == map->tagArray[i-1].tagIndex) {
+ fprintf(stderr, "%s: duplicate tag entries (%d:%s and %d:%s)\n",
+ OUT_TAG,
+ map->tagArray[i].tagIndex, map->tagArray[i].tagStr,
+ map->tagArray[i-1].tagIndex, map->tagArray[i-1].tagStr);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Dump the tag array for debugging.
+ */
+static void dumpTags(const EventTagMap* map)
+{
+ int i;
+
+ for (i = 0; i < map->numTags; i++) {
+ const EventTag* tag = &map->tagArray[i];
+ printf(" %3d: %6d '%s'\n", i, tag->tagIndex, tag->tagStr);
+ }
+}
+
diff --git a/liblog/fake_log_device.c b/liblog/fake_log_device.c
new file mode 100644
index 0000000..d9d67b4
--- /dev/null
+++ b/liblog/fake_log_device.c
@@ -0,0 +1,677 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+/*
+ * Intercepts log messages intended for the Android log device.
+ * When running in the context of the simulator, the messages are
+ * passed on to the underlying (fake) log device. When not in the
+ * simulator, messages are printed to stderr.
+ */
+#include "cutils/logd.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#ifdef HAVE_PTHREADS
+#include <pthread.h>
+#endif
+
+#define kMaxTagLen 16 /* from the long-dead utils/Log.cpp */
+
+#define kTagSetSize 16 /* arbitrary */
+
+#if 0
+#define TRACE(...) printf("fake_log_device: " __VA_ARGS__)
+#else
+#define TRACE(...) ((void)0)
+#endif
+
+/* from the long-dead utils/Log.cpp */
+typedef enum {
+ FORMAT_OFF = 0,
+ FORMAT_BRIEF,
+ FORMAT_PROCESS,
+ FORMAT_TAG,
+ FORMAT_THREAD,
+ FORMAT_RAW,
+ FORMAT_TIME,
+ FORMAT_THREADTIME,
+ FORMAT_LONG
+} LogFormat;
+
+
+/*
+ * Log driver state.
+ */
+typedef struct LogState {
+ /* the fake fd that's seen by the user */
+ int fakeFd;
+
+ /* a printable name for this fake device */
+ char *debugName;
+
+ /* nonzero if this is a binary log */
+ int isBinary;
+
+ /* global minimum priority */
+ int globalMinPriority;
+
+ /* output format */
+ LogFormat outputFormat;
+
+ /* tags and priorities */
+ struct {
+ char tag[kMaxTagLen];
+ int minPriority;
+ } tagSet[kTagSetSize];
+} LogState;
+
+
+#ifdef HAVE_PTHREADS
+/*
+ * Locking. Since we're emulating a device, we need to be prepared
+ * to have multiple callers at the same time. This lock is used
+ * to both protect the fd list and to prevent LogStates from being
+ * freed out from under a user.
+ */
+static pthread_mutex_t fakeLogDeviceLock = PTHREAD_MUTEX_INITIALIZER;
+
+static void lock()
+{
+ pthread_mutex_lock(&fakeLogDeviceLock);
+}
+
+static void unlock()
+{
+ pthread_mutex_unlock(&fakeLogDeviceLock);
+}
+#else // !HAVE_PTHREADS
+#define lock() ((void)0)
+#define unlock() ((void)0)
+#endif // !HAVE_PTHREADS
+
+
+/*
+ * File descriptor management.
+ */
+#define FAKE_FD_BASE 10000
+#define MAX_OPEN_LOGS 16
+static LogState *openLogTable[MAX_OPEN_LOGS];
+
+/*
+ * Allocate an fd and associate a new LogState with it.
+ * The fd is available via the fakeFd field of the return value.
+ */
+static LogState *createLogState()
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(openLogTable); i++) {
+ if (openLogTable[i] == NULL) {
+ openLogTable[i] = calloc(1, sizeof(LogState));
+ openLogTable[i]->fakeFd = FAKE_FD_BASE + i;
+ return openLogTable[i];
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Translate an fd to a LogState.
+ */
+static LogState *fdToLogState(int fd)
+{
+ if (fd >= FAKE_FD_BASE && fd < FAKE_FD_BASE + MAX_OPEN_LOGS) {
+ return openLogTable[fd - FAKE_FD_BASE];
+ }
+ return NULL;
+}
+
+/*
+ * Unregister the fake fd and free the memory it pointed to.
+ */
+static void deleteFakeFd(int fd)
+{
+ LogState *ls;
+
+ lock();
+
+ ls = fdToLogState(fd);
+ if (ls != NULL) {
+ openLogTable[fd - FAKE_FD_BASE] = NULL;
+ free(ls->debugName);
+ free(ls);
+ }
+
+ unlock();
+}
+
+/*
+ * Configure logging based on ANDROID_LOG_TAGS environment variable. We
+ * need to parse a string that looks like
+ *
+ * *:v jdwp:d dalvikvm:d dalvikvm-gc:i dalvikvmi:i
+ *
+ * The tag (or '*' for the global level) comes first, followed by a colon
+ * and a letter indicating the minimum priority level we're expected to log.
+ * This can be used to reveal or conceal logs with specific tags.
+ *
+ * We also want to check ANDROID_PRINTF_LOG to determine how the output
+ * will look.
+ */
+static void configureInitialState(const char* pathName, LogState* logState)
+{
+ static const int kDevLogLen = sizeof("/dev/log/") - 1;
+
+ logState->debugName = strdup(pathName);
+
+ /* identify binary logs */
+ if (strcmp(pathName + kDevLogLen, "events") == 0) {
+ logState->isBinary = 1;
+ }
+
+ /* global min priority defaults to "info" level */
+ logState->globalMinPriority = ANDROID_LOG_INFO;
+
+ /*
+ * This is based on the the long-dead utils/Log.cpp code.
+ */
+ const char* tags = getenv("ANDROID_LOG_TAGS");
+ TRACE("Found ANDROID_LOG_TAGS='%s'\n", tags);
+ if (tags != NULL) {
+ int entry = 0;
+
+ while (*tags != '\0') {
+ char tagName[kMaxTagLen];
+ int i, minPrio;
+
+ while (isspace(*tags))
+ tags++;
+
+ i = 0;
+ while (*tags != '\0' && !isspace(*tags) && *tags != ':' &&
+ i < kMaxTagLen)
+ {
+ tagName[i++] = *tags++;
+ }
+ if (i == kMaxTagLen) {
+ TRACE("ERROR: env tag too long (%d chars max)\n", kMaxTagLen-1);
+ return;
+ }
+ tagName[i] = '\0';
+
+ /* default priority, if there's no ":" part; also zero out '*' */
+ minPrio = ANDROID_LOG_VERBOSE;
+ if (tagName[0] == '*' && tagName[1] == '\0') {
+ minPrio = ANDROID_LOG_DEBUG;
+ tagName[0] = '\0';
+ }
+
+ if (*tags == ':') {
+ tags++;
+ if (*tags >= '0' && *tags <= '9') {
+ if (*tags >= ('0' + ANDROID_LOG_SILENT))
+ minPrio = ANDROID_LOG_VERBOSE;
+ else
+ minPrio = *tags - '\0';
+ } else {
+ switch (*tags) {
+ case 'v': minPrio = ANDROID_LOG_VERBOSE; break;
+ case 'd': minPrio = ANDROID_LOG_DEBUG; break;
+ case 'i': minPrio = ANDROID_LOG_INFO; break;
+ case 'w': minPrio = ANDROID_LOG_WARN; break;
+ case 'e': minPrio = ANDROID_LOG_ERROR; break;
+ case 'f': minPrio = ANDROID_LOG_FATAL; break;
+ case 's': minPrio = ANDROID_LOG_SILENT; break;
+ default: minPrio = ANDROID_LOG_DEFAULT; break;
+ }
+ }
+
+ tags++;
+ if (*tags != '\0' && !isspace(*tags)) {
+ TRACE("ERROR: garbage in tag env; expected whitespace\n");
+ TRACE(" env='%s'\n", tags);
+ return;
+ }
+ }
+
+ if (tagName[0] == 0) {
+ logState->globalMinPriority = minPrio;
+ TRACE("+++ global min prio %d\n", logState->globalMinPriority);
+ } else {
+ logState->tagSet[entry].minPriority = minPrio;
+ strcpy(logState->tagSet[entry].tag, tagName);
+ TRACE("+++ entry %d: %s:%d\n",
+ entry,
+ logState->tagSet[entry].tag,
+ logState->tagSet[entry].minPriority);
+ entry++;
+ }
+ }
+ }
+
+
+ /*
+ * Taken from the long-dead utils/Log.cpp
+ */
+ const char* fstr = getenv("ANDROID_PRINTF_LOG");
+ LogFormat format;
+ if (fstr == NULL) {
+ format = FORMAT_BRIEF;
+ } else {
+ if (strcmp(fstr, "brief") == 0)
+ format = FORMAT_BRIEF;
+ else if (strcmp(fstr, "process") == 0)
+ format = FORMAT_PROCESS;
+ else if (strcmp(fstr, "tag") == 0)
+ format = FORMAT_PROCESS;
+ else if (strcmp(fstr, "thread") == 0)
+ format = FORMAT_PROCESS;
+ else if (strcmp(fstr, "raw") == 0)
+ format = FORMAT_PROCESS;
+ else if (strcmp(fstr, "time") == 0)
+ format = FORMAT_PROCESS;
+ else if (strcmp(fstr, "long") == 0)
+ format = FORMAT_PROCESS;
+ else
+ format = (LogFormat) atoi(fstr); // really?!
+ }
+
+ logState->outputFormat = format;
+}
+
+/*
+ * Return a human-readable string for the priority level. Always returns
+ * a valid string.
+ */
+static const char* getPriorityString(int priority)
+{
+ /* the first character of each string should be unique */
+ static const char* priorityStrings[] = {
+ "Verbose", "Debug", "Info", "Warn", "Error", "Assert"
+ };
+ int idx;
+
+ idx = (int) priority - (int) ANDROID_LOG_VERBOSE;
+ if (idx < 0 ||
+ idx >= (int) (sizeof(priorityStrings) / sizeof(priorityStrings[0])))
+ return "?unknown?";
+ return priorityStrings[idx];
+}
+
+#ifndef HAVE_WRITEV
+/*
+ * Some platforms like WIN32 do not have writev().
+ * Make up something to replace it.
+ */
+static ssize_t fake_writev(int fd, const struct iovec *iov, int iovcnt) {
+ int result = 0;
+ struct iovec* end = iov + iovcnt;
+ for (; iov < end; iov++) {
+ int w = write(fd, iov->iov_base, iov->iov_len);
+ if (w != iov->iov_len) {
+ if (w < 0)
+ return w;
+ return result + w;
+ }
+ result += w;
+ }
+ return result;
+}
+
+#define writev fake_writev
+#endif
+
+
+/*
+ * Write a filtered log message to stderr.
+ *
+ * Log format parsing taken from the long-dead utils/Log.cpp.
+ */
+static void showLog(LogState *state,
+ int logPrio, const char* tag, const char* msg)
+{
+#if defined(HAVE_LOCALTIME_R)
+ struct tm tmBuf;
+#endif
+ struct tm* ptm;
+ char timeBuf[32];
+ char prefixBuf[128], suffixBuf[128];
+ char priChar;
+ time_t when;
+ pid_t pid, tid;
+
+ TRACE("LOG %d: %s %s", logPrio, tag, msg);
+
+ priChar = getPriorityString(logPrio)[0];
+ when = time(NULL);
+ pid = tid = getpid(); // find gettid()?
+
+ /*
+ * Get the current date/time in pretty form
+ *
+ * It's often useful when examining a log with "less" to jump to
+ * a specific point in the file by searching for the date/time stamp.
+ * For this reason it's very annoying to have regexp meta characters
+ * in the time stamp. Don't use forward slashes, parenthesis,
+ * brackets, asterisks, or other special chars here.
+ */
+#if defined(HAVE_LOCALTIME_R)
+ ptm = localtime_r(&when, &tmBuf);
+#else
+ ptm = localtime(&when);
+#endif
+ //strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", ptm);
+ strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
+
+ /*
+ * Construct a buffer containing the log header and log message.
+ */
+ size_t prefixLen, suffixLen;
+
+ switch (state->outputFormat) {
+ case FORMAT_TAG:
+ prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+ "%c/%-8s: ", priChar, tag);
+ strcpy(suffixBuf, "\n"); suffixLen = 1;
+ break;
+ case FORMAT_PROCESS:
+ prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+ "%c(%5d) ", priChar, pid);
+ suffixLen = snprintf(suffixBuf, sizeof(suffixBuf),
+ " (%s)\n", tag);
+ break;
+ case FORMAT_THREAD:
+ prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+ "%c(%5d:%p) ", priChar, pid, (void*)tid);
+ strcpy(suffixBuf, "\n"); suffixLen = 1;
+ break;
+ case FORMAT_RAW:
+ prefixBuf[0] = 0; prefixLen = 0;
+ strcpy(suffixBuf, "\n"); suffixLen = 1;
+ break;
+ case FORMAT_TIME:
+ prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+ "%s %-8s\n\t", timeBuf, tag);
+ strcpy(suffixBuf, "\n"); suffixLen = 1;
+ break;
+ case FORMAT_THREADTIME:
+ prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+ "%s %5d %5d %c %-8s \n\t", timeBuf, pid, tid, priChar, tag);
+ strcpy(suffixBuf, "\n"); suffixLen = 1;
+ break;
+ case FORMAT_LONG:
+ prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+ "[ %s %5d:%p %c/%-8s ]\n",
+ timeBuf, pid, (void*)tid, priChar, tag);
+ strcpy(suffixBuf, "\n\n"); suffixLen = 2;
+ break;
+ default:
+ prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+ "%c/%-8s(%5d): ", priChar, tag, pid);
+ strcpy(suffixBuf, "\n"); suffixLen = 1;
+ break;
+ }
+
+ /*
+ * Figure out how many lines there will be.
+ */
+ const char* end = msg + strlen(msg);
+ size_t numLines = 0;
+ const char* p = msg;
+ while (p < end) {
+ if (*p++ == '\n') numLines++;
+ }
+ if (p > msg && *(p-1) != '\n') numLines++;
+
+ /*
+ * Create an array of iovecs large enough to write all of
+ * the lines with a prefix and a suffix.
+ */
+ const size_t INLINE_VECS = 6;
+ struct iovec stackVec[INLINE_VECS];
+ struct iovec* vec = stackVec;
+
+ numLines *= 3; // 3 iovecs per line.
+ if (numLines > INLINE_VECS) {
+ vec = (struct iovec*)malloc(sizeof(struct iovec)*numLines);
+ if (vec == NULL) {
+ msg = "LOG: write failed, no memory";
+ numLines = 3;
+ }
+ }
+
+ /*
+ * Fill in the iovec pointers.
+ */
+ p = msg;
+ struct iovec* v = vec;
+ int totalLen = 0;
+ while (p < end) {
+ if (prefixLen > 0) {
+ v->iov_base = prefixBuf;
+ v->iov_len = prefixLen;
+ totalLen += prefixLen;
+ v++;
+ }
+ const char* start = p;
+ while (p < end && *p != '\n') p++;
+ if ((p-start) > 0) {
+ v->iov_base = (void*)start;
+ v->iov_len = p-start;
+ totalLen += p-start;
+ v++;
+ }
+ if (*p == '\n') p++;
+ if (suffixLen > 0) {
+ v->iov_base = suffixBuf;
+ v->iov_len = suffixLen;
+ totalLen += suffixLen;
+ v++;
+ }
+ }
+
+ /*
+ * Write the entire message to the log file with a single writev() call.
+ * We need to use this rather than a collection of printf()s on a FILE*
+ * because of multi-threading and multi-process issues.
+ *
+ * If the file was not opened with O_APPEND, this will produce interleaved
+ * output when called on the same file from multiple processes.
+ *
+ * If the file descriptor is actually a network socket, the writev()
+ * call may return with a partial write. Putting the writev() call in
+ * a loop can result in interleaved data. This can be alleviated
+ * somewhat by wrapping the writev call in the Mutex.
+ */
+
+ for(;;) {
+ int cc = writev(fileno(stderr), vec, v-vec);
+
+ if (cc == totalLen) break;
+
+ if (cc < 0) {
+ if(errno == EINTR) continue;
+
+ /* can't really log the failure; for now, throw out a stderr */
+ fprintf(stderr, "+++ LOG: write failed (errno=%d)\n", errno);
+ break;
+ } else {
+ /* shouldn't happen when writing to file or tty */
+ fprintf(stderr, "+++ LOG: write partial (%d of %d)\n", cc, totalLen);
+ break;
+ }
+ }
+
+ /* if we allocated storage for the iovecs, free it */
+ if (vec != stackVec)
+ free(vec);
+}
+
+
+/*
+ * Receive a log message. We happen to know that "vector" has three parts:
+ *
+ * priority (1 byte)
+ * tag (N bytes -- null-terminated ASCII string)
+ * message (N bytes -- null-terminated ASCII string)
+ */
+static ssize_t logWritev(int fd, const struct iovec* vector, int count)
+{
+ LogState* state;
+
+ /* Make sure that no-one frees the LogState while we're using it.
+ * Also guarantees that only one thread is in showLog() at a given
+ * time (if it matters).
+ */
+ lock();
+
+ state = fdToLogState(fd);
+ if (state == NULL) {
+ errno = EBADF;
+ goto error;
+ }
+
+ if (state->isBinary) {
+ TRACE("%s: ignoring binary log\n", state->debugName);
+ goto bail;
+ }
+
+ if (count != 3) {
+ TRACE("%s: writevLog with count=%d not expected\n",
+ state->debugName, count);
+ goto error;
+ }
+
+ /* pull out the three fields */
+ int logPrio = *(const char*)vector[0].iov_base;
+ const char* tag = (const char*) vector[1].iov_base;
+ const char* msg = (const char*) vector[2].iov_base;
+
+ /* see if this log tag is configured */
+ int i;
+ int minPrio = state->globalMinPriority;
+ for (i = 0; i < kTagSetSize; i++) {
+ if (state->tagSet[i].minPriority == ANDROID_LOG_UNKNOWN)
+ break; /* reached end of configured values */
+
+ if (strcmp(state->tagSet[i].tag, tag) == 0) {
+ //TRACE("MATCH tag '%s'\n", tag);
+ minPrio = state->tagSet[i].minPriority;
+ break;
+ }
+ }
+
+ if (logPrio >= minPrio) {
+ showLog(state, logPrio, tag, msg);
+ } else {
+ //TRACE("+++ NOLOG(%d): %s %s", logPrio, tag, msg);
+ }
+
+bail:
+ unlock();
+ return vector[0].iov_len + vector[1].iov_len + vector[2].iov_len;
+error:
+ unlock();
+ return -1;
+}
+
+/*
+ * Free up our state and close the fake descriptor.
+ */
+static int logClose(int fd)
+{
+ deleteFakeFd(fd);
+ return 0;
+}
+
+/*
+ * Open a log output device and return a fake fd.
+ */
+static int logOpen(const char* pathName, int flags)
+{
+ LogState *logState;
+ int fd = -1;
+
+ lock();
+
+ logState = createLogState();
+ if (logState != NULL) {
+ configureInitialState(pathName, logState);
+ fd = logState->fakeFd;
+ } else {
+ errno = ENFILE;
+ }
+
+ unlock();
+
+ return fd;
+}
+
+
+/*
+ * Runtime redirection. If this binary is running in the simulator,
+ * just pass log messages to the emulated device. If it's running
+ * outside of the simulator, write the log messages to stderr.
+ */
+
+static int (*redirectOpen)(const char *pathName, int flags) = NULL;
+static int (*redirectClose)(int fd) = NULL;
+static ssize_t (*redirectWritev)(int fd, const struct iovec* vector, int count)
+ = NULL;
+
+static void setRedirects()
+{
+ const char *ws;
+
+ /* Wrapsim sets this environment variable on children that it's
+ * created using its LD_PRELOAD wrapper.
+ */
+ ws = getenv("ANDROID_WRAPSIM");
+ if (ws != NULL && strcmp(ws, "1") == 0) {
+ /* We're running inside wrapsim, so we can just write to the device. */
+ redirectOpen = (int (*)(const char *pathName, int flags))open;
+ redirectClose = close;
+ redirectWritev = writev;
+ } else {
+ /* There's no device to delegate to; handle the logging ourselves. */
+ redirectOpen = logOpen;
+ redirectClose = logClose;
+ redirectWritev = logWritev;
+ }
+}
+
+int fakeLogOpen(const char *pathName, int flags)
+{
+ if (redirectOpen == NULL) {
+ setRedirects();
+ }
+ return redirectOpen(pathName, flags);
+}
+
+int fakeLogClose(int fd)
+{
+ /* Assume that open() was called first. */
+ return redirectClose(fd);
+}
+
+ssize_t fakeLogWritev(int fd, const struct iovec* vector, int count)
+{
+ /* Assume that open() was called first. */
+ return redirectWritev(fd, vector, count);
+}
diff --git a/liblog/logd_write.c b/liblog/logd_write.c
new file mode 100644
index 0000000..d5d8138
--- /dev/null
+++ b/liblog/logd_write.c
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2007 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 <time.h>
+#include <stdio.h>
+#ifdef HAVE_PTHREADS
+#include <pthread.h>
+#endif
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include <utils/logger.h>
+#include <cutils/logd.h>
+
+#define LOG_BUF_SIZE 1024
+
+#if FAKE_LOG_DEVICE
+// This will be defined when building for the host.
+#define log_open(pathname, flags) fakeLogOpen(pathname, flags)
+#define log_writev(filedes, vector, count) fakeLogWritev(filedes, vector, count)
+#define log_close(filedes) fakeLogClose(filedes)
+#else
+#define log_open(pathname, flags) open(pathname, flags)
+#define log_writev(filedes, vector, count) writev(filedes, vector, count)
+#define log_close(filedes) close(filedes)
+#endif
+
+typedef enum {
+ LOG_ID_MAIN = 0,
+ LOG_ID_RADIO,
+ LOG_ID_EVENTS,
+ LOG_ID_MAX
+} log_id_t;
+
+static int __write_to_log_init(log_id_t, struct iovec *vec, size_t nr);
+static int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) =
+ __write_to_log_init;
+#ifdef HAVE_PTHREADS
+static pthread_mutex_t log_init_lock = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+static int log_fds[(int)LOG_ID_MAX] = { -1, -1, -1 };
+
+/*
+ * This is used by the C++ code to decide if it should write logs through
+ * the C code. Basically, if /dev/log/... is available, we're running in
+ * the simulator rather than a desktop tool and want to use the device.
+ */
+static enum {
+ kLogUninitialized, kLogNotAvailable, kLogAvailable
+} g_log_status = kLogUninitialized;
+int __android_log_dev_available(void)
+{
+ if (g_log_status == kLogUninitialized) {
+ if (access("/dev/"LOGGER_LOG_MAIN, W_OK) == 0)
+ g_log_status = kLogAvailable;
+ else
+ g_log_status = kLogNotAvailable;
+ }
+
+ return (g_log_status == kLogAvailable);
+}
+
+static int __write_to_log_null(log_id_t log_fd, struct iovec *vec, size_t nr)
+{
+ return -1;
+}
+
+static int __write_to_log_kernel(log_id_t log_id, struct iovec *vec, size_t nr)
+{
+ ssize_t ret;
+ int log_fd;
+
+ if (/*(int)log_id >= 0 &&*/ (int)log_id < (int)LOG_ID_MAX) {
+ log_fd = log_fds[(int)log_id];
+ } else {
+ return EBADF;
+ }
+
+ do {
+ ret = log_writev(log_fd, vec, nr);
+ } while (ret < 0 && errno == EINTR);
+
+ return ret;
+}
+
+static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr)
+{
+#ifdef HAVE_PTHREADS
+ pthread_mutex_lock(&log_init_lock);
+#endif
+
+ if (write_to_log == __write_to_log_init) {
+ log_fds[LOG_ID_MAIN] = log_open("/dev/"LOGGER_LOG_MAIN, O_WRONLY);
+ log_fds[LOG_ID_RADIO] = log_open("/dev/"LOGGER_LOG_RADIO, O_WRONLY);
+ log_fds[LOG_ID_EVENTS] = log_open("/dev/"LOGGER_LOG_EVENTS, O_WRONLY);
+
+ write_to_log = __write_to_log_kernel;
+
+ if (log_fds[LOG_ID_MAIN] < 0 || log_fds[LOG_ID_RADIO] < 0 ||
+ log_fds[LOG_ID_EVENTS] < 0) {
+ log_close(log_fds[LOG_ID_MAIN]);
+ log_close(log_fds[LOG_ID_RADIO]);
+ log_close(log_fds[LOG_ID_EVENTS]);
+ log_fds[LOG_ID_MAIN] = -1;
+ log_fds[LOG_ID_RADIO] = -1;
+ log_fds[LOG_ID_EVENTS] = -1;
+ write_to_log = __write_to_log_null;
+ }
+ }
+
+#ifdef HAVE_PTHREADS
+ pthread_mutex_unlock(&log_init_lock);
+#endif
+
+ return write_to_log(log_id, vec, nr);
+}
+
+int __android_log_write(int prio, const char *tag, const char *msg)
+{
+ struct iovec vec[3];
+ log_id_t log_id = LOG_ID_MAIN;
+
+ if (!tag)
+ tag = "";
+
+ /* XXX: This needs to go! */
+ if (!strcmp(tag, "HTC_RIL") ||
+ !strcmp(tag, "RILJ") ||
+ !strcmp(tag, "RILC") ||
+ !strcmp(tag, "RIL") ||
+ !strcmp(tag, "AT") ||
+ !strcmp(tag, "GSM") ||
+ !strcmp(tag, "STK"))
+ log_id = LOG_ID_RADIO;
+
+ vec[0].iov_base = (unsigned char *) &prio;
+ vec[0].iov_len = 1;
+ vec[1].iov_base = (void *) tag;
+ vec[1].iov_len = strlen(tag) + 1;
+ vec[2].iov_base = (void *) msg;
+ vec[2].iov_len = strlen(msg) + 1;
+
+ return write_to_log(log_id, vec, 3);
+}
+
+int __android_log_vprint(int prio, const char *tag, const char *fmt, va_list ap)
+{
+ char buf[LOG_BUF_SIZE];
+
+ vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
+
+ return __android_log_write(prio, tag, buf);
+}
+
+int __android_log_print(int prio, const char *tag, const char *fmt, ...)
+{
+ va_list ap;
+ char buf[LOG_BUF_SIZE];
+
+ va_start(ap, fmt);
+ vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
+ va_end(ap);
+
+ return __android_log_write(prio, tag, buf);
+}
+
+void __android_log_assert(const char *cond, const char *tag,
+ const char *fmt, ...)
+{
+ va_list ap;
+ char buf[LOG_BUF_SIZE];
+
+ va_start(ap, fmt);
+ vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
+ va_end(ap);
+
+ __android_log_write(ANDROID_LOG_FATAL, tag, buf);
+
+ __builtin_trap(); /* trap so we have a chance to debug the situation */
+}
+
+int __android_log_bwrite(int32_t tag, const void *payload, size_t len)
+{
+ struct iovec vec[2];
+
+ vec[0].iov_base = &tag;
+ vec[0].iov_len = sizeof(tag);
+ vec[1].iov_base = (void*)payload;
+ vec[1].iov_len = len;
+
+ return write_to_log(LOG_ID_EVENTS, vec, 2);
+}
+
+/*
+ * Like __android_log_bwrite, but takes the type as well. Doesn't work
+ * for the general case where we're generating lists of stuff, but very
+ * handy if we just want to dump an integer into the log.
+ */
+int __android_log_btwrite(int32_t tag, char type, const void *payload,
+ size_t len)
+{
+ struct iovec vec[3];
+
+ vec[0].iov_base = &tag;
+ vec[0].iov_len = sizeof(tag);
+ vec[1].iov_base = &type;
+ vec[1].iov_len = sizeof(type);
+ vec[2].iov_base = (void*)payload;
+ vec[2].iov_len = len;
+
+ return write_to_log(LOG_ID_EVENTS, vec, 3);
+}
diff --git a/liblog/logprint.c b/liblog/logprint.c
new file mode 100644
index 0000000..2cf1254
--- /dev/null
+++ b/liblog/logprint.c
@@ -0,0 +1,972 @@
+/* //device/libs/cutils/logprint.c
+**
+** Copyright 2006, 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.
+*/
+
+#define _GNU_SOURCE /* for asprintf */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <alloca.h>
+#include <assert.h>
+#include <arpa/inet.h>
+
+#include <cutils/logd.h>
+#include <cutils/logprint.h>
+
+typedef struct FilterInfo_t {
+ char *mTag;
+ android_LogPriority mPri;
+ struct FilterInfo_t *p_next;
+} FilterInfo;
+
+struct AndroidLogFormat_t {
+ android_LogPriority global_pri;
+ FilterInfo *filters;
+ AndroidLogPrintFormat format;
+};
+
+static FilterInfo * filterinfo_new(const char * tag, android_LogPriority pri)
+{
+ FilterInfo *p_ret;
+
+ p_ret = (FilterInfo *)calloc(1, sizeof(FilterInfo));
+ p_ret->mTag = strdup(tag);
+ p_ret->mPri = pri;
+
+ return p_ret;
+}
+
+static void filterinfo_free(FilterInfo *p_info)
+{
+ if (p_info == NULL) {
+ return;
+ }
+
+ free(p_info->mTag);
+ p_info->mTag = NULL;
+}
+
+/*
+ * Note: also accepts 0-9 priorities
+ * returns ANDROID_LOG_UNKNOWN if the character is unrecognized
+ */
+static android_LogPriority filterCharToPri (char c)
+{
+ android_LogPriority pri;
+
+ c = tolower(c);
+
+ if (c >= '0' && c <= '9') {
+ if (c >= ('0'+ANDROID_LOG_SILENT)) {
+ pri = ANDROID_LOG_VERBOSE;
+ } else {
+ pri = (android_LogPriority)(c - '0');
+ }
+ } else if (c == 'v') {
+ pri = ANDROID_LOG_VERBOSE;
+ } else if (c == 'd') {
+ pri = ANDROID_LOG_DEBUG;
+ } else if (c == 'i') {
+ pri = ANDROID_LOG_INFO;
+ } else if (c == 'w') {
+ pri = ANDROID_LOG_WARN;
+ } else if (c == 'e') {
+ pri = ANDROID_LOG_ERROR;
+ } else if (c == 'f') {
+ pri = ANDROID_LOG_FATAL;
+ } else if (c == 's') {
+ pri = ANDROID_LOG_SILENT;
+ } else if (c == '*') {
+ pri = ANDROID_LOG_DEFAULT;
+ } else {
+ pri = ANDROID_LOG_UNKNOWN;
+ }
+
+ return pri;
+}
+
+static char filterPriToChar (android_LogPriority pri)
+{
+ switch (pri) {
+ case ANDROID_LOG_VERBOSE: return 'V';
+ case ANDROID_LOG_DEBUG: return 'D';
+ case ANDROID_LOG_INFO: return 'I';
+ case ANDROID_LOG_WARN: return 'W';
+ case ANDROID_LOG_ERROR: return 'E';
+ case ANDROID_LOG_FATAL: return 'F';
+ case ANDROID_LOG_SILENT: return 'S';
+
+ case ANDROID_LOG_DEFAULT:
+ case ANDROID_LOG_UNKNOWN:
+ default: return '?';
+ }
+}
+
+static android_LogPriority filterPriForTag(
+ AndroidLogFormat *p_format, const char *tag)
+{
+ FilterInfo *p_curFilter;
+
+ for (p_curFilter = p_format->filters
+ ; p_curFilter != NULL
+ ; p_curFilter = p_curFilter->p_next
+ ) {
+ if (0 == strcmp(tag, p_curFilter->mTag)) {
+ if (p_curFilter->mPri == ANDROID_LOG_DEFAULT) {
+ return p_format->global_pri;
+ } else {
+ return p_curFilter->mPri;
+ }
+ }
+ }
+
+ return p_format->global_pri;
+}
+
+/** for debugging */
+static void dumpFilters(AndroidLogFormat *p_format)
+{
+ FilterInfo *p_fi;
+
+ for (p_fi = p_format->filters ; p_fi != NULL ; p_fi = p_fi->p_next) {
+ char cPri = filterPriToChar(p_fi->mPri);
+ if (p_fi->mPri == ANDROID_LOG_DEFAULT) {
+ cPri = filterPriToChar(p_format->global_pri);
+ }
+ fprintf(stderr,"%s:%c\n", p_fi->mTag, cPri);
+ }
+
+ fprintf(stderr,"*:%c\n", filterPriToChar(p_format->global_pri));
+
+}
+
+/**
+ * returns 1 if this log line should be printed based on its priority
+ * and tag, and 0 if it should not
+ */
+int android_log_shouldPrintLine (
+ AndroidLogFormat *p_format, const char *tag, android_LogPriority pri)
+{
+ return pri >= filterPriForTag(p_format, tag);
+}
+
+AndroidLogFormat *android_log_format_new()
+{
+ AndroidLogFormat *p_ret;
+
+ p_ret = calloc(1, sizeof(AndroidLogFormat));
+
+ p_ret->global_pri = ANDROID_LOG_VERBOSE;
+ p_ret->format = FORMAT_BRIEF;
+
+ return p_ret;
+}
+
+void android_log_format_free(AndroidLogFormat *p_format)
+{
+ FilterInfo *p_info, *p_info_old;
+
+ p_info = p_format->filters;
+
+ while (p_info != NULL) {
+ p_info_old = p_info;
+ p_info = p_info->p_next;
+
+ free(p_info_old);
+ }
+
+ free(p_format);
+}
+
+
+
+void android_log_setPrintFormat(AndroidLogFormat *p_format,
+ AndroidLogPrintFormat format)
+{
+ p_format->format=format;
+}
+
+/**
+ * Returns FORMAT_OFF on invalid string
+ */
+AndroidLogPrintFormat android_log_formatFromString(const char * formatString)
+{
+ static AndroidLogPrintFormat format;
+
+ if (strcmp(formatString, "brief") == 0) format = FORMAT_BRIEF;
+ else if (strcmp(formatString, "process") == 0) format = FORMAT_PROCESS;
+ else if (strcmp(formatString, "tag") == 0) format = FORMAT_TAG;
+ else if (strcmp(formatString, "thread") == 0) format = FORMAT_THREAD;
+ else if (strcmp(formatString, "raw") == 0) format = FORMAT_RAW;
+ else if (strcmp(formatString, "time") == 0) format = FORMAT_TIME;
+ else if (strcmp(formatString, "threadtime") == 0) format = FORMAT_THREADTIME;
+ else if (strcmp(formatString, "long") == 0) format = FORMAT_LONG;
+ else format = FORMAT_OFF;
+
+ return format;
+}
+
+/**
+ * filterExpression: a single filter expression
+ * eg "AT:d"
+ *
+ * returns 0 on success and -1 on invalid expression
+ *
+ * Assumes single threaded execution
+ */
+
+int android_log_addFilterRule(AndroidLogFormat *p_format,
+ const char *filterExpression)
+{
+ size_t i=0;
+ size_t tagNameLength;
+ android_LogPriority pri = ANDROID_LOG_DEFAULT;
+
+ tagNameLength = strcspn(filterExpression, ":");
+
+ if (tagNameLength == 0) {
+ goto error;
+ }
+
+ if(filterExpression[tagNameLength] == ':') {
+ pri = filterCharToPri(filterExpression[tagNameLength+1]);
+
+ if (pri == ANDROID_LOG_UNKNOWN) {
+ goto error;
+ }
+ }
+
+ if(0 == strncmp("*", filterExpression, tagNameLength)) {
+ // This filter expression refers to the global filter
+ // The default level for this is DEBUG if the priority
+ // is unspecified
+ if (pri == ANDROID_LOG_DEFAULT) {
+ pri = ANDROID_LOG_DEBUG;
+ }
+
+ p_format->global_pri = pri;
+ } else {
+ // for filter expressions that don't refer to the global
+ // filter, the default is verbose if the priority is unspecified
+ if (pri == ANDROID_LOG_DEFAULT) {
+ pri = ANDROID_LOG_VERBOSE;
+ }
+
+ char *tagName;
+
+// Presently HAVE_STRNDUP is never defined, so the second case is always taken
+// Darwin doesn't have strnup, everything else does
+#ifdef HAVE_STRNDUP
+ tagName = strndup(filterExpression, tagNameLength);
+#else
+ //a few extra bytes copied...
+ tagName = strdup(filterExpression);
+ tagName[tagNameLength] = '\0';
+#endif /*HAVE_STRNDUP*/
+
+ FilterInfo *p_fi = filterinfo_new(tagName, pri);
+ free(tagName);
+
+ p_fi->p_next = p_format->filters;
+ p_format->filters = p_fi;
+ }
+
+ return 0;
+error:
+ return -1;
+}
+
+
+/**
+ * filterString: a comma/whitespace-separated set of filter expressions
+ *
+ * eg "AT:d *:i"
+ *
+ * returns 0 on success and -1 on invalid expression
+ *
+ * Assumes single threaded execution
+ *
+ */
+
+int android_log_addFilterString(AndroidLogFormat *p_format,
+ const char *filterString)
+{
+ char *filterStringCopy = strdup (filterString);
+ char *p_cur = filterStringCopy;
+ char *p_ret;
+ int err;
+
+ // Yes, I'm using strsep
+ while (NULL != (p_ret = strsep(&p_cur, " \t,"))) {
+ // ignore whitespace-only entries
+ if(p_ret[0] != '\0') {
+ err = android_log_addFilterRule(p_format, p_ret);
+
+ if (err < 0) {
+ goto error;
+ }
+ }
+ }
+
+ free (filterStringCopy);
+ return 0;
+error:
+ free (filterStringCopy);
+ return -1;
+}
+
+static inline char * strip_end(char *str)
+{
+ char *end = str + strlen(str) - 1;
+
+ while (end >= str && isspace(*end))
+ *end-- = '\0';
+ return str;
+}
+
+/**
+ * Splits a wire-format buffer into an AndroidLogEntry
+ * entry allocated by caller. Pointers will point directly into buf
+ *
+ * Returns 0 on success and -1 on invalid wire format (entry will be
+ * in unspecified state)
+ */
+int android_log_processLogBuffer(struct logger_entry *buf,
+ AndroidLogEntry *entry)
+{
+ size_t tag_len;
+
+ entry->tv_sec = buf->sec;
+ entry->tv_nsec = buf->nsec;
+ entry->priority = buf->msg[0];
+ entry->pid = buf->pid;
+ entry->tid = buf->tid;
+ entry->tag = buf->msg + 1;
+ tag_len = strlen(entry->tag);
+ entry->messageLen = buf->len - tag_len - 3;
+ entry->message = entry->tag + tag_len + 1;
+
+ return 0;
+}
+
+/*
+ * Extract a 4-byte value from a byte stream.
+ */
+static inline uint32_t get4LE(const uint8_t* src)
+{
+ return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
+}
+
+/*
+ * Extract an 8-byte value from a byte stream.
+ */
+static inline uint64_t get8LE(const uint8_t* src)
+{
+ uint32_t low, high;
+
+ low = src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
+ high = src[4] | (src[5] << 8) | (src[6] << 16) | (src[7] << 24);
+ return ((long long) high << 32) | (long long) low;
+}
+
+
+/*
+ * Recursively convert binary log data to printable form.
+ *
+ * This needs to be recursive because you can have lists of lists.
+ *
+ * If we run out of room, we stop processing immediately. It's important
+ * for us to check for space on every output element to avoid producing
+ * garbled output.
+ *
+ * Returns 0 on success, 1 on buffer full, -1 on failure.
+ */
+static int android_log_printBinaryEvent(const unsigned char** pEventData,
+ size_t* pEventDataLen, char** pOutBuf, size_t* pOutBufLen)
+{
+ const unsigned char* eventData = *pEventData;
+ size_t eventDataLen = *pEventDataLen;
+ char* outBuf = *pOutBuf;
+ size_t outBufLen = *pOutBufLen;
+ unsigned char type;
+ size_t outCount;
+ int result = 0;
+
+ if (eventDataLen < 1)
+ return -1;
+ type = *eventData++;
+ eventDataLen--;
+
+ //fprintf(stderr, "--- type=%d (rem len=%d)\n", type, eventDataLen);
+
+ switch (type) {
+ case EVENT_TYPE_INT:
+ /* 32-bit signed int */
+ {
+ int ival;
+
+ if (eventDataLen < 4)
+ return -1;
+ ival = get4LE(eventData);
+ eventData += 4;
+ eventDataLen -= 4;
+
+ outCount = snprintf(outBuf, outBufLen, "%d", ival);
+ if (outCount < outBufLen) {
+ outBuf += outCount;
+ outBufLen -= outCount;
+ } else {
+ /* halt output */
+ goto no_room;
+ }
+ }
+ break;
+ case EVENT_TYPE_LONG:
+ /* 64-bit signed long */
+ {
+ long long lval;
+
+ if (eventDataLen < 8)
+ return -1;
+ lval = get8LE(eventData);
+ eventData += 8;
+ eventDataLen -= 8;
+
+ outCount = snprintf(outBuf, outBufLen, "%lld", lval);
+ if (outCount < outBufLen) {
+ outBuf += outCount;
+ outBufLen -= outCount;
+ } else {
+ /* halt output */
+ goto no_room;
+ }
+ }
+ break;
+ case EVENT_TYPE_STRING:
+ /* UTF-8 chars, not NULL-terminated */
+ {
+ unsigned int strLen;
+
+ if (eventDataLen < 4)
+ return -1;
+ strLen = get4LE(eventData);
+ eventData += 4;
+ eventDataLen -= 4;
+
+ if (eventDataLen < strLen)
+ return -1;
+
+ if (strLen < outBufLen) {
+ memcpy(outBuf, eventData, strLen);
+ outBuf += strLen;
+ outBufLen -= strLen;
+ } else if (outBufLen > 0) {
+ /* copy what we can */
+ memcpy(outBuf, eventData, outBufLen);
+ outBuf += outBufLen;
+ outBufLen -= outBufLen;
+ goto no_room;
+ }
+ eventData += strLen;
+ eventDataLen -= strLen;
+ break;
+ }
+ case EVENT_TYPE_LIST:
+ /* N items, all different types */
+ {
+ unsigned char count;
+ int i;
+
+ if (eventDataLen < 1)
+ return -1;
+
+ count = *eventData++;
+ eventDataLen--;
+
+ if (outBufLen > 0) {
+ *outBuf++ = '[';
+ outBufLen--;
+ } else {
+ goto no_room;
+ }
+
+ for (i = 0; i < count; i++) {
+ result = android_log_printBinaryEvent(&eventData, &eventDataLen,
+ &outBuf, &outBufLen);
+ if (result != 0)
+ goto bail;
+
+ if (i < count-1) {
+ if (outBufLen > 0) {
+ *outBuf++ = ',';
+ outBufLen--;
+ } else {
+ goto no_room;
+ }
+ }
+ }
+
+ if (outBufLen > 0) {
+ *outBuf++ = ']';
+ outBufLen--;
+ } else {
+ goto no_room;
+ }
+ }
+ break;
+ default:
+ fprintf(stderr, "Unknown binary event type %d\n", type);
+ return -1;
+ }
+
+bail:
+ *pEventData = eventData;
+ *pEventDataLen = eventDataLen;
+ *pOutBuf = outBuf;
+ *pOutBufLen = outBufLen;
+ return result;
+
+no_room:
+ result = 1;
+ goto bail;
+}
+
+/**
+ * Convert a binary log entry to ASCII form.
+ *
+ * For convenience we mimic the processLogBuffer API. There is no
+ * pre-defined output length for the binary data, since we're free to format
+ * it however we choose, which means we can't really use a fixed-size buffer
+ * here.
+ */
+int android_log_processBinaryLogBuffer(struct logger_entry *buf,
+ AndroidLogEntry *entry, const EventTagMap* map, char* messageBuf,
+ int messageBufLen)
+{
+ size_t inCount;
+ unsigned int tagIndex;
+ const unsigned char* eventData;
+
+ entry->tv_sec = buf->sec;
+ entry->tv_nsec = buf->nsec;
+ entry->priority = ANDROID_LOG_INFO;
+ entry->pid = buf->pid;
+ entry->tid = buf->tid;
+
+ /*
+ * Pull the tag out.
+ */
+ eventData = (const unsigned char*) buf->msg;
+ inCount = buf->len;
+ if (inCount < 4)
+ return -1;
+ tagIndex = get4LE(eventData);
+ eventData += 4;
+ inCount -= 4;
+
+ if (map != NULL) {
+ entry->tag = android_lookupEventTag(map, tagIndex);
+ } else {
+ entry->tag = NULL;
+ }
+
+ /*
+ * If we don't have a map, or didn't find the tag number in the map,
+ * stuff a generated tag value into the start of the output buffer and
+ * shift the buffer pointers down.
+ */
+ if (entry->tag == NULL) {
+ int tagLen;
+
+ tagLen = snprintf(messageBuf, messageBufLen, "[%d]", tagIndex);
+ entry->tag = messageBuf;
+ messageBuf += tagLen+1;
+ messageBufLen -= tagLen+1;
+ }
+
+ /*
+ * Format the event log data into the buffer.
+ */
+ char* outBuf = messageBuf;
+ size_t outRemaining = messageBufLen-1; /* leave one for nul byte */
+ int result;
+ result = android_log_printBinaryEvent(&eventData, &inCount, &outBuf,
+ &outRemaining);
+ if (result < 0) {
+ fprintf(stderr, "Binary log entry conversion failed\n");
+ return -1;
+ } else if (result == 1) {
+ if (outBuf > messageBuf) {
+ /* leave an indicator */
+ *(outBuf-1) = '!';
+ } else {
+ /* no room to output anything at all */
+ *outBuf++ = '!';
+ outRemaining--;
+ }
+ /* pretend we ate all the data */
+ inCount = 0;
+ }
+
+ /* eat the silly terminating '\n' */
+ if (inCount == 1 && *eventData == '\n') {
+ eventData++;
+ inCount--;
+ }
+
+ if (inCount != 0) {
+ fprintf(stderr,
+ "Warning: leftover binary log data (%d bytes)\n", inCount);
+ }
+
+ /*
+ * Terminate the buffer. The NUL byte does not count as part of
+ * entry->messageLen.
+ */
+ *outBuf = '\0';
+ entry->messageLen = outBuf - messageBuf;
+ assert(entry->messageLen == (messageBufLen-1) - outRemaining);
+
+ entry->message = messageBuf;
+
+ return 0;
+}
+
+/**
+ * Formats a log message into a buffer
+ *
+ * Uses defaultBuffer if it can, otherwise malloc()'s a new buffer
+ * If return value != defaultBuffer, caller must call free()
+ * Returns NULL on malloc error
+ */
+
+char *android_log_formatLogLine (
+ AndroidLogFormat *p_format,
+ char *defaultBuffer,
+ size_t defaultBufferSize,
+ const AndroidLogEntry *entry,
+ size_t *p_outLength)
+{
+#if defined(HAVE_LOCALTIME_R)
+ struct tm tmBuf;
+#endif
+ struct tm* ptm;
+ char timeBuf[32];
+ char headerBuf[128];
+ char prefixBuf[128], suffixBuf[128];
+ char priChar;
+ int prefixSuffixIsHeaderFooter = 0;
+ char * ret = NULL;
+
+ priChar = filterPriToChar(entry->priority);
+
+ /*
+ * Get the current date/time in pretty form
+ *
+ * It's often useful when examining a log with "less" to jump to
+ * a specific point in the file by searching for the date/time stamp.
+ * For this reason it's very annoying to have regexp meta characters
+ * in the time stamp. Don't use forward slashes, parenthesis,
+ * brackets, asterisks, or other special chars here.
+ */
+#if defined(HAVE_LOCALTIME_R)
+ ptm = localtime_r(&(entry->tv_sec), &tmBuf);
+#else
+ ptm = localtime(&(entry->tv_sec));
+#endif
+ //strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", ptm);
+ strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
+
+ /*
+ * Construct a buffer containing the log header and log message.
+ */
+ size_t prefixLen, suffixLen;
+
+ switch (p_format->format) {
+ case FORMAT_TAG:
+ prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+ "%c/%-8s: ", priChar, entry->tag);
+ strcpy(suffixBuf, "\n"); suffixLen = 1;
+ break;
+ case FORMAT_PROCESS:
+ prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+ "%c(%5d) ", priChar, entry->pid);
+ suffixLen = snprintf(suffixBuf, sizeof(suffixBuf),
+ " (%s)\n", entry->tag);
+ break;
+ case FORMAT_THREAD:
+ prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+ "%c(%5d:%p) ", priChar, entry->pid, (void*)entry->tid);
+ strcpy(suffixBuf, "\n");
+ suffixLen = 1;
+ break;
+ case FORMAT_RAW:
+ prefixBuf[0] = 0;
+ prefixLen = 0;
+ strcpy(suffixBuf, "\n");
+ suffixLen = 1;
+ break;
+ case FORMAT_TIME:
+ prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+ "%s.%03ld %c/%-8s(%5d): ", timeBuf, entry->tv_nsec / 1000000,
+ priChar, entry->tag, entry->pid);
+ strcpy(suffixBuf, "\n");
+ suffixLen = 1;
+ break;
+ case FORMAT_THREADTIME:
+ prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+ "%s.%03ld %5d %5d %c %-8s: ", timeBuf, entry->tv_nsec / 1000000,
+ (int)entry->pid, (int)entry->tid, priChar, entry->tag);
+ strcpy(suffixBuf, "\n");
+ suffixLen = 1;
+ break;
+ case FORMAT_LONG:
+ prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+ "[ %s.%03ld %5d:%p %c/%-8s ]\n",
+ timeBuf, entry->tv_nsec / 1000000, entry->pid,
+ (void*)entry->tid, priChar, entry->tag);
+ strcpy(suffixBuf, "\n\n");
+ suffixLen = 2;
+ prefixSuffixIsHeaderFooter = 1;
+ break;
+ case FORMAT_BRIEF:
+ default:
+ prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
+ "%c/%-8s(%5d): ", priChar, entry->tag, entry->pid);
+ strcpy(suffixBuf, "\n");
+ suffixLen = 1;
+ break;
+ }
+
+ /* the following code is tragically unreadable */
+
+ size_t numLines;
+ size_t i;
+ char *p;
+ size_t bufferSize;
+ const char *pm;
+
+ if (prefixSuffixIsHeaderFooter) {
+ // we're just wrapping message with a header/footer
+ numLines = 1;
+ } else {
+ pm = entry->message;
+ numLines = 0;
+
+ // The line-end finding here must match the line-end finding
+ // in for ( ... numLines...) loop below
+ while (pm < (entry->message + entry->messageLen)) {
+ if (*pm++ == '\n') numLines++;
+ }
+ // plus one line for anything not newline-terminated at the end
+ if (pm > entry->message && *(pm-1) != '\n') numLines++;
+ }
+
+ // this is an upper bound--newlines in message may be counted
+ // extraneously
+ bufferSize = (numLines * (prefixLen + suffixLen)) + entry->messageLen + 1;
+
+ if (defaultBufferSize >= bufferSize) {
+ ret = defaultBuffer;
+ } else {
+ ret = (char *)malloc(bufferSize);
+
+ if (ret == NULL) {
+ return ret;
+ }
+ }
+
+ ret[0] = '\0'; /* to start strcat off */
+
+ p = ret;
+ pm = entry->message;
+
+ if (prefixSuffixIsHeaderFooter) {
+ strcat(p, prefixBuf);
+ p += prefixLen;
+ strncat(p, entry->message, entry->messageLen);
+ p += entry->messageLen;
+ strcat(p, suffixBuf);
+ p += suffixLen;
+ } else {
+ while(pm < (entry->message + entry->messageLen)) {
+ const char *lineStart;
+ size_t lineLen;
+
+ lineStart = pm;
+
+ // Find the next end-of-line in message
+ while (pm < (entry->message + entry->messageLen)
+ && *pm != '\n') pm++;
+ lineLen = pm - lineStart;
+
+ strcat(p, prefixBuf);
+ p += prefixLen;
+ strncat(p, lineStart, lineLen);
+ p += lineLen;
+ strcat(p, suffixBuf);
+ p += suffixLen;
+
+ if (*pm == '\n') pm++;
+ }
+ }
+
+ if (p_outLength != NULL) {
+ *p_outLength = p - ret;
+ }
+
+ return ret;
+}
+
+/**
+ * Either print or do not print log line, based on filter
+ *
+ * Returns count bytes written
+ */
+
+int android_log_filterAndPrintLogLine(
+ AndroidLogFormat *p_format,
+ int fd,
+ const AndroidLogEntry *entry)
+{
+ int ret;
+ char defaultBuffer[512];
+ char *outBuffer = NULL;
+ size_t totalLen;
+
+ if (0 == android_log_shouldPrintLine(p_format, entry->tag,
+ entry->priority)) {
+ return 0;
+ }
+
+ outBuffer = android_log_formatLogLine(p_format, defaultBuffer,
+ sizeof(defaultBuffer), entry, &totalLen);
+
+ if (!outBuffer)
+ return -1;
+
+ do {
+ ret = write(fd, outBuffer, totalLen);
+ } while (ret < 0 && errno == EINTR);
+
+ if (ret < 0) {
+ fprintf(stderr, "+++ LOG: write failed (errno=%d)\n", errno);
+ ret = 0;
+ goto done;
+ }
+
+ if (((size_t)ret) < totalLen) {
+ fprintf(stderr, "+++ LOG: write partial (%d of %d)\n", ret,
+ (int)totalLen);
+ goto done;
+ }
+
+done:
+ if (outBuffer != defaultBuffer) {
+ free(outBuffer);
+ }
+
+ return ret;
+}
+
+
+
+void logprint_run_tests()
+{
+#if 0
+
+ fprintf(stderr, "tests disabled\n");
+
+#else
+
+ int err;
+ const char *tag;
+ AndroidLogFormat *p_format;
+
+ p_format = android_log_format_new();
+
+ fprintf(stderr, "running tests\n");
+
+ tag = "random";
+
+ android_log_addFilterRule(p_format,"*:i");
+
+ assert (ANDROID_LOG_INFO == filterPriForTag(p_format, "random"));
+ assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) == 0);
+ android_log_addFilterRule(p_format, "*");
+ assert (ANDROID_LOG_DEBUG == filterPriForTag(p_format, "random"));
+ assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
+ android_log_addFilterRule(p_format, "*:v");
+ assert (ANDROID_LOG_VERBOSE == filterPriForTag(p_format, "random"));
+ assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
+ android_log_addFilterRule(p_format, "*:i");
+ assert (ANDROID_LOG_INFO == filterPriForTag(p_format, "random"));
+ assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) == 0);
+
+ android_log_addFilterRule(p_format, "random");
+ assert (ANDROID_LOG_VERBOSE == filterPriForTag(p_format, "random"));
+ assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
+ android_log_addFilterRule(p_format, "random:v");
+ assert (ANDROID_LOG_VERBOSE == filterPriForTag(p_format, "random"));
+ assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
+ android_log_addFilterRule(p_format, "random:d");
+ assert (ANDROID_LOG_DEBUG == filterPriForTag(p_format, "random"));
+ assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) > 0);
+ android_log_addFilterRule(p_format, "random:w");
+ assert (ANDROID_LOG_WARN == filterPriForTag(p_format, "random"));
+ assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) == 0);
+
+ android_log_addFilterRule(p_format, "crap:*");
+ assert (ANDROID_LOG_VERBOSE== filterPriForTag(p_format, "crap"));
+ assert(android_log_shouldPrintLine(p_format, "crap", ANDROID_LOG_VERBOSE) > 0);
+
+ // invalid expression
+ err = android_log_addFilterRule(p_format, "random:z");
+ assert (err < 0);
+ assert (ANDROID_LOG_WARN == filterPriForTag(p_format, "random"));
+ assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) == 0);
+
+ // Issue #550946
+ err = android_log_addFilterString(p_format, " ");
+ assert(err == 0);
+ assert(ANDROID_LOG_WARN == filterPriForTag(p_format, "random"));
+
+ // note trailing space
+ err = android_log_addFilterString(p_format, "*:s random:d ");
+ assert(err == 0);
+ assert(ANDROID_LOG_DEBUG == filterPriForTag(p_format, "random"));
+
+ err = android_log_addFilterString(p_format, "*:s random:z");
+ assert(err < 0);
+
+
+#if 0
+ char *ret;
+ char defaultBuffer[512];
+
+ ret = android_log_formatLogLine(p_format,
+ defaultBuffer, sizeof(defaultBuffer), 0, ANDROID_LOG_ERROR, 123,
+ 123, 123, "random", "nofile", strlen("Hello"), "Hello", NULL);
+#endif
+
+
+ fprintf(stderr, "tests complete\n");
+#endif
+}