blob: efc1e89595caf47423d1f0595a573572bb8141ca [file] [log] [blame]
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001/*
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) {
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800175 fd_set readfds;
176 int maxFd, fdCount;
177
178 FD_ZERO(&readfds);
179 FD_SET(pipeStorage->stdoutPipe[0], &readfds);
180 FD_SET(pipeStorage->stderrPipe[0], &readfds);
181 maxFd = MAX(pipeStorage->stdoutPipe[0], pipeStorage->stderrPipe[0]);
182
183 fdCount = select(maxFd+1, &readfds, NULL, NULL, NULL);
184
185 if (fdCount < 0) {
186 if (errno != EINTR) {
187 LOGE("select on stdout/stderr failed\n");
188 break;
189 }
190 LOGD("Got EINTR, ignoring\n");
191 } else if (fdCount == 0) {
192 LOGD("WEIRD: select returned zero\n");
193 } else {
194 bool err = false;
195 if (FD_ISSET(pipeStorage->stdoutPipe[0], &readfds)) {
196 err |= !readAndLog(pipeStorage->stdoutPipe[0], stdoutData,
197 "stdout");
198 }
199 if (FD_ISSET(pipeStorage->stderrPipe[0], &readfds)) {
200 err |= !readAndLog(pipeStorage->stderrPipe[0], stderrData,
201 "stderr");
202 }
203
204 /* probably EOF; give up */
205 if (err) {
206 LOGW("stdio converter got read error; shutting it down\n");
207 break;
208 }
209 }
210 }
211
212 close(pipeStorage->stdoutPipe[0]);
213 close(pipeStorage->stderrPipe[0]);
214
215 free(pipeStorage);
216 free(stdoutData);
217 free(stderrData);
218
219 /* change back for shutdown sequence */
220 dvmChangeStatus(NULL, THREAD_RUNNING);
221 return NULL;
222#undef MAX
223}
224
225/*
226 * Data is pending on "fd". Read as much as will fit in "data", then
227 * write out any full lines and compact "data".
228 */
229static bool readAndLog(int fd, BufferedData* data, const char* tag)
230{
231 ssize_t actual;
232 size_t want;
233
234 assert(data->count < kMaxLine);
235
236 want = kMaxLine - data->count;
237 actual = read(fd, data->buf + data->count, want);
238 if (actual <= 0) {
239 LOGW("read %s: (%d,%d) failed (%d): %s\n",
240 tag, fd, want, (int)actual, strerror(errno));
241 return false;
242 } else {
243 //LOGI("read %s: %d at %d\n", tag, actual, data->count);
244 }
245 data->count += actual;
246
247 /*
248 * Got more data, look for an EOL. We expect LF or CRLF, but will
249 * try to handle a standalone CR.
250 */
251 char* cp = data->buf;
252 const char* start = data->buf;
253 int i = data->count;
254 for (i = data->count; i > 0; i--, cp++) {
255 if (*cp == '\n' || (*cp == '\r' && i != 0 && *(cp+1) != '\n')) {
256 *cp = '\0';
257 //LOGW("GOT %d at %d '%s'\n", cp - start, start - data->buf, start);
258 LOG(LOG_INFO, tag, "%s", start);
259 start = cp+1;
260 }
261 }
262
263 /*
264 * See if we overflowed. If so, cut it off.
265 */
266 if (start == data->buf && data->count == kMaxLine) {
267 data->buf[kMaxLine] = '\0';
268 LOG(LOG_INFO, tag, "%s!", start);
269 start = cp + kMaxLine;
270 }
271
272 /*
273 * Update "data" if we consumed some output. If there's anything left
274 * in the buffer, it's because we didn't see an EOL and need to keep
275 * reading until we see one.
276 */
277 if (start != data->buf) {
278 if (start >= data->buf + data->count) {
279 /* consumed all available */
280 data->count = 0;
281 } else {
282 /* some left over */
283 int remaining = data->count - (start - data->buf);
284 memmove(data->buf, start, remaining);
285 data->count = remaining;
286 }
287 }
288
289 return true;
290}
291