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