Home | History | Annotate | Download | only in vm
      1 /*
      2  * Copyright (C) 2008 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  * Thread that reads from stdout/stderr and converts them to log messages.
     18  * (Sort of a hack.)
     19  */
     20 #include "Dalvik.h"
     21 
     22 #include <stdlib.h>
     23 #include <unistd.h>
     24 #include <fcntl.h>
     25 #include <errno.h>
     26 
     27 #define kFilenoStdout   1
     28 #define kFilenoStderr   2
     29 
     30 #define kMaxLine    512
     31 
     32 /*
     33  * Hold some data.
     34  */
     35 struct BufferedData {
     36     char    buf[kMaxLine+1];
     37     int     count;
     38 };
     39 
     40 // fwd
     41 static void* stdioConverterThreadStart(void* arg);
     42 static bool readAndLog(int fd, BufferedData* data, const char* tag);
     43 
     44 
     45 /*
     46  * Crank up the stdout/stderr converter thread.
     47  *
     48  * Returns immediately.
     49  */
     50 bool dvmStdioConverterStartup()
     51 {
     52     gDvm.haltStdioConverter = false;
     53 
     54     dvmInitMutex(&gDvm.stdioConverterLock);
     55     pthread_cond_init(&gDvm.stdioConverterCond, NULL);
     56 
     57     if (pipe(gDvm.stdoutPipe) != 0) {
     58         ALOGW("pipe failed: %s", strerror(errno));
     59         return false;
     60     }
     61     if (pipe(gDvm.stderrPipe) != 0) {
     62         ALOGW("pipe failed: %s", strerror(errno));
     63         return false;
     64     }
     65 
     66     if (dup2(gDvm.stdoutPipe[1], kFilenoStdout) != kFilenoStdout) {
     67         ALOGW("dup2(1) failed: %s", strerror(errno));
     68         return false;
     69     }
     70     close(gDvm.stdoutPipe[1]);
     71     gDvm.stdoutPipe[1] = -1;
     72 #ifdef HAVE_ANDROID_OS
     73     /* don't redirect stderr on sim -- logs get written there! */
     74     /* (don't need this on the sim anyway) */
     75     if (dup2(gDvm.stderrPipe[1], kFilenoStderr) != kFilenoStderr) {
     76         ALOGW("dup2(2) failed: %d %s", errno, strerror(errno));
     77         return false;
     78     }
     79     close(gDvm.stderrPipe[1]);
     80     gDvm.stderrPipe[1] = -1;
     81 #endif
     82 
     83 
     84     /*
     85      * Create the thread.
     86      */
     87     dvmLockMutex(&gDvm.stdioConverterLock);
     88 
     89     if (!dvmCreateInternalThread(&gDvm.stdioConverterHandle,
     90                                  "Stdio Converter",
     91                                  stdioConverterThreadStart,
     92                                  NULL)) {
     93         return false;
     94     }
     95 
     96     while (!gDvm.stdioConverterReady) {
     97         dvmWaitCond(&gDvm.stdioConverterCond, &gDvm.stdioConverterLock);
     98     }
     99     dvmUnlockMutex(&gDvm.stdioConverterLock);
    100 
    101     return true;
    102 }
    103 
    104 /*
    105  * Shut down the stdio converter thread if it was started.
    106  *
    107  * Since we know the thread is just sitting around waiting for something
    108  * to arrive on stdout, print something.
    109  */
    110 void dvmStdioConverterShutdown()
    111 {
    112     gDvm.haltStdioConverter = true;
    113     if (gDvm.stdioConverterHandle == 0)    // not started, or still starting
    114         return;
    115 
    116     /* print something to wake it up */
    117     printf("Shutting down\n");
    118     fflush(stdout);
    119 
    120     ALOGD("Joining stdio converter...");
    121     pthread_join(gDvm.stdioConverterHandle, NULL);
    122 }
    123 
    124 /*
    125  * Select on stdout/stderr pipes, waiting for activity.
    126  *
    127  * DO NOT use printf from here.
    128  */
    129 static void* stdioConverterThreadStart(void* arg)
    130 {
    131     int cc;
    132 
    133     /* tell the main thread that we're ready */
    134     dvmLockMutex(&gDvm.stdioConverterLock);
    135     gDvm.stdioConverterReady = true;
    136     cc = pthread_cond_signal(&gDvm.stdioConverterCond);
    137     assert(cc == 0);
    138     dvmUnlockMutex(&gDvm.stdioConverterLock);
    139 
    140     /* we never do anything that affects the rest of the VM */
    141     dvmChangeStatus(NULL, THREAD_VMWAIT);
    142 
    143     /*
    144      * Allocate read buffers.
    145      */
    146     BufferedData* stdoutData = new BufferedData;
    147     BufferedData* stderrData = new BufferedData;
    148     stdoutData->count = stderrData->count = 0;
    149 
    150     /*
    151      * Read until shutdown time.
    152      */
    153     while (!gDvm.haltStdioConverter) {
    154         fd_set readfds;
    155         int maxFd, fdCount;
    156 
    157         FD_ZERO(&readfds);
    158         FD_SET(gDvm.stdoutPipe[0], &readfds);
    159         FD_SET(gDvm.stderrPipe[0], &readfds);
    160         maxFd = MAX(gDvm.stdoutPipe[0], gDvm.stderrPipe[0]);
    161 
    162         fdCount = select(maxFd+1, &readfds, NULL, NULL, NULL);
    163 
    164         if (fdCount < 0) {
    165             if (errno != EINTR) {
    166                 ALOGE("select on stdout/stderr failed");
    167                 break;
    168             }
    169             ALOGD("Got EINTR, ignoring");
    170         } else if (fdCount == 0) {
    171             ALOGD("WEIRD: select returned zero");
    172         } else {
    173             bool err = false;
    174             if (FD_ISSET(gDvm.stdoutPipe[0], &readfds)) {
    175                 err |= !readAndLog(gDvm.stdoutPipe[0], stdoutData,
    176                     "stdout");
    177             }
    178             if (FD_ISSET(gDvm.stderrPipe[0], &readfds)) {
    179                 err |= !readAndLog(gDvm.stderrPipe[0], stderrData,
    180                     "stderr");
    181             }
    182 
    183             /* probably EOF; give up */
    184             if (err) {
    185                 ALOGW("stdio converter got read error; shutting it down");
    186                 break;
    187             }
    188         }
    189     }
    190 
    191     close(gDvm.stdoutPipe[0]);
    192     close(gDvm.stderrPipe[0]);
    193 
    194     delete stdoutData;
    195     delete stderrData;
    196 
    197     /* change back for shutdown sequence */
    198     dvmChangeStatus(NULL, THREAD_RUNNING);
    199     return NULL;
    200 }
    201 
    202 /*
    203  * Data is pending on "fd".  Read as much as will fit in "data", then
    204  * write out any full lines and compact "data".
    205  */
    206 static bool readAndLog(int fd, BufferedData* data, const char* tag)
    207 {
    208     ssize_t actual;
    209     size_t want;
    210 
    211     assert(data->count < kMaxLine);
    212 
    213     want = kMaxLine - data->count;
    214     actual = read(fd, data->buf + data->count, want);
    215     if (actual <= 0) {
    216         ALOGW("read %s: (%d,%d) failed (%d): %s",
    217             tag, fd, want, (int)actual, strerror(errno));
    218         return false;
    219     } else {
    220         //ALOGI("read %s: %d at %d", tag, actual, data->count);
    221     }
    222     data->count += actual;
    223 
    224     /*
    225      * Got more data, look for an EOL.  We expect LF or CRLF, but will
    226      * try to handle a standalone CR.
    227      */
    228     char* cp = data->buf;
    229     const char* start = data->buf;
    230     int i = data->count;
    231     for (i = data->count; i > 0; i--, cp++) {
    232         if (*cp == '\n' || (*cp == '\r' && i != 0 && *(cp+1) != '\n')) {
    233             *cp = '\0';
    234             //ALOGW("GOT %d at %d '%s'", cp - start, start - data->buf, start);
    235             ALOG(LOG_INFO, tag, "%s", start);
    236             start = cp+1;
    237         }
    238     }
    239 
    240     /*
    241      * See if we overflowed.  If so, cut it off.
    242      */
    243     if (start == data->buf && data->count == kMaxLine) {
    244         data->buf[kMaxLine] = '\0';
    245         ALOG(LOG_INFO, tag, "%s!", start);
    246         start = cp + kMaxLine;
    247     }
    248 
    249     /*
    250      * Update "data" if we consumed some output.  If there's anything left
    251      * in the buffer, it's because we didn't see an EOL and need to keep
    252      * reading until we see one.
    253      */
    254     if (start != data->buf) {
    255         if (start >= data->buf + data->count) {
    256             /* consumed all available */
    257             data->count = 0;
    258         } else {
    259             /* some left over */
    260             int remaining = data->count - (start - data->buf);
    261             memmove(data->buf, start, remaining);
    262             data->count = remaining;
    263         }
    264     }
    265 
    266     return true;
    267 }
    268