/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Thread that reads from stdout/stderr and converts them to log messages.
* (Sort of a hack.)
*/
#include "Dalvik.h"
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#define kFilenoStdout 1
#define kFilenoStderr 2
#define kMaxLine 512
/*
* Hold some data.
*/
struct BufferedData {
char buf[kMaxLine+1];
int count;
};
// fwd
static void* stdioConverterThreadStart(void* arg);
static bool readAndLog(int fd, BufferedData* data, const char* tag);
/*
* Crank up the stdout/stderr converter thread.
*
* Returns immediately.
*/
bool dvmStdioConverterStartup()
{
gDvm.haltStdioConverter = false;
dvmInitMutex(&gDvm.stdioConverterLock);
pthread_cond_init(&gDvm.stdioConverterCond, NULL);
if (pipe(gDvm.stdoutPipe) != 0) {
ALOGW("pipe failed: %s", strerror(errno));
return false;
}
if (pipe(gDvm.stderrPipe) != 0) {
ALOGW("pipe failed: %s", strerror(errno));
return false;
}
if (dup2(gDvm.stdoutPipe[1], kFilenoStdout) != kFilenoStdout) {
ALOGW("dup2(1) failed: %s", strerror(errno));
return false;
}
close(gDvm.stdoutPipe[1]);
gDvm.stdoutPipe[1] = -1;
#ifdef HAVE_ANDROID_OS
/* don't redirect stderr on sim -- logs get written there! */
/* (don't need this on the sim anyway) */
if (dup2(gDvm.stderrPipe[1], kFilenoStderr) != kFilenoStderr) {
ALOGW("dup2(2) failed: %d %s", errno, strerror(errno));
return false;
}
close(gDvm.stderrPipe[1]);
gDvm.stderrPipe[1] = -1;
#endif
/*
* Create the thread.
*/
dvmLockMutex(&gDvm.stdioConverterLock);
if (!dvmCreateInternalThread(&gDvm.stdioConverterHandle,
"Stdio Converter",
stdioConverterThreadStart,
NULL)) {
return false;
}
while (!gDvm.stdioConverterReady) {
dvmWaitCond(&gDvm.stdioConverterCond, &gDvm.stdioConverterLock);
}
dvmUnlockMutex(&gDvm.stdioConverterLock);
return true;
}
/*
* Shut down the stdio converter thread if it was started.
*
* Since we know the thread is just sitting around waiting for something
* to arrive on stdout, print something.
*/
void dvmStdioConverterShutdown()
{
gDvm.haltStdioConverter = true;
if (gDvm.stdioConverterHandle == 0) // not started, or still starting
return;
/* print something to wake it up */
printf("Shutting down\n");
fflush(stdout);
ALOGD("Joining stdio converter...");
pthread_join(gDvm.stdioConverterHandle, NULL);
}
/*
* Select on stdout/stderr pipes, waiting for activity.
*
* DO NOT use printf from here.
*/
static void* stdioConverterThreadStart(void* arg)
{
int cc;
/* tell the main thread that we're ready */
dvmLockMutex(&gDvm.stdioConverterLock);
gDvm.stdioConverterReady = true;
cc = pthread_cond_signal(&gDvm.stdioConverterCond);
assert(cc == 0);
dvmUnlockMutex(&gDvm.stdioConverterLock);
/* we never do anything that affects the rest of the VM */
dvmChangeStatus(NULL, THREAD_VMWAIT);
/*
* Allocate read buffers.
*/
BufferedData* stdoutData = new BufferedData;
BufferedData* stderrData = new BufferedData;
stdoutData->count = stderrData->count = 0;
/*
* Read until shutdown time.
*/
while (!gDvm.haltStdioConverter) {
fd_set readfds;
int maxFd, fdCount;
FD_ZERO(&readfds);
FD_SET(gDvm.stdoutPipe[0], &readfds);
FD_SET(gDvm.stderrPipe[0], &readfds);
maxFd = MAX(gDvm.stdoutPipe[0], gDvm.stderrPipe[0]);
fdCount = select(maxFd+1, &readfds, NULL, NULL, NULL);
if (fdCount < 0) {
if (errno != EINTR) {
ALOGE("select on stdout/stderr failed");
break;
}
ALOGD("Got EINTR, ignoring");
} else if (fdCount == 0) {
ALOGD("WEIRD: select returned zero");
} else {
bool err = false;
if (FD_ISSET(gDvm.stdoutPipe[0], &readfds)) {
err |= !readAndLog(gDvm.stdoutPipe[0], stdoutData,
"stdout");
}
if (FD_ISSET(gDvm.stderrPipe[0], &readfds)) {
err |= !readAndLog(gDvm.stderrPipe[0], stderrData,
"stderr");
}
/* probably EOF; give up */
if (err) {
ALOGW("stdio converter got read error; shutting it down");
break;
}
}
}
close(gDvm.stdoutPipe[0]);
close(gDvm.stderrPipe[0]);
delete stdoutData;
delete stderrData;
/* change back for shutdown sequence */
dvmChangeStatus(NULL, THREAD_RUNNING);
return NULL;
}
/*
* Data is pending on "fd". Read as much as will fit in "data", then
* write out any full lines and compact "data".
*/
static bool readAndLog(int fd, BufferedData* data, const char* tag)
{
ssize_t actual;
size_t want;
assert(data->count < kMaxLine);
want = kMaxLine - data->count;
actual = read(fd, data->buf + data->count, want);
if (actual <= 0) {
ALOGW("read %s: (%d,%d) failed (%d): %s",
tag, fd, want, (int)actual, strerror(errno));
return false;
} else {
//ALOGI("read %s: %d at %d", tag, actual, data->count);
}
data->count += actual;
/*
* Got more data, look for an EOL. We expect LF or CRLF, but will
* try to handle a standalone CR.
*/
char* cp = data->buf;
const char* start = data->buf;
int i = data->count;
for (i = data->count; i > 0; i--, cp++) {
if (*cp == '\n' || (*cp == '\r' && i != 0 && *(cp+1) != '\n')) {
*cp = '\0';
//ALOGW("GOT %d at %d '%s'", cp - start, start - data->buf, start);
ALOG(LOG_INFO, tag, "%s", start);
start = cp+1;
}
}
/*
* See if we overflowed. If so, cut it off.
*/
if (start == data->buf && data->count == kMaxLine) {
data->buf[kMaxLine] = '\0';
ALOG(LOG_INFO, tag, "%s!", start);
start = cp + kMaxLine;
}
/*
* Update "data" if we consumed some output. If there's anything left
* in the buffer, it's because we didn't see an EOL and need to keep
* reading until we see one.
*/
if (start != data->buf) {
if (start >= data->buf + data->count) {
/* consumed all available */
data->count = 0;
} else {
/* some left over */
int remaining = data->count - (start - data->buf);
memmove(data->buf, start, remaining);
data->count = remaining;
}
}
return true;
}