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;
+}