diff options
Diffstat (limited to 'vm/StdioConverter.cpp')
-rw-r--r-- | vm/StdioConverter.cpp | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/vm/StdioConverter.cpp b/vm/StdioConverter.cpp new file mode 100644 index 000000000..6a4d84584 --- /dev/null +++ b/vm/StdioConverter.cpp @@ -0,0 +1,286 @@ +/* + * 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 + +/* + * Hold our replacement stdout/stderr. + */ +typedef struct StdPipes { + int stdoutPipe[2]; + int stderrPipe[2]; +} StdPipes; + +#define kMaxLine 512 + +/* + * Hold some data. + */ +typedef struct BufferedData { + char buf[kMaxLine+1]; + int count; +} BufferedData; + +// 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(void) +{ + StdPipes* pipeStorage; + + gDvm.haltStdioConverter = false; + + dvmInitMutex(&gDvm.stdioConverterLock); + pthread_cond_init(&gDvm.stdioConverterCond, NULL); + + pipeStorage = (StdPipes*) malloc(sizeof(StdPipes)); + if (pipeStorage == NULL) + return false; + + if (pipe(pipeStorage->stdoutPipe) != 0) { + LOGW("pipe failed: %s\n", strerror(errno)); + return false; + } + if (pipe(pipeStorage->stderrPipe) != 0) { + LOGW("pipe failed: %s\n", strerror(errno)); + return false; + } + + if (dup2(pipeStorage->stdoutPipe[1], kFilenoStdout) != kFilenoStdout) { + LOGW("dup2(1) failed: %s\n", strerror(errno)); + return false; + } + close(pipeStorage->stdoutPipe[1]); + pipeStorage->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(pipeStorage->stderrPipe[1], kFilenoStderr) != kFilenoStderr) { + LOGW("dup2(2) failed: %d %s\n", errno, strerror(errno)); + return false; + } + close(pipeStorage->stderrPipe[1]); + pipeStorage->stderrPipe[1] = -1; +#endif + + + /* + * Create the thread. + */ + dvmLockMutex(&gDvm.stdioConverterLock); + + if (!dvmCreateInternalThread(&gDvm.stdioConverterHandle, + "Stdio Converter", stdioConverterThreadStart, pipeStorage)) + { + free(pipeStorage); + return false; + } + /* new thread owns pipeStorage */ + + 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(void) +{ + 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); + + LOGD("Joining stdio converter...\n"); + pthread_join(gDvm.stdioConverterHandle, NULL); +} + +/* + * Select on stdout/stderr pipes, waiting for activity. + * + * DO NOT use printf from here. + */ +static void* stdioConverterThreadStart(void* arg) +{ + StdPipes* pipeStorage = (StdPipes*) arg; + BufferedData* stdoutData; + BufferedData* stderrData; + 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. + */ + stdoutData = (BufferedData*) malloc(sizeof(*stdoutData)); + stderrData = (BufferedData*) malloc(sizeof(*stderrData)); + stdoutData->count = stderrData->count = 0; + + /* + * Read until shutdown time. + */ + while (!gDvm.haltStdioConverter) { + fd_set readfds; + int maxFd, fdCount; + + FD_ZERO(&readfds); + FD_SET(pipeStorage->stdoutPipe[0], &readfds); + FD_SET(pipeStorage->stderrPipe[0], &readfds); + maxFd = MAX(pipeStorage->stdoutPipe[0], pipeStorage->stderrPipe[0]); + + fdCount = select(maxFd+1, &readfds, NULL, NULL, NULL); + + if (fdCount < 0) { + if (errno != EINTR) { + LOGE("select on stdout/stderr failed\n"); + break; + } + LOGD("Got EINTR, ignoring\n"); + } else if (fdCount == 0) { + LOGD("WEIRD: select returned zero\n"); + } else { + bool err = false; + if (FD_ISSET(pipeStorage->stdoutPipe[0], &readfds)) { + err |= !readAndLog(pipeStorage->stdoutPipe[0], stdoutData, + "stdout"); + } + if (FD_ISSET(pipeStorage->stderrPipe[0], &readfds)) { + err |= !readAndLog(pipeStorage->stderrPipe[0], stderrData, + "stderr"); + } + + /* probably EOF; give up */ + if (err) { + LOGW("stdio converter got read error; shutting it down\n"); + break; + } + } + } + + close(pipeStorage->stdoutPipe[0]); + close(pipeStorage->stderrPipe[0]); + + free(pipeStorage); + free(stdoutData); + free(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) { + LOGW("read %s: (%d,%d) failed (%d): %s\n", + tag, fd, want, (int)actual, strerror(errno)); + return false; + } else { + //LOGI("read %s: %d at %d\n", 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'; + //LOGW("GOT %d at %d '%s'\n", cp - start, start - data->buf, start); + LOG(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'; + LOG(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; +} |