liblogcat: add android_logcat_popen and android_logcat_system

Supply a wrapper to the logcat API that provides some analogous
functionality to popen and system libc calls with some bits of
KISS shell-like parsing for environment, quotes and error
redirection handling.

Test: gTest logcat-unit-tests
Bug: 35326290
Change-Id: I9494ce71267ad2b2bec7fcccfc7d4beddae9aea6
diff --git a/logcat/logcat_system.cpp b/logcat/logcat_system.cpp
new file mode 100644
index 0000000..ea393bd
--- /dev/null
+++ b/logcat/logcat_system.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 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.
+ * 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.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <log/logcat.h>
+
+static std::string unquote(const char*& cp, const char*& delim) {
+    if ((*cp == '\'') || (*cp == '"')) {
+        // KISS: Simple quotes. Do not handle the case
+        //       of concatenation like "blah"foo'bar'
+        char quote = *cp++;
+        delim = strchr(cp, quote);
+        if (!delim) delim = cp + strlen(cp);
+        std::string str(cp, delim);
+        if (*delim) ++delim;
+        return str;
+    }
+    delim = strpbrk(cp, " \t\f\r\n");
+    if (!delim) delim = cp + strlen(cp);
+    return std::string(cp, delim);
+}
+
+static bool __android_logcat_parse(const char* command,
+                                   std::vector<std::string>& args,
+                                   std::vector<std::string>& envs) {
+    for (const char *delim, *cp = command; cp && *cp; cp = delim) {
+        while (isspace(*cp)) ++cp;
+        if ((args.size() == 0) && (*cp != '=') && !isdigit(*cp)) {
+            const char* env = cp;
+            while (isalnum(*cp) || (*cp == '_')) ++cp;
+            if (cp && (*cp == '=')) {
+                std::string str(env, ++cp);
+                str += unquote(cp, delim);
+                envs.push_back(str);
+                continue;
+            }
+            cp = env;
+        }
+        args.push_back(unquote(cp, delim));
+        if ((args.size() == 1) && (args[0] != "logcat") &&
+            (args[0] != "/system/bin/logcat")) {
+            return false;
+        }
+    }
+    return args.size() != 0;
+}
+
+FILE* android_logcat_popen(android_logcat_context* ctx, const char* command) {
+    *ctx = NULL;
+
+    std::vector<std::string> args;
+    std::vector<std::string> envs;
+    if (!__android_logcat_parse(command, args, envs)) return NULL;
+
+    std::vector<const char*> argv;
+    for (auto& str : args) {
+        argv.push_back(str.c_str());
+    }
+    argv.push_back(NULL);
+
+    std::vector<const char*> envp;
+    for (auto& str : envs) {
+        envp.push_back(str.c_str());
+    }
+    envp.push_back(NULL);
+
+    *ctx = create_android_logcat();
+    if (!*ctx) return NULL;
+
+    int fd = android_logcat_run_command_thread(
+        *ctx, argv.size() - 1, (char* const*)&argv[0], (char* const*)&envp[0]);
+    argv.clear();
+    args.clear();
+    envp.clear();
+    envs.clear();
+    if (fd < 0) {
+        android_logcat_destroy(ctx);
+        return NULL;
+    }
+
+    FILE* retval = fdopen(fd, "reb");
+    if (!retval) android_logcat_destroy(ctx);
+    return retval;
+}
+
+int android_logcat_pclose(android_logcat_context* ctx, FILE* output) {
+    if (*ctx) {
+        static const useconds_t wait_sample = 20000;
+        // Wait two seconds maximum
+        for (size_t retry = ((2 * 1000000) + wait_sample - 1) / wait_sample;
+             android_logcat_run_command_thread_running(*ctx) && retry; --retry) {
+            usleep(wait_sample);
+        }
+    }
+
+    if (output) fclose(output);
+    return android_logcat_destroy(ctx);
+}
+
+int android_logcat_system(const char* command) {
+    std::vector<std::string> args;
+    std::vector<std::string> envs;
+    if (!__android_logcat_parse(command, args, envs)) return -1;
+
+    std::vector<const char*> argv;
+    for (auto& str : args) {
+        argv.push_back(str.c_str());
+    }
+    argv.push_back(NULL);
+
+    std::vector<const char*> envp;
+    for (auto& str : envs) {
+        envp.push_back(str.c_str());
+    }
+    envp.push_back(NULL);
+
+    android_logcat_context ctx = create_android_logcat();
+    if (!ctx) return -1;
+    /* Command return value */
+    int retval = android_logcat_run_command(ctx, -1, -1, argv.size() - 1,
+                                            (char* const*)&argv[0],
+                                            (char* const*)&envp[0]);
+    /* destroy return value */
+    int ret = android_logcat_destroy(&ctx);
+    /* Paranoia merging any discrepancies between the two return values */
+    if (!ret) ret = retval;
+    return ret;
+}