/* * 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. * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_MAX_ROTATED_LOGS 4 struct android_logcat_context_internal { // status 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 args; std::vector argv_hold; std::vector envs; std::vector envp_hold; int output_fd; // duplication of fileno(output) (below) int error_fd; // duplication of fileno(error) (below) // library 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" // 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->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; } // logd prefixes records with a length field #define RECORD_LENGTH_FIELD_SIZE_BYTES sizeof(uint32_t) struct log_device_t { const char* device; bool binary; struct logger* logger; struct logger_list* logger_list; bool printed; log_device_t* next; log_device_t(const char* d, bool b) { device = d; binary = b; next = NULL; printed = false; logger = NULL; logger_list = NULL; } }; namespace android { enum helpType { HELP_FALSE, HELP_TRUE, HELP_FORMAT }; // if showHelp is set, newline required in fmt statement to transition to usage 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 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; } 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; } } 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; } if (context->fds[1] == fileno(context->error)) { context->fds[1] = -1; } fclose(context->error); } context->error = NULL; } 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; } } static void rotateLogs(android_logcat_context_internal* context) { int err; // Can't rotate logs if we're not outputting to a file if (context->outputFileName == NULL) { return; } close_output(context); // Compute the maximum number of digits needed to count up to // maxRotatedLogs in decimal. eg: // maxRotatedLogs == 30 // -> log10(30) == 1.477 // -> maxRotationCountDigits == 2 int maxRotationCountDigits = (context->maxRotatedLogs > 0) ? (int)(floor(log10(context->maxRotatedLogs) + 1)) : 0; for (int i = context->maxRotatedLogs; i > 0; i--) { std::string file1 = android::base::StringPrintf( "%s.%.*d", context->outputFileName, maxRotationCountDigits, i); std::string file0; if (i - 1 == 0) { file0 = android::base::StringPrintf("%s", context->outputFileName); } else { file0 = android::base::StringPrintf("%s.%.*d", context->outputFileName, maxRotationCountDigits, i - 1); } if ((file0.length() == 0) || (file1.length() == 0)) { perror("while rotating log files"); break; } err = rename(file0.c_str(), file1.c_str()); if (err < 0 && errno != ENOENT) { perror("while rotating log files"); } } context->output_fd = openLogFile(context->outputFileName); if (context->output_fd < 0) { logcat_panic(context, HELP_FALSE, "couldn't open output file"); return; } context->output = fdopen(context->output_fd, "web"); if (context->output == NULL) { logcat_panic(context, HELP_FALSE, "couldn't fdopen output file"); return; } if (context->stderr_stdout) { close_error(context); context->error = context->output; context->error_fd = context->output_fd; } context->outByteCount = 0; } void printBinary(android_logcat_context_internal* context, struct log_msg* buf) { size_t size = buf->len(); TEMP_FAILURE_RETRY(write(context->output_fd, buf, size)); } static bool regexOk(android_logcat_context_internal* context, const AndroidLogEntry& entry) { if (!context->regex) { return true; } std::string messageString(entry.message, entry.messageLen); return context->regex->PartialMatch(messageString); } 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) { if (!context->eventTagMap && !context->hasOpenedEventTagMap) { context->eventTagMap = android_openEventTagMap(NULL); context->hasOpenedEventTagMap = true; } 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) && !context->debug) { return; } if (android_log_shouldPrintLine( context->logformat, std::string(entry.tag, entry.tagLen).c_str(), entry.priority)) { bool match = regexOk(context, entry); context->printCount += match; if (match || context->printItAnyways) { bytesWritten = android_log_printLogLine(context->logformat, context->output_fd, &entry); if (bytesWritten < 0) { logcat_panic(context, HELP_FALSE, "output error"); return; } } } context->outByteCount += bytesWritten; if (context->logRotateSizeKBytes > 0 && (context->outByteCount / 1024) >= context->logRotateSizeKBytes) { rotateLogs(context); } } static void maybePrintStart(android_logcat_context_internal* context, log_device_t* dev, bool printDividers) { if (!dev->printed || printDividers) { 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(context->output_fd, buf, strlen(buf)) < 0) { logcat_panic(context, HELP_FALSE, "output error"); return; } } dev->printed = true; } } 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) && context->error) { fprintf(context->error, "failed to set background scheduling policy\n"); } struct sched_param param; memset(¶m, 0, sizeof(param)); if (sched_setscheduler((pid_t)0, SCHED_BATCH, ¶m) < 0) { fprintf(stderr, "failed to set to batch scheduler\n"); } if ((setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND) < 0) && context->error) { fprintf(context->error, "failed set to priority\n"); } } close_output(context); 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(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_output(context); logcat_panic(context, HELP_FALSE, "invalid output file stat\n"); return; } context->output = fdopen(context->output_fd, "web"); context->outByteCount = statbuf.st_size; } // clang-format off static void show_help(android_logcat_context_internal* context) { if (!context->error) return; 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= Log to file. Default is stdout\n" " -r , --rotate-kbytes=\n" " Rotate log every kbytes. Requires -f option\n" " -n , --rotate-count=\n" " Sets max number of rotated logs to , default 4\n" " --id= If the signature id for logging to file changes, then clear\n" " the fileset and continue\n" " -v , --format=\n" " Sets log print format verb and adverbs, where is:\n" " brief help long process raw tag thread threadtime time\n" " and individually flagged modifying adverbs can be added:\n" " color descriptive epoch monotonic printable uid\n" " usec UTC year zone\n" // private and undocumented nsec, no signal, too much noise // useful for -T or -t accurate testing though. " -D, --dividers Print dividers between each log buffer\n" " -c, --clear Clear (flush) the entire log and exit\n" " if Log to File specified, clear fileset instead\n" " -d Dump the log and then exit (don't block)\n" " -e , --regex=\n" " Only print lines where the log message matches \n" " where is a regular expression\n" // Leave --head undocumented as alias for -m " -m , --max-count=\n" " Quit after printing lines. This is meant to be\n" " paired with --regex, but will work on its own.\n" " --print Paired with --regex and --max-count to let content bypass\n" " regex filter but still stop at number of matches.\n" // Leave --tail undocumented as alias for -t " -t Print only the most recent lines (implies -d)\n" " -t '