/* * 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. */ /* * Thread that reads from stdout/stderr and converts them to log messages. * (Sort of a hack.) */ #include "Dalvik.h" #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #define kFilenoStdout 1 #define kFilenoStderr 2 #define kMaxLine 512 /* * Hold some data. */ struct BufferedData { char buf[kMaxLine+1]; int count; }; // fwd static void* stdioConverterThreadStart(void* arg); static bool readAndLog(int fd, BufferedData* data, const char* tag); /* * Crank up the stdout/stderr converter thread. * * Returns immediately. */ bool dvmStdioConverterStartup() { gDvm.haltStdioConverter = false; dvmInitMutex(&gDvm.stdioConverterLock); pthread_cond_init(&gDvm.stdioConverterCond, NULL); if (pipe(gDvm.stdoutPipe) != 0) { ALOGW("pipe failed: %s", strerror(errno)); return false; } if (pipe(gDvm.stderrPipe) != 0) { ALOGW("pipe failed: %s", strerror(errno)); return false; } if (dup2(gDvm.stdoutPipe[1], kFilenoStdout) != kFilenoStdout) { ALOGW("dup2(1) failed: %s", strerror(errno)); return false; } close(gDvm.stdoutPipe[1]); gDvm.stdoutPipe[1] = -1; #ifdef HAVE_ANDROID_OS /* don't redirect stderr on sim -- logs get written there! */ /* (don't need this on the sim anyway) */ if (dup2(gDvm.stderrPipe[1], kFilenoStderr) != kFilenoStderr) { ALOGW("dup2(2) failed: %d %s", errno, strerror(errno)); return false; } close(gDvm.stderrPipe[1]); gDvm.stderrPipe[1] = -1; #endif /* * Create the thread. */ dvmLockMutex(&gDvm.stdioConverterLock); if (!dvmCreateInternalThread(&gDvm.stdioConverterHandle, "Stdio Converter", stdioConverterThreadStart, NULL)) { return false; } while (!gDvm.stdioConverterReady) { dvmWaitCond(&gDvm.stdioConverterCond, &gDvm.stdioConverterLock); } dvmUnlockMutex(&gDvm.stdioConverterLock); return true; } /* * Shut down the stdio converter thread if it was started. * * Since we know the thread is just sitting around waiting for something * to arrive on stdout, print something. */ void dvmStdioConverterShutdown() { gDvm.haltStdioConverter = true; if (gDvm.stdioConverterHandle == 0) // not started, or still starting return; /* print something to wake it up */ printf("Shutting down\n"); fflush(stdout); ALOGD("Joining stdio converter..."); pthread_join(gDvm.stdioConverterHandle, NULL); } /* * Select on stdout/stderr pipes, waiting for activity. * * DO NOT use printf from here. */ static void* stdioConverterThreadStart(void* arg) { int cc; /* tell the main thread that we're ready */ dvmLockMutex(&gDvm.stdioConverterLock); gDvm.stdioConverterReady = true; cc = pthread_cond_signal(&gDvm.stdioConverterCond); assert(cc == 0); dvmUnlockMutex(&gDvm.stdioConverterLock); /* we never do anything that affects the rest of the VM */ dvmChangeStatus(NULL, THREAD_VMWAIT); /* * Allocate read buffers. */ BufferedData* stdoutData = new BufferedData; BufferedData* stderrData = new BufferedData; stdoutData->count = stderrData->count = 0; /* * Read until shutdown time. */ while (!gDvm.haltStdioConverter) { fd_set readfds; int maxFd, fdCount; FD_ZERO(&readfds); FD_SET(gDvm.stdoutPipe[0], &readfds); FD_SET(gDvm.stderrPipe[0], &readfds); maxFd = MAX(gDvm.stdoutPipe[0], gDvm.stderrPipe[0]); fdCount = select(maxFd+1, &readfds, NULL, NULL, NULL); if (fdCount < 0) { if (errno != EINTR) { ALOGE("select on stdout/stderr failed"); break; } ALOGD("Got EINTR, ignoring"); } else if (fdCount == 0) { ALOGD("WEIRD: select returned zero"); } else { bool err = false; if (FD_ISSET(gDvm.stdoutPipe[0], &readfds)) { err |= !readAndLog(gDvm.stdoutPipe[0], stdoutData, "stdout"); } if (FD_ISSET(gDvm.stderrPipe[0], &readfds)) { err |= !readAndLog(gDvm.stderrPipe[0], stderrData, "stderr"); } /* probably EOF; give up */ if (err) { ALOGW("stdio converter got read error; shutting it down"); break; } } } close(gDvm.stdoutPipe[0]); close(gDvm.stderrPipe[0]); delete stdoutData; delete stderrData; /* change back for shutdown sequence */ dvmChangeStatus(NULL, THREAD_RUNNING); return NULL; } /* * Data is pending on "fd". Read as much as will fit in "data", then * write out any full lines and compact "data". */ static bool readAndLog(int fd, BufferedData* data, const char* tag) { ssize_t actual; size_t want; assert(data->count < kMaxLine); want = kMaxLine - data->count; actual = read(fd, data->buf + data->count, want); if (actual <= 0) { ALOGW("read %s: (%d,%d) failed (%d): %s", tag, fd, want, (int)actual, strerror(errno)); return false; } else { //ALOGI("read %s: %d at %d", tag, actual, data->count); } data->count += actual; /* * Got more data, look for an EOL. We expect LF or CRLF, but will * try to handle a standalone CR. */ char* cp = data->buf; const char* start = data->buf; int i = data->count; for (i = data->count; i > 0; i--, cp++) { if (*cp == '\n' || (*cp == '\r' && i != 0 && *(cp+1) != '\n')) { *cp = '\0'; //ALOGW("GOT %d at %d '%s'", cp - start, start - data->buf, start); ALOG(LOG_INFO, tag, "%s", start); start = cp+1; } } /* * See if we overflowed. If so, cut it off. */ if (start == data->buf && data->count == kMaxLine) { data->buf[kMaxLine] = '\0'; ALOG(LOG_INFO, tag, "%s!", start); start = cp + kMaxLine; } /* * Update "data" if we consumed some output. If there's anything left * in the buffer, it's because we didn't see an EOL and need to keep * reading until we see one. */ if (start != data->buf) { if (start >= data->buf + data->count) { /* consumed all available */ data->count = 0; } else { /* some left over */ int remaining = data->count - (start - data->buf); memmove(data->buf, start, remaining); data->count = remaining; } } return true; }