16

I am porting some existing C code to run on Android. This C code writes lots of output to stdout/stderr. I need to capture this output, either in a memory buffer or a file, so I can then send it by email or otherwise share it.

How can I achieve this, ideally without modifying the existing C code?

Note: this question is NOT about redirecting the output to adb or logcat; I need to buffer the output locally on the device. I am aware of the following questions, which do not appear to address my query:

Community
  • 1
  • 1
Graham Borland
  • 60,055
  • 21
  • 138
  • 179

3 Answers3

22

Use something like this to redirect stderr to a pipe. Have a reader on the other side of the pipe write to logcat:

extern "C" void Java_com_test_yourApp_yourJavaClass_nativePipeSTDERRToLogcat(JNIEnv* env, jclass cls, jobject obj)
{
    int pipes[2];
    pipe(pipes);
    dup2(pipes[1], STDERR_FILENO);
    FILE *inputFile = fdopen(pipes[0], "r");
    char readBuffer[256];
    while (1) {
        fgets(readBuffer, sizeof(readBuffer), inputFile);
        __android_log_write(2, "stderr", readBuffer);
    }
}

You'll want to run this in its own thread. I spin up the thread in Java and then have the Java thread call this NDK code like this:

new Thread() {
    public void run() {
        nativePipeSTDERRToLogcat();
    }
}.start();
Alex
  • 2,893
  • 25
  • 24
James Moore
  • 8,636
  • 5
  • 71
  • 90
  • 2
    This code leaks a file descriptor -- you should `close(pipes[1]`) after the call to `dup2()`. Also, you should check for errors, though I'm not quite sure what you'd do if any of those system calls failed. – Adam Rosenfield Aug 23 '13 at 22:02
1

I used the answer submitted by James Moore, but I wanted to be able to turn the logging on and off. With this I can set the mKeepRunning to false and it will shut down. I also needed to add the O_NONBLOCK flag to the file so it was no longer a blocking call.

    int lWriteFD = dup(STDERR_FILENO);

if ( lWriteFD < 0 )
{
    // WE failed to get our file descriptor
    LOGE("Unable to get STDERR file descriptor.");
    return;
}

int pipes[2];
pipe(pipes);
dup2(pipes[1], STDERR_FILENO);
FILE *inputFile = fdopen(pipes[0], "r");

close(pipes[1]);

int fd = fileno(inputFile);
int flags = fcntl(fd, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);

if ( nullptr == inputFile )
{
    LOGE("Unable to get read pipe for STDERR");
    return;
}

char readBuffer[256];

while (true == mKeepRunning)
{
    fgets(readBuffer, sizeof(readBuffer), inputFile);

    if ( strlen(readBuffer) == 0 )
    {
       sleep(1);
       continue;
    }

    __android_log_write(ANDROID_LOG_ERROR, "stderr", readBuffer);
}

close(pipes[0]);
fclose(inputFile);
Brian S
  • 3,096
  • 37
  • 55
1

stdout is path 1 and stderr is path 2. Knowing this, you can establish new path(s) that you want to be the output destination, then coerce them into stdout and/or stderr. There's an example showing how to do this at practical examples use dup or dup2.

Community
  • 1
  • 1
mah
  • 39,056
  • 9
  • 76
  • 93
  • I'd like to point out that there's nothing special about Android here. This is the normal way to do things on most unix-like systems. – James Moore Jun 12 '12 at 22:29