diff options
| author | Mark Salyzyn <salyzyn@google.com> | 2017-02-10 13:09:07 -0800 |
|---|---|---|
| committer | Mark Salyzyn <salyzyn@google.com> | 2017-02-17 14:42:14 -0800 |
| commit | c0cf90d1fb6aca1e6ae6c06d4d6231dd4d47d3cb (patch) | |
| tree | 560c1e2d2a4dfab393e630d25f7839d05396f505 /logcat | |
| parent | f329029081f41f86d4631b4caeeb1ecf7ccae3d9 (diff) | |
| download | system_core-c0cf90d1fb6aca1e6ae6c06d4d6231dd4d47d3cb.tar.gz system_core-c0cf90d1fb6aca1e6ae6c06d4d6231dd4d47d3cb.tar.bz2 system_core-c0cf90d1fb6aca1e6ae6c06d4d6231dd4d47d3cb.zip | |
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
Diffstat (limited to 'logcat')
| -rw-r--r-- | logcat/Android.mk | 22 | ||||
| -rw-r--r-- | logcat/include/log/logcat.h | 94 | ||||
| -rw-r--r-- | logcat/logcat.cpp | 768 | ||||
| -rw-r--r-- | logcat/logcat_main.cpp | 30 | ||||
| -rw-r--r-- | logcat/tests/logcat_test.cpp | 2 |
5 files changed, 675 insertions, 241 deletions
diff --git a/logcat/Android.mk b/logcat/Android.mk index 1dacbe1ad..4b8746c9a 100644 --- a/logcat/Android.mk +++ b/logcat/Android.mk @@ -1,20 +1,32 @@ # Copyright 2006-2014 The Android Open Source Project -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) +LOCAL_PATH := $(call my-dir) -LOCAL_SRC_FILES:= logcat.cpp event.logtags +logcatLibs := liblog libbase libcutils libpcrecpp -LOCAL_SHARED_LIBRARIES := liblog libbase libcutils libpcrecpp +include $(CLEAR_VARS) LOCAL_MODULE := logcat - +LOCAL_SRC_FILES := logcat_main.cpp event.logtags +LOCAL_SHARED_LIBRARIES := liblogcat $(logcatLibs) +LOCAL_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_CFLAGS := -Werror include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) +LOCAL_MODULE := liblogcat +LOCAL_SRC_FILES := logcat.cpp +LOCAL_SHARED_LIBRARIES := $(logcatLibs) +LOCAL_C_INCLUDES := $(LOCAL_PATH)/include +LOCAL_EXPORT_C_INCLUDES_DIR := $(LOCAL_PATH)/include +LOCAL_CFLAGS := -Werror + +include $(BUILD_SHARED_LIBRARY) + +include $(CLEAR_VARS) + LOCAL_MODULE := logpersist.start LOCAL_MODULE_TAGS := debug LOCAL_MODULE_CLASS := EXECUTABLES diff --git a/logcat/include/log/logcat.h b/logcat/include/log/logcat.h new file mode 100644 index 000000000..41104feaf --- /dev/null +++ b/logcat/include/log/logcat.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2005-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. + */ + +#ifndef _LIBS_LOGCAT_H /* header boilerplate */ +#define _LIBS_LOGCAT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __ANDROID_USE_LIBLOG_LOGCAT_INTERFACE +#ifndef __ANDROID_API__ +#define __ANDROID_USE_LIBLOG_LOGCAT_INTERFACE 1 +#elif __ANDROID_API__ > 24 /* > Nougat */ +#define __ANDROID_USE_LIBLOG_LOGCAT_INTERFACE 1 +#else +#define __ANDROID_USE_LIBLOG_LOGCAT_INTERFACE 0 +#endif +#endif + +#if __ANDROID_USE_LIBLOG_LOGCAT_INTERFACE + +/* For managing an in-process logcat function, rather than forking/execing + * + * It also serves as the basis for the logcat command. + * + * The following C API allows a logcat instance to be created, run + * to completion, and then release all the associated resources. + */ + +/* + * The opaque context + */ +#ifndef __android_logcat_context_defined /* typedef boilerplate */ +#define __android_logcat_context_defined +typedef struct android_logcat_context_internal* android_logcat_context; +#endif + +/* Creates a context associated with this logcat instance + * + * Returns a pointer to the context, or a NULL on error. + */ +android_logcat_context create_android_logcat(); + +/* Collects and outputs the logcat data to output and error file descriptors + * + * Will block, performed in-thread and in-process + * + * The output file descriptor variable, if greater than or equal to 0, is + * where the output (ie: stdout) will be sent. The file descriptor is closed + * on android_logcat_destroy which terminates the instance, or when an -f flag + * (output redirect to a file) is present in the command. The error file + * descriptor variable, if greater than or equal to 0, is where the error + * stream (ie: stderr) will be sent, also closed on android_logcat_destroy. + * The error file descriptor can be set to equal to the output file descriptor, + * which will mix output and error stream content, and will defer closure of + * the file descriptor on -f flag redirection. Negative values for the file + * descriptors will use stdout and stderr FILE references respectively + * internally, and will not close the references as noted above. + * + * Return value is 0 for success, non-zero for errors. + */ +int android_logcat_run_command(android_logcat_context ctx, int output, int error, + int argc, char* const* argv, char* const* envp); + +/* Finished with context + * + * Free up all associated resources. + * + * Return value is the result of the android_logcat_run_command, or + * non-zero for any errors. + */ +int android_logcat_destroy(android_logcat_context* ctx); + +#endif /* __ANDROID_USE_LIBLOG_LOGCAT_INTERFACE */ + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBS_LOGCAT_H */ diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp index adedf881c..b04828965 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 @@ struct log_device_t { 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 @@ static void rotateLogs() { } } - 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 @@ static void setupOutputAndSchedulingPolicy(bool blocking) { 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); + + 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; } 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 @@ static void show_help(const char* cmd) { " 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 @@ static void show_help(const char* cmd) { "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 @@ static void show_format_help() { } // 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 @@ static int setLogFormat(const char* formatString) { 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 @@ static bool getSizeTArg(const char* ptr, size_t* val, size_t min = 0, 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 @@ static log_time lastLogTime(char* outputFileName) { 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 @@ void reportErrorName(const char** current, const char* name, *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 @@ int main(int argc, char** argv) { 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; + + context->output = stdout; + context->error = stderr; - g_logformat = android_log_format_new(); + 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(argv[0]); - return EXIT_SUCCESS; + show_help(context); + context->retval = EXIT_SUCCESS; + goto exit; } + // danger: getopt is _not_ reentrant + optind = 1; for (;;) { int ret; @@ -677,8 +880,9 @@ int main(int argc, char** argv) { 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 @@ int main(int argc, char** argv) { // 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 @@ int main(int argc, char** argv) { 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 @@ int main(int argc, char** argv) { 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 @@ int main(int argc, char** argv) { 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 @@ int main(int argc, char** argv) { 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 @@ int main(int argc, char** argv) { } 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 @@ int main(int argc, char** argv) { 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 @@ int main(int argc, char** argv) { } else { devices = dev = d; } - g_devCount++; + context->devCount++; } } break; case 'B': - g_printBinary = 1; + context->printBinary = 1; break; case 'f': @@ -902,32 +1118,36 @@ int main(int argc, char** argv) { 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 @@ int main(int argc, char** argv) { 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 @@ int main(int argc, char** argv) { 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 @@ int main(int argc, char** argv) { 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 @@ int main(int argc, char** argv) { } 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 @@ int main(int argc, char** argv) { } 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 @@ int main(int argc, char** argv) { } 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 @@ int main(int argc, char** argv) { 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 @@ int main(int argc, char** argv) { 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 @@ int main(int argc, char** argv) { } 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 @@ int main(int argc, char** argv) { } } - 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 @@ int main(int argc, char** argv) { } 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; } diff --git a/logcat/logcat_main.cpp b/logcat/logcat_main.cpp new file mode 100644 index 000000000..9477e793e --- /dev/null +++ b/logcat/logcat_main.cpp @@ -0,0 +1,30 @@ +/* + * 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 <signal.h> +#include <stdlib.h> + +#include <log/logcat.h> + +int main(int argc, char** argv, char** envp) { + android_logcat_context ctx = create_android_logcat(); + if (!ctx) return -1; + signal(SIGPIPE, exit); + int retval = android_logcat_run_command(ctx, -1, -1, argc, argv, envp); + int ret = android_logcat_destroy(&ctx); + if (!ret) ret = retval; + return ret; +} diff --git a/logcat/tests/logcat_test.cpp b/logcat/tests/logcat_test.cpp index a12524cc1..8f2da2109 100644 --- a/logcat/tests/logcat_test.cpp +++ b/logcat/tests/logcat_test.cpp @@ -278,7 +278,7 @@ static void do_tail(int num) { char buffer[BIG_BUFFER]; snprintf(buffer, sizeof(buffer), - "logcat -v long -b all -t %d 2>/dev/null", num); + "ANDROID_PRINTF_LOG=long logcat -b all -t %d 2>/dev/null", num); FILE* fp; ASSERT_TRUE(NULL != (fp = popen(buffer, "r"))); |
