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