/*
* 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
/*
* Hold our replacement stdout/stderr.
*/
typedef struct StdPipes {
int stdoutPipe[2];
int stderrPipe[2];
} StdPipes;
#define kMaxLine 512
/*
* Hold some data.
*/
typedef struct BufferedData {
char buf[kMaxLine+1];
int count;
} BufferedData;
// 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(void)
{
StdPipes* pipeStorage;
gDvm.haltStdioConverter = false;
dvmInitMutex(&gDvm.stdioConverterLock);
pthread_cond_init(&gDvm.stdioConverterCond, NULL);
pipeStorage = (StdPipes*) malloc(sizeof(StdPipes));
if (pipeStorage == NULL)
return false;
if (pipe(pipeStorage->stdoutPipe) != 0) {
LOGW("pipe failed: %s\n", strerror(errno));
return false;
}
if (pipe(pipeStorage->stderrPipe) != 0) {
LOGW("pipe failed: %s\n", strerror(errno));
return false;
}
if (dup2(pipeStorage->stdoutPipe[1], kFilenoStdout) != kFilenoStdout) {
LOGW("dup2(1) failed: %s\n", strerror(errno));
return false;
}
close(pipeStorage->stdoutPipe[1]);
pipeStorage->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(pipeStorage->stderrPipe[1], kFilenoStderr) != kFilenoStderr) {
LOGW("dup2(2) failed: %d %s\n", errno, strerror(errno));
return false;
}
close(pipeStorage->stderrPipe[1]);
pipeStorage->stderrPipe[1] = -1;
#endif
/*
* Create the thread.
*/
dvmLockMutex(&gDvm.stdioConverterLock);
if (!dvmCreateInternalThread(&gDvm.stdioConverterHandle,
"Stdio Converter", stdioConverterThreadStart, pipeStorage))
{
free(pipeStorage);
return false;
}
/* new thread owns pipeStorage */
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(void)
{
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);
LOGD("Joining stdio converter...\n");
pthread_join(gDvm.stdioConverterHandle, NULL);
}
/*
* Select on stdout/stderr pipes, waiting for activity.
*
* DO NOT use printf from here.
*/
static void* stdioConverterThreadStart(void* arg)
{
StdPipes* pipeStorage = (StdPipes*) arg;
BufferedData* stdoutData;
BufferedData* stderrData;
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.
*/
stdoutData = (BufferedData*) malloc(sizeof(*stdoutData));
stderrData = (BufferedData*) malloc(sizeof(*stderrData));
stdoutData->count = stderrData->count = 0;
/*
* Read until shutdown time.
*/
while (!gDvm.haltStdioConverter) {
fd_set readfds;
int maxFd, fdCount;
FD_ZERO(&readfds);
FD_SET(pipeStorage->stdoutPipe[0], &readfds);
FD_SET(pipeStorage->stderrPipe[0], &readfds);
maxFd = MAX(pipeStorage->stdoutPipe[0], pipeStorage->stderrPipe[0]);
fdCount = select(maxFd+1, &readfds, NULL, NULL, NULL);
if (fdCount < 0) {
if (errno != EINTR) {
LOGE("select on stdout/stderr failed\n");
break;
}
LOGD("Got EINTR, ignoring\n");
} else if (fdCount == 0) {
LOGD("WEIRD: select returned zero\n");
} else {
bool err = false;
if (FD_ISSET(pipeStorage->stdoutPipe[0], &readfds)) {
err |= !readAndLog(pipeStorage->stdoutPipe[0], stdoutData,
"stdout");
}
if (FD_ISSET(pipeStorage->stderrPipe[0], &readfds)) {
err |= !readAndLog(pipeStorage->stderrPipe[0], stderrData,
"stderr");
}
/* probably EOF; give up */
if (err) {
LOGW("stdio converter got read error; shutting it down\n");
break;
}
}
}
close(pipeStorage->stdoutPipe[0]);
close(pipeStorage->stderrPipe[0]);
free(pipeStorage);
free(stdoutData);
free(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) {
LOGW("read %s: (%d,%d) failed (%d): %s\n",
tag, fd, want, (int)actual, strerror(errno));
return false;
} else {
//LOGI("read %s: %d at %d\n", 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';
//LOGW("GOT %d at %d '%s'\n", cp - start, start - data->buf, start);
LOG(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';
LOG(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;
}