blob: 987fdf0f17c8533794fc14aaca57a1f3ec2eb368 [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) {
Carl Shapirob31b3012010-05-25 18:35:37 -0700112 dvmWaitCond(&gDvm.stdioConverterCond, &gDvm.stdioConverterLock);
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800113 }
114 dvmUnlockMutex(&gDvm.stdioConverterLock);
115
116 return true;
117}
118
119/*
120 * Shut down the stdio converter thread if it was started.
121 *
122 * Since we know the thread is just sitting around waiting for something
123 * to arrive on stdout, print something.
124 */
125void dvmStdioConverterShutdown(void)
126{
127 gDvm.haltStdioConverter = true;
128 if (gDvm.stdioConverterHandle == 0) // not started, or still starting
129 return;
130
131 /* print something to wake it up */
132 printf("Shutting down\n");
133 fflush(stdout);
134
135 LOGD("Joining stdio converter...\n");
136 pthread_join(gDvm.stdioConverterHandle, NULL);
137}
138
139/*
140 * Select on stdout/stderr pipes, waiting for activity.
141 *
142 * DO NOT use printf from here.
143 */
144static void* stdioConverterThreadStart(void* arg)
145{
146#define MAX(a,b) ((a) > (b) ? (a) : (b))
147 StdPipes* pipeStorage = (StdPipes*) arg;
148 BufferedData* stdoutData;
149 BufferedData* stderrData;
150 int cc;
151
152 /* tell the main thread that we're ready */
153 dvmLockMutex(&gDvm.stdioConverterLock);
154 gDvm.stdioConverterReady = true;
155 cc = pthread_cond_signal(&gDvm.stdioConverterCond);
156 assert(cc == 0);
157 dvmUnlockMutex(&gDvm.stdioConverterLock);
158
159 /* we never do anything that affects the rest of the VM */
160 dvmChangeStatus(NULL, THREAD_VMWAIT);
161
162 /*
163 * Allocate read buffers.
164 */
165 stdoutData = (BufferedData*) malloc(sizeof(*stdoutData));
166 stderrData = (BufferedData*) malloc(sizeof(*stderrData));
167 stdoutData->count = stderrData->count = 0;
168
169 /*
170 * Read until shutdown time.
171 */
172 while (!gDvm.haltStdioConverter) {
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800173 fd_set readfds;
174 int maxFd, fdCount;
175
176 FD_ZERO(&readfds);
177 FD_SET(pipeStorage->stdoutPipe[0], &readfds);
178 FD_SET(pipeStorage->stderrPipe[0], &readfds);
179 maxFd = MAX(pipeStorage->stdoutPipe[0], pipeStorage->stderrPipe[0]);
180
181 fdCount = select(maxFd+1, &readfds, NULL, NULL, NULL);
182
183 if (fdCount < 0) {
184 if (errno != EINTR) {
185 LOGE("select on stdout/stderr failed\n");
186 break;
187 }
188 LOGD("Got EINTR, ignoring\n");
189 } else if (fdCount == 0) {
190 LOGD("WEIRD: select returned zero\n");
191 } else {
192 bool err = false;
193 if (FD_ISSET(pipeStorage->stdoutPipe[0], &readfds)) {
194 err |= !readAndLog(pipeStorage->stdoutPipe[0], stdoutData,
195 "stdout");
196 }
197 if (FD_ISSET(pipeStorage->stderrPipe[0], &readfds)) {
198 err |= !readAndLog(pipeStorage->stderrPipe[0], stderrData,
199 "stderr");
200 }
201
202 /* probably EOF; give up */
203 if (err) {
204 LOGW("stdio converter got read error; shutting it down\n");
205 break;
206 }
207 }
208 }
209
210 close(pipeStorage->stdoutPipe[0]);
211 close(pipeStorage->stderrPipe[0]);
212
213 free(pipeStorage);
214 free(stdoutData);
215 free(stderrData);
216
217 /* change back for shutdown sequence */
218 dvmChangeStatus(NULL, THREAD_RUNNING);
219 return NULL;
220#undef MAX
221}
222
223/*
224 * Data is pending on "fd". Read as much as will fit in "data", then
225 * write out any full lines and compact "data".
226 */
227static bool readAndLog(int fd, BufferedData* data, const char* tag)
228{
229 ssize_t actual;
230 size_t want;
231
232 assert(data->count < kMaxLine);
233
234 want = kMaxLine - data->count;
235 actual = read(fd, data->buf + data->count, want);
236 if (actual <= 0) {
237 LOGW("read %s: (%d,%d) failed (%d): %s\n",
238 tag, fd, want, (int)actual, strerror(errno));
239 return false;
240 } else {
241 //LOGI("read %s: %d at %d\n", tag, actual, data->count);
242 }
243 data->count += actual;
244
245 /*
246 * Got more data, look for an EOL. We expect LF or CRLF, but will
247 * try to handle a standalone CR.
248 */
249 char* cp = data->buf;
250 const char* start = data->buf;
251 int i = data->count;
252 for (i = data->count; i > 0; i--, cp++) {
253 if (*cp == '\n' || (*cp == '\r' && i != 0 && *(cp+1) != '\n')) {
254 *cp = '\0';
255 //LOGW("GOT %d at %d '%s'\n", cp - start, start - data->buf, start);
256 LOG(LOG_INFO, tag, "%s", start);
257 start = cp+1;
258 }
259 }
260
261 /*
262 * See if we overflowed. If so, cut it off.
263 */
264 if (start == data->buf && data->count == kMaxLine) {
265 data->buf[kMaxLine] = '\0';
266 LOG(LOG_INFO, tag, "%s!", start);
267 start = cp + kMaxLine;
268 }
269
270 /*
271 * Update "data" if we consumed some output. If there's anything left
272 * in the buffer, it's because we didn't see an EOL and need to keep
273 * reading until we see one.
274 */
275 if (start != data->buf) {
276 if (start >= data->buf + data->count) {
277 /* consumed all available */
278 data->count = 0;
279 } else {
280 /* some left over */
281 int remaining = data->count - (start - data->buf);
282 memmove(data->buf, start, remaining);
283 data->count = remaining;
284 }
285 }
286
287 return true;
288}