Home | History | Annotate | Download | only in liblog
      1 /*
      2  * Copyright (C) 2008-2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 /*
     17  * Intercepts log messages intended for the Android log device.
     18  * When running in the context of the simulator, the messages are
     19  * passed on to the underlying (fake) log device.  When not in the
     20  * simulator, messages are printed to stderr.
     21  */
     22 #include <ctype.h>
     23 #include <errno.h>
     24 #include <fcntl.h>
     25 #if !defined(_WIN32)
     26 #include <pthread.h>
     27 #endif
     28 #include <stdlib.h>
     29 #include <string.h>
     30 
     31 #include <log/logd.h>
     32 
     33 #include "fake_log_device.h"
     34 #include "log_portability.h"
     35 
     36 #define kMaxTagLen  16      /* from the long-dead utils/Log.cpp */
     37 
     38 #define kTagSetSize 16      /* arbitrary */
     39 
     40 #if 0
     41 #define TRACE(...) printf("fake_log_device: " __VA_ARGS__)
     42 #else
     43 #define TRACE(...) ((void)0)
     44 #endif
     45 
     46 /* from the long-dead utils/Log.cpp */
     47 typedef enum {
     48     FORMAT_OFF = 0,
     49     FORMAT_BRIEF,
     50     FORMAT_PROCESS,
     51     FORMAT_TAG,
     52     FORMAT_THREAD,
     53     FORMAT_RAW,
     54     FORMAT_TIME,
     55     FORMAT_THREADTIME,
     56     FORMAT_LONG
     57 } LogFormat;
     58 
     59 
     60 /*
     61  * Log driver state.
     62  */
     63 typedef struct LogState {
     64     /* the fake fd that's seen by the user */
     65     int     fakeFd;
     66 
     67     /* a printable name for this fake device */
     68     char   debugName[sizeof("/dev/log/security")];
     69 
     70     /* nonzero if this is a binary log */
     71     int     isBinary;
     72 
     73     /* global minimum priority */
     74     int     globalMinPriority;
     75 
     76     /* output format */
     77     LogFormat outputFormat;
     78 
     79     /* tags and priorities */
     80     struct {
     81         char    tag[kMaxTagLen];
     82         int     minPriority;
     83     } tagSet[kTagSetSize];
     84 } LogState;
     85 
     86 
     87 #if !defined(_WIN32)
     88 /*
     89  * Locking.  Since we're emulating a device, we need to be prepared
     90  * to have multiple callers at the same time.  This lock is used
     91  * to both protect the fd list and to prevent LogStates from being
     92  * freed out from under a user.
     93  */
     94 static pthread_mutex_t fakeLogDeviceLock = PTHREAD_MUTEX_INITIALIZER;
     95 
     96 static void lock()
     97 {
     98     /*
     99      * If we trigger a signal handler in the middle of locked activity and the
    100      * signal handler logs a message, we could get into a deadlock state.
    101      */
    102     pthread_mutex_lock(&fakeLogDeviceLock);
    103 }
    104 
    105 static void unlock()
    106 {
    107     pthread_mutex_unlock(&fakeLogDeviceLock);
    108 }
    109 
    110 #else   // !defined(_WIN32)
    111 
    112 #define lock() ((void)0)
    113 #define unlock() ((void)0)
    114 
    115 #endif  // !defined(_WIN32)
    116 
    117 
    118 /*
    119  * File descriptor management.
    120  */
    121 #define FAKE_FD_BASE 10000
    122 #define MAX_OPEN_LOGS 8
    123 static LogState openLogTable[MAX_OPEN_LOGS];
    124 
    125 /*
    126  * Allocate an fd and associate a new LogState with it.
    127  * The fd is available via the fakeFd field of the return value.
    128  */
    129 static LogState *createLogState()
    130 {
    131     size_t i;
    132 
    133     for (i = 0; i < (sizeof(openLogTable) / sizeof(openLogTable[0])); i++) {
    134         if (openLogTable[i].fakeFd == 0) {
    135             openLogTable[i].fakeFd = FAKE_FD_BASE + i;
    136             return &openLogTable[i];
    137         }
    138     }
    139     return NULL;
    140 }
    141 
    142 /*
    143  * Translate an fd to a LogState.
    144  */
    145 static LogState *fdToLogState(int fd)
    146 {
    147     if (fd >= FAKE_FD_BASE && fd < FAKE_FD_BASE + MAX_OPEN_LOGS) {
    148         return &openLogTable[fd - FAKE_FD_BASE];
    149     }
    150     return NULL;
    151 }
    152 
    153 /*
    154  * Unregister the fake fd and free the memory it pointed to.
    155  */
    156 static void deleteFakeFd(int fd)
    157 {
    158     LogState *ls;
    159 
    160     lock();
    161 
    162     ls = fdToLogState(fd);
    163     if (ls != NULL) {
    164         memset(&openLogTable[fd - FAKE_FD_BASE], 0, sizeof(openLogTable[0]));
    165     }
    166 
    167     unlock();
    168 }
    169 
    170 /*
    171  * Configure logging based on ANDROID_LOG_TAGS environment variable.  We
    172  * need to parse a string that looks like
    173  *
    174  *   *:v jdwp:d dalvikvm:d dalvikvm-gc:i dalvikvmi:i
    175  *
    176  * The tag (or '*' for the global level) comes first, followed by a colon
    177  * and a letter indicating the minimum priority level we're expected to log.
    178  * This can be used to reveal or conceal logs with specific tags.
    179  *
    180  * We also want to check ANDROID_PRINTF_LOG to determine how the output
    181  * will look.
    182  */
    183 static void configureInitialState(const char* pathName, LogState* logState)
    184 {
    185     static const int kDevLogLen = sizeof("/dev/log/") - 1;
    186 
    187     strncpy(logState->debugName, pathName, sizeof(logState->debugName));
    188     logState->debugName[sizeof(logState->debugName) - 1] = '\0';
    189 
    190     /* identify binary logs */
    191     if (!strcmp(pathName + kDevLogLen, "events") ||
    192             !strcmp(pathName + kDevLogLen, "security")) {
    193         logState->isBinary = 1;
    194     }
    195 
    196     /* global min priority defaults to "info" level */
    197     logState->globalMinPriority = ANDROID_LOG_INFO;
    198 
    199     /*
    200      * This is based on the the long-dead utils/Log.cpp code.
    201      */
    202     const char* tags = getenv("ANDROID_LOG_TAGS");
    203     TRACE("Found ANDROID_LOG_TAGS='%s'\n", tags);
    204     if (tags != NULL) {
    205         int entry = 0;
    206 
    207         while (*tags != '\0') {
    208             char tagName[kMaxTagLen];
    209             int i, minPrio;
    210 
    211             while (isspace(*tags))
    212                 tags++;
    213 
    214             i = 0;
    215             while (*tags != '\0' && !isspace(*tags) && *tags != ':' &&
    216                     i < kMaxTagLen) {
    217                 tagName[i++] = *tags++;
    218             }
    219             if (i == kMaxTagLen) {
    220                 TRACE("ERROR: env tag too long (%d chars max)\n", kMaxTagLen-1);
    221                 return;
    222             }
    223             tagName[i] = '\0';
    224 
    225             /* default priority, if there's no ":" part; also zero out '*' */
    226             minPrio = ANDROID_LOG_VERBOSE;
    227             if (tagName[0] == '*' && tagName[1] == '\0') {
    228                 minPrio = ANDROID_LOG_DEBUG;
    229                 tagName[0] = '\0';
    230             }
    231 
    232             if (*tags == ':') {
    233                 tags++;
    234                 if (*tags >= '0' && *tags <= '9') {
    235                     if (*tags >= ('0' + ANDROID_LOG_SILENT))
    236                         minPrio = ANDROID_LOG_VERBOSE;
    237                     else
    238                         minPrio = *tags - '\0';
    239                 } else {
    240                     switch (*tags) {
    241                     case 'v':   minPrio = ANDROID_LOG_VERBOSE;  break;
    242                     case 'd':   minPrio = ANDROID_LOG_DEBUG;    break;
    243                     case 'i':   minPrio = ANDROID_LOG_INFO;     break;
    244                     case 'w':   minPrio = ANDROID_LOG_WARN;     break;
    245                     case 'e':   minPrio = ANDROID_LOG_ERROR;    break;
    246                     case 'f':   minPrio = ANDROID_LOG_FATAL;    break;
    247                     case 's':   minPrio = ANDROID_LOG_SILENT;   break;
    248                     default:    minPrio = ANDROID_LOG_DEFAULT;  break;
    249                     }
    250                 }
    251 
    252                 tags++;
    253                 if (*tags != '\0' && !isspace(*tags)) {
    254                     TRACE("ERROR: garbage in tag env; expected whitespace\n");
    255                     TRACE("       env='%s'\n", tags);
    256                     return;
    257                 }
    258             }
    259 
    260             if (tagName[0] == 0) {
    261                 logState->globalMinPriority = minPrio;
    262                 TRACE("+++ global min prio %d\n", logState->globalMinPriority);
    263             } else {
    264                 logState->tagSet[entry].minPriority = minPrio;
    265                 strcpy(logState->tagSet[entry].tag, tagName);
    266                 TRACE("+++ entry %d: %s:%d\n",
    267                     entry,
    268                     logState->tagSet[entry].tag,
    269                     logState->tagSet[entry].minPriority);
    270                 entry++;
    271             }
    272         }
    273     }
    274 
    275 
    276     /*
    277      * Taken from the long-dead utils/Log.cpp
    278      */
    279     const char* fstr = getenv("ANDROID_PRINTF_LOG");
    280     LogFormat format;
    281     if (fstr == NULL) {
    282         format = FORMAT_BRIEF;
    283     } else {
    284         if (strcmp(fstr, "brief") == 0)
    285             format = FORMAT_BRIEF;
    286         else if (strcmp(fstr, "process") == 0)
    287             format = FORMAT_PROCESS;
    288         else if (strcmp(fstr, "tag") == 0)
    289             format = FORMAT_PROCESS;
    290         else if (strcmp(fstr, "thread") == 0)
    291             format = FORMAT_PROCESS;
    292         else if (strcmp(fstr, "raw") == 0)
    293             format = FORMAT_PROCESS;
    294         else if (strcmp(fstr, "time") == 0)
    295             format = FORMAT_PROCESS;
    296         else if (strcmp(fstr, "long") == 0)
    297             format = FORMAT_PROCESS;
    298         else
    299             format = (LogFormat) atoi(fstr);        // really?!
    300     }
    301 
    302     logState->outputFormat = format;
    303 }
    304 
    305 /*
    306  * Return a human-readable string for the priority level.  Always returns
    307  * a valid string.
    308  */
    309 static const char* getPriorityString(int priority)
    310 {
    311     /* the first character of each string should be unique */
    312     static const char* priorityStrings[] = {
    313         "Verbose", "Debug", "Info", "Warn", "Error", "Assert"
    314     };
    315     int idx;
    316 
    317     idx = (int)priority - (int)ANDROID_LOG_VERBOSE;
    318     if (idx < 0 ||
    319             idx >= (int)(sizeof(priorityStrings) / sizeof(priorityStrings[0])))
    320         return "?unknown?";
    321     return priorityStrings[idx];
    322 }
    323 
    324 #if defined(_WIN32)
    325 /*
    326  * WIN32 does not have writev().
    327  * Make up something to replace it.
    328  */
    329 static ssize_t fake_writev(int fd, const struct iovec *iov, int iovcnt) {
    330     ssize_t result = 0;
    331     const struct iovec* end = iov + iovcnt;
    332     for (; iov < end; iov++) {
    333         ssize_t w = write(fd, iov->iov_base, iov->iov_len);
    334         if (w != (ssize_t) iov->iov_len) {
    335             if (w < 0)
    336                 return w;
    337             return result + w;
    338         }
    339         result += w;
    340     }
    341     return result;
    342 }
    343 
    344 #define writev fake_writev
    345 #endif
    346 
    347 
    348 /*
    349  * Write a filtered log message to stderr.
    350  *
    351  * Log format parsing taken from the long-dead utils/Log.cpp.
    352  */
    353 static void showLog(LogState *state,
    354         int logPrio, const char* tag, const char* msg)
    355 {
    356 #if !defined(_WIN32)
    357     struct tm tmBuf;
    358 #endif
    359     struct tm* ptm;
    360     char timeBuf[32];
    361     char prefixBuf[128], suffixBuf[128];
    362     char priChar;
    363     time_t when;
    364 #if !defined(_WIN32)
    365     pid_t pid, tid;
    366 #else
    367     uint32_t pid, tid;
    368 #endif
    369 
    370     TRACE("LOG %d: %s %s", logPrio, tag, msg);
    371 
    372     priChar = getPriorityString(logPrio)[0];
    373     when = time(NULL);
    374     pid = tid = getpid();       // find gettid()?
    375 
    376     /*
    377      * Get the current date/time in pretty form
    378      *
    379      * It's often useful when examining a log with "less" to jump to
    380      * a specific point in the file by searching for the date/time stamp.
    381      * For this reason it's very annoying to have regexp meta characters
    382      * in the time stamp.  Don't use forward slashes, parenthesis,
    383      * brackets, asterisks, or other special chars here.
    384      */
    385 #if !defined(_WIN32)
    386     ptm = localtime_r(&when, &tmBuf);
    387 #else
    388     ptm = localtime(&when);
    389 #endif
    390     //strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", ptm);
    391     strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
    392 
    393     /*
    394      * Construct a buffer containing the log header and log message.
    395      */
    396     size_t prefixLen, suffixLen;
    397 
    398     switch (state->outputFormat) {
    399     case FORMAT_TAG:
    400         prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
    401             "%c/%-8s: ", priChar, tag);
    402         strcpy(suffixBuf, "\n"); suffixLen = 1;
    403         break;
    404     case FORMAT_PROCESS:
    405         prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
    406             "%c(%5d) ", priChar, pid);
    407         suffixLen = snprintf(suffixBuf, sizeof(suffixBuf),
    408             "  (%s)\n", tag);
    409         break;
    410     case FORMAT_THREAD:
    411         prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
    412             "%c(%5d:%5d) ", priChar, pid, tid);
    413         strcpy(suffixBuf, "\n"); suffixLen = 1;
    414         break;
    415     case FORMAT_RAW:
    416         prefixBuf[0] = 0; prefixLen = 0;
    417         strcpy(suffixBuf, "\n"); suffixLen = 1;
    418         break;
    419     case FORMAT_TIME:
    420         prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
    421             "%s %-8s\n\t", timeBuf, tag);
    422         strcpy(suffixBuf, "\n"); suffixLen = 1;
    423         break;
    424     case FORMAT_THREADTIME:
    425         prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
    426             "%s %5d %5d %c %-8s \n\t", timeBuf, pid, tid, priChar, tag);
    427         strcpy(suffixBuf, "\n"); suffixLen = 1;
    428         break;
    429     case FORMAT_LONG:
    430         prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
    431             "[ %s %5d:%5d %c/%-8s ]\n",
    432             timeBuf, pid, tid, priChar, tag);
    433         strcpy(suffixBuf, "\n\n"); suffixLen = 2;
    434         break;
    435     default:
    436         prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
    437             "%c/%-8s(%5d): ", priChar, tag, pid);
    438         strcpy(suffixBuf, "\n"); suffixLen = 1;
    439         break;
    440      }
    441 
    442     /*
    443      * Figure out how many lines there will be.
    444      */
    445     const char* end = msg + strlen(msg);
    446     size_t numLines = 0;
    447     const char* p = msg;
    448     while (p < end) {
    449         if (*p++ == '\n') numLines++;
    450     }
    451     if (p > msg && *(p-1) != '\n') {
    452         numLines++;
    453     }
    454 
    455     /*
    456      * Create an array of iovecs large enough to write all of
    457      * the lines with a prefix and a suffix.
    458      */
    459     const size_t INLINE_VECS = 64;
    460     const size_t MAX_LINES   = ((size_t)~0)/(3*sizeof(struct iovec*));
    461     struct iovec stackVec[INLINE_VECS];
    462     struct iovec* vec = stackVec;
    463     size_t numVecs;
    464 
    465     if (numLines > MAX_LINES)
    466         numLines = MAX_LINES;
    467 
    468     numVecs = numLines * 3;  // 3 iovecs per line.
    469     if (numVecs > INLINE_VECS) {
    470         vec = (struct iovec*)malloc(sizeof(struct iovec)*numVecs);
    471         if (vec == NULL) {
    472             msg = "LOG: write failed, no memory";
    473             numVecs = INLINE_VECS;
    474             numLines = numVecs / 3;
    475             vec = stackVec;
    476         }
    477     }
    478 
    479     /*
    480      * Fill in the iovec pointers.
    481      */
    482     p = msg;
    483     struct iovec* v = vec;
    484     int totalLen = 0;
    485     while (numLines > 0 && p < end) {
    486         if (prefixLen > 0) {
    487             v->iov_base = prefixBuf;
    488             v->iov_len = prefixLen;
    489             totalLen += prefixLen;
    490             v++;
    491         }
    492         const char* start = p;
    493         while (p < end && *p != '\n') {
    494             p++;
    495         }
    496         if ((p-start) > 0) {
    497             v->iov_base = (void*)start;
    498             v->iov_len = p-start;
    499             totalLen += p-start;
    500             v++;
    501         }
    502         if (*p == '\n') p++;
    503         if (suffixLen > 0) {
    504             v->iov_base = suffixBuf;
    505             v->iov_len = suffixLen;
    506             totalLen += suffixLen;
    507             v++;
    508         }
    509         numLines -= 1;
    510     }
    511 
    512     /*
    513      * Write the entire message to the log file with a single writev() call.
    514      * We need to use this rather than a collection of printf()s on a FILE*
    515      * because of multi-threading and multi-process issues.
    516      *
    517      * If the file was not opened with O_APPEND, this will produce interleaved
    518      * output when called on the same file from multiple processes.
    519      *
    520      * If the file descriptor is actually a network socket, the writev()
    521      * call may return with a partial write.  Putting the writev() call in
    522      * a loop can result in interleaved data.  This can be alleviated
    523      * somewhat by wrapping the writev call in the Mutex.
    524      */
    525 
    526     for(;;) {
    527         int cc = writev(fileno(stderr), vec, v-vec);
    528 
    529         if (cc == totalLen) break;
    530 
    531         if (cc < 0) {
    532             if(errno == EINTR) continue;
    533 
    534                 /* can't really log the failure; for now, throw out a stderr */
    535             fprintf(stderr, "+++ LOG: write failed (errno=%d)\n", errno);
    536             break;
    537         } else {
    538                 /* shouldn't happen when writing to file or tty */
    539             fprintf(stderr, "+++ LOG: write partial (%d of %d)\n", cc, totalLen);
    540             break;
    541         }
    542     }
    543 
    544     /* if we allocated storage for the iovecs, free it */
    545     if (vec != stackVec)
    546         free(vec);
    547 }
    548 
    549 
    550 /*
    551  * Receive a log message.  We happen to know that "vector" has three parts:
    552  *
    553  *  priority (1 byte)
    554  *  tag (N bytes -- null-terminated ASCII string)
    555  *  message (N bytes -- null-terminated ASCII string)
    556  */
    557 static ssize_t logWritev(int fd, const struct iovec* vector, int count)
    558 {
    559     LogState* state;
    560 
    561     /* Make sure that no-one frees the LogState while we're using it.
    562      * Also guarantees that only one thread is in showLog() at a given
    563      * time (if it matters).
    564      */
    565     lock();
    566 
    567     state = fdToLogState(fd);
    568     if (state == NULL) {
    569         errno = EBADF;
    570         goto error;
    571     }
    572 
    573     if (state->isBinary) {
    574         TRACE("%s: ignoring binary log\n", state->debugName);
    575         goto bail;
    576     }
    577 
    578     if (count != 3) {
    579         TRACE("%s: writevLog with count=%d not expected\n",
    580             state->debugName, count);
    581         goto error;
    582     }
    583 
    584     /* pull out the three fields */
    585     int logPrio = *(const char*)vector[0].iov_base;
    586     const char* tag = (const char*) vector[1].iov_base;
    587     const char* msg = (const char*) vector[2].iov_base;
    588 
    589     /* see if this log tag is configured */
    590     int i;
    591     int minPrio = state->globalMinPriority;
    592     for (i = 0; i < kTagSetSize; i++) {
    593         if (state->tagSet[i].minPriority == ANDROID_LOG_UNKNOWN)
    594             break;      /* reached end of configured values */
    595 
    596         if (strcmp(state->tagSet[i].tag, tag) == 0) {
    597             //TRACE("MATCH tag '%s'\n", tag);
    598             minPrio = state->tagSet[i].minPriority;
    599             break;
    600         }
    601     }
    602 
    603     if (logPrio >= minPrio) {
    604         showLog(state, logPrio, tag, msg);
    605     } else {
    606         //TRACE("+++ NOLOG(%d): %s %s", logPrio, tag, msg);
    607     }
    608 
    609 bail:
    610     unlock();
    611     return vector[0].iov_len + vector[1].iov_len + vector[2].iov_len;
    612 error:
    613     unlock();
    614     return -1;
    615 }
    616 
    617 /*
    618  * Free up our state and close the fake descriptor.
    619  */
    620 static int logClose(int fd)
    621 {
    622     deleteFakeFd(fd);
    623     return 0;
    624 }
    625 
    626 /*
    627  * Open a log output device and return a fake fd.
    628  */
    629 static int logOpen(const char* pathName, int flags __unused)
    630 {
    631     LogState *logState;
    632     int fd = -1;
    633 
    634     lock();
    635 
    636     logState = createLogState();
    637     if (logState != NULL) {
    638         configureInitialState(pathName, logState);
    639         fd = logState->fakeFd;
    640     } else  {
    641         errno = ENFILE;
    642     }
    643 
    644     unlock();
    645 
    646     return fd;
    647 }
    648 
    649 
    650 /*
    651  * Runtime redirection.  If this binary is running in the simulator,
    652  * just pass log messages to the emulated device.  If it's running
    653  * outside of the simulator, write the log messages to stderr.
    654  */
    655 
    656 static int (*redirectOpen)(const char *pathName, int flags) = NULL;
    657 static int (*redirectClose)(int fd) = NULL;
    658 static ssize_t (*redirectWritev)(int fd, const struct iovec* vector, int count)
    659         = NULL;
    660 
    661 static void setRedirects()
    662 {
    663     const char *ws;
    664 
    665     /* Wrapsim sets this environment variable on children that it's
    666      * created using its LD_PRELOAD wrapper.
    667      */
    668     ws = getenv("ANDROID_WRAPSIM");
    669     if (ws != NULL && strcmp(ws, "1") == 0) {
    670         /* We're running inside wrapsim, so we can just write to the device. */
    671         redirectOpen = (int (*)(const char *pathName, int flags))open;
    672         redirectClose = close;
    673         redirectWritev = writev;
    674     } else {
    675         /* There's no device to delegate to; handle the logging ourselves. */
    676         redirectOpen = logOpen;
    677         redirectClose = logClose;
    678         redirectWritev = logWritev;
    679     }
    680 }
    681 
    682 LIBLOG_HIDDEN int fakeLogOpen(const char *pathName, int flags)
    683 {
    684     if (redirectOpen == NULL) {
    685         setRedirects();
    686     }
    687     return redirectOpen(pathName, flags);
    688 }
    689 
    690 /*
    691  * The logger API has no means or need to 'stop' or 'close' using the logs,
    692  * and as such, there is no way for that 'stop' or 'close' to translate into
    693  * a close operation to the fake log handler. fakeLogClose is provided for
    694  * completeness only.
    695  *
    696  * We have no intention of adding a log close operation as it would complicate
    697  * every user of the logging API with no gain since the only valid place to
    698  * call is in the exit handler. Logging can continue in the exit handler to
    699  * help debug HOST tools ...
    700  */
    701 LIBLOG_HIDDEN int fakeLogClose(int fd)
    702 {
    703     /* Assume that open() was called first. */
    704     return redirectClose(fd);
    705 }
    706 
    707 LIBLOG_HIDDEN ssize_t fakeLogWritev(int fd,
    708                                     const struct iovec* vector, int count)
    709 {
    710     /* Assume that open() was called first. */
    711     return redirectWritev(fd, vector, count);
    712 }
    713 
    714 LIBLOG_ABI_PUBLIC int __android_log_is_loggable(int prio,
    715                                                 const char *tag __unused,
    716                                                 int def)
    717 {
    718     int logLevel = def;
    719     return logLevel >= 0 && prio >= logLevel;
    720 }
    721