logcat: Create liblogcat

Try to leverage as much of logcat as-is and produce a viable
library API that others can use for their own logcat execution.

Added a test to check ANDROID_PRINTF_LOG environment variable.

Test: gTest logcat-unit-tests
Bug: 35326290
Change-Id: I30de692ea9d83e6fd6e5d9e7cf93d31401a88a40
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index adedf88..b048289 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006-2015 The Android Open Source Project
+ * Copyright (C) 2006-2017 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.
@@ -23,7 +23,6 @@
 #include <getopt.h>
 #include <math.h>
 #include <sched.h>
-#include <signal.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -45,6 +44,7 @@
 #include <cutils/sched_policy.h>
 #include <cutils/sockets.h>
 #include <log/event_tag_map.h>
+#include <log/logcat.h>
 #include <log/logprint.h>
 #include <private/android_logger.h>
 #include <system/thread_defs.h>
@@ -53,7 +53,59 @@
 
 #define DEFAULT_MAX_ROTATED_LOGS 4
 
-static AndroidLogFormat* g_logformat;
+struct android_logcat_context_internal {
+    // status
+    int retval;
+    // Arguments passed in
+    int argc;
+    char* const* argv;
+    char* const* envp;
+    int output_fd;
+    int error_fd;
+
+    // library
+    FILE* output;        // everything writes to fileno(output), buffer unused
+    FILE* error;         // unless error == output.
+    bool stop;           // quick exit flag
+    bool stderr_null;    // shell "2>/dev/null"
+    bool stderr_stdout;  // shell "2>&1"
+
+    // global variables
+    AndroidLogFormat* logformat;
+    const char* outputFileName;
+    // 0 means "no log rotation"
+    size_t logRotateSizeKBytes;
+    // 0 means "unbounded"
+    size_t maxRotatedLogs;
+    size_t outByteCount;
+    int printBinary;
+    int devCount;  // >1 means multiple
+    pcrecpp::RE* regex;
+    // 0 means "infinite"
+    size_t maxCount;
+    size_t printCount;
+    bool printItAnyways;
+    bool debug;
+
+    // static variables
+    bool hasOpenedEventTagMap;
+    EventTagMap* eventTagMap;
+};
+
+// Creates a context associated with this logcat instance
+android_logcat_context create_android_logcat() {
+    android_logcat_context_internal* context;
+
+    context = (android_logcat_context_internal*)calloc(
+        1, sizeof(android_logcat_context_internal));
+    if (!context) return NULL;
+
+    context->output_fd = -1;
+    context->error_fd = -1;
+    context->maxRotatedLogs = DEFAULT_MAX_ROTATED_LOGS;
+
+    return (android_logcat_context)context;
+}
 
 // logd prefixes records with a length field
 #define RECORD_LENGTH_FIELD_SIZE_BYTES sizeof(uint32_t)
@@ -79,62 +131,109 @@
 
 namespace android {
 
-// Global Variables
-
-static const char* g_outputFileName;
-// 0 means "no log rotation"
-static size_t g_logRotateSizeKBytes;
-// 0 means "unbounded"
-static size_t g_maxRotatedLogs = DEFAULT_MAX_ROTATED_LOGS;
-static int g_outFD = -1;
-static size_t g_outByteCount;
-static int g_printBinary;
-static int g_devCount;  // >1 means multiple
-static pcrecpp::RE* g_regex;
-// 0 means "infinite"
-static size_t g_maxCount;
-static size_t g_printCount;
-static bool g_printItAnyways;
-static bool g_debug;
-
 enum helpType { HELP_FALSE, HELP_TRUE, HELP_FORMAT };
 
 // if showHelp is set, newline required in fmt statement to transition to usage
-__noreturn static void logcat_panic(enum helpType showHelp, const char* fmt,
-                                    ...) __printflike(2, 3);
+static void logcat_panic(android_logcat_context_internal* context,
+                         enum helpType showHelp, const char* fmt, ...)
+    __printflike(3, 4);
 
 static int openLogFile(const char* pathname) {
     return open(pathname, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
 }
 
-static void rotateLogs() {
+static void close_output(android_logcat_context_internal* context) {
+    // split output_from_error
+    if (context->error == context->output) {
+        context->output = NULL;
+        context->output_fd = -1;
+    }
+    if (context->error && (context->output_fd == fileno(context->error))) {
+        context->output_fd = -1;
+    }
+    if (context->output_fd == context->error_fd) {
+        context->output_fd = -1;
+    }
+    // close output channel
+    if (context->output) {
+        if (context->output != stdout) {
+            if (context->output_fd == fileno(context->output)) {
+                context->output_fd = -1;
+            }
+            fclose(context->output);
+        }
+        context->output = NULL;
+    }
+    if (context->output_fd >= 0) {
+        if (context->output_fd != fileno(stdout)) {
+            close(context->output_fd);
+        }
+        context->output_fd = -1;
+    }
+}
+
+static void close_error(android_logcat_context_internal* context) {
+    // split error_from_output
+    if (context->output == context->error) {
+        context->error = NULL;
+        context->error_fd = -1;
+    }
+    if (context->output && (context->error_fd == fileno(context->output))) {
+        context->error_fd = -1;
+    }
+    if (context->error_fd == context->output_fd) {
+        context->error_fd = -1;
+    }
+    // close error channel
+    if (context->error) {
+        if ((context->error != stderr) && (context->error != stdout)) {
+            if (context->error_fd == fileno(context->error)) {
+                context->error_fd = -1;
+            }
+            fclose(context->error);
+        }
+        context->error = NULL;
+    }
+    if (context->error_fd >= 0) {
+        if ((context->error_fd != fileno(stdout)) &&
+            (context->error_fd != fileno(stderr))) {
+            close(context->error_fd);
+        }
+        context->error_fd = -1;
+    }
+}
+
+static void rotateLogs(android_logcat_context_internal* context) {
     int err;
 
     // Can't rotate logs if we're not outputting to a file
-    if (g_outputFileName == NULL) {
+    if (context->outputFileName == NULL) {
         return;
     }
 
-    close(g_outFD);
+    close_output(context);
 
     // Compute the maximum number of digits needed to count up to
-    // g_maxRotatedLogs in decimal.  eg:
-    // g_maxRotatedLogs == 30
+    // maxRotatedLogs in decimal.  eg:
+    // maxRotatedLogs == 30
     //   -> log10(30) == 1.477
     //   -> maxRotationCountDigits == 2
     int maxRotationCountDigits =
-        (g_maxRotatedLogs > 0) ? (int)(floor(log10(g_maxRotatedLogs) + 1)) : 0;
+        (context->maxRotatedLogs > 0)
+            ? (int)(floor(log10(context->maxRotatedLogs) + 1))
+            : 0;
 
-    for (int i = g_maxRotatedLogs; i > 0; i--) {
+    for (int i = context->maxRotatedLogs; i > 0; i--) {
         std::string file1 = android::base::StringPrintf(
-            "%s.%.*d", g_outputFileName, maxRotationCountDigits, i);
+            "%s.%.*d", context->outputFileName, maxRotationCountDigits, i);
 
         std::string file0;
         if (i - 1 == 0) {
-            file0 = android::base::StringPrintf("%s", g_outputFileName);
+            file0 = android::base::StringPrintf("%s", context->outputFileName);
         } else {
-            file0 = android::base::StringPrintf("%s.%.*d", g_outputFileName,
-                                                maxRotationCountDigits, i - 1);
+            file0 =
+                android::base::StringPrintf("%s.%.*d", context->outputFileName,
+                                            maxRotationCountDigits, i - 1);
         }
 
         if ((file0.length() == 0) || (file1.length() == 0)) {
@@ -149,109 +248,109 @@
         }
     }
 
-    g_outFD = openLogFile(g_outputFileName);
+    context->output_fd = openLogFile(context->outputFileName);
 
-    if (g_outFD < 0) {
-        logcat_panic(HELP_FALSE, "couldn't open output file");
+    if (context->output_fd < 0) {
+        logcat_panic(context, HELP_FALSE, "couldn't open output file");
+        return;
     }
+    context->output = fdopen(context->output_fd, "web");
 
-    g_outByteCount = 0;
+    context->outByteCount = 0;
 }
 
-void printBinary(struct log_msg* buf) {
+void printBinary(android_logcat_context_internal* context, struct log_msg* buf) {
     size_t size = buf->len();
 
-    TEMP_FAILURE_RETRY(write(g_outFD, buf, size));
+    TEMP_FAILURE_RETRY(write(context->output_fd, buf, size));
 }
 
-static bool regexOk(const AndroidLogEntry& entry) {
-    if (!g_regex) {
+static bool regexOk(android_logcat_context_internal* context,
+                    const AndroidLogEntry& entry) {
+    if (!context->regex) {
         return true;
     }
 
     std::string messageString(entry.message, entry.messageLen);
 
-    return g_regex->PartialMatch(messageString);
+    return context->regex->PartialMatch(messageString);
 }
 
-static void processBuffer(log_device_t* dev, struct log_msg* buf) {
+static void processBuffer(android_logcat_context_internal* context,
+                          log_device_t* dev, struct log_msg* buf) {
     int bytesWritten = 0;
     int err;
     AndroidLogEntry entry;
     char binaryMsgBuf[1024];
 
     if (dev->binary) {
-        static bool hasOpenedEventTagMap = false;
-        static EventTagMap* eventTagMap = NULL;
-
-        if (!eventTagMap && !hasOpenedEventTagMap) {
-            eventTagMap = android_openEventTagMap(NULL);
-            hasOpenedEventTagMap = true;
+        if (!context->eventTagMap && !context->hasOpenedEventTagMap) {
+            context->eventTagMap = android_openEventTagMap(NULL);
+            context->hasOpenedEventTagMap = true;
         }
-        err = android_log_processBinaryLogBuffer(&buf->entry_v1, &entry,
-                                                 eventTagMap, binaryMsgBuf,
-                                                 sizeof(binaryMsgBuf));
+        err = android_log_processBinaryLogBuffer(
+            &buf->entry_v1, &entry, context->eventTagMap, binaryMsgBuf,
+            sizeof(binaryMsgBuf));
         // printf(">>> pri=%d len=%d msg='%s'\n",
         //    entry.priority, entry.messageLen, entry.message);
     } else {
         err = android_log_processLogBuffer(&buf->entry_v1, &entry);
     }
-    if ((err < 0) && !g_debug) {
-        goto error;
+    if ((err < 0) && !context->debug) {
+        return;
     }
 
     if (android_log_shouldPrintLine(
-            g_logformat, std::string(entry.tag, entry.tagLen).c_str(),
+            context->logformat, std::string(entry.tag, entry.tagLen).c_str(),
             entry.priority)) {
-        bool match = regexOk(entry);
+        bool match = regexOk(context, entry);
 
-        g_printCount += match;
-        if (match || g_printItAnyways) {
-            bytesWritten =
-                android_log_printLogLine(g_logformat, g_outFD, &entry);
+        context->printCount += match;
+        if (match || context->printItAnyways) {
+            bytesWritten = android_log_printLogLine(context->logformat,
+                                                    context->output_fd, &entry);
 
             if (bytesWritten < 0) {
-                logcat_panic(HELP_FALSE, "output error");
+                logcat_panic(context, HELP_FALSE, "output error");
+                return;
             }
         }
     }
 
-    g_outByteCount += bytesWritten;
+    context->outByteCount += bytesWritten;
 
-    if (g_logRotateSizeKBytes > 0 &&
-        (g_outByteCount / 1024) >= g_logRotateSizeKBytes) {
-        rotateLogs();
+    if (context->logRotateSizeKBytes > 0 &&
+        (context->outByteCount / 1024) >= context->logRotateSizeKBytes) {
+        rotateLogs(context);
     }
-
-error:
-    return;
 }
 
-static void maybePrintStart(log_device_t* dev, bool printDividers) {
+static void maybePrintStart(android_logcat_context_internal* context,
+                            log_device_t* dev, bool printDividers) {
     if (!dev->printed || printDividers) {
-        if (g_devCount > 1 && !g_printBinary) {
+        if (context->devCount > 1 && !context->printBinary) {
             char buf[1024];
             snprintf(buf, sizeof(buf), "--------- %s %s\n",
                      dev->printed ? "switch to" : "beginning of", dev->device);
-            if (write(g_outFD, buf, strlen(buf)) < 0) {
-                logcat_panic(HELP_FALSE, "output error");
+            if (write(context->output_fd, buf, strlen(buf)) < 0) {
+                logcat_panic(context, HELP_FALSE, "output error");
+                return;
             }
         }
         dev->printed = true;
     }
 }
 
-static void setupOutputAndSchedulingPolicy(bool blocking) {
-    if (g_outputFileName == NULL) {
-        g_outFD = STDOUT_FILENO;
-        return;
-    }
+static void setupOutputAndSchedulingPolicy(
+    android_logcat_context_internal* context, bool blocking) {
+    if (context->outputFileName == NULL) return;
 
     if (blocking) {
         // Lower priority and set to batch scheduling if we are saving
         // the logs into files and taking continuous content.
-        if (set_sched_policy(0, SP_BACKGROUND) < 0) {
-            fprintf(stderr, "failed to set background scheduling policy\n");
+        if ((set_sched_policy(0, SP_BACKGROUND) < 0) && context->error) {
+            fprintf(context->error,
+                    "failed to set background scheduling policy\n");
         }
 
         struct sched_param param;
@@ -260,36 +359,49 @@
             fprintf(stderr, "failed to set to batch scheduler\n");
         }
 
-        if (setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND) < 0) {
-            fprintf(stderr, "failed set to priority\n");
+        if ((setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND) < 0) &&
+            context->error) {
+            fprintf(context->error, "failed set to priority\n");
         }
     }
 
-    g_outFD = openLogFile(g_outputFileName);
+    close_output(context);
 
-    if (g_outFD < 0) {
-        logcat_panic(HELP_FALSE, "couldn't open output file");
+    context->output_fd = openLogFile(context->outputFileName);
+
+    if (context->output_fd < 0) {
+        logcat_panic(context, HELP_FALSE, "couldn't open output file");
+        return;
     }
 
     struct stat statbuf;
-    if (fstat(g_outFD, &statbuf) == -1) {
-        close(g_outFD);
-        logcat_panic(HELP_FALSE, "couldn't get output file stat\n");
+    if (fstat(context->output_fd, &statbuf) == -1) {
+        close_output(context);
+        logcat_panic(context, HELP_FALSE, "couldn't get output file stat\n");
+        return;
     }
 
     if ((size_t)statbuf.st_size > SIZE_MAX || statbuf.st_size < 0) {
-        close(g_outFD);
-        logcat_panic(HELP_FALSE, "invalid output file stat\n");
+        close_output(context);
+        logcat_panic(context, HELP_FALSE, "invalid output file stat\n");
+        return;
     }
 
-    g_outByteCount = statbuf.st_size;
+    context->output = fdopen(context->output_fd, "web");
+
+    context->outByteCount = statbuf.st_size;
 }
 
 // clang-format off
-static void show_help(const char* cmd) {
-    fprintf(stderr, "Usage: %s [options] [filterspecs]\n", cmd);
+static void show_help(android_logcat_context_internal* context) {
+    if (!context->error) return;
 
-    fprintf(stderr, "options include:\n"
+    const char* cmd = strrchr(context->argv[0], '/');
+    cmd = cmd ? cmd + 1 : context->argv[0];
+
+    fprintf(context->error, "Usage: %s [options] [filterspecs]\n", cmd);
+
+    fprintf(context->error, "options include:\n"
                     "  -s              Set default filter to silent. Equivalent to filterspec '*:S'\n"
                     "  -f <file>, --file=<file>               Log to file. Default is stdout\n"
                     "  -r <kbytes>, --rotate-kbytes=<kbytes>\n"
@@ -353,7 +465,7 @@
                     "                  comes first. Improves efficiency of polling by providing\n"
                     "                  an about-to-wrap wakeup.\n");
 
-    fprintf(stderr, "\nfilterspecs are a series of \n"
+    fprintf(context->error, "\nfilterspecs are a series of \n"
                    "  <tag>[:priority]\n\n"
                    "where <tag> is a log component tag (or * for all) and priority is:\n"
                    "  V    Verbose (default for <tag>)\n"
@@ -371,8 +483,9 @@
                    "or defaults to \"threadtime\"\n\n");
 }
 
-static void show_format_help() {
-    fprintf(stderr,
+static void show_format_help(android_logcat_context_internal* context) {
+    if (!context->error) return;
+    fprintf(context->error,
         "-v <format>, --format=<format> options:\n"
         "  Sets log print format verb and adverbs, where <format> is:\n"
         "    brief long process raw tag thread threadtime time\n"
@@ -405,7 +518,8 @@
 }
 // clang-format on
 
-static int setLogFormat(const char* formatString) {
+static int setLogFormat(android_logcat_context_internal* context,
+                        const char* formatString) {
     AndroidLogPrintFormat format;
 
     format = android_log_formatFromString(formatString);
@@ -415,7 +529,7 @@
         return -1;
     }
 
-    return android_log_setPrintFormat(g_logformat, format);
+    return android_log_setPrintFormat(context->logformat, format);
 }
 
 static const char multipliers[][2] = { { "" }, { "K" }, { "M" }, { "G" } };
@@ -460,25 +574,32 @@
     return true;
 }
 
-static void logcat_panic(enum helpType showHelp, const char* fmt, ...) {
+static void logcat_panic(android_logcat_context_internal* context,
+                         enum helpType showHelp, const char* fmt, ...) {
+    context->retval = EXIT_FAILURE;
+    if (!context->error) {
+        context->stop = true;
+        return;
+    }
+
     va_list args;
     va_start(args, fmt);
-    vfprintf(stderr, fmt, args);
+    vfprintf(context->error, fmt, args);
     va_end(args);
 
     switch (showHelp) {
         case HELP_TRUE:
-            show_help(getprogname());
+            show_help(context);
             break;
         case HELP_FORMAT:
-            show_format_help();
+            show_format_help(context);
             break;
         case HELP_FALSE:
         default:
             break;
     }
 
-    exit(EXIT_FAILURE);
+    context->stop = true;
 }
 
 static char* parseTime(log_time& t, const char* cp) {
@@ -576,6 +697,16 @@
     return retval;
 }
 
+const char* getenv(android_logcat_context_internal* context, const char* name) {
+    if (!context->envp || !name || !*name) return NULL;
+
+    for (size_t len = strlen(name), i = 0; context->envp[i]; ++i) {
+        if (strncmp(context->envp[i], name, len)) continue;
+        if (context->envp[i][len] == '=') return &context->envp[i][len + 1];
+    }
+    return NULL;
+}
+
 }  // namespace android
 
 void reportErrorName(const char** current, const char* name,
@@ -589,7 +720,7 @@
     *current = name;
 }
 
-int main(int argc, char** argv) {
+static int __logcat(android_logcat_context_internal* context) {
     using namespace android;
     int err;
     int hasSetLogFormat = 0;
@@ -612,15 +743,87 @@
     size_t pid = 0;
     bool got_t = false;
 
-    signal(SIGPIPE, exit);
+    // object instantiations before goto's can happen
+    log_device_t unexpected("unexpected", false);
+    const char* openDeviceFail = NULL;
+    const char* clearFail = NULL;
+    const char* setSizeFail = NULL;
+    const char* getSizeFail = NULL;
+    int argc = context->argc;
+    char* const* argv = context->argv;
 
-    g_logformat = android_log_format_new();
+    context->output = stdout;
+    context->error = stderr;
 
-    if (argc == 2 && 0 == strcmp(argv[1], "--help")) {
-        show_help(argv[0]);
-        return EXIT_SUCCESS;
+    for (int i = 0; i < argc; ++i) {
+        // Simulate shell stderr redirect parsing
+        if ((argv[i][0] != '2') || (argv[i][1] != '>')) continue;
+
+        size_t skip = (argv[i][2] == '>') + 2;
+        if (!strcmp(&argv[i][skip], "/dev/null")) {
+            context->stderr_null = true;
+        } else if (!strcmp(&argv[i][skip], "&1")) {
+            context->stderr_stdout = true;
+        } else {
+            // stderr file redirections are not supported
+            fprintf(context->stderr_stdout ? stdout : stderr,
+                    "stderr redirection to file %s unsupported, skipping\n",
+                    &argv[i][skip]);
+        }
     }
 
+    // Deal with setting up file descriptors and FILE pointers
+    if (context->error_fd >= 0) {
+        if (context->error_fd == context->output_fd) {
+            context->stderr_stdout = true;
+        } else if (context->stderr_null) {
+            close(context->error_fd);
+            context->error_fd = -1;
+        } else {
+            context->error = fdopen(context->error_fd, "web");
+            if (!context->error) {
+                context->retval = -errno;
+                fprintf(context->stderr_stdout ? stdout : stderr,
+                        "Failed to fdopen(error_fd=%d) %s\n", context->error_fd,
+                        strerror(errno));
+                goto exit;
+            }
+        }
+    }
+    if (context->output_fd >= 0) {
+        context->output = fdopen(context->output_fd, "web");
+        if (!context->output) {
+            context->retval = -errno;
+            fprintf(context->stderr_stdout ? stdout : context->error,
+                    "Failed to fdopen(output_fd=%d) %s\n", context->output_fd,
+                    strerror(errno));
+            goto exit;
+        }
+    }
+    if (context->stderr_stdout) context->error = context->output;
+    if (context->stderr_null) {
+        context->error_fd = -1;
+        context->error = NULL;
+    }
+    // Only happens if output=stdout
+    if ((context->output_fd < 0) && context->output) {
+        context->output_fd = fileno(context->output);
+    }
+    // Only happens if error=stdout || error=stderr
+    if ((context->error_fd < 0) && context->error) {
+        context->error_fd = fileno(context->error);
+    }
+
+    context->logformat = android_log_format_new();
+
+    if (argc == 2 && 0 == strcmp(argv[1], "--help")) {
+        show_help(context);
+        context->retval = EXIT_SUCCESS;
+        goto exit;
+    }
+
+    // danger: getopt is _not_ reentrant
+    optind = 1;
     for (;;) {
         int ret;
 
@@ -677,8 +880,9 @@
                 if (long_options[option_index].name == pid_str) {
                     // ToDo: determine runtime PID_MAX?
                     if (!getSizeTArg(optarg, &pid, 1)) {
-                        logcat_panic(HELP_TRUE, "%s %s out of range\n",
+                        logcat_panic(context, HELP_TRUE, "%s %s out of range\n",
                                      long_options[option_index].name, optarg);
+                        goto exit;
                     }
                     break;
                 }
@@ -688,11 +892,13 @@
                     // ToDo: implement API that supports setting a wrap timeout
                     size_t dummy = ANDROID_LOG_WRAP_DEFAULT_TIMEOUT;
                     if (optarg && !getSizeTArg(optarg, &dummy, 1)) {
-                        logcat_panic(HELP_TRUE, "%s %s out of range\n",
+                        logcat_panic(context, HELP_TRUE, "%s %s out of range\n",
                                      long_options[option_index].name, optarg);
+                        goto exit;
                     }
-                    if (dummy != ANDROID_LOG_WRAP_DEFAULT_TIMEOUT) {
-                        fprintf(stderr,
+                    if ((dummy != ANDROID_LOG_WRAP_DEFAULT_TIMEOUT) &&
+                        context->error) {
+                        fprintf(context->error,
                                 "WARNING: %s %u seconds, ignoring %zu\n",
                                 long_options[option_index].name,
                                 ANDROID_LOG_WRAP_DEFAULT_TIMEOUT, dummy);
@@ -700,11 +906,11 @@
                     break;
                 }
                 if (long_options[option_index].name == print_str) {
-                    g_printItAnyways = true;
+                    context->printItAnyways = true;
                     break;
                 }
                 if (long_options[option_index].name == debug_str) {
-                    g_debug = true;
+                    context->debug = true;
                     break;
                 }
                 if (long_options[option_index].name == id_str) {
@@ -715,7 +921,7 @@
 
             case 's':
                 // default to all silent
-                android_log_addFilterRule(g_logformat, "*:s");
+                android_log_addFilterRule(context->logformat, "*:s");
                 break;
 
             case 'c':
@@ -740,23 +946,29 @@
                 if (strspn(optarg, "0123456789") != strlen(optarg)) {
                     char* cp = parseTime(tail_time, optarg);
                     if (!cp) {
-                        logcat_panic(HELP_FALSE,
+                        logcat_panic(context, HELP_FALSE,
                                      "-%c \"%s\" not in time format\n", ret,
                                      optarg);
+                        goto exit;
                     }
                     if (*cp) {
                         char c = *cp;
                         *cp = '\0';
-                        fprintf(stderr,
+                        if (context->error) {
+                            fprintf(
+                                context->error,
                                 "WARNING: -%c \"%s\"\"%c%s\" time truncated\n",
                                 ret, optarg, c, cp + 1);
+                        }
                         *cp = c;
                     }
                 } else {
                     if (!getSizeTArg(optarg, &tail_lines, 1)) {
-                        fprintf(stderr,
-                                "WARNING: -%c %s invalid, setting to 1\n", ret,
-                                optarg);
+                        if (context->error) {
+                            fprintf(context->error,
+                                    "WARNING: -%c %s invalid, setting to 1\n",
+                                    ret, optarg);
+                        }
                         tail_lines = 1;
                     }
                 }
@@ -767,16 +979,17 @@
                 break;
 
             case 'e':
-                g_regex = new pcrecpp::RE(optarg);
+                context->regex = new pcrecpp::RE(optarg);
                 break;
 
             case 'm': {
                 char* end = NULL;
-                if (!getSizeTArg(optarg, &g_maxCount)) {
-                    logcat_panic(
-                        HELP_FALSE,
-                        "-%c \"%s\" isn't an integer greater than zero\n", ret,
-                        optarg);
+                if (!getSizeTArg(optarg, &context->maxCount)) {
+                    logcat_panic(context, HELP_FALSE,
+                                 "-%c \"%s\" isn't an "
+                                 "integer greater than zero\n",
+                                 ret, optarg);
+                    goto exit;
                 }
             } break;
 
@@ -816,7 +1029,9 @@
                 }
 
                 if (!setLogSize) {
-                    logcat_panic(HELP_FALSE, "ERROR: -G <num><multiplier>\n");
+                    logcat_panic(context, HELP_FALSE,
+                                 "ERROR: -G <num><multiplier>\n");
+                    goto exit;
                 }
             } break;
 
@@ -845,8 +1060,9 @@
                         const char* name = android_log_id_to_name(log_id);
 
                         if (strcmp(name, optarg) != 0) {
-                            logcat_panic(HELP_TRUE, "unknown buffer %s\n",
-                                         optarg);
+                            logcat_panic(context, HELP_TRUE,
+                                         "unknown buffer %s\n", optarg);
+                            goto exit;
                         }
                         if (log_id == LOG_ID_SECURITY) allSelected = false;
                         idMask |= (1 << log_id);
@@ -889,12 +1105,12 @@
                     } else {
                         devices = dev = d;
                     }
-                    g_devCount++;
+                    context->devCount++;
                 }
             } break;
 
             case 'B':
-                g_printBinary = 1;
+                context->printBinary = 1;
                 break;
 
             case 'f':
@@ -902,32 +1118,36 @@
                     tail_time = lastLogTime(optarg);
                 }
                 // redirect output to a file
-                g_outputFileName = optarg;
+                context->outputFileName = optarg;
                 break;
 
             case 'r':
-                if (!getSizeTArg(optarg, &g_logRotateSizeKBytes, 1)) {
-                    logcat_panic(HELP_TRUE, "Invalid parameter \"%s\" to -r\n",
-                                 optarg);
+                if (!getSizeTArg(optarg, &context->logRotateSizeKBytes, 1)) {
+                    logcat_panic(context, HELP_TRUE,
+                                 "Invalid parameter \"%s\" to -r\n", optarg);
+                    goto exit;
                 }
                 break;
 
             case 'n':
-                if (!getSizeTArg(optarg, &g_maxRotatedLogs, 1)) {
-                    logcat_panic(HELP_TRUE, "Invalid parameter \"%s\" to -n\n",
-                                 optarg);
+                if (!getSizeTArg(optarg, &context->maxRotatedLogs, 1)) {
+                    logcat_panic(context, HELP_TRUE,
+                                 "Invalid parameter \"%s\" to -n\n", optarg);
+                    goto exit;
                 }
                 break;
 
             case 'v':
                 if (!strcmp(optarg, "help") || !strcmp(optarg, "--help")) {
-                    show_format_help();
-                    exit(0);
+                    show_format_help(context);
+                    context->retval = EXIT_SUCCESS;
+                    goto exit;
                 }
-                err = setLogFormat(optarg);
+                err = setLogFormat(context, optarg);
                 if (err < 0) {
-                    logcat_panic(HELP_FORMAT,
+                    logcat_panic(context, HELP_FORMAT,
                                  "Invalid parameter \"%s\" to -v\n", optarg);
+                    goto exit;
                 }
                 hasSetLogFormat |= err;
                 break;
@@ -947,7 +1167,10 @@
 
                     const char* logcat = strstr(cmdline.c_str(), KERNEL_OPTION);
                     // if nothing found or invalid filters, exit quietly
-                    if (!logcat) return EXIT_SUCCESS;
+                    if (!logcat) {
+                        context->retval = EXIT_SUCCESS;
+                        goto exit;
+                    }
 
                     const char* p = logcat + strlen(KERNEL_OPTION);
                     const char* q = strpbrk(p, " \t\n\r");
@@ -965,16 +1188,22 @@
                     std::string devname = "/dev/" + std::string(p, len);
                     cmdline.erase();
 
-                    fprintf(stderr, "logcat using %s\n", devname.c_str());
+                    if (context->error) {
+                        fprintf(context->error, "logcat using %s\n",
+                                devname.c_str());
+                    }
 
-                    int fd = open(devname.c_str(), O_WRONLY);
+                    FILE* fp = fopen(devname.c_str(), "web");
                     devname.erase();
-                    if (fd < 0) break;
+                    if (!fp) break;
 
                     // close output and error channels, replace with console
-                    dup2(fd, 1);
-                    dup2(fd, 2);
-                    close(fd);
+                    android::close_output(context);
+                    android::close_error(context);
+                    context->stderr_stdout = true;
+                    context->output = fp;
+                    context->error = context->output;
+                    if (context->stderr_null) context->error = NULL;
                 }
                 break;
 
@@ -983,56 +1212,62 @@
                 break;
 
             case ':':
-                logcat_panic(HELP_TRUE, "Option -%c needs an argument\n",
-                             optopt);
-                break;
+                logcat_panic(context, HELP_TRUE,
+                             "Option -%c needs an argument\n", optopt);
+                goto exit;
 
             default:
-                logcat_panic(HELP_TRUE, "Unrecognized Option %c\n", optopt);
-                break;
+                logcat_panic(context, HELP_TRUE, "Unrecognized Option %c\n",
+                             optopt);
+                goto exit;
         }
     }
 
-    if (g_maxCount && got_t) {
-        logcat_panic(HELP_TRUE,
+    if (context->maxCount && got_t) {
+        logcat_panic(context, HELP_TRUE,
                      "Cannot use -m (--max-count) and -t together\n");
+        goto exit;
     }
-    if (g_printItAnyways && (!g_regex || !g_maxCount)) {
+    if (context->printItAnyways && (!context->regex || !context->maxCount)) {
         // One day it would be nice if --print -v color and --regex <expr>
         // could play with each other and show regex highlighted content.
         // clang-format off
-        fprintf(stderr, "WARNING: "
+        if (context->error) {
+            fprintf(context->error, "WARNING: "
                             "--print ignored, to be used in combination with\n"
-                        "         "
+                                "         "
                             "--regex <expr> and --max-count <N>\n");
-        // clang-format on
-        g_printItAnyways = false;
+        }
+        context->printItAnyways = false;
     }
 
     if (!devices) {
         dev = devices = new log_device_t("main", false);
-        g_devCount = 1;
+        context->devCount = 1;
         if (android_name_to_log_id("system") == LOG_ID_SYSTEM) {
             dev = dev->next = new log_device_t("system", false);
-            g_devCount++;
+            context->devCount++;
         }
         if (android_name_to_log_id("crash") == LOG_ID_CRASH) {
             dev = dev->next = new log_device_t("crash", false);
-            g_devCount++;
+            context->devCount++;
         }
     }
 
-    if (g_logRotateSizeKBytes != 0 && g_outputFileName == NULL) {
-        logcat_panic(HELP_TRUE, "-r requires -f as well\n");
+    if (context->logRotateSizeKBytes != 0 && context->outputFileName == NULL) {
+        logcat_panic(context, HELP_TRUE, "-r requires -f as well\n");
+        goto exit;
     }
 
     if (setId != NULL) {
-        if (g_outputFileName == NULL) {
-            logcat_panic(HELP_TRUE, "--id='%s' requires -f as well\n", setId);
+        if (context->outputFileName == NULL) {
+            logcat_panic(context, HELP_TRUE,
+                         "--id='%s' requires -f as well\n", setId);
+            goto exit;
         }
 
-        std::string file_name =
-            android::base::StringPrintf("%s.id", g_outputFileName);
+        std::string file_name = android::base::StringPrintf(
+                                        "%s.id", context->outputFileName);
         std::string file;
         bool file_ok = android::base::ReadFileToString(file_name, &file);
         android::base::WriteStringToFile(setId, file_name, S_IRUSR | S_IWUSR,
@@ -1043,45 +1278,53 @@
     }
 
     if (hasSetLogFormat == 0) {
-        const char* logFormat = getenv("ANDROID_PRINTF_LOG");
+        const char* logFormat = android::getenv(context, "ANDROID_PRINTF_LOG");
 
         if (logFormat != NULL) {
-            err = setLogFormat(logFormat);
-            if (err < 0) {
-                fprintf(stderr, "invalid format in ANDROID_PRINTF_LOG '%s'\n",
+            err = setLogFormat(context, logFormat);
+            if ((err < 0) && context->error) {
+                fprintf(context->error,
+                        "invalid format in ANDROID_PRINTF_LOG '%s'\n",
                         logFormat);
             }
         } else {
-            setLogFormat("threadtime");
+            setLogFormat(context, "threadtime");
         }
     }
 
     if (forceFilters.size()) {
-        err = android_log_addFilterString(g_logformat, forceFilters.c_str());
+        err = android_log_addFilterString(context->logformat,
+                                          forceFilters.c_str());
         if (err < 0) {
-            logcat_panic(HELP_FALSE,
+            logcat_panic(context, HELP_FALSE,
                          "Invalid filter expression in logcat args\n");
+            goto exit;
         }
     } else if (argc == optind) {
         // Add from environment variable
-        char* env_tags_orig = getenv("ANDROID_LOG_TAGS");
+        const char* env_tags_orig = android::getenv(context, "ANDROID_LOG_TAGS");
 
         if (env_tags_orig != NULL) {
-            err = android_log_addFilterString(g_logformat, env_tags_orig);
+            err = android_log_addFilterString(context->logformat,
+                                              env_tags_orig);
 
             if (err < 0) {
-                logcat_panic(HELP_TRUE,
-                             "Invalid filter expression in ANDROID_LOG_TAGS\n");
+                logcat_panic(context, HELP_TRUE,
+                            "Invalid filter expression in ANDROID_LOG_TAGS\n");
+                goto exit;
             }
         }
     } else {
         // Add from commandline
-        for (int i = optind; i < argc; i++) {
-            err = android_log_addFilterString(g_logformat, argv[i]);
+        for (int i = optind ; i < argc ; i++) {
+            // skip stderr redirections of _all_ kinds
+            if ((argv[i][0] == '2') && (argv[i][1] == '>')) continue;
 
+            err = android_log_addFilterString(context->logformat, argv[i]);
             if (err < 0) {
-                logcat_panic(HELP_TRUE, "Invalid filter expression '%s'\n",
-                             argv[i]);
+                logcat_panic(context, HELP_TRUE,
+                             "Invalid filter expression '%s'\n", argv[i]);
+                goto exit;
             }
         }
     }
@@ -1092,10 +1335,6 @@
     } else {
         logger_list = android_logger_list_alloc(mode, tail_lines, pid);
     }
-    const char* openDeviceFail = NULL;
-    const char* clearFail = NULL;
-    const char* setSizeFail = NULL;
-    const char* getSizeFail = NULL;
     // We have three orthogonal actions below to clear, set log size and
     // get log size. All sharing the same iteration loop.
     while (dev) {
@@ -1109,22 +1348,21 @@
         }
 
         if (clearLog || setId) {
-            if (g_outputFileName) {
+            if (context->outputFileName) {
                 int maxRotationCountDigits =
-                    (g_maxRotatedLogs > 0)
-                        ? (int)(floor(log10(g_maxRotatedLogs) + 1))
-                        : 0;
+                    (context->maxRotatedLogs > 0) ?
+                        (int)(floor(log10(context->maxRotatedLogs) + 1)) :
+                        0;
 
-                for (int i = g_maxRotatedLogs; i >= 0; --i) {
+                for (int i = context->maxRotatedLogs ; i >= 0 ; --i) {
                     std::string file;
 
                     if (i == 0) {
-                        file =
-                            android::base::StringPrintf("%s", g_outputFileName);
-                    } else {
                         file = android::base::StringPrintf(
-                            "%s.%.*d", g_outputFileName, maxRotationCountDigits,
-                            i);
+                            "%s", context->outputFileName);
+                    } else {
+                        file = android::base::StringPrintf("%s.%.*d",
+                            context->outputFileName, maxRotationCountDigits, i);
                     }
 
                     if (file.length() == 0) {
@@ -1158,32 +1396,44 @@
             if ((size < 0) || (readable < 0)) {
                 reportErrorName(&getSizeFail, dev->device, allSelected);
             } else {
-                printf(
-                    "%s: ring buffer is %ld%sb (%ld%sb consumed),"
-                    " max entry is %db, max payload is %db\n",
-                    dev->device, value_of_size(size), multiplier_of_size(size),
-                    value_of_size(readable), multiplier_of_size(readable),
-                    (int)LOGGER_ENTRY_MAX_LEN, (int)LOGGER_ENTRY_MAX_PAYLOAD);
+                std::string str = android::base::StringPrintf(
+                       "%s: ring buffer is %ld%sb (%ld%sb consumed),"
+                         " max entry is %db, max payload is %db\n",
+                       dev->device,
+                       value_of_size(size), multiplier_of_size(size),
+                       value_of_size(readable), multiplier_of_size(readable),
+                       (int)LOGGER_ENTRY_MAX_LEN,
+                       (int)LOGGER_ENTRY_MAX_PAYLOAD);
+                TEMP_FAILURE_RETRY(write(context->output_fd,
+                                         str.data(), str.length()));
             }
         }
 
         dev = dev->next;
     }
+
+    context->retval = EXIT_SUCCESS;
+
     // report any errors in the above loop and exit
     if (openDeviceFail) {
-        logcat_panic(HELP_FALSE, "Unable to open log device '%s'\n",
-                     openDeviceFail);
+        logcat_panic(context, HELP_FALSE,
+                     "Unable to open log device '%s'\n", openDeviceFail);
+        goto close;
     }
     if (clearFail) {
-        logcat_panic(HELP_FALSE, "failed to clear the '%s' log\n", clearFail);
+        logcat_panic(context, HELP_FALSE,
+                     "failed to clear the '%s' log\n", clearFail);
+        goto close;
     }
     if (setSizeFail) {
-        logcat_panic(HELP_FALSE, "failed to set the '%s' log size\n",
-                     setSizeFail);
+        logcat_panic(context, HELP_FALSE,
+                     "failed to set the '%s' log size\n", setSizeFail);
+        goto close;
     }
     if (getSizeFail) {
-        logcat_panic(HELP_FALSE, "failed to get the readable '%s' log size",
-                     getSizeFail);
+        logcat_panic(context, HELP_FALSE,
+                     "failed to get the readable '%s' log size", getSizeFail);
+        goto close;
     }
 
     if (setPruneList) {
@@ -1194,13 +1444,15 @@
         if (asprintf(&buf, "%-*s", (int)(bLen - 1), setPruneList) > 0) {
             buf[len] = '\0';
             if (android_logger_set_prune_list(logger_list, buf, bLen)) {
-                logcat_panic(HELP_FALSE, "failed to set the prune list");
+                logcat_panic(context, HELP_FALSE,
+                             "failed to set the prune list");
             }
             free(buf);
         } else {
-            logcat_panic(HELP_FALSE, "failed to set the prune list (alloc)");
+            logcat_panic(context, HELP_FALSE,
+                         "failed to set the prune list (alloc)");
         }
-        return EXIT_SUCCESS;
+        goto close;
     }
 
     if (printStatistics || getPruneList) {
@@ -1229,7 +1481,8 @@
         }
 
         if (!buf) {
-            logcat_panic(HELP_FALSE, "failed to read data");
+            logcat_panic(context, HELP_FALSE, "failed to read data");
+            goto close;
         }
 
         // remove trailing FF
@@ -1251,31 +1504,32 @@
             }
         }
 
-        printf("%s", cp);
+        len = strlen(cp);
+        TEMP_FAILURE_RETRY(write(context->output_fd, cp, len));
         delete[] buf;
-        return EXIT_SUCCESS;
+        goto close;
     }
 
     if (getLogSize || setLogSize || clearLog) {
-        return EXIT_SUCCESS;
+        goto close;
     }
 
-    setupOutputAndSchedulingPolicy((mode & ANDROID_LOG_NONBLOCK) == 0);
+    setupOutputAndSchedulingPolicy(context, (mode & ANDROID_LOG_NONBLOCK) == 0);
+    if (context->stop) goto close;
 
     // LOG_EVENT_INT(10, 12345);
     // LOG_EVENT_LONG(11, 0x1122334455667788LL);
     // LOG_EVENT_STRING(0, "whassup, doc?");
 
     dev = NULL;
-    log_device_t unexpected("unexpected", false);
 
-    while (!g_maxCount || (g_printCount < g_maxCount)) {
+    while (!context->stop &&
+           (!context->maxCount || (context->printCount < context->maxCount))) {
         struct log_msg log_msg;
-        log_device_t* d;
         int ret = android_logger_list_read(logger_list, &log_msg);
-
         if (ret == 0) {
-            logcat_panic(HELP_FALSE, "read: unexpected EOF!\n");
+            logcat_panic(context, HELP_FALSE, "read: unexpected EOF!\n");
+            break;
         }
 
         if (ret < 0) {
@@ -1284,37 +1538,81 @@
             }
 
             if (ret == -EIO) {
-                logcat_panic(HELP_FALSE, "read: unexpected EOF!\n");
+                logcat_panic(context, HELP_FALSE, "read: unexpected EOF!\n");
+                break;
             }
             if (ret == -EINVAL) {
-                logcat_panic(HELP_FALSE, "read: unexpected length.\n");
+                logcat_panic(context, HELP_FALSE, "read: unexpected length.\n");
+                break;
             }
-            logcat_panic(HELP_FALSE, "logcat read failure");
+            logcat_panic(context, HELP_FALSE, "logcat read failure");
+            break;
         }
 
+        log_device_t* d;
         for (d = devices; d; d = d->next) {
             if (android_name_to_log_id(d->device) == log_msg.id()) {
                 break;
             }
         }
         if (!d) {
-            g_devCount = 2;  // set to Multiple
+            context->devCount = 2; // set to Multiple
             d = &unexpected;
             d->binary = log_msg.id() == LOG_ID_EVENTS;
         }
 
         if (dev != d) {
             dev = d;
-            maybePrintStart(dev, printDividers);
+            maybePrintStart(context, dev, printDividers);
+            if (context->stop) break;
         }
-        if (g_printBinary) {
-            printBinary(&log_msg);
+        if (context->printBinary) {
+            printBinary(context, &log_msg);
         } else {
-            processBuffer(dev, &log_msg);
+            processBuffer(context, dev, &log_msg);
         }
     }
 
+close:
     android_logger_list_free(logger_list);
 
-    return EXIT_SUCCESS;
+exit:
+    return context->retval;
+}
+
+// Can block
+int android_logcat_run_command(android_logcat_context ctx,
+                               int output, int error,
+                               int argc, char* const* argv,
+                               char* const* envp) {
+    android_logcat_context_internal* context = ctx;
+
+    context->output_fd = output;
+    context->error_fd = error;
+    context->argc = argc;
+    context->argv = argv;
+    context->envp = envp;
+    context->stop = false;
+    return __logcat(context);
+}
+
+// Finished with context
+int android_logcat_destroy(android_logcat_context* ctx) {
+    android_logcat_context_internal* context = *ctx;
+
+    *ctx = NULL;
+
+    context->stop = true;
+
+    delete context->regex;
+    android::close_output(context);
+    android::close_error(context);
+
+    android_closeEventTagMap(context->eventTagMap);
+
+    int retval = context->retval;
+
+    free(context);
+
+    return retval;
 }