2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 * Thread that reads from stdout/stderr and converts them to log messages.
27 #define kFilenoStdout 1
28 #define kFilenoStderr 2
31 * Hold our replacement stdout/stderr.
49 static void* stdioConverterThreadStart(void* arg);
50 static bool readAndLog(int fd, BufferedData* data, const char* tag);
54 * Crank up the stdout/stderr converter thread.
56 * Returns immediately.
58 bool dvmStdioConverterStartup()
60 StdPipes* pipeStorage;
62 gDvm.haltStdioConverter = false;
64 dvmInitMutex(&gDvm.stdioConverterLock);
65 pthread_cond_init(&gDvm.stdioConverterCond, NULL);
67 pipeStorage = (StdPipes*) malloc(sizeof(StdPipes));
68 if (pipeStorage == NULL)
71 if (pipe(pipeStorage->stdoutPipe) != 0) {
72 LOGW("pipe failed: %s\n", strerror(errno));
75 if (pipe(pipeStorage->stderrPipe) != 0) {
76 LOGW("pipe failed: %s\n", strerror(errno));
80 if (dup2(pipeStorage->stdoutPipe[1], kFilenoStdout) != kFilenoStdout) {
81 LOGW("dup2(1) failed: %s\n", strerror(errno));
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));
93 close(pipeStorage->stderrPipe[1]);
94 pipeStorage->stderrPipe[1] = -1;
101 dvmLockMutex(&gDvm.stdioConverterLock);
103 if (!dvmCreateInternalThread(&gDvm.stdioConverterHandle,
104 "Stdio Converter", stdioConverterThreadStart, pipeStorage))
109 /* new thread owns pipeStorage */
111 while (!gDvm.stdioConverterReady) {
112 dvmWaitCond(&gDvm.stdioConverterCond, &gDvm.stdioConverterLock);
114 dvmUnlockMutex(&gDvm.stdioConverterLock);
120 * Shut down the stdio converter thread if it was started.
122 * Since we know the thread is just sitting around waiting for something
123 * to arrive on stdout, print something.
125 void dvmStdioConverterShutdown()
127 gDvm.haltStdioConverter = true;
128 if (gDvm.stdioConverterHandle == 0) // not started, or still starting
131 /* print something to wake it up */
132 printf("Shutting down\n");
135 LOGD("Joining stdio converter...\n");
136 pthread_join(gDvm.stdioConverterHandle, NULL);
140 * Select on stdout/stderr pipes, waiting for activity.
142 * DO NOT use printf from here.
144 static void* stdioConverterThreadStart(void* arg)
146 StdPipes* pipeStorage = (StdPipes*) arg;
147 BufferedData* stdoutData;
148 BufferedData* stderrData;
151 /* tell the main thread that we're ready */
152 dvmLockMutex(&gDvm.stdioConverterLock);
153 gDvm.stdioConverterReady = true;
154 cc = pthread_cond_signal(&gDvm.stdioConverterCond);
156 dvmUnlockMutex(&gDvm.stdioConverterLock);
158 /* we never do anything that affects the rest of the VM */
159 dvmChangeStatus(NULL, THREAD_VMWAIT);
162 * Allocate read buffers.
164 stdoutData = (BufferedData*) malloc(sizeof(*stdoutData));
165 stderrData = (BufferedData*) malloc(sizeof(*stderrData));
166 stdoutData->count = stderrData->count = 0;
169 * Read until shutdown time.
171 while (!gDvm.haltStdioConverter) {
176 FD_SET(pipeStorage->stdoutPipe[0], &readfds);
177 FD_SET(pipeStorage->stderrPipe[0], &readfds);
178 maxFd = MAX(pipeStorage->stdoutPipe[0], pipeStorage->stderrPipe[0]);
180 fdCount = select(maxFd+1, &readfds, NULL, NULL, NULL);
183 if (errno != EINTR) {
184 LOGE("select on stdout/stderr failed\n");
187 LOGD("Got EINTR, ignoring\n");
188 } else if (fdCount == 0) {
189 LOGD("WEIRD: select returned zero\n");
192 if (FD_ISSET(pipeStorage->stdoutPipe[0], &readfds)) {
193 err |= !readAndLog(pipeStorage->stdoutPipe[0], stdoutData,
196 if (FD_ISSET(pipeStorage->stderrPipe[0], &readfds)) {
197 err |= !readAndLog(pipeStorage->stderrPipe[0], stderrData,
201 /* probably EOF; give up */
203 LOGW("stdio converter got read error; shutting it down\n");
209 close(pipeStorage->stdoutPipe[0]);
210 close(pipeStorage->stderrPipe[0]);
216 /* change back for shutdown sequence */
217 dvmChangeStatus(NULL, THREAD_RUNNING);
222 * Data is pending on "fd". Read as much as will fit in "data", then
223 * write out any full lines and compact "data".
225 static bool readAndLog(int fd, BufferedData* data, const char* tag)
230 assert(data->count < kMaxLine);
232 want = kMaxLine - data->count;
233 actual = read(fd, data->buf + data->count, want);
235 LOGW("read %s: (%d,%d) failed (%d): %s\n",
236 tag, fd, want, (int)actual, strerror(errno));
239 //LOGI("read %s: %d at %d\n", tag, actual, data->count);
241 data->count += actual;
244 * Got more data, look for an EOL. We expect LF or CRLF, but will
245 * try to handle a standalone CR.
247 char* cp = data->buf;
248 const char* start = data->buf;
250 for (i = data->count; i > 0; i--, cp++) {
251 if (*cp == '\n' || (*cp == '\r' && i != 0 && *(cp+1) != '\n')) {
253 //LOGW("GOT %d at %d '%s'\n", cp - start, start - data->buf, start);
254 LOG(LOG_INFO, tag, "%s", start);
260 * See if we overflowed. If so, cut it off.
262 if (start == data->buf && data->count == kMaxLine) {
263 data->buf[kMaxLine] = '\0';
264 LOG(LOG_INFO, tag, "%s!", start);
265 start = cp + kMaxLine;
269 * Update "data" if we consumed some output. If there's anything left
270 * in the buffer, it's because we didn't see an EOL and need to keep
271 * reading until we see one.
273 if (start != data->buf) {
274 if (start >= data->buf + data->count) {
275 /* consumed all available */
279 int remaining = data->count - (start - data->buf);
280 memmove(data->buf, start, remaining);
281 data->count = remaining;