liblogcat: add android_logcat_run_command_thread

A non-blocking API to run a logcat function in a background thread.
Returns a read end of a pipe to collect the output.

Test: gTest logcat-unit-tests
Bug: 35326290
Change-Id: Idc14e4ad955e0b2b9fafa5d3aeed8cd7fb4069fb
diff --git a/logcat/include/log/logcat.h b/logcat/include/log/logcat.h
index 41104fe..c41a6b7 100644
--- a/logcat/include/log/logcat.h
+++ b/logcat/include/log/logcat.h
@@ -76,9 +76,19 @@
 int android_logcat_run_command(android_logcat_context ctx, int output, int error,
                                int argc, char* const* argv, char* const* envp);
 
+/* Will not block, performed in-process
+ *
+ * Starts a thread, opens a pipe, returns reading end fd, saves off argv.
+ * The command supports 2>&1 (mix content) and 2>/dev/null (drop content) for
+ * scripted error (stderr) redirection.
+ */
+int android_logcat_run_command_thread(android_logcat_context ctx, int argc,
+                                      char* const* argv, char* const* envp);
+int android_logcat_run_command_thread_running(android_logcat_context ctx);
+
 /* Finished with context
  *
- * Free up all associated resources.
+ * Kill the command thread ASAP (if any), and free up all associated resources.
  *
  * Return value is the result of the android_logcat_run_command, or
  * non-zero for any errors.
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index b048289..15cef1a 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -22,6 +22,7 @@
 #include <fcntl.h>
 #include <getopt.h>
 #include <math.h>
+#include <pthread.h>
 #include <sched.h>
 #include <stdarg.h>
 #include <stdio.h>
@@ -35,8 +36,10 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <atomic>
 #include <memory>
 #include <string>
+#include <vector>
 
 #include <android-base/file.h>
 #include <android-base/stringprintf.h>
@@ -55,18 +58,25 @@
 
 struct android_logcat_context_internal {
     // status
-    int retval;
-    // Arguments passed in
+    volatile std::atomic_int retval;  // valid if thread_stopped set
+    // Arguments passed in, or copies and storage thereof if a thread.
     int argc;
     char* const* argv;
     char* const* envp;
+    std::vector<std::string> args;
+    std::vector<const char*> argv_hold;
+    std::vector<std::string> envs;
+    std::vector<const char*> envp_hold;
     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
+    int fds[2];    // From popen call
+    FILE* output;  // everything writes to fileno(output), buffer unused
+    FILE* error;   // unless error == output.
+    pthread_t thr;
+    volatile std::atomic_bool stop;  // quick exit flag
+    volatile std::atomic_bool thread_stopped;
     bool stderr_null;    // shell "2>/dev/null"
     bool stderr_stdout;  // shell "2>&1"
 
@@ -100,10 +110,17 @@
         1, sizeof(android_logcat_context_internal));
     if (!context) return NULL;
 
+    context->fds[0] = -1;
+    context->fds[1] = -1;
     context->output_fd = -1;
     context->error_fd = -1;
     context->maxRotatedLogs = DEFAULT_MAX_ROTATED_LOGS;
 
+    context->argv_hold.clear();
+    context->args.clear();
+    context->envp_hold.clear();
+    context->envs.clear();
+
     return (android_logcat_context)context;
 }
 
@@ -160,12 +177,18 @@
             if (context->output_fd == fileno(context->output)) {
                 context->output_fd = -1;
             }
+            if (context->fds[1] == fileno(context->output)) {
+                context->fds[1] = -1;
+            }
             fclose(context->output);
         }
         context->output = NULL;
     }
     if (context->output_fd >= 0) {
         if (context->output_fd != fileno(stdout)) {
+            if (context->fds[1] == context->output_fd) {
+                context->fds[1] = -1;
+            }
             close(context->output_fd);
         }
         context->output_fd = -1;
@@ -190,6 +213,9 @@
             if (context->error_fd == fileno(context->error)) {
                 context->error_fd = -1;
             }
+            if (context->fds[1] == fileno(context->error)) {
+                context->fds[1] = -1;
+            }
             fclose(context->error);
         }
         context->error = NULL;
@@ -197,6 +223,9 @@
     if (context->error_fd >= 0) {
         if ((context->error_fd != fileno(stdout)) &&
             (context->error_fd != fileno(stderr))) {
+            if (context->fds[1] == context->error_fd) {
+                context->fds[1] = -1;
+            }
             close(context->error_fd);
         }
         context->error_fd = -1;
@@ -1577,6 +1606,21 @@
     android_logger_list_free(logger_list);
 
 exit:
+    // close write end of pipe to help things along
+    if (context->output_fd == context->fds[1]) {
+        android::close_output(context);
+    }
+    if (context->error_fd == context->fds[1]) {
+        android::close_error(context);
+    }
+    if (context->fds[1] >= 0) {
+        // NB: should be closed by the above
+        int save_errno = errno;
+        close(context->fds[1]);
+        errno = save_errno;
+        context->fds[1] = -1;
+    }
+    context->thread_stopped = true;
     return context->retval;
 }
 
@@ -1593,9 +1637,111 @@
     context->argv = argv;
     context->envp = envp;
     context->stop = false;
+    context->thread_stopped = false;
     return __logcat(context);
 }
 
+// starts a thread, opens a pipe, returns reading end.
+int android_logcat_run_command_thread(android_logcat_context ctx,
+                                      int argc, char* const* argv,
+                                      char* const* envp) {
+    android_logcat_context_internal* context = ctx;
+
+    int save_errno = EBUSY;
+    if ((context->fds[0] >= 0) || (context->fds[1] >= 0)) {
+        goto exit;
+    }
+
+    if (pipe(context->fds) < 0) {
+        save_errno = errno;
+        goto exit;
+    }
+
+    pthread_attr_t attr;
+    if (pthread_attr_init(&attr)) {
+        save_errno = errno;
+        goto close_exit;
+    }
+
+    struct sched_param param;
+    memset(&param, 0, sizeof(param));
+    pthread_attr_setschedparam(&attr, &param);
+    pthread_attr_setschedpolicy(&attr, SCHED_BATCH);
+    if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) {
+        int save_errno = errno;
+        goto pthread_attr_exit;
+    }
+
+    context->stop = false;
+    context->thread_stopped = false;
+    context->output_fd = context->fds[1];
+    // save off arguments so they remain while thread is active.
+    for (int i = 0; i < argc; ++i) {
+        context->args.push_back(std::string(argv[i]));
+    }
+    // save off environment so they remain while thread is active.
+    if (envp) for (size_t i = 0; envp[i]; ++i) {
+        context->envs.push_back(std::string(envp[i]));
+    }
+
+    for (auto& str : context->args) {
+        context->argv_hold.push_back(str.c_str());
+    }
+    context->argv_hold.push_back(NULL);
+    for (auto& str : context->envs) {
+        context->envp_hold.push_back(str.c_str());
+    }
+    context->envp_hold.push_back(NULL);
+
+    context->argc = context->argv_hold.size() - 1;
+    context->argv = (char* const*)&context->argv_hold[0];
+    context->envp = (char* const*)&context->envp_hold[0];
+
+#ifdef DEBUG
+    fprintf(stderr, "argv[%d] = {", context->argc);
+    for (auto str : context->argv_hold) {
+        fprintf(stderr, " \"%s\"", str ?: "NULL");
+    }
+    fprintf(stderr, " }\n");
+    fflush(stderr);
+#endif
+    context->retval = EXIT_SUCCESS;
+    if (pthread_create(&context->thr, &attr,
+                       (void*(*)(void*))__logcat, context)) {
+        int save_errno = errno;
+        goto argv_exit;
+    }
+    pthread_attr_destroy(&attr);
+
+    return context->fds[0];
+
+argv_exit:
+    context->argv_hold.clear();
+    context->args.clear();
+    context->envp_hold.clear();
+    context->envs.clear();
+pthread_attr_exit:
+    pthread_attr_destroy(&attr);
+close_exit:
+    close(context->fds[0]);
+    context->fds[0] = -1;
+    close(context->fds[1]);
+    context->fds[1] = -1;
+exit:
+    errno = save_errno;
+    context->stop = true;
+    context->thread_stopped = true;
+    context->retval = EXIT_FAILURE;
+    return -1;
+}
+
+// test if the thread is still doing 'stuff'
+int android_logcat_run_command_thread_running(android_logcat_context ctx) {
+    android_logcat_context_internal* context = ctx;
+
+    return context->thread_stopped == false;
+}
+
 // Finished with context
 int android_logcat_destroy(android_logcat_context* ctx) {
     android_logcat_context_internal* context = *ctx;
@@ -1604,9 +1750,28 @@
 
     context->stop = true;
 
+    while (context->thread_stopped == false) {
+        sched_yield();
+    }
+
     delete context->regex;
+    context->argv_hold.clear();
+    context->args.clear();
+    context->envp_hold.clear();
+    context->envs.clear();
+    if (context->fds[0] >= 0) {
+        close(context->fds[0]);
+        context->fds[0] = -1;
+    }
     android::close_output(context);
     android::close_error(context);
+    if (context->fds[1] >= 0) {
+        // NB: could be closed by the above fclose(s), ignore error.
+        int save_errno = errno;
+        close(context->fds[1]);
+        errno = save_errno;
+        context->fds[1] = -1;
+    }
 
     android_closeEventTagMap(context->eventTagMap);