The Android Open Source Project | f6c3871 | 2009-03-03 19:28:47 -0800 | [diff] [blame] | 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 | */ |
| 33 | typedef struct StdPipes { |
| 34 | int stdoutPipe[2]; |
| 35 | int stderrPipe[2]; |
| 36 | } StdPipes; |
| 37 | |
| 38 | #define kMaxLine 512 |
| 39 | |
| 40 | /* |
| 41 | * Hold some data. |
| 42 | */ |
| 43 | typedef struct BufferedData { |
| 44 | char buf[kMaxLine+1]; |
| 45 | int count; |
| 46 | } BufferedData; |
| 47 | |
| 48 | // fwd |
| 49 | static void* stdioConverterThreadStart(void* arg); |
| 50 | static 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 | */ |
| 58 | bool 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 | */ |
| 127 | void 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 | */ |
| 146 | static 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 | */ |
| 230 | static 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 | |